shared/options: add equivalent of "-" in getopt_long
The parsing "mode" is specified as an exclusive mode, i.e. a combination of "+" and "-" is not supported. In principle this could be supported, but we don't use that in our code and are unlikely ever to do so.
This commit is contained in:
+31
-6
@@ -22,6 +22,7 @@ static bool option_arg_required(const Option *opt) {
|
||||
static bool option_is_metadata(const Option *opt) {
|
||||
/* A metadata entry that is not a real option, like the group marker */
|
||||
return ASSERT_PTR(opt)->flags & (OPTION_GROUP_MARKER |
|
||||
OPTION_POSITIONAL_ENTRY |
|
||||
OPTION_HELP_ENTRY |
|
||||
OPTION_HELP_ENTRY_VERBATIM);
|
||||
}
|
||||
@@ -89,9 +90,10 @@ int option_parse(
|
||||
const char *optname = NULL, *optval = NULL;
|
||||
_cleanup_free_ char *_optname = NULL; /* allocated option name */
|
||||
bool separate_optval = false;
|
||||
bool handling_positional_arg = false;
|
||||
|
||||
if (state->short_option_offset == 0) {
|
||||
/* Skip over non-option parameters */
|
||||
/* Handle non-option parameters */
|
||||
for (;;) {
|
||||
if (state->optind == state->argc)
|
||||
return 0;
|
||||
@@ -116,14 +118,31 @@ int option_parse(
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (state->mode == OPTION_PARSER_RETURN_POSITIONAL_ARGS) {
|
||||
handling_positional_arg = true;
|
||||
optval = state->argv[state->optind];
|
||||
break;
|
||||
}
|
||||
|
||||
state->optind++;
|
||||
}
|
||||
|
||||
/* Find matching option entry.
|
||||
* First, figure out if we have a long option or a short option. */
|
||||
assert(state->argv[state->optind][0] == '-');
|
||||
assert(handling_positional_arg || state->argv[state->optind][0] == '-');
|
||||
|
||||
if (state->argv[state->optind][1] == '-') {
|
||||
if (handling_positional_arg)
|
||||
/* We are supposed to return the positional arg to be handled. */
|
||||
for (option = options;; option++) {
|
||||
/* If OPTION_PARSER_RETURN_POSITIONAL_ARGS is specified,
|
||||
* OPTION_POSITIONAL must be used. */
|
||||
assert(option < options_end);
|
||||
|
||||
if (FLAGS_SET(option->flags, OPTION_POSITIONAL_ENTRY))
|
||||
break;
|
||||
}
|
||||
|
||||
else if (state->argv[state->optind][1] == '-') {
|
||||
/* We have a long option. */
|
||||
char *eq = strchr(state->argv[state->optind], '=');
|
||||
if (eq) {
|
||||
@@ -206,11 +225,11 @@ int option_parse(
|
||||
|
||||
assert(option);
|
||||
|
||||
if (optval && !option_takes_arg(option))
|
||||
if (!handling_positional_arg && optval && !option_takes_arg(option))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"%s: option '%s' doesn't allow an argument",
|
||||
program_invocation_short_name, optname);
|
||||
if (!optval && option_arg_required(option)) {
|
||||
if (!handling_positional_arg && !optval && option_arg_required(option)) {
|
||||
if (!state->argv[state->optind + 1])
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"%s: option '%s' requires an argument",
|
||||
@@ -220,7 +239,13 @@ int option_parse(
|
||||
}
|
||||
|
||||
if (state->short_option_offset == 0) {
|
||||
/* We're done with this option. Adjust the array and position. */
|
||||
/* We're done with this parameter. Adjust the array and position. */
|
||||
if (handling_positional_arg) {
|
||||
/* Sanity check */
|
||||
assert(state->positional_offset == state->optind);
|
||||
assert(!separate_optval);
|
||||
}
|
||||
|
||||
shift_arg(state->argv, state->positional_offset++, state->optind++);
|
||||
if (separate_optval)
|
||||
shift_arg(state->argv, state->positional_offset++, state->optind++);
|
||||
|
||||
+10
-4
@@ -6,10 +6,11 @@
|
||||
|
||||
typedef enum OptionFlags {
|
||||
OPTION_OPTIONAL_ARG = 1U << 0, /* Same as optional_argument in getopt */
|
||||
OPTION_STOPS_PARSING = 1U << 1, /* This option acts like "--" */
|
||||
OPTION_GROUP_MARKER = 1U << 2, /* Fake option entry to separate groups */
|
||||
OPTION_HELP_ENTRY = 1U << 3, /* Fake option entry to insert an additional help line */
|
||||
OPTION_HELP_ENTRY_VERBATIM = 1U << 4, /* Same, but use the long_code in the first column as written */
|
||||
OPTION_POSITIONAL_ENTRY = 1U << 1, /* The "option" to handle positional arguments */
|
||||
OPTION_STOPS_PARSING = 1U << 2, /* This option acts like "--" */
|
||||
OPTION_GROUP_MARKER = 1U << 3, /* Fake option entry to separate groups */
|
||||
OPTION_HELP_ENTRY = 1U << 4, /* Fake option entry to insert an additional help line */
|
||||
OPTION_HELP_ENTRY_VERBATIM = 1U << 5, /* Same, but use the long_code in the first column as written */
|
||||
} OptionFlags;
|
||||
|
||||
typedef struct Option {
|
||||
@@ -50,6 +51,7 @@ typedef struct Option {
|
||||
#define OPTION_LONG_FLAGS(fl, lc, mv, h) OPTION_FULL(fl, /* sc= */ 0, lc, mv, h)
|
||||
#define OPTION_SHORT(sc, mv, h) OPTION(sc, /* lc= */ NULL, mv, h)
|
||||
#define OPTION_SHORT_FLAGS(fl, sc, mv, h) OPTION_FULL(fl, sc, /* lc= */ NULL, mv, h)
|
||||
#define OPTION_POSITIONAL OPTION_FULL(OPTION_POSITIONAL_ENTRY, /* sc= */ 0, "(positional)", /* mv= */ NULL, /* h= */ NULL)
|
||||
#define OPTION_HELP_VERBATIM(lc, h) OPTION_FULL(OPTION_HELP_ENTRY_VERBATIM, /* sc= */ 0, lc, /* mv= */ NULL, h)
|
||||
|
||||
#define OPTION_COMMON_HELP \
|
||||
@@ -99,6 +101,10 @@ typedef enum OptionParserMode {
|
||||
/* Same as "+…" for getopt_long — only parse options before the first positional argument. */
|
||||
OPTION_PARSER_STOP_AT_FIRST_NONOPTION,
|
||||
|
||||
/* Same as "-…" for getopt_long — return positional arguments as "options" to be handled by the
|
||||
* option handler specified with OPTION_POSITIONAL. */
|
||||
OPTION_PARSER_RETURN_POSITIONAL_ARGS,
|
||||
|
||||
_OPTION_PARSER_MODE_MAX,
|
||||
} OptionParserMode;
|
||||
|
||||
|
||||
+85
-26
@@ -741,7 +741,8 @@ TEST(option_optional_arg) {
|
||||
static void test_macros_parse_one(
|
||||
char **argv,
|
||||
const Entry *entries,
|
||||
char **remaining) {
|
||||
char **remaining,
|
||||
OptionParserMode mode) {
|
||||
|
||||
_cleanup_free_ char *joined = strv_join(argv, ", ");
|
||||
log_debug("/* %s(%s) */", __func__, joined);
|
||||
@@ -755,7 +756,7 @@ static void test_macros_parse_one(
|
||||
for (const Entry *e = entries; e && (e->long_code || e->short_code != 0); e++)
|
||||
n_entries++;
|
||||
|
||||
OptionParser state = { argc, argv };
|
||||
OptionParser state = { argc, argv, mode };
|
||||
const Option *opt;
|
||||
const char *arg;
|
||||
|
||||
@@ -807,6 +808,9 @@ static void test_macros_parse_one(
|
||||
OPTION_LONG("debug", NULL, "Enable debug mode"):
|
||||
break;
|
||||
|
||||
OPTION_POSITIONAL:
|
||||
break;
|
||||
|
||||
default:
|
||||
log_error("Unexpected option id: %d", c);
|
||||
ASSERT_TRUE(false);
|
||||
@@ -828,7 +832,8 @@ TEST(option_macros) {
|
||||
{ "help" },
|
||||
{}
|
||||
},
|
||||
NULL);
|
||||
NULL,
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* OPTION: short form */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -837,7 +842,8 @@ TEST(option_macros) {
|
||||
{ "help" },
|
||||
{}
|
||||
},
|
||||
NULL);
|
||||
NULL,
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* OPTION_LONG: only accessible via long form */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -846,7 +852,8 @@ TEST(option_macros) {
|
||||
{ "version" },
|
||||
{}
|
||||
},
|
||||
NULL);
|
||||
NULL,
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* OPTION_SHORT: only accessible via short form */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -855,7 +862,8 @@ TEST(option_macros) {
|
||||
{ .short_code = 'v' },
|
||||
{}
|
||||
},
|
||||
NULL);
|
||||
NULL,
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* OPTION with required arg: long --required=ARG */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -864,7 +872,8 @@ TEST(option_macros) {
|
||||
{ "required", "val1" },
|
||||
{}
|
||||
},
|
||||
NULL);
|
||||
NULL,
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* OPTION with required arg: long --required ARG */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -873,7 +882,8 @@ TEST(option_macros) {
|
||||
{ "required", "val1" },
|
||||
{}
|
||||
},
|
||||
NULL);
|
||||
NULL,
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* OPTION with required arg: short -r ARG */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -882,7 +892,8 @@ TEST(option_macros) {
|
||||
{ "required", "val1" },
|
||||
{}
|
||||
},
|
||||
NULL);
|
||||
NULL,
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* OPTION with required arg: short -rARG */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -891,7 +902,8 @@ TEST(option_macros) {
|
||||
{ "required", "val1" },
|
||||
{}
|
||||
},
|
||||
NULL);
|
||||
NULL,
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* OPTION_FULL with OPTION_OPTIONAL_ARG: long with = */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -900,7 +912,8 @@ TEST(option_macros) {
|
||||
{ "optional", "val1" },
|
||||
{}
|
||||
},
|
||||
NULL);
|
||||
NULL,
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* OPTION_FULL with OPTION_OPTIONAL_ARG: long without = doesn't consume next */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -909,7 +922,8 @@ TEST(option_macros) {
|
||||
{ "optional", NULL },
|
||||
{}
|
||||
},
|
||||
STRV_MAKE("pos1"));
|
||||
STRV_MAKE("pos1"),
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* OPTION_FULL with OPTION_OPTIONAL_ARG: short inline */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -918,7 +932,8 @@ TEST(option_macros) {
|
||||
{ "optional", "val1" },
|
||||
{}
|
||||
},
|
||||
NULL);
|
||||
NULL,
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* OPTION_FULL with OPTION_OPTIONAL_ARG: short without inline */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -927,7 +942,8 @@ TEST(option_macros) {
|
||||
{ "optional", NULL },
|
||||
{}
|
||||
},
|
||||
STRV_MAKE("pos1"));
|
||||
STRV_MAKE("pos1"),
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* OPTION_FULL with OPTION_STOPS_PARSING: stops further option parsing */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -939,7 +955,8 @@ TEST(option_macros) {
|
||||
{}
|
||||
},
|
||||
STRV_MAKE("--help",
|
||||
"--version"));
|
||||
"--version"),
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* OPTION_STOPS_PARSING: options before are still parsed */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -953,7 +970,8 @@ TEST(option_macros) {
|
||||
{}
|
||||
},
|
||||
STRV_MAKE("-h",
|
||||
"--debug"));
|
||||
"--debug"),
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* OPTION_STOPS_PARSING with "--": "--" after exec is still consumed */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -964,7 +982,8 @@ TEST(option_macros) {
|
||||
{ "exec" },
|
||||
{}
|
||||
},
|
||||
STRV_MAKE("--help"));
|
||||
STRV_MAKE("--help"),
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* OPTION_STOPS_PARSING with "--": "--" before exec takes precedence */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -975,7 +994,8 @@ TEST(option_macros) {
|
||||
{}
|
||||
},
|
||||
STRV_MAKE("--exec",
|
||||
"--help"));
|
||||
"--help"),
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* OPTION_GROUP: group marker is transparent to parsing, --debug in Advanced group works */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -984,7 +1004,8 @@ TEST(option_macros) {
|
||||
{ "debug" },
|
||||
{}
|
||||
},
|
||||
NULL);
|
||||
NULL,
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* Mixed: all macro types together */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -1010,7 +1031,8 @@ TEST(option_macros) {
|
||||
{}
|
||||
},
|
||||
STRV_MAKE("pos1",
|
||||
"pos2"));
|
||||
"pos2"),
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* Short option combos with macros: -hv (help + verbose) */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -1020,7 +1042,8 @@ TEST(option_macros) {
|
||||
{ .short_code = 'v' },
|
||||
{}
|
||||
},
|
||||
NULL);
|
||||
NULL,
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* Short option combo with required arg: -hrval (help + required with arg "val") */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -1030,7 +1053,8 @@ TEST(option_macros) {
|
||||
{ "required", "val" },
|
||||
{}
|
||||
},
|
||||
NULL);
|
||||
NULL,
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* Short option combo with optional arg: -hoval (help + optional with arg "val") */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -1040,7 +1064,8 @@ TEST(option_macros) {
|
||||
{ "optional", "val" },
|
||||
{}
|
||||
},
|
||||
NULL);
|
||||
NULL,
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* OPTION_STOPS_PARSING then "--": "--" is still consumed after exec */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -1055,7 +1080,8 @@ TEST(option_macros) {
|
||||
{}
|
||||
},
|
||||
STRV_MAKE("--version",
|
||||
"-h"));
|
||||
"-h"),
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* OPTION_STOPS_PARSING then later "--": "--" is not consumed */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -1071,7 +1097,8 @@ TEST(option_macros) {
|
||||
},
|
||||
STRV_MAKE("--version",
|
||||
"--",
|
||||
"-h"));
|
||||
"-h"),
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* OPTION_STOPS_PARSING then "--" twice: second "--" is not consumed */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
@@ -1088,7 +1115,39 @@ TEST(option_macros) {
|
||||
},
|
||||
STRV_MAKE("--",
|
||||
"--version",
|
||||
"-h"));
|
||||
"-h"),
|
||||
OPTION_PARSER_NORMAL);
|
||||
|
||||
/* Basic OPTION_POSITIONAL use */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
"--help",
|
||||
"arg1",
|
||||
"--debug",
|
||||
"arg2"),
|
||||
(Entry[]) {
|
||||
{ "help" },
|
||||
{ "(positional)", "arg1" },
|
||||
{ "debug" },
|
||||
{ "(positional)", "arg2" },
|
||||
{}
|
||||
},
|
||||
NULL,
|
||||
OPTION_PARSER_RETURN_POSITIONAL_ARGS);
|
||||
|
||||
/* OPTION_POSITIONAL combined with OPTION_STOPS_PARSING */
|
||||
test_macros_parse_one(STRV_MAKE("arg0",
|
||||
"--help",
|
||||
"arg1",
|
||||
"--exec",
|
||||
"arg2"),
|
||||
(Entry[]) {
|
||||
{ "help" },
|
||||
{ "(positional)", "arg1" },
|
||||
{ "exec" },
|
||||
{}
|
||||
},
|
||||
STRV_MAKE("arg2"),
|
||||
OPTION_PARSER_RETURN_POSITIONAL_ARGS);
|
||||
}
|
||||
|
||||
/* Test the pattern used by nspawn's --user: an optional-arg option that also
|
||||
|
||||
Reference in New Issue
Block a user