rich-gradient-cli 0.1.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.
- rich_gradient_cli-0.1.0/PKG-INFO +107 -0
- rich_gradient_cli-0.1.0/README.md +88 -0
- rich_gradient_cli-0.1.0/pyproject.toml +31 -0
- rich_gradient_cli-0.1.0/src/rich_gradient_cli/__init__.py +96 -0
- rich_gradient_cli-0.1.0/src/rich_gradient_cli/__main__.py +6 -0
- rich_gradient_cli-0.1.0/src/rich_gradient_cli/common.py +87 -0
- rich_gradient_cli-0.1.0/src/rich_gradient_cli/help.py +231 -0
- rich_gradient_cli-0.1.0/src/rich_gradient_cli/markdown_command.py +168 -0
- rich_gradient_cli-0.1.0/src/rich_gradient_cli/panel_command.py +290 -0
- rich_gradient_cli-0.1.0/src/rich_gradient_cli/py.typed +0 -0
- rich_gradient_cli-0.1.0/src/rich_gradient_cli/rule_command.py +128 -0
- rich_gradient_cli-0.1.0/src/rich_gradient_cli/text_command.py +139 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: rich-gradient-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLI for printing text in gradient color using the rich-gradient library.
|
|
5
|
+
Author: Maxwell Ludden
|
|
6
|
+
Author-email: Maxwell Ludden <maxludden@me.com>
|
|
7
|
+
Requires-Dist: typer>=0.21.1
|
|
8
|
+
Requires-Dist: rich>=14.3.2
|
|
9
|
+
Requires-Dist: rich-color-ext>=0.1.9
|
|
10
|
+
Requires-Dist: rich-gradient>=0.3.9
|
|
11
|
+
Requires-Dist: ruff>=0.15.0
|
|
12
|
+
Requires-Dist: mkdocs>=1.6.0 ; extra == 'docs'
|
|
13
|
+
Requires-Dist: mkdocs-material>=9.5.0 ; extra == 'docs'
|
|
14
|
+
Requires-Dist: mkdocstrings>=0.25.0 ; extra == 'docs'
|
|
15
|
+
Requires-Dist: mkdocstrings-python>=1.10.0 ; extra == 'docs'
|
|
16
|
+
Requires-Python: >=3.14
|
|
17
|
+
Provides-Extra: docs
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# rich-gradient-cli
|
|
21
|
+
|
|
22
|
+
`rich-gradient-cli` is a CLI for [`rich-gradient`](https://github.com/maxludden/rich-gradient). The tool allows you to create colorful gradients in your terminal output, making it easier to visualize data or simply add some flair to your command-line applications.
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
You can install rich-gradient-cli from PyPI
|
|
27
|
+
|
|
28
|
+
### uv (recommended)
|
|
29
|
+
|
|
30
|
+
```shell
|
|
31
|
+
uv add rich-gradient-cli
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### pip
|
|
35
|
+
|
|
36
|
+
```shell
|
|
37
|
+
pip install rich-gradient-cli
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
### Print
|
|
44
|
+
|
|
45
|
+
To use the `rich-gradient-cli`, you can run the following command in your terminal:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
gradient print --colors 'red,#ff9900,#ffff00' "This is gradient text that starts red, transitions through orange, and ends in yellow\!"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+

|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
### Rule
|
|
55
|
+
|
|
56
|
+
You can also create gradient rules using the `rule` command:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
gradient rule --colors 'blue,#00ff00,cyan' "Blue to Green to Cyan Rule"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+

|
|
63
|
+
|
|
64
|
+
### Panel
|
|
65
|
+
|
|
66
|
+
To create a gradient panel, you can use the `panel` command:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
gradient panel --colors "red,#ff9999" -t "Error" --title-style "bold #ffffff" --title-align left 'This is an error message with a red to pink gradient background.'
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+

|
|
73
|
+
|
|
74
|
+
### Markdown
|
|
75
|
+
|
|
76
|
+
You can also render Markdown with gradients:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
echo "# Hello\n\n- This is **bold**.\n- This is *italic*." | gradient markdown -
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+

|
|
83
|
+
|
|
84
|
+
## Docs
|
|
85
|
+
|
|
86
|
+
Build and preview the documentation locally with MkDocs:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
uv pip install -e ".[docs]"
|
|
90
|
+
mkdocs serve
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
<div align="center">
|
|
96
|
+
<a href="https://github.com/maxludden/maxludden" style="text-decoration:none; color:inherit;">
|
|
97
|
+
<p>Designed by Max Ludden</p>
|
|
98
|
+
</a>
|
|
99
|
+
<br />
|
|
100
|
+
<a href="https://github.com/maxludden/maxludden" style="text-decoration:none; color:inherit;">
|
|
101
|
+
<img
|
|
102
|
+
src="https://github.com/maxludden/maxludden/blob/f3913ecf11f90c97435e2b55d2e43dadd51c1fe4/Images/MaxLogo-animated.svg"
|
|
103
|
+
alt="Max Ludden's Logo"
|
|
104
|
+
width="20%"
|
|
105
|
+
/>
|
|
106
|
+
</a>
|
|
107
|
+
</div>
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# rich-gradient-cli
|
|
2
|
+
|
|
3
|
+
`rich-gradient-cli` is a CLI for [`rich-gradient`](https://github.com/maxludden/rich-gradient). The tool allows you to create colorful gradients in your terminal output, making it easier to visualize data or simply add some flair to your command-line applications.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
You can install rich-gradient-cli from PyPI
|
|
8
|
+
|
|
9
|
+
### uv (recommended)
|
|
10
|
+
|
|
11
|
+
```shell
|
|
12
|
+
uv add rich-gradient-cli
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### pip
|
|
16
|
+
|
|
17
|
+
```shell
|
|
18
|
+
pip install rich-gradient-cli
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
### Print
|
|
25
|
+
|
|
26
|
+
To use the `rich-gradient-cli`, you can run the following command in your terminal:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
gradient print --colors 'red,#ff9900,#ffff00' "This is gradient text that starts red, transitions through orange, and ends in yellow\!"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+

|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
### Rule
|
|
36
|
+
|
|
37
|
+
You can also create gradient rules using the `rule` command:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
gradient rule --colors 'blue,#00ff00,cyan' "Blue to Green to Cyan Rule"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+

|
|
44
|
+
|
|
45
|
+
### Panel
|
|
46
|
+
|
|
47
|
+
To create a gradient panel, you can use the `panel` command:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
gradient panel --colors "red,#ff9999" -t "Error" --title-style "bold #ffffff" --title-align left 'This is an error message with a red to pink gradient background.'
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+

|
|
54
|
+
|
|
55
|
+
### Markdown
|
|
56
|
+
|
|
57
|
+
You can also render Markdown with gradients:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
echo "# Hello\n\n- This is **bold**.\n- This is *italic*." | gradient markdown -
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+

|
|
64
|
+
|
|
65
|
+
## Docs
|
|
66
|
+
|
|
67
|
+
Build and preview the documentation locally with MkDocs:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
uv pip install -e ".[docs]"
|
|
71
|
+
mkdocs serve
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
<div align="center">
|
|
77
|
+
<a href="https://github.com/maxludden/maxludden" style="text-decoration:none; color:inherit;">
|
|
78
|
+
<p>Designed by Max Ludden</p>
|
|
79
|
+
</a>
|
|
80
|
+
<br />
|
|
81
|
+
<a href="https://github.com/maxludden/maxludden" style="text-decoration:none; color:inherit;">
|
|
82
|
+
<img
|
|
83
|
+
src="https://github.com/maxludden/maxludden/blob/f3913ecf11f90c97435e2b55d2e43dadd51c1fe4/Images/MaxLogo-animated.svg"
|
|
84
|
+
alt="Max Ludden's Logo"
|
|
85
|
+
width="20%"
|
|
86
|
+
/>
|
|
87
|
+
</a>
|
|
88
|
+
</div>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "rich-gradient-cli"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "CLI for printing text in gradient color using the rich-gradient library."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Maxwell Ludden", email = "maxludden@me.com" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.14"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"typer>=0.21.1",
|
|
12
|
+
"rich>=14.3.2",
|
|
13
|
+
"rich-color-ext>=0.1.9",
|
|
14
|
+
"rich-gradient>=0.3.9",
|
|
15
|
+
"ruff>=0.15.0",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[project.optional-dependencies]
|
|
19
|
+
docs = [
|
|
20
|
+
"mkdocs>=1.6.0",
|
|
21
|
+
"mkdocs-material>=9.5.0",
|
|
22
|
+
"mkdocstrings>=0.25.0",
|
|
23
|
+
"mkdocstrings-python>=1.10.0",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.scripts]
|
|
27
|
+
gradient = "rich_gradient_cli:app"
|
|
28
|
+
|
|
29
|
+
[build-system]
|
|
30
|
+
requires = ["uv_build>=0.10.0,<0.11.0"]
|
|
31
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""rich-gradient CLI entry point."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from typing import Any, Sequence
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
import typer
|
|
10
|
+
|
|
11
|
+
from .common import VERSION
|
|
12
|
+
from .help import RichTyperCommand, RichTyperGroup
|
|
13
|
+
from .markdown_command import markdown_command
|
|
14
|
+
from .panel_command import panel_command
|
|
15
|
+
from .rule_command import rule_command
|
|
16
|
+
from .text_command import print_command
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class DefaultTyperGroup(RichTyperGroup):
|
|
20
|
+
"""Route unknown commands/options to the default command."""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
name: str | None = None,
|
|
25
|
+
commands: dict[str, click.Command] | Sequence[click.Command] | None = None,
|
|
26
|
+
invoke_without_command: bool = False,
|
|
27
|
+
no_args_is_help: bool | None = None,
|
|
28
|
+
subcommand_metavar: str | None = None,
|
|
29
|
+
chain: bool = False,
|
|
30
|
+
result_callback: Any | None = None,
|
|
31
|
+
*,
|
|
32
|
+
default_cmd_name: str = "print",
|
|
33
|
+
**kwargs: Any,
|
|
34
|
+
) -> None:
|
|
35
|
+
super().__init__(
|
|
36
|
+
name=name,
|
|
37
|
+
commands=commands,
|
|
38
|
+
invoke_without_command=invoke_without_command,
|
|
39
|
+
no_args_is_help=no_args_is_help,
|
|
40
|
+
subcommand_metavar=subcommand_metavar,
|
|
41
|
+
chain=chain,
|
|
42
|
+
result_callback=result_callback,
|
|
43
|
+
**kwargs,
|
|
44
|
+
)
|
|
45
|
+
self.default_cmd_name = default_cmd_name
|
|
46
|
+
|
|
47
|
+
def resolve_command(
|
|
48
|
+
self, ctx: click.Context, args: list[str]
|
|
49
|
+
) -> tuple[str | None, click.Command | None, list[str]]:
|
|
50
|
+
if args:
|
|
51
|
+
cmd = self.get_command(ctx, args[0])
|
|
52
|
+
if cmd is None or args[0].startswith("-"):
|
|
53
|
+
args.insert(0, self.default_cmd_name)
|
|
54
|
+
return super().resolve_command(ctx, args)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
app = typer.Typer(
|
|
58
|
+
cls=DefaultTyperGroup,
|
|
59
|
+
invoke_without_command=True,
|
|
60
|
+
add_completion=False,
|
|
61
|
+
help="Create gradient-rich text, panels, and markdown.",
|
|
62
|
+
rich_markup_mode="rich",
|
|
63
|
+
context_settings={"help_option_names": ["-h", "--help"], "color": True},
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@app.callback()
|
|
68
|
+
def main(
|
|
69
|
+
ctx: typer.Context,
|
|
70
|
+
version: bool = typer.Option(
|
|
71
|
+
False,
|
|
72
|
+
"--version",
|
|
73
|
+
help="Show the version and exit.",
|
|
74
|
+
is_eager=True,
|
|
75
|
+
),
|
|
76
|
+
) -> None:
|
|
77
|
+
"""CLI entry callback for version handling and default routing."""
|
|
78
|
+
if version:
|
|
79
|
+
typer.echo(f"gradient version {VERSION}")
|
|
80
|
+
raise typer.Exit()
|
|
81
|
+
if ctx.invoked_subcommand is None:
|
|
82
|
+
if ctx.args or not sys.stdin.isatty():
|
|
83
|
+
print_command(text=list(ctx.args) if ctx.args else None)
|
|
84
|
+
raise typer.Exit()
|
|
85
|
+
typer.echo(ctx.get_help())
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
app.command("print", cls=RichTyperCommand)(print_command)
|
|
89
|
+
app.command("panel", cls=RichTyperCommand)(panel_command)
|
|
90
|
+
app.command("rule", cls=RichTyperCommand)(rule_command)
|
|
91
|
+
app.command("markdown", cls=RichTyperCommand)(markdown_command)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
cli = app
|
|
95
|
+
|
|
96
|
+
__all__ = ["app", "cli"]
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""Shared helpers and configuration for the rich-gradient CLI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import List, Optional
|
|
7
|
+
|
|
8
|
+
from rich.console import Console, RenderableType
|
|
9
|
+
from rich.padding import Padding
|
|
10
|
+
from rich.style import Style
|
|
11
|
+
from rich_gradient.theme import GRADIENT_TERMINAL_THEME
|
|
12
|
+
|
|
13
|
+
console = Console()
|
|
14
|
+
VERSION = "0.3.10"
|
|
15
|
+
|
|
16
|
+
HEADER_TEXT = (
|
|
17
|
+
"[#ff5500]r[/][#ff6f00]i[/][#ff8300]c[/]"
|
|
18
|
+
"[#ff9500]h[/][#ffa600]-[/][#ffb600]g[/][#ffc500]r[/]"
|
|
19
|
+
"[#ffd400]a[/][#ffe500]d[/][#fff400]i[/][#f9ff00]e[/]"
|
|
20
|
+
"[#e2ff00]n[/][#c8ff00]t[/][#a9ff00] [/][bold #fff]CLI[/]"
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
FOOTER_TEXT = (
|
|
24
|
+
"[#ff5500]\U0001f308[/][#ff7400] [/][#ff8c00]h[/]"
|
|
25
|
+
"[#ffa100]t[/][#ffb500]t[/][#ffc700]p[/][#ffdc00]s[/]"
|
|
26
|
+
"[#ffef00]:[/][#fbff00]/[/][#dfff00]/[/][#bdff00]m[/]"
|
|
27
|
+
"[#9aff00]a[/][#73ff00]x[/][#2aff00]l[/][#00ff5c]u[/]"
|
|
28
|
+
"[#00ff83]d[/][#00ffa6]d[/][#00ffd1]e[/][#00fff3]n[/]"
|
|
29
|
+
"[#00f4ff].[/][#00e1ff]g[/][#00ccff]i[/][#00baff]t[/]"
|
|
30
|
+
"[#00a6ff]h[/][#2192ff]u[/][#3b81ff]b[/][#4c6cff].[/]"
|
|
31
|
+
"[#6061ff]i[/][#725bff]o[/][#8253ff]/[/][#9648ff]r[/]"
|
|
32
|
+
"[#a83bff]i[/][#c22eff]c[/][#e122ff]h[/][#fb0cff]-[/]"
|
|
33
|
+
"[#ff00e6]g[/][#ff00c6]r[/][#ff00a4]a[/][#ff0089]d[/]"
|
|
34
|
+
"[#ff0066]i[/][#ff004b]e[/][#ff0036]n[/][#ff0000]t[/]"
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
USAGE_PREFIX = "[bold #00ff00]Usage:[/]"
|
|
39
|
+
USAGE_PROG_STYLE = "#af00ff"
|
|
40
|
+
USAGE_CMD_STYLE = "#0099ff"
|
|
41
|
+
USAGE_BRACKET_STYLE = "#ffffff"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def parse_colors(colors: Optional[str]) -> Optional[List[str]]:
|
|
45
|
+
"""Parse comma-separated color tokens into a list."""
|
|
46
|
+
if colors is None:
|
|
47
|
+
return None
|
|
48
|
+
return [c.strip() for c in colors.split(",") if c.strip()]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def parse_style(style: Optional[str]) -> Style:
|
|
52
|
+
"""Parse a Rich style string or return a null style."""
|
|
53
|
+
if style is None:
|
|
54
|
+
return Style.null()
|
|
55
|
+
return Style.parse(style)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def export_svg(
|
|
59
|
+
renderable: RenderableType, svg_path: str, *, end: str = "\n", no_wrap: bool = False
|
|
60
|
+
) -> None:
|
|
61
|
+
"""Render a Rich renderable to an SVG file."""
|
|
62
|
+
svg_console = Console(record=True, force_terminal=True, color_system="truecolor")
|
|
63
|
+
padded = Padding(renderable, (1, 4))
|
|
64
|
+
svg_console.print(padded, end=end, no_wrap=no_wrap)
|
|
65
|
+
|
|
66
|
+
Path(svg_path).write_text(
|
|
67
|
+
svg_console.export_svg(
|
|
68
|
+
title="rich-gradient",
|
|
69
|
+
theme=GRADIENT_TERMINAL_THEME,
|
|
70
|
+
),
|
|
71
|
+
encoding="utf-8",
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
__all__ = [
|
|
76
|
+
"VERSION",
|
|
77
|
+
"console",
|
|
78
|
+
"parse_colors",
|
|
79
|
+
"parse_style",
|
|
80
|
+
"HEADER_TEXT",
|
|
81
|
+
"FOOTER_TEXT",
|
|
82
|
+
"USAGE_PREFIX",
|
|
83
|
+
"USAGE_PROG_STYLE",
|
|
84
|
+
"USAGE_CMD_STYLE",
|
|
85
|
+
"USAGE_BRACKET_STYLE",
|
|
86
|
+
"export_svg",
|
|
87
|
+
]
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"""Custom Rich help rendering for Typer commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import io
|
|
6
|
+
import re
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Iterable, List, Optional, Sequence, Tuple
|
|
9
|
+
|
|
10
|
+
import click
|
|
11
|
+
from rich import box
|
|
12
|
+
from rich.console import Console
|
|
13
|
+
from rich.panel import Panel
|
|
14
|
+
from rich.table import Table
|
|
15
|
+
from rich.text import Text
|
|
16
|
+
from typer.core import TyperCommand, TyperGroup
|
|
17
|
+
|
|
18
|
+
from .common import (
|
|
19
|
+
FOOTER_TEXT,
|
|
20
|
+
HEADER_TEXT,
|
|
21
|
+
USAGE_BRACKET_STYLE,
|
|
22
|
+
USAGE_CMD_STYLE,
|
|
23
|
+
USAGE_PREFIX,
|
|
24
|
+
USAGE_PROG_STYLE,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True)
|
|
29
|
+
class HelpStyles:
|
|
30
|
+
border: str = "#2a4fff"
|
|
31
|
+
option_name: str = "cyan"
|
|
32
|
+
metavar: str = "bold #99ff00"
|
|
33
|
+
help_text: str = "white"
|
|
34
|
+
dim: str = "dim"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
_STYLES = HelpStyles()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _usage_markup(command_path: str, pieces: Sequence[str]) -> str:
|
|
41
|
+
parts = command_path.split()
|
|
42
|
+
prog = parts[0] if parts else ""
|
|
43
|
+
subcommands = parts[1:]
|
|
44
|
+
|
|
45
|
+
def color_token(token: str) -> str:
|
|
46
|
+
if token.startswith("[") and token.endswith("]"):
|
|
47
|
+
inner = token[1:-1]
|
|
48
|
+
return (
|
|
49
|
+
f"[{USAGE_BRACKET_STYLE}][[/][{USAGE_CMD_STYLE}]"
|
|
50
|
+
f"{inner}[/][{USAGE_BRACKET_STYLE}]][/]"
|
|
51
|
+
)
|
|
52
|
+
if token.startswith("[") and token.endswith("]..."):
|
|
53
|
+
inner = token[1:-4]
|
|
54
|
+
return (
|
|
55
|
+
f"[{USAGE_BRACKET_STYLE}][[/][{USAGE_CMD_STYLE}]"
|
|
56
|
+
f"{inner}[/][{USAGE_BRACKET_STYLE}]][/]"
|
|
57
|
+
f"[{USAGE_CMD_STYLE}]...[/]"
|
|
58
|
+
)
|
|
59
|
+
if token.isupper() or re.match(r"^[A-Z_]+(\.\.\.)?$", token):
|
|
60
|
+
return f"[{USAGE_CMD_STYLE}]{token}[/]"
|
|
61
|
+
return token
|
|
62
|
+
|
|
63
|
+
colored_prog = f"[{USAGE_PROG_STYLE}]{prog}[/]" if prog else ""
|
|
64
|
+
colored_subcommands = " ".join(
|
|
65
|
+
f"[{USAGE_CMD_STYLE}]{cmd}[/]" for cmd in subcommands
|
|
66
|
+
)
|
|
67
|
+
args = " ".join(color_token(piece) for piece in pieces)
|
|
68
|
+
if args:
|
|
69
|
+
args = f" {args}"
|
|
70
|
+
usage = " ".join(p for p in [colored_prog, colored_subcommands] if p)
|
|
71
|
+
return f"{USAGE_PREFIX} {usage}{args}".strip()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _split_option_name(name: str) -> Tuple[str, str]:
|
|
75
|
+
parts = name.split()
|
|
76
|
+
if not parts:
|
|
77
|
+
return name, ""
|
|
78
|
+
last = parts[-1]
|
|
79
|
+
if not last.startswith("-") and last.isupper():
|
|
80
|
+
return " ".join(parts[:-1]), last
|
|
81
|
+
return name, ""
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _normalize_metavar(metavar: str) -> str:
|
|
85
|
+
value = metavar.strip()
|
|
86
|
+
if value.startswith("[") and value.endswith("]..."):
|
|
87
|
+
value = value[1:-4]
|
|
88
|
+
elif value.startswith("[") and value.endswith("]"):
|
|
89
|
+
value = value[1:-1]
|
|
90
|
+
if value.endswith("..."):
|
|
91
|
+
value = value[:-3]
|
|
92
|
+
return value
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _build_arguments_table(
|
|
96
|
+
args: Iterable[click.Argument], ctx: click.Context
|
|
97
|
+
) -> Optional[Table]:
|
|
98
|
+
rows: List[Tuple[str, str]] = []
|
|
99
|
+
for arg in args:
|
|
100
|
+
metavar = _normalize_metavar(arg.make_metavar(ctx))
|
|
101
|
+
rows.append((metavar, metavar))
|
|
102
|
+
|
|
103
|
+
if not rows:
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
table = Table.grid(padding=(0, 2))
|
|
107
|
+
table.add_column(justify="left", style="bold #ffff00", no_wrap=True)
|
|
108
|
+
table.add_column(justify="left", style=_STYLES.metavar)
|
|
109
|
+
for name, meta in rows:
|
|
110
|
+
table.add_row(name, meta)
|
|
111
|
+
return table
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _build_options_table(
|
|
115
|
+
options: Iterable[click.Option], ctx: click.Context
|
|
116
|
+
) -> Optional[Table]:
|
|
117
|
+
rows: List[Tuple[str, str, Text]] = []
|
|
118
|
+
for opt in options:
|
|
119
|
+
record = opt.get_help_record(ctx)
|
|
120
|
+
if record is None:
|
|
121
|
+
continue
|
|
122
|
+
name, help_text = record
|
|
123
|
+
opt_names, metavar = _split_option_name(name)
|
|
124
|
+
help_rich = Text.from_markup(help_text, style=_STYLES.help_text)
|
|
125
|
+
rows.append((opt_names, metavar, help_rich))
|
|
126
|
+
|
|
127
|
+
if not rows:
|
|
128
|
+
return None
|
|
129
|
+
|
|
130
|
+
table = Table.grid(padding=(0, 2))
|
|
131
|
+
table.add_column(justify="left", style="bold #ffff00", no_wrap=True)
|
|
132
|
+
table.add_column(justify="left", style=_STYLES.metavar, no_wrap=True)
|
|
133
|
+
table.add_column(justify="left", style=_STYLES.help_text)
|
|
134
|
+
for opt_names, metavar, help_rich in rows:
|
|
135
|
+
table.add_row(opt_names, metavar, help_rich)
|
|
136
|
+
return table
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _build_commands_table(group: click.Group) -> Optional[Table]:
|
|
140
|
+
if not group.commands:
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
table = Table.grid(padding=(0, 2))
|
|
144
|
+
table.add_column(justify="left", style="bold #ffff00", no_wrap=True)
|
|
145
|
+
table.add_column(justify="left", style=_STYLES.help_text)
|
|
146
|
+
for name, cmd in group.commands.items():
|
|
147
|
+
help_text = cmd.get_short_help_str() if cmd.help else ""
|
|
148
|
+
table.add_row(name, help_text)
|
|
149
|
+
return table
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def render_help(command: click.Command, ctx: click.Context) -> str:
|
|
153
|
+
"""Render help text for a Click command using Rich for formatting."""
|
|
154
|
+
console = Console(
|
|
155
|
+
record=True,
|
|
156
|
+
force_terminal=True,
|
|
157
|
+
color_system="truecolor",
|
|
158
|
+
file=io.StringIO(),
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
console.print(Text.from_markup(HEADER_TEXT))
|
|
162
|
+
console.print()
|
|
163
|
+
|
|
164
|
+
usage_line = _usage_markup(ctx.command_path, command.collect_usage_pieces(ctx))
|
|
165
|
+
console.print(Text.from_markup(usage_line))
|
|
166
|
+
|
|
167
|
+
if command.help:
|
|
168
|
+
console.print()
|
|
169
|
+
console.print(Text.from_markup(command.help))
|
|
170
|
+
|
|
171
|
+
args = [p for p in command.params if isinstance(p, click.Argument)]
|
|
172
|
+
options = [p for p in command.params if isinstance(p, click.Option)]
|
|
173
|
+
|
|
174
|
+
args_table = _build_arguments_table(args, ctx)
|
|
175
|
+
if args_table is not None:
|
|
176
|
+
console.print()
|
|
177
|
+
console.print(
|
|
178
|
+
Panel(
|
|
179
|
+
args_table,
|
|
180
|
+
title="[b #ffffff]Arguments[/]",
|
|
181
|
+
title_align="left",
|
|
182
|
+
border_style=_STYLES.border,
|
|
183
|
+
box=box.SQUARE,
|
|
184
|
+
)
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
options_table = _build_options_table(options, ctx)
|
|
188
|
+
if options_table is not None:
|
|
189
|
+
console.print()
|
|
190
|
+
console.print(
|
|
191
|
+
Panel(
|
|
192
|
+
options_table,
|
|
193
|
+
title="[b #ffffff]Options[/]",
|
|
194
|
+
title_align="left",
|
|
195
|
+
border_style=_STYLES.border,
|
|
196
|
+
box=box.SQUARE,
|
|
197
|
+
)
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
if isinstance(command, click.Group):
|
|
201
|
+
commands_table = _build_commands_table(command)
|
|
202
|
+
if commands_table is not None:
|
|
203
|
+
console.print()
|
|
204
|
+
console.print(
|
|
205
|
+
Panel(
|
|
206
|
+
commands_table,
|
|
207
|
+
title="[b #ffffff]Commands[/]",
|
|
208
|
+
title_align="left",
|
|
209
|
+
border_style=_STYLES.border,
|
|
210
|
+
box=box.SQUARE,
|
|
211
|
+
)
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
console.print()
|
|
215
|
+
console.print(Text.from_markup(FOOTER_TEXT))
|
|
216
|
+
|
|
217
|
+
return console.export_text(styles=True)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
class RichTyperCommand(TyperCommand):
|
|
221
|
+
"""Custom TyperCommand that uses Rich for help rendering."""
|
|
222
|
+
|
|
223
|
+
def get_help(self, ctx: click.Context) -> str: # type: ignore[override]
|
|
224
|
+
return render_help(self, ctx).rstrip("\n")
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class RichTyperGroup(TyperGroup):
|
|
228
|
+
"""Custom TyperGroup that uses Rich for help rendering."""
|
|
229
|
+
|
|
230
|
+
def get_help(self, ctx: click.Context) -> str: # type: ignore[override]
|
|
231
|
+
return render_help(self, ctx).rstrip("\n")
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""Markdown command wiring for the CLI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Literal, Optional, cast
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from rich.align import AlignMethod, VerticalAlignMethod
|
|
9
|
+
|
|
10
|
+
from rich_gradient.animated_markdown import AnimatedMarkdown
|
|
11
|
+
from rich_gradient.markdown import Markdown
|
|
12
|
+
|
|
13
|
+
from .common import console, export_svg, parse_colors, parse_style
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def markdown_command(
|
|
17
|
+
markdown: str = typer.Argument(..., metavar="MARKDOWN"),
|
|
18
|
+
colors: Optional[str] = typer.Option(
|
|
19
|
+
None,
|
|
20
|
+
"-c",
|
|
21
|
+
"--colors",
|
|
22
|
+
metavar="COLORS",
|
|
23
|
+
help=(
|
|
24
|
+
"Comma-separated list of colors for the gradient. [dim](e.g., "
|
|
25
|
+
"`[/][red]red[/][dim], [/][#ff9900]#ff9900[/][dim], [/][yellow]yellow[/][dim]`). "
|
|
26
|
+
"If no colors are provided, the color stops are automatically generated."
|
|
27
|
+
),
|
|
28
|
+
),
|
|
29
|
+
bgcolors: Optional[str] = typer.Option(
|
|
30
|
+
None,
|
|
31
|
+
"--bgcolors",
|
|
32
|
+
metavar="BGCOLORS",
|
|
33
|
+
help=(
|
|
34
|
+
"Comma-separated list of background colors for the gradient. [dim](e.g., "
|
|
35
|
+
"`[/][red]red[/][dim], [/][#ff9900]#ff9900[/][dim], [/][#ff0]#ff0[/][dim]`). "
|
|
36
|
+
"Defaults to [/][bold #fff]transparent[/][dim].[/dim]"
|
|
37
|
+
),
|
|
38
|
+
),
|
|
39
|
+
rainbow: bool = typer.Option(
|
|
40
|
+
False,
|
|
41
|
+
"-r",
|
|
42
|
+
"--rainbow",
|
|
43
|
+
help="[#ff0000]U[/][#ff3b00]s[/][#ff5100]e[/][#ff7400] [/]\
|
|
44
|
+
[#ff9000]r[/][#ffa900]a[/][#ffc000]i[/][#ffd700]n[/]\
|
|
45
|
+
[#ffee00]b[/][#f7ff00]o[/][#d3ff00]w[/][#a7ff00] [/] \
|
|
46
|
+
[#7dff00]c[/][#2eff00]o[/][#00ff64]l[/][#00ff8e]o[/][#00ffc0]r[/]\
|
|
47
|
+
[#00ffec]s[/][#00f4ff] [/][#00ddff]f[/][#00c5ff]o[/][#00afff]r[/]\
|
|
48
|
+
[#1596ff] [/][#3b81ff]t[/][#4e67ff]h[/][#675fff]e[/][#7b57ff] [/]\
|
|
49
|
+
[#924bff]g[/][#a73bff]r[/][#c72cff]a[/][#eb1cff]d[/][#ff00f2]i[/]\
|
|
50
|
+
[#ff00ce]e[/][#ff00a4]n[/][#ff0084]t[/][#ff0054].[/]",
|
|
51
|
+
),
|
|
52
|
+
hues: int = typer.Option(
|
|
53
|
+
7,
|
|
54
|
+
"--hues",
|
|
55
|
+
metavar="HUES",
|
|
56
|
+
help="The number of hues to use for a random gradient.",
|
|
57
|
+
show_default=True,
|
|
58
|
+
),
|
|
59
|
+
style: Optional[str] = typer.Option(
|
|
60
|
+
None,
|
|
61
|
+
"--style",
|
|
62
|
+
metavar="STYLE",
|
|
63
|
+
help=(
|
|
64
|
+
"The style to apply to the markdown text. [dim italic]*Only non-color styles "
|
|
65
|
+
"will be applied as the gradient's colors override color styles.[/]"
|
|
66
|
+
),
|
|
67
|
+
),
|
|
68
|
+
justify: Literal["left", "center", "right"] = typer.Option(
|
|
69
|
+
"left",
|
|
70
|
+
"-j",
|
|
71
|
+
"--justify",
|
|
72
|
+
metavar="JUSTIFY",
|
|
73
|
+
help="Justification of the markdown text. [lime](left, center, right)[/]",
|
|
74
|
+
show_default=True,
|
|
75
|
+
case_sensitive=False,
|
|
76
|
+
),
|
|
77
|
+
vertical_justify: Literal["top", "middle", "bottom"] = typer.Option(
|
|
78
|
+
"top",
|
|
79
|
+
"--vertical-justify",
|
|
80
|
+
metavar="VERTICAL_JUSTIFY",
|
|
81
|
+
help="Vertical justification of the markdown text. [lime](top, middle, bottom)[/]",
|
|
82
|
+
show_default=True,
|
|
83
|
+
case_sensitive=False,
|
|
84
|
+
),
|
|
85
|
+
no_wrap: bool = typer.Option(
|
|
86
|
+
False,
|
|
87
|
+
"--no-wrap",
|
|
88
|
+
help="Disable wrapping of markdown text.",
|
|
89
|
+
show_default=True,
|
|
90
|
+
),
|
|
91
|
+
end: str = typer.Option(
|
|
92
|
+
"\n",
|
|
93
|
+
"--end",
|
|
94
|
+
metavar="END",
|
|
95
|
+
help=(
|
|
96
|
+
"String appended after the text is printed. [dim]\\[default: '\\n'][/dim]"
|
|
97
|
+
),
|
|
98
|
+
),
|
|
99
|
+
animate: bool = typer.Option(
|
|
100
|
+
False,
|
|
101
|
+
"--animate",
|
|
102
|
+
help="Animate the gradient markdown text.",
|
|
103
|
+
),
|
|
104
|
+
duration: float = typer.Option(
|
|
105
|
+
5.0,
|
|
106
|
+
"-d",
|
|
107
|
+
"--duration",
|
|
108
|
+
metavar="DURATION",
|
|
109
|
+
help="Duration of the animation in seconds (only used if --animate).",
|
|
110
|
+
),
|
|
111
|
+
svg: Optional[str] = typer.Option(
|
|
112
|
+
None,
|
|
113
|
+
"--svg",
|
|
114
|
+
metavar="SVG",
|
|
115
|
+
help="Save output as an SVG file.",
|
|
116
|
+
),
|
|
117
|
+
) -> None:
|
|
118
|
+
"""Render markdown text with gradient colors in a rich console."""
|
|
119
|
+
if markdown == "-":
|
|
120
|
+
markdown = typer.get_text_stream("stdin").read().rstrip("\n")
|
|
121
|
+
if not markdown:
|
|
122
|
+
raise typer.UsageError("Missing markdown argument.")
|
|
123
|
+
|
|
124
|
+
_colors = parse_colors(colors)
|
|
125
|
+
_bgcolors = parse_colors(bgcolors)
|
|
126
|
+
markdown_kwargs: dict[str, Any] = {}
|
|
127
|
+
if style:
|
|
128
|
+
markdown_kwargs["style"] = parse_style(style)
|
|
129
|
+
|
|
130
|
+
justify_value = cast(AlignMethod, justify)
|
|
131
|
+
vertical_value = cast(VerticalAlignMethod, vertical_justify)
|
|
132
|
+
|
|
133
|
+
if animate and svg:
|
|
134
|
+
raise typer.UsageError("--svg is not supported with --animate.")
|
|
135
|
+
if animate and console.is_terminal is True:
|
|
136
|
+
console.clear()
|
|
137
|
+
animated = AnimatedMarkdown(
|
|
138
|
+
markdown,
|
|
139
|
+
colors=_colors,
|
|
140
|
+
rainbow=rainbow,
|
|
141
|
+
hues=hues,
|
|
142
|
+
justify=justify_value,
|
|
143
|
+
vertical_justify=vertical_value,
|
|
144
|
+
bg_colors=_bgcolors,
|
|
145
|
+
markdown_kwargs=markdown_kwargs or None,
|
|
146
|
+
animate=True,
|
|
147
|
+
duration=duration,
|
|
148
|
+
)
|
|
149
|
+
animated.run()
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
md = Markdown(
|
|
153
|
+
markdown,
|
|
154
|
+
colors=_colors,
|
|
155
|
+
rainbow=rainbow,
|
|
156
|
+
hues=hues,
|
|
157
|
+
justify=justify_value,
|
|
158
|
+
vertical_justify=vertical_value,
|
|
159
|
+
bg_colors=_bgcolors,
|
|
160
|
+
markdown_kwargs=markdown_kwargs or None,
|
|
161
|
+
)
|
|
162
|
+
if svg:
|
|
163
|
+
export_svg(md, svg, end=end, no_wrap=no_wrap)
|
|
164
|
+
return
|
|
165
|
+
console.print(md, end=end, no_wrap=no_wrap)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
__all__ = ["markdown_command"]
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
"""Panel command wiring for the CLI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from typing import Any, Literal, Optional, Tuple, cast
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
from rich.align import Align, AlignMethod, VerticalAlignMethod
|
|
10
|
+
|
|
11
|
+
from rich_gradient.animated_panel import AnimatedPanel
|
|
12
|
+
from rich_gradient.panel import Panel
|
|
13
|
+
|
|
14
|
+
from .common import console, export_svg, parse_colors, parse_style
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def panel_command(
|
|
18
|
+
renderable: str = typer.Argument(..., metavar="TEXT"),
|
|
19
|
+
colors: Optional[str] = typer.Option(
|
|
20
|
+
None,
|
|
21
|
+
"-c",
|
|
22
|
+
"--colors",
|
|
23
|
+
metavar="COLORS",
|
|
24
|
+
help=(
|
|
25
|
+
"Comma-separated list of colors for the gradient. [dim](e.g., "
|
|
26
|
+
"`[/][red]red[/][dim], [/][#ff9900]#ff9900[/][dim], [/][yellow]yellow[/][dim]`). "
|
|
27
|
+
"If no colors are provided, the color stops are automatically generated."
|
|
28
|
+
),
|
|
29
|
+
),
|
|
30
|
+
bgcolors: Optional[str] = typer.Option(
|
|
31
|
+
None,
|
|
32
|
+
"--bgcolors",
|
|
33
|
+
metavar="BGCOLORS",
|
|
34
|
+
help=(
|
|
35
|
+
"Comma-separated list of background colors for the gradient. [dim](e.g., "
|
|
36
|
+
"`[/][red]red[/][dim],[/][#ff9900]#ff9900[/][dim],[/][#ff0]#ff0[/][dim]`). "
|
|
37
|
+
"Defaults to [/][bold #fff]transparent[/][dim].[/dim]"
|
|
38
|
+
),
|
|
39
|
+
),
|
|
40
|
+
rainbow: bool = typer.Option(
|
|
41
|
+
False,
|
|
42
|
+
"-r",
|
|
43
|
+
"--rainbow",
|
|
44
|
+
help="[#ff0000]U[/][#ff3b00]s[/][#ff5100]e[/][#ff7400] [/]\
|
|
45
|
+
[#ff9000]r[/][#ffa900]a[/][#ffc000]i[/][#ffd700]n[/]\
|
|
46
|
+
[#ffee00]b[/][#f7ff00]o[/][#d3ff00]w[/][#a7ff00] [/] \
|
|
47
|
+
[#7dff00]c[/][#2eff00]o[/][#00ff64]l[/][#00ff8e]o[/][#00ffc0]r[/]\
|
|
48
|
+
[#00ffec]s[/][#00f4ff] [/][#00ddff]f[/][#00c5ff]o[/][#00afff]r[/]\
|
|
49
|
+
[#1596ff] [/][#3b81ff]t[/][#4e67ff]h[/][#675fff]e[/][#7b57ff] [/]\
|
|
50
|
+
[#924bff]g[/][#a73bff]r[/][#c72cff]a[/][#eb1cff]d[/][#ff00f2]i[/]\
|
|
51
|
+
[#ff00ce]e[/][#ff00a4]n[/][#ff0084]t[/][#ff0054].[/]",
|
|
52
|
+
),
|
|
53
|
+
hues: int = typer.Option(
|
|
54
|
+
5,
|
|
55
|
+
"--hues",
|
|
56
|
+
metavar="HUES",
|
|
57
|
+
help="The number of hues to use for a random gradient.",
|
|
58
|
+
show_default=True,
|
|
59
|
+
),
|
|
60
|
+
title: Optional[str] = typer.Option(
|
|
61
|
+
None,
|
|
62
|
+
"-t",
|
|
63
|
+
"-title",
|
|
64
|
+
"--title",
|
|
65
|
+
metavar="TITLE",
|
|
66
|
+
help="Title of the panel.",
|
|
67
|
+
),
|
|
68
|
+
title_style: str = typer.Option(
|
|
69
|
+
"bold",
|
|
70
|
+
"--title-style",
|
|
71
|
+
metavar="TITLE_STYLE",
|
|
72
|
+
help="Style of the panel title text (requires -t/--title).",
|
|
73
|
+
),
|
|
74
|
+
title_align: Literal["left", "center", "right"] = typer.Option(
|
|
75
|
+
"center",
|
|
76
|
+
"--title-align",
|
|
77
|
+
metavar="TITLE_ALIGN",
|
|
78
|
+
help="Alignment of the panel title. [lime](left, center, right)[/]",
|
|
79
|
+
show_default=True,
|
|
80
|
+
case_sensitive=False,
|
|
81
|
+
),
|
|
82
|
+
subtitle: Optional[str] = typer.Option(
|
|
83
|
+
None,
|
|
84
|
+
"-s",
|
|
85
|
+
"--subtitle",
|
|
86
|
+
metavar="SUBTITLE",
|
|
87
|
+
help="Subtitle of the panel.",
|
|
88
|
+
),
|
|
89
|
+
subtitle_style: Optional[str] = typer.Option(
|
|
90
|
+
None,
|
|
91
|
+
"--subtitle-style",
|
|
92
|
+
metavar="SUBTITLE_STYLE",
|
|
93
|
+
help="Style of the panel subtitle text (requires --subtitle).",
|
|
94
|
+
),
|
|
95
|
+
subtitle_align: Literal["left", "center", "right"] = typer.Option(
|
|
96
|
+
"right",
|
|
97
|
+
"--subtitle-align",
|
|
98
|
+
metavar="SUBTITLE_ALIGN",
|
|
99
|
+
help="Alignment of the panel subtitle. [lime](left, center, right)[/]",
|
|
100
|
+
show_default=True,
|
|
101
|
+
case_sensitive=False,
|
|
102
|
+
),
|
|
103
|
+
style: Optional[str] = typer.Option(
|
|
104
|
+
None,
|
|
105
|
+
"--style",
|
|
106
|
+
metavar="STYLE",
|
|
107
|
+
help="The style to apply to the panel.",
|
|
108
|
+
),
|
|
109
|
+
border_style: Optional[str] = typer.Option(
|
|
110
|
+
None,
|
|
111
|
+
"--border-style",
|
|
112
|
+
metavar="BORDER_STYLE",
|
|
113
|
+
help=(
|
|
114
|
+
"The style to apply to the panel border. [dim italic]*Only non-color styles "
|
|
115
|
+
"will be applied as the gradient's colors override color styles.[/]"
|
|
116
|
+
),
|
|
117
|
+
),
|
|
118
|
+
padding: Optional[str] = typer.Option(
|
|
119
|
+
"0,1",
|
|
120
|
+
"-p",
|
|
121
|
+
"--padding",
|
|
122
|
+
metavar="PADDING",
|
|
123
|
+
help="Padding inside the panel (1, 2, or 4 comma-separated integers).",
|
|
124
|
+
),
|
|
125
|
+
vertical_justify: Literal["top", "middle", "bottom"] = typer.Option(
|
|
126
|
+
"top",
|
|
127
|
+
"-V",
|
|
128
|
+
"--vertical-justify",
|
|
129
|
+
metavar="VERTICAL_JUSTIFY",
|
|
130
|
+
help="Vertical justification of the panel inner text. [lime](top, middle, bottom)[/]",
|
|
131
|
+
show_default=True,
|
|
132
|
+
case_sensitive=False,
|
|
133
|
+
),
|
|
134
|
+
text_justify: Literal["left", "center", "right"] = typer.Option(
|
|
135
|
+
"left",
|
|
136
|
+
"-J",
|
|
137
|
+
"--text-justify",
|
|
138
|
+
metavar="TEXT_JUSTIFY",
|
|
139
|
+
help="Justification of the text inside the panel. [lime](left, center, right)[/]",
|
|
140
|
+
show_default=True,
|
|
141
|
+
case_sensitive=False,
|
|
142
|
+
),
|
|
143
|
+
justify: Literal["left", "center", "right"] = typer.Option(
|
|
144
|
+
"left",
|
|
145
|
+
"-j",
|
|
146
|
+
"--justify",
|
|
147
|
+
metavar="JUSTIFY",
|
|
148
|
+
help="Justification of the panel itself. [lime](left, center, right)[/]",
|
|
149
|
+
show_default=True,
|
|
150
|
+
case_sensitive=False,
|
|
151
|
+
),
|
|
152
|
+
expand: bool = typer.Option(
|
|
153
|
+
True,
|
|
154
|
+
"--expand/--no-expand",
|
|
155
|
+
help="Whether to expand the panel to fill the width.",
|
|
156
|
+
),
|
|
157
|
+
width: Optional[int] = typer.Option(
|
|
158
|
+
None,
|
|
159
|
+
"--width",
|
|
160
|
+
metavar="WIDTH",
|
|
161
|
+
help="Width of the panel (requires --no-expand if set).",
|
|
162
|
+
),
|
|
163
|
+
height: Optional[int] = typer.Option(
|
|
164
|
+
None,
|
|
165
|
+
"--height",
|
|
166
|
+
metavar="HEIGHT",
|
|
167
|
+
help="Height of the panel; content determines by default.",
|
|
168
|
+
),
|
|
169
|
+
end: str = typer.Option(
|
|
170
|
+
"\n",
|
|
171
|
+
"--end",
|
|
172
|
+
metavar="END",
|
|
173
|
+
help=(
|
|
174
|
+
"String appended after the text is printed. [dim]\\[default: '\\n'][/dim]"
|
|
175
|
+
),
|
|
176
|
+
),
|
|
177
|
+
box: Literal["SQUARE", "ROUNDED", "HEAVY", "DOUBLE", "ASCII"] = typer.Option(
|
|
178
|
+
"ROUNDED",
|
|
179
|
+
"--box",
|
|
180
|
+
metavar="BOX",
|
|
181
|
+
help=(
|
|
182
|
+
"Box style for the panel border. [dim](e.g., SQUARE, ROUNDED, HEAVY, "
|
|
183
|
+
"DOUBLE, ASCII).[/dim]"
|
|
184
|
+
),
|
|
185
|
+
case_sensitive=False,
|
|
186
|
+
),
|
|
187
|
+
animate: bool = typer.Option(
|
|
188
|
+
False,
|
|
189
|
+
"-a",
|
|
190
|
+
"--animate",
|
|
191
|
+
help="Animate the panel gradient.",
|
|
192
|
+
),
|
|
193
|
+
duration: float = typer.Option(
|
|
194
|
+
5.0,
|
|
195
|
+
"-d",
|
|
196
|
+
"--duration",
|
|
197
|
+
metavar="DURATION",
|
|
198
|
+
help="Duration of the panel animation in seconds (only used if --animate).",
|
|
199
|
+
),
|
|
200
|
+
svg: Optional[str] = typer.Option(
|
|
201
|
+
None,
|
|
202
|
+
"--svg",
|
|
203
|
+
metavar="SVG",
|
|
204
|
+
help="Save output as an SVG file.",
|
|
205
|
+
),
|
|
206
|
+
) -> None:
|
|
207
|
+
"""Display a renderable inside a gradient panel."""
|
|
208
|
+
if renderable == "-":
|
|
209
|
+
renderable = typer.get_text_stream("stdin").read().rstrip("\n")
|
|
210
|
+
if not renderable:
|
|
211
|
+
raise typer.UsageError("Missing text argument.")
|
|
212
|
+
|
|
213
|
+
fg_list = parse_colors(colors)
|
|
214
|
+
bg_list = parse_colors(bgcolors)
|
|
215
|
+
style_obj = parse_style(style)
|
|
216
|
+
_text_justify = cast(AlignMethod, text_justify)
|
|
217
|
+
padding_tuple: Optional[Tuple[int, ...]] = None
|
|
218
|
+
if padding:
|
|
219
|
+
padding_tuple = tuple(int(x) for x in padding.split(",") if x.strip())
|
|
220
|
+
|
|
221
|
+
from rich import box as rich_box
|
|
222
|
+
|
|
223
|
+
box_map: dict[str, Any] = {
|
|
224
|
+
"SQUARE": rich_box.SQUARE,
|
|
225
|
+
"ROUNDED": rich_box.ROUNDED,
|
|
226
|
+
"HEAVY": rich_box.HEAVY,
|
|
227
|
+
"DOUBLE": rich_box.DOUBLE,
|
|
228
|
+
"ASCII": rich_box.ASCII,
|
|
229
|
+
}
|
|
230
|
+
box_style = box_map.get(box.upper(), rich_box.ROUNDED)
|
|
231
|
+
|
|
232
|
+
if animate and svg:
|
|
233
|
+
raise typer.UsageError("--svg is not supported with --animate.")
|
|
234
|
+
if animate and console.is_terminal is True:
|
|
235
|
+
animated_panel: AnimatedPanel = AnimatedPanel(
|
|
236
|
+
Align(renderable, align=_text_justify),
|
|
237
|
+
colors=cast(Any, fg_list),
|
|
238
|
+
rainbow=rainbow,
|
|
239
|
+
hues=hues,
|
|
240
|
+
bg_colors=cast(Any, bg_list),
|
|
241
|
+
title=title,
|
|
242
|
+
title_style=parse_style(title_style),
|
|
243
|
+
title_align=cast(AlignMethod, title_align),
|
|
244
|
+
subtitle=subtitle,
|
|
245
|
+
subtitle_style=parse_style(subtitle_style),
|
|
246
|
+
subtitle_align=cast(AlignMethod, subtitle_align),
|
|
247
|
+
style=style_obj,
|
|
248
|
+
border_style=parse_style(border_style),
|
|
249
|
+
padding=cast(Any, padding_tuple),
|
|
250
|
+
vertical_justify=cast(Any, vertical_justify),
|
|
251
|
+
justify=cast(AlignMethod, justify),
|
|
252
|
+
expand=expand,
|
|
253
|
+
width=width,
|
|
254
|
+
height=height,
|
|
255
|
+
box=box_style,
|
|
256
|
+
animate=True,
|
|
257
|
+
duration=duration,
|
|
258
|
+
)
|
|
259
|
+
animated_panel.run()
|
|
260
|
+
sys.exit(0)
|
|
261
|
+
|
|
262
|
+
panel = Panel(
|
|
263
|
+
Align(renderable, align=_text_justify),
|
|
264
|
+
colors=cast(Any, fg_list),
|
|
265
|
+
rainbow=rainbow,
|
|
266
|
+
hues=hues,
|
|
267
|
+
bg_colors=cast(Any, bg_list),
|
|
268
|
+
title=title,
|
|
269
|
+
title_style=parse_style(title_style),
|
|
270
|
+
title_align=cast(AlignMethod, title_align),
|
|
271
|
+
subtitle=subtitle,
|
|
272
|
+
subtitle_style=parse_style(subtitle_style),
|
|
273
|
+
subtitle_align=cast(AlignMethod, subtitle_align),
|
|
274
|
+
style=style_obj,
|
|
275
|
+
border_style=parse_style(border_style),
|
|
276
|
+
padding=cast(Any, padding_tuple),
|
|
277
|
+
vertical_justify=cast(Any, vertical_justify),
|
|
278
|
+
justify=cast(AlignMethod, justify),
|
|
279
|
+
expand=expand,
|
|
280
|
+
width=width,
|
|
281
|
+
height=height,
|
|
282
|
+
box=box_style,
|
|
283
|
+
)
|
|
284
|
+
if svg:
|
|
285
|
+
export_svg(panel, svg, end=end)
|
|
286
|
+
return
|
|
287
|
+
console.print(panel, end=end)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
__all__ = ["panel_command"]
|
|
File without changes
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""Rule command wiring for the CLI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Literal, Optional, cast
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from rich.align import AlignMethod
|
|
9
|
+
|
|
10
|
+
from rich_gradient.rule import Rule
|
|
11
|
+
|
|
12
|
+
from .common import console, export_svg, parse_colors, parse_style
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def rule_command(
|
|
16
|
+
title: Optional[str] = typer.Option(
|
|
17
|
+
None,
|
|
18
|
+
"-t",
|
|
19
|
+
"--title",
|
|
20
|
+
metavar="TITLE",
|
|
21
|
+
help="Title of the rule.",
|
|
22
|
+
),
|
|
23
|
+
title_style: Optional[str] = typer.Option(
|
|
24
|
+
"bold",
|
|
25
|
+
"-s",
|
|
26
|
+
"--title-style",
|
|
27
|
+
metavar="TITLE_STYLE",
|
|
28
|
+
help=(
|
|
29
|
+
"The style of the rule's title text. [dim italic]*Only non-color styles "
|
|
30
|
+
"will be applied as the gradient's colors override color styles.[/]"
|
|
31
|
+
),
|
|
32
|
+
show_default=True,
|
|
33
|
+
),
|
|
34
|
+
colors: Optional[str] = typer.Option(
|
|
35
|
+
"",
|
|
36
|
+
"-c",
|
|
37
|
+
"--colors",
|
|
38
|
+
metavar="COLORS",
|
|
39
|
+
help=(
|
|
40
|
+
"Comma-separated list of colors for the gradient. [dim](e.g., "
|
|
41
|
+
"`[/][red]red[/][dim], [/][#ff9900]#ff9900[/][dim], [/][yellow]yellow[/][dim]`)."
|
|
42
|
+
),
|
|
43
|
+
show_default=True,
|
|
44
|
+
),
|
|
45
|
+
bgcolors: Optional[str] = typer.Option(
|
|
46
|
+
"",
|
|
47
|
+
"--bgcolors",
|
|
48
|
+
metavar="BGCOLORS",
|
|
49
|
+
help=(
|
|
50
|
+
"Comma-separated list of background colors for the gradient. [dim](e.g., "
|
|
51
|
+
"`[/][red]red[/][dim], [/][#ff9900]#ff9900[/][dim], [/][#ff0]#ff0[/][dim]`). "
|
|
52
|
+
"Defaults to [/][bold #fff]transparent[/][dim].[/dim]"
|
|
53
|
+
),
|
|
54
|
+
),
|
|
55
|
+
rainbow: bool = typer.Option(
|
|
56
|
+
False,
|
|
57
|
+
"-r",
|
|
58
|
+
"--rainbow",
|
|
59
|
+
help="[#ff0000]U[/][#ff3b00]s[/][#ff5100]e[/][#ff7400] [/]\
|
|
60
|
+
[#ff9000]r[/][#ffa900]a[/][#ffc000]i[/][#ffd700]n[/]\
|
|
61
|
+
[#ffee00]b[/][#f7ff00]o[/][#d3ff00]w[/][#a7ff00] [/] \
|
|
62
|
+
[#7dff00]c[/][#2eff00]o[/][#00ff64]l[/][#00ff8e]o[/][#00ffc0]r[/]\
|
|
63
|
+
[#00ffec]s[/][#00f4ff] [/][#00ddff]f[/][#00c5ff]o[/][#00afff]r[/]\
|
|
64
|
+
[#1596ff] [/][#3b81ff]t[/][#4e67ff]h[/][#675fff]e[/][#7b57ff] [/]\
|
|
65
|
+
[#924bff]g[/][#a73bff]r[/][#c72cff]a[/][#eb1cff]d[/][#ff00f2]i[/]\
|
|
66
|
+
[#ff00ce]e[/][#ff00a4]n[/][#ff0084]t[/][#ff0054].[/]",
|
|
67
|
+
),
|
|
68
|
+
hues: int = typer.Option(
|
|
69
|
+
10,
|
|
70
|
+
"--hues",
|
|
71
|
+
metavar="HUES",
|
|
72
|
+
help="The number of hues to use for a random gradient.",
|
|
73
|
+
show_default=True,
|
|
74
|
+
),
|
|
75
|
+
end: str = typer.Option(
|
|
76
|
+
"\n",
|
|
77
|
+
"--end",
|
|
78
|
+
metavar="END",
|
|
79
|
+
help=(
|
|
80
|
+
"String appended after the text is printed. [dim]\\[default: '\\n'][/dim]"
|
|
81
|
+
),
|
|
82
|
+
),
|
|
83
|
+
thickness: int = typer.Option(
|
|
84
|
+
2,
|
|
85
|
+
"-T",
|
|
86
|
+
"--thickness",
|
|
87
|
+
metavar="THICKNESS",
|
|
88
|
+
help="Thickness of the rule line (choices 0-3).",
|
|
89
|
+
),
|
|
90
|
+
align: Literal["left", "center", "right"] = typer.Option(
|
|
91
|
+
"center",
|
|
92
|
+
"-a",
|
|
93
|
+
"--align",
|
|
94
|
+
metavar="ALIGN",
|
|
95
|
+
help="Alignment of the rule in the console. [lime](left, center, right)[/]",
|
|
96
|
+
show_default=True,
|
|
97
|
+
case_sensitive=False,
|
|
98
|
+
),
|
|
99
|
+
svg: Optional[str] = typer.Option(
|
|
100
|
+
None,
|
|
101
|
+
"--svg",
|
|
102
|
+
metavar="SVG",
|
|
103
|
+
help="Save output as an SVG file.",
|
|
104
|
+
),
|
|
105
|
+
) -> None:
|
|
106
|
+
"""Display a gradient rule in the console."""
|
|
107
|
+
_colors = parse_colors(colors)
|
|
108
|
+
_bgcolors = parse_colors(bgcolors)
|
|
109
|
+
_title_style = parse_style(title_style)
|
|
110
|
+
|
|
111
|
+
rule = Rule(
|
|
112
|
+
title=title or "",
|
|
113
|
+
title_style=_title_style,
|
|
114
|
+
colors=_colors,
|
|
115
|
+
rainbow=rainbow,
|
|
116
|
+
hues=hues,
|
|
117
|
+
bg_colors=_bgcolors,
|
|
118
|
+
thickness=thickness,
|
|
119
|
+
end=end,
|
|
120
|
+
align=cast(AlignMethod, align),
|
|
121
|
+
)
|
|
122
|
+
if svg:
|
|
123
|
+
export_svg(rule, svg, end=end)
|
|
124
|
+
return
|
|
125
|
+
console.print(rule)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
__all__ = ["rule_command"]
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""Text/print command wiring for the CLI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from typing import List, Literal, Optional, cast
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
from rich.console import JustifyMethod, OverflowMethod
|
|
10
|
+
|
|
11
|
+
from rich_gradient.text import Text
|
|
12
|
+
|
|
13
|
+
from .common import console, export_svg, parse_colors, parse_style
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def print_command(
|
|
17
|
+
text: Optional[List[str]] = typer.Argument(None),
|
|
18
|
+
colors: Optional[str] = typer.Option(
|
|
19
|
+
None,
|
|
20
|
+
"-c",
|
|
21
|
+
"--colors",
|
|
22
|
+
metavar="COLORS",
|
|
23
|
+
help=(
|
|
24
|
+
"Comma-separated list of colors for the gradient. [dim](e.g., "
|
|
25
|
+
"`[/][red]red[/][dim], [/][#ff9900]#ff9900[/][dim], [/][yellow]yellow[/][dim]`). "
|
|
26
|
+
"If no colors are provided, the color stops are automatically generated."
|
|
27
|
+
),
|
|
28
|
+
),
|
|
29
|
+
rainbow: bool = typer.Option(
|
|
30
|
+
False,
|
|
31
|
+
"-r",
|
|
32
|
+
"--rainbow",
|
|
33
|
+
help="[#ff0000]U[/][#ff3b00]s[/][#ff5100]e[/][#ff7400] [/]\
|
|
34
|
+
[#ff9000]r[/][#ffa900]a[/][#ffc000]i[/][#ffd700]n[/]\
|
|
35
|
+
[#ffee00]b[/][#f7ff00]o[/][#d3ff00]w[/][#a7ff00] [/] \
|
|
36
|
+
[#7dff00]c[/][#2eff00]o[/][#00ff64]l[/][#00ff8e]o[/][#00ffc0]r[/]\
|
|
37
|
+
[#00ffec]s[/][#00f4ff] [/][#00ddff]f[/][#00c5ff]o[/][#00afff]r[/]\
|
|
38
|
+
[#1596ff] [/][#3b81ff]t[/][#4e67ff]h[/][#675fff]e[/][#7b57ff] [/]\
|
|
39
|
+
[#924bff]g[/][#a73bff]r[/][#c72cff]a[/][#eb1cff]d[/][#ff00f2]i[/]\
|
|
40
|
+
[#ff00ce]e[/][#ff00a4]n[/][#ff0084]t[/][#ff0054].[/]",
|
|
41
|
+
),
|
|
42
|
+
hues: int = typer.Option(
|
|
43
|
+
7,
|
|
44
|
+
"-h",
|
|
45
|
+
"--hues",
|
|
46
|
+
metavar="HUES",
|
|
47
|
+
help="The number of hues to use for a random gradient.",
|
|
48
|
+
show_default=True,
|
|
49
|
+
),
|
|
50
|
+
style: Optional[str] = typer.Option(
|
|
51
|
+
None,
|
|
52
|
+
"--style",
|
|
53
|
+
metavar="STYLE",
|
|
54
|
+
help=(
|
|
55
|
+
"The style to apply to the text. [dim italic]*Only non-color styles "
|
|
56
|
+
"will be applied as the gradient's colors override color styles.[/]"
|
|
57
|
+
),
|
|
58
|
+
),
|
|
59
|
+
justify: Literal["left", "center", "right"] = typer.Option(
|
|
60
|
+
"left",
|
|
61
|
+
"-j",
|
|
62
|
+
"--justify",
|
|
63
|
+
metavar="JUSTIFY",
|
|
64
|
+
help="Justification of the text. [lime](left, center, right)[/]",
|
|
65
|
+
show_default=True,
|
|
66
|
+
case_sensitive=False,
|
|
67
|
+
),
|
|
68
|
+
overflow: Literal["crop", "fold", "ellipsis"] = typer.Option(
|
|
69
|
+
"fold",
|
|
70
|
+
"--overflow",
|
|
71
|
+
metavar="OVERFLOW",
|
|
72
|
+
help="How to handle overflow of text. [lime](crop, fold, ellipsis)[/]",
|
|
73
|
+
show_default=True,
|
|
74
|
+
case_sensitive=False,
|
|
75
|
+
),
|
|
76
|
+
no_wrap: bool = typer.Option(
|
|
77
|
+
False,
|
|
78
|
+
"--no-wrap",
|
|
79
|
+
help="Disable wrapping of text.",
|
|
80
|
+
show_default=True,
|
|
81
|
+
),
|
|
82
|
+
end: str = typer.Option(
|
|
83
|
+
"\n",
|
|
84
|
+
"--end",
|
|
85
|
+
metavar="END",
|
|
86
|
+
help=(
|
|
87
|
+
"String appended after the text is printed. [dim]\\[default: '\\n'][/dim]"
|
|
88
|
+
),
|
|
89
|
+
),
|
|
90
|
+
bgcolors: Optional[str] = typer.Option(
|
|
91
|
+
None,
|
|
92
|
+
"--bgcolors",
|
|
93
|
+
metavar="BGCOLORS",
|
|
94
|
+
help="Optional comma separated string of background colors. [dim]If not provided, background is transparent.[/]",
|
|
95
|
+
),
|
|
96
|
+
svg: Optional[str] = typer.Option(
|
|
97
|
+
None,
|
|
98
|
+
"--svg",
|
|
99
|
+
metavar="SVG",
|
|
100
|
+
help="Save output as an SVG file.",
|
|
101
|
+
),
|
|
102
|
+
) -> None:
|
|
103
|
+
"""Print text in gradient color to the console."""
|
|
104
|
+
if text:
|
|
105
|
+
if len(text) == 1 and text[0] == "-":
|
|
106
|
+
content = typer.get_text_stream("stdin").read().rstrip("\n")
|
|
107
|
+
if not content:
|
|
108
|
+
raise typer.BadParameter("Missing text argument.")
|
|
109
|
+
else:
|
|
110
|
+
content = " ".join(text)
|
|
111
|
+
else:
|
|
112
|
+
if sys.stdin.isatty():
|
|
113
|
+
raise typer.BadParameter("Missing text argument.")
|
|
114
|
+
content = typer.get_text_stream("stdin").read().rstrip("\n")
|
|
115
|
+
if not content:
|
|
116
|
+
raise typer.BadParameter("Missing text argument.")
|
|
117
|
+
|
|
118
|
+
fg_list = parse_colors(colors)
|
|
119
|
+
bg_list = parse_colors(bgcolors)
|
|
120
|
+
style_obj = parse_style(style)
|
|
121
|
+
gradient = Text(
|
|
122
|
+
content,
|
|
123
|
+
colors=fg_list,
|
|
124
|
+
rainbow=rainbow,
|
|
125
|
+
hues=hues,
|
|
126
|
+
style=style_obj,
|
|
127
|
+
justify=cast(JustifyMethod, justify),
|
|
128
|
+
overflow=cast(OverflowMethod, overflow),
|
|
129
|
+
end=end,
|
|
130
|
+
no_wrap=no_wrap,
|
|
131
|
+
bg_colors=bg_list,
|
|
132
|
+
)
|
|
133
|
+
if svg:
|
|
134
|
+
export_svg(gradient, svg, end="")
|
|
135
|
+
return
|
|
136
|
+
console.print(gradient)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
__all__ = ["print_command"]
|