Skip to content

Commit

Permalink
Change output format for instance names table in display_cimobject
Browse files Browse the repository at this point in the history
The original output formatting for

instance enumerate --no

was useless.  It showed 3 4 columns host, ns, class, keybindings

where the keybindings was a single string.

Modified it to show"

host, namespace, class, one column per key which allows the user to
compare keys between instances.

This does not do anything for associations where the reference
properties are keys, however.

In the case of enumerates of instances starting from a point above
the hierarchy (ex. CIM_ManagedElement) it produces a table for each
set of key definitions (i.e. all the instances with the same keys
in the same table.   This means producing multiple tables.
Note also, that the elements in each table are only linked by

1. They are instances of some class in the hierarchy.
2. They have the same keynames.

They may be instances that are not strongly related (i.e. from
completely different parts of the hiearchy.

Fixes the current tests for instance enumerate ... --no
  • Loading branch information
kschopmeyer authored and andy-maier committed Jan 1, 2022
1 parent b55025f commit 3a9493f
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 72 deletions.
3 changes: 3 additions & 0 deletions docs/changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ Released: not yet

* Implement an end-end test for the subscription command group.

* Changed output format for table output of instance enumerate --no option to
show each key as a column in the table so that keys are more readable.

**Cleanup:**

* Prepared the development environment for having more than one pywbemtools
Expand Down
95 changes: 74 additions & 21 deletions pywbemtools/pywbemcli/_display_cimobjects.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@
CIMError, CIM_ERR_NOT_SUPPORTED
from pywbem._nocasedict import NocaseDict

from ._common import sort_cimobjects
from ._common import sort_cimobjects, to_wbem_uri_folded
from ._cimvalueformatter import cimvalue_to_fmtd_string
from .._utils import get_terminal_width
from .._output_formatting import DEFAULT_MAX_CELL_WIDTH, \
output_format_is_table, format_table, fold_strings, format_keys
output_format_is_table, format_table, fold_strings

INT_TYPE_PATTERN = re.compile(r'^[su]int(8|16|32|64)$')

Expand Down Expand Up @@ -296,30 +296,83 @@ def _display_paths_as_table(objects, table_width, table_format):
title = 'Classnames:'
headers = ['Class Name']
rows = [[obj] for obj in objects]

elif isinstance(objects[0], CIMClassName):
title = 'Classnames'
headers = ('host', 'namespace', 'class')
rows = [[obj.host, obj.namespace, obj.classname] for obj in objects]

elif isinstance(objects[0], CIMInstanceName):
title = 'InstanceNames: {}'.format(objects[0].classname)
host_hdr = 'host'
ns_hdr = 'namespace'
class_hdr = 'class'
host_hdr_len = len(host_hdr) + 4
ns_hdr_len = len(ns_hdr) + 3
class_hdr_len = len(class_hdr) + 3
headers = (host_hdr, ns_hdr, class_hdr, 'keysbindings')

host_lens = [len(obj.host) for obj in objects if obj.host]
host_max = max(host_lens) if host_lens else host_hdr_len
ns_lens = [len(obj.namespace) for obj in objects if obj.namespace]
ns_max = max(ns_lens) if ns_lens else ns_hdr_len
class_lens = [len(obj.classname) for obj in objects]
class_max = max(class_lens) if class_lens else class_hdr_len

max_key_len = (table_width) - (host_max + ns_max + class_max + 3)
rows = [[obj.host, obj.namespace, obj.classname,
format_keys(obj, max_key_len)] for obj in objects]
# Since there are cases where the list has instances with
# different keys (i.e. enumerate CIM_ManagedElement), build
# a dictionary for instances with the same key names.
# Sort into dictionary for each set of key names. If all keys are
# the same, there will be one table. This allows building
# a table for each set of keynames
objs_by_key_set = {}
original_keys = {}

for instname in objects:
# key_names is a tuple of sorted key names in lower case so
# all instances with the same set of keys ends up in a
# single dictionary item. NOTE: objects must be tuple to be
# a dictionary key, the key.
test_key_names = tuple(
sorted(map(lambda x: x.lower(), instname.keys())))
try:
objs_by_key_set[test_key_names].append(instname)
except KeyError:
objs_by_key_set[test_key_names] = [instname]

# Set the original keys into dict to recover correct key
# case later
if test_key_names not in original_keys:
original_keys[test_key_names] = instname.keys()

# If multiple key_names we create multiple tables and add table
# number to each table title.
table_number = 0
for key_names, inst_names in objs_by_key_set.items():
# Build headers for this table with the common elements and key
# names for each key in the object. We use inst_names[0] to
# restore original case to key strings.
inst_keys = original_keys[key_names]

rows = []
for instname in inst_names:
# Build header row for this table
row = [instname.host, instname.namespace,
instname.classname]
for key in inst_keys:
if isinstance(instname[key], CIMInstanceName):
# If key is CIMInstanceName, fold the value
row.append(to_wbem_uri_folded(
instname[key], format='standard', max_len=30))
else:
row.append(instname[key])
rows.append(row)

# If multiple tables, number them as hint to reader that there
# are multiples.
if len(objs_by_key_set) > 1:
table_number += 1
table_number_str = ", (table #{})".format(table_number)
else:
table_number_str = ''

headers = ['host', 'namespace', 'class'] + \
["key=\n{0}".format(kn) for kn in inst_keys]

title = 'InstanceNames: {0}{1}'.format(inst_names[0].classname,
table_number_str)

# Generate multiple tables, one for each key_name and
# return local to this scope.
click.echo(format_table(rows, headers, title=title,
table_format=table_format))

return # Return to avoid following table output

else:
raise click.ClickException("{0} invalid type ({1})for path display".
format(objects[0], type(objects[0])))
Expand Down
100 changes: 55 additions & 45 deletions tests/unit/pywbemcli/test_instance_cmds.py
Original file line number Diff line number Diff line change
Expand Up @@ -806,35 +806,25 @@ def GET_TEST_PATH_STR(filename): # pylint: disable=invalid-name

['Verify instance command -o grid enumerate CIM_Foo --di --no',
{'args': ['enumerate', 'CIM_Foo', '--di', '--no'],
'general': ['--output-format', 'grid']},
'general': ['--output-format', 'table']},
{'stdout': """InstanceNames: CIM_Foo
+--------+-------------+-----------------+-------------------------------+
| host | namespace | class | keysbindings |
+========+=============+=================+===============================+
| | root/cimv2 | CIM_Foo | InstanceID="CIM_Foo1" |
+--------+-------------+-----------------+-------------------------------+
| | root/cimv2 | CIM_Foo | InstanceID="CIM_Foo2" |
+--------+-------------+-----------------+-------------------------------+
| | root/cimv2 | CIM_Foo | InstanceID="CIM_Foo3" |
+--------+-------------+-----------------+-------------------------------+
| | root/cimv2 | CIM_Foo | InstanceID="CIM_Foo30" |
+--------+-------------+-----------------+-------------------------------+
| | root/cimv2 | CIM_Foo | InstanceID="CIM_Foo31" |
+--------+-------------+-----------------+-------------------------------+
| | root/cimv2 | CIM_Foo_sub | InstanceID="CIM_Foo_sub1" |
+--------+-------------+-----------------+-------------------------------+
| | root/cimv2 | CIM_Foo_sub | InstanceID="CIM_Foo_sub2" |
+--------+-------------+-----------------+-------------------------------+
| | root/cimv2 | CIM_Foo_sub | InstanceID="CIM_Foo_sub3" |
+--------+-------------+-----------------+-------------------------------+
| | root/cimv2 | CIM_Foo_sub | InstanceID="CIM_Foo_sub4" |
+--------+-------------+-----------------+-------------------------------+
| | root/cimv2 | CIM_Foo_sub_sub | InstanceID="CIM_Foo_sub_sub1" |
+--------+-------------+-----------------+-------------------------------+
| | root/cimv2 | CIM_Foo_sub_sub | InstanceID="CIM_Foo_sub_sub2" |
+--------+-------------+-----------------+-------------------------------+
| | root/cimv2 | CIM_Foo_sub_sub | InstanceID="CIM_Foo_sub_sub3" |
+--------+-------------+-----------------+-------------------------------+
+--------+-------------+-----------------+------------------+
| host | namespace | class | key= |
| | | | InstanceID |
|--------+-------------+-----------------+------------------|
| | root/cimv2 | CIM_Foo | CIM_Foo1 |
| | root/cimv2 | CIM_Foo | CIM_Foo2 |
| | root/cimv2 | CIM_Foo | CIM_Foo3 |
| | root/cimv2 | CIM_Foo | CIM_Foo30 |
| | root/cimv2 | CIM_Foo | CIM_Foo31 |
| | root/cimv2 | CIM_Foo_sub | CIM_Foo_sub1 |
| | root/cimv2 | CIM_Foo_sub | CIM_Foo_sub2 |
| | root/cimv2 | CIM_Foo_sub | CIM_Foo_sub3 |
| | root/cimv2 | CIM_Foo_sub | CIM_Foo_sub4 |
| | root/cimv2 | CIM_Foo_sub_sub | CIM_Foo_sub_sub1 |
| | root/cimv2 | CIM_Foo_sub_sub | CIM_Foo_sub_sub2 |
| | root/cimv2 | CIM_Foo_sub_sub | CIM_Foo_sub_sub3 |
+--------+-------------+-----------------+------------------+
""",
'test': 'innows'},
SIMPLE_MOCK_FILE, OK],
Expand Down Expand Up @@ -946,28 +936,48 @@ def GET_TEST_PATH_STR(filename): # pylint: disable=invalid-name
{'args': ['enumerate', 'CIM_Foo', '--names-only'],
'general': ['--output-format', 'table']},
{'stdout': """InstanceNames: CIM_Foo
+--------+-------------+-----------------+-------------------------------+
| host | namespace | class | keysbindings |
|--------+-------------+-----------------+-------------------------------|
| | root/cimv2 | CIM_Foo | InstanceID="CIM_Foo1" |
| | root/cimv2 | CIM_Foo | InstanceID="CIM_Foo2" |
| | root/cimv2 | CIM_Foo | InstanceID="CIM_Foo3" |
| | root/cimv2 | CIM_Foo | InstanceID="CIM_Foo30" |
| | root/cimv2 | CIM_Foo | InstanceID="CIM_Foo31" |
| | root/cimv2 | CIM_Foo_sub | InstanceID="CIM_Foo_sub1" |
| | root/cimv2 | CIM_Foo_sub | InstanceID="CIM_Foo_sub2" |
| | root/cimv2 | CIM_Foo_sub | InstanceID="CIM_Foo_sub3" |
| | root/cimv2 | CIM_Foo_sub | InstanceID="CIM_Foo_sub4" |
| | root/cimv2 | CIM_Foo_sub_sub | InstanceID="CIM_Foo_sub_sub1" |
| | root/cimv2 | CIM_Foo_sub_sub | InstanceID="CIM_Foo_sub_sub2" |
| | root/cimv2 | CIM_Foo_sub_sub | InstanceID="CIM_Foo_sub_sub3" |
+--------+-------------+-----------------+-------------------------------+
+--------+-------------+-----------------+------------------+
| host | namespace | class | key= |
| | | | InstanceID |
|--------+-------------+-----------------+------------------|
| | root/cimv2 | CIM_Foo | CIM_Foo1 |
| | root/cimv2 | CIM_Foo | CIM_Foo2 |
| | root/cimv2 | CIM_Foo | CIM_Foo3 |
| | root/cimv2 | CIM_Foo | CIM_Foo30 |
| | root/cimv2 | CIM_Foo | CIM_Foo31 |
| | root/cimv2 | CIM_Foo_sub | CIM_Foo_sub1 |
| | root/cimv2 | CIM_Foo_sub | CIM_Foo_sub2 |
| | root/cimv2 | CIM_Foo_sub | CIM_Foo_sub3 |
| | root/cimv2 | CIM_Foo_sub | CIM_Foo_sub4 |
| | root/cimv2 | CIM_Foo_sub_sub | CIM_Foo_sub_sub1 |
| | root/cimv2 | CIM_Foo_sub_sub | CIM_Foo_sub_sub2 |
| | root/cimv2 | CIM_Foo_sub_sub | CIM_Foo_sub_sub3 |
+--------+-------------+-----------------+------------------+
""",
'rc': 0,
'test': 'linesnows'},
SIMPLE_MOCK_FILE, OK],

['Verify command enumerate with ssociation class inst name table output',
{'args': ['enumerate', 'TST_A3', '--names-only'],
'general': ['--output-format', 'table']},
{'stdout': """InstanceNames: TST_A3
+--------+-------------+---------+---------------------+---------------------+---------------------+
| host | namespace | class | key= | key= | key= |
| | | | Initiator | Target | LogicalUnit |
|--------+-------------+---------+---------------------+---------------------+---------------------|
| | root/cimv2 | TST_A3 | /root/cimv2:TST_EP. | /root/cimv2:TST_EP. | /root/cimv2:TST_LD. |
| | | | InstanceID=1 | InstanceID=2 | InstanceID=3 |
| | root/cimv2 | TST_A3 | /root/cimv2:TST_EP. | /root/cimv2:TST_EP. | /root/cimv2:TST_LD. |
| | | | InstanceID=1 | InstanceID=5 | InstanceID=6 |
| | root/cimv2 | TST_A3 | /root/cimv2:TST_EP. | /root/cimv2:TST_EP. | /root/cimv2:TST_LD. |
| | | | InstanceID=1 | InstanceID=7 | InstanceID=8 |
+--------+-------------+---------+---------------------+---------------------+---------------------+
""", # noqa: E501
'rc': 0,
'test': 'linesnows'},
COMPLEX_ASSOC_MODEL, OK],

['Verify command enumerate with CIM_Foo summary table output',
{'args': ['enumerate', 'CIM_Foo', '--summary'],
'general': ['--output-format', 'table']},
Expand Down
14 changes: 8 additions & 6 deletions tests/unit/pywbemcli/test_subscription_cmds.py
Original file line number Diff line number Diff line change
Expand Up @@ -784,8 +784,9 @@ def GET_TEST_PATH_STR(filename): # pylint: disable=invalid-name
' Destination = "http://someone:50000";',
' Protocol = 2;',
# Result -o plain subscription list-destinations --names-only
'host namespace class keysbindings',
'interop CIM_ListenerDestinationCIMXML',
'host namespace class key= key= key=',
'interop CIM_ListenerDestinationCIMXML CIM_ComputerSystem '
'MockSystem_WBEMServerTest CIM_ListenerDestinationCIMXML',
# Result list
'1 CIMInstance(s) returned',
'};'],
Expand Down Expand Up @@ -834,8 +835,8 @@ def GET_TEST_PATH_STR(filename): # pylint: disable=invalid-name
'};',
'1 CIMInstance(s) returned',
# Result from -o plain subscription list-filters --names-only
'host namespace class keysbindings',
'interop CIM_IndicationFilter', ],
'host namespace class key= key= key= key=',
'interop CIM_IndicationFilter CIM_ComputerSystem MockSystem_WBEMServerTest CIM_IndicationFilter pywbemfilter:defaultpywbemcliSubMgr:ofilter1', ], # noqa: E501
'stderr': [],
'test': 'innows'},
None, OK],
Expand Down Expand Up @@ -874,8 +875,9 @@ def GET_TEST_PATH_STR(filename): # pylint: disable=invalid-name
' SubscriptionState = 2;',
'};',
# Limited result -o plain ... list-subscriptions --name-only
# The actual output it to ugly to compare.
'host namespace class keysbindings',
'host namespace class key=',
'Filter Handler',
'interop CIM_IndicationSubscription /interop:CIM_IndicationFilter. /interop:CIM_ListenerDestinationCIMXML.', # noqa: E501
'};'],
'stderr': [],
'test': 'innows'},
Expand Down

0 comments on commit 3a9493f

Please sign in to comment.