Regen charmonizer for CLI changes.
diff --git a/compiler/common/charmonizer.c b/compiler/common/charmonizer.c
index 7b3e900..f346714 100644
--- a/compiler/common/charmonizer.c
+++ b/compiler/common/charmonizer.c
@@ -179,6 +179,97 @@
 
 /***************************************************************************/
 
+#line 21 "src/Charmonizer/Core/CLI.h"
+#ifndef H_CHAZ_CLI
+#define H_CHAZ_CLI 1
+
+#define CHAZ_CLI_NO_ARG       0
+#define CHAZ_CLI_ARG_REQUIRED (1 << 0)
+#define CHAZ_CLI_ARG_OPTIONAL (1 << 1)
+
+/* The CLI module provides argument parsing for a command line interface.
+ */
+
+typedef struct chaz_CLI chaz_CLI;
+
+/* Constructor.
+ *
+ * @param name The name of the application.
+ * @param description A description of the application.
+ */
+chaz_CLI*
+chaz_CLI_new(const char *name, const char *description);
+
+/* Destructor.
+ */
+void
+chaz_CLI_destroy(chaz_CLI *self);
+
+/* Return a string combining usage header with documentation of options.
+ */
+const char*
+chaz_CLI_help(chaz_CLI *self);
+
+/* Override the generated usage header.
+ */
+void
+chaz_CLI_set_usage(chaz_CLI *self, const char *usage);
+
+/* Register an option.  Updates the "help" string, invalidating previous
+ * values.  Returns true on success, or reports an error and returns false if
+ * the option was already registered.
+ */
+int
+chaz_CLI_register(chaz_CLI *self, const char *name, const char *help,
+                  int flags);
+
+/* Set an option.  The specified option must have been registered previously.
+ * The supplied `value` is optional and will be copied.
+ *
+ * Returns true on success.  Reports an error and returns false on failure.
+ */
+int
+chaz_CLI_set(chaz_CLI *self, const char *name, const char *value);
+
+/* Returns true if the option has been set, false otherwise.
+ */
+int
+chaz_CLI_defined(chaz_CLI *self, const char *name);
+
+/* Return the value of a given option converted to a long int.  Defaults to 0.
+ * Reports an error if the named option has not been registered.
+ */
+long
+chaz_CLI_longval(chaz_CLI *self, const char *name);
+
+/* Return the value of an option as a C string.  Defaults to NULL.  Reports an
+ * error if the named option has not been registered.
+ */
+const char*
+chaz_CLI_strval(chaz_CLI *self, const char *name);
+
+/* Unset an option, making subsequent calls to `get` return false and making
+ * it possible to call `set` again.
+ *
+ * Returns true if the option exists and was able to be unset.
+ */
+int
+chaz_CLI_unset(chaz_CLI *self, const char *name);
+
+/* Parse `argc` and `argv`, setting options as appropriate.  Returns true on
+ * success.  Reports an error and returns false if either an unexpected option
+ * was encountered or an option which requires an argument was supplied
+ * without one.
+ */
+int
+chaz_CLI_parse(chaz_CLI *self, int argc, const char *argv[]);
+
+#endif /* H_CHAZ_CLI */
+
+
+
+/***************************************************************************/
+
 #line 21 "src/Charmonizer/Core/Compiler.h"
 /* Charmonizer/Core/Compiler.h
  */
@@ -924,20 +1015,7 @@
 #include <stddef.h>
 #include <stdio.h>
 
-#define CHAZ_PROBE_MAX_CC_LEN 100
-#define CHAZ_PROBE_MAX_CFLAGS_LEN 2000
-
-struct chaz_CLIArgs {
-    char cc[CHAZ_PROBE_MAX_CC_LEN + 1];
-    char cflags[CHAZ_PROBE_MAX_CFLAGS_LEN + 1];
-    int  charmony_h;
-    int  charmony_pm;
-    int  charmony_py;
-    int  charmony_rb;
-    int  verbosity;
-    int  write_makefile;
-    int  code_coverage;
-};
+struct chaz_CLI;
 
 /* Parse command line arguments, initializing and filling in the supplied
  * `args` struct.
@@ -954,7 +1032,7 @@
  */
 int
 chaz_Probe_parse_cli_args(int argc, const char *argv[],
-                          struct chaz_CLIArgs *args);
+                          struct chaz_CLI *cli);
 
 /* Exit after printing usage instructions to stderr.
  */
@@ -971,7 +1049,7 @@
  *      2 - debugging
  */
 void
-chaz_Probe_init(struct chaz_CLIArgs *args);
+chaz_Probe_init(struct chaz_CLI *cli);
 
 /* Clean up the Charmonizer environment -- deleting tempfiles, etc.  This
  * should be called only after everything else finishes.
@@ -2099,6 +2177,351 @@
 
 /***************************************************************************/
 
+#line 17 "src/Charmonizer/Core/CLI.c"
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
+/* #include "Charmonizer/Core/CLI.h" */
+/* #include "Charmonizer/Core/Util.h" */
+
+typedef struct chaz_CLIOption {
+    char *name;
+    char *help;
+    char *value;
+    int   defined;
+    int   flags;
+} chaz_CLIOption;
+
+struct chaz_CLI {
+    char *name;
+    char *desc;
+    char *usage;
+    char *help;
+    chaz_CLIOption *opts;
+    int   num_opts;
+};
+
+static void
+S_chaz_CLI_error(chaz_CLI *self, const char *pattern, ...) {
+    va_list ap;
+    if (chaz_Util_verbosity > 0) {
+        va_start(ap, pattern);
+        vfprintf(stderr, pattern, ap);
+        va_end(ap);
+        fprintf(stderr, "\n");
+    }
+}
+
+static void
+S_chaz_CLI_rebuild_help(chaz_CLI *self) {
+    int i;
+    size_t amount = 200; // Length of section headers.
+
+    // Allocate space.
+    if (self->usage) {
+        amount += strlen(self->usage);
+    }
+    else {
+        amount += strlen(self->name);
+    }
+    if (self->desc) {
+        amount += strlen(self->desc);
+    }
+    for (i = 0; i < self->num_opts; i++) {
+        chaz_CLIOption *opt = &self->opts[i];
+        amount += 24 + 2 * strlen(opt->name);
+        if (opt->flags) {
+            amount += strlen(opt->name);
+        }
+        if (opt->help) {
+            amount += strlen(opt->help);
+        }
+    }
+    free(self->help);
+    self->help = (char*)malloc(amount);
+    self->help[0] = '\0';
+
+    // Accumulate "help" string.
+    if (self->usage) {
+        strcat(self->help, self->usage);
+    }
+    else {
+        strcat(self->help, "Usage: ");
+        strcat(self->help, self->name);
+        if (self->num_opts) {
+            strcat(self->help, " [OPTIONS]");
+        }
+    }
+    if (self->desc) {
+        strcat(self->help, "\n\n");
+        strcat(self->help, self->desc);
+    }
+    strcat(self->help, "\n");
+    if (self->num_opts) {
+        strcat(self->help, "\nOptional arguments:\n");
+        for (i = 0; i < self->num_opts; i++) {
+            chaz_CLIOption *opt = &self->opts[i];
+            size_t line_start = strlen(self->help);
+            size_t current_len;
+
+            strcat(self->help, "  --");
+            strcat(self->help, opt->name);
+            current_len = strlen(self->help);
+            if (opt->flags) {
+                int j;
+                if (opt->flags & CHAZ_CLI_ARG_OPTIONAL) {
+                    self->help[current_len++] = '[';
+                }
+                self->help[current_len++] = '=';
+                for (j = 0; opt->name[j]; j++) {
+                    self->help[current_len++] = toupper(opt->name[j]);
+                }
+                if (opt->flags & CHAZ_CLI_ARG_OPTIONAL) {
+                    strcat(self->help, "]");
+                }
+                self->help[current_len] = '\0';
+            }
+            if (opt->help) {
+                self->help[current_len++] = ' ';
+                while (current_len - line_start < 25) {
+                    self->help[current_len++] = ' ';
+                }
+                self->help[current_len] = '\0';
+                strcpy(self->help + current_len, opt->help);
+            }
+            strcat(self->help, "\n");
+        }
+    }
+    strcat(self->help, "\n");
+}
+
+chaz_CLI*
+chaz_CLI_new(const char *name, const char *description) {
+    chaz_CLI *self  = calloc(1, sizeof(chaz_CLI));
+    self->name      = chaz_Util_strdup(name ? name : "PROGRAM");
+    self->desc      = description ? chaz_Util_strdup(description) : NULL;
+    self->help      = NULL;
+    self->opts      = NULL;
+    self->num_opts  = 0;
+    S_chaz_CLI_rebuild_help(self);
+    return self;
+}
+
+void
+chaz_CLI_destroy(chaz_CLI *self) {
+    int i;
+    for (i = 0; i < self->num_opts; i++) {
+        chaz_CLIOption *opt = &self->opts[i];
+        free(opt->name);
+        free(opt->help);
+        free(opt->value);
+    }
+    free(self->name);
+    free(self->desc);
+    free(self->opts);
+    free(self->usage);
+    free(self->help);
+}
+
+void
+chaz_CLI_set_usage(chaz_CLI *self, const char *usage) {
+    free(self->usage);
+    self->usage = chaz_Util_strdup(usage);
+}
+
+const char*
+chaz_CLI_help(chaz_CLI *self) {
+    return self->help;
+}
+
+int
+chaz_CLI_register(chaz_CLI *self, const char *name, const char *help,
+                  int flags) {
+    int rank;
+    int i;
+    int arg_required = !!(flags & CHAZ_CLI_ARG_REQUIRED);
+    int arg_optional = !!(flags & CHAZ_CLI_ARG_OPTIONAL);
+
+    // Validate flags
+    if (arg_required && arg_optional) {
+        S_chaz_CLI_error(self, "Conflicting flags: value both optional "
+                         "and required");
+        return 0;
+    }
+
+    // Insert new option.  Keep options sorted by name.
+    for (rank = self->num_opts; rank > 0; rank--) {
+        int comparison = strcmp(name, self->opts[rank - 1].name);
+        if (comparison == 0) {
+            S_chaz_CLI_error(self, "Option '%s' already registered", name);
+            return 0;
+        }
+        else if (comparison > 0) {
+            break;
+        }
+    }
+    self->num_opts += 1;
+    self->opts = realloc(self->opts, self->num_opts * sizeof(chaz_CLIOption));
+    for (i = self->num_opts - 1; i > rank; i--) {
+        self->opts[i] = self->opts[i - 1];
+    }
+    self->opts[rank].name    = chaz_Util_strdup(name);
+    self->opts[rank].help    = help ? chaz_Util_strdup(help) : NULL;
+    self->opts[rank].flags   = flags;
+    self->opts[rank].defined = 0;
+    self->opts[rank].value   = NULL;
+
+    // Update `help` with new option.
+    S_chaz_CLI_rebuild_help(self);
+
+    return 1;
+}
+
+int
+chaz_CLI_set(chaz_CLI *self, const char *name, const char *value) {
+    int i;
+    for (i = 0; i < self->num_opts; i++) {
+        chaz_CLIOption *opt = &self->opts[i];
+        if (strcmp(opt->name, name) == 0) {
+            if (opt->defined) {
+                S_chaz_CLI_error(self, "'%s' specified multiple times", name);
+                return 0;
+            }
+            opt->defined = 1;
+            if (value != NULL) {
+                opt->value = chaz_Util_strdup(value);
+            }
+            return 1;
+        }
+    }
+    S_chaz_CLI_error(self, "Attempt to set unknown option: '%s'", name);
+    return 0;
+}
+
+int
+chaz_CLI_unset(chaz_CLI *self, const char *name) {
+    int i;
+    for (i = 0; i < self->num_opts; i++) {
+        chaz_CLIOption *opt = &self->opts[i];
+        if (strcmp(opt->name, name) == 0) {
+            free(opt->value);
+            opt->value = NULL;
+            opt->defined = 0;
+            return 1;
+        }
+    }
+    S_chaz_CLI_error(self, "Attempt to unset unknown option: '%s'", name);
+    return 0;
+}
+
+int
+chaz_CLI_defined(chaz_CLI *self, const char *name) {
+    int i;
+    for (i = 0; i < self->num_opts; i++) {
+        chaz_CLIOption *opt = &self->opts[i];
+        if (strcmp(opt->name, name) == 0) {
+            return opt->defined;
+        }
+    }
+    S_chaz_CLI_error(self, "Inquiry for unknown option: '%s'", name);
+    return 0;
+}
+
+long
+chaz_CLI_longval(chaz_CLI *self, const char *name) {
+    int i;
+    for (i = 0; i < self->num_opts; i++) {
+        chaz_CLIOption *opt = &self->opts[i];
+        if (strcmp(opt->name, name) == 0) {
+            if (!opt->defined || !opt->value) {
+                return 0;
+            }
+            return strtol(opt->value, NULL, 10);
+        }
+    }
+    S_chaz_CLI_error(self, "Longval request for unknown option: '%s'", name);
+    return 0;
+}
+
+const char*
+chaz_CLI_strval(chaz_CLI *self, const char *name) {
+    int i;
+    for (i = 0; i < self->num_opts; i++) {
+        chaz_CLIOption *opt = &self->opts[i];
+        if (strcmp(opt->name, name) == 0) {
+            return opt->value;
+        }
+    }
+    S_chaz_CLI_error(self, "Strval request for unknown option: '%s'", name);
+    return 0;
+}
+
+int
+chaz_CLI_parse(chaz_CLI *self, int argc, const char *argv[]) {
+    int i;
+    char *name = NULL;
+    size_t name_cap = 0;
+
+    /* Parse most args. */
+    for (i = 1; i < argc; i++) {
+        const char *arg = argv[i];
+        size_t name_len = 0;
+        int has_equals = 0;
+        const char *value = NULL;
+
+        /* Stop processing if we see `-` or `--`. */
+        if (strcmp(arg, "--") == 0 || strcmp(arg, "-") == 0) {
+            break;
+        }
+
+        if (strncmp(arg, "--", 2) != 0) {
+            S_chaz_CLI_error(self, "Unexpected argument: '%s'", arg);
+            free(name);
+            return 0;
+        }
+
+        /* Extract the name of the argument, look for a potential value. */
+        while (1) {
+            char c = arg[name_len + 2];
+            if (isalnum(c) || c == '-' || c == '_') {
+                name_len++;
+            }
+            else if (c == '\0') {
+                break;
+            }
+            else if (c == '=') {
+                /* The rest of the arg is the value. */
+                value = arg + 2 + name_len + 1;
+                break;
+            }
+            else {
+                free(name);
+                S_chaz_CLI_error(self, "Malformed argument: '%s'", arg);
+                return 0;
+            }
+        }
+        if (name_len + 1 > name_cap) {
+            name_cap = name_len + 1;
+            name = (char*)realloc(name, name_cap);
+        }
+        memcpy(name, arg + 2, name_len);
+        name[name_len] = '\0';
+
+        /* Attempt to set the option. */
+        if (!chaz_CLI_set(self, name, value)) {
+            free(name);
+            return 0;
+        }
+    }
+
+    return 1;
+}
+
+
+/***************************************************************************/
+
 #line 17 "src/Charmonizer/Core/Compiler.c"
 #include <string.h>
 #include <stdlib.h>
@@ -5110,99 +5533,82 @@
 /* #include "Charmonizer/Core/ConfWriterPython.h" */
 /* #include "Charmonizer/Core/ConfWriterRuby.h" */
 /* #include "Charmonizer/Core/Util.h" */
+/* #include "Charmonizer/Core/CLI.h" */
 /* #include "Charmonizer/Core/Compiler.h" */
 /* #include "Charmonizer/Core/Make.h" */
 /* #include "Charmonizer/Core/OperatingSystem.h" */
 
 int
-chaz_Probe_parse_cli_args(int argc, const char *argv[],
-                          struct chaz_CLIArgs *args) {
+chaz_Probe_parse_cli_args(int argc, const char *argv[], chaz_CLI *cli) {
     int i;
     int output_enabled = 0;
 
-    /* Zero out args struct. */
-    memset(args, 0, sizeof(struct chaz_CLIArgs));
+    /* Register Charmonizer-specific options. */
+    chaz_CLI_register(cli, "enable-c", "generate charmony.h", CHAZ_CLI_NO_ARG);
+    chaz_CLI_register(cli, "enable-perl", "generate Charmony.pm", CHAZ_CLI_NO_ARG);
+    chaz_CLI_register(cli, "enable-python", "generate charmony.py", CHAZ_CLI_NO_ARG);
+    chaz_CLI_register(cli, "enable-ruby", "generate charmony.rb", CHAZ_CLI_NO_ARG);
+    chaz_CLI_register(cli, "enable-makefile", NULL, CHAZ_CLI_NO_ARG);
+    chaz_CLI_register(cli, "enable-coverage", NULL, CHAZ_CLI_NO_ARG);
+    chaz_CLI_register(cli, "cc", "compiler command", CHAZ_CLI_ARG_REQUIRED);
+    chaz_CLI_register(cli, "cflags", NULL, CHAZ_CLI_ARG_REQUIRED);
 
-    /* Parse most args. */
-    for (i = 1; i < argc; i++) {
-        const char *arg = argv[i];
-        if (strcmp(arg, "--") == 0) {
-            /* From here on out, everything will be a compiler flag. */
-            i++;
-            break;
-        }
-        if (strcmp(arg, "--enable-c") == 0) {
-            args->charmony_h = 1;
-            output_enabled = 1;
-        }
-        else if (strcmp(arg, "--enable-perl") == 0) {
-            args->charmony_pm = 1;
-            output_enabled = 1;
-        }
-        else if (strcmp(arg, "--enable-python") == 0) {
-            args->charmony_py = 1;
-            output_enabled = 1;
-        }
-        else if (strcmp(arg, "--enable-ruby") == 0) {
-            args->charmony_rb = 1;
-            output_enabled = 1;
-        }
-        else if (strcmp(arg, "--enable-makefile") == 0) {
-            args->write_makefile = 1;
-        }
-        else if (strcmp(arg, "--enable-coverage") == 0) {
-            args->code_coverage = 1;
-        }
-        else if (memcmp(arg, "--cc=", 5) == 0) {
-            size_t len = strlen(arg);
-            size_t l   = 5;
-            size_t r   = len;
-            size_t trimmed_len;
-
-            if (len > CHAZ_PROBE_MAX_CC_LEN - 5) {
-                fprintf(stderr, "Exceeded max length for compiler command");
-                exit(1);
-            }
-
-            /*
-             * Some Perl setups have a 'cc' config value with leading
-             * whitespace.
-             */
-            while (isspace(arg[l])) {
-                ++l;
-            }
-            while (r > l && isspace(arg[r-1])) {
-                --r;
-            }
-
-            trimmed_len = r - l;
-            memcpy(args->cc, arg + l, trimmed_len);
-            args->cc[trimmed_len] = '\0';
-        }
-    } /* preserve value of i */
-
-    /* Accumulate compiler flags. */
-    for (; i < argc; i++) {
-        const char *arg = argv[i];
-        size_t new_len = strlen(arg) + strlen(args->cflags) + 2;
-        if (new_len >= CHAZ_PROBE_MAX_CFLAGS_LEN) {
-            fprintf(stderr, "Exceeded max length for compiler flags");
-            exit(1);
-        }
-        strcat(args->cflags, " ");
-        strcat(args->cflags, arg);
+    /* Parse options, exiting on failure. */
+    if (!chaz_CLI_parse(cli, argc, argv)) {
+        fprintf(stderr, "%s", chaz_CLI_help(cli));
+        exit(1);
     }
 
-    /* Process CHARM_VERBOSITY environment variable. */
+    /* Accumulate compiler flags. */
     {
-        const char *verbosity_env = getenv("CHARM_VERBOSITY");
-        if (verbosity_env && strlen(verbosity_env)) {
-            args->verbosity = strtol(verbosity_env, NULL, 10);
+        char *cflags = chaz_Util_strdup("");
+        size_t cflags_len = 0;
+        for (i = 0; i < argc; i++) {
+            if (strcmp(argv[i], "--") == 0) {
+                i++;
+                break;
+            }
         }
+        for (; i < argc; i++) {
+            const char *arg = argv[i];
+            cflags_len += strlen(arg) + 2;
+            cflags = (char*)realloc(cflags, cflags_len);
+            strcat(cflags, " ");
+            strcat(cflags, arg);
+        }
+        chaz_CLI_set(cli, "cflags", cflags);
+        free(cflags);
+    }
+
+    /* Some Perl setups have a 'cc' config value with leading whitespace. */
+    if (chaz_CLI_defined(cli, "cc")) {
+        const char *arg = chaz_CLI_strval(cli, "cc");
+        char  *cc;
+        size_t len = strlen(arg);
+        size_t l   = 0;
+        size_t r   = len;
+        size_t trimmed_len;
+
+        while (isspace(arg[l])) {
+            ++l;
+        }
+        while (r > l && isspace(arg[r-1])) {
+            --r;
+        }
+
+        trimmed_len = r - l;
+        cc = (char*)malloc(trimmed_len + 1);
+        memcpy(cc, arg + l, trimmed_len);
+        cc[trimmed_len] = '\0';
+        chaz_CLI_unset(cli, "cc");
+        chaz_CLI_set(cli, "cc", cc);
+        free(cc);
     }
 
     /* Validate. */
-    if (!strlen(args->cc) || !output_enabled) {
+    if (!chaz_CLI_defined(cli, "cc")
+        || !strlen(chaz_CLI_strval(cli, "cc"))
+       ) {
         return false;
     }
 
@@ -5218,7 +5624,7 @@
 }
 
 void
-chaz_Probe_init(struct chaz_CLIArgs *args) {
+chaz_Probe_init(struct chaz_CLI *cli) {
     int output_enabled = 0;
 
     {
@@ -5231,25 +5637,25 @@
 
     /* Dispatch other initializers. */
     chaz_OS_init();
-    chaz_CC_init(args->cc, args->cflags);
+    chaz_CC_init(chaz_CLI_strval(cli, "cc"), chaz_CLI_strval(cli, "cflags"));
     chaz_ConfWriter_init();
     chaz_HeadCheck_init();
     chaz_Make_init();
 
     /* Enable output. */
-    if (args->charmony_h) {
+    if (chaz_CLI_defined(cli, "enable-c")) {
         chaz_ConfWriterC_enable();
         output_enabled = true;
     }
-    if (args->charmony_pm) {
+    if (chaz_CLI_defined(cli, "enable-perl")) {
         chaz_ConfWriterPerl_enable();
         output_enabled = true;
     }
-    if (args->charmony_py) {
+    if (chaz_CLI_defined(cli, "enable-python")) {
         chaz_ConfWriterPython_enable();
         output_enabled = true;
     }
-    if (args->charmony_rb) {
+    if (chaz_CLI_defined(cli, "enable-ruby")) {
         chaz_ConfWriterRuby_enable();
         output_enabled = true;
     }
@@ -7296,7 +7702,7 @@
 } SourceFileContext;
 
 static void
-S_add_compiler_flags(struct chaz_CLIArgs *args) {
+S_add_compiler_flags(struct chaz_CLI *cli) {
     chaz_CFlags *extra_cflags = chaz_CC_get_extra_cflags();
 
     if (chaz_Probe_gcc_version_num()) {
@@ -7308,7 +7714,7 @@
                 "-DLUCY_DEBUG -pedantic -Wall -Wextra -Wno-variadic-macros"
             );
         }
-        if (args->charmony_pm) {
+        if (chaz_CLI_defined(cli, "enable-perl")) {
             chaz_CFlags_append(extra_cflags, "-DPERL_GCC_PEDANTIC");
         }
 
@@ -7359,7 +7765,7 @@
 }
 
 static void
-S_write_makefile(struct chaz_CLIArgs *args) {
+S_write_makefile(struct chaz_CLI *cli) {
     SourceFileContext sfc;
 
     const char *base_dir = "..";
@@ -7407,7 +7813,7 @@
     chaz_CFlags_add_include_dir(makefile_cflags, ".");
     chaz_CFlags_add_include_dir(makefile_cflags, include_dir);
     chaz_CFlags_add_include_dir(makefile_cflags, src_dir);
-    if (args->code_coverage) {
+    if (chaz_CLI_defined(cli, "enable-coverage")) {
         chaz_CFlags_enable_code_coverage(makefile_cflags);
     }
 
@@ -7456,7 +7862,7 @@
     if (chaz_CC_msvc_version_num()) {
         chaz_CFlags_append(link_flags, "/nologo");
     }
-    if (args->code_coverage) {
+    if (chaz_CLI_defined(cli, "enable-coverage")) {
         chaz_CFlags_enable_code_coverage(link_flags);
     }
     chaz_MakeFile_add_exe(makefile, cfc_exe, "$(COMMON_OBJS) $(CFC_OBJS)",
@@ -7468,7 +7874,7 @@
     rule = chaz_MakeFile_add_rule(makefile, "test", test_cfc_exe);
     chaz_MakeRule_add_command(rule, test_cfc_exe);
 
-    if (args->code_coverage) {
+    if (chaz_CLI_defined(cli, "enable-coverage")) {
         rule = chaz_MakeFile_add_rule(makefile, "coverage", test_cfc_exe);
         chaz_MakeRule_add_command(rule,
                                   "lcov"
@@ -7495,7 +7901,7 @@
     chaz_MakeRule_add_rm_command(clean_rule, "$(CFC_OBJS)");
     chaz_MakeRule_add_rm_command(clean_rule, "$(TEST_CFC_OBJS)");
 
-    if (args->code_coverage) {
+    if (chaz_CLI_defined(cli, "enable-coverage")) {
         chaz_MakeRule_add_rm_command(clean_rule, "cfc.info");
         chaz_MakeRule_add_recursive_rm_command(clean_rule, "coverage");
     }
@@ -7517,14 +7923,16 @@
 
 int main(int argc, const char **argv) {
     /* Initialize. */
-    struct chaz_CLIArgs args;
+    chaz_CLI *cli
+        = chaz_CLI_new(argv[0], "charmonizer: Probe C build environment");
+    chaz_CLI_set_usage(cli, "Usage: charmonizer [OPTIONS] [-- [CFLAGS]]");
     {
-        int result = chaz_Probe_parse_cli_args(argc, argv, &args);
+        int result = chaz_Probe_parse_cli_args(argc, argv, cli);
         if (!result) {
             chaz_Probe_die_usage();
         }
-        chaz_Probe_init(&args);
-        S_add_compiler_flags(&args);
+        chaz_Probe_init(cli);
+        S_add_compiler_flags(cli);
     }
 
     /* Define stdint types in charmony.h. */
@@ -7545,11 +7953,12 @@
     chaz_UnusedVars_run();
     chaz_VariadicMacros_run();
 
-    if (args.write_makefile) {
-        S_write_makefile(&args);
+    if (chaz_CLI_defined(cli, "enable-makefile")) {
+        S_write_makefile(cli);
     }
 
     /* Clean up. */
+    chaz_CLI_destroy(cli);
     chaz_Probe_clean_up();
 
     return 0;
diff --git a/runtime/common/charmonizer.c b/runtime/common/charmonizer.c
index 0b1444a..e6a39bd 100644
--- a/runtime/common/charmonizer.c
+++ b/runtime/common/charmonizer.c
@@ -179,6 +179,97 @@
 
 /***************************************************************************/
 
+#line 21 "src/Charmonizer/Core/CLI.h"
+#ifndef H_CHAZ_CLI
+#define H_CHAZ_CLI 1
+
+#define CHAZ_CLI_NO_ARG       0
+#define CHAZ_CLI_ARG_REQUIRED (1 << 0)
+#define CHAZ_CLI_ARG_OPTIONAL (1 << 1)
+
+/* The CLI module provides argument parsing for a command line interface.
+ */
+
+typedef struct chaz_CLI chaz_CLI;
+
+/* Constructor.
+ *
+ * @param name The name of the application.
+ * @param description A description of the application.
+ */
+chaz_CLI*
+chaz_CLI_new(const char *name, const char *description);
+
+/* Destructor.
+ */
+void
+chaz_CLI_destroy(chaz_CLI *self);
+
+/* Return a string combining usage header with documentation of options.
+ */
+const char*
+chaz_CLI_help(chaz_CLI *self);
+
+/* Override the generated usage header.
+ */
+void
+chaz_CLI_set_usage(chaz_CLI *self, const char *usage);
+
+/* Register an option.  Updates the "help" string, invalidating previous
+ * values.  Returns true on success, or reports an error and returns false if
+ * the option was already registered.
+ */
+int
+chaz_CLI_register(chaz_CLI *self, const char *name, const char *help,
+                  int flags);
+
+/* Set an option.  The specified option must have been registered previously.
+ * The supplied `value` is optional and will be copied.
+ *
+ * Returns true on success.  Reports an error and returns false on failure.
+ */
+int
+chaz_CLI_set(chaz_CLI *self, const char *name, const char *value);
+
+/* Returns true if the option has been set, false otherwise.
+ */
+int
+chaz_CLI_defined(chaz_CLI *self, const char *name);
+
+/* Return the value of a given option converted to a long int.  Defaults to 0.
+ * Reports an error if the named option has not been registered.
+ */
+long
+chaz_CLI_longval(chaz_CLI *self, const char *name);
+
+/* Return the value of an option as a C string.  Defaults to NULL.  Reports an
+ * error if the named option has not been registered.
+ */
+const char*
+chaz_CLI_strval(chaz_CLI *self, const char *name);
+
+/* Unset an option, making subsequent calls to `get` return false and making
+ * it possible to call `set` again.
+ *
+ * Returns true if the option exists and was able to be unset.
+ */
+int
+chaz_CLI_unset(chaz_CLI *self, const char *name);
+
+/* Parse `argc` and `argv`, setting options as appropriate.  Returns true on
+ * success.  Reports an error and returns false if either an unexpected option
+ * was encountered or an option which requires an argument was supplied
+ * without one.
+ */
+int
+chaz_CLI_parse(chaz_CLI *self, int argc, const char *argv[]);
+
+#endif /* H_CHAZ_CLI */
+
+
+
+/***************************************************************************/
+
 #line 21 "src/Charmonizer/Core/Compiler.h"
 /* Charmonizer/Core/Compiler.h
  */
@@ -924,20 +1015,7 @@
 #include <stddef.h>
 #include <stdio.h>
 
-#define CHAZ_PROBE_MAX_CC_LEN 100
-#define CHAZ_PROBE_MAX_CFLAGS_LEN 2000
-
-struct chaz_CLIArgs {
-    char cc[CHAZ_PROBE_MAX_CC_LEN + 1];
-    char cflags[CHAZ_PROBE_MAX_CFLAGS_LEN + 1];
-    int  charmony_h;
-    int  charmony_pm;
-    int  charmony_py;
-    int  charmony_rb;
-    int  verbosity;
-    int  write_makefile;
-    int  code_coverage;
-};
+struct chaz_CLI;
 
 /* Parse command line arguments, initializing and filling in the supplied
  * `args` struct.
@@ -954,7 +1032,7 @@
  */
 int
 chaz_Probe_parse_cli_args(int argc, const char *argv[],
-                          struct chaz_CLIArgs *args);
+                          struct chaz_CLI *cli);
 
 /* Exit after printing usage instructions to stderr.
  */
@@ -971,7 +1049,7 @@
  *      2 - debugging
  */
 void
-chaz_Probe_init(struct chaz_CLIArgs *args);
+chaz_Probe_init(struct chaz_CLI *cli);
 
 /* Clean up the Charmonizer environment -- deleting tempfiles, etc.  This
  * should be called only after everything else finishes.
@@ -2099,6 +2177,351 @@
 
 /***************************************************************************/
 
+#line 17 "src/Charmonizer/Core/CLI.c"
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
+/* #include "Charmonizer/Core/CLI.h" */
+/* #include "Charmonizer/Core/Util.h" */
+
+typedef struct chaz_CLIOption {
+    char *name;
+    char *help;
+    char *value;
+    int   defined;
+    int   flags;
+} chaz_CLIOption;
+
+struct chaz_CLI {
+    char *name;
+    char *desc;
+    char *usage;
+    char *help;
+    chaz_CLIOption *opts;
+    int   num_opts;
+};
+
+static void
+S_chaz_CLI_error(chaz_CLI *self, const char *pattern, ...) {
+    va_list ap;
+    if (chaz_Util_verbosity > 0) {
+        va_start(ap, pattern);
+        vfprintf(stderr, pattern, ap);
+        va_end(ap);
+        fprintf(stderr, "\n");
+    }
+}
+
+static void
+S_chaz_CLI_rebuild_help(chaz_CLI *self) {
+    int i;
+    size_t amount = 200; // Length of section headers.
+
+    // Allocate space.
+    if (self->usage) {
+        amount += strlen(self->usage);
+    }
+    else {
+        amount += strlen(self->name);
+    }
+    if (self->desc) {
+        amount += strlen(self->desc);
+    }
+    for (i = 0; i < self->num_opts; i++) {
+        chaz_CLIOption *opt = &self->opts[i];
+        amount += 24 + 2 * strlen(opt->name);
+        if (opt->flags) {
+            amount += strlen(opt->name);
+        }
+        if (opt->help) {
+            amount += strlen(opt->help);
+        }
+    }
+    free(self->help);
+    self->help = (char*)malloc(amount);
+    self->help[0] = '\0';
+
+    // Accumulate "help" string.
+    if (self->usage) {
+        strcat(self->help, self->usage);
+    }
+    else {
+        strcat(self->help, "Usage: ");
+        strcat(self->help, self->name);
+        if (self->num_opts) {
+            strcat(self->help, " [OPTIONS]");
+        }
+    }
+    if (self->desc) {
+        strcat(self->help, "\n\n");
+        strcat(self->help, self->desc);
+    }
+    strcat(self->help, "\n");
+    if (self->num_opts) {
+        strcat(self->help, "\nOptional arguments:\n");
+        for (i = 0; i < self->num_opts; i++) {
+            chaz_CLIOption *opt = &self->opts[i];
+            size_t line_start = strlen(self->help);
+            size_t current_len;
+
+            strcat(self->help, "  --");
+            strcat(self->help, opt->name);
+            current_len = strlen(self->help);
+            if (opt->flags) {
+                int j;
+                if (opt->flags & CHAZ_CLI_ARG_OPTIONAL) {
+                    self->help[current_len++] = '[';
+                }
+                self->help[current_len++] = '=';
+                for (j = 0; opt->name[j]; j++) {
+                    self->help[current_len++] = toupper(opt->name[j]);
+                }
+                if (opt->flags & CHAZ_CLI_ARG_OPTIONAL) {
+                    strcat(self->help, "]");
+                }
+                self->help[current_len] = '\0';
+            }
+            if (opt->help) {
+                self->help[current_len++] = ' ';
+                while (current_len - line_start < 25) {
+                    self->help[current_len++] = ' ';
+                }
+                self->help[current_len] = '\0';
+                strcpy(self->help + current_len, opt->help);
+            }
+            strcat(self->help, "\n");
+        }
+    }
+    strcat(self->help, "\n");
+}
+
+chaz_CLI*
+chaz_CLI_new(const char *name, const char *description) {
+    chaz_CLI *self  = calloc(1, sizeof(chaz_CLI));
+    self->name      = chaz_Util_strdup(name ? name : "PROGRAM");
+    self->desc      = description ? chaz_Util_strdup(description) : NULL;
+    self->help      = NULL;
+    self->opts      = NULL;
+    self->num_opts  = 0;
+    S_chaz_CLI_rebuild_help(self);
+    return self;
+}
+
+void
+chaz_CLI_destroy(chaz_CLI *self) {
+    int i;
+    for (i = 0; i < self->num_opts; i++) {
+        chaz_CLIOption *opt = &self->opts[i];
+        free(opt->name);
+        free(opt->help);
+        free(opt->value);
+    }
+    free(self->name);
+    free(self->desc);
+    free(self->opts);
+    free(self->usage);
+    free(self->help);
+}
+
+void
+chaz_CLI_set_usage(chaz_CLI *self, const char *usage) {
+    free(self->usage);
+    self->usage = chaz_Util_strdup(usage);
+}
+
+const char*
+chaz_CLI_help(chaz_CLI *self) {
+    return self->help;
+}
+
+int
+chaz_CLI_register(chaz_CLI *self, const char *name, const char *help,
+                  int flags) {
+    int rank;
+    int i;
+    int arg_required = !!(flags & CHAZ_CLI_ARG_REQUIRED);
+    int arg_optional = !!(flags & CHAZ_CLI_ARG_OPTIONAL);
+
+    // Validate flags
+    if (arg_required && arg_optional) {
+        S_chaz_CLI_error(self, "Conflicting flags: value both optional "
+                         "and required");
+        return 0;
+    }
+
+    // Insert new option.  Keep options sorted by name.
+    for (rank = self->num_opts; rank > 0; rank--) {
+        int comparison = strcmp(name, self->opts[rank - 1].name);
+        if (comparison == 0) {
+            S_chaz_CLI_error(self, "Option '%s' already registered", name);
+            return 0;
+        }
+        else if (comparison > 0) {
+            break;
+        }
+    }
+    self->num_opts += 1;
+    self->opts = realloc(self->opts, self->num_opts * sizeof(chaz_CLIOption));
+    for (i = self->num_opts - 1; i > rank; i--) {
+        self->opts[i] = self->opts[i - 1];
+    }
+    self->opts[rank].name    = chaz_Util_strdup(name);
+    self->opts[rank].help    = help ? chaz_Util_strdup(help) : NULL;
+    self->opts[rank].flags   = flags;
+    self->opts[rank].defined = 0;
+    self->opts[rank].value   = NULL;
+
+    // Update `help` with new option.
+    S_chaz_CLI_rebuild_help(self);
+
+    return 1;
+}
+
+int
+chaz_CLI_set(chaz_CLI *self, const char *name, const char *value) {
+    int i;
+    for (i = 0; i < self->num_opts; i++) {
+        chaz_CLIOption *opt = &self->opts[i];
+        if (strcmp(opt->name, name) == 0) {
+            if (opt->defined) {
+                S_chaz_CLI_error(self, "'%s' specified multiple times", name);
+                return 0;
+            }
+            opt->defined = 1;
+            if (value != NULL) {
+                opt->value = chaz_Util_strdup(value);
+            }
+            return 1;
+        }
+    }
+    S_chaz_CLI_error(self, "Attempt to set unknown option: '%s'", name);
+    return 0;
+}
+
+int
+chaz_CLI_unset(chaz_CLI *self, const char *name) {
+    int i;
+    for (i = 0; i < self->num_opts; i++) {
+        chaz_CLIOption *opt = &self->opts[i];
+        if (strcmp(opt->name, name) == 0) {
+            free(opt->value);
+            opt->value = NULL;
+            opt->defined = 0;
+            return 1;
+        }
+    }
+    S_chaz_CLI_error(self, "Attempt to unset unknown option: '%s'", name);
+    return 0;
+}
+
+int
+chaz_CLI_defined(chaz_CLI *self, const char *name) {
+    int i;
+    for (i = 0; i < self->num_opts; i++) {
+        chaz_CLIOption *opt = &self->opts[i];
+        if (strcmp(opt->name, name) == 0) {
+            return opt->defined;
+        }
+    }
+    S_chaz_CLI_error(self, "Inquiry for unknown option: '%s'", name);
+    return 0;
+}
+
+long
+chaz_CLI_longval(chaz_CLI *self, const char *name) {
+    int i;
+    for (i = 0; i < self->num_opts; i++) {
+        chaz_CLIOption *opt = &self->opts[i];
+        if (strcmp(opt->name, name) == 0) {
+            if (!opt->defined || !opt->value) {
+                return 0;
+            }
+            return strtol(opt->value, NULL, 10);
+        }
+    }
+    S_chaz_CLI_error(self, "Longval request for unknown option: '%s'", name);
+    return 0;
+}
+
+const char*
+chaz_CLI_strval(chaz_CLI *self, const char *name) {
+    int i;
+    for (i = 0; i < self->num_opts; i++) {
+        chaz_CLIOption *opt = &self->opts[i];
+        if (strcmp(opt->name, name) == 0) {
+            return opt->value;
+        }
+    }
+    S_chaz_CLI_error(self, "Strval request for unknown option: '%s'", name);
+    return 0;
+}
+
+int
+chaz_CLI_parse(chaz_CLI *self, int argc, const char *argv[]) {
+    int i;
+    char *name = NULL;
+    size_t name_cap = 0;
+
+    /* Parse most args. */
+    for (i = 1; i < argc; i++) {
+        const char *arg = argv[i];
+        size_t name_len = 0;
+        int has_equals = 0;
+        const char *value = NULL;
+
+        /* Stop processing if we see `-` or `--`. */
+        if (strcmp(arg, "--") == 0 || strcmp(arg, "-") == 0) {
+            break;
+        }
+
+        if (strncmp(arg, "--", 2) != 0) {
+            S_chaz_CLI_error(self, "Unexpected argument: '%s'", arg);
+            free(name);
+            return 0;
+        }
+
+        /* Extract the name of the argument, look for a potential value. */
+        while (1) {
+            char c = arg[name_len + 2];
+            if (isalnum(c) || c == '-' || c == '_') {
+                name_len++;
+            }
+            else if (c == '\0') {
+                break;
+            }
+            else if (c == '=') {
+                /* The rest of the arg is the value. */
+                value = arg + 2 + name_len + 1;
+                break;
+            }
+            else {
+                free(name);
+                S_chaz_CLI_error(self, "Malformed argument: '%s'", arg);
+                return 0;
+            }
+        }
+        if (name_len + 1 > name_cap) {
+            name_cap = name_len + 1;
+            name = (char*)realloc(name, name_cap);
+        }
+        memcpy(name, arg + 2, name_len);
+        name[name_len] = '\0';
+
+        /* Attempt to set the option. */
+        if (!chaz_CLI_set(self, name, value)) {
+            free(name);
+            return 0;
+        }
+    }
+
+    return 1;
+}
+
+
+/***************************************************************************/
+
 #line 17 "src/Charmonizer/Core/Compiler.c"
 #include <string.h>
 #include <stdlib.h>
@@ -5110,99 +5533,82 @@
 /* #include "Charmonizer/Core/ConfWriterPython.h" */
 /* #include "Charmonizer/Core/ConfWriterRuby.h" */
 /* #include "Charmonizer/Core/Util.h" */
+/* #include "Charmonizer/Core/CLI.h" */
 /* #include "Charmonizer/Core/Compiler.h" */
 /* #include "Charmonizer/Core/Make.h" */
 /* #include "Charmonizer/Core/OperatingSystem.h" */
 
 int
-chaz_Probe_parse_cli_args(int argc, const char *argv[],
-                          struct chaz_CLIArgs *args) {
+chaz_Probe_parse_cli_args(int argc, const char *argv[], chaz_CLI *cli) {
     int i;
     int output_enabled = 0;
 
-    /* Zero out args struct. */
-    memset(args, 0, sizeof(struct chaz_CLIArgs));
+    /* Register Charmonizer-specific options. */
+    chaz_CLI_register(cli, "enable-c", "generate charmony.h", CHAZ_CLI_NO_ARG);
+    chaz_CLI_register(cli, "enable-perl", "generate Charmony.pm", CHAZ_CLI_NO_ARG);
+    chaz_CLI_register(cli, "enable-python", "generate charmony.py", CHAZ_CLI_NO_ARG);
+    chaz_CLI_register(cli, "enable-ruby", "generate charmony.rb", CHAZ_CLI_NO_ARG);
+    chaz_CLI_register(cli, "enable-makefile", NULL, CHAZ_CLI_NO_ARG);
+    chaz_CLI_register(cli, "enable-coverage", NULL, CHAZ_CLI_NO_ARG);
+    chaz_CLI_register(cli, "cc", "compiler command", CHAZ_CLI_ARG_REQUIRED);
+    chaz_CLI_register(cli, "cflags", NULL, CHAZ_CLI_ARG_REQUIRED);
 
-    /* Parse most args. */
-    for (i = 1; i < argc; i++) {
-        const char *arg = argv[i];
-        if (strcmp(arg, "--") == 0) {
-            /* From here on out, everything will be a compiler flag. */
-            i++;
-            break;
-        }
-        if (strcmp(arg, "--enable-c") == 0) {
-            args->charmony_h = 1;
-            output_enabled = 1;
-        }
-        else if (strcmp(arg, "--enable-perl") == 0) {
-            args->charmony_pm = 1;
-            output_enabled = 1;
-        }
-        else if (strcmp(arg, "--enable-python") == 0) {
-            args->charmony_py = 1;
-            output_enabled = 1;
-        }
-        else if (strcmp(arg, "--enable-ruby") == 0) {
-            args->charmony_rb = 1;
-            output_enabled = 1;
-        }
-        else if (strcmp(arg, "--enable-makefile") == 0) {
-            args->write_makefile = 1;
-        }
-        else if (strcmp(arg, "--enable-coverage") == 0) {
-            args->code_coverage = 1;
-        }
-        else if (memcmp(arg, "--cc=", 5) == 0) {
-            size_t len = strlen(arg);
-            size_t l   = 5;
-            size_t r   = len;
-            size_t trimmed_len;
-
-            if (len > CHAZ_PROBE_MAX_CC_LEN - 5) {
-                fprintf(stderr, "Exceeded max length for compiler command");
-                exit(1);
-            }
-
-            /*
-             * Some Perl setups have a 'cc' config value with leading
-             * whitespace.
-             */
-            while (isspace(arg[l])) {
-                ++l;
-            }
-            while (r > l && isspace(arg[r-1])) {
-                --r;
-            }
-
-            trimmed_len = r - l;
-            memcpy(args->cc, arg + l, trimmed_len);
-            args->cc[trimmed_len] = '\0';
-        }
-    } /* preserve value of i */
-
-    /* Accumulate compiler flags. */
-    for (; i < argc; i++) {
-        const char *arg = argv[i];
-        size_t new_len = strlen(arg) + strlen(args->cflags) + 2;
-        if (new_len >= CHAZ_PROBE_MAX_CFLAGS_LEN) {
-            fprintf(stderr, "Exceeded max length for compiler flags");
-            exit(1);
-        }
-        strcat(args->cflags, " ");
-        strcat(args->cflags, arg);
+    /* Parse options, exiting on failure. */
+    if (!chaz_CLI_parse(cli, argc, argv)) {
+        fprintf(stderr, "%s", chaz_CLI_help(cli));
+        exit(1);
     }
 
-    /* Process CHARM_VERBOSITY environment variable. */
+    /* Accumulate compiler flags. */
     {
-        const char *verbosity_env = getenv("CHARM_VERBOSITY");
-        if (verbosity_env && strlen(verbosity_env)) {
-            args->verbosity = strtol(verbosity_env, NULL, 10);
+        char *cflags = chaz_Util_strdup("");
+        size_t cflags_len = 0;
+        for (i = 0; i < argc; i++) {
+            if (strcmp(argv[i], "--") == 0) {
+                i++;
+                break;
+            }
         }
+        for (; i < argc; i++) {
+            const char *arg = argv[i];
+            cflags_len += strlen(arg) + 2;
+            cflags = (char*)realloc(cflags, cflags_len);
+            strcat(cflags, " ");
+            strcat(cflags, arg);
+        }
+        chaz_CLI_set(cli, "cflags", cflags);
+        free(cflags);
+    }
+
+    /* Some Perl setups have a 'cc' config value with leading whitespace. */
+    if (chaz_CLI_defined(cli, "cc")) {
+        const char *arg = chaz_CLI_strval(cli, "cc");
+        char  *cc;
+        size_t len = strlen(arg);
+        size_t l   = 0;
+        size_t r   = len;
+        size_t trimmed_len;
+
+        while (isspace(arg[l])) {
+            ++l;
+        }
+        while (r > l && isspace(arg[r-1])) {
+            --r;
+        }
+
+        trimmed_len = r - l;
+        cc = (char*)malloc(trimmed_len + 1);
+        memcpy(cc, arg + l, trimmed_len);
+        cc[trimmed_len] = '\0';
+        chaz_CLI_unset(cli, "cc");
+        chaz_CLI_set(cli, "cc", cc);
+        free(cc);
     }
 
     /* Validate. */
-    if (!strlen(args->cc) || !output_enabled) {
+    if (!chaz_CLI_defined(cli, "cc")
+        || !strlen(chaz_CLI_strval(cli, "cc"))
+       ) {
         return false;
     }
 
@@ -5218,7 +5624,7 @@
 }
 
 void
-chaz_Probe_init(struct chaz_CLIArgs *args) {
+chaz_Probe_init(struct chaz_CLI *cli) {
     int output_enabled = 0;
 
     {
@@ -5231,25 +5637,25 @@
 
     /* Dispatch other initializers. */
     chaz_OS_init();
-    chaz_CC_init(args->cc, args->cflags);
+    chaz_CC_init(chaz_CLI_strval(cli, "cc"), chaz_CLI_strval(cli, "cflags"));
     chaz_ConfWriter_init();
     chaz_HeadCheck_init();
     chaz_Make_init();
 
     /* Enable output. */
-    if (args->charmony_h) {
+    if (chaz_CLI_defined(cli, "enable-c")) {
         chaz_ConfWriterC_enable();
         output_enabled = true;
     }
-    if (args->charmony_pm) {
+    if (chaz_CLI_defined(cli, "enable-perl")) {
         chaz_ConfWriterPerl_enable();
         output_enabled = true;
     }
-    if (args->charmony_py) {
+    if (chaz_CLI_defined(cli, "enable-python")) {
         chaz_ConfWriterPython_enable();
         output_enabled = true;
     }
-    if (args->charmony_rb) {
+    if (chaz_CLI_defined(cli, "enable-ruby")) {
         chaz_ConfWriterRuby_enable();
         output_enabled = true;
     }
@@ -7300,6 +7706,7 @@
 /* #include "Charmonizer/Probe/SymbolVisibility.h" */
 /* #include "Charmonizer/Probe/VariadicMacros.h" */
 /* #include "Charmonizer/Core/HeaderChecker.h" */
+/* #include "Charmonizer/Core/CLI.h" */
 /* #include "Charmonizer/Core/ConfWriter.h" */
 /* #include "Charmonizer/Core/ConfWriterC.h" */
 /* #include "Charmonizer/Core/ConfWriterPerl.h" */
@@ -7313,7 +7720,7 @@
 static const char cfish_major_version[] = "0.4";
 
 static void
-S_add_compiler_flags(struct chaz_CLIArgs *args) {
+S_add_compiler_flags(struct chaz_CLI *cli) {
     chaz_CFlags *extra_cflags = chaz_CC_get_extra_cflags();
 
     if (chaz_Probe_gcc_version_num()) {
@@ -7324,7 +7731,7 @@
         else if (getenv("LUCY_DEBUG")) {
             chaz_CFlags_append(extra_cflags,
                 "-DLUCY_DEBUG -pedantic -Wall -Wextra -Wno-variadic-macros");
-            if (args->charmony_pm) {
+            if (chaz_CLI_defined(cli, "enable-perl")) {
                 chaz_CFlags_append(extra_cflags, "-DPERL_GCC_PEDANTIC");
             }
         }
@@ -7398,7 +7805,7 @@
 }
 
 static void
-S_write_makefile(struct chaz_CLIArgs *args) {
+S_write_makefile(struct chaz_CLI *cli) {
     SourceFileContext sfc;
 
     const char *base_dir = "..";
@@ -7455,7 +7862,7 @@
     chaz_CFlags_enable_debugging(makefile_cflags);
     chaz_CFlags_disable_strict_aliasing(makefile_cflags);
     chaz_CFlags_compile_shared_library(makefile_cflags);
-    if (args->code_coverage) {
+    if (chaz_CLI_defined(cli, "enable-coverage")) {
         chaz_CFlags_enable_code_coverage(makefile_cflags);
     }
 
@@ -7532,7 +7939,7 @@
     if (math_library) {
         chaz_CFlags_add_external_library(link_flags, math_library);
     }
-    if (args->code_coverage) {
+    if (chaz_CLI_defined(cli, "enable-coverage")) {
         chaz_CFlags_enable_code_coverage(link_flags);
     }
     chaz_MakeFile_add_shared_lib(makefile, shared_lib, "$(CLOWNFISH_OBJS)",
@@ -7561,7 +7968,7 @@
     }
     chaz_MakeRule_add_command(rule, test_command);
 
-    if (args->code_coverage) {
+    if (chaz_CLI_defined(cli, "enable-coverage")) {
         rule = chaz_MakeFile_add_rule(makefile, "coverage", test_cfish_exe);
         chaz_MakeRule_add_command(rule,
                                   "lcov"
@@ -7586,7 +7993,7 @@
     chaz_MakeRule_add_rm_command(clean_rule, "$(CLOWNFISH_OBJS)");
     chaz_MakeRule_add_recursive_rm_command(clean_rule, "autogen");
 
-    if (args->code_coverage) {
+    if (chaz_CLI_defined(cli, "enable-coverage")) {
         chaz_MakeRule_add_rm_command(clean_rule, "clownfish.info");
         chaz_MakeRule_add_recursive_rm_command(clean_rule, "coverage");
     }
@@ -7618,14 +8025,16 @@
 
 int main(int argc, const char **argv) {
     /* Initialize. */
-    struct chaz_CLIArgs args;
+    chaz_CLI *cli
+        = chaz_CLI_new(argv[0], "charmonizer: Probe C build environment");
+    chaz_CLI_set_usage(cli, "Usage: charmonizer [OPTIONS] [-- [CFLAGS]]");
     {
-        int result = chaz_Probe_parse_cli_args(argc, argv, &args);
+        int result = chaz_Probe_parse_cli_args(argc, argv, cli);
         if (!result) {
             chaz_Probe_die_usage();
         }
-        chaz_Probe_init(&args);
-        S_add_compiler_flags(&args);
+        chaz_Probe_init(cli);
+        S_add_compiler_flags(cli);
     }
     {
         int i;
@@ -7687,11 +8096,12 @@
         "#endif\n\n"
     );
 
-    if (args.write_makefile) {
-        S_write_makefile(&args);
+    if (chaz_CLI_defined(cli, "enable-makefile")) {
+        S_write_makefile(cli);
     }
 
     /* Clean up. */
+    chaz_CLI_destroy(cli);
     chaz_Probe_clean_up();
 
     return 0;