Commit c6dd82d9 by Edward Thomson

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.
parent 8526cbd5
# cli # cli
A git-compatible command-line interface that uses libgit2. 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.
/*
* 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;
}
/*
* 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__ */
/*
* 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;
}
...@@ -8,19 +8,36 @@ ...@@ -8,19 +8,36 @@
#include <stdio.h> #include <stdio.h>
#include <git2.h> #include <git2.h>
#include "cli.h" #include "cli.h"
#include "cmd.h"
static int show_help = 0;
static int show_version = 0; static int show_version = 0;
static char *command = NULL;
static char **args = NULL;
static const cli_opt_spec common_opts[] = { const cli_opt_spec cli_common_opts[] = {
{ CLI_OPT_TYPE_SWITCH, "version", 0, &show_version, 1, { CLI_OPT_TYPE_SWITCH, "help", 0, &show_help, 1,
CLI_OPT_USAGE_DEFAULT, NULL, "display the version" }, 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 } { 0 }
}; };
const cli_cmd_spec cli_cmds[] = {
{ "help", cmd_help, "Display help information" },
{ NULL }
};
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
const cli_cmd_spec *cmd;
cli_opt_parser optparser; cli_opt_parser optparser;
cli_opt opt; cli_opt opt;
int args_len = 0;
int ret = 0; int ret = 0;
if (git_libgit2_init() < 0) { if (git_libgit2_init() < 0) {
...@@ -28,16 +45,26 @@ int main(int argc, char **argv) ...@@ -28,16 +45,26 @@ int main(int argc, char **argv)
exit(CLI_EXIT_GIT); 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 */ /* Parse the top-level (common) options and command information */
while (cli_opt_parser_next(&opt, &optparser)) { while (cli_opt_parser_next(&opt, &optparser)) {
if (!opt.spec) { if (!opt.spec) {
cli_opt_status_fprint(stderr, PROGRAM_NAME, &opt); 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; ret = CLI_EXIT_USAGE;
goto done; 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) { if (show_version) {
...@@ -45,6 +72,20 @@ int main(int argc, char **argv) ...@@ -45,6 +72,20 @@ int main(int argc, char **argv)
goto done; 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: done:
git_libgit2_shutdown(); git_libgit2_shutdown();
return ret; return ret;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment