Merge commit 'fml/master'
[adu.git] / interactive.c
index 686a89f192aecfbc65373b49a7c0cd2486a57b71..1c7562271a0948941c8b5e5ba41f8826dec1d7e9 100644 (file)
+/*
+ * Copyright (C) 2008 Andre Noll <maan@systemlinux.org>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+
+/** \file interactive.c \brief Commands for interactive mode. */
+
+#include <ctype.h> /* isspace() */
+
 #include "adu.h"
+#include "format.h"
+#include "user.h"
 #include "string.h"
-#include "error.h"
 #include "cmdline.h"
 #include "select.cmdline.h"
+#include "select.h"
+#include "error.h"
 
-struct select_args_info select_conf;
-
+/**
+ * Describes one valid command for interactive mode.
+ *
+ * When invoked in interactive mode, adu reads commands from stdin. There's a
+ * static array of all such commands.
+ */
 struct interactive_command {
+       /** The name of the command. */
        const char *name;
+       /** Pointer to The function that is being executed. */
        int (*handler)(char *);
+       /** Help text. */
+       const char *desc;
 };
 
+static struct uid_range *admissible_uids;
+static struct format_info *fi;
+
+/** The set of supported interactive commands. */
 #define INTERACTIVE_COMMANDS \
-       INTERACTIVE_COMMAND(dump) \
-       INTERACTIVE_COMMAND(set) \
-       INTERACTIVE_COMMAND(def) \
+       INTERACTIVE_COMMAND(set, "change the current configuration") \
+       INTERACTIVE_COMMAND(reset, "reset configuration to defaults") \
+       INTERACTIVE_COMMAND(help, "show list of commands and one-line descriptions") \
+       INTERACTIVE_COMMAND(run, "start the query according to the current configuration") \
+       INTERACTIVE_COMMAND(source, "read and execute interactive commands from a file")
+
 
-#define INTERACTIVE_COMMAND(name) \
+/** \cond doxygen is not smart enough for this */
+#define INTERACTIVE_COMMAND(name, desc) \
        static int icom_ ## name (char *line);
 
 INTERACTIVE_COMMANDS
 
 #undef INTERACTIVE_COMMAND
 
-#define INTERACTIVE_COMMAND(_name) \
+#define INTERACTIVE_COMMAND(_name, _desc) \
        { \
        .name = #_name, \
-       .handler = icom_ ## _name \
+       .handler = icom_ ## _name, \
+       .desc = _desc \
        },
 
 struct interactive_command icmds[] = {
        INTERACTIVE_COMMANDS
        {.name  = NULL}
 };
+/** \endcond */
+
+/** Iterate over the list of all interactive commands. */
+#define FOR_EACH_COMMAND(c) for (c = icmds; c->name; c++)
 
 static int read_input_line(char *line, size_t size)
 {
        return fgets(line, size, stdin)? 1 : -1;
 }
 
-static int icom_def(__a_unused char *line)
+static int icom_run(__a_unused char *line)
 {
-       select_cmdline_parser_init(&select_conf);
+       return run_select_query(admissible_uids, fi);
+}
+
+static int icom_help(__a_unused char *line)
+{
+       struct interactive_command *c;
+
+       FOR_EACH_COMMAND(c)
+               fprintf(stdout, "%s\t%s\n", c->name, c->desc);
        return 1;
 }
 
+/**
+ * Print the list of commands with short descriptions.
+ */
+void print_interactive_help(void)
+{
+       struct interactive_command *c;
+       FOR_EACH_COMMAND(c)
+               fprintf(stdout, "\t%s\t%s\n", c->name, c->desc);
+}
+
+static int icom_reset(__a_unused char *line)
+{
+       NOTICE_LOG("resetting configuration to default\n");
+       free_format_info(fi);
+       fi = NULL;
+       free(admissible_uids);
+       admissible_uids = NULL;
+       select_cmdline_parser_init(&select_conf);
+       return parse_select_options(NULL, NULL, &admissible_uids, &fi);
+}
+
 static int icom_set(char *line)
 {
+       int ret;
        struct select_cmdline_parser_params params = {
                .override = 1,
                .initialize = 0,
@@ -54,54 +118,110 @@ static int icom_set(char *line)
                .check_ambiguity = 0,
                .print_errors = 1
        };
-       return select_cmdline_parser_string_ext(line, &select_conf, "select",
-               &params)?
-               -E_SYNTAX : 1;
-}
+       if (!line) {
+               select_cmdline_parser_dump(stdout, &select_conf);
+               return 1;
+       }
 
-static int icom_dump(__a_unused char *line)
-{
-       ERROR_LOG("dump: %s\n", select_conf.format_arg);
-       select_cmdline_parser_dump(stdout, &select_conf);
-       return 1;
+       free_format_info(fi);
+       fi = NULL;
+       free(admissible_uids);
+       admissible_uids = NULL;
+       ret = parse_select_options(line, &params, &admissible_uids, &fi);
+       if (ret >= 0)
+               return ret;
+       return icom_reset(NULL);
 }
 
 static int exec_interactive_command(char *line)
 {
-       const char const *delim = "\t\n ";
+       const char const *delim = "\t\n\f\r\v ";
        int i;
-       char *cmd = adu_strdup(line + strspn(line, delim));
-       char *p = cmd + strcspn(cmd, delim);
+       char *cmd, *args;
        int ret = -E_SYNTAX;
+       size_t len;
 
-       *p = '\0';
-       p++;
+       if (!line || !*line)
+               return 1;
+       len = strlen(line);
+
+       while (len && isspace(line[len - 1])) {
+               line[len - 1] = '\0';
+               len--;
+       }
+       if (!len)
+               return 1;
+       line += strspn(line, delim); /* skip initial whitespace */
+       if (!*line)
+               return 1;
+       /* OK, we have a non-empty line */
+       if (*line == '#')
+               return 1;
+       cmd = adu_strdup(line);
+       args = cmd + strcspn(cmd, delim);
+       if (!*args)
+               args = NULL;
+       else {
+               *args = '\0';
+               args++;
+               /* let args point to the next non-whitespace char */
+               args += strspn(args, delim);
+               if (!*args)
+                       args = NULL;
+       }
+       DEBUG_LOG("name: %s, args: %s.\n", cmd, args);
        for (i = 0; icmds[i].name; i++) {
-               ERROR_LOG("name: %s, cmd: %s.\n", icmds[i].name, cmd);
                if (strcmp(icmds[i].name, cmd))
                        continue;
-               ERROR_LOG("exec cmd: %s, args: %s\n", cmd, p);
-               ret = icmds[i].handler(p);
+               INFO_LOG("exec cmd: %s, args: %s\n", cmd, args);
+               ret = icmds[i].handler(args);
                break;
        }
        free(cmd);
        return ret;
 }
 
+static int icom_source(char *args)
+{
+       char line[255];
+       FILE *src = fopen(args, "r");
+       int ret;
+
+       if (!src)
+               return -ERRNO_TO_ERROR(errno);
+       while (fgets(line, sizeof(line), src)) {
+               ret = exec_interactive_command(line);
+               if (ret < 0)
+                       goto out;
+       }
+       ret = 1;
+out:
+       fclose(src);
+       return ret;
+}
+
+/**
+ * The main function for interactive mode.
+ *
+ * \return Standard.
+ */
 int com_interactive(void)
 {
        char line[255];
        int ret = 1;
 
+       select_cmdline_parser_init(&select_conf);
+       ret = parse_select_options(NULL, NULL, &admissible_uids, &fi);
+       if (ret< 0)
+               return ret;
+       ret = read_uid_file();
+       if (ret < 0)
+               return ret;
        while (read_input_line(line, sizeof(line)) >= 0) {
-               size_t len = strlen(line);
-               if (!len)
-                       continue;
-               if (line[len - 1] == '\n')
-                       line[len - 1] = '\0';
                ret = exec_interactive_command(line);
                if (ret < 0)
                        printf("%s\n", adu_strerror(-ret));
+               fflush(NULL);
        }
        return ret;
 }