{ "info": { "author": "igrek51", "author_email": "igrek51.dev@gmail.com", "bugtrack_url": null, "classifiers": [ "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3" ], "description": "# cliglue - glue for CLI\n[![GitHub version](https://badge.fury.io/gh/igrek51%2Fcliglue.svg)](https://github.com/igrek51/cliglue)\n[![PyPI version](https://badge.fury.io/py/cliglue.svg)](https://pypi.org/project/cliglue)\n[![Documentation Status](https://readthedocs.org/projects/cliglue/badge/?version=latest)](https://cliglue.readthedocs.io/en/latest/?badge=latest)\n[![Build Status](https://travis-ci.org/igrek51/cliglue.svg?branch=master)](https://travis-ci.org/igrek51/cliglue)\n[![Coverage Status](https://coveralls.io/repos/github/igrek51/cliglue/badge.svg?branch=master)](https://coveralls.io/github/igrek51/cliglue?branch=master)\n\n\n`cliglue` is a declarative parser for command line interfaces in Python.\nIt's a binding glue between CLI shell arguments and functions being invoked.\n\n`cliglue` parses and validates command line arguments provided by user when running console application.\nThen it automatically triggers matched action, based on the declared Command-Line Interface rules, injecting all needed parameters.\nYou don't need to write the \"glue\" code for binding & parsing parameters every time.\nSo it makes writing console aplications faster and simpler.\n\n## Features\n- [Auto-generated help and usage](https://cliglue.readthedocs.io/en/latest#auto-generated-help) (`--help`)\n- [Shell autocompletion](https://cliglue.readthedocs.io/en/latest#auto-completion) (getting most relevant hints on hitting `Tab`)\n- [Multilevel sub-commands](https://cliglue.readthedocs.io/en/latest#sub-commands) (e.g. `git remote add ...` syntax)\n- [Named parameters](https://cliglue.readthedocs.io/en/latest#named-parameters): supporting both `--name value` and `--name=value`, multiple parameter occurrences\n- [Flags](https://cliglue.readthedocs.io/en/latest#flags): supporting both short (`-f`) and long (`--force`), combining short flags (`-tulpn`), multiple flag occurrences (`-vvv`)\n- [Positional arguments](https://cliglue.readthedocs.io/en/latest#positional-arguments) (e.g. `git push `)\n- [Key-value dictionaries](https://cliglue.readthedocs.io/en/latest#dictionaries) (e.g. `--config key value`)\n- [Invoking matched action function & injecting parameters](https://cliglue.readthedocs.io/en/latest#injecting-parameters)\n- [Custom type validators / parsers](https://cliglue.readthedocs.io/en/latest#custom-type-parsers)\n- [Custom auto-completers](https://cliglue.readthedocs.io/en/latest#custom-completers) (providers of possible values)\n- [Handling syntax errors, parameters validation](https://cliglue.readthedocs.io/en/latest#errors-handling)\n- [Typed values](https://cliglue.readthedocs.io/en/latest#data-types) (int, time, date, file, etc.)\n- [Standard options](https://cliglue.readthedocs.io/en/latest#clibuilder) enabled by default (`--help`, `--version`)\n- [Declarative CLI builder](https://cliglue.readthedocs.io/en/latest#cli-rules-cheatsheet)\n\n## Quick start\nLet's create simple command-line application using `cliglue`.\nLet's assume we already have a function as follows:\n```python\ndef say_hello(name: str, reverse: bool, repeat: int):\n if reverse:\n name = name[::-1]\n print(f'Hello {name}.' * repeat)\n```\nand we need a \"glue\" which binds it with a CLI (Command-Line Interface).\nWe want it to be run with different parameters provided by user to the terminal shell in a manner:\n`./hello.py WORLD --reverse --repeat=1`.\nWe've just identified one positional argument, one flag and one numerical parameter.\nSo our CLI definition may be declared using `cliglue`:\n```python\nCliBuilder('hello-app', run=say_hello).has(\n argument('name'),\n flag('reverse'),\n parameter('repeat', type=int, default=1),\n)\n```\nGetting all together, we've binded our function with a Command-Line Interface:\n\n**hello.py**:\n```python\n#!/usr/bin/env python3\nfrom cliglue import CliBuilder, argument, flag, parameter\n\ndef say_hello(name: str, reverse: bool, repeat: int):\n if reverse:\n name = name[::-1]\n print(f'Hello {name}.' * repeat)\n\nCliBuilder('hello-app', run=say_hello).has(\n argument('name'),\n flag('reverse'),\n parameter('repeat', type=int, default=1),\n).run()\n```\n\nLet's trace what is happening here:\n\n- `CliBuilder` builds CLI tree for entire application.\n- `'hello-app'` is a name for that application to be displayed in help output.\n- `run=say_hello` sets default action for the application. Now a function `say_hello` is binded as a main action and will be invoked if no other action is matched.\n- `.has(...)` allows to embed other rules inside that builder. Returns `CliBuilder` itself.\n- `argument('name')` declares positional argument. From now, first CLI argument (after binary name) will be recognized as `name` variable.\n- `flag('reverse')` binds `--reverse` keyword to a flag named `reverse`. So as it may be used later on.\n- `parameter('repeat', type=int, default=1)` binds `--repeat` keyword to a parameter named `repeat`, which type is `int` and its default value is `1`.\n- Finally, invoking `.run()` does all the magic.\nIt gets system arguments list, starts to process them and invokes relevant action.\n\n### Help / Usage\n`CliBuilder` has some basic options added by default, like `--help` or `--version`.\nThus, you can check the usage by running application with `--help` flag:\n```console\nfoo@bar:~$ ./hello.py --help\nhello-app\n\nUsage:\n ./hello.py [OPTIONS] NAME\n\nOptions:\n -h, --help [SUCOMMANDS...] - Display this help and exit\n --reverse \n --repeat REPEAT \n```\n\nNotice there are already rules being displayed, which were declared before:\n\n- positional argument `name`: `./hello.py [OPTIONS] NAME`\n- flag `reverse`: `--reverse`\n- parameter `repeat`: `--repeat REPEAT`\n\n### Injecting parameters\nNow when we execute our application with one argument provided, we get:\n```console\nfoo@bar:~$ ./hello.py world\nHello world.\n```\nNote that `world` has been recognized as `name` argument.\nWe've binded `say_hello` as a default action, so it has been invoked with particular parameters:\n```python\nsay_hello(name='world', reverse=False, repeat=1)\n```\n\n- positional argument `name` has been assigned a `'world'` value.\n- flag `reverse` was not given, so it's `False` by default.\n- parameter `repeat` was not given either, so it was set to its default value `1`.\n\nLet's provide all of the parameters explicitly, then we get:\n```console\nfoo@bar:~$ ./hello.py --reverse world --repeat 2\nHello dlrow.Hello dlrow.\n```\nOr we can do the same in a different way:\n```console\nfoo@bar:~$ ./hello.py world --repeat=2 --reverse\nHello dlrow.Hello dlrow.\n```\n\nWhen you are writing function for your action and you need to access some of the variables (flags, parameters, arguments, etc.),\njust simply add a parameter to the function with a name same as the variable you need.\nThen, the proper value will be injected by `cliglue`.\n\n## How does it work?\n1. You define all required CLI rules for your program in a declarative tree.\n2. User provides command-line arguments when running program in a shell.\n3. `cliglue` parses and validates all the parameters, flags, sub-commands, positional arguments, etc, and stores them internally.\n4. `cliglue` finds the most relevant action (starting from the most specific) and invokes it.\n5. When invoking a function, `cliglue` injects all its needed parameters based on the previously defined & parsed values.\n\nYou only need to bind the keywords to the rules and `cliglue` will handle all the rest for you.\n\n## `cliglue` vs `argparse`\nWhy to use `cliglue`, since we already have Python `argparse`? Here are some subjective advantages of `cliglue`:\n\n- declarative way of CLI logic in one place,\n- autocompletion out of the box,\n- easier way of building multilevel sub-commands,\n- automatic action binding & injecting arguments, no need to pass `args` to functions manually,\n- CLI logic separated from the application logic, \n- simpler & concise CLI building - when reading the code, it's easier to distinguish particular CLI rules between them (i.e. flags from positional arguments, parameters or sub-commands),\n- CLI definition code as a clear documentation.\n\n### Migrating from `argparse` to `cliglue`\n\n#### Migrating: Basic CLI\nargparse:\n```python\nimport argparse\n\nparser = argparse.ArgumentParser(description='Program description')\n[here come the rules...]\nargs = parser.parse_args()\ndo_something(args)\n```\ncliglue:\n```python\nfrom cliglue import CliBuilder\n\nCliBuilder(help='Program description', run=do_something).has(\n [here come the rules...]\n).run()\n```\n\n#### Migrating: Flags\nargparse:\n```python\nparser.add_argument(\"-v\", \"--verbose\", help=\"increase output verbosity\", action=\"store_true\")\n```\ncliglue:\n```python\nflag(\"-v\", \"--verbose\", help=\"increase output verbosity\"),\n```\n\n#### Migrating: Positional arguments\nargparse:\n```python\nparser.add_argument(\"square\", help=\"display a square of a given number\", type=int)\n```\ncliglue:\n```python\nargument(\"square\", help=\"display a square of a given number\", type=int),\n```\n\n#### Migrating: Sub-commands\nargparse:\n```python\ndef foo(args):\n print(args.x * args.y)\n\ndef bar(args):\n print(args.z)\n\n\nparser = argparse.ArgumentParser()\nsubparsers = parser.add_subparsers()\n\nparser_foo = subparsers.add_parser('foo', help='foo help')\nparser_foo.add_argument('-x', type=int, default=1)\nparser_foo.add_argument('y', type=float)\nparser_foo.set_defaults(func=foo)\n\nparser_bar = subparsers.add_parser('bar', help='bar help')\nparser_bar.add_argument('z')\nparser_bar.set_defaults(func=bar)\n\nargs = parser.parse_args()\nargs.func(args)\n```\ncliglue:\n```python\ndef foo(x, y):\n print(x * y)\n\ndef bar(z):\n print(z)\n\n\nCliBuilder().has(\n subcommand('foo', help='foo help', run=foo).has(\n parameter('-x', type=int, default=1),\n argument('y', type=float),\n ),\n subcommand('bar', help='bar help', run=bar).has(\n argument('z'),\n ),\n).run()\n```\n\n#### Migrating: Transferring values to functions\nargparse:\n```python\ndo_action(args.square, args.verbose)\n```\ncliglue:\n```python\nCliBuilder(run=do_action) # invoking actions is done automatically by binding\n```\n\n## Installation\n### Step 1. Prerequisites\n- Python 3.6 (or newer)\n- pip\n#### on Debian 10 (buster)\n```bash\nsudo apt install python3.7 python3-pip\n```\n#### on Debian 9 (stretch)\nUnfortunately, Debian stretch distribution does not have Python 3.6+ in its repositories, but it can be compiled from the source:\n```bash\nwget https://www.python.org/ftp/python/3.6.9/Python-3.6.9.tgz\ntar xvf Python-3.6.9.tgz\ncd Python-3.6.9\n./configure --enable-optimizations --with-ensurepip=install\nmake -j8\nsudo make altinstall\n```\n#### on Ubuntu 18\n```bash\nsudo apt install python3.6 python3-pip\n```\n#### on Centos 7\n```bash\nsudo yum install -y python36u python36u-libs python36u-devel python36u-pip\n```\n#### on Fedora\n```bash\nsudo dnf install python36\n```\n\n### Step 2. Install package using pip\nInstall package from [PyPI repository](https://pypi.org/project/cliglue) using pip:\n```bash\npip3 install cliglue\n```\nOr using explicit python version:\n```bash\npython3.6 -m pip install cliglue\n```\n### Install package in develop mode\nYou can install package in develop mode in order to make any changes for your own:\n```bash\npip3 install -r requirements.txt\npython3 setup.py develop\n```\n\n## Testing\nRunning tests:\n```bash\npip3 install -r requirements.txt -r requirements-dev.txt\n./pytest.sh\n```\n\n## CliBuilder\n`CliBuilder` is a main class of `cliglue` package which allows to build CLI definition.\nIt's a builder for Command Line Interface specification.\nAfter that, you can invoke `.run()` method in order to parse provided arguments and invoke particular actions.\n\nEmpty CliBuilder has standard options enabled by default:\n\n- `--help` - displaying usage and help\n- `--version` - displaying application version number (if it has been defined)\n\n### Step 1. Creating CliBuilder\nIn this step you can create new `CliBuilder` and set a custom configuration for it.\n The constructor is as follows:\n```python\nfrom cliglue import CliBuilder\n\nCliBuilder(\n name: Optional[str] = None,\n version: Optional[str] = None,\n help: Optional[str] = None,\n run: Optional[Action] = None,\n with_defaults: bool = True,\n usage_onerror: bool = True,\n reraise_error: bool = False,\n hide_internal: bool = True,\n)\n```\n\n`name` - name of the application for which the CLI is built\n\n`version` - application version (displayed in help/version output)\n\n`help` - short description of application\n\n`run` - reference for a function which should be the default action for empty arguments list\n\n`with_defaults` - whether default rules and actions should be added.\nDefaults options are:\n-h, --help: displaying help,\n--version: displaying version,\n--bash-install APP-NAME: installing application in bash with autocompleting,\n--bash-autocomplete [CMDLINE...]: internal action for generating autocompleted proposals to be handled by bash\n\n`usage_onerror` - wheter usage output should be displayed on syntax error\n\n`reraise_error` - wheter syntax error should not be caught but reraised instead.\nEnabling this causes stack trace to be flooded to the user.\n\n`hide_internal` - wheter internal options (`--bash-install`, `--bash-autocomplete`) should be hidden on help output.\n\n### Step 2. Declaring CLI rules\nThe next step is to declare CLI rules for `CliBuilder` using `.has()` method\n\n`has(*subrules: CliRule) -> 'CliBuilder'` method receives a CLI rules in its parameters and returns the `CliBuilder` itself for further building.\nIt is used to introduce the next level of sub-rules.\n\nAvailable rules are:\n\n- subcommand\n- flag\n- parameter\n- argument\n- arguments\n- default_action\n- primary_option\n\nExample:\n```python\nfrom cliglue import CliBuilder, argument, parameter, flag, subcommand, arguments, default_action\n\nCliBuilder('multiapp', version='1.0.0', help='many apps launcher',\n with_defaults=True, usage_onerror=False, reraise_error=True).has(\n subcommand('checkout'),\n argument('commit'),\n arguments('files'),\n flag('-u', '--upstream', help='set upstream'),\n parameter('--count', type=int, required=True),\n default_action(lambda: print('default action')),\n)\n```\n\n### Step 3. Running CLI arguments through parser\nThe final step is calling `.run()` on `CliBuilder`.\nIt parses all the CLI arguments passed to application.\nThen it invokes triggered action which were defined before.\nIf actions need some parameters, they will be injected based on the parsed arguments.\n\nRunning empty builder:\n```python\nfrom cliglue import CliBuilder\n\nCliBuilder().run()\n```\njust prints the standard help output, because it's the default action for an empty builder if no arguments are provided.\n\n## Sub-commands\nCommands may form a multilevel tree with nested sub-commands.\nSub-commands syntax is commonly known, e.g.:\n\n- `git remote rename ...`\n- `docker container ls`\n- `nmcli device wifi list`\n- `ip address show`\n\nSub-commands split the CLI into many nested CLI levels, forming a tree.\nThey decide where to direct the parser, which seeks for a most relevant action to invoke and decides which rules are active.\n\nSub-commands create nested levels of sub-parsers, which not only may have different actions but also contains different CLI rules, such as named parameters, flags or other sub-commands, which are only enabled when parent command is enabled as well.\nSubcommand can have more subrules which are activated only when corresponding subcommand is active.\nSo subcommand is just a keyword which narrows down the context.\n\n### Sub-commands specification\nIn order to create subcommand rule specification, use:\n```python\nfrom cliglue import subcommand\n\nsubcommand(\n *keywords: str,\n run: Optional[Action] = None,\n help: str = None,\n)\n```\n\n`keywords` - possible keyword arguments which any of them triggers a subcommand\n\n`run` - optional action to be invoked when subcommand is matched\n\n`help` - description of the parameter displayed in help output\n\n### Nesting sub-commands\nWith sub-commands, you can nest other CLI rules.\nThey will be active only when corresponding subcommand is active.\n\nSubrules can be nested using `.has(*subrules: CliRule)` method.\nIt returns itself for further building, so it can be used just like `CliBuilder`:\n```python\nfrom cliglue import CliBuilder, argument, subcommand\n\nCliBuilder().has(\n subcommand('nmcli').has(\n subcommand('device').has(\n subcommand('wifi').has(\n subcommand('list'),\n ),\n ),\n ),\n subcommand('ip').has(\n subcommand('address', 'a').has(\n subcommand('show'),\n subcommand('del').has(\n argument('interface'),\n ),\n ),\n ),\n)\n```\nIn that manner, the formatted code above is composing a visual tree, which is clear.\n\n### Sub-commands example: subcommands.py\n```python\n#!/usr/bin/env python3\nfrom cliglue import CliBuilder, subcommand\n\nCliBuilder('subcommands-demo', run=lambda: print('default action')).has(\n subcommand('remote', run=lambda: print('action remote')).has(\n subcommand('push', run=lambda: print('action remote push')),\n subcommand('rename', run=lambda: print('action remote rename')),\n ),\n subcommand('checkout', run=lambda: print('action checkout')),\n subcommand('branch', run=lambda: print('action branch')),\n).run()\n```\nUsage is quite self-describing:\n```console\nfoo@bar:~$ ./subcommands.py remote\naction remote\nfoo@bar:~$ ./subcommands.py remote push\naction remote push\nfoo@bar:~$ ./subcommands.py branch\naction branch\nfoo@bar:~$ ./subcommands.py\ndefault action\nfoo@bar:~$ ./subcommands.py --help\nsubcommands-demo\n\nUsage:\n ./subcommands.py [COMMAND] [OPTIONS]\n\nOptions:\n -h, --help [SUBCOMMANDS...] - Display this help and exit\n\nCommands:\n remote - List remotes\n remote push \n remote rename\n checkout - Switch branches\n branch - List branches\n\nRun \"./subcommands.py COMMAND --help\" for more information on a command.\n```\n\nSee [sub-commands tests](https://github.com/igrek51/cliglue/blob/master/tests/parser/test_subcommand.py) for more detailed use cases.\n\n## Flags\nFlag is a boolean parameter which is toggled by single keyword.\nThere are supported both short (`-f`) and long (`--force`) formats.\n\nIn order to create flag rule specification, use:\n```python\nfrom cliglue import flag\n\nflag(\n *keywords: str,\n help: str = None,\n multiple: bool = False,\n)\n```\n`keywords` are arguments (one or many) which any of them enables flag when it occurs.\nFlag value is `False` by default.\nFlag keywords may be passed using direct format: `-f` or `--flag`,\nas well as by name: `f` or `flag`, which will be also evaluated to `-f` or `--flag`.\nSingle character flags will get single hyphen prefix (`-f`),\nlonger flag names will get double hyphen prefix (`--flag`).\n\n`help` is description of the flag displayed in help output\n\n`multiple` - whether flag is allowed to occur many times.\nThen flag has int type and stores number of its occurrences\n\nExample:\n```python\nfrom cliglue import CliBuilder, flag\n\nCliBuilder(run=lambda force: print(force)).has(\n flag('--force', '-f'),\n).run()\n```\nUsage:\n```console\nfoo@bar:~$ ./example.py --force\nTrue\nfoo@bar:~$ ./example.py\nFalse\n``` \n\n### Combining short flags\nMany short flags may be combined in one argument. Instead of `-t -u -l -p -n` you can just type `-tulpn`.\n\n### Multiple flag occurrences\nMultiple occurences are also supported for flags. When `multiple` is set to `True`, then the flag value represents how many times it was set. The value type is then `int`, not `bool`.\n```python\nCliBuilder(run=lambda verbose: print(f'verbosity level: {verbose}')).has(\n flag('verbose', 'v', multiple=True),\n).run()\n```\nThen `-vvv` should return `3`.\n\nSee [flag tests](https://github.com/igrek51/cliglue/blob/master/tests/parser/test_flag.py) for more detailed use cases.\n## Named parameters\nParameter is a named value, which will be injected to triggered action by its name.\nThere are supported both manners for setting parameter value:\n`--parameter-name value` or `--parameter-name=value`\n\nNamed parameters may appear anywhere in CLI arguments list: at the beginning or at the end, or even before positional arguments.\nAs long as they are matched as named parameters, they will not be recognized as positional arguments.\n\nThe parameters may be later referenced by its name or keywords\n(in lowercase format without hyphen prefix and with underscores instead of dashes,\ne.g. `--paramater-name` will be injected as `parameter_name`)\n\nIn order to create parameter rule specification, use:\n```python\nfrom cliglue import parameter\n\nparameter(\n *keywords: str,\n name: str = None,\n help: str = None,\n required: bool = False,\n default: Any = None,\n type: TypeOrParser = str,\n choices: ChoiceProvider = None,\n strict_choices: bool = False,\n multiple: bool = False,\n)\n```\n`keywords` keyword arguments which are matched to parameter.\nParameter keywords may be passed using direct format: `-p` or `--param`,\nas well as by name: `p` or `param`, which will be evaluated to `-p` or `--param`.\nSingle character parameter will get single hyphen prefix (`-p`),\nlonger parameter names will get double hyphen prefix (`--param`)\n\n`name` is explicit paramter name (can be used, when it's different from any keyword)\n\n`help` is description of the parameter displayed in help output\n\n`required` tells whether parameter is required.\nIf it's required but it's not given, the syntax error will be raised.\n\n`default` is default value for the parameter, if it's not given (and it's not required).\n\n`type` is a type of parameter value (e.g. str, int, float).\nReference to a parser function may be provided here as well.\nThen parameter value is evaluated by passing the string argument value to that function.\n\n`choices` is Explicit list of available choices for the parameter value\nor reference to a function which will be invoked to retrieve such possible values list\n\n`strict_choices` - whether given arguments should be validated against available choices\n\n`multiple` - whether parameter is allowed to occur many times.\nThen parameter has list type and stores list of values\n\nBasic parameter example:\n```python\nfrom cliglue import CliBuilder, parameter\n\nCliBuilder(run=lambda param: print(param)).has(\n parameter('param', 'p'),\n).run()\n```\n```console\nfoo@bar:~$ ./example.py --param OK\nOK\nfoo@bar:~$ ./example.py --param=OK\nOK\nfoo@bar:~$ ./example.py -p OK\nOK\nfoo@bar:~$ ./example.py\nNone\n```\n\n### Multiple parameter occurrences\nMultiple occurences are also supported for parameters.\nWhen `multiple` is set to `True`, then the parameter value represents list of values and can be appended mutliple times.\nThe value type is then `list`.\n```python\ndef what_to_skip(skip: List[str]):\n print(f'skipping: {skip}')\n\nCliBuilder(run=what_to_skip).has(\n parameter('skip', multiple=True, type=str),\n).run()\n```\n```console\nfoo@bar:~$ ./example.py --skip build --skip run\nskipping: ['build', 'run']\n``` \n\nSee [parameter tests](https://github.com/igrek51/cliglue/blob/master/tests/parser/test_param.py) for more detailed use cases.\n\n## Positional arguments\nPositional argument is an unnamed parameter, which is recognized only by its position on their order in command line arguments list.\nFor example first two arguments (except flags and named parameters) may be detected as positional arguments and matched to corresponding variables.\n\n### Single positional arguments\nLet's assume we have CLI syntax: `git push `.\n`git` is application binary name of course, `push` is a sub-command, which have 2 positional arguments: `origin` and `master`.\n\nIn order to create positional argument rule specification, use:\n```python\nfrom cliglue import argument\n\ndef argument(\n name: str,\n help: str = None,\n required: bool = True,\n default: Any = None,\n type: TypeOrParser = str,\n choices: ChoiceProvider = None,\n strict_choices: bool = False,\n)\n```\n\n`name` - internal argument name, which will be used to reference argument value\n\n`help` - description of the argument displayed in help output\n\n`required` - whether positional argument is required.\nIf it's required but it's not given, the syntax error will be raised.\n\n`default` - default value for the argument, if it's not given (and it's not required)\n\n`type` - type of argument value (e.g. str, int, float)\nReference to a parser function may be provided here as well.\nThen argument value is evaluated by passing the string argument value to that function.\n\n`choices` - Explicit list of available choices for the argument value\nor reference to a function which will be invoked to retrieve such possible values list.\n\n`strict_choices` - whether given arguments should be validated against available choices\n\n#### Example: pos-args.py\n```python\n#!/usr/bin/env python3\nfrom cliglue import CliBuilder, argument\n\n\ndef print_args(remote: str, branch: str):\n print(f'remote: {remote}, argument: {branch}')\n\n\nCliBuilder('pos-args', run=print_args).has(\n argument('remote', help='remote name', type=str, choices=['origin', 'local']),\n argument('branch', help='branch name', required=False, default='master'),\n).run()\n```\nUsage:\n```console\nfoo@bar:~$ ./pos-args.py --help\npos-args\n\nUsage:\n ./pos-args.py [OPTIONS] REMOTE [BRANCH]\n\nOptions:\n -h, --help [SUBCOMMANDS...] - Display this help and exit\n\nfoo@bar:~$ ./pos-args.py\n[ERROR] Syntax error: required positional argument \"remote\" is not given\npos-args\n\nUsage:\n ./pos-args.py [OPTIONS] REMOTE [BRANCH]\n\nOptions:\n -h, --help [SUBCOMMANDS...] - Display this help and exit\n\nfoo@bar:~$ ./pos-args.py origin\nremote: origin, argument: master\n\nfoo@bar:~$ ./pos-args.py origin develop\nremote: origin, argument: develop\n```\n\nSee [positional arguments tests](https://github.com/igrek51/cliglue/blob/master/tests/parser/test_positional_argument.py) as a specification.\n## Many positional arguments\n`cliglue` allows to match all remaining (not already matched) arguments.\nIt can be useful when using syntax like `docker cmd`:\n```docker run cmd ubuntu /bin/bash -c /script.sh```\nWith that syntax all arguments after `ubuntu` - `/bin/bash -c /script.sh` should be matched to one variable. \n\nYou can do it with `cliglue` using `arguments`.\nThat rule will force parser to store all remaining arguments in a list variable (or in a joined string).\n\nIn order to create \"multiple arguments\" rule specification, use:\n```python\nfrom cliglue import arguments\n\ndef arguments(\n name: str,\n type: Union[Type, Callable[[str], Any]] = str,\n choices: Union[List[Any], Callable[..., List[Any]]] = None,\n strict_choices: bool = False,\n count: Optional[int] = None,\n min_count: Optional[int] = None,\n max_count: Optional[int] = None,\n joined_with: Optional[str] = None,\n help: str = None,\n)\n```\nIt allows to retrieve specific number of CLI argumetns or all remaining arguments.\nAll matched arguments will be extracted to a list of arguments or a string (depending on `joined_with` parameter)\n\n`name` - internal variable name, which will be used to reference matched arguments\n\n`type` - explicit type of arguments values (e.g. str, int, float)\nReference to a parser function may be provided here as well.\nThen argument value is evaluated by passing the string argument value to that function\n\n`choices` - Explicit list of available choices for the argument value\nor reference to a function which will be invoked to retrieve such possible values list.\n\n`strict_choices` - whether given arguments should be validated against available choices\n\n`count` - explicit number of arguments to retrieve.\nIf undefined, there is no validation for arguments count.\nIf you need particular number of arguments, you can use this count instead of setting min_count=max_count.\n\n`min_count` - minimum number of arguments.\nBy default, there is no lower limit (it is 0).\n\n`max_count` - maximum number of arguments.\nIf undefined, there is no upper limit for arguments count.\n\n`joined_with` - optional string joiner for arguments.\nIf it's set, all matched arguments will be joined to string with that joiner.\nIt it's not given, matched arguments will be passed as list of strings.\nThis value (string or list) can be accessed by specified name, when it's being injected to a function.\n\n`help` - description of the arguments displayed in help output\n\nNote that `arguments(count=1)` rule with is like single `argument` rule, except that it stores list with one element.\n\nYou can use many consecutive `arguments` rules as long as they have `count` or `max_count` set.\n\n### Example: many-args.py\n```python\n#!/usr/bin/env python3\nfrom cliglue import CliBuilder, arguments, subcommand\n\n\ndef run_cmd(cmd: str):\n print(f'cmd: {cmd}')\n\n\nCliBuilder('many-args').has(\n subcommand('run', run=run_cmd).has(\n arguments('cmd', joined_with=' '),\n ),\n).run()\n```\nUsage:\n```console\nfoo@bar:~$ ./many-args.py\nmany-args\n\nUsage:\n ./many-args.py [COMMAND] [OPTIONS]\n\nOptions:\n -h, --help [SUBCOMMANDS...] - Display this help and exit\n\nCommands:\n run [CMD...]\n\nRun \"./many-args.py COMMAND --help\" for more information on a command.\n\nfoo@bar:~$ ./many-args.py run /bin/bash -c script.sh\ncmd: /bin/bash -c script.sh\n\nfoo@bar:~$ ./many-args.py run \"/bin/bash -c script.sh\"\ncmd: /bin/bash -c script.sh\n```\n\nSee [many arguments tests](https://github.com/igrek51/cliglue/blob/master/tests/parser/test_many_arguments.py) for more detailed use cases.\n\n## Dictionaries\nDictionary contains key-value pairs.\nYou can add multiple values to it by passing arguments in a manner:\n`-c name1 value1 -c name2 value2`.\n\nBy default it stores empty Python dict.\nThese values may be later referenced as dict by its explicit name or keywords\n(in lowercase format without hyphen prefix and with underscores instead of dashes,\ne.g. `--config-name` will be injected as `config_name` variable name)\n\nIn order to create dictionary rule specification, use:\n```python\nfrom cliglue import dictionary\n\ndictionary(\n *keywords: str,\n name: str = None,\n help: str = None,\n key_type: Union[Type, Callable[[str], Any]] = str,\n value_type: Union[Type, Callable[[str], Any]] = str,\n)\n```\n\n`keywords` - keyword arguments which are matched to this dictionary.\nKeywords may be passed using direct format: `-c` or `--config`,\nas well as by name: `c` or `config`, which will be evaluated to `-c` or `--config`.\nSingle character dictionary will get single hyphen prefix (`-c`),\nlonger dictionary names will get double hyphen prefix (`--config`)\n\n`name` - explicit internal dictionary name (can be used to distinguish it from any keyword)\n\n`help` - description of the dictionary displayed in help output\n\n`key_type` - type of dictionary key (e.g. str, int, float)\nReference to a parser function may be provided here as well.\nThen dictionary value is evaluated by passing the string argument value to that function.\n\n`value_type` - type of dictionary value (e.g. str, int, float)\nReference to a parser function may be provided here as well.\nThen dictionary value is evaluated by passing the string argument value to that function.\n\nBasic dictionary example:\n```python\nfrom cliglue import CliBuilder, dictionary\n\nCliBuilder(run=lambda config: print(config)).has(\n dictionary('config', 'c', value_type=int),\n).run()\n```\n```console\nfoo@bar:~$ ./example.py --config key1 5 -c key2 42\n{'key1': 5, 'key2': 42}\nfoo@bar:~$ ./example.py\n{}\n```\n\nSee [dictionaries tests](https://github.com/igrek51/cliglue/blob/master/tests/parser/test_dictionary.py) for more detailed use cases.\n\n## Auto-completion\nShell autocompletion allows to suggest most relevant hints on hitting `Tab` key, while typing a command line.\n\nAuto-completion provided by `cliglue` is enabled by default to all known keywords based on the declared subcommands and options.\n\nDefining possible choices may imporove auto-completing arguments. You can declare explicit possible values list or a function which provides such a list at runtime.\n\n**completers.py**:\n```python\n#!/usr/bin/env python3\nimport re\nfrom typing import List\n\nfrom cliglue import CliBuilder, parameter, default_action\nfrom cliglue.utils.shell import shell, shell_output\n\n\ndef list_screens() -> List[str]:\n \"\"\"Return list of available screen names in a system\"\"\"\n xrandr = shell_output('xrandr 2>/dev/null')\n regex_matcher = re.compile(r'^([a-zA-Z0-9\\-]+) connected(.*)')\n return [regex_matcher.sub('\\\\1', line)\n for line in xrandr.splitlines()\n if regex_matcher.match(line)]\n\n\ndef adjust_screen(output: str, mode: str):\n shell(f'xrandr --output {output} --mode {mode}')\n\n\nCliBuilder('completers-demo').has(\n parameter('output', choices=list_screens, required=True),\n parameter('mode', choices=['640x480', '800x480', '800x600'], required=True),\n default_action(adjust_screen),\n).run()\n```\n\nIn order to enable auto-completion, you need to install some extension to bash. Fortunately `cliglue` has built-in tools to do that:\n```console\nfoo@bar:~$ sudo ./completers.py --bash-install completers-demo\n[info] creating link: /usr/bin/completers-demo -> ~/cliglue/docs/example/completers.py\n#!/bin/bash\n_autocomplete_98246661() {\nCOMPREPLY=( $(completers-demo --bash-autocomplete \"${COMP_LINE}\") )\n}\ncomplete -F _autocomplete_98246661 completers-demo\n[info] Autocompleter has been installed in /etc/bash_completion.d/autocomplete_completers-demo.sh. Please restart your shell.\n```\nNow, we have `completers-demo` application installed in `/usr/bin/` (symbolic link to the current script) and bash completion script installed as well.\nWe can hit `[Tab]` key to complete command when typing. Here are some completions examples:\n```console\nfoo@bar:~$ completers-d[Tab]\nfoo@bar:~$ completers-demo\n\nfoo@bar:~$ completers-demo [Tab][Tab]\n--bash-autocomplete -h --mode --output\n--bash-install --help --mode= --output=\n\nfoo@bar:~$ completers-demo --mo[Tab]\nfoo@bar:~$ completers-demo --mode\n\nfoo@bar:~$ completers-demo --mode [Tab][Tab]\n640x480 800x480 800x600\n\nfoo@bar:~$ completers-demo --mode 640[Tab]\nfoo@bar:~$ completers-demo --mode 640x480\n\nfoo@bar:~$ completers-demo --mode 640x480 --output [Tab][Tab]\neDP-1 HDMI-1\n```\n\n### Custom completers\nYou can provide your custom auto-completers (providers of possible values) to the `choices` parameter.\n\nThe example is the function which returns a list of available screens:\n```python\ndef list_screens() -> List[str]:\n \"\"\"Return list of available screen names in a system\"\"\"\n xrandr = shell_output('xrandr 2>/dev/null')\n regex_matcher = re.compile(r'^([a-zA-Z0-9\\-]+) connected(.*)')\n return [regex_matcher.sub('\\\\1', line)\n for line in xrandr.splitlines()\n if regex_matcher.match(line)]\n```\nYou can use it to validate and propose available choices for parameter or positional argument:\n```python\nCliBuilder().has(\n parameter('output', choices=list_screens, required=True),\n)\n```\n\n### Installing Autocompletion\nIn order to enable the autocompletion, there must be a specific script in `/etc/bash_completion.d/`.\nWith `cliglue` you just need to run:\n```console\n# sudo ./sample-app.py --bash-install sample-app\n```\nIt will install autocompletion script and add a symbolic link in `/usr/bin/`,\nso as you can run your app with `sample-app` command instead of `./sample_app.py`.\n\nNow you can type `sample-app` and hit `Tab` to see what are the possible commands and options.\n\nIf you type `sample-app --he`, it will automatically fill the only possible option: `--help`.\n\nSometimes, you need to make some modifications in your code,\nbut after these modifications you will NOT need to reinstall autocompletion again.\nYou had to do it only once, because autocompletion script only redirects its query and run `sample_app.py`:\n```console\nsample-app --bash-autocomplete \"sample-app --he\"\n```\n\n### How does auto-completion work?\n1. While typing a command in `bash`, you hit `Tab` key. (`your-app.py cmd[TAB]`)\n2. `bash` looks for an autocompletion script in `/etc/bash_completion.d/`.\nThere should be a script installed for your command after running `--bash-install` on your application.\nSo when it's found, this script is called by bash.\n3. The autocompletion script redirects to your application, running it with `--bash-autocomplete` option, namely script runs `your-app.py --bash-autocomplete \"cmd\"`, asking it for returning the most relevant command proposals.\nNotice that in that manner, the autocompletion algorithm is being run always in up-to-date version.\n4. `your-app.py` has `--bash-autocomplete` option enabled by default so it starts to analyze which keyword from your CLI definition is the most relevant to the currently typed word (`cmd`).\n5. If you defined custom completers functions, they will be invoked right now (if needed) in order to get up-to-date proposals and analyze them as well. \n6. `your-app.py` returns a list of proposals to the `bash`.\n7. `bash` shows you these results.\nIf there's only one matching proposal, the currently typed word is automatically filled.\n\nNote that your application is being run each time when trying to get matching arguments proposals.\n\n## Auto-generated help\n`cliglue` auto-generates help and usage output based on the defined CLI rules.\n\nLet's say we have quite complex CLI definition:\n```python\nCliBuilder('multiapp', version='1.0.0', help='many apps launcher',\n with_defaults=True, usage_onerror=False, reraise_error=True).has(\n subcommand('git').has(\n subcommand('push', run=git_push).has(\n argument('remote'),\n argument('branch', required=False),\n flag('-u', '--upstream', help='set upstream'),\n ),\n subcommand('help', help='show help', run=lambda: print('show help')),\n subcommand('checkout', 'co', help='checkout branch').has(\n argument('branch', choices=['master', 'feature', 'develop'], type=str),\n flag('force', 'f'),\n ),\n subcommand('remote', help='show remotes list').has(\n subcommand('set-url', 'rename', help=\"change remote's name\").has(\n argument('remote-name', choices=['origin', 'backup'], type=str),\n argument('new-name'),\n ),\n ),\n parameter('--date', type=iso_datetime),\n parameter('--count', type=int, required=True),\n parameter('--work-tree', type=existing_directory, default='.', help='working directory'),\n ),\n subcommand('xrandr').has(\n parameter('output', required=True, choices=list_screens),\n flag('primary', 'p'),\n default_action(xrandr_run)\n ),\n subcommand('docker').has(\n subcommand('exec', run=docker_exec).has(\n parameter('-u', name='user', type=int),\n argument('container-name'),\n arguments(name='cmd', joined_with=' '),\n ),\n ),\n default_action(lambda: print('default action')),\n)\n```\n\nWe can see the usage and description of commands using `--help` or `-h`:\n```console\nfoo@bar:~$ python3 multiapp.py --help\nmultiapp v1.0.0 - many apps launcher\n\nUsage:\n multiapp.py [COMMAND] [OPTIONS]\n\nOptions:\n --version - Print version information and exit\n -h, --help [SUBCOMMANDS...] - Display this help and exit\n --bash-install APP-NAME - Install script as a bash binary and add autocompletion links\n --bash-autocomplete [CMDLINE...] - Return matching autocompletion proposals\n\nCommands:\n git \n git push REMOTE [BRANCH] \n git help - show help\n git co|checkout BRANCH - checkout branch\n git remote - show remotes list\n git remote rename|set-url REMOTE-NAME NEW-NAME - change remote's name\n xrandr \n docker \n docker exec CONTAINER-NAME [CMD...] \n\nRun \"multiapp.py COMMAND --help\" for more information on a command.\n```\n\n### Sub-commands help\nWe can also check the usage for a selected sub-command only:\n```console\nfoo@bar:~$ python3 multiapp.py git --help\nmultiapp v1.0.0 - many apps launcher\n\nUsage:\n multiapp.py git [COMMAND] [OPTIONS]\n\nOptions:\n --date DATE \n --count COUNT \n --work-tree WORK_TREE - working directory\n --version - Print version information and exit\n -h, --help [SUBCOMMANDS...] - Display this help and exit\n\nCommands:\n push REMOTE [BRANCH] \n help - show help\n co|checkout BRANCH - checkout branch\n remote - show remotes list\n remote rename|set-url REMOTE-NAME NEW-NAME - change remote's name\n\nRun \"multiapp.py git COMMAND --help\" for more information on a command.\n```\n\n### version check\nUse `--version` in order to show your application version:\n```console\nfoo@bar:~$ python3 multiapp.py --version\nmultiapp v1.0.0 (cliglue v1.0.1)\n```\n\n## Data types\n`cliglue` supports typed values for parameters or positional arguments.\nBy default, all values have string types.\nIt can be changed by defining `type` parameter.\n\nThere are 2 possible `type` values:\n- type name itself (e.g. `int`, `str`, `float`)\n- reference for a parser which returns a value\nIn both cases, the internal variable value is calculated by invoking `type(str_value)`.\nWhen argument value has invalid format, there is syntax error raised. \n\n### Basic types (int, float, etc.)\n```python\n#!/usr/bin/env python3\nfrom cliglue import CliBuilder, argument\n\ndef print_it(count: int):\n print(count * 2)\n\nCliBuilder(run=print_it).has(\n argument('count', type=int),\n).run()\n```\n```console\nfoo@bar:~$ ./example.py 21\n42\n\nfoo@bar:~$ ./example.py dupa\n[ERROR] Syntax error: parsing positional argument \"count\": invalid literal for int() with base 10: 'dupa'\nUsage:\n ./pyt.py [OPTIONS] COUNT\n\nOptions:\n -h, --help [SUBCOMMANDS...] - Display this help and exit\n\nfoo@bar:~$ ./example.py\n[ERROR] Syntax error: required positional argument \"count\" is not given\nUsage:\n ./pyt.py [OPTIONS] COUNT\n\nOptions:\n -h, --help [SUBCOMMANDS...] - Display this help and exit\n```\n\n### Built-in data types\n`cliglue` has built-in parsers / validators for some types\n\n#### Filesystem types\n- `cliglue.types.filesystem.existing_file` validates if given string is an existing regular file (not a directory).\nAfter validation, the value is internally stored as `str`.\n```python\nfrom cliglue.types.filesystem import existing_file\n\nargument('file', type=existing_file)\n```\n\n- `cliglue.types.filesystem.existing_directory` validates if given string is an existing directory.\nAfter validation, the value is internally stored as `str`.\n```python\nfrom cliglue.types.filesystem import existing_directory\n\nargument('file', type=existing_directory)\n```\n\n#### Datetime types\n- `cliglue.types.time.iso_date` parses / validates given string as a date in ISO format: `%Y-%m-%d`.\nAfter validation, the value is internally stored as `datetime.datetime`.\n```python\nfrom cliglue.types.time import iso_date\n\nargument('date', type=iso_date)\n```\n\n- `cliglue.types.time.iso_time` parses / validates given string as a time in ISO format: `%H:%M:%S`.\nAfter validation, the value is internally stored as `datetime.datetime`.\n```python\nfrom cliglue.types.time import iso_time\n\nargument('time', type=iso_time)\n```\n\n- `cliglue.types.time.iso_time` parses / validates given string as a datetime in ISO format: `%Y-%m-%d %H:%M:%S`.\nAfter validation, the value is internally stored as `datetime.datetime`.\n```python\nfrom cliglue.types.time import iso_datetime\n\nargument('datetime', type=iso_datetime)\n```\nExample:\n```python\n#!/usr/bin/env python3\nfrom cliglue import CliBuilder, argument\nfrom cliglue.types.time import iso_datetime\nfrom datetime import datetime\n\ndef print_it(to: datetime):\n print(to)\n\nCliBuilder(run=print_it).has(\n argument('to', type=iso_datetime),\n).run()\n```\n```console\nfoo@bar:~$ ./example.py 2019-07-13\n[ERROR] Syntax error: invalid datetime format: 2019-07-13\nUsage:\n ./pyt.py [OPTIONS] TO\n\nOptions:\n -h, --help [SUBCOMMANDS...] - Display this help and exit\n\nfoo@bar:~$ ./example.py \"2019-07-13 20:00:05\"\n2019-07-13 20:00:05\n```\n\n- `cliglue.types.time.datetime_format` parses / validates given string as a datetime in custom formats specified by user.\nYou may specify multiple formats and the CLI argument will be parsed sequentially for each format.\nThe first successfully parsed datetime is returned.\nAfter that, the value is internally stored as `datetime.datetime`.\n\n```python\n#!/usr/bin/env python3\nfrom cliglue import CliBuilder, argument\nfrom cliglue.types.time import datetime_format\nfrom datetime import datetime\n\ndef print_it(to: datetime):\n print(to)\n\nCliBuilder(run=print_it).has(\n argument('to', type=datetime_format('%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M', '%Y-%m-%d')),\n).run()\n```\n```console\nfoo@bar:~$ ./example.py \"2019-07-13 20:00:05\"\n2019-07-13 20:00:05\nfoo@bar:~$ ./example.py 2019-07-13\n2019-07-13 00:00:00\n```\n\n- `cliglue.types.time.today_format` parses / validates given string as a time in custom formats specified by user.\nIt gets time from input and combines it with the today date.\nYou may specify multiple formats and the CLI argument will be parsed sequentially for each format.\nThe first successfully parsed datetime is returned.\nAfter that, the value is internally stored as `datetime.datetime`.\n\n```python\n#!/usr/bin/env python3\nfrom cliglue import CliBuilder, argument\nfrom cliglue.types.time import today_format\nfrom datetime import datetime\n\ndef print_it(to: datetime):\n print(to)\n\nCliBuilder(run=print_it).has(\n argument('to', type=today_format('%H:%M:%S', '%H:%M')),\n).run()\n```\n```console\nfoo@bar:~$ ./example.py 12:42\n2019-07-13 12:15:00\n```\n\n### Custom type parsers\nYou can define custom parser/validator function.\nIt should take one `str` argument and return expected value type.\n\n```python\n#!/usr/bin/env python3\nimport re\nfrom dataclasses import dataclass\nfrom cliglue import CliBuilder, argument\n\n@dataclass\nclass Person:\n name: str\n age: int\n\n @staticmethod\n def parse(arg: str) -> 'Person':\n match = re.compile('(.+)-([0-9]+)').match(arg)\n return Person(match.group(1), int(match.group(2)))\n\n\ndef print_it(human: Person):\n print(human)\n\nCliBuilder(run=print_it).has(\n argument('human', type=Person.parse),\n).run()\n```\n```console\nfoo@bar:~$ ./example.py Eric-33\nPerson(name='Eric', age=33)\n```\n\n## Errors handling\n`cliglue` validates passed CLI arguments on running `CliBuilder.run()`\n\n### Handling syntax errors - CliSyntaxError\nIn case of syntax error, `CliBuilder.run()` raises `CliSyntaxError`, it's when:\n\n- parameter value is missing: `--param-name` without next argument\n- required parameter is not given\n- required positional argument is not given\n- positiona argument or parameter has invalid type (there was parsing type error)\n\nBy default `CliSyntaxError` caught by `CliBuilder` is rethrown.\nYou can disable raising this error again by seting `reraise_error = False` when creating `CliBuilder`:\n`CliBuilder(reraise_error = False)`.\nThen only the error log will be displayed in console stdout.\n\n`usage_onerror` parameter decides wheter usage output should be displayed on syntax error.\n\n#### Erros handling example\n```python\n#!/usr/bin/env python3\nfrom cliglue import CliBuilder, argument\n\nCliBuilder(usage_onerror=False).has(\n argument('remote', required=True),\n).run()\n```\n\n```console\nfoo@bar:~$ ./pos-args.py\n[ERROR] Syntax error: required positional argument \"remote\" is not given\n```\n\n### CliDefinitionError\nIn case of invalid CLI definition, `CliBuilder.run()` raises `CliDefinitionError`. It's e.g. when:\n\n- positional argument or parameter is set to required and has default value set (it doesn't make any sense)\n- positional argument is placed after all remaining arguments\n- parameter / argument value does not belong to strict available choices list\n\n#### Wrong CLI Definition example\n```python\n#!/usr/bin/env python3\nfrom cliglue import CliBuilder, argument\n\nCliBuilder().has(\n argument('remote', required=True, default='def'),\n).run()\n```\n\n```console\nfoo@bar:~$ ./errors.py\n[ERROR] CLI Definition error: argument value may be either required or have the default value\nTraceback (most recent call last):\n File \"pyt.py\", line 4, in \n argument('remote', required=True, default='def'),\n File \"~/cliglue/cliglue/builder/builder.py\", line 69, in run\n self.run_with_args(sys.argv[1:])\n File \"~/cliglue/cliglue/builder/builder.py\", line 76, in run_with_args\n raise e\n File \"~/cliglue/cliglue/builder/builder.py\", line 73, in run_with_args\n Parser(self.__subrules).parse_args(args)\n File \"~/cliglue/cliglue/parser/parser.py\", line 37, in __init__\n self._init_rules()\n File \"~/cliglue/cliglue/parser/parser.py\", line 57, in _init_rules\n raise CliDefinitionError('argument value may be either required or have the default value')\ncliglue.parser.error.CliDefinitionError: argument value may be either required or have the default value\n```\n\n## CLI Rules cheatsheet\nHere is the cheatsheet for the most important CLI rules:\n```python\n#!/usr/bin/env python3\nfrom cliglue import CliBuilder, argument, arguments, flag, parameter, subcommand, dictionary\n\n\ndef main():\n CliBuilder('hello-app', version='1.0.0', help='welcome', run=say_hello).has(\n flag('--verbose', '-v', help='verbosity', multiple=True),\n parameter('repeat', 'r', help='how many times', type=int, required=False, default=1, choices=[1, 2, 3, 5, 8]),\n argument('name', help='description', required=False, default='world', type=str, choices=['monty', 'python']),\n arguments('cmd', joined_with=' '),\n subcommand('run', help='runs something').has(\n subcommand('now', 'n', run=lambda cmd: print(f'run now: {cmd}')),\n ),\n dictionary('config', 'c', help='configuration', key_type=str, value_type=int)\n ).run()\n\n\ndef say_hello(name: str, verbose: int, repeat: int, cmd: str, config: dict):\n print(f'Hello {name}')\n\n\nif __name__ == '__main__':\n main()\n```\n\n## Complex CLI tree\nHere's an example of more complex CLI definition tree:\n\n**multiapp.py**:\n```python\n#!/usr/bin/env python3\nfrom cliglue import CliBuilder, argument, parameter, flag, subcommand, arguments, default_action\nfrom cliglue.types.filesystem import existing_directory\nfrom cliglue.types.time import iso_datetime\n\n\ndef main():\n CliBuilder('multiapp', version='1.0.0', help='many apps launcher',\n with_defaults=True, usage_onerror=False, reraise_error=True, hide_internal=True).has(\n subcommand('git').has(\n subcommand('push', run=git_push).has(\n argument('remote'),\n argument('branch', required=False),\n flag('-u', '--upstream', help='set upstream'),\n ),\n subcommand('help', help='show help', run=lambda: print('show help')),\n subcommand('checkout', 'co', help='checkout branch').has(\n argument('branch', choices=['master', 'feature', 'develop'], type=str),\n flag('force', 'f'),\n ),\n subcommand('remote', help='show remotes list').has(\n subcommand('set-url', 'rename', help=\"change remote's name\").has(\n argument('remote-name', choices=['origin', 'backup'], type=str),\n argument('new-name'),\n ),\n ),\n parameter('--date', type=iso_datetime),\n parameter('--count', type=int, required=True),\n parameter('--work-tree', type=existing_directory, default='.', help='working directory'),\n ),\n subcommand('xrandr').has(\n parameter('output', required=True, choices=list_screens),\n flag('primary', 'p'),\n default_action(xrandr_run)\n ),\n subcommand('docker').has(\n subcommand('exec', run=docker_exec).has(\n parameter('-u', name='user', type=int),\n argument('container-name'),\n arguments(name='cmd', joined_with=' '),\n ),\n ),\n default_action(lambda: print('default action')),\n ).run()\n\n\ndef git_push(remote: str, branch: str, upstream: bool):\n print(f'git push: {remote}, {branch}, {upstream}')\n\n\ndef xrandr_run(output, primary):\n print(f'xrandr: {output} {primary}')\n\n\ndef list_screens():\n return ['eDP1', 'HDMI2']\n\n\ndef docker_exec(user: int, container_name: str, cmd: str):\n print(f'docker exec {user}, {container_name}, {cmd}')\n\n\nif __name__ == '__main__':\n main()\n```\n\nUsage:\n```console\nfoo@bar:~$ ./multiapp.py --help\nmultiapp v1.0.0 - many apps launcher\n\nUsage:\n ./multiapp.py [COMMAND] [OPTIONS]\n\nOptions:\n --version - Print version information and exit\n -h, --help [SUCOMMANDS...] - Display this help and exit\n --bash-install APP-NAME - Install script as a bash binary and add autocompletion links\n --bash-autocomplete [CMDLINE...] - Return matching autocompletion proposals\n\nCommands:\n git \n git push REMOTE [BRANCH] \n git help - show help\n git co|checkout BRANCH - checkout branch\n git remote - show remotes list\n git remote rename|set-url REMOTE-NAME NEW-NAME - change remote's name\n xrandr \n docker \n docker exec CONTAINER-NAME [CMD...] \n\nRun \"./multiapp.py COMMAND --help\" for more information on a command.\n```\n\n\n\n", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/igrek51/cliglue", "keywords": "", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "cliglue", "package_url": "https://pypi.org/project/cliglue/", "platform": "", "project_url": "https://pypi.org/project/cliglue/", "project_urls": { "Homepage": "https://github.com/igrek51/cliglue" }, "release_url": "https://pypi.org/project/cliglue/1.0.6/", "requires_dist": [ "dataclasses" ], "requires_python": ">=3.6.0", "summary": "Declarative parser for command line interfaces", "version": "1.0.6" }, "last_serial": 5914353, "releases": { "0.2.1": [ { "comment_text": "", "digests": { "md5": "5e6f3da787aa159c6612339cc18e11df", "sha256": "36be7fabfcc06d6300bc4d1106285ee0733b84c9c8bcab225263055d5c3694ad" }, "downloads": -1, "filename": "cliglue-0.2.1-py3-none-any.whl", "has_sig": false, "md5_digest": "5e6f3da787aa159c6612339cc18e11df", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">3.6.0", "size": 39374, "upload_time": "2019-07-04T20:40:38", "url": "https://files.pythonhosted.org/packages/c1/5e/1c152b7ee8f8279e99315f2608ab9902144075b46435a0b6ba95e7939e70/cliglue-0.2.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "8513d229659e65128200069c8b603e11", "sha256": "eb0313b9ec6832bcc424e6236cc5e51f57459149e616d058e07b5bb139beb225" }, "downloads": -1, "filename": "cliglue-0.2.1.tar.gz", "has_sig": false, "md5_digest": "8513d229659e65128200069c8b603e11", "packagetype": "sdist", "python_version": "source", "requires_python": ">3.6.0", "size": 24122, "upload_time": "2019-07-04T20:40:40", "url": "https://files.pythonhosted.org/packages/da/95/84f1a3d45cbb140aac2008230f0db90852a79bb929554e59e86c06689966/cliglue-0.2.1.tar.gz" } ], "0.2.3": [ { "comment_text": "", "digests": { "md5": "f04b2cf9d04d30e2275b90066f662128", "sha256": "9e7bc1e778141a13b66224441d04f35fef888746dfdcda5d2d6e9ae3303d4370" }, "downloads": -1, "filename": "cliglue-0.2.3-py3-none-any.whl", "has_sig": false, "md5_digest": "f04b2cf9d04d30e2275b90066f662128", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6.0", "size": 40018, "upload_time": "2019-07-06T21:31:20", "url": "https://files.pythonhosted.org/packages/8d/1c/ccb455b0f1556db02756b52dc6029f0a6a676acc3bb6dcbbcb5ca2703757/cliglue-0.2.3-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "28fcb7d2c4450020801e8d21c398b566", "sha256": "7a251044406b644678d57469b41bb8650775708de8405fd742e54034048819d1" }, "downloads": -1, "filename": "cliglue-0.2.3.tar.gz", "has_sig": false, "md5_digest": "28fcb7d2c4450020801e8d21c398b566", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6.0", "size": 24995, "upload_time": "2019-07-06T21:31:22", "url": "https://files.pythonhosted.org/packages/7b/f3/47f73377dda90e27af4d54715d8fe4cbc5a98e5032a9bd17d0acd1c8e966/cliglue-0.2.3.tar.gz" } ], "0.2.4": [ { "comment_text": "", "digests": { "md5": "3a1ef6f8914a59b776afe06034cbd58d", "sha256": "80e50840567e261a867714819c59c6388048f43d1068cb16ec9cdc62fbaa8805" }, "downloads": -1, "filename": "cliglue-0.2.4-py3-none-any.whl", "has_sig": false, "md5_digest": "3a1ef6f8914a59b776afe06034cbd58d", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6.0", "size": 46946, "upload_time": "2019-07-12T21:01:33", "url": "https://files.pythonhosted.org/packages/ee/6f/7f381431e2febbc0771f9fc27e4db891f058884f19fff93f7538c9e08376/cliglue-0.2.4-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "50f69a5e0500d0dfe31a7a781ff28cd8", "sha256": "78172d96515c012648a33e2eb3dce2e47c4791a76498de39fbc725a12df6028e" }, "downloads": -1, "filename": "cliglue-0.2.4.tar.gz", "has_sig": false, "md5_digest": "50f69a5e0500d0dfe31a7a781ff28cd8", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6.0", "size": 52324, "upload_time": "2019-07-12T21:01:37", "url": "https://files.pythonhosted.org/packages/ed/d0/6b0fd437c272a52a9b1e49c4979b002602c5a7859aeb92bcec3132995ea2/cliglue-0.2.4.tar.gz" } ], "0.2.5": [ { "comment_text": "", "digests": { "md5": "78f18021d1f9ea20ac31953e180dbd6e", "sha256": "e18699d753fffe5eea5b2cdf17beb93dc24d00d8349469a7734e587adf2c656d" }, "downloads": -1, "filename": "cliglue-0.2.5-py3-none-any.whl", "has_sig": false, "md5_digest": "78f18021d1f9ea20ac31953e180dbd6e", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6.0", "size": 50688, "upload_time": "2019-07-23T16:29:36", "url": "https://files.pythonhosted.org/packages/f2/b8/482b097a2a03a4360a2d2cc7da18804a64c6de5f0d9ffa17ecfe7ec21208/cliglue-0.2.5-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "40e7124bf40a294c9d2557ac3f569ae1", "sha256": "e77fbdbceb69c20dcb29f8f1e283a671935cf6691dba85c429646cb543e3858a" }, "downloads": -1, "filename": "cliglue-0.2.5.tar.gz", "has_sig": false, "md5_digest": "40e7124bf40a294c9d2557ac3f569ae1", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6.0", "size": 59191, "upload_time": "2019-07-23T16:29:39", "url": "https://files.pythonhosted.org/packages/49/e4/b8a39027bb80506312f26d0989c24bbca71d06de85bc2b35fa13fc0b89d5/cliglue-0.2.5.tar.gz" } ], "0.3.0": [ { "comment_text": "", "digests": { "md5": "c31a25626331089e71e985ee004fb3dd", "sha256": "7a29f6152da0806917016a3b8293790854b4a2aac21d0063780b2bb98558c786" }, "downloads": -1, "filename": "cliglue-0.3.0-py3-none-any.whl", "has_sig": false, "md5_digest": "c31a25626331089e71e985ee004fb3dd", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6.0", "size": 54352, "upload_time": "2019-07-26T16:34:39", "url": "https://files.pythonhosted.org/packages/fd/ae/28fc097f7b79647dcdeb8670614e84973759e880f6c007bce7b37024612c/cliglue-0.3.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "9a8120740dae0ca7c72572392b267be1", "sha256": "6ea7f60d5659e51260fe75c4c2cf00fba8dacee15e10a3f83811425f6a8a68ec" }, "downloads": -1, "filename": "cliglue-0.3.0.tar.gz", "has_sig": false, "md5_digest": "9a8120740dae0ca7c72572392b267be1", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6.0", "size": 62782, "upload_time": "2019-07-26T16:34:42", "url": "https://files.pythonhosted.org/packages/84/4b/d1598869c80f439df30a29a46db8c2582cef33b2172e16faecb5eeef702f/cliglue-0.3.0.tar.gz" } ], "0.3.1": [ { "comment_text": "", "digests": { "md5": "c276be257671f7f457e12f5a48c6bc40", "sha256": "6958051d7e13718889a60b5acf4f02a63ee35507481d281204058d1c473b5dad" }, "downloads": -1, "filename": "cliglue-0.3.1-py3-none-any.whl", "has_sig": false, "md5_digest": "c276be257671f7f457e12f5a48c6bc40", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6.0", "size": 57319, "upload_time": "2019-08-01T22:20:06", "url": "https://files.pythonhosted.org/packages/2b/cc/f3c80e01da9a0f8bfa96f0578ea99748118fc5c66c14f3aa18c31d2021b0/cliglue-0.3.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "68e118fdc360deef8e07119ba1d31010", "sha256": "e844c499284e8471c50b12dc3ae536405e18da2c252eef17f665e1239548f2a5" }, "downloads": -1, "filename": "cliglue-0.3.1.tar.gz", "has_sig": false, "md5_digest": "68e118fdc360deef8e07119ba1d31010", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6.0", "size": 64566, "upload_time": "2019-08-01T22:20:09", "url": "https://files.pythonhosted.org/packages/52/f1/48325d049dc6361045b821c3e37e378e0df48a9ef992879f004f3b933072/cliglue-0.3.1.tar.gz" } ], "1.0.0": [ { "comment_text": "", "digests": { "md5": "b4efcbfdcb094a4ee8da2a9c095659fd", "sha256": "500596f3a1cfbc3e669e4d17ab41ee68710be98ba81eb11af40cf24ed43a6aff" }, "downloads": -1, "filename": "cliglue-1.0.0-py3-none-any.whl", "has_sig": false, "md5_digest": "b4efcbfdcb094a4ee8da2a9c095659fd", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6.0", "size": 57420, "upload_time": "2019-08-03T21:31:31", "url": "https://files.pythonhosted.org/packages/74/63/af87c0e277ca0bc01578152f030eade19bb2f183e9045e4b901be1f6c5b6/cliglue-1.0.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "eca8b4b8fa5c9033b00be6327ebbaf72", "sha256": "7cc6e515d8120e74eb8e2d233796b5d73810baf931daef5655601b84763b00dd" }, "downloads": -1, "filename": "cliglue-1.0.0.tar.gz", "has_sig": false, "md5_digest": "eca8b4b8fa5c9033b00be6327ebbaf72", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6.0", "size": 64666, "upload_time": "2019-08-03T21:31:34", "url": "https://files.pythonhosted.org/packages/1b/24/3a445eed4b60fc81befd40b56a4ed7e5b23b5a57d1d6e5948d7aa9640134/cliglue-1.0.0.tar.gz" } ], "1.0.4": [ { "comment_text": "", "digests": { "md5": "72a38d2bd29c47c637976f750a0802db", "sha256": "f451c87ab5a1f49e80c0445699182648c201067d3d355b918dbc64fa3f8d13e1" }, "downloads": -1, "filename": "cliglue-1.0.4-py3-none-any.whl", "has_sig": false, "md5_digest": "72a38d2bd29c47c637976f750a0802db", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6.0", "size": 58379, "upload_time": "2019-09-18T21:08:47", "url": "https://files.pythonhosted.org/packages/c5/c7/e1cbdf13dd806a4c881e091e30bbb2d5f957efb925603815955a1158fd56/cliglue-1.0.4-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "1bf4fcb5385e1cd694b4e32bbea57656", "sha256": "eee1caed0ac44c471811aae6a7e032f945faa5d23dc06e33d140acb6f9b6f332" }, "downloads": -1, "filename": "cliglue-1.0.4.tar.gz", "has_sig": false, "md5_digest": "1bf4fcb5385e1cd694b4e32bbea57656", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6.0", "size": 65690, "upload_time": "2019-09-18T21:08:50", "url": "https://files.pythonhosted.org/packages/73/7d/70566c8fbe3a8fd5a6e00febf26b454470ac13a6381cc5a35dc88c5285ee/cliglue-1.0.4.tar.gz" } ], "1.0.5": [ { "comment_text": "", "digests": { "md5": "825fb2fd00c9258308fd4872fdc76b4b", "sha256": "f0acee3abdbde72825cfc3dd0da7a76455668b92f9b24fbb298d4a9dfcacab92" }, "downloads": -1, "filename": "cliglue-1.0.5-py3-none-any.whl", "has_sig": false, "md5_digest": "825fb2fd00c9258308fd4872fdc76b4b", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6.0", "size": 58831, "upload_time": "2019-09-22T12:20:18", "url": "https://files.pythonhosted.org/packages/44/91/7aab6fe558131a70969b6e142afd5d211c630bf2dd6a99dcfd9188752034/cliglue-1.0.5-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "744a2580787a5cbe0722ab75b407520c", "sha256": "7544283d256739afcc811917dc6bef39074de34943d17d51a3d5df727e92b236" }, "downloads": -1, "filename": "cliglue-1.0.5.tar.gz", "has_sig": false, "md5_digest": "744a2580787a5cbe0722ab75b407520c", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6.0", "size": 66133, "upload_time": "2019-09-22T12:20:21", "url": "https://files.pythonhosted.org/packages/cb/d6/f679f4dd5f4756951d1587a192c698635e7a4f870887c6ca3dd6e40ab8ce/cliglue-1.0.5.tar.gz" } ], "1.0.6": [ { "comment_text": "", "digests": { "md5": "1a78708dede5b1435ef3e561629788e9", "sha256": "24278cf2af56bd9ad48692a0927813119b11cfc6ef13fd645a7292fe873d4e14" }, "downloads": -1, "filename": "cliglue-1.0.6-py3-none-any.whl", "has_sig": false, "md5_digest": "1a78708dede5b1435ef3e561629788e9", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6.0", "size": 59283, "upload_time": "2019-10-01T19:25:53", "url": "https://files.pythonhosted.org/packages/d0/ed/18e51a533c685f96e3fbb47a9f8164facc5b046290326b8db48fe673fa8b/cliglue-1.0.6-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "3c8e2a13085390d170478d568f9d0f1c", "sha256": "a632228cef634fdee9e4d037ea6ab43c5e42163ce8633d135db8c502df3dadc0" }, "downloads": -1, "filename": "cliglue-1.0.6.tar.gz", "has_sig": false, "md5_digest": "3c8e2a13085390d170478d568f9d0f1c", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6.0", "size": 66527, "upload_time": "2019-10-01T19:25:57", "url": "https://files.pythonhosted.org/packages/75/d2/98dc8ec84535342cc950b71b6a69dbb9b5a8163e4017ff6f2e6d3f16dc04/cliglue-1.0.6.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "1a78708dede5b1435ef3e561629788e9", "sha256": "24278cf2af56bd9ad48692a0927813119b11cfc6ef13fd645a7292fe873d4e14" }, "downloads": -1, "filename": "cliglue-1.0.6-py3-none-any.whl", "has_sig": false, "md5_digest": "1a78708dede5b1435ef3e561629788e9", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6.0", "size": 59283, "upload_time": "2019-10-01T19:25:53", "url": "https://files.pythonhosted.org/packages/d0/ed/18e51a533c685f96e3fbb47a9f8164facc5b046290326b8db48fe673fa8b/cliglue-1.0.6-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "3c8e2a13085390d170478d568f9d0f1c", "sha256": "a632228cef634fdee9e4d037ea6ab43c5e42163ce8633d135db8c502df3dadc0" }, "downloads": -1, "filename": "cliglue-1.0.6.tar.gz", "has_sig": false, "md5_digest": "3c8e2a13085390d170478d568f9d0f1c", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6.0", "size": 66527, "upload_time": "2019-10-01T19:25:57", "url": "https://files.pythonhosted.org/packages/75/d2/98dc8ec84535342cc950b71b6a69dbb9b5a8163e4017ff6f2e6d3f16dc04/cliglue-1.0.6.tar.gz" } ] }