
This changes how we sort columns in listings and how we display the column headings. The default columns are now stored as lists so that they are the same from run to run. This simplifies some of the logic in the shell modules as well. Instead of keeping static mappings of attributes to column headings, we now use some simple python logic, to title case the columns. This commit covers: - cell-* commands - cloud-* commands - host-* commands - project-* commands - region-* commands Finally, we noticed that the cloud-list and region-list commands were behaving differently from the rest of the -list commands. This unifies the interface to add the --detail flag. Closes-bug: #1659103 Closes-bug: #1659427 Closes-bug: #1668221 Change-Id: If5906780e501c7b9ba93ecf54a7bcf6db5ddfa1c
182 lines
6.5 KiB
Python
182 lines
6.5 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.common import cliutils
|
|
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 = None
|
|
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 = cliutils.field_labels_from(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)
|