📜 ⬆️ ⬇️

Simple things. Settings file and a small utility trailer

This post is for those who write in C [/ C ++]. The rest can not read.

As always, working on projects I want to share the next technology. Probably loudly said. Rather, a simple solution in creating and working with the program settings file.

There are many ready-made solutions in the world. From XML format to ... In short, a lot. This article does not claim to be something supernovae, does not encourage discussion of style, methods and implementation.
')
I simply share a quick decision on how to read the parameters and values ​​separated by "equal" signs from a format settings file similar to, say, php.ini



Some lyrics.

I'm lazy. I'm damn lazy to spend my time analyzing XML, looking for tools for this, exploring the possibilities of various IDEs ... It’s easier for me to sit down and do it myself the way I like, the way I find it easier. Long-term use of configuration files of the type <parameter> = <value> for UNIX-like operating systems justifies my choice.
Yes, I reluctantly write on "two pluses".

And I will not enter into discussions in the comments feed. I’ll uncheck message tracking suggestions for this post.
If you like it, take it. Get (have) better, look, thank you.
The goal is to minimize the use of additional libraries other than stdlib.

More practice.


The description of this method is part of the library being created for personal use.
So, we define the source data and settings.
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 */ 


The basic ideas for defining macros I have “slyamzil” in OpenCV.

Now we will indicate the data preparation functions and various related utilities: file
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 */ 



As an old sclerotic with the diagnosis “created - tested - works - forgot” I always write comments. I think the function descriptions are clear.

And the main declaration of data and methods specifically for the configuration file is written in
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 */ 



We see that there are only two functions for opening and initializing the structure of parameters and closing with clearing the memory.

Think about it.

Perhaps your tastes will allow you to add cfg_val_type_t to the enumeration, for example, ptBool , by creating a parameter processing branch. Then you - lazier than I myself!

Implementations.

For general utilities.

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; } if(i_base == 10) { if(param_value[strlen(param_value)-1] < (int)'0' || param_value[strlen(param_value)-1] > (int)'9') { return (0); /**/ } } 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\0", 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] = '\0'; return(str); } else { free(ptr); errno = EBADE; } } else { errno = ENOMEM; } } return (NULL); } 



To work with the settings file.

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 = %s\n", *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; } } } 


Think about it!

In the body of the parse_string function parse_string try applying a regular expression parsing rule. So you get rid of the obligation to put spaces on both sides of the equal sign.
I was lazy.

That's all. :)

We are testing.



I use to work on NetBeans IDE C. Why? Like! :)
In this environment, I use Framework CUnit to build tests.

ltrim utility


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

Test: test1 ...
Check.
The string "test1". Result: "test1"
The string "test 2". Result: "test 2"
The string "! 23test3". Result: "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 utility

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

Test: test1 ...
Check.
The string "test1". Result: "test1"
The string "test 2". Result: "test 2"
The string "! 23test3 #". Result: "! 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 utility

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

Enter a string to check for numeric value representation: 0xffff

Your string: "0xffff"

Suite: is_digital_test
Test: test1 ... passed
Test: test2 ... Test result:
Number = 65535, Calculus = 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

Enter a string to check for numeric value representation: 1277o

Your line: "1277o"

Suite: is_digital_test
Test: test1 ... passed
Test: test2 ... Test result:
Number = 703, Calculus = 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

Enter a string to check for numeric value representation: 4ret

Your line: "4ret"

Suite: is_digital_test
Test: test1 ... passed
Test: test2 ... Test result:
Number = 0, Calculus = 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

Enter a string to check for numeric value representation: 1024

Your line: "1024"

Suite: is_digital_test
Test: test1 ... passed
Test: test2 ... Test result:
Number = 1024, Calculus = 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



Parsing configuration file.

File:
 #------------------------------------------------------------------------------- # :          #    . #------------------------------------------------------------------------------- # #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 


Result:
CUnit - A unit testing framework for C - Version 2.1-2
cunit.sourceforge.net

Test: test_parse_confif_file ...
Parameter:
Name - "usb_ep_out"
Type - number
Value - 0x0082
Base system - 16

Parameter:
The name is "usb_ep_in"
Type - number
Value - 0x0001
Base system - 16

Parameter:
Name - "usb_product"
Type - number
Value - 1234
Base system - 8

Parameter:
The name is "usb_vendor"
Type - number
Value - 1
Basic system - 2

Parameter:
Name - "server_family"
Type - string
Value is "SOCKET_STREAM"

Parameter:
Name - "server_port"
Type - number
Value - 8989
Basic system - 10

Parameter:
Name - "server_broadcast"
Type - string
Value - "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



Thanks



All the best!

Source: https://habr.com/ru/post/148930/


All Articles