termcore 0.2.0__py3-none-any.whl

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 (55) hide show
  1. termcore/__init__.py +6 -0
  2. termcore/cli/__init__.py +3 -0
  3. termcore/cli/output.py +70 -0
  4. termcore/io/__init__.py +7 -0
  5. termcore/io/app_state_storage.py +185 -0
  6. termcore/io/database.py +655 -0
  7. termcore/io/errors.py +71 -0
  8. termcore/io/file.py +263 -0
  9. termcore/io/textfile.py +92 -0
  10. termcore/py.typed +0 -0
  11. termcore/tui/__init__.py +9 -0
  12. termcore/tui/custom_bindings.py +551 -0
  13. termcore/tui/custom_widgets/__init__.py +1 -0
  14. termcore/tui/custom_widgets/custom_checkbox.py +41 -0
  15. termcore/tui/custom_widgets/custom_data_table.py +247 -0
  16. termcore/tui/custom_widgets/custom_selection.py +80 -0
  17. termcore/tui/custom_widgets/multiline_footer.py +240 -0
  18. termcore/tui/question_screen.py +107 -0
  19. termcore/tui/theme_loader.py +495 -0
  20. termcore/tui/themes/__init__.py +2 -0
  21. termcore/tui/themes/classic-black-saturated/style.css +57 -0
  22. termcore/tui/themes/classic-black-saturated/theme.py +22 -0
  23. termcore/tui/themes/classic-black-v1/style.css +57 -0
  24. termcore/tui/themes/classic-black-v1/theme.py +22 -0
  25. termcore/tui/themes/classic-black-v2/style.css +57 -0
  26. termcore/tui/themes/classic-black-v2/theme.py +24 -0
  27. termcore/tui/themes/classic-blue/theme.py +21 -0
  28. termcore/tui/themes/compact-gray/style.tcss +175 -0
  29. termcore/tui/themes/compact-gray/theme.py +22 -0
  30. termcore/tui/themes/mnml-black/style.css +57 -0
  31. termcore/tui/themes/mnml-black/theme.py +21 -0
  32. termcore/tui/themes/mnml-deepblack/style.css +57 -0
  33. termcore/tui/themes/mnml-deepblack/theme.py +21 -0
  34. termcore/tui/themes/pure-amber/theme.py +18 -0
  35. termcore/tui/themes/pure-black/theme.py +18 -0
  36. termcore/tui/themes/pure-blue/theme.py +18 -0
  37. termcore/tui/themes/pure-green/theme.py +18 -0
  38. termcore/tui/themes/pure-sweet16/theme.py +20 -0
  39. termcore/tui/themes/xplore-black/theme.py +18 -0
  40. termcore/tui/themes/xplore-blue/theme.py +18 -0
  41. termcore/tui/themes/xplore-blue-muted/theme.py +18 -0
  42. termcore/tui/themes/xplore-teal/theme.py +18 -0
  43. termcore/util/__init__.py +10 -0
  44. termcore/util/datetime.py +173 -0
  45. termcore/util/debug.py +196 -0
  46. termcore/util/index.py +112 -0
  47. termcore/util/logger.py +36 -0
  48. termcore/util/singleton.py +46 -0
  49. termcore/util/string.py +263 -0
  50. termcore/util/validation.py +31 -0
  51. termcore/util/version.py +56 -0
  52. termcore-0.2.0.dist-info/METADATA +504 -0
  53. termcore-0.2.0.dist-info/RECORD +55 -0
  54. termcore-0.2.0.dist-info/WHEEL +4 -0
  55. termcore-0.2.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,504 @@
1
+ Metadata-Version: 2.4
2
+ Name: termcore
3
+ Version: 0.2.0
4
+ Summary: Terminal utilities for CLI, TUI, IO and general use
5
+ Project-URL: Homepage, https://github.com/cgroening/py-termcore
6
+ Project-URL: Repository, https://github.com/cgroening/py-termcore
7
+ License: MIT
8
+ License-File: LICENSE
9
+ Keywords: cli,io,terminal,tui,utils
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Software Development :: Libraries
18
+ Classifier: Topic :: Terminals
19
+ Requires-Python: >=3.12
20
+ Requires-Dist: pyyaml
21
+ Requires-Dist: rich
22
+ Requires-Dist: textual
23
+ Provides-Extra: dev
24
+ Requires-Dist: basedpyright>=1.31; extra == 'dev'
25
+ Requires-Dist: coverage>=7.13.4; extra == 'dev'
26
+ Requires-Dist: pytest-asyncio>=1.0; extra == 'dev'
27
+ Requires-Dist: pytest-cov>=7.0; extra == 'dev'
28
+ Requires-Dist: pytest>=9.0; extra == 'dev'
29
+ Requires-Dist: ruff>=0.15; extra == 'dev'
30
+ Description-Content-Type: text/markdown
31
+
32
+ # termcore
33
+
34
+ Terminal utilities for CLI, TUI, IO and general use.
35
+
36
+ ## Overview
37
+
38
+ **termcore** is a Python library that bundles reusable building blocks for terminal applications. It is organized into four sub-packages:
39
+
40
+ | Package | Contents |
41
+ |---------|----------|
42
+ | `termcore.cli` | Styled console output via [Rich](https://github.com/Textualize/rich) |
43
+ | `termcore.tui` | [Textual](https://github.com/Textualize/textual) TUI helpers – theme loading, custom widgets, modal screens |
44
+ | `termcore.io` | SQLite database abstraction, JSON app-state storage, file utilities |
45
+ | `termcore.util` | Datetime helpers, string utilities, singleton metaclass, debug decorators, logging setup |
46
+
47
+ ## Requirements
48
+
49
+ - Python >= 3.12
50
+ - [`rich`](https://pypi.org/project/rich/)
51
+ - [`textual`](https://pypi.org/project/textual/) (required for `termcore.tui`)
52
+
53
+ ## Installation
54
+
55
+ ```bash
56
+ pip install termcore
57
+ ```
58
+
59
+ ## termcore.cli – Styled CLI Output
60
+
61
+ Provides helpers for printing color-coded panels using Rich and for clearing terminal output.
62
+
63
+ ```python
64
+ from termcore import print_error, print_warning, print_success, print_info, clear_lines
65
+
66
+ print_success("File saved.")
67
+ print_warning("Disk space is low.")
68
+ print_error("Connection refused.")
69
+ print_info("Starting process...")
70
+
71
+ # Remove the last 4 rendered lines from the terminal
72
+ clear_lines(4)
73
+ ```
74
+
75
+ ### Functions
76
+
77
+ | Function | Description |
78
+ |----------|-------------|
79
+ | `print_error(message)` | Red panel with ✗ prefix |
80
+ | `print_warning(message)` | Yellow panel with ⚠ prefix |
81
+ | `print_success(message)` | Green panel with ✓ prefix |
82
+ | `print_info(message)` | Cyan panel with ℹ prefix |
83
+ | `print_panel(message, color)` | Panel with bold colored text |
84
+ | `print_custom_panel(formatted_message, panel_color)` | Panel with pre-formatted Rich markup |
85
+ | `clear_lines(count)` | Move cursor up `count` lines and clear everything below |
86
+ | `get_console()` | Returns the shared Rich `Console` instance |
87
+
88
+ ## termcore.tui – Textual TUI Helpers
89
+
90
+ ### ThemeLoader
91
+
92
+ Dynamically loads and registers [Textual](https://github.com/Textualize/textual) themes from a folder. Each theme lives in its own sub-directory and must expose a `TEXTUAL_THEME` variable of type `textual.theme.Theme`. Optional `.css` / `.tcss` files in the same folder are loaded automatically when the theme is activated.
93
+
94
+ A theme is identified by the `name` of its `TEXTUAL_THEME` plus the prefix it is registered under – the name of its directory carries no meaning and is free to differ. A theme without a stylesheet is equally valid; ten of the built-in themes below ship none. Each theme folder is read on its own, so an app can register several loaders and its own themes never collide with the ones the toolkit brings.
95
+
96
+ termcore ships 16 built-in themes:
97
+
98
+ `classic-black-saturated`, `classic-black-v1`, `classic-black-v2`, `classic-blue`, `compact-gray`, `mnml-black`, `mnml-deepblack`, `pure-amber`, `pure-black`, `pure-blue`, `pure-green`, `pure-sweet16`, `xplore-black`, `xplore-blue`, `xplore-blue-muted`, `xplore-teal`
99
+
100
+ ```python
101
+ from pathlib import Path
102
+ from termcore import ThemeLoader
103
+
104
+ loader = ThemeLoader("themes") # bundled themes plus that folder
105
+ loader = ThemeLoader.custom_only("themes") # only that folder
106
+
107
+ # In your Textual App.on_mount():
108
+ loader.register_themes_in_textual_app(app)
109
+ loader.set_previous_theme_in_textual_app(
110
+ app,
111
+ default_theme_name="TERMCORE_xplore-blue",
112
+ theme_config_file=Path("~/.config/myapp/theme.json").expanduser(),
113
+ )
114
+
115
+ # When the user changes theme:
116
+ loader.save_theme_to_config(app.theme, Path("~/.config/myapp/theme.json").expanduser())
117
+ loader.load_theme_css(app.theme, app)
118
+
119
+ # Cycle through themes with arrow keys:
120
+ loader.change_to_next_or_previous_theme(direction=1, app=app)
121
+ ```
122
+
123
+ Theme name prefixes:
124
+
125
+ - Built-in termcore themes: `TERMCORE_` (e.g. `TERMCORE_xplore-blue`)
126
+ - Custom themes: `CUSTOM_` (e.g. `CUSTOM_mytheme`)
127
+
128
+ Both prefixes can be customized via the `ThemeLoader` constructor.
129
+
130
+ ### QuestionScreen
131
+
132
+ A Textual `ModalScreen` that presents a yes/no dialog and returns a `bool`.
133
+
134
+ ```python
135
+ from termcore import QuestionScreen, ButtonColor
136
+
137
+ async def confirm_delete(self):
138
+ answer = await self.app.push_screen_wait(
139
+ QuestionScreen(
140
+ question="Delete this entry?",
141
+ yes_button_color=ButtonColor.ERROR,
142
+ no_button_color=ButtonColor.PRIMARY,
143
+ )
144
+ )
145
+ if answer:
146
+ self.do_delete()
147
+ ```
148
+
149
+ `ButtonColor` values: `DEFAULT`, `PRIMARY`, `ERROR`, `SUCCESS`, `WARNING`.
150
+
151
+ ### CustomDataTable
152
+
153
+ A subclass of Textual's `DataTable` that supports *flexible columns* – columns that automatically fill the remaining width when the terminal is resized.
154
+
155
+ ```python
156
+ from termcore.tui.custom_widgets.custom_data_table import CustomDataTable
157
+
158
+ table = CustomDataTable()
159
+ col_name = table.add_column("Name", width=20)
160
+ col_desc = table.add_column("Description")
161
+ table.flexible_columns = [col_desc] # This column will stretch to fill space
162
+ ```
163
+
164
+ ### CustomCheckbox
165
+
166
+ A subclass of Textual's `Checkbox` that shows a `✔` when checked and an empty box when unchecked, instead of the default `X`.
167
+
168
+ ```python
169
+ from termcore.tui.custom_widgets.custom_checkbox import CustomCheckbox
170
+
171
+ yield CustomCheckbox("Enable feature", value=True)
172
+ ```
173
+
174
+ ### CustomSelectionList
175
+
176
+ A subclass of Textual's `SelectionList` whose items show a `✔` when selected and an empty box when unselected, instead of the default `X`.
177
+
178
+ ```python
179
+ from termcore.tui.custom_widgets.custom_selection import CustomSelectionList
180
+
181
+ yield CustomSelectionList(
182
+ ("Option A", "a"),
183
+ ("Option B", "b"),
184
+ )
185
+ ```
186
+
187
+ ### MultiLineFooter
188
+
189
+ A drop-in replacement for Textual's built-in `Footer` that supports multiple rows of key bindings. Two modes are available:
190
+
191
+ - **`auto_wrap=True`** (default) – bindings wrap automatically when the row is full.
192
+ - **`auto_wrap=False`** – bindings are assigned to rows explicitly via `row_map`.
193
+
194
+ ```python
195
+ from termcore.tui.custom_widgets.multiline_footer import MultiLineFooter
196
+
197
+ # Auto-wrap (default)
198
+ yield MultiLineFooter()
199
+
200
+ # Manual row assignment
201
+ yield MultiLineFooter(
202
+ auto_wrap=False,
203
+ row_map={
204
+ 'quit': 0,
205
+ 'save': 0,
206
+ 'toggle_dark': 1,
207
+ 'help': 1,
208
+ },
209
+ )
210
+ ```
211
+
212
+ ### CustomBindings
213
+
214
+ Loads keyboard bindings from a YAML file and exposes them as Textual `Binding` objects. Supports global bindings, tab-specific bindings, and screen-specific bindings.
215
+
216
+ **Group naming conventions:**
217
+
218
+ | Group name | Scope | Action prefix |
219
+ |------------|-------|---------------|
220
+ | `_global` | Always visible | none (used as-is) |
221
+ | `<name>_tab` | Shown when that tab is active | `<name>_tab_` |
222
+ | `<name>_screen` | Shown on that screen | none (used as-is) |
223
+
224
+ **YAML example (`bindings.yaml`):**
225
+
226
+ ```yaml
227
+ _global:
228
+ - key: q
229
+ action: quit
230
+ description: Quit
231
+ priority: true
232
+ row: 1
233
+
234
+ tasks_tab:
235
+ - key: a
236
+ action: add_task
237
+ description: Add
238
+ row: 0
239
+
240
+ add_screen:
241
+ - key: escape
242
+ action: cancel
243
+ description: Cancel
244
+ row: 0
245
+ ```
246
+
247
+ **Usage:**
248
+
249
+ ```python
250
+ from termcore.tui.custom_bindings import CustomBindings
251
+
252
+ bindings = CustomBindings("bindings.yaml") # file order
253
+ bindings = CustomBindings.sorted_by_key("bindings.yaml") # sorted by key
254
+
255
+ # In your App or Screen:
256
+ # On the App: globals dispatch as declared
257
+ BINDINGS = bindings.get_bindings() # every tab + global
258
+ BINDINGS = bindings.get_bindings(tab_name="tasks_tab") # one tab + global
259
+
260
+ # On a Screen: globals are prefixed with `app.` so they dispatch on the App
261
+ BINDINGS = bindings.get_screen_bindings() # every tab + global
262
+ BINDINGS = bindings.get_screen_bindings("add") # that screen + global
263
+
264
+ # Row map for MultiLineFooter(auto_wrap=False):
265
+ row_map = bindings.get_row_map() # pair with get_bindings
266
+ row_map = bindings.get_screen_row_map() # pair with get_screen_bindings
267
+
268
+ # In check_action to hide tab bindings that don't belong to the active tab:
269
+ def check_action(self, action, parameters):
270
+ return bindings.handle_check_action(action, parameters, active_group=self.active_tab)
271
+ ```
272
+
273
+ ## termcore.io – IO Utilities
274
+
275
+ ### AppStateStorage
276
+
277
+ A JSON-backed singleton for persisting small application states (scroll position, last selection, command history, etc.).
278
+
279
+ ```python
280
+ from termcore import AppStateStorage
281
+
282
+ # Initialize once (e.g. at startup)
283
+ storage = AppStateStorage(package_name="myapp")
284
+ # State file is created at ~/.local/state/myapp/state.json
285
+
286
+ # Read / write simple values
287
+ storage.set("last_tab", "settings")
288
+ tab = storage.get("last_tab", default_value="home")
289
+
290
+ # List operations
291
+ storage.list_insert("history", 0, "command_1")
292
+ storage.edit_list_item("history", 0, "label", "renamed")
293
+ storage.move_list_item("history", 0, 2)
294
+ storage.delete_list_item("history", 0)
295
+ ```
296
+
297
+ Because `AppStateStorage` is a singleton, subsequent calls to `AppStateStorage()` anywhere in the application return the same instance. Supply an explicit `state_file_path` instead of `package_name` for a custom path.
298
+
299
+ ### Database
300
+
301
+ A lightweight SQLite abstraction.
302
+
303
+ Values are always bound as query parameters, never formatted into the statement, so a value can never be read as SQL. Identifiers cannot be parameters, so a table or column name is checked against the schema of the open database and quoted before it is used; a name that is not part of the schema raises `UnknownIdentifierError` instead of reaching the database. `update` and `remove` refuse an empty condition list with `EmptyConditionsError` rather than rewriting or emptying the whole table.
304
+
305
+ ```python
306
+ from termcore import Database, Condition, SQLComparisonOperator, SQLOrderByDirection, ColumnOrder
307
+
308
+ with Database("data.db") as db:
309
+ # Fetch with conditions and ordering
310
+ rows = db.fetch(
311
+ table="tasks",
312
+ columns=["id", "title", "due_date"],
313
+ conditions=[
314
+ Condition("done", SQLComparisonOperator.EQ, 0),
315
+ ],
316
+ orderby=[ColumnOrder("due_date", SQLOrderByDirection.ASC)],
317
+ limit=50,
318
+ )
319
+
320
+ # Insert
321
+ inserted = db.insert("tasks", [{"title": "Buy milk", "done": 0}])
322
+
323
+ # Update - returns the number of changed rows
324
+ changed = db.update(
325
+ "tasks",
326
+ {"done": 1},
327
+ [Condition("id", SQLComparisonOperator.EQ, inserted[0]["id"])],
328
+ )
329
+
330
+ # Delete
331
+ db.remove("tasks", [Condition("id", SQLComparisonOperator.EQ, 42)])
332
+
333
+ # Raw SQL - pass values through params, never format them into the string
334
+ db.query("CREATE TABLE IF NOT EXISTS tasks (id INTEGER PRIMARY KEY, title TEXT, done INTEGER)")
335
+ db.query("INSERT INTO tasks (title) VALUES (?)", ["Buy bread"])
336
+ db.save()
337
+ ```
338
+
339
+ Leaving the `with` block commits, unless it is left through an exception, in which case the transaction is rolled back. Available comparison operators are `LT`, `LE`, `EQ`, `NE`, `GE`, `GT`, `LIKE`, `IS_NULL` and `IS_NOT_NULL`; the last two take no value.
340
+
341
+ ### File
342
+
343
+ Static helpers for file and folder operations.
344
+
345
+ ```python
346
+ from termcore import File
347
+
348
+ # List folder contents (optionally filtered by extension and recursive)
349
+ items = File.folder_content("./data", extfilter="csv", withsubfolders=True)
350
+
351
+ # Copy a folder
352
+ File.copy_folder("./src_folder", "./dst_folder")
353
+
354
+ # File extension helpers
355
+ ext = File.extension("report.csv") # "csv"
356
+ new_name = File.change_extension("report.csv", "txt") # "report.txt"
357
+ folder = File.path("/home/user/docs/file.txt") # "/home/user/docs"
358
+ ```
359
+
360
+ ### Textfile
361
+
362
+ Simple UTF-8 text file read/write.
363
+
364
+ ```python
365
+ from termcore import Textfile # or: from termcore.io.textfile import Textfile
366
+
367
+ content = Textfile.read("notes.txt")
368
+ lines = Textfile.readlines("notes.txt")
369
+ Textfile.write("notes.txt", "New content")
370
+ ```
371
+
372
+ ## termcore.util – General Utilities
373
+
374
+ ### Logging
375
+
376
+ ```python
377
+ from termcore import setup_logging
378
+ import logging
379
+
380
+ setup_logging("myapp", level=logging.INFO)
381
+ # Writes to ~/.local/state/myapp/app.log
382
+ ```
383
+
384
+ ### Singleton
385
+
386
+ A metaclass that enforces the singleton pattern.
387
+
388
+ ```python
389
+ from termcore import Singleton
390
+
391
+ class Config(metaclass=Singleton):
392
+ def __init__(self):
393
+ self.debug = False
394
+
395
+ a = Config()
396
+ b = Config()
397
+ assert a is b # True
398
+ ```
399
+
400
+ ### Datetime
401
+
402
+ ```python
403
+ from termcore import DateFormat, timestamp_to_date, date_to_timestamp, date_diff, today_timestamp, today_date
404
+
405
+ ts = date_to_timestamp("01.04.2025") # German format (default)
406
+ ts = date_to_timestamp("2025-04-01", DateFormat.ISO)
407
+
408
+ s = timestamp_to_date(ts) # "01.04.2025"
409
+ s = timestamp_to_date(ts, DateFormat.ISO) # "2025-04-01"
410
+
411
+ days = date_diff(ts1, ts2) # difference in days
412
+
413
+ ts_today = today_timestamp() # midnight UNIX timestamp
414
+ s_today = today_date() # "02.04.2026"
415
+ ```
416
+
417
+ ### String
418
+
419
+ ```python
420
+ from termcore import linewrap, charpos, cell_width, str_with_fixed_width
421
+
422
+ wrapped = linewrap("A long piece of text that should be wrapped.", linewidth=20)
423
+ positions = charpos("hello world", "l") # [2, 3, 9]
424
+
425
+ cell_width("abc") # 3
426
+ cell_width("日本語") # 6 - one CJK glyph occupies two terminal cells
427
+
428
+ # Exactly `width` terminal cells, truncated with an ellipsis or padded
429
+ str_with_fixed_width("a long value", 8) # "a long …"
430
+ str_with_fixed_width("a long value", 8, "right") # "…g value"
431
+ str_with_fixed_width("ok", 8, "center") # " ok "
432
+ str_with_fixed_width("日本語テキスト", 8) # "日本語… "
433
+ ```
434
+
435
+ Widths are counted in terminal cells rather than in code points, so a column stays aligned whatever the data contains. A double-width glyph cannot be split, so where one would straddle the boundary the result is padded with a space to reach the requested width exactly.
436
+
437
+ ### Index Navigation
438
+
439
+ ```python
440
+ from termcore import next_index, clamped_index
441
+
442
+ # Navigate a list of 5 items, wrapping around at edges
443
+ idx = next_index(current_index=4, length=5, direction=1) # 0 (wraps)
444
+ idx = next_index(current_index=0, length=5, direction=-1) # 4 (wraps)
445
+
446
+ # Clamp at the boundaries instead of wrapping
447
+ idx = clamped_index(current_index=4, length=5, direction=1) # 4
448
+
449
+ # An empty list has no valid index
450
+ idx = next_index(current_index=0, length=0) # 0
451
+ ```
452
+
453
+ ### Validation
454
+
455
+ ```python
456
+ from termcore import is_number
457
+
458
+ is_number("3.14") # True
459
+ is_number("abc") # False
460
+ is_number(None) # False
461
+ ```
462
+
463
+ ### Debug Decorators
464
+
465
+ ```python
466
+ from termcore import print_arguments, timing, timing_ns
467
+
468
+ @print_arguments
469
+ def add(a: int, b: int) -> int:
470
+ return a + b
471
+
472
+ add(3, 5)
473
+ # Logged at debug level on the `termcore.util.debug` logger:
474
+ # Function add called
475
+ # Args: (3, 5)
476
+ # Kwargs: {}
477
+ # Function add returns: 8
478
+
479
+
480
+ @timing() # seconds
481
+ @timing_ns() # nanoseconds
482
+ def heavy_computation():
483
+ ...
484
+ ```
485
+
486
+ The decorators log at debug level rather than printing, so enable that logger to see them.
487
+
488
+ ## Development
489
+
490
+ ```zsh
491
+ uv venv
492
+ source .venv/bin/activate
493
+ uv pip install -e ".[dev]"
494
+
495
+ ruff check . # linting; the rule set is the style guide's baseline
496
+ basedpyright # strict type checking
497
+ pytest # tests, with coverage
498
+ ```
499
+
500
+ The ruff formatter is deliberately not configured: line breaking is hand-made and the 80-column limit is enforced by `E501`.
501
+
502
+ ## License
503
+
504
+ MIT
@@ -0,0 +1,55 @@
1
+ termcore/__init__.py,sha256=-sTFmQMycWEjQ-otXyl7qZIBC7aqIO8cAsVwsoWZy5w,136
2
+ termcore/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ termcore/cli/__init__.py,sha256=dDze7NMCAvbq5np9_JxU1SgpJ0FGjb07CakKHG6CtXo,82
4
+ termcore/cli/output.py,sha256=Fiy_WKSPNj_pMQaoRzn5IFukDe-OhSAqQpmA22kROX0,1918
5
+ termcore/io/__init__.py,sha256=qs-Qpc6Ftwrlo4hty_0sUy4IbVXHtQ-jWQKcf5AChr8,187
6
+ termcore/io/app_state_storage.py,sha256=BG7Ff-H8XyCO-DVyOVJ-u0gQGFDxDlRcyUsavql5OO8,6667
7
+ termcore/io/database.py,sha256=lpWeLa7YQlcaG6ryMK3Dd-albG-YcDxD9E8amWkHeLE,21209
8
+ termcore/io/errors.py,sha256=VyUBqum8JyignWz-39VyVgon9fDKc-mRbmq9qWCI41w,2262
9
+ termcore/io/file.py,sha256=LStUYFEO_CkkObvDJTlYyS0OdVikQ1_4yBskBLOSJ88,8057
10
+ termcore/io/textfile.py,sha256=bDGFz1vDEiVFSZqkXNtVoKNqWbAfQ9Uowk-oWkwgqR8,2777
11
+ termcore/tui/__init__.py,sha256=1zQioSa_GTD9CDI0oe1AXJBuWWbcfFSCqnU0pgbn2Dw,343
12
+ termcore/tui/custom_bindings.py,sha256=ANLGSh1cPRgIb1ryxn_jHTe8iAOEMRuzChtyS1bJ2t4,19589
13
+ termcore/tui/question_screen.py,sha256=Y4r5PokHgK0o_s_3kZub0LbK5wrvbzXzxf0f7NJbbQk,3135
14
+ termcore/tui/theme_loader.py,sha256=LiseGo5X215lqDsYmRRqJuqhhQPOEcsXwgI8yv6VdfQ,17472
15
+ termcore/tui/custom_widgets/__init__.py,sha256=yCe0Ve2_vY8Mk-JXXKYEyfRsGHkJz8SBSnhKzmhBLqQ,75
16
+ termcore/tui/custom_widgets/custom_checkbox.py,sha256=C3ZHCdYWUCHx9m0yFY3GRBAI8BB98PGV8l8DgzOgoZM,1214
17
+ termcore/tui/custom_widgets/custom_data_table.py,sha256=ZRSB8fiwNr7yYFleeztCqqw3FntF1Y5l7hTROTs_jzI,8767
18
+ termcore/tui/custom_widgets/custom_selection.py,sha256=cIxYdGuqQ8b2EfJe7wEtXScGh_w6FCRpYs3MMuwJBA0,2648
19
+ termcore/tui/custom_widgets/multiline_footer.py,sha256=RE3CUNh1Q_JBjaCuZBHwE5LytUect5N3xmjM9k_29Nc,8221
20
+ termcore/tui/themes/__init__.py,sha256=LApCQM6Ho8FULZB2-1wVq4i5AgkIzFLRr4qqQdV-xJo,63
21
+ termcore/tui/themes/classic-black-saturated/style.css,sha256=Z01O6mC_f2oKjs1Fi_NsujtjEqqvdOzWnHJMut67wO8,1419
22
+ termcore/tui/themes/classic-black-saturated/theme.py,sha256=9LQgbhCo25Rh7J601OEvYU_nXf_ieoJpnPh9aJPfhbg,520
23
+ termcore/tui/themes/classic-black-v1/style.css,sha256=Z01O6mC_f2oKjs1Fi_NsujtjEqqvdOzWnHJMut67wO8,1419
24
+ termcore/tui/themes/classic-black-v1/theme.py,sha256=9DbXpgAI_wpRgiXSxvvyi7UTz7BsoAsLZY7_UW47PuQ,513
25
+ termcore/tui/themes/classic-black-v2/style.css,sha256=Z01O6mC_f2oKjs1Fi_NsujtjEqqvdOzWnHJMut67wO8,1419
26
+ termcore/tui/themes/classic-black-v2/theme.py,sha256=83YdFDM-TvpDcumtzHcc4Z9tzQPmszRy-Vr0gAGF_y8,605
27
+ termcore/tui/themes/classic-blue/theme.py,sha256=bdSptsI2gyKfd1HGl2liwJVI9vhPpsnPr6wXwp9NDhs,494
28
+ termcore/tui/themes/compact-gray/style.tcss,sha256=ENdB4UGVRzC8hkGR0RPppBctV7vQoHTc223RL_A9ZbA,3050
29
+ termcore/tui/themes/compact-gray/theme.py,sha256=gnj9voHh_NiRllwA4kwg37zX6-_NfHtUSgv8sSXtp6o,509
30
+ termcore/tui/themes/mnml-black/style.css,sha256=Gl3kwzTWmJL6XEIlomJS4rq7g7L1gzdK_P9u2teVowA,1398
31
+ termcore/tui/themes/mnml-black/theme.py,sha256=CD-VvI6_nKLNO2aetPKLXehwZfZB64Kj2xxUTNrxcQ0,462
32
+ termcore/tui/themes/mnml-deepblack/style.css,sha256=Gl3kwzTWmJL6XEIlomJS4rq7g7L1gzdK_P9u2teVowA,1398
33
+ termcore/tui/themes/mnml-deepblack/theme.py,sha256=dqg6qR0I-tn9nt3Pm9ulTiaf7dZXBUomvGyp0P9GcqA,466
34
+ termcore/tui/themes/pure-amber/theme.py,sha256=XxF7d0ckdUC__MHSvn5tXRdXFQzVVdLOTczKiSwFPXI,351
35
+ termcore/tui/themes/pure-black/theme.py,sha256=rYC4xMW6OvUsDzBOiR4v8wAfah3VXmcDZc9-pGaJ4PE,351
36
+ termcore/tui/themes/pure-blue/theme.py,sha256=pFvYV71YBFcSUW4uj5uSMsOxGhgKbopOoU95D2fArYw,350
37
+ termcore/tui/themes/pure-green/theme.py,sha256=irtXe99nsAjvkjiNyO-06x5Lfh_PyHPsdjlPzzEl7J4,351
38
+ termcore/tui/themes/pure-sweet16/theme.py,sha256=ZZ1-k5rrDdwwNRuYZHlpUzI4esHHTdZPJBIKkCfUQQQ,435
39
+ termcore/tui/themes/xplore-black/theme.py,sha256=JBN2UHZ1ftiniSxX4uhP2gtj1JV2dZhNCytxwteHHeU,353
40
+ termcore/tui/themes/xplore-blue/theme.py,sha256=Qahy0y371Z1md2lh8DkZQv_a6yQ7ma9lU5-Ko0AxAVM,352
41
+ termcore/tui/themes/xplore-blue-muted/theme.py,sha256=muzAP6q1eMCrkDlqi_YWRbX9rtCrcsPV_dm7EJM5FVI,358
42
+ termcore/tui/themes/xplore-teal/theme.py,sha256=_A3ecWsc6uScbEUAPVOixh0O2afo3jUkNa7HRwQ6HaQ,352
43
+ termcore/util/__init__.py,sha256=wnUtySdSvNp_nE763n2qnRiqZi8scXKYgU65MIHwikA,247
44
+ termcore/util/datetime.py,sha256=Ul-6GL6U2ECqsQdXrMI26ZCLoiK1IAY69qR0bAC1jzc,5047
45
+ termcore/util/debug.py,sha256=a1GUzvrv7IcCxtJKOw4GPiuEVl2J8JBL2wKHS-21ZHA,4891
46
+ termcore/util/index.py,sha256=14fobhL1sak4gtf-MfIL6_j__x2CmVUBjOtpvHlwn6c,2970
47
+ termcore/util/logger.py,sha256=JPV9FPa-sMOF_0ZAsR7d0RWEOwUa5c0wW9b0PouzyRw,973
48
+ termcore/util/singleton.py,sha256=UrrsmPKnVdup0H4ht_FXcpGIS-YpYuwy-YOC5WOcfjc,1384
49
+ termcore/util/string.py,sha256=dcKhc05EnvNlRkFSsRm0HAVgtfDnDQjzbM9sEQCwOzs,7506
50
+ termcore/util/validation.py,sha256=eTMCExnyh4loFuLaIWQSWWzOz3r0CZrVNEavrwYmhYc,570
51
+ termcore/util/version.py,sha256=GVWpw_m947CS-7FZG6suSPSWM_Nz8YTCO0yI8_4xIns,1688
52
+ termcore-0.2.0.dist-info/METADATA,sha256=lLyNV07DAYjd6Ljg6qqBr3oZsitTOtFRxEGIGpNmHZY,16336
53
+ termcore-0.2.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
54
+ termcore-0.2.0.dist-info/licenses/LICENSE,sha256=KGU4zmBB5SJOxn_dfYTxKVB-tRV94wl3vVLe8HiQ_X8,1072
55
+ termcore-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -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.