Skip to content

Commit a72aac4

Browse files
some basic textualize support #1630
1 parent 10c6c31 commit a72aac4

File tree

4 files changed

+124
-13
lines changed

4 files changed

+124
-13
lines changed

SoftLayer/CLI/core.py

Lines changed: 114 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
66
:license: MIT, see LICENSE for more details.
77
"""
8+
import inspect
89
import logging
910
import os
1011
import sys
@@ -15,6 +16,15 @@
1516
import click
1617

1718
import requests
19+
from rich.console import Console, RenderableType
20+
from rich.markup import escape
21+
from rich.text import Text
22+
from rich.highlighter import RegexHighlighter
23+
from rich.panel import Panel
24+
from rich.table import Table
25+
from rich.theme import Theme
26+
27+
1828
import SoftLayer
1929
from SoftLayer.CLI import environment
2030
from SoftLayer.CLI import exceptions
@@ -40,13 +50,36 @@
4050
DEFAULT_FORMAT = 'table'
4151

4252

53+
class OptionHighlighter(RegexHighlighter):
54+
highlights = [
55+
r"(?P<switch>\-\w)", # single options like -v
56+
r"(?P<option>\-\-[\w\-]+)", # long options like --verbose
57+
r"(?P<default_option>\[[^\]]+\])", # anything between [], usually default options
58+
59+
]
60+
61+
SLCLI_THEME = Theme(
62+
{
63+
"option": "bold cyan",
64+
"switch": "bold green",
65+
"default_option": "light_pink1",
66+
"option_keyword": "bold cyan",
67+
"args_keyword": "bold green"
68+
}
69+
)
70+
4371
class CommandLoader(click.MultiCommand):
4472
"""Loads module for click."""
4573

4674
def __init__(self, *path, **attrs):
4775
click.MultiCommand.__init__(self, **attrs)
4876
self.path = path
4977

78+
self.highlighter = OptionHighlighter()
79+
self.console = Console(
80+
theme=SLCLI_THEME
81+
)
82+
5083
def list_commands(self, ctx):
5184
"""List all sub-commands."""
5285
env = ctx.ensure_object(environment.Environment)
@@ -71,6 +104,78 @@ def get_command(self, ctx, cmd_name):
71104
else:
72105
return module
73106

107+
def format_usage(self, ctx: click.Context, formatter: click.formatting.HelpFormatter) -> None:
108+
"""Formats and colorizes the usage information."""
109+
pieces = self.collect_usage_pieces(ctx)
110+
for index, piece in enumerate(pieces):
111+
if piece == "[OPTIONS]":
112+
pieces[index] = "[bold cyan][OPTIONS][/bold cyan]"
113+
elif piece == "COMMAND [ARGS]...":
114+
pieces[index] = "[orange1]COMMAND[/orange1] [bold cyan][ARGS][/bold cyan] ..."
115+
else:
116+
# print(f"OK this was {piece}")
117+
continue
118+
self.console.print(f"[bold red]{ctx.command_path}[/bold red] {' '.join(pieces)}")
119+
120+
def format_help_text(self, ctx: click.Context, formatter: click.formatting.HelpFormatter) -> None:
121+
"""Writes the help text to the formatter if it exists."""
122+
text = self.help if self.help is not None else ""
123+
124+
if self.deprecated:
125+
text = _("(Deprecated) {text}").format(text=text)
126+
127+
if text:
128+
text = inspect.cleandoc(text).partition("\f")[0]
129+
formatter.write_paragraph()
130+
131+
with formatter.indentation():
132+
formatter.write_text(text)
133+
134+
def format_epilog(self, ctx: click.Context, formatter: click.formatting.HelpFormatter) -> None:
135+
"""Writes the epilog into the formatter if it exists."""
136+
if self.epilog:
137+
epilog = inspect.cleandoc(self.epilog)
138+
formatter.write_paragraph()
139+
140+
with formatter.indentation():
141+
formatter.write_text(epilog)
142+
143+
def format_options(self, ctx, formatter):
144+
145+
options_table = Table(highlight=True, box=None, show_header=False)
146+
147+
for param in self.get_params(ctx):
148+
if len(param.opts) == 2:
149+
opt1 = self.highlighter(param.opts[1])
150+
opt2 = self.highlighter(param.opts[0])
151+
else:
152+
opt2 = self.highlighter(param.opts[0])
153+
opt1 = Text("")
154+
155+
# Ensures the short option is always in opt1.
156+
if len(opt2) == 2:
157+
opt1, opt2 = opt2, opt1
158+
159+
if param.metavar:
160+
opt2 += Text(f" {param.metavar}", style="bold yellow")
161+
162+
options = Text(" ".join(reversed(param.opts)))
163+
help_record = param.get_help_record(ctx)
164+
help_message = ""
165+
if help_record:
166+
help_message = param.get_help_record(ctx)[-1]
167+
168+
if param.metavar:
169+
options += f" {param.metavar}"
170+
options_table.add_row(opt1, opt2, self.highlighter(help_message))
171+
172+
self.console.print(options_table)
173+
self.format_commands(ctx, formatter)
174+
175+
# click.echo(click.style('Hello World!', fg='green'))
176+
# print("HEEEELP")
177+
178+
74179

75180
def get_latest_version():
76181
"""Gets the latest version of the Softlayer library."""
@@ -83,6 +188,11 @@ def get_latest_version():
83188
return latest
84189

85190

191+
CONTEXT_SETTINGS = dict(
192+
help_option_names=['--help', '-h'],
193+
auto_envvar_prefix='SLCLI',
194+
max_content_width=999
195+
)
86196
def get_version_message(ctx, param, value):
87197
"""Gets current and latest release versions message."""
88198
if not value or ctx.resilient_parsing:
@@ -99,15 +209,13 @@ def get_version_message(ctx, param, value):
99209
username and api_key need to be configured. The easiest way to do that is to
100210
use: 'slcli setup'""",
101211
cls=CommandLoader,
102-
context_settings={'help_option_names': ['-h', '--help'],
103-
'auto_envvar_prefix': 'SLCLI',
104-
'max_content_width': 999})
212+
context_settings=CONTEXT_SETTINGS)
105213
@click.option('--format',
106214
default=DEFAULT_FORMAT,
107215
show_default=True,
108216
help="Output format",
109217
type=click.Choice(VALID_FORMATS))
110-
@click.option('--config', '-C',
218+
@click.option('-C',
111219
required=False,
112220
default=click.get_app_dir('softlayer', force_posix=True),
113221
show_default=True,
@@ -119,7 +227,7 @@ def get_version_message(ctx, param, value):
119227
count=True)
120228
@click.option('--proxy',
121229
required=False,
122-
help="HTTP[S] proxy to be use to make API calls")
230+
help="HTTPS or HTTP proxy to be use to make API calls")
123231
@click.option('--really / --not-really', '-y',
124232
is_flag=True,
125233
required=False,
@@ -162,7 +270,7 @@ def cli(env,
162270
env.client.transport = env.vars['_timings']
163271

164272

165-
@cli.resultcallback()
273+
@cli.result_callback()
166274
@environment.pass_env
167275
def output_diagnostics(env, result, verbose=0, **kwargs):
168276
"""Output diagnostic information."""

setup.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,13 @@
3434
},
3535
python_requires='>=3.5',
3636
install_requires=[
37-
'prettytable >= 2.0.0',
38-
'click == 8.0.4',
37+
'prettytable >= 3.2.0',
38+
'click == 8.1.3',
3939
'requests >= 2.20.0',
4040
'prompt_toolkit >= 2',
4141
'pygments >= 2.0.0',
42-
'urllib3 >= 1.24'
42+
'urllib3 >= 1.24',
43+
'rich == 12.3.0'
4344
],
4445
keywords=['softlayer', 'cloud', 'slcli'],
4546
classifiers=[

tools/requirements.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
prettytable >= 2.0.0
2-
click == 8.0.4
1+
prettytable == 3.2.0
2+
click == 8.1.3
33
requests >= 2.20.0
44
prompt_toolkit >= 2
55
pygments >= 2.0.0
66
urllib3 >= 1.24
7+
rich == 12.3.0

tools/test-requirements.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ pytest
44
pytest-cov
55
mock
66
sphinx
7-
prettytable >= 2.0.0
8-
click == 8.0.4
7+
prettytable == 3.2.0
8+
click == 8.1.3
99
requests >= 2.20.0
1010
prompt_toolkit >= 2
1111
pygments >= 2.0.0
1212
urllib3 >= 1.24
13+
rich == 12.3.0

0 commit comments

Comments
 (0)