зеркало из https://github.com/microsoft/git.git
Merge branch 'maint'
* maint: Extend parse-options test suite api-parse-options.txt: Introduce documentation for parse options API parse-options.c: fix documentation syntax of optional arguments api-builtin.txt: update and fix typo
This commit is contained in:
Коммит
0bd64f82ba
|
@ -4,7 +4,7 @@ builtin API
|
|||
Adding a new built-in
|
||||
---------------------
|
||||
|
||||
There are 4 things to do to add a bulit-in command implementation to
|
||||
There are 4 things to do to add a built-in command implementation to
|
||||
git:
|
||||
|
||||
. Define the implementation of the built-in command `foo` with
|
||||
|
@ -18,8 +18,8 @@ git:
|
|||
defined in `git.c`. The entry should look like:
|
||||
|
||||
{ "foo", cmd_foo, <options> },
|
||||
|
||||
where options is the bitwise-or of:
|
||||
+
|
||||
where options is the bitwise-or of:
|
||||
|
||||
`RUN_SETUP`::
|
||||
|
||||
|
@ -33,6 +33,12 @@ git:
|
|||
If the standard output is connected to a tty, spawn a pager and
|
||||
feed our output to it.
|
||||
|
||||
`NEED_WORK_TREE`::
|
||||
|
||||
Make sure there is a work tree, i.e. the command cannot act
|
||||
on bare repositories.
|
||||
This makes only sense when `RUN_SETUP` is also set.
|
||||
|
||||
. Add `builtin-foo.o` to `BUILTIN_OBJS` in `Makefile`.
|
||||
|
||||
Additionally, if `foo` is a new command, there are 3 more things to do:
|
||||
|
@ -41,8 +47,7 @@ Additionally, if `foo` is a new command, there are 3 more things to do:
|
|||
|
||||
. Write documentation in `Documentation/git-foo.txt`.
|
||||
|
||||
. Add an entry for `git-foo` to the list at the end of
|
||||
`Documentation/cmd-list.perl`.
|
||||
. Add an entry for `git-foo` to `command-list.txt`.
|
||||
|
||||
|
||||
How a built-in is called
|
||||
|
|
|
@ -1,6 +1,206 @@
|
|||
parse-options API
|
||||
=================
|
||||
|
||||
Talk about <parse-options.h>
|
||||
The parse-options API is used to parse and massage options in git
|
||||
and to provide a usage help with consistent look.
|
||||
|
||||
(Pierre)
|
||||
Basics
|
||||
------
|
||||
|
||||
The argument vector `argv[]` may usually contain mandatory or optional
|
||||
'non-option arguments', e.g. a filename or a branch, and 'options'.
|
||||
Options are optional arguments that start with a dash and
|
||||
that allow to change the behavior of a command.
|
||||
|
||||
* There are basically three types of options:
|
||||
'boolean' options,
|
||||
options with (mandatory) 'arguments' and
|
||||
options with 'optional arguments'
|
||||
(i.e. a boolean option that can be adjusted).
|
||||
|
||||
* There are basically two forms of options:
|
||||
'Short options' consist of one dash (`-`) and one alphanumeric
|
||||
character.
|
||||
'Long options' begin with two dashes (`\--`) and some
|
||||
alphanumeric characters.
|
||||
|
||||
* Options are case-sensitive.
|
||||
Please define 'lower-case long options' only.
|
||||
|
||||
The parse-options API allows:
|
||||
|
||||
* 'sticked' and 'separate form' of options with arguments.
|
||||
`-oArg` is sticked, `-o Arg` is separate form.
|
||||
`\--option=Arg` is sticked, `\--option Arg` is separate form.
|
||||
|
||||
* Long options may be 'abbreviated', as long as the abbreviation
|
||||
is unambiguous.
|
||||
|
||||
* Short options may be bundled, e.g. `-a -b` can be specified as `-ab`.
|
||||
|
||||
* Boolean long options can be 'negated' (or 'unset') by prepending
|
||||
`no-`, e.g. `\--no-abbrev` instead of `\--abbrev`.
|
||||
|
||||
* Options and non-option arguments can clearly be separated using the `\--`
|
||||
option, e.g. `-a -b \--option \-- \--this-is-a-file` indicates that
|
||||
`\--this-is-a-file` must not be processed as an option.
|
||||
|
||||
Steps to parse options
|
||||
----------------------
|
||||
|
||||
. `#include "parse-options.h"`
|
||||
|
||||
. define a NULL-terminated
|
||||
`static const char * const builtin_foo_usage[]` array
|
||||
containing alternative usage strings
|
||||
|
||||
. define `builtin_foo_options` array as described below
|
||||
in section 'Data Structure'.
|
||||
|
||||
. in `cmd_foo(int argc, const char **argv, const char *prefix)`
|
||||
call
|
||||
|
||||
argc = parse_options(argc, argv, builtin_foo_options, builtin_foo_usage, flags);
|
||||
+
|
||||
`parse_options()` will filter out the processed options of `argv[]` and leave the
|
||||
non-option arguments in `argv[]`.
|
||||
`argc` is updated appropriately because of the assignment.
|
||||
+
|
||||
Flags are the bitwise-or of:
|
||||
|
||||
`PARSE_OPT_KEEP_DASHDASH`::
|
||||
Keep the `\--` that usually separates options from
|
||||
non-option arguments.
|
||||
|
||||
`PARSE_OPT_STOP_AT_NON_OPTION`::
|
||||
Usually the whole argument vector is massaged and reordered.
|
||||
Using this flag, processing is stopped at the first non-option
|
||||
argument.
|
||||
|
||||
Data Structure
|
||||
--------------
|
||||
|
||||
The main data structure is an array of the `option` struct,
|
||||
say `static struct option builtin_add_options[]`.
|
||||
There are some macros to easily define options:
|
||||
|
||||
`OPT__ABBREV(&int_var)`::
|
||||
Add `\--abbrev[=<n>]`.
|
||||
|
||||
`OPT__DRY_RUN(&int_var)`::
|
||||
Add `-n, \--dry-run`.
|
||||
|
||||
`OPT__QUIET(&int_var)`::
|
||||
Add `-q, \--quiet`.
|
||||
|
||||
`OPT__VERBOSE(&int_var)`::
|
||||
Add `-v, \--verbose`.
|
||||
|
||||
`OPT_GROUP(description)`::
|
||||
Start an option group. `description` is a short string that
|
||||
describes the group or an empty string.
|
||||
Start the description with an upper-case letter.
|
||||
|
||||
`OPT_BOOLEAN(short, long, &int_var, description)`::
|
||||
Introduce a boolean option.
|
||||
`int_var` is incremented on each use.
|
||||
|
||||
`OPT_BIT(short, long, &int_var, description, mask)`::
|
||||
Introduce a boolean option.
|
||||
If used, `int_var` is bitwise-ored with `mask`.
|
||||
|
||||
`OPT_SET_INT(short, long, &int_var, description, integer)`::
|
||||
Introduce a boolean option.
|
||||
If used, set `int_var` to `integer`.
|
||||
|
||||
`OPT_SET_PTR(short, long, &ptr_var, description, ptr)`::
|
||||
Introduce a boolean option.
|
||||
If used, set `ptr_var` to `ptr`.
|
||||
|
||||
`OPT_STRING(short, long, &str_var, arg_str, description)`::
|
||||
Introduce an option with string argument.
|
||||
The string argument is put into `str_var`.
|
||||
|
||||
`OPT_INTEGER(short, long, &int_var, description)`::
|
||||
Introduce an option with integer argument.
|
||||
The integer is put into `int_var`.
|
||||
|
||||
`OPT_DATE(short, long, &int_var, description)`::
|
||||
Introduce an option with date argument, see `approxidate()`.
|
||||
The timestamp is put into `int_var`.
|
||||
|
||||
`OPT_CALLBACK(short, long, &var, arg_str, description, func_ptr)`::
|
||||
Introduce an option with argument.
|
||||
The argument will be fed into the function given by `func_ptr`
|
||||
and the result will be put into `var`.
|
||||
See 'Option Callbacks' below for a more elaborate description.
|
||||
|
||||
`OPT_ARGUMENT(long, description)`::
|
||||
Introduce a long-option argument that will be kept in `argv[]`.
|
||||
|
||||
|
||||
The last element of the array must be `OPT_END()`.
|
||||
|
||||
If not stated otherwise, interpret the arguments as follows:
|
||||
|
||||
* `short` is a character for the short option
|
||||
(e.g. `\'e\'` for `-e`, use `0` to omit),
|
||||
|
||||
* `long` is a string for the long option
|
||||
(e.g. `"example"` for `\--example`, use `NULL` to omit),
|
||||
|
||||
* `int_var` is an integer variable,
|
||||
|
||||
* `str_var` is a string variable (`char *`),
|
||||
|
||||
* `arg_str` is the string that is shown as argument
|
||||
(e.g. `"branch"` will result in `<branch>`).
|
||||
If set to `NULL`, three dots (`...`) will be displayed.
|
||||
|
||||
* `description` is a short string to describe the effect of the option.
|
||||
It shall begin with a lower-case letter and a full stop (`.`) shall be
|
||||
omitted at the end.
|
||||
|
||||
Option Callbacks
|
||||
----------------
|
||||
|
||||
The function must be defined in this form:
|
||||
|
||||
int func(const struct option *opt, const char *arg, int unset)
|
||||
|
||||
The callback mechanism is as follows:
|
||||
|
||||
* Inside `funct`, the only interesting member of the structure
|
||||
given by `opt` is the void pointer `opt->value`.
|
||||
`\*opt->value` will be the value that is saved into `var`, if you
|
||||
use `OPT_CALLBACK()`.
|
||||
For example, do `*(unsigned long *)opt->value = 42;` to get 42
|
||||
into an `unsigned long` variable.
|
||||
|
||||
* Return value `0` indicates success and non-zero return
|
||||
value will invoke `usage_with_options()` and, thus, die.
|
||||
|
||||
* If the user negates the option, `arg` is `NULL` and `unset` is 1.
|
||||
|
||||
Sophisticated option parsing
|
||||
----------------------------
|
||||
|
||||
If you need, for example, option callbacks with optional arguments
|
||||
or without arguments at all, or if you need other special cases,
|
||||
that are not handled by the macros above, you need to specify the
|
||||
members of the `option` structure manually.
|
||||
|
||||
This is not covered in this document, but well documented
|
||||
in `parse-options.h` itself.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
See `test-parse-options.c` and
|
||||
`builtin-add.c`,
|
||||
`builtin-clone.c`,
|
||||
`builtin-commit.c`,
|
||||
`builtin-fetch.c`,
|
||||
`builtin-fsck.c`,
|
||||
`builtin-rm.c`
|
||||
for real-world examples.
|
||||
|
|
|
@ -348,7 +348,10 @@ void usage_with_options_internal(const char * const *usagestr,
|
|||
break;
|
||||
case OPTION_INTEGER:
|
||||
if (opts->flags & PARSE_OPT_OPTARG)
|
||||
pos += fprintf(stderr, "[<n>]");
|
||||
if (opts->long_name)
|
||||
pos += fprintf(stderr, "[=<n>]");
|
||||
else
|
||||
pos += fprintf(stderr, "[<n>]");
|
||||
else
|
||||
pos += fprintf(stderr, " <n>");
|
||||
break;
|
||||
|
@ -359,12 +362,18 @@ void usage_with_options_internal(const char * const *usagestr,
|
|||
case OPTION_STRING:
|
||||
if (opts->argh) {
|
||||
if (opts->flags & PARSE_OPT_OPTARG)
|
||||
pos += fprintf(stderr, " [<%s>]", opts->argh);
|
||||
if (opts->long_name)
|
||||
pos += fprintf(stderr, "[=<%s>]", opts->argh);
|
||||
else
|
||||
pos += fprintf(stderr, "[<%s>]", opts->argh);
|
||||
else
|
||||
pos += fprintf(stderr, " <%s>", opts->argh);
|
||||
} else {
|
||||
if (opts->flags & PARSE_OPT_OPTARG)
|
||||
pos += fprintf(stderr, " [...]");
|
||||
if (opts->long_name)
|
||||
pos += fprintf(stderr, "[=...]");
|
||||
else
|
||||
pos += fprintf(stderr, "[...]");
|
||||
else
|
||||
pos += fprintf(stderr, " ...");
|
||||
}
|
||||
|
|
|
@ -11,23 +11,35 @@ cat > expect.err << EOF
|
|||
usage: test-parse-options <options>
|
||||
|
||||
-b, --boolean get a boolean
|
||||
-4, --or4 bitwise-or boolean with ...0100
|
||||
|
||||
-i, --integer <n> get a integer
|
||||
-j <n> get a integer, too
|
||||
--set23 set integer to 23
|
||||
-t <time> get timestamp of <time>
|
||||
-L, --length <str> get length of <str>
|
||||
|
||||
string options
|
||||
String options
|
||||
-s, --string <string>
|
||||
get a string
|
||||
--string2 <str> get another string
|
||||
--st <st> get another string (pervert ordering)
|
||||
-o <str> get another string
|
||||
--default-string set string to default
|
||||
|
||||
magic arguments
|
||||
Magic arguments
|
||||
--quux means --quux
|
||||
|
||||
Standard options
|
||||
--abbrev[=<n>] use <n> digits to display SHA-1s
|
||||
-v, --verbose be verbose
|
||||
-n, --dry-run dry run
|
||||
-q, --quiet be quiet
|
||||
|
||||
EOF
|
||||
|
||||
test_expect_success 'test help' '
|
||||
! test-parse-options -h > output 2> output.err &&
|
||||
test_must_fail test-parse-options -h > output 2> output.err &&
|
||||
test ! -s output &&
|
||||
test_cmp expect.err output.err
|
||||
'
|
||||
|
@ -36,21 +48,31 @@ cat > expect << EOF
|
|||
boolean: 2
|
||||
integer: 1729
|
||||
string: 123
|
||||
abbrev: 7
|
||||
verbose: 2
|
||||
quiet: no
|
||||
dry run: yes
|
||||
EOF
|
||||
|
||||
test_expect_success 'short options' '
|
||||
test-parse-options -s123 -b -i 1729 -b > output 2> output.err &&
|
||||
test-parse-options -s123 -b -i 1729 -b -vv -n > output 2> output.err &&
|
||||
test_cmp expect output &&
|
||||
test ! -s output.err
|
||||
'
|
||||
|
||||
cat > expect << EOF
|
||||
boolean: 2
|
||||
integer: 1729
|
||||
string: 321
|
||||
abbrev: 10
|
||||
verbose: 2
|
||||
quiet: no
|
||||
dry run: no
|
||||
EOF
|
||||
|
||||
test_expect_success 'long options' '
|
||||
test-parse-options --boolean --integer 1729 --boolean --string2=321 \
|
||||
--verbose --verbose --no-dry-run --abbrev=10 \
|
||||
> output 2> output.err &&
|
||||
test ! -s output.err &&
|
||||
test_cmp expect output
|
||||
|
@ -60,6 +82,10 @@ cat > expect << EOF
|
|||
boolean: 1
|
||||
integer: 13
|
||||
string: 123
|
||||
abbrev: 7
|
||||
verbose: 0
|
||||
quiet: no
|
||||
dry run: no
|
||||
arg 00: a1
|
||||
arg 01: b1
|
||||
arg 02: --boolean
|
||||
|
@ -76,6 +102,10 @@ cat > expect << EOF
|
|||
boolean: 0
|
||||
integer: 2
|
||||
string: (not set)
|
||||
abbrev: 7
|
||||
verbose: 0
|
||||
quiet: no
|
||||
dry run: no
|
||||
EOF
|
||||
|
||||
test_expect_success 'unambiguously abbreviated option' '
|
||||
|
@ -99,6 +129,10 @@ cat > expect << EOF
|
|||
boolean: 0
|
||||
integer: 0
|
||||
string: 123
|
||||
abbrev: 7
|
||||
verbose: 0
|
||||
quiet: no
|
||||
dry run: no
|
||||
EOF
|
||||
|
||||
test_expect_success 'non ambiguous option (after two options it abbreviates)' '
|
||||
|
@ -107,20 +141,24 @@ test_expect_success 'non ambiguous option (after two options it abbreviates)' '
|
|||
test_cmp expect output
|
||||
'
|
||||
|
||||
cat > expect.err << EOF
|
||||
cat > typo.err << EOF
|
||||
error: did you mean \`--boolean\` (with two dashes ?)
|
||||
EOF
|
||||
|
||||
test_expect_success 'detect possible typos' '
|
||||
! test-parse-options -boolean > output 2> output.err &&
|
||||
test_must_fail test-parse-options -boolean > output 2> output.err &&
|
||||
test ! -s output &&
|
||||
test_cmp expect.err output.err
|
||||
test_cmp typo.err output.err
|
||||
'
|
||||
|
||||
cat > expect <<EOF
|
||||
boolean: 0
|
||||
integer: 0
|
||||
string: (not set)
|
||||
abbrev: 7
|
||||
verbose: 0
|
||||
quiet: no
|
||||
dry run: no
|
||||
arg 00: --quux
|
||||
EOF
|
||||
|
||||
|
@ -130,4 +168,68 @@ test_expect_success 'keep some options as arguments' '
|
|||
test_cmp expect output
|
||||
'
|
||||
|
||||
cat > expect <<EOF
|
||||
boolean: 0
|
||||
integer: 1
|
||||
string: default
|
||||
abbrev: 7
|
||||
verbose: 0
|
||||
quiet: yes
|
||||
dry run: no
|
||||
arg 00: foo
|
||||
EOF
|
||||
|
||||
test_expect_success 'OPT_DATE() and OPT_SET_PTR() work' '
|
||||
test-parse-options -t "1970-01-01 00:00:01 +0000" --default-string \
|
||||
foo -q > output 2> output.err &&
|
||||
test ! -s output.err &&
|
||||
test_cmp expect output
|
||||
'
|
||||
|
||||
cat > expect <<EOF
|
||||
Callback: "four", 0
|
||||
boolean: 5
|
||||
integer: 4
|
||||
string: (not set)
|
||||
abbrev: 7
|
||||
verbose: 0
|
||||
quiet: no
|
||||
dry run: no
|
||||
EOF
|
||||
|
||||
test_expect_success 'OPT_CALLBACK() and OPT_BIT() work' '
|
||||
test-parse-options --length=four -b -4 > output 2> output.err &&
|
||||
test ! -s output.err &&
|
||||
test_cmp expect output
|
||||
'
|
||||
|
||||
cat > expect <<EOF
|
||||
Callback: "not set", 1
|
||||
EOF
|
||||
|
||||
test_expect_success 'OPT_CALLBACK() and callback errors work' '
|
||||
test_must_fail test-parse-options --no-length > output 2> output.err &&
|
||||
test_cmp expect output &&
|
||||
test_cmp expect.err output.err
|
||||
'
|
||||
|
||||
cat > expect <<EOF
|
||||
boolean: 1
|
||||
integer: 23
|
||||
string: (not set)
|
||||
abbrev: 7
|
||||
verbose: 0
|
||||
quiet: no
|
||||
dry run: no
|
||||
EOF
|
||||
|
||||
test_expect_success 'OPT_BIT() and OPT_SET_INT() work' '
|
||||
test-parse-options --set23 -bbbbb --no-or4 > output 2> output.err &&
|
||||
test ! -s output.err &&
|
||||
test_cmp expect output
|
||||
'
|
||||
|
||||
# --or4
|
||||
# --no-or4
|
||||
|
||||
test_done
|
||||
|
|
|
@ -13,7 +13,7 @@ usage: some-command [options] <args>...
|
|||
--bar ... some cool option --bar with an argument
|
||||
|
||||
An option group Header
|
||||
-C [...] option C with an optional argument
|
||||
-C[...] option C with an optional argument
|
||||
|
||||
Extras
|
||||
--extra1 line above used to cause a segfault but no longer does
|
||||
|
|
|
@ -2,9 +2,22 @@
|
|||
#include "parse-options.h"
|
||||
|
||||
static int boolean = 0;
|
||||
static int integer = 0;
|
||||
static unsigned long integer = 0;
|
||||
static int abbrev = 7;
|
||||
static int verbose = 0, dry_run = 0, quiet = 0;
|
||||
static char *string = NULL;
|
||||
|
||||
int length_callback(const struct option *opt, const char *arg, int unset)
|
||||
{
|
||||
printf("Callback: \"%s\", %d\n",
|
||||
(arg ? arg : "not set"), unset);
|
||||
if (unset)
|
||||
return 1; /* do not support unset */
|
||||
|
||||
*(unsigned long *)opt->value = strlen(arg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, const char **argv)
|
||||
{
|
||||
const char *usage[] = {
|
||||
|
@ -13,15 +26,29 @@ int main(int argc, const char **argv)
|
|||
};
|
||||
struct option options[] = {
|
||||
OPT_BOOLEAN('b', "boolean", &boolean, "get a boolean"),
|
||||
OPT_BIT('4', "or4", &boolean,
|
||||
"bitwise-or boolean with ...0100", 4),
|
||||
OPT_GROUP(""),
|
||||
OPT_INTEGER('i', "integer", &integer, "get a integer"),
|
||||
OPT_INTEGER('j', NULL, &integer, "get a integer, too"),
|
||||
OPT_GROUP("string options"),
|
||||
OPT_SET_INT(0, "set23", &integer, "set integer to 23", 23),
|
||||
OPT_DATE('t', NULL, &integer, "get timestamp of <time>"),
|
||||
OPT_CALLBACK('L', "length", &integer, "str",
|
||||
"get length of <str>", length_callback),
|
||||
OPT_GROUP("String options"),
|
||||
OPT_STRING('s', "string", &string, "string", "get a string"),
|
||||
OPT_STRING(0, "string2", &string, "str", "get another string"),
|
||||
OPT_STRING(0, "st", &string, "st", "get another string (pervert ordering)"),
|
||||
OPT_STRING('o', NULL, &string, "str", "get another string"),
|
||||
OPT_GROUP("magic arguments"),
|
||||
OPT_SET_PTR(0, "default-string", &string,
|
||||
"set string to default", (unsigned long)"default"),
|
||||
OPT_GROUP("Magic arguments"),
|
||||
OPT_ARGUMENT("quux", "means --quux"),
|
||||
OPT_GROUP("Standard options"),
|
||||
OPT__ABBREV(&abbrev),
|
||||
OPT__VERBOSE(&verbose),
|
||||
OPT__DRY_RUN(&dry_run),
|
||||
OPT__QUIET(&quiet),
|
||||
OPT_END(),
|
||||
};
|
||||
int i;
|
||||
|
@ -29,8 +56,12 @@ int main(int argc, const char **argv)
|
|||
argc = parse_options(argc, argv, options, usage, 0);
|
||||
|
||||
printf("boolean: %d\n", boolean);
|
||||
printf("integer: %d\n", integer);
|
||||
printf("integer: %lu\n", integer);
|
||||
printf("string: %s\n", string ? string : "(not set)");
|
||||
printf("abbrev: %d\n", abbrev);
|
||||
printf("verbose: %d\n", verbose);
|
||||
printf("quiet: %s\n", quiet ? "yes" : "no");
|
||||
printf("dry run: %s\n", dry_run ? "yes" : "no");
|
||||
|
||||
for (i = 0; i < argc; i++)
|
||||
printf("arg %02d: %s\n", i, argv[i]);
|
||||
|
|
Загрузка…
Ссылка в новой задаче