termcore 0.2.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.
- termcore-0.2.0/.gitignore +222 -0
- termcore-0.2.0/CHANGELOG.md +95 -0
- termcore-0.2.0/CLAUDE.md +55 -0
- termcore-0.2.0/LICENSE +21 -0
- termcore-0.2.0/PKG-INFO +504 -0
- termcore-0.2.0/README.md +473 -0
- termcore-0.2.0/TODO.md +107 -0
- termcore-0.2.0/docs/CLEAN-UP.md +118 -0
- termcore-0.2.0/docs/DEVELOPMENT.md +155 -0
- termcore-0.2.0/pyproject.toml +199 -0
- termcore-0.2.0/termcore/__init__.py +6 -0
- termcore-0.2.0/termcore/cli/__init__.py +3 -0
- termcore-0.2.0/termcore/cli/output.py +70 -0
- termcore-0.2.0/termcore/io/__init__.py +7 -0
- termcore-0.2.0/termcore/io/app_state_storage.py +185 -0
- termcore-0.2.0/termcore/io/database.py +655 -0
- termcore-0.2.0/termcore/io/errors.py +71 -0
- termcore-0.2.0/termcore/io/file.py +263 -0
- termcore-0.2.0/termcore/io/textfile.py +92 -0
- termcore-0.2.0/termcore/py.typed +0 -0
- termcore-0.2.0/termcore/tui/__init__.py +9 -0
- termcore-0.2.0/termcore/tui/custom_bindings.py +551 -0
- termcore-0.2.0/termcore/tui/custom_widgets/__init__.py +1 -0
- termcore-0.2.0/termcore/tui/custom_widgets/custom_checkbox.py +41 -0
- termcore-0.2.0/termcore/tui/custom_widgets/custom_data_table.py +247 -0
- termcore-0.2.0/termcore/tui/custom_widgets/custom_selection.py +80 -0
- termcore-0.2.0/termcore/tui/custom_widgets/multiline_footer.py +240 -0
- termcore-0.2.0/termcore/tui/question_screen.py +107 -0
- termcore-0.2.0/termcore/tui/theme_loader.py +495 -0
- termcore-0.2.0/termcore/tui/themes/__init__.py +2 -0
- termcore-0.2.0/termcore/tui/themes/classic-black-saturated/style.css +57 -0
- termcore-0.2.0/termcore/tui/themes/classic-black-saturated/theme.py +22 -0
- termcore-0.2.0/termcore/tui/themes/classic-black-v1/style.css +57 -0
- termcore-0.2.0/termcore/tui/themes/classic-black-v1/theme.py +22 -0
- termcore-0.2.0/termcore/tui/themes/classic-black-v2/style.css +57 -0
- termcore-0.2.0/termcore/tui/themes/classic-black-v2/theme.py +24 -0
- termcore-0.2.0/termcore/tui/themes/classic-blue/theme.py +21 -0
- termcore-0.2.0/termcore/tui/themes/compact-gray/style.tcss +175 -0
- termcore-0.2.0/termcore/tui/themes/compact-gray/theme.py +22 -0
- termcore-0.2.0/termcore/tui/themes/mnml-black/style.css +57 -0
- termcore-0.2.0/termcore/tui/themes/mnml-black/theme.py +21 -0
- termcore-0.2.0/termcore/tui/themes/mnml-deepblack/style.css +57 -0
- termcore-0.2.0/termcore/tui/themes/mnml-deepblack/theme.py +21 -0
- termcore-0.2.0/termcore/tui/themes/pure-amber/theme.py +18 -0
- termcore-0.2.0/termcore/tui/themes/pure-black/theme.py +18 -0
- termcore-0.2.0/termcore/tui/themes/pure-blue/theme.py +18 -0
- termcore-0.2.0/termcore/tui/themes/pure-green/theme.py +18 -0
- termcore-0.2.0/termcore/tui/themes/pure-sweet16/theme.py +20 -0
- termcore-0.2.0/termcore/tui/themes/xplore-black/theme.py +18 -0
- termcore-0.2.0/termcore/tui/themes/xplore-blue/theme.py +18 -0
- termcore-0.2.0/termcore/tui/themes/xplore-blue-muted/theme.py +18 -0
- termcore-0.2.0/termcore/tui/themes/xplore-teal/theme.py +18 -0
- termcore-0.2.0/termcore/util/__init__.py +10 -0
- termcore-0.2.0/termcore/util/datetime.py +173 -0
- termcore-0.2.0/termcore/util/debug.py +196 -0
- termcore-0.2.0/termcore/util/index.py +112 -0
- termcore-0.2.0/termcore/util/logger.py +36 -0
- termcore-0.2.0/termcore/util/singleton.py +46 -0
- termcore-0.2.0/termcore/util/string.py +263 -0
- termcore-0.2.0/termcore/util/validation.py +31 -0
- termcore-0.2.0/termcore/util/version.py +56 -0
- termcore-0.2.0/tests/__init__.py +0 -0
- termcore-0.2.0/tests/io/__init__.py +0 -0
- termcore-0.2.0/tests/io/conftest.py +34 -0
- termcore-0.2.0/tests/io/test_database.py +549 -0
- termcore-0.2.0/tests/test_docs.py +192 -0
- termcore-0.2.0/tests/test_public_api.py +66 -0
- termcore-0.2.0/tests/tui/__init__.py +0 -0
- termcore-0.2.0/tests/tui/conftest.py +67 -0
- termcore-0.2.0/tests/tui/test_custom_bindings.py +575 -0
- termcore-0.2.0/tests/tui/test_theme_loader.py +646 -0
- termcore-0.2.0/tests/util/__init__.py +0 -0
- termcore-0.2.0/tests/util/test_datetime.py +152 -0
- termcore-0.2.0/tests/util/test_index.py +65 -0
- termcore-0.2.0/tests/util/test_string.py +186 -0
|
@@ -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,95 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be 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
|
+
## [0.2.0] – 2026-07-19
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- `util/version.py`: New utility module to retrieve the package version via `importlib.metadata` with a fallback to `pyproject.toml`
|
|
12
|
+
- First test suite, covering `tui/theme_loader.py`, together with the pytest, coverage and dev-dependency setup the project was missing
|
|
13
|
+
- Test suites for `tui/custom_bindings.py`, `io/database.py`, `util/datetime.py`, `util/index.py` and `util/string.py`, which completes the list of modules named in `TODO.md`
|
|
14
|
+
- `io/errors.py`: `DatabaseError` and its two subclasses `UnknownIdentifierError` and `EmptyConditionsError`, the project's first domain exceptions
|
|
15
|
+
- `SQLComparisonOperator`: `NE`, `LIKE`, `IS_NULL` and `IS_NOT_NULL`. There was previously no way to express "not equal" at all
|
|
16
|
+
- `py.typed`. Without the marker a consumer's type checker treated the whole package as untyped and degraded every import to `Any`
|
|
17
|
+
- A `[tool.ruff]` section with the rule set from section 3.2.1 of the style guide, and `typeCheckingMode = "strict"` for basedpyright. The library was previously linted on ruff's defaults, which select no rules and enforce no line length
|
|
18
|
+
- `io/errors.py`: `AppStateError` and `StateFileError`
|
|
19
|
+
- `termcore/tui/custom_widgets/__init__.py`. The directory was an implicit namespace package, which is one packaging tool away from being dropped from the wheel entirely
|
|
20
|
+
- `clamped_index`, `timing_ns`, `File.folder_content_recursive`, `ThemeLoader.custom_only`, `CustomBindings.sorted_by_key`, `CustomBindings.get_screen_bindings` and `CustomBindings.get_screen_row_map`, each replacing a boolean parameter (section 1.1.2)
|
|
21
|
+
- `DateFormat`, an enum replacing the `english_format` flag of the datetime helpers
|
|
22
|
+
- `cell_width`, which counts the terminal cells a string occupies rather than its code points
|
|
23
|
+
- An `__all__` in every module, so that `from termcore import *` carries the public API instead of every module's own imports. The star export shrank from 141 names to 85
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- The project is now called `termcore`. This is a breaking change on three levels: the distribution on PyPI (`pip install termcore`), the import path (`from termcore.tui.theme_loader import ThemeLoader`), and the registration prefix of the bundled themes, which is `TERMCORE_` instead of `TERMZ_`. A theme name stored in a consumer's config file therefore no longer resolves; `ThemeLoader.set_previous_theme` reports this and falls back to the default rather than failing. The `termz` releases on PyPI are not maintained further
|
|
28
|
+
- `ThemeLoader`: The keyword argument `termz_theme_prefix` is now `termcore_theme_prefix`, and the constant `DEFAULT_TERMZ_THEME_PREFIX` is `DEFAULT_TERMCORE_THEME_PREFIX`
|
|
29
|
+
- `CustomBindings`: Global bindings are no longer prefixed with `*`
|
|
30
|
+
- Updated colors of Textual themes `compact-gray` and `mnml-deepblack`
|
|
31
|
+
- `ThemeLoader`: A theme is now identified by the `name` in its `theme.py` plus its registration prefix. The directory name is no longer used as a key, so it may differ freely, and a standard and a custom theme may share a name
|
|
32
|
+
- `ThemeLoader`: Theme modules are imported by file path instead of through `sys.path`, so loading no longer alters the interpreter's import state
|
|
33
|
+
- `ThemeData`: Now frozen, and its redundant `name` field was removed
|
|
34
|
+
- `Database`: Values are bound as query parameters instead of being formatted into the statement, and identifiers are checked against the schema of the open database and quoted. Neither a value nor a name can be read as SQL any more
|
|
35
|
+
- `Database.update`: Takes the table, the new values and the conditions as three arguments, replacing the list of dictionaries carrying the conditions under an `@`-prefixed key. It now returns the number of changed rows
|
|
36
|
+
- `Database.remove`: Returns the number of deleted rows
|
|
37
|
+
- `Database.query`: Takes an optional `params` sequence, so callers never have to format values into the statement themselves
|
|
38
|
+
- `Database`: Leaving a `with` block commits, or rolls back if the block is left through an exception. It previously closed the connection without committing, so writes made through `query()` were lost
|
|
39
|
+
- `Database`: `debug_mode` writes to the module logger at debug level instead of printing to stdout
|
|
40
|
+
- `next_index`: The `max_index` parameter is now called `length`. It was a length in both the code and the docstring, and the old name invited an off-by-one
|
|
41
|
+
- `util/datetime`: Every conversion states the local time zone instead of leaving it implied. Results are unchanged on a given machine
|
|
42
|
+
- `str_with_fixed_width`: The alignment is validated before the text length is looked at, so an invalid value is rejected either way
|
|
43
|
+
- Every boolean parameter that selected behaviour is gone, replaced by a second named function, a factory or an enum: `next_index(loop_behavior=)`, `timing(use_ns_timer=)`, `File.folder_content(withsubfolders=)`, `ThemeLoader(include_standard_themes=)`, `CustomBindings(sort_alphabetically=)`, `get_bindings(for_screen=)`, `get_row_map(for_screen=)` and `english_format=` on the five datetime helpers. All of these are breaking
|
|
44
|
+
- `AppStateStorage`: A failure to read, create or write the state file raises `StateFileError` instead of printing a message and calling `sys.exit()`. A library taking the whole process down left the caller no way to react
|
|
45
|
+
- `termcore.util.debug`: The decorators log at debug level through a module logger instead of printing to stdout
|
|
46
|
+
- File and directory handling moved from `os.path` to `pathlib` throughout
|
|
47
|
+
- `str_with_fixed_width` counts terminal cells instead of code points, so a column holding CJK text or emoji stays aligned. A double-width glyph cannot be split, so where one would straddle the boundary the result is padded to reach the width exactly. Breaking for anyone who relied on the returned string having exactly `width` characters
|
|
48
|
+
- `date_diff` compares local calendar dates instead of dividing by 86400. Two moments on the same day are 0 days apart whatever the clock says, and a span crossing a daylight saving switch is no longer off by one
|
|
49
|
+
- `CustomBindings` reads `show`, `priority` and `system` as genuine YAML booleans. The quoted string `"false"` used to be read as `True`, so a quoted boolean silently meant its opposite; it now falls back to the documented default and says so
|
|
50
|
+
- `CustomBindings`: an explicitly declared `key_display` is no longer overridden for function keys. The field exists to override how a key is rendered
|
|
51
|
+
|
|
52
|
+
### Fixed
|
|
53
|
+
|
|
54
|
+
- `CustomBindings`: The binding registry was held in class attributes shared by every instance, the same defect as in `ThemeLoader`. A second `CustomBindings` in one process inherited the first one's groups and appended its own on top, so every binding appeared twice
|
|
55
|
+
- `next_index`: An empty list raised `ZeroDivisionError` instead of yielding index 0, and the clamped branch ignored the step size while the wrapping branch honoured it
|
|
56
|
+
- `str_with_fixed_width`: Did not return the promised number of characters at the edges. Width 1 with `align="right"` returned the entire string, because the offset became `-0`; width 0 returned the text minus its last character
|
|
57
|
+
- `linewrap`: Raised `ValueError` on a word longer than the line, and looped forever for a width below 1
|
|
58
|
+
- `termcore.util.datetime` resolved to the `datetime` class rather than the module, because the package's star-import re-exported the imported name over the submodule
|
|
59
|
+
- `Database.fetch`: An offset without a limit produced a syntax error, `limit=0` was silently dropped, and an empty column list produced `SELECT FROM`
|
|
60
|
+
- `Database`: `ORDER BY` and `WHERE` were joined using identity checks against the first and last element, so passing the same `Condition` or `ColumnOrder` object twice produced malformed SQL
|
|
61
|
+
- `Database.insert`: Read the new row back through a hard-coded `id` column, which failed after the write on any table without one, and committed each row separately, so a failure half way through left the earlier rows behind
|
|
62
|
+
- `Database.update`: Deleted the conditions out of the caller's own dictionary while parsing them, so a second call with the same data built a statement with an empty `WHERE` clause
|
|
63
|
+
- `Database`: `__del__` raised `AttributeError` as an unraisable when the connection had never been assigned
|
|
64
|
+
- `ThemeLoader`: The theme registry was held in class attributes shared by every instance, and registration re-prefixed the same `Theme` objects each time. A second application in one process saw `CUSTOM_CUSTOM_<name>`, a third `CUSTOM_CUSTOM_CUSTOM_<name>`. State is now per instance and the prefix is applied once, to a copy
|
|
65
|
+
- `ThemeLoader`: A theme folder whose name differed from the `name` in its `theme.py` registered but silently lost its stylesheet
|
|
66
|
+
- `ThemeLoader`: From the second loader in a process on, the bundled themes were not found at all, because a consumer's theme folder shadowed them under the shared package name `themes`. The bug was masked by the shared registry described above
|
|
67
|
+
- `ThemeLoader`: Switching themes never removed the stylesheet of a theme outside termcore's own theme folder, so a consumer's CSS stayed applied on top of every theme it switched to
|
|
68
|
+
- `ThemeLoader`: A theme without a stylesheet, and any theme not registered by termcore such as Textual's built-ins, logged a warning during entirely normal use. This is reported at debug level now
|
|
69
|
+
- `ThemeLoader`: Diagnostics went to the root logger and surfaced as `WARNING:root:` in every consumer; they now use a module logger and lazy formatting
|
|
70
|
+
- `ThemeLoader`: Two theme folders declaring the same name silently overwrote each other; the second is now refused with an error
|
|
71
|
+
- `Singleton`: The metaclass reported `object` as the type of every instance it built, so a consumer type-checking against a singleton got no type at all. It now reports the class it constructed
|
|
72
|
+
- `CustomDataTable.flexible_columns` and `AppStateStorage._json_dict` were class attributes with mutable defaults, so every instance in a process shared one object. Same defect as in `ThemeLoader` and `CustomBindings`
|
|
73
|
+
- `File.change_extension` printed the split file name to stdout, a debug statement left in a pure function
|
|
74
|
+
- A message in `AppStateStorage` was in German, against the rule that all visible text is English
|
|
75
|
+
- `ThemeLoader.set_previous_theme_in_textual_app` did nothing at all when the stored theme was no longer registered, so a theme removed between two runs left the application on Textual's default rather than on the `default_theme_name` the caller passed. It now falls back explicitly and reports both that and the case where the default is unknown too
|
|
76
|
+
- `ThemeLoader` called `Stylesheet.reparse()` unguarded, so a theme shipping invalid TCSS took the whole application down from inside the library. The failure is logged and the previous stylesheet is kept
|
|
77
|
+
- `CustomBindings` was silent in three places that now warn: a binding dropped for a missing `key`, `action` or `description`, a second group claiming an action name and overwriting the first one's footer row, and a tab or screen name matching no group at all
|
|
78
|
+
|
|
79
|
+
### Removed
|
|
80
|
+
|
|
81
|
+
- `Database.tostr`: Values are parameters now, so the method had no purpose left; leaving it would have invited hand-built statements
|
|
82
|
+
- `Database.update` no longer accepts the list-of-dictionaries form with an `@`-prefixed conditions key
|
|
83
|
+
- `CustomBindings._action_belongs_to_group`: Never called
|
|
84
|
+
- Commented-out code in `CustomBindings` and `CustomDataTable`
|
|
85
|
+
- Two of the six blanket pyright suppressions, after measuring that they report nothing in either checking mode
|
|
86
|
+
|
|
87
|
+
## [0.1.1] – 2026-04-07
|
|
88
|
+
|
|
89
|
+
- Initial Release
|
|
90
|
+
|
|
91
|
+
## [0.1.0] – 2026-03-25
|
|
92
|
+
|
|
93
|
+
*PyPI name reservation – no functional code.*
|
|
94
|
+
|
|
95
|
+
[0.2.0]: https://github.com/cgroening/py-termcore/compare/v0.1.1...v0.2.0 [0.1.1]: https://github.com/cgroening/py-termcore/compare/v0.1.0...v0.1.1 [0.1.0]: https://github.com/cgroening/py-termcore/releases/tag/v0.1.0
|
termcore-0.2.0/CLAUDE.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
Project-specific instructions for termcore. The global style guide referenced in `~/.claude/CLAUDE.md` remains authoritative; this file only records what is specific to this repository.
|
|
4
|
+
|
|
5
|
+
## What this project is
|
|
6
|
+
|
|
7
|
+
termcore is a library, not an application. It is the foundation under termplate, which section 3.2.9 of the style guide names as the template every Textual-based TUI is checked against.
|
|
8
|
+
|
|
9
|
+
That ordering has a consequence worth stating plainly: a defect here reaches every application built on the toolkit, and it reaches them silently, because applications trust a library rather than testing it. Four of the theme bugs that held up termplate lived in this repository and none of them failed loudly – they just rendered the wrong colours. Prefer failing loudly over carrying on, and prefer a test over a comment.
|
|
10
|
+
|
|
11
|
+
## Toolchain
|
|
12
|
+
|
|
13
|
+
All of these must be clean before a change is finished:
|
|
14
|
+
|
|
15
|
+
```zsh
|
|
16
|
+
ruff check .
|
|
17
|
+
basedpyright
|
|
18
|
+
pytest
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`ruff format` is deliberately absent. The formatter is not configured, line breaking is hand-made, and `E501` enforces the column limit; the reasoning sits in a comment in `pyproject.toml`. Do not add `[tool.ruff.format]` without deciding that question again.
|
|
22
|
+
|
|
23
|
+
The binary is `basedpyright`, never `pyright`, even though the configuration section is called `[tool.pyright]`. Its default mode is stricter than the `strict` the configuration asks for, which is why the four remaining suppressions read as inert – they describe real properties of this code and keep a switch to `recommended` a one-line change.
|
|
24
|
+
|
|
25
|
+
Coverage runs through `--cov` in `addopts`. The gate in `[tool.coverage.report]` holds the level currently reached: raise `fail_under` when a module gains tests, never lower it to make a run pass.
|
|
26
|
+
|
|
27
|
+
## Architecture
|
|
28
|
+
|
|
29
|
+
Four sub-packages, each a coherent purpose rather than a layer:
|
|
30
|
+
|
|
31
|
+
- `cli` – styled console output through Rich.
|
|
32
|
+
- `io` – SQLite, JSON application state, files and text files.
|
|
33
|
+
- `tui` – Textual helpers: theme loading, key bindings, custom widgets.
|
|
34
|
+
- `util` – dates, strings, index arithmetic, logging, debugging decorators.
|
|
35
|
+
|
|
36
|
+
The public API of each package is re-exported through `from .module import *` in its `__init__.py`, as section 3.2.2 requires. That has a consequence which has already caused a defect: a module without `__all__` re-exports its own imports too. `termcore/util/datetime.py` re-exported the `datetime` class over the submodule of the same name, so `import termcore.util.datetime` handed back the class. Every module declares `__all__`, and a new one has to as well – `tests/test_public_api.py` fails otherwise, and also checks that no borrowed name such as `json` or `Path` reaches the package namespace.
|
|
37
|
+
|
|
38
|
+
## The defect this repository keeps producing
|
|
39
|
+
|
|
40
|
+
Mutable class attributes shared by every instance. It has been found and fixed four times: `ThemeLoader._theme_names` and `_theme_data`, the four registries in `CustomBindings`, `CustomDataTable.flexible_columns` and `AppStateStorage._json_dict`. Each time the symptom was different and none of them raised.
|
|
41
|
+
|
|
42
|
+
Declare instance attributes annotated at class level and assign them in `__init__`, as section 3.2.5 says. `RUF012` catches the annotated-with-a-value form; it does not catch every shape, so this stays worth looking for by hand.
|
|
43
|
+
|
|
44
|
+
## Conventions particular to this repo
|
|
45
|
+
|
|
46
|
+
- No boolean parameter selects behaviour anywhere in the public API. Section 1.1.2 is enforced by `FBT`, and the established answers are two named functions (`next_index` and `clamped_index`, `timing` and `timing_ns`), a classmethod factory (`ThemeLoader.custom_only`, `CustomBindings.sorted_by_key`) or an enum (`DateFormat`). Pick whichever fits the parameter; a format selector is an enum, a behaviour switch is two functions.
|
|
47
|
+
- Domain exceptions live in `termcore/io/errors.py`. A library that calls `sys.exit()` or prints an error leaves its caller no way to react – `AppStateStorage` did both until recently.
|
|
48
|
+
- Diagnostics go to a module logger with lazy `%`-formatting, never to `print`. `termcore/cli/output.py` is the one module where printing is the product, and it carries a per-file ignore saying so.
|
|
49
|
+
- Every SQL statement binds its values as parameters, and every identifier passes `_quoted_table` or `_quoted_column`, which check it against the schema of the open database before quoting it. The `# noqa: S608` markers state exactly what the rule cannot see. There is no other way to build a statement here.
|
|
50
|
+
- A theme is identified by the `name` in its `theme.py` plus its registration prefix; the directory it lives in carries no meaning. Theme modules are imported by file path, never as a package – importing them by folder name made the first folder loaded in a process shadow every later one of the same name.
|
|
51
|
+
- Textual reads annotations at runtime, so type-only imports stay at module level under `termcore/tui/`. The per-file ignore for `TC002` and `TC003` in `pyproject.toml` is that rule, not an oversight.
|
|
52
|
+
- hatchling ships everything inside the package directory, so runtime data (`py.typed`, the theme `.css` and `theme.py` files) needs no `package-data` entry. Every package directory does need an `__init__.py`, or it becomes an implicit namespace package that a stricter packaging tool would drop.
|
|
53
|
+
- termplate is the only consumer. A breaking change is pulled through it in the same pass, and its suite has to be green before the change is finished.
|
|
54
|
+
- `TODO.md` tracks deferred work, including findings that were deliberately left alone and why. Read it before starting, so the same gap is not filled twice in different ways.
|
|
55
|
+
- `docs/DEVELOPMENT.md` holds the module tree, which shared helper already solves which problem, how to exercise a library that has no entry point, and the release procedure. `docs/CLEAN-UP.md` is the checklist for a structured review pass. Both are guarded by `tests/test_docs.py`, so a path that moves fails the suite.
|
termcore-0.2.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.
|