Lely core libraries 2.3.4
config_ini.c
Go to the documentation of this file.
1
24#include "util.h"
25
26#if !LELY_NO_STDIO
27
28#include <lely/util/config.h>
29#include <lely/util/diag.h>
30#include <lely/util/frbuf.h>
31#include <lely/util/fwbuf.h>
32#include <lely/util/lex.h>
33#include <lely/util/membuf.h>
34#include <lely/util/print.h>
35
36#include <assert.h>
37
38static int issection(int c);
39static int iskey(int c);
40static int isvalue(int c);
41
42static size_t skip(const char *begin, const char *end, struct floc *at);
43
44static void membuf_print_chars(struct membuf *buf, const char *s, size_t n);
45
46static void config_print_ini_func(const char *section, const char *key,
47 const char *value, void *data);
48
49size_t
50config_parse_ini_file(config_t *config, const char *filename)
51{
52 frbuf_t *buf = frbuf_create(filename);
53 if (!buf) {
54 diag(DIAG_ERROR, get_errc(), "%s", filename);
55 return 0;
56 }
57
58 size_t size = 0;
59 const void *map = frbuf_map(buf, 0, &size);
60 if (!map) {
61 diag(DIAG_ERROR, get_errc(), "%s: unable to map file",
62 filename);
63 frbuf_destroy(buf);
64 return 0;
65 }
66
67 const char *begin = map;
68 const char *end = begin + size;
69 struct floc at = { filename, 1, 1 };
70 size_t chars = config_parse_ini_text(config, begin, end, &at);
71
72 frbuf_destroy(buf);
73
74 return chars;
75}
76
77size_t
78config_parse_ini_text(config_t *config, const char *begin, const char *end,
79 struct floc *at)
80{
81 assert(config);
82 assert(begin);
83
84 struct membuf section = MEMBUF_INIT;
85 struct membuf key = MEMBUF_INIT;
86 struct membuf value = MEMBUF_INIT;
87
88 const char *cp = begin;
89 size_t chars = 0;
90
91 for (;;) {
92 // Skip comments and empty lines.
93 for (;;) {
94 cp += skip(cp, end, at);
95 if ((chars = lex_break(cp, end, at)) > 0)
96 cp += chars;
97 else
98 break;
99 }
100 if ((end && cp >= end) || !*cp)
101 break;
102
103 if ((chars = lex_char('[', cp, end, at)) > 0) {
104 cp += chars;
105 cp += skip(cp, end, at);
106 if ((chars = lex_ctype(&issection, cp, end, at)) > 0) {
107 membuf_print_chars(&section, cp, chars);
108 cp += chars;
109 cp += skip(cp, end, at);
110 if ((chars = lex_char(']', cp, end, at)) > 0)
111 cp += chars;
112 else
113 diag_if(DIAG_ERROR, 0, at,
114 "expected ']' after section name");
115 } else {
116 diag_if(DIAG_ERROR, 0, at,
117 "expected section name after '['");
118 }
119 cp += lex_line_comment(NULL, cp, end, at);
120 } else if ((chars = lex_ctype(&iskey, cp, end, at)) > 0) {
121 membuf_print_chars(&key, cp, chars);
122 cp += chars;
123 cp += skip(cp, end, at);
124 if ((chars = lex_char('=', cp, end, at)) > 0) {
125 cp += chars;
126 cp += skip(cp, end, at);
127 if ((chars = lex_char('\"', cp, end, at)) > 0) {
128 cp += chars;
129 lex_c99_str(cp, end, at, NULL, &chars);
130 chars++;
131 membuf_clear(&value);
132 membuf_reserve(&value, chars);
133 char *s = membuf_alloc(&value, &chars);
134 if (s && chars)
135 s[--chars] = '\0';
136 cp += lex_c99_str(cp, end, NULL, s,
137 &chars);
138 // clang-format off
139 if ((chars = lex_char('\"', cp, end,
140 at)) > 0)
141 // clang-format on
142 cp += chars;
143 else
144 diag_if(DIAG_ERROR, 0, at,
145 "expected '\"' after string");
146 } else {
147 chars = lex_ctype(
148 &isvalue, cp, end, at);
149 membuf_print_chars(&value, cp, chars);
150 cp += chars;
151 }
152 config_set(config, membuf_begin(&section),
153 membuf_begin(&key),
154 membuf_begin(&value));
155 membuf_clear(&value);
156 } else {
157 diag_if(DIAG_ERROR, 0, at,
158 "expected '=' after key");
159 }
160 cp += lex_line_comment(NULL, cp, end, at);
161 } else {
162 if (isgraph((unsigned char)*cp))
163 diag_if(DIAG_ERROR, 0, at,
164 "unknown character '%c'", *cp);
165 else
166 diag_if(DIAG_ERROR, 0, at,
167 "unknown character '\\%o'",
168 *cp);
169 // Skip the offending character.
170 cp += lex_char(*cp, cp, end, at);
171 }
172 }
173
174 membuf_fini(&value);
175 membuf_fini(&key);
176 membuf_fini(&section);
177
178 return cp - begin;
179}
180
181size_t
182config_print_ini_file(const config_t *config, const char *filename)
183{
184 fwbuf_t *buf = fwbuf_create(filename);
185 if (!buf) {
186 diag(DIAG_ERROR, get_errc(), "%s", filename);
187 return 0;
188 }
189
190 size_t size = config_print_ini_text(config, NULL, NULL);
191 void *map = fwbuf_map(buf, 0, &size);
192 if (!map) {
193 diag(DIAG_ERROR, get_errc(), "%s: unable to map file",
194 filename);
195 fwbuf_destroy(buf);
196 return 0;
197 }
198
199 char *begin = map;
200 char *end = begin + size;
201 size_t chars = config_print_ini_text(config, &begin, end);
202
203 if (fwbuf_commit(buf) == -1) {
204 diag(DIAG_ERROR, get_errc(), "%s: unable to commit file",
205 filename);
206 fwbuf_destroy(buf);
207 return 0;
208 }
209
210 fwbuf_destroy(buf);
211
212 return chars;
213}
214
215size_t
216config_print_ini_text(const config_t *config, char **pbegin, char *end)
217{
218 assert(config);
219
220 struct {
221 char **pbegin;
222 char *end;
223 const char *section;
224 size_t chars;
225 } ctx = { .pbegin = pbegin, .end = end, .section = NULL, .chars = 0 };
226
227 config_foreach(config, &config_print_ini_func, &ctx);
228
229 return ctx.chars;
230}
231
232static int
233issection(int c)
234{
235 return isgraph(c) && c != '#' && c != ';' && c != '[' && c != ']';
236}
237
238static int
239iskey(int c)
240{
241 return isgraph(c) && c != '#' && c != ';' && c != '=';
242}
243
244static int
245isvalue(int c)
246{
247 return isprint(c) && c != '#' && c != ';';
248}
249
250static size_t
251skip(const char *begin, const char *end, struct floc *at)
252{
253 assert(begin);
254
255 const char *cp = begin;
256
257 cp += lex_ctype(&isblank, cp, end, at);
258 cp += lex_line_comment("#", cp, end, at);
259 cp += lex_line_comment(";", cp, end, at);
260
261 return cp - begin;
262}
263
264static void
265membuf_print_chars(struct membuf *buf, const char *s, size_t n)
266{
267 assert(buf);
268
269 // Remove trailing whitespace.
270 while (n && isspace((unsigned char)s[n - 1]))
271 n--;
272
273 membuf_clear(buf);
274 if (!membuf_reserve(buf, n + 1))
275 return;
276 membuf_write(buf, s, n);
277 membuf_write(buf, "", 1);
278}
279
280static void
281config_print_ini_func(const char *section, const char *key, const char *value,
282 void *data)
283{
284 assert(section);
285 assert(key);
286 assert(value);
287 struct {
288 char **pbegin;
289 char *end;
290 const char *section;
291 size_t chars;
292 } *ctx = data;
293 assert(ctx);
294 char **pbegin = ctx->pbegin;
295 char *end = ctx->end;
296 size_t chars = ctx->chars;
297
298 if (ctx->section != section) {
299 ctx->section = section;
300 // Prepend the section with a newline, if necessary.
301 if (chars)
302 chars += print_char(pbegin, end, '\n');
303 if (*section) {
304 chars += print_char(pbegin, end, '[');
305 while (*section)
306 chars += print_char(pbegin, end, *section++);
307 chars += print_char(pbegin, end, ']');
308 chars += print_char(pbegin, end, '\n');
309 }
310 }
311
312 while (*key)
313 chars += print_char(pbegin, end, *key++);
314
315 chars += print_char(pbegin, end, ' ');
316 chars += print_char(pbegin, end, '=');
317
318 if (*value) {
319 chars += print_char(pbegin, end, ' ');
320 size_t n = strlen(value);
321 // Check for leading or trailing whitespace.
322 int esc = isspace((unsigned char)value[0])
323 || isspace((unsigned char)value[n - 1]);
324 // Check for non-printable ASCII characters or comments.
325 for (size_t i = 0; !esc && i < n; i++)
326 esc = !isvalue((unsigned char)value[i]);
327 if (esc) {
328 chars += print_char(pbegin, end, '"');
329 chars += print_c99_str(pbegin, end, value, n);
330 chars += print_char(pbegin, end, '"');
331 } else {
332 while (*value)
333 chars += print_char(pbegin, end, *value++);
334 }
335 }
336
337 chars += print_char(pbegin, end, '\n');
338
339 ctx->chars = chars;
340}
341
342#endif // !LELY_NO_STDIO
This header file is part of the utilities library; it contains the configuration functions.
const char * config_set(config_t *config, const char *section, const char *key, const char *value)
Sets a key in or removes a key from a configuration struct.
Definition: config.c:218
void config_foreach(const config_t *config, config_foreach_func_t *func, void *data)
Invokes a function for each key in a configuration struct.
Definition: config.c:238
size_t config_parse_ini_file(config_t *config, const char *filename)
Parses an INI file and adds the keys to a configuration struct.
Definition: config_ini.c:50
size_t config_print_ini_file(const config_t *config, const char *filename)
Prints a configuration struct to an INI file.
Definition: config_ini.c:182
size_t config_parse_ini_text(config_t *config, const char *begin, const char *end, struct floc *at)
Parses a string in INI-format and adds the keys to a configuration struct.
Definition: config_ini.c:78
size_t config_print_ini_text(const config_t *config, char **pbegin, char *end)
Prints a configuration struct in INI-format to a memory buffer.
Definition: config_ini.c:216
This header file is part of the utilities library; it contains the diagnostic declarations.
@ DIAG_ERROR
An error.
Definition: diag.h:57
void diag_if(enum diag_severity severity, int errc, const struct floc *at, const char *format,...)
Emits a diagnostic message occurring at a location in a text file.
Definition: diag.c:205
void diag(enum diag_severity severity, int errc, const char *format,...)
Emits a diagnostic message.
Definition: diag.c:171
int get_errc(void)
Returns the last (thread-specific) native error code set by a system call or library function.
Definition: errnum.c:932
This header file is part of the utilities library; it contains the read file buffer declarations.
const void * frbuf_map(frbuf_t *buf, intmax_t pos, size_t *psize)
Maps (part of) the contents of a read file buffer to memory.
Definition: frbuf.c:394
void frbuf_destroy(frbuf_t *buf)
Destroys a read file buffer.
Definition: frbuf.c:163
frbuf_t * frbuf_create(const char *filename)
Creates a new read file buffer.
Definition: frbuf.c:138
This header file is part of the utilities library; it contains the (atomic) write file buffer declara...
void * fwbuf_map(fwbuf_t *buf, intmax_t pos, size_t *psize)
Maps (part of) the contents of a write file buffer to memory.
Definition: fwbuf.c:658
void fwbuf_destroy(fwbuf_t *buf)
Destroys a write file buffer.
Definition: fwbuf.c:330
int fwbuf_commit(fwbuf_t *buf)
Commits all changes to a write file buffer to disk if all previous file operations were successful,...
Definition: fwbuf.c:979
fwbuf_t * fwbuf_create(const char *filename)
Creates a new (atomic) write file buffer.
Definition: fwbuf.c:305
This header file is part of the utilities library; it contains the lexer function declarations.
size_t lex_char(int c, const char *begin, const char *end, struct floc *at)
Lexes the specified character from a memory buffer.
Definition: lex.c:41
size_t lex_ctype(int(*ctype)(int), const char *begin, const char *end, struct floc *at)
Greedily lexes a sequence of characters of the specified class from a memory buffer.
Definition: lex.c:54
size_t lex_line_comment(const char *delim, const char *begin, const char *end, struct floc *at)
Lexes a single line-comment (excluding the line break) starting with the specified delimiter from a m...
Definition: lex.c:633
size_t lex_c99_str(const char *begin, const char *end, struct floc *at, char *s, size_t *pn)
Lexes a UTF-8 encoded Unicode string from a memory buffer.
Definition: lex.c:250
size_t lex_break(const char *begin, const char *end, struct floc *at)
Lexes a single line break from a memory buffer.
Definition: lex.c:69
This header file is part of the utilities library; it contains the memory buffer declarations.
void * membuf_begin(const struct membuf *buf)
Returns a pointer to the first byte in a memory buffer.
Definition: membuf.h:161
size_t membuf_reserve(struct membuf *buf, size_t size)
Resizes a memory buffer, if necessary, to make room for at least an additional size bytes.
Definition: membuf.c:52
void membuf_fini(struct membuf *buf)
Finalizes a memory buffer.
Definition: membuf.c:40
size_t membuf_write(struct membuf *buf, const void *ptr, size_t size)
Writes data to a memory buffer.
Definition: membuf.h:222
void * membuf_alloc(struct membuf *buf, size_t *size)
Creates region of *size bytes in a memory buffer, starting at the current position indicator given by...
Definition: membuf.h:211
void membuf_clear(struct membuf *buf)
Clears a memory buffer.
Definition: membuf.h:169
#define MEMBUF_INIT
The static initializer for struct membuf.
Definition: membuf.h:46
This header file is part of the utilities library; it contains the printing function declarations.
size_t print_c99_str(char **pbegin, char *end, const char *s, size_t n)
Prints a UTF-8 encoded Unicode string to a memory buffer.
Definition: print.c:193
size_t print_char(char **pbegin, char *end, int c)
Prints a single character to a memory buffer.
Definition: print.h:286
This is the internal header file of the utilities library.
A configuration struct.
Definition: config.c:38
An read file buffer struct.
Definition: frbuf.c:52
An (atomic) write file buffer struct.
Definition: fwbuf.c:59
A location in a text file.
Definition: diag.h:39
const char * filename
The name of the file.
Definition: diag.h:41
A memory buffer.
Definition: membuf.h:36
char * end
A pointer to one past the last byte in the buffer.
Definition: membuf.h:42
char * begin
A pointer to the first byte in the buffer.
Definition: membuf.h:40