]> git.saurik.com Git - apple/libc.git/blobdiff - libdarwin/ctl.c
Libc-1439.100.3.tar.gz
[apple/libc.git] / libdarwin / ctl.c
index adcef271ba8fe823b0c2f9aac31209d0305417cc..28daa35ef84b661dc1d57bd598d177fac1e80166 100755 (executable)
@@ -2,8 +2,8 @@
 
 #pragma mark Definitions
 #define CTL_OUTPUT_WIDTH (80)
-#define CTL_OUTPUT_OPTARG_PAD (28)
-#define CTL_OUTPUT_LIST_PAD (4)
+#define CTL_OUTPUT_OPTARG_INDENT (32)
+#define CTL_OUTPUT_OPTARG_OVERFLOW (CTL_OUTPUT_OPTARG_INDENT - 4)
 #define SUBCOMMAND_LINKER_SET "__subcommands"
 
 #define OS_SUBCOMMAND_OPTIONS_FOREACH(_osco_i, _osc, _which, _i) \
@@ -31,6 +31,9 @@ static void _os_subcommand_print_usage(
 static void _os_subcommand_print_help_line(
                const os_subcommand_t *osc,
                FILE *f);
+static void _print_subcommand_list(
+               const os_subcommand_t *osc,
+               FILE *f);
 
 #pragma mark Module Globals
 static const os_subcommand_t __help_cmd;
@@ -40,6 +43,56 @@ static const os_subcommand_t __main_cmd;
 static const os_subcommand_t *_main_cmd = &__main_cmd;
 static const os_subcommand_t *_internal_main_cmd = &__main_cmd;
 
+static struct ttysize __ttys = {
+       .ts_lines = 24,
+       .ts_cols = CTL_OUTPUT_WIDTH,
+};
+
+static const struct ttysize *_ttys = &__ttys;
+
+#pragma mark Module Private
+static void
+_init_column_count(void)
+{
+       const char *columns_env = NULL;
+       char *end = NULL;
+       struct ttysize ttys = {
+               .ts_lines = 24,
+               .ts_cols = CTL_OUTPUT_WIDTH,
+       };
+       int ret = -1;
+
+       columns_env = getenv("COLUMNS");
+       if (columns_env) {
+               unsigned short cols = -1;
+
+               cols = strtoul(columns_env, &end, 0);
+               if (end != columns_env && end[0] != 0) {
+                       ttys.ts_lines = cols;
+               }
+       } else {
+               ret = ioctl(0, TIOCGSIZE, &ttys);
+               if (ret) {
+                       ttys.ts_lines = 24;
+                       ttys.ts_cols = CTL_OUTPUT_WIDTH;
+               }
+       }
+
+       __ttys = ttys;
+}
+
+static void
+_stoupper(char *str)
+{
+       size_t i = 0;
+       size_t len = strlen(str);
+
+       for (i = 0; i < len; i++) {
+               char *cp = &str[i];
+               *cp = ___toupper(*cp);
+       }
+}
+
 #pragma mark Main Subcommand
 static int _main_invoke(const os_subcommand_t *osc,
        int argc,
@@ -85,7 +138,7 @@ static const os_subcommand_option_t _help_positional[] = {
                .osco_version = OS_SUBCOMMAND_OPTION_VERSION,
                .osco_flags = 0,
                .osco_option = NULL,
-               .osco_argument_usage = "subcommand",
+               .osco_argument_usage = "SUBCOMMAND",
                .osco_argument_human = "The subcommand to query for help",
        },
        OS_SUBCOMMAND_OPTION_TERMINATOR,
@@ -116,19 +169,14 @@ _help_invoke(const os_subcommand_t *osc, int argc, const char *argv[])
                cmdname = argv[1];
        }
 
-       if (cmdname) {
-               // Print usage information for the requested subcommand.
-               target = _os_subcommand_find(argv[1]);
-               if (!target) {
-                       os_subcommand_fprintf(osc, stderr, "unrecognized subcommand: %s",
-                                       argv[1]);
-                       xit = EX_USAGE;
-               } else {
-                       xit = 0;
-               }
-       } else {
-               // Print general, top-level usage followed by a list of subcommands.
+       // Print usage information for the requested subcommand.
+       target = _os_subcommand_find(cmdname);
+       if (!target) {
+               // If it's a bogus subcommand, just print top-level usage.
+               fprintf(stderr, "unrecognized subcommand: %s\n", cmdname);
                target = _main_cmd;
+               xit = EX_UNAVAILABLE;
+       } else {
                xit = 0;
        }
 
@@ -139,20 +187,7 @@ _help_invoke(const os_subcommand_t *osc, int argc, const char *argv[])
        _os_subcommand_print_usage(target, f);
 
        if (target == _main_cmd) {
-               const os_subcommand_t **oscip = NULL;
-               const os_subcommand_t *osci = NULL;
-               bool header_printed = false;
-
-               LINKER_SET_FOREACH(oscip, const os_subcommand_t **,
-                               SUBCOMMAND_LINKER_SET) {
-                       osci = *oscip;
-
-                       _print_header(f, "subcommands", &header_printed);
-                       _os_subcommand_print_help_line(osci, f);
-               }
-
-               // Print the help subcommand last.
-               _os_subcommand_print_help_line(osc, f);
+               _print_subcommand_list(_help_cmd, f);
        }
 
        return xit;
@@ -162,13 +197,17 @@ _help_invoke(const os_subcommand_t *osc, int argc, const char *argv[])
 static void
 _print_header(FILE *f, const char *hdr, bool *already_done)
 {
-       if (*already_done) {
+       if (already_done && *already_done) {
                return;
        }
 
        crfprintf_np(f, "");
        crfprintf_np(f, "%s:", hdr);
-       *already_done = true;
+       crfprintf_np(f, "");
+
+       if (already_done) {
+               *already_done = true;
+       }
 }
 
 #pragma mark Module Routines
@@ -199,8 +238,15 @@ _os_subcommand_copy_option_spec_short(const os_subcommand_t *osc,
                default:
                        __builtin_unreachable();
                }
+
+               if (!(osco->osco_flags & OS_SUBCOMMAND_OPTION_FLAG_ENUM)) {
+                       _stoupper(argbuff);
+               }
        } else {
                snprintf(optbuff, sizeof(optbuff), "%s", osco->osco_argument_usage);
+               if (!(osco->osco_flags & OS_SUBCOMMAND_OPTION_FLAG_ENUM)) {
+                       _stoupper(optbuff);
+               }
        }
 
        ret = asprintf(&final, "%s%s", optbuff, argbuff);
@@ -238,8 +284,15 @@ _os_subcommand_copy_option_spec_long(const os_subcommand_t *osc,
                default:
                        __builtin_unreachable();
                }
+
+               if (!(osco->osco_flags & OS_SUBCOMMAND_OPTION_FLAG_ENUM)) {
+                       _stoupper(argbuff);
+               }
        } else {
                snprintf(optbuff, sizeof(optbuff), "%s", osco->osco_argument_usage);
+               if (!(osco->osco_flags & OS_SUBCOMMAND_OPTION_FLAG_ENUM)) {
+                       _stoupper(optbuff);
+               }
        }
 
        ret = asprintf(&final, "%s%s", optbuff, argbuff);
@@ -270,7 +323,7 @@ _os_subcommand_copy_option_spec(const os_subcommand_t *osc,
                if (osco->osco_option) {
                        spec_old = spec;
 
-                       ret = asprintf(&spec, "%s | -%c", spec, osco->osco_option->val);
+                       ret = asprintf(&spec, "-%c | %s", osco->osco_option->val, spec);
                        if (ret < 0) {
                                os_assert_zero(ret);
                        }
@@ -355,13 +408,13 @@ _os_subcommand_copy_usage_line(const os_subcommand_t *osc)
                // Always include the positional subcommand when printing usage for the
                // main subcommand. We do not expect it to be specified in a user-
                // provided main subcommand.
+               const os_subcommand_option_t *subopt = &_main_positional[0];
                char *__os_free usage_line_old = NULL;
                char *__os_free osco_spec = NULL;
 
                usage_line_old = usage_line;
 
-               osco_spec = _os_subcommand_copy_option_spec_long(osc,
-                               &_main_positional[0]);
+               osco_spec = _os_subcommand_copy_option_spec_long(osc, subopt);
                ret = asprintf(&usage_line, "%s <%s>", usage_line, osco_spec);
                if (ret < 0) {
                        os_assert_zero(ret);
@@ -376,38 +429,36 @@ _os_subcommand_print_option_usage(const os_subcommand_t *osc,
                const os_subcommand_option_t *osco, FILE *f)
 {
        char *__os_free opt_spec = NULL;
-       ssize_t initpad = -CTL_OUTPUT_OPTARG_PAD;
+       ssize_t initpad = -CTL_OUTPUT_OPTARG_INDENT;
 
        opt_spec = _os_subcommand_copy_option_spec(osc, osco,
                        OS_SUBCOMMAND_OPTION_SPEC_COMBINED);
-       fprintf(f, "    %-24s", opt_spec);
+       fprintf(f, "    %-24s    ", opt_spec);
 
        // If the usage specifier is long, start the description on the next line.
-       if (strlen(opt_spec) >= CTL_OUTPUT_OPTARG_PAD) {
-               initpad = CTL_OUTPUT_OPTARG_PAD;
+       if (strlen(opt_spec) >= CTL_OUTPUT_OPTARG_OVERFLOW) {
+               initpad = CTL_OUTPUT_OPTARG_INDENT;
                crfprintf_np(f, "");
        }
 
-       wfprintf_np(f, initpad, CTL_OUTPUT_OPTARG_PAD,
-                       CTL_OUTPUT_WIDTH, "%s",
+       wfprintf_np(f, initpad, CTL_OUTPUT_OPTARG_INDENT, _ttys->ts_cols, "%s",
                        osco->osco_argument_human);
 }
 
 static void
 _os_subcommand_print_help_line(const os_subcommand_t *osc, FILE *f)
 {
-       ssize_t initpad = -CTL_OUTPUT_OPTARG_PAD;
+       ssize_t initpad = -CTL_OUTPUT_OPTARG_INDENT;
 
-       fprintf(f, "    %-24s", osc->osc_name);
+       fprintf(f, "    %-24s    ", osc->osc_name);
 
        // If the usage specifier is long, start the description on the next line.
-       if (strlen(osc->osc_name) >= CTL_OUTPUT_OPTARG_PAD) {
-               initpad = CTL_OUTPUT_OPTARG_PAD;
+       if (strlen(osc->osc_name) >= CTL_OUTPUT_OPTARG_OVERFLOW) {
+               initpad = CTL_OUTPUT_OPTARG_INDENT;
                crfprintf_np(f, "");
        }
 
-       wfprintf_np(f, initpad, CTL_OUTPUT_OPTARG_PAD,
-                       CTL_OUTPUT_WIDTH, "%s",
+       wfprintf_np(f, initpad, CTL_OUTPUT_OPTARG_INDENT, _ttys->ts_cols, "%s",
                        osc->osc_desc);
 }
 
@@ -420,35 +471,44 @@ _os_subcommand_print_usage(const os_subcommand_t *osc, FILE *f)
        bool header_printed = false;
 
        usage_line = _os_subcommand_copy_usage_line(osc);
-       wfprintf_np(f, 0, 0, CTL_OUTPUT_WIDTH, "usage: %s", usage_line);
+
+       wfprintf_np(f, 0, 4, _ttys->ts_cols, "USAGE:");
+       crfprintf_np(f, "");
+       wfprintf_np(f, 4, 4, _ttys->ts_cols, "%s", usage_line);
+
+       if (osc->osc_long_desc) {
+               // The long description gets printed in its own paragraph.
+               _print_header(f, "DESCRIPTION", NULL);
+               wfprintf_np(f, 4, 4, _ttys->ts_cols, "%s", osc->osc_long_desc);
+       } else if (osc->osc_desc) {
+               // The short description gets printed on the same line.
+               crfprintf_np(f, "");
+               wfprintf_np(f, 0, 4, _ttys->ts_cols, "DESCRIPTION: %s",
+                               osc->osc_desc);
+       }
 
        if (osc->osc_required || osc->osc_positional || osc == _main_cmd) {
                i = 0;
                OS_SUBCOMMAND_OPTIONS_FOREACH(osco_i, osc, required, i) {
-                       _print_header(f, "required", &header_printed);
-
-                       crfprintf_np(f, "");
+                       _print_header(f, "REQUIRED", &header_printed);
                        _os_subcommand_print_option_usage(osc, osco_i, f);
                }
 
                i = 0;
                OS_SUBCOMMAND_OPTIONS_FOREACH(osco_i, osc, positional, i) {
-                       _print_header(f, "required", &header_printed);
+                       _print_header(f, "REQUIRED", &header_printed);
 
                        if (osco_i->osco_flags & OS_SUBCOMMAND_OPTION_FLAG_OPTIONAL_POS) {
                                continue;
                        }
 
-                       crfprintf_np(f, "");
                        _os_subcommand_print_option_usage(osc, osco_i, f);
                }
 
                if (osc == _main_cmd && osc != _internal_main_cmd) {
                        // We do not expect the user's main command to specify that a
                        // subcommand must follow, so always defer to ours.
-                       _print_header(f, "required", &header_printed);
-
-                       crfprintf_np(f, "");
+                       _print_header(f, "REQUIRED", &header_printed);
                        _os_subcommand_print_option_usage(osc, &_main_positional[0], f);
                }
        }
@@ -458,18 +518,14 @@ _os_subcommand_print_usage(const os_subcommand_t *osc, FILE *f)
        if (osc->osc_optional || osc->osc_positional) {
                i = 0;
                OS_SUBCOMMAND_OPTIONS_FOREACH(osco_i, osc, optional, i) {
-                       _print_header(f, "optional", &header_printed);
-
-                       crfprintf_np(f, "");
+                       _print_header(f, "OPTIONAL", &header_printed);
                        _os_subcommand_print_option_usage(osc, osco_i, f);
                }
 
                i = 0;
                OS_SUBCOMMAND_OPTIONS_FOREACH(osco_i, osc, positional, i) {
                        if (osco_i->osco_flags & OS_SUBCOMMAND_OPTION_FLAG_OPTIONAL_POS) {
-                               _print_header(f, "optional", &header_printed);
-
-                               crfprintf_np(f, "");
+                               _print_header(f, "OPTIONAL", &header_printed);
                                _os_subcommand_print_option_usage(osc, osco_i, f);
                        }
                }
@@ -481,6 +537,10 @@ _os_subcommand_find(const char *name)
 {
        const os_subcommand_t **oscip = NULL;
 
+       if (!name) {
+               return _main_cmd;
+       }
+
        if (strcmp(_help_cmd->osc_name, name) == 0) {
                return &__help_cmd;
        }
@@ -501,6 +561,59 @@ _os_subcommand_find(const char *name)
        return NULL;
 }
 
+static int
+_os_subcommand_be_helpful(const os_subcommand_t *osc,
+               int argc, const char *argv[])
+{
+       int res = 0;
+
+       if (osc->osc_flags & OS_SUBCOMMAND_FLAG_HELPFUL) {
+               if (argc == 1) {
+                       _os_subcommand_print_usage(osc, stdout);
+                       res = 1;
+                       goto __out;
+               }
+       }
+
+       if (osc->osc_flags & OS_SUBCOMMAND_FLAG_HELPFUL_FIRST_OPTION) {
+               if (argc == 2 && (strcmp(argv[1], "help") == 0 ||
+                               strcmp(argv[1], "-h") == 0 ||
+                               strcmp(argv[1], "-help") == 0 ||
+                               strcmp(argv[1], "--help") == 0)) {
+                       _os_subcommand_print_usage(osc, stdout);
+                       res = 1;
+                       goto __out;
+               }
+       }
+
+__out:
+       return res;
+}
+
+static void
+_print_subcommand_list(const os_subcommand_t *osc, FILE *f)
+{
+       const os_subcommand_t **oscip = NULL;
+       bool header_printed = false;
+
+       LINKER_SET_FOREACH(oscip, const os_subcommand_t **,
+                       SUBCOMMAND_LINKER_SET) {
+               const os_subcommand_t *osci = *oscip;
+
+               _print_header(f, "SUBCOMMANDS", &header_printed);
+
+               if ((osci->osc_flags & OS_SUBCOMMAND_FLAG_MAIN) ||
+                               (osci->osc_flags & OS_SUBCOMMAND_FLAG_HIDDEN)) {
+                       continue;
+               }
+
+               _os_subcommand_print_help_line(osci, f);
+       }
+
+       // Print the help subcommand last.
+       _os_subcommand_print_help_line(osc, f);
+}
+
 #pragma mark API
 int
 os_subcommand_main(int argc, const char *argv[],
@@ -511,11 +624,7 @@ os_subcommand_main(int argc, const char *argv[],
        const os_subcommand_t *osc = NULL;
        const os_subcommand_t **oscip = NULL;
 
-       if (argc < 2) {
-               os_subcommand_fprintf(NULL, stderr, "please provide a subcommand");
-               xit = EX_USAGE;
-               goto __out;
-       }
+       _init_column_count();
 
        // Find the main subcommand if any exists. Otherwise we'll just use our pre-
        // canned main subcommand.
@@ -529,6 +638,13 @@ os_subcommand_main(int argc, const char *argv[],
 
        osc = NULL;
 
+       // See if we just need to print help for the main command.
+       if (_os_subcommand_be_helpful(_main_cmd, argc, argv)) {
+               _print_subcommand_list(_help_cmd, stdout);
+               xit = 0;
+               goto __out;
+       }
+
        // Invoke the main subcommand to snarf any global options. Our default
        // implementation does nothing and just returns 0.
        xit = _main_cmd->osc_invoke(_main_cmd, argc, argv);
@@ -569,19 +685,26 @@ os_subcommand_main(int argc, const char *argv[],
                        }
                }
 
+               if (_os_subcommand_be_helpful(osc, argc, argv)) {
+                       xit = 0;
+                       goto __out;
+               }
+
                xit = osc->osc_invoke(osc, argc, argv);
        } else {
-               os_subcommand_fprintf(NULL, stderr, "unknonwn subcommand: %s", cmdname);
+               os_subcommand_fprintf(NULL, stderr, "unknown subcommand: %s", cmdname);
                xit = EX_USAGE;
        }
 
 __out:
        if (xit == EX_USAGE) {
                if (!osc) {
-                       osc = _main_cmd;
+                       // If we couldn't find the subcommand, then print the list of known
+                       // subcommands.
+                       _print_subcommand_list(_help_cmd, stderr);
+               } else {
+                       _os_subcommand_print_usage(osc, stderr);
                }
-
-               _os_subcommand_print_usage(osc, stderr);
        }
 
        return xit;
@@ -605,7 +728,7 @@ os_subcommand_vfprintf(const os_subcommand_t *osc, FILE *f,
        if (!osc || (osc->osc_flags & OS_SUBCOMMAND_FLAG_MAIN)) {
                fprintf(f, "%s: ", getprogname());
        } else {
-               fprintf(f, "%s::%s: ", getprogname(), osc->osc_name);
+               fprintf(f, "%s-%s: ", getprogname(), osc->osc_name);
        }
 
        vcrfprintf_np(f, fmt, ap);