summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEdward Thomson <ethomson@edwardthomson.com>2020-02-23 11:54:33 +0000
committerEdward Thomson <ethomson@edwardthomson.com>2022-02-26 14:43:48 -0500
commitc6dd82d9f14e9e24a52af637c4a56218ead13eda (patch)
tree65b9590acf66811be45896845409b2ad6acce5de
parent8526cbd56b0395b9427727e81e6f3c89768337b9 (diff)
downloadlibgit2-c6dd82d9f14e9e24a52af637c4a56218ead13eda.tar.gz
cli: introduce a help command
Add a framework for commands to be defined, and add our first one, "help". When `git2_cli help` is run, the `cmd_help` function will be invoked with the remaining command line arguments. This allows users to invoke `git2_cli help foo` to get information about the `foo` subcommand.
-rw-r--r--src/cli/README.md19
-rw-r--r--src/cli/cmd.c21
-rw-r--r--src/cli/cmd.h30
-rw-r--r--src/cli/cmd_help.c77
-rw-r--r--src/cli/main.c51
5 files changed, 193 insertions, 5 deletions
diff --git a/src/cli/README.md b/src/cli/README.md
index eefd2ff27..26f11d90a 100644
--- a/src/cli/README.md
+++ b/src/cli/README.md
@@ -1,3 +1,22 @@
# cli
A git-compatible command-line interface that uses libgit2.
+
+## Adding commands
+
+1. Individual commands have a `main`-like top-level entrypoint. For example:
+
+ ```c
+ int cmd_help(int argc, char **argv)
+ ```
+
+ Although this is the same signature as `main`, commands are not built as
+ individual standalone executables, they'll be linked into the main cli.
+ (Though there may be an option for command executables to be built as
+ standalone executables in the future.)
+
+2. Commands are prototyped in `cmd.h` and added to `main.c`'s list of
+ commands (`cli_cmds[]`). Commands should be specified with their name,
+ entrypoint and a brief description that can be printed in `git help`.
+ This is done because commands are linked into the main cli.
+
diff --git a/src/cli/cmd.c b/src/cli/cmd.c
new file mode 100644
index 000000000..2a7e71cdb
--- /dev/null
+++ b/src/cli/cmd.c
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "cli.h"
+#include "cmd.h"
+
+const cli_cmd_spec *cli_cmd_spec_byname(const char *name)
+{
+ const cli_cmd_spec *cmd;
+
+ for (cmd = cli_cmds; cmd->name; cmd++) {
+ if (!strcmp(cmd->name, name))
+ return cmd;
+ }
+
+ return NULL;
+}
diff --git a/src/cli/cmd.h b/src/cli/cmd.h
new file mode 100644
index 000000000..816614efc
--- /dev/null
+++ b/src/cli/cmd.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifndef CLI_cmd_h__
+#define CLI_cmd_h__
+
+/* Command definitions */
+typedef struct {
+ const char *name;
+ int (*fn)(int argc, char **argv);
+ const char *desc;
+} cli_cmd_spec;
+
+/* Options that are common to all commands (eg --help, --git-dir) */
+extern const cli_opt_spec cli_common_opts[];
+
+/* All the commands supported by the CLI */
+extern const cli_cmd_spec cli_cmds[];
+
+/* Find a command by name */
+extern const cli_cmd_spec *cli_cmd_spec_byname(const char *name);
+
+/* Commands */
+extern int cmd_help(int argc, char **argv);
+
+#endif /* CLI_cmd_h__ */
diff --git a/src/cli/cmd_help.c b/src/cli/cmd_help.c
new file mode 100644
index 000000000..d2ff5d4f4
--- /dev/null
+++ b/src/cli/cmd_help.c
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include <stdio.h>
+#include <git2.h>
+#include "cli.h"
+#include "cmd.h"
+
+#define COMMAND_NAME "help"
+
+static char *command;
+static int show_help;
+
+static const cli_opt_spec opts[] = {
+ { CLI_OPT_TYPE_SWITCH, "help", 0, &show_help, 1,
+ CLI_OPT_USAGE_HIDDEN, NULL, "display help about the help command" },
+ { CLI_OPT_TYPE_ARG, "command", 0, &command, 0,
+ CLI_OPT_USAGE_DEFAULT, "command", "the command to show help for" },
+ { 0 },
+};
+
+static int print_help(void)
+{
+ cli_opt_usage_fprint(stdout, PROGRAM_NAME, COMMAND_NAME, opts);
+ printf("\n");
+
+ printf("Display help information about %s. If a command is specified, help\n", PROGRAM_NAME);
+ printf("about that command will be shown. Otherwise, general information about\n");
+ printf("%s will be shown, including the commands available.\n", PROGRAM_NAME);
+
+ return 0;
+}
+
+static int print_commands(void)
+{
+ const cli_cmd_spec *cmd;
+
+ cli_opt_usage_fprint(stdout, PROGRAM_NAME, NULL, cli_common_opts);
+ printf("\n");
+
+ printf("These are the %s commands available:\n\n", PROGRAM_NAME);
+
+ for (cmd = cli_cmds; cmd->name; cmd++)
+ printf(" %-8s %s\n", cmd->name, cmd->desc);
+
+ printf("\nSee '%s help <command>' for more information on a specific command.\n", PROGRAM_NAME);
+
+ return 0;
+}
+
+int cmd_help(int argc, char **argv)
+{
+ cli_opt invalid_opt;
+
+ if (cli_opt_parse(&invalid_opt, opts, argv + 1, argc - 1, CLI_OPT_PARSE_GNU))
+ return cli_opt_usage_error(COMMAND_NAME, opts, &invalid_opt);
+
+ /* Show the meta-help */
+ if (show_help)
+ return print_help();
+
+ /* We were not asked to show help for a specific command. */
+ if (!command)
+ return print_commands();
+
+ /* If the user asks for help with the help command */
+ if (strcmp(command, "help") == 0)
+ return print_help();
+
+ fprintf(stderr, "%s: '%s' is not a %s command. See '%s help'.\n",
+ PROGRAM_NAME, command, PROGRAM_NAME, PROGRAM_NAME);
+ return CLI_EXIT_ERROR;
+}
diff --git a/src/cli/main.c b/src/cli/main.c
index 5eff56a1d..c68c349f7 100644
--- a/src/cli/main.c
+++ b/src/cli/main.c
@@ -8,19 +8,36 @@
#include <stdio.h>
#include <git2.h>
#include "cli.h"
+#include "cmd.h"
+static int show_help = 0;
static int show_version = 0;
+static char *command = NULL;
+static char **args = NULL;
-static const cli_opt_spec common_opts[] = {
- { CLI_OPT_TYPE_SWITCH, "version", 0, &show_version, 1,
- CLI_OPT_USAGE_DEFAULT, NULL, "display the version" },
+const cli_opt_spec cli_common_opts[] = {
+ { CLI_OPT_TYPE_SWITCH, "help", 0, &show_help, 1,
+ CLI_OPT_USAGE_DEFAULT, NULL, "display help information" },
+ { CLI_OPT_TYPE_SWITCH, "version", 0, &show_version, 1,
+ CLI_OPT_USAGE_DEFAULT, NULL, "display the version" },
+ { CLI_OPT_TYPE_ARG, "command", 0, &command, 0,
+ CLI_OPT_USAGE_REQUIRED, "command", "the command to run" },
+ { CLI_OPT_TYPE_ARGS, "args", 0, &args, 0,
+ CLI_OPT_USAGE_DEFAULT, "args", "arguments for the command" },
{ 0 }
};
+const cli_cmd_spec cli_cmds[] = {
+ { "help", cmd_help, "Display help information" },
+ { NULL }
+};
+
int main(int argc, char **argv)
{
+ const cli_cmd_spec *cmd;
cli_opt_parser optparser;
cli_opt opt;
+ int args_len = 0;
int ret = 0;
if (git_libgit2_init() < 0) {
@@ -28,16 +45,26 @@ int main(int argc, char **argv)
exit(CLI_EXIT_GIT);
}
- cli_opt_parser_init(&optparser, common_opts, argv + 1, argc - 1, CLI_OPT_PARSE_GNU);
+ cli_opt_parser_init(&optparser, cli_common_opts, argv + 1, argc - 1, CLI_OPT_PARSE_GNU);
/* Parse the top-level (common) options and command information */
while (cli_opt_parser_next(&opt, &optparser)) {
if (!opt.spec) {
cli_opt_status_fprint(stderr, PROGRAM_NAME, &opt);
- cli_opt_usage_fprint(stderr, PROGRAM_NAME, NULL, common_opts);
+ cli_opt_usage_fprint(stderr, PROGRAM_NAME, NULL, cli_common_opts);
ret = CLI_EXIT_USAGE;
goto done;
}
+
+ /*
+ * When we see a command, stop parsing and capture the
+ * remaining arguments as args for the command itself.
+ */
+ if (command) {
+ args = &argv[optparser.idx];
+ args_len = (int)(argc - optparser.idx);
+ break;
+ }
}
if (show_version) {
@@ -45,6 +72,20 @@ int main(int argc, char **argv)
goto done;
}
+ /* If there was no command, we want to invoke "help" */
+ if (!command || show_help) {
+ cli_opt_usage_fprint(stdout, PROGRAM_NAME, NULL, cli_common_opts);
+ goto done;
+ }
+
+ if ((cmd = cli_cmd_spec_byname(command)) == NULL) {
+ ret = cli_error("'%s' is not a %s command. See '%s help'.",
+ command, PROGRAM_NAME, PROGRAM_NAME);
+ goto done;
+ }
+
+ ret = cmd->fn(args_len, args);
+
done:
git_libgit2_shutdown();
return ret;