ctdb-common: Add command line processing abstraction
authorAmitay Isaacs <amitay@gmail.com>
Tue, 24 Apr 2018 13:17:18 +0000 (23:17 +1000)
committerMartin Schwenke <martins@samba.org>
Sat, 12 May 2018 10:06:28 +0000 (12:06 +0200)
Signed-off-by: Amitay Isaacs <amitay@gmail.com>
Reviewed-by: Martin Schwenke <martin@meltin.net>
ctdb/common/cmdline.c [new file with mode: 0644]
ctdb/common/cmdline.h [new file with mode: 0644]
ctdb/tests/cunit/cmdline_test_001.sh [new file with mode: 0755]
ctdb/tests/src/cmdline_test.c [new file with mode: 0644]
ctdb/wscript

diff --git a/ctdb/common/cmdline.c b/ctdb/common/cmdline.c
new file mode 100644 (file)
index 0000000..2540ce5
--- /dev/null
@@ -0,0 +1,517 @@
+/*
+   Command line processing
+
+   Copyright (C) Amitay Isaacs  2018
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+
+#include <popt.h>
+#include <talloc.h>
+#include <tevent.h>
+
+#include "lib/util/debug.h"
+
+#include "common/cmdline.h"
+
+#define CMDLINE_MAX_LEN                80
+
+struct cmdline_context {
+       const char *prog;
+       struct poptOption *options;
+       struct cmdline_command *commands;
+       int max_len;
+       poptContext pc;
+       int argc, arg0;
+       const char **argv;
+       struct cmdline_command *match_cmd;
+};
+
+static bool cmdline_show_help = false;
+
+static void cmdline_popt_help(poptContext pc,
+                             enum poptCallbackReason reason,
+                             struct poptOption *key,
+                             const char *arg,
+                             void *data)
+{
+       if (key->shortName == 'h') {
+               cmdline_show_help = true;
+       }
+}
+
+struct poptOption cmdline_help_options[] = {
+       { NULL, '\0', POPT_ARG_CALLBACK, cmdline_popt_help, 0, NULL, NULL },
+       { "help", 'h', 0, NULL, 'h', "Show this help message", NULL },
+       POPT_TABLEEND
+};
+
+#define CMDLINE_HELP_OPTIONS \
+       { NULL, '\0', POPT_ARG_INCLUDE_TABLE, cmdline_help_options, \
+         0, "Help Options:", NULL }
+
+static bool cmdline_option_check(struct poptOption *option)
+{
+       if (option->longName == NULL) {
+               D_ERR("Option has no long name\n");
+               return false;
+       }
+
+       if (option->argInfo != POPT_ARG_STRING &&
+           option->argInfo != POPT_ARG_INT &&
+           option->argInfo != POPT_ARG_LONG &&
+           option->argInfo != POPT_ARG_VAL &&
+           option->argInfo != POPT_ARG_FLOAT &&
+           option->argInfo != POPT_ARG_DOUBLE) {
+               D_ERR("Option '%s' has unsupported type\n", option->longName);
+               return false;
+       }
+
+       if (option->arg == NULL) {
+               D_ERR("Option '%s' has invalid arg\n", option->longName);
+               return false;
+       }
+
+       if (option->descrip == NULL) {
+               D_ERR("Option '%s' has no help msg\n", option->longName);
+               return false;
+       }
+
+       return true;
+}
+
+static bool cmdline_options_check(struct poptOption *options)
+{
+       int i;
+       bool ok;
+
+       if (options == NULL) {
+               return true;
+       }
+
+       i = 0;
+       while (options[i].longName != NULL || options[i].shortName != '\0') {
+               ok = cmdline_option_check(&options[i]);
+               if (!ok) {
+                       return false;
+               }
+               i++;
+       }
+
+       return true;
+}
+
+static int cmdline_options_define(TALLOC_CTX *mem_ctx,
+                                 struct poptOption *user_options,
+                                 struct poptOption **result)
+{
+       struct poptOption *options;
+       int count, i;
+
+       count = (user_options == NULL ? 2 : 3);
+
+       options = talloc_array(mem_ctx, struct poptOption, count);
+       if (options == NULL) {
+               return ENOMEM;
+       }
+
+       i = 0;
+       options[i++] = (struct poptOption) CMDLINE_HELP_OPTIONS;
+       if (user_options != NULL) {
+               options[i++] = (struct poptOption) {
+                       .argInfo = POPT_ARG_INCLUDE_TABLE,
+                       .arg = user_options,
+                       .descrip = "Options:",
+               };
+       }
+       options[i++] = (struct poptOption) POPT_TABLEEND;
+
+       *result = options;
+       return 0;
+}
+
+static bool cmdline_command_check(struct cmdline_command *cmd, int *max_len)
+{
+       size_t len;
+
+       if (cmd->name == NULL) {
+               return false;
+       }
+
+       if (cmd->fn == NULL) {
+               D_ERR("Command '%s' has no implementation function\n",
+                     cmd->name);
+               return false;
+       }
+
+       if (cmd->msg_help == NULL) {
+               D_ERR("Command '%s' has no help msg\n", cmd->name);
+               return false;
+       }
+
+       len = strlen(cmd->name);
+       if (cmd->msg_args != NULL) {
+               len += strlen(cmd->msg_args);
+       }
+       if (len > CMDLINE_MAX_LEN) {
+               D_ERR("Command '%s' is too long (%zu)\n", cmd->name, len);
+               return false;
+       }
+
+       if (len > *max_len) {
+               *max_len = (int)len;
+       }
+
+       len = strlen(cmd->msg_help);
+       if (len > CMDLINE_MAX_LEN) {
+               D_ERR("Command '%s' help too long (%zu)\n", cmd->name, len);
+               return false;
+       }
+
+       return true;
+}
+
+static bool cmdline_commands_check(struct cmdline_command *commands,
+                                  int *max_len)
+{
+       int i;
+       bool ok;
+
+       if (commands == NULL) {
+               return false;
+       }
+
+       for (i=0; commands[i].name != NULL; i++) {
+               ok = cmdline_command_check(&commands[i], max_len);
+               if (!ok) {
+                       return false;
+               }
+       }
+
+       return true;
+}
+
+static int cmdline_context_destructor(struct cmdline_context *cmdline);
+
+int cmdline_init(TALLOC_CTX *mem_ctx,
+                const char *prog,
+                struct poptOption *options,
+                struct cmdline_command *commands,
+                struct cmdline_context **result)
+{
+       struct cmdline_context *cmdline;
+       int ret, max_len = 0;
+       bool ok;
+
+       if (prog == NULL) {
+               return EINVAL;
+       }
+
+       ok = cmdline_options_check(options);
+       if (!ok) {
+               return EINVAL;
+       }
+
+       ok = cmdline_commands_check(commands, &max_len);
+       if (!ok) {
+               return EINVAL;
+       }
+
+       cmdline = talloc_zero(mem_ctx, struct  cmdline_context);
+       if (cmdline == NULL) {
+               return ENOMEM;
+       }
+
+       cmdline->prog = talloc_strdup(cmdline, prog);
+       if (cmdline->prog == NULL) {
+               talloc_free(cmdline);
+               return ENOMEM;
+       }
+
+       ret = cmdline_options_define(cmdline, options, &cmdline->options);
+       if (ret != 0) {
+               talloc_free(cmdline);
+               return ret;
+       }
+       cmdline->commands = commands;
+       cmdline->max_len = max_len;
+
+       cmdline->argc = 1;
+       cmdline->argv = talloc_array(cmdline, const char *, 2);
+       if (cmdline->argv == NULL) {
+               talloc_free(cmdline);
+               return ENOMEM;
+       }
+       cmdline->argv[0] = cmdline->prog;
+       cmdline->argv[1] = NULL;
+
+       /* Dummy popt context for generating help */
+       cmdline->pc = poptGetContext(cmdline->prog,
+                                    cmdline->argc,
+                                    cmdline->argv,
+                                    cmdline->options,
+                                    0);
+       if (cmdline->pc == NULL) {
+               talloc_free(cmdline);
+               return ENOMEM;
+       }
+
+       talloc_set_destructor(cmdline, cmdline_context_destructor);
+
+       *result = cmdline;
+       return 0;
+}
+
+static int cmdline_context_destructor(struct cmdline_context *cmdline)
+{
+       if (cmdline->pc != NULL) {
+               poptFreeContext(cmdline->pc);
+       }
+
+       return 0;
+}
+
+static int cmdline_parse_options(struct cmdline_context *cmdline,
+                                int argc,
+                                const char **argv)
+{
+       int opt;
+
+       if (cmdline->pc != NULL) {
+               poptFreeContext(cmdline->pc);
+       }
+
+       cmdline->pc = poptGetContext(cmdline->prog,
+                                    argc,
+                                    argv,
+                                    cmdline->options,
+                                    0);
+       if (cmdline->pc == NULL) {
+               return ENOMEM;
+       }
+
+       while ((opt = poptGetNextOpt(cmdline->pc)) != -1) {
+               D_ERR("Invalid option %s: %s\n",
+                     poptBadOption(cmdline->pc, 0),
+                     poptStrerror(opt));
+               return EINVAL;
+       }
+
+       /* Set up remaining arguments for commands */
+       cmdline->argc = 0;
+       cmdline->argv = poptGetArgs(cmdline->pc);
+       if (cmdline->argv != NULL) {
+               while (cmdline->argv[cmdline->argc] != NULL) {
+                       cmdline->argc++;
+               }
+       }
+
+       return 0;
+}
+
+static int cmdline_match(struct cmdline_context *cmdline)
+{
+       int i;
+
+       if (cmdline->argc == 0 || cmdline->argv == NULL) {
+               cmdline->match_cmd = NULL;
+               return EINVAL;
+       }
+
+       for (i=0; cmdline->commands[i].name != NULL; i++) {
+               struct cmdline_command *cmd;
+               char name[CMDLINE_MAX_LEN+1];
+               size_t len;
+               char *t, *str;
+               int n = 0;
+               bool match;
+
+               cmd = &cmdline->commands[i];
+               len = strlcpy(name, cmd->name, sizeof(name));
+               if (len >= sizeof(name)) {
+                       D_ERR("Skipping long command '%s'\n", cmd->name);
+                       continue;
+               }
+
+               str = name;
+               while ((t = strtok(str, " ")) != NULL) {
+                       if (n >= cmdline->argc) {
+                               match = false;
+                               break;
+                       }
+                       if (cmdline->argv[n] == NULL) {
+                               match = false;
+                               break;
+                       }
+                       if (strcmp(cmdline->argv[n], t) == 0) {
+                               match = true;
+                               cmdline->arg0 = n+1;
+                       } else {
+                               match = false;
+                               break;
+                       }
+
+                       n += 1;
+                       str = NULL;
+               }
+
+               if (match) {
+                       cmdline->match_cmd = cmd;
+                       return 0;
+               }
+       }
+
+       cmdline->match_cmd = NULL;
+       return ENOENT;
+}
+
+int cmdline_parse(struct cmdline_context *cmdline,
+                 int argc,
+                 const char **argv,
+                 bool parse_options)
+{
+       int ret;
+
+       if (argc < 2) {
+               return EINVAL;
+       }
+
+       cmdline_show_help = false;
+
+       if (parse_options) {
+               ret = cmdline_parse_options(cmdline, argc, argv);
+               if (ret != 0) {
+                       return ret;
+               }
+       } else {
+               cmdline->argc = argc;
+               cmdline->argv = argv;
+       }
+
+       ret = cmdline_match(cmdline);
+       if (!cmdline_show_help && ret != 0) {
+               return ret;
+       }
+
+       return 0;
+}
+
+static void cmdline_usage_command(struct cmdline_context *cmdline,
+                                 struct cmdline_command *cmd,
+                                 bool print_all)
+{
+       int len;
+
+       len = (int)strlen(cmd->name);
+
+       printf("  %s ", cmd->name);
+       if (print_all) {
+               printf("%-*s",
+                      cmdline->max_len-len,
+                      cmd->msg_args == NULL ? "" : cmd->msg_args);
+       } else {
+               printf("%s", cmd->msg_args == NULL ? "" : cmd->msg_args);
+       }
+       printf("     %s\n", cmd->msg_help);
+}
+
+static void cmdline_usage_full(struct cmdline_context *cmdline)
+{
+       int i;
+
+       poptSetOtherOptionHelp(cmdline->pc, "[<options>] <command> [<args>]");
+       poptPrintHelp(cmdline->pc, stdout, 0);
+
+       printf("\nCommands:\n");
+       for (i=0; cmdline->commands[i].name != NULL; i++) {
+               cmdline_usage_command(cmdline, &cmdline->commands[i], true);
+
+       }
+}
+
+void cmdline_usage(struct cmdline_context *cmdline, const char *cmd_name)
+{
+       struct cmdline_command *cmd = NULL;
+       int i;
+
+       if (cmd_name == NULL) {
+               cmdline_usage_full(cmdline);
+               return;
+       }
+
+       for (i=0; cmdline->commands[i].name != NULL; i++) {
+               if (strcmp(cmdline->commands[i].name, cmd_name) == 0) {
+                       cmd = &cmdline->commands[i];
+                       break;
+               }
+       }
+
+       if (cmd == NULL) {
+               cmdline_usage_full(cmdline);
+               return;
+       }
+
+       poptSetOtherOptionHelp(cmdline->pc, "<command> [<args>]");
+       poptPrintUsage(cmdline->pc, stdout, 0);
+
+       printf("\n");
+       cmdline_usage_command(cmdline, cmd, false);
+}
+
+int cmdline_run(struct cmdline_context *cmdline,
+               void *private_data,
+               int *result)
+{
+       struct cmdline_command *cmd = cmdline->match_cmd;
+       TALLOC_CTX *tmp_ctx;
+       int ret;
+
+       if (cmdline_show_help) {
+               const char *name = NULL;
+
+               if (cmd != NULL) {
+                       name = cmdline->match_cmd->name;
+               }
+
+               cmdline_usage(cmdline, name);
+
+               if (result != NULL) {
+                       *result = 0;
+               }
+               return EAGAIN;
+       }
+
+       if (cmd == NULL) {
+               return ENOENT;
+       }
+
+       tmp_ctx = talloc_new(cmdline);
+       if (tmp_ctx == NULL) {
+               return ENOMEM;
+       }
+
+       ret = cmd->fn(tmp_ctx,
+                     cmdline->argc - cmdline->arg0,
+                     &cmdline->argv[cmdline->arg0],
+                     private_data);
+
+       talloc_free(tmp_ctx);
+
+       if (result != NULL) {
+               *result = ret;
+       }
+       return 0;
+}
diff --git a/ctdb/common/cmdline.h b/ctdb/common/cmdline.h
new file mode 100644 (file)
index 0000000..1e11c66
--- /dev/null
@@ -0,0 +1,148 @@
+/*
+   Command line processing
+
+   Copyright (C) Amitay Isaacs  2018
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __CTDB_CMDLINE_H__
+#define __CTDB_CMDLINE_H__
+
+#include <popt.h>
+#include <talloc.h>
+
+/**
+ * @file cmdline.h
+ *
+ * @brief Command-line handling with options and commands
+ *
+ * This abstraction encapsulates the boiler-plate for parsing options,
+ * commands and arguments on command-line.
+ *
+ * Options handling is done using popt.
+ */
+
+/**
+ * @brief Abstract data structure holding command-line configuration
+ */
+struct cmdline_context;
+
+/**
+ * @brief A command definition structure
+ *
+ * @name is the name of the command
+ * @fn is the implementation of the command
+ * @msg_help is the help message describing command
+ * @msg_args is the help message describing arguments
+ *
+ * A command name can be a single word or multiple words separated with spaces.
+ *
+ * An implementation function should return 0 on success and non-zero value
+ * on failure.  This value is returned as result in @cmdline_run.
+ */
+struct cmdline_command {
+       const char *name;
+       int (*fn)(TALLOC_CTX *mem_ctx,
+                 int argc,
+                 const char **argv,
+                 void *private_data);
+       const char *msg_help;
+       const char *msg_args;
+};
+
+/**
+ * @brief convinience macro to define the end of commands list
+ *
+ * Here is an example of defining commands list.
+ *
+ * struct cmdline_command commands[] = {
+ *     { "command1", command1_func, "Run command1", NULL },
+ *     { "command2", command2_func, "Run command2", "<filename>" },
+ *     CMDLINE_TABLEEND
+ * };
+ */
+#define CMDLINE_TABLEEND  { NULL, NULL, NULL, NULL }
+
+/**
+ * @brief Initialize cmdline abstraction
+ *
+ * If there are no options, options can be NULL.
+ *
+ * Help options (--help, -h) are automatically added to the options.
+ *
+ * @param[in] mem_ctx Talloc memory context
+ * @param[in] prog Program name
+ * @param[in] options Command-line options
+ * @param[in] commands Commands array
+ * @param[out] result New cmdline context
+ * @return 0 on success, errno on failure
+ *
+ * Freeing cmdline context will free up all the resources.
+ */
+int cmdline_init(TALLOC_CTX *mem_ctx,
+                const char *prog,
+                struct poptOption *options,
+                struct cmdline_command *commands,
+                struct cmdline_context **result);
+
+/**
+ * @brief Parse command line options and commands/arguments
+ *
+ * This function parses the arguments to process options and commands.
+ *
+ * This function should be passed the arguments to main() and parse_options
+ * should be set to true.  If cmdline is used for handling second-level
+ * commands, then parse_options should be set to false.
+ *
+ * If argv does not match any command, then ENOENT is returned.
+ *
+ * @param[in] cmdline Cmdline context
+ * @param[in] argc Number of arguments
+ * @param[in] argv Arguments array
+ * @param[in] parse_options Whether to parse for options
+ * @return 0 on success, errno on failure
+ */
+int cmdline_parse(struct cmdline_context *cmdline,
+                 int argc,
+                 const char **argv,
+                 bool parse_options);
+
+/**
+ * @brief Excecute the function for the command matched by @cmdline_parse
+ *
+ * @param[in] cmdline Cmdline context
+ * @param[in] private_data Private data for implementation function
+ * @param[out] result Return value from the implementation function
+ * @return 0 on success, errno on failure
+ *
+ * If help options are specified, then detailed help will be printed and
+ * the return value will be EAGAIN.
+ */
+int cmdline_run(struct cmdline_context *cmdline,
+               void *private_data,
+               int *result);
+
+/**
+ * @brief Print usage help message to stdout
+ *
+ * @param[in] cmdline Cmdline context
+ * @param[in] command Command string
+ *
+ * If command is NULL, then full help is printed.
+ * If command is specified, then compact help is printed.
+ */
+void cmdline_usage(struct cmdline_context *cmdline, const char *command);
+
+#endif /* __CTDB_CMDLINE_H__ */
diff --git a/ctdb/tests/cunit/cmdline_test_001.sh b/ctdb/tests/cunit/cmdline_test_001.sh
new file mode 100755 (executable)
index 0000000..527b114
--- /dev/null
@@ -0,0 +1,58 @@
+#!/bin/sh
+
+. "${TEST_SCRIPTS_DIR}/unit.sh"
+
+ok_null
+unit_test cmdline_test 1
+
+ok <<EOF
+Command 'nofunc' has no implementation function
+Command 'nohelp' has no help msg
+Command 'really really long command with lots of words' is too long (85)
+Command 'longhelp' help too long (90)
+EOF
+unit_test cmdline_test 2
+
+ok <<EOF
+Option has no long name
+Option 'debug' has unsupported type
+Option 'debug' has invalid arg
+EOF
+unit_test cmdline_test 3
+
+ok <<EOF
+Usage: test4 [<options>] <command> [<args>]
+
+Help Options:
+  -h, --help                              Show this help message
+
+Options:
+  -c, --count=INT                         Option help of length thirty.
+  -v, --value=Value help of length 23     Short description
+
+Commands:
+  A really really long command <a long arguments message>     This is a really long help message
+  short command <short arg msg>                               short msg for short command
+Usage: test4 [-h] [-h|--help] [-c|--count=INT]
+        [-v|--value=Value help of length 23] <command> [<args>]
+
+  short command <short arg msg>     short msg for short command
+EOF
+unit_test cmdline_test 4
+
+ok <<EOF
+Usage: test5 [<options>] <command> [<args>]
+
+Help Options:
+  -h, --help     Show this help message
+
+Commands:
+  action one      action one help
+  action two      action two help
+EOF
+unit_test cmdline_test 5
+
+ok <<EOF
+arg1
+EOF
+unit_test cmdline_test 6
diff --git a/ctdb/tests/src/cmdline_test.c b/ctdb/tests/src/cmdline_test.c
new file mode 100644 (file)
index 0000000..8f5b602
--- /dev/null
@@ -0,0 +1,351 @@
+/*
+   Command line processing tests
+
+   Copyright (C) Amitay Isaacs  2018
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+
+#include <popt.h>
+#include <talloc.h>
+
+#include <assert.h>
+
+#include "common/cmdline.c"
+
+static int dummy_func(TALLOC_CTX *mem_ctx,
+                     int argc,
+                     const char **argv,
+                     void *private_data)
+{
+       return 0;
+}
+
+static struct poptOption dummy_options[] = {
+       POPT_TABLEEND
+};
+
+static struct cmdline_command dummy_commands[] = {
+       CMDLINE_TABLEEND
+};
+
+static void test1(void)
+{
+       TALLOC_CTX *mem_ctx;
+       struct cmdline_context *cmdline;
+       int ret;
+
+       mem_ctx = talloc_new(NULL);
+       assert(mem_ctx != NULL);
+
+       ret = cmdline_init(mem_ctx, NULL, NULL, NULL, &cmdline);
+       assert(ret == EINVAL);
+
+       ret = cmdline_init(mem_ctx, "test1", NULL, NULL, &cmdline);
+       assert(ret == EINVAL);
+
+       ret = cmdline_init(mem_ctx, "test1", dummy_options, NULL, &cmdline);
+       assert(ret == EINVAL);
+
+       talloc_free(mem_ctx);
+}
+
+static struct cmdline_command test2_nofunc[] = {
+       { "nofunc", NULL, NULL, NULL },
+       CMDLINE_TABLEEND
+};
+
+static struct cmdline_command test2_nohelp[] = {
+       { "nohelp", dummy_func, NULL, NULL },
+       CMDLINE_TABLEEND
+};
+
+static struct cmdline_command test2_long[] = {
+       { "really really long command with lots of words",
+         dummy_func, "long command help",
+         "<and lots of really long long arguments>" },
+       CMDLINE_TABLEEND
+};
+
+static struct cmdline_command test2_longhelp[] = {
+       { "longhelp", dummy_func,
+         "this is a really really really long help message" \
+         "with lots of words and lots of description",
+         NULL },
+       CMDLINE_TABLEEND
+};
+
+static struct cmdline_command test2_twowords[] = {
+       { "multiple words", dummy_func, "multiple words help", NULL },
+       CMDLINE_TABLEEND
+};
+
+static void test2(void)
+{
+       TALLOC_CTX *mem_ctx;
+       struct cmdline_context *cmdline;
+       int ret;
+
+       mem_ctx = talloc_new(NULL);
+       assert(mem_ctx != NULL);
+
+       ret = cmdline_init(mem_ctx, "test2", NULL, test2_nofunc, &cmdline);
+       assert(ret == EINVAL);
+
+       ret = cmdline_init(mem_ctx, "test2", NULL, test2_nohelp, &cmdline);
+       assert(ret == EINVAL);
+
+       ret = cmdline_init(mem_ctx, "test2", NULL, test2_long, &cmdline);
+       assert(ret == EINVAL);
+
+       ret = cmdline_init(mem_ctx, "test2", NULL, test2_longhelp, &cmdline);
+       assert(ret == EINVAL);
+
+       ret = cmdline_init(mem_ctx, "test2", NULL, test2_twowords, &cmdline);
+       assert(ret == 0);
+
+       talloc_free(mem_ctx);
+}
+
+struct {
+       const char *str;
+} test3_data;
+
+static struct poptOption test3_noname[] = {
+       { NULL, 'o', POPT_ARG_STRING, &test3_data.str, 0,
+         "Noname option", NULL },
+       POPT_TABLEEND
+};
+
+static struct poptOption test3_notype[] = {
+       { "debug", 'd', POPT_ARG_NONE, NULL, 0,
+         "No argument option", NULL },
+       POPT_TABLEEND
+};
+
+static struct poptOption test3_noarg[] = {
+       { "debug", 'd', POPT_ARG_STRING, NULL, 0,
+         "No argument option", NULL },
+       POPT_TABLEEND
+};
+
+static void test3(void)
+{
+       TALLOC_CTX *mem_ctx;
+       struct cmdline_context *cmdline;
+       int ret;
+
+       mem_ctx = talloc_new(NULL);
+       assert(mem_ctx != NULL);
+
+       ret = cmdline_init(mem_ctx,
+                          "test3",
+                          test3_noname,
+                          dummy_commands,
+                          &cmdline);
+       assert(ret == EINVAL);
+
+       ret = cmdline_init(mem_ctx,
+                          "test3",
+                          test3_notype,
+                          dummy_commands,
+                          &cmdline);
+       assert(ret == EINVAL);
+
+       ret = cmdline_init(mem_ctx,
+                          "test3",
+                          test3_noarg,
+                          dummy_commands,
+                          &cmdline);
+       assert(ret == EINVAL);
+
+       talloc_free(mem_ctx);
+}
+
+static int test4_count;
+static int test4_value;
+
+static struct poptOption test4_options[] = {
+       { "count", 'c', POPT_ARG_INT, &test4_count, 0,
+         "Option help of length thirty.", NULL },
+       { "value", 'v', POPT_ARG_INT, &test4_value, 0,
+         "Short description", "Value help of length 23" },
+       POPT_TABLEEND
+};
+
+static struct cmdline_command test4_commands[] = {
+       { "A really really long command", dummy_func,
+         "This is a really long help message",
+         "<a long arguments message>" },
+       { "short command", dummy_func,
+         "short msg for short command", "<short arg msg>" },
+       CMDLINE_TABLEEND
+};
+
+static void test4(void)
+{
+       TALLOC_CTX *mem_ctx;
+       struct cmdline_context *cmdline;
+       int ret;
+
+       mem_ctx = talloc_new(NULL);
+       assert(mem_ctx != NULL);
+
+       ret = cmdline_init(mem_ctx,
+                          "test4",
+                          test4_options,
+                          test4_commands,
+                          &cmdline);
+       assert(ret == 0);
+
+       cmdline_usage(cmdline, NULL);
+       cmdline_usage(cmdline, "short command");
+
+       talloc_free(mem_ctx);
+}
+
+static int action_func(TALLOC_CTX *mem_ctx,
+                      int argc,
+                      const char **argv,
+                      void *private_data)
+{
+       if (argc != 1) {
+               return 100;
+       }
+
+       printf("%s\n", argv[0]);
+       return 200;
+}
+
+static struct cmdline_command action_commands[] = {
+       { "action one", dummy_func, "action one help", NULL },
+       { "action two", action_func, "action two help", NULL },
+       CMDLINE_TABLEEND
+};
+
+static void test5(void)
+{
+       TALLOC_CTX *mem_ctx;
+       struct cmdline_context *cmdline;
+       const char *argv1[] = { "test5", "--help" };
+       const char *argv2[] = { "test5", "action" };
+       const char *argv3[] = { "test5", "action", "--help" };
+       const char *argv4[] = { "test5", "action", "one" };
+       int ret, result;
+
+       mem_ctx = talloc_new(NULL);
+       assert(mem_ctx != NULL);
+
+       ret = cmdline_init(mem_ctx, "test5", NULL, action_commands, &cmdline);
+       assert(ret == 0);
+
+       ret = cmdline_parse(cmdline, 2, argv1, true);
+       assert(ret == 0);
+
+       ret = cmdline_run(cmdline, NULL, &result);
+       assert(ret == EAGAIN);
+       assert(result == 0);
+
+       ret = cmdline_parse(cmdline, 2, argv2, true);
+       assert(ret == ENOENT);
+
+       ret = cmdline_parse(cmdline, 3, argv3, true);
+       assert(ret == 0);
+
+       ret = cmdline_parse(cmdline, 3, argv4, true);
+       assert(ret == 0);
+
+       talloc_free(mem_ctx);
+}
+
+static void test6(void)
+{
+       TALLOC_CTX *mem_ctx;
+       struct cmdline_context *cmdline;
+       const char *argv1[] = { "action", "two" };
+       const char *argv2[] = { "action", "two", "arg1" };
+       const char *argv3[] = { "action", "two", "arg1", "arg2" };
+       int ret, result;
+
+       mem_ctx = talloc_new(NULL);
+       assert(mem_ctx != NULL);
+
+       ret = cmdline_init(mem_ctx, "test6", NULL, action_commands, &cmdline);
+       assert(ret == 0);
+
+       ret = cmdline_parse(cmdline, 2, argv1, false);
+       assert(ret == 0);
+
+       ret = cmdline_run(cmdline, NULL, &result);
+       assert(ret == 0);
+       assert(result == 100);
+
+       ret = cmdline_parse(cmdline, 3, argv2, false);
+       assert(ret == 0);
+
+       ret = cmdline_run(cmdline, NULL, &result);
+       assert(ret == 0);
+       assert(result == 200);
+
+       ret = cmdline_parse(cmdline, 4, argv3, false);
+       assert(ret == 0);
+
+       ret = cmdline_run(cmdline, NULL, &result);
+       assert(ret == 0);
+       assert(result == 100);
+
+       talloc_free(mem_ctx);
+}
+
+int main(int argc, const char **argv)
+{
+       int num;
+
+       if (argc < 2) {
+               fprintf(stderr, "Usage %s <testnum>\n", argv[0]);
+               exit(1);
+       }
+
+       num = atoi(argv[1]);
+
+       switch (num) {
+       case 1:
+               test1();
+               break;
+
+       case 2:
+               test2();
+               break;
+
+       case 3:
+               test3();
+               break;
+
+       case 4:
+               test4();
+               break;
+
+       case 5:
+               test5();
+               break;
+
+       case 6:
+               test6();
+               break;
+       }
+
+       return 0;
+}
index 89020c5d122de64a96159a32acf830ee187781d9..c1a3dad28700d5d43ce90fa5973ed561e1eec63f 100644 (file)
@@ -399,9 +399,11 @@ def build(bld):
                                              logging.c rb_tree.c tunable.c
                                              pidfile.c run_proc.c
                                              hash_count.c run_event.c
-                                             sock_client.c version.c'''),
+                                             sock_client.c version.c
+                                             cmdline.c
+                                          '''),
                         deps='''samba-util sys_rw tevent-util
-                                replace talloc tevent tdb''')
+                                replace talloc tevent tdb popt''')
 
     bld.SAMBA_SUBSYSTEM('ctdb-protocol',
                         source=bld.SUBDIR('protocol',
@@ -748,6 +750,7 @@ def build(bld):
         'sock_io_test',
         'hash_count_test',
         'run_event_test',
+        'cmdline_test'
     ]
 
     for target in ctdb_unit_tests:
@@ -755,7 +758,7 @@ def build(bld):
 
         bld.SAMBA_BINARY(target,
                          source=src,
-                         deps='''talloc tevent tdb tevent-util
+                         deps='''talloc tevent tdb tevent-util popt
                                  LIBASYNC_REQ samba-util sys_rw''',
                          install_path='${CTDB_TEST_LIBEXECDIR}')