3 #pragma mark Definitions
4 #define CTL_OUTPUT_WIDTH (80)
5 #define CTL_OUTPUT_OPTARG_PAD (28)
6 #define CTL_OUTPUT_LIST_PAD (4)
7 #define SUBCOMMAND_LINKER_SET "__subcommands"
9 #define OS_SUBCOMMAND_OPTIONS_FOREACH(_osco_i, _osc, _which, _i) \
10 while (((_osco_i) = &osc->osc_ ## _which[(_i)]) && \
12 !((_osco_i)->osco_flags & OS_SUBCOMMAND_OPTION_FLAG_TERMINATOR))
15 OS_ENUM(os_subcommand_option_spec_fmt
, uint64_t,
16 OS_SUBCOMMAND_OPTION_SPEC_SHORT
,
17 OS_SUBCOMMAND_OPTION_SPEC_LONG
,
18 OS_SUBCOMMAND_OPTION_SPEC_COMBINED
,
21 #pragma mark Forward Declarations
22 static void _print_header(
26 static const os_subcommand_t
*_os_subcommand_find(
28 static void _os_subcommand_print_usage(
29 const os_subcommand_t
*osc
,
31 static void _os_subcommand_print_help_line(
32 const os_subcommand_t
*osc
,
35 #pragma mark Module Globals
36 static const os_subcommand_t __help_cmd
;
37 static const os_subcommand_t
*_help_cmd
= &__help_cmd
;
39 static const os_subcommand_t __main_cmd
;
40 static const os_subcommand_t
*_main_cmd
= &__main_cmd
;
41 static const os_subcommand_t
*_internal_main_cmd
= &__main_cmd
;
43 #pragma mark Main Subcommand
44 static int _main_invoke(const os_subcommand_t
*osc
,
48 static const os_subcommand_option_t _main_positional
[] = {
50 .osco_version
= OS_SUBCOMMAND_OPTION_VERSION
,
53 .osco_argument_usage
= "subcommand",
54 .osco_argument_human
= "The subcommand to invoke",
56 OS_SUBCOMMAND_OPTION_TERMINATOR
,
59 static const os_subcommand_t __main_cmd
= {
60 .osc_version
= OS_SUBCOMMAND_VERSION
,
61 .osc_flags
= OS_SUBCOMMAND_FLAG_MAIN
,
63 .osc_desc
= "main command",
64 .osc_optstring
= NULL
,
68 .osc_positional
= _main_positional
,
69 .osc_invoke
= &_main_invoke
,
73 _main_invoke(const os_subcommand_t
*osc
, int argc
, const char *argv
[])
78 #pragma mark Help Subcommand
79 static int _help_invoke(const os_subcommand_t
*osc
,
83 static const os_subcommand_option_t _help_positional
[] = {
85 .osco_version
= OS_SUBCOMMAND_OPTION_VERSION
,
88 .osco_argument_usage
= "subcommand",
89 .osco_argument_human
= "The subcommand to query for help",
91 OS_SUBCOMMAND_OPTION_TERMINATOR
,
94 static const os_subcommand_t __help_cmd
= {
95 .osc_version
= OS_SUBCOMMAND_VERSION
,
98 .osc_desc
= "prints helpful information",
99 .osc_optstring
= NULL
,
101 .osc_required
= NULL
,
102 .osc_optional
= NULL
,
103 .osc_positional
= _help_positional
,
104 .osc_invoke
= &_help_invoke
,
108 _help_invoke(const os_subcommand_t
*osc
, int argc
, const char *argv
[])
111 const char *cmdname
= NULL
;
112 const os_subcommand_t
*target
= NULL
;
120 // Print usage information for the requested subcommand.
121 target
= _os_subcommand_find(argv
[1]);
123 os_subcommand_fprintf(osc
, stderr
, "unrecognized subcommand: %s",
130 // Print general, top-level usage followed by a list of subcommands.
139 _os_subcommand_print_usage(target
, f
);
141 if (target
== _main_cmd
) {
142 const os_subcommand_t
**oscip
= NULL
;
143 const os_subcommand_t
*osci
= NULL
;
144 bool header_printed
= false;
146 LINKER_SET_FOREACH(oscip
, const os_subcommand_t
**,
147 SUBCOMMAND_LINKER_SET
) {
150 _print_header(f
, "subcommands", &header_printed
);
151 _os_subcommand_print_help_line(osci
, f
);
154 // Print the help subcommand last.
155 _os_subcommand_print_help_line(osc
, f
);
161 #pragma mark Utilities
163 _print_header(FILE *f
, const char *hdr
, bool *already_done
)
170 crfprintf_np(f
, "%s:", hdr
);
171 *already_done
= true;
174 #pragma mark Module Routines
176 _os_subcommand_copy_option_spec_short(const os_subcommand_t
*osc
,
177 const os_subcommand_option_t
*osco
)
179 const struct option
*opt
= osco
->osco_option
;
180 char optbuff
[64] = "";
181 char argbuff
[64] = "";
186 snprintf(optbuff
, sizeof(optbuff
), "-%c", opt
->val
);
188 switch (opt
->has_arg
) {
191 case optional_argument
:
192 snprintf(argbuff
, sizeof(argbuff
), "[%s]",
193 osco
->osco_argument_usage
);
195 case required_argument
:
196 snprintf(argbuff
, sizeof(argbuff
), "<%s>",
197 osco
->osco_argument_usage
);
200 __builtin_unreachable();
203 snprintf(optbuff
, sizeof(optbuff
), "%s", osco
->osco_argument_usage
);
206 ret
= asprintf(&final
, "%s%s", optbuff
, argbuff
);
215 _os_subcommand_copy_option_spec_long(const os_subcommand_t
*osc
,
216 const os_subcommand_option_t
*osco
)
218 const struct option
*opt
= osco
->osco_option
;
219 char optbuff
[64] = "";
220 char argbuff
[64] = "";
225 snprintf(optbuff
, sizeof(optbuff
), "--%s", opt
->name
);
227 switch (opt
->has_arg
) {
230 case optional_argument
:
231 snprintf(argbuff
, sizeof(argbuff
), "[=%s]",
232 osco
->osco_argument_usage
);
234 case required_argument
:
235 snprintf(argbuff
, sizeof(argbuff
), "=<%s>",
236 osco
->osco_argument_usage
);
239 __builtin_unreachable();
242 snprintf(optbuff
, sizeof(optbuff
), "%s", osco
->osco_argument_usage
);
245 ret
= asprintf(&final
, "%s%s", optbuff
, argbuff
);
254 _os_subcommand_copy_option_spec(const os_subcommand_t
*osc
,
255 const os_subcommand_option_t
*osco
, os_subcommand_option_spec_fmt_t fmt
)
259 char *__os_free spec_old
= NULL
;
262 case OS_SUBCOMMAND_OPTION_SPEC_SHORT
:
263 _os_subcommand_copy_option_spec_short(osc
, osco
);
265 case OS_SUBCOMMAND_OPTION_SPEC_LONG
:
266 _os_subcommand_copy_option_spec_long(osc
, osco
);
268 case OS_SUBCOMMAND_OPTION_SPEC_COMBINED
:
269 spec
= _os_subcommand_copy_option_spec_long(osc
, osco
);
270 if (osco
->osco_option
) {
273 ret
= asprintf(&spec
, "%s | -%c", spec
, osco
->osco_option
->val
);
281 __builtin_unreachable();
288 _os_subcommand_copy_usage_line(const os_subcommand_t
*osc
)
290 char *usage_line
= NULL
;
292 const os_subcommand_option_t
*osco_i
= NULL
;
293 const char *optional_spec
= "";
294 char subcmd_name
[64];
297 // The usage line does not enumerate all possible optional options, just the
298 // required options. If there are optional options, then display that but
299 // otherwise leave them to be described by more extensive usage information.
300 if (osc
->osc_optional
) {
301 optional_spec
= " [options]";
304 if (osc
== _main_cmd
) {
305 strlcpy(subcmd_name
, "", sizeof(subcmd_name
));
307 snprintf(subcmd_name
, sizeof(subcmd_name
), " %s", osc
->osc_name
);
310 ret
= asprintf(&usage_line
, "%s%s%s",
311 getprogname(), subcmd_name
, optional_spec
);
317 OS_SUBCOMMAND_OPTIONS_FOREACH(osco_i
, osc
, required
, i
) {
318 char *__os_free usage_line_old
= NULL
;
319 char *__os_free osco_spec
= NULL
;
321 usage_line_old
= usage_line
;
323 osco_spec
= _os_subcommand_copy_option_spec_long(osc
, osco_i
);
324 ret
= asprintf(&usage_line
, "%s %s", usage_line
, osco_spec
);
331 OS_SUBCOMMAND_OPTIONS_FOREACH(osco_i
, osc
, positional
, i
) {
332 char *__os_free usage_line_old
= NULL
;
333 char *__os_free osco_spec
= NULL
;
334 const char *braces
[] = {
339 if (osco_i
->osco_flags
& OS_SUBCOMMAND_OPTION_FLAG_OPTIONAL_POS
) {
344 usage_line_old
= usage_line
;
346 osco_spec
= _os_subcommand_copy_option_spec_long(osc
, osco_i
);
347 ret
= asprintf(&usage_line
, "%s %s%s%s",
348 usage_line
, braces
[0], osco_spec
, braces
[1]);
354 if (osc
== _main_cmd
&& osc
!= _internal_main_cmd
) {
355 // Always include the positional subcommand when printing usage for the
356 // main subcommand. We do not expect it to be specified in a user-
357 // provided main subcommand.
358 char *__os_free usage_line_old
= NULL
;
359 char *__os_free osco_spec
= NULL
;
361 usage_line_old
= usage_line
;
363 osco_spec
= _os_subcommand_copy_option_spec_long(osc
,
364 &_main_positional
[0]);
365 ret
= asprintf(&usage_line
, "%s <%s>", usage_line
, osco_spec
);
375 _os_subcommand_print_option_usage(const os_subcommand_t
*osc
,
376 const os_subcommand_option_t
*osco
, FILE *f
)
378 char *__os_free opt_spec
= NULL
;
379 ssize_t initpad
= -CTL_OUTPUT_OPTARG_PAD
;
381 opt_spec
= _os_subcommand_copy_option_spec(osc
, osco
,
382 OS_SUBCOMMAND_OPTION_SPEC_COMBINED
);
383 fprintf(f
, " %-24s", opt_spec
);
385 // If the usage specifier is long, start the description on the next line.
386 if (strlen(opt_spec
) >= CTL_OUTPUT_OPTARG_PAD
) {
387 initpad
= CTL_OUTPUT_OPTARG_PAD
;
391 wfprintf_np(f
, initpad
, CTL_OUTPUT_OPTARG_PAD
,
392 CTL_OUTPUT_WIDTH
, "%s",
393 osco
->osco_argument_human
);
397 _os_subcommand_print_help_line(const os_subcommand_t
*osc
, FILE *f
)
399 ssize_t initpad
= -CTL_OUTPUT_OPTARG_PAD
;
401 fprintf(f
, " %-24s", osc
->osc_name
);
403 // If the usage specifier is long, start the description on the next line.
404 if (strlen(osc
->osc_name
) >= CTL_OUTPUT_OPTARG_PAD
) {
405 initpad
= CTL_OUTPUT_OPTARG_PAD
;
409 wfprintf_np(f
, initpad
, CTL_OUTPUT_OPTARG_PAD
,
410 CTL_OUTPUT_WIDTH
, "%s",
415 _os_subcommand_print_usage(const os_subcommand_t
*osc
, FILE *f
)
418 const os_subcommand_option_t
*osco_i
= NULL
;
419 char *__os_free usage_line
= NULL
;
420 bool header_printed
= false;
422 usage_line
= _os_subcommand_copy_usage_line(osc
);
423 wfprintf_np(f
, 0, 0, CTL_OUTPUT_WIDTH
, "usage: %s", usage_line
);
425 if (osc
->osc_required
|| osc
->osc_positional
|| osc
== _main_cmd
) {
427 OS_SUBCOMMAND_OPTIONS_FOREACH(osco_i
, osc
, required
, i
) {
428 _print_header(f
, "required", &header_printed
);
431 _os_subcommand_print_option_usage(osc
, osco_i
, f
);
435 OS_SUBCOMMAND_OPTIONS_FOREACH(osco_i
, osc
, positional
, i
) {
436 _print_header(f
, "required", &header_printed
);
438 if (osco_i
->osco_flags
& OS_SUBCOMMAND_OPTION_FLAG_OPTIONAL_POS
) {
443 _os_subcommand_print_option_usage(osc
, osco_i
, f
);
446 if (osc
== _main_cmd
&& osc
!= _internal_main_cmd
) {
447 // We do not expect the user's main command to specify that a
448 // subcommand must follow, so always defer to ours.
449 _print_header(f
, "required", &header_printed
);
452 _os_subcommand_print_option_usage(osc
, &_main_positional
[0], f
);
456 header_printed
= false;
458 if (osc
->osc_optional
|| osc
->osc_positional
) {
460 OS_SUBCOMMAND_OPTIONS_FOREACH(osco_i
, osc
, optional
, i
) {
461 _print_header(f
, "optional", &header_printed
);
464 _os_subcommand_print_option_usage(osc
, osco_i
, f
);
468 OS_SUBCOMMAND_OPTIONS_FOREACH(osco_i
, osc
, positional
, i
) {
469 if (osco_i
->osco_flags
& OS_SUBCOMMAND_OPTION_FLAG_OPTIONAL_POS
) {
470 _print_header(f
, "optional", &header_printed
);
473 _os_subcommand_print_option_usage(osc
, osco_i
, f
);
479 static const os_subcommand_t
*
480 _os_subcommand_find(const char *name
)
482 const os_subcommand_t
**oscip
= NULL
;
484 if (strcmp(_help_cmd
->osc_name
, name
) == 0) {
488 LINKER_SET_FOREACH(oscip
, const os_subcommand_t
**, SUBCOMMAND_LINKER_SET
) {
489 const os_subcommand_t
*osci
= *oscip
;
491 if (osci
->osc_flags
& OS_SUBCOMMAND_FLAG_MAIN
) {
492 // The main subcommand cannot be invoked directly.
496 if (strcmp(osci
->osc_name
, name
) == 0) {
506 os_subcommand_main(int argc
, const char *argv
[],
507 os_subcommand_main_flags_t flags
)
510 const char *cmdname
= NULL
;
511 const os_subcommand_t
*osc
= NULL
;
512 const os_subcommand_t
**oscip
= NULL
;
515 os_subcommand_fprintf(NULL
, stderr
, "please provide a subcommand");
520 // Find the main subcommand if any exists. Otherwise we'll just use our pre-
521 // canned main subcommand.
522 LINKER_SET_FOREACH(oscip
, const os_subcommand_t
**, SUBCOMMAND_LINKER_SET
) {
524 if (osc
->osc_flags
& OS_SUBCOMMAND_FLAG_MAIN
) {
532 // Invoke the main subcommand to snarf any global options. Our default
533 // implementation does nothing and just returns 0.
534 xit
= _main_cmd
->osc_invoke(_main_cmd
, argc
, argv
);
539 // Advance argument pointer and make the subcommand argv[0].
545 os_subcommand_fprintf(NULL
, stderr
, "please provide a subcommand");
550 osc
= _os_subcommand_find(cmdname
);
552 if (osc
->osc_flags
& OS_SUBCOMMAND_FLAG_REQUIRE_ROOT
) {
554 os_subcommand_fprintf(osc
, stderr
,
555 "subcommand requires root: %s",
562 if (osc
->osc_flags
& OS_SUBCOMMAND_FLAG_TTYONLY
) {
563 if (!isatty(STDOUT_FILENO
) || !isatty(STDIN_FILENO
)) {
564 os_subcommand_fprintf(osc
, stderr
,
565 "subcommand requires a tty: %s",
567 xit
= EX_UNAVAILABLE
;
572 xit
= osc
->osc_invoke(osc
, argc
, argv
);
574 os_subcommand_fprintf(NULL
, stderr
, "unknonwn subcommand: %s", cmdname
);
579 if (xit
== EX_USAGE
) {
584 _os_subcommand_print_usage(osc
, stderr
);
591 os_subcommand_fprintf(const os_subcommand_t
*osc
, FILE *f
,
592 const char *fmt
, ...)
597 vcrfprintf_np(f
, fmt
, ap
);
602 os_subcommand_vfprintf(const os_subcommand_t
*osc
, FILE *f
,
603 const char *fmt
, va_list ap
)
605 if (!osc
|| (osc
->osc_flags
& OS_SUBCOMMAND_FLAG_MAIN
)) {
606 fprintf(f
, "%s: ", getprogname());
608 fprintf(f
, "%s::%s: ", getprogname(), osc
->osc_name
);
611 vcrfprintf_np(f
, fmt
, ap
);