Advanced features¶
The Command Line Interface created with clig can be customized in some ways.
Some of these ways are already provided by the
argparse module, but other
additional parameters can be used to add extra customization.
Parameters for clig.run() function¶
The first parameter of the clig.run() function is typically a
function that will be turned into a command. The second positional parameter
could be a
list of strings to pass to the commad inside the code
(which is defaulted to sys.argv).
On top of that, other parameters can be passed as keyword arguments. They are
the parameters of the original
ArgumentParser()
constructor and some new
extra parameters, as
detailed below.
Parameters of the original ArgumentParser() object¶
All of these parameters should be passed as keyword arguments to the
clig.run() function. Refer to the
original argparse documentation
for details. Some parameters has predefined values assumed by clig (which can
be modified), as detailed in the short descriptions below:
prog: The name of the new created program command. The default value is the name of the input function, with hyphens-replacing underscores_:
>>> import clig
...
>>> def my_program():
... """Short description"""
... pass
...
>>> clig.run(my_program, ["-h"])
usage: my-program [-h]
Short description
options:
-h, --help show this help message and exit
>>> clig.run(my_program, ["-h"], prog="myNewProgram")
usage: myNewProgram [-h]
Short description
options:
-h, --help show this help message and exit
description: A text to display before the arguments help. By default,cligtries to get this parameter as the first line of the function docstring, which can be customized.
>>> clig.run(my_program, ["-h"], description="The description of my program")
usage: my-program [-h]
The description of my program
options:
-h, --help show this help message and exit
epilog: A text to display after the command help. By default,cligtries to get this parameter from the function docstring after its first line, but this also can be customized.
>>> clig.run(my_program, ["-h"], epilog="Text displayed after, with additional info.")
usage: my-program [-h]
Short description
options:
-h, --help show this help message and exit
Text displayed after, with additional info.
Other
ArgumentParser()
parameters behave the same as in the original object. For instance, you can
change the
add_help parameter
to False (This parameter adds a -h/--help option to the command and the
default is True)
>>> clig.run(my_program, ["-h"], add_help=False)
usage: my-program
my-program: error: unrecognized arguments: -h
Extra parameters specific of the clig.run() function¶
The clig.run() function has some extra parameters that help to
customize the interface.
Metavar modifiers¶
The parameter metavarmodifier lets
you input a function that changes the
metavar keyword
argument for all command arguments. The defined function can receive the
argument name (not uppercased) and must return a string,
# ex01.py
import clig
def main(foo: str, bar: int = 32):
return locals()
clig.run(main, metavarmodifier=lambda name: f"<<{name}>>")
> python ex01.py -h
usage: main [-h] [--bar <<bar>>] <<foo>>
positional arguments:
<<foo>>
options:
-h, --help show this help message and exit
--bar <<bar>>
To specify different modifiers for positional and optional arguments, use
posmetavarmodifier and
optmetavarmodifier, which takes
precedence over metavarmodifier.
# ex02.py
import clig
def main(foo: str, bar: int = 32):
return locals()
clig.run(main, optmetavarmodifier=lambda s: f"<<<{s}>>>")
> python ex02.py -h
usage: main [-h] [--bar <<<bar>>>] foo
positional arguments:
foo
options:
-h, --help show this help message and exit
--bar <<<bar>>>
Help modifiers¶
Similarly to metavarmodifier,
helpmodifier lets you define functions
that change the help
keyword argument for all command arguments. The function should receive the
already set help
argument and return a new string.
This can be useful to include
format specifiers, already available in the original help
keyword argument, for example.
To specify different modifiers for positional and optional arguments, you can
use poshelpmodifier and
opthelpmodifier (which takes
precedence over helpmodifier).
# ex03.py
import clig
def myprogram(foo: str, bar: int = 32):
"""Summary
Args:
foo: Description of foo.
bar: Description of bar.
"""
return locals()
posmodifier = lambda h: "The '%(dest)s' argument of '%(prog)s'. " + h
optmodifier = lambda h: "The '%(dest)s' argument of '%(prog)s'. " + h + " Defaults to %(default)s"
clig.run(myprogram, poshelpmodifier=posmodifier, opthelpmodifier=optmodifier)
> python ex03.py -h
usage: myprogram [-h] [--bar BAR] foo
Summary
positional arguments:
foo The 'foo' argument of 'myprogram'. Description of foo.
options:
-h, --help show this help message and exit
--bar BAR The 'bar' argument of 'myprogram'. Description of bar. Defaults to 32
Help flags and messages¶
As you may know,
argparser’s objects add an
option by default, which simply displays the command’s help message (Normally
“-h, --help show this help message and exit”) that can be disabled with
add_help=False.
Occasionally, you may not want to disable the help option, but simply change its
flags or message: that can be achieved by disabling the help option and adding a
new function argument with parameter
action="help" in the
command line.
However, you may not want to add any new extra argument in the function to just
handle help messages, but still want to change them. For these cases, there are
two extra arguments, help_flags and
help_msg, which do exactly that: Set
different help flags or different help message.
# ex04.py
import clig
def main():
pass
clig.run(main, help_flags=["-?", "--show-help"])
> python ex04.py -?
usage: main [-?]
options:
-?, --show-help show this help message and exit
The parameter help_msg could be used as a
simple way to change the help message, maybe to a different language:
# ex05.py
import clig
def main():
pass
clig.run(main, help_msg="Diese Hilfe Meldung anzeigen und beenden")
> python ex05.py -h
usage: main [-h]
options:
-h, --help Diese Hilfe Meldung anzeigen und beenden
Version¶
Normally, you can add a new function argument with parameter
action="version" in
the command line, which expects a version= keyword argument in the
data() call, prints version
information and exits when invoked.
However, you may not want to add any new extra argument in the function to just
handle version information, but still want to use that feature. For these cases,
there the extra arguments: version,
versionmodifier and
version_msg.
# ex06.py
import clig
def my_program():
pass
clig.run(my_program, version='%(prog)s 2.0')
> python ex06.py -h
usage: my-program [-h] [--version]
options:
-h, --help show this help message and exit
--version show program's version number and exit
> python ex06.py --version
my-program 2.0
The version argument accepts a string like in
the original argparse
module (as shown above) but also accepts a boolean. If version=True, clig
tries to find the version information from the function’s package metadata.
# ex07.py
import clig
import yaml
clig.run(yaml.add_constructor, version=True)
> python ex07.py --version
6.0.3
Similarly to metavarmodifier and
helpmodifier,
versionmodifier lets you define a
function that change the version string. The function should receive the version
string and return a new string.
# ex08.py
import clig
import yaml
clig.run(yaml.add_constructor, version=True, versionmodifier=lambda s: f"addconstructor v{s}")
> python ex08.py --version
addconstructor v6.0.3
The option versionhelp lets you change
the default help message for the --version argument (the default is
show program's version number and exit).
# ex09.py
import clig
def main():
pass
clig.run(main, version="1.2.3", versionhelp="Show the AWESOME information about version!")
> python ex09.py --help
usage: main [-h] [--version]
options:
-h, --help show this help message and exit
--version Show the AWESOME information about version!
Automatic argument flags¶
As you may know, you can add extra flags (options with prefix, normally - or
--) to arguments
using the data() function in the argument annotation
(on the function signature). However, you may want to add/change argument flags
automatically, without touching the function signature. For these cases, you can
use the booleans make_flags or
make_shorts.
Using make_flags¶
Setting make_flags=True creates flags even for required arguments
# ex10.py
import clig
def main(foo: str, bar: int):
pass
clig.run(main, make_flags=True)
> python ex10.py -h
usage: main [-h] --foo FOO --bar BAR
options:
-h, --help show this help message and exit
--foo FOO
--bar BAR
For non-required arguments, make_flags=False turns them into required
arguments.
# ex11.py
import clig
def main(foo: str = "baz", bar: int = 42):
pass
clig.run(main, make_flags=False)
> python ex11.py -h
usage: main [-h] foo bar
positional arguments:
foo
bar
options:
-h, --help show this help message and exit
For non-required arguments, make_flags=True creates the regular flags from the
argument names in the cases where they are not present in the data.
# ex12.py
import clig
def main(
foo: clig.Arg[str, clig.data("--foobar")] = "baz",
bar: clig.Arg[int, clig.data("--barfoo")] = 42,
):
pass
clig.run(main, make_flags=True) # forces creation of --foo and --bar
> python ex12.py -h
usage: main [-h] [--foobar FOO] [--barfoo BAR]
options:
-h, --help show this help message and exit
--foobar FOO, --foo FOO
--barfoo BAR, --bar BAR
Using make_shorts¶
Setting make_shorts=True creates “short flags” only for the non-required
arguments.
# ex13.py
import clig
def main(foo: str, bar: int = 42):
pass
clig.run(main, make_shorts=True)
> python ex13.py -h
usage: main [-h] [-b BAR] foo
positional arguments:
foo
options:
-h, --help show this help message and exit
-b BAR, --bar BAR
To force the creation of “short flags” for required arguments, pass both
make_flags=True and make_shorts=True:
# ex14.py
import clig
def main(foo: str, bar: int = 42):
pass
clig.run(main, make_shorts=True, make_flags=True)
> python ex14.py -h
usage: main [-h] -f FOO [-b BAR]
options:
-h, --help show this help message and exit
-f FOO, --foo FOO
-b BAR, --bar BAR
The strategy to create “short flags” uses the following order:
First letter of the argument name (the simplest case)
First letter of the argument name UPPERCASED (when there are two names starting with same letter)
First letter of the each argument name part if it has TWO_PARTS (separated by underscores)
If the rules above produce ambiguous flags, the strategy starts searching again using first and second letters. If ambiguity is found again, shearch for first, second and third letters, and so on.
# ex15.py
import clig
def main(
file: str = ".",
filename: str = ".",
filedir: str = ".",
filepath: str = ".",
file_name: str = ".",
file_dir: str = ".",
file_path: str = ".",
folder: str = ".",
foldername: str = ".",
folderdir: str = ".",
folderpath: str = ".",
folder_name: str = ".",
folder_dir: str = ".",
folder_path: str = ".",
):
pass
clig.run(main, make_shorts=True)
> python ex15.py -h
usage: main [-h] [-f FILE] [-F FILENAME] [-fi FILEDIR] [-FI FILEPATH]
[-fn FILE_NAME] [-fd FILE_DIR] [-fp FILE_PATH] [-fo FOLDER]
[-FO FOLDERNAME] [-fol FOLDERDIR] [-FOL FOLDERPATH]
[-fona FOLDER_NAME] [-fodi FOLDER_DIR] [-fopa FOLDER_PATH]
options:
-h, --help show this help message and exit
-f FILE, --file FILE
-F FILENAME, --filename FILENAME
-fi FILEDIR, --filedir FILEDIR
-FI FILEPATH, --filepath FILEPATH
-fn FILE_NAME, --file-name FILE_NAME
-fd FILE_DIR, --file-dir FILE_DIR
-fp FILE_PATH, --file-path FILE_PATH
-fo FOLDER, --folder FOLDER
-FO FOLDERNAME, --foldername FOLDERNAME
-fol FOLDERDIR, --folderdir FOLDERDIR
-FOL FOLDERPATH, --folderpath FOLDERPATH
-fona FOLDER_NAME, --folder-name FOLDER_NAME
-fodi FOLDER_DIR, --folder-dir FOLDER_DIR
-fopa FOLDER_PATH, --folder-path FOLDER_PATH
Docstring templates¶
Getting information from docstrings is a central feature in clig.
The parameter docstring_template takes a “string template” and tries to match
it with the docstring given in the function.
The searched fields in the template are the following:
{{description}}{{epilog}}{{parameter_name}}{{parameter_description}}
After the first pair of {{parameter_name}}/{{parameter_description}}, the
pattern repeats.
Set this explicitly to skip auto-detection and enforce a specific format.
# ex16.py
import clig
my_template = """
{{description}}
Arguments:
* {{parameter_name}} -- {{parameter_description}}
"""
def main(a, b, c):
"""Summary of function
Arguments:
* a -- Description of parameter a
* b -- Description of parameter b
* c -- Description of parameter c
"""
pass
clig.run(main, docstring_template=my_template)
> python ex16.py -h
usage: main [-h] a b c
Summary of function
positional arguments:
a Description of parameter a
b Description of parameter b
c Description of parameter c
options:
-h, --help show this help message and exit
If not given, a list of default templates is
shearched by default. You can use a DocStr enum member that has
those default templates.
# ex17.py
import clig
def main(a, b, c):
"""Summary of function with Google style.
Args:
a: Description of parameter a
b: Description of parameter b
c: Description of parameter c
"""
pass
clig.run(main, docstring_template=clig.DocStr.GOOGLE_DOCSTRING_NOTYPES)
> python ex17.py -h
usage: main [-h] a b c
Summary of function with Google style.
positional arguments:
a Description of parameter a
b Description of parameter b
c Description of parameter c
options:
-h, --help show this help message and exit
Description, epilog and usage modifiers¶
Similarly to other modifiers
descriptionmodifier,
epilogmodifier and
usagemodifier let you define a function
that change the description,
epilog and
usage strings. The functions should receive
strings and return new strings.
# ex18.py
import clig
def main():
"""My awesome command description"""
pass
clig.run(main, descriptionmodifier=lambda s: f"{'-'*len(s)}\n{s}\n{'-'*len(s)}")
> python ex18.py -h
usage: main [-h]
------------------------------
My awesome command description
------------------------------
options:
-h, --help show this help message and exit
# ex19.py
import clig
def main(a, b, c):
"""This command had usage modified"""
pass
clig.run(main, usagemodifier=lambda s: s.replace("[", "[inserted after PROG and -h] ["))
> python ex19.py --help
usage: main [inserted after PROG and -h] [-h] a b c
This command had usage modified
positional arguments:
a
b
c
options:
-h, --help show this help message and exit
Keep prefix for keywords variadic arguments (**kwargs)¶
As described in the Userguide, unspecified arguments
passed with flags will be wrapped up in a dictionary of arbitrary keyword
arguments in the form **kwargs.
# ex20.py
import clig
def main(foo, **kwargs):
print(locals())
clig.run(main)
> python ex20.py bar --name adam -f myfile --title mister
{'foo': 'bar', 'kwargs': {'name': 'adam', 'f': 'myfile', 'title': 'mister'}}
With
keepvariadicprefixchars=True
the prefix chars (normally -- or -) will be preserved in the resulting
dictionary:
# ex21.py
import clig
def main(foo, **kwargs):
print(locals())
clig.run(main, keepvariadicprefixchars=True)
> python ex21.py bar --name adam -f myfile --title mister
{'foo': 'bar', 'kwargs': {'--name': 'adam', '-f': 'myfile', '--title': 'mister'}}
Calling clig.run() without a function¶
It is possible to call the clig.run() without any arguments, even without the
function argument. To do this, a Command instance must have been created
first, using the
clig.command() function as a function decorator.
# ex22.py
import clig
@clig.command
def main(foo: str, bar: int):
pass
clig.run()
> python ex22.py -h
usage: main [-h] foo bar
positional arguments:
foo
bar
options:
-h, --help show this help message and exit
Parameters for clig.Command() constructor¶
The clig.Command() constructor accepts
all arguments of the clig.run() function.
It also accepts some other arguments related to subcommands.
The first parameter of the clig.Command() constructor is typically a function
that will be turned into a command. Additionally, other parameters can be
passed. They are the
extra parameters of the clig.run() function,
parameters of the original
ArgumentParser() constructor,
some parameters of the
ArgumentParser.add_subparsers() method
(to control subcommands) and parameters of the
add_parser()
method, as detailed below.
The parameters of the original
ArgumentParser()
constructor can be passed in their original form (as positional or keyword
arguments). Some default values follow the
description in the previous section describing the clig.run() function.
The parameters of the original
ArgumentParser.add_subparsers() method
have to be passed only as keyword arguments, with names prefixed by
subcommands_ and has some default values, detailed in the following.
The parameters of the
original add_parser() method
might be passed to the methods that create subcommands
(new_subcommand(),
add_subcommand() and
end_subcommand()) and not directly to the
Command() constructor.
Parameters of the original add_subparsers() method¶
Except for some arguments (like
action and
dest) all parameter of
the
original ArgumentParser.add_subparsers() method
can be specified in the Command() constructor, for whose the
names are prepended by subcommands_. The supported parameters are the
following:
subcommands_title: title for the sub-parser group in help output. By default it is"subcommands"if adescriptionis provided, otherwise it uses the title forpositional arguments:, like the original behavior inargparse.
>>> from clig import Command
...
>>> def main():
... pass
...
>>> def foo():
... pass
...
>>> def bar():
... pass
...
>>> cmd = Command(main, subcommands_title="My subcommands")
>>> cmd.add_subcommand(foo)
>>> cmd.add_subcommand(bar)
...
>>> cmd.print_help()
usage: main [-h] {foo,bar} ...
options:
-h, --help show this help message and exit
My subcommands:
{foo,bar}
subcommands_description: description for the sub-commands group in help output. By default, it is not passed to the underlyingadd_subparsers()method. When eithersubcommands_titleorsubcommands_descriptionis present, the subcommands will appear in their own group in the help output. If onlydescriptionis provided,subcommands_titlewill be"subcommands"by default.
>>> from clig import Command
...
>>> def main():
... pass
...
>>> def foo():
... pass
...
>>> def bar():
... pass
...
>>> cmd = Command(main, subcommands_description="additional help")
>>> cmd.add_subcommand(foo)
>>> cmd.add_subcommand(bar)
...
>>> cmd.print_help()
usage: main [-h] {foo,bar} ...
options:
-h, --help show this help message and exit
subcommands:
additional help
{foo,bar}
Note
Like in argparse, when subcommands_description is provided, an empty line
is added between this description and the subcommands list, as you can see
above.
subcommands_prog: Usage information that will be displayed with subcommand help, by default the name of the program and any positional arguments before the subparser argument.subcommands_required: Whether or not a subcommand must be provided, by defaultFalse.subcommands_help: Help for sub-parser group in help output, by defaultNone.subcommands_metavar: String presenting available subcommands in help; by default it isNoneand presents subcommands in form{cmd1, cmd2, ..}.
The parameters of the original add_parser() method¶
The parameters of the original
add_parser()
method are passed only to the methods
new_subcommand(),
add_subcommand() or
end_subcommand(), not directly to the
Command() constructor. The following are supported:
name: Name of the subcommand, taken by theadd_parser()method. The default is generated from the function name.
>>> from clig import Command
...
>>> def main():
... pass
...
>>> def foobar():
... pass
...
>>> cmd = Command(main)
>>> sub = cmd.new_subcommand(foobar, name="bazham")
...
>>> cmd.print_help()
usage: main [-h] {bazham} ...
positional arguments:
{bazham}
options:
-h, --help show this help message and exit
help: A help message for the subcommand. By default, it is taken from the subcommand’s function description.
>>> from clig import Command
...
>>> def main():
... pass
...
>>> def foo():
... """Subcommand description"""
... pass
...
>>> cmd1 = Command(main)
>>> cmd1.new_subcommand(foo)
...
>>> cmd1.print_help()
usage: main [-h] {foo} ...
positional arguments:
{foo}
foo Subcommand description
options:
-h, --help show this help message and exit
>>> cmd2 = Command(main)
>>> cmd2.new_subcommand(foo, help="Overwritten help")
...
>>> cmd2.print_help()
usage: main [-h] {foo} ...
positional arguments:
{foo}
foo Overwritten help
options:
-h, --help show this help message and exit
aliases: List that allows multiple strings to refer to the same subcommand.
>>> from clig import Command
...
>>> def main():
... pass
...
>>> def foo():
... pass
...
>>> cmd1 = Command(main)
>>> cmd1.new_subcommand(foo, aliases=['fo', 'fooham'])
...
>>> cmd1.print_help()
usage: main [-h] {foo,fo,fooham} ...
positional arguments:
{foo,fo,fooham}
options:
-h, --help show this help message and exit
Calling clig.Command() without a function¶
It is possible to call the clig.Command() without any arguments, even without
the function argument. This may be usefull when you want to create an object not
associated with any function, and add subcommands after:
# ex23.py
from clig import Command
cmd = Command()
def foo():
pass
def bar():
pass
cmd.add_subcommand(foo).add_subcommand(bar).run()
> python ex23.py -h
usage: ex23.py [-h] {foo,bar} ...
positional arguments:
{foo,bar}
options:
-h, --help show this help message and exit