src/properties.c

changeset 1031
8a90552bba29
parent 985
68754c7de906
equal deleted inserted replaced
1030:06091e067bee 1031:8a90552bba29
28 28
29 #include "cx/properties.h" 29 #include "cx/properties.h"
30 30
31 #include <assert.h> 31 #include <assert.h>
32 32
33 static const int CX_PROPERTIES_FLAG_USE_STACK = 0x01;
34
35 const CxPropertiesConfig cx_properties_config_default = { 33 const CxPropertiesConfig cx_properties_config_default = {
36 '=', 34 '=',
37 //'\\', 35 //'\\',
38 '#', 36 '#',
39 '\0', 37 '\0',
47 memset(prop, 0, sizeof(CxProperties)); 45 memset(prop, 0, sizeof(CxProperties));
48 prop->config = config; 46 prop->config = config;
49 } 47 }
50 48
51 void cxPropertiesDestroy(CxProperties *prop) { 49 void cxPropertiesDestroy(CxProperties *prop) {
52 if (0 == (prop->flags & CX_PROPERTIES_FLAG_USE_STACK)) { 50 cxBufferDestroy(&prop->input);
53 free(prop->buf); 51 cxBufferDestroy(&prop->buffer);
54 }
55 prop->buf = NULL;
56 prop->buf_capacity = prop->buf_size = 0;
57 }
58
59 static int cx_properties_ensure_buf_capacity(CxProperties *prop, size_t cap) {
60 if (prop->buf_capacity >= cap) {
61 return 0;
62 }
63
64 // not enough capacity - are we on the stack right now?
65 if ((prop->flags & CX_PROPERTIES_FLAG_USE_STACK) != 0) {
66 // move to the heap
67 char *newbuf = malloc(cap);
68 if (newbuf == NULL) return 1;
69 memcpy(newbuf, prop->buf, prop->buf_size);
70 prop->buf = newbuf;
71 prop->flags &= CX_PROPERTIES_FLAG_USE_STACK;
72 } else {
73 // we are on the heap already, reallocate
74 // this is legit, because realloc() behaves like malloc() when the
75 // current pointer is NULL
76 char *newbuf = realloc(prop->buf, cap);
77 if (newbuf == NULL) return 1;
78 prop->buf = newbuf;
79 }
80
81 // store new capacity and return
82 prop->buf_capacity = cap;
83 return 0;
84 }
85
86 static int cx_properties_rescuen_input(CxProperties *prop, size_t len) {
87 if (cx_properties_ensure_buf_capacity(prop, prop->buf_size + len)) {
88 return 1;
89 }
90 const char *src = prop->text + prop->text_pos;
91 char *dest = prop->buf + prop->buf_size;
92 memcpy(dest, src, len);
93 prop->buf_size += len;
94 prop->text_pos += len;
95 return 0;
96 }
97
98 static int cx_properties_rescue_input(CxProperties *prop) {
99 // someone fucked around with our integers, exit immediately
100 if (prop->text_pos > prop->text_size) return 0;
101
102 // determine the bytes needed
103 size_t len = prop->text_size - prop->text_pos;
104
105 return cx_properties_rescuen_input(prop, len);
106 } 52 }
107 53
108 int cxPropertiesFilln( 54 int cxPropertiesFilln(
109 CxProperties *prop, 55 CxProperties *prop,
110 const char *buf, 56 const char *buf,
111 size_t len 57 size_t len
112 ) { 58 ) {
113 if (cx_properties_rescue_input(prop)) return 1; 59 if (cxBufferEof(&prop->input)) {
114 prop->text = buf; 60 // destroy a possible previously initialized buffer
115 prop->text_size = len; 61 cxBufferDestroy(&prop->input);
116 prop->text_pos = 0; 62 cxBufferInit(&prop->input, (void*) buf, len,
63 NULL, CX_BUFFER_COPY_ON_WRITE | CX_BUFFER_AUTO_EXTEND);
64 prop->input.size = len;
65 } else {
66 if (cxBufferAppend(buf, 1, len, &prop->input) < len) return -1;
67 }
117 return 0; 68 return 0;
118 } 69 }
119 70
120 void cxPropertiesUseStack( 71 void cxPropertiesUseStack(
121 CxProperties *prop, 72 CxProperties *prop,
122 char *buf, 73 char *buf,
123 size_t capacity 74 size_t capacity
124 ) { 75 ) {
125 assert(prop->buf == NULL); 76 cxBufferInit(&prop->buffer, buf, capacity, NULL, CX_BUFFER_COPY_ON_EXTEND);
126 prop->buf = buf;
127 prop->buf_capacity = capacity;
128 prop->buf_size = 0;
129 prop->flags |= CX_PROPERTIES_FLAG_USE_STACK;
130 } 77 }
131 78
132 CxPropertiesStatus cxPropertiesNext( 79 CxPropertiesStatus cxPropertiesNext(
133 CxProperties *prop, 80 CxProperties *prop,
134 cxstring *key, 81 cxstring *key,
135 cxstring *value 82 cxstring *value
136 ) { 83 ) {
137 // check if we have a text buffer 84 // check if we have a text buffer
138 if (prop->text == NULL) { 85 if (prop->input.space == NULL) {
139 return CX_PROPERTIES_NULL_INPUT; 86 return CX_PROPERTIES_NULL_INPUT;
140 } 87 }
88
89 // a pointer to the buffer we want to read from
90 CxBuffer *current_buffer = &prop->input;
91
141 // check if we have rescued data 92 // check if we have rescued data
142 if (prop->buf_size > 0) { 93 if (!cxBufferEof(&prop->buffer)) {
143 // check if we can now get a complete line 94 // check if we can now get a complete line
144 const char *buf = prop->text + prop->text_pos; 95 cxstring input = cx_strn(prop->input.space + prop->input.pos,
145 size_t len = prop->text_size - prop->text_pos; 96 prop->input.size - prop->input.pos);
146 cxstring str = cx_strn(buf, len); 97 cxstring nl = cx_strchr(input, '\n');
147 cxstring nl = cx_strchr(str, '\n'); 98 if (nl.length > 0) {
148 if(nl.length > 0) {
149 // we add as much data to the rescue buffer as we need 99 // we add as much data to the rescue buffer as we need
150 // to complete the line 100 // to complete the line
151 size_t len_until_nl = (size_t)(nl.ptr - buf) + 1; 101 size_t len_until_nl = (size_t)(nl.ptr - input.ptr) + 1;
152 102
153 if (cx_properties_rescuen_input(prop, len_until_nl)) { 103 if (cxBufferAppend(input.ptr, 1,
104 len_until_nl, &prop->buffer) < len_until_nl) {
154 return CX_PROPERTIES_BUFFER_ALLOC_FAILED; 105 return CX_PROPERTIES_BUFFER_ALLOC_FAILED;
155 } 106 }
156 107
157 // the tmp buffer contains exactly one line now 108 // advance the position in the input buffer
158 // we use a trick here: we swap the buffers and recurse 109 prop->input.pos += len_until_nl;
159 const char *orig_text = prop->text; 110
160 size_t orig_size = prop->text_size; 111 // we now want to read from the rescue buffer
161 prop->text = prop->buf; 112 current_buffer = &prop->buffer;
162 prop->text_size = prop->buf_size;
163 prop->text_pos = 0;
164 prop->buf_size = 0;
165
166 CxPropertiesStatus result;
167 result = cxPropertiesNext(prop, key, value);
168
169 // restore original buffer
170 prop->text = orig_text;
171 prop->text_size = orig_size;
172
173 // set the position to after the newline
174 prop->text_pos = len_until_nl;
175
176 // check the result
177 if (result == CX_PROPERTIES_NO_ERROR) {
178 // reset the rescue buffer and return with the result
179 prop->buf_size = 0;
180 return result;
181 } else if (result == CX_PROPERTIES_NO_DATA) {
182 // rescue buffer contained only blanks or comments
183 // reset the rescue buffer and retry with text buffer
184 prop->buf_size = 0;
185 return cxPropertiesNext(prop, key, value);
186 } else {
187 // CX_PROPERTIES_INCOMPLETE_DATA is not possible
188 // so it must have been another error
189 // do not reset the rescue buffer and return the error
190 return result;
191 }
192 } else { 113 } else {
193 // still not enough data 114 // still not enough data, copy input buffer to internal buffer
194 if (cx_properties_rescue_input(prop)) { 115 if (cxBufferAppend(input.ptr, 1,
116 input.length, &prop->buffer) < input.length) {
195 return CX_PROPERTIES_BUFFER_ALLOC_FAILED; 117 return CX_PROPERTIES_BUFFER_ALLOC_FAILED;
196 } 118 }
119 // reset the input buffer (make way for a re-fill)
120 cxBufferReset(&prop->input);
197 return CX_PROPERTIES_INCOMPLETE_DATA; 121 return CX_PROPERTIES_INCOMPLETE_DATA;
198 } 122 }
199 } 123 }
200 124
201 char comment1 = prop->config.comment1; 125 char comment1 = prop->config.comment1;
202 char comment2 = prop->config.comment2; 126 char comment2 = prop->config.comment2;
203 char comment3 = prop->config.comment3; 127 char comment3 = prop->config.comment3;
204 char delimiter = prop->config.delimiter; 128 char delimiter = prop->config.delimiter;
205 129
206 // get one line and parse it 130 // get one line and parse it
207 while (prop->text_pos < prop->text_size) { 131 while (!cxBufferEof(current_buffer)) {
208 const char *buf = prop->text + prop->text_pos; 132 const char *buf = current_buffer->space + current_buffer->pos;
209 size_t len = prop->text_size - prop->text_pos; 133 size_t len = current_buffer->size - current_buffer->pos;
210 134
211 /* 135 /*
212 * First we check if we have at least one line. We also get indices of 136 * First we check if we have at least one line. We also get indices of
213 * delimiter and comment chars 137 * delimiter and comment chars
214 */ 138 */
233 break; 157 break;
234 } 158 }
235 } 159 }
236 160
237 if (c != '\n') { 161 if (c != '\n') {
238 // we don't have enough data for a line 162 // we don't have enough data for a line, use the rescue buffer
239 if (cx_properties_rescue_input(prop)) { 163 assert(current_buffer != &prop->buffer);
164 // make sure that the rescue buffer does not already contain something
165 assert(cxBufferEof(&prop->buffer));
166 if (prop->buffer.space == NULL) {
167 // initialize a rescue buffer, if the user did not provide one
168 cxBufferInit(&prop->buffer, NULL, 256, NULL, CX_BUFFER_AUTO_EXTEND);
169 } else {
170 // from a previous rescue there might be already read data
171 // reset the buffer to avoid unnecessary buffer extension
172 cxBufferReset(&prop->buffer);
173 }
174 if (cxBufferAppend(buf, 1, len, &prop->buffer) < len) {
240 return CX_PROPERTIES_BUFFER_ALLOC_FAILED; 175 return CX_PROPERTIES_BUFFER_ALLOC_FAILED;
241 } 176 }
177 // reset the input buffer (make way for a re-fill)
178 cxBufferReset(&prop->input);
242 return CX_PROPERTIES_INCOMPLETE_DATA; 179 return CX_PROPERTIES_INCOMPLETE_DATA;
243 } 180 }
244 181
245 cxstring line = has_comment ? 182 cxstring line = has_comment ?
246 cx_strn(buf, comment_index) : 183 cx_strn(buf, comment_index) :
254 if (line.ptr[0] == delimiter) { 191 if (line.ptr[0] == delimiter) {
255 return CX_PROPERTIES_INVALID_EMPTY_KEY; 192 return CX_PROPERTIES_INVALID_EMPTY_KEY;
256 } else { 193 } else {
257 return CX_PROPERTIES_INVALID_MISSING_DELIMITER; 194 return CX_PROPERTIES_INVALID_MISSING_DELIMITER;
258 } 195 }
196 } else {
197 // skip blank line
198 // if it was the rescue buffer, return to the original buffer
199 if (current_buffer == &prop->buffer) {
200 // assert that the rescue buffer really does not contain more data
201 assert(current_buffer->pos + i + 1 == current_buffer->size);
202 // reset the rescue buffer, but don't destroy it!
203 cxBufferReset(&prop->buffer);
204 // continue with the input buffer
205 current_buffer = &prop->input;
206 } else {
207 // if it was the input buffer already, just advance the position
208 current_buffer->pos += i + 1;
209 }
210 continue;
259 } 211 }
260 } else { 212 } else {
261 cxstring k = cx_strn(buf, delimiter_index); 213 cxstring k = cx_strn(buf, delimiter_index);
262 cxstring val = cx_strn( 214 cxstring val = cx_strn(
263 buf + delimiter_index + 1, 215 buf + delimiter_index + 1,
265 k = cx_strtrim(k); 217 k = cx_strtrim(k);
266 val = cx_strtrim(val); 218 val = cx_strtrim(val);
267 if (k.length > 0) { 219 if (k.length > 0) {
268 *key = k; 220 *key = k;
269 *value = val; 221 *value = val;
270 prop->text_pos += i + 1; 222 current_buffer->pos += i + 1;
271 assert(prop->text_pos <= prop->text_size); 223 assert(current_buffer->pos <= current_buffer->size);
272 return CX_PROPERTIES_NO_ERROR; 224 return CX_PROPERTIES_NO_ERROR;
273 } else { 225 } else {
274 return CX_PROPERTIES_INVALID_EMPTY_KEY; 226 return CX_PROPERTIES_INVALID_EMPTY_KEY;
275 } 227 }
276 } 228 }
277 229 // unreachable - either we returned or skipped a blank line
278 prop->text_pos += i + 1; 230 assert(false);
279 } 231 }
280 232
281 // when we come to this point, all data must have been read 233 // when we come to this point, all data must have been read
282 assert(prop->text_pos == prop->text_size); 234 assert(cxBufferEof(&prop->buffer));
235 assert(cxBufferEof(&prop->input));
236
283 return CX_PROPERTIES_NO_DATA; 237 return CX_PROPERTIES_NO_DATA;
284 } 238 }
285 239
286 static int cx_properties_sink_map( 240 static int cx_properties_sink_map(
287 cx_attr_unused CxProperties *prop, 241 cx_attr_unused CxProperties *prop,
308 static int cx_properties_read_string( 262 static int cx_properties_read_string(
309 CxProperties *prop, 263 CxProperties *prop,
310 CxPropertiesSource *src, 264 CxPropertiesSource *src,
311 cxstring *target 265 cxstring *target
312 ) { 266 ) {
313 if (prop->text == src->src) { 267 if (prop->input.space == src->src) {
314 // when the input buffer already contains the string 268 // when the input buffer already contains the string
315 // we have nothing more to provide 269 // we have nothing more to provide
316 target->length = 0; 270 target->length = 0;
317 } else { 271 } else {
318 target->ptr = src->src; 272 target->ptr = src->src;
349 303
350 CxPropertiesSource cxPropertiesStringSource(cxstring str) { 304 CxPropertiesSource cxPropertiesStringSource(cxstring str) {
351 CxPropertiesSource src; 305 CxPropertiesSource src;
352 src.src = (void*) str.ptr; 306 src.src = (void*) str.ptr;
353 src.data_size = str.length; 307 src.data_size = str.length;
308 src.data_ptr = NULL;
354 src.read_func = cx_properties_read_string; 309 src.read_func = cx_properties_read_string;
355 src.read_init_func = NULL; 310 src.read_init_func = NULL;
356 src.read_clean_func = NULL; 311 src.read_clean_func = NULL;
357 return src; 312 return src;
358 } 313 }
359 314
360 CxPropertiesSource cxPropertiesCstrnSource(const char *str, size_t len) { 315 CxPropertiesSource cxPropertiesCstrnSource(const char *str, size_t len) {
361 CxPropertiesSource src; 316 CxPropertiesSource src;
362 src.src = (void*) str; 317 src.src = (void*) str;
363 src.data_size = len; 318 src.data_size = len;
319 src.data_ptr = NULL;
364 src.read_func = cx_properties_read_string; 320 src.read_func = cx_properties_read_string;
365 src.read_init_func = NULL; 321 src.read_init_func = NULL;
366 src.read_clean_func = NULL; 322 src.read_clean_func = NULL;
367 return src; 323 return src;
368 } 324 }
369 325
370 CxPropertiesSource cxPropertiesCstrSource(const char *str) { 326 CxPropertiesSource cxPropertiesCstrSource(const char *str) {
371 CxPropertiesSource src; 327 CxPropertiesSource src;
372 src.src = (void*) str; 328 src.src = (void*) str;
373 src.data_size = strlen(str); 329 src.data_size = strlen(str);
330 src.data_ptr = NULL;
374 src.read_func = cx_properties_read_string; 331 src.read_func = cx_properties_read_string;
375 src.read_init_func = NULL; 332 src.read_init_func = NULL;
376 src.read_clean_func = NULL; 333 src.read_clean_func = NULL;
377 return src; 334 return src;
378 } 335 }
379 336
380 CxPropertiesSource cxPropertiesFileSource(FILE *file, size_t chunk_size) { 337 CxPropertiesSource cxPropertiesFileSource(FILE *file, size_t chunk_size) {
381 CxPropertiesSource src; 338 CxPropertiesSource src;
382 src.src = file; 339 src.src = file;
383 src.data_size = chunk_size; 340 src.data_size = chunk_size;
341 src.data_ptr = NULL;
384 src.read_func = cx_properties_read_file; 342 src.read_func = cx_properties_read_file;
385 src.read_init_func = cx_properties_read_init_file; 343 src.read_init_func = cx_properties_read_init_file;
386 src.read_clean_func = cx_properties_read_clean_file; 344 src.read_clean_func = cx_properties_read_clean_file;
387 return src; 345 return src;
388 } 346 }
418 status = found ? CX_PROPERTIES_NO_ERROR : CX_PROPERTIES_NO_DATA; 376 status = found ? CX_PROPERTIES_NO_ERROR : CX_PROPERTIES_NO_DATA;
419 break; 377 break;
420 } 378 }
421 379
422 // set the input buffer and read the k/v-pairs 380 // set the input buffer and read the k/v-pairs
423 prop->text = input.ptr; 381 cxPropertiesFill(prop, input);
424 prop->text_size = input.length;
425 prop->text_pos = 0;
426 382
427 CxPropertiesStatus kv_status; 383 CxPropertiesStatus kv_status;
428 do { 384 do {
429 cxstring key, value; 385 cxstring key, value;
430 kv_status = cxPropertiesNext(prop, &key, &value); 386 kv_status = cxPropertiesNext(prop, &key, &value);

mercurial