/* grecs - Gray's Extensible Configuration System Copyright (C) 2007-2011 Sergey Poznyakoff Grecs is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Grecs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Grecs. If not, see . */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include "grecs.h" #include "wordsplit.h" static int _grecs_list_eq(struct grecs_value *a, struct grecs_value *b) { struct grecs_list_entry *aent, *bent; if (grecs_list_size(a->v.list) != grecs_list_size(b->v.list)) return 0; for (aent = a->v.list->head, bent = b->v.list->head;; aent = aent->next, bent = bent->next) { if (!aent) return bent == NULL; if (!bent) return 0; if (!grecs_value_eq(aent->data, bent->data)) return 0; } /*notreached*/ return 1; } static int _grecs_array_eq(struct grecs_value *a, struct grecs_value *b) { size_t i; if (a->v.arg.c != b->v.arg.c) return 0; for (i = 0; i < a->v.arg.c; i++) if (!grecs_value_eq(a->v.arg.v[i], b->v.arg.v[i])) return 0; return 1; } /* Return 1 if configuration value A equals B */ int grecs_value_eq(struct grecs_value *a, struct grecs_value *b) { if (a->type != b->type) return 0; switch (a->type) { case GRECS_TYPE_STRING: if (a->v.string == NULL) return b->v.string == NULL; return strcmp(a->v.string, b->v.string) == 0; case GRECS_TYPE_LIST: return _grecs_list_eq(a, b); case GRECS_TYPE_ARRAY: return _grecs_array_eq(a, b); } return 0; } static int split_cfg_path(const char *path, int *pargc, char ***pargv) { int argc; char **argv; char *delim = "."; char static_delim[2] = { 0, 0 }; if (path[0] == '\\') { argv = calloc(2, sizeof (*argv)); if (!argv) return ENOMEM; argv[0] = strdup(path + 1); if (!argv[0]) { free(argv); return ENOMEM; } argv[1] = NULL; argc = 1; } else { struct wordsplit ws; if (ispunct(path[0])) { delim = static_delim; delim[0] = path[0]; path++; } ws.ws_delim = delim; if (wordsplit(path, &ws, WRDSF_DEFFLAGS|WRDSF_DELIM)) return errno; argc = ws.ws_wordc; argv = ws.ws_wordv; ws.ws_wordc = 0; ws.ws_wordv = NULL; wordsplit_free(&ws); } *pargc = argc; *pargv = argv; return 0; } static struct grecs_value * parse_label(const char *str) { struct grecs_value *val = NULL; size_t i; struct wordsplit ws; size_t len = strlen (str); if (len > 1 && str[0] == '(' && str[len-1] == ')') { struct grecs_list *lst; ws.ws_delim = ","; if (wordsplit_len (str + 1, len - 2, &ws, WRDSF_DEFFLAGS|WRDSF_DELIM| WRDSF_WS)) { return NULL; } lst = grecs_value_list_create(); for (i = 0; i < ws.ws_wordc; i++) { struct grecs_value *p = grecs_malloc(sizeof(*p)); p->type = GRECS_TYPE_STRING; p->v.string = ws.ws_wordv[i]; grecs_list_append(lst, p); } val = grecs_malloc(sizeof(*val)); val->type = GRECS_TYPE_LIST; val->v.list = lst; } else { if (wordsplit(str, &ws, WRDSF_DEFFLAGS)) return NULL; val = grecs_malloc(sizeof(*val)); if (ws.ws_wordc == 1) { val->type = GRECS_TYPE_STRING; val->v.string = ws.ws_wordv[0]; } else { val->type = GRECS_TYPE_ARRAY; val->v.arg.c = ws.ws_wordc; val->v.arg.v = grecs_calloc(ws.ws_wordc, sizeof(val->v.arg.v[0])); for (i = 0; i < ws.ws_wordc; i++) { val->v.arg.v[i]->type = GRECS_TYPE_STRING; val->v.arg.v[i]->v.string = ws.ws_wordv[i]; } } ws.ws_wordc = 0; wordsplit_free(&ws); } return val; } struct find_closure { int argc; char **argv; int tag; struct grecs_value *label; struct grecs_node *node; }; static void parse_tag(struct find_closure *fptr) { char *p = strchr(fptr->argv[fptr->tag], '='); if (p) { *p++ = 0; fptr->label = parse_label(p); } else fptr->label = NULL; } static enum grecs_tree_recurse_res node_finder(enum grecs_tree_recurse_op op, struct grecs_node *node, void *data) { struct find_closure *fdptr = data; if (op == grecs_tree_recurse_post || node->type == grecs_node_root) return grecs_tree_recurse_ok; if (strcmp(fdptr->argv[fdptr->tag], node->ident) == 0 && (!fdptr->label || grecs_value_eq(fdptr->label, node->v.value))) { fdptr->tag++; if (fdptr->tag == fdptr->argc) { fdptr->node = node; return grecs_tree_recurse_stop; } parse_tag(fdptr); return grecs_tree_recurse_ok; } return node->type == grecs_node_block ? grecs_tree_recurse_skip : grecs_tree_recurse_ok; } struct grecs_node * grecs_find_node(struct grecs_node *node, const char *path) { int rc, i; struct find_closure clos; rc = split_cfg_path(path, &clos.argc, &clos.argv); if (rc || !clos.argc) return NULL; clos.tag = 0; clos.label = NULL; clos.node = NULL; parse_tag(&clos); grecs_tree_recurse(node, node_finder, &clos); for (i = 0; i < clos.argc; i++) free(clos.argv[i]); free(clos.argv); grecs_value_free(clos.label); return clos.node; } struct grecs_node * grecs_node_from_path(const char *path, const char *value) { int rc; int i; int argc; char **argv; struct grecs_node *dn = NULL; rc = split_cfg_path(path, &argc, &argv); if (rc) return NULL; dn = grecs_node_create(grecs_node_stmt, NULL); dn->ident = argv[argc - 1]; if (value) { struct grecs_value *gval = parse_label(value); dn->v.value = gval; } else dn->v.value = NULL; for (i = argc - 2; i >= 0; i--) { struct grecs_value *label = NULL; struct grecs_node *node; char *p, *q = argv[i]; do { p = strchr(q, '='); if (p && p > argv[i] && p[-1] != '\\') { *p++ = 0; label = parse_label(p); break; } else if (p) q = p + 1; else break; } while (*q); node = grecs_node_create(grecs_node_block, NULL); node->ident = argv[i]; if (label) node->v.value = label; node->down = dn; if (dn) dn->up = node; dn = node; } free(argv); return dn; }