py-sparcli 0.3.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.
- py_sparcli-0.3.0/.editorconfig +25 -0
- py_sparcli-0.3.0/.gitattributes +29 -0
- py_sparcli-0.3.0/.gitignore +222 -0
- py_sparcli-0.3.0/.pre-commit-config.yaml +32 -0
- py_sparcli-0.3.0/CHANGELOG.md +57 -0
- py_sparcli-0.3.0/CLAUDE.md +107 -0
- py_sparcli-0.3.0/CONTRIBUTING.md +52 -0
- py_sparcli-0.3.0/LICENSE +21 -0
- py_sparcli-0.3.0/PKG-INFO +257 -0
- py_sparcli-0.3.0/README.md +231 -0
- py_sparcli-0.3.0/TODO.md +7 -0
- py_sparcli-0.3.0/docs/DEVELOPMENT.md +121 -0
- py_sparcli-0.3.0/examples/output_dynamic.py +101 -0
- py_sparcli-0.3.0/examples/output_gallery.py +264 -0
- py_sparcli-0.3.0/examples/output_readme.py +223 -0
- py_sparcli-0.3.0/examples/prompt_readme.py +138 -0
- py_sparcli-0.3.0/examples/prompts.py +161 -0
- py_sparcli-0.3.0/images/screenshot-1.png +0 -0
- py_sparcli-0.3.0/images/screenshot-2.png +0 -0
- py_sparcli-0.3.0/pyproject.toml +76 -0
- py_sparcli-0.3.0/sparcli/__init__.py +184 -0
- py_sparcli-0.3.0/sparcli/core/__init__.py +92 -0
- py_sparcli-0.3.0/sparcli/core/border.py +160 -0
- py_sparcli-0.3.0/sparcli/core/color.py +306 -0
- py_sparcli-0.3.0/sparcli/core/cursor.py +59 -0
- py_sparcli-0.3.0/sparcli/core/geometry.py +141 -0
- py_sparcli-0.3.0/sparcli/core/markup.py +215 -0
- py_sparcli-0.3.0/sparcli/core/render.py +245 -0
- py_sparcli-0.3.0/sparcli/core/style.py +128 -0
- py_sparcli-0.3.0/sparcli/core/terminal.py +127 -0
- py_sparcli-0.3.0/sparcli/core/text.py +143 -0
- py_sparcli-0.3.0/sparcli/core/theme.py +117 -0
- py_sparcli-0.3.0/sparcli/core/width.py +244 -0
- py_sparcli-0.3.0/sparcli/errors.py +38 -0
- py_sparcli-0.3.0/sparcli/input/__init__.py +42 -0
- py_sparcli-0.3.0/sparcli/input/calc.py +214 -0
- py_sparcli-0.3.0/sparcli/input/confirm.py +188 -0
- py_sparcli-0.3.0/sparcli/input/datepicker.py +298 -0
- py_sparcli-0.3.0/sparcli/input/editor.py +189 -0
- py_sparcli-0.3.0/sparcli/input/event.py +551 -0
- py_sparcli-0.3.0/sparcli/input/field.py +90 -0
- py_sparcli-0.3.0/sparcli/input/fuzzy.py +401 -0
- py_sparcli-0.3.0/sparcli/input/guard.py +103 -0
- py_sparcli-0.3.0/sparcli/input/history.py +160 -0
- py_sparcli-0.3.0/sparcli/input/line_edit.py +229 -0
- py_sparcli-0.3.0/sparcli/input/number.py +232 -0
- py_sparcli-0.3.0/sparcli/input/outcome.py +107 -0
- py_sparcli-0.3.0/sparcli/input/password.py +233 -0
- py_sparcli-0.3.0/sparcli/input/prompt.py +134 -0
- py_sparcli-0.3.0/sparcli/input/select.py +336 -0
- py_sparcli-0.3.0/sparcli/input/shortcut.py +123 -0
- py_sparcli-0.3.0/sparcli/input/text.py +564 -0
- py_sparcli-0.3.0/sparcli/input/textarea.py +242 -0
- py_sparcli-0.3.0/sparcli/input/validate.py +67 -0
- py_sparcli-0.3.0/sparcli/output/__init__.py +56 -0
- py_sparcli-0.3.0/sparcli/output/alert.py +107 -0
- py_sparcli-0.3.0/sparcli/output/badge.py +82 -0
- py_sparcli-0.3.0/sparcli/output/box.py +196 -0
- py_sparcli-0.3.0/sparcli/output/columns.py +157 -0
- py_sparcli-0.3.0/sparcli/output/compose.py +111 -0
- py_sparcli-0.3.0/sparcli/output/diff.py +229 -0
- py_sparcli-0.3.0/sparcli/output/kv.py +175 -0
- py_sparcli-0.3.0/sparcli/output/layout.py +77 -0
- py_sparcli-0.3.0/sparcli/output/list.py +274 -0
- py_sparcli-0.3.0/sparcli/output/live.py +129 -0
- py_sparcli-0.3.0/sparcli/output/multiprogress.py +73 -0
- py_sparcli-0.3.0/sparcli/output/pager.py +83 -0
- py_sparcli-0.3.0/sparcli/output/panel.py +125 -0
- py_sparcli-0.3.0/sparcli/output/progress.py +233 -0
- py_sparcli-0.3.0/sparcli/output/rule.py +134 -0
- py_sparcli-0.3.0/sparcli/output/spinner.py +138 -0
- py_sparcli-0.3.0/sparcli/output/table/__init__.py +17 -0
- py_sparcli-0.3.0/sparcli/output/table/plan.py +198 -0
- py_sparcli-0.3.0/sparcli/output/table/render.py +302 -0
- py_sparcli-0.3.0/sparcli/output/table/table.py +351 -0
- py_sparcli-0.3.0/sparcli/output/tree.py +157 -0
- py_sparcli-0.3.0/sparcli/py.typed +0 -0
- py_sparcli-0.3.0/tests/test_confirm.py +94 -0
- py_sparcli-0.3.0/tests/test_core.py +248 -0
- py_sparcli-0.3.0/tests/test_cursor.py +107 -0
- py_sparcli-0.3.0/tests/test_datepicker.py +178 -0
- py_sparcli-0.3.0/tests/test_fuzzy.py +152 -0
- py_sparcli-0.3.0/tests/test_input_infra.py +372 -0
- py_sparcli-0.3.0/tests/test_number.py +186 -0
- py_sparcli-0.3.0/tests/test_output_basic.py +222 -0
- py_sparcli-0.3.0/tests/test_output_dynamic.py +269 -0
- py_sparcli-0.3.0/tests/test_output_structured.py +202 -0
- py_sparcli-0.3.0/tests/test_password.py +109 -0
- py_sparcli-0.3.0/tests/test_public_api.py +23 -0
- py_sparcli-0.3.0/tests/test_select.py +154 -0
- py_sparcli-0.3.0/tests/test_table.py +295 -0
- py_sparcli-0.3.0/tests/test_text_input.py +181 -0
- py_sparcli-0.3.0/tests/test_textarea.py +110 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# EditorConfig: https://editorconfig.org
|
|
2
|
+
root = true
|
|
3
|
+
|
|
4
|
+
[*]
|
|
5
|
+
charset = utf-8
|
|
6
|
+
end_of_line = lf
|
|
7
|
+
insert_final_newline = true
|
|
8
|
+
trim_trailing_whitespace = true
|
|
9
|
+
indent_style = space
|
|
10
|
+
indent_size = 4
|
|
11
|
+
max_line_length = 80
|
|
12
|
+
|
|
13
|
+
[*.py]
|
|
14
|
+
indent_size = 4
|
|
15
|
+
|
|
16
|
+
[*.{md,markdown}]
|
|
17
|
+
indent_size = 2
|
|
18
|
+
# Keep trailing spaces off; hard line breaks use a backslash, not two spaces.
|
|
19
|
+
trim_trailing_whitespace = true
|
|
20
|
+
|
|
21
|
+
[*.{toml,yaml,yml,json}]
|
|
22
|
+
indent_size = 2
|
|
23
|
+
|
|
24
|
+
[Makefile]
|
|
25
|
+
indent_style = tab
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Normalize line endings: LF in the repository, auto on checkout.
|
|
2
|
+
* text=auto eol=lf
|
|
3
|
+
|
|
4
|
+
# Explicitly text.
|
|
5
|
+
*.py text
|
|
6
|
+
*.pyi text
|
|
7
|
+
*.md text
|
|
8
|
+
*.toml text
|
|
9
|
+
*.cfg text
|
|
10
|
+
*.txt text
|
|
11
|
+
*.yml text
|
|
12
|
+
*.yaml text
|
|
13
|
+
|
|
14
|
+
# Binary assets (add screenshots/GIFs here as they are introduced).
|
|
15
|
+
*.png binary
|
|
16
|
+
*.gif binary
|
|
17
|
+
*.jpg binary
|
|
18
|
+
*.svg text
|
|
19
|
+
|
|
20
|
+
# Keep source-distribution / `git archive` tarballs lean: development-only
|
|
21
|
+
# files are excluded from exports.
|
|
22
|
+
.editorconfig export-ignore
|
|
23
|
+
.gitattributes export-ignore
|
|
24
|
+
.gitignore export-ignore
|
|
25
|
+
.github/ export-ignore
|
|
26
|
+
CLAUDE.md export-ignore
|
|
27
|
+
TODO.md export-ignore
|
|
28
|
+
tests/ export-ignore
|
|
29
|
+
examples/ export-ignore
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# ============================================================
|
|
2
|
+
# Python
|
|
3
|
+
# ============================================================
|
|
4
|
+
|
|
5
|
+
# macOS
|
|
6
|
+
.DS_Store
|
|
7
|
+
**/.DS_Store
|
|
8
|
+
._*
|
|
9
|
+
.AppleDouble
|
|
10
|
+
.LSOverride
|
|
11
|
+
.Spotlight-V100
|
|
12
|
+
.Trashes
|
|
13
|
+
.fseventsd
|
|
14
|
+
.DocumentRevisions-V100
|
|
15
|
+
Icon
|
|
16
|
+
|
|
17
|
+
# Windows
|
|
18
|
+
Thumbs.db
|
|
19
|
+
ehthumbs.db
|
|
20
|
+
Desktop.ini
|
|
21
|
+
$RECYCLE.BIN/
|
|
22
|
+
|
|
23
|
+
# Linux / editor backups
|
|
24
|
+
*~
|
|
25
|
+
.netrwhist
|
|
26
|
+
|
|
27
|
+
# Editors / IDEs
|
|
28
|
+
.vscode
|
|
29
|
+
.idea
|
|
30
|
+
*.code-workspace
|
|
31
|
+
*.sw?
|
|
32
|
+
.history
|
|
33
|
+
|
|
34
|
+
# Environment files
|
|
35
|
+
.env
|
|
36
|
+
# ...but keep committable example/template files:
|
|
37
|
+
!.env.example
|
|
38
|
+
!.env.template
|
|
39
|
+
|
|
40
|
+
# Logs
|
|
41
|
+
*.log
|
|
42
|
+
|
|
43
|
+
# Claude Code
|
|
44
|
+
.claude
|
|
45
|
+
|
|
46
|
+
# Byte-compiled / optimized / DLL files
|
|
47
|
+
__pycache__/
|
|
48
|
+
*.py[codz]
|
|
49
|
+
*$py.class
|
|
50
|
+
|
|
51
|
+
# C extensions
|
|
52
|
+
*.so
|
|
53
|
+
|
|
54
|
+
# Distribution / packaging
|
|
55
|
+
.Python
|
|
56
|
+
build/
|
|
57
|
+
develop-eggs/
|
|
58
|
+
dist/
|
|
59
|
+
downloads/
|
|
60
|
+
eggs/
|
|
61
|
+
.eggs/
|
|
62
|
+
lib/
|
|
63
|
+
lib64/
|
|
64
|
+
parts/
|
|
65
|
+
sdist/
|
|
66
|
+
var/
|
|
67
|
+
wheels/
|
|
68
|
+
share/python-wheels/
|
|
69
|
+
*.egg-info/
|
|
70
|
+
src/*.egg-info/
|
|
71
|
+
.installed.cfg
|
|
72
|
+
*.egg
|
|
73
|
+
MANIFEST
|
|
74
|
+
|
|
75
|
+
# PyInstaller
|
|
76
|
+
*.manifest
|
|
77
|
+
*.spec
|
|
78
|
+
|
|
79
|
+
# Installer logs
|
|
80
|
+
pip-log.txt
|
|
81
|
+
pip-delete-this-directory.txt
|
|
82
|
+
|
|
83
|
+
# Unit test / coverage reports
|
|
84
|
+
htmlcov/
|
|
85
|
+
.tox/
|
|
86
|
+
.nox/
|
|
87
|
+
.coverage
|
|
88
|
+
.coverage.*
|
|
89
|
+
.cache
|
|
90
|
+
.benchmarks/
|
|
91
|
+
nosetests.xml
|
|
92
|
+
coverage.xml
|
|
93
|
+
*.cover
|
|
94
|
+
*.lcov
|
|
95
|
+
.hypothesis/
|
|
96
|
+
.pytest_cache/
|
|
97
|
+
cover/
|
|
98
|
+
|
|
99
|
+
# Translations
|
|
100
|
+
*.mo
|
|
101
|
+
*.pot
|
|
102
|
+
|
|
103
|
+
# Django / Flask / Scrapy
|
|
104
|
+
local_settings.py
|
|
105
|
+
db.sqlite3
|
|
106
|
+
db.sqlite3-journal
|
|
107
|
+
instance/
|
|
108
|
+
.webassets-cache
|
|
109
|
+
.scrapy
|
|
110
|
+
|
|
111
|
+
# Sphinx / mkdocs documentation
|
|
112
|
+
docs/_build/
|
|
113
|
+
docs/build/
|
|
114
|
+
/site
|
|
115
|
+
|
|
116
|
+
# PyBuilder
|
|
117
|
+
.pybuilder/
|
|
118
|
+
target/
|
|
119
|
+
|
|
120
|
+
# Jupyter Notebook
|
|
121
|
+
.ipynb_checkpoints
|
|
122
|
+
|
|
123
|
+
# IPython
|
|
124
|
+
profile_default/
|
|
125
|
+
ipython_config.py
|
|
126
|
+
|
|
127
|
+
# pyenv
|
|
128
|
+
.python-version
|
|
129
|
+
|
|
130
|
+
# pdm
|
|
131
|
+
.pdm.toml
|
|
132
|
+
.pdm-python
|
|
133
|
+
.pdm-build/
|
|
134
|
+
|
|
135
|
+
# PEP 582
|
|
136
|
+
__pypackages__/
|
|
137
|
+
|
|
138
|
+
# Celery / message brokers
|
|
139
|
+
celerybeat-schedule*
|
|
140
|
+
celerybeat.pid
|
|
141
|
+
*.rdb
|
|
142
|
+
*.aof
|
|
143
|
+
*.pid
|
|
144
|
+
mnesia/
|
|
145
|
+
rabbitmq/
|
|
146
|
+
rabbitmq-data/
|
|
147
|
+
activemq-data/
|
|
148
|
+
|
|
149
|
+
# SageMath
|
|
150
|
+
*.sage.py
|
|
151
|
+
|
|
152
|
+
# pixi
|
|
153
|
+
.pixi/*
|
|
154
|
+
!.pixi/config.toml
|
|
155
|
+
|
|
156
|
+
# Environments
|
|
157
|
+
.envrc
|
|
158
|
+
.venv
|
|
159
|
+
.venv/
|
|
160
|
+
# .venv may be a symlink -> .venv.nosync (iCloud-excluded real dir)
|
|
161
|
+
.venv.nosync/
|
|
162
|
+
env/
|
|
163
|
+
venv/
|
|
164
|
+
ENV/
|
|
165
|
+
env.bak/
|
|
166
|
+
venv.bak/
|
|
167
|
+
|
|
168
|
+
# Spyder / Rope project settings
|
|
169
|
+
.spyderproject
|
|
170
|
+
.spyproject
|
|
171
|
+
.ropeproject
|
|
172
|
+
|
|
173
|
+
# mypy
|
|
174
|
+
.mypy_cache*
|
|
175
|
+
.dmypy.json
|
|
176
|
+
dmypy.json
|
|
177
|
+
|
|
178
|
+
# Pyre / pytype
|
|
179
|
+
.pyre/
|
|
180
|
+
.pytype/
|
|
181
|
+
|
|
182
|
+
# Cython debug symbols
|
|
183
|
+
cython_debug/
|
|
184
|
+
|
|
185
|
+
# Ruff
|
|
186
|
+
.ruff_cache/
|
|
187
|
+
|
|
188
|
+
# isort
|
|
189
|
+
*.isorted
|
|
190
|
+
|
|
191
|
+
# PyPI configuration file
|
|
192
|
+
.pypirc
|
|
193
|
+
|
|
194
|
+
# Cursor
|
|
195
|
+
.cursorignore
|
|
196
|
+
.cursorindexingignore
|
|
197
|
+
|
|
198
|
+
# Marimo
|
|
199
|
+
marimo/_static/
|
|
200
|
+
marimo/_lsp/
|
|
201
|
+
__marimo__/
|
|
202
|
+
|
|
203
|
+
# Streamlit
|
|
204
|
+
.streamlit/secrets.toml
|
|
205
|
+
|
|
206
|
+
# Abstra
|
|
207
|
+
.abstra/
|
|
208
|
+
|
|
209
|
+
# VS Code "Code Runner" extension
|
|
210
|
+
tempCodeRunnerFile.py
|
|
211
|
+
|
|
212
|
+
# Temp dirs
|
|
213
|
+
temp/
|
|
214
|
+
_temp/
|
|
215
|
+
tmp/
|
|
216
|
+
temp-*/
|
|
217
|
+
tmp-*/
|
|
218
|
+
|
|
219
|
+
# ------------------------------------------------------------
|
|
220
|
+
# Project-specific
|
|
221
|
+
# ------------------------------------------------------------
|
|
222
|
+
.pixi
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Pre-commit hooks: https://pre-commit.com
|
|
2
|
+
# Install once with `pre-commit install`; run on demand with
|
|
3
|
+
# `pre-commit run --all-files`. Optional but recommended for contributors,
|
|
4
|
+
# since the project has no CI.
|
|
5
|
+
repos:
|
|
6
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
7
|
+
rev: v5.0.0
|
|
8
|
+
hooks:
|
|
9
|
+
- id: check-toml
|
|
10
|
+
- id: check-yaml
|
|
11
|
+
- id: check-merge-conflict
|
|
12
|
+
- id: end-of-file-fixer
|
|
13
|
+
- id: trailing-whitespace
|
|
14
|
+
args: [--markdown-linebreak-ext=md]
|
|
15
|
+
- id: mixed-line-ending
|
|
16
|
+
args: [--fix=lf]
|
|
17
|
+
|
|
18
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
19
|
+
rev: v0.15.21
|
|
20
|
+
hooks:
|
|
21
|
+
- id: ruff-check
|
|
22
|
+
args: [--fix]
|
|
23
|
+
- id: ruff-format
|
|
24
|
+
|
|
25
|
+
- repo: local
|
|
26
|
+
hooks:
|
|
27
|
+
- id: basedpyright
|
|
28
|
+
name: basedpyright (strict)
|
|
29
|
+
entry: basedpyright sparcli examples
|
|
30
|
+
language: system
|
|
31
|
+
types: [python]
|
|
32
|
+
pass_filenames: false
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.3.0] – 2026-07-11
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `Style.remove_modifier` clears one or more attributes from a layered style (the counterpart to `add_modifier`).
|
|
14
|
+
- `TextInput` and `PasswordInput` gained keyword-only constructor options mirroring their fluent setters, so they configure like every other widget.
|
|
15
|
+
- `OutcomeKind` is re-exported from the flat `sparcli` namespace.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- Hide the terminal hardware cursor during in-place redraws (spinner, progress, multi-progress, live) and interactive prompts, and restore it reliably on finish, on prompt exit and at interpreter exit.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- `DatePicker` navigation past the representable date range (before year 1 or after year 9999) no longer raises; it clamps to `date.min` / `date.max`.
|
|
24
|
+
- Inline markup no longer swallows a closed bracket that names no known style or attribute (such as `array[0]`); such text is emitted literally.
|
|
25
|
+
- `char_width` no longer raises on an empty or multi-character string.
|
|
26
|
+
- A negative `NumberInput.decimals` setting is clamped to zero instead of raising when the value is formatted.
|
|
27
|
+
- The raw-mode paths in `TerminalGuard` and the external-editor suspension now also catch `termios.error`, so a non-terminal stdin falls back to the documented no-op instead of raising (`termios.error` is not an `OSError`).
|
|
28
|
+
- `TextInput`'s Ctrl-G external editor now runs with raw mode suspended (cooked), matching `Textarea` and the Rust original; previously the editor launched while the terminal was still in raw mode.
|
|
29
|
+
- `KeyValue` rows are no longer padded with trailing spaces when there is no right margin; shorter rows stay ragged, matching the Rust original.
|
|
30
|
+
- Word wrapping splits on runs of whitespace (tabs, repeated spaces), matching Rust's `split_whitespace`, instead of on a single space only.
|
|
31
|
+
- History files are split into entries on newlines only (like Rust's `str::lines`), so an entry containing an exotic line separator is no longer split in two.
|
|
32
|
+
|
|
33
|
+
### Security
|
|
34
|
+
|
|
35
|
+
- Control characters (C0, DEL and C1, except tab) are stripped from span content and OSC-8 link URLs before they reach the terminal, so untrusted text can no longer inject escape sequences or terminate a hyperlink early.
|
|
36
|
+
- History files are written atomically (temp file plus rename), so a crash or a concurrent writer can no longer truncate the file.
|
|
37
|
+
|
|
38
|
+
### Documentation
|
|
39
|
+
|
|
40
|
+
- Align the minor version with the Rust `sparcli` port (both at 0.3.x); the two ports now track the same minor number.
|
|
41
|
+
- Add `CONTRIBUTING.md`, `SECURITY.md`, `CODE_OF_CONDUCT.md`, `TODO.md`, README badges and editor/VCS tooling configs (`.editorconfig`, `.gitattributes`); migrate packaging metadata to an SPDX license expression and expand the project URLs for PyPI.
|
|
42
|
+
|
|
43
|
+
### Notes
|
|
44
|
+
|
|
45
|
+
- `DatePicker`'s initial "today" uses the local date here, while the Rust original uses UTC (it stays dependency-free). This one-point divergence is intentional; near midnight the default day can differ by one between the two ports.
|
|
46
|
+
|
|
47
|
+
## [0.1.0] – 2026-07-10
|
|
48
|
+
|
|
49
|
+
### Added
|
|
50
|
+
|
|
51
|
+
- Initial release: idiomatic Python port of the Rust `sparcli` library.
|
|
52
|
+
- Styled output widgets: `Panel`, `Table`, `List`, `Rule`, `Alert`, `Badge`, `Columns`, `Tree`, `KeyValue`, `Diff`, `Spinner`, `ProgressBar`, `MultiProgress`, `Live`, `Pager`.
|
|
53
|
+
- Interactive input widgets: `TextInput`, `PasswordInput`, `NumberInput`, `Confirm`, `Select`, `FuzzySelect`, `DatePicker`, `Textarea`.
|
|
54
|
+
- Unified `Theme`, inline `markup`, width utilities, terminal capability detection, validators, shortcuts and command history.
|
|
55
|
+
- Dependency-free (standard library only).
|
|
56
|
+
|
|
57
|
+
[Unreleased]: https://github.com/cgroening/py-sparcli/compare/v0.3.0...HEAD [0.3.0]: https://github.com/cgroening/py-sparcli/compare/v0.1.0...v0.3.0 [0.1.0]: https://github.com/cgroening/py-sparcli/releases/tag/v0.1.0
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# CLAUDE.md – sparcli (Python)
|
|
2
|
+
|
|
3
|
+
Requirements for all future sessions in this project. On conflict, this file takes precedence over general conventions, but not over explicit user instructions. The global style guide (`~/.claude/CLAUDE.md` → §12 Python) applies additionally; this file refines and overrides it wherever it is more specific.
|
|
4
|
+
|
|
5
|
+
## What is this?
|
|
6
|
+
|
|
7
|
+
`sparcli` is a **lightweight, cross-platform** toolkit (macOS, Windows, Linux) for **styled CLI output** and **interactive single-input widgets** – an **idiomatic, dependency-free Python reimplementation** of the Rust library `sparcli`. Guiding idea: small, for little CLI tools. No async, no rich/textual/prompt_toolkit, minimal footprint.
|
|
8
|
+
|
|
9
|
+
- **Foundation:** a custom ANSI renderer built directly on the standard library (no framework).
|
|
10
|
+
- **API feel:** ratatui-familiar vocabulary (`Style`, `Color`, `Span`, `Line`, `Text`, `Modifier`); **both** a keyword constructor **and** fluent builder methods per widget.
|
|
11
|
+
- **Scope:** output is complete; input covers single widgets only (no form/app/args). Fuzzy is only an inline select.
|
|
12
|
+
- **Rust original is the SSOT for behavior:** `/Users/cgroening/Developer/Rust/ libs/sparcli`. For behavior questions, check there and preserve parity.
|
|
13
|
+
|
|
14
|
+
## Rust parity (mandatory)
|
|
15
|
+
|
|
16
|
+
- The Rust version lives at `/Users/cgroening/Developer/Rust/libs/sparcli`.
|
|
17
|
+
- **On every change, check whether the same change also has to be made in the Rust version** (behavior, API, docs, examples) to keep both in parity.
|
|
18
|
+
- When such a change might be needed, **ask the user** whether it should be carried over to the Rust version instead of deciding silently.
|
|
19
|
+
- **Version sync:** the Python and Rust ports keep their **minor version in sync** (at minimum the minor number matches). On a release, bump this port to the Rust port's minor. This is why the Python port jumped 0.1.0 -> 0.3.0 to meet Rust `sparcli` v0.3.
|
|
20
|
+
- **Known intentional divergence:** `DatePicker`'s initial "today" uses the **local** date here (`datetime.date.today`), while the Rust version uses **UTC** (it has no local-time API without a dependency, and both stay dependency-free). Near midnight the default day can differ by one between the two ports. Do not "fix" this into parity without user sign-off.
|
|
21
|
+
|
|
22
|
+
## Distribution & import
|
|
23
|
+
|
|
24
|
+
- PyPI distribution name: **`py-sparcli`** (the name `sparcli` is taken).
|
|
25
|
+
- Import name: **`import sparcli`**.
|
|
26
|
+
- Build backend: hatchling, static version in `pyproject.toml`, `[tool.hatch.build.targets.wheel] packages = ["sparcli"]`, ships `py.typed`.
|
|
27
|
+
|
|
28
|
+
## Architecture (keep the layers strictly separated)
|
|
29
|
+
|
|
30
|
+
- `core/` – foundation: style, color, text, render, geometry, border, theme, width, terminal, markup. No widget logic.
|
|
31
|
+
- `output/` – printable widgets, subclass `Renderable` (`render(max_width) -> Rendered`). Shared helpers: `layout`, `compose`, `box`, `live`.
|
|
32
|
+
- `input/` – interactive prompts over `EventSource` (DI) + the `prompt.run_prompt` loop + `line_edit.LineEditor` (SSOT for text editing) + `field` (rendering).
|
|
33
|
+
- **Dependency direction:** `output`/`input` → `core`. Never cyclic, never `core` → widget layer.
|
|
34
|
+
- **A single unified theme** in `core/theme.py` drives both input and output.
|
|
35
|
+
- The public API is re-exported flat (`from sparcli import ...`, 80 symbols) plus the namespaces `sparcli.width/terminal/markup/event/validate/shortcut`. Every `__init__.py` carries an explicit `__all__` list.
|
|
36
|
+
|
|
37
|
+
## Dependencies
|
|
38
|
+
|
|
39
|
+
- **Zero runtime dependencies – standard library only.** Do not add a new runtime dependency without first checking with the user.
|
|
40
|
+
- Deliberate stdlib decisions (do not soften them):
|
|
41
|
+
- Display width via `unicodedata` (no `wcwidth`).
|
|
42
|
+
- DatePicker via `datetime.date`/`calendar` (no hand-rolled date class).
|
|
43
|
+
- Fuzzy scorer written by hand (no `nucleo`/`rapidfuzz`).
|
|
44
|
+
- Terminal raw mode / key parsing via `termios`/`tty` + `msvcrt`.
|
|
45
|
+
- `markup`, `fuzzy` and `pager` are always included (no feature flags/extras).
|
|
46
|
+
|
|
47
|
+
## Style & tooling
|
|
48
|
+
|
|
49
|
+
- Target Python: **3.12+** (`requires-python = ">=3.12"`).
|
|
50
|
+
- **Strings: double quotes `"..."`** – a deliberate, user-mandated deviation from the style-guide default (§12.5). Enforced via `[tool.ruff.format] quote-style = "double"`. Exceptions that stay single-quoted: string literals nested inside a double-quoted f-string (`';'`, `' '`) and `repr` outputs in doctests.
|
|
51
|
+
- **Formatting/linting:** `ruff` (format + lint). 80-character lines. Imports isort-sorted (stdlib → third-party → local). `from __future__ import annotations` as the first line of every module.
|
|
52
|
+
- **Type checking:** `basedpyright` in **strict** mode must report 0 errors. Complete, modern type hints (`X | None`, builtin generics, PEP 695).
|
|
53
|
+
- No em dash; straight quotes/apostrophes; named constants instead of magic numbers/strings.
|
|
54
|
+
|
|
55
|
+
Commands (from the project root):
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
ruff check --fix . && ruff format .
|
|
59
|
+
basedpyright sparcli examples
|
|
60
|
+
python -m pytest -q
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## API conventions (idiomatic)
|
|
64
|
+
|
|
65
|
+
- Widgets: regular classes with `__slots__`, subclass `Renderable`, a keyword constructor (`*` keyword-only options) **and** fluent builder methods that mutate `self` and return it. `Rendered` is itself a `Renderable`.
|
|
66
|
+
- Value types (`Style`, `Color`, `Edges`, `Title`, `BorderChars`, `Theme`) as `@dataclass(frozen=True, slots=True)` or `enum.Enum`; enums instead of magic strings.
|
|
67
|
+
- Interfaces as `ABC` + `@abstractmethod` (`Renderable`, `EventSource`); DI via constructor/parameters.
|
|
68
|
+
- Docstrings in NumPy style with an RST-title module docstring; public items get `Parameters`/`Returns`/`Examples`, third-person present ("Returns…").
|
|
69
|
+
- Keep functions small (SRP), ≤ 3 parameters (otherwise bundle into an options/state object), guard clauses, at most 2 levels of nesting.
|
|
70
|
+
|
|
71
|
+
## Error handling
|
|
72
|
+
|
|
73
|
+
- Exception hierarchy in `sparcli/errors.py`: `SparcliError` (base) with `TerminalError`, `NoTerminalError`, `ConfigError`. Wrap foreign errors sensibly.
|
|
74
|
+
- **No `assert` for validation** (stripped under `-O`). Do not return `None` for "no result"; use an empty collection, a special-case object, or an exception. (Parser optionals such as `Color.from_hex -> Color | None` are the documented exception.)
|
|
75
|
+
- Prompts return `Outcome[T]` (`submitted`/`cancelled`/`shortcut`), not a cancel exception.
|
|
76
|
+
- `TerminalGuard` (context manager, RAII) restores raw mode / bracketed paste on exit or error.
|
|
77
|
+
- **Logging:** only `logging.getLogger(__name__)` and only `warning`/`debug` where a result would otherwise be silently swallowed (terminal restore, history load/save, temp cleanup). Do not ship a logger/backend; nothing in render/event loops; real errors come back via `SparcliError`.
|
|
78
|
+
- `subprocess` only with an argument list (never `shell=True`/`os.system`); use `tempfile` safely; honor `NO_COLOR`/non-TTY.
|
|
79
|
+
|
|
80
|
+
## Look
|
|
81
|
+
|
|
82
|
+
- Muted look, a single accent tone, `dim` for secondary text. Default border `Rounded`; truncate overflow with `…`.
|
|
83
|
+
- Glyphs in two tiers (Unicode + ASCII fallback), selected via the theme.
|
|
84
|
+
- Selection lists navigate cyclically; honor `NO_COLOR` and non-TTY (then plain, no ANSI codes, no OSC-8).
|
|
85
|
+
|
|
86
|
+
## Tests – mandatory
|
|
87
|
+
|
|
88
|
+
- `pytest`; tests in `tests/`, grouped in `class TestXxx`; test names describe the expected behavior; prefer fakes over mocks.
|
|
89
|
+
- **Output:** render to `Rendered` and assert on `plain()` or ANSI-stripped content and styles (no TTY).
|
|
90
|
+
- **Input:** drive headlessly via `event.ScriptedSource` (scripted keys) + `run_with(source)` and assert on the `Outcome`.
|
|
91
|
+
- Doctests in `# Examples` count as tests (`--doctest-modules`) and must pass. `repr` expected-outputs keep single quotes.
|
|
92
|
+
- Test override: `SPARCLI_NO_TTY=1` forces non-TTY.
|
|
93
|
+
- **Run all tests after every change** and keep ruff + basedpyright clean.
|
|
94
|
+
|
|
95
|
+
## Environment variables
|
|
96
|
+
|
|
97
|
+
`NO_COLOR` (disables color), `CLICOLOR_FORCE` (forces color), `COLORTERM` (truecolor/24bit → TrueColor), `SPARCLI_NO_TTY` (test override, non-TTY).
|
|
98
|
+
|
|
99
|
+
## Docs / maintenance
|
|
100
|
+
|
|
101
|
+
- On changes, update README.md, `docs/DEVELOPMENT.md`, CHANGELOG.md, docstrings and tests together. The CHANGELOG follows Keep a Changelog + SemVer.
|
|
102
|
+
- Keep the `examples/` (five counterparts to the Rust version, including the two README generators `output_readme.py`/`prompt_readme.py` via `frame()`) current on API changes.
|
|
103
|
+
- Remove dead/commented-out code; fix the cause, not the symptom.
|
|
104
|
+
|
|
105
|
+
## Git
|
|
106
|
+
|
|
107
|
+
- **No commits on your own.** At the end of a change, suggest a commit title (English, imperative, Conventional Commits).
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Contributing to sparcli
|
|
2
|
+
|
|
3
|
+
Thanks for your interest in improving `sparcli`. This is a small, dependency-free library, so the workflow is deliberately lightweight.
|
|
4
|
+
|
|
5
|
+
## Getting started
|
|
6
|
+
|
|
7
|
+
The full build, test and lint workflow lives in [`docs/DEVELOPMENT.md`](docs/DEVELOPMENT.md). In short:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install -e .
|
|
11
|
+
pip install ruff basedpyright pytest # or: uv sync --group dev
|
|
12
|
+
|
|
13
|
+
python -m pytest -q # tests + doctests
|
|
14
|
+
ruff check . # lint
|
|
15
|
+
ruff format --check . # formatting (drop --check to apply)
|
|
16
|
+
basedpyright sparcli examples # strict type checking
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
All three checks must be clean before a change is ready. Optionally, install the pre-commit hooks to run linting, formatting and type checks automatically:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pre-commit install # once
|
|
23
|
+
pre-commit run --all-files # on demand
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Ground rules
|
|
27
|
+
|
|
28
|
+
- **Target Python 3.12+.** The code uses PEP 695 generics and `type` statements.
|
|
29
|
+
- **Zero runtime dependencies.** Do not add a runtime dependency; the standard library only. Open an issue first if you think one is unavoidable.
|
|
30
|
+
- **Style gates** (enforced by `ruff` and `basedpyright`): 80-column lines, double-quoted strings, isort-ordered imports, complete modern type hints, and `basedpyright` strict with zero errors.
|
|
31
|
+
- **Layering.** `output/` and `input/` depend on `core/`, never the reverse, and `core/` depends on nothing internal.
|
|
32
|
+
- **Tests are mandatory.** Add headless tests under `tests/` following the patterns in `docs/DEVELOPMENT.md`; doctests in `# Examples` count as tests.
|
|
33
|
+
- **Docs travel with code.** Update `README.md`, `CHANGELOG.md`, docstrings and, where relevant, the `examples/` gallery in the same change.
|
|
34
|
+
|
|
35
|
+
The "Adding a widget" checklist in `docs/DEVELOPMENT.md` walks through a typical contribution end to end.
|
|
36
|
+
|
|
37
|
+
## Rust parity
|
|
38
|
+
|
|
39
|
+
`sparcli` is an idiomatic port of the Rust library at a matching version (the two ports keep their **minor version in sync**). The Rust original is the source of truth for behavior. When a change affects behavior, API or docs, note in your PR whether the same change should be mirrored to the Rust port.
|
|
40
|
+
|
|
41
|
+
## Commits and pull requests
|
|
42
|
+
|
|
43
|
+
- Use [Conventional Commits](https://www.conventionalcommits.org/) for commit titles (`feat:`, `fix:`, `docs:`, `refactor:`, `test:`, `chore:`), imperative mood, English.
|
|
44
|
+
- Add a `CHANGELOG.md` entry under `## [Unreleased]` (Keep a Changelog format).
|
|
45
|
+
- Keep pull requests focused; describe the behavior change and how you verified it.
|
|
46
|
+
|
|
47
|
+
## Reporting bugs and security issues
|
|
48
|
+
|
|
49
|
+
- Regular bugs and feature requests: open a [GitHub issue](https://github.com/cgroening/py-sparcli/issues).
|
|
50
|
+
- Security vulnerabilities: **do not** open a public issue; follow [`SECURITY.md`](SECURITY.md).
|
|
51
|
+
|
|
52
|
+
By contributing you agree that your contributions are licensed under the project's [MIT license](LICENSE).
|
py_sparcli-0.3.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Corvin Gröning
|
|
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.
|