Nesting and grouping output#

Nesting output with the . character#

By default, introducing a . character into the name of an argument, option, or flag will induce nested output:

>>> from dollar_lambda import argument, option, flag
>>> argument("a.b", type=int).parse_args("1")
{'a': {'b': 1}}
>>> option("a.b", type=int).parse_args("--a.b", "1")
{'a': {'b': 1}}
>>> flag("a.b").parse_args("--a.b")
{'a': {'b': True}}

This mechanism handles collisions:

>>> (flag("a.b") >> flag("a.c")).parse_args("--a.b", "--a.c")
{'a': {'b': True, 'c': True}}

even when mixing nested and unnested output:

>>> (flag("a") >> flag("a.b")).parse_args("-a", "--a.b")
{'a': [True, {'b': True}]}

It can also go arbitrarily deep:

>>> (flag("a.b.c") >> flag("a.b.d")).parse_args("-a.b.c", "-a.b.d")
{'a': {'b': {'c': True, 'd': True}}}

This behavior can always be disabled by setting nesting=False (or just not using . in the name).

Grouping with the @parser decorator#

A common situation when processing command line arguments is to send one subset of arguments to one function, another subset to another, and so on. provides a convenient syntax for doing this.

Deriving an argument subset from a function#

Suppose f is the function that will require some subset of our command line arguments:

>>> from dollar_lambda import parser
>>> @parser()
... def f(a: int, b: float):
...     print("Running f with:", dict(a=a, b=b))

A function wrapped with @parser can still be called like normal:

>>> f(1, 2.0)
Running f with: {'a': 1, 'b': 2.0}

However, it now has a .parser property:

>>> f.parser.parse_args("-a", "1", "-b", "2.0")
{'a': 1, 'b': 2.0}

This parser can be used to easily feed arguments to f as follows:

>>> from dollar_lambda import command
>>> @command(parsers=dict(kwargs=f.parser))  # reserves kwargs for output of f.parser
... def main(c: bool, **kwargs: dict):
...     print("Running main with", dict(c=c))
...     f(**kwargs)
...
>>> main("-a", "1", "-b", "2.0", "-c")
Running main with {'c': True}
Running f with: {'a': 1, 'b': 2.0}

Deriving subsets of arguments from multiple functions#

Suppose we have a second function, f2:

>>> @parser()
... def f2(c: bool):
...     print("Running f2 with", dict(c=c))

This is one way we can bind both the f arguments and the f2 arguments to the kwargs argument:

>>> @command(parsers=dict(kwargs=[f.parser, f2.parser]))  # note the list
... def main(d: bool, **kwargs: dict):
...     print("Running main with", dict(d=d))
...     f(kwargs["a"], kwargs["b"])
...     f2(kwargs["c"])
...
>>> main("-a", "1", "-b", "2.0", "-c", "-d")
Running main with {'d': True}
Running f with: {'a': 1, 'b': 2.0}
Running f2 with {'c': True}

Note

We can rearrange the order of command line arguments as long as we don’t break up the function groups:

>>> main("-d", "-c", "-a", "1", "-b", "2.0")  # works
Running main with {'d': True}
Running f with: {'a': 1, 'b': 2.0}
Running f2 with {'c': True}

This fails because -c is between -a and -b:

>>> main("-d", "-a", "1", "-c", "-b", "2.0")
usage: -d -a A -b B -c
Expected '-b'. Got '-c'

Nesting @parser output#

If the functions have a lot of arguments, unpacking them like this will be cumbersome. Moreover, if they have conflicting namespaces, we will need another solution. To this end, we can use the nesting functionality that we discussed earlier.

Let’s add an argument to the @parser decorator:

>>> @parser("args")
... def f(a: int, b: float):
...     print("Running f with:", dict(a=a, b=b))

Now the parser output will be nested:

>>> f.parser.parse_args("--args.a", "1", "--args.b", "2.0")
{'args': {'a': 1, 'b': 2.0}}

This allows us to easily group arguments for multiple functions, even with conflicting namespaces:

>>> @parser("args2")  # nests f2's arguments under args2
... def f2(a: bool):
...     print("Running f2 with:", dict(a=a))
...
>>> @command(
...     parsers=dict(args=f.parser, args2=f2.parser)
... )  # these keys should match the @parser arguments
... def main(args: dict, args2: dict, a: bool):
...     print("Running main with", dict(a=a))
...     f(**args)
...     f2(**args2)
...
>>> main("-h")
usage: --args.a ARGS.A --args.b ARGS.B --args2.a -a
>>> main("--args.a", "1", "--args.b", "2.0", "--args2.a", "-a")
Running main with {'a': True}
Running f with: {'a': 1, 'b': 2.0}
Running f2 with: {'a': True}