forked from dnkl/yambar
Modular status panel for X11 and Wayland, inspired by https://github.com/jaagr/polybar
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
899 lines
23 KiB
899 lines
23 KiB
#include "yml.h" |
|
|
|
#include <stdlib.h> |
|
#include <stdio.h> |
|
#include <stdint.h> |
|
#include <stdbool.h> |
|
#include <assert.h> |
|
|
|
#include <yaml.h> |
|
#include <tllist.h> |
|
|
|
enum yml_error { |
|
YML_ERR_NONE, |
|
YML_ERR_DUPLICATE_KEY, |
|
YML_ERR_INVALID_ANCHOR, |
|
YML_ERR_UNKNOWN, |
|
}; |
|
|
|
enum node_type { |
|
ROOT, |
|
SCALAR, |
|
DICT, |
|
LIST, |
|
}; |
|
|
|
struct yml_node; |
|
|
|
struct dict_pair { |
|
struct yml_node *key; |
|
struct yml_node *value; |
|
}; |
|
|
|
struct anchor_map { |
|
char *anchor; |
|
const struct yml_node *node; |
|
}; |
|
|
|
struct yml_node { |
|
enum node_type type; |
|
union { |
|
struct { |
|
struct yml_node *root; |
|
struct anchor_map anchors[100]; /* TODO: dynamic resize */ |
|
size_t anchor_count; |
|
} root; |
|
struct { |
|
char *value; |
|
} scalar; |
|
struct { |
|
tll(struct dict_pair) pairs; |
|
bool next_is_value; |
|
} dict; |
|
struct { |
|
tll(struct yml_node *) values; |
|
} list; |
|
}; |
|
|
|
size_t line; |
|
size_t column; |
|
|
|
struct yml_node *parent; |
|
}; |
|
|
|
static struct yml_node * |
|
clone_node(struct yml_node *parent, const struct yml_node *node) |
|
{ |
|
struct yml_node *clone = calloc(1, sizeof(*clone)); |
|
clone->type = node->type; |
|
clone->line = node->line; |
|
clone->column = node->column; |
|
clone->parent = parent; |
|
|
|
switch (node->type) { |
|
case SCALAR: |
|
clone->scalar.value = strdup(node->scalar.value); |
|
break; |
|
|
|
case DICT: |
|
tll_foreach(node->dict.pairs, it) { |
|
struct dict_pair p = { |
|
.key = clone_node(clone, it->item.key), |
|
.value = clone_node(clone, it->item.value), |
|
}; |
|
tll_push_back(clone->dict.pairs, p); |
|
} |
|
break; |
|
|
|
case LIST: |
|
tll_foreach(node->list.values, it) |
|
tll_push_back(clone->list.values, clone_node(clone, it->item)); |
|
break; |
|
|
|
case ROOT: |
|
assert(false); |
|
break; |
|
} |
|
|
|
return clone; |
|
} |
|
|
|
static bool |
|
node_equal(const struct yml_node *a, const struct yml_node *b) |
|
{ |
|
if (a->type != b->type) |
|
return false; |
|
|
|
if (a->type != SCALAR) { |
|
/* TODO... */ |
|
return false; |
|
} |
|
|
|
return strcmp(a->scalar.value, b->scalar.value) == 0; |
|
} |
|
|
|
static bool |
|
dict_has_key(const struct yml_node *node, const struct yml_node *key) |
|
{ |
|
assert(node->type == DICT); |
|
|
|
tll_foreach(node->dict.pairs, pair) { |
|
if (node_equal(pair->item.key, key)) |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
static enum yml_error |
|
add_node(struct yml_node *parent, struct yml_node *new_node, yaml_mark_t loc) |
|
{ |
|
new_node->line = loc.line + 1; /* yaml uses 0-based line numbers */ |
|
new_node->column = loc.column; |
|
|
|
switch (parent->type) { |
|
case ROOT: |
|
assert(parent->root.root == NULL); |
|
parent->root.root = new_node; |
|
new_node->parent = parent; |
|
break; |
|
|
|
case DICT: |
|
if (!parent->dict.next_is_value) { |
|
if (dict_has_key(parent, new_node)) |
|
return YML_ERR_DUPLICATE_KEY; |
|
|
|
tll_push_back(parent->dict.pairs, (struct dict_pair){.key = new_node}); |
|
parent->dict.next_is_value = true; |
|
} else { |
|
tll_back(parent->dict.pairs).value = new_node; |
|
parent->dict.next_is_value = false; |
|
} |
|
new_node->parent = parent; |
|
break; |
|
|
|
case LIST: |
|
tll_push_back(parent->list.values, new_node); |
|
new_node->parent = parent; |
|
break; |
|
|
|
case SCALAR: |
|
assert(false); |
|
return YML_ERR_UNKNOWN; |
|
} |
|
|
|
return YML_ERR_NONE; |
|
} |
|
|
|
static void |
|
add_anchor(struct yml_node *root, const char *anchor, |
|
const struct yml_node *node) |
|
{ |
|
assert(root->type == ROOT); |
|
|
|
struct anchor_map *map = &root->root.anchors[root->root.anchor_count]; |
|
map->anchor = strdup(anchor); |
|
map->node = node; |
|
root->root.anchor_count++; |
|
} |
|
|
|
static bool |
|
post_process(struct yml_node *node, char **error) |
|
{ |
|
switch (node->type) { |
|
case ROOT: |
|
if (node->root.root != NULL) |
|
if (!post_process(node->root.root, error)) |
|
return false; |
|
break; |
|
|
|
case SCALAR: |
|
//assert(strcmp(node->scalar.value, "<<") != 0); |
|
break; |
|
|
|
case LIST: |
|
tll_foreach(node->list.values, it) |
|
if (!post_process(it->item, error)) |
|
return false; |
|
break; |
|
|
|
case DICT: |
|
tll_foreach(node->dict.pairs, it) { |
|
if (!post_process(it->item.key, error) || |
|
!post_process(it->item.value, error)) |
|
{ |
|
return false; |
|
} |
|
} |
|
|
|
tll_foreach(node->dict.pairs, it) { |
|
if (it->item.key->type != SCALAR) |
|
continue; |
|
|
|
if (strcmp(it->item.key->scalar.value, "<<") != 0) |
|
continue; |
|
|
|
if (it->item.value->type == LIST) { |
|
/* |
|
* Merge value is a list (of dictionaries) |
|
* e.g. <<: [*foo, *bar] |
|
*/ |
|
tll_foreach(it->item.value->list.values, v_it) { |
|
if (v_it->item->type != DICT) { |
|
int cnt = snprintf( |
|
NULL, 0, "%zu:%zu: cannot merge non-dictionary anchor", |
|
v_it->item->line, v_it->item->column); |
|
*error = malloc(cnt + 1); |
|
snprintf( |
|
*error, cnt + 1, "%zu:%zu: cannot merge non-dictionary anchor", |
|
v_it->item->line, v_it->item->column); |
|
return false; |
|
} |
|
|
|
tll_foreach(v_it->item->dict.pairs, vv_it) { |
|
struct dict_pair p = { |
|
.key = vv_it->item.key, |
|
.value = vv_it->item.value, |
|
}; |
|
/* TODO: handle this. Is it an error? Or |
|
* should we replace the existing key/value |
|
* pair */ |
|
assert(!dict_has_key(node, vv_it->item.key)); |
|
|
|
tll_push_back(node->dict.pairs, p); |
|
} |
|
|
|
/* Destroy list, but don't free (since its nodes |
|
* have been moved to this node), *before* |
|
* destroying the key/value nodes. This ensures |
|
* the dict nodes aren't free:d in the |
|
* yml_destroy() below). */ |
|
tll_free(v_it->item->dict.pairs); |
|
} |
|
} else { |
|
/* |
|
* Merge value is a dictionary only |
|
* e.g. <<: *foo |
|
*/ |
|
if (it->item.value->type != DICT) { |
|
int cnt = snprintf( |
|
NULL, 0, "%zu:%zu: cannot merge non-dictionary anchor", |
|
it->item.value->line, it->item.value->column); |
|
*error = malloc(cnt + 1); |
|
snprintf( |
|
*error, cnt + 1, "%zu:%zu: cannot merge non-dictionary anchor", |
|
it->item.value->line, it->item.value->column); |
|
return false; |
|
} |
|
|
|
tll_foreach(it->item.value->dict.pairs, v_it) { |
|
struct dict_pair p = { |
|
.key = v_it->item.key, |
|
.value = v_it->item.value, |
|
}; |
|
|
|
/* TODO: handle this. Is it an error? Or should we |
|
* replace the existing key/value pair */ |
|
assert(!dict_has_key(node, v_it->item.key)); |
|
|
|
tll_push_back(node->dict.pairs, p); |
|
} |
|
|
|
/* Destroy list here, *without* freeing nodes (since |
|
* nodes have been moved to this node), *before* |
|
* destroying the key/value nodes. This ensures the |
|
* dict nodes aren't free:d in the yml_destroy() |
|
* below */ |
|
tll_free(it->item.value->dict.pairs); |
|
} |
|
|
|
yml_destroy(it->item.key); |
|
yml_destroy(it->item.value); |
|
|
|
tll_remove(node->dict.pairs, it); |
|
} |
|
break; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
static const char * |
|
format_error(enum yml_error err, |
|
const struct yml_node *parent, |
|
const struct yml_node *node, |
|
const char *anchor) |
|
{ |
|
static char err_str[512]; |
|
|
|
switch (err) { |
|
case YML_ERR_NONE: |
|
assert(false); |
|
break; |
|
|
|
case YML_ERR_DUPLICATE_KEY: { |
|
/* Find parent's key (i.e its name) */ |
|
if (parent->parent != NULL && |
|
parent->parent->type == DICT && |
|
node->type == SCALAR) |
|
{ |
|
tll_foreach(parent->parent->dict.pairs, pair) { |
|
if (pair->item.value != parent) |
|
continue; |
|
|
|
if (pair->item.key->type != SCALAR) |
|
break; |
|
|
|
assert(pair->item.key->type == SCALAR); |
|
assert(node->type == SCALAR); |
|
|
|
snprintf(err_str, sizeof(err_str), |
|
"%s: duplicate key: '%s'", |
|
pair->item.key->scalar.value, |
|
node->scalar.value); |
|
return err_str; |
|
} |
|
} |
|
|
|
if (node->type == SCALAR) { |
|
snprintf(err_str, sizeof(err_str), |
|
"duplicate key: %s", node->scalar.value); |
|
} else |
|
snprintf(err_str, sizeof(err_str), "duplicate key"); |
|
break; |
|
} |
|
|
|
case YML_ERR_INVALID_ANCHOR: |
|
if (parent->parent != NULL && parent->parent->type == DICT) { |
|
tll_foreach(parent->parent->dict.pairs, pair) { |
|
if (pair->item.value != parent) |
|
continue; |
|
if (pair->item.key->type != SCALAR) |
|
break; |
|
|
|
snprintf(err_str, sizeof(err_str), |
|
"%s: invalid anchor: %s", |
|
pair->item.key->scalar.value, |
|
anchor != NULL ? anchor : "<unknown>"); |
|
return err_str; |
|
} |
|
} |
|
|
|
snprintf(err_str, sizeof(err_str), "invalid anchor: %s", |
|
anchor != NULL ? anchor : "<unknown>"); |
|
break; |
|
|
|
case YML_ERR_UNKNOWN: |
|
snprintf(err_str, sizeof(err_str), "unknown error"); |
|
break; |
|
} |
|
|
|
return err_str; |
|
} |
|
|
|
struct yml_node * |
|
yml_load(FILE *yml, char **error) |
|
{ |
|
yaml_parser_t yaml; |
|
yaml_parser_initialize(&yaml); |
|
|
|
yaml_parser_set_input_file(&yaml, yml); |
|
|
|
bool done = false; |
|
int indent = 0; |
|
|
|
struct yml_node *root = malloc(sizeof(*root)); |
|
root->type = ROOT; |
|
root->root.root = NULL; |
|
root->root.anchor_count = 0; |
|
|
|
struct yml_node *n = root; |
|
|
|
const char *error_str = NULL; |
|
|
|
while (!done) { |
|
yaml_event_t event; |
|
if (!yaml_parser_parse(&yaml, &event)) { |
|
if (error != NULL) { |
|
int cnt = snprintf( |
|
NULL, 0, "%zu:%zu: %s %s", |
|
yaml.problem_mark.line + 1, |
|
yaml.problem_mark.column, |
|
yaml.problem, |
|
yaml.context != NULL ? yaml.context : ""); |
|
|
|
*error = malloc(cnt + 1); |
|
snprintf(*error, cnt + 1, "%zu:%zu: %s %s", |
|
yaml.problem_mark.line + 1, |
|
yaml.problem_mark.column, |
|
yaml.problem, |
|
yaml.context != NULL ? yaml.context : ""); |
|
} |
|
|
|
goto err_no_error_formatting; |
|
} |
|
|
|
switch (event.type) { |
|
case YAML_NO_EVENT: |
|
break; |
|
|
|
case YAML_STREAM_START_EVENT: |
|
indent += 2; |
|
break; |
|
|
|
case YAML_STREAM_END_EVENT: |
|
indent -= 2; |
|
done = true; |
|
break; |
|
|
|
case YAML_DOCUMENT_START_EVENT: |
|
indent += 2; |
|
break; |
|
|
|
case YAML_DOCUMENT_END_EVENT: |
|
indent -= 2; |
|
break; |
|
|
|
case YAML_ALIAS_EVENT: { |
|
bool got_match = false; |
|
for (size_t i = 0; i < root->root.anchor_count; i++) { |
|
const struct anchor_map *map = &root->root.anchors[i]; |
|
|
|
if (strcmp(map->anchor, (const char *)event.data.alias.anchor) != 0) |
|
continue; |
|
|
|
struct yml_node *clone = clone_node(NULL, map->node); |
|
assert(clone != NULL); |
|
|
|
enum yml_error err = add_node(n, clone, event.start_mark); |
|
if (err != YML_ERR_NONE) { |
|
error_str = format_error(err, n, clone, NULL); |
|
yml_destroy(clone); |
|
yaml_event_delete(&event); |
|
goto err; |
|
} |
|
|
|
got_match = true; |
|
break; |
|
} |
|
|
|
if (!got_match) { |
|
error_str = format_error( |
|
YML_ERR_INVALID_ANCHOR, n, NULL, |
|
(const char *)event.data.alias.anchor); |
|
yaml_event_delete(&event); |
|
goto err; |
|
} |
|
break; |
|
} |
|
|
|
case YAML_SCALAR_EVENT: { |
|
struct yml_node *new_scalar = calloc(1, sizeof(*new_scalar)); |
|
new_scalar->type = SCALAR; |
|
new_scalar->scalar.value = strndup( |
|
(const char*)event.data.scalar.value, event.data.scalar.length); |
|
|
|
enum yml_error err = add_node(n, new_scalar, event.start_mark); |
|
if (err != YML_ERR_NONE) { |
|
error_str = format_error(err, n, new_scalar, NULL); |
|
yml_destroy(new_scalar); |
|
yaml_event_delete(&event); |
|
goto err; |
|
} |
|
|
|
if (event.data.scalar.anchor != NULL) { |
|
const char *anchor = (const char *)event.data.scalar.anchor; |
|
add_anchor(root, anchor, new_scalar); |
|
} |
|
|
|
break; |
|
} |
|
|
|
case YAML_SEQUENCE_START_EVENT: { |
|
indent += 2; |
|
struct yml_node *new_list = calloc(1, sizeof(*new_list)); |
|
new_list->type = LIST; |
|
|
|
enum yml_error err = add_node(n, new_list, event.start_mark); |
|
if (err != YML_ERR_NONE) { |
|
error_str = format_error(err, n, new_list, NULL); |
|
yml_destroy(new_list); |
|
yaml_event_delete(&event); |
|
goto err; |
|
} |
|
|
|
n = new_list; |
|
|
|
if (event.data.sequence_start.anchor != NULL) { |
|
const char *anchor = (const char *)event.data.sequence_start.anchor; |
|
add_anchor(root, anchor, new_list); |
|
} |
|
break; |
|
} |
|
|
|
case YAML_SEQUENCE_END_EVENT: |
|
indent -= 2; |
|
assert(n->parent != NULL); |
|
n = n->parent; |
|
break; |
|
|
|
case YAML_MAPPING_START_EVENT: { |
|
indent += 2; |
|
|
|
struct yml_node *new_dict = calloc(1, sizeof(*new_dict)); |
|
new_dict->type = DICT; |
|
|
|
enum yml_error err = add_node(n, new_dict, event.start_mark); |
|
if (err != YML_ERR_NONE) { |
|
error_str = format_error(err, n, new_dict, NULL); |
|
yml_destroy(new_dict); |
|
yaml_event_delete(&event); |
|
goto err; |
|
} |
|
|
|
n = new_dict; |
|
|
|
if (event.data.mapping_start.anchor != NULL) { |
|
const char *anchor = (const char *)event.data.mapping_start.anchor; |
|
add_anchor(root, anchor, new_dict); |
|
} |
|
break; |
|
} |
|
|
|
case YAML_MAPPING_END_EVENT: |
|
assert(!n->dict.next_is_value); |
|
indent -= 2; |
|
assert(n->parent != NULL); |
|
n = n->parent; |
|
break; |
|
} |
|
|
|
yaml_event_delete(&event); |
|
} |
|
|
|
yaml_parser_delete(&yaml); |
|
|
|
if (!post_process(root, error)) { |
|
yml_destroy(root); |
|
return NULL; |
|
} |
|
return root; |
|
|
|
err: |
|
if (error_str != NULL) { |
|
int cnt = snprintf( |
|
NULL, 0, "%zu:%zu: %s", |
|
yaml.mark.line + 1, |
|
yaml.mark.column, |
|
error_str); |
|
*error = malloc(cnt + 1); |
|
snprintf( |
|
*error, cnt + 1, "%zu:%zu: %s", |
|
yaml.mark.line + 1, |
|
yaml.mark.column, |
|
error_str); |
|
} else { |
|
int cnt = snprintf(NULL, 0, "%zu:%zu: unknown error", |
|
yaml.mark.line + 1, yaml.mark.column); |
|
*error = malloc(cnt + 1); |
|
snprintf(*error, cnt + 1, "%zu:%zu: unknown error", |
|
yaml.mark.line + 1, yaml.mark.column); |
|
} |
|
|
|
err_no_error_formatting: |
|
|
|
yml_destroy(root); |
|
yaml_parser_delete(&yaml); |
|
return NULL; |
|
} |
|
|
|
void |
|
yml_destroy(struct yml_node *node) |
|
{ |
|
if (node == NULL) |
|
return; |
|
|
|
switch (node->type) { |
|
case ROOT: |
|
yml_destroy(node->root.root); |
|
for (size_t i = 0; i < node->root.anchor_count; i++) |
|
free(node->root.anchors[i].anchor); |
|
break; |
|
|
|
case SCALAR: |
|
free(node->scalar.value); |
|
break; |
|
|
|
case LIST: |
|
tll_free_and_free(node->list.values, yml_destroy); |
|
break; |
|
|
|
case DICT: |
|
tll_foreach(node->dict.pairs, it) { |
|
yml_destroy(it->item.key); |
|
yml_destroy(it->item.value); |
|
} |
|
tll_free(node->dict.pairs); |
|
break; |
|
} |
|
|
|
free(node); |
|
} |
|
|
|
bool |
|
yml_is_scalar(const struct yml_node *node) |
|
{ |
|
return node->type == SCALAR; |
|
} |
|
|
|
bool |
|
yml_is_dict(const struct yml_node *node) |
|
{ |
|
return node->type == DICT; |
|
} |
|
|
|
bool |
|
yml_is_list(const struct yml_node *node) |
|
{ |
|
return node->type == LIST; |
|
} |
|
|
|
const struct yml_node * |
|
yml_get_value(const struct yml_node *node, const char *_path) |
|
{ |
|
if (node != NULL && node->type == ROOT) |
|
node = node->root.root; |
|
|
|
if (node == NULL) |
|
return NULL; |
|
|
|
char *path = strdup(_path); |
|
|
|
for (const char *part = strtok(path, "."), *next_part = strtok(NULL, "."); |
|
part != NULL; |
|
part = next_part, next_part = strtok(NULL, ".")) |
|
{ |
|
assert(yml_is_dict(node)); |
|
|
|
tll_foreach(node->dict.pairs, it) { |
|
assert(yml_is_scalar(it->item.key)); |
|
if (strcmp(it->item.key->scalar.value, part) == 0) { |
|
if (next_part == NULL) { |
|
free(path); |
|
return it->item.value; |
|
} |
|
|
|
node = it->item.value; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
free(path); |
|
return NULL; |
|
} |
|
|
|
struct yml_list_iter |
|
yml_list_iter(const struct yml_node *list) |
|
{ |
|
assert(yml_is_list(list)); |
|
tll_foreach(list->list.values, it) { |
|
return (struct yml_list_iter){ |
|
.node = it->item, |
|
.private = it, |
|
}; |
|
} |
|
|
|
return (struct yml_list_iter){ |
|
.node = NULL, |
|
.private = NULL, |
|
}; |
|
} |
|
|
|
void |
|
yml_list_next(struct yml_list_iter *iter) |
|
{ |
|
if (iter->private == NULL) |
|
return; |
|
|
|
const struct yml_node *d = (const void *)(uintptr_t)0xdeadbeef; |
|
__typeof__(d->list.values.head) it = (__typeof__(d->list.values.head))iter->private; |
|
__typeof__(d->list.values.head) next = it->next; |
|
|
|
iter->node = next != NULL ? next->item : NULL; |
|
iter->private = next; |
|
} |
|
|
|
size_t |
|
yml_list_length(const struct yml_node *list) |
|
{ |
|
assert(yml_is_list(list)); |
|
|
|
size_t length = 0; |
|
for (struct yml_list_iter it = yml_list_iter(list); |
|
it.node != NULL; |
|
yml_list_next(&it), length++) |
|
; |
|
|
|
return length; |
|
} |
|
|
|
struct yml_dict_iter |
|
yml_dict_iter(const struct yml_node *dict) |
|
{ |
|
assert(yml_is_dict(dict)); |
|
|
|
tll_foreach(dict->dict.pairs, it) { |
|
return (struct yml_dict_iter){ |
|
.key = it->item.key, |
|
.value = it->item.value, |
|
.private1 = it, |
|
}; |
|
} |
|
|
|
return (struct yml_dict_iter) { |
|
.key = NULL, |
|
.value = NULL, |
|
.private1 = NULL, |
|
}; |
|
} |
|
|
|
void |
|
yml_dict_next(struct yml_dict_iter *iter) |
|
{ |
|
const struct yml_node *d = (const void *)(uintptr_t)0xdeadbeef; |
|
__typeof__(d->dict.pairs.head) it = (__typeof__(d->dict.pairs.head))iter->private1; |
|
|
|
if (it == NULL) |
|
return; |
|
|
|
__typeof__(d->dict.pairs.head) next = it->next; |
|
iter->key = next != NULL ? next->item.key : NULL; |
|
iter->value = next != NULL ? next->item.value : NULL; |
|
iter->private1 = next; |
|
} |
|
|
|
size_t |
|
yml_dict_length(const struct yml_node *dict) |
|
{ |
|
assert(yml_is_dict(dict)); |
|
return tll_length(dict->dict.pairs); |
|
} |
|
|
|
const char * |
|
yml_value_as_string(const struct yml_node *value) |
|
{ |
|
if (!yml_is_scalar(value)) |
|
return NULL; |
|
return value->scalar.value; |
|
} |
|
|
|
static bool |
|
_as_int(const struct yml_node *value, long *ret) |
|
{ |
|
const char *s = yml_value_as_string(value); |
|
if (s == NULL) |
|
return false; |
|
|
|
int cnt; |
|
int res = sscanf(s, "%ld%n", ret, &cnt); |
|
return res == 1 && strlen(s) == (size_t)cnt; |
|
} |
|
|
|
bool |
|
yml_value_is_int(const struct yml_node *value) |
|
{ |
|
long dummy; |
|
return _as_int(value, &dummy); |
|
} |
|
|
|
long |
|
yml_value_as_int(const struct yml_node *value) |
|
{ |
|
long ret = -1; |
|
_as_int(value, &ret); |
|
return ret; |
|
} |
|
|
|
static bool |
|
_as_bool(const struct yml_node *value, bool *ret) |
|
{ |
|
if (!yml_is_scalar(value)) |
|
return false; |
|
|
|
const char *v = yml_value_as_string(value); |
|
if (strcasecmp(v, "y") == 0 || |
|
strcasecmp(v, "yes") == 0 || |
|
strcasecmp(v, "true") == 0 || |
|
strcasecmp(v, "on") == 0) |
|
{ |
|
*ret = true; |
|
return true; |
|
} else if (strcasecmp(v, "n") == 0 || |
|
strcasecmp(v, "no") == 0 || |
|
strcasecmp(v, "false") == 0 || |
|
strcasecmp(v, "off") == 0) |
|
{ |
|
*ret = false; |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
bool |
|
yml_value_is_bool(const struct yml_node *value) |
|
{ |
|
bool dummy; |
|
return _as_bool(value, &dummy); |
|
} |
|
|
|
bool |
|
yml_value_as_bool(const struct yml_node *value) |
|
{ |
|
bool ret = false; |
|
_as_bool(value, &ret); |
|
return ret; |
|
} |
|
|
|
size_t |
|
yml_source_line(const struct yml_node *node) |
|
{ |
|
return node->line; |
|
} |
|
|
|
size_t |
|
yml_source_column(const struct yml_node *node) |
|
{ |
|
return node->column; |
|
} |
|
|
|
static void |
|
_print_node(const struct yml_node *n, int indent) |
|
{ |
|
if (n == NULL) |
|
return; |
|
|
|
switch (n->type) { |
|
case ROOT: |
|
_print_node(n->root.root, indent); |
|
break; |
|
|
|
case DICT: |
|
tll_foreach(n->dict.pairs, it) { |
|
_print_node(it->item.key, indent); |
|
printf(": "); |
|
|
|
if (it->item.value->type != SCALAR) { |
|
printf("\n"); |
|
_print_node(it->item.value, indent + 2); |
|
} else { |
|
_print_node(it->item.value, 0); |
|
printf("\n"); |
|
} |
|
} |
|
break; |
|
|
|
case LIST: |
|
tll_foreach(n->list.values, it) { |
|
printf("%*s- ", indent, ""); |
|
if (it->item->type != SCALAR) { |
|
printf("\n"); |
|
_print_node(it->item, indent + 2); |
|
} else { |
|
_print_node(it->item, 0); |
|
} |
|
} |
|
break; |
|
|
|
case SCALAR: |
|
printf("%*s%s", indent, "", n->scalar.value); |
|
break; |
|
} |
|
} |
|
|
|
void |
|
print_node(const struct yml_node *n) |
|
{ |
|
_print_node(n, 0); |
|
}
|
|
|