shared/verbs: introduce verb groups
This mirrors the idea and implementation for options. Previously, the Verb struct was named as verb_func_data_nn, but with the group marker entry, we don't have 'verb_func', so let's just call the item verb_data_nn. Using the verb here is a complication that is not needed.
This commit is contained in:
@@ -143,7 +143,6 @@
|
||||
|
||||
#define XCONCATENATE(x, y) x ## y
|
||||
#define CONCATENATE(x, y) XCONCATENATE(x, y)
|
||||
#define CONCATENATE3(x, y, z) CONCATENATE(x, CONCATENATE(y, z))
|
||||
|
||||
#define assert_cc(expr) _Static_assert(expr, #expr)
|
||||
|
||||
|
||||
@@ -266,7 +266,7 @@ int _option_parser_get_help_table(
|
||||
|
||||
bool in_group = group == NULL; /* Are we currently in the section on the array that forms
|
||||
* group <group>? The first part is the default group, so
|
||||
* the group was not specified, we are in. */
|
||||
* if the group was not specified, we are in. */
|
||||
|
||||
for (const Option *opt = options; opt < options_end; opt++) {
|
||||
bool group_marker = FLAGS_SET(opt->flags, OPTION_GROUP_MARKER);
|
||||
|
||||
+33
-4
@@ -58,12 +58,21 @@ bool should_bypass(const char *env_prefix) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool verb_is_metadata(const Verb *verb) {
|
||||
/* A metadata entry that is not a real verb, like the group marker */
|
||||
return FLAGS_SET(ASSERT_PTR(verb)->flags, VERB_GROUP_MARKER);
|
||||
}
|
||||
|
||||
const Verb* verbs_find_verb(const char *name, const Verb verbs[], const Verb verbs_end[]) {
|
||||
assert(verbs);
|
||||
|
||||
for (const Verb *verb = verbs; verb < verbs_end; verb++)
|
||||
for (const Verb *verb = verbs; verb < verbs_end; verb++) {
|
||||
if (verb_is_metadata(verb))
|
||||
continue;
|
||||
|
||||
if (name ? streq(name, verb->verb) : FLAGS_SET(verb->flags, VERB_DEFAULT))
|
||||
return verb;
|
||||
}
|
||||
|
||||
/* At the end of the list? */
|
||||
return NULL;
|
||||
@@ -85,6 +94,9 @@ int _dispatch_verb_with_args(char **args, const Verb verbs[], const Verb verbs_e
|
||||
_cleanup_strv_free_ char **verb_strv = NULL;
|
||||
|
||||
for (verb = verbs; verb < verbs_end; verb++) {
|
||||
if (verb_is_metadata(verb))
|
||||
continue;
|
||||
|
||||
r = strv_extend(&verb_strv, verb->verb);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
@@ -142,13 +154,17 @@ int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) {
|
||||
assert(argc >= optind);
|
||||
|
||||
size_t n = 0;
|
||||
while (verbs[n].dispatch)
|
||||
while (verbs[n].verb)
|
||||
n++;
|
||||
|
||||
return _dispatch_verb_with_args(strv_skip(argv, optind), verbs, verbs + n, userdata);
|
||||
}
|
||||
|
||||
int _verbs_get_help_table(const Verb verbs[], const Verb verbs_end[], Table **ret) {
|
||||
int _verbs_get_help_table(
|
||||
const Verb verbs[],
|
||||
const Verb verbs_end[],
|
||||
const char *group,
|
||||
Table **ret) {
|
||||
int r;
|
||||
|
||||
assert(ret);
|
||||
@@ -157,10 +173,23 @@ int _verbs_get_help_table(const Verb verbs[], const Verb verbs_end[], Table **re
|
||||
if (!table)
|
||||
return log_oom();
|
||||
|
||||
bool in_group = group == NULL; /* Are we currently in the section on the array that forms
|
||||
* group <group>? The first part is the default group, so
|
||||
* if the group was not specified, we are in. */
|
||||
|
||||
for (const Verb *verb = verbs; verb < verbs_end; verb++) {
|
||||
assert(verb->dispatch);
|
||||
assert(verb->verb);
|
||||
|
||||
bool group_marker = FLAGS_SET(verb->flags, VERB_GROUP_MARKER);
|
||||
if (!in_group) {
|
||||
in_group = group_marker && streq(group, verb->verb);
|
||||
continue;
|
||||
}
|
||||
if (group_marker)
|
||||
break; /* End of group */
|
||||
|
||||
if (!verb->help)
|
||||
/* No help string — we do not show the verb */
|
||||
continue;
|
||||
|
||||
/* We indent the option string by two spaces. We could set the minimum cell width and
|
||||
|
||||
+23
-8
@@ -8,6 +8,7 @@
|
||||
typedef enum VerbFlags {
|
||||
VERB_DEFAULT = 1 << 0, /* The verb to run if no verb is specified */
|
||||
VERB_ONLINE_ONLY = 1 << 1, /* Just do nothing when running in chroot or offline */
|
||||
VERB_GROUP_MARKER = 1 << 2, /* Fake verb entry to separate groups */
|
||||
} VerbFlags;
|
||||
|
||||
typedef struct {
|
||||
@@ -20,16 +21,13 @@ typedef struct {
|
||||
const char *help;
|
||||
} Verb;
|
||||
|
||||
#define VERB_FULL(d, v, a, amin, amax, f, dat, h) \
|
||||
DISABLE_WARNING_REDUNDANT_DECLS \
|
||||
static int d(int, char**, uintptr_t, void*); \
|
||||
REENABLE_WARNING \
|
||||
#define _VERB_DATA(d, v, a, amin, amax, f, dat, h) \
|
||||
_section_("SYSTEMD_VERBS") \
|
||||
_alignptr_ \
|
||||
_used_ \
|
||||
_retain_ \
|
||||
_variable_no_sanitize_address_ \
|
||||
static const Verb CONCATENATE3(d, _data_, __COUNTER__) = { \
|
||||
static const Verb CONCATENATE(verb_data_, __COUNTER__) = { \
|
||||
.verb = v, \
|
||||
.min_args = amin, \
|
||||
.max_args = amax, \
|
||||
@@ -40,6 +38,12 @@ typedef struct {
|
||||
.help = h, \
|
||||
}
|
||||
|
||||
#define VERB_FULL(d, v, a, amin, amax, f, dat, h) \
|
||||
DISABLE_WARNING_REDUNDANT_DECLS \
|
||||
static int d(int, char**, uintptr_t, void*); \
|
||||
REENABLE_WARNING \
|
||||
_VERB_DATA(d, v, a, amin, amax, f, dat, h)
|
||||
|
||||
/* The same as VERB_FULL, but without the data argument */
|
||||
#define VERB(d, v, a, amin, amax, f, h) \
|
||||
VERB_FULL(d, v, a, amin, amax, f, /* dat= */ 0, h)
|
||||
@@ -48,6 +52,11 @@ typedef struct {
|
||||
#define VERB_NOARG(d, v, h) \
|
||||
VERB(d, v, /* a= */ NULL, /* amin= */ VERB_ANY, /* amax= */ 1, /* f= */ 0, h)
|
||||
|
||||
/* Magic entry in the table (which will not be returned) that designates the start of the group <gr>. */
|
||||
#define VERB_GROUP(gr) \
|
||||
_VERB_DATA(/* d= */ NULL, /* v= */ gr, /* a= */ NULL, /* amin= */ 0, /* amax= */ 0, \
|
||||
/* f= */ VERB_GROUP_MARKER, /* dat= */ 0, /* h= */ NULL)
|
||||
|
||||
/* This is magically mapped to the beginning and end of the section */
|
||||
extern const Verb __start_SYSTEMD_VERBS[];
|
||||
extern const Verb __stop_SYSTEMD_VERBS[];
|
||||
@@ -64,9 +73,15 @@ int _dispatch_verb_with_args(char **args, const Verb verbs[], const Verb verbs_e
|
||||
|
||||
int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata);
|
||||
|
||||
int _verbs_get_help_table(const Verb verbs[], const Verb verbs_end[], Table **ret);
|
||||
#define verbs_get_help_table(ret) \
|
||||
_verbs_get_help_table(ALIGN_PTR(__start_SYSTEMD_VERBS), __stop_SYSTEMD_VERBS, ret)
|
||||
int _verbs_get_help_table(
|
||||
const Verb verbs[],
|
||||
const Verb verbs_end[],
|
||||
const char *group,
|
||||
Table **ret);
|
||||
#define verbs_get_help_table_group(group, ret) \
|
||||
_verbs_get_help_table(ALIGN_PTR(__start_SYSTEMD_VERBS), __stop_SYSTEMD_VERBS, group, ret)
|
||||
#define verbs_get_help_table(ret) \
|
||||
verbs_get_help_table_group(/* group= */ NULL, ret)
|
||||
|
||||
#define _VERB_COMMON_HELP_IMPL(impl) \
|
||||
static int verb_help(int argc, char **argv, uintptr_t data, void *userdata) { \
|
||||
|
||||
+26
-16
@@ -16,14 +16,16 @@ static int noop_dispatcher(int argc, char *argv[], uintptr_t _data, void *userda
|
||||
|
||||
TEST(verbs) {
|
||||
static const Verb verbs[] = {
|
||||
{ "help", VERB_ANY, VERB_ANY, 0, noop_dispatcher },
|
||||
{ "list-images", VERB_ANY, 1, 0, noop_dispatcher },
|
||||
{ "list", VERB_ANY, 2, VERB_DEFAULT, noop_dispatcher },
|
||||
{ "status", 2, VERB_ANY, 0, noop_dispatcher },
|
||||
{ "show", VERB_ANY, VERB_ANY, 0, noop_dispatcher },
|
||||
{ "terminate", 2, VERB_ANY, 0, noop_dispatcher },
|
||||
{ "login", 2, 2, 0, noop_dispatcher },
|
||||
{ "copy-to", 3, 4, 0, noop_dispatcher },
|
||||
{ "help", VERB_ANY, VERB_ANY, 0, noop_dispatcher },
|
||||
{ "list-images", VERB_ANY, 1, 0, noop_dispatcher },
|
||||
{ "list", VERB_ANY, 2, VERB_DEFAULT, noop_dispatcher },
|
||||
{ "status", 2, VERB_ANY, 0, noop_dispatcher },
|
||||
{ "Group2", VERB_ANY, VERB_ANY, VERB_GROUP_MARKER, NULL },
|
||||
{ "show", VERB_ANY, VERB_ANY, 0, noop_dispatcher },
|
||||
{ "terminate", 2, VERB_ANY, 0, noop_dispatcher },
|
||||
{ "Group3", 0, 0, VERB_GROUP_MARKER, NULL },
|
||||
{ "login", 2, 2, 0, noop_dispatcher },
|
||||
{ "copy-to", 3, 4, 0, noop_dispatcher },
|
||||
{}
|
||||
};
|
||||
|
||||
@@ -44,6 +46,12 @@ TEST(verbs) {
|
||||
|
||||
/* no verb, but a default is set */
|
||||
test_dispatch_one(STRV_EMPTY, verbs, 0);
|
||||
|
||||
/* the group entry shall not be found */
|
||||
test_dispatch_one(STRV_MAKE("Group2"), verbs, -EINVAL);
|
||||
|
||||
/* the group entry shall not be found */
|
||||
test_dispatch_one(STRV_MAKE("Group3"), verbs, -EINVAL);
|
||||
}
|
||||
|
||||
TEST(verbs_no_default) {
|
||||
@@ -60,14 +68,15 @@ TEST(verbs_no_default) {
|
||||
|
||||
TEST(verbs_no_default_many) {
|
||||
static const Verb verbs[] = {
|
||||
{ "help", VERB_ANY, VERB_ANY, 0, noop_dispatcher },
|
||||
{ "list-images", VERB_ANY, 1, 0, noop_dispatcher },
|
||||
{ "list", VERB_ANY, 2, 0, noop_dispatcher },
|
||||
{ "status", 2, VERB_ANY, 0, noop_dispatcher },
|
||||
{ "show", VERB_ANY, VERB_ANY, 0, noop_dispatcher },
|
||||
{ "terminate", 2, VERB_ANY, 0, noop_dispatcher },
|
||||
{ "login", 2, 2, 0, noop_dispatcher },
|
||||
{ "copy-to", 3, 4, 0, noop_dispatcher },
|
||||
{ "help", VERB_ANY, VERB_ANY, 0, noop_dispatcher },
|
||||
{ "list-images", VERB_ANY, 1, 0, noop_dispatcher },
|
||||
{ "list", VERB_ANY, 2, 0, noop_dispatcher },
|
||||
{ "status", 2, VERB_ANY, 0, noop_dispatcher },
|
||||
{ "Specials", VERB_ANY, VERB_ANY, VERB_GROUP_MARKER, NULL },
|
||||
{ "show", VERB_ANY, VERB_ANY, 0, noop_dispatcher },
|
||||
{ "terminate", 2, VERB_ANY, 0, noop_dispatcher },
|
||||
{ "login", 2, 2, 0, noop_dispatcher },
|
||||
{ "copy-to", 3, 4, 0, noop_dispatcher },
|
||||
{}
|
||||
};
|
||||
|
||||
@@ -75,6 +84,7 @@ TEST(verbs_no_default_many) {
|
||||
test_dispatch_one(STRV_MAKE("hel"), verbs, -EINVAL);
|
||||
test_dispatch_one(STRV_MAKE("helpp"), verbs, -EINVAL);
|
||||
test_dispatch_one(STRV_MAKE("hgrejgoraoiosafso"), verbs, -EINVAL);
|
||||
test_dispatch_one(STRV_MAKE("Specials"), verbs, -EINVAL);
|
||||
}
|
||||
|
||||
DEFINE_TEST_MAIN(LOG_INFO);
|
||||
|
||||
Reference in New Issue
Block a user