Простые вещи. Файл настроек и небольшой прицеп утилит

в 9:23, , рубрики: настройки программы, Программирование, разработка, системное программирование, метки: , , ,

Этот пост, для тех, кто пишет на C/C++. Остальные могут не читать.

Как всегда, работая над проектами хочу поделится очередной технологией. Наверное, громко сказано. Скорее, простым решением в области создания и работы с файлом настройки программ.

В мире много готовых решений. От XML-формата до… Одним словом, много. Данная статья не претендует на что-то сверхновое, не побуждает к дискуссиям о стиле, методах и реализации.

Я, просто, делюсь быстрым решением, как читать параметры и значения, разделённые знаками «равно» из файла настроек формата, похожего на, скажем, php.ini

Немного лирики.

Я — лентяй. Мне чертовски лениво тратить своё время на разбор XML, искать для этого инструментарий, изучать возможности различных IDE… Мне проще сесть и сделать самому так, как нравится, так, как я считаю проще. Многолетние применение файлов настроек типа <параметр> = <значение> для UNIX-подобных ОС оправдывает мой выбор.
И я не буду отвечать на коментарии и вступать в дискуссии. Я сниму «галочки» с предложений отслеживания сообщений для данного поста.

Побольше практики.

Описание данного метода является частью создаваемой библиотеки для личных целей.
Итак, определимся с исзодными данными и настройками.
cs_types.h

#ifndef CS_TYPES_H
#define	CS_TYPES_H

#if defined WIN32 || defined _WIN32
#  define CS_CDECL __cdecl
#  define CS_STDCALL __stdcall
#else
#  define CS_CDECL
#  define CS_STDCALL
#endif

#ifndef CS_EXTERN_C_FUNC_PTR
#  ifdef __cplusplus
#    define CS_EXTERN_C_FUNC_PTR(x) extern "C" { typedef x; }
#  else
#    define CS_EXTERN_C_FUNC_PTR(x) typedef x
#  endif
#endif

#ifndef CS_INLINE
#  if defined __cplusplus
#    define CS_INLINE inline
#  elif (defined WIN32 || defined _WIN32 || defined WINCE) && !defined __GNUC__
#    define CS_INLINE __inline
#  else
#    define CS_INLINE static
#  endif
#endif /* CS_INLINE */

#ifdef	__cplusplus
extern "C" {
#endif

#if (defined WIN32 || defined _WIN32 || defined WINCE) && defined CSAPI_EXPORTS
#  define CS_EXPORTS __declspec(dllexport)
#else
#  define CS_EXPORTS
#endif

#ifndef CS_API
#  define CS_API(rettype) CS_EXPORTS rettype CS_CDECL
#endif

  
#ifdef	__cplusplus
}
#endif

//#define DEBUG_MODE
#ifdef DEBUG_MODE
  static short debug_level = 3;
#endif

#endif	/* CS_TYPES_H */

Основные идеи по определению макросов я «слямзил» у OpenCV.

Теперь укажем функции подготовки данных и различные сопутствующие утилиты: файл cs_common.h

#ifndef SC_COMMON_H
#define	SC_COMMON_H

#include "cs_types.h"

#include <getopt.h>

/*----------------------------------------------------------------------------*/
/**
 * @typedef opt_t
 * @brief Приведённый тип структуры struct option (@see man 3 getopt) 
 */  
typedef
  struct option
opt_t;


/*----------------------------------------------------------------------------*/
/**
 * @fn [CS_API] int is_digital ( 
 *                      const char* __restrict  param_value, 
 *                      long long*  __restrict  param_digit, 
 *                      int*                    param_base );
 * @brief Проверка, представляет ли значение строки число.
 * 
 * Если значение строки представления @a param_value содержит число в строковом 
 * виде, то функция возвращает число в формате long long. Значение
 * базовой системы исчисления при этом записывается в параметр @a param_base, 
 * если его указатель не NULL
 * 
 * @warning Числа с "плавающей запятой" определяются корректно, но результат не 
 * содержит остатка (приводится к длинному целому).
 * 
 * Используемы строковые представления чисел:
 * Формат десятичных чисел - <значение>[.e[+|-]<степень>]>
 * Формат шеснадцатиричных чисел - [<значение><[h|H]> | [0x|0X]<значение>]
 * Формат восьмиричных чисел - <значение><[o|O]>
 * Формат двоичных чисел - <значение><[b|B]>
 * 
 * @param param_value - строка представления
 * @param param_digit - число после преобразования строкового значения
 * @param param_base - базовая система исчисления для числового значения
 * @return Представлено тремя значениями: 
 *   0, значение строки представления @a param_value - строка.
 *   1, значение строки представления @a param_value - число.
 *  -1, возникла системная ошибка.
 * При значении кода результата, равного -1, значение системной переменной errno
 * уставнавливается в одно из вариантов:
 *  EINVAL  - ошибка в значениях параметров функции;
 *  ERANGE  - ошибка размера числа в количестве цифр
 *  ENOMEM  - ошибка распределения памяти при вычислении
 *  EBADE   - ошибка копирования областей памяти при вычислении
 * 
 */

CS_API(int)
is_digital ( 
        const char* __restrict  param_value, 
        long long*  __restrict  param_digit, 
        int*                    param_base 
);

/*----------------------------------------------------------------------------*/
/**
 * @fn CS_API(char*) ltrim( const char* param_str );
 * @brief Обрезка всех левых пробелов строки.
 * 
 * @param param_str - исходная строка
 * @return Приведённая строка или NULL.
 * 
 * Возвращаемое значение - указатель на первый символ @a param_str, не равный 
 * знаку "пробел".
 * При значении кода результата, равного NULL, значение системной переменной errno
 * уставнавливается в EINVAL (ошибка в значениях параметров функции);
 */

CS_API(char*)
ltrim( const char* param_str );

/*----------------------------------------------------------------------------*/
/**
 * @fn CS_API(char*) rtrim( const char* param_str );
 * @brief Обрезка всех правых пробелов строки.
 * 
 * @param param_str - исходная строка
 * @return Приведённая (static pointer) строка или NULL.
 * При значении кода результата, равного NULL, значение системной переменной errno
 * уставнавливается в одно из вариантов:
 *  EINVAL  - ошибка в значениях параметров функции;
 *  ENOMEM  - ошибка распределения памяти при вычислении
 *  EBADE   - ошибка копирования областей памяти при вычислении
 * 
 * @warning Тип памяти результата - static pointer. Будте внимательны!
 */

CS_API(char*)
rtrim( const char* param_str );

#ifdef	__cplusplus
}
#endif

#endif	/* SC_COMMON_H */

Как старый склеротик с диагнозом «создал — оттестировал — работает — забыл» всегда пишу комментарии. Думаю, с описаниями функций всё ясно.

И основное объявление данных и методов конкретно для конфигурационного файла пропишем в cs_configfile.h

#ifndef CONFIG_UTILS_H
#define	CONFIG_UTILS_H

#include "../include/cs_types.h"

/**
 * @typedef cfg_val_type_t
 * @brief Типы значений параметра
 */
typedef
  enum config_value_type {
      ptDigital   = 0x01, /**< Значение параметра - число */
      ptString    = 0x02  /**< Значение параметра - строка символов  */
  }
cfg_val_type_t;

/**
 * @typedef config_t
 * @brief Тип экземпляра параметра конфигурации
 */
typedef
  struct {
      char*           name;   /**< Название параметра */
      cfg_val_type_t  type;   /**< Тип параметьра @a param_type*/

      union {                 /**< Значение параметра */
        char*       str;      /**< Строковое представление */
        struct {
          long long digit;    /**< Числовое представление */
          int       base;     /**< Базовая система числового представления */
        };
      };
  }
config_t;

/**
 * @typedef txt_cfg_t
 * @brief Список параметров конфигурации.
 */
typedef 
  struct txt_cfg {
    config_t value;
    struct txt_cfg* prev;/**< Указатель на предыдущий элемент списка */
    struct txt_cfg* next;/**< Указатель на следующий элемент в списке */
  }
txt_cfg_t;

#ifdef	__cplusplus
extern "C" {
#endif

/*----------------------------------------------------------------------------*/
/**
 * @fn [CS_API] txt_cfg_t* config_Open (const char* __restrict param_filename);
 * @brief Открывает файл параметров и формирует из него список типа @a txt_cfg_t.
 * 
 * @param param_file_name - конфигурационный файл
 * @return Указатель на структуру списка параметров, сформированную чтением 
 * названий и значений из файла a param_filename или пустой указатель (NULL).
 * 
 * Если функция возвращает NULL, то системная переменная errno содержит код 
 * ошибки выполнения.
 * Возможные значения системной переменной errno:
 *  EINVAL  - пустой указатель в параметре param_filename
 *  ENOMEM  - ошибка распределения памяти
 *  ERANGE  - ошибка мат. вычислений
 *  EBADE   - ошибка при копировании строк
 * 
 * Файл конфигурационных параметров состоит из коментариев, пустых строк и 
 * кортежа параметров формата <название параметра> = <значение параметра>.
 * Например:
 * 
 * # server_broadcast = ANY (по умолчанию)
 * server_broadcast = ANY
 * 
 * # Порт для подключения к серверу
 * server_port = 8989
 * 
 * # Семейство протоколов
 * # Возможные значения:
 * # 
 * # server_family = SOCKET_STREAM (по умолчанию)
 * server_family = SOCKET_STREAM
 * 
 * #usb_vendor = 0x1111
 * usb_vendor = 0xffff
 * usb_product = 0xaaaa
 * usb_ep_in = 0x01
 * 
 * @see config_Close
*/  
CS_API(txt_cfg_t*)
config_Open (const char* __restrict param_filename);

/*----------------------------------------------------------------------------*/
/**
 * @fn [CS_API] void config_Close (txt_cfg_t** param_item);
 * @brief Закрыть список параметров.
 * 
 * @param param_item - список параметров типа @a txt_cfg_t
 * 
 * После закрытия область памяти по значению указателя @a param_item освобождается.
 * Значение указателя устанавливается в NULL.
 * 
 * @see @a config_Open
 * @see @a free
 */
CS_API(void)
config_Close (txt_cfg_t** param_item);



#ifdef	__cplusplus
}
#endif

#endif	/* CONFIG_UTILS_H */

Видим, что есть лишь две функции для открытия и инициализации структуры параметров и закрытия с очисткой памяти.

Подумайте!

Возможно, ваши вкусы позволят добавить к перечислению cfg_val_type_t, например, ptBool, создав ветку обработки параметра. Тогда вы — ленивее, чем я сам!

Реализации.

Для общих утилит.

Файл cs_common.c

#include "../include/cs_common.h"
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
#include <string.h>
#include <stdio.h>
//#include <netdb.h>

/*----------------------------------------------------------------------------*/
int 
is_digital ( 
        const char* __restrict  param_value, 
        long long*  __restrict  param_digit, 
        int*                    param_base 
)
{
  char* c_endptr;
  char* c_value;
  int i_base;
  int i_ret = 1;
  size_t u_len;

  errno = 0;

  if(param_value == NULL || param_digit == NULL) 
  {
    errno = EINVAL;
    return (-1);
  }
  
  *param_digit = 0;
  errno = 0;
  
  /* определение системы исчисления */
  switch(param_value[strlen(param_value)-1]) 
  {
    case 'h' :
    case 'H' : i_base = 16;
    break;
    case 'o' :
    case 'O' : i_base = 8;
    break;
    case 'b' :
    case 'B' : i_base = 2;
    break;
    default  : i_base = 10;
  }
  
  u_len = strlen(param_value);

  if(u_len > 1) 
  {
    if((c_value = malloc(u_len+1)) == NULL) 
    {
      /* Да, на вас ОЗУ не напасёшься! */
      errno = ENOMEM;
      return (-1);
    }

    if((c_value = strncpy(c_value, param_value, u_len)) == NULL) 
    {
      errno = EBADE;
      i_ret = -1;
    } else {
      /* проверка возможных вариантов строкового представления 16-х чисел */
      if(param_value[0] == '0') 
      {
        switch(param_value[1]) 
        {
          case 'X' : 
          case 'x' : i_base = 16;
          break;
          default : ;
        }

        if(i_base == 16) 
        {
          if((c_value = strncpy(c_value, param_value+2, u_len-2)) == NULL) 
          {
            errno = EBADE;
            i_ret = -1;
          } else {
            if( strncpy(c_value+u_len-2, "h", 2) == NULL ) 
            {
              errno = EBADE;
              i_ret = -1;
            } 
          }
        }
      }
    }

    if(i_ret > -1) 
    {
      /* строку - в целое */
      *param_digit = strtoll(c_value, &c_endptr, i_base);

      if(c_endptr == c_value) 
      {
        /* строка */
        i_ret = 0;
      } else {
        /* целое число */
        if(
          (errno == ERANGE && (*param_digit == LLONG_MIN || *param_digit == LLONG_MAX)) 
          || 
          (errno != 0 && *param_digit == 0)
        ) 
        {
          /* ошибка преобразования */
          i_ret = -1;
        }
      }
    }
   
    /* если требуется вернуть в параметре систему исчисления */
    if(param_base != NULL)
      *param_base = i_base;

    /* освободим память */
    free(c_value);
  }
  
  return(i_ret);
}

/*----------------------------------------------------------------------------*/
char*
ltrim ( const char* param_str )
{
  size_t i;
  size_t u_length;
  
  if(param_str == NULL) {
    errno = EINVAL;
    return (NULL);
  }
  
  errno = 0;
  u_length = strlen(param_str);
  
  if(0 == u_length)
    return ("");
  
  for(i = 0; i < u_length; i++ ) 
    if((int)(param_str[i]) != ' '/* пробел */) 
      /* перенос указателя на первый символ, отличный от пробела*/
      return ((char*)param_str+i); 

  return (NULL);
}

/*----------------------------------------------------------------------------*/
char*
rtrim( const char* param_str )
{
  size_t i;
  size_t u_length;
  char* ptr;
  /* !!! */ static /* !!! */ char* str;

  if(NULL == param_str) {
    errno = EINVAL;
    return (NULL);
  }
  
  u_length = strlen(param_str);
  
  if(u_length == 0)
    return ("");
  
  
  
  for(i = u_length, errno = 0; (0 == errno) && (i > 0); i--) 
  {
    if((int)(param_str[i-1]) == ' ') 
      continue;

    if(NULL != (ptr = malloc(i + 1)))
    {
      if(NULL != (str = strncpy(ptr, param_str, i))) 
      {
        str[i] = '';
        return(str);
      } else {
        free(ptr);
        errno = EBADE;
      }
    } else {
      errno = ENOMEM;
    }  
  }

  return (NULL);
}
Для работы с файлом настроек.

Файл cs_configfile.c

#include "../include/cs_configfile.h"
#include "../include/cs_common.h"

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <limits.h>
#include <string.h>

/*----------------------------------------------------------------------------*/
/**@internal
 * @fn void free_cfg_data (txt_cfg_t*  param_result);
 * @brief Освобождение пула пямяти структуры параметра типа @a txt_cfg_t.
 * 
 * @param param_result - указатель на выделенный и заполненный значениями пул.
 */
void
free_cfg_data (txt_cfg_t*  param_result)
{
  txt_cfg_t* p;
  errno = 0;
  
  for(p = param_result; p != NULL; p = p->next) {
    free(p->value.name);
    if(p->value.type == ptString)
      free(p->value.str);
  }
  
  if(param_result)
    free(param_result);
}

/*----------------------------------------------------------------------------*/
/**@internal
 * @fn int  alloc_cfg_data (
 *            const char* __restrict  param_name, 
 *            const char* __restrict  param_value, 
 *            txt_cfg_t** __restrict  param_result
 *          );

 * @brief Распределение памяти для param_result типа txt_cfg_t.
 * 
 * @param param_name - название параметра
 * @param param_value - строковое представление значение
 * @param param_result - указатель на выделенный и заполненный значениями пул 
 * структуры параметра типа txt_params_t.
 * 
 * @return Значение 0 при успешном выполнении и -1 при ошике распределения.
 * При получении кода возврата, равного -1, следует рассмотреть системную 
 * переменную errno на предмет кода ошибки.
 * 
 * @warning Если передаётся непустой указатель *param_result на распределённый 
 * пул памяти, то он будет сброшен без проверки при перераспределении памяти.
 * Следует позаботится о хранении указателя до вызова функции или сбросить его 
 * самостятельно.
 */
int
alloc_cfg_data (
        const char* __restrict  param_name, 
        const char* __restrict  param_value, 
        txt_cfg_t** __restrict  param_result
)
{
    size_t u_length_n, u_length_v;

    if(param_name == NULL || param_value == NULL || param_result == NULL) {
      errno = EINVAL;
      return(-1);
    }
    
    errno = 0;

    if((*param_result = malloc(sizeof(txt_cfg_t))) == NULL)
        return(-1);
    
    /* Обязательное "обнуление"! 
     * При возможном переходе на метку освобождения общей памяти (вызов 
     * free_param(...)) после неудачного распределения памяти для названия 
     * параметра, произойдёт проверка поля str структуры на NULL. Сравнение 
     * указателя не даст значения NULL, т.к. не устанавливается по умолчанию.
     * Следствие - разрушение системы вызовом стандартной free() */
    if(memset (*param_result, 0, sizeof(txt_cfg_t)) == NULL)
        return(-1);

    u_length_n = strlen(param_name);
    if(u_length_n == 0)
        return(-1);
  
    (*param_result)->value.name = malloc(u_length_n+1);
    if((*param_result)->value.name == NULL)
      /* ошибка распределения памяти */
        goto free;
            
    if(strncpy((*param_result)->value.name, param_name, u_length_n) != NULL) {
      /* успешное копирование строки */
      switch( is_digital( param_value, &((*param_result)->value.digit), &((*param_result)->value.base) ) ) {
        case 0 : {
          /* строка */
          u_length_v = strlen(param_value);
          
          if(u_length_v > 0) {
            /* непустиая строка */
            (*param_result)->value.str = malloc(u_length_v+1);
            if((*param_result)->value.str == NULL)
                goto free;

            if(strncpy((*param_result)->value.str, param_value, u_length_v) == NULL) 
                goto free;
          }
          (*param_result)->value.type = ptString;
        }
        break;
        case 1 : 
          /* число */
          (*param_result)->value.type = ptDigital;
        break;
        default : { 
          /* что-то невозможное */
          errno = ERANGE;
          goto free; 
        }
      }
      return (0);
    }
      
  free:
      free_cfg_data(*param_result);
      *param_result = NULL;

  return (-1);
}

/*----------------------------------------------------------------------------*/
/**@internal
 * 
 * Разбор строки типа <параметр> = <значение> на две строки с названием параметра 
 * и строковым представлением значения.
 * 
 * @param param_str   - строка типа [параметр] = [значение]
 * @param param_name  - указатель на название параметра 
 * @param param_value - указатель на значение параметра
 * @return Возвращает 0 при успешном выполнении и -1 при ошике распределения.
 * 
 * При получении кода возврата, равного -1, следует рассмотреть системную 
 * переменную errno на предмет кода ошибки.
 * 
 * @warning Если передаваемые указатели *param_value и/или *param_name не NULL, 
 * они будут сброшены без очистки памяти. Ответственность за распределение и 
 * очистку пямяти лежит на разработчике.
 */
int
parse_string (
        const char* param_str, 
        char**      param_name, 
        char**      param_value 
) 
{
  if(param_name == NULL || param_value == NULL) {
    errno = EINVAL;
    return -1;
  }
  
  *param_value = *param_name = NULL;

  if(param_str == NULL) {
    errno = EINVAL;
    return -1;
  }
  
  switch(param_str[0]) {
      case 'n' :
      case '#'  :
      case 0    :
        return (0);
  }

  char c_name[MAX_INPUT];
  char c_value[MAX_INPUT];

  errno = 0;
  int i = sscanf(param_str, "%s = %s", c_name, c_value);
  char* p_name = ltrim(rtrim(c_name));
  char* p_value = ltrim(rtrim(c_value));
  
  if(errno != 0 || i != 2 ) return (-1);
  
  *param_name = malloc(strlen(p_name)+1);
    
  if(*param_name != NULL) {
    *param_value = malloc(strlen(p_value)+1);
    
    if(*param_value != NULL) {
      if(strcpy(*param_name, p_name) == NULL) {
        /* ошибка копирования */
        errno = EXFULL;
        goto freemem; 
      }

      if(strcpy(*param_value, p_value) == NULL) {
        /* ошибка копирования */
        errno = EXFULL;
        goto freemem;
      }
      
#ifdef DEBUG_MODE
if(debug_level > 3) {
printf("DEBUG parse_string(...). c_name = %s, c_value = %s, ", c_name, c_value);
printf("*param_name = %s, *param_value = %sn", *param_name, *param_value);
}
#endif      
      return (0);
    }
  }
  
  freemem:
    if(*param_name != NULL) free(*param_name);
    if(*param_value != NULL) free(*param_value);
    *param_value = *param_name = NULL;
  
  return(-1);
}

/*----------------------------------------------------------------------------*/
/**@internal
 * 
 * Разбор файла конфигурационных параметров.
 * 
 * @param param_file_name - название файла конфигурации с полным описанием пути 
 *                          к каталогу
 * @param param_result    - указатель на список структур описания параметров
 * @return При успешном завершении возвращает 0 и заполненный список параметров 
 * и их значений @a param_result. Иначе возвращает -1.
 * 
 * @warning Если передаваемый указатель *param_result не NULL, он будет сброшен 
 * без очистки памяти. Ответственность за распределение и очистку пямяти лежит 
 * на разработчике.
 * 
 * Функция последовательно считывает строки, пропуская незначащие и заполняет 
 * список параметров типа @a txt_params_t.
 * При ошибочном выполнении (код возврата -1) разработчик может прочесть код 
 * ошибки из системной переменной errno. При этом значение указателя *param_result
 * сбрасывается в NULL (память для него не распределяется)
 * 
 */
int
parse_config_file ( const char* param_file_name, txt_cfg_t** param_result )
{
  if(param_result == NULL || param_result == NULL) {
    errno = EINVAL;
    return (-1);
  }
  
  char c_str[MAX_INPUT];
  txt_cfg_t* t_param;
  char* c_name;
  char* c_value;

  errno = 0;
  FILE* p_fd = fopen(param_file_name, "r");

  if(p_fd == NULL) 
    /* невозможно получить указатель на структуру файла */
    return (-1);
  
  int i_res = 0;
  *param_result = NULL;
  errno = 0;
  
  /* чтение строк конфигурации до конца файла */
  while(fgets(c_str,MAX_INPUT,p_fd) != NULL && i_res == 0 ) 
  {
    /* разбор очередной строки */
    i_res = parse_string(c_str, &c_name, &c_value);
    
    if(i_res == 0) {
      if(c_name == NULL) 
        /* получена незначащая строка (коментарий, перевод строки,...) */
        continue;

#ifdef DEBUG_MODE
  if(debug_level > 3)
    printf(
    "DEBUG parse_confif_file(...): source = "%s", name = %s, value = %s n", 
    c_str, c_name, c_value
            );
#endif      

      if((i_res = alloc_cfg_data(c_name, c_value, &t_param)) == 0 ) {
        /* ресурсы распределены */
        t_param->next = *param_result;
        if(*param_result != NULL)
          (*param_result)->prev = t_param;
      } /* иначе выход из цикла по условию значения i_res */

      /* Обратить внимание: обязательная очистка памяти! */
      free(c_name);
      free(c_value);

      *param_result = t_param;
    }/* иначе выход из цикла по условию значения i_res */
  }
  
  if(i_res != 0) {
    free_cfg_data(*param_result);
    *param_result = NULL;
  }
  
  fclose(p_fd);

  return (i_res);
}

/*----------------------------------------------------------------------------*/
txt_cfg_t*
config_Open (const char* param_filename)
{
  txt_cfg_t* p_item = NULL;
  
  (void)parse_config_file(param_filename, &p_item);
  
  return (p_item);
}

/*----------------------------------------------------------------------------*/
void
config_Close (txt_cfg_t** param_item)
{
  if(param_item) {
    if(*param_item) {
      free_cfg_data(*param_item);
      *param_item = NULL;
    }
  }
}

Подумайте!

В теле функции parse_string попробуйте применить правило разбора регулярных выражений. Так вы избавитесь от обязательства ставить пробелы по обе стороны знака «равно».
Мне было лень.

Вот, и всё. :)

Тестируем.

Я тспользую для работы на С IDE NetBeans. Почему? Нравится! :)
В данной среде для построения тестов применяю Framework CUnit.

Утилита ltrim

CUnit — A unit testing framework for C — Version 2.1-2
cunit.sourceforge.net/

Test: test1…
Проверка.
Строка " test1". Результат: «test1»
Строка " test 2". Результат: «test 2»
Строка " !23test3". Результат: "!23test3"
passed

suites 1 1 n/a 0 0
tests 1 1 1 0 0
asserts 1 1 1 0 n/a

Elapsed time = 0.000 seconds

Утилита rtrim

CUnit — A unit testing framework for C — Version 2.1-2
cunit.sourceforge.net/

Test: test1…
Проверка.
Строка «test1». Результат: «test1»
Строка «test 2 ». Результат: «test 2»
Строка "!23test3# ". Результат: "!23test3#"
passed

suites 1 1 n/a 0 0
tests 1 1 1 0 0
asserts 1 1 1 0 n/a

Elapsed time = 0.000 seconds

Утилита is_digital

CUnit — A unit testing framework for C — Version 2.1-2
cunit.sourceforge.net/

Введите строку для проверки на представление числового значения: 0xffff

Ваша строка: «0xffff»

Suite: is_digital_test
Test: test1 ...passed
Test: test2… Результат проверки:
Число = 65535, Система исчисления = 16
passed

Run Summary: Type Total Ran Passed Failed Inactive
suites 1 1 n/a 0 0
tests 2 2 2 0 0
asserts 2 2 2 0 n/a

Elapsed time = 0.000 seconds

CUnit — A unit testing framework for C — Version 2.1-2
cunit.sourceforge.net/

Введите строку для проверки на представление числового значения: 1277o

Ваша строка: «1277o»

Suite: is_digital_test
Test: test1 ...passed
Test: test2… Результат проверки:
Число = 703, Система исчисления = 8
passed

Run Summary: Type Total Ran Passed Failed Inactive
suites 1 1 n/a 0 0
tests 2 2 2 0 0
asserts 2 2 2 0 n/a

Elapsed time = 0.000 seconds

CUnit — A unit testing framework for C — Version 2.1-2
cunit.sourceforge.net/

Введите строку для проверки на представление числового значения: 4ret

Ваша строка: «4ret»

Suite: is_digital_test
Test: test1 ...passed
Test: test2… Результат проверки:
Число = 0, Система исчисления = 0
FAILED
1. tests/is_digital_test.c:59 — (i_result = is_digital(c_test, &ll_digit, &i_base)) == 1

Run Summary: Type Total Ran Passed Failed Inactive
suites 1 1 n/a 0 0
tests 2 2 1 1 0
asserts 2 2 1 1 n/a

Elapsed time = 0.000 seconds

CUnit — A unit testing framework for C — Version 2.1-2
cunit.sourceforge.net/

Введите строку для проверки на представление числового значения: 1024

Ваша строка: «1024»

Suite: is_digital_test
Test: test1 ...passed
Test: test2… Результат проверки:
Число = 1024, Система исчисления = 10
passed

Run Summary: Type Total Ran Passed Failed Inactive
suites 1 1 n/a 0 0
tests 2 2 2 0 0
asserts 2 2 2 0 n/a

Elapsed time = 0.000 seconds

Разбор файла конфигурации.

Файл:

#-------------------------------------------------------------------------------
# Правило: Знак равенства разделён хотя бы одним пробелом с названием 
# параметра и значением параметра.
#-------------------------------------------------------------------------------
#
#pid_file = ereusb.pid

# Тип вещания сервера
# Возможные значения: bla, bla, bla...
# 
# server_broadcast = ANY (по умолчанию)
server_broadcast = ANY

# Порт для подключения к серверу
server_port = 8989

# Семейство протоколов
# Возможные значения:
# 
# server_family = SOCKET_STREAM (по умолчанию)
server_family = SOCKET_STREAM

#usb_vendor = 0x1111
usb_vendor = 0001b
usb_product = 1234o
usb_ep_in = 0x01
usb_ep_out = 0x82

Результат:
CUnit — A unit testing framework for C — Version 2.1-2
cunit.sourceforge.net/

Test: test_parse_confif_file…
Параметр:
Название — «usb_ep_out»
Тип — число
Значение — 0x0082
Базовая система — 16

Параметр:
Название — «usb_ep_in»
Тип — число
Значение — 0x0001
Базовая система — 16

Параметр:
Название — «usb_product»
Тип — число
Значение — 1234
Базовая система — 8

Параметр:
Название — «usb_vendor»
Тип — число
Значение — 1
Базовая система — 2

Параметр:
Название — «server_family»
Тип — строка
Значение — «SOCKET_STREAM»

Параметр:
Название — «server_port»
Тип — число
Значение — 8989
Базовая система — 10

Параметр:
Название — «server_broadcast»
Тип — строка
Значение — «ANY»
passed

suites 1 1 n/a 0 0
tests 1 1 1 0 0
asserts 1 1 1 0 n/a

Elapsed time = 0.000 seconds

Благодарности.

  • Деннису Ритчи(R.I.P.) за гениальную разработку языка программирования «C»;
  • Sun Microsystems (R.I.P.) за разработку IDE NetBeans;
  • Джону Дугласу Лорду (R.I.P.) за «Concerto For Group and Orchestra», сопровождавшего пост.

Всех благ!

Автор: coffeesmoke

  1. airevent:

    принцип KISS автору этой простыни явно не знаком… хотя, чего я удивляюсь, выбор NetBeans в качестве комбайна(зачёркнуто) IDE, говорит о многом.

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js