Skip to content

Commit c6dd82d

Browse files
committed
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.
1 parent 8526cbd commit c6dd82d

File tree

5 files changed

+193
-5
lines changed

5 files changed

+193
-5
lines changed

src/cli/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,22 @@
11
# cli
22

33
A git-compatible command-line interface that uses libgit2.
4+
5+
## Adding commands
6+
7+
1. Individual commands have a `main`-like top-level entrypoint. For example:
8+
9+
```c
10+
int cmd_help(int argc, char **argv)
11+
```
12+
13+
Although this is the same signature as `main`, commands are not built as
14+
individual standalone executables, they'll be linked into the main cli.
15+
(Though there may be an option for command executables to be built as
16+
standalone executables in the future.)
17+
18+
2. Commands are prototyped in `cmd.h` and added to `main.c`'s list of
19+
commands (`cli_cmds[]`). Commands should be specified with their name,
20+
entrypoint and a brief description that can be printed in `git help`.
21+
This is done because commands are linked into the main cli.
22+

src/cli/cmd.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright (C) the libgit2 contributors. All rights reserved.
3+
*
4+
* This file is part of libgit2, distributed under the GNU GPL v2 with
5+
* a Linking Exception. For full terms see the included COPYING file.
6+
*/
7+
8+
#include "cli.h"
9+
#include "cmd.h"
10+
11+
const cli_cmd_spec *cli_cmd_spec_byname(const char *name)
12+
{
13+
const cli_cmd_spec *cmd;
14+
15+
for (cmd = cli_cmds; cmd->name; cmd++) {
16+
if (!strcmp(cmd->name, name))
17+
return cmd;
18+
}
19+
20+
return NULL;
21+
}

src/cli/cmd.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright (C) the libgit2 contributors. All rights reserved.
3+
*
4+
* This file is part of libgit2, distributed under the GNU GPL v2 with
5+
* a Linking Exception. For full terms see the included COPYING file.
6+
*/
7+
8+
#ifndef CLI_cmd_h__
9+
#define CLI_cmd_h__
10+
11+
/* Command definitions */
12+
typedef struct {
13+
const char *name;
14+
int (*fn)(int argc, char **argv);
15+
const char *desc;
16+
} cli_cmd_spec;
17+
18+
/* Options that are common to all commands (eg --help, --git-dir) */
19+
extern const cli_opt_spec cli_common_opts[];
20+
21+
/* All the commands supported by the CLI */
22+
extern const cli_cmd_spec cli_cmds[];
23+
24+
/* Find a command by name */
25+
extern const cli_cmd_spec *cli_cmd_spec_byname(const char *name);
26+
27+
/* Commands */
28+
extern int cmd_help(int argc, char **argv);
29+
30+
#endif /* CLI_cmd_h__ */

src/cli/cmd_help.c

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright (C) the libgit2 contributors. All rights reserved.
3+
*
4+
* This file is part of libgit2, distributed under the GNU GPL v2 with
5+
* a Linking Exception. For full terms see the included COPYING file.
6+
*/
7+
8+
#include <stdio.h>
9+
#include <git2.h>
10+
#include "cli.h"
11+
#include "cmd.h"
12+
13+
#define COMMAND_NAME "help"
14+
15+
static char *command;
16+
static int show_help;
17+
18+
static const cli_opt_spec opts[] = {
19+
{ CLI_OPT_TYPE_SWITCH, "help", 0, &show_help, 1,
20+
CLI_OPT_USAGE_HIDDEN, NULL, "display help about the help command" },
21+
{ CLI_OPT_TYPE_ARG, "command", 0, &command, 0,
22+
CLI_OPT_USAGE_DEFAULT, "command", "the command to show help for" },
23+
{ 0 },
24+
};
25+
26+
static int print_help(void)
27+
{
28+
cli_opt_usage_fprint(stdout, PROGRAM_NAME, COMMAND_NAME, opts);
29+
printf("\n");
30+
31+
printf("Display help information about %s. If a command is specified, help\n", PROGRAM_NAME);
32+
printf("about that command will be shown. Otherwise, general information about\n");
33+
printf("%s will be shown, including the commands available.\n", PROGRAM_NAME);
34+
35+
return 0;
36+
}
37+
38+
static int print_commands(void)
39+
{
40+
const cli_cmd_spec *cmd;
41+
42+
cli_opt_usage_fprint(stdout, PROGRAM_NAME, NULL, cli_common_opts);
43+
printf("\n");
44+
45+
printf("These are the %s commands available:\n\n", PROGRAM_NAME);
46+
47+
for (cmd = cli_cmds; cmd->name; cmd++)
48+
printf(" %-8s %s\n", cmd->name, cmd->desc);
49+
50+
printf("\nSee '%s help <command>' for more information on a specific command.\n", PROGRAM_NAME);
51+
52+
return 0;
53+
}
54+
55+
int cmd_help(int argc, char **argv)
56+
{
57+
cli_opt invalid_opt;
58+
59+
if (cli_opt_parse(&invalid_opt, opts, argv + 1, argc - 1, CLI_OPT_PARSE_GNU))
60+
return cli_opt_usage_error(COMMAND_NAME, opts, &invalid_opt);
61+
62+
/* Show the meta-help */
63+
if (show_help)
64+
return print_help();
65+
66+
/* We were not asked to show help for a specific command. */
67+
if (!command)
68+
return print_commands();
69+
70+
/* If the user asks for help with the help command */
71+
if (strcmp(command, "help") == 0)
72+
return print_help();
73+
74+
fprintf(stderr, "%s: '%s' is not a %s command. See '%s help'.\n",
75+
PROGRAM_NAME, command, PROGRAM_NAME, PROGRAM_NAME);
76+
return CLI_EXIT_ERROR;
77+
}

src/cli/main.c

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,43 +8,84 @@
88
#include <stdio.h>
99
#include <git2.h>
1010
#include "cli.h"
11+
#include "cmd.h"
1112

13+
static int show_help = 0;
1214
static int show_version = 0;
15+
static char *command = NULL;
16+
static char **args = NULL;
1317

14-
static const cli_opt_spec common_opts[] = {
15-
{ CLI_OPT_TYPE_SWITCH, "version", 0, &show_version, 1,
16-
CLI_OPT_USAGE_DEFAULT, NULL, "display the version" },
18+
const cli_opt_spec cli_common_opts[] = {
19+
{ CLI_OPT_TYPE_SWITCH, "help", 0, &show_help, 1,
20+
CLI_OPT_USAGE_DEFAULT, NULL, "display help information" },
21+
{ CLI_OPT_TYPE_SWITCH, "version", 0, &show_version, 1,
22+
CLI_OPT_USAGE_DEFAULT, NULL, "display the version" },
23+
{ CLI_OPT_TYPE_ARG, "command", 0, &command, 0,
24+
CLI_OPT_USAGE_REQUIRED, "command", "the command to run" },
25+
{ CLI_OPT_TYPE_ARGS, "args", 0, &args, 0,
26+
CLI_OPT_USAGE_DEFAULT, "args", "arguments for the command" },
1727
{ 0 }
1828
};
1929

30+
const cli_cmd_spec cli_cmds[] = {
31+
{ "help", cmd_help, "Display help information" },
32+
{ NULL }
33+
};
34+
2035
int main(int argc, char **argv)
2136
{
37+
const cli_cmd_spec *cmd;
2238
cli_opt_parser optparser;
2339
cli_opt opt;
40+
int args_len = 0;
2441
int ret = 0;
2542

2643
if (git_libgit2_init() < 0) {
2744
cli_error("failed to initialize libgit2");
2845
exit(CLI_EXIT_GIT);
2946
}
3047

31-
cli_opt_parser_init(&optparser, common_opts, argv + 1, argc - 1, CLI_OPT_PARSE_GNU);
48+
cli_opt_parser_init(&optparser, cli_common_opts, argv + 1, argc - 1, CLI_OPT_PARSE_GNU);
3249

3350
/* Parse the top-level (common) options and command information */
3451
while (cli_opt_parser_next(&opt, &optparser)) {
3552
if (!opt.spec) {
3653
cli_opt_status_fprint(stderr, PROGRAM_NAME, &opt);
37-
cli_opt_usage_fprint(stderr, PROGRAM_NAME, NULL, common_opts);
54+
cli_opt_usage_fprint(stderr, PROGRAM_NAME, NULL, cli_common_opts);
3855
ret = CLI_EXIT_USAGE;
3956
goto done;
4057
}
58+
59+
/*
60+
* When we see a command, stop parsing and capture the
61+
* remaining arguments as args for the command itself.
62+
*/
63+
if (command) {
64+
args = &argv[optparser.idx];
65+
args_len = (int)(argc - optparser.idx);
66+
break;
67+
}
4168
}
4269

4370
if (show_version) {
4471
printf("%s version %s\n", PROGRAM_NAME, LIBGIT2_VERSION);
4572
goto done;
4673
}
4774

75+
/* If there was no command, we want to invoke "help" */
76+
if (!command || show_help) {
77+
cli_opt_usage_fprint(stdout, PROGRAM_NAME, NULL, cli_common_opts);
78+
goto done;
79+
}
80+
81+
if ((cmd = cli_cmd_spec_byname(command)) == NULL) {
82+
ret = cli_error("'%s' is not a %s command. See '%s help'.",
83+
command, PROGRAM_NAME, PROGRAM_NAME);
84+
goto done;
85+
}
86+
87+
ret = cmd->fn(args_len, args);
88+
4889
done:
4990
git_libgit2_shutdown();
5091
return ret;

0 commit comments

Comments
 (0)