API#

dollar_lambda.args

Defines the Args dataclass and associated functions.

dollar_lambda.data_structures

Defines Sequence, a strongly-typed immutable list that implements MonadPlus.

dollar_lambda.decorators

Defines the @command decorator and the CommandTree class.

dollar_lambda.errors

Defines errors which can be raised by parsers.

dollar_lambda.parsers

Defines parsing functions and the Parser class that they instantiate.

Defines Sequence, a strongly-typed immutable list that implements MonadPlus.

class dollar_lambda.data_structures.KeyValue(key, value)#

Simple dataclass for storing key-value pairs.

class dollar_lambda.data_structures.Output(get)#

This is the wrapper class for the output of Parser.

class dollar_lambda.data_structures.Sequence(get)#

This class combines the functionality of MonadPlus and typing.Sequence

>>> from dollar_lambda.data_structures import Sequence
>>> s = Sequence([1, 2])
>>> len(s)
2
>>> s[0]
1
>>> s[-1]
2
>>> s + s  # sequences emulate list behavior when added
Sequence(get=[1, 2, 1, 2])
>>> [x + 1 for x in s]  # sequences can be iterated over
[2, 3]
>>> Sequence([1, 2]) >= (lambda x: Sequence([x, -x]))
Sequence(get=[1, -1, 2, -2])
bind(f)#
>>> Sequence([1, 2]) >= (lambda x: Sequence([x, -x]))
Sequence(get=[1, -1, 2, -2])
static return_(a)#
>>> Sequence.return_(1)
Sequence(get=[1])
to_colliding_dict()#
>>> from dollar_lambda import Sequence, KeyValue
>>> Sequence([KeyValue("a", 1), KeyValue("b", 2), KeyValue("a", 3)]).to_colliding_dict()
{'a': [1, 3], 'b': 2}
>>> Sequence([KeyValue("a", [1]), KeyValue("b", 2), KeyValue("a", [3])]).to_colliding_dict()
{'a': [[1], [3]], 'b': 2}
to_dict()#
>>> from dollar_lambda import Sequence, KeyValue
>>> Sequence([KeyValue("a", 1), KeyValue("b", 2), KeyValue("a", 3)]).to_dict()
{'a': [1, 3], 'b': 2}
>>> from dollar_lambda import Sequence, KeyValue
>>> Sequence([KeyValue("a", [1]), KeyValue("b", 2), KeyValue("a", [3])]).to_dict()
{'a': [[1], [3]], 'b': 2}
>>> from dollar_lambda.data_structures import _TreePath
>>> Sequence([KeyValue("a", _TreePath.make("b", leaf="c"))]).to_dict()
{'a': {'b': 'c'}}
>>> Sequence(
...     [
...         KeyValue("a", "b"),
...         KeyValue("a", _TreePath.make("b", leaf="c")),
...         KeyValue("a", _TreePath.make("b", "c", leaf=1)),
...         KeyValue("a", _TreePath.make("b", "c", leaf=2)),
...     ]
... ).to_dict()
{'a': ['b', {'b': ['c', {'c': [1, 2]}]}]}

Defines parsing functions and the Parser class that they instantiate.

class dollar_lambda.parsers.Parse(parsed, unparsed)#

A Parse is the output of parsing.

Parameters
  • parsed (A) – Component parsed by the parsed

  • unparsed (Sequence[str]) – Component yet to be parsed

class dollar_lambda.parsers.Parser(f, usage, helps, nonoptional=None)#

Main class powering the argument parser.

__add__(other)#

Parse two arguments in either order.

>>> from dollar_lambda import flag
>>> p = flag("verbose") + flag("debug")
>>> p.parse_args("--verbose", "--debug")
{'verbose': True, 'debug': True}
>>> p.parse_args("--debug", "--verbose")
{'debug': True, 'verbose': True}
>>> p.parse_args("--debug")
usage: --verbose --debug
Expected '--verbose'. Got '--debug'

Note that if more than two arguments are chained together with +, some combinations will not parse:

>>> p = flag("a") + flag("b") + flag("c")
>>> p.parse_args("-c", "-a", "-b")   # this works
{'c': True, 'a': True, 'b': True}
>>> p.parse_args("-a", "-c", "-b")   # this doesn't
usage: -a -b -c
Expected '-b'. Got '-c'

This makes more sense when one supplies the implicit parentheses:

>>> p = (flag("a") + flag("b")) + flag("c")

In order to chain together more than two arguments, use nonpositional:

>>> from dollar_lambda import nonpositional
>>> p = nonpositional(flag("a"), flag("b"), flag("c"))
>>> p.parse_args("-a", "-c", "-b")
{'a': True, 'c': True, 'b': True}
__ge__(f)#

Sugar for Parser.bind.

__or__(other)#

Tries apply the first parser. If it fails, tries the second. If that fails, the parser fails.

>>> from dollar_lambda import argument, option, flag
>>> p = option("option") | flag("verbose")
>>> p.parse_args("--option", "x")
{'option': 'x'}
>>> p.parse_args("--verbose")
{'verbose': True}

Note that by default, Parser.parse_args adds >> Parser.done() to the end of parsers, causing p to fail when both arguments are supplied:

>>> p.parse_args("--verbose", "--option", "x")
usage: [--option OPTION | --verbose]
Unrecognized argument: --option

To disable this behavior, use allow_unparsed:

>>> p.parse_args("--verbose", "--option", "x", allow_unparsed=True)
{'verbose': True}
__rshift__(p)#

This applies parsers in sequence. If the first parser succeeds, the unparsed remainder gets handed off to the second parser. If either parser fails, the whole thing fails.

>>> from dollar_lambda import argument, flag
>>> p = argument("first") >> argument("second")
>>> p.parse_args("a", "b")
{'first': 'a', 'second': 'b'}
>>> p.parse_args("a")
usage: FIRST SECOND
The following arguments are required: second
>>> p.parse_args("b")
usage: FIRST SECOND
The following arguments are required: second

This is how this method handles formatting of usage strings:

>>> long = nonpositional(flag("a"), flag("b"), flag("c"), flag("d"))
>>> short = argument("pos")
>>> (short >> short >> short).parse_args("-h")
usage: POS POS POS
>>> (short >> short >> long).parse_args("-h")
usage:
      POS POS
        -a
        -b
        -c
        -d
>>> (short >> (short >> long)).parse_args("-h")
usage:
      POS
        POS
          -a
          -b
          -c
          -d
>>> (long >> short >> short).parse_args("-h")
usage:
      -a
      -b
      -c
      -d
      POS
      POS
>>> (long >> (short >> short)).parse_args("-h")
usage:
      -a
      -b
      -c
      -d
      POS POS
>>> (long >> short >> long).parse_args("-h")
usage:
      -a
      -b
      -c
      -d
      POS
      -a
      -b
      -c
      -d
>>> (long >> (short >> long)).parse_args("-h")
usage:
      -a
      -b
      -c
      -d
      POS
        -a
        -b
        -c
        -d
__xor__(other)#

This is the same as |, but it succeeds only if one of the two parsers fails.

>>> p = argument("int", type=int) ^ argument("div", type=lambda x: 1 / float(x))
>>> p.parse_args("inf")  # succeeds because int("inf") fails
{'div': 0.0}
>>> p.parse_args("0")  # succeeds because 1 / 0 throws an error
{'int': 0}
>>> p.parse_args("1")  # fails because both parsers succeed
Both parsers succeeded. This causes ^ to fail.
apply(f)#

Takes the output of parser and applies f to it. Convert any errors that arise into ArgumentError.

>>> p1 = flag("hello")
>>> p1.parse_args("--hello")
{'hello': True}

This will double p1’s output:

>>> from dollar_lambda import Result
>>> p2 = p1.apply(lambda out: Result.return_(out + out))
>>> p2.parse_args("--hello")
{'hello': [True, True]}
bind(f)#

Returns a new parser that

  1. applies self;

  2. if this succeeds, applies f to the parsed component of the result.

Parser.bind() is one of the functions that makes Parser a Monad. But most users will avoid using it directly, preferring higher level combinators like >>, | and +.

Note that >= as a synonym for bind (as defined in pytypeclass) and we typically prefer using the infix operator to the spelled out method.

To demonstrate one use of bind or >=, let’s use the matches() parser to write a function that takes the output of a parser and fails unless the next argument is the same as the first:

>>> from dollar_lambda import Output, Sequence, KeyValue, Parser, matches, argument
>>> def f(out: Output[Sequence[KeyValue]]) -> Parser[Output[str]]:
...     *_, kv = out.get
...     return matches(kv.value)
...
>>> p = argument("some_dest") >= f
>>> p.parse_args("a", "a")
{'a': 'a'}
>>> p.parse_args("a", "b")
Expected 'a'. Got 'b'
classmethod done(a=None)#

Parser.done() succeeds on the end of input and fails on everything else.

>>> Parser.done().parse_args()
{}
>>> Parser.done().parse_args("arg")
Unrecognized argument: arg

When allow_unparsed=False (the default), Parser.parse_args() adds >> done() to the end of the parser. If you invoke Parser.parse_args() with ``allow_unparsed=True` and without Parser.done() the parser will not complain about leftover (unparsed) input:

>>> flag("verbose").parse_args("--verbose", "--quiet", allow_unparsed=True)
{'verbose': True}
>>> flag("verbose").parse_args("--verbose", "--quiet", allow_unparsed=False)
usage: --verbose
Unrecognized argument: --quiet
>>> (flag("verbose") >> Parser.done()).parse_args("--verbose", "--quiet", allow_unparsed=True)
usage: --verbose
Unrecognized argument: --quiet
classmethod empty(a=None)#

Always returns {}, no matter the input. Used by several other parsers.

>>> Parser.empty().parse_args("any", "arguments", allow_unparsed=True)
{}
fails(a=None)#

Succeeds only if self fails. Does not consume any input.

>>> flag("x").fails().parse_args("not x", allow_unparsed=True)  # succeeds
{}
>>> flag("x").fails().parse_args("-x", allow_unparsed=True)  # fails
Parser unexpectedly succeeded.
findall(pattern)#

This method assumes that the most recent output from applying self is a string. It then finds all occurrences of pattern in this string and binds them to the same key.

>>> p = item("a").findall(r"[1-2]")
>>> p.parse_args("1")
{'a': '1'}

Multiple matches are returned in a list: >>> p.parse_args(“123”) {‘a’: [‘1’, ‘2’]}

Multiple matches are returned in a list: >>> (item(“a”) >> item(“b”)).findall(r”[1-2]”).parse_args(“bound-to-a”, “12”) {‘a’: ‘bound-to-a’, ‘b’: [‘1’, ‘2’]}

findall fails when the most recent output is not a string: >>> flag(“a”).findall(r”[1-2]”).parse_args(“-a”) usage: -a An argument Output(get=Sequence(get=[KeyValue(key=’a’, value=True)])): raised exception expected string or bytes-like object

ignore(a=None)#

Ignores the output from a parser. This is useful when you expect to give arguments to the command line that some other utility will handle.

>>> p = flag("hello").ignore()

This will not bind any value to "hello":

>>> p.parse_args("--hello")
{}

But --hello is still required:

>>> p.parse_args()
The following arguments are required: --hello
many(max=80)#

Applies self zero or more times (like * in regexes).

Parameters

max (int) – Limits the number of times Parser.many() is applied in order to prevent a RecursionError. The default for this can be increased by either setting parser.MAX_MANY or the environment variable DOLLAR_LAMBDA_MAX_MANY.

Examples

>>> from dollar_lambda import argument, flag
>>> p = argument("as-many-as-you-like").many()
>>> p.parse_args()
{}
>>> p = argument("as-many-as-you-like").many()
>>> p.parse_args("a")
{'as-many-as-you-like': 'a'}
>>> p = argument("as-many-as-you-like").many()
>>> p.parse_args("a", "b")
{'as-many-as-you-like': ['a', 'b']}

Note that if self contains |, the arguments can be heterogenous:

>>> p = flag("verbose") | flag("quiet")
>>> p = p.many()
>>> p.parse_args("--verbose", "--quiet") # mix --verbose and --quiet
{'verbose': True, 'quiet': True}
many1(max=80)#

Applies self one or more times (like + in regexes).

Parameters

max (int) – Limits the number of times Parser.many() is applied in order to prevent a RecursionError. The default for this can be increased by either setting parser.MAX_MANY or the environment variable DOLLAR_LAMBDA_MAX_MANY.

Examples

>>> from dollar_lambda import argument, flag
>>> p = argument("1-or-more").many1()
>>> p.parse_args("1")
{'1-or-more': '1'}
>>> p.parse_args("1", "2")
{'1-or-more': ['1', '2']}
>>> p.parse_args()
usage: 1-OR-MORE [1-OR-MORE ...]
The following arguments are required: 1-or-more
n_times(n)#
>>> argument("a").n_times(0).parse_args()
{}
>>> argument("a").n_times(0).parse_args("b")
Unrecognized argument: b
>>> argument("a").n_times(1).parse_args()
usage: A
The following arguments are required: a
>>> argument("a").n_times(1).parse_args("b")
{'a': 'b'}
>>> argument("a").n_times(1).parse_args("b", "c")
usage: A
Unrecognized argument: c
>>> argument("a").n_times(2).parse_args("b")
usage: A A
The following arguments are required: a
>>> argument("a").n_times(2).parse_args("b", "c")
{'a': ['b', 'c']}
>>> argument("a").n_times(2).parse_args("b", "c", "d")
usage: A A
Unrecognized argument: d
nesting()#

Breaks the output of the wrapped parser into nested outputs. See the Nesting and grouping output section of the documentation for more information.

optional()#

Allows arguments to be optional:

>>> p1 = flag("optional")
>>> p = p1.optional()
>>> p.parse_args("--optional")
{'optional': True}
>>> p.parse_args("--misspelled", allow_unparsed=True)  # succeeds with no output
{}
>>> p1.parse_args("--misspelled")
usage: --optional
Expected '--optional'. Got '--misspelled'
parse(cs)#

Applies the parser to the input sequence cs.

parse_args(*args, allow_unparsed=False, check_help=True)#

The main way the user extracts parsed results from the parser.

Parameters
  • args (str) – A sequence of strings to parse. If empty, defaults to sys.argv[1:].

  • allow_unparsed (bool) – Whether to cause parser to fail if there are unparsed inputs. Note that setting this to false may cause unexpected behavior when using nonpositional() or Args.

  • check_help (bool) – Before running the parser, checks if the input string is --help or -h. If it is, returns the usage message.

Examples

>>> argument("a").parse_args("-h")
usage: A
>>> argument("a").parse_args("--help")
usage: A
classmethod return_(a)#

This method is required to make Parser a Monad. It consumes none of the input and always returns a as the result. For the most part, the user will not use this method unless building custom parsers.

>>> Parser.return_(Output.from_dict(some_key="some_value")).parse_args()
{'some_key': 'some_value'}
sat(predicate, on_fail)#

Applies parser, applies predicate to the result and fails if this returns false.

>>> from dollar_lambda import ArgumentError
>>> p = option("x", type=int).many().sat(
...     lambda out: sum(out.get.values()) > 0,
...     lambda out: ArgumentError(f"The values in {list(out.get.values())} must sum to more than 0."),
... )
>>> p.parse_args("-x", "-1", "-x", "1")  # fails
usage: [-x X ...]
The values in [-1, 1] must sum to more than 0.
>>> p.parse_args("-x", "-1", "-x", "2")  # succeeds
{'x': [-1, 2]}
Parameters
  • parser (Parser[Monoid]) – The parser to apply.

  • predicate (Callable[[Monoid], bool]) – The predicate to apply to the result of parser. Parser.sat() fails if this predicate returns false.

  • on_fail (Callable[[Monoid], ArgumentError]) – A function producing an ArgumentError to return if the predicate fails. Takes the output of parser as an argument.

type(f)#

A wrapper around Parser.apply() that simply applies f to the value of the most recently parsed input.

>>> p1 = option("x") >> option("y")
>>> p = p1.type(int)
>>> p.parse_args("-x", "1", "-y", "2")  # converts "1" but not "2"
{'x': '1', 'y': 2}
wrap_help(a=None)#

This checks for the --help or -h flag before applying parser. If either of the flags is present, returns the usage message for parser.

>>> p = flag("help", help="Print this help message.").wrap_help()
>>> p.parse_args("--help", check_help=False)  # true by default
usage: --help
help: Print this help message.
>>> p.parse_args("-h", check_help=False)  # true by default
usage: --help
help: Print this help message.

We can use Parser.wrap_help() to print partial usage messages, e.g. for subcommands:

>>> subcommand1 = matches("subcommand1") >> option("option1").wrap_help()
>>> subcommand2 = matches("subcommand2") >> option("option2")
>>> p = subcommand1 | subcommand2
>>> p.parse_args("subcommand1", "-h", check_help=False)
usage: --option1 OPTION1
>>> p.parse_args("subcommand2", "-h", check_help=False)
usage: [subcommand1 --option1 OPTION1 | subcommand2 --option2 OPTION2]
Expected 'subcommand1'. Got 'subcommand2'
classmethod zero(error=None)#

This parser always fails. This method is necessary to make Parser a Monoid.

Parameters

error (Optional[ArgumentError]) – Customize the error returned by Parser.zero().

Examples

>>> Parser.zero().parse_args()
zero
>>> Parser.zero().parse_args("a")
zero
>>> Parser.zero(error=ArgumentError("This is a test.")).parse_args("a")
This is a test.
exception dollar_lambda.parsers.SuccessError(usage: str, input: 'Sequence[str]', output: 'NonemptyList[Parse[A_monoid]]')#
dollar_lambda.parsers.apply(f, description)#

A shortcut for item(description).apply(f).

In contrast to Parser.apply(), this function spares f the trouble of outputting a Result object. Here is an example of usage. First we define a simple argument() parser:

>>> p1 = argument("foo")
>>> p1.parse_args("bar")
{'foo': 'bar'}

Here we use f to directly manipulate the binding generated by argument():

>>> from dollar_lambda import apply
>>> p2 = apply(lambda bar: Output.from_dict(**{bar + "e": bar + "f"}), description="baz")
>>> p2.parse_args("bar")
{'bare': 'barf'}
dollar_lambda.parsers.argument(dest, nesting=True, help=None, type=None)#

Parses a single word and binds it to dest. Useful for positional arguments.

Parameters
  • dest (str) – The name of variable to bind to:

  • nesting (bool) – If True, then the parser will split the parsed output on . yielding nested output. See Examples for more details.

  • help (Optional[str]) – The help message to display for the option:

  • type (Optional[Callable[[str], Any]]) – Use the type argument to convert the input to a different type:

Examples

>>> from dollar_lambda import argument
>>> argument("name").parse_args("Dante")
{'name': 'Dante'}
>>> argument("name").parse_args()
usage: NAME
The following arguments are required: name

Here are some examples that take advantage of nesting=True:

>>> argument("config.name").parse_args("-h")
usage: CONFIG.NAME
>>> argument("config.name").parse_args("Dante")
{'config': {'name': 'Dante'}}

You can disable this by setting nesting=False:

>>> argument("config.name", nesting=False).parse_args("Dante")
{'config.name': 'Dante'}
>>> (argument("config.first.name") >> argument("config.last.name")).parse_args("Dante", "Alighieri")
{'config': {'first': {'name': 'Dante'}, 'last': {'name': 'Alighieri'}}}
dollar_lambda.parsers.binary_usage(a, op, b, add_brackets=True)#

Utility for generating usage strings for binary operators.

dollar_lambda.parsers.defaults(**kwargs)#

Useful for assigning default values to arguments. It ignore the input and always returns kwargs converted into a Sequence of KeyValue pairs. defaults() never fails:

>>> from dollar_lambda import defaults
>>> defaults(a=1, b=2).parse_args()
{'a': 1, 'b': 2}
>>> (flag("fails") | defaults(fails="succeeds")).parse_args()
{'fails': 'succeeds'}

Here’s a more complex example derived from the tutorial:

>>> from dollar_lambda import nonpositional, flag, defaults, option
>>> p = nonpositional(
...     (
...         flag("verbose") + defaults(quiet=False)  # either --verbose and default "quiet" to False
...         | flag("quiet") + defaults(verbose=False)  # or --quiet and default "verbose" to False
...     ),
...     option("x", type=int, help="the base"),
...     option("y", type=int, help="the exponent"),
... )
...
>>> p.parse_args("-x", "1", "-y", "2", "--verbose")
{'x': 1, 'y': 2, 'verbose': True, 'quiet': False}
dollar_lambda.parsers.flag(dest, default=<dataclasses._MISSING_TYPE object>, help=None, nesting=True, regex=True, replace_dash=True, replace_underscores=True, short=True, string=None)#

Binds a boolean value to a variable.

>>> p = flag("verbose")
>>> p.parse_args("--verbose")
{'verbose': True}
Parameters
  • dest (str) – The variable to which the value will be bound.

  • default (bool | _MISSING_TYPE) – An optional default value.

  • help (Optional[str]) – An optional help string.

  • nesting (bool) – If True, then the parser will split the parsed output on . yielding nested output. See Examples for more details.

  • regex (bool) – If True, then the parser will use a regex to match the flag string.

  • replace_dash (bool) – If True, then the parser will replace - with _ in the dest string in order to make dest a valid Python identifier.

  • replace_underscores (bool) – If True, then the parser will replace _ with - in the flag string.

  • short (bool) – Whether to check for the short form of the flag, which uses a single dash and the first character of dest, e.g. -f for foo.

  • string (Optional[str]) – A custom string to use for the flag. Defaults to --{dest}.

Examples

Here is an example using the default parameter:

>>> p = flag("verbose", default=False)
>>> p.parse_args("-h")
usage: --verbose
verbose: (default: False)
>>> p.parse_args()
{'verbose': False}

By default flag fails when it does not receive expected input:

>>> p = flag("verbose")
>>> p.parse_args()
usage: --verbose
The following arguments are required: --verbose

Here is an example using the help parameter:

>>> p = flag("verbose", help="Turn on verbose output.")
>>> p.parse_args("-h")
usage: --verbose
verbose: Turn on verbose output.

Here is an example using the replace_underscores parameter:

>>> p = flag("hello_world", replace_underscores=False).parse_args("-h")
usage: --hello_world
>>> p = flag("hello_world", replace_underscores=True).parse_args("-h")
usage: --hello-world

Here is an example using the short parameter:

>>> flag("verbose", short=True).parse_args("-v")  # this is the default
{'verbose': True}
>>> flag("verbose", short=False).parse_args("-v")  # fails
usage: --verbose
Expected '--verbose'. Got '-v'

Here is an example using the string parameter:

>>> flag("value", string="v").parse_args("v")  # note that string does not have to start with -
{'value': True}
>>> flag("config.value").parse_args("--config.value")
{'config': {'value': True}}
dollar_lambda.parsers.item(name, usage_name=None)#

Parses a single word and binds it to dest. One of the lowest level building blocks for parsers.

Parameters

usage_name (Optional[str]) – Used for generating usage text

Examples

>>> from dollar_lambda import item
>>> p = item("name", usage_name="Your first name")
>>> p.parse_args("Alice")
{'name': 'Alice'}
>>> p.parse_args()
usage: name
The following arguments are required: Your first name
dollar_lambda.parsers.matches(s, peak=False, regex=True)#

Checks if the next word is s.

>>> from dollar_lambda import matches
>>> matches("hello").parse_args("hello")
{'hello': 'hello'}
>>> matches("hello").parse_args("goodbye")
usage: hello
Expected 'hello'. Got 'goodbye'
Parameters
  • s (str) – The word to that input will be checked against for equality.

  • peak (bool) – If False, then the parser will consume the word and return the remaining words as unparsed. If True, then the parser leaves the unparsed component unchanged.

  • regex (bool) – Whether to treat s as a regular expression. If False, then the parser will only succeed on string equality.

Examples

>>> p = matches("hello") >> matches("goodbye")
>>> p.parse_args("hello", "goodbye")
{'hello': 'hello', 'goodbye': 'goodbye'}

Look what happens when peak=True:

>>> p = matches("hello", peak=True) >> matches("goodbye")
>>> p.parse_args("hello", "goodbye")
usage: hello goodbye
Expected 'goodbye'. Got 'hello'

The first parser didn’t consume the word and so "hello" got passed on to equals("goodbye"). But this would work:

>>> p = matches("hello", peak=True) >> matches("hello") >> matches("goodbye")
>>> p.parse_args("hello", "goodbye")
{'hello': ['hello', 'hello'], 'goodbye': 'goodbye'}
dollar_lambda.parsers.nonpositional(*parsers, max=80, repeated=None)#

nonpositional() takes a sequence of parsers as arguments and attempts all permutations of them, returning the first permutations that is successful:

>>> from dollar_lambda import nonpositional, flag
>>> p = nonpositional(flag("verbose"), flag("quiet"))
>>> p.parse_args("--verbose", "--quiet")
{'verbose': True, 'quiet': True}
>>> p.parse_args("--quiet", "--verbose")  # reverse order also works
{'quiet': True, 'verbose': True}
Parameters
  • max (int) – Limits the number of times repeated is applied in order to prevent a RecursionError. The default for this can be increased by either setting parser.MAX_MANY or the environment variable DOLLAR_LAMBDA_MAX_MANY.

  • repeated (Optional[Parser[Sequence[Monoid]]]) – If provided, this parser gets applied repeatedly (zero or more times) at all positions.

Examples

>>> p = nonpositional(repeated=flag("x"))
>>> p.parse_args()
{}
>>> p.parse_args("-x")
{'x': True}
>>> p.parse_args("-x", "-x")
{'x': [True, True]}
>>> p = nonpositional(flag("y"), repeated=flag("x"))
>>> p.parse_args("-y")
{'y': True}
>>> p.parse_args("-y", "-x")
{'y': True, 'x': True}
>>> p.parse_args("-x", "-y")
{'x': True, 'y': True}
>>> p.parse_args("-y", "-x", "-x")
{'y': True, 'x': [True, True]}
>>> p.parse_args("-x", "-y", "-x")
{'x': [True, True], 'y': True}
>>> p.parse_args("-x", "-x", "-y")
{'x': [True, True], 'y': True}
>>> p = nonpositional(flag("y"), repeated=(flag("x") | flag("z")).ignore())
>>> p.parse_args("-x", "-y", "-z")
{'y': True}

Stress test:

>>> p = nonpositional(
...     flag("a", default=False),
...     flag("b", default=False),
...     flag("c", default=False),
...     flag("d", default=False),
...     flag("e", default=False),
...     flag("f", default=False),
...     flag("g", default=False),
... )
>>> p.parse_args("-g", "-f", "-e", "-d", "-c", "-b", "-a")
{'g': True, 'f': True, 'e': True, 'd': True, 'c': True, 'b': True, 'a': True}
>>> p.parse_args("-f", "-e", "-d", "-c", "-b", "-a")
{'f': True, 'e': True, 'd': True, 'c': True, 'b': True, 'a': True, 'g': False}
>>> p.parse_args("-e", "-d", "-c", "-b", "-a")
{'e': True, 'd': True, 'c': True, 'b': True, 'a': True, 'f': False, 'g': False}
>>> p.parse_args("-d", "-c", "-b", "-a")
{'d': True, 'c': True, 'b': True, 'a': True, 'e': False, 'f': False, 'g': False}
>>> p.parse_args("-c", "-b", "-a")
{'c': True, 'b': True, 'a': True, 'd': False, 'e': False, 'f': False, 'g': False}
>>> p.parse_args("-b", "-a")
{'b': True, 'a': True, 'c': False, 'd': False, 'e': False, 'f': False, 'g': False}
>>> p.parse_args("-a")
{'a': True, 'b': False, 'c': False, 'd': False, 'e': False, 'f': False, 'g': False}
>>> p.parse_args()
{'a': False, 'b': False, 'c': False, 'd': False, 'e': False, 'f': False, 'g': False}
dollar_lambda.parsers.option(dest, choices=None, default=<dataclasses._MISSING_TYPE object>, flag=None, help=None, nargs=1, nesting=True, regex=True, replace_dash=True, replace_underscores=True, short=True, type=<class 'str'>)#

Parses two words, binding the second to the first.

Parameters
  • dest (str) – The name of variable to bind to:

  • choices (Optional[List[str]]) – A list of valid values for the option.

  • default (Any | _MISSING_TYPE) – The default value to bind on failure:

  • flag (Optional[str]) – The flag to use for the option. If not provided, defaults to --{dest}.

  • help (Optional[str]) – The help message to display for the option:

  • nargs (int) – The number of space-separated arguments to parse for the option.

  • nesting (bool) – If True, then the parser will split the parsed output on . yielding nested output. See Examples for more details.

  • regex (bool) – If True, then the parser will match the flag string as a regex.

  • replace_dash (bool) – If True, then the parser will replace - with _ in the dest string in order to make dest a valid Python identifier.

  • replace_underscores (bool) – If True, then the parser will replace _ with - in the flag string.

  • short (bool) – Whether to check for the short form of the flag, which uses a single dash and the first character of dest, e.g. -c for count.

  • type (Callable[[str], Any]) – Use the type argument to convert the input to a different type:

Examples

>>> option("count").parse_args("--count", "1")
{'count': '1'}

In this example, you can see that the flag parameter allows the user to specify an arbitrary lead string, including one that doesn’t start with a dash.

>>> option("count", flag="ct").parse_args("ct", "1")
{'count': '1'}

This example demonstrates the use of the default parameter:

>>> p = option("count", default=2)
>>> p.parse_args("-h")
usage: --count COUNT
count: (default: 2)
>>> p.parse_args()
{'count': 2}

Here we specify a help-string using the help parameter:

>>> option("count", help="The number we should count to").parse_args("-h")
usage: --count COUNT
count: The number we should count to

Here is an example using the replace_underscores parameter:

>>> p = option("hello_world", replace_underscores=False).parse_args("-h")
usage: --hello_world HELLO_WORLD
>>> p = option("hello_world", replace_underscores=True).parse_args("-h")
usage: --hello-world HELLO_WORLD

This example demonstrates the difference between short=True and short=False:

>>> option("count", short=True).parse_args("-c", "1")
{'count': '1'}
>>> option("count", short=False).parse_args("-c", "1")
usage: --count COUNT
Expected '--count'. Got '-c'

As with argparse, the type argument allows you to convert the input to a different type using a function that takes a single string argument:

>>> option("x", type=int).parse_args("-x", "1")  # converts "1" to an int
{'x': 1}
>>> option("x", type=lambda x: int(x) + 1).parse_args("-x", "1")
{'x': 2}
>>> option("config.x").parse_args("--config.x", "a")
{'config': {'x': 'a'}}

You can optionally use = as a separator between the flag and the value: >>> option(“x”, type=int, default=1).parse_args(“-x=2”) {‘x’: 2}

The option can receive multiple arguments when nargs is greater than 1: >>> option(“x”, nargs=2).parse_args(“-x”, “1”, “2”) {‘x’: [‘1’, ‘2’]}

Outputs can be constrained to a set of values using the choices parameter: >>> option(“x”, choices=[“a”, “b”]).parse_args(“-x”, “a”) {‘x’: ‘a’} >>> option(“x”, choices=[“a”, “b”]).parse_args(“-x”, “c”) usage: -x {a,b} argument c: raised exception invalid choice: ‘c’. Choose from [‘a’, ‘b’].

dollar_lambda.parsers.peak(name, description=None)#

Bind the next word to a variable but keep that word in the input (so that other parsers can still see it).

Parameters
  • name (str) – The name to bind the variable to.

  • description (Optional[str]) – Used for usage message

dollar_lambda.parsers.sat(predicate, on_fail, name)#

A wrapper around Parser.sat() that uses item() to parse the argument and just applies predicate to the value output by item().

>>> from dollar_lambda import sat, ArgumentError
>>> p = sat(lambda x: len(x) == 1, lambda x: ArgumentError(f"'{x}' must have exactly one character."), "x")
>>> p.parse_args("a")  # succeeds
{'x': 'a'}
>>> p.parse_args("aa")  # fails
usage: x
'aa' must have exactly one character.
Parameters
  • predicate (Callable[[A], bool]) – The predicate to apply to the result of item(). sat() fails if this predicate returns false.

  • on_fail (Callable[[A], ArgumentError]) – A function producing an ArgumentError to return if the predicate fails. Takes the output of item() as an argument.

  • name (str) – The value to bind the result to.

dollar_lambda.parsers.sat_peak(predicate, on_fail, name)#

A convenience function that peaks at the next word using peak() and then checks if it satisfies the predicate.

Defines the @command decorator and the CommandTree class.

class dollar_lambda.decorators.CommandTree(_children=<factory>, _can_run=True)#

Allows parsers to dynamically dispatch their results based on the input. For usage details, see the CommandTree Tutorial.

command(can_run=True, flip_bools=True, help=None, parsers=None, repeated=None, replace_underscores=True)#

A decorator for adding a function as a child of this tree.

Parameters
  • can_run (bool) – Whether the parser will permit the decorated function to run if no further arguments are supplied.

  • flip_bools (bool) – Whether to add --no-<argument> before arguments that default to True.

  • help (dict) – A dictionary of help strings for the arguments.

  • parsers (Dict[str, Parser | List[Parser]]) – A dictionary reserving arguments for custom parsers. If the value is a list, the key must correspond to a variadic argument. See @command for examples.

  • repeated (Optional[Parser[Sequence[KeyValue[Any]]]]) – If provided, this parser gets applied repeatedly (zero or more times) at all positions.

  • replace_underscores (bool) – If true, underscores in argument names are replaced with dashes.

Examples

With flip_bools set to True:

>>> from dollar_lambda import CommandTree
>>> tree = CommandTree()
...
>>> @tree.command(flip_bools=True)
... def f1(b: bool = True):
...     return dict(f1=dict(b=b))
...
>>> tree("-h")
usage: --no-b
b: (default: True)

With flip_bools set to False:

>>> tree = CommandTree()
...
>>> @tree.command(flip_bools=False)
... def f1(b: bool = True):
...     return dict(f1=dict(b=b))
...
>>> tree("-h")
usage: -b
b: (default: True)

With can_run set to True (the default), we can run f1 by not passing arguments for the f1’s children:

>>> tree = CommandTree()
...
>>> @tree.command(can_run=True)  # <-
... def f1(b: bool):
...     return dict(f1=dict(b=b))
...
>>> @f1.command()
... def g1(n: int):
...     return dict(g1=dict(b=b, n=n))
...
>>> tree("-h")
usage: -b -n N
>>> tree("-b")
{'f1': {'b': True}}

With can_run set to False, the parser will fail if the child function arguments are not supplied:

>>> tree = CommandTree()
...
>>> @tree.command(can_run=False)  # <-
... def f1(b: bool):
...     return dict(f1=dict(b=b))
...
>>> @f1.command()
... def g1(n: int):
...     return dict(g1=dict(b=b, n=n))
...
>>> tree("-h")
usage: -b -n N
>>> tree("f1", "-b")
usage: -b -n N
Expected '-b'. Got 'f1'
subcommand(can_run=True, flip_bools=True, help=None, parsers=None, repeated=None, replace_underscores=True)#

A decorator for adding a function as a child of this tree. As a subcommand, the function’s name must be invoked on the command line for the function to be called.

Parameters
  • can_run (bool) – Whether the parser will permit the decorated function to run if no further arguments are supplied.

  • flip_bools (bool) – Whether to add --no-<argument> before arguments that default to True.

  • help (Dict[str, str]) – A dictionary of help strings for the arguments.

  • parsers (Dict[str, Parser | List[Parser]]) – A dictionary reserving arguments for custom parsers. If the value is a list, the key must correspond to a variadic argument. See @command for examples.

  • repeated (Optional[Parser[Sequence[KeyValue[Any]]]]) – If provided, this parser gets applied repeatedly (zero or more times) at all positions. See nonpositional for examples.

  • replace_underscores (bool) – If true, underscores in argument names are replaced with dashes.

Examples

With flip_bools set to True:

>>> tree = CommandTree()
...
>>> @tree.subcommand(flip_bools=True)
... def f1(b: bool = True):
...     return dict(f1=dict(b=b))
...
>>> tree("-h")
usage: f1 --no-b
b: (default: True)

With flip_bools set to False:

>>> tree = CommandTree()
...
>>> @tree.subcommand(flip_bools=False)
... def f1(b: bool = True):
...     return dict(f1=dict(b=b))
...
>>> tree("-h")
usage: f1 -b
b: (default: True)

With can_run set to True (the default), we can run f1 by not passing arguments for the f1’s children:

>>> tree = CommandTree()
...
>>> @tree.subcommand(can_run=True)  # <-
... def f1(b: bool):
...     return dict(f1=dict(b=b))
...
>>> @f1.subcommand()
... def g1(b: bool, n: int):
...     return dict(g1=dict(b=b, n=n))
...
>>> tree("-h")
usage: f1 -b g1 -n N
>>> tree("f1", "-b")
{'f1': {'b': True}}

With can_run set to False, the parser will fail if the child function arguments are not supplied:

>>> tree = CommandTree()
...
>>> @tree.subcommand(can_run=False)  # <-
... def f1(b: bool):
...     return dict(f1=dict(b=b))
...
>>> @f1.subcommand()
... def g1(b: bool, n: int):
...     return dict(g1=dict(b=b, n=n))
...
>>> tree("-h")
usage: f1 -b g1 -n N
>>> tree("f1", "-b")
usage: f1 -b g1 -n N
The following arguments are required: g1
dollar_lambda.decorators.command(flip_bools=True, help=None, parsers=None, repeated=None, replace_underscores=True)#

A succinct way to generate a simple nonpositional parser. @command derives the component parsers from the function’s signature and automatically executes the function with the parsed arguments, if parsing succeeds:

>>> from dollar_lambda import command
>>> @command(help=dict(a="something about a"))
... def f(a: int = 1, b: bool = False):
...     print(dict(a=a, b=b))
...
>>> f("-h")
usage: -a A -b
a: something about a (default: 1)
b: (default: False)
>>> f("-a", "2", "-b")
{'a': 2, 'b': True}

If the wrapped function receives no arguments (as in f()), the parser will take sys.argv[1:] as the input.

Parameters
  • flip_bools (bool) – For boolean arguments that default to true, this changes the flag from --{dest} to --no-{dest}:

  • help (dict[str, str]) – A dictionary of help strings for the arguments.

  • parsers (Dict[str, Parser | List[Parser]]) – A dictionary reserving arguments for custom parsers. If the value is a list, the key must correspond to a variadic argument.

  • repeated (Optional[Parser[Sequence[KeyValue[Any]]]]) – If provided, this parser gets applied repeatedly (zero or more times) at all positions.

  • replace_underscores (bool) – If true, underscores in argument names are replaced with dashes.

Examples

>>> @command()
... def f(cuda: bool = True):
...     return dict(cuda=cuda)
>>> f()
{'cuda': True}
>>> f("--no-cuda")  # flip_bools adds --no- to the flag
{'cuda': False}

As the following example demonstrates, when flip_bools=False output can be somewhat confusing:

>>> @command(flip_bools=False)
... def f(cuda: bool = True):
...     return dict(cuda=cuda)
>>> f("--cuda")
{'cuda': False}

Here is an example using the help parameter:

>>> @command(help=dict(quiet="Be quiet"))
... def f(quiet: bool):
...     return dict(quiet=quiet)
>>> f("--help")
usage: --quiet
quiet: Be quiet

Here is an example using the parsers parameter:

>>> from dollar_lambda import flag
>>> @command(parsers=dict(kwargs=(flag("dev") | flag("prod"))))
... def main(x: int, **kwargs):
...     print(dict(x=x, **kwargs))

This parser requires either a --dev or --prod flag and maps it to the kwargs argument:

>>> main("-h")
usage: -x X [--dev | --prod]
>>> main("-x", "1", "-dev")
{'x': 1, 'dev': True}
>>> main("-x", "1", "-prod")
{'x': 1, 'prod': True}
>>> main("-x", "1")
usage: -x X [--dev | --prod]
The following arguments are required: --dev

The Literal type can also be used to constrain arguments to a finite set of values: >>> @command() … def main(x: typing.Literal[“a”, “b”]): … print(dict(x=x))

This parser requires an argument of value "a" or "b": >>> main(“-x”, “a”) {‘x’: ‘a’}

# >>> main(“-x”, “b”) # {‘x’: ‘b’} # >>> main(“-x”, “c”) # usage: -x X # argument c: raised exception invalid choice: ‘c’. Choose from (‘a’, ‘b’).

dollar_lambda.decorators.parser(prefix=None, flip_bools=True, help=None, parsers=None, repeated=None, replace_underscores=True)#

Adds a .parser attribute to the wrapped function which can then be used in combination with other parsers. For examples, see: Grouping with the @parser decorator.

Parameters
  • prefix (Optional[str]) – This causes the variables bound by this parser to nest under the given prefix, as demonstrated in Grouping with the @parser decorator.

  • flip_bools (bool) – For boolean arguments that default to true, this changes the flag from --{dest} to --no-{dest}:

  • help (dict[str, str]) – A dictionary of help strings for the arguments.

  • parsers (Dict[str, Parser | List[Parser]]) – A dictionary reserving arguments for custom parsers. If the value is a list, the key must correspond to a variadic argument.

  • repeated (Optional[Parser[Sequence[KeyValue[Any]]]]) – If provided, this parser gets applied repeatedly (zero or more times) at all positions.

  • replace_underscores (bool) – If true, underscores in argument names are replaced with dashes.

Defines the Args dataclass and associated functions.

class dollar_lambda.args.Args#

Args is sugar for the nonpositional function and removes much of the boilerplate from defining parsers with many arguments.

>>> from dataclasses import dataclass
>>> from dollar_lambda import Args
>>> @dataclass
... class MyArgs(Args):
...     verbose: bool
...     count: int
>>> MyArgs.parse_args("--verbose", "--count", "1")
{'verbose': True, 'count': 1}

MyArgs will accept these arguments in any order:

>>> MyArgs.parse_args("--count", "1", "--verbose")
{'count': 1, 'verbose': True}

Note that when the default value of an argument is True, Args will, by default add --no- to the front of the flag (while still assigning the value to the original key):

>>> @dataclass
... class MyArgs(Args):
...     tests: bool = True
>>> MyArgs.parse_args("--no-tests")
{'tests': False}
>>> MyArgs.parse_args()
{'tests': True}

To suppress this behavior, set flip_bools=False:

>>> MyArgs.parse_args("--tests", flip_bools=False)
{'tests': False}

By using the Args.parser() method, Args can take advantage of all the same combinators as other parsers:

>>> from dollar_lambda import argument
>>> p = MyArgs.parser()
>>> p1 = p >> argument("a")
>>> p1.parse_args("--no-tests", "hello")
{'tests': False, 'a': 'hello'}

To supply other metadata, like help text or custom parsers, use field():

>>> from dollar_lambda import field, option
>>> @dataclass
... class MyArgs(Args):
...     x: int = field(default=0, help="a number")
...     y: int = field(
...         default=1,
...         parser=option("y", type=lambda s: int(s) + 1, help="a number to increment"),
...     )
>>> MyArgs.parse_args("-h")
usage: -x X -y Y
x: a number (default: 0)
y: a number to increment

This supplies defaults for y when omitted:

>>> MyArgs.parse_args("-x", "10")
{'x': 10, 'y': 1}

It also applies the custom type to y when "-y" is given

>>> MyArgs.parse_args()
{'y': 1, 'x': 0}
classmethod parse_args(*args, flip_bools=True, repeated=None)#

Parses the arguments and returns a dictionary of the parsed values.

classmethod parser(flip_bools=True, repeated=None, replace_underscores=True)#

Returns a parser for the dataclass. Converts each field to a parser (option or flag depending on its type). Combines these parsers using nonpositional.

Parameters
  • flip_bools (bool) – Whether to add --no-<argument> before arguments that default to True.

  • replace_underscores (bool) – If true, underscores in argument names are replaced with dashes.

Examples

>>> @dataclass
... class MyArgs(Args):
...     tests: bool = True

Note the leading --no-:

>>> MyArgs.parse_args("--no-tests")
{'tests': False}
>>> MyArgs.parse_args()
{'tests': True}

To suppress this behavior, set flip_bools=False:

>>> MyArgs.parse_args("--tests", flip_bools=False)
{'tests': False}
dollar_lambda.args.field(help=None, metadata=None, parser=None, **kwargs)#

This is a thin wrapper around dataclasses.field().

Parameters
Return type

A dataclasses.Field object that can be used in place of a default argument as described in the dataclasses.Field documentation.

Defines errors which can be raised by parsers.

exception dollar_lambda.errors.ArgumentError(usage: str)#
exception dollar_lambda.errors.BinaryError(usage: str, error1: dollar_lambda.errors.ArgumentError, error2: dollar_lambda.errors.ArgumentError)#
exception dollar_lambda.errors.ExceptionError(usage: str, exception: Exception)#
exception dollar_lambda.errors.HelpError(usage: str)#
exception dollar_lambda.errors.MissingError(usage: str, missing: str)#
exception dollar_lambda.errors.UnequalError(usage: str, left: A, right: A)#
exception dollar_lambda.errors.UnexpectedError(usage: str, unexpected: str)#
exception dollar_lambda.errors.ZeroError(usage: str)#