beautiful-python-cli 0.0.0-stage → 1.0.0
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.
- package/LICENSE +21 -0
- package/README.md +106 -2
- package/SKILL.md +178 -0
- package/examples/concurrent_progress.py +100 -0
- package/examples/interactive_prompts.py +79 -0
- package/examples/live_dashboard.py +85 -0
- package/examples/showcase.py +113 -0
- package/examples/tui_layout.py +112 -0
- package/install.sh +158 -0
- package/package.json +41 -4
- package/references/activation-tests.md +34 -0
- package/references/concurrent-progress.md +69 -0
- package/references/interaction-patterns.md +109 -0
- package/references/layouts-and-tui.md +57 -0
- package/references/prompts-and-inputs.md +91 -0
- package/references/rich-components.md +118 -0
- package/references/testing-and-accessibility.md +81 -0
- package/references/tracebacks-and-debugging.md +88 -0
- package/requirements.txt +1 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Layouts and Terminal User Interfaces (TUIs)
|
|
2
|
+
|
|
3
|
+
Use `rich.layout.Layout` to partition the terminal screen into responsive grids, sidebars, headers, and footers for dashboards and monitoring tools.
|
|
4
|
+
|
|
5
|
+
## 1. Basic Layout Hierarchy
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
from rich.layout import Layout
|
|
9
|
+
from rich.panel import Panel
|
|
10
|
+
|
|
11
|
+
layout = Layout()
|
|
12
|
+
|
|
13
|
+
# Split into header, body, and footer
|
|
14
|
+
layout.split_column(
|
|
15
|
+
Layout(name="header", size=3),
|
|
16
|
+
Layout(name="body", ratio=1),
|
|
17
|
+
Layout(name="footer", size=3),
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
# Split the body into sidebar and main area
|
|
21
|
+
layout["body"].split_row(
|
|
22
|
+
Layout(name="sidebar", ratio=1),
|
|
23
|
+
Layout(name="main", ratio=3),
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# Populate panels
|
|
27
|
+
layout["header"].update(Panel("[accent]Monitoring Dashboard[/accent]", style="bold"))
|
|
28
|
+
layout["sidebar"].update(Panel("Node Status\n• Node 1: [success]OK[/success]\n• Node 2: [warning]BUSY[/warning]"))
|
|
29
|
+
layout["main"].update(Panel("Recent Telemetry Events", border_style="cyan"))
|
|
30
|
+
layout["footer"].update(Panel("[muted]Press Ctrl+C to exit | Refresh: 1.0s[/muted]"))
|
|
31
|
+
|
|
32
|
+
console.print(layout)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## 2. Dynamic Live TUI
|
|
36
|
+
|
|
37
|
+
Combine `Layout` with `rich.live.Live` for continuously updating full-screen or embedded monitoring:
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
import time
|
|
41
|
+
from rich.live import Live
|
|
42
|
+
|
|
43
|
+
with Live(layout, console=console, screen=False, refresh_per_second=4) as live:
|
|
44
|
+
try:
|
|
45
|
+
while True:
|
|
46
|
+
# Update individual panes without re-creating the layout tree
|
|
47
|
+
layout["main"].update(render_telemetry_table())
|
|
48
|
+
time.sleep(0.25)
|
|
49
|
+
except KeyboardInterrupt:
|
|
50
|
+
pass
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## 3. Best Practices
|
|
54
|
+
- **Use `screen=False` by default**: Leaves scrollback history intact when the command terminates. Only use `screen=True` for full-screen curses-style apps.
|
|
55
|
+
- **Set fixed `size` for headers and footers**: Let the body use `ratio=1` to absorb the remaining vertical space.
|
|
56
|
+
- **Test at 80x24**: Ensure the layout degrades gracefully in small terminal windows without raising truncation errors.
|
|
57
|
+
- **Provide clean exit handling**: Catch `KeyboardInterrupt` cleanly so the terminal state restores properly.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Interactive Prompts and User Input
|
|
2
|
+
|
|
3
|
+
Use `rich.prompt` to collect interactive input from users in terminal sessions while preserving styling and validation.
|
|
4
|
+
|
|
5
|
+
## 1. Text Prompt
|
|
6
|
+
|
|
7
|
+
Collect strings with default values and styling:
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
from rich.prompt import Prompt
|
|
11
|
+
|
|
12
|
+
# Basic prompt with default
|
|
13
|
+
username = Prompt.ask("Enter username", default="admin", console=console)
|
|
14
|
+
|
|
15
|
+
# Styled prompt with required non-empty input
|
|
16
|
+
project = Prompt.ask(
|
|
17
|
+
"[bold cyan]Project name[/bold cyan]",
|
|
18
|
+
default="my-cli",
|
|
19
|
+
console=console,
|
|
20
|
+
)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 2. Constrained Choices
|
|
24
|
+
|
|
25
|
+
Restrict input to a list of allowed values:
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
env = Prompt.ask(
|
|
29
|
+
"Deployment target",
|
|
30
|
+
choices=["development", "staging", "production"],
|
|
31
|
+
default="development",
|
|
32
|
+
case_sensitive=False,
|
|
33
|
+
console=console,
|
|
34
|
+
)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Rich automatically validates user input against the choices, re-prompting with an error message if an invalid value is entered.
|
|
38
|
+
|
|
39
|
+
## 3. Confirmations (Yes/No)
|
|
40
|
+
|
|
41
|
+
Ask binary questions:
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from rich.prompt import Confirm
|
|
45
|
+
|
|
46
|
+
if Confirm.ask("Proceed with deployment?", default=True, console=console):
|
|
47
|
+
console.print("[success]Deploying…[/success]")
|
|
48
|
+
else:
|
|
49
|
+
console.print("[warning]Aborted by user.[/warning]")
|
|
50
|
+
raise SystemExit(1)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## 4. Typed Numeric Prompts
|
|
54
|
+
|
|
55
|
+
Enforce integers or floats:
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from rich.prompt import IntPrompt, FloatPrompt
|
|
59
|
+
|
|
60
|
+
port = IntPrompt.ask("Listen port", default=8080, console=console)
|
|
61
|
+
rate = FloatPrompt.ask("Sample rate", default=0.05, console=console)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## 5. Password & Secret Input
|
|
65
|
+
|
|
66
|
+
Hide input characters for credentials, tokens, or private keys:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
api_key = Prompt.ask(
|
|
70
|
+
"[accent]API Token[/accent]",
|
|
71
|
+
password=True,
|
|
72
|
+
console=console,
|
|
73
|
+
)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Never print the collected secret back to the terminal or logs.
|
|
77
|
+
|
|
78
|
+
## 6. Non-Interactive Fallbacks & Guardrails
|
|
79
|
+
|
|
80
|
+
Interactive prompts will fail or block indefinitely in CI/CD, cron jobs, or redirected stdin (`sys.stdin.isatty() is False`). Always guard prompts:
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
import sys
|
|
84
|
+
|
|
85
|
+
def get_target_environment(cli_flag: str | None) -> str:
|
|
86
|
+
if cli_flag:
|
|
87
|
+
return cli_flag
|
|
88
|
+
if not sys.stdin.isatty():
|
|
89
|
+
return "development" # Non-interactive default or fail explicitly
|
|
90
|
+
return Prompt.ask("Target environment", choices=["dev", "prod"], default="dev")
|
|
91
|
+
```
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Rich components and API selection
|
|
2
|
+
|
|
3
|
+
Use this as a focused API map. Confirm exact signatures against the Rich version already installed in the project.
|
|
4
|
+
|
|
5
|
+
## Console and themes
|
|
6
|
+
|
|
7
|
+
Create one application-level console. Keep stdout and stderr separate when the command supports machine-readable output.
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
from rich.theme import Theme
|
|
12
|
+
|
|
13
|
+
theme = Theme({
|
|
14
|
+
"success": "bold green",
|
|
15
|
+
"warning": "bold yellow",
|
|
16
|
+
"error": "bold red",
|
|
17
|
+
"info": "cyan",
|
|
18
|
+
"muted": "dim",
|
|
19
|
+
"accent": "bold bright_magenta",
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
console = Console(theme=theme)
|
|
23
|
+
error_console = Console(stderr=True, theme=theme)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Do not set `force_terminal=True` in normal application code. Let Rich detect terminal capability. Use `no_color=True` only when an explicit no-color mode is selected.
|
|
27
|
+
|
|
28
|
+
## Core renderables
|
|
29
|
+
|
|
30
|
+
| Component | Use it for |
|
|
31
|
+
|---|---|
|
|
32
|
+
| `Panel` | A cohesive summary, warning, or grouped section |
|
|
33
|
+
| `Table` | Structured rows and columns |
|
|
34
|
+
| `Rule` | Major section separators |
|
|
35
|
+
| `Columns` | Compact side-by-side renderables |
|
|
36
|
+
| `Group` | Grouping multiple renderables into one display |
|
|
37
|
+
| `Tree` | Hierarchical data such as directories or attack paths |
|
|
38
|
+
| `Layout` | Responsive screen partitioning (header, body, sidebar, footer) |
|
|
39
|
+
| `Prompt` / `Confirm` | Interactive user input with validation and defaults |
|
|
40
|
+
| `Syntax` | Source code with syntax highlighting |
|
|
41
|
+
| `Traceback` | Readable, formatted stack traces with frame suppression |
|
|
42
|
+
| `Markdown` | Render Markdown in a terminal |
|
|
43
|
+
| `Pretty` / `inspect` | Object introspection and structure formatting |
|
|
44
|
+
| `Text` | Fine-grained spans, styling, and markup-safe text |
|
|
45
|
+
|
|
46
|
+
Example:
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from rich.panel import Panel
|
|
50
|
+
from rich.table import Table
|
|
51
|
+
from rich.text import Text
|
|
52
|
+
|
|
53
|
+
table = Table(title="Findings", expand=True)
|
|
54
|
+
table.add_column("Severity", no_wrap=True)
|
|
55
|
+
table.add_column("Finding", ratio=2)
|
|
56
|
+
table.add_column("Status", justify="right")
|
|
57
|
+
|
|
58
|
+
table.add_row(
|
|
59
|
+
Text("HIGH", style="bold red"),
|
|
60
|
+
"Unauthenticated endpoint disclosure",
|
|
61
|
+
"[warning]Needs review[/warning]",
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
console.print(Panel(table, title="[accent]Assessment summary[/accent]"))
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Tables
|
|
68
|
+
|
|
69
|
+
- Use `expand=True` when the table should occupy available width; otherwise let it size naturally.
|
|
70
|
+
- Use `ratio` to allocate flexible width between descriptive columns.
|
|
71
|
+
- Use `no_wrap=True` only for short values such as IDs, severity, and status.
|
|
72
|
+
- Use `overflow="fold"` for descriptions that must remain readable; use `ellipsis` for secondary text.
|
|
73
|
+
- Right-align numeric columns.
|
|
74
|
+
- Add a table caption or explicit empty-state text when no records exist.
|
|
75
|
+
- Avoid very wide tables. Prefer a concise default view and an optional verbose/details view.
|
|
76
|
+
|
|
77
|
+
## Progress columns
|
|
78
|
+
|
|
79
|
+
Common columns:
|
|
80
|
+
|
|
81
|
+
- `SpinnerColumn`
|
|
82
|
+
- `TextColumn`
|
|
83
|
+
- `BarColumn`
|
|
84
|
+
- `TaskProgressColumn`
|
|
85
|
+
- `MofNCompleteColumn`
|
|
86
|
+
- `TimeElapsedColumn`
|
|
87
|
+
- `TimeRemainingColumn`
|
|
88
|
+
- `DownloadColumn`
|
|
89
|
+
- `TransferSpeedColumn`
|
|
90
|
+
- `FileSizeColumn`
|
|
91
|
+
|
|
92
|
+
Build a reusable `Progress` factory if multiple commands share the same style. Use only columns whose values are meaningful for the task.
|
|
93
|
+
|
|
94
|
+
## Logging
|
|
95
|
+
|
|
96
|
+
Use `rich.logging.RichHandler` with the standard `logging` module when possible. Configure it once at the CLI boundary. Avoid configuring handlers from imported library modules.
|
|
97
|
+
|
|
98
|
+
## Windows and UTF-8 terminal compatibility
|
|
99
|
+
|
|
100
|
+
On Windows systems (PowerShell / Command Prompt), ensure UTF-8 encoding is enabled to avoid `UnicodeEncodeError`:
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
import sys
|
|
104
|
+
|
|
105
|
+
# Ensure UTF-8 output encoding for terminals
|
|
106
|
+
if sys.platform == "win32" and sys.stdout.encoding.lower() != "utf-8":
|
|
107
|
+
try:
|
|
108
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
|
109
|
+
sys.stderr.reconfigure(encoding="utf-8")
|
|
110
|
+
except AttributeError:
|
|
111
|
+
pass
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Rich natively handles Windows console color (VT100) via colorama integration. Avoid manual ANSI sequences.
|
|
115
|
+
|
|
116
|
+
## Dependency policy
|
|
117
|
+
|
|
118
|
+
Rich is the core dependency. Typer, Click, Questionary, Loguru, and PyFiglet are optional. Do not add them merely for decoration; use the project's existing CLI and logging stack.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Testing, accessibility, and output contracts
|
|
2
|
+
|
|
3
|
+
## Terminal and redirected output
|
|
4
|
+
|
|
5
|
+
- Let Rich detect terminal capability; do not force ANSI output by default.
|
|
6
|
+
- Disable animated progress when `console.is_terminal` is false.
|
|
7
|
+
- Respect `NO_COLOR` and explicit `--no-color`.
|
|
8
|
+
- Test SSH, CI, redirected stdout, and narrow terminal widths.
|
|
9
|
+
- Do not rely on Unicode symbols being available. Use text labels as a fallback.
|
|
10
|
+
- Never use color as the only indicator of severity or success.
|
|
11
|
+
|
|
12
|
+
## stdout, stderr, and JSON
|
|
13
|
+
|
|
14
|
+
If stdout is a machine-readable stream, keep it free of banners, progress bars, logs, and status messages.
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
import json
|
|
18
|
+
import sys
|
|
19
|
+
from rich.console import Console
|
|
20
|
+
|
|
21
|
+
data_console = Console(file=sys.stdout, no_color=True)
|
|
22
|
+
diagnostic_console = Console(file=sys.stderr)
|
|
23
|
+
|
|
24
|
+
if args.json:
|
|
25
|
+
json.dump(result, sys.stdout)
|
|
26
|
+
sys.stdout.write("\n")
|
|
27
|
+
else:
|
|
28
|
+
render_human_result(data_console, result)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Send progress and diagnostics to stderr when stdout is reserved for JSON, CSV, or another pipeable format. Keep `--quiet`, `--plain`, and `--json` behavior documented and tested.
|
|
32
|
+
|
|
33
|
+
## Narrow widths
|
|
34
|
+
|
|
35
|
+
- Avoid hardcoded terminal widths in normal rendering.
|
|
36
|
+
- Use flexible table columns and wrapping.
|
|
37
|
+
- Test at widths such as 40, 60, 80, and 120 columns.
|
|
38
|
+
- Keep essential identifiers visible; wrap or truncate only secondary descriptions.
|
|
39
|
+
|
|
40
|
+
## Snapshot and unit testing
|
|
41
|
+
|
|
42
|
+
Rich supports an in-memory console:
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from io import StringIO
|
|
46
|
+
from rich.console import Console
|
|
47
|
+
|
|
48
|
+
buffer = StringIO()
|
|
49
|
+
test_console = Console(
|
|
50
|
+
file=buffer,
|
|
51
|
+
force_terminal=True,
|
|
52
|
+
width=80,
|
|
53
|
+
record=True,
|
|
54
|
+
)
|
|
55
|
+
test_console.print("[success]Completed[/success]")
|
|
56
|
+
|
|
57
|
+
plain_text = test_console.export_text(styles=False)
|
|
58
|
+
assert "Completed" in plain_text
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Test semantic content, not brittle ANSI escape sequences. Add tests for:
|
|
62
|
+
- empty result sets
|
|
63
|
+
- success and failure states
|
|
64
|
+
- exceptions and Ctrl-C/cancellation
|
|
65
|
+
- `--no-color` / `NO_COLOR`
|
|
66
|
+
- non-TTY and redirected output
|
|
67
|
+
- JSON purity
|
|
68
|
+
- narrow terminal widths
|
|
69
|
+
- progress completion and cleanup
|
|
70
|
+
|
|
71
|
+
For deterministic snapshots, fix time values and avoid asserting spinner animation frames.
|
|
72
|
+
|
|
73
|
+
## UX checklist
|
|
74
|
+
|
|
75
|
+
- Can a user identify the current operation immediately?
|
|
76
|
+
- Is the difference between unknown progress and measurable progress clear?
|
|
77
|
+
- Are success, warning, and failure distinguishable without color?
|
|
78
|
+
- Are tables readable when the terminal is narrow?
|
|
79
|
+
- Can users copy results without decorative noise?
|
|
80
|
+
- Does the application still work when output is piped?
|
|
81
|
+
- Does every spinner/live display stop on all exit paths?
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Tracebacks, Syntax, and Introspection
|
|
2
|
+
|
|
3
|
+
Use Rich's diagnostics to make errors actionable and debugging intuitive without leaking internal implementation details to end users.
|
|
4
|
+
|
|
5
|
+
## 1. Polished Tracebacks
|
|
6
|
+
|
|
7
|
+
Install Rich's traceback handler at the application boundary (entry point):
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
from rich.traceback import install
|
|
11
|
+
|
|
12
|
+
# Production mode: concise, syntax-highlighted tracebacks with suppressed internal frames
|
|
13
|
+
install(
|
|
14
|
+
show_locals=False,
|
|
15
|
+
max_frames=5,
|
|
16
|
+
suppress=["click", "typer", "urllib3", "requests"],
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
# Debug mode (--debug / --verbose): include local variable values for troubleshooting
|
|
20
|
+
if debug_mode:
|
|
21
|
+
install(show_locals=True, max_frames=20)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Guidelines
|
|
25
|
+
- In production, keep `show_locals=False` to prevent leaking sensitive variables (tokens, passwords, PII) in stack dumps.
|
|
26
|
+
- Suppress framework frames (`click`, `typer`, `asyncio`) to direct attention to user-authored code.
|
|
27
|
+
- For expected operational errors (e.g. file not found, bad network connection), print a concise error panel instead of dumping a full stack trace:
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
try:
|
|
31
|
+
connect_to_server(host)
|
|
32
|
+
except ConnectionError as err:
|
|
33
|
+
error_console.print(
|
|
34
|
+
Panel(
|
|
35
|
+
f"[error]Could not connect to {host}:[/error] {err}\n"
|
|
36
|
+
"[muted]Check your network connection or verify --host.[/muted]",
|
|
37
|
+
title="[error]Connection Failed[/error]",
|
|
38
|
+
border_style="red",
|
|
39
|
+
)
|
|
40
|
+
)
|
|
41
|
+
raise SystemExit(1)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## 2. Syntax Highlighting
|
|
45
|
+
|
|
46
|
+
Render snippets of Python, JSON, YAML, or SQL directly in the terminal:
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from rich.syntax import Syntax
|
|
50
|
+
|
|
51
|
+
code_snippet = """def hello(name: str) -> str:
|
|
52
|
+
return f"Hello, {name}!"
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
syntax = Syntax(
|
|
56
|
+
code_snippet,
|
|
57
|
+
lexer="python",
|
|
58
|
+
theme="monokai",
|
|
59
|
+
line_numbers=True,
|
|
60
|
+
word_wrap=True,
|
|
61
|
+
)
|
|
62
|
+
console.print(syntax)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## 3. Object Introspection with `inspect`
|
|
66
|
+
|
|
67
|
+
Debug complex objects, types, or modules interactively:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from rich import inspect
|
|
71
|
+
|
|
72
|
+
# Inspect attributes and methods
|
|
73
|
+
inspect(my_object, methods=True)
|
|
74
|
+
|
|
75
|
+
# Inspect with docstrings and help
|
|
76
|
+
inspect(my_object, help=True)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## 4. Pretty-Printing Structures
|
|
80
|
+
|
|
81
|
+
Format complex dictionaries, lists, or dataclasses without messy `pprint`:
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
from rich.pretty import pprint
|
|
85
|
+
|
|
86
|
+
data = {"user": "alice", "roles": ["admin", "developer"], "active": True}
|
|
87
|
+
pprint(data, indent_guides=True, expand_all=False)
|
|
88
|
+
```
|
package/requirements.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
rich>=13.7
|