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);
}
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;
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 (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 (state->argv[state->optind][1] == '-') {
+ 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) {
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",
}
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++);
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 {
#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 \
/* 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;
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);
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;
OPTION_LONG("debug", NULL, "Enable debug mode"):
break;
+ OPTION_POSITIONAL:
+ break;
+
default:
log_error("Unexpected option id: %d", c);
ASSERT_TRUE(false);
{ "help" },
{}
},
- NULL);
+ NULL,
+ OPTION_PARSER_NORMAL);
/* OPTION: short form */
test_macros_parse_one(STRV_MAKE("arg0",
{ "help" },
{}
},
- NULL);
+ NULL,
+ OPTION_PARSER_NORMAL);
/* OPTION_LONG: only accessible via long form */
test_macros_parse_one(STRV_MAKE("arg0",
{ "version" },
{}
},
- NULL);
+ NULL,
+ OPTION_PARSER_NORMAL);
/* OPTION_SHORT: only accessible via short form */
test_macros_parse_one(STRV_MAKE("arg0",
{ .short_code = 'v' },
{}
},
- NULL);
+ NULL,
+ OPTION_PARSER_NORMAL);
/* OPTION with required arg: long --required=ARG */
test_macros_parse_one(STRV_MAKE("arg0",
{ "required", "val1" },
{}
},
- NULL);
+ NULL,
+ OPTION_PARSER_NORMAL);
/* OPTION with required arg: long --required ARG */
test_macros_parse_one(STRV_MAKE("arg0",
{ "required", "val1" },
{}
},
- NULL);
+ NULL,
+ OPTION_PARSER_NORMAL);
/* OPTION with required arg: short -r ARG */
test_macros_parse_one(STRV_MAKE("arg0",
{ "required", "val1" },
{}
},
- NULL);
+ NULL,
+ OPTION_PARSER_NORMAL);
/* OPTION with required arg: short -rARG */
test_macros_parse_one(STRV_MAKE("arg0",
{ "required", "val1" },
{}
},
- NULL);
+ NULL,
+ OPTION_PARSER_NORMAL);
/* OPTION_FULL with OPTION_OPTIONAL_ARG: long with = */
test_macros_parse_one(STRV_MAKE("arg0",
{ "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",
{ "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",
{ "optional", "val1" },
{}
},
- NULL);
+ NULL,
+ OPTION_PARSER_NORMAL);
/* OPTION_FULL with OPTION_OPTIONAL_ARG: short without inline */
test_macros_parse_one(STRV_MAKE("arg0",
{ "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",
{}
},
STRV_MAKE("--help",
- "--version"));
+ "--version"),
+ OPTION_PARSER_NORMAL);
/* OPTION_STOPS_PARSING: options before are still parsed */
test_macros_parse_one(STRV_MAKE("arg0",
{}
},
STRV_MAKE("-h",
- "--debug"));
+ "--debug"),
+ OPTION_PARSER_NORMAL);
/* OPTION_STOPS_PARSING with "--": "--" after exec is still consumed */
test_macros_parse_one(STRV_MAKE("arg0",
{ "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",
{}
},
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",
{ "debug" },
{}
},
- NULL);
+ NULL,
+ OPTION_PARSER_NORMAL);
/* Mixed: all macro types together */
test_macros_parse_one(STRV_MAKE("arg0",
{}
},
STRV_MAKE("pos1",
- "pos2"));
+ "pos2"),
+ OPTION_PARSER_NORMAL);
/* Short option combos with macros: -hv (help + verbose) */
test_macros_parse_one(STRV_MAKE("arg0",
{ .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",
{ "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",
{ "optional", "val" },
{}
},
- NULL);
+ NULL,
+ OPTION_PARSER_NORMAL);
/* OPTION_STOPS_PARSING then "--": "--" is still consumed after exec */
test_macros_parse_one(STRV_MAKE("arg0",
{}
},
STRV_MAKE("--version",
- "-h"));
+ "-h"),
+ OPTION_PARSER_NORMAL);
/* OPTION_STOPS_PARSING then later "--": "--" is not consumed */
test_macros_parse_one(STRV_MAKE("arg0",
},
STRV_MAKE("--version",
"--",
- "-h"));
+ "-h"),
+ OPTION_PARSER_NORMAL);
/* OPTION_STOPS_PARSING then "--" twice: second "--" is not consumed */
test_macros_parse_one(STRV_MAKE("arg0",
},
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