cliss 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cliss-0.1.0/LICENSE +21 -0
- cliss-0.1.0/PKG-INFO +269 -0
- cliss-0.1.0/README.md +260 -0
- cliss-0.1.0/cliss/__init__.py +233 -0
- cliss-0.1.0/cliss.egg-info/PKG-INFO +269 -0
- cliss-0.1.0/cliss.egg-info/SOURCES.txt +8 -0
- cliss-0.1.0/cliss.egg-info/dependency_links.txt +1 -0
- cliss-0.1.0/cliss.egg-info/top_level.txt +2 -0
- cliss-0.1.0/pyproject.toml +13 -0
- cliss-0.1.0/setup.cfg +4 -0
cliss-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 kernel
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
cliss-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cliss
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: cliss â A lightweight framework for building CLI applications on top of argparse
|
|
5
|
+
Requires-Python: >=3.9
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Dynamic: license-file
|
|
9
|
+
|
|
10
|
+
# cliss â A lightweight framework for building CLI applications on top of argparse
|
|
11
|
+
|
|
12
|
+
[](https://python.org)
|
|
13
|
+
[](https://pypi.org/project/cliss/)
|
|
14
|
+
[](LICENSE)
|
|
15
|
+
[]()
|
|
16
|
+
[](https://docs.astral.sh/ruff/)
|
|
17
|
+
|
|
18
|
+
Write type-annotated Python functions, get a full CLI â automatic `--help`, validation, and async support with zero dependencies.
|
|
19
|
+
|
|
20
|
+
## âĻ Features
|
|
21
|
+
|
|
22
|
+
- **ðŠķ Zero Dependencies** â Pure stdlib: `argparse`, `asyncio`, `inspect`
|
|
23
|
+
- **ð·ïļ Type-Driven** â Automatic arguments from function signatures and type hints
|
|
24
|
+
- **ð§Đ Flexible** â Declarative `Argument` objects, type inference, or both
|
|
25
|
+
- **⥠Async-Native** â `async def` handlers with automatic event loop management
|
|
26
|
+
- **ð Global Args** â Define flags shared across all commands
|
|
27
|
+
- **ð§ argparse Access** â Full access to underlying parsers for advanced use
|
|
28
|
+
|
|
29
|
+
## ð Quick Start
|
|
30
|
+
|
|
31
|
+
### Prerequisites
|
|
32
|
+
- Python 3.9+
|
|
33
|
+
|
|
34
|
+
### Installation
|
|
35
|
+
```bash
|
|
36
|
+
pip install cliss
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
#### Via uv
|
|
40
|
+
```bash
|
|
41
|
+
uv pip install cliss
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
#### Via pipx (isolated environment)
|
|
45
|
+
```bash
|
|
46
|
+
pipx install cliss
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
#### From source (development)
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
git clone https://github.com/yourusername/cliss.git && cd cliss
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**pip**
|
|
56
|
+
```bash
|
|
57
|
+
pip install .
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**uv**
|
|
61
|
+
```bash
|
|
62
|
+
uv pip install .
|
|
63
|
+
```
|
|
64
|
+
**pipx**
|
|
65
|
+
```bash
|
|
66
|
+
pipx install .
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Usage
|
|
70
|
+
```python
|
|
71
|
+
from cliss import CLI
|
|
72
|
+
|
|
73
|
+
cli = CLI(name="todo", description="Task manager", version="1.0.0")
|
|
74
|
+
|
|
75
|
+
@cli.command()
|
|
76
|
+
def add(task: str, priority: int = 1, done: bool = False):
|
|
77
|
+
"""Add a new task."""
|
|
78
|
+
status = "â" if done else "â"
|
|
79
|
+
return f"[{status}] {task} (priority: {priority})"
|
|
80
|
+
|
|
81
|
+
if __name__ == "__main__":
|
|
82
|
+
cli.handle()
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
$ python todo.py add "Buy milk" --priority 2
|
|
87
|
+
[â] Buy milk (priority: 2)
|
|
88
|
+
$ python todo.py add "Call mom" --done --priority 3
|
|
89
|
+
[â] Call mom (priority: 3)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## ð Commands
|
|
93
|
+
|
|
94
|
+
### `CLI` class
|
|
95
|
+
```python
|
|
96
|
+
CLI(name="myapp", description="...", version="1.0.0", auto_help=True)
|
|
97
|
+
```
|
|
98
|
+
| Parameter | Type | Default | Description |
|
|
99
|
+
|-----------|------|---------|-------------|
|
|
100
|
+
| `name` | `str` | â | Program name in help output |
|
|
101
|
+
| `description` | `str` | â | Description in help output |
|
|
102
|
+
| `version` | `str` | â | Adds `--version` flag |
|
|
103
|
+
| `auto_help` | `bool` | `True` | Adds `--help` flag |
|
|
104
|
+
|
|
105
|
+
### `Argument` class
|
|
106
|
+
```python
|
|
107
|
+
Argument("--output", "-o", type=str, default=None, help="...", required=False, choices=["a","b"], action="store_true")
|
|
108
|
+
```
|
|
109
|
+
| Parameter | Type | Default | Description |
|
|
110
|
+
|-----------|------|---------|-------------|
|
|
111
|
+
| `*flags` | `str` | â | Argument flags (e.g., `--output`, `-o`) |
|
|
112
|
+
| `type` | `type` | `str` | Value type for coercion |
|
|
113
|
+
| `default` | `Any` | `None` | Default value |
|
|
114
|
+
| `help` | `str` | `""` | Help text |
|
|
115
|
+
| `required` | `bool` | `False` | Make argument required |
|
|
116
|
+
| `choices` | `list` | â | Restrict allowed values |
|
|
117
|
+
| `action` | `str` | â | argparse action (`store_true`, etc.) |
|
|
118
|
+
|
|
119
|
+
### Type â CLI Mapping
|
|
120
|
+
| Function Signature | CLI Argument |
|
|
121
|
+
|--------------------|--------------|
|
|
122
|
+
| `name: str` | Positional `name` |
|
|
123
|
+
| `count: int = 1` | `--count` with type `int`, default `1` |
|
|
124
|
+
| `verbose: bool = False` | `--verbose` flag (store_true) |
|
|
125
|
+
| `quiet: bool = True` | `--quiet` flag (store_false) |
|
|
126
|
+
|
|
127
|
+
## ð Examples
|
|
128
|
+
|
|
129
|
+
### CRUD Application
|
|
130
|
+
```python
|
|
131
|
+
from cliss import CLI
|
|
132
|
+
|
|
133
|
+
cli = CLI(name="db", description="Key-value store")
|
|
134
|
+
db = {}
|
|
135
|
+
|
|
136
|
+
@cli.command()
|
|
137
|
+
def set(key: str, value: str):
|
|
138
|
+
"""Store a value."""
|
|
139
|
+
db[key] = value
|
|
140
|
+
return f"OK: {key} = {value}"
|
|
141
|
+
|
|
142
|
+
@cli.command()
|
|
143
|
+
def get(key: str):
|
|
144
|
+
"""Retrieve a value."""
|
|
145
|
+
return db.get(key, "Not found")
|
|
146
|
+
|
|
147
|
+
@cli.command()
|
|
148
|
+
def delete(key: str, force: bool = False):
|
|
149
|
+
"""Delete a key."""
|
|
150
|
+
if force or key in db:
|
|
151
|
+
db.pop(key, None)
|
|
152
|
+
return f"Deleted: {key}"
|
|
153
|
+
return f"Not found: {key} (use --force)"
|
|
154
|
+
|
|
155
|
+
cli.handle()
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Explicit Arguments
|
|
159
|
+
```python
|
|
160
|
+
from cliss import CLI, Argument
|
|
161
|
+
|
|
162
|
+
cli = CLI(name="convert")
|
|
163
|
+
|
|
164
|
+
@cli.command(arguments=[
|
|
165
|
+
Argument("input", help="Input file"),
|
|
166
|
+
Argument("--output", "-o", default="out.txt"),
|
|
167
|
+
Argument("--format", "-f", choices=["json", "csv"], default="json")
|
|
168
|
+
])
|
|
169
|
+
def convert(input: str, output: str = "out.txt", format: str = "json"):
|
|
170
|
+
"""Convert file format."""
|
|
171
|
+
return f"{input} -> {output} [{format}]"
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Async Handler
|
|
175
|
+
```python
|
|
176
|
+
@cli.command()
|
|
177
|
+
async def fetch(url: str, retries: int = 3):
|
|
178
|
+
"""Fetch data asynchronously."""
|
|
179
|
+
return f"Fetched {url} (retries: {retries})"
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Global Arguments
|
|
183
|
+
```python
|
|
184
|
+
cli = CLI(name="myapp")
|
|
185
|
+
cli.add_global_argument("--verbose", "-v", action="store_true")
|
|
186
|
+
|
|
187
|
+
@cli.command()
|
|
188
|
+
def status(verbose: bool = False):
|
|
189
|
+
return "Detailed status..." if verbose else "OK"
|
|
190
|
+
```
|
|
191
|
+
```bash
|
|
192
|
+
$ myapp --verbose status
|
|
193
|
+
Detailed status...
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## ð Project Structure
|
|
197
|
+
```
|
|
198
|
+
cliss/
|
|
199
|
+
âââ cliss/
|
|
200
|
+
â âââ __init__.py # CLI, Argument classes
|
|
201
|
+
âââ pyproject.toml # Project metadata
|
|
202
|
+
âââ README.md # Documentation
|
|
203
|
+
âââ LICENSE # MIT License
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## ð§ Requirements
|
|
207
|
+
|
|
208
|
+
| Dependency | Purpose |
|
|
209
|
+
|------------|---------|
|
|
210
|
+
| Python 3.9+ | Type hints, `inspect.signature` |
|
|
211
|
+
|
|
212
|
+
No external dependencies â stdlib only.
|
|
213
|
+
|
|
214
|
+
## â FAQ
|
|
215
|
+
|
|
216
|
+
### Why cliss when argparse already works?
|
|
217
|
+
|
|
218
|
+
argparse is powerful but verbose. A simple app with 3 commands can easily require 100+ lines of parser setup. cliss reduces this to type-annotated functions â the boilerplate is inferred, not written.
|
|
219
|
+
|
|
220
|
+
### What about Click/Typer/Fire?
|
|
221
|
+
|
|
222
|
+
| Tool | Dependencies | Style |
|
|
223
|
+
|------|-------------|-------|
|
|
224
|
+
| **cliss** | 0 (stdlib) | Decorators + type hints |
|
|
225
|
+
| Click | Click | Decorators |
|
|
226
|
+
| Typer | Click, typing-extensions | Type hints |
|
|
227
|
+
| Fire | 0 (stdlib) | Introspection |
|
|
228
|
+
|
|
229
|
+
cliss sits between Fire (zero-config, no validation) and Typer (rich features, heavy deps). It gives you type-driven CLI generation with argparse-compatible control, all in ~200 lines.
|
|
230
|
+
|
|
231
|
+
### Can I use argparse features directly?
|
|
232
|
+
|
|
233
|
+
Yes. `cli.parser` and `cli.subparsers` are standard argparse objects. Add custom actions, mutually exclusive groups, or parent parsers as needed.
|
|
234
|
+
|
|
235
|
+
### Does it support nested commands?
|
|
236
|
+
|
|
237
|
+
For subcommand groups, access `cli.subparsers` directly or use dotted command names:
|
|
238
|
+
|
|
239
|
+
```python
|
|
240
|
+
@cli.command(name="compute:start")
|
|
241
|
+
def start(instance: str):
|
|
242
|
+
return f"Starting {instance}"
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### How does async work?
|
|
246
|
+
|
|
247
|
+
If the command handler is `async def` or returns a coroutine, cliss automatically runs it with `asyncio.run()`. No manual event loop setup needed.
|
|
248
|
+
|
|
249
|
+
## ð Troubleshooting
|
|
250
|
+
|
|
251
|
+
| Issue | Solution |
|
|
252
|
+
|-------|----------|
|
|
253
|
+
| **Arguments not appearing** | Check that explicit `Argument` objects' `dest` matches parameter names |
|
|
254
|
+
| **Bool flag inverted** | `bool = False` â `store_true`, `bool = True` â `store_false` |
|
|
255
|
+
| **Type coercion fails** | argparse error message shown automatically |
|
|
256
|
+
| **Subcommand not found** | Verify command name: `func.__name__` with `_` â `-` unless overridden |
|
|
257
|
+
|
|
258
|
+
## ð License
|
|
259
|
+
|
|
260
|
+
MIT License â see [LICENSE](LICENSE) file.
|
|
261
|
+
|
|
262
|
+
## ð Acknowledgments
|
|
263
|
+
|
|
264
|
+
- [argparse](https://docs.python.org/3/library/argparse.html) â The foundation this is built on
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
**Repository:** [github.com/Fkernel653/cliss](https://github.com/Fkernel653/cliss)
|
|
269
|
+
**PyPI:** [pypi.org/project/cliss](https://pypi.org/project/cliss/)
|
cliss-0.1.0/README.md
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# cliss â A lightweight framework for building CLI applications on top of argparse
|
|
2
|
+
|
|
3
|
+
[](https://python.org)
|
|
4
|
+
[](https://pypi.org/project/cliss/)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[]()
|
|
7
|
+
[](https://docs.astral.sh/ruff/)
|
|
8
|
+
|
|
9
|
+
Write type-annotated Python functions, get a full CLI â automatic `--help`, validation, and async support with zero dependencies.
|
|
10
|
+
|
|
11
|
+
## âĻ Features
|
|
12
|
+
|
|
13
|
+
- **ðŠķ Zero Dependencies** â Pure stdlib: `argparse`, `asyncio`, `inspect`
|
|
14
|
+
- **ð·ïļ Type-Driven** â Automatic arguments from function signatures and type hints
|
|
15
|
+
- **ð§Đ Flexible** â Declarative `Argument` objects, type inference, or both
|
|
16
|
+
- **⥠Async-Native** â `async def` handlers with automatic event loop management
|
|
17
|
+
- **ð Global Args** â Define flags shared across all commands
|
|
18
|
+
- **ð§ argparse Access** â Full access to underlying parsers for advanced use
|
|
19
|
+
|
|
20
|
+
## ð Quick Start
|
|
21
|
+
|
|
22
|
+
### Prerequisites
|
|
23
|
+
- Python 3.9+
|
|
24
|
+
|
|
25
|
+
### Installation
|
|
26
|
+
```bash
|
|
27
|
+
pip install cliss
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
#### Via uv
|
|
31
|
+
```bash
|
|
32
|
+
uv pip install cliss
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
#### Via pipx (isolated environment)
|
|
36
|
+
```bash
|
|
37
|
+
pipx install cliss
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
#### From source (development)
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
git clone https://github.com/yourusername/cliss.git && cd cliss
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**pip**
|
|
47
|
+
```bash
|
|
48
|
+
pip install .
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**uv**
|
|
52
|
+
```bash
|
|
53
|
+
uv pip install .
|
|
54
|
+
```
|
|
55
|
+
**pipx**
|
|
56
|
+
```bash
|
|
57
|
+
pipx install .
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Usage
|
|
61
|
+
```python
|
|
62
|
+
from cliss import CLI
|
|
63
|
+
|
|
64
|
+
cli = CLI(name="todo", description="Task manager", version="1.0.0")
|
|
65
|
+
|
|
66
|
+
@cli.command()
|
|
67
|
+
def add(task: str, priority: int = 1, done: bool = False):
|
|
68
|
+
"""Add a new task."""
|
|
69
|
+
status = "â" if done else "â"
|
|
70
|
+
return f"[{status}] {task} (priority: {priority})"
|
|
71
|
+
|
|
72
|
+
if __name__ == "__main__":
|
|
73
|
+
cli.handle()
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
$ python todo.py add "Buy milk" --priority 2
|
|
78
|
+
[â] Buy milk (priority: 2)
|
|
79
|
+
$ python todo.py add "Call mom" --done --priority 3
|
|
80
|
+
[â] Call mom (priority: 3)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## ð Commands
|
|
84
|
+
|
|
85
|
+
### `CLI` class
|
|
86
|
+
```python
|
|
87
|
+
CLI(name="myapp", description="...", version="1.0.0", auto_help=True)
|
|
88
|
+
```
|
|
89
|
+
| Parameter | Type | Default | Description |
|
|
90
|
+
|-----------|------|---------|-------------|
|
|
91
|
+
| `name` | `str` | â | Program name in help output |
|
|
92
|
+
| `description` | `str` | â | Description in help output |
|
|
93
|
+
| `version` | `str` | â | Adds `--version` flag |
|
|
94
|
+
| `auto_help` | `bool` | `True` | Adds `--help` flag |
|
|
95
|
+
|
|
96
|
+
### `Argument` class
|
|
97
|
+
```python
|
|
98
|
+
Argument("--output", "-o", type=str, default=None, help="...", required=False, choices=["a","b"], action="store_true")
|
|
99
|
+
```
|
|
100
|
+
| Parameter | Type | Default | Description |
|
|
101
|
+
|-----------|------|---------|-------------|
|
|
102
|
+
| `*flags` | `str` | â | Argument flags (e.g., `--output`, `-o`) |
|
|
103
|
+
| `type` | `type` | `str` | Value type for coercion |
|
|
104
|
+
| `default` | `Any` | `None` | Default value |
|
|
105
|
+
| `help` | `str` | `""` | Help text |
|
|
106
|
+
| `required` | `bool` | `False` | Make argument required |
|
|
107
|
+
| `choices` | `list` | â | Restrict allowed values |
|
|
108
|
+
| `action` | `str` | â | argparse action (`store_true`, etc.) |
|
|
109
|
+
|
|
110
|
+
### Type â CLI Mapping
|
|
111
|
+
| Function Signature | CLI Argument |
|
|
112
|
+
|--------------------|--------------|
|
|
113
|
+
| `name: str` | Positional `name` |
|
|
114
|
+
| `count: int = 1` | `--count` with type `int`, default `1` |
|
|
115
|
+
| `verbose: bool = False` | `--verbose` flag (store_true) |
|
|
116
|
+
| `quiet: bool = True` | `--quiet` flag (store_false) |
|
|
117
|
+
|
|
118
|
+
## ð Examples
|
|
119
|
+
|
|
120
|
+
### CRUD Application
|
|
121
|
+
```python
|
|
122
|
+
from cliss import CLI
|
|
123
|
+
|
|
124
|
+
cli = CLI(name="db", description="Key-value store")
|
|
125
|
+
db = {}
|
|
126
|
+
|
|
127
|
+
@cli.command()
|
|
128
|
+
def set(key: str, value: str):
|
|
129
|
+
"""Store a value."""
|
|
130
|
+
db[key] = value
|
|
131
|
+
return f"OK: {key} = {value}"
|
|
132
|
+
|
|
133
|
+
@cli.command()
|
|
134
|
+
def get(key: str):
|
|
135
|
+
"""Retrieve a value."""
|
|
136
|
+
return db.get(key, "Not found")
|
|
137
|
+
|
|
138
|
+
@cli.command()
|
|
139
|
+
def delete(key: str, force: bool = False):
|
|
140
|
+
"""Delete a key."""
|
|
141
|
+
if force or key in db:
|
|
142
|
+
db.pop(key, None)
|
|
143
|
+
return f"Deleted: {key}"
|
|
144
|
+
return f"Not found: {key} (use --force)"
|
|
145
|
+
|
|
146
|
+
cli.handle()
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Explicit Arguments
|
|
150
|
+
```python
|
|
151
|
+
from cliss import CLI, Argument
|
|
152
|
+
|
|
153
|
+
cli = CLI(name="convert")
|
|
154
|
+
|
|
155
|
+
@cli.command(arguments=[
|
|
156
|
+
Argument("input", help="Input file"),
|
|
157
|
+
Argument("--output", "-o", default="out.txt"),
|
|
158
|
+
Argument("--format", "-f", choices=["json", "csv"], default="json")
|
|
159
|
+
])
|
|
160
|
+
def convert(input: str, output: str = "out.txt", format: str = "json"):
|
|
161
|
+
"""Convert file format."""
|
|
162
|
+
return f"{input} -> {output} [{format}]"
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Async Handler
|
|
166
|
+
```python
|
|
167
|
+
@cli.command()
|
|
168
|
+
async def fetch(url: str, retries: int = 3):
|
|
169
|
+
"""Fetch data asynchronously."""
|
|
170
|
+
return f"Fetched {url} (retries: {retries})"
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Global Arguments
|
|
174
|
+
```python
|
|
175
|
+
cli = CLI(name="myapp")
|
|
176
|
+
cli.add_global_argument("--verbose", "-v", action="store_true")
|
|
177
|
+
|
|
178
|
+
@cli.command()
|
|
179
|
+
def status(verbose: bool = False):
|
|
180
|
+
return "Detailed status..." if verbose else "OK"
|
|
181
|
+
```
|
|
182
|
+
```bash
|
|
183
|
+
$ myapp --verbose status
|
|
184
|
+
Detailed status...
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## ð Project Structure
|
|
188
|
+
```
|
|
189
|
+
cliss/
|
|
190
|
+
âââ cliss/
|
|
191
|
+
â âââ __init__.py # CLI, Argument classes
|
|
192
|
+
âââ pyproject.toml # Project metadata
|
|
193
|
+
âââ README.md # Documentation
|
|
194
|
+
âââ LICENSE # MIT License
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## ð§ Requirements
|
|
198
|
+
|
|
199
|
+
| Dependency | Purpose |
|
|
200
|
+
|------------|---------|
|
|
201
|
+
| Python 3.9+ | Type hints, `inspect.signature` |
|
|
202
|
+
|
|
203
|
+
No external dependencies â stdlib only.
|
|
204
|
+
|
|
205
|
+
## â FAQ
|
|
206
|
+
|
|
207
|
+
### Why cliss when argparse already works?
|
|
208
|
+
|
|
209
|
+
argparse is powerful but verbose. A simple app with 3 commands can easily require 100+ lines of parser setup. cliss reduces this to type-annotated functions â the boilerplate is inferred, not written.
|
|
210
|
+
|
|
211
|
+
### What about Click/Typer/Fire?
|
|
212
|
+
|
|
213
|
+
| Tool | Dependencies | Style |
|
|
214
|
+
|------|-------------|-------|
|
|
215
|
+
| **cliss** | 0 (stdlib) | Decorators + type hints |
|
|
216
|
+
| Click | Click | Decorators |
|
|
217
|
+
| Typer | Click, typing-extensions | Type hints |
|
|
218
|
+
| Fire | 0 (stdlib) | Introspection |
|
|
219
|
+
|
|
220
|
+
cliss sits between Fire (zero-config, no validation) and Typer (rich features, heavy deps). It gives you type-driven CLI generation with argparse-compatible control, all in ~200 lines.
|
|
221
|
+
|
|
222
|
+
### Can I use argparse features directly?
|
|
223
|
+
|
|
224
|
+
Yes. `cli.parser` and `cli.subparsers` are standard argparse objects. Add custom actions, mutually exclusive groups, or parent parsers as needed.
|
|
225
|
+
|
|
226
|
+
### Does it support nested commands?
|
|
227
|
+
|
|
228
|
+
For subcommand groups, access `cli.subparsers` directly or use dotted command names:
|
|
229
|
+
|
|
230
|
+
```python
|
|
231
|
+
@cli.command(name="compute:start")
|
|
232
|
+
def start(instance: str):
|
|
233
|
+
return f"Starting {instance}"
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### How does async work?
|
|
237
|
+
|
|
238
|
+
If the command handler is `async def` or returns a coroutine, cliss automatically runs it with `asyncio.run()`. No manual event loop setup needed.
|
|
239
|
+
|
|
240
|
+
## ð Troubleshooting
|
|
241
|
+
|
|
242
|
+
| Issue | Solution |
|
|
243
|
+
|-------|----------|
|
|
244
|
+
| **Arguments not appearing** | Check that explicit `Argument` objects' `dest` matches parameter names |
|
|
245
|
+
| **Bool flag inverted** | `bool = False` â `store_true`, `bool = True` â `store_false` |
|
|
246
|
+
| **Type coercion fails** | argparse error message shown automatically |
|
|
247
|
+
| **Subcommand not found** | Verify command name: `func.__name__` with `_` â `-` unless overridden |
|
|
248
|
+
|
|
249
|
+
## ð License
|
|
250
|
+
|
|
251
|
+
MIT License â see [LICENSE](LICENSE) file.
|
|
252
|
+
|
|
253
|
+
## ð Acknowledgments
|
|
254
|
+
|
|
255
|
+
- [argparse](https://docs.python.org/3/library/argparse.html) â The foundation this is built on
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
**Repository:** [github.com/Fkernel653/cliss](https://github.com/Fkernel653/cliss)
|
|
260
|
+
**PyPI:** [pypi.org/project/cliss](https://pypi.org/project/cliss/)
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import asyncio
|
|
3
|
+
import inspect
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Argument:
|
|
9
|
+
"""Description of an argument for a command."""
|
|
10
|
+
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
*flags: str,
|
|
14
|
+
type: type = str,
|
|
15
|
+
default: Any = None,
|
|
16
|
+
help: str = "",
|
|
17
|
+
required: bool = False,
|
|
18
|
+
choices: Optional[List[Any]] = None,
|
|
19
|
+
action: Optional[str] = None,
|
|
20
|
+
):
|
|
21
|
+
"""
|
|
22
|
+
Initialize a command argument descriptor.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
*flags: Argument flags (e.g., "--output", "-o").
|
|
26
|
+
type: Expected type of the argument value.
|
|
27
|
+
default: Default value if the argument is not provided.
|
|
28
|
+
help: Help text describing the argument.
|
|
29
|
+
required: Whether the argument must be provided.
|
|
30
|
+
choices: List of allowed values for the argument.
|
|
31
|
+
action: Custom argparse action (e.g., "store_true", "store_false").
|
|
32
|
+
"""
|
|
33
|
+
self.flags = flags
|
|
34
|
+
self.type = type
|
|
35
|
+
self.default = default
|
|
36
|
+
self.help = help
|
|
37
|
+
self.required = required
|
|
38
|
+
self.choices = choices
|
|
39
|
+
self.action = action
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class CLI:
|
|
43
|
+
"""Advanced wrapper over argparse for building command-line interfaces."""
|
|
44
|
+
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
name: Optional[str] = None,
|
|
48
|
+
description: Optional[str] = None,
|
|
49
|
+
version: Optional[str] = None,
|
|
50
|
+
auto_help: bool = True,
|
|
51
|
+
):
|
|
52
|
+
"""
|
|
53
|
+
Initialize the CLI application.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
name: Name of the application (shown in help).
|
|
57
|
+
description: Description of the application (shown in help).
|
|
58
|
+
version: Version string for --version flag. If provided, adds automatic version display.
|
|
59
|
+
auto_help: Whether to automatically add a --help flag.
|
|
60
|
+
"""
|
|
61
|
+
self.name = name
|
|
62
|
+
self.description = description
|
|
63
|
+
self.version = version
|
|
64
|
+
self.auto_help = auto_help
|
|
65
|
+
|
|
66
|
+
self.parser = argparse.ArgumentParser(
|
|
67
|
+
prog=name, description=description, add_help=auto_help
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
if version:
|
|
71
|
+
self.parser.add_argument("--version", action="version", version=version)
|
|
72
|
+
|
|
73
|
+
self.subparsers = self.parser.add_subparsers(dest="_command", title="Commands")
|
|
74
|
+
self._commands: Dict[str, dict] = {}
|
|
75
|
+
self._global_args: List[Argument] = []
|
|
76
|
+
|
|
77
|
+
def add_global_argument(self, *flags, **kwargs):
|
|
78
|
+
"""
|
|
79
|
+
Add a global argument that applies to all commands.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
*flags: Argument flags (e.g., "--verbose", "-v").
|
|
83
|
+
**kwargs: Additional keyword arguments passed to argparse.
|
|
84
|
+
"""
|
|
85
|
+
self._global_args.append(Argument(*flags, **kwargs))
|
|
86
|
+
self.parser.add_argument(*flags, **kwargs)
|
|
87
|
+
|
|
88
|
+
def command(
|
|
89
|
+
self,
|
|
90
|
+
name: Optional[str] = None,
|
|
91
|
+
description: Optional[str] = None,
|
|
92
|
+
arguments: Optional[List[Argument]] = None,
|
|
93
|
+
**parser_kwargs,
|
|
94
|
+
) -> Callable:
|
|
95
|
+
"""
|
|
96
|
+
Decorator for creating a command.
|
|
97
|
+
|
|
98
|
+
You can pass a list of Argument objects or use type annotations in the function
|
|
99
|
+
to automatically generate arguments.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
name: Name of the command. If not provided, uses the function name
|
|
103
|
+
with underscores replaced by hyphens.
|
|
104
|
+
description: Description of the command. If not provided, uses the
|
|
105
|
+
function's docstring.
|
|
106
|
+
arguments: Optional list of Argument objects describing the command's
|
|
107
|
+
arguments.
|
|
108
|
+
**parser_kwargs: Additional keyword arguments passed to the subparser.
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
A decorator that registers the function as a command.
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
def decorator(func: Callable) -> Callable:
|
|
115
|
+
cmd_name = name or func.__name__.replace("_", "-")
|
|
116
|
+
cmd_help = description or (func.__doc__ or "").strip()
|
|
117
|
+
|
|
118
|
+
parser = self.subparsers.add_parser(
|
|
119
|
+
cmd_name,
|
|
120
|
+
help=cmd_help.split("\n")[0] if cmd_help else None,
|
|
121
|
+
description=cmd_help,
|
|
122
|
+
**parser_kwargs,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
explicit_dests = set()
|
|
126
|
+
|
|
127
|
+
# Add explicitly specified arguments
|
|
128
|
+
if arguments:
|
|
129
|
+
for arg in arguments:
|
|
130
|
+
kwargs = {
|
|
131
|
+
"type": arg.type,
|
|
132
|
+
"default": arg.default,
|
|
133
|
+
"help": arg.help,
|
|
134
|
+
"required": arg.required,
|
|
135
|
+
"choices": arg.choices,
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if arg.action:
|
|
139
|
+
kwargs["action"] = arg.action
|
|
140
|
+
|
|
141
|
+
action = parser.add_argument(*arg.flags, **kwargs)
|
|
142
|
+
explicit_dests.add(action.dest)
|
|
143
|
+
|
|
144
|
+
# Automatically add arguments from the function signature
|
|
145
|
+
sig = inspect.signature(func)
|
|
146
|
+
for param_name, param in sig.parameters.items():
|
|
147
|
+
if param_name in explicit_dests:
|
|
148
|
+
continue # Argument already explicitly added
|
|
149
|
+
|
|
150
|
+
if param.default is inspect.Parameter.empty:
|
|
151
|
+
# Positional argument
|
|
152
|
+
arg_type = (
|
|
153
|
+
param.annotation
|
|
154
|
+
if param.annotation != inspect.Parameter.empty
|
|
155
|
+
else str
|
|
156
|
+
)
|
|
157
|
+
parser.add_argument(param_name, type=arg_type, help=param_name)
|
|
158
|
+
else:
|
|
159
|
+
# Optional argument
|
|
160
|
+
flag = f"--{param_name.replace('_', '-')}"
|
|
161
|
+
is_bool = param.annotation == bool or (
|
|
162
|
+
isinstance(param.default, bool)
|
|
163
|
+
and param.annotation in (bool, inspect.Parameter.empty)
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
if is_bool:
|
|
167
|
+
action = "store_false" if param.default else "store_true"
|
|
168
|
+
parser.add_argument(
|
|
169
|
+
flag,
|
|
170
|
+
action=action,
|
|
171
|
+
default=param.default,
|
|
172
|
+
help=f"{param_name} (default: {param.default})",
|
|
173
|
+
)
|
|
174
|
+
else:
|
|
175
|
+
arg_type = (
|
|
176
|
+
param.annotation
|
|
177
|
+
if param.annotation != inspect.Parameter.empty
|
|
178
|
+
else type(param.default)
|
|
179
|
+
)
|
|
180
|
+
parser.add_argument(
|
|
181
|
+
flag,
|
|
182
|
+
type=arg_type,
|
|
183
|
+
default=param.default,
|
|
184
|
+
help=f"{param_name} (default: {param.default})",
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
self._commands[cmd_name] = {"func": func, "parser": parser}
|
|
188
|
+
|
|
189
|
+
return func
|
|
190
|
+
|
|
191
|
+
return decorator
|
|
192
|
+
|
|
193
|
+
def handle(self, args: Optional[List[str]] = None) -> None:
|
|
194
|
+
"""
|
|
195
|
+
Parse command-line arguments and execute the appropriate command.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
args: List of command-line arguments. If None, uses sys.argv[1:].
|
|
199
|
+
|
|
200
|
+
Raises:
|
|
201
|
+
SystemExit: If argument parsing fails or an error occurs during execution.
|
|
202
|
+
"""
|
|
203
|
+
if args is None:
|
|
204
|
+
args = sys.argv[1:]
|
|
205
|
+
|
|
206
|
+
if not args:
|
|
207
|
+
self.parser.print_help()
|
|
208
|
+
return
|
|
209
|
+
|
|
210
|
+
try:
|
|
211
|
+
namespace = self.parser.parse_args(args)
|
|
212
|
+
|
|
213
|
+
command = getattr(namespace, "_command", None)
|
|
214
|
+
if not command or command not in self._commands:
|
|
215
|
+
self.parser.print_help()
|
|
216
|
+
return
|
|
217
|
+
|
|
218
|
+
cmd_data = self._commands[command]
|
|
219
|
+
func_kwargs = {k: v for k, v in vars(namespace).items() if k != "_command"}
|
|
220
|
+
|
|
221
|
+
result = cmd_data["func"](**func_kwargs)
|
|
222
|
+
if asyncio.iscoroutine(result):
|
|
223
|
+
result = asyncio.run(result)
|
|
224
|
+
|
|
225
|
+
if result is not None:
|
|
226
|
+
print(result)
|
|
227
|
+
|
|
228
|
+
except SystemExit as e:
|
|
229
|
+
if e.code is not None and e.code != 0:
|
|
230
|
+
raise
|
|
231
|
+
except (ValueError, TypeError) as e:
|
|
232
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
233
|
+
sys.exit(1)
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cliss
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: cliss â A lightweight framework for building CLI applications on top of argparse
|
|
5
|
+
Requires-Python: >=3.9
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Dynamic: license-file
|
|
9
|
+
|
|
10
|
+
# cliss â A lightweight framework for building CLI applications on top of argparse
|
|
11
|
+
|
|
12
|
+
[](https://python.org)
|
|
13
|
+
[](https://pypi.org/project/cliss/)
|
|
14
|
+
[](LICENSE)
|
|
15
|
+
[]()
|
|
16
|
+
[](https://docs.astral.sh/ruff/)
|
|
17
|
+
|
|
18
|
+
Write type-annotated Python functions, get a full CLI â automatic `--help`, validation, and async support with zero dependencies.
|
|
19
|
+
|
|
20
|
+
## âĻ Features
|
|
21
|
+
|
|
22
|
+
- **ðŠķ Zero Dependencies** â Pure stdlib: `argparse`, `asyncio`, `inspect`
|
|
23
|
+
- **ð·ïļ Type-Driven** â Automatic arguments from function signatures and type hints
|
|
24
|
+
- **ð§Đ Flexible** â Declarative `Argument` objects, type inference, or both
|
|
25
|
+
- **⥠Async-Native** â `async def` handlers with automatic event loop management
|
|
26
|
+
- **ð Global Args** â Define flags shared across all commands
|
|
27
|
+
- **ð§ argparse Access** â Full access to underlying parsers for advanced use
|
|
28
|
+
|
|
29
|
+
## ð Quick Start
|
|
30
|
+
|
|
31
|
+
### Prerequisites
|
|
32
|
+
- Python 3.9+
|
|
33
|
+
|
|
34
|
+
### Installation
|
|
35
|
+
```bash
|
|
36
|
+
pip install cliss
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
#### Via uv
|
|
40
|
+
```bash
|
|
41
|
+
uv pip install cliss
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
#### Via pipx (isolated environment)
|
|
45
|
+
```bash
|
|
46
|
+
pipx install cliss
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
#### From source (development)
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
git clone https://github.com/yourusername/cliss.git && cd cliss
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**pip**
|
|
56
|
+
```bash
|
|
57
|
+
pip install .
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**uv**
|
|
61
|
+
```bash
|
|
62
|
+
uv pip install .
|
|
63
|
+
```
|
|
64
|
+
**pipx**
|
|
65
|
+
```bash
|
|
66
|
+
pipx install .
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Usage
|
|
70
|
+
```python
|
|
71
|
+
from cliss import CLI
|
|
72
|
+
|
|
73
|
+
cli = CLI(name="todo", description="Task manager", version="1.0.0")
|
|
74
|
+
|
|
75
|
+
@cli.command()
|
|
76
|
+
def add(task: str, priority: int = 1, done: bool = False):
|
|
77
|
+
"""Add a new task."""
|
|
78
|
+
status = "â" if done else "â"
|
|
79
|
+
return f"[{status}] {task} (priority: {priority})"
|
|
80
|
+
|
|
81
|
+
if __name__ == "__main__":
|
|
82
|
+
cli.handle()
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
$ python todo.py add "Buy milk" --priority 2
|
|
87
|
+
[â] Buy milk (priority: 2)
|
|
88
|
+
$ python todo.py add "Call mom" --done --priority 3
|
|
89
|
+
[â] Call mom (priority: 3)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## ð Commands
|
|
93
|
+
|
|
94
|
+
### `CLI` class
|
|
95
|
+
```python
|
|
96
|
+
CLI(name="myapp", description="...", version="1.0.0", auto_help=True)
|
|
97
|
+
```
|
|
98
|
+
| Parameter | Type | Default | Description |
|
|
99
|
+
|-----------|------|---------|-------------|
|
|
100
|
+
| `name` | `str` | â | Program name in help output |
|
|
101
|
+
| `description` | `str` | â | Description in help output |
|
|
102
|
+
| `version` | `str` | â | Adds `--version` flag |
|
|
103
|
+
| `auto_help` | `bool` | `True` | Adds `--help` flag |
|
|
104
|
+
|
|
105
|
+
### `Argument` class
|
|
106
|
+
```python
|
|
107
|
+
Argument("--output", "-o", type=str, default=None, help="...", required=False, choices=["a","b"], action="store_true")
|
|
108
|
+
```
|
|
109
|
+
| Parameter | Type | Default | Description |
|
|
110
|
+
|-----------|------|---------|-------------|
|
|
111
|
+
| `*flags` | `str` | â | Argument flags (e.g., `--output`, `-o`) |
|
|
112
|
+
| `type` | `type` | `str` | Value type for coercion |
|
|
113
|
+
| `default` | `Any` | `None` | Default value |
|
|
114
|
+
| `help` | `str` | `""` | Help text |
|
|
115
|
+
| `required` | `bool` | `False` | Make argument required |
|
|
116
|
+
| `choices` | `list` | â | Restrict allowed values |
|
|
117
|
+
| `action` | `str` | â | argparse action (`store_true`, etc.) |
|
|
118
|
+
|
|
119
|
+
### Type â CLI Mapping
|
|
120
|
+
| Function Signature | CLI Argument |
|
|
121
|
+
|--------------------|--------------|
|
|
122
|
+
| `name: str` | Positional `name` |
|
|
123
|
+
| `count: int = 1` | `--count` with type `int`, default `1` |
|
|
124
|
+
| `verbose: bool = False` | `--verbose` flag (store_true) |
|
|
125
|
+
| `quiet: bool = True` | `--quiet` flag (store_false) |
|
|
126
|
+
|
|
127
|
+
## ð Examples
|
|
128
|
+
|
|
129
|
+
### CRUD Application
|
|
130
|
+
```python
|
|
131
|
+
from cliss import CLI
|
|
132
|
+
|
|
133
|
+
cli = CLI(name="db", description="Key-value store")
|
|
134
|
+
db = {}
|
|
135
|
+
|
|
136
|
+
@cli.command()
|
|
137
|
+
def set(key: str, value: str):
|
|
138
|
+
"""Store a value."""
|
|
139
|
+
db[key] = value
|
|
140
|
+
return f"OK: {key} = {value}"
|
|
141
|
+
|
|
142
|
+
@cli.command()
|
|
143
|
+
def get(key: str):
|
|
144
|
+
"""Retrieve a value."""
|
|
145
|
+
return db.get(key, "Not found")
|
|
146
|
+
|
|
147
|
+
@cli.command()
|
|
148
|
+
def delete(key: str, force: bool = False):
|
|
149
|
+
"""Delete a key."""
|
|
150
|
+
if force or key in db:
|
|
151
|
+
db.pop(key, None)
|
|
152
|
+
return f"Deleted: {key}"
|
|
153
|
+
return f"Not found: {key} (use --force)"
|
|
154
|
+
|
|
155
|
+
cli.handle()
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Explicit Arguments
|
|
159
|
+
```python
|
|
160
|
+
from cliss import CLI, Argument
|
|
161
|
+
|
|
162
|
+
cli = CLI(name="convert")
|
|
163
|
+
|
|
164
|
+
@cli.command(arguments=[
|
|
165
|
+
Argument("input", help="Input file"),
|
|
166
|
+
Argument("--output", "-o", default="out.txt"),
|
|
167
|
+
Argument("--format", "-f", choices=["json", "csv"], default="json")
|
|
168
|
+
])
|
|
169
|
+
def convert(input: str, output: str = "out.txt", format: str = "json"):
|
|
170
|
+
"""Convert file format."""
|
|
171
|
+
return f"{input} -> {output} [{format}]"
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Async Handler
|
|
175
|
+
```python
|
|
176
|
+
@cli.command()
|
|
177
|
+
async def fetch(url: str, retries: int = 3):
|
|
178
|
+
"""Fetch data asynchronously."""
|
|
179
|
+
return f"Fetched {url} (retries: {retries})"
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Global Arguments
|
|
183
|
+
```python
|
|
184
|
+
cli = CLI(name="myapp")
|
|
185
|
+
cli.add_global_argument("--verbose", "-v", action="store_true")
|
|
186
|
+
|
|
187
|
+
@cli.command()
|
|
188
|
+
def status(verbose: bool = False):
|
|
189
|
+
return "Detailed status..." if verbose else "OK"
|
|
190
|
+
```
|
|
191
|
+
```bash
|
|
192
|
+
$ myapp --verbose status
|
|
193
|
+
Detailed status...
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## ð Project Structure
|
|
197
|
+
```
|
|
198
|
+
cliss/
|
|
199
|
+
âââ cliss/
|
|
200
|
+
â âââ __init__.py # CLI, Argument classes
|
|
201
|
+
âââ pyproject.toml # Project metadata
|
|
202
|
+
âââ README.md # Documentation
|
|
203
|
+
âââ LICENSE # MIT License
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## ð§ Requirements
|
|
207
|
+
|
|
208
|
+
| Dependency | Purpose |
|
|
209
|
+
|------------|---------|
|
|
210
|
+
| Python 3.9+ | Type hints, `inspect.signature` |
|
|
211
|
+
|
|
212
|
+
No external dependencies â stdlib only.
|
|
213
|
+
|
|
214
|
+
## â FAQ
|
|
215
|
+
|
|
216
|
+
### Why cliss when argparse already works?
|
|
217
|
+
|
|
218
|
+
argparse is powerful but verbose. A simple app with 3 commands can easily require 100+ lines of parser setup. cliss reduces this to type-annotated functions â the boilerplate is inferred, not written.
|
|
219
|
+
|
|
220
|
+
### What about Click/Typer/Fire?
|
|
221
|
+
|
|
222
|
+
| Tool | Dependencies | Style |
|
|
223
|
+
|------|-------------|-------|
|
|
224
|
+
| **cliss** | 0 (stdlib) | Decorators + type hints |
|
|
225
|
+
| Click | Click | Decorators |
|
|
226
|
+
| Typer | Click, typing-extensions | Type hints |
|
|
227
|
+
| Fire | 0 (stdlib) | Introspection |
|
|
228
|
+
|
|
229
|
+
cliss sits between Fire (zero-config, no validation) and Typer (rich features, heavy deps). It gives you type-driven CLI generation with argparse-compatible control, all in ~200 lines.
|
|
230
|
+
|
|
231
|
+
### Can I use argparse features directly?
|
|
232
|
+
|
|
233
|
+
Yes. `cli.parser` and `cli.subparsers` are standard argparse objects. Add custom actions, mutually exclusive groups, or parent parsers as needed.
|
|
234
|
+
|
|
235
|
+
### Does it support nested commands?
|
|
236
|
+
|
|
237
|
+
For subcommand groups, access `cli.subparsers` directly or use dotted command names:
|
|
238
|
+
|
|
239
|
+
```python
|
|
240
|
+
@cli.command(name="compute:start")
|
|
241
|
+
def start(instance: str):
|
|
242
|
+
return f"Starting {instance}"
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### How does async work?
|
|
246
|
+
|
|
247
|
+
If the command handler is `async def` or returns a coroutine, cliss automatically runs it with `asyncio.run()`. No manual event loop setup needed.
|
|
248
|
+
|
|
249
|
+
## ð Troubleshooting
|
|
250
|
+
|
|
251
|
+
| Issue | Solution |
|
|
252
|
+
|-------|----------|
|
|
253
|
+
| **Arguments not appearing** | Check that explicit `Argument` objects' `dest` matches parameter names |
|
|
254
|
+
| **Bool flag inverted** | `bool = False` â `store_true`, `bool = True` â `store_false` |
|
|
255
|
+
| **Type coercion fails** | argparse error message shown automatically |
|
|
256
|
+
| **Subcommand not found** | Verify command name: `func.__name__` with `_` â `-` unless overridden |
|
|
257
|
+
|
|
258
|
+
## ð License
|
|
259
|
+
|
|
260
|
+
MIT License â see [LICENSE](LICENSE) file.
|
|
261
|
+
|
|
262
|
+
## ð Acknowledgments
|
|
263
|
+
|
|
264
|
+
- [argparse](https://docs.python.org/3/library/argparse.html) â The foundation this is built on
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
**Repository:** [github.com/Fkernel653/cliss](https://github.com/Fkernel653/cliss)
|
|
269
|
+
**PyPI:** [pypi.org/project/cliss](https://pypi.org/project/cliss/)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "cliss"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "cliss â A lightweight framework for building CLI applications on top of argparse"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
|
|
12
|
+
[tool.setuptools.packages.find]
|
|
13
|
+
where = ["."]
|
cliss-0.1.0/setup.cfg
ADDED