Skip to content

Commit 447db99

Browse files
committed
docs(readme): rewrite with clearer examples and structure
1 parent 615381f commit 447db99

1 file changed

Lines changed: 75 additions & 72 deletions

File tree

README.md

Lines changed: 75 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,16 @@
66
[![Downloads](https://static.pepy.tech/badge/interfacy)](https://pepy.tech/project/interfacy)
77
[![license](https://img.shields.io/github/license/zigai/interfacy.svg)](https://github.com/zigai/interfacy/blob/main/LICENSE)
88

9-
Interfacy is a CLI framework for building command-line interfaces from Python functions, classes, and class instances using type annotations and docstrings.
9+
Interfacy is a CLI framework that turns Python functions, classes, and class instances into command-line interfaces. It derives the CLI from signatures, type annotations, and docstrings instead of making you define it twice.
1010

1111
## Features
1212

13-
- Generate CLIs from functions, class methods, or class instances.
14-
- Nested subcommands and command groups with aliases.
15-
- Type inference from annotations, with support for custom parsers.
13+
- Generate CLIs from functions, classes, class methods, and class instances.
14+
- Nested subcommands and manual command groups with aliases.
15+
- Type-driven parsing from annotations, with support for custom parsers.
16+
- Model expansion for dataclasses, Pydantic models, and plain classes.
1617
- `--help` text generated from docstrings.
17-
- Run a target function, class, or class instance directly from the CLI (e.g. `interfacy path.py:main`).
18-
- Multiple help layouts and color themes.
19-
- Optional class initializer arguments exposed as CLI options.
20-
- Argparse-compatible backend, including a drop-in `ArgumentParser` replacement.
18+
- Highly customizable help output with multiple layouts, color themes, and configurable ordering.
2119
- Stdin piping support with configurable routing to parameters.
2220
- Optional tab completion via `argcomplete`.
2321

@@ -43,114 +41,119 @@ pip install git+https://github.com/zigai/interfacy.git
4341
uv add "git+https://github.com/zigai/interfacy.git"
4442
```
4543

46-
## Quick start
44+
## First CLI
4745

4846
```python
4947
from interfacy import Argparser
5048

51-
def greet(name: str, times: int = 1) -> None:
52-
for _ in range(times):
53-
print(f"Hello, {name}!")
49+
def greet(name: str, times: int = 1) -> str:
50+
"""Return a greeting."""
51+
return " ".join([f"Hello, {name}!" for _ in range(times)])
5452

5553
if __name__ == "__main__":
56-
Argparser().run(greet)
54+
Argparser(print_result=True).run(greet)
55+
```
56+
57+
```text
58+
$ python app.py Ada
59+
Hello, Ada!
60+
61+
$ python app.py Ada --times 2
62+
Hello, Ada! Hello, Ada!
63+
```
64+
65+
By default, required non-boolean parameters become positional arguments and optional parameters become flags.
66+
67+
## Class-Based Commands
68+
69+
Classes become command namespaces. `__init__` parameters live at the command level and public methods become subcommands.
70+
71+
```python
72+
from interfacy import Argparser
73+
74+
class Calculator:
75+
def __init__(self, precision: int = 2) -> None:
76+
self.precision = precision
77+
78+
def add(self, a: float, b: float) -> float:
79+
return round(a + b, self.precision)
80+
81+
def mul(self, a: float, b: float) -> float:
82+
return round(a * b, self.precision)
83+
84+
if __name__ == "__main__":
85+
Argparser(print_result=True).run(Calculator)
86+
```
87+
88+
```text
89+
$ python app.py --precision 3 add 1.25 2.75
90+
4.0
5791
```
5892

59-
## Classes as flags
93+
## Structured Parameters
94+
95+
Dataclasses, Pydantic models, and plain classes with typed `__init__` parameters can be expanded into nested flags and reconstructed before execution.
6096

6197
```python
6298
from dataclasses import dataclass
6399
from interfacy import Argparser
64100

65101
@dataclass
66102
class Address:
67-
"""Mailing address data for a user.
68-
69-
Args:
70-
city: City name.
71-
zip: Postal or ZIP code.
72-
"""
73103
city: str
74-
zip: int
104+
postal_code: int
75105

76106
@dataclass
77107
class User:
78-
"""User profile information for the CLI.
79-
80-
Args:
81-
name: Display name.
82-
age: Age in years.
83-
address: Optional mailing address details.
84-
"""
85108
name: str
86109
age: int
87110
address: Address | None = None
88111

89112
def greet(user: User) -> str:
90-
if user.address is None:
91-
return f"Hello {user.name}, age {user.age}"
92-
return f"Hello {user.name}, age {user.age} from {user.address.city} {user.address.zip}"
113+
return f"Hello {user.name}, age {user.age}"
93114

94115
if __name__ == "__main__":
95116
Argparser(print_result=True).run(greet)
96117
```
97118

98-
Help output:
99-
100119
```text
101-
usage: app.py greet [--help] --user.name USER.NAME --user.age USER.AGE
102-
[--user.address.city] [--user.address.zip]
103-
104-
options:
105-
--help show this help message and exit
106-
--user.name Display name. [type: str] (*)
107-
--user.age Age in years. [type: int] (*)
108-
--user.address.city City name. [type: str]
109-
--user.address.zip Postal or ZIP code. [type: int]
120+
$ python app.py --user.name Ada --user.age 32
121+
Hello Ada, age 32
110122
```
111123

112-
## Class-based commands
124+
## Manual Groups
125+
126+
Use `CommandGroup` when your command tree is not naturally rooted in one callable:
113127

114128
```python
115-
from interfacy import Argparser
129+
from interfacy import Argparser, CommandGroup
116130

117-
class Calculator:
118-
def add(self, a: int, b: int) -> int:
119-
return a + b
131+
def clone(url: str) -> str:
132+
return f"clone:{url}"
133+
134+
class Releases:
135+
def cut(self, version: str) -> str:
136+
return f"cut:{version}"
120137

121-
def mul(self, a: int, b: int) -> int:
122-
return a * b
138+
ops = CommandGroup("ops", description="Operational commands")
139+
ops.add_command(clone)
140+
ops.add_command(Releases)
123141

124142
if __name__ == "__main__":
125-
Argparser(print_result=True).run(Calculator)
143+
Argparser(print_result=True).run(ops)
126144
```
127145

128-
## Decorator-based commands
129-
130-
```python
131-
from interfacy import Argparser
132-
133-
parser = Argparser()
146+
## Interfacy CLI Entrypoint
134147

135-
@parser.command()
136-
def greet(name: str) -> str:
137-
return f"Hello, {name}!"
148+
Interfacy also ships a CLI that can run an existing function, class, or class instance directly from a module or Python file:
138149

139-
@parser.command(name="calc", aliases=["c"])
140-
class Calculator:
141-
def add(self, a: int, b: int) -> int:
142-
return a + b
143-
144-
def mul(self, a: int, b: int) -> int:
145-
return a * b
146-
147-
if __name__ == "__main__":
148-
parser.run()
150+
```text
151+
$ interfacy app.py:greet Ada
152+
$ interfacy app.py:greet --help
153+
$ interfacy package.cli:Calculator add 1 2
149154
```
150155

151-
## CLI entrypoint
152-
153-
Use the CLI entrypoint to run a target function, class, or class instance from a module or file, or to inspect its help:
156+
The entrypoint supports configuration via TOML, loaded from `~/.config/interfacy/config.toml` or `INTERFACY_CONFIG`.
154157

155158
```text
156159
usage: interfacy [--help] [--version] [--config-paths] [TARGET] ...

0 commit comments

Comments
 (0)