Tiramisu-cmdline-parser

This tutorial is intended to be a gentle introduction to Tiramisu command-line parser, a command-line parsing module that comes included with the Tiramisu’s library.

Note

There are a lot of other modules that fulfill the same task, namely getopt (an equivalent for getopt() from the C language) and argparse, from the python standard library.

tiramisu-cmdline-parser enables us to validate the command line, wich is a quite different scope – much more powerfull. It is a superset of the argparse module

What is Tiramisu-cmdline-parser ?

Tiramisu-cmdline-parser is a free project that turns Tiramisu’s Config into a command line interface.

It automatically generates arguments, help and usage messages. Tiramisu (or Tiramisu-API) validates all arguments provided by the command line’s user.

Tiramisu-cmdline-parser uses the well known argparse module and adds functionnalities upon it.

Installation

The best way is to use the python pip installer

And then type:

pip install tiramisu-cmdline-parser

Build a Tiramisu-cmdline-parser

Let’s show the sort of functionality that we are going to explore in this introductory tutorial.

We are going to start with a simple example, like making a proxy’s configuration script.

First we are going to build the corresponding Tiramisu config object:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
from tiramisu import IPOption, PortOption, BoolOption, ChoiceOption, DomainnameOption, \
                     URLOption, NetworkOption, NetmaskOption, \
                     SymLinkOption, OptionDescription, Leadership, Config


proxy_mode = ChoiceOption('proxy_mode', 'Proxy\'s config mode', ('No proxy',
                                                                 'Manual proxy configuration',
                                                                 'Automatic proxy configuration URL'),
                          properties=('positional', 'mandatory'))
http_ip_address = IPOption('http_ip_address', 'Proxy\'s HTTP IP', properties=('mandatory',))
http_ip_short = SymLinkOption('i', http_ip_address)
http_port = PortOption('http_port', 'Proxy\'s HTTP Port', default='8080', properties=('mandatory',))
http_port_short = SymLinkOption('p', http_port)
manual_proxy = OptionDescription('manual_proxy', 'Manual proxy settings', [http_ip_address, http_ip_short, http_port, http_port_short],
                                 requires=[{'option': proxy_mode, 'expected': 'Manual proxy configuration', 'action':'disabled', 'inverse':True}])

auto_config_url = URLOption('auto_config_url','Proxy\'s auto config URL', properties=('mandatory',))
auto_config_url_short = SymLinkOption('i', auto_config_url)
automatic_proxy = OptionDescription('automatic_proxy', 'Automatic proxy setting',
                                    [auto_config_url, auto_config_url_short],
                                    requires=[{'option': proxy_mode, 'expected': 'Automatic proxy configuration URL', 'action':'disabled', 'inverse': True}])

configuration = OptionDescription('configuration', None,
                                  [manual_proxy, automatic_proxy])

no_proxy_domain = DomainnameOption('no_proxy_domain', 'Domain names for which proxy will be desactivated', multi=True)
no_proxy_network = NetworkOption('no_proxy_network', 'Network addresses', multi=True)
no_proxy_network_short = SymLinkOption('n', no_proxy_network)
no_proxy_netmask = NetmaskOption('no_proxy_netmask', 'Netmask addresses', multi=True, properties=('mandatory',))
no_proxy_network_leadership = Leadership('no_proxy_network', 'Network for which proxy will be desactivated', [no_proxy_network, no_proxy_netmask])
no_proxy = OptionDescription('no_proxy', 'Disabled proxy',
                             [no_proxy_domain, no_proxy_network_leadership],
			     requires=[{'option': proxy_mode, 'expected': 'No proxy', 'action':'disabled'}, {'option': proxy_mode, 'expected': None, 'action':'disabled'}])

dns_over_https = BoolOption('dns_over_https', 'Enable DNS over HTTPS', default=False)

root = OptionDescription('proxy', 'Proxy parameters',
                         [proxy_mode, configuration, no_proxy, dns_over_https])

def display_name(option, dyn_name):
    return "--" + option.impl_getpath()

proxy_config = Config(root, display_name=display_name)
proxy_config.property.read_write()

Then we invopque the command line parsing library by creating a commandline parser, and we give the configuration’s object to it:

1
2
3
from tiramisu_cmdline_parser import TiramisuCmdlineParser
parser = TiramisuCmdlineParser(proxy_config)
parser.parse_args()

Finally pretty printing the configuration:

1
2
from pprint import pprint
pprint(proxy_config.value.dict())

Let’s display the help:

$ python3 src/proxy.py -h
usage: proxy.py [-h] [--dns_over_https] [--no-dns_over_https]
                {No proxy,Manual proxy configuration,Automatic proxy
                configuration URL}

positional arguments:
  {No proxy,Manual proxy configuration,Automatic proxy configuration URL}
                        Proxy's config mode

optional arguments:
  -h, --help            show this help message and exit
  --dns_over_https      Enable DNS over HTTPS
  --no-dns_over_https

Positional argument

First of all, we have to set the positional argument proxy_mode.

proxy_mode

As it’s a ChoiceOption, you only have three choices:

  • No proxy
  • Manual proxy configuration
  • Automatic proxy configuration URL

Set proxy_mode to No proxy:

$ python3 src/proxy.py "No proxy"
{'dns_over_https': False,
 'proxy_mode': 'No proxy'}

Requirements

Disabled options are not visible as arguments in the command line. Those parameters appears or disappears following the context:

$ python3 src/proxy.py "No proxy" -h
usage: proxy.py "No proxy" [-h] [--dns_over_https] [--no-dns_over_https]
                           {No proxy,Manual proxy configuration,Automatic
                           proxy configuration URL}

positional arguments:
  {No proxy,Manual proxy configuration,Automatic proxy configuration URL}
                        Proxy's config mode

optional arguments:
  -h, --help            show this help message and exit
  --dns_over_https      Enable DNS over HTTPS
  --no-dns_over_https

If proxy_mode is set to “Automatic proxy configuration URL”, some new options are visible:

$ python3 src/proxy.py "Automatic proxy configuration URL" -h
usage: proxy.py "Automatic proxy configuration URL" [-h] -i AUTO_CONFIG_URL
                                                    [--no_proxy.no_proxy_network.no_proxy_network [NO_PROXY_NETWORK [NO_PROXY_NETWORK ...]]]
                                                    [--no_proxy.no_proxy_network.pop-no_proxy_network INDEX]
                                                    --no_proxy.no_proxy_network.no_proxy_netmask
                                                    INDEX NO_PROXY_NETMASK
                                                    [--no_proxy.no_proxy_domain [NO_PROXY_DOMAIN [NO_PROXY_DOMAIN ...]]]
                                                    [--dns_over_https]
                                                    [--no-dns_over_https]
                                                    {No proxy,Manual proxy
                                                    configuration,Automatic
                                                    proxy configuration URL}

positional arguments:
  {No proxy,Manual proxy configuration,Automatic proxy configuration URL}
                        Proxy's config mode

optional arguments:
  -h, --help            show this help message and exit
  --dns_over_https      Enable DNS over HTTPS
  --no-dns_over_https

configuration.automatic_proxy:
  Automatic proxy setting

  -i AUTO_CONFIG_URL, --configuration.automatic_proxy.auto_config_url AUTO_CONFIG_URL
                        Proxy's auto config URL

no_proxy:
  Disabled proxy

  --no_proxy.no_proxy_domain [NO_PROXY_DOMAIN [NO_PROXY_DOMAIN ...]]
                        Domain names for which proxy will be desactivated

no_proxy.no_proxy_network:
  Network for which proxy will be desactivated

  --no_proxy.no_proxy_network.no_proxy_network [NO_PROXY_NETWORK [NO_PROXY_NETWORK ...]]
                        Network addresses
  --no_proxy.no_proxy_network.pop-no_proxy_network INDEX
  --no_proxy.no_proxy_network.no_proxy_netmask INDEX NO_PROXY_NETMASK
                        Netmask addresses

Arguments

Each option creates an argument. To change the value of this option, just launch the application with the appropriate argument:

$ python3 src/proxy.py "Manual proxy configuration" \
                       --configuration.manual_proxy.http_ip_address 192.168.1.1
{'configuration.manual_proxy.http_ip_address': '192.168.1.1',
 'configuration.manual_proxy.http_port': '8080',
 'configuration.manual_proxy.i': '192.168.1.1',
 'configuration.manual_proxy.p': '8080',
 'dns_over_https': False,
 'no_proxy.no_proxy_domain': [],
 'no_proxy.no_proxy_network.no_proxy_netmask': [],
 'no_proxy.no_proxy_network.no_proxy_network': [],
 'proxy_mode': 'Manual proxy configuration'}

Fullpath argument or named argument

By default, arguments are build with fullpath of option. The option http_ip_address is in manual_proxy optiondescription, which is also in configuration optiondescription. So the argument is --configuration.manual_proxy.http_ip_address:

$ python3 src/proxy.py "Manual proxy configuration" \
                       --configuration.manual_proxy.http_ip_address 192.168.1.1
{'configuration.manual_proxy.http_ip_address': '192.168.1.1',
 'configuration.manual_proxy.http_port': '8080',
 'configuration.manual_proxy.i': '192.168.1.1',
 'configuration.manual_proxy.p': '8080',
 'dns_over_https': False,
 'no_proxy.no_proxy_domain': [],
 'no_proxy.no_proxy_network.no_proxy_netmask': [],
 'no_proxy.no_proxy_network.no_proxy_network': [],
 'proxy_mode': 'Manual proxy configuration'}

If we set fullpath to False:

parser = TiramisuCmdlineParser(proxy_config, fullpath=False)

Arguments are build with the name of the option. The option http_ip_address is now :option`–http_ip_address`:

$ python3 src/proxy.py "Manual proxy configuration" \
                       --http_ip_address 192.168.1.1
{'configuration.manual_proxy.http_ip_address': '192.168.1.1',
 'configuration.manual_proxy.http_port': '8080',
 'configuration.manual_proxy.i': '192.168.1.1',
 'configuration.manual_proxy.p': '8080',
 'dns_over_https': False,
 'no_proxy.no_proxy_domain': [],
 'no_proxy.no_proxy_network.no_proxy_netmask': [],
 'no_proxy.no_proxy_network.no_proxy_network': [],
 'proxy_mode': 'Manual proxy configuration'}

Short argument

To have short argument, you just have to make SymLinkOption to this option:

1
2
http_ip_address = IPOption('http_ip_address', 'Proxy\'s HTTP IP', properties=('mandatory',))
http_ip_short = SymLinkOption('i', http_ip_address)

Now argument -i or --configuration.manual_proxy.http_ip_address can be used alternatively:

$ python3 src/proxy.py "Manual proxy configuration" \
                       --configuration.manual_proxy.http_ip_address 192.168.1.1
{'configuration.manual_proxy.http_ip_address': '192.168.1.1',
 'configuration.manual_proxy.http_port': '8080',
 'configuration.manual_proxy.i': '192.168.1.1',
 'configuration.manual_proxy.p': '8080',
 'dns_over_https': False,
 'no_proxy.no_proxy_domain': [],
 'no_proxy.no_proxy_network.no_proxy_netmask': [],
 'no_proxy.no_proxy_network.no_proxy_network': [],
 'proxy_mode': 'Manual proxy configuration'}


$ python3 src/proxy.py "Manual proxy configuration" \
                       -i 192.168.1.1
{'configuration.manual_proxy.http_ip_address': '192.168.1.1',
 'configuration.manual_proxy.http_port': '8080',
 'configuration.manual_proxy.i': '192.168.1.1',
 'configuration.manual_proxy.p': '8080',
 'dns_over_https': False,
 'no_proxy.no_proxy_domain': [],
 'no_proxy.no_proxy_network.no_proxy_netmask': [],
 'no_proxy.no_proxy_network.no_proxy_network': [],
 'proxy_mode': 'Manual proxy configuration'}

Be carefull, short argument have to be uniqe in the whole configuration.

Here -i argument is define a second time in same Config:

1
2
auto_config_url = URLOption('auto_config_url','Proxy\'s auto config URL', properties=('mandatory',))
auto_config_url_short = SymLinkOption('i', auto_config_url)

But http_ip_address and auto_config_url are not accessible together:

  • http_ip_address is visible only if proxy_mode is “Manual proxy configuration”
  • auto_config_url is only visible when proxy_mode is “Automatic proxy configuration URL”

Boolean argument

Boolean option creates two arguments:

  • –<boolean_name>: it activates (set to True) the option
  • –no-<boolean_name>: it deactivates (set to False) the option
$ python3 src/proxy.py "No proxy" \
                       --dns_over_https
{'dns_over_https': True,
 'proxy_mode': 'No proxy'}

$ python3 src/proxy.py "No proxy" \
                       --no-dns_over_https
{'dns_over_https': False,
 'proxy_mode': 'No proxy'}

Multi

Some values are multi. So we can set several value for this option.

For example, we can set serveral domain (cadoles.com and gnu.org) to “Domain names for which proxy will be desactivated” option:

$ python3 src/proxy.py "Automatic proxy configuration URL" \
                       --configuration.automatic_proxy.auto_config_url http://proxy.cadoles.com/proxy.pac \
                       --no_proxy.no_proxy_domain cadoles.com gnu.org
{'configuration.automatic_proxy.auto_config_url': 'http://proxy.cadoles.com/proxy.pac',
 'configuration.automatic_proxy.i': 'http://proxy.cadoles.com/proxy.pac',
 'dns_over_https': False,
 'no_proxy.no_proxy_domain': ['cadoles.com', 'gnu.org'],
 'no_proxy.no_proxy_network.no_proxy_netmask': [],
 'no_proxy.no_proxy_network.no_proxy_network': [],
 'proxy_mode': 'Automatic proxy configuration URL'}

Leadership

Leadership option are also supported. The leader option is a standard multi option. But follower option are not view as a multi option. Follower value are separate and we need to set index to set a follower option.

If we want to had two “Network for which proxy will be desactivated”:

  • 192.168.1.1/255.255.255.255
  • 192.168.0.0/255.255.255.0

We have to do:

$ python3 src/proxy.py "Automatic proxy configuration URL" \
                       --configuration.automatic_proxy.auto_config_url http://proxy.cadoles.com/proxy.pac \
                       --no_proxy.no_proxy_network.no_proxy_network 192.168.1.1 192.168.0.0 \
                       --no_proxy.no_proxy_network.no_proxy_netmask 0 255.255.255.255 \
                       --no_proxy.no_proxy_network.no_proxy_netmask 1 255.255.255.0
{'configuration.automatic_proxy.auto_config_url': 'http://proxy.cadoles.com/proxy.pac',
 'configuration.automatic_proxy.i': 'http://proxy.cadoles.com/proxy.pac',
 'dns_over_https': False,
 'no_proxy.no_proxy_domain': [],
 'no_proxy.no_proxy_network.no_proxy_netmask': ['255.255.255.255',
                                                '255.255.255.0'],
 'no_proxy.no_proxy_network.no_proxy_network': ['192.168.1.1', '192.168.0.0'],
 'proxy_mode': 'Automatic proxy configuration URL'}

We cannot reduce leader lenght:

$ python3 src/proxy.py "Automatic proxy configuration URL" \
                       --configuration.automatic_proxy.auto_config_url http://proxy.cadoles.com/proxy.pac \
                       --no_proxy.no_proxy_network.no_proxy_network 192.168.1.1 192.168.0.0 \
                       --no_proxy.no_proxy_network.no_proxy_netmask 0 255.255.255.255 \
                       --no_proxy.no_proxy_network.no_proxy_netmask 1 255.255.255.0 \
                       --no_proxy.no_proxy_network.no_proxy_network 192.168.1.1
usage: proxy.py -i "http://proxy.cadoles.com/proxy.pac" --no_proxy.no_proxy_network.no_proxy_network "192.168.1.1" "192.168.0.0" "Automatic proxy configuration URL"
       [-h] -i AUTO_CONFIG_URL
       [--no_proxy.no_proxy_network.no_proxy_network [NO_PROXY_NETWORK [NO_PROXY_NETWORK ...]]]
       [--no_proxy.no_proxy_network.pop-no_proxy_network INDEX]
       --no_proxy.no_proxy_network.no_proxy_netmask INDEX NO_PROXY_NETMASK
       [--no_proxy.no_proxy_domain [NO_PROXY_DOMAIN [NO_PROXY_DOMAIN ...]]]
       [--dns_over_https] [--no-dns_over_https]
       {No proxy,Manual proxy configuration,Automatic proxy configuration URL}
proxy.py: error: cannot reduce length of the leader "--no_proxy.no_proxy_network.no_proxy_network"

So an argument –pop-<leader> is automatically created. You need to specified index as parameter:

$ python3 src/proxy.py "Automatic proxy configuration URL" \
                       --configuration.automatic_proxy.auto_config_url http://proxy.cadoles.com/proxy.pac \
                       --no_proxy.no_proxy_network.no_proxy_network 192.168.1.1 192.168.0.0 \
                       --no_proxy.no_proxy_network.no_proxy_netmask 0 255.255.255.255 \
                       --no_proxy.no_proxy_network.pop-no_proxy_network 1
{'configuration.automatic_proxy.auto_config_url': 'http://proxy.cadoles.com/proxy.pac',
 'configuration.automatic_proxy.i': 'http://proxy.cadoles.com/proxy.pac',
 'dns_over_https': False,
 'no_proxy.no_proxy_domain': [],
 'no_proxy.no_proxy_network.no_proxy_netmask': ['255.255.255.255'],
 'no_proxy.no_proxy_network.no_proxy_network': ['192.168.1.1'],
 'proxy_mode': 'Automatic proxy configuration URL'}

Validation

All arguments are validated successively by argparser and Tiramisu:

$ python3 src/proxy.py "Automatic proxy configuration URL" \
                       --configuration.automatic_proxy.auto_config_url cadoles.com
usage: proxy.py "Automatic proxy configuration URL" [-h] -i AUTO_CONFIG_URL
                                                    [--no_proxy.no_proxy_network.no_proxy_network [NO_PROXY_NETWORK [NO_PROXY_NETWORK ...]]]
                                                    [--no_proxy.no_proxy_network.pop-no_proxy_network INDEX]
                                                    --no_proxy.no_proxy_network.no_proxy_netmask
                                                    INDEX NO_PROXY_NETMASK
                                                    [--no_proxy.no_proxy_domain [NO_PROXY_DOMAIN [NO_PROXY_DOMAIN ...]]]
                                                    [--dns_over_https]
                                                    [--no-dns_over_https]
                                                    {No proxy,Manual proxy
                                                    configuration,Automatic
                                                    proxy configuration URL}
proxy.py: error: "cadoles.com" is an invalid URL for "Proxy’s auto config URL", must start with http:// or https://

In error message, we have the option description (“Proxy’s auto config URL”) by default.

That why we redefined display_name function:

1
2
3
4
def display_name(option, dyn_name):
    return "--" + option.impl_getpath()

proxy_config = Config(root, display_name=display_name)

Now we have –<path> as description:

$ python3 src/proxy.py "Automatic proxy configuration URL" \
                       --configuration.automatic_proxy.auto_config_url cadoles.com
usage: proxy.py "Automatic proxy configuration URL" [-h] -i AUTO_CONFIG_URL
                                                    [--no_proxy.no_proxy_network.no_proxy_network [NO_PROXY_NETWORK [NO_PROXY_NETWORK ...]]]
                                                    [--no_proxy.no_proxy_network.pop-no_proxy_network INDEX]
                                                    --no_proxy.no_proxy_network.no_proxy_netmask
                                                    INDEX NO_PROXY_NETMASK
                                                    [--no_proxy.no_proxy_domain [NO_PROXY_DOMAIN [NO_PROXY_DOMAIN ...]]]
                                                    [--dns_over_https]
                                                    [--no-dns_over_https]
                                                    {No proxy,Manual proxy
                                                    configuration,Automatic
                                                    proxy configuration URL}
proxy.py: error: "cadoles.com" is an invalid URL for "--configuration.automatic_proxy.auto_config_url", must start with http:// or https://

Mandatory

Obviously the mandatory options are checked.

The positional argument is mandatory, so if we don’t set it, an error occured:

$ python3 src/proxy.py
usage: proxy.py [-h] [--dns_over_https] [--no-dns_over_https]
                {No proxy,Manual proxy configuration,Automatic proxy
                configuration URL}
proxy.py: error: the following arguments are required: proxy_mode

Others arguments are also check:

$ python3 src/proxy.py "Automatic proxy configuration URL"
usage: proxy.py "Automatic proxy configuration URL" [-h] -i AUTO_CONFIG_URL
                                                    [--no_proxy.no_proxy_network.no_proxy_network [NO_PROXY_NETWORK [NO_PROXY_NETWORK ...]]]
                                                    [--no_proxy.no_proxy_network.pop-no_proxy_network INDEX]
                                                    --no_proxy.no_proxy_network.no_proxy_netmask
                                                    INDEX NO_PROXY_NETMASK
                                                    [--no_proxy.no_proxy_domain [NO_PROXY_DOMAIN [NO_PROXY_DOMAIN ...]]]
                                                    [--dns_over_https]
                                                    [--no-dns_over_https]
                                                    {No proxy,Manual proxy
                                                    configuration,Automatic
                                                    proxy configuration URL}
proxy.py: error: the following arguments are required: --configuration.automatic_proxy.auto_config_url

Persistence configuration and mandatories validation

First of all, activate persistence configuration and remove mandatory validation:

1
2
3
4
from tiramisu import default_storage
default_storage.setting(engine='sqlite3')

proxy_config = Config(root, display_name=display_name, persistent=True, session_id='proxy')

We can disabled mandatory validation in parse_args function.

1
parser.parse_args(valid_mandatory=False)

In this case, we can store incomplete value:

$ python3 src/proxy_persistent.py 'Manual proxy configuration'
{'configuration.manual_proxy.http_ip_address': None,
 'configuration.manual_proxy.http_port': '8080',
 'configuration.manual_proxy.i': None,
 'configuration.manual_proxy.p': '8080',
 'dns_over_https': False,
 'no_proxy.no_proxy_domain': [],
 'no_proxy.no_proxy_network.no_proxy_netmask': [],
 'no_proxy.no_proxy_network.no_proxy_network': [],
 'proxy_mode': 'Manual proxy configuration'}

We can complete configuration after:

$ python3 src/proxy_persistent.py -i 192.168.1.1
{'configuration.manual_proxy.http_ip_address': '192.168.1.1',
 'configuration.manual_proxy.http_port': '8080',
 'configuration.manual_proxy.i': '192.168.1.1',
 'configuration.manual_proxy.p': '8080',
 'dns_over_https': False,
 'no_proxy.no_proxy_domain': [],
 'no_proxy.no_proxy_network.no_proxy_netmask': [],
 'no_proxy.no_proxy_network.no_proxy_network': [],
 'proxy_mode': 'Manual proxy configuration'}

When configuration is already set, help command, display already set options is usage ligne.

$ python3 src/proxy_persistent.py -h
usage: proxy_persistent.py -i "192.168.1.1" "Manual proxy configuration"
       [..]

Description and epilog

As argparser, description and epilog message can be added to the generated help:

parser = TiramisuCmdlineParser(proxy_config, description='New description!', epilog='New epilog!')
$ python3 src/proxy.py -h
usage: proxy.py [-h] [--dns_over_https] [--no-dns_over_https]
                {No proxy,Manual proxy configuration,Automatic proxy
                configuration URL}

New description!

positional arguments:
  {No proxy,Manual proxy configuration,Automatic proxy configuration URL}
                        Proxy's config mode

optional arguments:
  -h, --help            show this help message and exit
  --dns_over_https      Enable DNS over HTTPS
  --no-dns_over_https

New epilog!

By default, TiramisuCmdlineParser objects line-wrap the description and epilog texts in command-line help messages.

If there are line breaks in description or epilog, it automatically replace by a space. You need to change formatter class:

from argparse import RawDescriptionHelpFormatter
parser = TiramisuCmdlineParser(proxy_config, description='New description!\nLine breaks', epilog='New epilog!\nLine breaks', formatter_class=RawDescriptionHelpFormatter)
$ python3 src/proxy.py -h
usage: proxy.py [-h] [--dns_over_https] [--no-dns_over_https]
                {No proxy,Manual proxy configuration,Automatic proxy
                configuration URL}

New description!
Line breaks

positional arguments:
  {No proxy,Manual proxy configuration,Automatic proxy configuration URL}
                        Proxy's config mode

optional arguments:
  -h, --help            show this help message and exit
  --dns_over_https      Enable DNS over HTTPS
  --no-dns_over_https

New epilog!
Line breaks

Hide empty optiondescription

An empty optiondescription, is an optiondescription without any option (could have others optiondescriptions).

For example, configuration is an empty optiondescription:

1
2
configuration = OptionDescription('configuration', None,
                                  [manual_proxy, automatic_proxy])

This optiondescription doesn’t appears in help:

$ python3 src/proxy.py "No proxy" -h
usage: proxy.py "No proxy" [-h] [--dns_over_https] [--no-dns_over_https]
                           {No proxy,Manual proxy configuration,Automatic
                           proxy configuration URL}

positional arguments:
  {No proxy,Manual proxy configuration,Automatic proxy configuration URL}
                        Proxy's config mode

optional arguments:
  -h, --help            show this help message and exit
  --dns_over_https      Enable DNS over HTTPS
  --no-dns_over_https

This behavior is, in fact, due to two conditions:

  • there is no option
  • there is no description (None)

If we add description:

configuration = OptionDescription('configuration', 'Configuration',
                                  [manual_proxy, automatic_proxy])

This optiondescription is specified in help:

$ python3 src/proxy.py "No proxy" -h
usage: proxy.py "No proxy" [-h] [--dns_over_https] [--no-dns_over_https]
                           {No proxy,Manual proxy configuration,Automatic
                           proxy configuration URL}

positional arguments:
  {No proxy,Manual proxy configuration,Automatic proxy configuration URL}
                        Proxy's config mode

optional arguments:
  -h, --help            show this help message and exit
  --dns_over_https      Enable DNS over HTTPS
  --no-dns_over_https

configuration:
  Configuration

If you don’t want empty optiondescription even if there is a description, you could add remove_empty_od to True in parse_args function:

parser = TiramisuCmdlineParser(proxy_config, remove_empty_od=True)

SubConfig

Entire Config is transformed into an argument by default.

It could be interesting to display only ‘configuration’ OptionDescription.

To do this, we have to define default all mandatories options outside this scope:

proxy_mode = ChoiceOption('proxy_mode', 'Proxy\'s config mode', ('No proxy',
                                                                 'Manual proxy configuration',
                                                                 'Automatic proxy configuration URL'),
                          default='Manual proxy configuration',
                          properties=('positional', 'mandatory'))

Finally specified the root argument to TiramisuCmdlineParser:

parser = TiramisuCmdlineParser(proxy_config, root='configuration')

Now, only sub option of configuration is proposed:

$ python3 src/proxy.py -h
usage: proxy.py [-h] -i HTTP_IP_ADDRESS -p [HTTP_PORT]

optional arguments:
  -h, --help            show this help message and exit

configuration.manual_proxy:
  Manual proxy settings

  -i HTTP_IP_ADDRESS, --configuration.manual_proxy.http_ip_address HTTP_IP_ADDRESS
                        Proxy's HTTP IP
  -p [HTTP_PORT], --configuration.manual_proxy.http_port [HTTP_PORT]
                        Proxy's HTTP Port