معرفی شرکت ها


cliglue-1.1.3


Card image cap
تبلیغات ما

مشتریان به طور فزاینده ای آنلاین هستند. تبلیغات می تواند به آنها کمک کند تا کسب و کار شما را پیدا کنند.

مشاهده بیشتر
Card image cap
تبلیغات ما

مشتریان به طور فزاینده ای آنلاین هستند. تبلیغات می تواند به آنها کمک کند تا کسب و کار شما را پیدا کنند.

مشاهده بیشتر
Card image cap
تبلیغات ما

مشتریان به طور فزاینده ای آنلاین هستند. تبلیغات می تواند به آنها کمک کند تا کسب و کار شما را پیدا کنند.

مشاهده بیشتر
Card image cap
تبلیغات ما

مشتریان به طور فزاینده ای آنلاین هستند. تبلیغات می تواند به آنها کمک کند تا کسب و کار شما را پیدا کنند.

مشاهده بیشتر
Card image cap
تبلیغات ما

مشتریان به طور فزاینده ای آنلاین هستند. تبلیغات می تواند به آنها کمک کند تا کسب و کار شما را پیدا کنند.

مشاهده بیشتر

توضیحات

Declarative parser for command line interfaces
ویژگی مقدار
سیستم عامل -
نام فایل cliglue-1.1.3
نام cliglue
نسخه کتابخانه 1.1.3
نگهدارنده []
ایمیل نگهدارنده []
نویسنده igrek51
ایمیل نویسنده igrek51.dev@gmail.com
آدرس صفحه اصلی https://github.com/igrek51/cliglue
آدرس اینترنتی https://pypi.org/project/cliglue/
مجوز MIT
# cliglue - glue for CLI [![GitHub version](https://badge.fury.io/gh/igrek51%2Fcliglue.svg)](https://github.com/igrek51/cliglue) [![PyPI version](https://badge.fury.io/py/cliglue.svg)](https://pypi.org/project/cliglue) [![Documentation Status](https://readthedocs.org/projects/cliglue/badge/?version=latest)](https://cliglue.readthedocs.io/en/latest/?badge=latest) [![Build Status](https://travis-ci.org/igrek51/cliglue.svg?branch=master)](https://travis-ci.org/igrek51/cliglue) [![Coverage Status](https://coveralls.io/repos/github/igrek51/cliglue/badge.svg?branch=master)](https://coveralls.io/github/igrek51/cliglue?branch=master) [![codecov](https://codecov.io/gh/igrek51/cliglue/branch/master/graph/badge.svg)](https://codecov.io/gh/igrek51/cliglue) `cliglue` is a declarative parser for command line interfaces in Python. It's a binding glue between CLI shell arguments and functions being invoked. It mostly focuses on building multi level command trees. `cliglue` parses and validates command line arguments provided by user when running console application. Then it automatically triggers matched action, based on the declared Command-Line Interface rules, injecting all needed parameters. You don't need to write the "glue" code for binding & parsing parameters every time. So it makes writing console aplications simpler and more clear. ## Features - [Auto-generated help and usage](https://cliglue.readthedocs.io/en/latest#auto-generated-help) (`--help`) - [Shell autocompletion](https://cliglue.readthedocs.io/en/latest#auto-completion) (getting most relevant hints on hitting `Tab`) - [Multilevel sub-commands](https://cliglue.readthedocs.io/en/latest#sub-commands) (e.g. `git remote add ...` syntax) - [Named parameters](https://cliglue.readthedocs.io/en/latest#named-parameters): supporting both `--name value` and `--name=value`, multiple parameter occurrences - [Flags](https://cliglue.readthedocs.io/en/latest#flags): supporting both short (`-f`) and long (`--force`), combining short flags (`-tulpn`), multiple flag occurrences (`-vvv`) - [Positional arguments](https://cliglue.readthedocs.io/en/latest#positional-arguments) (e.g. `git push <origin> <master>`) - [Key-value dictionaries](https://cliglue.readthedocs.io/en/latest#dictionaries) (e.g. `--config key value`) - [Invoking matched action function & injecting parameters](https://cliglue.readthedocs.io/en/latest#injecting-parameters) - [Custom type validators / parsers](https://cliglue.readthedocs.io/en/latest#custom-type-parsers) - [Custom auto-completers](https://cliglue.readthedocs.io/en/latest#custom-completers) (providers of possible values) - [Handling syntax errors, parameters validation](https://cliglue.readthedocs.io/en/latest#errors-handling) - [Typed values](https://cliglue.readthedocs.io/en/latest#data-types) (int, time, date, file, etc.) - [Standard options](https://cliglue.readthedocs.io/en/latest#clibuilder) enabled by default (`--help`, `--version`) - [Declarative CLI builder](https://cliglue.readthedocs.io/en/latest#cli-rules-cheatsheet) ## Quick start Let's create a simple command-line application using `cliglue`. Let's assume we already have our fancy functions as follows: ```python def say_hello(name: str, decode: bool, repeat: int): if decode: name = base64.b64decode(name).decode('utf-8') print(' '.join([f"I'm a {name}!"] * repeat)) def calculate_factorial(n: int): print(reduce(lambda x, y: x * y, range(1, n + 1))) def calculate_primes(n: int): print(sorted(reduce((lambda r, x: r - set(range(x ** 2, n, x)) if (x in r) else r), range(2, int(n ** 0.5)), set(range(2, n))))) ``` and we need a "glue" which binds them with a CLI (Command-Line Interface). We want it to be run with different keywords and parameters provided by user to the terminal shell in a following manner: - `./quickstart.py hello NAME --decode --repeat=3` mapped to `say_hello` function, - `./quickstart.py calculate factorial N` mapped to `calculate_factorial` function, - `./quickstart.py calculate primes N` mapped to `calculate_primes` function, We've just identified 2 main commands in a program: `hello` and `calculate` (which in turn contains 2 subcommands: `factorial` & `primes`). That forms a tree: - `hello` command has one positional argument `NAME`, one boolean flag `decode` and one numerical parameter `repeat`. - `calculate` command has 2 another subcommands: * `factorial` subcommand has one positional argument `N`, * `primes` subcommand has one positional argument `N`, So our CLI definition may be declared using `cliglue` in a following way: ```python CliBuilder().has( subcommand('hello', run=say_hello).has( argument('name'), flag('decode', help='Decode name as base64'), parameter('repeat', type=int, default=1), ), subcommand('calculate').has( subcommand('factorial', help='Calculate factorial', run=calculate_factorial).has( argument('n', type=int), ), subcommand('primes', help='List prime numbers using Sieve of Eratosthenes', run=calculate_primes).has( argument('n', type=int, required=False, default=100, help='maximum number to check'), ), ), ) ``` Getting it all together, we've bound our function with a Command-Line Interface: **quickstart.py**: ```python #!/usr/bin/env python3 import base64 from functools import reduce from cliglue import CliBuilder, argument, flag, parameter, subcommand def say_hello(name: str, decode: bool, repeat: int): if decode: name = base64.b64decode(name).decode('utf-8') print(' '.join([f"I'm a {name}!"] * repeat)) def calculate_factorial(n: int): print(reduce(lambda x, y: x * y, range(1, n + 1))) def calculate_primes(n: int): print(sorted(reduce((lambda r, x: r - set(range(x ** 2, n, x)) if (x in r) else r), range(2, int(n ** 0.5)), set(range(2, n))))) CliBuilder().has( subcommand('hello', run=say_hello).has( argument('name'), flag('decode', help='Decode name as base64'), parameter('repeat', type=int, default=1), ), subcommand('calculate').has( subcommand('factorial', help='Calculate factorial', run=calculate_factorial).has( argument('n', type=int), ), subcommand('primes', help='List prime numbers using Sieve of Eratosthenes', run=calculate_primes).has( argument('n', type=int, required=False, default=100, help='maximum number to check'), ), ), ).run() ``` Let's trace what is happening here: - `CliBuilder()` builds CLI tree for entire application. - `.has(...)` allows to embed other nested rules inside that builder. Returns `CliBuilder` itself for further building. - `subcommand('hello', run=say_hello)` binds `hello` command to `say_hello` function. From now, it will be invoked when `hello` command occurrs. - `subcommand.has(...)` embeds nested subrules on lower level for that subcommand only. - `argument('name')` declares positional argument. From now, first CLI argument (after binary name and commands) will be recognized as `name` variable. - `flag('decode')` binds `--decode` keyword to a flag named `decode`. So as it may be used later on. Providing `help` adds description to help screen. - `parameter('repeat', type=int, default=1)` binds `--repeat` keyword to a parameter named `repeat`, which type is `int` and its default value is `1`. - Finally, invoking `.run()` does all the magic. It gets system arguments list, starts to process them and invokes most relevant action. ### Help / Usage `CliBuilder` has some basic options added by default, e.g. `--help`. Thus, you can check the usage by running application with `--help` flag: ```console foo@bar:~$ ./quickstart.py --help Usage: ./quickstart.py [COMMAND] [OPTIONS] Options: -h, --help [SUBCOMMANDS...] - Display this help and exit Commands: hello NAME calculate factorial N - Calculate factorial calculate primes [N] - List prime numbers using Sieve of Eratosthenes Run "./quickstart.py COMMAND --help" for more information on a command. ``` As prompted, we can check more detailed subcommand helps: ```console foo@bar:~$ ./quickstart.py hello --help Usage: ./quickstart.py hello [OPTIONS] NAME Arguments: NAME Options: --decode - Decode name as base64 --repeat REPEAT - Default: 1 -h, --help [SUBCOMMANDS...] - Display this help and exit ``` ### Injecting parameters Let's invoke `say_hello` function on a first run. Now when we execute our application with required argument provided, we get: ```console foo@bar:~$ ./quickstart.py hello world I'm a world! ``` Note that `world` has been recognized as `name` argument. We've binded `say_hello` as a default action, so it has been invoked with particular parameters: ```python say_hello(name='world', decode=False, repeat=1) ``` - positional argument `name` has been assigned a `'world'` value. - flag `decode` was not given, so it's `False` by default. - parameter `repeat` was not given either, so it was set to its default value `1`. Let's provide all of the parameters explicitly, then we get: ```console foo@bar:~$ ./quickstart.py hello UGlja2xl --decode --repeat=3 I'm a Pickle! I'm a Pickle! I'm a Pickle! ``` Or we can do the same in arbitrary order: ```console foo@bar:~$ ./quickstart.py hello --repeat 3 --decode UGlja2xl I'm a Pickle! I'm a Pickle! I'm a Pickle! ``` Invoking other subcommands is just as easy: ```console foo@bar:~$ ./quickstart.py calculate primes 50 [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 49] ``` When you are writing function for your action and you need to access some of the variables (flags, parameters, arguments, etc.), just simply add a parameter to the function with a name same as the variable you need. Then, the proper value will be parsed and injected by `cliglue`. ## How does it work? 1. You define all required CLI rules for your program in a declarative tree. 2. User provides command-line arguments when running program in a shell. 3. `cliglue` parses and validates all the parameters, flags, sub-commands, positional arguments, etc, and stores them internally. 4. `cliglue` finds the most relevant action (starting from the most specific) and invokes it. 5. When invoking a function, `cliglue` injects all its needed parameters based on the previously defined & parsed values. You only need to bind the keywords to the rules and `cliglue` will handle all the rest for you. ## `cliglue` vs `argparse` Why use `cliglue`, since Python has already `argparse`? Here are some subjective advantages of `cliglue`: - declarative way of CLI logic in one place, - autocompletion out of the box, - easier way of building multilevel sub-commands, - automatic action binding & injecting arguments, no need to pass `args` to functions manually, - CLI logic separated from the application logic, - 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), - CLI definition code as a clear documentation. ### Migrating from `argparse` to `cliglue` #### Migrating: Sub-commands argparse: ```python def foo(args): print(args.x * args.y) def bar(args): print(args.z) parser = argparse.ArgumentParser() subparsers = parser.add_subparsers() parser_foo = subparsers.add_parser('foo', help='foo help') parser_foo.add_argument('-x', type=int, default=1) parser_foo.add_argument('y', type=float) parser_foo.set_defaults(func=foo) parser_bar = subparsers.add_parser('bar', help='bar help') parser_bar.add_argument('z') parser_bar.set_defaults(func=bar) args = parser.parse_args() args.func(args) ``` with cliglue it's much simpler and more clear: ```python def foo(x, y): print(x * y) def bar(z): print(z) CliBuilder().has( subcommand('foo', help='foo help', run=foo).has( parameter('-x', type=int, default=1), argument('y', type=float), ), subcommand('bar', help='bar help', run=bar).has( argument('z'), ), ).run() ``` #### Migrating: Basic CLI argparse: ```python import argparse parser = argparse.ArgumentParser(description='Program description') [here come the rules...] args = parser.parse_args() do_something(args) ``` cliglue: ```python from cliglue import CliBuilder CliBuilder(help='Program description', run=do_something).has( [here come the rules...] ).run() ``` #### Migrating: Flags argparse: ```python parser.add_argument("-v", "--verbose", help="increase output verbosity", action="store_true") ``` cliglue: ```python flag("-v", "--verbose", help="increase output verbosity"), ``` #### Migrating: Positional arguments argparse: ```python parser.add_argument("square", help="display a square of a given number", type=int) ``` cliglue: ```python argument("square", help="display a square of a given number", type=int), ``` #### Migrating: Transferring values to functions argparse: ```python do_action(args.square, args.verbose) ``` cliglue: ```python CliBuilder(run=do_action) # invoking actions is done automatically by binding ``` ## Installation ### Step 1. Prerequisites - Python 3.6 (or newer) - pip #### on Debian 10 (buster) ```bash sudo apt install python3.7 python3-pip ``` #### on Debian 9 (stretch) Unfortunately, Debian stretch distribution does not have Python 3.6+ in its repositories, but it can be compiled from the source: ```bash wget https://www.python.org/ftp/python/3.6.9/Python-3.6.9.tgz tar xvf Python-3.6.9.tgz cd Python-3.6.9 ./configure --enable-optimizations --with-ensurepip=install make -j8 sudo make altinstall ``` #### on Ubuntu 18 ```bash sudo apt install python3.6 python3-pip ``` #### on Centos 7 ```bash sudo yum install -y python36u python36u-libs python36u-devel python36u-pip ``` #### on Fedora ```bash sudo dnf install python36 ``` ### Step 2. Install package using pip Install package from [PyPI repository](https://pypi.org/project/cliglue) using pip: ```bash pip3 install cliglue ``` Or using explicit python version: ```bash python3.6 -m pip install cliglue ``` ### Install package in develop mode You can install package in develop mode in order to make any changes for your own: ```bash pip3 install -r requirements.txt python3 setup.py develop ``` ## Testing Running tests: ```bash pip3 install -r requirements.txt -r requirements-dev.txt ./pytest.sh ``` ## CliBuilder `CliBuilder` is a main class of `cliglue` package which allows to build CLI definition. It's a builder for Command Line Interface specification. After that, you can invoke `.run()` method in order to parse provided arguments and invoke particular actions. Empty CliBuilder has standard options enabled by default: - `--help` - displaying usage and help - `--version` - displaying application version number (if it has been defined) ### Step 1. Creating CliBuilder In this step you can create new `CliBuilder` and set a custom configuration for it. The constructor is as follows: ```python from cliglue import CliBuilder CliBuilder( name: Optional[str] = None, version: Optional[str] = None, help: Optional[str] = None, run: Optional[Action] = None, with_defaults: bool = True, usage_onerror: bool = True, reraise_error: bool = False, hide_internal: bool = True, ) ``` `name` - name of the application for which the CLI is built `version` - application version (displayed in help/version output) `help` - short description of application `run` - reference for a function which should be the default action for empty arguments list `with_defaults` - whether default rules and actions should be added. Defaults options are: -h, --help: displaying help, --version: displaying version, --install-bash APP-NAME: installing application in bash with autocompleting, --autocomplete [CMDLINE...]: internal action for generating autocompleted proposals to be handled by bash `usage_onerror` - wheter usage output should be displayed on syntax error `reraise_error` - wheter syntax error should not be caught but reraised instead. Enabling this causes stack trace to be flooded to the user. `hide_internal` - wheter internal options (`--install-bash`, `--autocomplete`) should be hidden on help output. ### Step 2. Declaring CLI rules The next step is to declare CLI rules for `CliBuilder` using `.has()` method `has(*subrules: CliRule) -> 'CliBuilder'` method receives a CLI rules in its parameters and returns the `CliBuilder` itself for further building. It is used to introduce the next level of sub-rules. Available rules are: - subcommand - flag - parameter - argument - arguments - default_action - primary_option Example: ```python from cliglue import CliBuilder, argument, parameter, flag, subcommand, arguments, default_action CliBuilder('multiapp', version='1.0.0', help='many apps launcher', with_defaults=True, usage_onerror=False, reraise_error=True).has( subcommand('checkout'), argument('commit'), arguments('files'), flag('-u', '--upstream', help='set upstream'), parameter('--count', type=int, required=True), default_action(lambda: print('default action')), ) ``` ### Step 3. Running CLI arguments through parser The final step is calling `.run()` on `CliBuilder`. It parses all the CLI arguments passed to application. Then it invokes triggered action which were defined before. If actions need some parameters, they will be injected based on the parsed arguments. Running empty builder: ```python from cliglue import CliBuilder CliBuilder().run() ``` just prints the standard help output, because it's the default action for an empty builder if no arguments are provided. ## Sub-commands Commands may form a multilevel tree with nested sub-commands. Sub-commands syntax is commonly known, e.g.: - `git remote rename ...` - `docker container ls` - `nmcli device wifi list` - `ip address show` Sub-commands split the CLI into many nested CLI levels, forming a tree. They decide where to direct the parser, which seeks for a most relevant action to invoke and decides which rules are active. Sub-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. Subcommand can have more subrules which are activated only when corresponding subcommand is active. So subcommand is just a keyword which narrows down the context. ### Sub-commands specification In order to create subcommand rule specification, use: ```python from cliglue import subcommand subcommand( *keywords: str, run: Optional[Action] = None, help: str = None, ) ``` `keywords` - possible keyword arguments which any of them triggers a subcommand `run` - optional action to be invoked when subcommand is matched `help` - description of the parameter displayed in help output ### Nesting sub-commands With sub-commands, you can nest other CLI rules. They will be active only when corresponding subcommand is active. Subrules can be nested using `.has(*subrules: CliRule)` method. It returns itself for further building, so it can be used just like `CliBuilder`: ```python from cliglue import CliBuilder, argument, subcommand CliBuilder().has( subcommand('nmcli').has( subcommand('device').has( subcommand('wifi').has( subcommand('list'), ), ), ), subcommand('ip').has( subcommand('address', 'a').has( subcommand('show'), subcommand('del').has( argument('interface'), ), ), ), ) ``` In that manner, the formatted code above is composing a visual tree, which is clear. ### Sub-commands example: subcommands.py ```python #!/usr/bin/env python3 from cliglue import CliBuilder, subcommand CliBuilder('subcommands-demo', run=lambda: print('default action')).has( subcommand('remote', run=lambda: print('action remote')).has( subcommand('push', run=lambda: print('action remote push')), subcommand('rename', run=lambda: print('action remote rename')), ), subcommand('checkout', run=lambda: print('action checkout')), subcommand('branch', run=lambda: print('action branch')), ).run() ``` Usage is quite self-describing: ```console foo@bar:~$ ./subcommands.py remote action remote foo@bar:~$ ./subcommands.py remote push action remote push foo@bar:~$ ./subcommands.py branch action branch foo@bar:~$ ./subcommands.py default action foo@bar:~$ ./subcommands.py --help subcommands-demo Usage: ./subcommands.py [COMMAND] [OPTIONS] Options: -h, --help [SUBCOMMANDS...] - Display this help and exit Commands: remote - List remotes remote push remote rename checkout - Switch branches branch - List branches Run "./subcommands.py COMMAND --help" for more information on a command. ``` See [sub-commands tests](https://github.com/igrek51/cliglue/blob/master/tests/parser/test_subcommand.py) for more detailed use cases. ## Flags Flag is a boolean parameter which is toggled by single keyword. There are supported both short (`-f`) and long (`--force`) formats. In order to create flag rule specification, use: ```python from cliglue import flag flag( *keywords: str, help: str = None, multiple: bool = False, ) ``` `keywords` are arguments (one or many) which any of them enables flag when it occurs. Flag value is `False` by default. Flag keywords may be passed using direct format: `-f` or `--flag`, as well as by name: `f` or `flag`, which will be also evaluated to `-f` or `--flag`. Single character flags will get single hyphen prefix (`-f`), longer flag names will get double hyphen prefix (`--flag`). `help` is description of the flag displayed in help output `multiple` - whether flag is allowed to occur many times. Then flag has int type and stores number of its occurrences Example: ```python from cliglue import CliBuilder, flag CliBuilder(run=lambda force: print(force)).has( flag('--force', '-f'), ).run() ``` Usage: ```console foo@bar:~$ ./example.py --force True foo@bar:~$ ./example.py False ``` ### Combining short flags Many short flags may be combined in one argument. Instead of `-t -u -l -p -n` you can just type `-tulpn`. ### Multiple flag occurrences Multiple 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`. ```python CliBuilder(run=lambda verbose: print(f'verbosity level: {verbose}')).has( flag('verbose', 'v', multiple=True), ).run() ``` Then `-vvv` should return `3`. See [flag tests](https://github.com/igrek51/cliglue/blob/master/tests/parser/test_flag.py) for more detailed use cases. ## Named parameters Parameter is a named value, which will be injected to triggered action by its name. There are supported both manners for setting parameter value: `--parameter-name value` or `--parameter-name=value` Named parameters may appear anywhere in CLI arguments list: at the beginning or at the end, or even before positional arguments. As long as they are matched as named parameters, they will not be recognized as positional arguments. The parameters may be later referenced by its name or keywords (in lowercase format without hyphen prefix and with underscores instead of dashes, e.g. `--paramater-name` will be injected as `parameter_name`) In order to create parameter rule specification, use: ```python from cliglue import parameter parameter( *keywords: str, name: str = None, help: str = None, required: bool = False, default: Any = None, type: TypeOrParser = str, choices: ChoiceProvider = None, strict_choices: bool = False, multiple: bool = False, ) ``` `keywords` keyword arguments which are matched to parameter. Parameter keywords may be passed using direct format: `-p` or `--param`, as well as by name: `p` or `param`, which will be evaluated to `-p` or `--param`. Single character parameter will get single hyphen prefix (`-p`), longer parameter names will get double hyphen prefix (`--param`) `name` is explicit paramter name (can be used, when it's different from any keyword) `help` is description of the parameter displayed in help output `required` tells whether parameter is required. If it's required but it's not given, the syntax error will be raised. `default` is default value for the parameter, if it's not given (and it's not required). `type` is a type of parameter value (e.g. str, int, float). Reference to a parser function may be provided here as well. Then parameter value is evaluated by passing the string argument value to that function. `choices` is Explicit list of available choices for the parameter value or reference to a function which will be invoked to retrieve such possible values list `strict_choices` - whether given arguments should be validated against available choices `multiple` - whether parameter is allowed to occur many times. Then parameter has list type and stores list of values Basic parameter example: ```python from cliglue import CliBuilder, parameter CliBuilder(run=lambda param: print(param)).has( parameter('param', 'p'), ).run() ``` ```console foo@bar:~$ ./example.py --param OK OK foo@bar:~$ ./example.py --param=OK OK foo@bar:~$ ./example.py -p OK OK foo@bar:~$ ./example.py None ``` ### Multiple parameter occurrences Multiple occurences are also supported for parameters. When `multiple` is set to `True`, then the parameter value represents list of values and can be appended mutliple times. The value type is then `list`. ```python def what_to_skip(skip: List[str]): print(f'skipping: {skip}') CliBuilder(run=what_to_skip).has( parameter('skip', multiple=True, type=str), ).run() ``` ```console foo@bar:~$ ./example.py --skip build --skip run skipping: ['build', 'run'] ``` See [parameter tests](https://github.com/igrek51/cliglue/blob/master/tests/parser/test_param.py) for more detailed use cases. ## Positional arguments Positional argument is an unnamed parameter, which is recognized only by its position on their order in command line arguments list. For example first two arguments (except flags and named parameters) may be detected as positional arguments and matched to corresponding variables. ### Single positional arguments Let's assume we have CLI syntax: `git push <origin> <master>`. `git` is application binary name of course, `push` is a sub-command, which have 2 positional arguments: `origin` and `master`. In order to create positional argument rule specification, use: ```python from cliglue import argument def argument( name: str, help: str = None, required: bool = True, default: Any = None, type: TypeOrParser = str, choices: ChoiceProvider = None, strict_choices: bool = False, ) ``` `name` - internal argument name, which will be used to reference argument value `help` - description of the argument displayed in help output `required` - whether positional argument is required. If it's required but it's not given, the syntax error will be raised. `default` - default value for the argument, if it's not given (and it's not required) `type` - type of argument value (e.g. str, int, float) Reference to a parser function may be provided here as well. Then argument value is evaluated by passing the string argument value to that function. `choices` - Explicit list of available choices for the argument value or reference to a function which will be invoked to retrieve such possible values list. `strict_choices` - whether given arguments should be validated against available choices #### Example: pos-args.py ```python #!/usr/bin/env python3 from cliglue import CliBuilder, argument def print_args(remote: str, branch: str): print(f'remote: {remote}, argument: {branch}') CliBuilder('pos-args', run=print_args).has( argument('remote', help='remote name', type=str, choices=['origin', 'local']), argument('branch', help='branch name', required=False, default='master'), ).run() ``` Usage: ```console foo@bar:~$ ./pos-args.py --help pos-args Usage: ./pos-args.py [OPTIONS] REMOTE [BRANCH] Options: -h, --help [SUBCOMMANDS...] - Display this help and exit foo@bar:~$ ./pos-args.py [ERROR] Syntax error: required positional argument "remote" is not given pos-args Usage: ./pos-args.py [OPTIONS] REMOTE [BRANCH] Options: -h, --help [SUBCOMMANDS...] - Display this help and exit foo@bar:~$ ./pos-args.py origin remote: origin, argument: master foo@bar:~$ ./pos-args.py origin develop remote: origin, argument: develop ``` See [positional arguments tests](https://github.com/igrek51/cliglue/blob/master/tests/parser/test_positional_argument.py) as a specification. ## Many positional arguments `cliglue` allows to match all remaining (not already matched) arguments. It can be useful when using syntax like `docker cmd`: ```docker run cmd ubuntu /bin/bash -c /script.sh``` With that syntax all arguments after `ubuntu` - `/bin/bash -c /script.sh` should be matched to one variable. You can do it with `cliglue` using `arguments`. That rule will force parser to store all remaining arguments in a list variable (or in a joined string). In order to create "multiple arguments" rule specification, use: ```python from cliglue import arguments def arguments( name: str, type: Union[Type, Callable[[str], Any]] = str, choices: Union[List[Any], Callable[..., List[Any]]] = None, strict_choices: bool = False, count: Optional[int] = None, min_count: Optional[int] = None, max_count: Optional[int] = None, joined_with: Optional[str] = None, help: str = None, ) ``` It allows to retrieve specific number of CLI argumetns or all remaining arguments. All matched arguments will be extracted to a list of arguments or a string (depending on `joined_with` parameter) `name` - internal variable name, which will be used to reference matched arguments `type` - explicit type of arguments values (e.g. str, int, float) Reference to a parser function may be provided here as well. Then argument value is evaluated by passing the string argument value to that function `choices` - Explicit list of available choices for the argument value or reference to a function which will be invoked to retrieve such possible values list. `strict_choices` - whether given arguments should be validated against available choices `count` - explicit number of arguments to retrieve. If undefined, there is no validation for arguments count. If you need particular number of arguments, you can use this count instead of setting min_count=max_count. `min_count` - minimum number of arguments. By default, there is no lower limit (it is 0). `max_count` - maximum number of arguments. If undefined, there is no upper limit for arguments count. `joined_with` - optional string joiner for arguments. If it's set, all matched arguments will be joined to string with that joiner. It it's not given, matched arguments will be passed as list of strings. This value (string or list) can be accessed by specified name, when it's being injected to a function. `help` - description of the arguments displayed in help output Note that `arguments(count=1)` rule with is like single `argument` rule, except that it stores list with one element. You can use many consecutive `arguments` rules as long as they have `count` or `max_count` set. ### Example: many-args.py ```python #!/usr/bin/env python3 from cliglue import CliBuilder, arguments, subcommand def run_cmd(cmd: str): print(f'cmd: {cmd}') CliBuilder('many-args').has( subcommand('run', run=run_cmd).has( arguments('cmd', joined_with=' '), ), ).run() ``` Usage: ```console foo@bar:~$ ./many-args.py many-args Usage: ./many-args.py [COMMAND] [OPTIONS] Options: -h, --help [SUBCOMMANDS...] - Display this help and exit Commands: run [CMD...] Run "./many-args.py COMMAND --help" for more information on a command. foo@bar:~$ ./many-args.py run /bin/bash -c script.sh cmd: /bin/bash -c script.sh foo@bar:~$ ./many-args.py run "/bin/bash -c script.sh" cmd: /bin/bash -c script.sh ``` See [many arguments tests](https://github.com/igrek51/cliglue/blob/master/tests/parser/test_many_arguments.py) for more detailed use cases. ## Dictionaries Dictionary contains key-value pairs. You can add multiple values to it by passing arguments in a manner: `-c name1 value1 -c name2 value2`. By default it stores empty Python dict. These values may be later referenced as dict by its explicit name or keywords (in lowercase format without hyphen prefix and with underscores instead of dashes, e.g. `--config-name` will be injected as `config_name` variable name) In order to create dictionary rule specification, use: ```python from cliglue import dictionary dictionary( *keywords: str, name: str = None, help: str = None, key_type: Union[Type, Callable[[str], Any]] = str, value_type: Union[Type, Callable[[str], Any]] = str, ) ``` `keywords` - keyword arguments which are matched to this dictionary. Keywords may be passed using direct format: `-c` or `--config`, as well as by name: `c` or `config`, which will be evaluated to `-c` or `--config`. Single character dictionary will get single hyphen prefix (`-c`), longer dictionary names will get double hyphen prefix (`--config`) `name` - explicit internal dictionary name (can be used to distinguish it from any keyword) `help` - description of the dictionary displayed in help output `key_type` - type of dictionary key (e.g. str, int, float) Reference to a parser function may be provided here as well. Then dictionary value is evaluated by passing the string argument value to that function. `value_type` - type of dictionary value (e.g. str, int, float) Reference to a parser function may be provided here as well. Then dictionary value is evaluated by passing the string argument value to that function. Basic dictionary example: ```python from cliglue import CliBuilder, dictionary CliBuilder(run=lambda config: print(config)).has( dictionary('config', 'c', value_type=int), ).run() ``` ```console foo@bar:~$ ./example.py --config key1 5 -c key2 42 {'key1': 5, 'key2': 42} foo@bar:~$ ./example.py {} ``` See [dictionaries tests](https://github.com/igrek51/cliglue/blob/master/tests/parser/test_dictionary.py) for more detailed use cases. ## Auto-completion Shell autocompletion allows to suggest most relevant hints on hitting `Tab` key, while typing a command line. Auto-completion provided by `cliglue` is enabled by default to all known keywords based on the declared subcommands and options. Defining possible choices may imporove auto-completing arguments. You can declare explicit possible values list or a function which provides such a list at runtime. **completers.py**: ```python #!/usr/bin/env python3 import re from typing import List from cliglue import CliBuilder, parameter, default_action from cliglue.utils.shell import shell, shell_output def list_screens() -> List[str]: """Return list of available screen names in a system""" xrandr = shell_output('xrandr 2>/dev/null') regex_matcher = re.compile(r'^([a-zA-Z0-9\-]+) connected(.*)') return [regex_matcher.sub('\\1', line) for line in xrandr.splitlines() if regex_matcher.match(line)] def adjust_screen(output: str, mode: str): shell(f'xrandr --output {output} --mode {mode}') CliBuilder('completers-demo').has( parameter('output', choices=list_screens, required=True), parameter('mode', choices=['640x480', '800x480', '800x600'], required=True), default_action(adjust_screen), ).run() ``` In order to enable auto-completion, you need to install some extension to bash. Fortunately `cliglue` has built-in tools to do that: ```console foo@bar:~$ sudo ./completers.py --install-bash completers-demo [info] creating link: /usr/bin/completers-demo -> ~/cliglue/docs/example/completers.py #!/bin/bash _autocomplete_98246661() { COMPREPLY=( $(completers-demo --autocomplete "${COMP_LINE}") ) } complete -F _autocomplete_98246661 completers-demo [info] Autocompleter has been installed in /etc/bash_completion.d/autocomplete_completers-demo.sh. Please restart your shell. ``` Now, we have `completers-demo` application installed in `/usr/bin/` (symbolic link to the current script) and bash completion script installed as well. We can hit `[Tab]` key to complete command when typing. Here are some completions examples: ```console foo@bar:~$ completers-d[Tab] foo@bar:~$ completers-demo foo@bar:~$ completers-demo [Tab][Tab] --autocomplete -h --mode --output --install-bash --help --mode= --output= foo@bar:~$ completers-demo --mo[Tab] foo@bar:~$ completers-demo --mode foo@bar:~$ completers-demo --mode [Tab][Tab] 640x480 800x480 800x600 foo@bar:~$ completers-demo --mode 640[Tab] foo@bar:~$ completers-demo --mode 640x480 foo@bar:~$ completers-demo --mode 640x480 --output [Tab][Tab] eDP-1 HDMI-1 ``` ### Custom completers You can provide your custom auto-completers (providers of possible values) to the `choices` parameter. The example is the function which returns a list of available screens: ```python def list_screens() -> List[str]: """Return list of available screen names in a system""" xrandr = shell_output('xrandr 2>/dev/null') regex_matcher = re.compile(r'^([a-zA-Z0-9\-]+) connected(.*)') return [regex_matcher.sub('\\1', line) for line in xrandr.splitlines() if regex_matcher.match(line)] ``` You can use it to validate and propose available choices for parameter or positional argument: ```python CliBuilder().has( parameter('output', choices=list_screens, required=True), ) ``` ### Installing Autocompletion In order to enable the autocompletion, there must be a specific script in `/etc/bash_completion.d/`. With `cliglue` you just need to run: ```console # sudo ./sample-app.py --install-bash sample-app ``` It will install autocompletion script and add a symbolic link in `/usr/bin/`, so as you can run your app with `sample-app` command instead of `./sample_app.py`. Now you can type `sample-app` and hit `Tab` to see what are the possible commands and options. If you type `sample-app --he`, it will automatically fill the only possible option: `--help`. Sometimes, you need to make some modifications in your code, but after these modifications you will NOT need to reinstall autocompletion again. You had to do it only once, because autocompletion script only redirects its query and run `sample_app.py`: ```console sample-app --autocomplete "sample-app --he" ``` ### How does auto-completion work? 1. While typing a command in `bash`, you hit `Tab` key. (`your-app.py cmd[TAB]`) 2. `bash` looks for an autocompletion script in `/etc/bash_completion.d/`. There should be a script installed for your command after running `--install-bash` on your application. So when it's found, this script is called by bash. 3. The autocompletion script redirects to your application, running it with `--autocomplete` option, namely script runs `your-app.py --autocomplete "cmd"`, asking it for returning the most relevant command proposals. Notice that in that manner, the autocompletion algorithm is being run always in up-to-date version. 4. `your-app.py` has `--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`). 5. 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. 6. `your-app.py` returns a list of proposals to the `bash`. 7. `bash` shows you these results. If there's only one matching proposal, the currently typed word is automatically filled. Note that your application is being run each time when trying to get matching arguments proposals. ## Auto-generated help `cliglue` auto-generates help and usage output based on the defined CLI rules. Let's say we have quite complex CLI definition: ```python CliBuilder('multiapp', version='1.0.0', help='many apps launcher', with_defaults=True, usage_onerror=False, reraise_error=True).has( subcommand('git').has( subcommand('push', run=git_push).has( argument('remote'), argument('branch', required=False), flag('-u', '--upstream', help='set upstream'), ), subcommand('help', help='show help', run=lambda: print('show help')), subcommand('checkout', 'co', help='checkout branch').has( argument('branch', choices=['master', 'feature', 'develop'], type=str), flag('force', 'f'), ), subcommand('remote', help='show remotes list').has( subcommand('set-url', 'rename', help="change remote's name").has( argument('remote-name', choices=['origin', 'backup'], type=str), argument('new-name'), ), ), parameter('--date', type=iso_datetime), parameter('--count', type=int, required=True), parameter('--work-tree', type=existing_directory, default='.', help='working directory'), ), subcommand('xrandr').has( parameter('output', required=True, choices=list_screens), flag('primary', 'p'), default_action(xrandr_run) ), subcommand('docker').has( subcommand('exec', run=docker_exec).has( parameter('-u', name='user', type=int), argument('container-name'), arguments(name='cmd', joined_with=' '), ), ), default_action(lambda: print('default action')), ) ``` We can see the usage and description of commands using `--help` or `-h`: ```console foo@bar:~$ python3 multiapp.py --help multiapp v1.0.0 - many apps launcher Usage: multiapp.py [COMMAND] [OPTIONS] Options: --version - Print version information and exit -h, --help [SUBCOMMANDS...] - Display this help and exit --install-bash APP-NAME - Install script as a bash binary and add autocompletion links --autocomplete [CMDLINE...] - Return matching autocompletion proposals Commands: git git push REMOTE [BRANCH] git help - show help git co|checkout BRANCH - checkout branch git remote - show remotes list git remote rename|set-url REMOTE-NAME NEW-NAME - change remote's name xrandr docker docker exec CONTAINER-NAME [CMD...] Run "multiapp.py COMMAND --help" for more information on a command. ``` ### Sub-commands help We can also check the usage for a selected sub-command only: ```console foo@bar:~$ python3 multiapp.py git --help multiapp v1.0.0 - many apps launcher Usage: multiapp.py git [COMMAND] [OPTIONS] Options: --date DATE --count COUNT --work-tree WORK_TREE - working directory --version - Print version information and exit -h, --help [SUBCOMMANDS...] - Display this help and exit Commands: push REMOTE [BRANCH] help - show help co|checkout BRANCH - checkout branch remote - show remotes list remote rename|set-url REMOTE-NAME NEW-NAME - change remote's name Run "multiapp.py git COMMAND --help" for more information on a command. ``` ### version check Use `--version` in order to show your application version: ```console foo@bar:~$ python3 multiapp.py --version multiapp v1.0.0 (cliglue v1.0.1) ``` ## Data types `cliglue` supports typed values for parameters or positional arguments. By default, all values have string types. It can be changed by defining `type` parameter. There are 2 possible `type` values: - type name itself (e.g. `int`, `str`, `float`) - reference for a parser which returns a value In both cases, the internal variable value is calculated by invoking `type(str_value)`. When argument value has invalid format, there is syntax error raised. ### Basic types (int, float, etc.) ```python #!/usr/bin/env python3 from cliglue import CliBuilder, argument def print_it(count: int): print(count * 2) CliBuilder(run=print_it).has( argument('count', type=int), ).run() ``` ```console foo@bar:~$ ./example.py 21 42 foo@bar:~$ ./example.py dupa [ERROR] Syntax error: parsing positional argument "count": invalid literal for int() with base 10: 'dupa' Usage: ./pyt.py [OPTIONS] COUNT Options: -h, --help [SUBCOMMANDS...] - Display this help and exit foo@bar:~$ ./example.py [ERROR] Syntax error: required positional argument "count" is not given Usage: ./pyt.py [OPTIONS] COUNT Options: -h, --help [SUBCOMMANDS...] - Display this help and exit ``` ### Built-in data types `cliglue` has built-in parsers / validators for some types #### Filesystem types - `cliglue.types.filesystem.existing_file` validates if given string is an existing regular file (not a directory). After validation, the value is internally stored as `str`. ```python from cliglue.types.filesystem import existing_file argument('file', type=existing_file) ``` - `cliglue.types.filesystem.existing_directory` validates if given string is an existing directory. After validation, the value is internally stored as `str`. ```python from cliglue.types.filesystem import existing_directory argument('file', type=existing_directory) ``` #### Datetime types - `cliglue.types.time.iso_date` parses / validates given string as a date in ISO format: `%Y-%m-%d`. After validation, the value is internally stored as `datetime.datetime`. ```python from cliglue.types.time import iso_date argument('date', type=iso_date) ``` - `cliglue.types.time.iso_time` parses / validates given string as a time in ISO format: `%H:%M:%S`. After validation, the value is internally stored as `datetime.datetime`. ```python from cliglue.types.time import iso_time argument('time', type=iso_time) ``` - `cliglue.types.time.iso_time` parses / validates given string as a datetime in ISO format: `%Y-%m-%d %H:%M:%S`. After validation, the value is internally stored as `datetime.datetime`. ```python from cliglue.types.time import iso_datetime argument('datetime', type=iso_datetime) ``` Example: ```python #!/usr/bin/env python3 from cliglue import CliBuilder, argument from cliglue.types.time import iso_datetime from datetime import datetime def print_it(to: datetime): print(to) CliBuilder(run=print_it).has( argument('to', type=iso_datetime), ).run() ``` ```console foo@bar:~$ ./example.py 2019-07-13 [ERROR] Syntax error: invalid datetime format: 2019-07-13 Usage: ./pyt.py [OPTIONS] TO Options: -h, --help [SUBCOMMANDS...] - Display this help and exit foo@bar:~$ ./example.py "2019-07-13 20:00:05" 2019-07-13 20:00:05 ``` - `cliglue.types.time.datetime_format` parses / validates given string as a datetime in custom formats specified by user. You may specify multiple formats and the CLI argument will be parsed sequentially for each format. The first successfully parsed datetime is returned. After that, the value is internally stored as `datetime.datetime`. ```python #!/usr/bin/env python3 from cliglue import CliBuilder, argument from cliglue.types.time import datetime_format from datetime import datetime def print_it(to: datetime): print(to) CliBuilder(run=print_it).has( argument('to', type=datetime_format('%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M', '%Y-%m-%d')), ).run() ``` ```console foo@bar:~$ ./example.py "2019-07-13 20:00:05" 2019-07-13 20:00:05 foo@bar:~$ ./example.py 2019-07-13 2019-07-13 00:00:00 ``` - `cliglue.types.time.today_format` parses / validates given string as a time in custom formats specified by user. It gets time from input and combines it with the today date. You may specify multiple formats and the CLI argument will be parsed sequentially for each format. The first successfully parsed datetime is returned. After that, the value is internally stored as `datetime.datetime`. ```python #!/usr/bin/env python3 from cliglue import CliBuilder, argument from cliglue.types.time import today_format from datetime import datetime def print_it(to: datetime): print(to) CliBuilder(run=print_it).has( argument('to', type=today_format('%H:%M:%S', '%H:%M')), ).run() ``` ```console foo@bar:~$ ./example.py 12:42 2019-07-13 12:15:00 ``` ### Custom type parsers You can define custom parser/validator function. It should take one `str` argument and return expected value type. ```python #!/usr/bin/env python3 import re from dataclasses import dataclass from cliglue import CliBuilder, argument @dataclass class Person: name: str age: int @staticmethod def parse(arg: str) -> 'Person': match = re.compile('(.+)-([0-9]+)').match(arg) return Person(match.group(1), int(match.group(2))) def print_it(human: Person): print(human) CliBuilder(run=print_it).has( argument('human', type=Person.parse), ).run() ``` ```console foo@bar:~$ ./example.py Eric-33 Person(name='Eric', age=33) ``` ## Errors handling `cliglue` validates passed CLI arguments on running `CliBuilder.run()` ### Handling syntax errors - CliSyntaxError In case of syntax error, `CliBuilder.run()` raises `CliSyntaxError`, it's when: - parameter value is missing: `--param-name` without next argument - required parameter is not given - required positional argument is not given - positiona argument or parameter has invalid type (there was parsing type error) By default `CliSyntaxError` caught by `CliBuilder` is rethrown. You can disable raising this error again by seting `reraise_error = False` when creating `CliBuilder`: `CliBuilder(reraise_error = False)`. Then only the error log will be displayed in console stdout. `usage_onerror` parameter decides wheter usage output should be displayed on syntax error. #### Erros handling example ```python #!/usr/bin/env python3 from cliglue import CliBuilder, argument CliBuilder(usage_onerror=False).has( argument('remote', required=True), ).run() ``` ```console foo@bar:~$ ./pos-args.py [ERROR] Syntax error: required positional argument "remote" is not given ``` ### CliDefinitionError In case of invalid CLI definition, `CliBuilder.run()` raises `CliDefinitionError`. It's e.g. when: - positional argument or parameter is set to required and has default value set (it doesn't make any sense) - positional argument is placed after all remaining arguments - parameter / argument value does not belong to strict available choices list #### Wrong CLI Definition example ```python #!/usr/bin/env python3 from cliglue import CliBuilder, argument CliBuilder().has( argument('remote', required=True, default='def'), ).run() ``` ```console foo@bar:~$ ./errors.py [ERROR] CLI Definition error: argument value may be either required or have the default value Traceback (most recent call last): File "pyt.py", line 4, in <module> argument('remote', required=True, default='def'), File "~/cliglue/cliglue/builder/builder.py", line 69, in run self.run_with_args(sys.argv[1:]) File "~/cliglue/cliglue/builder/builder.py", line 76, in run_with_args raise e File "~/cliglue/cliglue/builder/builder.py", line 73, in run_with_args Parser(self.__subrules).parse_args(args) File "~/cliglue/cliglue/parser/parser.py", line 37, in __init__ self._init_rules() File "~/cliglue/cliglue/parser/parser.py", line 57, in _init_rules raise CliDefinitionError('argument value may be either required or have the default value') cliglue.parser.error.CliDefinitionError: argument value may be either required or have the default value ``` ## CLI Rules cheatsheet Here is the cheatsheet for the most important CLI rules: ```python #!/usr/bin/env python3 from cliglue import CliBuilder, argument, arguments, flag, parameter, subcommand, dictionary def main(): CliBuilder('hello-app', version='1.0.0', help='welcome', run=say_hello).has( flag('--verbose', '-v', help='verbosity', multiple=True), parameter('repeat', 'r', help='how many times', type=int, required=False, default=1, choices=[1, 2, 3, 5, 8]), argument('name', help='description', required=False, default='world', type=str, choices=['monty', 'python']), arguments('cmd', joined_with=' '), subcommand('run', help='runs something').has( subcommand('now', 'n', run=lambda cmd: print(f'run now: {cmd}')), ), dictionary('config', 'c', help='configuration', key_type=str, value_type=int) ).run() def say_hello(name: str, verbose: int, repeat: int, cmd: str, config: dict): print(f'Hello {name}') if __name__ == '__main__': main() ``` ## Complex CLI tree Here's an example of more complex CLI definition tree: **multiapp.py**: ```python #!/usr/bin/env python3 from cliglue import CliBuilder, argument, parameter, flag, subcommand, arguments, default_action from cliglue.types.filesystem import existing_directory from cliglue.types.time import iso_datetime def main(): CliBuilder('multiapp', version='1.0.0', help='many apps launcher', with_defaults=True, usage_onerror=False, reraise_error=True, hide_internal=True).has( subcommand('git').has( subcommand('push', run=git_push).has( argument('remote'), argument('branch', required=False), flag('-u', '--upstream', help='set upstream'), ), subcommand('help', help='show help', run=lambda: print('show help')), subcommand('checkout', 'co', help='checkout branch').has( argument('branch', choices=['master', 'feature', 'develop'], type=str), flag('force', 'f'), ), subcommand('remote', help='show remotes list').has( subcommand('set-url', 'rename', help="change remote's name").has( argument('remote-name', choices=['origin', 'backup'], type=str), argument('new-name'), ), ), parameter('--date', type=iso_datetime), parameter('--count', type=int, required=True), parameter('--work-tree', type=existing_directory, default='.', help='working directory'), ), subcommand('xrandr').has( parameter('output', required=True, choices=list_screens), flag('primary', 'p'), default_action(xrandr_run) ), subcommand('docker').has( subcommand('exec', run=docker_exec).has( parameter('-u', name='user', type=int), argument('container-name'), arguments(name='cmd', joined_with=' '), ), ), default_action(lambda: print('default action')), ).run() def git_push(remote: str, branch: str, upstream: bool): print(f'git push: {remote}, {branch}, {upstream}') def xrandr_run(output, primary): print(f'xrandr: {output} {primary}') def list_screens(): return ['eDP1', 'HDMI2'] def docker_exec(user: int, container_name: str, cmd: str): print(f'docker exec {user}, {container_name}, {cmd}') if __name__ == '__main__': main() ``` Usage: ```console foo@bar:~$ ./multiapp.py --help multiapp v1.0.0 - many apps launcher Usage: ./multiapp.py [COMMAND] [OPTIONS] Options: --version - Print version information and exit -h, --help [SUCOMMANDS...] - Display this help and exit --install-bash APP-NAME - Install script as a bash binary and add autocompletion links --autocomplete [CMDLINE...] - Return matching autocompletion proposals Commands: git git push REMOTE [BRANCH] git help - show help git co|checkout BRANCH - checkout branch git remote - show remotes list git remote rename|set-url REMOTE-NAME NEW-NAME - change remote's name xrandr docker docker exec CONTAINER-NAME [CMD...] Run "./multiapp.py COMMAND --help" for more information on a command. ```


نیازمندی

مقدار نام
- dataclasses


زبان مورد نیاز

مقدار نام
>=3.6.0 Python


نحوه نصب


نصب پکیج whl cliglue-1.1.3:

    pip install cliglue-1.1.3.whl


نصب پکیج tar.gz cliglue-1.1.3:

    pip install cliglue-1.1.3.tar.gz