3 #pragma mark Definitions
4 #define CTL_OUTPUT_WIDTH (80)
5 #define CTL_OUTPUT_OPTARG_INDENT (32)
6 #define CTL_OUTPUT_OPTARG_OVERFLOW (CTL_OUTPUT_OPTARG_INDENT - 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
,
34 static void _print_subcommand_list(
35 const os_subcommand_t
*osc
,
38 #pragma mark Module Globals
39 static const os_subcommand_t __help_cmd
;
40 static const os_subcommand_t
*_help_cmd
= &__help_cmd
;
42 static const os_subcommand_t __main_cmd
;
43 static const os_subcommand_t
*_main_cmd
= &__main_cmd
;
44 static const os_subcommand_t
*_internal_main_cmd
= &__main_cmd
;
46 static struct ttysize __ttys
= {
48 .ts_cols
= CTL_OUTPUT_WIDTH
,
51 static const struct ttysize
*_ttys
= &__ttys
;
53 #pragma mark Module Private
55 _init_column_count(void)
57 const char *columns_env
= NULL
;
59 struct ttysize ttys
= {
61 .ts_cols
= CTL_OUTPUT_WIDTH
,
65 columns_env
= getenv("COLUMNS");
67 unsigned short cols
= -1;
69 cols
= strtoul(columns_env
, &end
, 0);
70 if (end
!= columns_env
&& end
[0] != 0) {
74 ret
= ioctl(0, TIOCGSIZE
, &ttys
);
77 ttys
.ts_cols
= CTL_OUTPUT_WIDTH
;
88 size_t len
= strlen(str
);
90 for (i
= 0; i
< len
; i
++) {
92 *cp
= ___toupper(*cp
);
96 #pragma mark Main Subcommand
97 static int _main_invoke(const os_subcommand_t
*osc
,
101 static const os_subcommand_option_t _main_positional
[] = {
103 .osco_version
= OS_SUBCOMMAND_OPTION_VERSION
,
106 .osco_argument_usage
= "subcommand",
107 .osco_argument_human
= "The subcommand to invoke",
109 OS_SUBCOMMAND_OPTION_TERMINATOR
,
112 static const os_subcommand_t __main_cmd
= {
113 .osc_version
= OS_SUBCOMMAND_VERSION
,
114 .osc_flags
= OS_SUBCOMMAND_FLAG_MAIN
,
116 .osc_desc
= "main command",
117 .osc_optstring
= NULL
,
119 .osc_required
= NULL
,
120 .osc_optional
= NULL
,
121 .osc_positional
= _main_positional
,
122 .osc_invoke
= &_main_invoke
,
126 _main_invoke(const os_subcommand_t
*osc
, int argc
, const char *argv
[])
131 #pragma mark Help Subcommand
132 static int _help_invoke(const os_subcommand_t
*osc
,
136 static const os_subcommand_option_t _help_positional
[] = {
138 .osco_version
= OS_SUBCOMMAND_OPTION_VERSION
,
141 .osco_argument_usage
= "SUBCOMMAND",
142 .osco_argument_human
= "The subcommand to query for help",
144 OS_SUBCOMMAND_OPTION_TERMINATOR
,
147 static const os_subcommand_t __help_cmd
= {
148 .osc_version
= OS_SUBCOMMAND_VERSION
,
151 .osc_desc
= "prints helpful information",
152 .osc_optstring
= NULL
,
154 .osc_required
= NULL
,
155 .osc_optional
= NULL
,
156 .osc_positional
= _help_positional
,
157 .osc_invoke
= &_help_invoke
,
161 _help_invoke(const os_subcommand_t
*osc
, int argc
, const char *argv
[])
164 const char *cmdname
= NULL
;
165 const os_subcommand_t
*target
= NULL
;
172 // Print usage information for the requested subcommand.
173 target
= _os_subcommand_find(cmdname
);
175 // If it's a bogus subcommand, just print top-level usage.
176 fprintf(stderr
, "unrecognized subcommand: %s\n", cmdname
);
178 xit
= EX_UNAVAILABLE
;
187 _os_subcommand_print_usage(target
, f
);
189 if (target
== _main_cmd
) {
190 _print_subcommand_list(_help_cmd
, f
);
196 #pragma mark Utilities
198 _print_header(FILE *f
, const char *hdr
, bool *already_done
)
200 if (already_done
&& *already_done
) {
205 crfprintf_np(f
, "%s:", hdr
);
209 *already_done
= true;
213 #pragma mark Module Routines
215 _os_subcommand_copy_option_spec_short(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
), "-%c", opt
->val
);
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 if (!(osco
->osco_flags
& OS_SUBCOMMAND_OPTION_FLAG_ENUM
)) {
246 snprintf(optbuff
, sizeof(optbuff
), "%s", osco
->osco_argument_usage
);
247 if (!(osco
->osco_flags
& OS_SUBCOMMAND_OPTION_FLAG_ENUM
)) {
252 ret
= asprintf(&final
, "%s%s", optbuff
, argbuff
);
261 _os_subcommand_copy_option_spec_long(const os_subcommand_t
*osc
,
262 const os_subcommand_option_t
*osco
)
264 const struct option
*opt
= osco
->osco_option
;
265 char optbuff
[64] = "";
266 char argbuff
[64] = "";
271 snprintf(optbuff
, sizeof(optbuff
), "--%s", opt
->name
);
273 switch (opt
->has_arg
) {
276 case optional_argument
:
277 snprintf(argbuff
, sizeof(argbuff
), "[=%s]",
278 osco
->osco_argument_usage
);
280 case required_argument
:
281 snprintf(argbuff
, sizeof(argbuff
), "=<%s>",
282 osco
->osco_argument_usage
);
285 __builtin_unreachable();
288 if (!(osco
->osco_flags
& OS_SUBCOMMAND_OPTION_FLAG_ENUM
)) {
292 snprintf(optbuff
, sizeof(optbuff
), "%s", osco
->osco_argument_usage
);
293 if (!(osco
->osco_flags
& OS_SUBCOMMAND_OPTION_FLAG_ENUM
)) {
298 ret
= asprintf(&final
, "%s%s", optbuff
, argbuff
);
307 _os_subcommand_copy_option_spec(const os_subcommand_t
*osc
,
308 const os_subcommand_option_t
*osco
, os_subcommand_option_spec_fmt_t fmt
)
312 char *__os_free spec_old
= NULL
;
315 case OS_SUBCOMMAND_OPTION_SPEC_SHORT
:
316 _os_subcommand_copy_option_spec_short(osc
, osco
);
318 case OS_SUBCOMMAND_OPTION_SPEC_LONG
:
319 _os_subcommand_copy_option_spec_long(osc
, osco
);
321 case OS_SUBCOMMAND_OPTION_SPEC_COMBINED
:
322 spec
= _os_subcommand_copy_option_spec_long(osc
, osco
);
323 if (osco
->osco_option
) {
326 ret
= asprintf(&spec
, "-%c | %s", osco
->osco_option
->val
, spec
);
334 __builtin_unreachable();
341 _os_subcommand_copy_usage_line(const os_subcommand_t
*osc
)
343 char *usage_line
= NULL
;
345 const os_subcommand_option_t
*osco_i
= NULL
;
346 const char *optional_spec
= "";
347 char subcmd_name
[64];
350 // The usage line does not enumerate all possible optional options, just the
351 // required options. If there are optional options, then display that but
352 // otherwise leave them to be described by more extensive usage information.
353 if (osc
->osc_optional
) {
354 optional_spec
= " [options]";
357 if (osc
== _main_cmd
) {
358 strlcpy(subcmd_name
, "", sizeof(subcmd_name
));
360 snprintf(subcmd_name
, sizeof(subcmd_name
), " %s", osc
->osc_name
);
363 ret
= asprintf(&usage_line
, "%s%s%s",
364 getprogname(), subcmd_name
, optional_spec
);
370 OS_SUBCOMMAND_OPTIONS_FOREACH(osco_i
, osc
, required
, i
) {
371 char *__os_free usage_line_old
= NULL
;
372 char *__os_free osco_spec
= NULL
;
374 usage_line_old
= usage_line
;
376 osco_spec
= _os_subcommand_copy_option_spec_long(osc
, osco_i
);
377 ret
= asprintf(&usage_line
, "%s %s", usage_line
, osco_spec
);
384 OS_SUBCOMMAND_OPTIONS_FOREACH(osco_i
, osc
, positional
, i
) {
385 char *__os_free usage_line_old
= NULL
;
386 char *__os_free osco_spec
= NULL
;
387 const char *braces
[] = {
392 if (osco_i
->osco_flags
& OS_SUBCOMMAND_OPTION_FLAG_OPTIONAL_POS
) {
397 usage_line_old
= usage_line
;
399 osco_spec
= _os_subcommand_copy_option_spec_long(osc
, osco_i
);
400 ret
= asprintf(&usage_line
, "%s %s%s%s",
401 usage_line
, braces
[0], osco_spec
, braces
[1]);
407 if (osc
== _main_cmd
&& osc
!= _internal_main_cmd
) {
408 // Always include the positional subcommand when printing usage for the
409 // main subcommand. We do not expect it to be specified in a user-
410 // provided main subcommand.
411 const os_subcommand_option_t
*subopt
= &_main_positional
[0];
412 char *__os_free usage_line_old
= NULL
;
413 char *__os_free osco_spec
= NULL
;
415 usage_line_old
= usage_line
;
417 osco_spec
= _os_subcommand_copy_option_spec_long(osc
, subopt
);
418 ret
= asprintf(&usage_line
, "%s <%s>", usage_line
, osco_spec
);
428 _os_subcommand_print_option_usage(const os_subcommand_t
*osc
,
429 const os_subcommand_option_t
*osco
, FILE *f
)
431 char *__os_free opt_spec
= NULL
;
432 ssize_t initpad
= -CTL_OUTPUT_OPTARG_INDENT
;
434 opt_spec
= _os_subcommand_copy_option_spec(osc
, osco
,
435 OS_SUBCOMMAND_OPTION_SPEC_COMBINED
);
436 fprintf(f
, " %-24s ", opt_spec
);
438 // If the usage specifier is long, start the description on the next line.
439 if (strlen(opt_spec
) >= CTL_OUTPUT_OPTARG_OVERFLOW
) {
440 initpad
= CTL_OUTPUT_OPTARG_INDENT
;
444 wfprintf_np(f
, initpad
, CTL_OUTPUT_OPTARG_INDENT
, _ttys
->ts_cols
, "%s",
445 osco
->osco_argument_human
);
449 _os_subcommand_print_help_line(const os_subcommand_t
*osc
, FILE *f
)
451 ssize_t initpad
= -CTL_OUTPUT_OPTARG_INDENT
;
453 fprintf(f
, " %-24s ", osc
->osc_name
);
455 // If the usage specifier is long, start the description on the next line.
456 if (strlen(osc
->osc_name
) >= CTL_OUTPUT_OPTARG_OVERFLOW
) {
457 initpad
= CTL_OUTPUT_OPTARG_INDENT
;
461 wfprintf_np(f
, initpad
, CTL_OUTPUT_OPTARG_INDENT
, _ttys
->ts_cols
, "%s",
466 _os_subcommand_print_usage(const os_subcommand_t
*osc
, FILE *f
)
469 const os_subcommand_option_t
*osco_i
= NULL
;
470 char *__os_free usage_line
= NULL
;
471 bool header_printed
= false;
473 usage_line
= _os_subcommand_copy_usage_line(osc
);
475 wfprintf_np(f
, 0, 4, _ttys
->ts_cols
, "USAGE:");
477 wfprintf_np(f
, 4, 4, _ttys
->ts_cols
, "%s", usage_line
);
479 if (osc
->osc_long_desc
) {
480 // The long description gets printed in its own paragraph.
481 _print_header(f
, "DESCRIPTION", NULL
);
482 wfprintf_np(f
, 4, 4, _ttys
->ts_cols
, "%s", osc
->osc_long_desc
);
483 } else if (osc
->osc_desc
) {
484 // The short description gets printed on the same line.
486 wfprintf_np(f
, 0, 4, _ttys
->ts_cols
, "DESCRIPTION: %s",
490 if (osc
->osc_required
|| osc
->osc_positional
|| osc
== _main_cmd
) {
492 OS_SUBCOMMAND_OPTIONS_FOREACH(osco_i
, osc
, required
, i
) {
493 _print_header(f
, "REQUIRED", &header_printed
);
494 _os_subcommand_print_option_usage(osc
, osco_i
, f
);
498 OS_SUBCOMMAND_OPTIONS_FOREACH(osco_i
, osc
, positional
, i
) {
499 _print_header(f
, "REQUIRED", &header_printed
);
501 if (osco_i
->osco_flags
& OS_SUBCOMMAND_OPTION_FLAG_OPTIONAL_POS
) {
505 _os_subcommand_print_option_usage(osc
, osco_i
, f
);
508 if (osc
== _main_cmd
&& osc
!= _internal_main_cmd
) {
509 // We do not expect the user's main command to specify that a
510 // subcommand must follow, so always defer to ours.
511 _print_header(f
, "REQUIRED", &header_printed
);
512 _os_subcommand_print_option_usage(osc
, &_main_positional
[0], f
);
516 header_printed
= false;
518 if (osc
->osc_optional
|| osc
->osc_positional
) {
520 OS_SUBCOMMAND_OPTIONS_FOREACH(osco_i
, osc
, optional
, i
) {
521 _print_header(f
, "OPTIONAL", &header_printed
);
522 _os_subcommand_print_option_usage(osc
, osco_i
, f
);
526 OS_SUBCOMMAND_OPTIONS_FOREACH(osco_i
, osc
, positional
, i
) {
527 if (osco_i
->osco_flags
& OS_SUBCOMMAND_OPTION_FLAG_OPTIONAL_POS
) {
528 _print_header(f
, "OPTIONAL", &header_printed
);
529 _os_subcommand_print_option_usage(osc
, osco_i
, f
);
535 static const os_subcommand_t
*
536 _os_subcommand_find(const char *name
)
538 const os_subcommand_t
**oscip
= NULL
;
544 if (strcmp(_help_cmd
->osc_name
, name
) == 0) {
548 LINKER_SET_FOREACH(oscip
, const os_subcommand_t
**, SUBCOMMAND_LINKER_SET
) {
549 const os_subcommand_t
*osci
= *oscip
;
551 if (osci
->osc_flags
& OS_SUBCOMMAND_FLAG_MAIN
) {
552 // The main subcommand cannot be invoked directly.
556 if (strcmp(osci
->osc_name
, name
) == 0) {
565 _os_subcommand_be_helpful(const os_subcommand_t
*osc
,
566 int argc
, const char *argv
[])
570 if (osc
->osc_flags
& OS_SUBCOMMAND_FLAG_HELPFUL
) {
572 _os_subcommand_print_usage(osc
, stdout
);
578 if (osc
->osc_flags
& OS_SUBCOMMAND_FLAG_HELPFUL_FIRST_OPTION
) {
579 if (argc
== 2 && (strcmp(argv
[1], "help") == 0 ||
580 strcmp(argv
[1], "-h") == 0 ||
581 strcmp(argv
[1], "-help") == 0 ||
582 strcmp(argv
[1], "--help") == 0)) {
583 _os_subcommand_print_usage(osc
, stdout
);
594 _print_subcommand_list(const os_subcommand_t
*osc
, FILE *f
)
596 const os_subcommand_t
**oscip
= NULL
;
597 bool header_printed
= false;
599 LINKER_SET_FOREACH(oscip
, const os_subcommand_t
**,
600 SUBCOMMAND_LINKER_SET
) {
601 const os_subcommand_t
*osci
= *oscip
;
603 _print_header(f
, "SUBCOMMANDS", &header_printed
);
605 if ((osci
->osc_flags
& OS_SUBCOMMAND_FLAG_MAIN
) ||
606 (osci
->osc_flags
& OS_SUBCOMMAND_FLAG_HIDDEN
)) {
610 _os_subcommand_print_help_line(osci
, f
);
613 // Print the help subcommand last.
614 _os_subcommand_print_help_line(osc
, f
);
619 os_subcommand_main(int argc
, const char *argv
[],
620 os_subcommand_main_flags_t flags
)
623 const char *cmdname
= NULL
;
624 const os_subcommand_t
*osc
= NULL
;
625 const os_subcommand_t
**oscip
= NULL
;
627 _init_column_count();
629 // Find the main subcommand if any exists. Otherwise we'll just use our pre-
630 // canned main subcommand.
631 LINKER_SET_FOREACH(oscip
, const os_subcommand_t
**, SUBCOMMAND_LINKER_SET
) {
633 if (osc
->osc_flags
& OS_SUBCOMMAND_FLAG_MAIN
) {
641 // See if we just need to print help for the main command.
642 if (_os_subcommand_be_helpful(_main_cmd
, argc
, argv
)) {
643 _print_subcommand_list(_help_cmd
, stdout
);
648 // Invoke the main subcommand to snarf any global options. Our default
649 // implementation does nothing and just returns 0.
650 xit
= _main_cmd
->osc_invoke(_main_cmd
, argc
, argv
);
655 // Advance argument pointer and make the subcommand argv[0].
661 os_subcommand_fprintf(NULL
, stderr
, "please provide a subcommand");
666 osc
= _os_subcommand_find(cmdname
);
668 if (osc
->osc_flags
& OS_SUBCOMMAND_FLAG_REQUIRE_ROOT
) {
670 os_subcommand_fprintf(osc
, stderr
,
671 "subcommand requires root: %s",
678 if (osc
->osc_flags
& OS_SUBCOMMAND_FLAG_TTYONLY
) {
679 if (!isatty(STDOUT_FILENO
) || !isatty(STDIN_FILENO
)) {
680 os_subcommand_fprintf(osc
, stderr
,
681 "subcommand requires a tty: %s",
683 xit
= EX_UNAVAILABLE
;
688 if (_os_subcommand_be_helpful(osc
, argc
, argv
)) {
693 xit
= osc
->osc_invoke(osc
, argc
, argv
);
695 os_subcommand_fprintf(NULL
, stderr
, "unknown subcommand: %s", cmdname
);
700 if (xit
== EX_USAGE
) {
702 // If we couldn't find the subcommand, then print the list of known
704 _print_subcommand_list(_help_cmd
, stderr
);
706 _os_subcommand_print_usage(osc
, stderr
);
714 os_subcommand_fprintf(const os_subcommand_t
*osc
, FILE *f
,
715 const char *fmt
, ...)
720 vcrfprintf_np(f
, fmt
, ap
);
725 os_subcommand_vfprintf(const os_subcommand_t
*osc
, FILE *f
,
726 const char *fmt
, va_list ap
)
728 if (!osc
|| (osc
->osc_flags
& OS_SUBCOMMAND_FLAG_MAIN
)) {
729 fprintf(f
, "%s: ", getprogname());
731 fprintf(f
, "%s-%s: ", getprogname(), osc
->osc_name
);
734 vcrfprintf_np(f
, fmt
, ap
);