Metadata-Version: 2.1
Name: funcli
Version: 0.7.1
Summary: Automatically generate a simple CLI.
Home-page: https://gitlab.com/valtron/funcli
Author: valtron
License: UNKNOWN
Platform: UNKNOWN
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Operating System :: OS Independent
Classifier: License :: OSI Approved :: MIT License
Requires-Python: >=3.6
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: docstring-parser
Requires-Dist: typing-extensions

# FunCLI [![PyPI](https://img.shields.io/pypi/pyversions/funcli.svg?style=plastic)](https://gitlab.com/valtron/funcli)

Automatically generate a simple CLI.

## Installation

```
pip install funcli
```

## Basic Usage

```python
def main(*args: int):
	"""
		Adds numbers together.
		
		:param args: some numbers
	"""
	print("Sum:", sum(args))

if __name__ == '__main__':
	import funcli
	funcli.main()

# $ python sum.py 1 2 3
# Sum: 6
```

`--help` is autogenerated by the backend (argparse):

```
$ python sum.py -h
usage: sum.py [-h] [args [args ...]]

Adds numbers together.

positional arguments:
	args        some numbers

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

## Advanced Usage

```python
from typing import Optional, List

def foo(): pass
def bleep(): pass
def bloop(a, second_arg: int = 0, *c: str, d: Optional[List[float]] = None):
	print("Args:", a, second_arg, c, d)

if __name__ == '__main__':
	import funcli
	funcli.main({ 'foo': foo, 'bar': { bleep, bloop } })

# $ python advanced.py bar bloop arg0 c0 c1 --second-arg=987 --d 42 -0.3
# Args: arg0 987 ('c0', 'c1'), [42, -0.3]
```

Note that underscores in variable names are automatically converted to dashes
in the command-line argument (`second_arg` -> `--second-arg`). You can disable
this by passing `dash = False` to `funcli.main` or `funcli.run`.

## Reference

```python
funcli.main(spec = None, dash = True)
```

Sugar. `spec` defaults to the `main` function from the caller's scope.
Calls `funcli.run` on `spec`, and calls `sys.exit` with the return value.
If `dash == True`, underscores in Python parameters will be changed to dashes
in the corresponding command-line arguments.

```python
funcli.run(spec, args = None, converters = None, dash = True)
```

- `spec` is either a callable, a sequence of callables, or a dict mapping strings to nested `specs`
- `args` default to `sys.argv[1:]`
- `converters` is a mapping from types (or whatever you want to use as annotations) to a function that parses a command-line argument
- if `dash == True`, underscores in Python parameters will be changed to dashes in the corresponding command-line arguments

Given functions `foo`, `bar`, `baz`, here are some sample invocations:

```python
funcli.run(foo, ['arg0']) # Calls foo('arg0')
funcli.run({ foo, bar }, ['bar', 'arg0']) # Calls bar('arg0')
funcli.run({ 'beep': foo, 'bloop': [bar, baz] }, ['beep', 'arg0']) # Calls foo('arg0')
funcli.run({ 'beep': foo, 'bloop': [bar, baz] }, ['bloop', 'bar', 'arg0']) # Calls bar('arg0')
```

### `bool` arguments

Non-optional bool values should be passed as `True` and `False` on the command line.
Optional bool values, on the other hand, must be omitted.

```python
def f(warnings: bool = False): ...
funcli.run(f, ['--warnings']) # f(warnings = True)
```

*Note:* currently, if the default value is `True`, there is no way to pass `False`.

### Converters

Built-in converters handle `int`, `float`, `bool`, and `pathlib.Path`. Unannotated args are kept as a `str`.
Basic sequence types (`list`/`List[T]`, `set`/`Set[T]`, `tuple`/`Tuple[T, ...]`, `Iterable[T]`, `Sequence[T]`) are supported, but only as optional arguments, e.g.:

```python
def f(mylist: List[int] = []): ...
funcli.run(f, ['--mylist', '1', '2']) # f([1, 2])
```

`Optional[T]` is supported, but there's currently no way to explicitly pass `None` values.

### Notes

Because of `argparse` limitations:
- `**kwargs` aren't supported; if your function has them, they'll always be empty
- optional arguments cannot be positional; `f(a = 'default')` has to be invoked as `python foo.py --a=nondefault`


