Skip to main content

## Module _concepts

Source
Available on crate feature `unstable-doc` only.
Expand description

### §CLI Concepts

Note: this will be speaking towards the general case.

#### §Environmental context

When you run a command line application, it is inside a terminal emulator, or terminal. This handles integration with the rest of your system including user input, rendering, etc.

The terminal will run inside of itself an interactive shell. The shell is responsible for showing the prompt, receiving input including the command you are writing, letting that command take over until completion, and then repeating. This is called a read-eval-print loop, or REPL. Typically the shell will take the command you typed and split it into separate arguments, including handling of quoting, escaping, and globbing. The parsing and evaluation of the command is shell specific. The shell will then determine which application to run and then pass the full command-line as individual arguments to your program. These arguments are exposed in Rust as `std::env::args_os`.

Windows is an exception in Shell behavior in that the command is passed as an individual string, verbatim, and the application must split the arguments. `std::env::args_os` will handle the splitting for you but will not handle globs.

Takeaways:

- Your application will only see quotes that have been escaped within the shell
  - e.g. to receive `message="hello world"`, you may need to type `'message="hello world"'` or `message=\"hello world\"`
- If your applications needs to parse a string into arguments, you will need to pick a syntax and do it yourself
  - POSIX’s shell syntax is a common choice and available in packages like shlex
  - See also our REPL cookbook entry
- On Windows, you will need to handle globbing yourself if desired
  - `wild` can help with that

#### §Argument Parsing

The first argument of `std::env::args_os` is the `Command::bin_name` which is usually limited to affecting `Command::render_usage`. `Command::no_binary_name` and `Command::multicall` exist for rare cases when this assumption is not valid.

Command-lines are a context-sensitive grammar, meaning the interpretation of an argument is based on the arguments that came before. Arguments come in one of several flavors:

- Values
- Flags
- Subcommands

When examining the next argument,

1. If it starts with a `--`, then that is a long Flag and all remaining text up to a `=` or the end is matched to a `Arg::long`, `Command::long_flag`, or alias.
   - Everything after the `=` is taken as a Value and parsing a new argument is examined.
   - If no `=` is present, then Values will be taken according to `Arg::num_args`
   - We generally call a Flag that takes a Value an Option
2. If it starts with a `-`, then that is a sequence of short Flags where each character is matched against a `Arg::short`, `Command::short_flag` or alias until `=`, the end, or a short Flag takes Values (see `Arg::num_args`)
3. If its a `--`, that is an escape and all future arguments are considered to be a Value, even if they start with `--` or `-`
4. If it matches a `Command::name`, then the argument is a subcommand
5. If there is an `Arg` at the next `Arg::index`, then the argument is considered a Positional argument

When a subcommand matches, all further arguments are parsed by that `Command`.

There are many settings that tweak this behavior, including:

- `Arg::last`: a positional that can only come after `--`
- `Arg::trailing_var_arg`: all further arguments are captured as additional Values
- `Arg::allow_hyphen_values` and `Arg::allow_negative_numbers`: assumes arguments starting with `-` are Values and not Flags.
- `Command::subcommand_precedence_over_arg`: when an `Arg::num_args` takes Values, stop if one matches a subCommand
- `Command::allow_missing_positional`: in limited cases a `Arg::index` may be skipped
- `Command::allow_external_subcommands`: treat any unknown argument as a subcommand, capturing all remaining arguments.

Takeaways

- Values that start with a `-` either need to be escaped by the user with `--` (if a positional), or you need to set `Arg::allow_hyphen_values` or `Arg::allow_negative_numbers`
- `Arg::num_args`, `ArgAction::Append` (on a positional), `Arg::trailing_var_arg`, and `Command::allow_external_subcommands` all affect the parser in similar but slightly different ways and which to use depends on your application

#### §Value Parsing

When reacting to a Flag (no Value), `Arg::default_missing_values` will be applied.

The Value will be split by `Arg::value_delimiter`.

The Value will then be stored according to its `ArgAction`. For most `ArgAction`s, the Value will be parsed according to `ValueParser` and stored in the `ArgMatches`.