|
| 1 | +<p align="center"> |
| 2 | + <img alt="arguably logo" src="https://raw.githubusercontent.com/treykeown/arguably/main/assets/arguably_black.png"> |
| 3 | +</p> |
| 4 | + |
| 5 | +<p align="center"> |
| 6 | + <em> |
| 7 | + turns functions into command line interfaces |
| 8 | + </em> |
| 9 | +</p> |
| 10 | + |
| 11 | +<p align="center"> |
| 12 | + <!-- TODO badges --> |
| 13 | +</p> |
| 14 | +<hr> |
| 15 | + |
| 16 | +`arguably` solves this problem: |
| 17 | +1. You've written a Python script |
| 18 | +2. Now you want to pass in parameters from the command line |
| 19 | +3. You don't want to read the docs for your favorite argument parsing library *again* |
| 20 | + |
| 21 | +By leveraging as many Python idioms as possible, `arguably` keeps its API small and memorable without sacrificing |
| 22 | +funcitonality. `arguably` uses functions and their docstrings to automatically set up argparse. Notably, `arguably` |
| 23 | +maps your function signature to a command-line interface like this: |
| 24 | + |
| 25 | +```python |
| 26 | +@arguably.command |
| 27 | +def some_function(required, not_required="foo", *others, option="bar"): |
| 28 | + ... |
| 29 | +``` |
| 30 | + |
| 31 | +<p align="center"><b><em>becomes</em></b></p> |
| 32 | + |
| 33 | +```text |
| 34 | +usage: script [--option OPTION] required [not-required] [others ...] |
| 35 | +``` |
| 36 | + |
| 37 | +In short, `arguably` turns your function's **positional parameters** into **positional command-line arguments**, and |
| 38 | +your function's **keyword-only arguments** into **command-line options**. From the example above: |
| 39 | + |
| 40 | +| Name | Type | Becomes | Usage | |
| 41 | +|----------------|-------------------------------------|---------------------------------|---------------------| |
| 42 | +| `required` | positional, no default value | required positional arg | `required` | |
| 43 | +| `not_required` | positional, with default value | optional positional arg | `[not-required]` | |
| 44 | +| `others` | positional, variadic (like `*args`) | the rest of the positional args | `[others ...]` | |
| 45 | +| `option` | keyword-only argument | an option | `[--option OPTION]` | |
| 46 | + |
| 47 | +`arguably` also enables you to easily add subcommands - just annotate more than one function with `@arguably.command`. |
| 48 | +You can even have nested subcommands (more on that later). |
| 49 | + |
| 50 | +`arguably` reads type annotations and automatically converts arguments to the declared types. It has smart handling for |
| 51 | +`tuple`, `list`, `enum.Enum`, and `enum.Flag`. There are also a few special behaviors you can attach to a parameter |
| 52 | +via `Annotated[]` and the `arguably.arg.*` functions. |
| 53 | + |
| 54 | +`arguably` parses docstrings to generate descriptions for your commands and parameters. If you want to give a parameter |
| 55 | +the alias `-X`, prefix its docstring description with `[-X]`. Wrapping a word in `{}` changes the *metavar* that gets |
| 56 | +printed (this is what's shown in the usage string after an option name, don't worry if you aren't familiar with this). |
| 57 | +For example: |
| 58 | + |
| 59 | +```python |
| 60 | +#!/usr/bin/env python3 |
| 61 | +"""docstrings for the file become the description for the script.""" |
| 62 | +__version__ = "1.0.0" # You can also set `version_flag=True` to add a version flag, it will read `__version__` |
| 63 | + |
| 64 | +import arguably |
| 65 | + |
| 66 | +@arguably.command(alias="h") |
| 67 | +def hello(name: str, *, lastname: str | None = None): |
| 68 | + """ |
| 69 | + says hello to you |
| 70 | + :param name: your name |
| 71 | + :param lastname: [-l] your {surname} |
| 72 | + """ |
| 73 | + full_name = name if lastname is None else f"{name} {lastname}" |
| 74 | + print(f"Hello, {full_name}!") |
| 75 | + |
| 76 | +@arguably.command(alias="g") |
| 77 | +def goodbye(name: str, *, is_sad: bool = False): |
| 78 | + """ |
| 79 | + says goodbye to you |
| 80 | + :param name: your name |
| 81 | + :param is_sad: [-s] whether or not it's sad to see you go |
| 82 | + """ |
| 83 | + print(f"Goodbye, {name}!") |
| 84 | + if is_sad: |
| 85 | + print(f"It's sad to see you go!") |
| 86 | + |
| 87 | +if __name__ == "__main__": |
| 88 | + arguably.run(version_flag=True) |
| 89 | +``` |
| 90 | + |
| 91 | +<p align="center"><b><em>becomes</em></b></p> |
| 92 | + |
| 93 | +```console |
| 94 | +user@machine:~$ python3 script.py |
| 95 | +usage: test_scripts.docs2 [-h] [--version] command ... |
| 96 | + |
| 97 | +docstrings for the file become the description for the script. |
| 98 | + |
| 99 | +positional arguments: |
| 100 | + command |
| 101 | + hello (h) says hello to you |
| 102 | + goodbye (g) says goodbye to you |
| 103 | + |
| 104 | +options: |
| 105 | + -h, --help show this help message and exit |
| 106 | + --version show program's version number and exit |
| 107 | + |
| 108 | + |
| 109 | +user@machine:~$ python3 script.py hello --help |
| 110 | +usage: test_scripts.docs2 hello [-h] [-l SURNAME] name |
| 111 | + |
| 112 | +says hello to you |
| 113 | + |
| 114 | +positional arguments: |
| 115 | + name your name |
| 116 | + |
| 117 | +options: |
| 118 | + -h, --help show this help message and exit |
| 119 | + -l, --lastname SURNAME your surname (default: None) |
| 120 | +``` |
| 121 | + |
| 122 | +More docs coming soon... |
0 commit comments