experimental/cmdline

    Dark Mode
Search:
  Source   Edit

This module implements a fast and lightweight declarative command line parser, aiming to simplify the creation of user-friendly command-line interfaces.

Key features:

  • Arbitrary command nesting.
  • Type-safe, declarative parsing.
  • Automatic generation of help output.

Examples

Greeting program

Example:

import experimental/cmdline
import std/sugar

type Args = object
  count: Natural
  name: string

var cli = commandBuilder(Args)
  .name("hello")
  .describe("simple greeting program")
  .initCli()
cli.addHelpFlag()
cli.flagBuilder()
  .name("count")
  .parser(Natural, (opt, val, var args) => (args.count = val))
  .describe("number of greetings")
  .addTo(cli)
cli.flagBuilder()
  .name("name")
  .parser(string, (opt, val, var args) => (args.name = val))
  .describe("the person to greet")
  .addTo(cli)

let args = cli.run(defaults = Args(count: 1))
for _ in 1..args.count:
  if args.name == "":
    echo "Hello!"
  else:
    echo "Hello ", args.name, "!"

What this looks like when run:

$ ./hello --count 3
Hello!
Hello!
Hello!

The help page is generated for you:

$ ./hello --help
simple greeting program

Usage: hello [OPTIONS]

Options:
  --help           display help message
  --count <VALUE>  number of greetings
  --name <VALUE>   the person to greet

Comes with error handling, too:

$ ./hello --count=no
error: invalid value for '--count <VALUE>': invalid integer: no

Usage: hello [OPTIONS]

Program with subcommands

Example: cmd: -r:off

import experimental/cmdline
import std/setutils
import std/sugar

type
  Operation = enum
    Ls
    Rm

  LsArgs = object
    paths: seq[string]

  RmOpt {.pure.} = enum
    Force
    Recursive

  RmArgs = object
    opts: set[RmOpt]
    paths: seq[string]

  Config = object
    case op: Operation
    of Ls:
      ls: LsArgs
    of Rm:
      rm: RmArgs

var cli = commandBuilder(Config)
  .name("cmd")
  .describe("multi tool")
  .initCli()
cli.addHelpFlag(RootCommand, "help", "h")

let lsCmd = cli.commandBuilder()
  .name("ls")
  .describe("list paths")
  .parser((_, var cfg) => (cfg = Config(op: Ls)))
  .addTo(cli, RootCommand)
cli.addHelpFlag(lsCmd)
cli.positionalBuilder()
  .name("PATH")
  .describe("path(s) to list, default to current directory")
  .optional()
  .catchAll()
  .parser(string, (val, var cfg) => cfg.ls.paths.add val)
  .addTo(cli, lsCmd)

let rmCmd = cli.commandBuilder()
  .name("rm")
  .describe("remove files")
  .parser((_, var cfg) => (cfg = Config(op: Rm)))
  .addTo(cli, RootCommand)
cli.addHelpFlag(rmCmd)
cli.flagBuilder()
  .name("force")
  .alias("f")
  .describe("force removal")
  .parser(bool, (_, val, var cfg) => (cfg.rm.opts[Force] = val))
  .addTo(cli, rmCmd)
cli.flagBuilder()
  .name("recursive")
  .alias("r")
  .describe("recursively remove files")
  .parser(bool, (_, val, var cfg) => (cfg.rm.opts[Recursive] = val))
  .addTo(cli, rmCmd)
cli.positionalBuilder()
  .name("PATH")
  .describe("path(s) to remove")
  .catchAll()
  .parser(string, (val, var cfg) => cfg.rm.paths.add val)
  .addTo(cli, rmCmd)

let config = cli.run()
case config.op
of Ls:
  echo "list files at: ", config.ls.paths
of Rm:
  echo "remove flags: ", config.rm.opts
  echo "remove paths: ", config.rm.paths

What this looks like when run:

$ ./cmd ls /
list files at: @["/"]

$ ./cmd rm -rf secret
remove flags: {Force, Recursive}
remove paths: @["secret"]

Generated help pages:

$ ./cmd --help
multi tool

Usage: cmd [OPTIONS] <COMMAND>

Commands:
  ls  list paths
  rm  remove files

Options:
  -h, --help  display help message

$ ./cmd ls --help
list paths

Usage: cmd ls [OPTIONS] [PATH]...

Arguments:
  [PATH]...  path(s) to list, default to current directory

Options:
  --help  display help message

$ ./cmd rm --help
remove files

Usage: cmd rm [OPTIONS] <PATH>...

Arguments:
  <PATH>...  path(s) to remove

Options:
  --help           display help message
  -f, --force      force removal
  -r, --recursive  recursively remove files

Interacting with the parsing process

cmdline allows registered parsers to interact with the command-line parsing process via a few mechanisms:

Parsers may signal errors in value interpretation by raising a ValueError, which will be processed automatically by the library. Any other exceptions are considered internal errors, and will be propagated directly to caller.

Example:

import experimental/cmdline
proc errorParser(key, value: string, acc: var string) =
  raise newException(ValueError, "some error with: " & value)

proc faultyParser(key, value: string, acc: var string) =
  raise newException(IOError, "something unexpected")

var cli = commandBuilder(string).initCli()
cli.flagBuilder()
  .name("error")
  .parser(errorParser)
  .addTo(cli)
cli.flagBuilder()
  .name("fault")
  .parser(faultyParser)
  .addTo(cli)

doAssertRaises(InvalidValueError):
  discard cli.parse(@["--error=value"])

doAssertRaises(IOError):
  discard cli.parse(@["--fault=value"])
Parsers may control parsing behavior following its parameter by returning an Action. The default action taken if not specified is Action.Continue.

Example:

import experimental/cmdline
import std/sugar

proc actionParser(key, value: string, acc: var seq[string]): Action =
  case value
  of "help": Action.ShowHelp
  of "noflag": Action.DisableFlagProcessing
  else: Action.Continue

var cli = commandBuilder(seq[string]).initCli()
cli.flagBuilder()
  .name("action")
  .parser(actionParser)
  .addTo(cli)
# To collect anything that's not a flag
cli.positionalBuilder()
  .name("ANY")
  .catchAll()
  .optional()
  .parser((v, var s) => s.add v)
  .addTo(cli)

doAssertRaises(HelpError):
  discard cli.parse(@["--action=help", "--invalid-flag"])

doAssert cli.parse(
  @["start", "--action=none", "--action=noflag", "--action=help"]
) == ["start", "--action=help"]

Types

Action {.pure.} = enum
  Continue,                 ## Continue parameter parsing.
  ShowHelp,                 ## Abort and show help message.
  DisableFlagProcessing      ## Parameters following this will no longer be
                             ## interpreted as flags.
Action to be taken after parsing.   Source   Edit
Cli[T] {.requiresInit.} = object
  commands: Table[CommandId, CliCommand] ## Lookup mapping of command to lookup tables.
  parsers: Store[ParameterId, ParserAny[T]] ## Parser to process input for ParameterId.
  names: Store[ParameterId, string] ## Canonical names for all ParameterIds.
  aliases: Table[ParameterId, seq[string]] ## Mapping of ParameterId to aliases.
  parents: Store[ParameterId, ParameterId] ## Mapping of ParameterId to their parent.
  usages: Store[ParameterId, string] ## Canonical usage for ParameterIds.
  placeholders: Store[ParameterId, string] ## Canonical placeholder for ParameterIds. Only used for flags.
  

A command line interface description.

See also:

  Source   Edit
CommandBuilder[T] = object
  cmdParser: CommandParser[T, Action]
  cmdName: string
  aliases: seq[string]
  usage: string
  isDefault: bool

Builder for command line subcommands.

See also:

  Source   Edit
CommandError = object of ParseError
  commandName*: string       ## Input value causing the error.
  
An error parsing command.   Source   Edit
CommandId = distinct ParameterId
A command line subcommand. Values of this type are tied to the originating Cli instance.   Source   Edit
CommandParser[T; R] = proc (command: CommandId; accumulator: var T): R

A parser for command parameter with command command. The accumulator passed to run or parse can be accessed and modified via accumulator.

See also:

  Source   Edit
FlagBuilder[T] = object
  flagParser: ParserAny[T]
  flagName: string
  aliases: seq[string]
  usage: string
  placeholder: string

Builder for command line flags.

See also:

  Source   Edit
FlagError = object of ParseError
  flagName*: string ## Name of the flag causing the error, as specified by
                    ## input
  
An error parsing flags.   Source   Edit
FlagId = distinct ParameterId
A command line flag. Values of this type are tied to the originating Cli instance.   Source   Edit
FlagOptionalParser[T; R] = proc (name: string; value: Option[string];
                                 accumulator: var T): R

A parser for flag with name and optional value. The accumulator passed to run or parse can be accessed and modified via accumulator.

See also:

  Source   Edit
FlagOptionalTypedParser[T; U; R] = proc (name: string; value: Option[U];
    accumulator: var T): R
Typed variant of FlagOptionalParser.   Source   Edit
FlagParser[T; R] = proc (name, value: string; accumulator: var T): R

A parser for flag with option and value. The accumulator passed to run or parse can be accessed and modified via accumulator.

See also:

  Source   Edit
FlagTypedParser[T; U; R] = proc (name: string; value: U; accumulator: var T): R
Typed variant of FlagParser.   Source   Edit
HelpError = object of ParseError
  paramName*: string ## Name of the parameter triggering help, as specified by
                     ## input.
  param*: ParameterId        ## Handle to the parameter triggering help.
  
Help was requested using Action.ShowHelp.   Source   Edit
InvalidCommandError = object of CommandError
  targetCommand*: CommandId  ## Handle to the target command
  

The command parsed was rejected by parser.

The ValueError causing this can be found in the parent field.

  Source   Edit
InvalidPositionalError = object of PositionalError
  positional*: PositionalId  ## Handle to the positional
  

The positional parsed was invalid.

The ValueError causing this can be found in the parent field.

  Source   Edit
InvalidValueError = object of FlagError
  flagValue*: Option[string] ## The string value received. This should always
                             ## be `some(string)` for flags with non-optional
                             ## value.
  flag*: FlagId              ## Handle to the flag.
  

Invalid value passed to a flag.

The ValueError causing this can be found in the parent field.

  Source   Edit
MaybeAction = Action | void
Typeclass to support parsers returning either Action or nothing.   Source   Edit
MissingCommandError = object of CommandError
  
A required command is missing from input.   Source   Edit
MissingPositionalError = object of PositionalError
  positional*: PositionalId  ## Handle to the positional
  
A required positional parameter is missing from input.   Source   Edit
MissingValueError = object of FlagError
  flag*: FlagId              ## Handle to the flag
  
The flag parsed requires a value but was not provided.   Source   Edit
ParameterId = distinct uint32

A command line parameter. Values of this type are tied to the originating Cli instance.

A ParameterId might be a CommandId, FlagId, or PositionalId. The classify function can be used to distinguish between them.

  Source   Edit
ParameterKind {.pure.} = enum
  Command, Flag, Positional
  Source   Edit
ParseError = object of CatchableError
  command*: CommandId        ## Active command during error.
  remaining*: seq[string]    ## ParameterIds that were not parsed.
  
An error during command line parsing.   Source   Edit
PositionalBuilder[T] = object
  posParser: PositionalParser[T, Action]
  posName: string
  usage: string
  isOptional: bool
  isCatchAll: bool

Builder for command line positional parameters.

See also:

  Source   Edit
PositionalError = object of ParseError
  positionalValue*: string   ## Input value causing the error
  
An error parsing positional parameters.   Source   Edit
PositionalId = distinct ParameterId
A command line positional parameter. Values of this type are tied to the originating Cli instance.   Source   Edit
PositionalParser[T; R] = proc (value: string; accumulator: var T): R

A parser for positional parameter with value value. The accumulator passed to run or parse can be accessed and modified via accumulator.

See also:

  Source   Edit
TypedPositionalParser[T; U; R] = proc (value: U; accumulator: var T): R
Typed variant of PositionalParser.   Source   Edit
UnknownCommandError = object of CommandError
  
The command parsed was not recognized.   Source   Edit
UnknownFlagError = object of FlagError
  flagValue*: Option[string] ## Inline value of flag causing error
  
The flag parsed was not recognized.   Source   Edit
UnknownPositionalError = object of PositionalError
  
The positional parsed was not recognized.   Source   Edit

Consts

RootCommand = 0'u32
The top-level command of Cli   Source   Edit

Procs

proc `==`(a, b: CommandId): bool {.borrow, ...raises: [], tags: [].}
  Source   Edit
proc `==`(a, b: FlagId): bool {.borrow, ...raises: [], tags: [].}
  Source   Edit
proc `==`(a, b: ParameterId): bool {.borrow, ...raises: [], tags: [].}
  Source   Edit
proc `==`(a, b: PositionalId): bool {.borrow, ...raises: [], tags: [].}
  Source   Edit
proc addHelpFlag[T](cli: var Cli[T]; command: CommandId = RootCommand;
                    name: sink string = "help"; aliases: varargs[string] = []): FlagId {.
    discardable.}

Adds a flag triggering Action.ShowHelp with the given name and aliases.

See also:

  Source   Edit
func addTo[T](b: sink CommandBuilder[T]; cli: var Cli[T]; command: CommandId): CommandId {.
    discardable.}

Adds the command specified by b as a subcommand of command.

A subcommand can only be added to command if:

  • command has no positional parameters.
  • The subcommand has a non-empty name.
  • Neither the name nor aliases are used by previously added subcommands.

ValueError will be raised if any of the specified constraints are violated.

See also:

  Source   Edit
func addTo[T](b: sink FlagBuilder[T]; cli: var Cli[T];
              command: CommandId = RootCommand): FlagId {.discardable.}

Adds the flag specified by b to command.

A flag can only be added to command if:

  • The flag has a non-empty name.
  • Neither the name nor aliases are used by previously added flags.
  • A parser or optional parser as set for the flag.

ValueError will be raised if any of the specified constraints are violated.

See also:

  Source   Edit
func addTo[T](b: sink PositionalBuilder[T]; cli: var Cli[T];
              command: CommandId = RootCommand): PositionalId {.discardable.}

Adds the positional parameter specified by b to command.

A positional parameter can only be added to command if:

  • command does not contain any subcommands.
  • The parameter has a non-empty name.
  • The name is not used by any previously added positional parameters.
  • A parser was set for the parameter.
  • No catch-all parameter have been added yet.
  • For non-optional positionals, no optional parameters have been added yet.

ValueError will be raised if any of the specified constraints are violated.

See also:

  Source   Edit
func alias[T](b: sink CommandBuilder[T]; names: varargs[string]): CommandBuilder[
    T]

Sets aliases for this command.

This command can then be matched using any of the provided names in addition to its canonical name.

See also:

Example:

import std/sugar

var cli = commandBuilder(string)
  .initCli()
cli.commandBuilder()
  .name("act")
  .alias("a", "do")
  .parser((_, var s) => (s = "acted"))
  .addTo(cli, RootCommand)
doAssert cli.parse(@["act"]) == "acted"
doAssert cli.parse(@["a"]) == "acted"
doAssert cli.parse(@["do"]) == "acted"
  Source   Edit
func alias[T](b: sink FlagBuilder[T]; names: varargs[string]): FlagBuilder[T]

Sets aliases for this flag.

The flag can be matched using any of the provided names in addition to its canonical name.

See also:

Example:

import std/sugar

var cli = commandBuilder(string)
  .initCli()
cli.flagBuilder()
  .name("string")
  .alias("str", "s")
  .parser(string, (_, val, var str) => (str = val))
  .addTo(cli)
doAssert cli.parse(@["--string=str"]) == "str"
doAssert cli.parse(@["--str=str"]) == "str"
doAssert cli.parse(@["-s=str"]) == "str"
  Source   Edit
func catchAll[T](b: sink PositionalBuilder[T]): PositionalBuilder[T]

Marks this positional as "catch all". All positional parameters encountered starting at this positional will be handled by the associated parser.

A catch all positional can only be added as the last parameter of a command. No other positional parameters might be added after this.

Example:

import std/sugar

var cli = commandBuilder(seq[string])
  .initCli()
cli.positionalBuilder()
  .name("STR")
  .catchAll()
  .parser((v, var s) => s.add v)
  .addTo(cli)

doAssert cli.parse(@["a", "b", "c"]) == ["a", "b", "c"]
  Source   Edit
func classify(cli: Cli; param: ParameterId): ParameterKind
Returns the type of param.

Example:

import std/sugar
var cli = commandBuilder(string)
  .initCli()
let strFlag = cli.flagBuilder()
  .name("str")
  .parser((_, v, var s) => (s = v))
  .addTo(cli)
let strPos = cli.positionalBuilder()
  .name("STR")
  .parser((v, var s) => (s = v))
  .addTo(cli)

doAssert cli.classify(ParameterId RootCommand) == ParameterKind.Command
doAssert cli.classify(ParameterId strFlag) == ParameterKind.Flag
doAssert cli.classify(ParameterId strPos) == ParameterKind.Positional
  Source   Edit
func commandBuilder(T: typedesc): CommandBuilder[T]
Creates a new CommandBuilder.   Source   Edit
func commandBuilder[T](cli: Cli[T]): CommandBuilder[T]
Creates a new CommandBuilder.   Source   Edit
func commandUsage(cli: Cli; command: CommandId;
                  rootName: string = cli.nameOf(RootCommand)): string

Produces a usage message for the given command. The message is meant to convey the general shape of the command line.

The name of the root command can be set for this message using rootName.

Refer to the code sample for example outputs.

Example:

func noop(v: auto, a: var auto): Action = discard
  ## A parser that does nothing

var cli = commandBuilder(string)
  .name("cmd")
  .initCli()

let joinCmd = cli.commandBuilder()
  .name("join")
  .addTo(cli, RootCommand)

cli.positionalBuilder()
  .name("FIRST")
  .describe("first value")
  .parser(noop)
  .addTo(cli, joinCmd)
cli.positionalBuilder()
  .name("SEPARATOR")
  .describe("value separator")
  .optional()
  .parser(noop)
  .addTo(cli, joinCmd)
cli.positionalBuilder()
  .name("VALUE")
  .describe("extra values to join")
  .optional()
  .catchAll()
  .parser(noop)
  .addTo(cli, joinCmd)

doAssert cli.commandUsage(RootCommand) == "cmd [OPTIONS] <COMMAND>"
doAssert cli.commandUsage(joinCmd) ==
  "cmd join [OPTIONS] <FIRST> [SEPARATOR] [VALUE]..."
doAssert cli.commandUsage(joinCmd, "cmdbox.exe") ==
  "cmdbox.exe join [OPTIONS] <FIRST> [SEPARATOR] [VALUE]..."
  Source   Edit
func commandWithName(cli: Cli; command: CommandId; name: string): Option[
    CommandId]
Returns the CommandId handle for the subcommand identifiable by name registered for command.

Example:

import std/options

var cli = commandBuilder(string)
  .initCli()
let actCmd = cli.commandBuilder()
  .name("act")
  .alias("a", "do")
  .addTo(cli, RootCommand)

doAssert cli.commandWithName(RootCommand, "a") == some(actCmd)
doAssert cli.commandWithName(RootCommand, "act") == some(actCmd)
doAssert cli.commandWithName(RootCommand, "not-found") == none(CommandId)
  Source   Edit
func default[T](b: sink CommandBuilder[T]): CommandBuilder[T]

Marks this command as the default subcommand of its parent. When no subcommand is present on the command line, this command will be selected.

Only one default subcommand is permitted per command. Attempting to add another default subcommand to a command will cause an error.

This attribute is ignored for the root command.

Example:

import std/sugar

var cli = commandBuilder(string)
  .initCli()
cli.commandBuilder()
  .name("act")
  .default()
  .parser((_, var s) => (s = "acted"))
  .addTo(cli, RootCommand)

doAssert cli.parse(@[]) == "acted"
  Source   Edit
func defaultCommandOf(cli: Cli; command: CommandId): Option[CommandId]
Returns the default subcommand of command.

Example:

import std/options

var cli = commandBuilder(string)
  .initCli()
let actCmd = cli.commandBuilder()
  .name("act")
  .addTo(cli, RootCommand)
let defCmd = cli.commandBuilder()
  .name("def")
  .default()
  .addTo(cli, actCmd)

doAssert cli.defaultCommandOf(RootCommand) == none(CommandId)
doAssert cli.defaultCommandOf(actCmd) == some(defCmd)
  Source   Edit
func describe[T](b: sink CommandBuilder[T]; usage: sink string): CommandBuilder[
    T]
Sets a short usage description for this command.
Note: usage should only be one-line long. This is not enforced, but the built-in documentation renderer assumes this and might not produce satisfactory results with multi-line usage.
  Source   Edit
func describe[T](b: sink FlagBuilder[T]; usage: sink string;
                 placeholder: sink string = ""): FlagBuilder[T]

Sets a short usage description and a value placeholder for this flag.

For required flags, an empty placeholder defaults to VALUE. For optional flags, an empty placeholder will be omitted from documentation rendering.

Note: usage should only be one-line long. This is not enforced, but the built-in documentation renderer assumes this and might not produce satisfactory results with multi-line usage.
  Source   Edit
func describe[T](b: sink PositionalBuilder[T]; usage: sink string): PositionalBuilder[
    T]
Sets a short usage description for this positional.
Note: usage should only be one-line long. This is not enforced, but the built-in documentation renderer assumes this and might not produce satisfactory results with multi-line usage.
  Source   Edit
func flagBuilder[T](cli: Cli[T]): FlagBuilder[T]
Creates a new FlagBuilder.   Source   Edit
func flagsUsage(cli: Cli; command: CommandId): string

Produces a usage message for all flags registered for command.

For each flag, the rendered message contains the first short and long name, a placeholder and the described usage.

The output is separated into two columns: how to specify the flag on the command line and its associated usage.

Refer to the code sample for an example output.

Example:

func noop(k, v: auto, a: var auto): Action = discard
  ## A parser that does nothing

var cli = commandBuilder(string)
  .initCli()

cli.flagBuilder()
  .name("x")
  .alias("exclude")
  .describe("exclude strings")
  .parser(noop)
  .addTo(cli, RootCommand)
cli.flagBuilder()
  .name("s")
  .describe("silence output")
  .optionalParser(noop)
  .addTo(cli, RootCommand)
cli.flagBuilder()
  .name("color")
  .describe("whether to show color", "MODE")
  .optionalParser(noop)
  .addTo(cli, RootCommand)

doAssert cli.flagsUsage(RootCommand) == """
  -x, --exclude <VALUE>  exclude strings
  -s                     silence output
  --color[=<MODE>]       whether to show color"""
  Source   Edit
func flagWithName(cli: Cli; command: CommandId; name: string): Option[FlagId]
Returns the FlagId handle for the flag identifiable by name registered for command.

Example:

import std/options
import std/sugar

var cli = commandBuilder(string)
  .initCli()
let strFlag = cli.flagBuilder()
  .name("string")
  .alias("str", "s")
  .parser(string, (_, val, var str) => (str = val))
  .addTo(cli)
doAssert cli.flagWithName(RootCommand, "string") == some(strFlag)
doAssert cli.flagWithName(RootCommand, "str") == some(strFlag)
doAssert cli.flagWithName(RootCommand, "not-found") == none(FlagId)
  Source   Edit
func hasDefaultSubcommand(cli: Cli; command: CommandId): bool
Returns whether command has a default subcommand.

Example:

var cli = commandBuilder(string)
  .initCli()
let actCmd = cli.commandBuilder()
  .name("act")
  .addTo(cli, RootCommand)
cli.commandBuilder()
  .name("def")
  .default()
  .addTo(cli, actCmd)

doAssert not cli.hasDefaultSubcommand(RootCommand)
doAssert cli.hasDefaultSubcommand(actCmd)
  Source   Edit
func hasSubcommand(cli: Cli; command: CommandId): bool
Returns whether command contains subcommands.

Example:

var cli = commandBuilder(string)
  .initCli()
let actCmd = cli.commandBuilder()
  .name("act")
  .addTo(cli, RootCommand)

doAssert cli.hasSubcommand(RootCommand)
doAssert not cli.hasSubcommand(actCmd)
  Source   Edit
func help(cli: Cli; command: CommandId;
          rootName: string = cli.nameOf(RootCommand)): string

Produces the help message for a command.

See the code example for sample outputs.

Example:

func noop(v: auto, a: var auto): Action = discard
  ## A parser that does nothing

var cli = commandBuilder(string)
  .name("cmd")
  .initCli()
cli.addHelpFlag(RootCommand, "help", "h")

let joinCmd = cli.commandBuilder()
  .name("join")
  .describe("join value(s)")
  .addTo(cli, RootCommand)
cli.addHelpFlag(joinCmd, "help", "h")

cli.positionalBuilder()
  .name("FIRST")
  .describe("first value")
  .parser(noop)
  .addTo(cli, joinCmd)
cli.positionalBuilder()
  .name("SEPARATOR")
  .describe("value separator")
  .optional()
  .parser(noop)
  .addTo(cli, joinCmd)
cli.positionalBuilder()
  .name("VALUE")
  .describe("extra values to join")
  .optional()
  .catchAll()
  .parser(noop)
  .addTo(cli, joinCmd)

cli.commandBuilder()
  .name("cat")
  .describe("concatenate file(s)")
  .addTo(cli, RootCommand)
cli.commandBuilder()
  .name("install")
  .describe("install file(s) to a given destination")
  .addTo(cli, RootCommand)

doAssert cli.help(RootCommand) == """
Usage: cmd [OPTIONS] <COMMAND>

Commands:
  join     join value(s)
  cat      concatenate file(s)
  install  install file(s) to a given destination

Options:
  -h, --help  display help message"""

doAssert cli.help(joinCmd) == """
join value(s)

Usage: cmd join [OPTIONS] <FIRST> [SEPARATOR] [VALUE]...

Arguments:
  <FIRST>      first value
  [SEPARATOR]  value separator
  [VALUE]...   extra values to join

Options:
  -h, --help  display help message"""
  Source   Edit
proc helpFlagBuilder[T](cli: Cli[T]; name: sink string = "help"): FlagBuilder[T]

Builds a simple flag that triggers Action.ShowHelp when specified on the command line.

The flag can be added directly to a cli or further customized.

See also:

  Source   Edit
func initCli[T: not void](b: sink CommandBuilder[T]): Cli[T]

Creates a new Cli, with b used to construct the root command.

The root command must:

  • Have no parser set.
  • Have no aliases.

Unlike subcommands, the root command may:

  • Have an empty (or unset) name, since the name is not used during parsing. A name, if specified, will be used in documentation generation.

The root command can be referred to using the RootCommand constant.

See also:

  Source   Edit
func isCatchAll(cli: Cli; positional: PositionalId): bool
Returns whether positional is a catch all parameter.

Example:

import std/sugar

var cli = commandBuilder(string)
  .initCli()
let reqPos = cli.positionalBuilder()
  .name("REQ")
  .parser((v, var s) => (s = v))
  .addTo(cli)
let anyPos = cli.positionalBuilder()
  .name("ANY")
  .catchAll()
  .parser((v, var s) => Action.Continue)
  .addTo(cli)

doAssert not cli.isCatchAll(reqPos)
doAssert cli.isCatchAll(anyPos)
  Source   Edit
func isDefault(cli: Cli; command: CommandId): bool
Returns whether command is the default subcommand of its parent.
Note: This is always false for RootCommand.

Example:

var cli = commandBuilder(string)
  .initCli()
let actCmd = cli.commandBuilder()
  .name("act")
  .addTo(cli, RootCommand)
let defCmd = cli.commandBuilder()
  .name("def")
  .default()
  .addTo(cli, actCmd)

doAssert not cli.isDefault(actCmd)
doAssert cli.isDefault(defCmd)
  Source   Edit
func isOptional(cli: Cli; positional: PositionalId): bool
Returns whether a value for positional is optional on the command line.

Example:

import std/sugar

var cli = commandBuilder(string)
  .initCli()
let reqPos = cli.positionalBuilder()
  .name("REQ")
  .parser((v, var s) => (s = v))
  .addTo(cli)
let optPos = cli.positionalBuilder()
  .name("OPT")
  .optional()
  .parser((v, var s) => (s = v))
  .addTo(cli)

doAssert not cli.isOptional(reqPos)
doAssert cli.isOptional(optPos)
  Source   Edit
func isValueOptional(cli: Cli; flag: FlagId): bool
Returns whether a value for flag is optional on the command line.

Example:

import std/sugar
var cli = commandBuilder(string)
  .initCli()
let strFlag = cli.flagBuilder()
  .name("str")
  .optionalParser((_, v, var s) => Action.Continue)
  .addTo(cli)
let strReqFlag = cli.flagBuilder()
  .name("str-req")
  .parser((_, v, var s) => Action.Continue)
  .addTo(cli)
let switchFlag = cli.flagBuilder()
  .name("switch")
  .parser(bool, (_, v, var s) => Action.Continue)
  .addTo(cli)

doAssert cli.isValueOptional(strFlag)
doAssert not cli.isValueOptional(strReqFlag)
doAssert cli.isValueOptional(switchFlag)
  Source   Edit
func longNameOf(cli: Cli; flag: FlagId): Option[string]

Returns the first long name of flag, if one exists.

A long name is defined as a name longer than one character.

Example:

import std/options
import std/sugar

var cli = commandBuilder(string)
  .initCli()
let strFlag = cli.flagBuilder()
  .name("s")
  .alias("str", "string")
  .parser(string, (_, val, var str) => (str = val))
  .addTo(cli)
doAssert cli.longNameOf(strFlag) == some("str")
  Source   Edit
func name[T](b: sink CommandBuilder[T]; name: string): CommandBuilder[T]
Sets the canonical name of this command, which is used to identify this command on the command line.
Note: A name is optional for the root command.

See also:

Example:

import std/sugar

var cli = commandBuilder(string)
  .initCli()
cli.commandBuilder()
  .name("act")
  .parser((_, var s) => (s = "acted"))
  .addTo(cli, RootCommand)
doAssert cli.parse(@["act"]) == "acted"
  Source   Edit
func name[T](b: sink FlagBuilder[T]; name: string): FlagBuilder[T]

Sets the canonical name of this flag, which is used to identify this flag on the command line.

If name is only one ASCII character long, it may use the short form syntax (e.g. -n).

See also:

Example:

import std/sugar

type Args = object
  str: string
  i: int

var cli = commandBuilder(Args)
  .initCli()
cli.flagBuilder()
  .name("string")
  .parser(string, (_, val, var args) => (args.str = val))
  .addTo(cli)
cli.flagBuilder()
  .name("i")
  .parser(int, (_, val, var args) => (args.i = val))
  .addTo(cli)
doAssert cli.parse(@["--string=str"]) == Args(str: "str")
doAssert cli.parse(@["-i=10"]) == Args(i: 10)
  Source   Edit
func name[T](b: sink PositionalBuilder[T]; name: string): PositionalBuilder[T]
Sets the canonical name of this positional, which is used when providing diagnostics and help message.   Source   Edit
func nameOf(cli: Cli; command: CommandId): lent string
Returns the canonical name for command.

Example:

var cli = commandBuilder(string)
  .initCli()
let actCmd = cli.commandBuilder()
  .name("act")
  .addTo(cli, RootCommand)
doAssert cli.nameOf(actCmd) == "act"
  Source   Edit
func nameOf(cli: Cli; flag: FlagId): lent string
Returns the canonical name for flag.

Example:

import std/sugar

var cli = commandBuilder(string)
  .initCli()
let strFlag = cli.flagBuilder()
  .name("string")
  .parser(string, (_, val, var str) => (str = val))
  .addTo(cli)
doAssert cli.nameOf(strFlag) == "string"
  Source   Edit
func nameOf(cli: Cli; positional: PositionalId): lent string
Returns the canonical name for positional.

Example:

import std/sugar

var cli = commandBuilder(string)
  .initCli()
let strPos = cli.positionalBuilder()
  .name("STR")
  .parser((v, var s) => (s = v))
  .addTo(cli)

doAssert cli.nameOf(strPos) == "STR"
  Source   Edit
func optional[T](b: sink PositionalBuilder[T]): PositionalBuilder[T]

Marks this positional as optional. When not specified on the command line, the associated parser will not be called.

No non-optional positional parameters might be added to a command after the first optional.

Example:

import std/sugar

var cli = commandBuilder(string)
  .initCli()
cli.positionalBuilder()
  .name("STR")
  .optional()
  .parser((v, var s) => (s = v))
  .addTo(cli)

doAssert cli.parse(@[]) == ""
doAssert cli.parse(@["a"]) == "a"
  Source   Edit
func optionalParser[T, U; R: MaybeAction](b: sink FlagBuilder[T];
    _: typedesc[U]; parser: sink FlagOptionalTypedParser[T, U, R]): FlagBuilder[
    T]

Sets the parser for this flag, and marks the flag as not requiring any value.

Input string values from the command line will first be parsed using parseCli before handing off to the parser. See parsers module for more information.

Flags with optional value will only receive their value when it is specified inline, for example: --flag=value or --flag:value.

See also:

Example:

import std/options
import std/sugar

var cli = commandBuilder(int)
  .initCli()
cli.flagBuilder()
  .name("int")
  .optionalParser(int, (_, val, var i) => (i = val.get(42)))
  .addTo(cli)
doAssert cli.parse(@["--int"]) == 42
doAssert cli.parse(@["--int", "--int"]) == 42
doAssertRaises(InvalidValueError):
  discard cli.parse(@["--int=--int"])
doAssert cli.parse(@["--int=1000"]) == 1000
  Source   Edit
func optionalParser[T](b: sink FlagBuilder[T];
                       p: sink FlagOptionalParser[T, Action]): FlagBuilder[T]

Sets the parser for this flag, and marks the flag as not requiring any value.

For flags with an optional value, their parser will only be called when the value is specified inline, for example: --flag=value or --flag:value.

See also:

Example:

import std/options

proc parser(opt: string, val: Option[string], str: var string): Action =
  if val == some("help"):
    Action.ShowHelp
  else:
    str = val.get(otherwise = "default")
    Action.Continue

var cli = commandBuilder(string)
  .initCli()
cli.flagBuilder()
  .name("string")
  .optionalParser(parser)
  .addTo(cli)
doAssert cli.parse(@["--string"]) == "default"
doAssert cli.parse(@["--string", "--string"]) == "default"
doAssert cli.parse(@["--string=--string"]) == "--string"
doAssertRaises(HelpError):
  discard cli.parse(@["--string:help"])
  Source   Edit
func optionalParser[T](b: sink FlagBuilder[T];
                       p: sink FlagOptionalParser[T, void]): FlagBuilder[T]

Sets the parser for this flag with Action.Continue as the default action, and marks the flag as not requiring any value.

Flags with optional value will only receive their value when it is specified inline, for example: --flag=value or --flag:value.

See also:

Example:

import std/options
import std/sugar

var cli = commandBuilder(string)
  .initCli()
cli.flagBuilder()
  .name("string")
  .optionalParser((_, val, var str) => (str = val.get("default")))
  .addTo(cli)
doAssert cli.parse(@["--string"]) == "default"
doAssert cli.parse(@["--string", "--string"]) == "default"
doAssert cli.parse(@["--string=--string"]) == "--string"
doAssert cli.parse(@["--string:help"]) == "help"
  Source   Edit
func parentOf(cli: Cli; command: CommandId): Option[CommandId]

Returns the parent command of command.

none(CommandId) is only returned for RootCommand.

Example:

import std/options

var cli = commandBuilder(string)
  .initCli()
let actCmd = cli.commandBuilder()
  .name("act")
  .addTo(cli, RootCommand)
doAssert cli.parentOf(actCmd) == some(RootCommand)
doAssert cli.parentOf(RootCommand) == none(CommandId)
  Source   Edit
func parse[T](cli: Cli[T]; accumulator: var T; args: sink seq[string]) {.
    ...raises: [ParseError].}

Parses the given args list based on the description in cli, with configured parsers updating values in the accumulator.

See also:

  Source   Edit
func parse[T](cli: Cli[T]; args: sink seq[string]; defaults: sink T = default(T)): T {.
    inline, ...raises: [ParseError].}

Parses the given args list based on the description in cli, returning accumulated changes from configured parsers.

An initial value for the internal accumulator can be specified using defaults.

See also:

  Source   Edit
proc parser[T, U; R: MaybeAction](b: sink FlagBuilder[T]; _: typedesc[U];
                                  parser: sink FlagTypedParser[T, U, R]): FlagBuilder[
    T]

Sets the parser for this flag, and marks the flag as requiring values.

Input string values from the command line will first be parsed using parseCli before handing off to the parser. See parsers module for more information.

When U is bool, this flag behaves like a switch and does not require a value to be passed. If values are to be passed, it must be inlined (i.e. --flag=false).

Flags with a required value can receive any of the following forms:

  • --flag=value
  • --flag:value
  • --flag value (only when U is not bool)

See also:

Example:

import std/options
import std/sugar

type Args = object
  i: int
  b: bool

var cli = commandBuilder(Args)
  .initCli()
cli.flagBuilder()
  .name("int")
  .parser(int, (_, val, var args) => (args.i = val))
  .addTo(cli)
cli.flagBuilder()
  .name("switch")
  .parser(bool, (_, val, var args) => (args.b = val))
  .addTo(cli)
doAssertRaises(MissingValueError):
  discard cli.parse(@["--int"])
doAssertRaises(InvalidValueError):
  discard cli.parse(@["--int", "string"])
doAssert cli.parse(@["--int", "1000"]) == Args(i: 1000)
doAssert cli.parse(@["--switch"]) == Args(b: true)
doAssert cli.parse(@["--switch", "--switch:false"]) == Args(b: false)
  Source   Edit
proc parser[T, U; R: MaybeAction](b: sink PositionalBuilder[T]; _: typedesc[U];
                                  parser: sink TypedPositionalParser[T, U, R]): PositionalBuilder[
    T]

Sets the parser for this positional parameter.

Input string values from the command line will first be parsed using parseCli before handing off to the parser. See parsers module for more information.

Example:

import std/sugar

type Args = object
  a, b: int

var cli = commandBuilder(Args)
  .initCli()
cli.positionalBuilder()
  .name("A")
  .parser(int, (v, var args) => (args.a = v; Action.DisableFlagProcessing))
  .addTo(cli)
cli.positionalBuilder()
  .name("B")
  .parser(int, (v, var args) => (args.b = v))
  .addTo(cli)

doAssert cli.parse(@["10", "-10"]) == Args(a: 10, b: -10)
  Source   Edit
func parser[T](b: sink CommandBuilder[T]; p: sink CommandParser[T, Action]): CommandBuilder[
    T]
Sets the parser for this command. This is called when the command is matched on the command line.
Note: A parser is optional for commands.
Warning: It is an error to set a parser for the root command.

Example:

import std/sugar

var cli = commandBuilder(string)
  .initCli()
cli.commandBuilder()
  .name("help")
  .parser((_, var s) => Action.ShowHelp)
  .addTo(cli, RootCommand)
doAssertRaises(HelpError):
  discard cli.parse(@["help"])
  Source   Edit
func parser[T](b: sink CommandBuilder[T]; p: sink CommandParser[T, void]): CommandBuilder[
    T] {.inline.}
Sets the parser for this command with Action.Continue as the default action.
Note: A parser is optional for commands.
Warning: It is an error to set a parser for the root command.

Example:

import std/sugar

var cli = commandBuilder(string)
  .initCli()
cli.commandBuilder()
  .name("act")
  .parser((_, var s) => (s = "acted"))
  .addTo(cli, RootCommand)
doAssert cli.parse(@["act"]) == "acted"
  Source   Edit
func parser[T](b: sink FlagBuilder[T]; p: sink FlagParser[T, Action]): FlagBuilder[
    T]

Sets the parser for this flag, and marks the flag as requiring values.

Flags with a required value can receive any of the following forms:

  • --flag=value
  • --flag:value
  • --flag value

See also:

Example:

proc parser(opt: string, val: string, str: var string): Action =
  if val == "help":
    Action.ShowHelp
  else:
    str = val
    Action.Continue

var cli = commandBuilder(string)
  .initCli()
cli.flagBuilder()
  .name("string")
  .parser(parser)
  .addTo(cli)
doAssertRaises(MissingValueError):
  discard cli.parse(@["--string"])

doAssert cli.parse(@["--string", "--string"]) == "--string"
doAssert cli.parse(@["--string=--string"]) == "--string"

doAssertRaises(HelpError):
  discard cli.parse(@["--string", "help"])
  Source   Edit
func parser[T](b: sink FlagBuilder[T]; p: sink FlagParser[T, void]): FlagBuilder[
    T]

Sets the parser for this flag with Action.Continue as the default action, and marks the flag as requiring values.

Flags with a required value can receive any of the following forms:

  • --flag=value
  • --flag:value
  • --flag value

See also:

Example:

import std/sugar

var cli = commandBuilder(string)
  .initCli()
cli.flagBuilder()
  .name("string")
  .parser((_, val, var str) => (str = val))
  .addTo(cli)

doAssertRaises(MissingValueError):
  discard cli.parse(@["--string"])
doAssert cli.parse(@["--string", "--string"]) == "--string"
doAssert cli.parse(@["--string=--string"]) == "--string"
doAssert cli.parse(@["--string", "help"]) == "help"
  Source   Edit
func parser[T](b: sink PositionalBuilder[T]; p: sink PositionalParser[T, Action]): PositionalBuilder[
    T]
Set the parser for this positional parameter.

Example:

import std/sugar

var cli = commandBuilder(string)
  .initCli()
cli.positionalBuilder()
  .name("NO-FLAGS")
  .parser((_, var s) => Action.DisableFlagProcessing)
  .addTo(cli)
cli.positionalBuilder()
  .name("STR")
  .parser((v, var s) => (s = v; Action.Continue))
  .addTo(cli)

doAssert cli.parse(@["a", "-b"]) == "-b"
  Source   Edit
func parser[T](b: sink PositionalBuilder[T]; p: sink PositionalParser[T, void]): PositionalBuilder[
    T]
Sets the parser for this positional parameter. On successful parse, Action.Continue is taken as the default action.

Example:

import std/sugar

var cli = commandBuilder(string)
  .initCli()
cli.positionalBuilder()
  .name("STR")
  .parser((v, var s) => (s = v))
  .addTo(cli)

doAssert cli.parse(@["a"]) == "a"
  Source   Edit
func pathOf(cli: Cli; command: CommandId): seq[CommandId]
Returns the path leading to command.

Example:

var cli = commandBuilder(string)
  .initCli()
let actCmd = cli.commandBuilder()
  .name("act")
  .addTo(cli, RootCommand)
doAssert cli.pathOf(actCmd) == [RootCommand, actCmd]
doAssert cli.pathOf(RootCommand) == [RootCommand]
  Source   Edit
func placeholderOf(cli: Cli; flag: FlagId): string
Returns the placeholder for flag, as described with describe.

Example:

import std/sugar

var cli = commandBuilder(string)
  .initCli()
let strFlag = cli.flagBuilder()
  .name("string")
  .describe("a string", "STR")
  .parser((_, v, var s) => (s = v))
  .addTo(cli)
let str2Flag = cli.flagBuilder()
  .name("string2")
  .describe("a string")
  .parser((_, v, var s) => (s = v))
  .addTo(cli)
doAssert cli.placeholderOf(strFlag) == "STR"
doAssert cli.placeholderOf(str2Flag) == ""
  Source   Edit
func positionalBuilder[T](cli: Cli[T]): PositionalBuilder[T]
Creates a new PositionalBuilder.   Source   Edit
func positionalsUsage(cli: Cli; command: CommandId): string

Produces a usage message for all positionals registered for command.

For each positional, the rendered message contains the name and the usage as described by describe. The name is wrapped in either <> or [] to signify its requirement with ... suffix added for catch all parameters.

The output is separated into two columns: the positional display form and its associated usage.

Refer to the code sample for an example output.

Example:

func noop(v: auto, a: var auto): Action = discard
  ## A parser that does nothing

var cli = commandBuilder(string)
  .initCli()

cli.positionalBuilder()
  .name("FIRST")
  .describe("first value")
  .parser(noop)
  .addTo(cli, RootCommand)
cli.positionalBuilder()
  .name("SEPARATOR")
  .describe("value separator")
  .optional()
  .parser(noop)
  .addTo(cli, RootCommand)
cli.positionalBuilder()
  .name("VALUE")
  .describe("extra values to join")
  .optional()
  .catchAll()
  .parser(noop)
  .addTo(cli, RootCommand)

doAssert cli.positionalsUsage(RootCommand) == """
  <FIRST>      first value
  [SEPARATOR]  value separator
  [VALUE]...   extra values to join"""
  Source   Edit
func prettifyError[T](cli: Cli[T]; error: ref ParseError): string
Produces a pretty error message for error. The provided error must have been raised by parse(cli).   Source   Edit
proc run[T](cli: Cli[T]; accumulator: var T;
            args: sink seq[string] = commandLineParams();
            messageOutput: File = stdmsg)

Parses the command line args based on the description in cli, with configured parsers updating values in the accumulator.

If an error occurs during parsing, the error message will be printed to messageOutput alongside helpful information and the command will terminate with a failure exit code automatically. The only exception to this is when HelpError occurs, of which the command will terminate with a successful exit code.

See also:

  Source   Edit
proc run[T](cli: Cli[T]; args: sink seq[string] = commandLineParams();
            messageOutput: File = stdmsg; defaults: sink T = default(T)): T

Parses the command line args based on the description in cli, returning accumulated changes from configured parsers.

An initial value for the internal accumulator can be specified using defaults.

See run proc for more information.

See also:

  Source   Edit
func shortNameOf(cli: Cli; flag: FlagId): Option[string]

Returns the first short name of flag, if one exists.

A short name is defined as a one-character long name.

Example:

import std/options
import std/sugar

var cli = commandBuilder(string)
  .initCli()
let strFlag = cli.flagBuilder()
  .name("string")
  .alias("str", "s")
  .parser(string, (_, val, var str) => (str = val))
  .addTo(cli)
doAssert cli.shortNameOf(strFlag) == some("s")
  Source   Edit
func subcommandsUsage(cli: Cli; command: CommandId): string

Produces a usage message for all subcommands registered for command.

The output is separated into two columns: the subcommand canonical name and its associated usage. A [default] suffix is added to the usage message for default subcommand.

Refer to the code sample for an example output.

Example:

func noop(v: auto, a: var auto): Action = discard
  ## A parser that does nothing

var cli = commandBuilder(string)
  .name("cmd")
  .initCli()
cli.commandBuilder()
  .name("join")
  .describe("join value(s)")
  .addTo(cli, RootCommand)
cli.commandBuilder()
  .name("cat")
  .describe("concatenate file(s)")
  .addTo(cli, RootCommand)
cli.commandBuilder()
  .name("install")
  .describe("install file(s) to a given destination")
  .addTo(cli, RootCommand)

doAssert cli.subcommandsUsage(RootCommand) == """
  join     join value(s)
  cat      concatenate file(s)
  install  install file(s) to a given destination"""
  Source   Edit
func usageOf(cli: Cli; command: CommandId): lent string
Returns the usage for command, as described with describe.

Example:

import std/sugar

let cli = commandBuilder(string)
  .describe("some usage")
  .initCli()
doAssert cli.usageOf(RootCommand) == "some usage"
  Source   Edit
func usageOf(cli: Cli; flag: FlagId): lent string
Returns the usage for flag, as described with describe.

Example:

import std/sugar

var cli = commandBuilder(string)
  .initCli()
let strFlag = cli.flagBuilder()
  .name("string")
  .describe("a string")
  .parser((_, v, var s) => (s = v))
  .addTo(cli)
doAssert cli.usageOf(strFlag) == "a string"
  Source   Edit
func usageOf(cli: Cli; positional: PositionalId): lent string
Returns the usage for positional, as described with describe.

Example:

import std/sugar

var cli = commandBuilder(string)
  .initCli()
let strPos = cli.positionalBuilder()
  .name("STR")
  .describe("a string")
  .parser((v, var s) => (s = v))
  .addTo(cli)
doAssert cli.usageOf(strPos) == "a string"
  Source   Edit

Iterators

iterator flags(cli: Cli; command: CommandId): FlagId
Returns all flags for command.   Source   Edit
iterator namesOf(cli: Cli; command: CommandId): lent string
Returns all names that can be used to refer to command. The canonical name is always returned first.

Example:

import std/sequtils

var cli = commandBuilder(string)
  .initCli()
let actCmd = cli.commandBuilder()
  .name("act")
  .alias("a", "do")
  .addTo(cli, RootCommand)
doAssert toSeq(cli.namesOf(actCmd)) == ["act", "a", "do"]
  Source   Edit
iterator namesOf(cli: Cli; flag: FlagId): lent string
Returns all names that can be used to refer to flag. The canonical name is always returned first.

Example:

import std/sequtils
import std/sugar

var cli = commandBuilder(string)
  .initCli()
let strFlag = cli.flagBuilder()
  .name("string")
  .alias("str", "s")
  .parser(string, (_, val, var str) => (str = val))
  .addTo(cli)
doAssert toSeq(cli.namesOf(strFlag)) == ["string", "str", "s"]
  Source   Edit
iterator positionals(cli: Cli; command: CommandId): PositionalId
Returns all positional parameters for command.   Source   Edit
iterator subcommands(cli: Cli; command: CommandId): CommandId
Returns all subcommands for command.   Source   Edit