CommandTree Tutorial
Contents
CommandTree
Tutorial#
CommandTree
has already shown up
in the
Highlights section
and in the Tutorial. In this section we will give
a more thorough treatment, exposing some of the underlying logic and
covering all the variations in functionality that CommandTree
offers.
CommandTree
draws inspiration from the
Click library.
CommandTree.subcommand
(discussed here)
closely approximates the functionality described in the
Commands and
Groups
section of the Click documentation.
CommandTree.command
#
First let’s walk through the use of the CommandTree.command
decorator, one step at a time. First we define the object:
>>> from dollar_lambda import CommandTree
>>> tree = CommandTree()
Now we define at least one child function:
>>> @tree.command()
... def f1(a: int):
... print(dict(f1=dict(a=a)))
CommandTree.command
automatically converts the function arguments
into a parser. We can run the parser and pass its output to our function
f1
by calling tree
:
>>> tree("-h")
usage: -a A
At this point the parser takes a single option -a
that binds an
int
to 'a'
:
>>> tree("-a", "1")
{'f1': {'a': 1}}
Note
Usually we would call tree
with no arguments, and it would get its
input from sys.argv[1:]
.
>>> import sys
>>> sys.argv[1:] = ["-a", "1"] # simulate command line
>>> parsers.TESTING = False # unnecessary outside doctests
>>> tree()
{'f1': {'a': 1}}
>>> parsers.TESTING = True
Now let’s add a second child function:
>>> @tree.command()
... def f2(b: bool):
... print(dict(f2=dict(b=b)))
...
>>> tree("-h")
usage: [-a A | -b]
tree
will execute either f1
or f2
based on which of the
parsers succeeds:
>>> tree("-a", "1") # executes ``f1``
{'f1': {'a': 1}}
>>> tree("-b") # executes ``f2``
{'f2': {'b': True}}
>>> tree() # fails
usage: [-a A | -b]
The following arguments are required: -a
Often in cases where there are alternative sets of argument like this, there is also a set of shared arguments. We can define a parent function to make our help text more concise and to allow the user to run the parent function when the child arguments are not provided.
>>> tree = CommandTree()
...
>>> @tree.command()
... def f1(a: int): # this will be the parent function
... return dict(f1=dict(a=a))
Now define a child function, g1
:
>>> @f1.command() # note f1, not tree
... def g1(a:int, b: bool):
... print(dict(g1=dict(b=b)))
Make sure to include all the arguments of f1
in g1
or else
g1
will fail when it is invoked. In its current state, tree
sequences the arguments of f1
and g1
:
>>> tree("-h")
usage: -a A -b
As before we can define an additional child function to induce alternative argument sets:
>>> @f1.command() # note f1, not tree
... def g2(a: int, c: str):
... print(dict(g2=dict(c=c)))
Note that our usage message shows -a A
preceding the brackets
because it corresponds to the parent function:
>>> tree("-h")
usage: -a A [-b | -c C]
To execute g1
, we give the -b
flag:
>>> tree("-a", "1", "-b")
{'g1': {'b': True}}
To execute g2
, we give the -c
flag:
>>> tree("-a", "1", "-c", "foo")
{'g2': {'c': 'foo'}}
Also, note that tree
can have arbitrary depth:
>>> @g1.command() # h1 is a child of g1
... def h1(a: int, b: bool, d: float):
... print(dict(h1=dict(d=d)))
Note the additional -d D
argument on the left side of the |
pipe:
>>> tree("-h")
usage: -a A [-b -d D | -c C]
That comes from the third argument of h1
.
CommandTree.subcommand
#
Often we want to explicitly specify which function to execute by naming
it on the command line. This would implement functionality similar to
argparse.ArgumentParser.add_subparsers()
or
click.Group
.
For this we would use the CommandTree.subcommand
decorator:
>>> tree = CommandTree()
...
>>> @tree.command()
... def f1(a: int):
... print(dict(f1=dict(a=a)))
...
>>> @f1.subcommand() # note subcommand, not command
... def g1(a:int, b: bool):
... print(dict(g1=dict(b=b)))
...
>>> @f1.subcommand() # again, subcommand, not command
... def g2(a: int, c: str):
... print(dict(g2=dict(c=c)))
Now the usage message indicates that g1
and g2
are required
arguments:
>>> tree("-h")
usage: -a A [g1 -b | g2 -c C]
>>> tree("-a", "1", "g1", "-b") # select g1
{'g1': {'b': True}}
>>> tree("-a", "1", "g2", "-c", "foo") # select g2
{'g2': {'c': 'foo'}}
You can freely mix and match CommandTree.subcommand
and CommandTree.command
:
>>> tree = CommandTree()
...
>>> @tree.command()
... def f1(a: int):
... print(dict(f1=dict(a=a)))
...
>>> @f1.subcommand()
... def g1(a:int, b: bool):
... print(dict(g1=dict(b=b)))
...
>>> @f1.command() # note command, not subcommand
... def g2(a: int, c: str):
... print(dict(g2=dict(c=c)))
Note that the left side of the pipe (corresponding to the g1
function) requires a "g1"
argument to run but the right side
(corresponding to the g2
function) does not:
>>> tree("-h")
usage: -a A [g1 -b | -c C]