cnfparse.c

Small library to make config parsing easier
git clone git://git.finwo.net/lib/cnfparse.c
Log | Files | Refs | README | LICENSE

cnfparse.c (5394B)


      1 #ifdef __cplusplus
      2 extern "C" {
      3 #endif
      4 
      5 // includes/system {{{
      6 #include <stdbool.h>
      7 #include <stdio.h>
      8 #include <stdlib.h>
      9 #include <string.h>
     10 // }}}
     11 // includes/deps {{{
     12 #include "finwo/buf.h"
     13 // }}}
     14 // includes/local {{{
     15 #include "cnfparse.h"
     16 // }}}
     17 
     18 #define STATE_BLANK   0
     19 #define STATE_COMMENT 1
     20 #define STATE_DQUOTE  2
     21 #define STATE_SQUOTE  3
     22 #define STATE_WORD    4
     23 
     24 // cnf_unescape(c) {{{
     25 // Unknown escapes keep the character verbatim
     26 static int cnf_unescape(char c) {
     27   switch (c) {
     28     case 'n':  return '\n';
     29     case 't':  return '\t';
     30     case 'r':  return '\r';
     31     case '\\': return '\\';
     32     case '"':  return '"';
     33     case '\'': return '\'';
     34     case '0':
     35       fprintf(stderr, "cnfparse: warning: \\0 becomes '0', not a null byte\n");
     36       return '0';
     37     default:   return c;
     38   }
     39 }
     40 // }}}
     41 
     42 // cnf_directive_append(subject,value,len) {{{
     43 // caller frees
     44 void cnf_directive_append(struct cnf_directive *subject, char *value, size_t len) {
     45   if (!value) value = ""; // len is leading, not value
     46   char *val = calloc(1, len+1);
     47   memcpy(val, value, len);
     48   if (!subject->name) {
     49     subject->name = val;
     50     return;
     51   }
     52   subject->argv = realloc(subject->argv, (subject->argc + 1) * sizeof(char*));
     53   subject->argv[subject->argc++] = val;
     54 }
     55 // }}}
     56 // cnf_directive_free(subject) {{{
     57 void cnf_directive_free(struct cnf_directive *subject) {
     58   if (!subject) return;
     59   if (subject->name) {
     60     free(subject->name);
     61   }
     62   for(size_t i = 0 ; i < subject->argc ; i++) {
     63     free(subject->argv[i]);
     64   }
     65   if (subject->argv) {
     66     free(subject->argv);
     67   }
     68   free(subject);
     69 }
     70 // }}}
     71 // cnf_directive_read(fd) {{{
     72 struct cnf_directive * cnf_directive_read(FILE *fd) {
     73   if (feof(fd)) return NULL;
     74 
     75   struct buf *acc = calloc(1, sizeof(struct buf));
     76   struct cnf_directive *output = calloc(1, sizeof(struct cnf_directive));
     77 
     78   int state = STATE_BLANK;
     79   char c;
     80   bool escape;
     81 
     82   while(!feof(fd)) {
     83     if (!fread(&c, sizeof(char), 1, fd)) break;
     84 
     85     switch(state) {
     86       case STATE_BLANK:
     87         switch(c) {
     88           case ' ':
     89           case '\t':
     90           case '\r':
     91             // Skip leading whitespace
     92             // Consume without further action
     93             continue;
     94           case '\n':
     95             // Output directive if we have something
     96             if (output->name) goto cnf_directive_read_finalize;
     97             continue;
     98           case '#':
     99             state = STATE_COMMENT;
    100             continue;
    101           case '"':
    102             state = STATE_DQUOTE;
    103             escape = false;
    104             continue;
    105           case '\'':
    106             state = STATE_SQUOTE;
    107             escape = false;
    108             continue;
    109           default:
    110             buf_append_byte(acc, c);
    111             state = STATE_WORD;
    112             continue;
    113         }
    114         break;
    115       case STATE_COMMENT:
    116         switch(c) {
    117           case '\n': // newline breaks comment
    118             if (output->name) goto cnf_directive_read_finalize; // Operator = directive complete
    119             state = STATE_BLANK;                                // No operator = read one more line
    120             // acc->len = 0;
    121             continue;
    122           default:
    123             // Consume without further action
    124             continue;
    125         }
    126         break;
    127       case STATE_DQUOTE:
    128         if (escape) {
    129           buf_append_byte(acc, cnf_unescape(c));
    130           escape = false;
    131           continue;
    132         }
    133         if (c == '\\') {
    134           escape = true;
    135           continue;
    136         }
    137         if (c == '"') {
    138           cnf_directive_append(output, acc->dat, acc->len);
    139           state = STATE_BLANK;
    140           memset(acc->dat, 0, acc->cap);
    141           acc->len = 0;
    142           continue;
    143         }
    144         buf_append_byte(acc, c);
    145         continue;
    146       case STATE_SQUOTE:
    147         if (escape) {
    148           // only \' is an escape inside single quotes; any other backslash stays literal
    149           if (c == '\'') {
    150             buf_append_byte(acc, '\'');
    151           } else {
    152             buf_append_byte(acc, '\\');
    153             buf_append_byte(acc, c);
    154           }
    155           escape = false;
    156           continue;
    157         }
    158         if (c == '\\') {
    159           escape = true;
    160           continue;
    161         }
    162         if (c == '\'') {
    163           cnf_directive_append(output, acc->dat, acc->len);
    164           state = STATE_BLANK;
    165           memset(acc->dat, 0, acc->cap);
    166           acc->len = 0;
    167           continue;
    168         }
    169         buf_append_byte(acc, c);
    170         continue;
    171       case STATE_WORD:
    172         switch(c) {
    173           case ' ':
    174           case '\t':
    175           case '\r':
    176           case '\n':
    177             // whitespace breaks word
    178             cnf_directive_append(output, acc->dat, acc->len);
    179             state = STATE_BLANK;
    180             memset(acc->dat, 0, acc->cap);
    181             acc->len = 0;
    182             if ('\n' == c) {
    183               goto cnf_directive_read_finalize;
    184             }
    185             continue;
    186           default:
    187             // append char to word
    188             buf_append_byte(acc, c);
    189             continue;
    190         }
    191         break;
    192       default:
    193         fprintf(stderr, "Invalid state: %d\n", state);
    194         exit(1);
    195     }
    196 
    197   }
    198 
    199 cnf_directive_read_finalize:
    200   if (acc->len) {
    201     cnf_directive_append(output, acc->dat, acc->len);
    202   }
    203   buf_clear(acc);
    204   free(acc);
    205   if (!output->name) {
    206     free(output);
    207     return NULL;
    208   }
    209   return output;
    210 }
    211 // }}}
    212 
    213 #ifdef __cplusplus
    214 } // extern "C"
    215 #endif
    216 
    217 // vim:fdm=marker:fdl=0