#
Chapter 9
`cmdline.ss`: Command-line Parsing

To load: `(require (lib "cmdline.ss"))`

`(`command-line` `program-name-expr argv-expr clause `···``)` SYNTAX

Parses a command line according to the specification in the `clause`s. The `program-name-expr` should produce a string to be used as the program name for reporting errors when the command-line is ill-formed. The `argv-expr` must evaluate to a vector of strings; typically, it is `(`current-command-line-arguments`)`.

The command-line is disassembled into flags (possibly with flag-specific arguments) followed by (non-flag) arguments. Command-line strings starting with ``-'' or ``+'' are parsed as flags, but arguments to flags are never parsed as flags, and integers and decimal numbers that start with ``-'' or ``+'' are not treated as flags. Non-flag arguments in the command-line must appear after all flags and the flags' arguments. No command-line string past the first non-flag argument is parsed as a flag. The built-in `--` flag signals the end of command-line flags; any command-line string past the `--` flag is parsed as a non-flag argument.

For defining the command line, each `clause` has one of the following forms:

    (multi flag-spec ···)
    (once-each flag-spec ···)
    (once-any flag-spec ···)
    (final flag-spec ···)
    (help-labels string ···)
    (args arg-formals body-expr ···1)
    (=> finish-proc-expr arg-help-expr help-proc-expr unknown-proc-expr)

    flag-spec is one of
      (flags variable ··· help-str ···1 body-expr ···1)
      (flags => handler-expr help-expr)

    flags is one of
      flag-str
      (flag-str ···1)

    arg-formals is one of
      variable
      (variable ···)
      (variable ···1 . variable)

A `multi`, `once-each`, `once-any`, or `final` clause introduces a set of command-line flag specifications. The clause tag indicates how many times the flag can appear on the command line:

- `multi` -- Each flag specified in the set can be represented any number of times on the command line; i.e., the flags in the set are independent and each flag can be used multiple times.

- `once-each` -- Each flag specified in the set can be represented once on the command line; i.e., the flags in the set are independent, but each flag should be specified at most once. If a flag specification is represented in the command line more than once, the `exn:fail` exception is raised.

- `once-any` -- Only one flag specified in the set can be represented on the command line; i.e., the flags in the set are mutually exclusive. If the set is represented in the command line more than once, the `exn:fail` exception is raised.

- `final` -- Like `multi`, except that no argument after the flag is treated as a flag. Note that multiple `final` flags can be specified if they have short names; for example, if `-a` is a `final` flag, then `--aa` combines two instances of `-a` in a single command-line argument.

A normal flag specification has four parts:

1. `flags` -- a flag string, or a set of flag strings. If a set of flags is provided, all of the flags are equivalent. Each flag string must be of the form `"-`x`"` or `"+`x`"` for some character `x`, or `"--`x`"` or `"++`x`"` for some sequence of characters `x`. An `x` cannot contain only digits or digits plus a single decimal point, since simple (signed) numbers are not treated as flags. In addition, the flags `"--"`, `"-h"`, and `"--help"` are predefined and cannot be changed.

2. `variable`s -- variables that are bound to the flag's arguments. The number of variables specified here determines how many arguments can be provided on the command line with the flag, and the names of these variables will appear in the help message describing the flag. The `variable`s are bound to string values in the `body-expr`s for handling the flag.

3. `help-str` -- a string that describes the flag. This string is used in the help message generated by the handler for the built-in `-h` (or `--help`) flag. If multiple `help-str`s are provided, the rest are displayed on subsequent lines.

4. `body-expr`s -- expressions that are evaluated when one of the `flags` appears on the command line. The flags are parsed left-to-right, and each sequence of `body-expr`s is evaluated as the corresponding flag is encountered. When the `body-expr`s are evaluated, the `variable`s are bound to the arguments provided for the flag on the command line.

A flag specification using `=>` escapes to a more general method of specifying the handler and help strings. In this case, the handler procedure and help string list returned by `handler-expr` and `help-expr` are embedded directly in the table for `parse-command-line`, the procedure used to implement command-line parsing.

A `help-labels` clause inserts text lines into the help table of command-line flags. Each string in the clause provides a separate line of text.

An `args` clause can be specified as the last clause. The variables in `arg-formals` are bound to the leftover command-line strings in the same way that variables are bound to the `formals` of a `lambda` expression. Thus, specifying a single `variable` (without parentheses) collects all of the leftover arguments into a list. The effective arity of the `arg-formals` specification determines the number of extra command-line arguments that the user can provide, and the names of the variables in `arg-formals` are used in the help string. When the command-line is parsed, if the number of provided arguments cannot be matched to variables in `arg-formals`, the `exn:fail` exception is raised. Otherwise, `args` clause's `body-expr`s are evaluated to handle the leftover arguments, and the result of the last `body-expr` is the result of the `command-line` expression.

Instead of an `args` clause, the `=>` clause can be used to escape to a more general method of handling the leftover arguments. In this case, the values of the expressions with `=>` are passed on directly as arguments to `parse-command-line`. The `help-proc-expr` and `unknown-proc-expr` expressions are optional.

Example:

    (command-line "compile" (current-command-line-arguments)
      (once-each
         [("-v" "--verbose") "Compile with verbose messages"
                             (verbose-mode #t)]
         [("-p" "--profile") "Compile with profiling"
                             (profiling-on #t)])
      (once-any
         [("-o" "--optimize-1") "Compile with optimization level 1"
                                (optimize-level 1)]
         ["--optimize-2"        "" ; show help on separate lines
                                "Compile with optimization level 2,"
                                "which implies all optimizations of level 1"
                                (optimize-level 2)])
      (multi
         [("-l" "--link-flags") lf ; flag takes one argument
                                "Add a flag for the linker"
                                (link-flags (cons lf (link-flags)))])
      (args (filename) ; expects one command-line argument: a filename
        filename)) ; return a single filename to compile

`(parse-command-line``` `progname argv table finish-proc arg-help``` [`help-proc unknown-proc`]`)` PROCEDURE

Parses a command-line using the specification in `table`. For an overview of command-line parsing, see the `command-line` form. The `table` argument to this procedural form encodes the information in `command-line`'s clauses, except for the `args` clause. Instead, arguments are handled by the `finish-proc` procedure, and help information about non-flag arguments is provided in `arg-help`. In addition, the `finish-proc` procedure receives information accumulated while parsing flags. The `help-proc` and `unknown-proc` arguments allow customization that is not possible with `command-line`.

When there are no more flags, the `finish-proc` procedure is called with a list of information accumulated for command-line flags (see below) and the remaining non-flag arguments from the command-line. The arity of the `finish-proc` procedure determines the number of non-flag arguments accepted and required from the command-line. For example, if `finish-proc` accepts either two or three arguments, then either one or two non-flag arguments must be provided on the command-line. The `finish-proc` procedure can have any arity (see section 3.12.1 in PLT MzScheme: Language Manual) except `0` or a list of `0`s (i.e., the procedure must at least accept one or more arguments).

The `arg-help` argument is a list of strings identifying the expected (non-flag) command-line arguments, one for each argument. (If an arbitrary number of arguments are allowed, the last string in `arg-help` represents all of them.)

The `help-proc` procedure is called with a help string if the `-h` or `--help` flag is included on the command line. If an unknown flag is encountered, the `unknown-proc` procedure is called just like a flag-handling procedure (as described below); it must at least accept one argument (the unknown flag), but it may also accept more arguments. The default `help-proc` displays the string and exits and the default `unknown-proc` raises the `exn:fail` exception.

A `table` is a list of flag specification sets. Each set is represented as a list of two items: a mode symbol and a list of either help strings or flag specifications. A mode symbol is one of `'once-each`, `'once-any`, `'multi`, `'final`, or `'help-labels`, with the same meanings as the corresponding clause tags in `command-line`. For the `'help-labels` mode, a list of help string is provided. For the other modes, a list of flag specifications is provided, where each specification maps a number of flags to a single handler procedure. A specification is a list of three items:

1. A list of strings for the flags defined by the spec. See `command-line` for information about the format of flag strings.

2. A procedure to handle the flag and its arguments when one of the flags is found on the command line. The arity of this handler procedure determines the number of arguments consumed by the flag: the handler procedure is called with a flag string plus the next few arguments from the command line to match the arity of the handler procedure. The handler procedure must accept at least one argument to receive the flag. If the handler accepts arbitrarily many arguments, all of the remaining arguments are passed to the handler. A handler procedure's arity must either be a number or an `arity-at-least` value (see section 3.12.1 in PLT MzScheme: Language Manual).

   The return value from the handler is added to a list that is eventually passed to `finish-proc`. If the handler returns void, no value is added onto this list. For all non-void values returned by handlers, the order of the values in the list is the same as the order of the arguments on the command-line.

3. A non-empty list for constructing help information for the spec. The first element of the list describes the flag; it can be a string or a non-empty list of strings, and in the latter case, each string is shown on its own line. Additional elements of the main list must be strings to name the expected arguments for the flag. The number of extra help strings provided for a spec must match the number of arguments accepted by the spec's handler procedure.

The following example is the same as the example for `command-line`, translated to the procedural form:

    (parse-command-line "compile" (current-command-line-arguments)
      `((once-each
         [("-v" "--verbose")
          ,(lambda (flag) (verbose-mode #t))
          ("Compile with verbose messages")]
         [("-p" "--profile")
          ,(lambda (flag) (profiling-on #t))
          ("Compile with profiling")])
        (once-any
         [("-o" "--optimize-1")
          ,(lambda (flag) (optimize-level 1))
          ("Compile with optimization level 1")]
         [("--optimize-2")
          ,(lambda (flag) (optimize-level 2))
          (("Compile with optimization level 2,"
            "which implies all optimizations of level 1"))])
        (multi
         [("-l" "--link-flags")
          ,(lambda (flag lf) (link-flags (cons lf (link-flags))))
          ("Add a flag for the linker" "flag")]))
       (lambda (flag-accum file) file)  ; return a single filename to compile
       '("filename")) ; expects one command-line argument: a filename