strictcli 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.
- strictcli-0.1.0/.claude/settings.json +3 -0
- strictcli-0.1.0/.github/workflows/ci.yml +21 -0
- strictcli-0.1.0/.github/workflows/publish.yml +30 -0
- strictcli-0.1.0/.gitignore +15 -0
- strictcli-0.1.0/.rlsbl/bases/.claude/settings.json +3 -0
- strictcli-0.1.0/.rlsbl/bases/.github/workflows/ci.yml +21 -0
- strictcli-0.1.0/.rlsbl/bases/.github/workflows/publish.yml +30 -0
- strictcli-0.1.0/.rlsbl/bases/.gitignore +15 -0
- strictcli-0.1.0/.rlsbl/bases/.rlsbl/hooks/post-release.sh +8 -0
- strictcli-0.1.0/.rlsbl/bases/.rlsbl/hooks/pre-release.sh +31 -0
- strictcli-0.1.0/.rlsbl/bases/CHANGELOG.md +5 -0
- strictcli-0.1.0/.rlsbl/bases/CLAUDE.md +20 -0
- strictcli-0.1.0/.rlsbl/bases/LICENSE +21 -0
- strictcli-0.1.0/.rlsbl/config.json +6 -0
- strictcli-0.1.0/.rlsbl/hashes.json +11 -0
- strictcli-0.1.0/.rlsbl/hooks/post-release.sh +8 -0
- strictcli-0.1.0/.rlsbl/hooks/pre-release.sh +31 -0
- strictcli-0.1.0/.rlsbl/version +1 -0
- strictcli-0.1.0/CHANGELOG.md +17 -0
- strictcli-0.1.0/CLAUDE.md +20 -0
- strictcli-0.1.0/LICENSE +21 -0
- strictcli-0.1.0/PKG-INFO +318 -0
- strictcli-0.1.0/README.md +300 -0
- strictcli-0.1.0/index.js +5 -0
- strictcli-0.1.0/package-lock.json +14 -0
- strictcli-0.1.0/package.json +25 -0
- strictcli-0.1.0/postinstall.js +17 -0
- strictcli-0.1.0/pyproject.toml +30 -0
- strictcli-0.1.0/strictcli/__init__.py +738 -0
- strictcli-0.1.0/tests/test_e2e.py +216 -0
- strictcli-0.1.0/tests/test_env.py +95 -0
- strictcli-0.1.0/tests/test_help.py +129 -0
- strictcli-0.1.0/tests/test_nesting.py +65 -0
- strictcli-0.1.0/tests/test_parser.py +178 -0
- strictcli-0.1.0/tests/test_registration.py +156 -0
- strictcli-0.1.0/tests/test_tags.py +103 -0
- strictcli-0.1.0/todo/.done/original-idea.md +36 -0
- strictcli-0.1.0/todo/deferred.md +15 -0
- strictcli-0.1.0/uv.lock +79 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
# requires-python: >= 3.11
|
|
15
|
+
python-version: ["3.12", "3.13", "3.14"]
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v6
|
|
18
|
+
- uses: astral-sh/setup-uv@v7
|
|
19
|
+
- run: uv python install ${{ matrix.python-version }}
|
|
20
|
+
- run: uv sync
|
|
21
|
+
- run: uv run python -c "import strictcli"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
id-token: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
pypi:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v6
|
|
16
|
+
- uses: astral-sh/setup-uv@v7
|
|
17
|
+
- run: uv build
|
|
18
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
19
|
+
|
|
20
|
+
npm:
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v6
|
|
24
|
+
- uses: actions/setup-node@v6
|
|
25
|
+
with:
|
|
26
|
+
node-version: 24
|
|
27
|
+
registry-url: https://registry.npmjs.org
|
|
28
|
+
- run: npm publish --provenance --access public
|
|
29
|
+
env:
|
|
30
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
# requires-python: >= 3.11
|
|
15
|
+
python-version: ["3.12", "3.13", "3.14"]
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v6
|
|
18
|
+
- uses: astral-sh/setup-uv@v7
|
|
19
|
+
- run: uv python install ${{ matrix.python-version }}
|
|
20
|
+
- run: uv sync
|
|
21
|
+
- run: uv run python -c "import strictcli"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
id-token: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
pypi:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v6
|
|
16
|
+
- uses: astral-sh/setup-uv@v7
|
|
17
|
+
- run: uv build
|
|
18
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
19
|
+
|
|
20
|
+
npm:
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v6
|
|
24
|
+
- uses: actions/setup-node@v6
|
|
25
|
+
with:
|
|
26
|
+
node-version: 24
|
|
27
|
+
registry-url: {{registryUrl}}
|
|
28
|
+
- run: npm publish --provenance --access public
|
|
29
|
+
env:
|
|
30
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Post-release hook. Runs after a successful release (non-fatal).
|
|
3
|
+
# Environment: RLSBL_VERSION is set to the released version.
|
|
4
|
+
# Customize this for your project (e.g., local install, deploy, notify).
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
echo "Post-release: v$RLSBL_VERSION"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Pre-release validation hook.
|
|
3
|
+
# Runs before rlsbl creates a release. Exit non-zero to abort.
|
|
4
|
+
# Detects project type and runs appropriate checks automatically.
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
echo "Running pre-release checks..."
|
|
9
|
+
|
|
10
|
+
if [ -f go.mod ]; then
|
|
11
|
+
echo " Go: vet + build + test"
|
|
12
|
+
go vet ./...
|
|
13
|
+
go build ./...
|
|
14
|
+
go test ./... -race -short -count=1
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
if [ -f pyproject.toml ]; then
|
|
18
|
+
echo " Python: pytest"
|
|
19
|
+
if command -v uv &>/dev/null; then
|
|
20
|
+
uv run pytest
|
|
21
|
+
elif command -v pytest &>/dev/null; then
|
|
22
|
+
pytest
|
|
23
|
+
fi
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
if [ -f package.json ] && node -e "process.exit(require('./package.json').scripts?.test ? 0 : 1)" 2>/dev/null; then
|
|
27
|
+
echo " npm: test"
|
|
28
|
+
npm test
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
echo "Pre-release checks passed."
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# cli
|
|
2
|
+
|
|
3
|
+
## Release workflow
|
|
4
|
+
|
|
5
|
+
This project uses [rlsbl](https://github.com/smm-h/rlsbl) for release orchestration.
|
|
6
|
+
|
|
7
|
+
- Update CHANGELOG.md with a `## X.Y.Z` entry describing changes
|
|
8
|
+
- Run `rlsbl release [patch|minor|major]` to bump version and create a GitHub Release
|
|
9
|
+
- CI handles publishing automatically via the publish workflow
|
|
10
|
+
- Never publish manually — always use `rlsbl release`
|
|
11
|
+
- Configure Trusted Publishing on pypi.org for automated PyPI releases
|
|
12
|
+
- Use `rlsbl release --dry-run` to preview a release without making changes
|
|
13
|
+
|
|
14
|
+
## Conventions
|
|
15
|
+
|
|
16
|
+
- No tokens or secrets in command-line arguments (use env vars or config files)
|
|
17
|
+
- All file writes to shared state should be atomic (write to tmp, then rename)
|
|
18
|
+
- External calls (APIs, CLI tools) must have timeouts and graceful fallbacks
|
|
19
|
+
- Use `npm link` (npm) or `uv pip install -e .` (Python) for local development
|
|
20
|
+
- CI runs smoke tests on every push; manual testing for UI/UX changes
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 smm-h
|
|
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.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
".github/workflows/ci.yml": "d71e558f1bfd93b43a783054577698da34c3e45455c6039686e06a7449b2bf1b",
|
|
3
|
+
".github/workflows/publish.yml": "5f4ac1bf0bdd4c70041e3d2502efaae28d29eca7575e8ff12fdaad0d3af5b17a",
|
|
4
|
+
"CHANGELOG.md": "7a6ec46141007c18090a0ff325e2dd68cd51588c4e0b5af7b580365e5eaaca85",
|
|
5
|
+
".gitignore": "c89d76c6c43e7f0897b3472a15b7c0103e0be5b3a04c2b44e585fa48bdcda2bb",
|
|
6
|
+
"LICENSE": "e958892abc1dd5d991fca6d528e695d335f25773d373d2e0be5f4a9507d8f8d5",
|
|
7
|
+
"CLAUDE.md": "7294b791570185dc31020dadb092b5b79681ba8ee932fe9a3974e27eaae56c4f",
|
|
8
|
+
".rlsbl/hooks/pre-release.sh": "9bae4134d0a4c302287766f9a4a2923b6c5706d2cc7078f553fec843b8cd5a06",
|
|
9
|
+
".rlsbl/hooks/post-release.sh": "b455f8511e0b2655509ecf5dcb0ab7da5bb7c961f47910ff8e00cab5bd51f833",
|
|
10
|
+
".claude/settings.json": "78922a784ee78e9e50587e93628cd3b9d4dfbe49087adc4514e6781cea38cbb9"
|
|
11
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Post-release hook. Runs after a successful release (non-fatal).
|
|
3
|
+
# Environment: RLSBL_VERSION is set to the released version.
|
|
4
|
+
# Customize this for your project (e.g., local install, deploy, notify).
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
echo "Post-release: v$RLSBL_VERSION"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Pre-release validation hook.
|
|
3
|
+
# Runs before rlsbl creates a release. Exit non-zero to abort.
|
|
4
|
+
# Detects project type and runs appropriate checks automatically.
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
echo "Running pre-release checks..."
|
|
9
|
+
|
|
10
|
+
if [ -f go.mod ]; then
|
|
11
|
+
echo " Go: vet + build + test"
|
|
12
|
+
go vet ./...
|
|
13
|
+
go build ./...
|
|
14
|
+
go test ./... -race -short -count=1
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
if [ -f pyproject.toml ]; then
|
|
18
|
+
echo " Python: pytest"
|
|
19
|
+
if command -v uv &>/dev/null; then
|
|
20
|
+
uv run pytest
|
|
21
|
+
elif command -v pytest &>/dev/null; then
|
|
22
|
+
pytest
|
|
23
|
+
fi
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
if [ -f package.json ] && node -e "process.exit(require('./package.json').scripts?.test ? 0 : 1)" 2>/dev/null; then
|
|
27
|
+
echo " npm: test"
|
|
28
|
+
npm test
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
echo "Pre-release checks passed."
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.21.2
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
- Decorator-based command registration with `@app.command` and `@strictcli.flag`/`@strictcli.arg`
|
|
6
|
+
- npm thin wrapper: `npm install strictcli` installs the Python package via uv/pip
|
|
7
|
+
- Two-level command nesting via `app.group()` with `@group.command`
|
|
8
|
+
- First-class environment variable support with prefix enforcement and `prefixed=False` opt-out
|
|
9
|
+
- Tags: reusable flag bundles applied to commands
|
|
10
|
+
- Plain-text help generation at app, group, and command levels
|
|
11
|
+
- Automatic `--help`/`-h` and `--version`/`-v` handling
|
|
12
|
+
- `--no-X` negation for boolean flags with opt-out
|
|
13
|
+
- Short flag aliases (`-v`, `-t`, etc.)
|
|
14
|
+
- Mandatory help text on all elements (commands, groups, flags, args, env vars)
|
|
15
|
+
- Handler signature validation at registration time
|
|
16
|
+
- `app.run()` full lifecycle and `app.test(argv)` for testing
|
|
17
|
+
- `--` separator to stop flag parsing
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# strictcli
|
|
2
|
+
|
|
3
|
+
## Release workflow
|
|
4
|
+
|
|
5
|
+
This project uses [rlsbl](https://github.com/smm-h/rlsbl) for release orchestration.
|
|
6
|
+
|
|
7
|
+
- Update CHANGELOG.md with a `## X.Y.Z` entry describing changes
|
|
8
|
+
- Run `rlsbl release [patch|minor|major]` to bump version and create a GitHub Release
|
|
9
|
+
- CI handles publishing automatically via the publish workflow
|
|
10
|
+
- Never publish manually — always use `rlsbl release`
|
|
11
|
+
- Configure Trusted Publishing on pypi.org for automated PyPI releases
|
|
12
|
+
- Use `rlsbl release --dry-run` to preview a release without making changes
|
|
13
|
+
|
|
14
|
+
## Conventions
|
|
15
|
+
|
|
16
|
+
- No tokens or secrets in command-line arguments (use env vars or config files)
|
|
17
|
+
- All file writes to shared state should be atomic (write to tmp, then rename)
|
|
18
|
+
- External calls (APIs, CLI tools) must have timeouts and graceful fallbacks
|
|
19
|
+
- Use `npm link` (npm) or `uv pip install -e .` (Python) for local development
|
|
20
|
+
- CI runs smoke tests on every push; manual testing for UI/UX changes
|
strictcli-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 smm-h
|
|
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.
|
strictcli-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: strictcli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A strict, zero-dependency CLI framework for Python
|
|
5
|
+
Project-URL: Homepage, https://github.com/smm-h/strictcli
|
|
6
|
+
Project-URL: Repository, https://github.com/smm-h/strictcli
|
|
7
|
+
Author-email: "S. M. Hosseini" <m.hosseini@veliu.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: argparse,cli,command-line,framework,rlsbl,strictcli
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
16
|
+
Requires-Python: >=3.11
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# strictcli
|
|
20
|
+
|
|
21
|
+
A strict, zero-dependency CLI framework for Python.
|
|
22
|
+
|
|
23
|
+
strictcli makes you declare everything -- every command, flag, argument, and environment variable must have help text or the framework errors at registration time. Types are `str` and `bool` only; there is no magic type inference. Environment variables are first-class, with prefix enforcement to keep your config namespace clean.
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
uv add strictcli
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Or:
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
pip install strictcli
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Requires Python 3.11+.
|
|
38
|
+
|
|
39
|
+
## Quickstart
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
# greet.py
|
|
43
|
+
import strictcli
|
|
44
|
+
|
|
45
|
+
app = strictcli.App(name="greet", version="0.1.0", help="a friendly greeter")
|
|
46
|
+
|
|
47
|
+
@app.command("hello", help="say hello", args=[strictcli.Arg(name="name", help="who to greet")])
|
|
48
|
+
@strictcli.flag("loud", short="l", type=bool, help="shout the greeting")
|
|
49
|
+
def hello(name, loud):
|
|
50
|
+
msg = f"Hello, {name}!"
|
|
51
|
+
if loud:
|
|
52
|
+
msg = msg.upper()
|
|
53
|
+
print(msg)
|
|
54
|
+
|
|
55
|
+
app.run()
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
$ python greet.py hello World
|
|
60
|
+
Hello, World!
|
|
61
|
+
|
|
62
|
+
$ python greet.py hello --loud World
|
|
63
|
+
HELLO, WORLD!
|
|
64
|
+
|
|
65
|
+
$ python greet.py hello --help
|
|
66
|
+
greet hello -- say hello
|
|
67
|
+
|
|
68
|
+
Arguments:
|
|
69
|
+
name who to greet
|
|
70
|
+
|
|
71
|
+
Flags:
|
|
72
|
+
--loud, --no-loud, -l shout the greeting [default: false]
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Commands and Groups
|
|
76
|
+
|
|
77
|
+
Register top-level commands with `@app.command`:
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
app = strictcli.App(name="myapp", version="1.0.0", help="manage deployments")
|
|
81
|
+
|
|
82
|
+
@app.command("status", help="show current status")
|
|
83
|
+
def status():
|
|
84
|
+
print("all systems go")
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Create groups for two-level nesting with `app.group`:
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
db = app.group("db", help="manage databases")
|
|
91
|
+
|
|
92
|
+
@db.command("migrate", help="run database migrations")
|
|
93
|
+
@strictcli.flag("dry-run", type=bool, help="preview without applying")
|
|
94
|
+
def migrate(dry_run):
|
|
95
|
+
if dry_run:
|
|
96
|
+
print("would run migrations")
|
|
97
|
+
else:
|
|
98
|
+
print("running migrations")
|
|
99
|
+
|
|
100
|
+
@db.command("seed", help="populate with sample data")
|
|
101
|
+
@strictcli.flag("count", type=str, help="number of records", default="100")
|
|
102
|
+
def seed(count):
|
|
103
|
+
print(f"seeding {count} records")
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
$ myapp db migrate --dry-run
|
|
108
|
+
would run migrations
|
|
109
|
+
|
|
110
|
+
$ myapp db seed --count 500
|
|
111
|
+
seeding 500 records
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Flags
|
|
115
|
+
|
|
116
|
+
Declare flags with the `@strictcli.flag` decorator. Every flag must have `help` text.
|
|
117
|
+
|
|
118
|
+
### String flags
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
@app.command("build", help="build the project")
|
|
122
|
+
@strictcli.flag("output", short="o", type=str, help="output directory", default="dist")
|
|
123
|
+
def build(output):
|
|
124
|
+
print(f"building to {output}")
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
String flags accept values via `--output dist` or `--output=dist`. A string flag without a `default` is required.
|
|
128
|
+
|
|
129
|
+
### Bool flags
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
@app.command("deploy", help="deploy the app")
|
|
133
|
+
@strictcli.flag("force", short="f", type=bool, help="skip confirmation")
|
|
134
|
+
def deploy(force):
|
|
135
|
+
if force:
|
|
136
|
+
print("deploying without confirmation")
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Bool flags default to `False`. Pass `--force` to set `True`, or `--no-force` to explicitly set `False`. The `--no-` negation form is available by default for all bool flags; disable it with `negatable=False`.
|
|
140
|
+
|
|
141
|
+
### Short aliases
|
|
142
|
+
|
|
143
|
+
Any flag can have a one-character short alias:
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
@strictcli.flag("verbose", short="v", type=bool, help="verbose output")
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
This allows both `--verbose` and `-v`.
|
|
150
|
+
|
|
151
|
+
### Required vs optional
|
|
152
|
+
|
|
153
|
+
- `str` flags with no `default` are required -- the parser errors if missing.
|
|
154
|
+
- `str` flags with a `default` are optional.
|
|
155
|
+
- `bool` flags always default to `False`.
|
|
156
|
+
|
|
157
|
+
## Arguments
|
|
158
|
+
|
|
159
|
+
Positional arguments are declared with `strictcli.Arg`. There are two equivalent forms.
|
|
160
|
+
|
|
161
|
+
Using the `args=` parameter:
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
@app.command("copy", help="copy files", args=[
|
|
165
|
+
strictcli.Arg(name="src", help="source path"),
|
|
166
|
+
strictcli.Arg(name="dst", help="destination path"),
|
|
167
|
+
])
|
|
168
|
+
def copy(src, dst):
|
|
169
|
+
print(f"copying {src} to {dst}")
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Using the `@strictcli.arg` decorator:
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
@app.command("show", help="show a file")
|
|
176
|
+
@strictcli.arg("path", help="file to show")
|
|
177
|
+
def show(path):
|
|
178
|
+
print(f"showing {path}")
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Arguments are matched in order. Use `required=False` for optional arguments. The `--` separator stops flag parsing, so everything after it becomes positional:
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
$ myapp cmd -- --not-a-flag
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Environment Variables
|
|
188
|
+
|
|
189
|
+
Flags can be backed by environment variables with the `env` parameter:
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
app = strictcli.App(name="myapp", version="1.0.0", help="my app", env_prefix="MYAPP")
|
|
193
|
+
|
|
194
|
+
@app.command("deploy", help="deploy the app")
|
|
195
|
+
@strictcli.flag("region", type=str, help="cloud region", env="MYAPP_REGION", default="us-east-1")
|
|
196
|
+
def deploy(region):
|
|
197
|
+
print(f"deploying to {region}")
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Prefix enforcement
|
|
201
|
+
|
|
202
|
+
When `env_prefix` is set on the App, all env vars must start with that prefix. This is validated at registration time:
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
# This raises ValueError: env var 'REGION' must start with 'MYAPP_'
|
|
206
|
+
@strictcli.flag("region", type=str, help="region", env="REGION", default="x")
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### External env vars
|
|
210
|
+
|
|
211
|
+
Use `prefixed=False` for env vars outside your app's namespace:
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
@strictcli.flag("token", type=str, help="auth token", env="GITHUB_TOKEN", prefixed=False, default="")
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Priority
|
|
218
|
+
|
|
219
|
+
Values resolve in this order: CLI argument > environment variable > default. If none of the three provides a value, the parser errors.
|
|
220
|
+
|
|
221
|
+
### Bool env vars
|
|
222
|
+
|
|
223
|
+
Bool flags from env vars accept `1`, `true`, `yes` (case-insensitive) for `True` and `0`, `false`, `no` for `False`. Any other value is an error.
|
|
224
|
+
|
|
225
|
+
## Tags
|
|
226
|
+
|
|
227
|
+
Tags are reusable bundles of flags that can be applied to multiple commands:
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
auth_tag = strictcli.Tag(
|
|
231
|
+
name="auth",
|
|
232
|
+
flags=[
|
|
233
|
+
strictcli.Flag(name="token", type=str, help="auth token", env="MYAPP_TOKEN", default=""),
|
|
234
|
+
strictcli.Flag(name="insecure", type=bool, help="skip TLS verification"),
|
|
235
|
+
],
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
@app.command("deploy", help="deploy the app", tags=[auth_tag])
|
|
239
|
+
def deploy(token, insecure):
|
|
240
|
+
print(f"token={'set' if token else 'unset'}, insecure={insecure}")
|
|
241
|
+
|
|
242
|
+
@app.command("status", help="check status", tags=[auth_tag])
|
|
243
|
+
def status(token, insecure):
|
|
244
|
+
print(f"checking status")
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Both commands now have `--token` and `--insecure` flags. Tag flags appear in help output and are parsed like any other flag.
|
|
248
|
+
|
|
249
|
+
## Help Output
|
|
250
|
+
|
|
251
|
+
Help is auto-generated at three levels. Pass `--help` or `-h` at any level, or invoke the app with no arguments.
|
|
252
|
+
|
|
253
|
+
**App level** (`myapp --help`):
|
|
254
|
+
|
|
255
|
+
```
|
|
256
|
+
myapp v1.0.0 -- manage deployments
|
|
257
|
+
|
|
258
|
+
Commands:
|
|
259
|
+
deploy deploy the application
|
|
260
|
+
|
|
261
|
+
Groups:
|
|
262
|
+
db manage databases
|
|
263
|
+
|
|
264
|
+
Use 'myapp <command> --help' for more information.
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
**Group level** (`myapp db --help`):
|
|
268
|
+
|
|
269
|
+
```
|
|
270
|
+
myapp db -- manage databases
|
|
271
|
+
|
|
272
|
+
Commands:
|
|
273
|
+
migrate run database migrations
|
|
274
|
+
seed populate with sample data
|
|
275
|
+
|
|
276
|
+
Use 'myapp db <command> --help' for more information.
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
**Command level** (`myapp deploy --help`):
|
|
280
|
+
|
|
281
|
+
```
|
|
282
|
+
myapp deploy -- deploy the application
|
|
283
|
+
|
|
284
|
+
Arguments:
|
|
285
|
+
target deployment target
|
|
286
|
+
|
|
287
|
+
Flags:
|
|
288
|
+
--region, -r <str> cloud region [env: MYAPP_REGION] [default: us-east-1]
|
|
289
|
+
--force, --no-force, -f skip confirmation prompt [default: false]
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Version: `--version` or `-v` prints `myapp 1.0.0`.
|
|
293
|
+
|
|
294
|
+
## Testing
|
|
295
|
+
|
|
296
|
+
`app.test(argv)` runs the CLI in-process and returns a `Result` with captured output:
|
|
297
|
+
|
|
298
|
+
```python
|
|
299
|
+
result = app.test(["deploy", "--force", "production"])
|
|
300
|
+
|
|
301
|
+
assert result.exit_code == 0
|
|
302
|
+
assert "deploying" in result.stdout
|
|
303
|
+
assert result.stderr == ""
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
The `Result` dataclass has three fields: `stdout`, `stderr`, and `exit_code`.
|
|
307
|
+
|
|
308
|
+
## Strict by Design
|
|
309
|
+
|
|
310
|
+
strictcli is opinionated about strictness:
|
|
311
|
+
|
|
312
|
+
- **Help is mandatory.** Every command, flag, and argument must have help text. Missing help raises `ValueError` at registration time, not at runtime.
|
|
313
|
+
- **Only str and bool.** No int, float, or list types. Parse them yourself in the handler -- it is one line of code and makes the conversion visible.
|
|
314
|
+
- **Handler signatures are validated.** Every declared flag and arg must have a matching parameter in the handler function, and vice versa. Extra or missing parameters raise `ValueError`.
|
|
315
|
+
- **Env var prefixes are enforced.** If you set `env_prefix="MYAPP"`, every env-backed flag must use that prefix (or explicitly opt out with `prefixed=False`).
|
|
316
|
+
- **No hidden defaults.** Required flags fail loudly. Bool flags default to `False`. Everything else must be declared.
|
|
317
|
+
|
|
318
|
+
If you want automatic type coercion, subcommand hierarchies deeper than two levels, or rich terminal formatting, consider [argparse](https://docs.python.org/3/library/argparse.html), [click](https://click.palletsprojects.com/), or [typer](https://typer.tiangolo.com/).
|