Skip to content

Commit

Permalink
Fix readme for PyPI
Browse files Browse the repository at this point in the history
  • Loading branch information
treykeown committed Jun 7, 2023
1 parent 3e2ee1f commit 9556792
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 1 deletion.
7 changes: 7 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,10 @@ repos:
hooks:
- id: mypy
exclude: ^test/
- repo: local
hooks:
- id: pypi_readme
name: PyPI README
entry: python3 assets/make_pypi_readme.py
language: system
files: ^README\.md$
122 changes: 122 additions & 0 deletions assets/PYPI_README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
<p align="center">
<img alt="arguably logo" src="https://raw.githubusercontent.com/treykeown/arguably/main/assets/arguably_black.png">
</p>

<p align="center">
<em>
turns functions into command line interfaces
</em>
</p>

<p align="center">
<!-- TODO badges -->
</p>
<hr>

`arguably` solves this problem:
1. You've written a Python script
2. Now you want to pass in parameters from the command line
3. You don't want to read the docs for your favorite argument parsing library *again*

By leveraging as many Python idioms as possible, `arguably` keeps its API small and memorable without sacrificing
funcitonality. `arguably` uses functions and their docstrings to automatically set up argparse. Notably, `arguably`
maps your function signature to a command-line interface like this:

```python
@arguably.command
def some_function(required, not_required="foo", *others, option="bar"):
...
```

<p align="center"><b><em>becomes</em></b></p>

```text
usage: script [--option OPTION] required [not-required] [others ...]
```

In short, `arguably` turns your function's **positional parameters** into **positional command-line arguments**, and
your function's **keyword-only arguments** into **command-line options**. From the example above:

| Name | Type | Becomes | Usage |
|----------------|-------------------------------------|---------------------------------|---------------------|
| `required` | positional, no default value | required positional arg | `required` |
| `not_required` | positional, with default value | optional positional arg | `[not-required]` |
| `others` | positional, variadic (like `*args`) | the rest of the positional args | `[others ...]` |
| `option` | keyword-only argument | an option | `[--option OPTION]` |

`arguably` also enables you to easily add subcommands - just annotate more than one function with `@arguably.command`.
You can even have nested subcommands (more on that later).

`arguably` reads type annotations and automatically converts arguments to the declared types. It has smart handling for
`tuple`, `list`, `enum.Enum`, and `enum.Flag`. There are also a few special behaviors you can attach to a parameter
via `Annotated[]` and the `arguably.arg.*` functions.

`arguably` parses docstrings to generate descriptions for your commands and parameters. If you want to give a parameter
the alias `-X`, prefix its docstring description with `[-X]`. Wrapping a word in `{}` changes the *metavar* that gets
printed (this is what's shown in the usage string after an option name, don't worry if you aren't familiar with this).
For example:

```python
#!/usr/bin/env python3
"""docstrings for the file become the description for the script."""
__version__ = "1.0.0" # You can also set `version_flag=True` to add a version flag, it will read `__version__`

import arguably

@arguably.command(alias="h")
def hello(name: str, *, lastname: str | None = None):
"""
says hello to you
:param name: your name
:param lastname: [-l] your {surname}
"""
full_name = name if lastname is None else f"{name} {lastname}"
print(f"Hello, {full_name}!")

@arguably.command(alias="g")
def goodbye(name: str, *, is_sad: bool = False):
"""
says goodbye to you
:param name: your name
:param is_sad: [-s] whether or not it's sad to see you go
"""
print(f"Goodbye, {name}!")
if is_sad:
print(f"It's sad to see you go!")

if __name__ == "__main__":
arguably.run(version_flag=True)
```

<p align="center"><b><em>becomes</em></b></p>

```console
user@machine:~$ python3 script.py
usage: test_scripts.docs2 [-h] [--version] command ...

docstrings for the file become the description for the script.

positional arguments:
command
hello (h) says hello to you
goodbye (g) says goodbye to you

options:
-h, --help show this help message and exit
--version show program's version number and exit


user@machine:~$ python3 script.py hello --help
usage: test_scripts.docs2 hello [-h] [-l SURNAME] name

says hello to you

positional arguments:
name your name

options:
-h, --help show this help message and exit
-l, --lastname SURNAME your surname (default: None)
```

More docs coming soon...
25 changes: 25 additions & 0 deletions assets/make_pypi_readme.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/usr/bin/env python3
"""
Converts our README.md to a PyPI-compatible version... needed because PyPI does not yet support <picture>, see:
https://github.com/pypi/warehouse/issues/11251
"""


from pathlib import Path

project_path = Path(__file__).resolve().parent.parent
readme_src = project_path / "README.md"
readme_dst = project_path / "assets" / "PYPI_README.md"

# A bit hacky, but works for now.
with readme_src.open("r") as src:
with readme_dst.open("w") as dst:
for line in src.readlines():
stripped_line = line.lstrip(" ")
if (
stripped_line.startswith("<picture")
or stripped_line.startswith("</picture")
or stripped_line.startswith("<source")
):
continue
dst.write(line)
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "arguably"
version = "1.0.0"
description = "turns functions into command line interfaces"
authors = ["treykeown <[email protected]>"]
readme = "README.md"
readme = "assets/PYPI_README.md"
homepage = "https://github.com/treykeown/arguably"
repository = "https://github.com/treykeown/arguably"

Expand Down

0 comments on commit 9556792

Please sign in to comment.