[augeas-devel] [PATCH 3/4] aug_srun: new API call

lutter at redhat.com lutter at redhat.com
Fri Jul 22 22:37:42 UTC 2011


From: David Lutterkort <lutter at redhat.com>

---
 src/Makefile.am        |    2 +-
 src/augeas.c           |   44 ++-
 src/augeas.h           |   15 +-
 src/augeas_sym.version |    7 +
 src/augrun.c           |  970 ++++++++++++++++++++++++++++++++++++++++++++++
 src/augtool.c          | 1003 +++---------------------------------------------
 src/internal.c         |    4 +-
 src/internal.h         |   10 +-
 tests/Makefile.am      |    5 +-
 tests/run.tests        |  225 +++++++++++
 tests/test-api.c       |   18 +
 tests/test-mv.sh       |    6 +-
 tests/test-run.c       |  247 ++++++++++++
 13 files changed, 1578 insertions(+), 978 deletions(-)
 create mode 100644 src/augrun.c
 create mode 100644 tests/run.tests
 create mode 100644 tests/test-run.c

diff --git a/src/Makefile.am b/src/Makefile.am
index adfd9bf..24f122e 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -18,7 +18,7 @@ bin_PROGRAMS = augtool augparse
 
 include_HEADERS = augeas.h fa.h
 
-libaugeas_la_SOURCES = augeas.h augeas.c pathx.c \
+libaugeas_la_SOURCES = augeas.h augeas.c augrun.c pathx.c \
 	internal.h internal.c \
 	memory.h memory.c ref.h ref.c \
     syntax.c syntax.h parser.y builtin.c lens.c lens.h regexp.c regexp.h \
diff --git a/src/augeas.c b/src/augeas.c
index 6e323ac..4b17384 100644
--- a/src/augeas.c
+++ b/src/augeas.c
@@ -73,7 +73,8 @@ static const char *const errcodes[] = {
     "Lens not found",                                   /* AUG_ENOLENS */
     "Multiple transforms",                              /* AUG_EMXFM */
     "Node has no span info",                            /* AUG_ENOSPAN */
-    "Cannot move node into its descendant"              /* AUG_EMVDESC */
+    "Cannot move node into its descendant",             /* AUG_EMVDESC */
+    "Failed to execute command"                         /* AUG_ECMDRUN */
 };
 
 static void tree_mark_dirty(struct tree *tree) {
@@ -198,21 +199,38 @@ int tree_set_value(struct tree *tree, const char *value) {
     return 0;
 }
 
+static void store_error(const struct augeas *aug, const char *label, const char *value,
+                 int nentries, ...) {
+    va_list ap;
+    struct tree *tree;
+
+    ensure(nentries % 2 == 0, aug);
+    tree = tree_path_cr(aug->origin, 3, s_augeas, s_error, label);
+    if (tree == NULL)
+        return;
+
+    tree_set_value(tree, value);
+
+    va_start(ap, nentries);
+    for (int i=0; i < nentries; i += 2) {
+        char *l = va_arg(ap, char *);
+        char *v = va_arg(ap, char *);
+        struct tree *t = tree_child_cr(tree, l);
+        if (t != NULL)
+            tree_set_value(t, v);
+    }
+    va_end(ap);
+ error:
+    return;
+}
+
 /* Report pathx errors in /augeas/pathx/error */
 static void store_pathx_error(const struct augeas *aug) {
     if (aug->error->code != AUG_EPATHX)
         return;
 
-    struct tree *error =
-        tree_path_cr(aug->origin, 3, s_augeas, s_pathx, s_error);
-    if (error == NULL)
-        return;
-    tree_set_value(error, aug->error->minor_details);
-
-    struct tree *tpos = tree_child_cr(error, s_pos);
-    if (tpos == NULL)
-        return;
-    tree_set_value(tpos, aug->error->details);
+    store_error(aug, s_pathx, aug->error->minor_details,
+                2, s_pos, aug->error->details);
 }
 
 struct pathx *pathx_aug_parse(const struct augeas *aug,
@@ -327,7 +345,7 @@ static void restore_locale(ATTRIBUTE_UNUSED struct augeas *aug) { }
  * that count is 0. That requires that all public functions enclose their
  * work within a matching pair of api_entry/api_exit calls.
  */
-static void api_entry(const struct augeas *aug) {
+void api_entry(const struct augeas *aug) {
     struct error *err = ((struct augeas *) aug)->error;
 
     ((struct augeas *) aug)->api_entries += 1;
@@ -339,7 +357,7 @@ static void api_entry(const struct augeas *aug) {
     save_locale((struct augeas *) aug);
 }
 
-static void api_exit(const struct augeas *aug) {
+void api_exit(const struct augeas *aug) {
     assert(aug->api_entries > 0);
     ((struct augeas *) aug)->api_entries -= 1;
     if (aug->api_entries == 0) {
diff --git a/src/augeas.h b/src/augeas.h
index 1c23d57..6fbff38 100644
--- a/src/augeas.h
+++ b/src/augeas.h
@@ -298,6 +298,18 @@ int aug_load(augeas *aug);
  */
 int aug_print(const augeas *aug, FILE *out, const char *path);
 
+/*
+ * Function: aug_srun
+ *
+ * Run one or more newline-separated commands. The output of the commands
+ * will be printed to OUT.
+ *
+ * Returns:
+ * the number of executed commands on success, -1 on failure, and -2 if a
+ * 'quit' command was encountered
+ */
+int aug_srun(augeas *aug, FILE *out, const char *text);
+
 /* Function: aug_close
  *
  * Close this Augeas instance and free any storage associated with
@@ -321,7 +333,8 @@ typedef enum {
     AUG_ENOLENS,        /* Lens lookup failed */
     AUG_EMXFM,          /* Multiple transforms */
     AUG_ENOSPAN,        /* No span for this node */
-    AUG_EMVDESC         /* Cannot move node into its descendant */
+    AUG_EMVDESC,        /* Cannot move node into its descendant */
+    AUG_ECMDRUN         /* Failed to execute command */
 } aug_errcode_t;
 
 /* Return the error code from the last API call */
diff --git a/src/augeas_sym.version b/src/augeas_sym.version
index 3f065f1..3fc9172 100644
--- a/src/augeas_sym.version
+++ b/src/augeas_sym.version
@@ -39,3 +39,10 @@ AUGEAS_0.12.0 {
     global:
       aug_span;
 } AUGEAS_0.11.0;
+
+AUGEAS_0.14.0 {
+    global:
+      aug_srun;
+      __aug_init_memstream;
+      __aug_close_memstream;
+} AUGEAS_0.12.0;
diff --git a/src/augrun.c b/src/augrun.c
new file mode 100644
index 0000000..636b6fe
--- /dev/null
+++ b/src/augrun.c
@@ -0,0 +1,970 @@
+/*
+ * augrun.c: command interpreter for augeas
+ *
+ * Copyright (C) 2011 David Lutterkort
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
+ *
+ * Author: David Lutterkort <lutter at redhat.com>
+ */
+
+#include <config.h>
+#include "augeas.h"
+#include "internal.h"
+#include "memory.h"
+#include "errcode.h"
+
+#include <ctype.h>
+
+static char *cleanstr(char *path, const char sep) {
+    if (path == NULL || strlen(path) == 0)
+        return path;
+    char *e = path + strlen(path) - 1;
+    while (e >= path && (*e == sep || isspace(*e)))
+        *e-- = '\0';
+    return path;
+}
+
+static char *cleanpath(char *path) {
+    if (path == NULL || strlen(path) == 0)
+        return path;
+    if (STREQ(path, "/"))
+        return path;
+    return cleanstr(path, SEP);
+}
+
+/*
+ * Command handling infrastructure
+ */
+enum command_opt_type {
+    CMD_NONE,
+    CMD_STR,           /* String argument */
+    CMD_PATH           /* Path expression */
+};
+
+struct command_opt_def {
+    bool                  optional; /* Optional or mandatory */
+    enum command_opt_type type;
+    const char           *name;
+    const char           *help;
+};
+
+#define CMD_OPT_DEF_LAST { .type = CMD_NONE, .name = NULL }
+
+struct command {
+    const struct command_def *def;
+    struct command_opt       *opt;
+    struct augeas            *aug;
+    struct error             *error; /* Same as aug->error */
+    FILE                     *out;
+    bool                      quit;
+};
+
+typedef void (*cmd_handler)(struct command*);
+
+struct command_def {
+    const char                   *name;
+    const struct command_opt_def *opts;
+    cmd_handler                   handler;
+    const char                   *synopsis;
+    const char                   *help;
+};
+
+static const struct command_def const *commands[];
+
+static const struct command_def cmd_def_last =
+    { .name = NULL, .opts = NULL, .handler = NULL,
+      .synopsis = NULL, .help = NULL };
+
+struct command_opt {
+    struct command_opt           *next;
+    const struct command_opt_def *def;
+    char                         *value;
+};
+
+static const struct command_def *lookup_cmd_def(const char *name) {
+    for (int i = 0; commands[i]->name != NULL; i++) {
+        if (STREQ(name, commands[i]->name))
+            return commands[i];
+    }
+    return NULL;
+}
+
+static const struct command_opt_def *
+find_def(const struct command *cmd, const char *name) {
+    const struct command_opt_def *def;
+    for (def = cmd->def->opts; def->name != NULL; def++) {
+        if (STREQ(def->name, name))
+            return def;
+    }
+    return NULL;
+}
+
+static struct command_opt *
+find_opt(const struct command *cmd, const char *name) {
+    const struct command_opt_def *def = find_def(cmd, name);
+    assert(def != NULL);
+
+    for (struct command_opt *opt = cmd->opt; opt != NULL; opt = opt->next) {
+        if (opt->def == def)
+            return opt;
+    }
+    assert(def->optional);
+    return NULL;
+}
+
+static const char *arg_value(const struct command *cmd, const char *name) {
+    struct command_opt *opt = find_opt(cmd, name);
+
+    return (opt == NULL) ? NULL : opt->value;
+}
+
+static char *nexttoken(struct command *cmd, char **line, bool path) {
+    char *r, *s;
+    char quot = '\0';
+    int nbracket = 0;
+
+    s = *line;
+
+    while (*s && isblank(*s)) s+= 1;
+    if (*s == '\'' || *s == '"') {
+        quot = *s;
+        s += 1;
+    }
+    r = s;
+    while (*s) {
+        if (*s == '[') nbracket += 1;
+        if (*s == ']') nbracket -= 1;
+        if (nbracket < 0) {
+            ERR_REPORT(cmd, AUG_ECMDRUN, "unmatched [");
+            return NULL;
+        }
+        if ((quot && *s == quot)
+            || (!quot && isblank(*s) && (!path || nbracket == 0)))
+            break;
+        s += 1;
+    }
+    if (*s == '\0' && path && nbracket > 0) {
+        ERR_REPORT(cmd, AUG_ECMDRUN, "unmatched [");
+        return NULL;
+    }
+    if (*s)
+        *s++ = '\0';
+    *line = s;
+    return r;
+}
+
+static struct command_opt *
+make_command_opt(struct command *cmd, const struct command_opt_def *def) {
+    struct command_opt *copt = NULL;
+    int r;
+
+    r = ALLOC(copt);
+    ERR_NOMEM(r < 0, cmd->aug);
+    copt->def = def;
+    list_append(cmd->opt, copt);
+ error:
+    return copt;
+}
+
+static void free_command_opts(struct command *cmd) {
+    struct command_opt *next;
+
+    next = cmd->opt;
+    while (next != NULL) {
+        struct command_opt *del = next;
+        next = del->next;
+        free(del);
+    }
+    cmd->opt = NULL;
+}
+
+static int parseline(struct command *cmd, char *line) {
+    char *tok;
+    int narg = 0, nopt = 0;
+    const struct command_opt_def *def;
+
+    free_command_opts(cmd);
+
+    tok = nexttoken(cmd, &line, false);
+    if (tok == NULL)
+        return -1;
+    cmd->def = lookup_cmd_def(tok);
+    if (cmd->def == NULL) {
+        ERR_REPORT(cmd, AUG_ECMDRUN, "Unknown command '%s'", tok);
+        return -1;
+    }
+
+    for (def = cmd->def->opts; def->name != NULL; def++) {
+        narg += 1;
+        if (def->optional)
+            nopt += 1;
+    }
+
+    int curarg = 0;
+    def = cmd->def->opts;
+    while (*line != '\0') {
+        while (*line && isblank(*line)) line += 1;
+
+        if (curarg >= narg) {
+            ERR_REPORT(cmd, AUG_ECMDRUN,
+                 "Too many arguments. Command %s takes only %d arguments",
+                  cmd->def->name, narg);
+                return -1;
+        }
+
+        struct command_opt *opt = make_command_opt(cmd, def);
+        if (opt == NULL)
+            return -1;
+
+        if (def->type == CMD_PATH) {
+            tok = nexttoken(cmd, &line, true);
+            cleanpath(tok);
+        } else {
+            tok = nexttoken(cmd, &line, false);
+        }
+        if (tok == NULL)
+            return -1;
+        opt->value = tok;
+        curarg += 1;
+        def += 1;
+    }
+
+    if (curarg < narg - nopt) {
+        ERR_REPORT(cmd, AUG_ECMDRUN, "Not enough arguments for %s", cmd->def->name);
+        return -1;
+    }
+
+    return 0;
+}
+
+/*
+ * Commands
+ */
+static void format_desc(const char *d) {
+    printf("    ");
+    for (const char *s = d; *s; s++) {
+        if (*s == '\n')
+            printf("\n   ");
+        else
+            putchar(*s);
+    }
+    printf("\n\n");
+}
+
+static void format_defname(char *buf, const struct command_opt_def *def,
+                           bool mark_optional) {
+    char *p;
+    if (mark_optional && def->optional)
+        p = stpcpy(buf, " [<");
+    else
+        p = stpcpy(buf, " <");
+    for (int i=0; i < strlen(def->name); i++)
+        *p++ = toupper(def->name[i]);
+    *p++ = '>';
+    if (mark_optional && def->optional)
+        *p++ = ']';
+    *p = '\0';
+}
+
+static void cmd_help(struct command *cmd) {
+    const char *name = arg_value(cmd, "command");
+    char buf[100];
+
+    if (name == NULL) {
+        fprintf(cmd->out, "Commands:\n\n");
+        for (int i=0; commands[i]->name != NULL; i++) {
+            const struct command_def *def = commands[i];
+            fprintf(cmd->out, "    %-10s - %s\n", def->name, def->synopsis);
+        }
+        fprintf(cmd->out,
+           "\nType 'help <command>' for more information on a command\n\n");
+    } else {
+        const struct command_def *def = lookup_cmd_def(name);
+        const struct command_opt_def *odef = NULL;
+
+        ERR_THROW(def == NULL, cmd->aug, AUG_ECMDRUN,
+                  "unknown command %s\n", name);
+        fprintf(cmd->out, "  COMMAND\n");
+        fprintf(cmd->out, "    %s - %s\n\n", name, def->synopsis);
+        fprintf(cmd->out, "  SYNOPSIS\n");
+        fprintf(cmd->out, "    %s", name);
+
+        for (odef = def->opts; odef->name != NULL; odef++) {
+            format_defname(buf, odef, true);
+            fprintf(cmd->out, "%s", buf);
+        }
+        fprintf(cmd->out, "\n\n");
+        fprintf(cmd->out, "  DESCRIPTION\n");
+        format_desc(def->help);
+        if (def->opts->name != NULL) {
+            fprintf(cmd->out, "  OPTIONS\n");
+            for (odef = def->opts; odef->name != NULL; odef++) {
+                const char *help = odef->help;
+                if (help == NULL)
+                    help = "";
+                format_defname(buf, odef, false);
+                fprintf(cmd->out, "    %-10s %s\n", buf, help);
+            }
+        }
+        fprintf(cmd->out, "\n");
+    }
+ error:
+    return;
+}
+
+static const struct command_opt_def cmd_help_opts[] = {
+    { .type = CMD_STR, .name = "command", .optional = true,
+      .help = "print help for this command only" },
+    CMD_OPT_DEF_LAST
+};
+
+static const struct command_def cmd_help_def = {
+    .name = "help",
+    .opts = cmd_help_opts,
+    .handler = cmd_help,
+    .synopsis = "print help",
+    .help = "list all commands or print details about one command"
+};
+
+static void cmd_quit(ATTRIBUTE_UNUSED struct command *cmd) {
+    cmd->quit = true;
+}
+
+static const struct command_opt_def cmd_quit_opts[] = {
+    CMD_OPT_DEF_LAST
+};
+
+static const struct command_def cmd_quit_def = {
+    .name = "quit",
+    .opts = cmd_quit_opts,
+    .handler = cmd_quit,
+    .synopsis = "exit the program",
+    .help = "Exit the program"
+};
+
+static char *ls_pattern(struct command *cmd, const char *path) {
+    char *q = NULL;
+    int r;
+
+    if (path[strlen(path)-1] == SEP)
+        r = xasprintf(&q, "%s*", path);
+    else
+        r = xasprintf(&q, "%s/*", path);
+    ERR_NOMEM(r < 0, cmd->aug);
+ error:
+    return q;
+}
+
+static int child_count(struct command *cmd, const char *path) {
+    char *q = ls_pattern(cmd, path);
+    int cnt;
+
+    if (q == NULL)
+        return 0;
+    cnt = aug_match(cmd->aug, q, NULL);
+    if (HAS_ERR(cmd))
+        cnt = -1;
+    free(q);
+    return cnt;
+}
+
+static void cmd_ls(struct command *cmd) {
+    int cnt = 0;
+    char *path = NULL;
+    char **paths = NULL;
+
+    path = ls_pattern(cmd, arg_value(cmd, "path"));
+    ERR_BAIL(cmd);
+
+    cnt = aug_match(cmd->aug, path, &paths);
+    ERR_BAIL(cmd);
+    for (int i=0; i < cnt; i++) {
+        const char *val;
+        const char *basnam = strrchr(paths[i], SEP);
+        int dir = child_count(cmd, paths[i]);
+        aug_get(cmd->aug, paths[i], &val);
+        ERR_BAIL(cmd);
+        basnam = (basnam == NULL) ? paths[i] : basnam + 1;
+        if (val == NULL)
+            val = "(none)";
+        fprintf(cmd->out, "%s%s= %s\n", basnam, dir ? "/ " : " ", val);
+        FREE(paths[i]);
+    }
+ error:
+    free(path);
+    for (int i=0; i < cnt; i++)
+        FREE(paths[i]);
+    free(paths);
+}
+
+static const struct command_opt_def cmd_ls_opts[] = {
+    { .type = CMD_PATH, .name = "path", .optional = false,
+      .help = "the node whose children to list" },
+    CMD_OPT_DEF_LAST
+};
+
+static const struct command_def cmd_ls_def = {
+    .name = "ls",
+    .opts = cmd_ls_opts,
+    .handler = cmd_ls,
+    .synopsis = "list children of a node",
+    .help = "list the direct children of a node"
+};
+
+static void cmd_match(struct command *cmd) {
+    int cnt = 0;
+    const char *pattern = arg_value(cmd, "path");
+    const char *value = arg_value(cmd, "value");
+    char **matches = NULL;
+    bool filter = (value != NULL) && (strlen(value) > 0);
+
+    cnt = aug_match(cmd->aug, pattern, &matches);
+    ERR_BAIL(cmd);
+    ERR_THROW(cnt < 0, cmd->aug, AUG_ECMDRUN,
+              "  (error matching %s)\n", pattern);
+    if (cnt == 0) {
+        fprintf(cmd->out, "  (no matches)\n");
+        goto done;
+    }
+
+    for (int i=0; i < cnt; i++) {
+        const char *val;
+        aug_get(cmd->aug, matches[i], &val);
+        ERR_BAIL(cmd);
+        if (val == NULL)
+            val = "(none)";
+        if (filter) {
+            if (STREQ(value, val))
+                fprintf(cmd->out, "%s\n", matches[i]);
+        } else {
+            fprintf(cmd->out, "%s = %s\n", matches[i], val);
+        }
+    }
+ error:
+ done:
+    for (int i=0; i < cnt; i++)
+        free(matches[i]);
+    free(matches);
+}
+
+static const struct command_opt_def cmd_match_opts[] = {
+    { .type = CMD_PATH, .name = "path", .optional = false,
+      .help = "the path expression to match" },
+    { .type = CMD_STR, .name = "value", .optional = true,
+      .help = "only show matches with this value" },
+    CMD_OPT_DEF_LAST
+};
+
+static const struct command_def cmd_match_def = {
+    .name = "match",
+    .opts = cmd_match_opts,
+    .handler = cmd_match,
+    .synopsis = "print matches for a path expression",
+    .help = "Find all paths that match the path expression PATH. "
+            "If VALUE is given,\n only the matching paths whose value equals "
+            "VALUE are printed"
+};
+
+static void cmd_rm(struct command *cmd) {
+    int cnt;
+    const char *path = arg_value(cmd, "path");
+    cnt = aug_rm(cmd->aug, path);
+    if (! HAS_ERR(cmd))
+        fprintf(cmd->out, "rm : %s %d\n", path, cnt);
+}
+
+static const struct command_opt_def cmd_rm_opts[] = {
+    { .type = CMD_PATH, .name = "path", .optional = false,
+      .help = "remove all nodes matching this path expression" },
+    CMD_OPT_DEF_LAST
+};
+
+static const struct command_def cmd_rm_def = {
+    .name = "rm",
+    .opts = cmd_rm_opts,
+    .handler = cmd_rm,
+    .synopsis = "delete nodes and subtrees",
+    .help = "Delete PATH and all its children from the tree"
+};
+
+static void cmd_mv(struct command *cmd) {
+    const char *src = arg_value(cmd, "src");
+    const char *dst = arg_value(cmd, "dst");
+    int r;
+
+    r = aug_mv(cmd->aug, src, dst);
+    if (r < 0)
+        ERR_REPORT(cmd, AUG_ECMDRUN,
+                   "Moving %s to %s failed", src, dst);
+}
+
+static const struct command_opt_def cmd_mv_opts[] = {
+    { .type = CMD_PATH, .name = "src", .optional = false,
+      .help = "the tree to move" },
+    { .type = CMD_PATH, .name = "dst", .optional = false,
+      .help = "where to put the source tree" },
+    CMD_OPT_DEF_LAST
+};
+
+static const struct command_def cmd_mv_def = {
+    .name = "mv",
+    .opts = cmd_mv_opts,
+    .handler = cmd_mv,
+    .synopsis = "move a subtree",
+    .help = "Move node  SRC to DST.  SRC must match  exactly one node in  "
+    "the tree.\n DST  must either  match  exactly one  node  in the  tree,  "
+    "or may  not\n exist  yet. If  DST exists  already, it  and all  its  "
+    "descendants are\n deleted.  If  DST  does  not   exist  yet,  it  and  "
+    "all  its  missing\n ancestors are created."
+};
+
+static void cmd_set(struct command *cmd) {
+    const char *path = arg_value(cmd, "path");
+    const char *val = arg_value(cmd, "value");
+    int r;
+
+    r = aug_set(cmd->aug, path, val);
+    if (r < 0)
+        ERR_REPORT(cmd, AUG_ECMDRUN, "Setting %s failed", path);
+}
+
+static const struct command_opt_def cmd_set_opts[] = {
+    { .type = CMD_PATH, .name = "path", .optional = false,
+      .help = "set the value of this node" },
+    { .type = CMD_STR, .name = "value", .optional = false,
+      .help = "the new value for the node" },
+    CMD_OPT_DEF_LAST
+};
+
+static const struct command_def cmd_set_def = {
+    .name = "set",
+    .opts = cmd_set_opts,
+    .handler = cmd_set,
+    .synopsis = "set the value of a node",
+    .help = "Associate VALUE with PATH.  If PATH is not in the tree yet, "
+    "it and all\n its ancestors will be created. These new tree entries "
+    "will appear last\n amongst their siblings"
+};
+
+static void cmd_setm(struct command *cmd) {
+    const char *base = arg_value(cmd, "base");
+    const char *sub  = arg_value(cmd, "sub");
+    const char *val  = arg_value(cmd, "value");
+
+    aug_setm(cmd->aug, base, sub, val);
+}
+
+static const struct command_opt_def cmd_setm_opts[] = {
+    { .type = CMD_PATH, .name = "base", .optional = false,
+      .help = "the base node" },
+    { .type = CMD_PATH, .name = "sub", .optional = false,
+      .help = "the subtree relative to the base" },
+    { .type = CMD_STR, .name = "value", .optional = false,
+      .help = "the value for the nodes" },
+    CMD_OPT_DEF_LAST
+};
+
+static const struct command_def cmd_setm_def = {
+    .name = "setm",
+    .opts = cmd_setm_opts,
+    .handler = cmd_setm,
+    .synopsis = "set the value of multiple nodes",
+    .help = "Set multiple nodes in one operation.  Find or create a node"
+    " matching SUB\n by interpreting SUB as a  path expression relative"
+    " to each node matching\n BASE. If SUB is '.', the nodes matching "
+    "BASE will be modified."
+};
+
+static void cmd_clearm(struct command *cmd) {
+    const char *base = arg_value(cmd, "base");
+    const char *sub  = arg_value(cmd, "sub");
+
+    aug_setm(cmd->aug, base, sub, NULL);
+}
+
+static const struct command_opt_def cmd_clearm_opts[] = {
+    { .type = CMD_PATH, .name = "base", .optional = false,
+      .help = "the base node" },
+    { .type = CMD_PATH, .name = "sub", .optional = false,
+      .help = "the subtree relative to the base" },
+    CMD_OPT_DEF_LAST
+};
+
+static const struct command_def cmd_clearm_def = {
+    .name = "clearm",
+    .opts = cmd_clearm_opts,
+    .handler = cmd_clearm,
+    .synopsis = "clear the value of multiple nodes",
+    .help = "Clear multiple nodes values in one operation. Find or create a"
+    " node matching SUB\n by interpreting SUB as a path expression relative"
+    " to each node matching\n BASE. If SUB is '.', the nodes matching "
+    "BASE will be modified."
+};
+
+static void cmd_span(struct command *cmd) {
+    const char *path = arg_value(cmd, "path");
+    int r;
+    uint label_start, label_end, value_start, value_end, span_start, span_end;
+    char *filename = NULL;
+    const char *option = NULL;
+
+    if (! streqv(AUG_DISABLE, option)) {
+        ERR_REPORT(cmd, AUG_ECMDRUN,
+                   "Span is not enabled. To enable, run the commands:\n"
+                   "    set %s %s\n    rm %s\n    load\n",
+                   AUGEAS_SPAN_OPTION, AUG_ENABLE, AUGEAS_FILES_TREE);
+        return;
+    } else if (streqv(AUG_ENABLE, option)) {
+        ERR_REPORT(cmd, AUG_ECMDRUN,
+                   "option %s must be %s or %s\n", AUGEAS_SPAN_OPTION,
+                   AUG_ENABLE, AUG_DISABLE);
+        return;
+    }
+    r = aug_span(cmd->aug, path, &filename, &label_start, &label_end,
+                 &value_start, &value_end, &span_start, &span_end);
+    ERR_THROW(r == -1, cmd, AUG_ECMDRUN, "failed to retrieve span");
+
+    fprintf(cmd->out, "%s label=(%i:%i) value=(%i:%i) span=(%i,%i)\n",
+            filename, label_start, label_end,
+            value_start, value_end, span_start, span_end);
+ error:
+    free(filename);
+}
+
+static const struct command_opt_def cmd_span_opts[] = {
+    { .type = CMD_PATH, .name = "path", .optional = false,
+      .help = "node path" },
+    CMD_OPT_DEF_LAST
+};
+
+static const struct command_def cmd_span_def = {
+    .name = "span",
+    .opts = cmd_span_opts,
+    .handler = cmd_span,
+    .synopsis = "get the filename, label and value position in the text of this node",
+    .help = "get the filename, label and value position in the text of this node"
+};
+
+static void cmd_defvar(struct command *cmd) {
+    const char *name = arg_value(cmd, "name");
+    const char *path = arg_value(cmd, "expr");
+
+    aug_defvar(cmd->aug, name, path);
+}
+
+static const struct command_opt_def cmd_defvar_opts[] = {
+    { .type = CMD_STR, .name = "name", .optional = false,
+      .help = "the name of the variable" },
+    { .type = CMD_PATH, .name = "expr", .optional = false,
+      .help = "the path expression" },
+    CMD_OPT_DEF_LAST
+};
+
+static const struct command_def cmd_defvar_def = {
+    .name = "defvar",
+    .opts = cmd_defvar_opts,
+    .handler = cmd_defvar,
+    .synopsis = "set a variable",
+    .help = "Evaluate EXPR and set the variable NAME to the resulting "
+    "nodeset. The\n variable can be used in path expressions as $NAME.  "
+    "Note that EXPR is\n evaluated when the variable is defined, not when "
+    "it is used."
+};
+
+static void cmd_defnode(struct command *cmd) {
+    const char *name = arg_value(cmd, "name");
+    const char *path = arg_value(cmd, "expr");
+    const char *value = arg_value(cmd, "value");
+
+    /* Our simple minded line parser treats non-existant and empty values
+     * the same. We choose to take the empty string to mean NULL */
+    if (value != NULL && strlen(value) == 0)
+        value = NULL;
+    aug_defnode(cmd->aug, name, path, value, NULL);
+}
+
+static const struct command_opt_def cmd_defnode_opts[] = {
+    { .type = CMD_STR, .name = "name", .optional = false,
+      .help = "the name of the variable" },
+    { .type = CMD_PATH, .name = "expr", .optional = false,
+      .help = "the path expression" },
+    { .type = CMD_STR, .name = "value", .optional = true,
+      .help = "the value for the new node" },
+    CMD_OPT_DEF_LAST
+};
+
+static const struct command_def cmd_defnode_def = {
+    .name = "defnode",
+    .opts = cmd_defnode_opts,
+    .handler = cmd_defnode,
+    .synopsis = "set a variable, possibly creating a new node",
+    .help = "Define the variable NAME to the result of evalutating EXPR, "
+    " which must\n be a nodeset.  If no node matching EXPR exists yet,  one "
+    "is created and\n NAME will refer to it.   When a node is created and "
+    "VALUE is given, the\n new node's value is set to VALUE."
+};
+
+static void cmd_clear(struct command *cmd) {
+    const char *path = arg_value(cmd, "path");
+    int r;
+
+    r = aug_set(cmd->aug, path, NULL);
+    if (r < 0)
+        ERR_REPORT(cmd, AUG_ECMDRUN, "Clearing %s failed", path);
+}
+
+static const struct command_opt_def cmd_clear_opts[] = {
+    { .type = CMD_PATH, .name = "path", .optional = false,
+      .help = "clear the value of this node" },
+    CMD_OPT_DEF_LAST
+};
+
+static const struct command_def cmd_clear_def = {
+    .name = "clear",
+    .opts = cmd_clear_opts,
+    .handler = cmd_clear,
+    .synopsis = "clear the value of a node",
+    .help = "Set the value for PATH to NULL. If PATH is not in the tree yet, "
+    "it and\n all its ancestors will be created.  These new tree entries "
+    "will appear\n last amongst their siblings"
+};
+
+static void cmd_get(struct command *cmd) {
+    const char *path = arg_value(cmd, "path");
+    const char *val;
+    int r;
+
+    r = aug_get(cmd->aug, path, &val);
+    ERR_RET(cmd);
+    fprintf(cmd->out, "%s", path);
+    if (r == 0) {
+        fprintf(cmd->out, " (o)\n");
+    } else if (val == NULL) {
+        fprintf(cmd->out, " (none)\n");
+    } else {
+        fprintf(cmd->out, " = %s\n", val);
+    }
+}
+
+static const struct command_opt_def cmd_get_opts[] = {
+    { .type = CMD_PATH, .name = "path", .optional = false,
+      .help = "get the value of this node" },
+    CMD_OPT_DEF_LAST
+};
+
+static const struct command_def cmd_get_def = {
+    .name = "get",
+    .opts = cmd_get_opts,
+    .handler = cmd_get,
+    .synopsis = "get the value of a node",
+    .help = "Get and print the value associated with PATH"
+};
+
+static void cmd_print(struct command *cmd) {
+    const char *path = arg_value(cmd, "path");
+
+    aug_print(cmd->aug, cmd->out, path);
+}
+
+static const struct command_opt_def cmd_print_opts[] = {
+    { .type = CMD_PATH, .name = "path", .optional = true,
+      .help = "print this subtree" },
+    CMD_OPT_DEF_LAST
+};
+
+static const struct command_def cmd_print_def = {
+    .name = "print",
+    .opts = cmd_print_opts,
+    .handler = cmd_print,
+    .synopsis = "print a subtree",
+    .help = "Print entries in the tree.  If PATH is given, printing starts there,\n otherwise the whole tree is printed"
+};
+
+static void cmd_save(struct command *cmd) {
+    int r;
+    r = aug_save(cmd->aug);
+    if (r == -1) {
+        ERR_REPORT(cmd, AUG_ECMDRUN,
+                   "saving failed (run 'print /augeas//error' for details)");
+    } else {
+        r = aug_match(cmd->aug, "/augeas/events/saved", NULL);
+        if (r > 0) {
+            fprintf(cmd->out, "Saved %d file(s)\n", r);
+        }
+    }
+}
+
+static const struct command_opt_def cmd_save_opts[] = {
+    CMD_OPT_DEF_LAST
+};
+
+static const struct command_def cmd_save_def = {
+    .name = "save",
+    .opts = cmd_save_opts,
+    .handler = cmd_save,
+    .synopsis = "save all pending changes",
+    .help = "Save all pending changes to disk. How exactly that is done depends on\n the value of the node /augeas/save, which can be changed by the user.\n The possible values for it are\n \n   noop      - do not write files; useful for finding errors that\n               might happen during a save\n   backup    - save the original file in a file by appending the extension\n               '.augsave' and overwrite the original with new content\n   newfile   - leave the original file untouched and write new content to\n               a file with extension '.augnew' next to the original file\n   overwrite - overwrite the original file with new content\n \n Save always tries to save all files for which entries in the tree have\n changed. When saving fails, some files will be written.  Details about\n why a save failed can by found by issuing the command 'print\n /augeas//error' (note the double slash)"
+};
+
+static void cmd_load(struct command *cmd) {
+    int r;
+    r = aug_load(cmd->aug);
+    if (r == -1) {
+        ERR_REPORT(cmd, AUG_ECMDRUN,
+                   "loading failed (run 'print /augeas//error' for details)");
+    }
+}
+
+static const struct command_opt_def cmd_load_opts[] = {
+    CMD_OPT_DEF_LAST
+};
+
+static const struct command_def cmd_load_def = {
+    .name = "load",
+    .opts = cmd_load_opts,
+    .handler = cmd_load,
+    .synopsis = "(re)load files under /files",
+    .help = "Load files  according to the  transforms in /augeas/load.  "
+    "A transform\n Foo  is  represented  with  a  subtree  /augeas/load/Foo."
+    "   Underneath\n /augeas/load/Foo, one node labelled  'lens' must exist,"
+    " whose value is\n the  fully  qualified name  of  a  lens,  for example  "
+    "'Foo.lns',  and\n multiple nodes 'incl' and 'excl' whose values are "
+    "globs that determine\n which files are  transformed by that lens. It "
+    "is an  error if one file\n can be processed by multiple transforms."
+};
+
+static void cmd_ins(struct command *cmd) {
+    const char *label = arg_value(cmd, "label");
+    const char *where = arg_value(cmd, "where");
+    const char *path = arg_value(cmd, "path");
+    int before;
+
+    if (STREQ(where, "after"))
+        before = 0;
+    else if (STREQ(where, "before"))
+        before = 1;
+    else {
+        ERR_REPORT(cmd, AUG_ECMDRUN,
+          "the <WHERE> argument for ins must be either 'before' or 'after'.");
+        return;
+    }
+
+    aug_insert(cmd->aug, path, label, before);
+}
+
+static const struct command_opt_def cmd_ins_opts[] = {
+    { .type = CMD_STR, .name = "label", .optional = false,
+      .help = "the label for the new node" },
+    { .type = CMD_STR, .name = "where", .optional = false,
+      .help = "either 'before' or 'after'" },
+    { .type = CMD_PATH, .name = "path", .optional = false,
+      .help = "the node before/after which to insert" },
+    CMD_OPT_DEF_LAST
+};
+
+static const struct command_def cmd_ins_def = {
+    .name = "ins",
+    .opts = cmd_ins_opts,
+    .handler = cmd_ins,
+    .synopsis = "insert new node before/after and existing node",
+    .help = "Insert a new node with label LABEL right before or after "
+    "PATH into the\n tree. WHERE must be either 'before' or 'after'."
+};
+
+static const struct command_def const *commands[] = {
+    &cmd_quit_def,
+    &cmd_clear_def,
+    &cmd_defnode_def,
+    &cmd_defvar_def,
+    &cmd_get_def,
+    &cmd_ins_def,
+    &cmd_load_def,
+    &cmd_ls_def,
+    &cmd_match_def,
+    &cmd_mv_def,
+    &cmd_print_def,
+    &cmd_rm_def,
+    &cmd_save_def,
+    &cmd_set_def,
+    &cmd_setm_def,
+    &cmd_clearm_def,
+    &cmd_span_def,
+    &cmd_help_def,
+    &cmd_def_last
+};
+
+int aug_srun(augeas *aug, FILE *out, const char *text) {
+    char *line = NULL;
+    const char *eol;
+    struct command cmd;
+    int result = 0;
+
+    api_entry(aug);
+
+    if (text == NULL)
+        goto done;
+
+    MEMZERO(&cmd, 1);
+    cmd.aug = aug;
+    cmd.error = aug->error;
+    cmd.out = out;
+
+    while (*text != '\0' && result >= 0) {
+        eol = strchrnul(text, '\n');
+        while (isspace(*text) && text < eol) text++;
+        if (*text == '\0')
+            break;
+        if (*text == '#' || text == eol) {
+            text = (*eol == '\0') ? eol : eol + 1;
+            continue;
+        }
+
+        line = strndup(text, eol - text);
+        ERR_NOMEM(line == NULL, aug);
+
+        if (parseline(&cmd, line) == 0) {
+            cmd.def->handler(&cmd);
+            result += 1;
+        } else {
+            result = -1;
+        }
+
+        ERR_BAIL(aug);
+        if (result >= 0 && cmd.quit) {
+            result = -2;
+            goto done;
+        }
+        FREE(line);
+        text = (*eol == '\0') ? eol : eol + 1;
+    }
+ done:
+    FREE(line);
+
+    api_exit(aug);
+    return result;
+ error:
+    result = -1;
+    goto done;
+}
+
+/*
+ * Local variables:
+ *  indent-tabs-mode: nil
+ *  c-indent-level: 4
+ *  c-basic-offset: 4
+ *  tab-width: 4
+ * End:
+ */
diff --git a/src/augtool.c b/src/augtool.c
index 4e86eff..9164bc7 100644
--- a/src/augtool.c
+++ b/src/augtool.c
@@ -38,7 +38,6 @@
 
 /* Global variables */
 
-static const struct command_def const *commands[];
 static augeas *aug = NULL;
 static const char *const progname = "augtool";
 static unsigned int flags = AUG_NONE;
@@ -52,25 +51,11 @@ bool interactive = false;
 /* History file is ~/.augeas/history */
 char *history_file = NULL;
 
+#define AUGTOOL_PROMPT "augtool> "
+
 /*
  * General utilities
  */
-static char *cleanstr(char *path, const char sep) {
-    if (path == NULL || strlen(path) == 0)
-        return path;
-    char *e = path + strlen(path) - 1;
-    while (e >= path && (*e == sep || isspace(*e)))
-        *e-- = '\0';
-    return path;
-}
-
-static char *cleanpath(char *path) {
-    if (path == NULL || strlen(path) == 0)
-        return path;
-    if (STREQ(path, "/"))
-        return path;
-    return cleanstr(path, SEP);
-}
 
 /* Not static, since prototype is in internal.h */
 int xasprintf(char **strp, const char *format, ...) {
@@ -85,900 +70,27 @@ int xasprintf(char **strp, const char *format, ...) {
   return result;
 }
 
-/*
- * Command handling infrastructure
- */
-enum command_opt_type {
-    CMD_NONE,
-    CMD_STR,           /* String argument */
-    CMD_PATH           /* Path expression */
-};
-
-struct command_opt_def {
-    bool                  optional; /* Optional or mandatory */
-    enum command_opt_type type;
-    const char           *name;
-    const char           *help;
-};
-
-#define CMD_OPT_DEF_LAST { .type = CMD_NONE, .name = NULL }
-
-#define AUGTOOL_PROMPT "augtool> "
-
-/* Handlers return one of these */
-enum command_result {
-    CMD_RES_OK,
-    CMD_RES_ERR,
-    CMD_RES_ENOMEM,
-    CMD_RES_QUIT
-};
-
-struct command {
-    const struct command_def *def;
-    struct command_opt       *opt;
-    enum command_result       result;
-};
-
-typedef void (*cmd_handler)(struct command*);
-
-struct command_def {
-    const char                   *name;
-    const struct command_opt_def *opts;
-    cmd_handler                   handler;
-    const char                   *synopsis;
-    const char                   *help;
-};
-
-static const struct command_def cmd_def_last =
-    { .name = NULL, .opts = NULL, .handler = NULL,
-      .synopsis = NULL, .help = NULL };
-
-struct command_opt {
-    struct command_opt           *next;
-    const struct command_opt_def *def;
-    char                         *value;
-};
-
-static const struct command_def *lookup_cmd_def(const char *name) {
-    for (int i = 0; commands[i]->name != NULL; i++) {
-        if (STREQ(name, commands[i]->name))
-            return commands[i];
-    }
-    return NULL;
-}
-
-static const struct command_opt_def *
-find_def(const struct command *cmd, const char *name) {
-    const struct command_opt_def *def;
-    for (def = cmd->def->opts; def->name != NULL; def++) {
-        if (STREQ(def->name, name))
-            return def;
-    }
-    return NULL;
-}
-
-static struct command_opt *
-find_opt(const struct command *cmd, const char *name) {
-    const struct command_opt_def *def = find_def(cmd, name);
-    assert(def != NULL);
-
-    for (struct command_opt *opt = cmd->opt; opt != NULL; opt = opt->next) {
-        if (opt->def == def)
-            return opt;
-    }
-    assert(def->optional);
-    return NULL;
-}
-
-static const char *arg_value(const struct command *cmd, const char *name) {
-    struct command_opt *opt = find_opt(cmd, name);
-
-    return (opt == NULL) ? NULL : opt->value;
-}
-
-static char *nexttoken(char **line, bool path) {
-    char *r, *s;
-    char quot = '\0';
-    int nbracket = 0;
-
-    s = *line;
-
-    while (*s && isblank(*s)) s+= 1;
-    if (*s == '\'' || *s == '"') {
-        quot = *s;
-        s += 1;
-    }
-    r = s;
-    while (*s) {
-        if (*s == '[') nbracket += 1;
-        if (*s == ']') nbracket -= 1;
-        if (nbracket < 0) {
-            fprintf(stderr, "unmatched [\n");
-            return NULL;
-        }
-        if ((quot && *s == quot)
-            || (!quot && isblank(*s) && (!path || nbracket == 0)))
-            break;
-        s += 1;
-    }
-    if (*s == '\0' && path && nbracket > 0) {
-        fprintf(stderr, "unmatched [\n");
-        return NULL;
-    }
-    if (*s)
-        *s++ = '\0';
-    *line = s;
-    return r;
-}
-
-static struct command_opt *
-make_command_opt(struct command *cmd, const struct command_opt_def *def) {
-    struct command_opt *copt = NULL;
-    if (ALLOC(copt) < 0) {
-        fprintf(stderr, "Allocation failed\n");
-        return NULL;
-    }
-    copt->def = def;
-    list_append(cmd->opt, copt);
-    return copt;
-}
-
-static int parseline(struct command *cmd, char *line) {
-    char *tok;
-    int narg = 0, nopt = 0;
-    const struct command_opt_def *def;
-
-    MEMZERO(cmd, 1);
-    tok = nexttoken(&line, false);
-    if (tok == NULL)
-        return -1;
-    cmd->def = lookup_cmd_def(tok);
-    if (cmd->def == NULL) {
-        fprintf(stderr, "Unknown command '%s'\n", tok);
-        return -1;
-    }
-
-    for (def = cmd->def->opts; def->name != NULL; def++) {
-        narg += 1;
-        if (def->optional)
-            nopt += 1;
-    }
-
-    int curarg = 0;
-    def = cmd->def->opts;
-    while (*line != '\0') {
-        while (*line && isblank(*line)) line += 1;
-
-        if (curarg >= narg) {
-            fprintf(stderr,
-                 "Too many arguments. Command %s takes only %d arguments\n",
-                  cmd->def->name, narg);
-                return -1;
-        }
-
-        struct command_opt *opt = make_command_opt(cmd, def);
-        if (opt == NULL)
-            return -1;
-
-        if (def->type == CMD_PATH) {
-            tok = nexttoken(&line, true);
-            cleanpath(tok);
-        } else {
-            tok = nexttoken(&line, false);
-        }
-        if (tok == NULL)
-            return -1;
-        opt->value = tok;
-        curarg += 1;
-        def += 1;
-    }
-
-    if (curarg < narg - nopt) {
-        fprintf(stderr, "Not enough arguments for %s\n", cmd->def->name);
-        return -1;
-    }
-
-    return 0;
-}
-
-static int err_check(struct command *cmd) {
-    if (aug_error(aug) != AUG_NOERROR) {
-        const char *minor = aug_error_minor_message(aug);
-        const char *details = aug_error_details(aug);
-
-        cmd->result = CMD_RES_ERR;
-        fprintf(stderr, "error: %s\n", aug_error_message(aug));
-        if (minor != NULL)
-            fprintf(stderr, "error: %s\n", minor);
-        if (details != NULL)
-            fprintf(stderr, "error: %s\n", details);
-        return -1;
-    }
-    return 0;
-}
-
-#define ERR_CHECK(cmd) if (err_check(cmd) < 0) return;
-
-#define ERR_RET(cmd) if ((cmd)->result != CMD_RES_OK) return;
-
-#define ERR_EXIT(cond, cmd, code)                \
-    if (cond) {                                  \
-        (cmd)->result = code;                    \
-        return;                                  \
-    }
-
-/*
- * Commands
- */
-static void format_desc(const char *d) {
-    printf("    ");
-    for (const char *s = d; *s; s++) {
-        if (*s == '\n')
-            printf("\n   ");
-        else
-            putchar(*s);
-    }
-    printf("\n\n");
-}
-
-static void format_defname(char *buf, const struct command_opt_def *def,
-                           bool mark_optional) {
-    char *p;
-    if (mark_optional && def->optional)
-        p = stpcpy(buf, " [<");
-    else
-        p = stpcpy(buf, " <");
-    for (int i=0; i < strlen(def->name); i++)
-        *p++ = toupper(def->name[i]);
-    *p++ = '>';
-    if (mark_optional && def->optional)
-        *p++ = ']';
-    *p = '\0';
-}
-
-static void cmd_help(struct command *cmd) {
-    const char *name = arg_value(cmd, "command");
-    char buf[100];
-
-    if (name == NULL) {
-        printf("Commands:\n\n");
-        for (int i=0; commands[i]->name != NULL; i++) {
-            const struct command_def *def = commands[i];
-            printf("    %-10s - %s\n", def->name, def->synopsis);
-        }
-        printf("\nType 'help <command>' for more information on a command\n\n");
-    } else {
-        const struct command_def *def = lookup_cmd_def(name);
-        const struct command_opt_def *odef = NULL;
-        if (def == NULL) {
-            fprintf(stderr, "unknown command %s\n", name);
-            cmd->result = CMD_RES_ERR;
-            return;
-        }
-        printf("  COMMAND\n");
-        printf("    %s - %s\n\n", name, def->synopsis);
-        printf("  SYNOPSIS\n");
-        printf("    %s", name);
-
-        for (odef = def->opts; odef->name != NULL; odef++) {
-            format_defname(buf, odef, true);
-            printf("%s", buf);
-        }
-        printf("\n\n");
-        printf("  DESCRIPTION\n");
-        format_desc(def->help);
-        if (def->opts->name != NULL) {
-            printf("  OPTIONS\n");
-            for (odef = def->opts; odef->name != NULL; odef++) {
-                const char *help = odef->help;
-                if (help == NULL)
-                    help = "";
-                format_defname(buf, odef, false);
-                printf("    %-10s %s\n", buf, help);
-            }
-        }
-        printf("\n");
-    }
-}
-
-static const struct command_opt_def cmd_help_opts[] = {
-    { .type = CMD_STR, .name = "command", .optional = true,
-      .help = "print help for this command only" },
-    CMD_OPT_DEF_LAST
-};
-
-static const struct command_def cmd_help_def = {
-    .name = "help",
-    .opts = cmd_help_opts,
-    .handler = cmd_help,
-    .synopsis = "print help",
-    .help = "list all commands or print details about one command"
-};
-
-static void cmd_quit(ATTRIBUTE_UNUSED struct command *cmd) {
-    cmd->result = CMD_RES_QUIT;
-}
-
-static const struct command_opt_def cmd_quit_opts[] = {
-    CMD_OPT_DEF_LAST
-};
-
-static const struct command_def cmd_quit_def = {
-    .name = "quit",
-    .opts = cmd_quit_opts,
-    .handler = cmd_quit,
-    .synopsis = "exit the program",
-    .help = "Exit the program"
-};
-
-static char *ls_pattern(struct command *cmd, const char *path) {
-    char *q;
+static int child_count(const char *path) {
+    char *pat = NULL;
     int r;
 
     if (path[strlen(path)-1] == SEP)
-        r = asprintf(&q, "%s*", path);
+        r = asprintf(&pat, "%s*", path);
     else
-        r = asprintf(&q, "%s/*", path);
-    if (r < 0) {
-        cmd->result = CMD_RES_ENOMEM;
-        return NULL;
-    }
-    return q;
-}
-
-static int child_count(struct command *cmd, const char *path) {
-    char *q = ls_pattern(cmd, path);
-    int cnt;
-
-    if (q == NULL)
-        return 0;
-    cnt = aug_match(aug, q, NULL);
-    err_check(cmd);
-    free(q);
-    return cnt;
-}
-
-static void cmd_ls(struct command *cmd) {
-    int cnt;
-    const char *path = arg_value(cmd, "path");
-    char **paths;
-
-    path = ls_pattern(cmd, path);
-    if (path == NULL)
-        ERR_RET(cmd);
-    cnt = aug_match(aug, path, &paths);
-    ERR_CHECK(cmd);
-    for (int i=0; i < cnt; i++) {
-        const char *val;
-        const char *basnam = strrchr(paths[i], SEP);
-        int dir = child_count(cmd, paths[i]);
-        aug_get(aug, paths[i], &val);
-        err_check(cmd);
-        basnam = (basnam == NULL) ? paths[i] : basnam + 1;
-        if (val == NULL)
-            val = "(none)";
-        printf("%s%s= %s\n", basnam, dir ? "/ " : " ", val);
-        free(paths[i]);
-    }
-    if (cnt > 0)
-        free(paths);
-}
-
-static const struct command_opt_def cmd_ls_opts[] = {
-    { .type = CMD_PATH, .name = "path", .optional = false,
-      .help = "the node whose children to list" },
-    CMD_OPT_DEF_LAST
-};
-
-static const struct command_def cmd_ls_def = {
-    .name = "ls",
-    .opts = cmd_ls_opts,
-    .handler = cmd_ls,
-    .synopsis = "list children of a node",
-    .help = "list the direct children of a node"
-};
-
-static void cmd_match(struct command *cmd) {
-    int cnt;
-    const char *pattern = arg_value(cmd, "path");
-    const char *value = arg_value(cmd, "value");
-    char **matches;
-    bool filter = (value != NULL) && (strlen(value) > 0);
-
-    cnt = aug_match(aug, pattern, &matches);
-    err_check(cmd);
-    if (cnt < 0) {
-        printf("  (error matching %s)\n", pattern);
-        cmd->result = CMD_RES_ERR;
-        goto done;
-    }
-    if (cnt == 0) {
-        printf("  (no matches)\n");
-        goto done;
-    }
-
-    for (int i=0; i < cnt; i++) {
-        const char *val;
-        aug_get(aug, matches[i], &val);
-        err_check(cmd);
-        if (val == NULL)
-            val = "(none)";
-        if (filter) {
-            if (STREQ(value, val))
-                printf("%s\n", matches[i]);
-        } else {
-            printf("%s = %s\n", matches[i], val);
-        }
-    }
- done:
-    for (int i=0; i < cnt; i++)
-        free(matches[i]);
-    free(matches);
-}
-
-static const struct command_opt_def cmd_match_opts[] = {
-    { .type = CMD_PATH, .name = "path", .optional = false,
-      .help = "the path expression to match" },
-    { .type = CMD_STR, .name = "value", .optional = true,
-      .help = "only show matches with this value" },
-    CMD_OPT_DEF_LAST
-};
-
-static const struct command_def cmd_match_def = {
-    .name = "match",
-    .opts = cmd_match_opts,
-    .handler = cmd_match,
-    .synopsis = "print matches for a path expression",
-    .help = "Find all paths that match the path expression PATH. "
-            "If VALUE is given,\n only the matching paths whose value equals "
-            "VALUE are printed"
-};
-
-static void cmd_rm(struct command *cmd) {
-    int cnt;
-    const char *path = arg_value(cmd, "path");
-    if (echo)
-        printf("rm : %s", path);
-    cnt = aug_rm(aug, path);
-    err_check(cmd);
-    if (echo)
-        printf(" %d\n", cnt);
-}
-
-static const struct command_opt_def cmd_rm_opts[] = {
-    { .type = CMD_PATH, .name = "path", .optional = false,
-      .help = "remove all nodes matching this path expression" },
-    CMD_OPT_DEF_LAST
-};
-
-static const struct command_def cmd_rm_def = {
-    .name = "rm",
-    .opts = cmd_rm_opts,
-    .handler = cmd_rm,
-    .synopsis = "delete nodes and subtrees",
-    .help = "Delete PATH and all its children from the tree"
-};
-
-static void cmd_mv(struct command *cmd) {
-    const char *src = arg_value(cmd, "src");
-    const char *dst = arg_value(cmd, "dst");
-    int r;
-
-    r = aug_mv(aug, src, dst);
-    err_check(cmd);
+        r = asprintf(&pat, "%s/*", path);
     if (r < 0)
-        printf("Failed\n");
-}
-
-static const struct command_opt_def cmd_mv_opts[] = {
-    { .type = CMD_PATH, .name = "src", .optional = false,
-      .help = "the tree to move" },
-    { .type = CMD_PATH, .name = "dst", .optional = false,
-      .help = "where to put the source tree" },
-    CMD_OPT_DEF_LAST
-};
-
-static const struct command_def cmd_mv_def = {
-    .name = "mv",
-    .opts = cmd_mv_opts,
-    .handler = cmd_mv,
-    .synopsis = "move a subtree",
-    .help = "Move node  SRC to DST.  SRC must match  exactly one node in  "
-    "the tree.\n DST  must either  match  exactly one  node  in the  tree,  "
-    "or may  not\n exist  yet. If  DST exists  already, it  and all  its  "
-    "descendants are\n deleted.  If  DST  does  not   exist  yet,  it  and  "
-    "all  its  missing\n ancestors are created."
-};
-
-static void cmd_set(struct command *cmd) {
-    const char *path = arg_value(cmd, "path");
-    const char *val = arg_value(cmd, "value");
-    int r;
-
-    r = aug_set(aug, path, val);
-    err_check(cmd);
-    if (r == -1)
-        printf ("Failed\n");
-}
-
-static const struct command_opt_def cmd_set_opts[] = {
-    { .type = CMD_PATH, .name = "path", .optional = false,
-      .help = "set the value of this node" },
-    { .type = CMD_STR, .name = "value", .optional = false,
-      .help = "the new value for the node" },
-    CMD_OPT_DEF_LAST
-};
-
-static const struct command_def cmd_set_def = {
-    .name = "set",
-    .opts = cmd_set_opts,
-    .handler = cmd_set,
-    .synopsis = "set the value of a node",
-    .help = "Associate VALUE with PATH.  If PATH is not in the tree yet, "
-    "it and all\n its ancestors will be created. These new tree entries "
-    "will appear last\n amongst their siblings"
-};
-
-static void cmd_setm(struct command *cmd) {
-    const char *base = arg_value(cmd, "base");
-    const char *sub  = arg_value(cmd, "sub");
-    const char *val  = arg_value(cmd, "value");
-    int r;
-
-    r = aug_setm(aug, base, sub, val);
-    err_check(cmd);
-    if (r == -1)
-        printf ("Failed\n");
-}
-
-static const struct command_opt_def cmd_setm_opts[] = {
-    { .type = CMD_PATH, .name = "base", .optional = false,
-      .help = "the base node" },
-    { .type = CMD_PATH, .name = "sub", .optional = false,
-      .help = "the subtree relative to the base" },
-    { .type = CMD_STR, .name = "value", .optional = false,
-      .help = "the value for the nodes" },
-    CMD_OPT_DEF_LAST
-};
-
-static const struct command_def cmd_setm_def = {
-    .name = "setm",
-    .opts = cmd_setm_opts,
-    .handler = cmd_setm,
-    .synopsis = "set the value of multiple nodes",
-    .help = "Set multiple nodes in one operation.  Find or create a node"
-    " matching SUB\n by interpreting SUB as a  path expression relative"
-    " to each node matching\n BASE. If SUB is '.', the nodes matching "
-    "BASE will be modified."
-};
-
-static void cmd_clearm(struct command *cmd) {
-    const char *base = arg_value(cmd, "base");
-    const char *sub  = arg_value(cmd, "sub");
-    int r;
-
-    r = aug_setm(aug, base, sub, NULL);
-    err_check(cmd);
-    if (r == -1)
-        printf ("Failed\n");
-}
-
-static const struct command_opt_def cmd_clearm_opts[] = {
-    { .type = CMD_PATH, .name = "base", .optional = false,
-      .help = "the base node" },
-    { .type = CMD_PATH, .name = "sub", .optional = false,
-      .help = "the subtree relative to the base" },
-    CMD_OPT_DEF_LAST
-};
-
-static const struct command_def cmd_clearm_def = {
-    .name = "clearm",
-    .opts = cmd_clearm_opts,
-    .handler = cmd_clearm,
-    .synopsis = "clear the value of multiple nodes",
-    .help = "Clear multiple nodes values in one operation. Find or create a"
-    " node matching SUB\n by interpreting SUB as a path expression relative"
-    " to each node matching\n BASE. If SUB is '.', the nodes matching "
-    "BASE will be modified."
-};
-
-static void cmd_span(struct command *cmd) {
-    const char *path = arg_value(cmd, "path");
-    int r;
-    uint label_start, label_end, value_start, value_end, span_start, span_end;
-    char *filename;
-    const char *option;
-    // FIXME: add check to see if AUG_ENABLE_SPAN is set
-
-    if (aug_get(aug, AUGEAS_SPAN_OPTION, &option) != 1) {
-        printf("Error: option " AUGEAS_SPAN_OPTION " not found\n");
-        return;
-    }
-    if (strcmp(AUG_DISABLE, option) == 0) {
-        printf("Span is not enabled. To enable, run commands:\n");
-        printf("set %s %s\n", AUGEAS_SPAN_OPTION, AUG_ENABLE);
-        printf("rm %s\n", AUGEAS_FILES_TREE);
-        printf("load\n");
-        return;
-    } else if (strcmp(AUG_ENABLE, option) != 0) {
-        printf("Error: option %s must be %s or %s\n", AUGEAS_SPAN_OPTION,
-               AUG_ENABLE, AUG_DISABLE);
-        return;
-    }
-    r = aug_span(aug, path, &filename, &label_start, &label_end, &value_start,
-                 &value_end, &span_start, &span_end);
-    err_check(cmd);
-    if (r == -1){
-        printf ("Failed\n");
-        return;
-    }
-    printf("%s label=(%i:%i) value=(%i:%i) span=(%i,%i)\n", filename,
-           label_start, label_end, value_start, value_end, span_start, span_end);
-}
-
-static const struct command_opt_def cmd_span_opts[] = {
-    { .type = CMD_PATH, .name = "path", .optional = false,
-      .help = "node path" },
-    CMD_OPT_DEF_LAST
-};
-
-static const struct command_def cmd_span_def = {
-    .name = "span",
-    .opts = cmd_span_opts,
-    .handler = cmd_span,
-    .synopsis = "get the filename, label and value position in the text of this node",
-    .help = "get the filename, label and value position in the text of this node"
-};
-
-static void cmd_defvar(struct command *cmd) {
-    const char *name = arg_value(cmd, "name");
-    const char *path = arg_value(cmd, "expr");
-    int r;
-
-    r = aug_defvar(aug, name, path);
-    err_check(cmd);
-    if (r == -1)
-        printf("Failed\n");
-}
-
-static const struct command_opt_def cmd_defvar_opts[] = {
-    { .type = CMD_STR, .name = "name", .optional = false,
-      .help = "the name of the variable" },
-    { .type = CMD_PATH, .name = "expr", .optional = false,
-      .help = "the path expression" },
-    CMD_OPT_DEF_LAST
-};
-
-static const struct command_def cmd_defvar_def = {
-    .name = "defvar",
-    .opts = cmd_defvar_opts,
-    .handler = cmd_defvar,
-    .synopsis = "set a variable",
-    .help = "Evaluate EXPR and set the variable NAME to the resulting "
-    "nodeset. The\n variable can be used in path expressions as $NAME.  "
-    "Note that EXPR is\n evaluated when the variable is defined, not when "
-    "it is used."
-};
-
-static void cmd_defnode(struct command *cmd) {
-    const char *name = arg_value(cmd, "name");
-    const char *path = arg_value(cmd, "expr");
-    const char *value = arg_value(cmd, "value");
-    int r;
-
-    /* Our simple minded line parser treats non-existant and empty values
-     * the same. We choose to take the empty string to mean NULL */
-    if (value != NULL && strlen(value) == 0)
-        value = NULL;
-    r = aug_defnode(aug, name, path, value, NULL);
-    err_check(cmd);
-    if (r == -1)
-        printf ("Failed\n");
-}
-
-static const struct command_opt_def cmd_defnode_opts[] = {
-    { .type = CMD_STR, .name = "name", .optional = false,
-      .help = "the name of the variable" },
-    { .type = CMD_PATH, .name = "expr", .optional = false,
-      .help = "the path expression" },
-    { .type = CMD_STR, .name = "value", .optional = true,
-      .help = "the value for the new node" },
-    CMD_OPT_DEF_LAST
-};
-
-static const struct command_def cmd_defnode_def = {
-    .name = "defnode",
-    .opts = cmd_defnode_opts,
-    .handler = cmd_defnode,
-    .synopsis = "set a variable, possibly creating a new node",
-    .help = "Define the variable NAME to the result of evalutating EXPR, "
-    " which must\n be a nodeset.  If no node matching EXPR exists yet,  one "
-    "is created and\n NAME will refer to it.   When a node is created and "
-    "VALUE is given, the\n new node's value is set to VALUE."
-};
-
-static void cmd_clear(struct command *cmd) {
-    const char *path = arg_value(cmd, "path");
-    int r;
-
-    r = aug_set(aug, path, NULL);
-    err_check(cmd);
-    if (r == -1)
-        printf ("Failed\n");
-}
-
-static const struct command_opt_def cmd_clear_opts[] = {
-    { .type = CMD_PATH, .name = "path", .optional = false,
-      .help = "clear the value of this node" },
-    CMD_OPT_DEF_LAST
-};
-
-static const struct command_def cmd_clear_def = {
-    .name = "clear",
-    .opts = cmd_clear_opts,
-    .handler = cmd_clear,
-    .synopsis = "clear the value of a node",
-    .help = "Set the value for PATH to NULL. If PATH is not in the tree yet, "
-    "it and\n all its ancestors will be created.  These new tree entries "
-    "will appear\n last amongst their siblings"
-};
-
-static void cmd_get(struct command *cmd) {
-    const char *path = arg_value(cmd, "path");
-    const char *val;
-    int r;
-
-    r = aug_get(aug, path, &val);
-    ERR_CHECK(cmd);
-    printf("%s", path);
-    if (r == 0) {
-        printf(" (o)\n");
-    } else if (val == NULL) {
-        printf(" (none)\n");
-    } else {
-        printf(" = %s\n", val);
-    }
-    err_check(cmd);
-}
-
-static const struct command_opt_def cmd_get_opts[] = {
-    { .type = CMD_PATH, .name = "path", .optional = false,
-      .help = "get the value of this node" },
-    CMD_OPT_DEF_LAST
-};
-
-static const struct command_def cmd_get_def = {
-    .name = "get",
-    .opts = cmd_get_opts,
-    .handler = cmd_get,
-    .synopsis = "get the value of a node",
-    .help = "Get and print the value associated with PATH"
-};
-
-static void cmd_print(struct command *cmd) {
-    const char *path = arg_value(cmd, "path");
-
-    aug_print(aug, stdout, path);
-    err_check(cmd);
-}
-
-static const struct command_opt_def cmd_print_opts[] = {
-    { .type = CMD_PATH, .name = "path", .optional = true,
-      .help = "print this subtree" },
-    CMD_OPT_DEF_LAST
-};
-
-static const struct command_def cmd_print_def = {
-    .name = "print",
-    .opts = cmd_print_opts,
-    .handler = cmd_print,
-    .synopsis = "print a subtree",
-    .help = "Print entries in the tree.  If PATH is given, printing starts there,\n otherwise the whole tree is printed"
-};
-
-static void cmd_save(struct command *cmd) {
-    int r;
-    r = aug_save(aug);
-    err_check(cmd);
-    if (r == -1) {
-        printf("Saving failed\n");
-        cmd->result = CMD_RES_ERR;
-    } else {
-        r = aug_match(aug, "/augeas/events/saved", NULL);
-        if (r > 0) {
-            if (echo)
-                printf("Saved %d file(s)\n", r);
-        } else if (r < 0) {
-            printf("Error during match: %d\n", r);
-        }
-    }
-}
-
-static const struct command_opt_def cmd_save_opts[] = {
-    CMD_OPT_DEF_LAST
-};
-
-static const struct command_def cmd_save_def = {
-    .name = "save",
-    .opts = cmd_save_opts,
-    .handler = cmd_save,
-    .synopsis = "save all pending changes",
-    .help = "Save all pending changes to disk. How exactly that is done depends on\n the value of the node /augeas/save, which can be changed by the user.\n The possible values for it are\n \n   noop      - do not write files; useful for finding errors that\n               might happen during a save\n   backup    - save the original file in a file by appending the extension\n               '.augsave' and overwrite the original with new content\n   newfile   - leave the original file untouched and write new content to\n               a file with extension '.augnew' next to the original file\n   overwrite - overwrite the original file with new content\n \n Save always tries to save all files for which entries in the tree have\n changed. When saving fails, some files will be written.  Details about\n why a save failed can by found by issuing the command 'print\n /augeas//error' (note the double slash)"
-};
-
-static void cmd_load(struct command *cmd) {
-    int r;
-    r = aug_load(aug);
-    err_check(cmd);
-    if (r == -1) {
-        printf("Loading failed\n");
-    }
-}
-
-static const struct command_opt_def cmd_load_opts[] = {
-    CMD_OPT_DEF_LAST
-};
-
-static const struct command_def cmd_load_def = {
-    .name = "load",
-    .opts = cmd_load_opts,
-    .handler = cmd_load,
-    .synopsis = "(re)load files under /files",
-    .help = "Load files  according to the  transforms in /augeas/load.  "
-    "A transform\n Foo  is  represented  with  a  subtree  /augeas/load/Foo."
-    "   Underneath\n /augeas/load/Foo, one node labelled  'lens' must exist,"
-    " whose value is\n the  fully  qualified name  of  a  lens,  for example  "
-    "'Foo.lns',  and\n multiple nodes 'incl' and 'excl' whose values are "
-    "globs that determine\n which files are  transformed by that lens. It "
-    "is an  error if one file\n can be processed by multiple transforms."
-};
-
-static void cmd_ins(struct command *cmd) {
-    const char *label = arg_value(cmd, "label");
-    const char *where = arg_value(cmd, "where");
-    const char *path = arg_value(cmd, "path");
-    int before;
-
-    if (STREQ(where, "after"))
-        before = 0;
-    else if (STREQ(where, "before"))
-        before = 1;
-    else {
-        printf("The <WHERE> argument must be either 'before' or 'after'.");
-        cmd->result = CMD_RES_ERR;
-        return;
-    }
+        return -1;
+    r = aug_match(aug, pat, NULL);
+    free(pat);
 
-    aug_insert(aug, path, label, before);
-    err_check(cmd);
+    return r;
 }
 
-static const struct command_opt_def cmd_ins_opts[] = {
-    { .type = CMD_STR, .name = "label", .optional = false,
-      .help = "the label for the new node" },
-    { .type = CMD_STR, .name = "where", .optional = false,
-      .help = "either 'before' or 'after'" },
-    { .type = CMD_PATH, .name = "path", .optional = false,
-      .help = "the node before/after which to insert" },
-    CMD_OPT_DEF_LAST
-};
-
-static const struct command_def cmd_ins_def = {
-    .name = "ins",
-    .opts = cmd_ins_opts,
-    .handler = cmd_ins,
-    .synopsis = "insert new node before/after and existing node",
-    .help = "Insert a new node with label LABEL right before or after "
-    "PATH into the\n tree. WHERE must be either 'before' or 'after'."
-};
-
 static char *readline_path_generator(const char *text, int state) {
     static int current = 0;
     static char **children = NULL;
     static int nchildren = 0;
-    struct command fake;  /* Used only for the result field */
 
-    MEMZERO(&fake, 1);
     if (state == 0) {
         char *end = strrchr(text, SEP);
         char *path;
@@ -1006,7 +118,7 @@ static char *readline_path_generator(const char *text, int state) {
         char *child = children[current];
         current += 1;
         if (STREQLEN(child, text, strlen(text))) {
-            if (child_count(&fake, child) > 0) {
+            if (child_count(child) > 0) {
                 char *c = realloc(child, strlen(child)+2);
                 if (c == NULL)
                     return NULL;
@@ -1023,29 +135,14 @@ static char *readline_path_generator(const char *text, int state) {
     return NULL;
 }
 
-static const struct command_def const *commands[] = {
-    &cmd_quit_def,
-    &cmd_clear_def,
-    &cmd_defnode_def,
-    &cmd_defvar_def,
-    &cmd_get_def,
-    &cmd_ins_def,
-    &cmd_load_def,
-    &cmd_ls_def,
-    &cmd_match_def,
-    &cmd_mv_def,
-    &cmd_print_def,
-    &cmd_rm_def,
-    &cmd_save_def,
-    &cmd_set_def,
-    &cmd_setm_def,
-    &cmd_clearm_def,
-    &cmd_span_def,
-    &cmd_help_def,
-    &cmd_def_last
-};
-
 static char *readline_command_generator(const char *text, int state) {
+    // FIXME: expose somewhere under /augeas
+    static const char *const commands[] = {
+        "quit", "clear", "defnode", "defvar",
+        "get", "ins", "load", "ls", "match",
+        "mv", "print", "rm", "save", "set", "setm",
+        "clearm", "span", "help", NULL };
+
     static int current = 0;
     const char *name;
 
@@ -1053,7 +150,7 @@ static char *readline_command_generator(const char *text, int state) {
         current = 0;
 
     rl_completion_append_character = ' ';
-    while ((name = commands[current]->name) != NULL) {
+    while ((name = commands[current]) != NULL) {
         current += 1;
         if (STREQLEN(text, name, strlen(text)))
             return strdup(name);
@@ -1265,33 +362,20 @@ static void print_version_info(void) {
     fprintf(stderr, "Something went terribly wrong internally - please file a bug\n");
 }
 
-static enum command_result
-run_command(const char *line) {
-    char *dup_line = strdup(line);
-    struct command cmd;
+static int run_command(const char *line) {
+    int result;
 
-    if (dup_line == NULL) {
-        fprintf(stderr, "Out of memory\n");
-        return CMD_RES_ENOMEM;
-    }
-
-    MEMZERO(&cmd, 1);
-    if (parseline(&cmd, dup_line) == 0) {
-        cmd.def->handler(&cmd);
-        if (isatty(fileno(stdin)))
-            add_history(line);
-    } else {
-        cmd.result = CMD_RES_ERR;
-    }
-    free(dup_line);
-    return cmd.result;
+    result = aug_srun(aug, stdout, line);
+    if (isatty(fileno(stdin)))
+        add_history(line);
+    return result;
 }
 
 static int main_loop(void) {
     char *line = NULL;
     int ret = 0;
     char inputline [128];
-    enum command_result code;
+    int code;
     bool end_reached = false;
     bool get_line = true;
     bool in_interactive = false;
@@ -1305,7 +389,7 @@ static int main_loop(void) {
                 perror("Failed to open input file");
             else
                 perror(msg);
-            return CMD_RES_ERR;
+            return -1;
         }
     }
 
@@ -1328,7 +412,7 @@ static int main_loop(void) {
                // reopen in and out streams
                if ((rl_instream = fopen("/dev/tty", "r")) == NULL) {
                    perror("Failed to open terminal for reading");
-                   return CMD_RES_ERR;
+                   return -1;
                }
                if (rl_outstream != NULL) {
                    fclose(rl_outstream);
@@ -1336,7 +420,7 @@ static int main_loop(void) {
                }
                if ((rl_outstream = fopen("/dev/stdout", "w")) == NULL) {
                    perror("Failed to reopen stdout");
-                   return CMD_RES_ERR;
+                   return -1;
                }
                continue;
             }
@@ -1353,8 +437,6 @@ static int main_loop(void) {
             get_line = false;
         }
 
-        cleanstr(line, '\n');
-
         if (end_reached) {
             if (echo)
                 printf("\n");
@@ -1365,13 +447,22 @@ static int main_loop(void) {
             continue;
 
         code = run_command(line);
-        if (code == CMD_RES_QUIT)
-            return 0;
-        if (code == CMD_RES_ERR)
+        if (code == -2)
+            return ret;
+        if (code < 0) {
             ret = -1;
-        if (code == CMD_RES_ENOMEM) {
-            fprintf(stderr, "Out of memory.\n");
-            return -1;
+            if (aug_error(aug) == AUG_ENOMEM) {
+                fprintf(stderr, "Out of memory.\n");
+                return -1;
+            }
+            if (aug_error(aug) != AUG_NOERROR) {
+                fprintf(stderr, "error: %s\n", aug_error_message(aug));
+                if (aug_error_minor_message(aug) != NULL)
+                    fprintf(stderr, "error: %s\n",
+                            aug_error_minor_message(aug));
+                if (aug_error_details(aug) != NULL)
+                    fprintf(stderr, "error: %s\n", aug_error_details(aug));
+            }
         }
     }
 }
@@ -1379,7 +470,7 @@ static int main_loop(void) {
 static int run_args(int argc, char **argv) {
     size_t len = 0;
     char *line = NULL;
-    enum command_result code;
+    int   code;
 
     for (int i=0; i < argc; i++)
         len += strlen(argv[i]) + 1;
@@ -1391,9 +482,9 @@ static int run_args(int argc, char **argv) {
     }
     code = run_command(line);
     free(line);
-    if (code == CMD_RES_OK && auto_save)
+    if (code == 0 && auto_save)
         code = run_command("save");
-    return (code == CMD_RES_OK || code == CMD_RES_QUIT) ? 0 : -1;
+    return (code == 0 || code == -2) ? 0 : -1;
 }
 
 int main(int argc, char **argv) {
diff --git a/src/internal.c b/src/internal.c
index 8419ca2..9f4b697 100644
--- a/src/internal.c
+++ b/src/internal.c
@@ -281,7 +281,7 @@ void print_pos(FILE *out, const char *text, int pos) {
     }
 }
 
-int init_memstream(struct memstream *ms) {
+int __aug_init_memstream(struct memstream *ms) {
     MEMZERO(ms, 1);
 #if HAVE_OPEN_MEMSTREAM
     ms->stream = open_memstream(&(ms->buf), &(ms->size));
@@ -295,7 +295,7 @@ int init_memstream(struct memstream *ms) {
 #endif
 }
 
-int close_memstream(struct memstream *ms) {
+int __aug_close_memstream(struct memstream *ms) {
 #if !HAVE_OPEN_MEMSTREAM
     rewind(ms->stream);
     ms->buf = fread_file_lim(ms->stream, MAX_READ_LEN, &(ms->size));
diff --git a/src/internal.h b/src/internal.h
index d4c266b..411a540 100644
--- a/src/internal.h
+++ b/src/internal.h
@@ -330,6 +330,10 @@ static inline struct error *err_of_aug(const struct augeas *aug) {
 /* Used by augparse for loading tests */
 int __aug_load_module_file(struct augeas *aug, const char *filename);
 
+/* Called at beginning and end of every _public_ API function */
+void api_entry(const struct augeas *aug);
+void api_exit(const struct augeas *aug);
+
 /* Struct: tree
  * An entry in the global config tree. The data structure allows associating
  * values with interior nodes, but the API currently marks that as an error.
@@ -437,7 +441,8 @@ struct memstream {
  *
  * MS must be allocated in advance; INIT_MEMSTREAM initializes it.
  */
-int init_memstream(struct memstream *ms);
+int __aug_init_memstream(struct memstream *ms);
+#define init_memstream(ms) __aug_init_memstream(ms);
 
 /* Function: close_memstream
  * Close a memstream. After calling this, MS->STREAM can not be used
@@ -446,7 +451,8 @@ int init_memstream(struct memstream *ms);
  *
  * The caller must free the MEMSTREAM structure.
  */
-int close_memstream(struct memstream *ms);
+int __aug_close_memstream(struct memstream *ms);
+#define close_memstream(ms) __aug_close_memstream(ms)
 
 /*
  * Path expressions
diff --git a/tests/Makefile.am b/tests/Makefile.am
index dd7164d..546ab62 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -145,7 +145,7 @@ EXTRA_DIST = \
 
 noinst_SCRIPTS = $(check_SCRIPTS)
 
-check_PROGRAMS = fatest test-xpath test-load test-save test-api
+check_PROGRAMS = fatest test-xpath test-load test-save test-api test-run
 
 TESTS_ENVIRONMENT = \
   PATH='$(abs_top_builddir)/src$(PATH_SEPARATOR)'"$$PATH" \
@@ -172,6 +172,9 @@ test_save_LDADD = $(top_builddir)/src/libaugeas.la $(GNULIB)
 test_api_SOURCES = test-api.c cutest.c cutest.h $(top_srcdir)/src/memory.c $(top_srcdir)/src/memory.h
 test_api_LDADD = $(top_builddir)/src/libaugeas.la $(GNULIB)
 
+test_run_SOURCES = test-run.c cutest.c cutest.h $(top_srcdir)/src/memory.c $(top_srcdir)/src/memory.h
+test_run_LDADD = $(top_builddir)/src/libaugeas.la $(GNULIB)
+
 FAILMALLOC_START ?= 1
 FAILMALLOC_REP   ?= 20
 FAILMALLOC_PROG ?= ./fatest
diff --git a/tests/run.tests b/tests/run.tests
new file mode 100644
index 0000000..9e49d92
--- /dev/null
+++ b/tests/run.tests
@@ -0,0 +1,225 @@
+test mv-into-multiple -1 EMMATCH
+  mv /files /augeas/version/save/*
+# Tests for aug_srun
+
+# Blank lines and lines starting with '#' are ignored. This file is
+# processed by test-run.c
+#
+# The syntax for a test specification is
+#   test NAME RESULT ERRCODE
+#     COMMANDS
+#   prints
+#     OUTPUT
+#
+# where
+#   NAME     - the name printed to identify the test
+#   RESULT   - an integer that is compared against the return code
+#              of aug_srun
+#   ERRCODE  - one of the error codes defined in enum errcode_t in augeas.h
+#              without the AUG_ prefix, i.e. NOERROR, EMMATCH etc. If ERRCODE
+#              is ommitted, it defaults to NOERROR
+#   COMMANDS - the commands to hand to aug_srun; can be multiple lines,
+#              which are passed as one string.
+#   OUTPUT   - the string that aug_srun should print on the OUT file stream
+#
+# The prints keyword and OUTPUT are optional; if they are not provided, the
+# output of aug_srun must be empty
+#
+# Leading spaces are stripped from COMMANDS and OUTPUT; a leading ':' is
+# also stripped, but the rest of the line is used verbatim.
+#
+# A test passes when RESULT and ERRCODE agree with what aug_srun given
+# COMMANDS produces, and OUTPUT coincides with what aug_srun prints on its
+# OUT stream.
+#
+# The test is run against a tree initialized with AUG_NO_STDINC|AUG_NO_LOAD
+# and file system root /dev/null
+
+#
+# Various corner cases
+#
+test null 0
+
+test empty 0
+  :
+
+test quit -2
+  quit
+
+test quit-2 -2
+  get /augeas
+  quit
+prints
+  /augeas (none)
+
+test two-commands 2
+  get /augeas/root
+  get /augeas/span
+prints
+  /augeas/root = /dev/null/
+  /augeas/span = disable
+
+test comment 1
+  :# Get /augeas
+  get /augeas
+prints
+  /augeas (none)
+
+test unknown-cmd -1 ECMDRUN
+  nocommand
+
+test help 1
+  help
+prints something
+
+#
+# ls tests
+#
+test ls-root 1
+  ls /
+prints
+  augeas/ = (none)
+  files = (none)
+
+test ls-bad-pathx -1 EPATHX
+  ls /files[]
+
+#
+# match tests
+#
+test match-root 1
+  match /
+prints
+  /augeas = (none)
+  /files = (none)
+
+test match-root-star 1
+  match /*
+prints
+  /augeas = (none)
+  /files = (none)
+
+test match-bad-pathx -1 EPATHX
+  match /files[]
+
+test match-nothing 1
+  match /not-there
+prints
+  :  (no matches)
+
+#
+# test rm
+#
+test rm-save-modes 1
+  rm /augeas/version/save
+prints
+  rm : /augeas/version/save 5
+
+test rm-bad-pathx -1 EPATHX
+  rm /files[]
+
+#
+# test mv
+#
+test mv 1
+  mv /augeas/version /files
+
+test mv-not-there -1 ENOMATCH
+  mv /not-there /files
+
+test mv-to-not-there 1
+  mv /files /new-node
+
+test mv-into-descendant -1 EMVDESC
+  mv /augeas /augeas/version
+
+test mv-into-self -1 EMVDESC
+  mv /augeas /augeas
+
+test mv-into-multiple -1 EMMATCH
+  mv /files /augeas/version/save/*
+
+test mv-multiple -1 EMMATCH
+  mv /augeas/version/save/* /files
+
+#
+# test set
+#
+test set-not-there 2
+  set /foo value
+  get /foo
+prints
+  /foo = value
+
+test set-existing 2
+  set /files value
+  get /files
+prints
+  /files = value
+
+test set-bad-pathx -1 EPATHX
+  set /files[] 1
+
+test set-multiple -1 EMMATCH
+  set /augeas/version/save/mode value
+
+test set-args -1 ECMDRUN
+  set /files
+
+#
+# test clear
+#
+test clear-not-there 2
+  clear /foo
+  get /foo
+prints
+  /foo (none)
+
+test clear-existing 2
+  clear /files
+  get /files
+prints
+  /files (none)
+
+test clear-bad-pathx -1 EPATHX
+  clear /files[]
+
+test clear-multiple -1 EMMATCH
+  clear /augeas/version/save/mode
+
+test clear-args -1 ECMDRUN
+  clear /files value
+
+#
+# test get
+#
+test get-save-mode 1
+  get /augeas/version/save/mode[1]
+prints
+ /augeas/version/save/mode[1] = backup
+
+test get-too-many -1 EMMATCH
+  get /augeas/*
+
+test get-not-there 1
+  get /not-there
+prints
+  /not-there (o)
+
+test get-bad-pathx -1 EPATHX
+  get /files[]
+
+#
+# test print
+#
+test print-save 1
+  print /augeas/version/save
+prints
+  /augeas/version/save
+  /augeas/version/save/mode[1] = "backup"
+  /augeas/version/save/mode[2] = "newfile"
+  /augeas/version/save/mode[3] = "noop"
+  /augeas/version/save/mode[4] = "overwrite"
+
+test print-root 1
+  print /
diff --git a/tests/test-api.c b/tests/test-api.c
index 54b4c7d..13e21d3 100644
--- a/tests/test-api.c
+++ b/tests/test-api.c
@@ -334,6 +334,23 @@ static void testNodeInfo(CuTest *tc) {
     aug_close(aug);
 }
 
+static void testMv(CuTest *tc) {
+    struct augeas *aug;
+    int r;
+
+    aug = aug_init(root, loadpath, AUG_NO_STDINC|AUG_NO_LOAD);
+    CuAssertPtrNotNull(tc, aug);
+
+    r = aug_set(aug, "/a/b/c", "value");
+    CuAssertRetSuccess(tc, r);
+
+    r = aug_mv(aug, "/a/b/c", "/a/b/c/d");
+    CuAssertIntEquals(tc, -1, r);
+    CuAssertIntEquals(tc, AUG_EMVDESC, aug_error(aug));
+
+    aug_close(aug);
+}
+
 int main(void) {
     char *output = NULL;
     CuSuite* suite = CuSuiteNew();
@@ -345,6 +362,7 @@ int main(void) {
     SUITE_ADD_TEST(suite, testDefNodeExistingMeta);
     SUITE_ADD_TEST(suite, testDefNodeCreateMeta);
     SUITE_ADD_TEST(suite, testNodeInfo);
+    SUITE_ADD_TEST(suite, testMv);
 
     abs_top_srcdir = getenv("abs_top_srcdir");
     if (abs_top_srcdir == NULL)
diff --git a/tests/test-mv.sh b/tests/test-mv.sh
index e7832e5..d208f4a 100755
--- a/tests/test-mv.sh
+++ b/tests/test-mv.sh
@@ -2,7 +2,7 @@
 
 aug_mv() {
 opts='--nostdinc -r /dev/null'
-(augtool $opts | grep -v '/augeas\|/files\|augtool' | tr '\n' ' ') <<EOF
+(augtool $opts | grep -v '/augeas\|/files\|augtool' | tr '\n' ' ') 2>&1 <<EOF
 set /a/b/c value
 mv $1 $2
 print
@@ -34,7 +34,9 @@ assert_eq /a/x
 
 # Check that we don't move into a descendant
 ACT=$(aug_mv /a/b/c /a/b/c/d)
-EXP='Failed /a /a/b /a/b/c = "value" /a/b/c/d '
+EXP='error: Cannot move node into its descendant
+error: destination /a/b/c/d is a descendant of /a/b/c
+/a /a/b /a/b/c = "value" /a/b/c/d '
 assert_eq /a/b/c/d
 
 ACT=$(aug_mv /a/b/c /a/b/d)
diff --git a/tests/test-run.c b/tests/test-run.c
new file mode 100644
index 0000000..eb8cbcc
--- /dev/null
+++ b/tests/test-run.c
@@ -0,0 +1,247 @@
+/*
+ * test-run.c: test the aug_srun API function
+ *
+ * Copyright (C) 2009 Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
+ *
+ * Author: David Lutterkort <lutter at redhat.com>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#include "augeas.h"
+
+#include "cutest.h"
+#include "internal.h"
+#include <memory.h>
+
+#include <unistd.h>
+
+static const char *abs_top_srcdir;
+
+#define KW_TEST "test"
+#define KW_PRINTS "prints"
+#define KW_SOMETHING "something"
+
+/* This array needs to be kept in sync with aug_errcode_t, and
+ * the entries need to be in the same order as in that enum; used
+ * by errtok
+ */
+static const char *const errtokens[] = {
+    "NOERROR", "ENOMEM", "EINTERNAL", "EPATHX", "ENOMATCH",
+    "EMMATCH", "ESYNTAX", "ENOLENS", "EMXFM", "ENOSPAN",
+    "EMVDESC", "ECMDRUN"
+};
+
+struct test {
+    struct test *next;
+    char *name;
+    int  result;
+    int  errcode;
+    char *cmd;
+    char *out;
+    bool  out_present;
+};
+
+#define die(msg)                                                    \
+    do {                                                            \
+        fprintf(stderr, "%d: Fatal error: %s\n", __LINE__, msg);    \
+        exit(EXIT_FAILURE);                                         \
+    } while(0)
+
+static char *skipws(char *s) {
+    while (isspace(*s)) s++;
+    return s;
+}
+
+static char *findws(char *s) {
+    while (*s && ! isspace(*s)) s++;
+    return s;
+}
+
+static char *token(char *s, char **tok) {
+    char *t = skipws(s);
+    s = findws(t);
+    *tok = strndup(t, s - t);
+    return s;
+}
+
+static char *inttok(char *s, int *tok) {
+    char *t = skipws(s);
+    s = findws(t);
+    *tok = strtol(t, NULL, 10);
+    return s;
+}
+
+static char *errtok(char *s, int *err) {
+    char *t = skipws(s);
+    s = findws(t);
+    if (s == t) {
+        *err = AUG_NOERROR;
+        return s;
+    }
+    for (*err = 0; *err < ARRAY_CARDINALITY(errtokens); *err += 1) {
+        const char *e = errtokens[*err];
+        if (strlen(e) == s - t && STREQLEN(e, t, s - t))
+            return s;
+    }
+    fprintf(stderr, "errtok: '%s'\n", t);
+    die("unknown error code");
+}
+
+static bool looking_at(const char *s, const char *kw) {
+    return STREQLEN(s, kw, strlen(kw));
+}
+
+static struct test *read_tests(void) {
+    char *fname;
+    FILE *fp;
+    char line[BUFSIZ];
+    struct test *result = NULL, *t = NULL;
+    int lc = 0;
+    bool append_cmd;
+
+    if (asprintf(&fname, "%s/tests/run.tests", abs_top_srcdir) < 0)
+        die("asprintf fname");
+
+    if ((fp = fopen(fname, "r")) == NULL)
+        die("fopen xpath.tests");
+
+    while (fgets(line, BUFSIZ, fp) != NULL) {
+        lc += 1;
+        char *s = skipws(line);
+        if (*s == '#' || *s == '\0')
+            continue;
+        if (*s == ':')
+            s += 1;
+        if (looking_at(s, KW_TEST)) {
+            if (ALLOC(t) < 0)
+                die_oom();
+            list_append(result, t);
+            append_cmd = true;
+            s = token(s + strlen(KW_TEST), &(t->name));
+            s = inttok(s, &t->result);
+            s = errtok(s, &t->errcode);
+        } else if (looking_at(s, KW_PRINTS)) {
+            s = skipws(s + strlen(KW_PRINTS));
+            t->out_present = looking_at(s, KW_SOMETHING);
+            append_cmd = false;
+        } else {
+            char **buf = append_cmd ? &(t->cmd) : &(t->out);
+            if (*buf == NULL) {
+                *buf = strdup(s);
+                if (*buf == NULL)
+                    die_oom();
+            } else {
+                if (REALLOC_N(*buf, strlen(*buf) + strlen(s) + 1) < 0)
+                    die_oom();
+                strcat(*buf, s);
+            }
+        }
+        if (t->out != NULL)
+            t->out_present = true;
+    }
+    return result;
+}
+
+#define fail(cond, msg ...)                     \
+    if (cond) {                                 \
+        printf("FAIL (");                       \
+        fprintf(stdout, ## msg);                \
+        printf(")\n");                          \
+        goto error;                             \
+    }
+
+static int run_one_test(struct test *test) {
+    int r;
+    struct augeas *aug = NULL;
+    struct memstream ms;
+    int result = 0;
+
+    aug = aug_init("/dev/null", NULL, AUG_NO_STDINC|AUG_NO_LOAD);
+    fail(aug == NULL, "aug_init");
+    fail(aug_error(aug) != AUG_NOERROR, "aug_init: errcode was %d",
+         aug_error(aug));
+
+    printf("%-30s ... ", test->name);
+
+    r = init_memstream(&ms);
+    fail(r < 0, "init_memstream");
+
+    r = aug_srun(aug, ms.stream, test->cmd);
+    fail(r != test->result, "return value: expected %d, actual %d",
+         test->result, r);
+    fail(aug_error(aug) != test->errcode, "errcode: expected %s, actual %s",
+         errtokens[test->errcode], errtokens[aug_error(aug)]);
+
+    r = close_memstream(&ms);
+    fail(r < 0, "close_memstream");
+    fail(ms.buf == NULL, "close_memstream left buf NULL");
+
+    if (test->out != NULL) {
+        fail(STRNEQ(ms.buf, test->out), "output: expected '%s', actual '%s'",
+             test->out, ms.buf);
+    } else if (test->out_present) {
+        fail(strlen(ms.buf) == 0,
+             "output: expected some output");
+    } else {
+        fail(strlen(ms.buf) > 0,
+             "output: expected nothing, actual '%s'", ms.buf);
+    }
+    printf("PASS\n");
+
+ done:
+    free(ms.buf);
+    aug_close(aug);
+    return result;
+ error:
+    result = -1;
+    goto done;
+}
+
+static int run_tests(struct test *tests) {
+    int result = EXIT_SUCCESS;
+
+    list_for_each(t, tests) {
+        if (run_one_test(t) < 0)
+            result = EXIT_FAILURE;
+    }
+    return result;
+}
+
+int main(void) {
+    struct test *tests;
+
+    abs_top_srcdir = getenv("abs_top_srcdir");
+    if (abs_top_srcdir == NULL)
+        die("env var abs_top_srcdir must be set");
+
+    tests = read_tests();
+    return run_tests(tests);
+}
+
+/*
+ * Local variables:
+ *  indent-tabs-mode: nil
+ *  c-indent-level: 4
+ *  c-basic-offset: 4
+ *  tab-width: 4
+ * End:
+ */
-- 
1.7.6




More information about the augeas-devel mailing list