Skip to content

Commit

Permalink
Improved table output for complex resource properties.
Browse files Browse the repository at this point in the history
HMC resource properties with complex types (arrays or nested objects)
so far were generated as the `repr()` string of the data value.

With this change, they are now generated as nested tables, wherei
possible. Specifically, the following constructs are used for these
inner tables, dependent on the output format:

* table/psql, simple, plain: Nested table in plain format.
* rst: Nested table in RST full table format (tabulate
  'grid' format).
* mediawiki: Nested table in mediawiki format.
* html: Nested table in html format.
* latex: Still the `repr()` string, as without this change. While
  LaTex very well supports nested tables, using them would require
  larger changes in the "tabulate" package to turn off the extra
  LaTex escaping it performs.

This change depends on the recently released version 0.8.1 of the
Python "tabulate" package. Adjusted the dependencies accordingly.

Signed-off-by: Andreas Maier <andreas.r.maier@gmx.de>
  • Loading branch information
andy-maier committed Oct 14, 2017
1 parent f5e88a6 commit de8f917
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 16 deletions.
4 changes: 3 additions & 1 deletion docs/changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
Change log
----------


Version 0.19.0
^^^^^^^^^^^^^^

Expand All @@ -36,6 +35,9 @@ Released: not yet

* Added initial set of function tests for zhmc command.

* Improved the table output of complex properties (arrays or nested objects),
to use nested tables, where possible. See issue #9.

**Known issues:**

* See `list of open issues`_.
Expand Down
2 changes: 1 addition & 1 deletion minimum-constraints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ pytz===2016.10
requests===2.12.4
six===1.10.0
stomp.py===4.1.15
tabulate===0.7.7
tabulate===0.8.1
pyreadline===2.1 #; sys_platform == "win32"


Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ click-repl>=0.1.0 # MIT
click-spinner>=0.1.6 # MIT
progressbar2>=3.12.0 # BSD
six>=1.10.0 # MIT
tabulate>=0.7.7 # MIT
tabulate>=0.8.1 # MIT
pyreadline>=2.1; sys_platform == "win32" # BSD

# Indirect dependencies (commented out, only listed to document their license):
Expand Down
126 changes: 113 additions & 13 deletions zhmccli/_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,24 @@
SYSLOG_FACILITIES = ['user', 'local0', 'local1', 'local2', 'local3', 'local4',
'local5', 'local6', 'local7']

# Inner table format for each outer table format, when tables are nested for
# complex property types (arrays, nested objects). If a format is not mapped
# here, the outer table format will be used for the inner table.
# The table formats are the format indicators of the "tabulate" package (not
# the formats supported by zhmccli). In addition, the inner table formats may
# be 'repr' which indicates to use the repr() string on the input data for
# the inner table.
INNER_TABLE_FORMAT = {
'psql': 'plain',
'simple': 'plain',
'rst': 'grid',
'grid': 'grid',
'latex': 'repr',
# TODO on latex: Use latex_raw once "tabulate" can better control escaping
# mediawiki: uses nested mediawiki tables
# html: uses nested html tables
}


def abort_if_false(ctx, param, value):
"""
Expand Down Expand Up @@ -351,18 +369,14 @@ def print_properties_as_table(properties, table_format, skip_list=None):
skip_list (iterable of string): The property names to be skipped.
If `None`, all properties are shown.
"""
additional_skip_list = (
'@@implementation-errors',
)
table = list()
sorted_fields = sorted(properties)
for field in sorted_fields:
if skip_list and field in skip_list or field in additional_skip_list:
continue
value = properties[field]
table.append((field, value))
headers = ['Field Name', 'Value']
click.echo(tabulate(table, headers, tablefmt=table_format))
_skip_list = [
'@@implementation-errors',
]
if skip_list:
_skip_list.extend(skip_list)
out_str = dict_as_table(properties, headers, table_format, _skip_list)
click.echo(out_str)


def print_resources_as_table(resources, table_format, show_list=None):
Expand Down Expand Up @@ -394,6 +408,7 @@ def print_resources_as_table(resources, table_format, show_list=None):
column order is ascending by property name.
"""
table = list()
inner_format = INNER_TABLE_FORMAT.get(table_format, table_format)
for i, resource in enumerate(resources):
properties = OrderedDict()
if show_list:
Expand All @@ -407,13 +422,98 @@ def print_resources_as_table(resources, table_format, show_list=None):
properties[name] = resource.prop(name)
if i == 0:
headers = properties.keys()
row = list(properties.values()) # Needed for Python 3 to sort by row
row = []
for value in properties.values():
value = value_as_table(value, inner_format)
row.append(value)
table.append(row)
if not table:
click.echo("No resources.")
else:
sorted_table = sorted(table, key=lambda row: row[0])
click.echo(tabulate(sorted_table, headers, tablefmt=table_format))
out_str = tabulate(sorted_table, headers, tablefmt=table_format)
click.echo(out_str)


def dict_as_table(data, headers, table_format, skip_list=None):
"""
Return a string with the dictionary data in tabular output format.
The order of rows is ascending by dictionary key.
Parameters:
data (dict): The dictionary data.
headers (list): The text for the header row. `None` means no header row.
table_format: Table format, see print_resources_as_table().
skip_list (iterable of string): The dict keys to be skipped.
If `None`, the entire dict data is shown.
"""
if table_format == 'repr':
ret_str = repr(data)
else:
table = list()
inner_format = INNER_TABLE_FORMAT.get(table_format, table_format)
sorted_fields = sorted(data)
for field in sorted_fields:
if skip_list and field in skip_list:
continue
value = value_as_table(data[field], inner_format)
table.append((field, value))
ret_str = tabulate(table, headers, tablefmt=table_format)
return ret_str


def list_as_table(data, table_format):
"""
Return a string with the list data in tabular output format.
The order of rows is the order of items in the list.
Parameters:
data (list): The list data.
table_format: Table format, see print_resources_as_table().
skip_list (iterable of string): The dict keys to be skipped.
If `None`, the entire dict data is shown.
"""
if table_format == 'repr':
ret_str = repr(data)
else:
table = list()
inner_format = INNER_TABLE_FORMAT.get(table_format, table_format)
for value in data:
value = value_as_table(value, inner_format)
table.append((value,))
ret_str = tabulate(table, headers=[], tablefmt=table_format)
return ret_str


def value_as_table(value, table_format):
"""
Return the value in the table format.
Parameters:
value (dict or list or simple type): The value to be converted.
table_format (string): The table format to be used.
Returns:
string or simple type: The value in the table format.
"""
if isinstance(value, list):
value = list_as_table(value, table_format)
elif isinstance(value, (dict, OrderedDict)):
value = dict_as_table(value, [], table_format)
else:
pass
return value


def print_properties_as_json(properties):
Expand Down

0 comments on commit de8f917

Please sign in to comment.