-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmicro-conf.h
More file actions
442 lines (392 loc) · 9.84 KB
/
micro-conf.h
File metadata and controls
442 lines (392 loc) · 9.84 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
///////////////////////////////////////////////////////////////////////
// SPDX-License-Identifier: MIT
//
// micro-conf.h
// ============
//
// Header-only library to parse config files, in C99.
//
// Author: Giovanni Santini
// Mail: giovanni.santini@proton.me
// Github: @San7o
//
//
// Example
// -------
//
// // Example of a struct holding some values
// typedef struct {
// int x;
// int y;
// } Vec2;
//
// int main(void)
// {
// Vec2 vec = (Vec2) {
// .x = 1,
// .y = 1,
// };
//
// // Define which variables should be parsed in the config file
// // and their symbols
// MicroConf config[] =
// {
// // type variable symbol
// {MICRO_CONF_INT, &vec.x, "vec.x"},
// {MICRO_CONF_INT, &vec.y, "vec.y"},
// };
// size_t num_conf = sizeof(config) / sizeof(config[0]);
//
// // Parse the file
// int err = micro_conf_parse(config, num_conf, "micro.conf");
// if (err < 0) return -err;
//
// return 0;
// }
//
//
// Usage
// -----
//
// Do this:
//
// #define MICRO_CONF_IMPLEMENTATION
//
// before you include this file in *one* C or C++ file to create the
// implementation.
//
// i.e. it should look like this:
//
// #include ...
// #include ...
// #include ...
// #define MICRO_CONF_IMPLEMENTATION
// #include "micro-conf.h"
//
// This library lets you define which variable to parse from a config
// file. You do so by creating an array of MicroConf, for example:
//
// MicroConf config[] =
// {
// {MICRO_CONF_INT, &x, "x"},
// };
//
// Here, &x is a pointer to a variable that will be set according to
// the value present in the configuration file, if present, and "x" is
// the key of this value.
//
// The configuration file is a list of key-value pairs separated by
// either a `=` or `:`
//
// x: 10
// x = 10
//
// The number of spaces is irrelevant. You can parse a config file
// with `micro_conf_parse` by specifying the configs, the number of
// entries, and the filename to parse. For example:
//
// micro_conf_parse(config, num_conf, "micro.conf");
// if (err != MICRO_CONF_OK) return -err;
//
//
// Code
// ----
//
// The official git repository of micro-conf.h is hosted at:
//
// https://github.com/San7o/micro-conf.h
//
// This is part of a bigger collection of header-only C99 libraries
// called "micro-headers", contributions are welcome:
//
// https://github.com/San7o/micro-headers
//
#ifndef MICRO_CONF
#define MICRO_CONF
#define MICRO_CONF_MAJOR 0
#define MICRO_CONF_MINOR 1
#ifdef __cplusplus
extern "C" {
#endif
//
// Confuration
//
// Conf: Prefix for all functions
// For function inlining, set this to `static inline` and then define
// the implementation in all the files
#ifndef MICRO_CONF_DEF
#define MICRO_CONF_DEF extern
#endif
// Conf: file I/O operations
#ifndef MICRO_CONF_FOPEN
#ifdef MICRO_HEADERS_FOPEN
#define MICRO_CONF_FOPEN MICRO_HEADERS_FOPEN
#else
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#define MICRO_CONF_FOPEN fopen
#endif
#ifndef MICRO_FILE_MODE_READ
#define MICRO_FILE_MODE_READ "r"
#endif
#endif
#ifndef MICRO_CONF_FCLOSE
#ifdef MICRO_HEADERS_FCLOSE
#define MICRO_CONF_FCLOSE MICRO_HEADERS_FCLOSE
#else
#define MICRO_CONF_FCLOSE fclose
#endif
#endif
//
// Macros
//
//
// Errors
//
#define MICRO_CONF_OK 0
#define MICRO_CONF_ERROR_CONF_NULL -1
#define MICRO_CONF_ERROR_OPENING_FILE -2
#define MICRO_CONF_ERROR_CLOSING_FILE -3
#define MICRO_CONF_ERROR_UNKNOWN_TYPE -4
#define MICRO_CONF_ERROR_INVALID_BOOL -5
#define MICRO_CONF_ERROR_INVALID_INT -6
#define MICRO_CONF_ERROR_INVALID_DOUBLE -7
#define MICRO_CONF_ERROR_INVALID_FLOAT -8
#define MICRO_CONF_ERROR_INVALID_CHAR -9
#define _MICRO_CONF_ERROR_MAX -10
//
// Types
//
typedef enum {
MICRO_CONF_BOOL,
MICRO_CONF_INT,
MICRO_CONF_FLOAT,
MICRO_CONF_DOUBLE,
MICRO_CONF_CHAR,
MICRO_CONF_STR,
} MicroConfType;
typedef struct {
MicroConfType type;
void* value;
char* name;
} MicroConf;
//
// Function declarations
//
#include <stddef.h>
#include <stdbool.h>
// Parse [pathname] with a specified [conf] of [num_conf] values
// Returns MICRO_CONF_OK on success, or a MICRO_CONF_ERROR_ otherwise
//
// Note: MICRO_CONF_STR allocates memory for the parsed string, you
// should free it
MICRO_CONF_DEF int
micro_conf_parse(MicroConf *conf, size_t num_conf, const char *pathname);
//
// Implementation
//
#ifdef MICRO_CONF_IMPLEMENTATION
#include <stdlib.h>
#include <string.h>
// Get the number of separator characters from the left of [input]
// until the first non-separator or [input_size]. Separator characters
// are specs, new lines and tabs. Updates [line] and/or [column] if
// non null.
int left_space(const char* input, int input_size,
unsigned int* line, unsigned int* column)
{
if (!input || input_size == 0) return 0;
int pos = 0;
while(pos < input_size &&
(input[pos] == ' ' ||
input[pos] == '\n' ||
input[pos] == '\t'))
{
if (input[pos] == '\n') {
if (line) (*line)++;
if (column) *column = 0;
} else {
if (column) (*column)++;
}
pos++;
}
return pos;
}
MICRO_CONF_DEF int
micro_conf_parse(MicroConf *conf, size_t num_conf, const char *pathname)
{
if (!conf) return MICRO_CONF_ERROR_CONF_NULL;
FILE *file = MICRO_CONF_FOPEN(pathname, MICRO_FILE_MODE_READ);
if (!file) return MICRO_CONF_ERROR_OPENING_FILE;
char *line = NULL;
size_t len = 0;
ssize_t read;
while ((read = getline(&line, &len, file)) >= 0)
{
char *comment = strchr(line, '#');
if (comment) *comment = '\0';
int space = left_space(line, (int)read, NULL, NULL);
char *trimmed = line + space;
if (*trimmed == '\0' || *trimmed == '\n') continue;
for (size_t i = 0; i < num_conf; ++i)
{
size_t name_len = strlen(conf[i].name);
if (strncmp(trimmed, conf[i].name, name_len) != 0)
continue;
char *after_name = trimmed + name_len;
space = left_space(after_name, (int)(read - space - name_len), NULL, NULL);
after_name += space;
if (*after_name == '=' || *after_name == ':') after_name++;
space = left_space(after_name, (int)(read - (after_name - line)), NULL, NULL);
after_name += space;
char *value_str = after_name;
size_t vlen = strlen(value_str);
while (vlen > 0 && (value_str[vlen-1] == '\n' || value_str[vlen-1] == ' '))
value_str[--vlen] = '\0';
switch (conf[i].type)
{
case MICRO_CONF_BOOL:
{
if (strcmp(value_str, "true") == 0 || strcmp(value_str, "1") == 0)
{
*((bool*)conf[i].value) = true;
}
else if (strcmp(value_str, "false") == 0 || strcmp(value_str, "0") == 0)
{
*((bool*)conf[i].value) = false;
}
else
{
free(line);
MICRO_CONF_FCLOSE(file);
return MICRO_CONF_ERROR_INVALID_BOOL;
}
break;
}
case MICRO_CONF_CHAR:
{
if (strlen(value_str) == 1)
{
*((char*)conf[i].value) = value_str[0];
}
else
{
free(line);
MICRO_CONF_FCLOSE(file);
return MICRO_CONF_ERROR_INVALID_CHAR;
}
break;
}
case MICRO_CONF_STR:
{
*((char**)conf[i].value) = strdup(value_str);
break;
}
case MICRO_CONF_INT:
{
char *endptr;
long val = strtol(value_str, &endptr, 10);
if (*endptr != '\0')
{
free(line);
MICRO_CONF_FCLOSE(file);
return MICRO_CONF_ERROR_INVALID_INT;
}
*((int*)conf[i].value) = (int)val;
break;
}
case MICRO_CONF_DOUBLE:
{
char *endptr;
double val = strtod(value_str, &endptr);
if (*endptr != '\0')
{
free(line);
MICRO_CONF_FCLOSE(file);
return MICRO_CONF_ERROR_INVALID_DOUBLE;
}
*((double*)conf[i].value) = val;
break;
}
case MICRO_CONF_FLOAT:
{
char *endptr;
double val = strtof(value_str, &endptr);
if (*endptr != '\0')
{
free(line);
MICRO_CONF_FCLOSE(file);
return MICRO_CONF_ERROR_INVALID_FLOAT;
}
*((float*)conf[i].value) = val;
break;
}
default:
free(line);
MICFO_CONF_FCLOSE(file);
return MICRO_CONF_ERROR_UNKNOWN_TYPE;
}
break;
}
}
free(line);
if (MICRO_CONF_FCLOSE(file) != 0) return MICRO_CONF_ERROR_CLOSING_FILE;
return MICRO_CONF_OK;
}
#endif // MICRO_CONF_IMPLEMENTATION
//
// Examples
//
#if 0
#define MICRO_CONF_IMPLEMENTATION
#include "micro-conf.h"
#include <assert.h>
typedef struct {
int x;
int y;
} Vec2;
typedef struct {
int an_integer;
float a_float;
double a_double;
bool a_bool;
char a_char;
char *a_str;
Vec2 vec;
} MyConf;
int main(void)
{
MyConf conf;
// Defaults
conf.an_integer = 10;
conf.a_float = 11.0f;
conf.a_double = 123.123;
conf.a_bool = true;
conf.a_char = 'F';
conf.a_str = "test";
conf.vec = (Vec2) {
.x = 1,
.y = 1,
};
MicroConf config[] =
{
{MICRO_CONF_INT, &conf.an_integer, "an_integer"},
{MICRO_CONF_FLOAT, &conf.a_float, "a_float"},
{MICRO_CONF_DOUBLE, &conf.a_double, "a_double"},
{MICRO_CONF_BOOL, &conf.a_bool, "a_bool"},
{MICRO_CONF_CHAR, &conf.a_char, "a_char"},
{MICRO_CONF_STR, &conf.a_str, "a_str"},
{MICRO_CONF_INT, &conf.vec.x, "vec.x"},
{MICRO_CONF_INT, &conf.vec.y, "vec.y"},
};
size_t num_conf = sizeof(config) / sizeof(config[0]);
int err = micro_conf_parse(config, num_conf, "micro.conf");
if (err != MICRO_CONF_OK) return -err;
// ...
return 0;
}
#endif // 0
#ifdef __cplusplus
}
#endif
#endif // MICRO_CONF