Skip to content

Commit 0b8801a

Browse files
committed
Added posibility to change theme in slcli like dark, light o maintain in default
1 parent e6a4b9e commit 0b8801a

9 files changed

Lines changed: 143 additions & 48 deletions

File tree

SoftLayer/CLI/command.py

Lines changed: 27 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,12 @@
1111
import click
1212

1313
from rich import box
14-
from rich.console import Console
1514
from rich.highlighter import RegexHighlighter
1615
from rich.table import Table
1716
from rich.text import Text
18-
from rich.theme import Theme
1917

2018
from SoftLayer.CLI import environment
19+
from SoftLayer import utils
2120

2221

2322
class OptionHighlighter(RegexHighlighter):
@@ -30,29 +29,19 @@ class OptionHighlighter(RegexHighlighter):
3029
]
3130

3231

33-
# Colors defined in https://rich.readthedocs.io/en/latest/_modules/rich/color.html#ColorType
34-
SLCLI_THEME = Theme(
35-
{
36-
"option": "bold cyan",
37-
"switch": "bold green",
38-
"default_option": "light_pink1",
39-
"option_keyword": "bold cyan",
40-
"args_keyword": "bold green"
41-
}
42-
)
43-
44-
4532
class CommandLoader(click.MultiCommand):
4633
"""Loads module for click."""
4734

4835
def __init__(self, *path, **attrs):
4936
click.MultiCommand.__init__(self, **attrs)
5037
self.path = path
51-
5238
self.highlighter = OptionHighlighter()
53-
self.console = Console(
54-
theme=SLCLI_THEME
55-
)
39+
self.console = utils.console_color_themes(theme=None)
40+
41+
def updated_console_whit_theme(self, theme):
42+
"""Update the console depending on the theme"""
43+
# print(theme)
44+
self.console = utils.console_color_themes(theme)
5645

5746
def list_commands(self, ctx):
5847
"""List all sub-commands."""
@@ -65,7 +54,7 @@ def get_command(self, ctx, cmd_name):
6554
"""Get command for click."""
6655
env = ctx.ensure_object(environment.Environment)
6756
env.load()
68-
57+
self.updated_console_whit_theme(env.theme)
6958
# Do alias lookup (only available for root commands)
7059
if len(self.path) == 0:
7160
cmd_name = env.resolve_alias(cmd_name)
@@ -80,14 +69,17 @@ def get_command(self, ctx, cmd_name):
8069

8170
def format_usage(self, ctx: click.Context, formatter: click.formatting.HelpFormatter) -> None:
8271
"""Formats and colorizes the usage information."""
72+
env = ctx.ensure_object(environment.Environment)
73+
env.load()
74+
self.updated_console_whit_theme(env.theme)
8375
pieces = self.collect_usage_pieces(ctx)
8476
for index, piece in enumerate(pieces):
8577
if piece == "[OPTIONS]":
86-
pieces[index] = "[bold cyan][OPTIONS][/bold cyan]"
78+
pieces[index] = "[options][OPTIONS][/]"
8779
elif piece == "COMMAND [ARGS]...":
88-
pieces[index] = "[orange1]COMMAND[/orange1] [bold cyan][ARGS][/bold cyan] ..."
80+
pieces[index] = "[command]COMMAND[/] [args][ARGS][/] ..."
8981

90-
self.console.print(f"Usage: [bold red]{ctx.command_path}[/bold red] {' '.join(pieces)}")
82+
self.console.print(f"Usage: [path]{ctx.command_path}[/] {' '.join(pieces)}")
9183

9284
def format_help_text(self, ctx: click.Context, formatter: click.formatting.HelpFormatter) -> None:
9385
"""Writes the help text"""
@@ -153,9 +145,9 @@ def format_commands(self, ctx, formatter):
153145
if len(commands):
154146
for subcommand, cmd in commands:
155147
help_text = cmd.get_short_help_str(120)
156-
command_style = Text(f" {subcommand}", style="orange1")
148+
command_style = Text(f" {subcommand}", style="sub_command")
157149
command_table.add_row(command_style, help_text)
158-
self.console.print("\n[bold orange1]Commands:[/]")
150+
self.console.print("\n[name_sub_command]Commands:[/]")
159151
self.console.print(command_table)
160152

161153

@@ -165,20 +157,25 @@ class SLCommand(click.Command):
165157
def __init__(self, **attrs):
166158
click.Command.__init__(self, **attrs)
167159
self.highlighter = OptionHighlighter()
168-
self.console = Console(
169-
theme=SLCLI_THEME
170-
)
160+
self.console = utils.console_color_themes(theme=None)
161+
162+
def updated_console_whit_theme(self, theme):
163+
"""Update the console depending on the theme"""
164+
self.console = utils.console_color_themes(theme)
171165

172166
def format_usage(self, ctx: click.Context, formatter: click.formatting.HelpFormatter) -> None:
173167
"""Formats and colorizes the usage information."""
168+
env = ctx.ensure_object(environment.Environment)
169+
env.load()
170+
self.updated_console_whit_theme(env.theme)
174171
pieces = self.collect_usage_pieces(ctx)
175172
for index, piece in enumerate(pieces):
176173
if piece == "[OPTIONS]":
177-
pieces[index] = "[bold cyan][OPTIONS][/bold cyan]"
174+
pieces[index] = "[options][OPTIONS][/]"
178175
elif piece == "COMMAND [ARGS]...":
179-
pieces[index] = "[orange1]COMMAND[/orange1] [bold cyan][ARGS][/bold cyan] ..."
176+
pieces[index] = "[command]COMMAND[/] [args][ARGS][/] ..."
180177

181-
self.console.print(f"Usage: [bold red]{ctx.command_path}[/bold red] {' '.join(pieces)}")
178+
self.console.print(f"Usage: [path]{ctx.command_path}[/] {' '.join(pieces)}")
182179

183180
def format_help_text(self, ctx: click.Context, formatter: click.formatting.HelpFormatter) -> None:
184181
"""Writes the help text"""

SoftLayer/CLI/config/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def _resolve_transport(transport):
1313
return _resolve_transport(nested_transport)
1414

1515

16-
def get_settings_from_client(client):
16+
def get_settings_from_client(client, theme):
1717
"""Pull out settings from a SoftLayer.BaseClient instance.
1818
1919
:param client: SoftLayer.BaseClient instance
@@ -23,6 +23,7 @@ def get_settings_from_client(client):
2323
'api_key': '',
2424
'timeout': '',
2525
'endpoint_url': '',
26+
'theme': theme
2627
}
2728
try:
2829
settings['username'] = client.auth.username
@@ -49,4 +50,5 @@ def config_table(settings):
4950
table.add_row(['API Key', settings['api_key'] or 'not set'])
5051
table.add_row(['Endpoint URL', settings['endpoint_url'] or 'not set'])
5152
table.add_row(['Timeout', settings['timeout'] or 'not set'])
53+
table.add_row(['Theme', settings['theme'] or 'not set'])
5254
return table

SoftLayer/CLI/config/setup.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def cli(env, auth):
6868
api_key = None
6969

7070
timeout = 0
71-
defaults = config.get_settings_from_client(env.client)
71+
defaults = config.get_settings_from_client(env.client, env.theme)
7272
endpoint_url = get_endpoint_url(env, defaults.get('endpoint_url', 'public'))
7373
# Get ths username and API key
7474
if auth == 'ibmid':
@@ -92,6 +92,9 @@ def cli(env, auth):
9292
# Ask for timeout, convert to float, then to int
9393
timeout = int(float(env.input('Timeout', default=defaults['timeout'] or 0)))
9494

95+
# Ask theme for console
96+
theme = env.input('Theme [dark/light]', default=defaults['theme'])
97+
9598
path = '~/.softlayer'
9699
if env.config_file:
97100
path = env.config_file
@@ -100,7 +103,8 @@ def cli(env, auth):
100103
env.out(env.fmt(config.config_table({'username': username,
101104
'api_key': api_key,
102105
'endpoint_url': endpoint_url,
103-
'timeout': timeout})))
106+
'timeout': timeout,
107+
'theme': theme})))
104108

105109
if not formatting.confirm('Are you sure you want to write settings to "%s"?' % config_path, default=True):
106110
raise exceptions.CLIAbort('Aborted.')
@@ -118,6 +122,7 @@ def cli(env, auth):
118122
parsed_config.set('softlayer', 'api_key', api_key)
119123
parsed_config.set('softlayer', 'endpoint_url', endpoint_url)
120124
parsed_config.set('softlayer', 'timeout', timeout)
125+
parsed_config.set('softlayer', 'theme', theme)
121126

122127
config_fd = os.fdopen(os.open(config_path, (os.O_WRONLY | os.O_CREAT | os.O_TRUNC), 0o600), 'w')
123128
try:

SoftLayer/CLI/config/show.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@
1313
def cli(env):
1414
"""Show current configuration."""
1515

16-
settings = config.get_settings_from_client(env.client)
16+
settings = config.get_settings_from_client(client=env.client, theme=env.theme)
1717
env.fout(config.config_table(settings))

SoftLayer/CLI/core.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ def cli(env,
118118
env.skip_confirmations = really
119119
env.config_file = config
120120
env.format = format
121+
env.set_env_theme(config_file=config)
121122
env.ensure_client(config_file=config, is_demo=demo, proxy=proxy)
122123
env.vars['_start'] = time.time()
123124
logger = logging.getLogger()

SoftLayer/CLI/environment.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
66
:license: MIT, see LICENSE for more details.
77
"""
8+
import configparser
9+
import os
10+
811
import importlib
912
from json.decoder import JSONDecodeError
1013

@@ -40,6 +43,7 @@ def __init__(self):
4043
self.format = 'table'
4144
self.skip_confirmations = False
4245
self.config_file = None
46+
self.theme = None
4347

4448
self._modules_loaded = False
4549

@@ -76,7 +80,7 @@ def fmt(self, output, fmt=None):
7680
"""Format output based on current the environment format."""
7781
if fmt is None:
7882
fmt = self.format
79-
return formatting.format_output(output, fmt)
83+
return formatting.format_output(output, fmt, self.theme)
8084

8185
def format_output_is_json(self):
8286
"""Return True if format output is json or jsonraw"""
@@ -208,6 +212,19 @@ def ensure_client(self, config_file=None, is_demo=False, proxy=None):
208212
)
209213
self.client = client
210214

215+
def set_env_theme(self, config_file=None):
216+
"""Get theme to color console and set in env"""
217+
theme = os.environ.get('SL_THEME')
218+
if theme:
219+
self.theme = theme
220+
else:
221+
config = configparser.RawConfigParser({
222+
'theme': '',
223+
})
224+
config.read(config_file)
225+
if config.has_section('softlayer'):
226+
self.theme = config.get('softlayer', 'theme')
227+
211228

212229
class ModuleLoader(object):
213230
"""Module loader that acts a little like an EntryPoint object."""

SoftLayer/CLI/formatting.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
FALSE_VALUES = ['0', 'false', 'FALSE', 'no', 'False']
2323

2424

25-
def format_output(data, fmt='table'): # pylint: disable=R0911,R0912
25+
def format_output(data, fmt='table', theme=None): # pylint: disable=R0911,R0912
2626
"""Given some data, will format it for console output.
2727
2828
:param data: One of: String, Table, FormattedItem, List, Tuple, SequentialOutput
@@ -40,7 +40,7 @@ def format_output(data, fmt='table'): # pylint: disable=R0911,R0912
4040

4141
# responds to .prettytable()
4242
if hasattr(data, 'prettytable') and fmt in ('table', 'raw'):
43-
return format_prettytable(data, fmt)
43+
return format_prettytable(data, fmt, theme)
4444

4545
# responds to .to_python()
4646
if hasattr(data, 'to_python'):
@@ -73,7 +73,7 @@ def format_output(data, fmt='table'): # pylint: disable=R0911,R0912
7373
return str(data)
7474

7575

76-
def format_prettytable(table, fmt='table'):
76+
def format_prettytable(table, fmt='table', theme=None):
7777
"""Converts SoftLayer.CLI.formatting.Table instance to a prettytable."""
7878
for i, row in enumerate(table.rows):
7979
for j, item in enumerate(row):
@@ -83,7 +83,7 @@ def format_prettytable(table, fmt='table'):
8383
table.rows[i][j] = format_output(item)
8484
else:
8585
table.rows[i][j] = str(item)
86-
ptable = table.prettytable(fmt)
86+
ptable = table.prettytable(fmt, theme)
8787
return ptable
8888

8989

@@ -267,12 +267,13 @@ def to_python(self):
267267
items.append(dict(zip(self.columns, formatted_row)))
268268
return items
269269

270-
def prettytable(self, fmt='table'):
270+
def prettytable(self, fmt='table', theme=None):
271271
"""Returns a RICH table instance."""
272272
box_style = box.SQUARE
273273
if fmt == 'raw':
274274
box_style = None
275-
table = rTable(title=self.title, box=box_style, header_style="bright_cyan")
275+
color_table = utils.table_color_theme(theme)
276+
table = rTable(title=self.title, box=box_style, header_style=color_table['header'])
276277
if self.sortby:
277278
try:
278279
# https://docs.python.org/3/howto/sorting.html#key-functions
@@ -300,7 +301,7 @@ def prettytable(self, fmt='table'):
300301
justify = 'left'
301302
# Special coloring for some columns
302303
if col in ('id', 'Id', 'ID'):
303-
style = "pale_violet_red1"
304+
style = color_table['id_columns']
304305
table.add_column(col, justify=justify, style=style)
305306

306307
for row in self.rows:

SoftLayer/utils.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
import re
1212
import time
1313

14+
from rich.console import Console
15+
from rich.theme import Theme
1416

1517
# pylint: disable=no-member, invalid-name
1618

@@ -459,3 +461,74 @@ def decode_stacked(document, pos=0, decoder=JSONDecoder()):
459461
obj, pos = decoder.raw_decode(document, pos)
460462

461463
yield obj
464+
465+
466+
def console_color_themes(theme):
467+
"""Colors in https://rich.readthedocs.io/en/stable/appendix/colors.html?highlight=light_pink1#standard-colors"""
468+
# Default theme
469+
if not theme:
470+
return Console(theme=Theme(
471+
{
472+
"options": "bold cyan", # OPTIONS
473+
"command": "orange3", # COMMAND
474+
"args": "bold cyan", # ARGS
475+
"path": "bold red", # command path
476+
"name_sub_command": "orange3", # sub command name
477+
"sub_command": "orange3", # sub command list
478+
# Help table colors options
479+
"option": "bold cyan",
480+
"switch": "bold green",
481+
"default_option": "light_coral",
482+
"option_keyword": "bold cyan",
483+
"args_keyword": "bold green",
484+
})
485+
)
486+
if theme == 'dark':
487+
return Console(theme=Theme(
488+
{
489+
"options": "bold cyan", # OPTIONS
490+
"command": "orange3", # COMMAND
491+
"args": "bold cyan", # ARGS
492+
"path": "bold red", # command path
493+
"name_sub_command": "orange3", # sub command name
494+
"sub_command": "orange3", # sub command list
495+
# Help table colors options
496+
"option": "bold cyan",
497+
"switch": "bold green",
498+
"default_option": "light_pink1",
499+
"option_keyword": "bold cyan",
500+
"args_keyword": "bold green",
501+
})
502+
)
503+
if theme == 'light':
504+
return Console(theme=Theme(
505+
{
506+
"options": "bold dark_cyan", # OPTIONS
507+
"command": "orange3", # COMMAND
508+
"args": "bold dark_cyan", # ARGS
509+
"path": "bold red", # command path
510+
"name_sub_command": "orange3", # sub command name
511+
"sub_command": "orange3", # sub command list
512+
# Help table colors options
513+
"option": "bold dark_cyan",
514+
"switch": "bold green4",
515+
"default_option": "light_coral",
516+
"option_keyword": "bold dark_cyan",
517+
"args_keyword": "bold green4",
518+
})
519+
)
520+
return None
521+
522+
523+
def table_color_theme(theme):
524+
"""Define result table colors"""
525+
if not theme:
526+
return {'header': 'bright_cyan',
527+
'id_columns': 'pale_violet_red1'}
528+
if theme == 'dark':
529+
return {'header': 'bright_cyan',
530+
'id_columns': 'pale_violet_red1'}
531+
if theme == 'light':
532+
return {'header': 'dark_cyan',
533+
'id_columns': 'light_coral'}
534+
return None

0 commit comments

Comments
 (0)