rich-transient 0.1.0__tar.gz → 0.1.1__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.
@@ -1,10 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rich-transient
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: Reusable Rich-based braille spinner and transient live panel for CLI output
5
- Project-URL: Homepage, https://github.com/your-org/rich-transient
6
- Project-URL: Repository, https://github.com/your-org/rich-transient
7
- Project-URL: Documentation, https://github.com/your-org/rich-transient#readme
5
+ Project-URL: Homepage, https://github.com/maravedi/rich-transient
6
+ Project-URL: Repository, https://github.com/maravedi/rich-transient
7
+ Project-URL: Documentation, https://github.com/maravedi/rich-transient#readme
8
+ Author: David Frazer <david.frazer336@gmail.com>
8
9
  License-Expression: MIT
9
10
  License-File: LICENSE
10
11
  Keywords: cli,live,panel,progress,rich,spinner,terminal
@@ -17,7 +18,7 @@ Classifier: Programming Language :: Python :: 3.11
17
18
  Classifier: Programming Language :: Python :: 3.12
18
19
  Classifier: Programming Language :: Python :: 3.13
19
20
  Classifier: Topic :: Software Development :: User Interfaces
20
- Requires-Python: >=3.11
21
+ Requires-Python: >=3.12
21
22
  Requires-Dist: rich>=13.7.1
22
23
  Description-Content-Type: text/markdown
23
24
 
@@ -38,13 +39,7 @@ pip install rich-transient
38
39
 
39
40
  **Standalone (from this repo):**
40
41
  ```bash
41
- pip install -e /path/to/AutoIaC/rich_transient
42
- ```
43
-
44
- **As part of AutoIaC:**
45
- ```bash
46
- pip install -e /path/to/AutoIaC
47
- # installs both auto_iac and rich_transient
42
+ pip install -e ./rich_transient
48
43
  ```
49
44
 
50
45
  ---
@@ -231,15 +226,124 @@ with transient_live_panel("Custom", config=custom) as panel:
231
226
 
232
227
  ---
233
228
 
229
+ ## CLI styling helpers (section rules and key-value panels)
230
+
231
+ For consistent command headers and summary blocks (e.g. run settings, completion messages), use semantic style constants and helpers so all commands share the same look.
232
+
233
+ **Semantic style constants** (use with `border_style=` or `style=`):
234
+
235
+ - `STYLE_SECTION` — `"blue"` (section headers, info panels)
236
+ - `STYLE_SUCCESS` — `"green"` (completion, success panels)
237
+ - `STYLE_WARNING` — `"yellow"` (warnings)
238
+ - `STYLE_DIM` — `"dim"` (separators)
239
+
240
+ **Section header (Rule):**
241
+
242
+ ```python
243
+ from rich.console import Console
244
+ from rich_transient import section_rule, dim_rule, STYLE_SUCCESS
245
+
246
+ console = Console()
247
+
248
+ console.print(section_rule("FETCH (THE FEED)"))
249
+ # ... content ...
250
+ console.print(dim_rule())
251
+
252
+ console.print(section_rule("Fetch complete", style=STYLE_SUCCESS))
253
+ ```
254
+
255
+ **Key-value panel** (static summary box with consistent padding and border):
256
+
257
+ ```python
258
+ from rich_transient import key_value_panel, STYLE_SECTION, STYLE_SUCCESS
259
+
260
+ # From (label, value) pairs
261
+ console.print(key_value_panel([
262
+ ("Backend", "sumologic"),
263
+ ("Hours Back", "24"),
264
+ ("Limit", "10000"),
265
+ ], title="[bold blue]Run settings[/]", border_style=STYLE_SECTION))
266
+
267
+ # Preformatted lines (full markup control)
268
+ console.print(key_value_panel([
269
+ " [bold]Logs saved to:[/] [cyan]/path/to/logs[/]",
270
+ " [bold]Next steps:[/]",
271
+ " [dim]# Run analysis:[/]",
272
+ " [cyan]ngate analyze /path/to/logs/[/]",
273
+ ], border_style=STYLE_SUCCESS))
274
+ ```
275
+
276
+ Use these with `transient_live_panel` so that after the transient panel clears, your final summary uses the same `STYLE_SUCCESS` and `key_value_panel` for a consistent “done” block.
277
+
278
+ ---
279
+
280
+ ## Themes
281
+
282
+ The module provides **themes** so you can keep the same semantic roles (section, success, warning, dim) but switch palettes. Use a themed console and pass **theme style names** (`"section"`, `"success"`, `"warning"`, `"dim"`) to `section_rule`, `key_value_panel`, and `Panel`/`Rule`; the theme maps those names to colors.
283
+
284
+ **Built-in themes:**
285
+
286
+ | Theme name | Description |
287
+ |------------------|-------------|
288
+ | `default` / `ngate` | Blue sections, green success, yellow warning (current nGate-style). |
289
+ | `muted` | Softer dim blue/green/yellow; good for low-contrast terminals. |
290
+ | `high_contrast` | Bold bright blue/green/yellow for accessibility. |
291
+ | `mono` | No color: bold for section/success, italic for warning, dim for separator. |
292
+ | `nord` | [Nord](https://www.nordtheme.com/) palette (blue, green, yellow). |
293
+ | `dracula` | [Dracula](https://draculatheme.com/) palette (purple, green, yellow). |
294
+
295
+ **Using a theme:**
296
+
297
+ ```python
298
+ from rich_transient import (
299
+ themed_console,
300
+ get_theme,
301
+ section_rule,
302
+ key_value_panel,
303
+ THEME_STYLE_SECTION,
304
+ THEME_STYLE_SUCCESS,
305
+ )
306
+
307
+ # Option 1: themed_console (one-liner)
308
+ console = themed_console("muted")
309
+ console.print(section_rule("Fetch complete", style=THEME_STYLE_SECTION))
310
+ console.print(key_value_panel([("Output", "/path/to/logs")], border_style=THEME_STYLE_SUCCESS))
311
+
312
+ # Option 2: get_theme with your own Console
313
+ from rich.console import Console
314
+ console = Console(theme=get_theme("nord"))
315
+ console.print(section_rule("Running", style="section"))
316
+ ```
317
+
318
+ **Theme style name constants:** `THEME_STYLE_SECTION`, `THEME_STYLE_SUCCESS`, `THEME_STYLE_WARNING`, `THEME_STYLE_DIM` — use these (or the strings `"section"`, `"success"`, etc.) when printing with a themed console so the theme supplies the actual color.
319
+
320
+ **Without a theme:** you can still pass raw colors to the helpers, e.g. `section_rule("Title", style=STYLE_SECTION)` (or `style="blue"`). That works with any Console.
321
+
322
+ ---
323
+
234
324
  ## API summary
235
325
 
236
326
  | Export | Type | Description |
237
327
  |--------|------|-------------|
238
328
  | `SPINNER_BRAILLE` | `tuple[str, ...]` | Braille spinner frames. |
239
329
  | `LIVE_REFRESH_PER_SECOND` | `float` | Default refresh rate for live displays. |
330
+ | `STYLE_SECTION` | `str` | `"blue"` for section headers and info panels. |
331
+ | `STYLE_SUCCESS` | `str` | `"green"` for completion/success. |
332
+ | `STYLE_WARNING` | `str` | `"yellow"` for warnings. |
333
+ | `STYLE_DIM` | `str` | `"dim"` for separators. |
334
+ | `THEMES` | `dict[str, Theme]` | Built-in themes: default, ngate, muted, high_contrast, mono, nord, dracula. |
335
+ | `THEME_STYLE_SECTION` | `str` | Theme key `"section"` for section headers/panels. |
336
+ | `THEME_STYLE_SUCCESS` | `str` | Theme key `"success"` for completion/success. |
337
+ | `THEME_STYLE_WARNING` | `str` | Theme key `"warning"` for warnings. |
338
+ | `THEME_STYLE_DIM` | `str` | Theme key `"dim"` for separators. |
339
+ | `get_theme(name)` | `(str) -> Theme` | Return a built-in theme by name. |
340
+ | `themed_console(theme_name, **kwargs)` | `() -> Console` | Console using a built-in theme. |
240
341
  | `get_braille_frame()` | `() -> int` | Current animation frame index for use with `SPINNER_BRAILLE`. |
241
342
  | `braille_spinner_for_status()` | `() -> str` | Registers braille spinner with Rich and returns `"braille"` for `console.status(spinner=...)`. |
242
343
  | `register_braille_spinner()` | `() -> None` | Idempotent registration of braille spinner in Rich's SPINNERS. |
344
+ | `section_rule(title, style=...)` | `() -> Rule` | Rule with bold section title. |
345
+ | `dim_rule()` | `() -> Rule` | Dim Rule separator. |
346
+ | `key_value_panel(lines, ...)` | `() -> Panel` | Panel from (label, value) pairs or preformatted lines; optional title, border_style, padding. |
243
347
  | `TransientPanelConfig` | dataclass | Panel configuration (max_lines, display_lines, border_style, etc.). |
244
348
  | `TRANSIENT_PANEL_PRESETS` | `dict[str, TransientPanelConfig]` | `"default"` and `"streaming"` presets. |
245
349
  | `transient_live_panel(...)` | context manager | Yields an object with `append`, `set_status`, `run_task`. |
@@ -0,0 +1,19 @@
1
+ [[source]]
2
+ url = "https://pypi.org/simple"
3
+ verify_ssl = true
4
+ name = "pypi"
5
+
6
+ [packages]
7
+ rich = ">=13.7.1"
8
+
9
+ [dev-packages]
10
+ build = "*"
11
+ twine = "*"
12
+
13
+ [scripts]
14
+ build = "python -m build"
15
+ publish = "twine upload dist/*"
16
+ release = "python scripts/release.py"
17
+
18
+ [requires]
19
+ python_version = "3.12"
@@ -15,13 +15,7 @@ pip install rich-transient
15
15
 
16
16
  **Standalone (from this repo):**
17
17
  ```bash
18
- pip install -e /path/to/AutoIaC/rich_transient
19
- ```
20
-
21
- **As part of AutoIaC:**
22
- ```bash
23
- pip install -e /path/to/AutoIaC
24
- # installs both auto_iac and rich_transient
18
+ pip install -e ./rich_transient
25
19
  ```
26
20
 
27
21
  ---
@@ -208,15 +202,124 @@ with transient_live_panel("Custom", config=custom) as panel:
208
202
 
209
203
  ---
210
204
 
205
+ ## CLI styling helpers (section rules and key-value panels)
206
+
207
+ For consistent command headers and summary blocks (e.g. run settings, completion messages), use semantic style constants and helpers so all commands share the same look.
208
+
209
+ **Semantic style constants** (use with `border_style=` or `style=`):
210
+
211
+ - `STYLE_SECTION` — `"blue"` (section headers, info panels)
212
+ - `STYLE_SUCCESS` — `"green"` (completion, success panels)
213
+ - `STYLE_WARNING` — `"yellow"` (warnings)
214
+ - `STYLE_DIM` — `"dim"` (separators)
215
+
216
+ **Section header (Rule):**
217
+
218
+ ```python
219
+ from rich.console import Console
220
+ from rich_transient import section_rule, dim_rule, STYLE_SUCCESS
221
+
222
+ console = Console()
223
+
224
+ console.print(section_rule("FETCH (THE FEED)"))
225
+ # ... content ...
226
+ console.print(dim_rule())
227
+
228
+ console.print(section_rule("Fetch complete", style=STYLE_SUCCESS))
229
+ ```
230
+
231
+ **Key-value panel** (static summary box with consistent padding and border):
232
+
233
+ ```python
234
+ from rich_transient import key_value_panel, STYLE_SECTION, STYLE_SUCCESS
235
+
236
+ # From (label, value) pairs
237
+ console.print(key_value_panel([
238
+ ("Backend", "sumologic"),
239
+ ("Hours Back", "24"),
240
+ ("Limit", "10000"),
241
+ ], title="[bold blue]Run settings[/]", border_style=STYLE_SECTION))
242
+
243
+ # Preformatted lines (full markup control)
244
+ console.print(key_value_panel([
245
+ " [bold]Logs saved to:[/] [cyan]/path/to/logs[/]",
246
+ " [bold]Next steps:[/]",
247
+ " [dim]# Run analysis:[/]",
248
+ " [cyan]ngate analyze /path/to/logs/[/]",
249
+ ], border_style=STYLE_SUCCESS))
250
+ ```
251
+
252
+ Use these with `transient_live_panel` so that after the transient panel clears, your final summary uses the same `STYLE_SUCCESS` and `key_value_panel` for a consistent “done” block.
253
+
254
+ ---
255
+
256
+ ## Themes
257
+
258
+ The module provides **themes** so you can keep the same semantic roles (section, success, warning, dim) but switch palettes. Use a themed console and pass **theme style names** (`"section"`, `"success"`, `"warning"`, `"dim"`) to `section_rule`, `key_value_panel`, and `Panel`/`Rule`; the theme maps those names to colors.
259
+
260
+ **Built-in themes:**
261
+
262
+ | Theme name | Description |
263
+ |------------------|-------------|
264
+ | `default` / `ngate` | Blue sections, green success, yellow warning (current nGate-style). |
265
+ | `muted` | Softer dim blue/green/yellow; good for low-contrast terminals. |
266
+ | `high_contrast` | Bold bright blue/green/yellow for accessibility. |
267
+ | `mono` | No color: bold for section/success, italic for warning, dim for separator. |
268
+ | `nord` | [Nord](https://www.nordtheme.com/) palette (blue, green, yellow). |
269
+ | `dracula` | [Dracula](https://draculatheme.com/) palette (purple, green, yellow). |
270
+
271
+ **Using a theme:**
272
+
273
+ ```python
274
+ from rich_transient import (
275
+ themed_console,
276
+ get_theme,
277
+ section_rule,
278
+ key_value_panel,
279
+ THEME_STYLE_SECTION,
280
+ THEME_STYLE_SUCCESS,
281
+ )
282
+
283
+ # Option 1: themed_console (one-liner)
284
+ console = themed_console("muted")
285
+ console.print(section_rule("Fetch complete", style=THEME_STYLE_SECTION))
286
+ console.print(key_value_panel([("Output", "/path/to/logs")], border_style=THEME_STYLE_SUCCESS))
287
+
288
+ # Option 2: get_theme with your own Console
289
+ from rich.console import Console
290
+ console = Console(theme=get_theme("nord"))
291
+ console.print(section_rule("Running", style="section"))
292
+ ```
293
+
294
+ **Theme style name constants:** `THEME_STYLE_SECTION`, `THEME_STYLE_SUCCESS`, `THEME_STYLE_WARNING`, `THEME_STYLE_DIM` — use these (or the strings `"section"`, `"success"`, etc.) when printing with a themed console so the theme supplies the actual color.
295
+
296
+ **Without a theme:** you can still pass raw colors to the helpers, e.g. `section_rule("Title", style=STYLE_SECTION)` (or `style="blue"`). That works with any Console.
297
+
298
+ ---
299
+
211
300
  ## API summary
212
301
 
213
302
  | Export | Type | Description |
214
303
  |--------|------|-------------|
215
304
  | `SPINNER_BRAILLE` | `tuple[str, ...]` | Braille spinner frames. |
216
305
  | `LIVE_REFRESH_PER_SECOND` | `float` | Default refresh rate for live displays. |
306
+ | `STYLE_SECTION` | `str` | `"blue"` for section headers and info panels. |
307
+ | `STYLE_SUCCESS` | `str` | `"green"` for completion/success. |
308
+ | `STYLE_WARNING` | `str` | `"yellow"` for warnings. |
309
+ | `STYLE_DIM` | `str` | `"dim"` for separators. |
310
+ | `THEMES` | `dict[str, Theme]` | Built-in themes: default, ngate, muted, high_contrast, mono, nord, dracula. |
311
+ | `THEME_STYLE_SECTION` | `str` | Theme key `"section"` for section headers/panels. |
312
+ | `THEME_STYLE_SUCCESS` | `str` | Theme key `"success"` for completion/success. |
313
+ | `THEME_STYLE_WARNING` | `str` | Theme key `"warning"` for warnings. |
314
+ | `THEME_STYLE_DIM` | `str` | Theme key `"dim"` for separators. |
315
+ | `get_theme(name)` | `(str) -> Theme` | Return a built-in theme by name. |
316
+ | `themed_console(theme_name, **kwargs)` | `() -> Console` | Console using a built-in theme. |
217
317
  | `get_braille_frame()` | `() -> int` | Current animation frame index for use with `SPINNER_BRAILLE`. |
218
318
  | `braille_spinner_for_status()` | `() -> str` | Registers braille spinner with Rich and returns `"braille"` for `console.status(spinner=...)`. |
219
319
  | `register_braille_spinner()` | `() -> None` | Idempotent registration of braille spinner in Rich's SPINNERS. |
320
+ | `section_rule(title, style=...)` | `() -> Rule` | Rule with bold section title. |
321
+ | `dim_rule()` | `() -> Rule` | Dim Rule separator. |
322
+ | `key_value_panel(lines, ...)` | `() -> Panel` | Panel from (label, value) pairs or preformatted lines; optional title, border_style, padding. |
220
323
  | `TransientPanelConfig` | dataclass | Panel configuration (max_lines, display_lines, border_style, etc.). |
221
324
  | `TRANSIENT_PANEL_PRESETS` | `dict[str, TransientPanelConfig]` | `"default"` and `"streaming"` presets. |
222
325
  | `transient_live_panel(...)` | context manager | Yields an object with `append`, `set_status`, `run_task`. |
@@ -1,32 +1,35 @@
1
- [build-system]
2
- requires = ["hatchling"]
3
- build-backend = "hatchling.build"
4
-
5
- [project]
6
- name = "rich-transient"
7
- version = "0.1.0"
8
- description = "Reusable Rich-based braille spinner and transient live panel for CLI output"
9
- readme = "README.md"
10
- license = "MIT"
11
- requires-python = ">=3.11"
12
- dependencies = ["rich>=13.7.1"]
13
- keywords = ["rich", "cli", "spinner", "live", "panel", "terminal", "progress"]
14
- classifiers = [
15
- "Development Status :: 4 - Beta",
16
- "Intended Audience :: Developers",
17
- "License :: OSI Approved :: MIT License",
18
- "Operating System :: OS Independent",
19
- "Programming Language :: Python :: 3",
20
- "Programming Language :: Python :: 3.11",
21
- "Programming Language :: Python :: 3.12",
22
- "Programming Language :: Python :: 3.13",
23
- "Topic :: Software Development :: User Interfaces",
24
- ]
25
-
26
- [project.urls]
27
- Homepage = "https://github.com/your-org/rich-transient"
28
- Repository = "https://github.com/your-org/rich-transient"
29
- Documentation = "https://github.com/your-org/rich-transient#readme"
30
-
31
- [tool.hatch.build.targets.wheel]
32
- packages = ["rich_transient"]
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "rich-transient"
7
+ version = "0.1.1"
8
+ description = "Reusable Rich-based braille spinner and transient live panel for CLI output"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.12"
12
+ authors = [
13
+ { name = "David Frazer <david.frazer336@gmail.com>" }
14
+ ]
15
+ dependencies = ["rich>=13.7.1"]
16
+ keywords = ["rich", "cli", "spinner", "live", "panel", "terminal", "progress"]
17
+ classifiers = [
18
+ "Development Status :: 4 - Beta",
19
+ "Intended Audience :: Developers",
20
+ "License :: OSI Approved :: MIT License",
21
+ "Operating System :: OS Independent",
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Programming Language :: Python :: 3.13",
26
+ "Topic :: Software Development :: User Interfaces",
27
+ ]
28
+
29
+ [project.urls]
30
+ Homepage = "https://github.com/maravedi/rich-transient"
31
+ Repository = "https://github.com/maravedi/rich-transient"
32
+ Documentation = "https://github.com/maravedi/rich-transient#readme"
33
+
34
+ [tool.hatch.build.targets.wheel]
35
+ packages = ["rich_transient"]
@@ -1,8 +1,9 @@
1
- """Reusable Rich-based braille spinner and transient live panel for CLI output.
1
+ """Reusable Rich-based braille spinner, transient live panel, and CLI styling for output.
2
2
 
3
3
  This package can be used by any project that needs:
4
4
  - A braille-style status spinner (accessible, compact)
5
5
  - A transient live panel that streams output and clears on exit
6
+ - Consistent section headers, separators, and key-value panels (Rule + Panel)
6
7
 
7
8
  Dependencies: rich
8
9
  """
@@ -14,15 +15,95 @@ import time
14
15
  from contextlib import contextmanager
15
16
  from dataclasses import dataclass, fields, replace
16
17
  from types import SimpleNamespace
17
- from typing import Callable, Literal, TypeVar
18
+ from typing import Callable, Literal, Sequence, TypeVar
18
19
 
19
20
  from rich.console import Console
20
21
  from rich.live import Live
21
22
  from rich.panel import Panel
23
+ from rich.rule import Rule
22
24
  from rich.text import Text
25
+ from rich.theme import Theme
23
26
 
24
27
  T = TypeVar("T")
25
28
 
29
+ # --- Semantic style names (raw colors; use with Rule/Panel when not using a theme) ---
30
+ STYLE_SECTION: str = "blue"
31
+ STYLE_SUCCESS: str = "green"
32
+ STYLE_WARNING: str = "yellow"
33
+ STYLE_DIM: str = "dim"
34
+
35
+ # --- Theme style keys: use these with Console(theme=get_theme(...)) then style="section" etc. ---
36
+ THEME_STYLE_SECTION: str = "section"
37
+ THEME_STYLE_SUCCESS: str = "success"
38
+ THEME_STYLE_WARNING: str = "warning"
39
+ THEME_STYLE_DIM: str = "dim"
40
+
41
+ # --- Built-in themes (same keys, different palettes); use with Console(theme=get_theme("name")) ---
42
+ THEMES: dict[str, Theme] = {
43
+ "default": Theme({
44
+ THEME_STYLE_SECTION: "blue",
45
+ THEME_STYLE_SUCCESS: "green",
46
+ THEME_STYLE_WARNING: "yellow",
47
+ THEME_STYLE_DIM: "dim",
48
+ }),
49
+ "ngate": Theme({
50
+ THEME_STYLE_SECTION: "blue",
51
+ THEME_STYLE_SUCCESS: "green",
52
+ THEME_STYLE_WARNING: "yellow",
53
+ THEME_STYLE_DIM: "dim",
54
+ }),
55
+ "muted": Theme({
56
+ THEME_STYLE_SECTION: "dim blue",
57
+ THEME_STYLE_SUCCESS: "dim green",
58
+ THEME_STYLE_WARNING: "dim yellow",
59
+ THEME_STYLE_DIM: "dim",
60
+ }),
61
+ "high_contrast": Theme({
62
+ THEME_STYLE_SECTION: "bold bright_blue",
63
+ THEME_STYLE_SUCCESS: "bold bright_green",
64
+ THEME_STYLE_WARNING: "bold bright_yellow",
65
+ THEME_STYLE_DIM: "dim",
66
+ }),
67
+ "mono": Theme({
68
+ THEME_STYLE_SECTION: "bold",
69
+ THEME_STYLE_SUCCESS: "bold",
70
+ THEME_STYLE_WARNING: "italic",
71
+ THEME_STYLE_DIM: "dim",
72
+ }),
73
+ "nord": Theme({
74
+ THEME_STYLE_SECTION: "#5e81ac",
75
+ THEME_STYLE_SUCCESS: "#a3be8c",
76
+ THEME_STYLE_WARNING: "#ebcb8b",
77
+ THEME_STYLE_DIM: "dim",
78
+ }),
79
+ "dracula": Theme({
80
+ THEME_STYLE_SECTION: "#bd93f9",
81
+ THEME_STYLE_SUCCESS: "#50fa7b",
82
+ THEME_STYLE_WARNING: "#f1fa8c",
83
+ THEME_STYLE_DIM: "dim",
84
+ }),
85
+ }
86
+
87
+
88
+ def get_theme(name: str) -> Theme:
89
+ """Return a built-in theme by name. Use with Console(theme=get_theme(\"muted\")).
90
+
91
+ Available names: default, ngate, muted, high_contrast, mono, nord, dracula.
92
+ Falls back to \"default\" if name is unknown.
93
+ """
94
+ return THEMES.get(name, THEMES["default"])
95
+
96
+
97
+ def themed_console(theme_name: str = "default", **console_kwargs: object) -> Console:
98
+ """Return a Console using a built-in theme. Use theme style names in helpers (e.g. style=\"section\").
99
+
100
+ Example:
101
+ console = themed_console(\"muted\")
102
+ console.print(section_rule(\"Fetch complete\", style=\"section\"))
103
+ console.print(key_value_panel([...], border_style=\"success\"))
104
+ """
105
+ return Console(theme=get_theme(theme_name), **console_kwargs)
106
+
26
107
  # Braille spinner frames (one per refresh) so the status line visibly animates.
27
108
  SPINNER_BRAILLE: tuple[str, ...] = ("⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏")
28
109
 
@@ -96,6 +177,81 @@ TRANSIENT_PANEL_PRESETS: dict[str, TransientPanelConfig] = {
96
177
  }
97
178
 
98
179
 
180
+ # --- CLI styling helpers (section rules, key-value panels) ---
181
+
182
+
183
+ def section_rule(
184
+ title: str,
185
+ *,
186
+ style: str = STYLE_SECTION,
187
+ ) -> Rule:
188
+ """Return a Rule with a bold section title (e.g. for command headers or stage labels).
189
+
190
+ Example:
191
+ console.print(section_rule("FETCH (THE FEED)"))
192
+ console.print(section_rule("Finding top source categories", style=STYLE_SECTION))
193
+ """
194
+ return Rule(f"[bold {style}]{title}[/]", style=style)
195
+
196
+
197
+ def dim_rule() -> Rule:
198
+ """Return a dim Rule for a subtle separator between sections."""
199
+ return Rule(style=STYLE_DIM)
200
+
201
+
202
+ def key_value_panel(
203
+ lines: Sequence[tuple[str, str | None]] | Sequence[str],
204
+ *,
205
+ title: str | None = None,
206
+ border_style: str = STYLE_SECTION,
207
+ padding: tuple[int, int] = (0, 1),
208
+ label_markup: str = "[bold]",
209
+ skip_none: bool = True,
210
+ ) -> Panel:
211
+ """Build a Panel from key-value pairs or preformatted lines.
212
+
213
+ Args:
214
+ lines: Either (label, value) pairs (values shown as plain; use markup in label)
215
+ or a sequence of already-marked-up line strings.
216
+ title: Optional panel title (e.g. "[bold blue]Top source[/]").
217
+ border_style: Panel border color (STYLE_SECTION, STYLE_SUCCESS, etc.).
218
+ padding: Panel padding (default (0, 1) for compact layout).
219
+ label_markup: Markup for labels when lines are (label, value) pairs.
220
+ skip_none: When True, omit pairs whose value is None.
221
+
222
+ Example (key-value pairs):
223
+ key_value_panel([
224
+ ("Backend", "sumologic"),
225
+ ("Hours Back", "24"),
226
+ ("Limit", "10000"),
227
+ ], title="[bold blue]Run settings[/]")
228
+
229
+ Example (preformatted lines):
230
+ key_value_panel([
231
+ " [bold]Size[/] [white]1,234[/]",
232
+ " [bold]Rate[/] [cyan]0.5 GB/hour[/]",
233
+ ], border_style=STYLE_SUCCESS)
234
+ """
235
+ if not lines:
236
+ content = ""
237
+ elif isinstance(lines[0], str):
238
+ content = "\n".join(lines)
239
+ else:
240
+ parts = []
241
+ for item in lines:
242
+ label, value = item[0], item[1]
243
+ if skip_none and value is None:
244
+ continue
245
+ parts.append(f" {label_markup}{label}:[/] {value}")
246
+ content = "\n".join(parts)
247
+ return Panel(
248
+ content,
249
+ title=title,
250
+ border_style=border_style,
251
+ padding=padding,
252
+ )
253
+
254
+
99
255
  def _resolve_panel_config(
100
256
  preset: Literal["default", "streaming"] | None = None,
101
257
  config: TransientPanelConfig | None = None,
@@ -221,10 +377,24 @@ def transient_live_panel(
221
377
  __all__ = [
222
378
  "SPINNER_BRAILLE",
223
379
  "LIVE_REFRESH_PER_SECOND",
380
+ "STYLE_DIM",
381
+ "STYLE_SECTION",
382
+ "STYLE_SUCCESS",
383
+ "STYLE_WARNING",
384
+ "THEMES",
385
+ "THEME_STYLE_DIM",
386
+ "THEME_STYLE_SECTION",
387
+ "THEME_STYLE_SUCCESS",
388
+ "THEME_STYLE_WARNING",
224
389
  "TransientPanelConfig",
225
390
  "TRANSIENT_PANEL_PRESETS",
226
391
  "braille_spinner_for_status",
392
+ "dim_rule",
227
393
  "get_braille_frame",
394
+ "get_theme",
395
+ "key_value_panel",
228
396
  "register_braille_spinner",
397
+ "section_rule",
398
+ "themed_console",
229
399
  "transient_live_panel",
230
400
  ]
@@ -0,0 +1,69 @@
1
+ """Run version bump, clean dist, build, then twine upload. Used by: pipenv run release."""
2
+ from __future__ import annotations
3
+
4
+ import glob
5
+ import re
6
+ import shutil
7
+ import subprocess
8
+ import sys
9
+ from pathlib import Path
10
+
11
+ ROOT = Path(__file__).resolve().parent.parent
12
+ PYPROJECT = ROOT / "pyproject.toml"
13
+ DIST_DIR = ROOT / "dist"
14
+
15
+ VERSION_PATTERN = re.compile(r'^version\s*=\s*["\'](\d+)\.(\d+)\.(\d+)["\']', re.MULTILINE)
16
+ VERSION_REPLACEMENT = 'version = "{major}.{minor}.{patch}"'
17
+
18
+
19
+ def get_current_version() -> tuple[int, int, int]:
20
+ text = PYPROJECT.read_text(encoding="utf-8")
21
+ m = VERSION_PATTERN.search(text)
22
+ if not m:
23
+ raise SystemExit("Could not find version in pyproject.toml")
24
+ return int(m.group(1)), int(m.group(2)), int(m.group(3))
25
+
26
+
27
+ def bump_patch(major: int, minor: int, patch: int) -> tuple[int, int, int]:
28
+ return major, minor, patch + 1
29
+
30
+
31
+ def set_version(major: int, minor: int, patch: int) -> None:
32
+ new_version = f"{major}.{minor}.{patch}"
33
+ text = PYPROJECT.read_text(encoding="utf-8")
34
+ text = VERSION_PATTERN.sub(
35
+ VERSION_REPLACEMENT.format(major=major, minor=minor, patch=patch), text, count=1
36
+ )
37
+ PYPROJECT.write_text(text, encoding="utf-8")
38
+ print(f"Bumped version to {new_version}")
39
+
40
+
41
+ def clean_dist() -> None:
42
+ if DIST_DIR.exists():
43
+ shutil.rmtree(DIST_DIR)
44
+ print("Cleaned dist/")
45
+ DIST_DIR.mkdir(parents=True, exist_ok=True)
46
+
47
+
48
+ def main() -> int:
49
+ major, minor, patch = get_current_version()
50
+ major, minor, patch = bump_patch(major, minor, patch)
51
+ set_version(major, minor, patch)
52
+ clean_dist()
53
+
54
+ r = subprocess.run([sys.executable, "-m", "build"], cwd=ROOT)
55
+ if r.returncode != 0:
56
+ return r.returncode
57
+
58
+ dist_files = sorted(glob.glob(str(ROOT / "dist" / "*")))
59
+ if not dist_files:
60
+ print("No files in dist/", file=sys.stderr)
61
+ return 1
62
+ return subprocess.run(
63
+ [sys.executable, "-m", "twine", "upload", *dist_files],
64
+ cwd=ROOT,
65
+ ).returncode
66
+
67
+
68
+ if __name__ == "__main__":
69
+ sys.exit(main())
File without changes