diff options
author | Edward Thomson <ethomson@edwardthomson.com> | 2020-02-23 11:54:33 +0000 |
---|---|---|
committer | Edward Thomson <ethomson@edwardthomson.com> | 2022-02-26 14:43:48 -0500 |
commit | c6dd82d9f14e9e24a52af637c4a56218ead13eda (patch) | |
tree | 65b9590acf66811be45896845409b2ad6acce5de | |
parent | 8526cbd56b0395b9427727e81e6f3c89768337b9 (diff) | |
download | libgit2-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.md | 19 | ||||
-rw-r--r-- | src/cli/cmd.c | 21 | ||||
-rw-r--r-- | src/cli/cmd.h | 30 | ||||
-rw-r--r-- | src/cli/cmd_help.c | 77 | ||||
-rw-r--r-- | src/cli/main.c | 51 |
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; |