[augeas-devel] [PATCH 10/11] Add variables for path expressions
David Lutterkort
lutter at redhat.com
Mon Mar 23 06:27:35 UTC 2009
The new API call aug_defvar allows defining variables that can later be
used in path expressions.
---
doc/xpath.txt | 3 +
src/augeas.c | 33 ++++++--
src/augeas.h | 15 +++
src/augeas_sym.version | 1 +
src/builtin.c | 6 +-
src/internal.h | 11 +++
src/pathx.c | 228 ++++++++++++++++++++++++++++++++++++++++++++++--
src/transform.c | 3 +-
tests/test-xpath.c | 46 +++++++++-
tests/xpath.tests | 6 +-
10 files changed, 330 insertions(+), 22 deletions(-)
diff --git a/doc/xpath.txt b/doc/xpath.txt
index b0b6a81..8ad4253 100644
--- a/doc/xpath.txt
+++ b/doc/xpath.txt
@@ -115,6 +115,8 @@ FilterExpr ::= PrimaryExpr Predicate
PrimaryExpr ::= Literal
| Number
| FunctionCall
+ | VariableReference
+
FunctionCall ::= Name '(' ( Expr ( ',' Expr )* )? ')'
Expr ::= EqualityExpr
@@ -128,6 +130,7 @@ Literal ::= '"' /[^"]* / '"' | "'" /[^']* / "'"
Number ::= /[0-9]+/
Name ::= /([^][/\= \t\n]|\\.)+/
+VariableReference ::= '$' /[a-zA-Z_][a-zA-Z0-9_]*/
Additional stuff
================
diff --git a/src/augeas.c b/src/augeas.c
index 7da4206..5e3abec 100644
--- a/src/augeas.c
+++ b/src/augeas.c
@@ -126,7 +126,7 @@ static struct pathx *parse_user_pathx(const struct augeas *aug,
struct pathx *result;
int pos;
- if (pathx_parse(aug->origin, path, need_nodeset, &result)
+ if (pathx_parse(aug->origin, path, need_nodeset, aug->symtab, &result)
== PATHX_NOERROR)
return result;
@@ -349,7 +349,7 @@ int aug_get(const struct augeas *aug, const char *path, const char **value) {
struct tree *match;
int r;
- p = parse_user_pathx(aug, true, path);
+ p = parse_user_pathx((struct augeas *) aug, true, path);
if (p == NULL)
return -1;
@@ -364,6 +364,22 @@ int aug_get(const struct augeas *aug, const char *path, const char **value) {
return r;
}
+int aug_defvar(augeas *aug, const char *name, const char *expr) {
+ struct pathx *p;
+ int r;
+
+ if (expr == NULL) {
+ r = pathx_symtab_undefine(&(aug->symtab), name);
+ } else {
+ p = parse_user_pathx((struct augeas *) aug, false, expr);
+ if (p == NULL)
+ return -1;
+ r = pathx_symtab_define(&(aug->symtab), name, p);
+ }
+ free_pathx(p);
+ return (r < 0) ? -1 : 0;
+}
+
struct tree *tree_set(struct pathx *p, const char *value) {
struct tree *tree;
int r;
@@ -517,6 +533,7 @@ int tree_rm(struct pathx *p) {
for (i = 0, tree = pathx_first(p); tree != NULL; tree = pathx_next(p)) {
if (TREE_HIDDEN(tree))
continue;
+ pathx_symtab_remove_descendants(p, tree);
del[i] = tree;
i += 1;
}
@@ -547,7 +564,7 @@ int tree_replace(struct tree *origin, const char *path, struct tree *sub) {
struct pathx *p = NULL;
int r;
- if (pathx_parse(origin, path, true, &p) != PATHX_NOERROR)
+ if (pathx_parse(origin, path, true, NULL, &p) != PATHX_NOERROR)
goto error;
r = tree_rm(p);
@@ -635,7 +652,7 @@ int aug_match(const struct augeas *aug, const char *pathin, char ***matches) {
pathin = "/*";
}
- p = parse_user_pathx(aug, true, pathin);
+ p = parse_user_pathx((struct augeas *) aug, true, pathin);
if (p == NULL)
return -1;
@@ -763,7 +780,7 @@ static int unlink_removed_files(struct augeas *aug,
if (tf == NULL) {
/* Unlink all files in tm */
struct pathx *px = NULL;
- if (pathx_parse(tm, file_nodes, true, &px)
+ if (pathx_parse(tm, file_nodes, true, NULL, &px)
!= PATHX_NOERROR) {
result = -1;
continue;
@@ -897,7 +914,7 @@ int dump_tree(FILE *out, struct tree *tree) {
struct pathx *p;
int result;
- if (pathx_parse(tree, "/*", true, &p) != PATHX_NOERROR)
+ if (pathx_parse(tree, "/*", true, NULL, &p) != PATHX_NOERROR)
return -1;
result = print_tree(out, p, 1);
@@ -913,7 +930,7 @@ int aug_print(const struct augeas *aug, FILE *out, const char *pathin) {
pathin = "/*";
}
- p = parse_user_pathx(aug, true, pathin);
+ p = parse_user_pathx((struct augeas *) aug, true, pathin);
if (p == NULL)
return -1;
@@ -930,6 +947,7 @@ void aug_close(struct augeas *aug) {
unref(aug->modules, module);
free((void *) aug->root);
free(aug->modpathz);
+ free_symtab(aug->symtab);
free(aug);
}
@@ -947,6 +965,7 @@ int tree_equal(const struct tree *t1, const struct tree *t2) {
return t1 == t2;
}
+
/*
* Local variables:
* indent-tabs-mode: nil
diff --git a/src/augeas.h b/src/augeas.h
index 73deafe..be1ad65 100644
--- a/src/augeas.h
+++ b/src/augeas.h
@@ -68,6 +68,21 @@ enum aug_flags {
*/
augeas *aug_init(const char *root, const char *loadpath, unsigned int flags);
+/* Function: aug_defvar
+ *
+ * Define a variable NAME whose value is the result of evaluating EXPR. If
+ * a variable NAME already exists, its name will be replaced with the
+ * result of evaluating EXPR.
+ *
+ * If EXPR is NULL, the variable NAME will be removed if it is defined.
+ *
+ * Path variables can be used in path expressions later on by prefixing
+ * them with '$'.
+ *
+ * Returns 0 on success, -1 on error
+ */
+int aug_defvar(augeas *aug, const char *name, const char *expr);
+
/* Function: aug_get
*
* Lookup the value associated with PATH. VALUE can be NULL, in which case
diff --git a/src/augeas_sym.version b/src/augeas_sym.version
index e479d13..f0c887e 100644
--- a/src/augeas_sym.version
+++ b/src/augeas_sym.version
@@ -1,6 +1,7 @@
{
global:
aug_init;
+ aug_defvar;
aug_close;
aug_get;
aug_set;
diff --git a/src/builtin.c b/src/builtin.c
index e256fa7..43c4e1d 100644
--- a/src/builtin.c
+++ b/src/builtin.c
@@ -212,7 +212,7 @@ static struct value *tree_set_glue(struct info *info, struct value *path,
fake = tree->origin->children;
}
- if (pathx_parse(tree->origin, path->string->str, true, &p)
+ if (pathx_parse(tree->origin, path->string->str, true, NULL, &p)
!= PATHX_NOERROR) {
result = make_pathx_exn(ref(info), p);
goto done;
@@ -249,7 +249,7 @@ static struct value *tree_insert_glue(struct info *info, struct value *label,
struct pathx *p = NULL;
struct value *result = NULL;
- if (pathx_parse(tree->origin, path->string->str, true, &p)
+ if (pathx_parse(tree->origin, path->string->str, true, NULL, &p)
!= PATHX_NOERROR) {
result = make_pathx_exn(ref(info), p);
goto done;
@@ -296,7 +296,7 @@ static struct value *tree_rm_glue(struct info *info,
struct pathx *p = NULL;
struct value *result = NULL;
- if (pathx_parse(tree->origin, path->string->str, true, &p)
+ if (pathx_parse(tree->origin, path->string->str, true, NULL, &p)
!= PATHX_NOERROR) {
result = make_pathx_exn(ref(info), p);
goto done;
diff --git a/src/internal.h b/src/internal.h
index d9d0e60..76d9c6d 100644
--- a/src/internal.h
+++ b/src/internal.h
@@ -275,6 +275,7 @@ struct augeas {
size_t nmodpath;
char *modpathz; /* The search path for modules as a
glibc argz vector */
+ struct pathx_symtab *symtab;
};
/* Struct: tree
@@ -383,10 +384,12 @@ typedef enum {
PATHX_ESLASH,
PATHX_EINTERNAL,
PATHX_ETYPE,
+ PATHX_ENOVAR,
PATHX_EEND
} pathx_errcode_t;
struct pathx;
+struct pathx_symtab;
const char *pathx_error(struct pathx *pathx, const char **txt, int *pos);
@@ -400,6 +403,7 @@ const char *pathx_error(struct pathx *pathx, const char **txt, int *pos);
*/
int pathx_parse(const struct tree *origin, const char *path,
bool need_nodeset,
+ struct pathx_symtab *symtab,
struct pathx **px);
struct tree *pathx_first(struct pathx *path);
struct tree *pathx_next(struct pathx *path);
@@ -407,6 +411,13 @@ int pathx_find_one(struct pathx *path, struct tree **match);
int pathx_expand_tree(struct pathx *path, struct tree **tree);
void free_pathx(struct pathx *path);
+int pathx_symtab_init(struct pathx_symtab **symtab);
+int pathx_symtab_define(struct pathx_symtab **symtab,
+ const char *name, struct pathx *px);
+int pathx_symtab_undefine(struct pathx_symtab **symtab, const char *name);
+void pathx_symtab_remove_descendants(struct pathx *pathx,
+ const struct tree *tree);
+void free_symtab(struct pathx_symtab *symtab);
#endif
diff --git a/src/pathx.c b/src/pathx.c
index 83ff956..9fa8f95 100644
--- a/src/pathx.c
+++ b/src/pathx.c
@@ -40,6 +40,7 @@ static const char *const errcodes[] = {
"expected a '/'",
"internal error", /* PATHX_EINTERNAL */
"type error", /* PATHX_ETYPE */
+ "undefined variable", /* PATHX_ENOVAR */
"garbage at end of path expression" /* PATHX_EEND */
};
@@ -59,6 +60,7 @@ enum expr_tag {
E_FILTER,
E_BINARY,
E_VALUE,
+ E_VAR,
E_APP
};
@@ -121,6 +123,12 @@ static struct tree *step_first(struct step *step, struct tree *ctx);
static struct tree *step_next(struct step *step, struct tree *ctx,
struct tree *node);
+struct pathx_symtab {
+ struct pathx_symtab *next;
+ char *name;
+ struct value *value;
+};
+
struct pathx {
struct state *state;
struct nodeset *nodeset;
@@ -168,6 +176,7 @@ struct expr {
struct expr *right;
};
value_ind_t value_ind; /* E_VALUE */
+ char *ident; /* E_VAR */
struct { /* E_APP */
const struct func *func;
struct expr *args[];
@@ -217,6 +226,8 @@ struct state {
Generally NULL, unless a trace is needed.
*/
struct locpath_trace *locpath_trace;
+ /* Symbol table for variable lookups */
+ struct pathx_symtab *symtab;
};
/* We consider NULL and the empty string to be equal */
@@ -340,6 +351,9 @@ static void free_expr(struct expr *expr) {
break;
case E_VALUE:
break;
+ case E_VAR:
+ free(expr->ident);
+ break;
case E_APP:
for (int i=0; i < expr->func->arity; i++)
free_expr(expr->args[i]);
@@ -770,6 +784,12 @@ static int eval_pred(struct expr *expr, struct state *state) {
}
}
+static void ns_remove(struct nodeset *ns, int ind) {
+ memmove(ns->nodes + ind, ns->nodes + ind+1,
+ sizeof(ns->nodes[0]) * (ns->used - (ind+1)));
+ ns->used -= 1;
+}
+
/*
* Remove all nodes from NS for which one of PRED is false
*/
@@ -790,9 +810,7 @@ static void ns_filter(struct nodeset *ns, struct pred *predicates,
if (eval_pred(predicates->exprs[p], state)) {
i+=1;
} else {
- memmove(ns->nodes + i, ns->nodes + i+1,
- sizeof(ns->nodes[0]) * (ns->used - (i+1)));
- ns->used -= 1;
+ ns_remove(ns, i);
}
}
}
@@ -898,6 +916,21 @@ static void eval_filter(struct expr *expr, struct state *state) {
}
}
+static struct value *lookup_var(const char *ident, struct state *state) {
+ list_for_each(tab, state->symtab) {
+ if (STREQ(ident, tab->name))
+ return tab->value;
+ }
+ return NULL;
+}
+
+static void eval_var(struct expr *expr, struct state *state) {
+ struct value *v = lookup_var(expr->ident, state);
+ value_ind_t vind = clone_value(v, state);
+ CHECK_ERROR;
+ push_value(vind, state);
+}
+
static void eval_expr(struct expr *expr, struct state *state) {
CHECK_ERROR;
switch (expr->tag) {
@@ -910,6 +943,9 @@ static void eval_expr(struct expr *expr, struct state *state) {
case E_VALUE:
push_value(expr->value_ind, state);
break;
+ case E_VAR:
+ eval_var(expr, state);
+ break;
case E_APP:
eval_app(expr, state);
break;
@@ -1041,6 +1077,15 @@ static void check_binary(struct expr *expr, struct state *state) {
}
}
+static void check_var(struct expr *expr, struct state *state) {
+ struct value *v = lookup_var(expr->ident, state);
+ if (v == NULL) {
+ STATE_ERROR(state, PATHX_ENOVAR);
+ return;
+ }
+ expr->type = v->tag;
+}
+
/* Typecheck an expression */
static void check_expr(struct expr *expr, struct state *state) {
CHECK_ERROR;
@@ -1054,6 +1099,9 @@ static void check_expr(struct expr *expr, struct state *state) {
case E_VALUE:
expr->type = expr_value(expr, state)->tag;
break;
+ case E_VAR:
+ check_var(expr, state);
+ break;
case E_APP:
check_app(expr, state);
break;
@@ -1531,6 +1579,39 @@ static void parse_function_call(struct state *state) {
}
/*
+ * VariableReference ::= '$' /[a-zA-Z_][a-zA-Z0-9_]* /
+ *
+ * The '$' is consumed by parse_primary_expr
+ */
+static void parse_var(struct state *state) {
+ const char *id = state->pos;
+ struct expr *expr = NULL;
+
+ if (!isalpha(*id) && *id != '_') {
+ STATE_ERROR(state, PATHX_ENAME);
+ return;
+ }
+ id++;
+ while (isalpha(*id) || isdigit(*id) || *id == '_')
+ id += 1;
+
+ if (ALLOC(expr) < 0)
+ goto err_nomem;
+ expr->tag = E_VAR;
+ expr->ident = strndup(state->pos, id - state->pos);
+ if (expr->ident == NULL)
+ goto err_nomem;
+
+ push_expr(expr, state);
+ state->pos = id;
+ return;
+ err_nomem:
+ STATE_ENOMEM;
+ free_expr(expr);
+ return;
+}
+
+/*
* PrimaryExpr ::= Literal
* | Number
* | FunctionCall
@@ -1549,6 +1630,8 @@ static void parse_primary_expr(struct state *state) {
STATE_ERROR(state, PATHX_EPAREN);
return;
}
+ } else if (match(state, '$')) {
+ parse_var(state);
} else {
parse_function_call(state);
}
@@ -1556,8 +1639,8 @@ static void parse_primary_expr(struct state *state) {
static int looking_at_primary_expr(struct state *state) {
const char *s = state->pos;
- /* Is it a Number or Literal ? */
- if (peek(state, "'\"0123456789"))
+ /* Is it a Number, Literal or VariableReference ? */
+ if (peek(state, "$'\"0123456789"))
return 1;
/* Or maybe a function call, i.e. a word followed by a '(' ?
@@ -1753,7 +1836,9 @@ static void parse_expr(struct state *state) {
}
int pathx_parse(const struct tree *tree, const char *txt,
- bool need_nodeset, struct pathx **pathx) {
+ bool need_nodeset,
+ struct pathx_symtab *symtab,
+ struct pathx **pathx) {
struct state *state = NULL;
*pathx = NULL;
@@ -1774,6 +1859,7 @@ int pathx_parse(const struct tree *tree, const char *txt,
state->errcode = PATHX_NOERROR;
state->txt = txt;
state->pos = txt;
+ state->symtab = symtab;
if (ALLOC_N(state->value_pool, 8) < 0) {
STATE_ENOMEM;
@@ -2060,6 +2146,136 @@ const char *pathx_error(struct pathx *path, const char **txt, int *pos) {
return errcodes[errcode];
}
+/*
+ * Symbol tables
+ */
+static struct pathx_symtab
+*make_symtab(struct pathx_symtab *symtab, const char *name,
+ struct value *value)
+{
+ struct pathx_symtab *new;
+ char *n = NULL;
+
+ n = strdup(name);
+ if (n == NULL)
+ return NULL;
+
+ if (ALLOC(new) < 0) {
+ free(n);
+ return NULL;
+ }
+ new->name = n;
+ new->value = value;
+ if (symtab == NULL) {
+ return new;
+ } else {
+ new->next = symtab->next;
+ symtab->next = new;
+ }
+ return symtab;
+}
+
+void free_symtab(struct pathx_symtab *symtab) {
+
+ while (symtab != NULL) {
+ struct pathx_symtab *del = symtab;
+ symtab = del->next;
+ free(del->name);
+ release_value(del->value);
+ free(del->value);
+ free(del);
+ }
+}
+
+int pathx_symtab_init(struct pathx_symtab **symtab) {
+ struct value *v = NULL;
+
+ *symtab = NULL;
+
+ if (ALLOC(v) < 0)
+ goto error;
+ v->tag = T_BOOLEAN;
+
+ *symtab = make_symtab(NULL, " unused ", v);
+ if (*symtab == NULL)
+ goto error;
+ return 0;
+ error:
+ release_value(v);
+ free(v);
+ free_symtab(*symtab);
+ *symtab = NULL;
+ return -1;
+}
+
+int pathx_symtab_define(struct pathx_symtab **symtab,
+ const char *name, struct pathx *px) {
+ struct pathx_symtab *new;
+ struct value *value = NULL, *v = NULL;
+
+ value = pathx_eval(px);
+ if (HAS_ERROR(px->state))
+ goto error;
+
+ if (ALLOC(v) < 0)
+ goto error;
+ *v = *value;
+ value->tag = T_BOOLEAN;
+
+ list_for_each(tab, *symtab) {
+ if (STREQ(tab->name, name)) {
+ release_value(tab->value);
+ free(tab->value);
+ tab->value = v;
+ return 0;
+ }
+ }
+
+ new = make_symtab(*symtab, name, v);
+ if (new == NULL)
+ goto error;
+
+ *symtab = new;
+ return 0;
+ error:
+ release_value(value);
+ free(value);
+ release_value(v);
+ free(v);
+ return -1;
+}
+
+int pathx_symtab_undefine(struct pathx_symtab **symtab, const char *name) {
+ struct pathx_symtab *del = NULL;
+
+ for(del = *symtab;
+ del != NULL && !STREQ(del->name, name);
+ del = del->next);
+ if (del == NULL)
+ return 0;
+ list_remove(del, *symtab);
+ free_symtab(del);
+ return 0;
+}
+
+void pathx_symtab_remove_descendants(struct pathx *pathx,
+ const struct tree *tree) {
+ struct pathx_symtab *symtab = pathx->state->symtab;
+ list_for_each(tab, symtab) {
+ if (tab->value->tag != T_NODESET)
+ continue;
+ struct nodeset *ns = tab->value->nodeset;
+ for (int i=0; i < ns->used;) {
+ struct tree *t = ns->nodes[i];
+ while (t != t->parent && t != tree)
+ t = t->parent;
+ if (t == tree)
+ ns_remove(ns, i);
+ else
+ i += 1;
+ }
+ }
+}
/*
* Local variables:
diff --git a/src/transform.c b/src/transform.c
index 92cdc8f..eda55ba 100644
--- a/src/transform.c
+++ b/src/transform.c
@@ -654,7 +654,8 @@ static int file_saved_event(struct augeas *aug, const char *path) {
struct tree *dummy;
int r;
- r = pathx_parse(aug->origin, AUGEAS_EVENTS_SAVED "[last()]", true, &px);
+ r = pathx_parse(aug->origin, AUGEAS_EVENTS_SAVED "[last()]",
+ true, NULL, &px);
if (r != PATHX_NOERROR)
return -1;
diff --git a/tests/test-xpath.c b/tests/test-xpath.c
index 444983e..d972738 100644
--- a/tests/test-xpath.c
+++ b/tests/test-xpath.c
@@ -194,10 +194,42 @@ static int run_one_test(struct augeas *aug, struct test *t) {
return result;
}
+static int test_rm_var(struct augeas *aug) {
+ int r;
+
+ printf("%-30s ... ", "rm_var");
+ r = aug_defvar(aug, "h", "/files/etc/hosts/2/ipaddr");
+ if (r < 0)
+ die("aug_defvar failed");
+
+ r = aug_match(aug, "$h", NULL);
+ if (r != 1) {
+ fprintf(stderr, "expected 1 match, got %d\n", r);
+ goto fail;
+ }
+
+ r = aug_rm(aug, "/files/etc/hosts/2");
+ if (r != 4) {
+ fprintf(stderr, "expected 4 nodes removed, got %d\n", r);
+ goto fail;
+ }
+
+ r = aug_match(aug, "$h", NULL);
+ if (r != 0) {
+ fprintf(stderr, "expected no match, got %d\n", r);
+ goto fail;
+ }
+ printf("PASS\n");
+ return 0;
+ fail:
+ printf("FAIL\n");
+ return -1;
+}
+
static int run_tests(struct test *tests) {
char *lensdir;
struct augeas *aug = NULL;
- int result = EXIT_SUCCESS;
+ int r, result = EXIT_SUCCESS;
if (asprintf(&lensdir, "%s/lenses", abs_top_srcdir) < 0)
die("asprintf lensdir failed");
@@ -205,11 +237,21 @@ static int run_tests(struct test *tests) {
aug = aug_init(root, lensdir, AUG_NO_STDINC|AUG_SAVE_NEWFILE);
if (aug == NULL)
die("aug_init");
- aug_pathvar(aug, "hosts", "/files/etc/hosts/*");
+ r = aug_defvar(aug, "hosts", "/files/etc/hosts/*");
+ if (r < 0)
+ die("aug_defvar $hosts");
+ r = aug_defvar(aug, "localhost", "'127.0.0.1'");
+ if (r < 0)
+ die("aug_defvar $localhost");
+
list_for_each(t, tests) {
if (run_one_test(aug, t) < 0)
result = EXIT_FAILURE;
}
+
+ if (test_rm_var(aug) < 0)
+ result = EXIT_FAILURE;
+
aug_close(aug);
return result;
diff --git a/tests/xpath.tests b/tests/xpath.tests
index ab06851..12ee3cc 100644
--- a/tests/xpath.tests
+++ b/tests/xpath.tests
@@ -31,14 +31,14 @@ test wildcard-var $hosts/ipaddr
test self-value /files/etc/hosts/*/ipaddr[ . = '127.0.0.1' ]
/files/etc/hosts/1/ipaddr = 127.0.0.1
-test self-value-var $hosts/ipaddr[ . = '127.0.0.1' ]
+test self-value-var $hosts/ipaddr[ . = $localhost ]
/files/etc/hosts/1/ipaddr = 127.0.0.1
# Find nodes that have a child named 'ipaddr' with a fixed value
test child-value /files/etc/hosts/*[ipaddr = '127.0.0.1']
/files/etc/hosts/1
-test child-value-var $hosts[ipaddr = '127.0.0.1']
+test child-value-var $hosts[ipaddr = $localhost]
/files/etc/hosts/1
# Find nodes that have a child 'ipaddr' that has no value
@@ -86,7 +86,7 @@ test pam-two-preds-control /files/etc/pam.d/*/*[module = 'system-auth'][type = '
test last /files/etc/hosts/*[ipaddr = "127.0.0.1"]/alias[last()]
/files/etc/hosts/1/alias[3] = galia
-test last-var $hosts[ipaddr = "127.0.0.1"]/alias[last()]
+test last-var $hosts[ipaddr = $localhost]/alias[last()]
/files/etc/hosts/1/alias[3] = galia
# We can get nodes counting from the right with 'last()-N'
--
1.6.0.6
More information about the augeas-devel
mailing list