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.
Files changed (26) hide show
  1. sprezzature_cli_gui-1.0.0/LICENSE +28 -0
  2. sprezzature_cli_gui-1.0.0/PKG-INFO +110 -0
  3. sprezzature_cli_gui-1.0.0/README.md +75 -0
  4. sprezzature_cli_gui-1.0.0/pyproject.toml +71 -0
  5. sprezzature_cli_gui-1.0.0/setup.cfg +4 -0
  6. sprezzature_cli_gui-1.0.0/sprezzature_cli_gui/__init__.py +17 -0
  7. sprezzature_cli_gui-1.0.0/sprezzature_cli_gui/_argparse.py +71 -0
  8. sprezzature_cli_gui-1.0.0/sprezzature_cli_gui/adapters/__init__.py +23 -0
  9. sprezzature_cli_gui-1.0.0/sprezzature_cli_gui/adapters/argparse.py +115 -0
  10. sprezzature_cli_gui-1.0.0/sprezzature_cli_gui/adapters/click.py +173 -0
  11. sprezzature_cli_gui-1.0.0/sprezzature_cli_gui/adapters/help_text.py +537 -0
  12. sprezzature_cli_gui-1.0.0/sprezzature_cli_gui/api.py +176 -0
  13. sprezzature_cli_gui-1.0.0/sprezzature_cli_gui/cli.py +163 -0
  14. sprezzature_cli_gui-1.0.0/sprezzature_cli_gui/loader.py +138 -0
  15. sprezzature_cli_gui-1.0.0/sprezzature_cli_gui/mcp.py +152 -0
  16. sprezzature_cli_gui-1.0.0/sprezzature_cli_gui/renderer.py +353 -0
  17. sprezzature_cli_gui-1.0.0/sprezzature_cli_gui/schema.py +107 -0
  18. sprezzature_cli_gui-1.0.0/sprezzature_cli_gui.egg-info/PKG-INFO +110 -0
  19. sprezzature_cli_gui-1.0.0/sprezzature_cli_gui.egg-info/SOURCES.txt +24 -0
  20. sprezzature_cli_gui-1.0.0/sprezzature_cli_gui.egg-info/dependency_links.txt +1 -0
  21. sprezzature_cli_gui-1.0.0/sprezzature_cli_gui.egg-info/entry_points.txt +3 -0
  22. sprezzature_cli_gui-1.0.0/sprezzature_cli_gui.egg-info/requires.txt +19 -0
  23. sprezzature_cli_gui-1.0.0/sprezzature_cli_gui.egg-info/top_level.txt +1 -0
  24. sprezzature_cli_gui-1.0.0/tests/test_api_mcp.py +85 -0
  25. sprezzature_cli_gui-1.0.0/tests/test_cli_gui.py +82 -0
  26. 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
+ [![License](https://img.shields.io/badge/license-BSD--3--Clause-blue.svg)](https://github.com/warith-harchaoui/sprezzature-cli-gui/blob/main/LICENSE)
39
+ [![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/)
40
+
41
+ [![logo](https://raw.githubusercontent.com/warith-harchaoui/sprezzature-cli-gui/main/assets/logo.png)](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
+ [![License](https://img.shields.io/badge/license-BSD--3--Clause-blue.svg)](https://github.com/warith-harchaoui/sprezzature-cli-gui/blob/main/LICENSE)
4
+ [![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/)
5
+
6
+ [![logo](https://raw.githubusercontent.com/warith-harchaoui/sprezzature-cli-gui/main/assets/logo.png)](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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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
+ }