/* 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 void
free_value_mem(struct grecs_value *p)
{
switch (p->type) {
case GRECS_TYPE_STRING:
free((char*)p->v.string);
break;
case GRECS_TYPE_LIST:
/* FIXME */
break;
case GRECS_TYPE_ARRAY: {
size_t i;
for (i = 0; i < p->v.arg.c; i++)
free_value_mem(&p->v.arg.v[i]);
}
}
}
static void
destroy_value(void *p)
{
struct grecs_value*val = p;
if (val) {
free_value_mem(val);
free(val);
}
}
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_list_create();
lst->free_entry = destroy_value;
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_malloc(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)
return grecs_tree_recurse_ok;
if (strcmp(fdptr->argv[fdptr->tag], node->ident) == 0
&& (!fdptr->label || grecs_value_eq(fdptr->label, &node->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 (!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);
destroy_value(clos.label);
return clos.node;
}