Skip to content

Commit 44773e6

Browse files
Merge pull request #1775 from edsonarios/issue1652
New feature to change theme in slcli like dark, light o maintain in default
2 parents a7c5b53 + d7238d5 commit 44773e6

9 files changed

Lines changed: 137 additions & 59 deletions

File tree

SoftLayer/CLI/command.py

Lines changed: 35 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,9 @@
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
2119

@@ -26,68 +24,58 @@ class OptionHighlighter(RegexHighlighter):
2624
r"(?P<switch>^\-\w)", # single options like -v
2725
r"(?P<option>\-\-[\w\-]+)", # long options like --verbose
2826
r"(?P<default_option>\[[^\]]+\])", # anything between [], usually default options
29-
3027
]
3128

3229

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-
4530
class CommandLoader(click.MultiCommand):
4631
"""Loads module for click."""
4732

4833
def __init__(self, *path, **attrs):
4934
click.MultiCommand.__init__(self, **attrs)
5035
self.path = path
51-
5236
self.highlighter = OptionHighlighter()
53-
self.console = Console(
54-
theme=SLCLI_THEME
55-
)
37+
self.env = None
38+
self.console = None
39+
40+
def ensure_env(self, ctx):
41+
"""ensures self.env is set"""
42+
if self.env is None:
43+
self.env = ctx.ensure_object(environment.Environment)
44+
self.env.load()
45+
if self.console is None:
46+
self.console = self.env.console
5647

5748
def list_commands(self, ctx):
5849
"""List all sub-commands."""
59-
env = ctx.ensure_object(environment.Environment)
60-
env.load()
61-
62-
return sorted(env.list_commands(*self.path))
50+
self.ensure_env(ctx)
51+
return sorted(self.env.list_commands(*self.path))
6352

6453
def get_command(self, ctx, cmd_name):
6554
"""Get command for click."""
66-
env = ctx.ensure_object(environment.Environment)
67-
env.load()
68-
55+
self.ensure_env(ctx)
6956
# Do alias lookup (only available for root commands)
7057
if len(self.path) == 0:
71-
cmd_name = env.resolve_alias(cmd_name)
58+
cmd_name = self.env.resolve_alias(cmd_name)
7259

7360
new_path = list(self.path)
7461
new_path.append(cmd_name)
75-
module = env.get_command(*new_path)
62+
module = self.env.get_command(*new_path)
7663
if isinstance(module, types.ModuleType):
7764
return CommandLoader(*new_path, help=module.__doc__ or '')
7865
else:
7966
return module
8067

8168
def format_usage(self, ctx: click.Context, formatter: click.formatting.HelpFormatter) -> None:
8269
"""Formats and colorizes the usage information."""
70+
self.ensure_env(ctx)
8371
pieces = self.collect_usage_pieces(ctx)
8472
for index, piece in enumerate(pieces):
8573
if piece == "[OPTIONS]":
86-
pieces[index] = "[bold cyan][OPTIONS][/bold cyan]"
74+
pieces[index] = "[options][OPTIONS][/]"
8775
elif piece == "COMMAND [ARGS]...":
88-
pieces[index] = "[orange1]COMMAND[/orange1] [bold cyan][ARGS][/bold cyan] ..."
76+
pieces[index] = "[command]COMMAND[/] [args][ARGS][/] ..."
8977

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

9280
def format_help_text(self, ctx: click.Context, formatter: click.formatting.HelpFormatter) -> None:
9381
"""Writes the help text"""
@@ -153,9 +141,9 @@ def format_commands(self, ctx, formatter):
153141
if len(commands):
154142
for subcommand, cmd in commands:
155143
help_text = cmd.get_short_help_str(120)
156-
command_style = Text(f" {subcommand}", style="orange1")
144+
command_style = Text(f" {subcommand}", style="sub_command")
157145
command_table.add_row(command_style, help_text)
158-
self.console.print("\n[bold orange1]Commands:[/]")
146+
self.console.print("\n[name_sub_command]Commands:[/]")
159147
self.console.print(command_table)
160148

161149

@@ -165,20 +153,28 @@ class SLCommand(click.Command):
165153
def __init__(self, **attrs):
166154
click.Command.__init__(self, **attrs)
167155
self.highlighter = OptionHighlighter()
168-
self.console = Console(
169-
theme=SLCLI_THEME
170-
)
156+
self.env = None
157+
self.console = None
158+
159+
def ensure_env(self, ctx):
160+
"""ensures self.env is set"""
161+
if self.env is None:
162+
self.env = ctx.ensure_object(environment.Environment)
163+
self.env.load()
164+
if self.console is None:
165+
self.console = self.env.console
171166

172167
def format_usage(self, ctx: click.Context, formatter: click.formatting.HelpFormatter) -> None:
173168
"""Formats and colorizes the usage information."""
169+
self.ensure_env(ctx)
174170
pieces = self.collect_usage_pieces(ctx)
175171
for index, piece in enumerate(pieces):
176172
if piece == "[OPTIONS]":
177-
pieces[index] = "[bold cyan][OPTIONS][/bold cyan]"
173+
pieces[index] = "[options][OPTIONS][/]"
178174
elif piece == "COMMAND [ARGS]...":
179-
pieces[index] = "[orange1]COMMAND[/orange1] [bold cyan][ARGS][/bold cyan] ..."
175+
pieces[index] = "[command]COMMAND[/] [args][ARGS][/] ..."
180176

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

183179
def format_help_text(self, ctx: click.Context, formatter: click.formatting.HelpFormatter) -> None:
184180
"""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'] or 'dark')
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: 26 additions & 2 deletions
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

@@ -16,6 +19,7 @@
1619
import SoftLayer
1720
from SoftLayer.CLI import formatting
1821
from SoftLayer.CLI import routes
22+
from SoftLayer import utils
1923

2024
# pylint: disable=too-many-instance-attributes, invalid-name
2125

@@ -35,7 +39,8 @@ def __init__(self):
3539
self.vars = {}
3640

3741
self.client = None
38-
self.console = Console()
42+
self.theme = self.set_env_theme()
43+
self.console = utils.console_color_themes(self.theme)
3944
self.err_console = Console(stderr=True)
4045
self.format = 'table'
4146
self.skip_confirmations = False
@@ -76,7 +81,7 @@ def fmt(self, output, fmt=None):
7681
"""Format output based on current the environment format."""
7782
if fmt is None:
7883
fmt = self.format
79-
return formatting.format_output(output, fmt)
84+
return formatting.format_output(output, fmt, self.theme)
8085

8186
def format_output_is_json(self):
8287
"""Return True if format output is json or jsonraw"""
@@ -208,6 +213,25 @@ def ensure_client(self, config_file=None, is_demo=False, proxy=None):
208213
)
209214
self.client = client
210215

216+
def set_env_theme(self, config_file=None):
217+
"""Get theme to color console and set in env"""
218+
theme = os.environ.get('SL_THEME')
219+
if theme:
220+
return theme
221+
else:
222+
config_files = ['/etc/softlayer.conf', '~/.softlayer']
223+
path_os = os.getenv('HOME')
224+
if path_os:
225+
config_files.append(path_os + '\\AppData\\Roaming\\softlayer')
226+
if config_file:
227+
config_files.append(config_file)
228+
config = configparser.RawConfigParser({'theme': 'dark'})
229+
config.read(config_files)
230+
if config.has_section('softlayer'):
231+
self.theme = config.get('softlayer', 'theme')
232+
return config.get('softlayer', 'theme')
233+
return 'dark'
234+
211235

212236
class ModuleLoader(object):
213237
"""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:

0 commit comments

Comments
 (0)