[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