Skip to content

Commit 9c55550

Browse files
Merge pull request #1646 from allmightyspiff/richTables
Rich tables
2 parents ab2fc76 + 80a9eb9 commit 9c55550

23 files changed

+254
-272
lines changed

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010
runs-on: ubuntu-latest
1111
strategy:
1212
matrix:
13-
python-version: [3.6,3.7,3.8,3.9]
13+
python-version: [3.7,3.8,3.9,'3.10']
1414

1515
steps:
1616
- uses: actions/checkout@v2

SoftLayer/CLI/call_api.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ def cli(env, service, method, parameters, _id, _filters, mask, limit, offset, or
172172
}
173173

174174
if output_python:
175-
env.out(_build_python_example(args, kwargs))
175+
env.python_output(_build_python_example(args, kwargs))
176176
else:
177177
result = env.client.call(*args, **kwargs)
178178
env.fout(formatting.iter_to_table(result))

SoftLayer/CLI/core.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
import click
1515
import requests
16+
from rich.markup import escape
1617

1718
import SoftLayer
1819
from SoftLayer.CLI.command import CommandLoader
@@ -143,9 +144,9 @@ def output_diagnostics(env, result, verbose=0, **kwargs):
143144
diagnostic_table = formatting.Table(['name', 'value'])
144145
diagnostic_table.add_row(['execution_time', '%fs' % (time.time() - START_TIME)])
145146

146-
api_call_value = []
147+
api_call_value = formatting.Table(['API Calls'], title=None, align="left")
147148
for call in env.client.transport.get_last_calls():
148-
api_call_value.append("%s::%s (%fs)" % (call.service, call.method, call.end_time - call.start_time))
149+
api_call_value.add_row(["%s::%s (%fs)" % (call.service, call.method, call.end_time - call.start_time)])
149150

150151
diagnostic_table.add_row(['api_calls', api_call_value])
151152
diagnostic_table.add_row(['version', consts.USER_AGENT])
@@ -156,13 +157,13 @@ def output_diagnostics(env, result, verbose=0, **kwargs):
156157

157158
if verbose > 1:
158159
for call in env.client.transport.get_last_calls():
159-
call_table = formatting.Table(['', '{}::{}'.format(call.service, call.method)])
160+
call_table = formatting.Table(['', '{}::{}'.format(call.service, call.method)], align="left")
160161
nice_mask = ''
161162
if call.mask is not None:
162163
nice_mask = call.mask
163-
164164
call_table.add_row(['id', call.identifier])
165-
call_table.add_row(['mask', nice_mask])
165+
# Need to escape this so Rich doesn't think the mask is a tag and hide it.
166+
call_table.add_row(['mask', escape(nice_mask)])
166167
call_table.add_row(['filter', call.filter])
167168
call_table.add_row(['limit', call.limit])
168169
call_table.add_row(['offset', call.offset])

SoftLayer/CLI/dns/zone_import.py

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import click
66

77
import SoftLayer
8+
from SoftLayer.CLI.command import SLCommand as SLCommand
89
from SoftLayer.CLI import environment
910
from SoftLayer.CLI import exceptions
1011
from SoftLayer.CLI import helpers
@@ -17,9 +18,8 @@
1718
RECORD_FMT = "type={type}, record={record}, data={data}, ttl={ttl}"
1819

1920

20-
@click.command(cls=SoftLayer.CLI.command.SLCommand, )
21-
@click.argument('zonefile',
22-
type=click.Path(exists=True, readable=True, resolve_path=True))
21+
@click.command(cls=SLCommand)
22+
@click.argument('zonefile', type=click.Path(exists=True, readable=True, resolve_path=True))
2323
@click.option('--dry-run', is_flag=True, help="Don't actually create records")
2424
@environment.pass_env
2525
def cli(env, zonefile, dry_run):
@@ -31,12 +31,12 @@ def cli(env, zonefile, dry_run):
3131

3232
zone, records, bad_lines = parse_zone_details(zone_contents)
3333

34-
env.out("Parsed: zone=%s" % zone)
34+
click.secho("Parsed: zone=%s" % zone)
3535
for record in records:
36-
env.out("Parsed: %s" % RECORD_FMT.format(**record))
36+
click.secho("Parsed: %s" % RECORD_FMT.format(**record), fg="green")
3737

3838
for line in bad_lines:
39-
env.out("Unparsed: %s" % line)
39+
click.secho("Unparsed: %s" % line, fg="yellow")
4040

4141
if dry_run:
4242
return
@@ -47,25 +47,19 @@ def cli(env, zonefile, dry_run):
4747
name='zone')
4848
except exceptions.CLIAbort:
4949
zone_id = manager.create_zone(zone)['id']
50-
env.out(click.style("Created: %s" % zone, fg='green'))
50+
click.secho(click.style("Created: %s" % zone, fg='green'))
5151

5252
# Attempt to create each record
5353
for record in records:
5454
try:
55-
manager.create_record(zone_id,
56-
record['record'],
57-
record['type'],
58-
record['data'],
59-
record['ttl'])
60-
61-
env.out(click.style("Created: %s" % RECORD_FMT.format(**record),
62-
fg='green'))
55+
manager.create_record(zone_id, record['record'], record['type'], record['data'], record['ttl'])
56+
57+
click.secho(click.style("Created: %s" % RECORD_FMT.format(**record), fg='green'))
6358
except SoftLayer.SoftLayerAPIError as ex:
64-
env.out(click.style("Failed: %s" % RECORD_FMT.format(**record),
65-
fg='red'))
66-
env.out(click.style(str(ex), fg='red'))
59+
click.secho(click.style("Failed: %s" % RECORD_FMT.format(**record), fg='red'))
60+
click.secho(click.style(str(ex), fg='red'))
6761

68-
env.out(click.style("Finished", fg='green'))
62+
click.secho(click.style("Finished", fg='green'))
6963

7064

7165
def parse_zone_details(zone_contents):
@@ -75,7 +69,10 @@ def parse_zone_details(zone_contents):
7569
zone_lines = [line.strip() for line in zone_contents.split('\n')]
7670

7771
zone_search = re.search(r'^\$ORIGIN (?P<zone>.*)\.', zone_lines[0])
78-
zone = zone_search.group('zone')
72+
if zone_search:
73+
zone = zone_search.group('zone')
74+
else:
75+
raise exceptions.ArgumentError("Invalid Zone File")
7976

8077
for line in zone_lines[1:]:
8178
record_search = re.search(RECORD_REGEX, line)

SoftLayer/CLI/environment.py

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@
66
:license: MIT, see LICENSE for more details.
77
"""
88
import importlib
9+
from json.decoder import JSONDecodeError
910

1011
import click
1112
import pkg_resources
13+
from rich.console import Console
14+
from rich.syntax import Syntax
1215

1316
import SoftLayer
1417
from SoftLayer.CLI import formatting
@@ -32,19 +35,38 @@ def __init__(self):
3235
self.vars = {}
3336

3437
self.client = None
38+
self.console = Console()
39+
self.err_console = Console(stderr=True)
3540
self.format = 'table'
3641
self.skip_confirmations = False
3742
self.config_file = None
3843

3944
self._modules_loaded = False
4045

41-
def out(self, output, newline=True):
46+
def out(self, output):
4247
"""Outputs a string to the console (stdout)."""
43-
click.echo(output, nl=newline)
48+
if self.format == 'json':
49+
try:
50+
self.console.print_json(output)
51+
# Tried to print not-json, so just print it out normally...
52+
except JSONDecodeError:
53+
click.echo(output)
54+
elif self.format == 'jsonraw':
55+
# Using Rich here is problematic because in the unit tests it thinks the terminal is 80 characters wide
56+
# and only prints out that many characters.
57+
click.echo(output)
58+
else:
59+
# If we want to print a list of tables, Rich doens't handle that well.
60+
if isinstance(output, list):
61+
for line in output:
62+
self.console.print(line, overflow='ignore')
63+
else:
64+
self.console.print(output, overflow='ignore')
4465

4566
def err(self, output, newline=True):
4667
"""Outputs an error string to the console (stderr)."""
47-
click.echo(output, nl=newline, err=True)
68+
69+
self.err_console.print(output, new_line_start=newline)
4870

4971
def fmt(self, output, fmt=None):
5072
"""Format output based on current the environment format."""
@@ -56,14 +78,18 @@ def format_output_is_json(self):
5678
"""Return True if format output is json or jsonraw"""
5779
return 'json' in self.format
5880

59-
def fout(self, output, newline=True):
81+
def fout(self, output):
6082
"""Format the input and output to the console (stdout)."""
6183
if output is not None:
6284
try:
63-
self.out(self.fmt(output), newline=newline)
85+
self.out(self.fmt(output))
6486
except UnicodeEncodeError:
6587
# If we hit an undecodeable entry, just try outputting as json.
66-
self.out(self.fmt(output, 'json'), newline=newline)
88+
self.out(self.fmt(output, 'json'))
89+
90+
def python_output(self, output):
91+
"""Prints out python code"""
92+
self.console.print(Syntax(output, "python"))
6793

6894
def input(self, prompt, default=None, show_default=True):
6995
"""Provide a command prompt."""

SoftLayer/CLI/firewall/add.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Create new firewall."""
1+
"""Add a server to the firewall"""
22
# :license: MIT, see LICENSE for more details.
33

44
import click
@@ -36,16 +36,14 @@ def cli(env, target, firewall_type, high_availability):
3636
pkg = mgr.get_standard_package(target, is_virt=False)
3737

3838
if not pkg:
39-
exceptions.CLIAbort(
40-
"Unable to add firewall - Is network public enabled?")
39+
exceptions.CLIAbort("Unable to add firewall - Is network public enabled?")
4140

42-
env.out("******************")
43-
env.out("Product: %s" % pkg[0]['description'])
44-
env.out("Price: $%s monthly" % pkg[0]['prices'][0]['recurringFee'])
45-
env.out("******************")
41+
click.echo("******************")
42+
click.echo("Product: %s" % pkg[0]['description'])
43+
click.echo("Price: $%s monthly" % pkg[0]['prices'][0]['recurringFee'])
44+
click.echo("******************")
4645

47-
if not formatting.confirm("This action will incur charges on your "
48-
"account. Continue?"):
46+
if not formatting.confirm("This action will incur charges on your account. Continue?"):
4947
raise exceptions.CLIAbort('Aborted.')
5048

5149
if firewall_type == 'vlan':
@@ -55,4 +53,4 @@ def cli(env, target, firewall_type, high_availability):
5553
elif firewall_type == 'server':
5654
mgr.add_standard_firewall(target, is_virt=False)
5755

58-
env.fout("Firewall is being created!")
56+
click.echo("Firewall is being created!")

0 commit comments

Comments
 (0)