canfigger v0.3.1
Lightweight config file parser library
Loading...
Searching...
No Matches
example-02.c
1/* Example: parse a config file containing both typed scalar fields and
2 * variable-length numeric attribute lists, using an offset-based dispatch
3 * table (ConfigType/ConfigEntry) for the scalar fields and
4 * canfigger_free_current_attr_str_advance() for the lists.
5 *
6 * Numeric attribute lists use the form:
7 * key = list, v1, v2, ...
8 */
9
10#include "tests/test.h"
11#include <stdbool.h>
12#include <stddef.h>
13#include <stdint.h>
14#include <stdlib.h>
15#include <string.h>
16#ifdef _MSC_VER
17 #define strcasecmp _stricmp
18#else
19 #include <strings.h>
20#endif
21
22typedef enum {
23 CFG_TYPE_STRING,
24 CFG_TYPE_INT,
25 CFG_TYPE_UINT8,
26 CFG_TYPE_UINT16,
27 CFG_TYPE_UINT32,
28 CFG_TYPE_BOOL
29} ConfigType;
30
31typedef struct {
32 const char *key;
33 ConfigType type;
34 const char *default_value;
35 size_t offset;
36 size_t size;
38
39#define MAX_NUMERIC_LIST 8
40#define MAX_STR_LEN 64
41
42typedef struct {
43 char host[MAX_STR_LEN];
44 char log_level[MAX_STR_LEN];
45 char description[MAX_STR_LEN];
46 int timeout;
47 uint8_t max_retries;
48 uint16_t port;
49 uint32_t max_connections;
50 bool verbose;
51 uint32_t bet_amounts[MAX_NUMERIC_LIST];
52 uint8_t bet_amount_count;
53 uint32_t difficulty_levels[MAX_NUMERIC_LIST];
54 uint8_t difficulty_level_count;
55 uint32_t port_list[MAX_NUMERIC_LIST];
56 uint8_t port_list_count;
58
59static const ConfigEntry entries[] = {
60 { "host", CFG_TYPE_STRING, "localhost", offsetof(AppConfig_t, host), MAX_STR_LEN },
61 { "log_level", CFG_TYPE_STRING, "info", offsetof(AppConfig_t, log_level), MAX_STR_LEN },
62 { "description", CFG_TYPE_STRING, "", offsetof(AppConfig_t, description), MAX_STR_LEN },
63 { "port", CFG_TYPE_UINT16, "8080", offsetof(AppConfig_t, port), sizeof(uint16_t) },
64 { "timeout", CFG_TYPE_INT, "-5", offsetof(AppConfig_t, timeout), sizeof(int) },
65 { "max_retries", CFG_TYPE_UINT8, "3", offsetof(AppConfig_t, max_retries), sizeof(uint8_t) },
66 { "max_connections", CFG_TYPE_UINT32, "100", offsetof(AppConfig_t, max_connections), sizeof(uint32_t) },
67 { "verbose", CFG_TYPE_BOOL, "no", offsetof(AppConfig_t, verbose), sizeof(bool) },
68};
69
70static void
71config_set_field(AppConfig_t *cfg, const ConfigEntry *entry, const char *val)
72{
73 void *field = (uint8_t *)cfg + entry->offset;
74 switch (entry->type)
75 {
76 case CFG_TYPE_STRING:
77 snprintf((char *)field, entry->size, "%s", val);
78 break;
79 case CFG_TYPE_INT:
80 *(int *)field = (int)strtol(val, NULL, 10);
81 break;
82 case CFG_TYPE_UINT8:
83 *(uint8_t *)field = (uint8_t)strtoul(val, NULL, 10);
84 break;
85 case CFG_TYPE_UINT16:
86 *(uint16_t *)field = (uint16_t)strtoul(val, NULL, 10);
87 break;
88 case CFG_TYPE_UINT32:
89 *(uint32_t *)field = (uint32_t)strtoul(val, NULL, 10);
90 break;
91 case CFG_TYPE_BOOL:
92 *(bool *)field = (strcasecmp(val, "yes") == 0
93 || strcasecmp(val, "true") == 0
94 || strcmp(val, "1") == 0);
95 break;
96 }
97}
98
99static void
100print_uint32_list(const char *label, const uint32_t *arr, uint8_t count)
101{
102 printf(" %-20s", label);
103 for (uint8_t i = 0; i < count; i++)
104 printf("%s%u", i ? ", " : "", arr[i]);
105 putchar('\n');
106}
107
108static void
109print_config(const AppConfig_t *cfg)
110{
111 printf("\n--- Parsed config ---\n");
112 printf(" %-20s%s\n", "host:", cfg->host);
113 printf(" %-20s%s\n", "log_level:", cfg->log_level);
114 printf(" %-20s%s\n", "description:", cfg->description);
115 printf(" %-20s%u\n", "port:", cfg->port);
116 printf(" %-20s%d\n", "timeout:", cfg->timeout);
117 printf(" %-20s%u\n", "max_retries:", cfg->max_retries);
118 printf(" %-20s%u\n", "max_connections:", cfg->max_connections);
119 printf(" %-20s%s\n", "verbose:", cfg->verbose ? "true" : "false");
120 print_uint32_list("bet_amounts:", cfg->bet_amounts, cfg->bet_amount_count);
121 print_uint32_list("difficulty_levels:", cfg->difficulty_levels, cfg->difficulty_level_count);
122 print_uint32_list("port_list:", cfg->port_list, cfg->port_list_count);
123 printf("---------------------\n");
124}
125
126static void
127parse_numeric_list(struct Canfigger *node, uint32_t *out, uint8_t *count)
128{
129 char *attr = NULL;
130 canfigger_free_current_attr_str_advance(node->attributes, &attr);
131 while (attr && *count < MAX_NUMERIC_LIST)
132 {
133 printf(" attribute: %s\n", attr);
134 out[(*count)++] = (uint32_t)strtoul(attr, NULL, 10);
135 canfigger_free_current_attr_str_advance(node->attributes, &attr);
136 }
137}
138
139int
140main(int argc, char *argv[])
141{
142 char *default_filename = SOURCE_DIR "/example-02.conf";
143 char *filename_ptr = default_filename;
144
145 if (argc == 2)
146 filename_ptr = argv[1];
147
148 if (argc > 2)
149 {
150 fputs("This example program only accepts a single argument:\n\n", stderr);
151 fprintf(stderr, "%s <config-file>\n\n", argv[0]);
152 return -1;
153 }
154
155 AppConfig_t config = { 0 };
156
157 struct Canfigger *list = canfigger_parse_file(filename_ptr, ',');
158 if (!list)
159 return -1;
160
161 bool found_keys[ARRAY_SIZE(entries)];
162 memset(found_keys, 0, sizeof(found_keys));
163
164 printf("--- Parsing %s ---\n", filename_ptr);
165
166 struct Canfigger *node = list;
167 while (node)
168 {
169 printf("key: %s, value: %s\n", node->key,
170 node->value ? node->value : "NULL");
171
172 if (strcmp(node->key, "bet_amounts") == 0)
173 parse_numeric_list(node, config.bet_amounts, &config.bet_amount_count);
174 else if (strcmp(node->key, "difficulty_levels") == 0)
175 parse_numeric_list(node, config.difficulty_levels, &config.difficulty_level_count);
176 else if (strcmp(node->key, "port_list") == 0)
177 parse_numeric_list(node, config.port_list, &config.port_list_count);
178 else
179 {
180 for (size_t i = 0; i < ARRAY_SIZE(entries); i++)
181 {
182 if (strcmp(node->key, entries[i].key) == 0)
183 {
184 config_set_field(&config, &entries[i],
185 node->value ? node->value : entries[i].default_value);
186 found_keys[i] = true;
187 break;
188 }
189 }
190 }
192 }
193
194 /* Apply defaults for any scalar keys absent from the file. */
195 for (size_t i = 0; i < ARRAY_SIZE(entries); i++)
196 if (!found_keys[i])
197 config_set_field(&config, &entries[i], entries[i].default_value);
198
199 if (filename_ptr != default_filename)
200 return 0;
201
202 print_config(&config);
203
204 assert(strcmp(config.host, "example.com") == 0);
205 assert(strcmp(config.log_level, "debug") == 0);
206 assert(strcmp(config.description, "A sample configuration file") == 0);
207 assert(config.port == 9000);
208 assert(config.timeout == -10);
209 assert(config.max_retries == 5);
210 assert(config.max_connections == 200);
211 assert(config.verbose == true);
212
213 const uint32_t expected_bet[] = { 100, 250, 500, 1000 };
214 const uint32_t expected_difficulty[] = { 1, 2, 3, 5, 8 };
215 const uint32_t expected_ports[] = { 8080, 9090 };
216
217 assert(config.bet_amount_count == ARRAY_SIZE(expected_bet));
218 for (size_t i = 0; i < config.bet_amount_count; i++)
219 assert(config.bet_amounts[i] == expected_bet[i]);
220
221 assert(config.difficulty_level_count == ARRAY_SIZE(expected_difficulty));
222 for (size_t i = 0; i < config.difficulty_level_count; i++)
223 assert(config.difficulty_levels[i] == expected_difficulty[i]);
224
225 assert(config.port_list_count == ARRAY_SIZE(expected_ports));
226 for (size_t i = 0; i < config.port_list_count; i++)
227 assert(config.port_list[i] == expected_ports[i]);
228
229 return 0;
230}
struct Canfigger * canfigger_parse_file(const char *file, const int delimiter)
Parse a configuration file into a linked list of key-value nodes.
Definition canfigger.c:351
void canfigger_free_current_key_node_advance(struct Canfigger **node)
Free the current node and advance the list pointer to the next node.
Definition canfigger.c:125
A single node in the parsed configuration linked list.
Definition canfigger.h:126