usecli 0.1.76__tar.gz → 0.1.78__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.
- {usecli-0.1.76 → usecli-0.1.78}/PKG-INFO +115 -7
- {usecli-0.1.76 → usecli-0.1.78}/README.md +107 -1
- {usecli-0.1.76 → usecli-0.1.78}/pyproject.toml +26 -12
- {usecli-0.1.76 → usecli-0.1.78}/pyproject.toml.orig +23 -12
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/__init__.py +57 -26
- usecli-0.1.78/src/usecli/bundler/__init__.py +275 -0
- usecli-0.1.78/src/usecli/bundler/__main__.py +6 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/commands/defaults/base/about_command.py +5 -6
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/commands/defaults/base/internal/fzf_command.py +24 -20
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/commands/defaults/core/utils.py +2 -6
- usecli-0.1.78/src/usecli/cli/commands/defaults/make/make_bundle_command.py +147 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/commands/defaults/make/make_command.py +1 -3
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/commands/defaults/make/make_theme_command.py +1 -2
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/commands/init_command.py +4 -3
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/config/colors.py +8 -5
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/core/base_command.py +19 -20
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/core/error/handler.py +4 -3
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/core/exceptions/validation.py +2 -2
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/core/ui/list.py +4 -6
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/core/ui/title.py +4 -5
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/services/command_service.py +0 -4
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/utils/interactive/terminal_menu.py +5 -4
- usecli-0.1.78/src/usecli/entry.py +201 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/params.py +7 -6
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/progress.py +7 -3
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/shared/config/manager.py +85 -20
- {usecli-0.1.76 → usecli-0.1.78}/LICENSE +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/__init__.py +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/commands/README.md +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/commands/__init__.py +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/commands/custom/README.md +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/commands/custom/__init__.py +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/commands/defaults/__init__.py +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/commands/defaults/base/__init__.py +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/commands/defaults/base/help_command.py +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/commands/defaults/base/inspire_command.py +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/commands/defaults/base/internal/__init__.py +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/commands/defaults/core/__init__.py +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/commands/defaults/make/__init__.py +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/config/__init__.py +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/core/__init__.py +9 -9
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/core/error/__init__.py +1 -1
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/core/error/utils.py +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/core/exceptions/__init__.py +2 -2
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/core/exceptions/base.py +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/core/exceptions/config.py +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/core/exceptions/usage.py +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/core/runtime.py +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/core/ui/__init__.py +1 -1
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/core/ui/title.txt +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/core/validators/__init__.py +5 -5
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/core/validators/network.py +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/core/validators/numeric.py +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/core/validators/path.py +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/core/validators/string.py +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/services/__init__.py +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/templates/command.py.j2 +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/templates/theme.toml.j2 +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/templates/usecli.config.toml.j2 +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/themes/ayu_dark.toml +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/themes/catppuccin_frappe.toml +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/themes/catppuccin_latte.toml +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/themes/catppuccin_macchiato.toml +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/themes/catppuccin_mocha.toml +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/themes/default.toml +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/themes/dracula.toml +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/themes/gruvbox_dark.toml +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/themes/nord.toml +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/themes/tokyo_night.toml +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/utils/__init__.py +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/cli/utils/interactive/__init__.py +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/menu.py +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/shared/__init__.py +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/shared/config/__init__.py +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/shared/config/globals.py +0 -0
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/ui.py +4 -4
- {usecli-0.1.76 → usecli-0.1.78}/src/usecli/usecli.config.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: usecli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.78
|
|
4
4
|
Summary: A powerful Python CLI framework for building beautiful, developer-friendly command-line tools.
|
|
5
5
|
Author: Edward Boswell
|
|
6
6
|
Author-email: Edward Boswell <thememium@gmail.com>
|
|
@@ -10,20 +10,22 @@ Classifier: Programming Language :: Python :: 3
|
|
|
10
10
|
Classifier: Operating System :: OS Independent
|
|
11
11
|
Classifier: Intended Audience :: Developers
|
|
12
12
|
Requires-Dist: case-converter>=1.2.0
|
|
13
|
-
Requires-Dist: click>=8.
|
|
14
|
-
Requires-Dist: fzf-bin>=0.
|
|
13
|
+
Requires-Dist: click>=8.4.2
|
|
14
|
+
Requires-Dist: fzf-bin>=0.74.1
|
|
15
15
|
Requires-Dist: jinja2>=3.1.6
|
|
16
16
|
Requires-Dist: pyfiglet>=1.0.4
|
|
17
|
-
Requires-Dist: rich>=
|
|
17
|
+
Requires-Dist: rich>=15.0.0
|
|
18
18
|
Requires-Dist: simple-term-menu>=1.6.6
|
|
19
|
-
Requires-Dist: tomli>=2.4.
|
|
20
|
-
Requires-Dist: typer>=0.
|
|
19
|
+
Requires-Dist: tomli>=2.4.1
|
|
20
|
+
Requires-Dist: typer>=0.27.0
|
|
21
|
+
Requires-Dist: pyinstaller>=6.21.0 ; extra == 'pyinstaller'
|
|
21
22
|
Requires-Python: >=3.10
|
|
22
23
|
Project-URL: Homepage, https://github.com/thememium/usecli
|
|
23
24
|
Project-URL: Documentation, https://github.com/thememium/usecli
|
|
24
25
|
Project-URL: Repository, https://github.com/thememium/usecli.git
|
|
25
26
|
Project-URL: Issues, https://github.com/thememium/usecli/issues
|
|
26
27
|
Project-URL: Changelog, https://github.com/thememium/usecli/blob/master/CHANGELOG.md
|
|
28
|
+
Provides-Extra: pyinstaller
|
|
27
29
|
Description-Content-Type: text/markdown
|
|
28
30
|
|
|
29
31
|
<a name="readme-top"></a>
|
|
@@ -52,6 +54,7 @@ Description-Content-Type: text/markdown
|
|
|
52
54
|
<li><a href="#about">About</a></li>
|
|
53
55
|
<li><a href="#quick-start">Quick Start</a></li>
|
|
54
56
|
<li><a href="#usage">Usage</a></li>
|
|
57
|
+
<li><a href="#entry-points--packaging">Entry Points & Packaging</a></li>
|
|
55
58
|
<li><a href="#development">Development</a></li>
|
|
56
59
|
<li><a href="#contributing">Contributing</a></li>
|
|
57
60
|
<li><a href="#license">License</a></li>
|
|
@@ -163,7 +166,7 @@ Space-separated signatures create nested subcommands:
|
|
|
163
166
|
|
|
164
167
|
```python
|
|
165
168
|
def signature(self) -> str:
|
|
166
|
-
return "spec show"
|
|
169
|
+
return "spec show" # usecli spec show
|
|
167
170
|
```
|
|
168
171
|
|
|
169
172
|
### JSON Output
|
|
@@ -244,10 +247,115 @@ init Initialize usecli (usecli only)
|
|
|
244
247
|
inspire Random quote
|
|
245
248
|
make:command Create new command (usecli only)
|
|
246
249
|
make:theme Create new theme (usecli only)
|
|
250
|
+
make:bundle Build a standalone executable with PyInstaller (usecli only)
|
|
247
251
|
```
|
|
248
252
|
|
|
249
253
|
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
250
254
|
|
|
255
|
+
<!-- ENTRY POINTS & PACKAGING -->
|
|
256
|
+
|
|
257
|
+
## Entry Points & Packaging
|
|
258
|
+
|
|
259
|
+
A usecli CLI can be exposed in several ways, depending on how you distribute and
|
|
260
|
+
run it. There are three common setups — none requires anything but the base
|
|
261
|
+
`usecli` install, and the optional bundler adds PyInstaller packaging on top.
|
|
262
|
+
|
|
263
|
+
### 1. Default — console script (no entry point)
|
|
264
|
+
|
|
265
|
+
The simplest setup. Declare a console script in your project's `pyproject.toml`:
|
|
266
|
+
|
|
267
|
+
```toml
|
|
268
|
+
[project.scripts]
|
|
269
|
+
mycli = "usecli:main"
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
usecli resolves your project's config automatically from the current directory
|
|
273
|
+
(config discovery), so no `main.py` is required. Install and run it:
|
|
274
|
+
|
|
275
|
+
```sh
|
|
276
|
+
uv add usecli
|
|
277
|
+
uv run mycli --help
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
This is the **default** — no entry point file of your own is needed.
|
|
281
|
+
|
|
282
|
+
### 2. Custom entry point — `main.py`
|
|
283
|
+
|
|
284
|
+
If you want your own entry point, use the public runtime API. It works in both
|
|
285
|
+
development and frozen bundles, and accepts an optional config path (the config
|
|
286
|
+
file is always named `usecli.config.toml`):
|
|
287
|
+
|
|
288
|
+
```python
|
|
289
|
+
# main.py
|
|
290
|
+
from usecli import run
|
|
291
|
+
|
|
292
|
+
if __name__ == "__main__":
|
|
293
|
+
run() # auto-detect project config
|
|
294
|
+
# run("path/to/usecli.config.toml") # or point at a specific config
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Run it in development (no PyInstaller required):
|
|
298
|
+
|
|
299
|
+
```sh
|
|
300
|
+
uv run python main.py --help
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Because `run()` injects the located config explicitly, a custom `main.py` works
|
|
304
|
+
regardless of whether your CLI also has a `[project.scripts]` entry.
|
|
305
|
+
|
|
306
|
+
### 3. Bundler — standalone executable (optional, requires PyInstaller)
|
|
307
|
+
|
|
308
|
+
The bundler is an **optional** feature gated on PyInstaller being installed.
|
|
309
|
+
Install it with:
|
|
310
|
+
|
|
311
|
+
```sh
|
|
312
|
+
uv add "usecli[pyinstaller]"
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Once installed, a `make:bundle` command appears (usecli only) that builds your
|
|
316
|
+
project into a standalone executable. It asks which mode to use via an
|
|
317
|
+
interactive menu, and confirms before building (defaults to no unless `-y`):
|
|
318
|
+
|
|
319
|
+
```sh
|
|
320
|
+
uv run usecli make:bundle # menu: one file vs one folder + confirm
|
|
321
|
+
uv run usecli make:bundle --mode onefile # single-file executable
|
|
322
|
+
uv run usecli make:bundle --mode onedir # one-folder bundle
|
|
323
|
+
uv run usecli make:bundle -y # skip the confirmation
|
|
324
|
+
uv run usecli make:bundle --mode onedir -y # fully programmatic
|
|
325
|
+
uv run usecli make:bundle --mode onedir --zip -y # one-folder bundle + zip
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
When building a **one-folder** bundle, `make:bundle` asks whether to also zip
|
|
329
|
+
the folder into `dist/<name>.zip` (PyInstaller's recommended distribution
|
|
330
|
+
format for one-folder mode). Pass `--zip` / `--no-zip` to skip the prompt and
|
|
331
|
+
force a choice; with `-y` and no flag, zipping is skipped.
|
|
332
|
+
|
|
333
|
+
A `usecli-bundle` console script wraps the same build, and you can drive it from
|
|
334
|
+
Python too:
|
|
335
|
+
|
|
336
|
+
```sh
|
|
337
|
+
uv run usecli-bundle
|
|
338
|
+
uv run usecli-bundle --mode onedir
|
|
339
|
+
uv run usecli-bundle --mode onedir --zip
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
```python
|
|
343
|
+
from usecli import pyinstaller
|
|
344
|
+
|
|
345
|
+
pyinstaller() # auto-detect config, single-file
|
|
346
|
+
pyinstaller(mode="onedir") # one-folder bundle
|
|
347
|
+
pyinstaller(mode="onedir", zip=True) # one-folder bundle + zip
|
|
348
|
+
pyinstaller("path/to/usecli.config.toml") # or point at a specific config
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
Output lands in `dist/` — `dist/mycli` for `onefile`, or a `dist/mycli/` folder
|
|
352
|
+
containing the executable (plus `_internal/` with the bundled assets) for
|
|
353
|
+
`onedir`. With `zip=True`, a `dist/mycli.zip` archive is created alongside the
|
|
354
|
+
folder. `make:bundle` and `usecli-bundle` are only available when PyInstaller
|
|
355
|
+
is installed.
|
|
356
|
+
|
|
357
|
+
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
358
|
+
|
|
251
359
|
<!-- DEVELOPMENT -->
|
|
252
360
|
|
|
253
361
|
## Development
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
<li><a href="#about">About</a></li>
|
|
25
25
|
<li><a href="#quick-start">Quick Start</a></li>
|
|
26
26
|
<li><a href="#usage">Usage</a></li>
|
|
27
|
+
<li><a href="#entry-points--packaging">Entry Points & Packaging</a></li>
|
|
27
28
|
<li><a href="#development">Development</a></li>
|
|
28
29
|
<li><a href="#contributing">Contributing</a></li>
|
|
29
30
|
<li><a href="#license">License</a></li>
|
|
@@ -135,7 +136,7 @@ Space-separated signatures create nested subcommands:
|
|
|
135
136
|
|
|
136
137
|
```python
|
|
137
138
|
def signature(self) -> str:
|
|
138
|
-
return "spec show"
|
|
139
|
+
return "spec show" # usecli spec show
|
|
139
140
|
```
|
|
140
141
|
|
|
141
142
|
### JSON Output
|
|
@@ -216,10 +217,115 @@ init Initialize usecli (usecli only)
|
|
|
216
217
|
inspire Random quote
|
|
217
218
|
make:command Create new command (usecli only)
|
|
218
219
|
make:theme Create new theme (usecli only)
|
|
220
|
+
make:bundle Build a standalone executable with PyInstaller (usecli only)
|
|
219
221
|
```
|
|
220
222
|
|
|
221
223
|
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
222
224
|
|
|
225
|
+
<!-- ENTRY POINTS & PACKAGING -->
|
|
226
|
+
|
|
227
|
+
## Entry Points & Packaging
|
|
228
|
+
|
|
229
|
+
A usecli CLI can be exposed in several ways, depending on how you distribute and
|
|
230
|
+
run it. There are three common setups — none requires anything but the base
|
|
231
|
+
`usecli` install, and the optional bundler adds PyInstaller packaging on top.
|
|
232
|
+
|
|
233
|
+
### 1. Default — console script (no entry point)
|
|
234
|
+
|
|
235
|
+
The simplest setup. Declare a console script in your project's `pyproject.toml`:
|
|
236
|
+
|
|
237
|
+
```toml
|
|
238
|
+
[project.scripts]
|
|
239
|
+
mycli = "usecli:main"
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
usecli resolves your project's config automatically from the current directory
|
|
243
|
+
(config discovery), so no `main.py` is required. Install and run it:
|
|
244
|
+
|
|
245
|
+
```sh
|
|
246
|
+
uv add usecli
|
|
247
|
+
uv run mycli --help
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
This is the **default** — no entry point file of your own is needed.
|
|
251
|
+
|
|
252
|
+
### 2. Custom entry point — `main.py`
|
|
253
|
+
|
|
254
|
+
If you want your own entry point, use the public runtime API. It works in both
|
|
255
|
+
development and frozen bundles, and accepts an optional config path (the config
|
|
256
|
+
file is always named `usecli.config.toml`):
|
|
257
|
+
|
|
258
|
+
```python
|
|
259
|
+
# main.py
|
|
260
|
+
from usecli import run
|
|
261
|
+
|
|
262
|
+
if __name__ == "__main__":
|
|
263
|
+
run() # auto-detect project config
|
|
264
|
+
# run("path/to/usecli.config.toml") # or point at a specific config
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Run it in development (no PyInstaller required):
|
|
268
|
+
|
|
269
|
+
```sh
|
|
270
|
+
uv run python main.py --help
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Because `run()` injects the located config explicitly, a custom `main.py` works
|
|
274
|
+
regardless of whether your CLI also has a `[project.scripts]` entry.
|
|
275
|
+
|
|
276
|
+
### 3. Bundler — standalone executable (optional, requires PyInstaller)
|
|
277
|
+
|
|
278
|
+
The bundler is an **optional** feature gated on PyInstaller being installed.
|
|
279
|
+
Install it with:
|
|
280
|
+
|
|
281
|
+
```sh
|
|
282
|
+
uv add "usecli[pyinstaller]"
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Once installed, a `make:bundle` command appears (usecli only) that builds your
|
|
286
|
+
project into a standalone executable. It asks which mode to use via an
|
|
287
|
+
interactive menu, and confirms before building (defaults to no unless `-y`):
|
|
288
|
+
|
|
289
|
+
```sh
|
|
290
|
+
uv run usecli make:bundle # menu: one file vs one folder + confirm
|
|
291
|
+
uv run usecli make:bundle --mode onefile # single-file executable
|
|
292
|
+
uv run usecli make:bundle --mode onedir # one-folder bundle
|
|
293
|
+
uv run usecli make:bundle -y # skip the confirmation
|
|
294
|
+
uv run usecli make:bundle --mode onedir -y # fully programmatic
|
|
295
|
+
uv run usecli make:bundle --mode onedir --zip -y # one-folder bundle + zip
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
When building a **one-folder** bundle, `make:bundle` asks whether to also zip
|
|
299
|
+
the folder into `dist/<name>.zip` (PyInstaller's recommended distribution
|
|
300
|
+
format for one-folder mode). Pass `--zip` / `--no-zip` to skip the prompt and
|
|
301
|
+
force a choice; with `-y` and no flag, zipping is skipped.
|
|
302
|
+
|
|
303
|
+
A `usecli-bundle` console script wraps the same build, and you can drive it from
|
|
304
|
+
Python too:
|
|
305
|
+
|
|
306
|
+
```sh
|
|
307
|
+
uv run usecli-bundle
|
|
308
|
+
uv run usecli-bundle --mode onedir
|
|
309
|
+
uv run usecli-bundle --mode onedir --zip
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
```python
|
|
313
|
+
from usecli import pyinstaller
|
|
314
|
+
|
|
315
|
+
pyinstaller() # auto-detect config, single-file
|
|
316
|
+
pyinstaller(mode="onedir") # one-folder bundle
|
|
317
|
+
pyinstaller(mode="onedir", zip=True) # one-folder bundle + zip
|
|
318
|
+
pyinstaller("path/to/usecli.config.toml") # or point at a specific config
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Output lands in `dist/` — `dist/mycli` for `onefile`, or a `dist/mycli/` folder
|
|
322
|
+
containing the executable (plus `_internal/` with the bundled assets) for
|
|
323
|
+
`onedir`. With `zip=True`, a `dist/mycli.zip` archive is created alongside the
|
|
324
|
+
folder. `make:bundle` and `usecli-bundle` are only available when PyInstaller
|
|
325
|
+
is installed.
|
|
326
|
+
|
|
327
|
+
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
328
|
+
|
|
223
329
|
<!-- DEVELOPMENT -->
|
|
224
330
|
|
|
225
331
|
## Development
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "usecli"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.78"
|
|
4
4
|
description = "A powerful Python CLI framework for building beautiful, developer-friendly command-line tools."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -11,14 +11,14 @@ classifiers = [
|
|
|
11
11
|
]
|
|
12
12
|
dependencies = [
|
|
13
13
|
"case-converter>=1.2.0",
|
|
14
|
-
"click>=8.
|
|
15
|
-
"fzf-bin>=0.
|
|
14
|
+
"click>=8.4.2",
|
|
15
|
+
"fzf-bin>=0.74.1",
|
|
16
16
|
"jinja2>=3.1.6",
|
|
17
17
|
"pyfiglet>=1.0.4",
|
|
18
|
-
"rich>=
|
|
18
|
+
"rich>=15.0.0",
|
|
19
19
|
"simple-term-menu>=1.6.6",
|
|
20
|
-
"tomli>=2.4.
|
|
21
|
-
"typer>=0.
|
|
20
|
+
"tomli>=2.4.1",
|
|
21
|
+
"typer>=0.27.0",
|
|
22
22
|
]
|
|
23
23
|
license = "MIT"
|
|
24
24
|
license-files = ["LICEN[CS]E*"]
|
|
@@ -27,6 +27,9 @@ license-files = ["LICEN[CS]E*"]
|
|
|
27
27
|
name = "Edward Boswell"
|
|
28
28
|
email = "thememium@gmail.com"
|
|
29
29
|
|
|
30
|
+
[project.optional-dependencies]
|
|
31
|
+
pyinstaller = ["pyinstaller>=6.21.0"]
|
|
32
|
+
|
|
30
33
|
[project.urls]
|
|
31
34
|
Homepage = "https://github.com/thememium/usecli"
|
|
32
35
|
Documentation = "https://github.com/thememium/usecli"
|
|
@@ -36,6 +39,7 @@ Changelog = "https://github.com/thememium/usecli/blob/master/CHANGELOG.md"
|
|
|
36
39
|
|
|
37
40
|
[project.scripts]
|
|
38
41
|
usecli = "usecli:main"
|
|
42
|
+
usecli-bundle = "usecli.bundler:cli"
|
|
39
43
|
|
|
40
44
|
[build-system]
|
|
41
45
|
requires = ["uv_build>=0.10.2,<0.11.0"]
|
|
@@ -43,13 +47,14 @@ build-backend = "uv_build"
|
|
|
43
47
|
|
|
44
48
|
[dependency-groups]
|
|
45
49
|
dev = [
|
|
46
|
-
"deptry>=0.
|
|
47
|
-
"isort>=
|
|
48
|
-
"poethepoet>=0.
|
|
49
|
-
"
|
|
50
|
+
"deptry>=0.25.1",
|
|
51
|
+
"isort>=8.0.1",
|
|
52
|
+
"poethepoet>=0.48.0",
|
|
53
|
+
"pyinstaller>=6.21.0",
|
|
54
|
+
"pytest>=9.1.1",
|
|
50
55
|
"pytest-cov>=7.1.0",
|
|
51
|
-
"ruff>=0.
|
|
52
|
-
"ty>=0.0.
|
|
56
|
+
"ruff>=0.16.1",
|
|
57
|
+
"ty>=0.0.65",
|
|
53
58
|
]
|
|
54
59
|
|
|
55
60
|
[tool.deptry]
|
|
@@ -75,6 +80,9 @@ typecheck = "uv run ty check"
|
|
|
75
80
|
build = "uv build --no-sources"
|
|
76
81
|
release = "bash scripts/release.sh"
|
|
77
82
|
|
|
83
|
+
[tool.ty.environment]
|
|
84
|
+
python-version = "3.12"
|
|
85
|
+
|
|
78
86
|
[tool.ty.src]
|
|
79
87
|
include = [
|
|
80
88
|
"src",
|
|
@@ -90,3 +98,9 @@ exclude = [
|
|
|
90
98
|
[tool.setuptools.packages.find]
|
|
91
99
|
where = ["."]
|
|
92
100
|
include = ["cli*"]
|
|
101
|
+
|
|
102
|
+
[tool.setuptools.package-data]
|
|
103
|
+
cli = [
|
|
104
|
+
"**/*.toml",
|
|
105
|
+
"**/*.j2",
|
|
106
|
+
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "usecli"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.78"
|
|
4
4
|
description = "A powerful Python CLI framework for building beautiful, developer-friendly command-line tools."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [{ name = "Edward Boswell", email = "thememium@gmail.com" }]
|
|
@@ -12,18 +12,21 @@ classifiers = [
|
|
|
12
12
|
]
|
|
13
13
|
dependencies = [
|
|
14
14
|
"case-converter>=1.2.0",
|
|
15
|
-
"click>=8.
|
|
16
|
-
"fzf-bin>=0.
|
|
15
|
+
"click>=8.4.2",
|
|
16
|
+
"fzf-bin>=0.74.1",
|
|
17
17
|
"jinja2>=3.1.6",
|
|
18
18
|
"pyfiglet>=1.0.4",
|
|
19
|
-
"rich>=
|
|
19
|
+
"rich>=15.0.0",
|
|
20
20
|
"simple-term-menu>=1.6.6",
|
|
21
|
-
"tomli>=2.4.
|
|
22
|
-
"typer>=0.
|
|
21
|
+
"tomli>=2.4.1",
|
|
22
|
+
"typer>=0.27.0",
|
|
23
23
|
]
|
|
24
24
|
license = "MIT"
|
|
25
25
|
license-files = ["LICEN[CS]E*"]
|
|
26
26
|
|
|
27
|
+
[project.optional-dependencies]
|
|
28
|
+
pyinstaller = ["pyinstaller>=6.21.0"]
|
|
29
|
+
|
|
27
30
|
[project.urls]
|
|
28
31
|
Homepage = "https://github.com/thememium/usecli"
|
|
29
32
|
Documentation = "https://github.com/thememium/usecli"
|
|
@@ -33,6 +36,7 @@ Changelog = "https://github.com/thememium/usecli/blob/master/CHANGELOG.md"
|
|
|
33
36
|
|
|
34
37
|
[project.scripts]
|
|
35
38
|
usecli = "usecli:main"
|
|
39
|
+
usecli-bundle = "usecli.bundler:cli"
|
|
36
40
|
|
|
37
41
|
[build-system]
|
|
38
42
|
requires = ["uv_build>=0.10.2,<0.11.0"]
|
|
@@ -40,13 +44,14 @@ build-backend = "uv_build"
|
|
|
40
44
|
|
|
41
45
|
[dependency-groups]
|
|
42
46
|
dev = [
|
|
43
|
-
"deptry>=0.
|
|
44
|
-
"isort>=
|
|
45
|
-
"poethepoet>=0.
|
|
46
|
-
"
|
|
47
|
+
"deptry>=0.25.1",
|
|
48
|
+
"isort>=8.0.1",
|
|
49
|
+
"poethepoet>=0.48.0",
|
|
50
|
+
"pyinstaller>=6.21.0",
|
|
51
|
+
"pytest>=9.1.1",
|
|
47
52
|
"pytest-cov>=7.1.0",
|
|
48
|
-
"ruff>=0.
|
|
49
|
-
"ty>=0.0.
|
|
53
|
+
"ruff>=0.16.1",
|
|
54
|
+
"ty>=0.0.65",
|
|
50
55
|
]
|
|
51
56
|
|
|
52
57
|
[tool.deptry]
|
|
@@ -74,6 +79,9 @@ release = "bash scripts/release.sh"
|
|
|
74
79
|
|
|
75
80
|
[tool.ty]
|
|
76
81
|
|
|
82
|
+
[tool.ty.environment]
|
|
83
|
+
python-version = "3.12"
|
|
84
|
+
|
|
77
85
|
[tool.ty.src]
|
|
78
86
|
include = ["src", "tests"]
|
|
79
87
|
exclude = ["**/__pycache__", "**/.venv"]
|
|
@@ -85,3 +93,6 @@ exclude = ["**/__pycache__", "**/.venv"]
|
|
|
85
93
|
[tool.setuptools.packages.find]
|
|
86
94
|
where = ["."]
|
|
87
95
|
include = ["cli*"]
|
|
96
|
+
|
|
97
|
+
[tool.setuptools.package-data]
|
|
98
|
+
cli = ["**/*.toml", "**/*.j2"]
|
|
@@ -5,6 +5,8 @@ from __future__ import annotations
|
|
|
5
5
|
import sys
|
|
6
6
|
from importlib import import_module
|
|
7
7
|
|
|
8
|
+
from click import Command as _ClickCommand
|
|
9
|
+
|
|
8
10
|
# Avoid importing typing at module level (costs ~6ms)
|
|
9
11
|
# TYPE_CHECKING is False at runtime, so the if-block never executes
|
|
10
12
|
TYPE_CHECKING = False
|
|
@@ -40,6 +42,8 @@ _LAZY_EXPORTS = {
|
|
|
40
42
|
"console": ("usecli.ui", "console"),
|
|
41
43
|
"Spinner": ("usecli.progress", "Spinner"),
|
|
42
44
|
"ProgressBar": ("usecli.progress", "ProgressBar"),
|
|
45
|
+
"pyinstaller": ("usecli.bundler", "pyinstaller"),
|
|
46
|
+
"run": ("usecli.entry", "run"),
|
|
43
47
|
}
|
|
44
48
|
|
|
45
49
|
|
|
@@ -84,11 +88,14 @@ def _ensure_cli_initialized() -> None:
|
|
|
84
88
|
from usecli.cli.services.command_service import CommandService
|
|
85
89
|
|
|
86
90
|
# Store exceptions for error handling
|
|
91
|
+
# Typer vendors click internally and raises its own exception types.
|
|
92
|
+
# We import them dynamically since typer._click has no type stubs.
|
|
87
93
|
try:
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
94
|
+
_typer_exceptions = import_module("typer._click.exceptions")
|
|
95
|
+
TyperBadParameter: Any = _typer_exceptions.BadParameter
|
|
96
|
+
TyperClickException: Any = _typer_exceptions.ClickException
|
|
97
|
+
TyperUsageError: Any = _typer_exceptions.UsageError
|
|
98
|
+
except (ImportError, AttributeError):
|
|
92
99
|
TyperBadParameter = BadParameter
|
|
93
100
|
TyperClickException = ClickException
|
|
94
101
|
TyperUsageError = UsageError
|
|
@@ -138,7 +145,7 @@ def _ensure_cli_initialized() -> None:
|
|
|
138
145
|
return None
|
|
139
146
|
|
|
140
147
|
if cmd_name in matches:
|
|
141
|
-
if (
|
|
148
|
+
if ( # pragma: no cover - unreachable duplicate of the branch above
|
|
142
149
|
cmd_name in group_alias_to_primary
|
|
143
150
|
and group_alias_to_primary[cmd_name] != cmd_name
|
|
144
151
|
):
|
|
@@ -252,7 +259,14 @@ def _ensure_cli_initialized() -> None:
|
|
|
252
259
|
write_document(success_document())
|
|
253
260
|
return None
|
|
254
261
|
fail("Exit", f"Command exited with status {code}", code)
|
|
255
|
-
except
|
|
262
|
+
except (
|
|
263
|
+
OSError,
|
|
264
|
+
RuntimeError,
|
|
265
|
+
ValueError,
|
|
266
|
+
TypeError,
|
|
267
|
+
AttributeError,
|
|
268
|
+
KeyError,
|
|
269
|
+
) as error:
|
|
256
270
|
print(
|
|
257
271
|
f"{error.__class__.__name__}: {error}",
|
|
258
272
|
file=sys.stderr,
|
|
@@ -340,19 +354,21 @@ def _get_service():
|
|
|
340
354
|
|
|
341
355
|
|
|
342
356
|
__all__ = [
|
|
343
|
-
"BaseCommand",
|
|
344
357
|
"COLOR",
|
|
345
|
-
"
|
|
358
|
+
"Argument",
|
|
359
|
+
"BaseCommand",
|
|
360
|
+
"Confirm",
|
|
346
361
|
"Console",
|
|
347
|
-
"main",
|
|
348
362
|
"Menu",
|
|
349
|
-
"Argument",
|
|
350
363
|
"Option",
|
|
364
|
+
"ProgressBar",
|
|
351
365
|
"Prompt",
|
|
352
|
-
"Confirm",
|
|
353
366
|
"Spinner",
|
|
354
|
-
"ProgressBar",
|
|
355
367
|
"colors",
|
|
368
|
+
"console",
|
|
369
|
+
"main",
|
|
370
|
+
"pyinstaller",
|
|
371
|
+
"run",
|
|
356
372
|
"theme",
|
|
357
373
|
]
|
|
358
374
|
|
|
@@ -432,32 +448,40 @@ def _build_alias_to_primary(alias_registry: dict[str, list[str]]) -> dict[str, s
|
|
|
432
448
|
PrefixMatchingGroup = None
|
|
433
449
|
|
|
434
450
|
|
|
435
|
-
class _FilteredListCommand:
|
|
451
|
+
class _FilteredListCommand(_ClickCommand):
|
|
436
452
|
"""Command that displays a filtered list of commands."""
|
|
437
453
|
|
|
438
454
|
def __init__(self, prefix_filter: str) -> None:
|
|
455
|
+
super().__init__(
|
|
456
|
+
name="filtered-list",
|
|
457
|
+
context_settings={},
|
|
458
|
+
params=[],
|
|
459
|
+
callback=None,
|
|
460
|
+
help=None,
|
|
461
|
+
epilog=None,
|
|
462
|
+
short_help=None,
|
|
463
|
+
no_args_is_help=False,
|
|
464
|
+
hidden=False,
|
|
465
|
+
deprecated=False,
|
|
466
|
+
)
|
|
439
467
|
self.prefix_filter = prefix_filter
|
|
440
|
-
self.name = "filtered-list"
|
|
441
468
|
self.allow_extra_args = True
|
|
442
469
|
self.allow_interspersed_args = True
|
|
443
470
|
self.ignore_unknown_options = True
|
|
444
|
-
self.hidden = False
|
|
445
|
-
self.deprecated = False
|
|
446
|
-
self.params = []
|
|
447
|
-
self.callback = None
|
|
448
|
-
self.help = None
|
|
449
|
-
self.epilog = None
|
|
450
|
-
self.short_help = None
|
|
451
|
-
self.no_args_is_help = False
|
|
452
|
-
self.context_settings = {}
|
|
453
471
|
|
|
454
472
|
def get_short_help_str(self, limit=45):
|
|
455
473
|
return ""
|
|
456
474
|
|
|
457
|
-
def make_context(
|
|
475
|
+
def make_context(
|
|
476
|
+
self,
|
|
477
|
+
info_name: str | None,
|
|
478
|
+
args: list[str],
|
|
479
|
+
parent: object | None = None,
|
|
480
|
+
**extra: Any,
|
|
481
|
+
):
|
|
458
482
|
from click import Context
|
|
459
483
|
|
|
460
|
-
ctx = Context(self, info_name=info_name, **
|
|
484
|
+
ctx = Context(self, info_name=info_name, **extra)
|
|
461
485
|
return ctx
|
|
462
486
|
|
|
463
487
|
def invoke(self, ctx):
|
|
@@ -615,7 +639,14 @@ def main() -> None:
|
|
|
615
639
|
if hasattr(e, "show"):
|
|
616
640
|
e.show()
|
|
617
641
|
sys.exit(e.exit_code if hasattr(e, "exit_code") else 1)
|
|
618
|
-
except
|
|
642
|
+
except (
|
|
643
|
+
OSError,
|
|
644
|
+
RuntimeError,
|
|
645
|
+
ValueError,
|
|
646
|
+
TypeError,
|
|
647
|
+
AttributeError,
|
|
648
|
+
KeyError,
|
|
649
|
+
) as e:
|
|
619
650
|
from usecli.cli.core.exceptions import UsecliUsageError
|
|
620
651
|
|
|
621
652
|
styled_error = UsecliUsageError(str(e))
|