README

Therapist - for when commands and arguments are getting you down

A simple to use, declarative, type-safe command line parser, with beautiful help messages and clear errors, suitable for simple scripts and complex tools.

Therapist allows you to use a carefully constructed tuple to specify how you want your commandline arguments to be parsed. Each value in the tuple must be set to a <Type>Arg of the appropriate type, which specifies how that argument will appear, what values it can take and provides a help string for the user.

A simple 'Hello world' example:

import therapist

# The parser is specified as a tuple
let spec = (
    # Name is a positional argument, by virtue of being surrounded by < and >
    name: newStringArg(@["<name>"], help="Person to greet"),
    # --times is an optional argument, by virtue of starting with - and/or --
    times: newIntArg(@["-t", "--times"], defaultVal=1, help="How many times to greet"),
    # --version will cause 0.1.0 to be printed
    version: newMessageArg(@["--version"], "0.1.0", help="Prints version"),
    # --help will cause a help message to be printed
    help: newHelpArg(@["-h", "--help"], help="Show help message"),
)
# `args` and `command` would normally be picked up from the commandline
spec.parseOrQuit(prolog="Greeter", args="-t 2 World", command="hello")
# If a help message or version was requested or a parse error generated it would be printed
# and then the parser would call `quit`. Getting past `parseOrQuit` implies we're ok.
for i in 1..spec.times.value:
    echo "Hello " & spec.name.value

doAssert spec.name.seen
doAssert spec.name.value == "World"
doAssert spec.times.seen
doAssert spec.times.value == 2

The above parser generates the following help message

Greeter

Usage:
  hello <name>
  hello --version
  hello -h|--help

Arguments:
  <name>               Person to greet

Options:
  -t, --times=<times>  How many times to greet [default: 1]
  --version            Prints version
  -h, --help           Show help message

The constructor for each <Type>Arg type takes the form:

# doctest: skip
proc newStringArg*(variants: seq[string], help: string, defaultVal="", choices=newSeq[string](),
                    helpvar="", required=false, optional=false, multi=false, env="", helpLevel=0)

Argument types provided out of the box

Creating your own argument type

Creating your own ValueArg is as simple as defining a parse method that turns a string into a value of an appropriate type (or raises a ValueError for invalid input). Suppose we want to create a DateArg type that only accepts ISO-formatted dates:

import therapist
import times

let DEFAULT_DATE = initDateTime(1, mJan, 2000, 0, 0, 0, 0)
proc parseDate(value: string): DateTime = parse(value, "YYYY-MM-dd")
defineArg[DateTime](DateArg, newDateArg, "date", parseDate, DEFAULT_DATE)

Now we can call newDateArg to ask the user to supply a date

Examples

At the other extreme, you can create complex parsers with subcommands (the example below may be familiar to those who have seen docopt.nim). Note that the help message is slightly different; this is in part because parser itself is stricter. For example, --moored is only valid inside the mine subcommand, and as such, will only appear in the help for that command, shown if you run navel_fate mine --help.

import options
import strutils
import therapist

let prolog = "Navel Fate."

let create = (
      name: newStringArg(@["<name>"], multi=true, help="Name of new ship")
)
let move = (
      name: newStringArg(@["<name>"], help="Name of ship to move"),
      x: newIntArg(@["<x>"], help="x grid reference"),
      y: newIntArg(@["<y>"], help="y grid reference"),
      speed: newIntArg(@["--speed"], defaultVal=10, help="Speed in knots"),
      help: newHelpArg()
)
let shoot = (
      x: newIntArg(@["<x>"], help="Name of new ship"),
      y: newIntArg(@["<y>"], help="Name of new ship"),
      help: newHelpArg()
)
let state = (
      moored: newCountArg(@["--moored"], help="Moored (anchored) mine"),
      drifting: newCountArg(@["--drifting"], help="Drifting mine"),
)
let mine = (
      action: newStringArg(@["<action>"], choices = @["set", "remove"], help="Action to perform"),
      x: newIntArg(@["<x>"], help="Name of new ship"),
      y: newIntArg(@["<y>"], help="Name of new ship"),
      state: state,
      help: newHelpArg()
)
let ship = (
      create: newCommandArg(@["new"], create, help="Create a new ship"),
      move: newCommandArg(@["move"], move, help="Move a ship"),
      shoot: newCommandArg(@["shoot"], shoot, help="Shoot at another ship"),
      help: newHelpArg()
)
let spec = (
      ship: newCommandArg(@["ship"], ship, help="Ship commands"),
      mine: newCommandArg(@["mine"], mine, help="Mine commands"),
      help: newHelpArg()
)

let (success, message) = spec.parseOrMessage(prolog="Navel Fate.", args="--help", command="navel_fate")

let expected = """
Navel Fate.

Usage:
  navel_fate ship new <name>...
  navel_fate ship move <name> <x> <y>
  navel_fate ship shoot <x> <y>
  navel_fate mine (set|remove) <x> <y>
  navel_fate -h|--help

Commands:
  ship        Ship commands
  mine        Mine commands

Options:
  -h, --help  Show help message""".strip()

doAssert success and message.isSome
doAssert message.get == expected

Many more examples are available in the source code and in the nimdoc for the various functions.

Possible features therapist does not have

In rough order of likelihood of being added:

Installation

Clone the repository and then run:

> nimble install

Contributing

The code lives on bitbucket. Pull requests (with tests) and bug reports welcome!

License

This library is made available under the LGPL. Use it to make any software you like, open source or not, but if you make improvements to therapist itself, please contribute them back.

Alternatives and prior art

This is therapist. There are many argument parsers like it, but this one is mine. Which one you prefer is likely a matter of taste. If you want to explore alternatives, you might like to look at:

Changes

0.2.0

0.1.0 2020-05-23

Initial release