#include "cx/properties.h"
#include <assert.h>
const CxPropertiesConfig cx_properties_config_default = {
'=',
'#',
'\0',
'\0'
};
void cxPropertiesInit(
CxProperties *prop,
CxPropertiesConfig config
) {
memset(prop,
0,
sizeof(CxProperties));
prop->config = config;
}
void cxPropertiesDestroy(CxProperties *prop) {
cxBufferDestroy(&prop->input);
cxBufferDestroy(&prop->buffer);
}
int cxPropertiesFilln(
CxProperties *prop,
const char *buf,
size_t len
) {
if (cxBufferEof(&prop->input)) {
cxBufferDestroy(&prop->input);
cxBufferInit(&prop->input, (
void*) buf, len,
NULL,
CX_BUFFER_COPY_ON_WRITE |
CX_BUFFER_AUTO_EXTEND);
prop->input.size = len;
}
else {
if (cxBufferAppend(buf,
1, len, &prop->input) < len)
return -
1;
}
return 0;
}
void cxPropertiesUseStack(
CxProperties *prop,
char *buf,
size_t capacity
) {
cxBufferInit(&prop->buffer, buf, capacity,
NULL,
CX_BUFFER_COPY_ON_EXTEND);
}
CxPropertiesStatus cxPropertiesNext(
CxProperties *prop,
cxstring *key,
cxstring *value
) {
if (prop->input.space ==
NULL) {
return CX_PROPERTIES_NULL_INPUT;
}
CxBuffer *current_buffer = &prop->input;
if (!cxBufferEof(&prop->buffer)) {
cxstring input = cx_strn(prop->input.space + prop->input.pos,
prop->input.size - prop->input.pos);
cxstring nl = cx_strchr(input,
'\n');
if (nl.length >
0) {
size_t len_until_nl = (
size_t)(nl.ptr - input.ptr) +
1;
if (cxBufferAppend(input.ptr,
1,
len_until_nl, &prop->buffer) < len_until_nl) {
return CX_PROPERTIES_BUFFER_ALLOC_FAILED;
}
prop->input.pos += len_until_nl;
current_buffer = &prop->buffer;
}
else {
if (cxBufferAppend(input.ptr,
1,
input.length, &prop->buffer) < input.length) {
return CX_PROPERTIES_BUFFER_ALLOC_FAILED;
}
cxBufferReset(&prop->input);
return CX_PROPERTIES_INCOMPLETE_DATA;
}
}
char comment1 = prop->config.comment1;
char comment2 = prop->config.comment2;
char comment3 = prop->config.comment3;
char delimiter = prop->config.delimiter;
while (!cxBufferEof(current_buffer)) {
const char *buf = current_buffer->space + current_buffer->pos;
size_t len = current_buffer->size - current_buffer->pos;
size_t delimiter_index =
0;
size_t comment_index =
0;
bool has_comment = false;
size_t i =
0;
char c =
0;
for (; i < len; i++) {
c = buf[i];
if (c == comment1 || c == comment2 || c == comment3) {
if (comment_index ==
0) {
comment_index = i;
has_comment = true;
}
}
else if (c == delimiter) {
if (delimiter_index ==
0 && !has_comment) {
delimiter_index = i;
}
}
else if (c ==
'\n') {
break;
}
}
if (c !=
'\n') {
assert(current_buffer != &prop->buffer);
assert(cxBufferEof(&prop->buffer));
if (prop->buffer.space ==
NULL) {
cxBufferInit(&prop->buffer,
NULL,
256,
NULL,
CX_BUFFER_AUTO_EXTEND);
}
else {
cxBufferReset(&prop->buffer);
}
if (cxBufferAppend(buf,
1, len, &prop->buffer) < len) {
return CX_PROPERTIES_BUFFER_ALLOC_FAILED;
}
cxBufferReset(&prop->input);
return CX_PROPERTIES_INCOMPLETE_DATA;
}
cxstring line = has_comment ?
cx_strn(buf, comment_index) :
cx_strn(buf, i);
if (delimiter_index ==
0) {
line = cx_strtrim(line);
if (line.length >
0) {
if (line.ptr[
0] == delimiter) {
return CX_PROPERTIES_INVALID_EMPTY_KEY;
}
else {
return CX_PROPERTIES_INVALID_MISSING_DELIMITER;
}
}
else {
if (current_buffer == &prop->buffer) {
assert(current_buffer->pos + i +
1 == current_buffer->size);
cxBufferReset(&prop->buffer);
current_buffer = &prop->input;
}
else {
current_buffer->pos += i +
1;
}
continue;
}
}
else {
cxstring k = cx_strn(buf, delimiter_index);
cxstring val = cx_strn(
buf + delimiter_index +
1,
line.length - delimiter_index -
1);
k = cx_strtrim(k);
val = cx_strtrim(val);
if (k.length >
0) {
*key = k;
*value = val;
current_buffer->pos += i +
1;
assert(current_buffer->pos <= current_buffer->size);
return CX_PROPERTIES_NO_ERROR;
}
else {
return CX_PROPERTIES_INVALID_EMPTY_KEY;
}
}
assert(false);
}
assert(cxBufferEof(&prop->buffer));
assert(cxBufferEof(&prop->input));
return CX_PROPERTIES_NO_DATA;
}
static int cx_properties_sink_map(
cx_attr_unused CxProperties *prop,
CxPropertiesSink *sink,
cxstring key,
cxstring value
) {
CxMap *map = sink->sink;
CxAllocator *alloc = sink->data;
cxmutstr v = cx_strdup_a(alloc, value);
int r = cx_map_put_cxstr(map, key, v.ptr);
if (r !=
0) cx_strfree_a(alloc, &v);
return r;
}
CxPropertiesSink cxPropertiesMapSink(CxMap *map) {
CxPropertiesSink sink;
sink.sink = map;
sink.data = cxDefaultAllocator;
sink.sink_func = cx_properties_sink_map;
return sink;
}
static int cx_properties_read_string(
CxProperties *prop,
CxPropertiesSource *src,
cxstring *target
) {
if (prop->input.space == src->src) {
target->length =
0;
}
else {
target->ptr = src->src;
target->length = src->data_size;
}
return 0;
}
static int cx_properties_read_file(
cx_attr_unused CxProperties *prop,
CxPropertiesSource *src,
cxstring *target
) {
target->ptr = src->data_ptr;
target->length = fread(src->data_ptr,
1, src->data_size, src->src);
return ferror(src->src);
}
static int cx_properties_read_init_file(
cx_attr_unused CxProperties *prop,
CxPropertiesSource *src
) {
src->data_ptr = malloc(src->data_size);
if (src->data_ptr ==
NULL)
return 1;
return 0;
}
static void cx_properties_read_clean_file(
cx_attr_unused CxProperties *prop,
CxPropertiesSource *src
) {
free(src->data_ptr);
}
CxPropertiesSource cxPropertiesStringSource(cxstring str) {
CxPropertiesSource src;
src.src = (
void*) str.ptr;
src.data_size = str.length;
src.data_ptr =
NULL;
src.read_func = cx_properties_read_string;
src.read_init_func =
NULL;
src.read_clean_func =
NULL;
return src;
}
CxPropertiesSource cxPropertiesCstrnSource(
const char *str,
size_t len) {
CxPropertiesSource src;
src.src = (
void*) str;
src.data_size = len;
src.data_ptr =
NULL;
src.read_func = cx_properties_read_string;
src.read_init_func =
NULL;
src.read_clean_func =
NULL;
return src;
}
CxPropertiesSource cxPropertiesCstrSource(
const char *str) {
CxPropertiesSource src;
src.src = (
void*) str;
src.data_size = strlen(str);
src.data_ptr =
NULL;
src.read_func = cx_properties_read_string;
src.read_init_func =
NULL;
src.read_clean_func =
NULL;
return src;
}
CxPropertiesSource cxPropertiesFileSource(
FILE *file,
size_t chunk_size) {
CxPropertiesSource src;
src.src = file;
src.data_size = chunk_size;
src.data_ptr =
NULL;
src.read_func = cx_properties_read_file;
src.read_init_func = cx_properties_read_init_file;
src.read_clean_func = cx_properties_read_clean_file;
return src;
}
CxPropertiesStatus cxPropertiesLoad(
CxProperties *prop,
CxPropertiesSink sink,
CxPropertiesSource source
) {
assert(source.read_func !=
NULL);
assert(sink.sink_func !=
NULL);
if (source.read_init_func !=
NULL) {
if (source.read_init_func(prop, &source)) {
return CX_PROPERTIES_READ_INIT_FAILED;
}
}
CxPropertiesStatus status;
bool found = false;
while (true) {
cxstring input;
if (source.read_func(prop, &source, &input)) {
status =
CX_PROPERTIES_READ_FAILED;
break;
}
if (input.length ==
0) {
status = found ?
CX_PROPERTIES_NO_ERROR :
CX_PROPERTIES_NO_DATA;
break;
}
cxPropertiesFill(prop, input);
CxPropertiesStatus kv_status;
do {
cxstring key, value;
kv_status = cxPropertiesNext(prop, &key, &value);
if (kv_status ==
CX_PROPERTIES_NO_ERROR) {
found = true;
if (sink.sink_func(prop, &sink, key, value)) {
kv_status =
CX_PROPERTIES_SINK_FAILED;
}
}
}
while (kv_status ==
CX_PROPERTIES_NO_ERROR);
if (kv_status >
CX_PROPERTIES_OK) {
status = kv_status;
break;
}
}
if (source.read_clean_func !=
NULL) {
source.read_clean_func(prop, &source);
}
return status;
}