Ian Cordasco d1a88bad18 Add --format to the client shell
Add a new way of formatting our output in a consistent way. This turns
print_list and print_dict into a formatter that has the same API as any
other formatter and allows users to create their own formatters and plug
them into cratonclient.

This includes tests for the base level formatter and our two default
formatters as well as some refactoring to allow users to specify their
own --format.

At the moment, however, the subcommand shells do *not* use the pluggable
formatter decided by the user. That change and all of the downstream
effects it has on testing is going to be *very* significant and deserves
its own commit as this one is large enough.

Change-Id: I6649ebce57d5ddf2d4aeb689e77e3c17ef3a2e97
2017-02-27 14:31:59 -06:00

181 lines
6.4 KiB
Python

# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Pretty-table formatter implementation for the craton CLI."""
from __future__ import print_function
import textwrap
from oslo_utils import encodeutils
import prettytable
import six
from cratonclient.formatters import base
class Formatter(base.Formatter):
"""Implementation of the default table-style formatter."""
def after_init(self):
"""Set-up after initialization."""
self.fields = []
self.formatters = {}
self.sortby_index = 0
self.mixed_case_fields = set([])
self.field_labels = []
self.dict_property = "Property"
self.wrap = 0
self.dict_value = "Value"
def configure(self, fields=None, formatters=None, sortby_index=False,
mixed_case_fields=None, field_labels=None,
dict_property=None, dict_value=None, wrap=None):
"""Configure some of the settings used to print the tables.
Parameters that configure list presentation:
:param list fields:
List of field names as strings.
:param dict formatters:
Mapping of field names to formatter functions that accept the
resource.
:param int sortby_index:
The index of the field name in :param:`fields` to sort the table
rows by. If ``None``, PrettyTable will not sort the items at all.
:param list mixed_case_fields:
List of field names also in :param:`fields` that are mixed case
and need preprocessing prior to retrieving the attribute.
:param list field_labels:
List of field labels that need to match :param:`fields`.
Parameters that configure the plain resource representation:
:param str dict_property:
The name of the first column.
:param str dict_value:
The name of the second column.
:param int wrap:
Length at which to wrap the second column.
All of these may be specified, but will be ignored based on how the
formatter is executed.
"""
if fields is not None:
self.fields = fields
if field_labels is None:
self.field_labels = self.fields
elif len(field_labels) != len(self.fields):
raise ValueError(
"Field labels list %(labels)s has different number "
"of elements than fields list %(fields)s" %
{'labels': field_labels, 'fields': fields}
)
else:
self.field_labels = field_labels
if formatters is not None:
self.formatters = formatters
if sortby_index is not False:
try:
sortby_index = int(sortby_index)
except TypeError:
if sortby_index is not None:
raise ValueError(
'sortby_index must be None or an integer'
)
except ValueError:
raise
else:
if self.field_labels and (
sortby_index < 0 or
sortby_index > len(self.field_labels)):
raise ValueError(
'sortby_index must be a non-negative number less '
'than {}'.format(len(self.field_labels))
)
self.sortby_index = sortby_index
if mixed_case_fields is not None:
self.mixed_case_fields = set(mixed_case_fields)
if dict_property is not None:
self.dict_property = dict_property
if dict_value is not None:
self.dict_value = dict_value
if wrap is not None:
self.wrap = wrap
return self
def sortby_kwargs(self):
"""Generate the sortby keyword argument for PrettyTable."""
if self.sortby_index is None:
return {}
return {'sortby': self.field_labels[self.sortby_index]}
def build_table(self, field_labels, alignment='l'):
"""Create a PrettyTable instance based off of the labels."""
table = prettytable.PrettyTable(field_labels)
table.align = alignment
return table
def handle_generator(self, generator):
"""Handle a generator of resources."""
sortby_kwargs = self.sortby_kwargs()
table = self.build_table(self.field_labels)
for resource in generator:
row = []
for field in self.fields:
formatter = self.formatters.get(field)
if formatter is not None:
data = formatter(resource)
else:
if field in self.mixed_case_fields:
field_name = field.replace(' ', '_')
else:
field_name = field.lower().replace(' ', '_')
data = getattr(resource, field_name, '')
row.append(data)
table.add_row(row)
output = encodeutils.safe_encode(table.get_string(**sortby_kwargs))
if six.PY3:
output = output.decode()
print(output)
def handle_instance(self, instance):
"""Handle a single resource."""
table = self.build_table([self.dict_property, self.dict_value])
for key, value in sorted(instance.to_dict().items()):
if isinstance(value, dict):
value = six.text_type(value)
if self.wrap > 0:
value = textwrap.fill(six.text_type(value), self.wrap)
if value and isinstance(value, six.string_types) and '\n' in value:
lines = value.strip().split('\n')
column1 = key
for line in lines:
table.add_row([column1, line])
column1 = ''
else:
table.add_row([key, value])
output = encodeutils.safe_encode(table.get_string())
if six.PY3:
output = output.decode()
print(output)