sprezzature-cli-gui 1.0.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.
- sprezzature_cli_gui-1.0.0/LICENSE +28 -0
- sprezzature_cli_gui-1.0.0/PKG-INFO +110 -0
- sprezzature_cli_gui-1.0.0/README.md +75 -0
- sprezzature_cli_gui-1.0.0/pyproject.toml +71 -0
- sprezzature_cli_gui-1.0.0/setup.cfg +4 -0
- sprezzature_cli_gui-1.0.0/sprezzature_cli_gui/__init__.py +17 -0
- sprezzature_cli_gui-1.0.0/sprezzature_cli_gui/_argparse.py +71 -0
- sprezzature_cli_gui-1.0.0/sprezzature_cli_gui/adapters/__init__.py +23 -0
- sprezzature_cli_gui-1.0.0/sprezzature_cli_gui/adapters/argparse.py +115 -0
- sprezzature_cli_gui-1.0.0/sprezzature_cli_gui/adapters/click.py +173 -0
- sprezzature_cli_gui-1.0.0/sprezzature_cli_gui/adapters/help_text.py +537 -0
- sprezzature_cli_gui-1.0.0/sprezzature_cli_gui/api.py +176 -0
- sprezzature_cli_gui-1.0.0/sprezzature_cli_gui/cli.py +163 -0
- sprezzature_cli_gui-1.0.0/sprezzature_cli_gui/loader.py +138 -0
- sprezzature_cli_gui-1.0.0/sprezzature_cli_gui/mcp.py +152 -0
- sprezzature_cli_gui-1.0.0/sprezzature_cli_gui/renderer.py +353 -0
- sprezzature_cli_gui-1.0.0/sprezzature_cli_gui/schema.py +107 -0
- sprezzature_cli_gui-1.0.0/sprezzature_cli_gui.egg-info/PKG-INFO +110 -0
- sprezzature_cli_gui-1.0.0/sprezzature_cli_gui.egg-info/SOURCES.txt +24 -0
- sprezzature_cli_gui-1.0.0/sprezzature_cli_gui.egg-info/dependency_links.txt +1 -0
- sprezzature_cli_gui-1.0.0/sprezzature_cli_gui.egg-info/entry_points.txt +3 -0
- sprezzature_cli_gui-1.0.0/sprezzature_cli_gui.egg-info/requires.txt +19 -0
- sprezzature_cli_gui-1.0.0/sprezzature_cli_gui.egg-info/top_level.txt +1 -0
- sprezzature_cli_gui-1.0.0/tests/test_api_mcp.py +85 -0
- sprezzature_cli_gui-1.0.0/tests/test_cli_gui.py +82 -0
- sprezzature_cli_gui-1.0.0/tests/test_units.py +526 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Warith HARCHAOUI
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice,
|
|
9
|
+
this list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its contributors
|
|
16
|
+
may be used to endorse or promote products derived from this software
|
|
17
|
+
without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sprezzature-cli-gui
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Auto-generate a single-file HTML GUI from any Python CLI (argparse, Click, Typer).
|
|
5
|
+
Author-email: Warith HARCHAOUI <warith.harchaoui@gmail.com>
|
|
6
|
+
License-Expression: BSD-3-Clause
|
|
7
|
+
Project-URL: Homepage, https://harchaoui.org/warith/sprezzature/
|
|
8
|
+
Project-URL: Repository, https://github.com/warith-harchaoui/sprezzature-cli-gui
|
|
9
|
+
Keywords: cli,gui,argparse,click,typer,html,sprezzature
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: Software Development :: User Interfaces
|
|
16
|
+
Classifier: Topic :: Utilities
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
22
|
+
Requires-Dist: ruff; extra == "dev"
|
|
23
|
+
Provides-Extra: click
|
|
24
|
+
Requires-Dist: click>=8.0; extra == "click"
|
|
25
|
+
Provides-Extra: typer
|
|
26
|
+
Requires-Dist: typer>=0.12; extra == "typer"
|
|
27
|
+
Provides-Extra: api
|
|
28
|
+
Requires-Dist: fastapi>=0.115; extra == "api"
|
|
29
|
+
Requires-Dist: uvicorn[standard]>=0.30; extra == "api"
|
|
30
|
+
Provides-Extra: mcp
|
|
31
|
+
Requires-Dist: sprezzature-cli-gui[api]; extra == "mcp"
|
|
32
|
+
Requires-Dist: fastapi-mcp>=0.3; extra == "mcp"
|
|
33
|
+
Requires-Dist: mcp<2.0; extra == "mcp"
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
|
|
36
|
+
# sprezzature-cli-gui
|
|
37
|
+
|
|
38
|
+
[](https://github.com/warith-harchaoui/sprezzature-cli-gui/blob/main/LICENSE)
|
|
39
|
+
[](https://www.python.org/)
|
|
40
|
+
|
|
41
|
+
[](https://harchaoui.org/warith/sprezzature/)
|
|
42
|
+
|
|
43
|
+
Turn any Python command-line tool into a clickable form, automatically. A command-line
|
|
44
|
+
tool takes typed text ("do this, with these options") instead of buttons and fields;
|
|
45
|
+
this package reads the tool's own definition of its commands and options and builds a
|
|
46
|
+
web page with a button and a field for each one, so a person who has never opened a
|
|
47
|
+
terminal can still use it.
|
|
48
|
+
|
|
49
|
+
Point this tool at a parser: the object inside a Python CLI that reads and validates
|
|
50
|
+
the typed command. `argparse` (in Python's standard library), `Click`, and `Typer` are
|
|
51
|
+
the three common ways to build one; all three are supported. Given that parser, this
|
|
52
|
+
package emits a single self-contained page written in plain JavaScript and styled with
|
|
53
|
+
Tailwind (a CSS framework, meaning it ships pre-made style classes instead of hand-
|
|
54
|
+
written CSS). Every sub-command becomes a section, every flag a field. The page never
|
|
55
|
+
runs the command itself: it assembles the exact command line as text, in the visitor's
|
|
56
|
+
own browser, ready to copy and paste, to hand to a desktop app through a `Tauri`
|
|
57
|
+
`invoke` call (`Tauri` wraps a web page as a native desktop app; `invoke` is how
|
|
58
|
+
that wrapper lets the page call back into it), or to send to a FastAPI endpoint (a small
|
|
59
|
+
Python web server) that runs it for real.
|
|
60
|
+
|
|
61
|
+
For example: a CLI with a `--verbose` flag and a `convert INPUT OUTPUT` sub-command
|
|
62
|
+
becomes a page with a checkbox for `--verbose` and two text fields for `INPUT` and
|
|
63
|
+
`OUTPUT`; filling them in and pressing "Build command" prints the exact line
|
|
64
|
+
`mytool convert in.csv out.json --verbose` for the visitor to copy.
|
|
65
|
+
|
|
66
|
+
No framework, no build step: the emitted page is one static HTML file. It does load
|
|
67
|
+
Tailwind's "Play" build from a CDN (`cdn.tailwindcss.com`) to turn the utility classes
|
|
68
|
+
into real CSS in the visitor's browser, so opening the file needs an internet
|
|
69
|
+
connection the first time; the form fields themselves still work, unstyled, without one.
|
|
70
|
+
|
|
71
|
+
## Install
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install sprezzature-cli-gui # core (argparse)
|
|
75
|
+
pip install "sprezzature-cli-gui[click]" # add Click support
|
|
76
|
+
pip install "sprezzature-cli-gui[typer]" # add Typer support
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Use
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# From a parser factory in an importable module
|
|
83
|
+
sprezzature-cli-gui my_pkg.my_cli:build_parser > gui.html
|
|
84
|
+
|
|
85
|
+
# From a script path
|
|
86
|
+
sprezzature-cli-gui ./my_cli.py:make_parser > gui.html
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
When the parser cannot be imported, the tool falls back to parsing the tool's `--help`
|
|
90
|
+
output, so it works even against a CLI it cannot introspect directly, including one
|
|
91
|
+
that is not written in Python at all.
|
|
92
|
+
|
|
93
|
+
## How it works
|
|
94
|
+
|
|
95
|
+
`cli_to_gui.py` is a thin wrapper that re-exports the real implementation, which lives
|
|
96
|
+
in the `sprezzature_cli_gui` package:
|
|
97
|
+
|
|
98
|
+
- `adapters/`: read (introspect, meaning: examine the parser object's own fields
|
|
99
|
+
directly, rather than guess from printed text) an argparse or Click parser; or, when
|
|
100
|
+
no parser object is reachable, fall back to parsing the tool's `--help` text instead.
|
|
101
|
+
- `schema.py`: the one normalized command/flag description every adapter produces, so
|
|
102
|
+
the renderer never needs to know which of the three frameworks it came from.
|
|
103
|
+
- `renderer.py`: turns that description into the self-contained HTML + Tailwind +
|
|
104
|
+
plain-JavaScript page.
|
|
105
|
+
- `loader.py`: resolves a `module:factory` or `path.py:factory` spec (a string naming
|
|
106
|
+
where the parser-building function lives) down to the actual parser object.
|
|
107
|
+
|
|
108
|
+
## License
|
|
109
|
+
|
|
110
|
+
BSD-3-Clause © Warith HARCHAOUI. Part of the [sprezzature](https://harchaoui.org/warith/sprezzature/) toolkit.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# sprezzature-cli-gui
|
|
2
|
+
|
|
3
|
+
[](https://github.com/warith-harchaoui/sprezzature-cli-gui/blob/main/LICENSE)
|
|
4
|
+
[](https://www.python.org/)
|
|
5
|
+
|
|
6
|
+
[](https://harchaoui.org/warith/sprezzature/)
|
|
7
|
+
|
|
8
|
+
Turn any Python command-line tool into a clickable form, automatically. A command-line
|
|
9
|
+
tool takes typed text ("do this, with these options") instead of buttons and fields;
|
|
10
|
+
this package reads the tool's own definition of its commands and options and builds a
|
|
11
|
+
web page with a button and a field for each one, so a person who has never opened a
|
|
12
|
+
terminal can still use it.
|
|
13
|
+
|
|
14
|
+
Point this tool at a parser: the object inside a Python CLI that reads and validates
|
|
15
|
+
the typed command. `argparse` (in Python's standard library), `Click`, and `Typer` are
|
|
16
|
+
the three common ways to build one; all three are supported. Given that parser, this
|
|
17
|
+
package emits a single self-contained page written in plain JavaScript and styled with
|
|
18
|
+
Tailwind (a CSS framework, meaning it ships pre-made style classes instead of hand-
|
|
19
|
+
written CSS). Every sub-command becomes a section, every flag a field. The page never
|
|
20
|
+
runs the command itself: it assembles the exact command line as text, in the visitor's
|
|
21
|
+
own browser, ready to copy and paste, to hand to a desktop app through a `Tauri`
|
|
22
|
+
`invoke` call (`Tauri` wraps a web page as a native desktop app; `invoke` is how
|
|
23
|
+
that wrapper lets the page call back into it), or to send to a FastAPI endpoint (a small
|
|
24
|
+
Python web server) that runs it for real.
|
|
25
|
+
|
|
26
|
+
For example: a CLI with a `--verbose` flag and a `convert INPUT OUTPUT` sub-command
|
|
27
|
+
becomes a page with a checkbox for `--verbose` and two text fields for `INPUT` and
|
|
28
|
+
`OUTPUT`; filling them in and pressing "Build command" prints the exact line
|
|
29
|
+
`mytool convert in.csv out.json --verbose` for the visitor to copy.
|
|
30
|
+
|
|
31
|
+
No framework, no build step: the emitted page is one static HTML file. It does load
|
|
32
|
+
Tailwind's "Play" build from a CDN (`cdn.tailwindcss.com`) to turn the utility classes
|
|
33
|
+
into real CSS in the visitor's browser, so opening the file needs an internet
|
|
34
|
+
connection the first time; the form fields themselves still work, unstyled, without one.
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install sprezzature-cli-gui # core (argparse)
|
|
40
|
+
pip install "sprezzature-cli-gui[click]" # add Click support
|
|
41
|
+
pip install "sprezzature-cli-gui[typer]" # add Typer support
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Use
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# From a parser factory in an importable module
|
|
48
|
+
sprezzature-cli-gui my_pkg.my_cli:build_parser > gui.html
|
|
49
|
+
|
|
50
|
+
# From a script path
|
|
51
|
+
sprezzature-cli-gui ./my_cli.py:make_parser > gui.html
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
When the parser cannot be imported, the tool falls back to parsing the tool's `--help`
|
|
55
|
+
output, so it works even against a CLI it cannot introspect directly, including one
|
|
56
|
+
that is not written in Python at all.
|
|
57
|
+
|
|
58
|
+
## How it works
|
|
59
|
+
|
|
60
|
+
`cli_to_gui.py` is a thin wrapper that re-exports the real implementation, which lives
|
|
61
|
+
in the `sprezzature_cli_gui` package:
|
|
62
|
+
|
|
63
|
+
- `adapters/`: read (introspect, meaning: examine the parser object's own fields
|
|
64
|
+
directly, rather than guess from printed text) an argparse or Click parser; or, when
|
|
65
|
+
no parser object is reachable, fall back to parsing the tool's `--help` text instead.
|
|
66
|
+
- `schema.py`: the one normalized command/flag description every adapter produces, so
|
|
67
|
+
the renderer never needs to know which of the three frameworks it came from.
|
|
68
|
+
- `renderer.py`: turns that description into the self-contained HTML + Tailwind +
|
|
69
|
+
plain-JavaScript page.
|
|
70
|
+
- `loader.py`: resolves a `module:factory` or `path.py:factory` spec (a string naming
|
|
71
|
+
where the parser-building function lives) down to the actual parser object.
|
|
72
|
+
|
|
73
|
+
## License
|
|
74
|
+
|
|
75
|
+
BSD-3-Clause © Warith HARCHAOUI. Part of the [sprezzature](https://harchaoui.org/warith/sprezzature/) toolkit.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "sprezzature-cli-gui"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Auto-generate a single-file HTML GUI from any Python CLI (argparse, Click, Typer)."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "BSD-3-Clause"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{name = "Warith HARCHAOUI", email = "warith.harchaoui@gmail.com"}]
|
|
14
|
+
keywords = ["cli", "gui", "argparse", "click", "typer", "html", "sprezzature"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 5 - Production/Stable",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.10",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Topic :: Software Development :: User Interfaces",
|
|
22
|
+
"Topic :: Utilities",
|
|
23
|
+
]
|
|
24
|
+
dependencies = []
|
|
25
|
+
|
|
26
|
+
[project.optional-dependencies]
|
|
27
|
+
dev = ["pytest>=8.0", "ruff"]
|
|
28
|
+
click = ["click>=8.0"]
|
|
29
|
+
typer = ["typer>=0.12"]
|
|
30
|
+
|
|
31
|
+
# HTTP surface (sprezzature_cli_gui/api.py): schema tree in, GUI HTML out.
|
|
32
|
+
# Deliberately does NOT expose loader.load_parser_from_spec — importing an
|
|
33
|
+
# arbitrary module behind an HTTP endpoint is remote code execution.
|
|
34
|
+
api = [
|
|
35
|
+
"fastapi>=0.115",
|
|
36
|
+
"uvicorn[standard]>=0.30",
|
|
37
|
+
]
|
|
38
|
+
# MCP surface: the same routes republished as agent tools, via fastapi-mcp.
|
|
39
|
+
mcp = [
|
|
40
|
+
"sprezzature-cli-gui[api]",
|
|
41
|
+
"fastapi-mcp>=0.3",
|
|
42
|
+
# fastapi-mcp 0.4.0 (the latest as of 2026-08) declares mcp>=1.12.0 with
|
|
43
|
+
# no upper bound, but calls mcp's Server(name, description) positionally
|
|
44
|
+
# -- mcp 2.0.0 made `description` keyword-only, so an unconstrained
|
|
45
|
+
# resolve breaks FastApiMCP(...) at construction time (verified: pip
|
|
46
|
+
# happily installs mcp 2.2.0 alongside fastapi-mcp 0.4.0, and
|
|
47
|
+
# `sprezzature_cli_gui.mcp` then raises TypeError on import). Pin below
|
|
48
|
+
# 2.0 until fastapi-mcp ships a fix.
|
|
49
|
+
"mcp<2.0",
|
|
50
|
+
]
|
|
51
|
+
[project.scripts]
|
|
52
|
+
sprezzature-cli-gui-mcp = "sprezzature_cli_gui.mcp:main"
|
|
53
|
+
sprezzature-cli-gui = "sprezzature_cli_gui.cli:main"
|
|
54
|
+
|
|
55
|
+
[project.urls]
|
|
56
|
+
Homepage = "https://harchaoui.org/warith/sprezzature/"
|
|
57
|
+
Repository = "https://github.com/warith-harchaoui/sprezzature-cli-gui"
|
|
58
|
+
|
|
59
|
+
[tool.setuptools.packages.find]
|
|
60
|
+
include = ["sprezzature_cli_gui*"]
|
|
61
|
+
|
|
62
|
+
[tool.ruff]
|
|
63
|
+
line-length = 100
|
|
64
|
+
target-version = "py310"
|
|
65
|
+
|
|
66
|
+
[tool.ruff.lint]
|
|
67
|
+
select = ["E", "F", "I", "UP"]
|
|
68
|
+
ignore = ["E501"]
|
|
69
|
+
|
|
70
|
+
[tool.pytest.ini_options]
|
|
71
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
sprezzature-cli-gui: turn a Python command-line tool into a clickable form.
|
|
3
|
+
|
|
4
|
+
Reads the parser object a Python CLI already builds for itself, whether with
|
|
5
|
+
`argparse` (the standard library's tool for this), `Click`, or `Typer`, and emits a
|
|
6
|
+
single self-contained web page (plain JavaScript, Tailwind CSS) that maps every
|
|
7
|
+
sub-command and flag to a form field. The page builds the command line locally, in the
|
|
8
|
+
visitor's browser, and shows it ready to copy, to hand to a `Tauri invoke` call (the
|
|
9
|
+
bridge a desktop-app wrapper uses to run a command on the visitor's behalf), or to send
|
|
10
|
+
to a FastAPI server over SSE (Server-Sent Events, a simple one-way stream a server uses
|
|
11
|
+
to push live output back to the page).
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
__version__ = "1.0.0"
|
|
15
|
+
__author__ = "Warith HARCHAOUI"
|
|
16
|
+
__email__ = "warith.harchaoui@gmail.com"
|
|
17
|
+
__license__ = "BSD-3-Clause"
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""
|
|
2
|
+
_argparse: one factory function for every script's command-line parser.
|
|
3
|
+
|
|
4
|
+
Python's standard library builds a command-line interface around an
|
|
5
|
+
``argparse.ArgumentParser`` object: you create one, register each flag
|
|
6
|
+
(``--lang``, ``--out``, and so on) on it, then call ``.parse_args()`` to
|
|
7
|
+
turn the words the user typed into a plain object with one attribute per
|
|
8
|
+
flag. Every script in this project needs the same handful of small
|
|
9
|
+
conveniences on top of that (a clean program name in ``--help``, instead
|
|
10
|
+
of a long file path; multi-line help text kept exactly as written instead
|
|
11
|
+
of being auto-reflowed; a ``-V``/``--version`` flag). Rather than
|
|
12
|
+
repeating that setup in every script, ``make_parser(prog, description,
|
|
13
|
+
epilog=None)`` builds one parser already configured that way, and each
|
|
14
|
+
script starts from it.
|
|
15
|
+
|
|
16
|
+
This file is duplicated on purpose into every sprezzature-* repository,
|
|
17
|
+
one copy each, so a skill stays self-contained and runs on its own:
|
|
18
|
+
including from a downloaded zip, with nothing available but Python's
|
|
19
|
+
standard library. The copies are meant to stay byte-for-byte identical
|
|
20
|
+
apart from ``SKILL_VERSION``, which each repository sets to its own
|
|
21
|
+
released version. So edit the canonical copy rather than this one, unless
|
|
22
|
+
this is it: ``scripts/sync_helpers.py``, in the sprezzature monorepo,
|
|
23
|
+
names the canonical copy, reports the ones that have drifted, and
|
|
24
|
+
propagates the change with ``--apply``.
|
|
25
|
+
|
|
26
|
+
Author
|
|
27
|
+
------
|
|
28
|
+
`Warith HARCHAOUI, Ph.D. <https://www.linkedin.com/in/warith-harchaoui/>`_
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
import argparse
|
|
34
|
+
|
|
35
|
+
SKILL_VERSION = "1.0.0"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def make_parser(
|
|
39
|
+
prog: str,
|
|
40
|
+
description: str,
|
|
41
|
+
epilog: str | None = None,
|
|
42
|
+
) -> argparse.ArgumentParser:
|
|
43
|
+
"""Build a pre-configured argparse parser.
|
|
44
|
+
|
|
45
|
+
Parameters
|
|
46
|
+
----------
|
|
47
|
+
prog : str
|
|
48
|
+
Program name shown in ``--help`` (e.g. ``"sprezzature-figures-make"``).
|
|
49
|
+
description : str
|
|
50
|
+
One-paragraph description shown above the options table.
|
|
51
|
+
epilog : str or None, optional
|
|
52
|
+
Text shown below the options table, usually usage examples.
|
|
53
|
+
|
|
54
|
+
Returns
|
|
55
|
+
-------
|
|
56
|
+
argparse.ArgumentParser
|
|
57
|
+
Parser with ``-V``/``--version`` pre-attached.
|
|
58
|
+
"""
|
|
59
|
+
parser = argparse.ArgumentParser(
|
|
60
|
+
prog=prog,
|
|
61
|
+
description=description,
|
|
62
|
+
epilog=epilog,
|
|
63
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
64
|
+
)
|
|
65
|
+
parser.add_argument(
|
|
66
|
+
"-V",
|
|
67
|
+
"--version",
|
|
68
|
+
action="version",
|
|
69
|
+
version=f"%(prog)s {SKILL_VERSION}",
|
|
70
|
+
)
|
|
71
|
+
return parser
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
sprezzature_cli_gui.adapters
|
|
3
|
+
=====================
|
|
4
|
+
|
|
5
|
+
One adapter per supported CLI framework: the small piece of code that knows
|
|
6
|
+
how to read that specific framework's parser object and reduce it to the
|
|
7
|
+
one shared dict shape (``prog`` / ``description`` / ``actions`` /
|
|
8
|
+
``sub_commands``) the HTML renderer consumes. Because every adapter ends
|
|
9
|
+
at the same shape, the renderer itself never needs to know or branch on
|
|
10
|
+
which framework produced it.
|
|
11
|
+
|
|
12
|
+
- :mod:`sprezzature_cli_gui.adapters.argparse`: reads a standard-library
|
|
13
|
+
:class:`argparse.ArgumentParser`, always available since argparse ships
|
|
14
|
+
with Python itself.
|
|
15
|
+
- :mod:`sprezzature_cli_gui.adapters.click`: reads an optional
|
|
16
|
+
:class:`click.Command`; also covers Typer apps, which are built on Click
|
|
17
|
+
internally.
|
|
18
|
+
- :mod:`sprezzature_cli_gui.adapters.help_text`: the fallback that needs no
|
|
19
|
+
parser object at all, reading a CLI's printed ``--help`` text instead
|
|
20
|
+
(used through ``--from-help``, at lower fidelity than the other two).
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""
|
|
2
|
+
sprezzature_cli_gui.adapters.argparse
|
|
3
|
+
==============================
|
|
4
|
+
|
|
5
|
+
The `argparse` adapter, for Python's own built-in way of building
|
|
6
|
+
command-line tools.
|
|
7
|
+
|
|
8
|
+
It reads an :class:`argparse.ArgumentParser` and reduces it to the shared
|
|
9
|
+
parser-tree dict, dropping the ``--help`` and ``--version`` actions along
|
|
10
|
+
the way: they make sense on a terminal, where a person types commands one
|
|
11
|
+
at a time, but not on a generated form, which shows every option to fill
|
|
12
|
+
in at once. Since argparse ships with every Python installation, this
|
|
13
|
+
adapter needs no extra dependency and is always available; :func:`walk`
|
|
14
|
+
reaches for it whenever the target factory returns an argparse parser.
|
|
15
|
+
|
|
16
|
+
One naming detail worth flagging: this file is itself named
|
|
17
|
+
``argparse.py``, yet its own ``import argparse`` line still reaches
|
|
18
|
+
Python's standard-library module of the same name, not itself. Python 3
|
|
19
|
+
resolves that import from the top-level package search path first (an
|
|
20
|
+
"absolute import"), so a file's own name never shadows the standard
|
|
21
|
+
library, and no special handling was needed to make this work.
|
|
22
|
+
|
|
23
|
+
Author
|
|
24
|
+
------
|
|
25
|
+
`Warith HARCHAOUI, Ph.D. <https://www.linkedin.com/in/warith-harchaoui/>`_
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import argparse
|
|
31
|
+
from typing import Any
|
|
32
|
+
|
|
33
|
+
from sprezzature_cli_gui.schema import NO_DEFAULT, _safe_default
|
|
34
|
+
|
|
35
|
+
# Re-exported for parity with the original single-module namespace, where
|
|
36
|
+
# the sentinel lived alongside these adapters.
|
|
37
|
+
__all__ = ["NO_DEFAULT", "_action_kind", "serialize_action", "walk_parser"]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _action_kind(a: argparse.Action) -> str:
|
|
41
|
+
"""
|
|
42
|
+
Map an :class:`argparse.Action` to an HTML form-field kind string.
|
|
43
|
+
|
|
44
|
+
Returns one of: ``"bool"``, ``"int"``, ``"float"``, ``"choice"``,
|
|
45
|
+
``"text"``, ``"file"`` (for actions whose ``type`` looks like
|
|
46
|
+
:func:`open` / :class:`argparse.FileType`).
|
|
47
|
+
"""
|
|
48
|
+
if isinstance(a, (argparse._StoreTrueAction, argparse._StoreFalseAction)):
|
|
49
|
+
return "bool"
|
|
50
|
+
if a.choices:
|
|
51
|
+
return "choice"
|
|
52
|
+
if a.type is None:
|
|
53
|
+
return "text"
|
|
54
|
+
if a.type is int:
|
|
55
|
+
return "int"
|
|
56
|
+
if a.type is float:
|
|
57
|
+
return "float"
|
|
58
|
+
if isinstance(a.type, argparse.FileType):
|
|
59
|
+
return "file"
|
|
60
|
+
# ``type=open`` or callable: render as text and let the user paste a path.
|
|
61
|
+
return "text"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def serialize_action(a: argparse.Action) -> dict[str, Any]:
|
|
65
|
+
"""Project one :class:`argparse.Action` into a JSON-friendly dict."""
|
|
66
|
+
return {
|
|
67
|
+
"dest": a.dest,
|
|
68
|
+
"flags": list(a.option_strings),
|
|
69
|
+
"kind": _action_kind(a),
|
|
70
|
+
"choices": list(a.choices) if a.choices else None,
|
|
71
|
+
"required": bool(a.required),
|
|
72
|
+
"default": _safe_default(a.default),
|
|
73
|
+
"help": (a.help or "").strip(),
|
|
74
|
+
"nargs": a.nargs if isinstance(a.nargs, (str, int)) else None,
|
|
75
|
+
"metavar": (
|
|
76
|
+
a.metavar
|
|
77
|
+
if isinstance(a.metavar, str)
|
|
78
|
+
else (a.metavar[0] if isinstance(a.metavar, tuple) else None)
|
|
79
|
+
),
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def walk_parser(parser: argparse.ArgumentParser) -> dict[str, Any]:
|
|
84
|
+
"""
|
|
85
|
+
Walk an :class:`argparse.ArgumentParser` into a serialisable tree.
|
|
86
|
+
|
|
87
|
+
The shape mirrors ``argparse`` itself: a ``prog`` / ``description``
|
|
88
|
+
pair, a list of leaf actions, and a (possibly empty)
|
|
89
|
+
``sub_commands`` dict for any nested sub-parsers.
|
|
90
|
+
|
|
91
|
+
Help / version actions are filtered out; they exist only on the
|
|
92
|
+
CLI surface, not in a GUI.
|
|
93
|
+
|
|
94
|
+
See also :func:`walk` for the framework-agnostic entry point that
|
|
95
|
+
dispatches between this and :func:`walk_click`.
|
|
96
|
+
"""
|
|
97
|
+
actions: list[dict[str, Any]] = []
|
|
98
|
+
sub_commands: dict[str, dict[str, Any]] = {}
|
|
99
|
+
for a in parser._actions:
|
|
100
|
+
if isinstance(a, argparse._SubParsersAction):
|
|
101
|
+
for name, sub in a.choices.items():
|
|
102
|
+
sub_commands[name] = walk_parser(sub)
|
|
103
|
+
elif isinstance(a, (argparse._HelpAction,)) or a.dest == argparse.SUPPRESS:
|
|
104
|
+
continue
|
|
105
|
+
elif getattr(a, "version", None) is not None:
|
|
106
|
+
# ``action="version"``: version banner, not a user input.
|
|
107
|
+
continue
|
|
108
|
+
else:
|
|
109
|
+
actions.append(serialize_action(a))
|
|
110
|
+
return {
|
|
111
|
+
"prog": parser.prog,
|
|
112
|
+
"description": (parser.description or "").strip(),
|
|
113
|
+
"actions": actions,
|
|
114
|
+
"sub_commands": sub_commands,
|
|
115
|
+
}
|