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 #include "cx/properties.h"
30
31 #include <assert.h>
32
33 const CxPropertiesConfig cx_properties_config_default = {
34 '=',
35 '#',
36 '\0',
37 '\0',
38 '\\',
39 };
40
41 void cxPropertiesInit(
42 CxProperties *prop,
43 CxPropertiesConfig config
44 ) {
45 memset(prop,
0,
sizeof(CxProperties));
46 prop->config = config;
47 }
48
49 void cxPropertiesDestroy(CxProperties *prop) {
50 cxBufferDestroy(&prop->input);
51 cxBufferDestroy(&prop->buffer);
52 }
53
54 void cxPropertiesReset(CxProperties *prop) {
55 CxPropertiesConfig config = prop->config;
56 cxPropertiesDestroy(prop);
57 cxPropertiesInit(prop, config);
58 }
59
60 int cxPropertiesFilln(
61 CxProperties *prop,
62 const char *buf,
63 size_t len
64 ) {
65 if (cxBufferEof(&prop->input)) {
66
67 cxBufferDestroy(&prop->input);
68 cxBufferInit(&prop->input, (
void*) buf, len,
69 NULL,
CX_BUFFER_COPY_ON_WRITE |
CX_BUFFER_AUTO_EXTEND);
70 prop->input.size = len;
71 }
else {
72 if (cxBufferAppend(buf,
1, len, &prop->input) < len)
return -1;
73 }
74 return 0;
75 }
76
77 void cxPropertiesUseStack(
78 CxProperties *prop,
79 char *buf,
80 size_t capacity
81 ) {
82 cxBufferInit(&prop->buffer, buf, capacity,
NULL,
CX_BUFFER_COPY_ON_EXTEND);
83 }
84
85 CxPropertiesStatus cxPropertiesNext(
86 CxProperties *prop,
87 cxstring *key,
88 cxstring *value
89 ) {
90
91 if (prop->input.space ==
NULL) {
92 return CX_PROPERTIES_NULL_INPUT;
93 }
94
95
96 CxBuffer *current_buffer = &prop->input;
97
98
99 if (!cxBufferEof(&prop->buffer)) {
100
101 cxstring input = cx_strn(prop->input.space + prop->input.pos,
102 prop->input.size - prop->input.pos);
103 cxstring nl = cx_strchr(input,
'\n');
104 if (nl.length >
0) {
105
106
107 size_t len_until_nl = (
size_t)(nl.ptr - input.ptr) +
1;
108
109 if (cxBufferAppend(input.ptr,
1,
110 len_until_nl, &prop->buffer) < len_until_nl) {
111 return CX_PROPERTIES_BUFFER_ALLOC_FAILED;
112 }
113
114
115 prop->input.pos += len_until_nl;
116
117
118 current_buffer = &prop->buffer;
119 }
else {
120
121 if (cxBufferAppend(input.ptr,
1,
122 input.length, &prop->buffer) < input.length) {
123 return CX_PROPERTIES_BUFFER_ALLOC_FAILED;
124 }
125
126 cxBufferReset(&prop->input);
127 return CX_PROPERTIES_INCOMPLETE_DATA;
128 }
129 }
130
131 char comment1 = prop->config.comment1;
132 char comment2 = prop->config.comment2;
133 char comment3 = prop->config.comment3;
134 char delimiter = prop->config.delimiter;
135
136
137 while (!cxBufferEof(current_buffer)) {
138 const char *buf = current_buffer->space + current_buffer->pos;
139 size_t len = current_buffer->size - current_buffer->pos;
140
141
142
143
144
145 size_t delimiter_index =
0;
146 size_t comment_index =
0;
147 bool has_comment = false;
148
149 size_t i =
0;
150 char c =
0;
151 for (; i < len; i++) {
152 c = buf[i];
153 if (c == comment1 || c == comment2 || c == comment3) {
154 if (comment_index ==
0) {
155 comment_index = i;
156 has_comment = true;
157 }
158 }
else if (c == delimiter) {
159 if (delimiter_index ==
0 && !has_comment) {
160 delimiter_index = i;
161 }
162 }
else if (c ==
'\n') {
163 break;
164 }
165 }
166
167 if (c !=
'\n') {
168
169 assert(current_buffer != &prop->buffer);
170
171 assert(cxBufferEof(&prop->buffer));
172 if (prop->buffer.space ==
NULL) {
173
174 cxBufferInit(&prop->buffer,
NULL,
256,
NULL,
CX_BUFFER_AUTO_EXTEND);
175 }
else {
176
177
178 cxBufferReset(&prop->buffer);
179 }
180 if (cxBufferAppend(buf,
1, len, &prop->buffer) < len) {
181 return CX_PROPERTIES_BUFFER_ALLOC_FAILED;
182 }
183
184 cxBufferReset(&prop->input);
185 return CX_PROPERTIES_INCOMPLETE_DATA;
186 }
187
188 cxstring line = has_comment ?
189 cx_strn(buf, comment_index) :
190 cx_strn(buf, i);
191
192 if (delimiter_index ==
0) {
193
194 line = cx_strtrim(line);
195
196 if (line.length >
0) {
197 if (line.ptr[
0] == delimiter) {
198 return CX_PROPERTIES_INVALID_EMPTY_KEY;
199 }
else {
200 return CX_PROPERTIES_INVALID_MISSING_DELIMITER;
201 }
202 }
else {
203
204
205 if (current_buffer == &prop->buffer) {
206
207 assert(current_buffer->pos + i +
1 == current_buffer->size);
208
209 cxBufferReset(&prop->buffer);
210
211 current_buffer = &prop->input;
212 }
else {
213
214 current_buffer->pos += i +
1;
215 }
216 continue;
217 }
218 }
else {
219 cxstring k = cx_strn(buf, delimiter_index);
220 cxstring val = cx_strn(
221 buf + delimiter_index +
1,
222 line.length - delimiter_index -
1);
223 k = cx_strtrim(k);
224 val = cx_strtrim(val);
225 if (k.length >
0) {
226 *key = k;
227 *value = val;
228 current_buffer->pos += i +
1;
229 assert(current_buffer->pos <= current_buffer->size);
230 return CX_PROPERTIES_NO_ERROR;
231 }
else {
232 return CX_PROPERTIES_INVALID_EMPTY_KEY;
233 }
234 }
235 }
236
237
238 assert(cxBufferEof(&prop->buffer));
239 assert(cxBufferEof(&prop->input));
240
241 return CX_PROPERTIES_NO_DATA;
242 }
243
244 static int cx_properties_sink_map(
245 cx_attr_unused CxProperties *prop,
246 CxPropertiesSink *sink,
247 cxstring key,
248 cxstring value
249 ) {
250 CxMap *map = sink->sink;
251 CxAllocator *alloc = sink->data;
252 cxmutstr v = cx_strdup_a(alloc, value);
253 int r = cxMapPut(map, key, v.ptr);
254 if (r !=
0) cx_strfree_a(alloc, &v);
255 return r;
256 }
257
258 CxPropertiesSink cxPropertiesMapSink(CxMap *map) {
259 CxPropertiesSink sink;
260 sink.sink = map;
261 sink.data = (
void*) cxDefaultAllocator;
262 sink.sink_func = cx_properties_sink_map;
263 return sink;
264 }
265
266 static int cx_properties_read_string(
267 CxProperties *prop,
268 CxPropertiesSource *src,
269 cxstring *target
270 ) {
271 if (prop->input.space == src->src) {
272
273
274 target->length =
0;
275 }
else {
276 target->ptr = src->src;
277 target->length = src->data_size;
278 }
279 return 0;
280 }
281
282 static int cx_properties_read_file(
283 cx_attr_unused CxProperties *prop,
284 CxPropertiesSource *src,
285 cxstring *target
286 ) {
287 target->ptr = src->data_ptr;
288 target->length = fread(src->data_ptr,
1, src->data_size, src->src);
289 return ferror((
FILE*)src->src);
290 }
291
292 static int cx_properties_read_init_file(
293 cx_attr_unused CxProperties *prop,
294 CxPropertiesSource *src
295 ) {
296 src->data_ptr = cxMallocDefault(src->data_size);
297 if (src->data_ptr ==
NULL)
return 1;
298 return 0;
299 }
300
301 static void cx_properties_read_clean_file(
302 cx_attr_unused CxProperties *prop,
303 CxPropertiesSource *src
304 ) {
305 cxFreeDefault(src->data_ptr);
306 }
307
308 CxPropertiesSource cxPropertiesStringSource(cxstring str) {
309 CxPropertiesSource src;
310 src.src = (
void*) str.ptr;
311 src.data_size = str.length;
312 src.data_ptr =
NULL;
313 src.read_func = cx_properties_read_string;
314 src.read_init_func =
NULL;
315 src.read_clean_func =
NULL;
316 return src;
317 }
318
319 CxPropertiesSource cxPropertiesCstrnSource(
const char *str,
size_t len) {
320 CxPropertiesSource src;
321 src.src = (
void*) str;
322 src.data_size = len;
323 src.data_ptr =
NULL;
324 src.read_func = cx_properties_read_string;
325 src.read_init_func =
NULL;
326 src.read_clean_func =
NULL;
327 return src;
328 }
329
330 CxPropertiesSource cxPropertiesCstrSource(
const char *str) {
331 CxPropertiesSource src;
332 src.src = (
void*) str;
333 src.data_size = strlen(str);
334 src.data_ptr =
NULL;
335 src.read_func = cx_properties_read_string;
336 src.read_init_func =
NULL;
337 src.read_clean_func =
NULL;
338 return src;
339 }
340
341 CxPropertiesSource cxPropertiesFileSource(
FILE *file,
size_t chunk_size) {
342 CxPropertiesSource src;
343 src.src = file;
344 src.data_size = chunk_size;
345 src.data_ptr =
NULL;
346 src.read_func = cx_properties_read_file;
347 src.read_init_func = cx_properties_read_init_file;
348 src.read_clean_func = cx_properties_read_clean_file;
349 return src;
350 }
351
352 CxPropertiesStatus cxPropertiesLoad(
353 CxProperties *prop,
354 CxPropertiesSink sink,
355 CxPropertiesSource source
356 ) {
357 assert(source.read_func !=
NULL);
358 assert(sink.sink_func !=
NULL);
359
360
361 if (source.read_init_func !=
NULL) {
362 if (source.read_init_func(prop, &source)) {
363 return CX_PROPERTIES_READ_INIT_FAILED;
364 }
365 }
366
367
368 CxPropertiesStatus status;
369 CxPropertiesStatus kv_status =
CX_PROPERTIES_NO_DATA;
370 bool found = false;
371 while (true) {
372
373 cxstring input;
374 if (source.read_func(prop, &source, &input)) {
375 status =
CX_PROPERTIES_READ_FAILED;
376 break;
377 }
378
379
380 if (input.length ==
0) {
381 if (found) {
382
383 if (kv_status ==
CX_PROPERTIES_INCOMPLETE_DATA) {
384 status =
CX_PROPERTIES_INCOMPLETE_DATA;
385 }
else {
386 status =
CX_PROPERTIES_NO_ERROR;
387 }
388 }
else {
389
390 status =
CX_PROPERTIES_NO_DATA;
391 }
392 break;
393 }
394
395
396 cxPropertiesFill(prop, input);
397
398 do {
399 cxstring key, value;
400 kv_status = cxPropertiesNext(prop, &key, &value);
401 if (kv_status ==
CX_PROPERTIES_NO_ERROR) {
402 found = true;
403 if (sink.sink_func(prop, &sink, key, value)) {
404 kv_status =
CX_PROPERTIES_SINK_FAILED;
405 }
406 }
407 }
while (kv_status ==
CX_PROPERTIES_NO_ERROR);
408
409 if (kv_status >
CX_PROPERTIES_OK) {
410 status = kv_status;
411 break;
412 }
413 }
414
415 if (source.read_clean_func !=
NULL) {
416 source.read_clean_func(prop, &source);
417 }
418
419 return status;
420 }
421