Skip to content

Commit 1238377

Browse files
#1639 adding support for fancy tables
1 parent b5802ee commit 1238377

File tree

3 files changed

+74
-84
lines changed

3 files changed

+74
-84
lines changed

SoftLayer/CLI/environment.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import click
1111
import pkg_resources
12+
from rich.console import Console
1213

1314
import SoftLayer
1415
from SoftLayer.CLI import formatting
@@ -38,9 +39,18 @@ def __init__(self):
3839

3940
self._modules_loaded = False
4041

41-
def out(self, output, newline=True):
42+
def out(self, output):
4243
"""Outputs a string to the console (stdout)."""
43-
click.echo(output, nl=newline)
44+
console = Console()
45+
if self.format in ('json', 'jsonraw'):
46+
console.print_json(output)
47+
else:
48+
# If we want to print a list of tables, Rich doens't handle that well.
49+
if isinstance(output, list):
50+
for line in output:
51+
console.print(line)
52+
else:
53+
console.print(output)
4454

4555
def err(self, output, newline=True):
4656
"""Outputs an error string to the console (stderr)."""
@@ -56,14 +66,14 @@ def format_output_is_json(self):
5666
"""Return True if format output is json or jsonraw"""
5767
return 'json' in self.format
5868

59-
def fout(self, output, newline=True):
69+
def fout(self, output):
6070
"""Format the input and output to the console (stdout)."""
6171
if output is not None:
6272
try:
63-
self.out(self.fmt(output), newline=newline)
73+
self.out(self.fmt(output))
6474
except UnicodeEncodeError:
6575
# If we hit an undecodeable entry, just try outputting as json.
66-
self.out(self.fmt(output, 'json'), newline=newline)
76+
self.out(self.fmt(output, 'json'))
6777

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

SoftLayer/CLI/formatting.py

Lines changed: 44 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
import os
1111

1212
import click
13-
14-
import prettytable
13+
from rich import box
14+
from rich.table import Table as rTable
1515

1616
from SoftLayer.CLI import exceptions
1717
from SoftLayer import utils
@@ -22,42 +22,38 @@
2222
def format_output(data, fmt='table'): # pylint: disable=R0911,R0912
2323
"""Given some data, will format it for console output.
2424
25-
:param data: One of: String, Table, FormattedItem, List, Tuple,
26-
SequentialOutput
25+
:param data: One of: String, Table, FormattedItem, List, Tuple, SequentialOutput
2726
:param string fmt (optional): One of: table, raw, json, python
2827
"""
29-
if isinstance(data, str):
30-
if fmt in ('json', 'jsonraw'):
31-
return json.dumps(data)
28+
if fmt in ('json', 'jsonraw'):
29+
return json.dumps(data, indent=4, cls=CLIJSONEncoder)
30+
31+
if isinstance(data, str) or isinstance(data, rTable):
3232
return data
3333

3434
# responds to .prettytable()
35-
if hasattr(data, 'prettytable'):
36-
if fmt == 'table':
37-
return str(format_prettytable(data))
38-
elif fmt == 'raw':
39-
return str(format_no_tty(data))
35+
if hasattr(data, 'prettytable') and fmt in ('table', 'raw'):
36+
return format_prettytable(data)
4037

4138
# responds to .to_python()
4239
if hasattr(data, 'to_python'):
40+
print("to_python()")
4341
if fmt == 'json':
44-
return json.dumps(
45-
format_output(data, fmt='python'),
46-
indent=4,
47-
cls=CLIJSONEncoder)
42+
return json.dumps(format_output(data, fmt='python'), indent=4, cls=CLIJSONEncoder)
4843
elif fmt == 'jsonraw':
49-
return json.dumps(format_output(data, fmt='python'),
50-
cls=CLIJSONEncoder)
44+
return json.dumps(format_output(data, fmt='python'), cls=CLIJSONEncoder)
5145
elif fmt == 'python':
5246
return data.to_python()
5347

5448
# responds to .formatted
5549
if hasattr(data, 'formatted'):
50+
print("formatted()")
5651
if fmt == 'table':
5752
return data.formatted
5853

5954
# responds to .separator
6055
if hasattr(data, 'separator'):
56+
print("FUCKING THISNGS UP HERE")
6157
output = [format_output(d, fmt=fmt) for d in data if d]
6258
return str(SequentialOutput(data.separator, output))
6359

@@ -66,42 +62,19 @@ def format_output(data, fmt='table'): # pylint: disable=R0911,R0912
6662
output = [format_output(d, fmt=fmt) for d in data]
6763
if fmt == 'python':
6864
return output
69-
return format_output(listing(output, separator=os.linesep))
65+
return output
7066

7167
# fallback, convert this odd object to a string
72-
return data
68+
print(f"Casting this to string {data}")
69+
return str(data)
7370

7471

7572
def format_prettytable(table):
7673
"""Converts SoftLayer.CLI.formatting.Table instance to a prettytable."""
7774
for i, row in enumerate(table.rows):
7875
for j, item in enumerate(row):
7976
table.rows[i][j] = format_output(item)
80-
81-
ptable = table.prettytable()
82-
ptable.hrules = prettytable.FRAME
83-
ptable.horizontal_char = '.'
84-
ptable.vertical_char = ':'
85-
ptable.junction_char = ':'
86-
return ptable
87-
88-
89-
def format_no_tty(table):
90-
"""Converts SoftLayer.CLI.formatting.Table instance to a prettytable."""
91-
92-
for i, row in enumerate(table.rows):
93-
for j, item in enumerate(row):
94-
table.rows[i][j] = format_output(item, fmt='raw')
9577
ptable = table.prettytable()
96-
97-
for col in table.columns:
98-
ptable.align[col] = 'l'
99-
100-
ptable.hrules = prettytable.NONE
101-
ptable.border = False
102-
ptable.header = False
103-
ptable.left_padding_width = 0
104-
ptable.right_padding_width = 2
10578
return ptable
10679

10780

@@ -238,6 +211,7 @@ def to_python(self):
238211
return self
239212

240213
def __str__(self):
214+
print("CASTSDFSDFSDFSDFSDF")
241215
return self.separator.join(str(x) for x in self)
242216

243217

@@ -271,6 +245,9 @@ def __init__(self, columns, title=None):
271245
self.sortby = None
272246
self.title = title
273247

248+
def __str__(self):
249+
print("OK")
250+
274251
def add_row(self, row):
275252
"""Add a row to the table.
276253
@@ -288,28 +265,30 @@ def to_python(self):
288265
return items
289266

290267
def prettytable(self):
291-
"""Returns a new prettytable instance."""
292-
table = prettytable.PrettyTable(self.columns)
293-
294-
if self.sortby:
295-
if self.sortby in self.columns:
296-
table.sortby = self.sortby
297-
else:
298-
msg = "Column (%s) doesn't exist to sort by" % self.sortby
299-
raise exceptions.CLIAbort(msg)
300-
301-
if isinstance(self.align, str):
302-
table.align = self.align
303-
else:
304-
# Required because PrettyTable has a strict setter function for alignment
305-
for a_col, alignment in self.align.items():
306-
table.align[a_col] = alignment
268+
"""Returns a RICH table instance."""
269+
table = rTable(title=self.title, box=box.SQUARE, header_style="bright_cyan")
270+
271+
for col in self.columns:
272+
justify = "center"
273+
style = None
274+
# This case aligns all columns in a table
275+
if isinstance(self.align, str):
276+
justify = self.align
277+
# This case alings a specific column
278+
elif isinstance(self.align, dict) and self.align.get(col, False):
279+
justify = self.align.get(col)
280+
# Backwards compatibility with PrettyTable style alignments
281+
if justify == 'r':
282+
justify = 'right'
283+
if justify == 'l':
284+
justify = 'left'
285+
# Special coloring for some columns
286+
if col in ('id', 'Id', 'ID'):
287+
style = "pale_violet_red1"
288+
table.add_column(col, justify=justify, style=style)
307289

308-
if self.title:
309-
table.title = self.title
310-
# Adding rows
311290
for row in self.rows:
312-
table.add_row(row)
291+
table.add_row(*row)
313292
return table
314293

315294

@@ -346,7 +325,7 @@ def __str__(self):
346325
"""returns the formatted value."""
347326
# If the original value is None, represent this as 'NULL'
348327
if self.original is None:
349-
return 'NULL'
328+
return self.formatted or "NULL"
350329

351330
try:
352331
return str(self.original)

tests/CLI/environment_tests.py

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
"""
77

88
import click
9+
from rich.console import Console
910
from unittest import mock as mock
1011

1112
from SoftLayer.CLI import environment
13+
from SoftLayer.CLI import formatting
1214
from SoftLayer import testing
1315

1416

@@ -44,9 +46,7 @@ def test_get_command(self):
4446
@mock.patch('click.prompt')
4547
def test_input(self, prompt_mock):
4648
r = self.env.input('input')
47-
prompt_mock.assert_called_with('input',
48-
default=None,
49-
show_default=True)
49+
prompt_mock.assert_called_with('input', default=None, show_default=True)
5050
self.assertEqual(prompt_mock(), r)
5151

5252
@mock.patch('click.prompt')
@@ -71,17 +71,18 @@ def test_resolve_alias(self):
7171
r = self.env.resolve_alias('realname')
7272
self.assertEqual(r, 'realname')
7373

74-
@mock.patch('click.echo')
75-
def test_print_unicode(self, echo):
76-
output = "\u3010TEST\u3011 image"
77-
# https://docs.python.org/3.6/library/exceptions.html#UnicodeError
78-
echo.side_effect = [
79-
UnicodeEncodeError('utf8', output, 0, 1, "Test Exception"),
80-
output
81-
]
82-
self.env.fout(output)
83-
self.assertEqual(2, echo.call_count)
84-
8574
def test_format_output_is_json(self):
8675
self.env.format = 'jsonraw'
8776
self.assertTrue(self.env.format_output_is_json())
77+
78+
@mock.patch('rich.console.Console.print')
79+
def test_multiple_tables(self, console):
80+
tables = []
81+
table1 = formatting.Table(["First", "Second"])
82+
table1.add_row(["1", 2])
83+
table2 = formatting.Table(["T2-1", "T2-2"])
84+
table2.add_row(["zzzzzzz", "123123123"])
85+
tables.append(table1)
86+
tables.append(table2)
87+
self.env.out(tables)
88+
self.assertEqual(2, len(console.call_args_list))

0 commit comments

Comments
 (0)