2007-10-14 23:35:37 +00:00
|
|
|
#include "git-compat-util.h"
|
|
|
|
#include "parse-options.h"
|
2008-06-23 20:55:11 +00:00
|
|
|
#include "cache.h"
|
2017-06-14 18:07:36 +00:00
|
|
|
#include "config.h"
|
2009-01-26 14:13:23 +00:00
|
|
|
#include "commit.h"
|
Add an optional argument for --color options
Make git-branch, git-show-branch, git-grep, and all the diff-based
programs accept an optional argument <when> for --color. The argument
is a colorbool: "always", "never", or "auto". If no argument is given,
"always" is used; --no-color is an alias for --color=never. This makes
the command-line interface consistent with other GNU tools, such as `ls'
and `grep', and with the git-config color options. Note that, without
an argument, --color and --no-color work exactly as before.
To implement this, two internal changes were made:
1. Allow the first argument of git_config_colorbool() to be NULL,
in which case it returns -1 if the argument isn't "always", "never",
or "auto".
2. Add OPT_COLOR_FLAG(), OPT__COLOR(), and parse_opt_color_flag_cb()
to the option parsing library. The callback uses
git_config_colorbool(), so color.h is now a dependency
of parse-options.c.
Signed-off-by: Mark Lodato <lodatom@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2010-02-17 04:55:58 +00:00
|
|
|
#include "color.h"
|
2013-02-09 06:31:09 +00:00
|
|
|
#include "utf8.h"
|
2007-10-14 23:35:37 +00:00
|
|
|
|
|
|
|
#define OPT_SHORT 1
|
|
|
|
#define OPT_UNSET 2
|
|
|
|
|
2011-08-11 09:15:37 +00:00
|
|
|
int optbug(const struct option *opt, const char *reason)
|
2010-12-02 06:01:18 +00:00
|
|
|
{
|
2014-09-03 19:42:37 +00:00
|
|
|
if (opt->long_name) {
|
|
|
|
if (opt->short_name)
|
|
|
|
return error("BUG: switch '%c' (--%s) %s",
|
|
|
|
opt->short_name, opt->long_name, reason);
|
2010-12-02 06:01:18 +00:00
|
|
|
return error("BUG: option '%s' %s", opt->long_name, reason);
|
2014-09-03 19:42:37 +00:00
|
|
|
}
|
2010-12-02 06:01:18 +00:00
|
|
|
return error("BUG: switch '%c' %s", opt->short_name, reason);
|
|
|
|
}
|
|
|
|
|
2008-07-08 10:34:08 +00:00
|
|
|
static int get_arg(struct parse_opt_ctx_t *p, const struct option *opt,
|
|
|
|
int flags, const char **arg)
|
|
|
|
{
|
|
|
|
if (p->opt) {
|
|
|
|
*arg = p->opt;
|
|
|
|
p->opt = NULL;
|
|
|
|
} else if (p->argc == 1 && (opt->flags & PARSE_OPT_LASTARG_DEFAULT)) {
|
|
|
|
*arg = (const char *)opt->defval;
|
2008-07-21 18:30:36 +00:00
|
|
|
} else if (p->argc > 1) {
|
2008-07-08 10:34:08 +00:00
|
|
|
p->argc--;
|
|
|
|
*arg = *++p->argv;
|
|
|
|
} else
|
2018-11-10 05:16:11 +00:00
|
|
|
return error(_("%s requires a value"), optname(opt, flags));
|
2008-07-08 10:34:08 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-05-23 18:53:13 +00:00
|
|
|
static void fix_filename(const char *prefix, const char **file)
|
|
|
|
{
|
|
|
|
if (!file || !*file || !prefix || is_absolute_path(*file)
|
|
|
|
|| !strcmp("-", *file))
|
|
|
|
return;
|
2017-03-21 01:28:49 +00:00
|
|
|
*file = prefix_filename(prefix, *file);
|
2009-05-23 18:53:13 +00:00
|
|
|
}
|
|
|
|
|
2013-07-30 19:06:01 +00:00
|
|
|
static int opt_command_mode_error(const struct option *opt,
|
|
|
|
const struct option *all_opts,
|
|
|
|
int flags)
|
|
|
|
{
|
|
|
|
const struct option *that;
|
|
|
|
struct strbuf that_name = STRBUF_INIT;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Find the other option that was used to set the variable
|
|
|
|
* already, and report that this is not compatible with it.
|
|
|
|
*/
|
|
|
|
for (that = all_opts; that->type != OPTION_END; that++) {
|
|
|
|
if (that == opt ||
|
|
|
|
that->type != OPTION_CMDMODE ||
|
|
|
|
that->value != opt->value ||
|
|
|
|
that->defval != *(int *)opt->value)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (that->long_name)
|
|
|
|
strbuf_addf(&that_name, "--%s", that->long_name);
|
|
|
|
else
|
|
|
|
strbuf_addf(&that_name, "-%c", that->short_name);
|
2018-11-10 05:16:11 +00:00
|
|
|
error(_("%s is incompatible with %s"),
|
|
|
|
optname(opt, flags), that_name.buf);
|
2013-07-30 19:06:01 +00:00
|
|
|
strbuf_release(&that_name);
|
|
|
|
return -1;
|
|
|
|
}
|
2018-11-10 05:16:11 +00:00
|
|
|
return error(_("%s : incompatible with something else"),
|
|
|
|
optname(opt, flags));
|
2013-07-30 19:06:01 +00:00
|
|
|
}
|
|
|
|
|
2008-06-23 19:59:37 +00:00
|
|
|
static int get_value(struct parse_opt_ctx_t *p,
|
2013-07-30 19:06:01 +00:00
|
|
|
const struct option *opt,
|
|
|
|
const struct option *all_opts,
|
|
|
|
int flags)
|
2007-10-14 23:35:37 +00:00
|
|
|
{
|
2007-10-14 23:45:45 +00:00
|
|
|
const char *s, *arg;
|
2007-11-07 10:20:27 +00:00
|
|
|
const int unset = flags & OPT_UNSET;
|
2009-05-23 18:53:13 +00:00
|
|
|
int err;
|
2007-10-14 23:35:37 +00:00
|
|
|
|
2007-11-07 10:20:27 +00:00
|
|
|
if (unset && p->opt)
|
2018-11-10 05:16:11 +00:00
|
|
|
return error(_("%s takes no value"), optname(opt, flags));
|
2007-11-07 10:20:27 +00:00
|
|
|
if (unset && (opt->flags & PARSE_OPT_NONEG))
|
2018-11-10 05:16:11 +00:00
|
|
|
return error(_("%s isn't available"), optname(opt, flags));
|
2010-12-01 23:30:40 +00:00
|
|
|
if (!(flags & OPT_SHORT) && p->opt && (opt->flags & PARSE_OPT_NOARG))
|
2018-11-10 05:16:11 +00:00
|
|
|
return error(_("%s takes no value"), optname(opt, flags));
|
2007-11-07 10:20:27 +00:00
|
|
|
|
|
|
|
switch (opt->type) {
|
parse-options: allow git commands to invent new option types
parse-options provides a variety of option behaviors, including
OPTION_CALLBACK, which should take care of just about any sane
behavior. All supported behaviors obey the following constraint:
A --foo option can only accept (and base its behavior on)
one argument, which would be the following command-line
argument in the "unsticked" form.
Alas, some existing git commands have options that do not obey that
constraint. For example, update-index --cacheinfo takes three
arguments, and update-index --resolve takes all later parameters as
arguments.
Introduces an OPTION_LOWLEVEL_CALLBACK backdoor to parse-options so
such option types can be supported without tempting inventors of other
commands through mention in the public API. Commands can set the
callback field to a function accepting three arguments: the option
parsing context, the option itself, and a flag indicating whether the
the option was negated. When the option is encountered, that function
is called to take over from get_value(). The return value should be
zero for success, -1 for usage errors.
Thanks to Stephen Boyd for API guidance.
Improved-by: Stephen Boyd <bebarino@gmail.com>
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2010-12-01 23:32:16 +00:00
|
|
|
case OPTION_LOWLEVEL_CALLBACK:
|
|
|
|
return (*(parse_opt_ll_cb *)opt->callback)(p, opt, unset);
|
|
|
|
|
2007-11-07 10:20:27 +00:00
|
|
|
case OPTION_BIT:
|
|
|
|
if (unset)
|
|
|
|
*(int *)opt->value &= ~opt->defval;
|
2007-10-14 23:35:37 +00:00
|
|
|
else
|
2007-11-07 10:20:27 +00:00
|
|
|
*(int *)opt->value |= opt->defval;
|
|
|
|
return 0;
|
|
|
|
|
2009-05-07 19:44:17 +00:00
|
|
|
case OPTION_NEGBIT:
|
|
|
|
if (unset)
|
|
|
|
*(int *)opt->value |= opt->defval;
|
|
|
|
else
|
|
|
|
*(int *)opt->value &= ~opt->defval;
|
|
|
|
return 0;
|
|
|
|
|
parse-options: deprecate OPT_BOOLEAN
It is natural to expect that an option defined with OPT_BOOLEAN() could be
used in this way:
int option = -1; /* unspecified */
struct option options[] = {
OPT_BOOLEAN(0, "option", &option, "set option"),
OPT_END()
};
parse_options(ac, av, prefix, options, usage, 0);
if (option < 0)
... do the default thing ...
else if (!option)
... --no-option was given ...
else
... --option was given ...
to easily tell three cases apart:
- There is no mention of the `--option` on the command line;
- The variable is positively set with `--option`; or
- The variable is explicitly negated with `--no-option`.
Unfortunately, this is not the case. OPT_BOOLEAN() increments the variable
every time `--option` is given, and resets it to zero when `--no-option`
is given.
As a first step to remedy this, introduce a true boolean OPT_BOOL(), and
rename OPT_BOOLEAN() to OPT_COUNTUP(). To help transitioning, OPT_BOOLEAN
and OPTION_BOOLEAN are defined as deprecated synonyms to OPT_COUNTUP and
OPTION_COUNTUP respectively.
This is what db7244b (parse-options new features., 2007-11-07) from four
years ago started by marking OPTION_BOOLEAN as "INCR would have been a
better name".
Some existing users do depend on the count-up semantics; for example,
users of OPT__VERBOSE() could use it to raise the verbosity level with
repeated use of `-v` on the command line, but they probably should be
rewritten to use OPT__VERBOSITY() instead these days. I suspect that some
users of OPT__FORCE() may also use it to implement different level of
forcibleness but I didn't check.
On top of this patch, here are the remaining clean-up tasks that other
people can help:
- Look at each hit in "git grep -e OPT_BOOLEAN"; trace all uses of the
value that is set to the underlying variable, and if it can proven that
the variable is only used as a boolean, replace it with OPT_BOOL(). If
the caller does depend on the count-up semantics, replace it with
OPT_COUNTUP() instead.
- Same for OPTION_BOOLEAN; replace it with OPTION_SET_INT and arrange to
set 1 to the variable for a true boolean, and otherwise replace it with
OPTION_COUNTUP.
- Look at each hit in "git grep -e OPT__VERBOSE -e OPT__QUIET" and see if
they can be replaced with OPT__VERBOSITY().
I'll follow this message up with a separate patch as an example.
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2011-09-27 23:56:49 +00:00
|
|
|
case OPTION_COUNTUP:
|
parse-options.c: make OPTION_COUNTUP respect "unspecified" values
OPT_COUNTUP() merely increments the counter upon --option, and resets it
to 0 upon --no-option, which means that there is no "unspecified" value
with which a client can initialize the counter to determine whether or
not --[no]-option was seen at all.
Make OPT_COUNTUP() treat any negative number as an "unspecified" value
to address this shortcoming. In particular, if a client initializes the
counter to -1, then if it is still -1 after parse_options(), then
neither --option nor --no-option was seen; if it is 0, then --no-option
was seen last, and if it is 1 or greater, than --option was seen last.
This change does not affect the behavior of existing clients because
they all use the initial value of 0 (or more).
Note that builtin/clean.c initializes the variable used with
OPT__FORCE (which uses OPT_COUNTUP()) to a negative value, but it is set
to either 0 or 1 by reading the configuration before the code calls
parse_options(), i.e. as far as parse_options() is concerned, the
initial value of the variable is not negative.
To test this behavior, in test-parse-options.c, "verbose" is set to
"unspecified" while quiet is set to 0 which will test the new behavior
with all sets of values.
Helped-by: Jeff King <peff@peff.net>
Helped-by: Eric Sunshine <sunshine@sunshineco.com>
Helped-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Pranit Bauva <pranit.bauva@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2016-05-05 09:50:00 +00:00
|
|
|
if (*(int *)opt->value < 0)
|
|
|
|
*(int *)opt->value = 0;
|
2007-11-07 10:20:27 +00:00
|
|
|
*(int *)opt->value = unset ? 0 : *(int *)opt->value + 1;
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
case OPTION_SET_INT:
|
|
|
|
*(int *)opt->value = unset ? 0 : opt->defval;
|
|
|
|
return 0;
|
|
|
|
|
2013-07-30 19:06:01 +00:00
|
|
|
case OPTION_CMDMODE:
|
|
|
|
/*
|
|
|
|
* Giving the same mode option twice, although is unnecessary,
|
|
|
|
* is not a grave error, so let it pass.
|
|
|
|
*/
|
|
|
|
if (*(int *)opt->value && *(int *)opt->value != opt->defval)
|
|
|
|
return opt_command_mode_error(opt, all_opts, flags);
|
|
|
|
*(int *)opt->value = opt->defval;
|
|
|
|
return 0;
|
|
|
|
|
2007-10-14 23:35:37 +00:00
|
|
|
case OPTION_STRING:
|
2008-07-08 10:34:08 +00:00
|
|
|
if (unset)
|
2007-11-07 10:20:27 +00:00
|
|
|
*(const char **)opt->value = NULL;
|
2008-07-08 10:34:08 +00:00
|
|
|
else if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
|
2007-10-14 23:45:45 +00:00
|
|
|
*(const char **)opt->value = (const char *)opt->defval;
|
2008-07-08 10:34:08 +00:00
|
|
|
else
|
|
|
|
return get_arg(p, opt, flags, (const char **)opt->value);
|
2007-10-14 23:35:37 +00:00
|
|
|
return 0;
|
|
|
|
|
2009-05-23 18:53:13 +00:00
|
|
|
case OPTION_FILENAME:
|
|
|
|
err = 0;
|
|
|
|
if (unset)
|
|
|
|
*(const char **)opt->value = NULL;
|
|
|
|
else if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
|
|
|
|
*(const char **)opt->value = (const char *)opt->defval;
|
|
|
|
else
|
|
|
|
err = get_arg(p, opt, flags, (const char **)opt->value);
|
|
|
|
|
|
|
|
if (!err)
|
|
|
|
fix_filename(p->prefix, (const char **)opt->value);
|
|
|
|
return err;
|
|
|
|
|
2007-10-14 23:45:45 +00:00
|
|
|
case OPTION_CALLBACK:
|
2007-11-07 10:20:27 +00:00
|
|
|
if (unset)
|
2008-06-23 20:46:36 +00:00
|
|
|
return (*opt->callback)(opt, NULL, 1) ? (-1) : 0;
|
2007-11-07 10:20:27 +00:00
|
|
|
if (opt->flags & PARSE_OPT_NOARG)
|
2008-06-23 20:46:36 +00:00
|
|
|
return (*opt->callback)(opt, NULL, 0) ? (-1) : 0;
|
2007-12-21 10:41:41 +00:00
|
|
|
if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
|
2008-06-23 20:46:36 +00:00
|
|
|
return (*opt->callback)(opt, NULL, 0) ? (-1) : 0;
|
2008-07-08 10:34:08 +00:00
|
|
|
if (get_arg(p, opt, flags, &arg))
|
|
|
|
return -1;
|
|
|
|
return (*opt->callback)(opt, arg, 0) ? (-1) : 0;
|
2007-10-14 23:45:45 +00:00
|
|
|
|
2007-10-14 23:35:37 +00:00
|
|
|
case OPTION_INTEGER:
|
2007-11-07 10:20:27 +00:00
|
|
|
if (unset) {
|
2007-10-14 23:35:37 +00:00
|
|
|
*(int *)opt->value = 0;
|
|
|
|
return 0;
|
|
|
|
}
|
2007-12-21 10:41:41 +00:00
|
|
|
if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
|
2007-10-14 23:45:45 +00:00
|
|
|
*(int *)opt->value = opt->defval;
|
|
|
|
return 0;
|
|
|
|
}
|
2008-07-08 10:34:08 +00:00
|
|
|
if (get_arg(p, opt, flags, &arg))
|
|
|
|
return -1;
|
|
|
|
*(int *)opt->value = strtol(arg, (char **)&s, 10);
|
2007-10-14 23:35:37 +00:00
|
|
|
if (*s)
|
2018-11-10 05:16:11 +00:00
|
|
|
return error(_("%s expects a numerical value"),
|
|
|
|
optname(opt, flags));
|
2007-10-14 23:35:37 +00:00
|
|
|
return 0;
|
|
|
|
|
2015-06-21 18:25:44 +00:00
|
|
|
case OPTION_MAGNITUDE:
|
|
|
|
if (unset) {
|
|
|
|
*(unsigned long *)opt->value = 0;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
|
|
|
|
*(unsigned long *)opt->value = opt->defval;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (get_arg(p, opt, flags, &arg))
|
|
|
|
return -1;
|
|
|
|
if (!git_parse_ulong(arg, opt->value))
|
2018-11-10 05:16:11 +00:00
|
|
|
return error(_("%s expects a non-negative integer value"
|
|
|
|
" with an optional k/m/g suffix"),
|
|
|
|
optname(opt, flags));
|
2015-06-21 18:25:44 +00:00
|
|
|
return 0;
|
|
|
|
|
2007-10-14 23:35:37 +00:00
|
|
|
default:
|
2018-11-10 05:16:12 +00:00
|
|
|
BUG("opt->type %d should not happen", opt->type);
|
2007-10-14 23:35:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-06-23 19:59:37 +00:00
|
|
|
static int parse_short_opt(struct parse_opt_ctx_t *p, const struct option *options)
|
2007-10-14 23:35:37 +00:00
|
|
|
{
|
2013-07-30 19:06:01 +00:00
|
|
|
const struct option *all_opts = options;
|
2009-05-07 19:45:08 +00:00
|
|
|
const struct option *numopt = NULL;
|
|
|
|
|
2007-10-14 23:35:37 +00:00
|
|
|
for (; options->type != OPTION_END; options++) {
|
|
|
|
if (options->short_name == *p->opt) {
|
|
|
|
p->opt = p->opt[1] ? p->opt + 1 : NULL;
|
2013-07-30 19:06:01 +00:00
|
|
|
return get_value(p, options, all_opts, OPT_SHORT);
|
2007-10-14 23:35:37 +00:00
|
|
|
}
|
2009-05-07 19:45:08 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Handle the numerical option later, explicit one-digit
|
|
|
|
* options take precedence over it.
|
|
|
|
*/
|
|
|
|
if (options->type == OPTION_NUMBER)
|
|
|
|
numopt = options;
|
|
|
|
}
|
|
|
|
if (numopt && isdigit(*p->opt)) {
|
|
|
|
size_t len = 1;
|
|
|
|
char *arg;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
while (isdigit(p->opt[len]))
|
|
|
|
len++;
|
|
|
|
arg = xmemdupz(p->opt, len);
|
|
|
|
p->opt = p->opt[len] ? p->opt + len : NULL;
|
|
|
|
rc = (*numopt->callback)(numopt, arg, 0) ? (-1) : 0;
|
|
|
|
free(arg);
|
|
|
|
return rc;
|
2007-10-14 23:35:37 +00:00
|
|
|
}
|
2008-06-23 20:46:36 +00:00
|
|
|
return -2;
|
2007-10-14 23:35:37 +00:00
|
|
|
}
|
|
|
|
|
2008-06-23 19:59:37 +00:00
|
|
|
static int parse_long_opt(struct parse_opt_ctx_t *p, const char *arg,
|
2018-12-06 15:42:06 +00:00
|
|
|
const struct option *options)
|
2007-10-14 23:35:37 +00:00
|
|
|
{
|
2013-07-30 19:06:01 +00:00
|
|
|
const struct option *all_opts = options;
|
2014-03-08 06:48:31 +00:00
|
|
|
const char *arg_end = strchrnul(arg, '=');
|
2007-11-05 13:15:21 +00:00
|
|
|
const struct option *abbrev_option = NULL, *ambiguous_option = NULL;
|
|
|
|
int abbrev_flags = 0, ambiguous_flags = 0;
|
2007-10-14 16:54:06 +00:00
|
|
|
|
2007-10-14 23:35:37 +00:00
|
|
|
for (; options->type != OPTION_END; options++) {
|
2012-02-25 19:14:54 +00:00
|
|
|
const char *rest, *long_name = options->long_name;
|
|
|
|
int flags = 0, opt_flags = 0;
|
2007-10-14 23:35:37 +00:00
|
|
|
|
2012-02-25 19:14:54 +00:00
|
|
|
if (!long_name)
|
2007-10-14 23:35:37 +00:00
|
|
|
continue;
|
|
|
|
|
2012-02-25 19:14:54 +00:00
|
|
|
again:
|
refactor skip_prefix to return a boolean
The skip_prefix() function returns a pointer to the content
past the prefix, or NULL if the prefix was not found. While
this is nice and simple, in practice it makes it hard to use
for two reasons:
1. When you want to conditionally skip or keep the string
as-is, you have to introduce a temporary variable.
For example:
tmp = skip_prefix(buf, "foo");
if (tmp)
buf = tmp;
2. It is verbose to check the outcome in a conditional, as
you need extra parentheses to silence compiler
warnings. For example:
if ((cp = skip_prefix(buf, "foo"))
/* do something with cp */
Both of these make it harder to use for long if-chains, and
we tend to use starts_with() instead. However, the first line
of "do something" is often to then skip forward in buf past
the prefix, either using a magic constant or with an extra
strlen(3) (which is generally computed at compile time, but
means we are repeating ourselves).
This patch refactors skip_prefix() to return a simple boolean,
and to provide the pointer value as an out-parameter. If the
prefix is not found, the out-parameter is untouched. This
lets you write:
if (skip_prefix(arg, "foo ", &arg))
do_foo(arg);
else if (skip_prefix(arg, "bar ", &arg))
do_bar(arg);
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-06-18 19:44:19 +00:00
|
|
|
if (!skip_prefix(arg, long_name, &rest))
|
|
|
|
rest = NULL;
|
2008-03-02 10:35:56 +00:00
|
|
|
if (options->type == OPTION_ARGUMENT) {
|
|
|
|
if (!rest)
|
|
|
|
continue;
|
|
|
|
if (*rest == '=')
|
2018-11-10 05:16:11 +00:00
|
|
|
return error(_("%s takes no value"),
|
|
|
|
optname(options, flags));
|
2008-03-02 10:35:56 +00:00
|
|
|
if (*rest)
|
|
|
|
continue;
|
|
|
|
p->out[p->cpidx++] = arg - 2;
|
|
|
|
return 0;
|
|
|
|
}
|
2007-10-14 23:35:37 +00:00
|
|
|
if (!rest) {
|
2007-10-14 16:54:06 +00:00
|
|
|
/* abbreviated? */
|
2012-02-25 19:14:54 +00:00
|
|
|
if (!strncmp(long_name, arg, arg_end - arg)) {
|
2007-10-14 16:54:06 +00:00
|
|
|
is_abbreviated:
|
2007-11-05 13:15:21 +00:00
|
|
|
if (abbrev_option) {
|
|
|
|
/*
|
|
|
|
* If this is abbreviated, it is
|
|
|
|
* ambiguous. So when there is no
|
|
|
|
* exact match later, we need to
|
|
|
|
* error out.
|
|
|
|
*/
|
|
|
|
ambiguous_option = abbrev_option;
|
|
|
|
ambiguous_flags = abbrev_flags;
|
|
|
|
}
|
2007-10-14 16:54:06 +00:00
|
|
|
if (!(flags & OPT_UNSET) && *arg_end)
|
|
|
|
p->opt = arg_end + 1;
|
|
|
|
abbrev_option = options;
|
2012-02-25 19:14:54 +00:00
|
|
|
abbrev_flags = flags ^ opt_flags;
|
2007-10-14 16:54:06 +00:00
|
|
|
continue;
|
|
|
|
}
|
2009-09-25 18:44:44 +00:00
|
|
|
/* negation allowed? */
|
|
|
|
if (options->flags & PARSE_OPT_NONEG)
|
|
|
|
continue;
|
2007-10-14 16:54:06 +00:00
|
|
|
/* negated and abbreviated very much? */
|
2013-11-30 20:55:40 +00:00
|
|
|
if (starts_with("no-", arg)) {
|
2007-10-14 16:54:06 +00:00
|
|
|
flags |= OPT_UNSET;
|
|
|
|
goto is_abbreviated;
|
|
|
|
}
|
|
|
|
/* negated? */
|
2013-11-30 20:55:40 +00:00
|
|
|
if (!starts_with(arg, "no-")) {
|
|
|
|
if (starts_with(long_name, "no-")) {
|
2012-02-25 19:14:54 +00:00
|
|
|
long_name += 3;
|
|
|
|
opt_flags |= OPT_UNSET;
|
|
|
|
goto again;
|
|
|
|
}
|
2007-10-14 23:35:37 +00:00
|
|
|
continue;
|
2012-02-25 19:14:54 +00:00
|
|
|
}
|
2007-10-14 23:35:37 +00:00
|
|
|
flags |= OPT_UNSET;
|
refactor skip_prefix to return a boolean
The skip_prefix() function returns a pointer to the content
past the prefix, or NULL if the prefix was not found. While
this is nice and simple, in practice it makes it hard to use
for two reasons:
1. When you want to conditionally skip or keep the string
as-is, you have to introduce a temporary variable.
For example:
tmp = skip_prefix(buf, "foo");
if (tmp)
buf = tmp;
2. It is verbose to check the outcome in a conditional, as
you need extra parentheses to silence compiler
warnings. For example:
if ((cp = skip_prefix(buf, "foo"))
/* do something with cp */
Both of these make it harder to use for long if-chains, and
we tend to use starts_with() instead. However, the first line
of "do something" is often to then skip forward in buf past
the prefix, either using a magic constant or with an extra
strlen(3) (which is generally computed at compile time, but
means we are repeating ourselves).
This patch refactors skip_prefix() to return a simple boolean,
and to provide the pointer value as an out-parameter. If the
prefix is not found, the out-parameter is untouched. This
lets you write:
if (skip_prefix(arg, "foo ", &arg))
do_foo(arg);
else if (skip_prefix(arg, "bar ", &arg))
do_bar(arg);
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-06-18 19:44:19 +00:00
|
|
|
if (!skip_prefix(arg + 3, long_name, &rest)) {
|
|
|
|
/* abbreviated and negated? */
|
|
|
|
if (starts_with(long_name, arg + 3))
|
|
|
|
goto is_abbreviated;
|
|
|
|
else
|
|
|
|
continue;
|
|
|
|
}
|
2007-10-14 23:35:37 +00:00
|
|
|
}
|
|
|
|
if (*rest) {
|
|
|
|
if (*rest != '=')
|
|
|
|
continue;
|
|
|
|
p->opt = rest + 1;
|
|
|
|
}
|
2013-07-30 19:06:01 +00:00
|
|
|
return get_value(p, options, all_opts, flags ^ opt_flags);
|
2007-10-14 23:35:37 +00:00
|
|
|
}
|
2007-11-05 13:15:21 +00:00
|
|
|
|
2018-03-22 18:43:51 +00:00
|
|
|
if (ambiguous_option) {
|
2018-11-10 05:16:13 +00:00
|
|
|
error(_("ambiguous option: %s "
|
|
|
|
"(could be --%s%s or --%s%s)"),
|
2007-11-05 13:15:21 +00:00
|
|
|
arg,
|
|
|
|
(ambiguous_flags & OPT_UNSET) ? "no-" : "",
|
|
|
|
ambiguous_option->long_name,
|
|
|
|
(abbrev_flags & OPT_UNSET) ? "no-" : "",
|
|
|
|
abbrev_option->long_name);
|
2018-03-22 18:43:51 +00:00
|
|
|
return -3;
|
|
|
|
}
|
2007-10-14 16:54:06 +00:00
|
|
|
if (abbrev_option)
|
2013-07-30 19:06:01 +00:00
|
|
|
return get_value(p, abbrev_option, all_opts, abbrev_flags);
|
2008-06-23 20:46:36 +00:00
|
|
|
return -2;
|
2007-10-14 23:35:37 +00:00
|
|
|
}
|
|
|
|
|
2009-05-07 19:45:42 +00:00
|
|
|
static int parse_nodash_opt(struct parse_opt_ctx_t *p, const char *arg,
|
|
|
|
const struct option *options)
|
|
|
|
{
|
2013-07-30 19:06:01 +00:00
|
|
|
const struct option *all_opts = options;
|
|
|
|
|
2009-05-07 19:45:42 +00:00
|
|
|
for (; options->type != OPTION_END; options++) {
|
|
|
|
if (!(options->flags & PARSE_OPT_NODASH))
|
|
|
|
continue;
|
|
|
|
if (options->short_name == arg[0] && arg[1] == '\0')
|
2013-07-30 19:06:01 +00:00
|
|
|
return get_value(p, options, all_opts, OPT_SHORT);
|
2009-05-07 19:45:42 +00:00
|
|
|
}
|
|
|
|
return -2;
|
|
|
|
}
|
|
|
|
|
2008-07-16 10:42:18 +00:00
|
|
|
static void check_typos(const char *arg, const struct option *options)
|
2008-01-26 11:26:57 +00:00
|
|
|
{
|
|
|
|
if (strlen(arg) < 3)
|
|
|
|
return;
|
|
|
|
|
2013-11-30 20:55:40 +00:00
|
|
|
if (starts_with(arg, "no-")) {
|
2018-11-10 05:16:13 +00:00
|
|
|
error(_("did you mean `--%s` (with two dashes ?)"), arg);
|
2008-01-26 11:26:57 +00:00
|
|
|
exit(129);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (; options->type != OPTION_END; options++) {
|
|
|
|
if (!options->long_name)
|
|
|
|
continue;
|
2013-11-30 20:55:40 +00:00
|
|
|
if (starts_with(options->long_name, arg)) {
|
2018-11-10 05:16:13 +00:00
|
|
|
error(_("did you mean `--%s` (with two dashes ?)"), arg);
|
2008-01-26 11:26:57 +00:00
|
|
|
exit(129);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-06-09 08:23:44 +00:00
|
|
|
static void parse_options_check(const struct option *opts)
|
|
|
|
{
|
|
|
|
int err = 0;
|
2014-09-03 19:42:37 +00:00
|
|
|
char short_opts[128];
|
2009-06-09 08:23:44 +00:00
|
|
|
|
2014-09-03 19:42:37 +00:00
|
|
|
memset(short_opts, '\0', sizeof(short_opts));
|
2009-06-09 08:23:44 +00:00
|
|
|
for (; opts->type != OPTION_END; opts++) {
|
|
|
|
if ((opts->flags & PARSE_OPT_LASTARG_DEFAULT) &&
|
2010-12-02 06:01:18 +00:00
|
|
|
(opts->flags & PARSE_OPT_OPTARG))
|
|
|
|
err |= optbug(opts, "uses incompatible flags "
|
|
|
|
"LASTARG_DEFAULT and OPTARG");
|
2014-09-03 19:42:37 +00:00
|
|
|
if (opts->short_name) {
|
|
|
|
if (0x7F <= opts->short_name)
|
|
|
|
err |= optbug(opts, "invalid short name");
|
|
|
|
else if (short_opts[opts->short_name]++)
|
|
|
|
err |= optbug(opts, "short name already used");
|
|
|
|
}
|
2010-12-02 06:05:05 +00:00
|
|
|
if (opts->flags & PARSE_OPT_NODASH &&
|
|
|
|
((opts->flags & PARSE_OPT_OPTARG) ||
|
|
|
|
!(opts->flags & PARSE_OPT_NOARG) ||
|
|
|
|
!(opts->flags & PARSE_OPT_NONEG) ||
|
|
|
|
opts->long_name))
|
|
|
|
err |= optbug(opts, "uses feature "
|
|
|
|
"not supported for dashless options");
|
2010-12-02 06:08:57 +00:00
|
|
|
switch (opts->type) {
|
parse-options: deprecate OPT_BOOLEAN
It is natural to expect that an option defined with OPT_BOOLEAN() could be
used in this way:
int option = -1; /* unspecified */
struct option options[] = {
OPT_BOOLEAN(0, "option", &option, "set option"),
OPT_END()
};
parse_options(ac, av, prefix, options, usage, 0);
if (option < 0)
... do the default thing ...
else if (!option)
... --no-option was given ...
else
... --option was given ...
to easily tell three cases apart:
- There is no mention of the `--option` on the command line;
- The variable is positively set with `--option`; or
- The variable is explicitly negated with `--no-option`.
Unfortunately, this is not the case. OPT_BOOLEAN() increments the variable
every time `--option` is given, and resets it to zero when `--no-option`
is given.
As a first step to remedy this, introduce a true boolean OPT_BOOL(), and
rename OPT_BOOLEAN() to OPT_COUNTUP(). To help transitioning, OPT_BOOLEAN
and OPTION_BOOLEAN are defined as deprecated synonyms to OPT_COUNTUP and
OPTION_COUNTUP respectively.
This is what db7244b (parse-options new features., 2007-11-07) from four
years ago started by marking OPTION_BOOLEAN as "INCR would have been a
better name".
Some existing users do depend on the count-up semantics; for example,
users of OPT__VERBOSE() could use it to raise the verbosity level with
repeated use of `-v` on the command line, but they probably should be
rewritten to use OPT__VERBOSITY() instead these days. I suspect that some
users of OPT__FORCE() may also use it to implement different level of
forcibleness but I didn't check.
On top of this patch, here are the remaining clean-up tasks that other
people can help:
- Look at each hit in "git grep -e OPT_BOOLEAN"; trace all uses of the
value that is set to the underlying variable, and if it can proven that
the variable is only used as a boolean, replace it with OPT_BOOL(). If
the caller does depend on the count-up semantics, replace it with
OPT_COUNTUP() instead.
- Same for OPTION_BOOLEAN; replace it with OPTION_SET_INT and arrange to
set 1 to the variable for a true boolean, and otherwise replace it with
OPTION_COUNTUP.
- Look at each hit in "git grep -e OPT__VERBOSE -e OPT__QUIET" and see if
they can be replaced with OPT__VERBOSITY().
I'll follow this message up with a separate patch as an example.
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2011-09-27 23:56:49 +00:00
|
|
|
case OPTION_COUNTUP:
|
2010-12-02 06:08:57 +00:00
|
|
|
case OPTION_BIT:
|
|
|
|
case OPTION_NEGBIT:
|
|
|
|
case OPTION_SET_INT:
|
|
|
|
case OPTION_NUMBER:
|
|
|
|
if ((opts->flags & PARSE_OPT_OPTARG) ||
|
|
|
|
!(opts->flags & PARSE_OPT_NOARG))
|
|
|
|
err |= optbug(opts, "should not accept an argument");
|
|
|
|
default:
|
|
|
|
; /* ok. (usually accepts an argument) */
|
|
|
|
}
|
2014-03-23 23:04:36 +00:00
|
|
|
if (opts->argh &&
|
|
|
|
strcspn(opts->argh, " _") != strlen(opts->argh))
|
|
|
|
err |= optbug(opts, "multi-word argh should use dash to separate words");
|
2009-06-09 08:23:44 +00:00
|
|
|
}
|
|
|
|
if (err)
|
2010-12-02 06:01:18 +00:00
|
|
|
exit(128);
|
2009-06-09 08:23:44 +00:00
|
|
|
}
|
|
|
|
|
2008-06-23 19:59:37 +00:00
|
|
|
void parse_options_start(struct parse_opt_ctx_t *ctx,
|
2009-05-23 18:53:12 +00:00
|
|
|
int argc, const char **argv, const char *prefix,
|
2010-12-06 07:57:42 +00:00
|
|
|
const struct option *options, int flags)
|
2008-06-23 19:59:37 +00:00
|
|
|
{
|
|
|
|
memset(ctx, 0, sizeof(*ctx));
|
2019-01-27 00:35:23 +00:00
|
|
|
ctx->argc = argc;
|
|
|
|
ctx->argv = argv;
|
|
|
|
if (!(flags & PARSE_OPT_ONE_SHOT)) {
|
|
|
|
ctx->argc--;
|
|
|
|
ctx->argv++;
|
|
|
|
}
|
|
|
|
ctx->total = ctx->argc;
|
|
|
|
ctx->out = argv;
|
2009-05-23 18:53:12 +00:00
|
|
|
ctx->prefix = prefix;
|
2008-06-23 22:31:31 +00:00
|
|
|
ctx->cpidx = ((flags & PARSE_OPT_KEEP_ARGV0) != 0);
|
2008-06-23 19:59:37 +00:00
|
|
|
ctx->flags = flags;
|
2009-03-09 20:57:38 +00:00
|
|
|
if ((flags & PARSE_OPT_KEEP_UNKNOWN) &&
|
2019-01-27 00:35:23 +00:00
|
|
|
(flags & PARSE_OPT_STOP_AT_NON_OPTION) &&
|
|
|
|
!(flags & PARSE_OPT_ONE_SHOT))
|
2018-11-10 05:16:12 +00:00
|
|
|
BUG("STOP_AT_NON_OPTION and KEEP_UNKNOWN don't go together");
|
2019-01-27 00:35:23 +00:00
|
|
|
if ((flags & PARSE_OPT_ONE_SHOT) &&
|
|
|
|
(flags & PARSE_OPT_KEEP_ARGV0))
|
|
|
|
BUG("Can't keep argv0 if you don't have it");
|
2010-12-06 07:57:42 +00:00
|
|
|
parse_options_check(options);
|
2008-06-23 19:59:37 +00:00
|
|
|
}
|
|
|
|
|
2018-06-06 09:41:39 +00:00
|
|
|
static void show_negated_gitcomp(const struct option *opts, int nr_noopts)
|
|
|
|
{
|
|
|
|
int printed_dashdash = 0;
|
|
|
|
|
|
|
|
for (; opts->type != OPTION_END; opts++) {
|
|
|
|
int has_unset_form = 0;
|
|
|
|
const char *name;
|
|
|
|
|
|
|
|
if (!opts->long_name)
|
|
|
|
continue;
|
|
|
|
if (opts->flags & (PARSE_OPT_HIDDEN | PARSE_OPT_NOCOMPLETE))
|
|
|
|
continue;
|
|
|
|
if (opts->flags & PARSE_OPT_NONEG)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
switch (opts->type) {
|
|
|
|
case OPTION_STRING:
|
|
|
|
case OPTION_FILENAME:
|
|
|
|
case OPTION_INTEGER:
|
|
|
|
case OPTION_MAGNITUDE:
|
|
|
|
case OPTION_CALLBACK:
|
|
|
|
case OPTION_BIT:
|
|
|
|
case OPTION_NEGBIT:
|
|
|
|
case OPTION_COUNTUP:
|
|
|
|
case OPTION_SET_INT:
|
|
|
|
has_unset_form = 1;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (!has_unset_form)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (skip_prefix(opts->long_name, "no-", &name)) {
|
|
|
|
if (nr_noopts < 0)
|
|
|
|
printf(" --%s", name);
|
|
|
|
} else if (nr_noopts >= 0) {
|
|
|
|
if (nr_noopts && !printed_dashdash) {
|
|
|
|
printf(" --");
|
|
|
|
printed_dashdash = 1;
|
|
|
|
}
|
|
|
|
printf(" --no-%s", opts->long_name);
|
|
|
|
nr_noopts++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-09 11:01:40 +00:00
|
|
|
static int show_gitcomp(struct parse_opt_ctx_t *ctx,
|
|
|
|
const struct option *opts)
|
|
|
|
{
|
2018-06-06 09:41:39 +00:00
|
|
|
const struct option *original_opts = opts;
|
|
|
|
int nr_noopts = 0;
|
|
|
|
|
2018-02-09 11:01:40 +00:00
|
|
|
for (; opts->type != OPTION_END; opts++) {
|
|
|
|
const char *suffix = "";
|
|
|
|
|
|
|
|
if (!opts->long_name)
|
|
|
|
continue;
|
|
|
|
if (opts->flags & (PARSE_OPT_HIDDEN | PARSE_OPT_NOCOMPLETE))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
switch (opts->type) {
|
|
|
|
case OPTION_GROUP:
|
|
|
|
continue;
|
|
|
|
case OPTION_STRING:
|
|
|
|
case OPTION_FILENAME:
|
|
|
|
case OPTION_INTEGER:
|
|
|
|
case OPTION_MAGNITUDE:
|
|
|
|
case OPTION_CALLBACK:
|
|
|
|
if (opts->flags & PARSE_OPT_NOARG)
|
|
|
|
break;
|
|
|
|
if (opts->flags & PARSE_OPT_OPTARG)
|
|
|
|
break;
|
|
|
|
if (opts->flags & PARSE_OPT_LASTARG_DEFAULT)
|
|
|
|
break;
|
|
|
|
suffix = "=";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2018-02-09 11:02:12 +00:00
|
|
|
if (opts->flags & PARSE_OPT_COMP_ARG)
|
|
|
|
suffix = "=";
|
2018-06-06 09:41:39 +00:00
|
|
|
if (starts_with(opts->long_name, "no-"))
|
|
|
|
nr_noopts++;
|
2018-02-09 11:01:40 +00:00
|
|
|
printf(" --%s%s", opts->long_name, suffix);
|
|
|
|
}
|
2018-06-06 09:41:39 +00:00
|
|
|
show_negated_gitcomp(original_opts, -1);
|
|
|
|
show_negated_gitcomp(original_opts, nr_noopts);
|
2018-02-09 11:01:40 +00:00
|
|
|
fputc('\n', stdout);
|
2018-12-11 15:35:01 +00:00
|
|
|
return PARSE_OPT_COMPLETE;
|
2018-02-09 11:01:40 +00:00
|
|
|
}
|
|
|
|
|
2010-06-12 12:57:39 +00:00
|
|
|
static int usage_with_options_internal(struct parse_opt_ctx_t *,
|
|
|
|
const char * const *,
|
2010-05-17 15:34:41 +00:00
|
|
|
const struct option *, int, int);
|
2007-11-19 09:21:44 +00:00
|
|
|
|
2008-06-23 20:38:58 +00:00
|
|
|
int parse_options_step(struct parse_opt_ctx_t *ctx,
|
|
|
|
const struct option *options,
|
|
|
|
const char * const usagestr[])
|
2007-10-14 23:35:37 +00:00
|
|
|
{
|
2009-03-08 18:15:08 +00:00
|
|
|
int internal_help = !(ctx->flags & PARSE_OPT_NO_INTERNAL_HELP);
|
|
|
|
|
2008-06-23 20:55:11 +00:00
|
|
|
/* we must reset ->opt, unknown short option leave it dangling */
|
|
|
|
ctx->opt = NULL;
|
|
|
|
|
2008-06-23 20:38:58 +00:00
|
|
|
for (; ctx->argc; ctx->argc--, ctx->argv++) {
|
|
|
|
const char *arg = ctx->argv[0];
|
2007-10-14 23:35:37 +00:00
|
|
|
|
2019-01-27 00:35:23 +00:00
|
|
|
if (ctx->flags & PARSE_OPT_ONE_SHOT &&
|
|
|
|
ctx->argc != ctx->total)
|
|
|
|
break;
|
|
|
|
|
2007-10-14 23:35:37 +00:00
|
|
|
if (*arg != '-' || !arg[1]) {
|
2009-05-07 19:45:42 +00:00
|
|
|
if (parse_nodash_opt(ctx, arg, options) == 0)
|
|
|
|
continue;
|
2008-06-23 20:38:58 +00:00
|
|
|
if (ctx->flags & PARSE_OPT_STOP_AT_NON_OPTION)
|
2010-12-01 23:32:55 +00:00
|
|
|
return PARSE_OPT_NON_OPTION;
|
2008-06-23 20:38:58 +00:00
|
|
|
ctx->out[ctx->cpidx++] = ctx->argv[0];
|
2007-10-14 23:35:37 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2015-11-17 10:25:38 +00:00
|
|
|
/* lone -h asks for help */
|
|
|
|
if (internal_help && ctx->total == 1 && !strcmp(arg + 1, "h"))
|
|
|
|
goto show_usage;
|
|
|
|
|
2018-02-09 11:01:40 +00:00
|
|
|
/* lone --git-completion-helper is asked by git-completion.bash */
|
|
|
|
if (ctx->total == 1 && !strcmp(arg + 1, "-git-completion-helper"))
|
|
|
|
return show_gitcomp(ctx, options);
|
|
|
|
|
2007-10-14 23:35:37 +00:00
|
|
|
if (arg[1] != '-') {
|
2008-06-23 20:38:58 +00:00
|
|
|
ctx->opt = arg + 1;
|
2008-06-23 20:46:36 +00:00
|
|
|
switch (parse_short_opt(ctx, options)) {
|
|
|
|
case -1:
|
2018-03-22 18:43:51 +00:00
|
|
|
return PARSE_OPT_ERROR;
|
2008-06-23 20:46:36 +00:00
|
|
|
case -2:
|
2012-03-03 11:00:29 +00:00
|
|
|
if (ctx->opt)
|
|
|
|
check_typos(arg + 1, options);
|
2015-11-17 10:25:38 +00:00
|
|
|
if (internal_help && *ctx->opt == 'h')
|
|
|
|
goto show_usage;
|
2009-03-08 18:12:47 +00:00
|
|
|
goto unknown;
|
2008-06-23 20:46:36 +00:00
|
|
|
}
|
2008-06-23 20:38:58 +00:00
|
|
|
if (ctx->opt)
|
2008-01-26 11:26:57 +00:00
|
|
|
check_typos(arg + 1, options);
|
2008-06-23 20:38:58 +00:00
|
|
|
while (ctx->opt) {
|
2008-06-23 20:46:36 +00:00
|
|
|
switch (parse_short_opt(ctx, options)) {
|
|
|
|
case -1:
|
2018-03-22 18:43:51 +00:00
|
|
|
return PARSE_OPT_ERROR;
|
2008-06-23 20:46:36 +00:00
|
|
|
case -2:
|
2015-11-17 10:25:38 +00:00
|
|
|
if (internal_help && *ctx->opt == 'h')
|
|
|
|
goto show_usage;
|
|
|
|
|
2008-06-23 20:55:11 +00:00
|
|
|
/* fake a short option thing to hide the fact that we may have
|
|
|
|
* started to parse aggregated stuff
|
|
|
|
*
|
|
|
|
* This is leaky, too bad.
|
|
|
|
*/
|
|
|
|
ctx->argv[0] = xstrdup(ctx->opt - 1);
|
|
|
|
*(char *)ctx->argv[0] = '-';
|
2009-03-08 18:12:47 +00:00
|
|
|
goto unknown;
|
2008-06-23 20:46:36 +00:00
|
|
|
}
|
2008-01-26 11:26:57 +00:00
|
|
|
}
|
2007-10-14 23:35:37 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!arg[2]) { /* "--" */
|
2008-06-23 20:38:58 +00:00
|
|
|
if (!(ctx->flags & PARSE_OPT_KEEP_DASHDASH)) {
|
|
|
|
ctx->argc--;
|
|
|
|
ctx->argv++;
|
2007-10-14 23:35:37 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2009-03-08 18:15:08 +00:00
|
|
|
if (internal_help && !strcmp(arg + 2, "help-all"))
|
2010-06-12 12:57:39 +00:00
|
|
|
return usage_with_options_internal(ctx, usagestr, options, 1, 0);
|
2009-03-08 18:15:08 +00:00
|
|
|
if (internal_help && !strcmp(arg + 2, "help"))
|
2015-11-17 10:25:14 +00:00
|
|
|
goto show_usage;
|
2008-06-23 20:46:36 +00:00
|
|
|
switch (parse_long_opt(ctx, arg + 2, options)) {
|
|
|
|
case -1:
|
2018-03-22 18:43:51 +00:00
|
|
|
return PARSE_OPT_ERROR;
|
2008-06-23 20:46:36 +00:00
|
|
|
case -2:
|
2009-03-08 18:12:47 +00:00
|
|
|
goto unknown;
|
2018-03-22 18:43:51 +00:00
|
|
|
case -3:
|
|
|
|
goto show_usage;
|
2008-06-23 20:46:36 +00:00
|
|
|
}
|
2009-03-08 18:12:47 +00:00
|
|
|
continue;
|
|
|
|
unknown:
|
2019-01-27 00:35:23 +00:00
|
|
|
if (ctx->flags & PARSE_OPT_ONE_SHOT)
|
|
|
|
break;
|
2009-03-08 18:12:47 +00:00
|
|
|
if (!(ctx->flags & PARSE_OPT_KEEP_UNKNOWN))
|
|
|
|
return PARSE_OPT_UNKNOWN;
|
|
|
|
ctx->out[ctx->cpidx++] = ctx->argv[0];
|
|
|
|
ctx->opt = NULL;
|
2008-06-23 20:38:58 +00:00
|
|
|
}
|
|
|
|
return PARSE_OPT_DONE;
|
2015-11-17 10:25:14 +00:00
|
|
|
|
|
|
|
show_usage:
|
2018-03-22 18:43:51 +00:00
|
|
|
return usage_with_options_internal(ctx, usagestr, options, 0, 0);
|
2008-06-23 20:38:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int parse_options_end(struct parse_opt_ctx_t *ctx)
|
|
|
|
{
|
2019-01-27 00:35:23 +00:00
|
|
|
if (ctx->flags & PARSE_OPT_ONE_SHOT)
|
|
|
|
return ctx->total - ctx->argc;
|
|
|
|
|
2018-01-22 17:50:09 +00:00
|
|
|
MOVE_ARRAY(ctx->out + ctx->cpidx, ctx->argv, ctx->argc);
|
2008-06-23 20:38:58 +00:00
|
|
|
ctx->out[ctx->cpidx + ctx->argc] = NULL;
|
|
|
|
return ctx->cpidx + ctx->argc;
|
|
|
|
}
|
|
|
|
|
2009-05-23 18:53:12 +00:00
|
|
|
int parse_options(int argc, const char **argv, const char *prefix,
|
|
|
|
const struct option *options, const char * const usagestr[],
|
|
|
|
int flags)
|
2008-06-23 20:38:58 +00:00
|
|
|
{
|
|
|
|
struct parse_opt_ctx_t ctx;
|
|
|
|
|
2010-12-06 07:57:42 +00:00
|
|
|
parse_options_start(&ctx, argc, argv, prefix, options, flags);
|
2008-06-23 20:38:58 +00:00
|
|
|
switch (parse_options_step(&ctx, options, usagestr)) {
|
|
|
|
case PARSE_OPT_HELP:
|
2018-03-22 18:43:51 +00:00
|
|
|
case PARSE_OPT_ERROR:
|
2008-06-23 20:38:58 +00:00
|
|
|
exit(129);
|
2018-12-11 15:35:01 +00:00
|
|
|
case PARSE_OPT_COMPLETE:
|
|
|
|
exit(0);
|
2010-12-01 23:32:55 +00:00
|
|
|
case PARSE_OPT_NON_OPTION:
|
2008-06-23 20:38:58 +00:00
|
|
|
case PARSE_OPT_DONE:
|
|
|
|
break;
|
|
|
|
default: /* PARSE_OPT_UNKNOWN */
|
2008-06-23 20:46:36 +00:00
|
|
|
if (ctx.argv[0][1] == '-') {
|
2018-11-10 05:16:13 +00:00
|
|
|
error(_("unknown option `%s'"), ctx.argv[0] + 2);
|
2013-02-11 23:13:48 +00:00
|
|
|
} else if (isascii(*ctx.opt)) {
|
2018-11-10 05:16:13 +00:00
|
|
|
error(_("unknown switch `%c'"), *ctx.opt);
|
2013-02-11 23:13:48 +00:00
|
|
|
} else {
|
2018-11-10 05:16:13 +00:00
|
|
|
error(_("unknown non-ascii option in string: `%s'"),
|
2013-02-11 23:13:48 +00:00
|
|
|
ctx.argv[0]);
|
2008-06-23 20:46:36 +00:00
|
|
|
}
|
|
|
|
usage_with_options(usagestr, options);
|
2007-10-14 23:35:37 +00:00
|
|
|
}
|
|
|
|
|
git on Mac OS and precomposed unicode
Mac OS X mangles file names containing unicode on file systems HFS+,
VFAT or SAMBA. When a file using unicode code points outside ASCII
is created on a HFS+ drive, the file name is converted into
decomposed unicode and written to disk. No conversion is done if
the file name is already decomposed unicode.
Calling open("\xc3\x84", ...) with a precomposed "Ä" yields the same
result as open("\x41\xcc\x88",...) with a decomposed "Ä".
As a consequence, readdir() returns the file names in decomposed
unicode, even if the user expects precomposed unicode. Unlike on
HFS+, Mac OS X stores files on a VFAT drive (e.g. an USB drive) in
precomposed unicode, but readdir() still returns file names in
decomposed unicode. When a git repository is stored on a network
share using SAMBA, file names are send over the wire and written to
disk on the remote system in precomposed unicode, but Mac OS X
readdir() returns decomposed unicode to be compatible with its
behaviour on HFS+ and VFAT.
The unicode decomposition causes many problems:
- The names "git add" and other commands get from the end user may
often be precomposed form (the decomposed form is not easily input
from the keyboard), but when the commands read from the filesystem
to see what it is going to update the index with already is on the
filesystem, readdir() will give decomposed form, which is different.
- Similarly "git log", "git mv" and all other commands that need to
compare pathnames found on the command line (often but not always
precomposed form; a command line input resulting from globbing may
be in decomposed) with pathnames found in the tree objects (should
be precomposed form to be compatible with other systems and for
consistency in general).
- The same for names stored in the index, which should be
precomposed, that may need to be compared with the names read from
readdir().
NFS mounted from Linux is fully transparent and does not suffer from
the above.
As Mac OS X treats precomposed and decomposed file names as equal,
we can
- wrap readdir() on Mac OS X to return the precomposed form, and
- normalize decomposed form given from the command line also to the
precomposed form,
to ensure that all pathnames used in Git are always in the
precomposed form. This behaviour can be requested by setting
"core.precomposedunicode" configuration variable to true.
The code in compat/precomposed_utf8.c implements basically 4 new
functions: precomposed_utf8_opendir(), precomposed_utf8_readdir(),
precomposed_utf8_closedir() and precompose_argv(). The first three
are to wrap opendir(3), readdir(3), and closedir(3) functions.
The argv[] conversion allows to use the TAB filename completion done
by the shell on command line. It tolerates other tools which use
readdir() to feed decomposed file names into git.
When creating a new git repository with "git init" or "git clone",
"core.precomposedunicode" will be set "false".
The user needs to activate this feature manually. She typically
sets core.precomposedunicode to "true" on HFS and VFAT, or file
systems mounted via SAMBA.
Helped-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Torsten Bögershausen <tboegi@web.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2012-07-08 13:50:25 +00:00
|
|
|
precompose_argv(argc, argv);
|
2008-06-23 19:59:37 +00:00
|
|
|
return parse_options_end(&ctx);
|
2007-10-14 23:35:37 +00:00
|
|
|
}
|
2007-10-14 23:38:30 +00:00
|
|
|
|
2010-05-17 15:34:41 +00:00
|
|
|
static int usage_argh(const struct option *opts, FILE *outfile)
|
2009-05-21 07:33:17 +00:00
|
|
|
{
|
|
|
|
const char *s;
|
2018-08-02 19:18:14 +00:00
|
|
|
int literal = (opts->flags & PARSE_OPT_LITERAL_ARGHELP) ||
|
|
|
|
!opts->argh || !!strpbrk(opts->argh, "()<>[]|");
|
2009-05-21 07:33:17 +00:00
|
|
|
if (opts->flags & PARSE_OPT_OPTARG)
|
|
|
|
if (opts->long_name)
|
|
|
|
s = literal ? "[=%s]" : "[=<%s>]";
|
|
|
|
else
|
|
|
|
s = literal ? "[%s]" : "[<%s>]";
|
|
|
|
else
|
|
|
|
s = literal ? " %s" : " <%s>";
|
2013-02-09 06:31:09 +00:00
|
|
|
return utf8_fprintf(outfile, s, opts->argh ? _(opts->argh) : _("..."));
|
2009-05-21 07:33:17 +00:00
|
|
|
}
|
|
|
|
|
2007-10-14 23:38:30 +00:00
|
|
|
#define USAGE_OPTS_WIDTH 24
|
|
|
|
#define USAGE_GAP 2
|
|
|
|
|
2010-06-12 12:57:39 +00:00
|
|
|
static int usage_with_options_internal(struct parse_opt_ctx_t *ctx,
|
|
|
|
const char * const *usagestr,
|
|
|
|
const struct option *opts, int full, int err)
|
2007-10-14 23:38:30 +00:00
|
|
|
{
|
2010-05-17 15:34:41 +00:00
|
|
|
FILE *outfile = err ? stderr : stdout;
|
2017-09-25 04:08:05 +00:00
|
|
|
int need_newline;
|
2010-05-17 15:34:41 +00:00
|
|
|
|
2009-03-08 18:16:58 +00:00
|
|
|
if (!usagestr)
|
|
|
|
return PARSE_OPT_HELP;
|
|
|
|
|
2010-06-12 12:57:39 +00:00
|
|
|
if (!err && ctx && ctx->flags & PARSE_OPT_SHELL_EVAL)
|
|
|
|
fprintf(outfile, "cat <<\\EOF\n");
|
|
|
|
|
2012-05-06 14:23:51 +00:00
|
|
|
fprintf_ln(outfile, _("usage: %s"), _(*usagestr++));
|
2007-10-13 22:10:51 +00:00
|
|
|
while (*usagestr && **usagestr)
|
C style: use standard style for "TRANSLATORS" comments
Change all the "TRANSLATORS: [...]" comments in the C code to use the
regular Git coding style, and amend the style guide so that the
example there uses that style.
This custom style was necessary back in 2010 when the gettext support
was initially added, and was subsequently documented in commit
cbcfd4e3ea ("i18n: mention "TRANSLATORS:" marker in
Documentation/CodingGuidelines", 2014-04-18).
GNU xgettext hasn't had the parsing limitation that necessitated this
exception for almost 3 years. Since its 0.19 release on 2014-06-02
it's been able to recognize TRANSLATOR comments in the standard Git
comment syntax[1].
Usually we'd like to keep compatibility with software that's that
young, but in this case literally the only person who needs to be
using a gettext newer than 3 years old is Jiang Xin (the only person
who runs & commits "make pot" results), so I think in this case we can
make an exception.
This xgettext parsing feature was added after a thread on the Git
mailing list[2] which continued on the bug-gettext[3] list, but we
never subsequently changed our style & styleguide, do so.
There are already longstanding changes in git that use the standard
comment style & have their TRANSLATORS comments extracted properly
without getting the literal "*"'s mixed up in the text, as would
happen before xgettext 0.19.
Commit 7ff2683253 ("builtin-am: implement -i/--interactive",
2015-08-04) added one such comment, which in commit df0617bfa7 ("l10n:
git.pot: v2.6.0 round 1 (123 new, 41 removed)", 2015-09-05) got picked
up in the po/git.pot file with the right format, showing that Jiang
already runs a modern xgettext.
The xgettext parser does not handle the sort of non-standard comment
style that I'm amending here in sequencer.c, but that isn't standard
Git comment syntax anyway. With this change to sequencer.c & "make
pot" the comment in the pot file is now correct:
#. TRANSLATORS: %s will be "revert", "cherry-pick" or
-#. * "rebase -i".
+#. "rebase -i".
1. http://git.savannah.gnu.org/cgit/gettext.git/commit/?id=10af7fe6bd
2. <2ce9ec406501d112e032c8208417f8100bed04c6.1397712142.git.worldhello.net@gmail.com>
(https://public-inbox.org/git/2ce9ec406501d112e032c8208417f8100bed04c6.1397712142.git.worldhello.net@gmail.com/)
3. https://lists.gnu.org/archive/html/bug-gettext/2014-04/msg00016.html
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Acked-by: Jiang Xin <worldhello.net@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-05-11 21:20:12 +00:00
|
|
|
/*
|
|
|
|
* TRANSLATORS: the colon here should align with the
|
|
|
|
* one in "usage: %s" translation.
|
|
|
|
*/
|
2012-05-06 14:23:51 +00:00
|
|
|
fprintf_ln(outfile, _(" or: %s"), _(*usagestr++));
|
2008-06-14 07:27:21 +00:00
|
|
|
while (*usagestr) {
|
2012-05-06 14:23:51 +00:00
|
|
|
if (**usagestr)
|
|
|
|
fprintf_ln(outfile, _(" %s"), _(*usagestr));
|
|
|
|
else
|
2017-09-25 04:08:04 +00:00
|
|
|
fputc('\n', outfile);
|
2008-06-14 07:27:21 +00:00
|
|
|
usagestr++;
|
|
|
|
}
|
2007-10-14 23:38:30 +00:00
|
|
|
|
2017-09-25 04:08:05 +00:00
|
|
|
need_newline = 1;
|
2007-10-14 23:38:30 +00:00
|
|
|
|
|
|
|
for (; opts->type != OPTION_END; opts++) {
|
|
|
|
size_t pos;
|
|
|
|
int pad;
|
|
|
|
|
|
|
|
if (opts->type == OPTION_GROUP) {
|
2010-05-17 15:34:41 +00:00
|
|
|
fputc('\n', outfile);
|
2017-09-25 04:08:05 +00:00
|
|
|
need_newline = 0;
|
2007-10-14 23:38:30 +00:00
|
|
|
if (*opts->help)
|
2012-05-06 14:23:51 +00:00
|
|
|
fprintf(outfile, "%s\n", _(opts->help));
|
2007-10-14 23:38:30 +00:00
|
|
|
continue;
|
|
|
|
}
|
2007-11-19 09:21:44 +00:00
|
|
|
if (!full && (opts->flags & PARSE_OPT_HIDDEN))
|
|
|
|
continue;
|
2007-10-14 23:38:30 +00:00
|
|
|
|
2017-09-25 04:08:05 +00:00
|
|
|
if (need_newline) {
|
|
|
|
fputc('\n', outfile);
|
|
|
|
need_newline = 0;
|
|
|
|
}
|
|
|
|
|
2010-05-17 15:34:41 +00:00
|
|
|
pos = fprintf(outfile, " ");
|
2012-02-28 19:06:09 +00:00
|
|
|
if (opts->short_name) {
|
2009-05-07 19:45:42 +00:00
|
|
|
if (opts->flags & PARSE_OPT_NODASH)
|
2010-05-17 15:34:41 +00:00
|
|
|
pos += fprintf(outfile, "%c", opts->short_name);
|
2009-05-07 19:45:42 +00:00
|
|
|
else
|
2010-05-17 15:34:41 +00:00
|
|
|
pos += fprintf(outfile, "-%c", opts->short_name);
|
2009-05-07 19:45:42 +00:00
|
|
|
}
|
2007-10-14 23:38:30 +00:00
|
|
|
if (opts->long_name && opts->short_name)
|
2010-05-17 15:34:41 +00:00
|
|
|
pos += fprintf(outfile, ", ");
|
2007-10-14 23:38:30 +00:00
|
|
|
if (opts->long_name)
|
2012-02-28 19:06:09 +00:00
|
|
|
pos += fprintf(outfile, "--%s", opts->long_name);
|
2009-05-07 19:45:08 +00:00
|
|
|
if (opts->type == OPTION_NUMBER)
|
2013-02-09 06:31:09 +00:00
|
|
|
pos += utf8_fprintf(outfile, _("-NUM"));
|
2007-10-14 23:38:30 +00:00
|
|
|
|
2010-12-01 23:31:36 +00:00
|
|
|
if ((opts->flags & PARSE_OPT_LITERAL_ARGHELP) ||
|
|
|
|
!(opts->flags & PARSE_OPT_NOARG))
|
2010-05-17 15:34:41 +00:00
|
|
|
pos += usage_argh(opts, outfile);
|
2007-10-14 23:38:30 +00:00
|
|
|
|
2007-10-13 22:10:51 +00:00
|
|
|
if (pos <= USAGE_OPTS_WIDTH)
|
|
|
|
pad = USAGE_OPTS_WIDTH - pos;
|
2007-10-14 23:38:30 +00:00
|
|
|
else {
|
2010-05-17 15:34:41 +00:00
|
|
|
fputc('\n', outfile);
|
2007-10-14 23:38:30 +00:00
|
|
|
pad = USAGE_OPTS_WIDTH;
|
|
|
|
}
|
2012-05-06 14:23:51 +00:00
|
|
|
fprintf(outfile, "%*s%s\n", pad + USAGE_GAP, "", _(opts->help));
|
2007-10-14 23:38:30 +00:00
|
|
|
}
|
2010-05-17 15:34:41 +00:00
|
|
|
fputc('\n', outfile);
|
2007-10-13 22:10:51 +00:00
|
|
|
|
2010-06-12 12:57:39 +00:00
|
|
|
if (!err && ctx && ctx->flags & PARSE_OPT_SHELL_EVAL)
|
|
|
|
fputs("EOF\n", outfile);
|
|
|
|
|
2008-06-23 20:28:04 +00:00
|
|
|
return PARSE_OPT_HELP;
|
2007-10-14 23:38:30 +00:00
|
|
|
}
|
2007-10-14 09:05:12 +00:00
|
|
|
|
Fix sparse warnings
Fix warnings from 'make check'.
- These files don't include 'builtin.h' causing sparse to complain that
cmd_* isn't declared:
builtin/clone.c:364, builtin/fetch-pack.c:797,
builtin/fmt-merge-msg.c:34, builtin/hash-object.c:78,
builtin/merge-index.c:69, builtin/merge-recursive.c:22
builtin/merge-tree.c:341, builtin/mktag.c:156, builtin/notes.c:426
builtin/notes.c:822, builtin/pack-redundant.c:596,
builtin/pack-refs.c:10, builtin/patch-id.c:60, builtin/patch-id.c:149,
builtin/remote.c:1512, builtin/remote-ext.c:240,
builtin/remote-fd.c:53, builtin/reset.c:236, builtin/send-pack.c:384,
builtin/unpack-file.c:25, builtin/var.c:75
- These files have symbols which should be marked static since they're
only file scope:
submodule.c:12, diff.c:631, replace_object.c:92, submodule.c:13,
submodule.c:14, trace.c:78, transport.c:195, transport-helper.c:79,
unpack-trees.c:19, url.c:3, url.c:18, url.c:104, url.c:117, url.c:123,
url.c:129, url.c:136, thread-utils.c:21, thread-utils.c:48
- These files redeclare symbols to be different types:
builtin/index-pack.c:210, parse-options.c:564, parse-options.c:571,
usage.c:49, usage.c:58, usage.c:63, usage.c:72
- These files use a literal integer 0 when they really should use a NULL
pointer:
daemon.c:663, fast-import.c:2942, imap-send.c:1072, notes-merge.c:362
While we're in the area, clean up some unused #includes in builtin files
(mostly exec_cmd.h).
Signed-off-by: Stephen Boyd <bebarino@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2011-03-22 07:51:05 +00:00
|
|
|
void NORETURN usage_with_options(const char * const *usagestr,
|
2008-06-23 20:38:58 +00:00
|
|
|
const struct option *opts)
|
2007-11-19 09:21:44 +00:00
|
|
|
{
|
2010-06-12 12:57:39 +00:00
|
|
|
usage_with_options_internal(NULL, usagestr, opts, 0, 1);
|
2008-06-23 20:38:58 +00:00
|
|
|
exit(129);
|
2007-11-19 09:21:44 +00:00
|
|
|
}
|
|
|
|
|
Fix sparse warnings
Fix warnings from 'make check'.
- These files don't include 'builtin.h' causing sparse to complain that
cmd_* isn't declared:
builtin/clone.c:364, builtin/fetch-pack.c:797,
builtin/fmt-merge-msg.c:34, builtin/hash-object.c:78,
builtin/merge-index.c:69, builtin/merge-recursive.c:22
builtin/merge-tree.c:341, builtin/mktag.c:156, builtin/notes.c:426
builtin/notes.c:822, builtin/pack-redundant.c:596,
builtin/pack-refs.c:10, builtin/patch-id.c:60, builtin/patch-id.c:149,
builtin/remote.c:1512, builtin/remote-ext.c:240,
builtin/remote-fd.c:53, builtin/reset.c:236, builtin/send-pack.c:384,
builtin/unpack-file.c:25, builtin/var.c:75
- These files have symbols which should be marked static since they're
only file scope:
submodule.c:12, diff.c:631, replace_object.c:92, submodule.c:13,
submodule.c:14, trace.c:78, transport.c:195, transport-helper.c:79,
unpack-trees.c:19, url.c:3, url.c:18, url.c:104, url.c:117, url.c:123,
url.c:129, url.c:136, thread-utils.c:21, thread-utils.c:48
- These files redeclare symbols to be different types:
builtin/index-pack.c:210, parse-options.c:564, parse-options.c:571,
usage.c:49, usage.c:58, usage.c:63, usage.c:72
- These files use a literal integer 0 when they really should use a NULL
pointer:
daemon.c:663, fast-import.c:2942, imap-send.c:1072, notes-merge.c:362
While we're in the area, clean up some unused #includes in builtin files
(mostly exec_cmd.h).
Signed-off-by: Stephen Boyd <bebarino@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2011-03-22 07:51:05 +00:00
|
|
|
void NORETURN usage_msg_opt(const char *msg,
|
2009-02-02 05:12:58 +00:00
|
|
|
const char * const *usagestr,
|
|
|
|
const struct option *options)
|
|
|
|
{
|
2016-12-14 15:10:10 +00:00
|
|
|
fprintf(stderr, "fatal: %s\n\n", msg);
|
2009-02-02 05:12:58 +00:00
|
|
|
usage_with_options(usagestr, options);
|
|
|
|
}
|
|
|
|
|
2018-11-10 05:16:11 +00:00
|
|
|
const char *optname(const struct option *opt, int flags)
|
2012-12-15 17:42:10 +00:00
|
|
|
{
|
2018-11-10 05:16:11 +00:00
|
|
|
static struct strbuf sb = STRBUF_INIT;
|
|
|
|
|
|
|
|
strbuf_reset(&sb);
|
2012-12-15 17:42:10 +00:00
|
|
|
if (flags & OPT_SHORT)
|
2018-11-10 05:16:11 +00:00
|
|
|
strbuf_addf(&sb, "switch `%c'", opt->short_name);
|
|
|
|
else if (flags & OPT_UNSET)
|
|
|
|
strbuf_addf(&sb, "option `no-%s'", opt->long_name);
|
|
|
|
else
|
|
|
|
strbuf_addf(&sb, "option `%s'", opt->long_name);
|
|
|
|
|
|
|
|
return sb.buf;
|
2012-12-15 17:42:10 +00:00
|
|
|
}
|