diff options
author | Edward Thomson <ethomson@edwardthomson.com> | 2021-11-26 09:37:29 -0500 |
---|---|---|
committer | Edward Thomson <ethomson@edwardthomson.com> | 2022-02-26 14:43:48 -0500 |
commit | 8526cbd56b0395b9427727e81e6f3c89768337b9 (patch) | |
tree | e68a8658fb2a7ea112e3f3910c1742885484302a | |
parent | 3a3ab065f0685202c854e13708ddfd2a93d75e2c (diff) | |
download | libgit2-8526cbd56b0395b9427727e81e6f3c89768337b9.tar.gz |
opt: use a custom function to print usage
Our argument parser (https://github.com/ethomson/adopt) includes a
function to print a usage message based on the allowed options. Omit
this and use a cutom function that understands that we have subcommands
("checkout", "revert", etc) that each have their own options.
-rw-r--r-- | src/cli/cli.h | 1 | ||||
-rw-r--r-- | src/cli/main.c | 2 | ||||
-rw-r--r-- | src/cli/opt.c | 83 | ||||
-rw-r--r-- | src/cli/opt.h | 15 | ||||
-rw-r--r-- | src/cli/opt_usage.c | 194 | ||||
-rw-r--r-- | src/cli/opt_usage.h | 35 |
6 files changed, 233 insertions, 97 deletions
diff --git a/src/cli/cli.h b/src/cli/cli.h index a27081d87..222d53a74 100644 --- a/src/cli/cli.h +++ b/src/cli/cli.h @@ -14,5 +14,6 @@ #include "error.h" #include "opt.h" +#include "opt_usage.h" #endif /* CLI_cli_h__ */ diff --git a/src/cli/main.c b/src/cli/main.c index 709f6b452..5eff56a1d 100644 --- a/src/cli/main.c +++ b/src/cli/main.c @@ -34,7 +34,7 @@ int main(int argc, char **argv) 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, common_opts); + cli_opt_usage_fprint(stderr, PROGRAM_NAME, NULL, common_opts); ret = CLI_EXIT_USAGE; goto done; } diff --git a/src/cli/opt.c b/src/cli/opt.c index 11faa92c5..72df5877f 100644 --- a/src/cli/opt.c +++ b/src/cli/opt.c @@ -10,7 +10,7 @@ * This file was produced by using the `rename.pl` script included with * adopt. The command-line specified was: * - * ./rename.pl cli_opt --filename=opt --include=cli.h --inline=GIT_INLINE --header-guard=CLI_opt_h__ --lowercase-status + * ./rename.pl cli_opt --filename=opt --include=cli.h --inline=GIT_INLINE --header-guard=CLI_opt_h__ --lowercase-status --without-usage */ #include <stdlib.h> @@ -667,84 +667,3 @@ int cli_opt_status_fprint( return error; } -int cli_opt_usage_fprint( - FILE *file, - const char *command, - const cli_opt_spec specs[]) -{ - const cli_opt_spec *spec; - int choice = 0, next_choice = 0, optional = 0; - int error; - - if ((error = fprintf(file, "usage: %s", command)) < 0) - goto done; - - for (spec = specs; spec->type; ++spec) { - if (!choice) - optional = !(spec->usage & CLI_OPT_USAGE_REQUIRED); - - next_choice = !!((spec + 1)->usage & CLI_OPT_USAGE_CHOICE); - - if (spec->usage & CLI_OPT_USAGE_HIDDEN) - continue; - - if (choice) - error = fprintf(file, "|"); - else - error = fprintf(file, " "); - - if (error < 0) - goto done; - - if (optional && !choice && (error = fprintf(file, "[")) < 0) - error = fprintf(file, "["); - if (!optional && !choice && next_choice) - error = fprintf(file, "("); - - if (error < 0) - goto done; - - if (spec->type == CLI_OPT_TYPE_VALUE && spec->alias && - !(spec->usage & CLI_OPT_USAGE_VALUE_OPTIONAL) && - !(spec->usage & CLI_OPT_USAGE_SHOW_LONG)) - error = fprintf(file, "-%c <%s>", spec->alias, spec->value_name); - else if (spec->type == CLI_OPT_TYPE_VALUE && spec->alias && - !(spec->usage & CLI_OPT_USAGE_SHOW_LONG)) - error = fprintf(file, "-%c [<%s>]", spec->alias, spec->value_name); - else if (spec->type == CLI_OPT_TYPE_VALUE && - !(spec->usage & CLI_OPT_USAGE_VALUE_OPTIONAL)) - error = fprintf(file, "--%s[=<%s>]", spec->name, spec->value_name); - else if (spec->type == CLI_OPT_TYPE_VALUE) - error = fprintf(file, "--%s=<%s>", spec->name, spec->value_name); - else if (spec->type == CLI_OPT_TYPE_ARG) - error = fprintf(file, "<%s>", spec->value_name); - else if (spec->type == CLI_OPT_TYPE_ARGS) - error = fprintf(file, "<%s>...", spec->value_name); - else if (spec->type == CLI_OPT_TYPE_LITERAL) - error = fprintf(file, "--"); - else if (spec->alias && !(spec->usage & CLI_OPT_USAGE_SHOW_LONG)) - error = fprintf(file, "-%c", spec->alias); - else - error = fprintf(file, "--%s", spec->name); - - if (error < 0) - goto done; - - if (!optional && choice && !next_choice) - error = fprintf(file, ")"); - else if (optional && !next_choice) - error = fprintf(file, "]"); - - if (error < 0) - goto done; - - choice = next_choice; - } - - error = fprintf(file, "\n"); - -done: - error = (error < 0) ? -1 : 0; - return error; -} - diff --git a/src/cli/opt.h b/src/cli/opt.h index f7b6b9326..6c1d4603e 100644 --- a/src/cli/opt.h +++ b/src/cli/opt.h @@ -10,7 +10,7 @@ * This file was produced by using the `rename.pl` script included with * adopt. The command-line specified was: * - * ./rename.pl cli_opt --filename=opt --include=cli.h --inline=GIT_INLINE --header-guard=CLI_opt_h__ --lowercase-status + * ./rename.pl cli_opt --filename=opt --include=cli.h --inline=GIT_INLINE --header-guard=CLI_opt_h__ --lowercase-status --without-usage */ #ifndef CLI_opt_h__ @@ -346,17 +346,4 @@ int cli_opt_status_fprint( const char *command, const cli_opt *opt); -/** - * Prints usage information to the given file handle. - * - * @param file The file to print information to - * @param command The name of the command to use when printing - * @param specs The specifications allowed by the command - * @return 0 on success, -1 on failure - */ -int cli_opt_usage_fprint( - FILE *file, - const char *command, - const cli_opt_spec specs[]); - #endif /* CLI_opt_h__ */ diff --git a/src/cli/opt_usage.c b/src/cli/opt_usage.c new file mode 100644 index 000000000..6e5d6006e --- /dev/null +++ b/src/cli/opt_usage.c @@ -0,0 +1,194 @@ +/* + * 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 "str.h" + +static int print_spec_name(git_str *out, const cli_opt_spec *spec) +{ + if (spec->type == CLI_OPT_TYPE_VALUE && spec->alias && + !(spec->usage & CLI_OPT_USAGE_VALUE_OPTIONAL) && + !(spec->usage & CLI_OPT_USAGE_SHOW_LONG)) + return git_str_printf(out, "-%c <%s>", spec->alias, spec->value_name); + if (spec->type == CLI_OPT_TYPE_VALUE && spec->alias && + !(spec->usage & CLI_OPT_USAGE_SHOW_LONG)) + return git_str_printf(out, "-%c [<%s>]", spec->alias, spec->value_name); + if (spec->type == CLI_OPT_TYPE_VALUE && + !(spec->usage & CLI_OPT_USAGE_VALUE_OPTIONAL)) + return git_str_printf(out, "--%s[=<%s>]", spec->name, spec->value_name); + if (spec->type == CLI_OPT_TYPE_VALUE) + return git_str_printf(out, "--%s=<%s>", spec->name, spec->value_name); + if (spec->type == CLI_OPT_TYPE_ARG) + return git_str_printf(out, "<%s>", spec->value_name); + if (spec->type == CLI_OPT_TYPE_ARGS) + return git_str_printf(out, "<%s>...", spec->value_name); + if (spec->type == CLI_OPT_TYPE_LITERAL) + return git_str_printf(out, "--"); + if (spec->alias && !(spec->usage & CLI_OPT_USAGE_SHOW_LONG)) + return git_str_printf(out, "-%c", spec->alias); + if (spec->name) + return git_str_printf(out, "--%s", spec->name); + + GIT_ASSERT(0); +} + +/* + * This is similar to adopt's function, but modified to understand + * that we have a command ("git") and a "subcommand" ("checkout"). + * It also understands a terminal's line length and wrap appropriately, + * using a `git_str` for storage. + */ +int cli_opt_usage_fprint( + FILE *file, + const char *command, + const char *subcommand, + const cli_opt_spec specs[]) +{ + git_str usage = GIT_BUF_INIT, opt = GIT_BUF_INIT; + const cli_opt_spec *spec; + size_t i, prefixlen, linelen; + bool choice = false, next_choice = false, optional = false; + int error; + + /* TODO: query actual console width. */ + int console_width = 80; + + if ((error = git_str_printf(&usage, "usage: %s", command)) < 0) + goto done; + + if (subcommand && + (error = git_str_printf(&usage, " %s", subcommand)) < 0) + goto done; + + linelen = git_str_len(&usage); + prefixlen = linelen + 1; + + for (spec = specs; spec->type; ++spec) { + if (!choice) + optional = !(spec->usage & CLI_OPT_USAGE_REQUIRED); + + next_choice = !!((spec + 1)->usage & CLI_OPT_USAGE_CHOICE); + + if (spec->usage & CLI_OPT_USAGE_HIDDEN) + continue; + + if (choice) + git_str_putc(&opt, '|'); + else + git_str_clear(&opt); + + if (optional && !choice) + git_str_putc(&opt, '['); + if (!optional && !choice && next_choice) + git_str_putc(&opt, '('); + + if ((error = print_spec_name(&opt, spec)) < 0) + goto done; + + if (!optional && choice && !next_choice) + git_str_putc(&opt, ')'); + else if (optional && !next_choice) + git_str_putc(&opt, ']'); + + if ((choice = next_choice)) + continue; + + if (git_str_oom(&opt)) { + error = -1; + goto done; + } + + if (linelen > prefixlen && + console_width > 0 && + linelen + git_str_len(&opt) + 1 > (size_t)console_width) { + git_str_putc(&usage, '\n'); + + for (i = 0; i < prefixlen; i++) + git_str_putc(&usage, ' '); + + linelen = prefixlen; + } else { + git_str_putc(&usage, ' '); + linelen += git_str_len(&opt) + 1; + } + + git_str_puts(&usage, git_str_cstr(&opt)); + + if (git_str_oom(&usage)) { + error = -1; + goto done; + } + } + + error = fprintf(file, "%s\n", git_str_cstr(&usage)); + +done: + error = (error < 0) ? -1 : 0; + + git_str_dispose(&usage); + git_str_dispose(&opt); + return error; +} + +int cli_opt_usage_error( + const char *subcommand, + const cli_opt_spec specs[], + const cli_opt *invalid_opt) +{ + cli_opt_status_fprint(stderr, PROGRAM_NAME, invalid_opt); + cli_opt_usage_fprint(stderr, PROGRAM_NAME, subcommand, specs); + return CLI_EXIT_USAGE; +} + +int cli_opt_help_fprint( + FILE *file, + const cli_opt_spec specs[]) +{ + git_str help = GIT_BUF_INIT; + const cli_opt_spec *spec; + int error; + + /* Display required arguments first */ + for (spec = specs; spec->type; ++spec) { + if (! (spec->usage & CLI_OPT_USAGE_REQUIRED) || + (spec->usage & CLI_OPT_USAGE_HIDDEN)) + continue; + + git_str_printf(&help, " "); + + if ((error = print_spec_name(&help, spec)) < 0) + goto done; + + git_str_printf(&help, ": %s\n", spec->help); + } + + /* Display the remaining arguments */ + for (spec = specs; spec->type; ++spec) { + if ((spec->usage & CLI_OPT_USAGE_REQUIRED) || + (spec->usage & CLI_OPT_USAGE_HIDDEN)) + continue; + + git_str_printf(&help, " "); + + if ((error = print_spec_name(&help, spec)) < 0) + goto done; + + git_str_printf(&help, ": %s\n", spec->help); + + } + + if (git_str_oom(&help) || + p_write(fileno(file), help.ptr, help.size) < 0) + error = -1; + +done: + error = (error < 0) ? -1 : 0; + + git_str_dispose(&help); + return error; +} + diff --git a/src/cli/opt_usage.h b/src/cli/opt_usage.h new file mode 100644 index 000000000..c752494e1 --- /dev/null +++ b/src/cli/opt_usage.h @@ -0,0 +1,35 @@ +/* + * 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_opt_usage_h__ +#define CLI_opt_usage_h__ + +/** + * Prints usage information to the given file handle. + * + * @param file The file to print information to + * @param command The name of the command to use when printing + * @param subcommand The name of the subcommand (eg "checkout") to use when printing, or NULL to skip + * @param specs The specifications allowed by the command + * @return 0 on success, -1 on failure + */ +int cli_opt_usage_fprint( + FILE *file, + const char *command, + const char *subcommand, + const cli_opt_spec specs[]); + +int cli_opt_usage_error( + const char *subcommand, + const cli_opt_spec specs[], + const cli_opt *invalid_opt); + +int cli_opt_help_fprint( + FILE *file, + const cli_opt_spec specs[]); + +#endif /* CLI_opt_usage_h__ */ |