canfigger v0.3.x
Lightweight config file parser library
Loading...
Searching...
No Matches
canfigger.c
1/*
2This file is part of canfigger<https://github.com/andy5995/canfigger>
3
4Copyright (C) 2021-2024 Andy Alt (arch_stanton5995@proton.me)
5
6This program is free software: you can redistribute it and/or modify
7it under the terms of the GNU General Public License as published by
8the Free Software Foundation, either version 3 of the License, or
9any later version.
10
11This program is distributed in the hope that it will be useful,
12but WITHOUT ANY WARRANTY; without even the implied warranty of
13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14GNU General Public License for more details.
15
16You should have received a copy of the GNU General Public License
17along with this program. If not, see <http://www.gnu.org/licenses/>.
18*/
19
20#include <ctype.h> // isspace()
21#include <errno.h>
22#include <stdbool.h>
23#include <stdio.h>
24#include <stdlib.h> // free(), malloc()
25#include <string.h>
26#include <stdarg.h> // valist (variable argument function)
27
28// This is only required for version info and can be removed
29// if you're copying the canfigger source files to use as
30// an embedded library with your own project (i.e., not building
31// canfigger with the build system it's shipped with).
32#include "config.h"
33
34#include "canfigger.h"
35
36#define strdup_wrap(...) strdup_wrap_real(__VA_ARGS__, (size_t)0)
37
38static char *grab_str_segment(char *a, char **dest, const int c);
39static void free_list(struct Canfigger **node);
40
42struct line
43{
44 size_t len;
45 char *start;
46 char *end;
47};
51static char *
52strdup_wrap_real(const char *argv, ...)
53{
54 va_list args;
55 char *src = (char *) argv;
56 va_start(args, argv);
57 size_t n = va_arg(args, size_t); // Try to get the second argument
58
59 char *retval = NULL;
60 if (n == 0) // If n is 0, it means there was no second argument
61 retval = strdup(src);
62 else
63 retval = strndup(src, n);
64
65 va_end(args);
66 if (retval)
67 return retval;
68
69 perror("Failed to duplicate string:");
70 return NULL;
71}
72
73
74void
75canfigger_free_current_attr_str_advance(struct attributes *attributes,
76 char **attr)
77{
78 if (!attributes)
79 {
80 *attr = NULL;
81 return;
82 }
83
84 if (attributes->current && attributes->iter_ptr)
85 free(attributes->current);
86
87 if (!attributes->iter_ptr)
88 {
89 free(attributes->current);
90 attributes->current = NULL;
91 *attr = NULL;
92 return;
93 }
94
95 attributes->iter_ptr = grab_str_segment(attributes->iter_ptr,
96 &attributes->current, '\n');
97
98 if (*attributes->current)
99 {
100 *attr = attributes->current;
101 return;
102 }
103
104 // If we're here, that means strdup() failed to allocate memory in grab_str_segment()
105 // If an expected attribute isn't returned, the caller may want to terminate
106 // the remainder of the loop that's iterating through the entire linked list
107 // and exit the program.
108 *attr = NULL;
109 return;
110}
111
112
113void
114canfigger_free_current_key_node_advance(struct Canfigger **node)
115{
116 if (*node)
117 {
118 if ((*node)->attributes)
119 {
120 if ((*node)->attributes->current)
121 {
122 free((*node)->attributes->current);
123 (*node)->attributes->current = NULL;
124 }
125
126 if ((*node)->attributes->str)
127 {
128 free((*node)->attributes->str);
129 (*node)->attributes->str = NULL;
130 }
131
132 free((*node)->attributes);
133 (*node)->attributes = NULL;
134 }
135
136 if ((*node)->value)
137 {
138 free((*node)->value);
139 (*node)->value = NULL;
140 }
141
142 free((*node)->key);
143 (*node)->key = NULL;
144
145 struct Canfigger *temp_node = (*node)->next;
146 free(*node);
147 *node = temp_node;
148 }
149
150 return;
151}
152
153
154static void
155free_list(struct Canfigger **node)
156{
157 if (*node)
158 {
159 while (*node)
161 }
162
163 return;
164}
165
166
167/*
168 * returns a pointer to the first character after lc
169 * If lc appears more than once, the pointer
170 * will move past that as well.
171 *
172 * Ex1: "__Hello World": the pointer will be set to the 'H'.
173 * Ex2: "_H_ello World": Again, the pointer will be set to the 'H'.
174 */
175static char *
176erase_lead_char(const int lc, char *haystack)
177{
178 char *ptr = haystack;
179 if (*ptr != lc)
180 return ptr;
181
182 while (*ptr == lc)
183 ptr++;
184
185 return ptr;
186}
187
188
189static void
190truncate_whitespace(char *str)
191{
192 if (!str)
193 return;
194
195 char *pos_0 = str;
196 /* Advance pointer until NULL terminator is found
197 * Don't try to use strchr() because you'll get a different
198 * result if the pointer is already at '\0'. */
199 while (*str != '\0')
200 str++;
201
202 /* set pointer to segment preceding NULL terminator */
203 if (str != pos_0)
204 str--;
205 else
206 return;
207
208 while (isspace(*str))
209 {
210 *str = '\0';
211 if (str != pos_0)
212 str--;
213 else
214 break;
215 }
216
217 return;
218}
219
220
221static char *
222grab_str_segment(char *a, char **dest, const int c)
223{
224 a = erase_lead_char(' ', a);
225
226 char *b = strchr(a, c);
227 if (!b)
228 {
229 *dest = strdup_wrap(a);
230 return b; // NULL
231 }
232
233 size_t len = b - a;
234 *dest = strdup_wrap(a, len);
235 if (!*dest)
236 return NULL;
237
238 truncate_whitespace(*dest);
239 return b + 1;
240}
241
242static void *
243malloc_wrap(size_t size)
244{
245 void *retval = malloc(size);
246 if (retval)
247 return retval;
248
249 perror("Failed to allocate memory: ");
250
251 return NULL;
252}
253
254static void
255add_key_node(struct Canfigger **root, struct Canfigger **cur_node)
256{
257 struct Canfigger *tmp_node = malloc_wrap(sizeof(struct Canfigger));
258 if (!tmp_node)
259 return;
260
261 if (*root)
262 (*cur_node)->next = tmp_node;
263 else
264 *root = tmp_node;
265
266 *cur_node = tmp_node;
267
268 return;
269}
270
271
272static char *
273read_entire_file(const char *filename)
274{
275 FILE *fp = fopen(filename, "r");
276 if (!fp)
277 {
278 fprintf(stderr, "Failed to open %s:%s\n", filename, strerror(errno));
279 return NULL;
280 }
281
282 fseek(fp, 0, SEEK_END);
283 long file_size = ftell(fp);
284 fseek(fp, 0, SEEK_SET);
285
286 char *buffer = malloc_wrap(file_size + 1);
287 if (!buffer)
288 {
289 fclose(fp);
290 return NULL;
291 }
292
293 fread(buffer, 1, file_size, fp);
294 buffer[file_size] = '\0';
295
296 int r = ferror(fp);
297 clearerr(fp);
298 fclose(fp);
299
300 if (r == 0)
301 return buffer;
302
303 fprintf(stderr, "Error reading %s(%d)\n", filename, r);
304 return NULL;
305}
306
307
308static void
309free_incomplete_node(struct Canfigger **node)
310{
311 if (*node)
312 {
313 if ((*node)->key)
314 free((*node)->key);
315
316 if ((*node)->value)
317 free((*node)->value);
318
319 if ((*node)->attributes)
320 if ((*node)->attributes->str)
321 free((*node)->attributes->str);
322 }
323 free(*node);
324
325 return;
326}
327
328
329struct Canfigger *
330canfigger_parse_file(const char *file, const int delimiter)
331{
332 struct Canfigger *root = NULL, *cur_node = NULL;
333
334 char *buffer = read_entire_file(file);
335 if (buffer == NULL)
336 return NULL;
337
338 char file_contents[strlen(buffer) + 1];
339 memcpy(file_contents, buffer, sizeof file_contents);
340 free(buffer);
341
342 struct line line;
343 line.start = file_contents;
344 line.end = strchr(line.start, '\n');
345
346 bool node_complete;
347
348 while (line.end)
349 {
350 line.len = line.end - line.start;
351 char tmp_line[line.len + 1];
352 memcpy(tmp_line, line.start, line.len);
353 tmp_line[line.len] = '\0';
354
355 // Used in the next loop
356 if (line.end)
357 {
358 line.start = line.end + 1;
359 line.end = strchr(line.start, '\n');
360 }
361
362 char *line_ptr = tmp_line;
363 truncate_whitespace(line_ptr);
364
365 while (isspace(*line_ptr))
366 line_ptr = erase_lead_char(*line_ptr, line_ptr);
367
368 if (*line_ptr == '\0' || *line_ptr == '#' || *line_ptr == '[')
369 continue;
370
371 node_complete = false;
372 add_key_node(&root, &cur_node);
373 if (!cur_node)
374 break;
375
376 // Get key
377 cur_node->key = NULL;
378 line_ptr = grab_str_segment(line_ptr, &cur_node->key, '=');
379 if (!cur_node->key)
380 {
381 free_incomplete_node(&cur_node);
382 break;
383 }
384
385 // Get value
386 cur_node->value = NULL;
387
388 if (line_ptr)
389 {
390 line_ptr = grab_str_segment(line_ptr, &cur_node->value, delimiter);
391 if (!cur_node->value)
392 {
393 free_incomplete_node(&cur_node);
394 break;
395 }
396 }
397
398 // Handle attributes
399 if (line_ptr)
400 {
401 cur_node->attributes = malloc_wrap(sizeof(struct attributes));
402 if (!cur_node->attributes)
403 {
404 free_incomplete_node(&cur_node);
405 break;
406 }
407
408 struct attributes *attr_ptr = cur_node->attributes;
409 attr_ptr->current = NULL;
410
411 attr_ptr->str = strdup_wrap(line_ptr);
412 if (!attr_ptr->str)
413 {
414 free_incomplete_node(&cur_node);
415 break;
416 }
417
418 attr_ptr->iter_ptr = attr_ptr->str;
419
420 // Change the delimiter, which will be used later
421 // in canfigger_free_current_attr_str_advance()
422 char *delimiter_ptr = strchr(attr_ptr->iter_ptr, delimiter);
423 while (delimiter_ptr)
424 {
425 *delimiter_ptr = '\n';
426 delimiter_ptr = strchr(delimiter_ptr, delimiter);
427 }
428 }
429 else
430 cur_node->attributes = NULL;
431
432 cur_node->next = NULL;
433 node_complete = true;
434 }
435
436 if (!root)
437 return NULL;
438
439 if (!node_complete)
440 {
441 free_list(&root);
442 return NULL;
443 }
444
445 return root;
446}
Header file for the Canfigger configuration parser.
void canfigger_free_current_key_node_advance(struct Canfigger **list)
Frees the current key node and advances to the next node in the list.
Definition canfigger.c:114
struct Canfigger * canfigger_parse_file(const char *file, const int delimiter)
Parses a configuration file and creates a linked list of key-value pairs.
Definition canfigger.c:330
Structure to represent a key-value pair with attributes in the configuration.
Definition canfigger.h:60
Structure to hold attribute details of a configuration key.
Definition canfigger.h:44