flag: Command-line flag parsing

Table of content

Introduction

The flag: module provides utilities for parsing command-line flags.

This module supports two different conventions of command-line flags. The Go convention is recommended for Elvish scripts (and followed by the Elvish command itself). The alternative getopt convention is also supported, and useful for writing scripts that wrap existing programs following this convention.

Go convention

Each flag looks like -flag=value.

For boolean flags, -flag is equivalent to -flag=true. For non-boolean flags, -flag treats the next argument as its value; in other words, -flag value is equivalent to -flag=value.

Flag parsing stops before any non-flag argument, or after the flag terminator --.

Examples (-verbose is a boolean flag and -port is a non-boolean flag):

  • In -port 10 foo -x, the port flag is 10, and the rest (foo -x) are non flag arguments.

  • In -verbose 10 foo -x, the verbose flag is true, and the rest (10 foo -x) are non-flag arguments.

  • In -port 10 -- -verbose foo, the port flag is 10, and the part after -- (-verbose foo) are non-flag arguments.

Using --flag is supported, and equivalent to -flag.

Note: Chaining of single-letter flags is not supported: -rf is one flag named rf, not equivalent to -r -f.

Getopt convention

A flag may have either or both of the following forms:

  • A short form: a single character preceded by -, like -f;

  • A long form: a string preceded by --, like --flag.

A flag may take:

  • No argument, like -f or --flag;

  • A required argument, like -f value, -fvalue, --flag=value or --flag value;

  • An optional argument, like -f, -fvalue, --flag or --flag=value.

A short flag that takes no argument can be followed immediately by another short flag. For example, if -r takes no arguments, -rf is equivalent to -r -f. The other short flag may be followed by more short flags (if it takes no argument), or its argument (if it takes one). Assuming that -f and -v take no arguments while -p does, here are some examples:

  • -rfv is equivalent to -r -f -v.

  • -rfp80 is equivalent to -r -f -p 80.

Some aspects of the behavior can be turned on and off as needed:

  • Optionally, flag parsing stops after seeing the flag terminator --.

  • Optionally, flag parsing stops before seeing any non-flag argument. Turning this off corresponds to the behavior of GNU’s getopt_long; turning it on corresponds to the behavior of BSD’s getopt_long.

  • Optionally, only long flags are supported, and they may start with -. Turning this on corresponds to the behavior of getopt_long_only and the Go convention.

Functions

flag:call

flag:call $fn $args &on-parse-error=$nil

Parses flags from $args according to the signature of the $fn, using the Go convention, and calls $fn.

The $fn must be a user-defined function (i.e. not a builtin function or external command). Each option corresponds to a flag; see flag:parse for how the default value affects the behavior of flags. After parsing, the non-flag arguments are used as function arguments.

The &on-parse-error function can be supplied to control the behavior when $args contains invalid flags or when the number of arguments after parsing flags doesn’t match what $fn expects. The function gets an argument that describes the error condition; the argument should be treated as an opaque value for now, but will expose more useful fields in future. If &on-parse-error is not supplied, such errors are raised as exceptions.

Example:

~> use flag
~> fn f {|&verbose=$false &port=(num 8000) name| put $verbose $port $name }
~> flag:call $f~ [-verbose -port 80 a.c]
▶ $true
▶ (num 80)
▶ a.c
~> flag:call $f~ [-unknown-flag] &on-parse-error={|_| echo 'bad usage' }
bad usage
~> flag:call $f~ [-verbose a b c] &on-parse-error={|_| echo 'bad usage' }
bad usage

This function is most useful when creating an Elvish script that accepts command-line arguments. For example, if a script a.elv contains the following code:

use flag
fn main { |&verbose=$false &port=(num 8000) name|
  ...
}
flag:call $main~ $args &on-parse-error={|_|
  echo 'Usage: '(src)[name]' [-verbose] [-port PORT-NUM] name'
  exit 1
}

Note: This example shows how &on-parse-error can be used to print out a usage text, but it needs to duplicate the names of the options and arguments accepted by main. This is a known limitation and will hopefully be addressed with a different API in future.

The script can be used as follows:

~> elvish a.elv -verbose -port 80 foo
...

See also flag:parse.

flag:parse

flag:parse $args $specs

Parses flags from $args according to the $specs, using the Go convention.

The $args must be a list of strings containing the command-line arguments to parse.

The $specs must be a list of flag specs:

[
  [flag default-value 'description of the flag']
  ...
]

Each flag spec consists of the name of the flag (without the leading -), its default value, and a description. The default value influences the how the flag gets converted from string:

  • If it is boolean, the flag is a boolean flag (see Go convention for implications). Flag values 0, f, F, false, False and FALSE are converted to $false, and 1, t, T, true, True and TRUE to $true. Other values are invalid.

  • If it is a string, no conversion is done.

  • If it is a typed number, the flag value is converted using num.

  • If it is a list, the flag value is split at , (equivalent to {|s| put [(str:split , $s)] }).

  • If it is none of the above, an exception is thrown.

On success, this command outputs two values: a map containing the value of flags defined in $specs (whether they appear in $args or not), and a list containing non-flag arguments.

Example:

~> flag:parse [-v -times 10 foo] [
     [v $false 'Verbose']
     [times (num 1) 'How many times']
   ]
▶ [&times=(num 10) &v=$true]
▶ [foo]
~> flag:parse [] [
     [v $false 'Verbose']
     [times (num 1) 'How many times']
   ]
▶ [&times=(num 1) &v=$false]
▶ []

See also flag:call and flag:parse-getopt.

flag:parse-getopt

flag:parse-getopt $args $specs &stop-after-double-dash=$true &stop-before-non-flag=$false &long-only=$false

Parses flags from $args according to the $specs, using the getopt convention (see there for the semantics of the options), and outputs the result.

The $args must be a list of strings containing the command-line arguments to parse.

The $specs must be a list of flag specs:

[
  [&short=f &long=flag &arg-optional=$false &arg-required=$false]
  ...
]

Each flag spec can contain the following:

  • The short and long form of the flag, without the leading - or --. The short form, if non-empty, must be one character. At least one of &short and &long must be non-empty.

  • Whether the flag takes an optional argument or a required argument. At most one of &arg-optional and &arg-required may be true.

It is not an error for a flag spec to contain more keys.

On success, this command outputs two values: a list describing all flags parsed from $args, and a list containing non-flag arguments. The former list looks like:

[
  [&spec=... &arg=value &long=$false]
  ...
]

Each entry contains the original spec for the flag, its argument, and whether the flag appeared in its long form.

Example (some output reformatted for readability):

~> var specs = [
     [&short=v &long=verbose]
     [&short=p &long=port &arg-required]
   ]
~> flag:parse-getopt [-v -p 80 foo] $specs
▶ [[&arg='' &long=$false &spec=[&long=verbose &short=v]] [&arg=80 &long=$false &spec=[&arg-required=$true &long=port &short=p]]]
▶ [foo]
~> flag:parse-getopt [--verbose] $specs
▶ [[&arg='' &long=$true &spec=[&long=verbose &short=v]]]
▶ []
~> flag:parse-getopt [-v] [[&short=v &extra-info=foo]] # extra key in spec
▶ [[&arg='' &long=$false &spec=[&extra-info=foo &short=v]]]
▶ []

See also flag:parse and edit:complete-getopt.