|
Packit Service |
f2d567 |
# Copyright (C) 2017 Red Hat, Inc., Bryn M. Reeves <bmr@redhat.com>
|
|
Packit Service |
f2d567 |
#
|
|
Packit Service |
f2d567 |
# boom/report.py - Text reporting
|
|
Packit Service |
f2d567 |
#
|
|
Packit Service |
f2d567 |
# This file is part of the boom project.
|
|
Packit Service |
f2d567 |
#
|
|
Packit Service |
f2d567 |
# This copyrighted material is made available to anyone wishing to use,
|
|
Packit Service |
f2d567 |
# modify, copy, or redistribute it subject to the terms and conditions
|
|
Packit Service |
f2d567 |
# of the GNU General Public License v.2.
|
|
Packit Service |
f2d567 |
#
|
|
Packit Service |
f2d567 |
# You should have received a copy of the GNU Lesser General Public License
|
|
Packit Service |
f2d567 |
# along with this program; if not, write to the Free Software Foundation,
|
|
Packit Service |
f2d567 |
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
Packit Service |
f2d567 |
"""The Boom reporting module contains a set of classes for creating
|
|
Packit Service |
f2d567 |
simple text based tabular reports for a user-defined set of object
|
|
Packit Service |
f2d567 |
types and fields. No restrictions are placed on the types of object
|
|
Packit Service |
f2d567 |
that can be reported: users of the ``BoomReport`` classes may define
|
|
Packit Service |
f2d567 |
additional object types outside the ``boom`` package and include these
|
|
Packit Service |
f2d567 |
types in reports generated by the module.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
The fields displayed in a specific report may be selected from the
|
|
Packit Service |
f2d567 |
available set of fields by specifying a simple comma-separated string
|
|
Packit Service |
f2d567 |
list of field names (in display order). In addition, custom multi-column
|
|
Packit Service |
f2d567 |
sorting is possible using a similar string notation.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
The ``BoomReport`` module is closely based on the ``device-mapper``
|
|
Packit Service |
f2d567 |
reporting engine and shares many features and behaviours with device
|
|
Packit Service |
f2d567 |
mapper reports.
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
from __future__ import print_function
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
from boom import find_minimum_sha_prefix, BOOM_DEBUG_REPORT
|
|
Packit Service |
f2d567 |
import logging
|
|
Packit Service |
f2d567 |
import sys
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
_log = logging.getLogger(__name__)
|
|
Packit Service |
f2d567 |
_log.set_debug_mask(BOOM_DEBUG_REPORT)
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
_log_debug = _log.debug
|
|
Packit Service |
f2d567 |
_log_debug_report = _log.debug_masked
|
|
Packit Service |
f2d567 |
_log_info = _log.info
|
|
Packit Service |
f2d567 |
_log_warn = _log.warning
|
|
Packit Service |
f2d567 |
_log_error = _log.error
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
_default_columns = 80
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
REP_NUM = "num"
|
|
Packit Service |
f2d567 |
REP_STR = "str"
|
|
Packit Service |
f2d567 |
REP_SHA = "sha"
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
_dtypes = [REP_NUM, REP_STR, REP_SHA]
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
_default_width = 8
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
ALIGN_LEFT = "left"
|
|
Packit Service |
f2d567 |
ALIGN_RIGHT = "right"
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
_align_types = [ALIGN_LEFT, ALIGN_RIGHT]
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
ASCENDING = "ascending"
|
|
Packit Service |
f2d567 |
DESCENDING = "descending"
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
STANDARD_QUOTE = "'"
|
|
Packit Service |
f2d567 |
STANDARD_PAIR = "="
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
MIN_SHA_WIDTH = 7
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
# Python2 vs. Python2 string types
|
|
Packit Service |
f2d567 |
try:
|
|
Packit Service |
f2d567 |
# Py2
|
|
Packit Service |
f2d567 |
string_types = (str, unicode)
|
|
Packit Service |
f2d567 |
except NameError:
|
|
Packit Service |
f2d567 |
# Py3
|
|
Packit Service |
f2d567 |
string_types = (str)
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
num_types = (int, float)
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
class BoomReportOpts(object):
|
|
Packit Service |
f2d567 |
"""BoomReportOpts()
|
|
Packit Service |
f2d567 |
Options controlling the formatting and output of a boom report.
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
columns = 0
|
|
Packit Service |
f2d567 |
headings = True
|
|
Packit Service |
f2d567 |
buffered = True
|
|
Packit Service |
f2d567 |
separator = None
|
|
Packit Service |
f2d567 |
field_name_prefix = None
|
|
Packit Service |
f2d567 |
unquoted = True
|
|
Packit Service |
f2d567 |
aligned = True
|
|
Packit Service |
f2d567 |
columns_as_rows = False
|
|
Packit Service |
f2d567 |
report_file = None
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
def __init__(self, columns=_default_columns, headings=True, buffered=True,
|
|
Packit Service |
f2d567 |
separator=" ", field_name_prefix="", unquoted=True,
|
|
Packit Service |
f2d567 |
aligned=True, report_file=sys.stdout):
|
|
Packit Service |
f2d567 |
"""Initialise BoomReportOpts object.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
Initialise a ``BoomReportOpts`` object to control output
|
|
Packit Service |
f2d567 |
of a ``BoomReport``.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
:param columns: the number of columns to use for output.
|
|
Packit Service |
f2d567 |
:param headings: a boolean indicating whether to output
|
|
Packit Service |
f2d567 |
column headings for this report.
|
|
Packit Service |
f2d567 |
:param buffered: a boolean indicating whether to buffer
|
|
Packit Service |
f2d567 |
output from this report.
|
|
Packit Service |
f2d567 |
:param report_file: a file to which output will be sent.
|
|
Packit Service |
f2d567 |
:returns: a new ``BoomReportOpts`` object.
|
|
Packit Service |
f2d567 |
:rtype: ``<class BoomReportOpts>``
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
self.columns = columns
|
|
Packit Service |
f2d567 |
self.headings = headings
|
|
Packit Service |
f2d567 |
self.buffered = buffered
|
|
Packit Service |
f2d567 |
self.separator = separator
|
|
Packit Service |
f2d567 |
self.field_name_prefix = field_name_prefix
|
|
Packit Service |
f2d567 |
self.unquoted = unquoted
|
|
Packit Service |
f2d567 |
self.aligned = aligned
|
|
Packit Service |
f2d567 |
self.report_file = report_file
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
class BoomReportObjType(object):
|
|
Packit Service |
f2d567 |
"""BoomReportObjType()
|
|
Packit Service |
f2d567 |
Class representing a type of objecct to be reported on.
|
|
Packit Service |
f2d567 |
Instances of ``BoomReportObjType`` must specify an identifier,
|
|
Packit Service |
f2d567 |
a description, and a data function that will return the correct
|
|
Packit Service |
f2d567 |
type of object from a compound object containing data objects
|
|
Packit Service |
f2d567 |
of different types. For reports that use only a single object
|
|
Packit Service |
f2d567 |
type the ``data_fn`` member may be simply ``lambda x: x``.
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
objtype = -1
|
|
Packit Service |
f2d567 |
desc = ""
|
|
Packit Service |
f2d567 |
prefix = ""
|
|
Packit Service |
f2d567 |
data_fn = None
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
def __init__(self, objtype, desc, prefix, data_fn):
|
|
Packit Service |
f2d567 |
"""Initialise BoomReportObjType.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
Initialise a new ``BoomReportObjType`` object with the
|
|
Packit Service |
f2d567 |
specified ``objtype``, ``desc``, optional ``prefix`` and
|
|
Packit Service |
f2d567 |
``data_fn``. The ``objtype`` must be an integer power of two
|
|
Packit Service |
f2d567 |
that is unique within a given report. The ``data_fn`` should
|
|
Packit Service |
f2d567 |
accept an object as its only argument and return an object
|
|
Packit Service |
f2d567 |
of the requested type.
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
if not objtype or objtype < 0:
|
|
Packit Service |
f2d567 |
raise ValueError("BoomReportObjType objtype cannot be <= 0.")
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
if not desc:
|
|
Packit Service |
f2d567 |
raise ValueError("BoomReportObjType desc cannot be empty.")
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
if not data_fn:
|
|
Packit Service |
f2d567 |
raise ValueError("BoomReportObjType requires data_fn.")
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
self.objtype = objtype
|
|
Packit Service |
f2d567 |
self.desc = desc
|
|
Packit Service |
f2d567 |
self.prefix = prefix
|
|
Packit Service |
f2d567 |
self.data_fn = data_fn
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
class BoomFieldType(object):
|
|
Packit Service |
f2d567 |
"""BoomFieldType()
|
|
Packit Service |
f2d567 |
The ``BoomFieldType`` class describes the properties of a field
|
|
Packit Service |
f2d567 |
available in a ``BoomReport`` instance.
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
objtype = -1
|
|
Packit Service |
f2d567 |
name = None
|
|
Packit Service |
f2d567 |
head = None
|
|
Packit Service |
f2d567 |
desc = None
|
|
Packit Service |
f2d567 |
width = _default_width
|
|
Packit Service |
f2d567 |
align = None
|
|
Packit Service |
f2d567 |
dtype = None
|
|
Packit Service |
f2d567 |
report_fn = None
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
def __init__(self, objtype, name, head, desc, width, dtype, report_fn,
|
|
Packit Service |
f2d567 |
align=None):
|
|
Packit Service |
f2d567 |
"""Initialise new BoomFieldType object.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
Initialise a new ``BoomFieldType`` object with the specified
|
|
Packit Service |
f2d567 |
properties.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
:param objtype: The numeric object type ID (power of two)
|
|
Packit Service |
f2d567 |
:param name: The field name used to select display fields
|
|
Packit Service |
f2d567 |
:param desc: A human-readable description of the field
|
|
Packit Service |
f2d567 |
:param width: The default (initial) field width
|
|
Packit Service |
f2d567 |
:param dtype: The BoomReport data type of the field
|
|
Packit Service |
f2d567 |
:param report_fn: The field reporting function
|
|
Packit Service |
f2d567 |
:param align: The field alignment value
|
|
Packit Service |
f2d567 |
:returns: A new BoomReportFieldType object
|
|
Packit Service |
f2d567 |
:rtype: BoomReportFieldType
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
if not objtype:
|
|
Packit Service |
f2d567 |
raise ValueError("'objtype' must be non-zero")
|
|
Packit Service |
f2d567 |
if not name:
|
|
Packit Service |
f2d567 |
raise ValueError("'name' is required")
|
|
Packit Service |
f2d567 |
self.objtype = objtype
|
|
Packit Service |
f2d567 |
self.name = name
|
|
Packit Service |
f2d567 |
self.head = head
|
|
Packit Service |
f2d567 |
self.desc = desc
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
if dtype not in _dtypes:
|
|
Packit Service |
f2d567 |
raise ValueError("Invalid field dtype: %s " % dtype)
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
if align and align not in _align_types:
|
|
Packit Service |
f2d567 |
raise ValueError("Invalid field alignment: %s" % align)
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
self.dtype = dtype
|
|
Packit Service |
f2d567 |
self.report_fn = report_fn
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
if not align:
|
|
Packit Service |
f2d567 |
if dtype == REP_STR or dtype == REP_SHA:
|
|
Packit Service |
f2d567 |
self.align = ALIGN_LEFT
|
|
Packit Service |
f2d567 |
if dtype == REP_NUM:
|
|
Packit Service |
f2d567 |
self.align = ALIGN_RIGHT
|
|
Packit Service |
f2d567 |
else:
|
|
Packit Service |
f2d567 |
self.align = align
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
if width < 0:
|
|
Packit Service |
f2d567 |
raise ValueError("Field width cannot be < 0")
|
|
Packit Service |
f2d567 |
self.width = width if width else _default_width
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
class BoomFieldProperties(object):
|
|
Packit Service |
f2d567 |
field_num = None
|
|
Packit Service |
f2d567 |
# sort_posn
|
|
Packit Service |
f2d567 |
initial_width = 0
|
|
Packit Service |
f2d567 |
width = 0
|
|
Packit Service |
f2d567 |
objtype = None
|
|
Packit Service |
f2d567 |
dtype = None
|
|
Packit Service |
f2d567 |
align = None
|
|
Packit Service |
f2d567 |
#
|
|
Packit Service |
f2d567 |
# Field flags
|
|
Packit Service |
f2d567 |
#
|
|
Packit Service |
f2d567 |
hidden = False
|
|
Packit Service |
f2d567 |
implicit = False
|
|
Packit Service |
f2d567 |
sort_key = False
|
|
Packit Service |
f2d567 |
sort_dir = None
|
|
Packit Service |
f2d567 |
compact_one = False # used for implicit fields
|
|
Packit Service |
f2d567 |
compacted = False
|
|
Packit Service |
f2d567 |
sort_posn = None
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
class BoomField(object):
|
|
Packit Service |
f2d567 |
"""BoomField()
|
|
Packit Service |
f2d567 |
A ``BoomField`` represents an instance of a ``BoomFieldType``
|
|
Packit Service |
f2d567 |
including its associated data values.
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
#: reference to the containing BoomReport
|
|
Packit Service |
f2d567 |
_report = None
|
|
Packit Service |
f2d567 |
#: reference to the BoomFieldProperties describing this field
|
|
Packit Service |
f2d567 |
_props = None
|
|
Packit Service |
f2d567 |
#: The formatted string to be reported for this field.
|
|
Packit Service |
f2d567 |
report_string = None
|
|
Packit Service |
f2d567 |
#: The raw value of this field. Used for sorting.
|
|
Packit Service |
f2d567 |
sort_value = None
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
def __init__(self, report, props):
|
|
Packit Service |
f2d567 |
"""Initialise a new BoomField object.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
Initialise a BoomField object and configure the supplied
|
|
Packit Service |
f2d567 |
``report`` and ``props`` attributes.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
:param report: The BoomReport that owns this field
|
|
Packit Service |
f2d567 |
:param props: The BoomFieldProperties object for this field
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
self._report = report
|
|
Packit Service |
f2d567 |
self._props = props
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
def report_str(self, value):
|
|
Packit Service |
f2d567 |
"""Report a string value for this BoomField object.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
Set the value for this field to the supplied ``value``.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
:param value: The string value to set
|
|
Packit Service |
f2d567 |
:rtype: None
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
if not isinstance(value, string_types):
|
|
Packit Service |
f2d567 |
raise TypeError("Value for report_str() must be a string type.")
|
|
Packit Service |
f2d567 |
self.set_value(value, sort_value=value)
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
def report_sha(self, value):
|
|
Packit Service |
f2d567 |
"""Report a SHA value for this BoomField object.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
Set the value for this field to the supplied ``value``.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
:param value: The SHA value to set
|
|
Packit Service |
f2d567 |
:rtype: None
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
if not isinstance(value, string_types):
|
|
Packit Service |
f2d567 |
raise TypeError("Value for report_sha() must be a string type.")
|
|
Packit Service |
f2d567 |
self.set_value(value, sort_value=value)
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
def report_num(self, value):
|
|
Packit Service |
f2d567 |
"""Report a numeric value for this BoomField object.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
Set the value for this field to the supplied ``value``.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
:param value: The numeric value to set
|
|
Packit Service |
f2d567 |
:rtype: None
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
if value is not None and not isinstance(value, num_types):
|
|
Packit Service |
f2d567 |
raise TypeError("Value for report_num() must be a numeric type.")
|
|
Packit Service |
f2d567 |
report_string = str(value)
|
|
Packit Service |
f2d567 |
sort_value = value if value is not None else -1
|
|
Packit Service |
f2d567 |
self.set_value(report_string, sort_value=sort_value)
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
def set_value(self, report_string, sort_value=None):
|
|
Packit Service |
f2d567 |
"""Report an arbitrary value for this BoomField object.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
Set the value for this field to the supplied ``value``,
|
|
Packit Service |
f2d567 |
and set the field's ``sort_value`` to the supplied
|
|
Packit Service |
f2d567 |
``sort_value``.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
:param report_string: The string value to set
|
|
Packit Service |
f2d567 |
:param sort_value: The sort value
|
|
Packit Service |
f2d567 |
:rtype: None
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
if report_string is None:
|
|
Packit Service |
f2d567 |
raise ValueError("No value assigned to field.")
|
|
Packit Service |
f2d567 |
self.report_string = report_string
|
|
Packit Service |
f2d567 |
self.sort_value = sort_value if sort_value else report_string
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
class BoomRow(object):
|
|
Packit Service |
f2d567 |
"""BoomRow()
|
|
Packit Service |
f2d567 |
A class representing a single data row making up a report.
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
#: the report that this BoomRow belongs to
|
|
Packit Service |
f2d567 |
_report = None
|
|
Packit Service |
f2d567 |
#: the list of report fields in display order
|
|
Packit Service |
f2d567 |
_fields = None
|
|
Packit Service |
f2d567 |
#: fields in sort order
|
|
Packit Service |
f2d567 |
_sort_fields = None
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
def __init__(self, report):
|
|
Packit Service |
f2d567 |
self._report = report
|
|
Packit Service |
f2d567 |
self._fields = []
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
def add_field(self, field):
|
|
Packit Service |
f2d567 |
"""Add a field to this BoomRow.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
:param field: The field to be added
|
|
Packit Service |
f2d567 |
:rtype: None
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
self._fields.append(field)
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
def __none_returning_fn(obj):
|
|
Packit Service |
f2d567 |
"""Dummy data function for special report types.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
:returns: None
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
return None
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
# Implicit report fields and types
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
BR_SPECIAL = 0x80000000
|
|
Packit Service |
f2d567 |
_implicit_special_report_types = [
|
|
Packit Service |
f2d567 |
BoomReportObjType(
|
|
Packit Service |
f2d567 |
BR_SPECIAL, "Special", "special_", __none_returning_fn
|
|
Packit Service |
f2d567 |
)
|
|
Packit Service |
f2d567 |
]
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
def __no_report_fn(f, d):
|
|
Packit Service |
f2d567 |
"""Dummy report function for special report types.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
:returns: None
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
return
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
_special_field_help_name = "help"
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
_implicit_special_report_fields = [
|
|
Packit Service |
f2d567 |
BoomFieldType(
|
|
Packit Service |
f2d567 |
BR_SPECIAL, _special_field_help_name, "Help", "Show help", 8,
|
|
Packit Service |
f2d567 |
REP_STR, __no_report_fn)
|
|
Packit Service |
f2d567 |
]
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
# BoomReport class
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
class BoomReport(object):
|
|
Packit Service |
f2d567 |
"""BoomReport()
|
|
Packit Service |
f2d567 |
A class representing a configurable text report with multiple
|
|
Packit Service |
f2d567 |
caller-defined fields. An optional title may be provided and he
|
|
Packit Service |
f2d567 |
``fields`` argument must contain a list of ``BoomField`` objects
|
|
Packit Service |
f2d567 |
describing the required report.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
report_types = 0
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
_fields = None
|
|
Packit Service |
f2d567 |
_types = None
|
|
Packit Service |
f2d567 |
_data = None
|
|
Packit Service |
f2d567 |
_rows = None
|
|
Packit Service |
f2d567 |
_keys_count = 0
|
|
Packit Service |
f2d567 |
_field_properties = None
|
|
Packit Service |
f2d567 |
_header_written = False
|
|
Packit Service |
f2d567 |
_field_calc_needed = True
|
|
Packit Service |
f2d567 |
_sort_required = False
|
|
Packit Service |
f2d567 |
_already_reported = False
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
# Implicit field support
|
|
Packit Service |
f2d567 |
_implicit_types = _implicit_special_report_types
|
|
Packit Service |
f2d567 |
_implicit_fields = _implicit_special_report_fields
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
private = None
|
|
Packit Service |
f2d567 |
opts = None
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
def __help_requested(self):
|
|
Packit Service |
f2d567 |
"""Check for presence of 'help' fields in output selection.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
Check the fields making up this BoomReport and return True
|
|
Packit Service |
f2d567 |
if any valid 'help' field synonym is present.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
:returns: True if help was requested or False otherwise
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
for fp in self._field_properties:
|
|
Packit Service |
f2d567 |
if fp.implicit:
|
|
Packit Service |
f2d567 |
name = self._implicit_fields[fp.field_num].name
|
|
Packit Service |
f2d567 |
if name == _special_field_help_name:
|
|
Packit Service |
f2d567 |
return True
|
|
Packit Service |
f2d567 |
return False
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
def __get_longest_field_name_len(self, fields):
|
|
Packit Service |
f2d567 |
"""Find the longest field name length.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
:returns: the length of the longest configured field name
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
max_len = 0
|
|
Packit Service |
f2d567 |
for f in fields:
|
|
Packit Service |
f2d567 |
cur_len = len(f.name)
|
|
Packit Service |
f2d567 |
max_len = cur_len if cur_len > max_len else max_len
|
|
Packit Service |
f2d567 |
for t in self._types:
|
|
Packit Service |
f2d567 |
cur_len = len(t.prefix) + 3
|
|
Packit Service |
f2d567 |
max_len = cur_len if cur_len > max_len else max_len
|
|
Packit Service |
f2d567 |
return max_len
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
def __display_fields(self, display_field_types):
|
|
Packit Service |
f2d567 |
"""Display report fields help message.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
Display a list of valid fields for this ``BoomReport``.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
:param fields: The list of fields to display
|
|
Packit Service |
f2d567 |
:param display_field_types: A boolean controling whether
|
|
Packit Service |
f2d567 |
field types (str, SHA, num)
|
|
Packit Service |
f2d567 |
are included in help output
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
fields = self._fields
|
|
Packit Service |
f2d567 |
name_len = self.__get_longest_field_name_len(fields)
|
|
Packit Service |
f2d567 |
last_desc = ""
|
|
Packit Service |
f2d567 |
banner = "-" * 79
|
|
Packit Service |
f2d567 |
for f in fields:
|
|
Packit Service |
f2d567 |
t = self.__find_type(f.objtype)
|
|
Packit Service |
f2d567 |
if t:
|
|
Packit Service |
f2d567 |
desc = t.desc
|
|
Packit Service |
f2d567 |
else:
|
|
Packit Service |
f2d567 |
desc = ""
|
|
Packit Service |
f2d567 |
if desc != last_desc:
|
|
Packit Service |
f2d567 |
if len(last_desc):
|
|
Packit Service |
f2d567 |
print(" ")
|
|
Packit Service |
f2d567 |
desc_len = len(desc) + 7
|
|
Packit Service |
f2d567 |
print("%s Fields" % desc)
|
|
Packit Service |
f2d567 |
print("%*.*s" % (desc_len, desc_len, banner))
|
|
Packit Service |
f2d567 |
print(" %-*s - %s%s%s%s" %
|
|
Packit Service |
f2d567 |
(name_len, f.name, f.desc,
|
|
Packit Service |
f2d567 |
" [" if display_field_types else "",
|
|
Packit Service |
f2d567 |
f.dtype if display_field_types else "",
|
|
Packit Service |
f2d567 |
"]" if display_field_types else ""))
|
|
Packit Service |
f2d567 |
last_desc = desc
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
def __find_type(self, report_type):
|
|
Packit Service |
f2d567 |
"""Resolve numeric type to corresponding BoomReportObjType.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
:param report_type: The numeric report type to look up
|
|
Packit Service |
f2d567 |
:returns: The requested BoomReportObjType.
|
|
Packit Service |
f2d567 |
:raises: ValueError if no matching type was found.
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
for t in self._implicit_types:
|
|
Packit Service |
f2d567 |
if t.objtype == report_type:
|
|
Packit Service |
f2d567 |
return t
|
|
Packit Service |
f2d567 |
for t in self._types:
|
|
Packit Service |
f2d567 |
if t.objtype == report_type:
|
|
Packit Service |
f2d567 |
return t
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
raise ValueError("Unknown report object type: %d" % report_type)
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
def __copy_field(self, field_num, implicit):
|
|
Packit Service |
f2d567 |
"""Copy field definition to BoomFieldProperties
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
Copy values from a BoomFieldType to BoomFieldProperties.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
:param field_num: The number of this field (fields order)
|
|
Packit Service |
f2d567 |
:param implicit: True if this field is implicit, else False
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
fp = BoomFieldProperties()
|
|
Packit Service |
f2d567 |
fp.field_num = field_num
|
|
Packit Service |
f2d567 |
fp.width = fp.initial_width = self._fields[field_num].width
|
|
Packit Service |
f2d567 |
fp.implicit = implicit
|
|
Packit Service |
f2d567 |
fp.objtype = self.__find_type(self._fields[field_num].objtype)
|
|
Packit Service |
f2d567 |
fp.dtype = self._fields[field_num].dtype
|
|
Packit Service |
f2d567 |
fp.align = self._fields[field_num].align
|
|
Packit Service |
f2d567 |
return fp
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
def __add_field(self, field_num, implicit):
|
|
Packit Service |
f2d567 |
"""Add a field to this BoomReport.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
Add the specified BoomFieldType to this BoomReport and
|
|
Packit Service |
f2d567 |
configure BoomFieldProperties for it.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
:param field_num: The number of this field (fields order)
|
|
Packit Service |
f2d567 |
:param implicit: True if this field is implicit, else False
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
fp = self.__copy_field(field_num, implicit)
|
|
Packit Service |
f2d567 |
if fp.hidden:
|
|
Packit Service |
f2d567 |
self._field_properties.insert(0, fp)
|
|
Packit Service |
f2d567 |
else:
|
|
Packit Service |
f2d567 |
self._field_properties.append(fp)
|
|
Packit Service |
f2d567 |
return fp
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
def __get_field(self, field_name):
|
|
Packit Service |
f2d567 |
"""Look up a field by name.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
Attempt to find the field named in ``field_name`` in this
|
|
Packit Service |
f2d567 |
BoomReport's tables of implicit and user-defined fields,
|
|
Packit Service |
f2d567 |
returning the a ``(field, implicit)`` tuple, where field
|
|
Packit Service |
f2d567 |
contains the requested ``BoomFieldType``, and ``implicit``
|
|
Packit Service |
f2d567 |
is a boolean indicating whether this field is implicit or
|
|
Packit Service |
f2d567 |
not.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
:param field_num: The number of this field (fields order)
|
|
Packit Service |
f2d567 |
:param implicit: True if this field is implicit, else False
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
# FIXME implicit fields
|
|
Packit Service |
f2d567 |
for field in self._implicit_fields:
|
|
Packit Service |
f2d567 |
if field.name == field_name:
|
|
Packit Service |
f2d567 |
return (self._implicit_fields.index(field), True)
|
|
Packit Service |
f2d567 |
for field in self._fields:
|
|
Packit Service |
f2d567 |
if field.name == field_name:
|
|
Packit Service |
f2d567 |
return (self._fields.index(field), False)
|
|
Packit Service |
f2d567 |
raise ValueError("No matching field name: %s" % field_name)
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
def __field_match(self, field_name, type_only):
|
|
Packit Service |
f2d567 |
"""Attempt to match a field and optionally update report type.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
Look up the named field and, if ``type_only`` is True,
|
|
Packit Service |
f2d567 |
update this BoomReport's ``report_types`` mask to include
|
|
Packit Service |
f2d567 |
the field's type identifier. If ``type_only`` is False the
|
|
Packit Service |
f2d567 |
field is also added to this BoomReport's field list.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
:param field_name: A string identifying the field
|
|
Packit Service |
f2d567 |
:param type_only: True if this call should only update types
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
try:
|
|
Packit Service |
f2d567 |
(f, implicit) = self.__get_field(field_name)
|
|
Packit Service |
f2d567 |
if (type_only):
|
|
Packit Service |
f2d567 |
if implicit:
|
|
Packit Service |
f2d567 |
self.report_types |= self._implicit_fields[f].objtype
|
|
Packit Service |
f2d567 |
else:
|
|
Packit Service |
f2d567 |
self.report_types |= self._fields[f].objtype
|
|
Packit Service |
f2d567 |
return
|
|
Packit Service |
f2d567 |
return self.__add_field(f, implicit)
|
|
Packit Service |
f2d567 |
except ValueError as e:
|
|
Packit Service |
f2d567 |
# FIXME handle '$PREFIX_all'
|
|
Packit Service |
f2d567 |
# re-raise 'e' if it fails.
|
|
Packit Service |
f2d567 |
raise e
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
def __parse_fields(self, field_format, type_only):
|
|
Packit Service |
f2d567 |
"""Parse report field list.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
Parse ``field_format`` and attempt to match the names of
|
|
Packit Service |
f2d567 |
field names found to registered BoomFieldType fields.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
If ``type_only`` is True only the ``report_types`` field
|
|
Packit Service |
f2d567 |
is updated: otherwise the parsed fields are added to the
|
|
Packit Service |
f2d567 |
BoomReport's field list.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
:param field_format: The list of fields to parse
|
|
Packit Service |
f2d567 |
:param type_only: True if this call should only update types
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
for word in field_format.split(','):
|
|
Packit Service |
f2d567 |
# Allow consecutive commas
|
|
Packit Service |
f2d567 |
if not word:
|
|
Packit Service |
f2d567 |
continue
|
|
Packit Service |
f2d567 |
try:
|
|
Packit Service |
f2d567 |
self.__field_match(word, type_only)
|
|
Packit Service |
f2d567 |
except ValueError as e:
|
|
Packit Service |
f2d567 |
self.__display_fields(True)
|
|
Packit Service |
f2d567 |
print("Unrecognised field: %s" % word)
|
|
Packit Service |
f2d567 |
raise e
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
def __add_sort_key(self, field_num, sort, implicit, type_only):
|
|
Packit Service |
f2d567 |
"""Add a new sort key to this BoomReport
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
Add the sort key identified by ``field_num`` to this list
|
|
Packit Service |
f2d567 |
of sort keys for this BoomReport.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
:param field_num: The field number of the key to add
|
|
Packit Service |
f2d567 |
:param sort: The sort direction for this key
|
|
Packit Service |
f2d567 |
:param implicit: True if field_num is implicit, else False
|
|
Packit Service |
f2d567 |
:param type_only: True if this call should only update types
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
fields = self._implicit_fields if implicit else self._fields
|
|
Packit Service |
f2d567 |
found = None
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
for fp in self._field_properties:
|
|
Packit Service |
f2d567 |
if fp.implicit == implicit and fp.field_num == field_num:
|
|
Packit Service |
f2d567 |
found = fp
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
if not found:
|
|
Packit Service |
f2d567 |
if type_only:
|
|
Packit Service |
f2d567 |
self.report_types |= fields[field_num].objtype
|
|
Packit Service |
f2d567 |
return
|
|
Packit Service |
f2d567 |
else:
|
|
Packit Service |
f2d567 |
found = self.__add_field(field_num, implicit)
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
if found.sort_key:
|
|
Packit Service |
f2d567 |
_log_info("Ignoring duplicate sort field: %s" %
|
|
Packit Service |
f2d567 |
fields[field_num].name)
|
|
Packit Service |
f2d567 |
found.sort_key = True
|
|
Packit Service |
f2d567 |
found.sort_dir = sort
|
|
Packit Service |
f2d567 |
found.sort_posn = self._keys_count
|
|
Packit Service |
f2d567 |
self._keys_count += 1
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
def __key_match(self, key_name, type_only):
|
|
Packit Service |
f2d567 |
"""Attempt to match a sort key and update report type.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
Look up the named sort key and, if ``type_only`` is True,
|
|
Packit Service |
f2d567 |
update this BoomReport's ``report_types`` mask to include
|
|
Packit Service |
f2d567 |
the field's type identifier. If ``type_only`` is False the
|
|
Packit Service |
f2d567 |
field is also added to this BoomReport's field list.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
:param field_name: A string identifying the sort key
|
|
Packit Service |
f2d567 |
:param type_only: True if this call should only update types
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
sort_dir = None
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
if not key_name:
|
|
Packit Service |
f2d567 |
raise ValueError("Sort key name cannot be empty")
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
if key_name.startswith('+'):
|
|
Packit Service |
f2d567 |
sort_dir = ASCENDING
|
|
Packit Service |
f2d567 |
key_name = key_name[1:]
|
|
Packit Service |
f2d567 |
elif key_name.startswith('-'):
|
|
Packit Service |
f2d567 |
sort_dir = DESCENDING
|
|
Packit Service |
f2d567 |
key_name = key_name[1:]
|
|
Packit Service |
f2d567 |
else:
|
|
Packit Service |
f2d567 |
sort_dir = ASCENDING
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
for field in self._implicit_fields:
|
|
Packit Service |
f2d567 |
fields = self._implicit_fields
|
|
Packit Service |
f2d567 |
if field.name == key_name:
|
|
Packit Service |
f2d567 |
return self.__add_sort_key(fields.index(field), sort_dir,
|
|
Packit Service |
f2d567 |
True, type_only)
|
|
Packit Service |
f2d567 |
for field in self._fields:
|
|
Packit Service |
f2d567 |
fields = self._fields
|
|
Packit Service |
f2d567 |
if field.name == key_name:
|
|
Packit Service |
f2d567 |
return self.__add_sort_key(fields.index(field), sort_dir,
|
|
Packit Service |
f2d567 |
False, type_only)
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
raise ValueError("Unknown sort key name: %s" % key_name)
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
def __parse_keys(self, keys, type_only):
|
|
Packit Service |
f2d567 |
"""Parse report sort key list.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
Parse ``keys`` and attempt to match the names of
|
|
Packit Service |
f2d567 |
sort keys found to registered BoomFieldType fields.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
If ``type_only`` is True only the ``report_types`` field
|
|
Packit Service |
f2d567 |
is updated: otherwise the parsed fields are added to the
|
|
Packit Service |
f2d567 |
BoomReport's sort key list.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
:param field_format: The list of fields to parse
|
|
Packit Service |
f2d567 |
:param type_only: True if this call should only update types
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
if not keys:
|
|
Packit Service |
f2d567 |
return
|
|
Packit Service |
f2d567 |
for word in keys.split(','):
|
|
Packit Service |
f2d567 |
# Allow consecutive commas
|
|
Packit Service |
f2d567 |
if not word:
|
|
Packit Service |
f2d567 |
continue
|
|
Packit Service |
f2d567 |
try:
|
|
Packit Service |
f2d567 |
self.__key_match(word, type_only)
|
|
Packit Service |
f2d567 |
except ValueError as e:
|
|
Packit Service |
f2d567 |
self.__display_fields(True)
|
|
Packit Service |
f2d567 |
print("Unrecognised field: %s" % word)
|
|
Packit Service |
f2d567 |
raise e
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
def __init__(self, types, fields, output_fields, opts,
|
|
Packit Service |
f2d567 |
sort_keys, private):
|
|
Packit Service |
f2d567 |
"""Initialise BoomReport.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
Initialise a new ``BoomReport`` object with the specified fields
|
|
Packit Service |
f2d567 |
and output control options.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
:param types: List of BoomReportObjType used in this report.
|
|
Packit Service |
f2d567 |
:param fields: A list of ``BoomField`` field descriptions.
|
|
Packit Service |
f2d567 |
:param output_fields: An optional list of output fields to
|
|
Packit Service |
f2d567 |
be rendered by this report.
|
|
Packit Service |
f2d567 |
:param opts: An instance of ``BoomReportOpts`` or None.
|
|
Packit Service |
f2d567 |
:returns: A new report object.
|
|
Packit Service |
f2d567 |
:rtype: ``BoomReport``.
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
self._fields = fields
|
|
Packit Service |
f2d567 |
self._types = types
|
|
Packit Service |
f2d567 |
self._private = private
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
if opts.buffered:
|
|
Packit Service |
f2d567 |
self._sort_required = True
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
self.opts = opts if opts else BoomReportOpts()
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
self._rows = []
|
|
Packit Service |
f2d567 |
self._field_properties = []
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
# set field_prefix from type
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
# canonicalize_field_ids()
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
if not output_fields:
|
|
Packit Service |
f2d567 |
output_fields = ",".join([field.name for field in fields])
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
# First pass: set up types
|
|
Packit Service |
f2d567 |
self.__parse_fields(output_fields, 1)
|
|
Packit Service |
f2d567 |
self.__parse_keys(sort_keys, 1)
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
# Second pass: initialise fields
|
|
Packit Service |
f2d567 |
self.__parse_fields(output_fields, 0)
|
|
Packit Service |
f2d567 |
self.__parse_keys(sort_keys, 0)
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
if self.__help_requested():
|
|
Packit Service |
f2d567 |
self._already_reported = True
|
|
Packit Service |
f2d567 |
self.__display_fields(display_field_types=True)
|
|
Packit Service |
f2d567 |
print("")
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
def __recalculate_sha_width(self):
|
|
Packit Service |
f2d567 |
"""Recalculate minimum SHA field widths.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
For each REP_SHA field present, recalculate the minimum
|
|
Packit Service |
f2d567 |
field width required to ensure uniqueness of the displayed
|
|
Packit Service |
f2d567 |
values.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
:rtype: None
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
shas = {}
|
|
Packit Service |
f2d567 |
props_map = {}
|
|
Packit Service |
f2d567 |
for row in self._rows:
|
|
Packit Service |
f2d567 |
for field in row._fields:
|
|
Packit Service |
f2d567 |
if self._fields[field._props.field_num].dtype == REP_SHA:
|
|
Packit Service |
f2d567 |
# Use field_num as index to apply check across rows
|
|
Packit Service |
f2d567 |
num = field._props.field_num
|
|
Packit Service |
f2d567 |
if num not in shas:
|
|
Packit Service |
f2d567 |
shas[num] = set()
|
|
Packit Service |
f2d567 |
props_map[num] = field._props
|
|
Packit Service |
f2d567 |
shas[num].add(field.report_string)
|
|
Packit Service |
f2d567 |
for num in shas.keys():
|
|
Packit Service |
f2d567 |
min_prefix = max(MIN_SHA_WIDTH, props_map[num].width)
|
|
Packit Service |
f2d567 |
props_map[num].width = find_minimum_sha_prefix(shas[num],
|
|
Packit Service |
f2d567 |
min_prefix)
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
def __recalculate_fields(self):
|
|
Packit Service |
f2d567 |
"""Recalculate field widths.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
For each field, recalculate the minimum field width by
|
|
Packit Service |
f2d567 |
finding the longest ``report_string`` value for that field
|
|
Packit Service |
f2d567 |
and updating the dynamic width stored in the corresponding
|
|
Packit Service |
f2d567 |
``BoomFieldProperties`` object.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
:rtype: None
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
for row in self._rows:
|
|
Packit Service |
f2d567 |
for field in row._fields:
|
|
Packit Service |
f2d567 |
if self._sort_required and field._props.sort_key:
|
|
Packit Service |
f2d567 |
row._sort_fields[field._props.sort_posn] = field
|
|
Packit Service |
f2d567 |
if self._fields[field._props.field_num].dtype == REP_SHA:
|
|
Packit Service |
f2d567 |
continue
|
|
Packit Service |
f2d567 |
field_len = len(field.report_string)
|
|
Packit Service |
f2d567 |
if field_len > field._props.width:
|
|
Packit Service |
f2d567 |
field._props.width = field_len
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
def __report_headings(self):
|
|
Packit Service |
f2d567 |
"""Output report headings.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
Output the column headings for this BoomReport.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
:rtype: None
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
self._header_written = True
|
|
Packit Service |
f2d567 |
if not self.opts.headings:
|
|
Packit Service |
f2d567 |
return
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
line = ""
|
|
Packit Service |
f2d567 |
props = self._field_properties
|
|
Packit Service |
f2d567 |
for fp in props:
|
|
Packit Service |
f2d567 |
if fp.hidden:
|
|
Packit Service |
f2d567 |
continue
|
|
Packit Service |
f2d567 |
fields = self._fields
|
|
Packit Service |
f2d567 |
heading = fields[fp.field_num].head
|
|
Packit Service |
f2d567 |
headertuple = (fp.width, fp.width, heading)
|
|
Packit Service |
f2d567 |
if self.opts.aligned:
|
|
Packit Service |
f2d567 |
heading = "%-*.*s" % headertuple
|
|
Packit Service |
f2d567 |
line += heading
|
|
Packit Service |
f2d567 |
if props.index(fp) != (len(props) - 1):
|
|
Packit Service |
f2d567 |
line += self.opts.separator
|
|
Packit Service |
f2d567 |
self.opts.report_file.write(line + "\n")
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
def __row_key_fn(self):
|
|
Packit Service |
f2d567 |
"""Return a Python key function to compare report rows.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
The ``cmp`` argument of sorting functions has been removed
|
|
Packit Service |
f2d567 |
in Python 3.x: to maintain similarity with the device-mapper
|
|
Packit Service |
f2d567 |
report library we keep a traditional "cmp"-style function
|
|
Packit Service |
f2d567 |
(that is structured identically to the version in the device
|
|
Packit Service |
f2d567 |
mapper library), and dynamically wrap it in a ``__RowKey``
|
|
Packit Service |
f2d567 |
object to conform to the Python sort key model.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
:returns: A __RowKey object wrapping _row_cmp()
|
|
Packit Service |
f2d567 |
:rtype: __RowKey
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
def _row_cmp(row_a, row_b):
|
|
Packit Service |
f2d567 |
"""Compare two report rows for sorting.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
Compare the report rows ``row_a`` and ``row_b`` and
|
|
Packit Service |
f2d567 |
return a "cmp"-style comparison value:
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
1 if row_a > row_b
|
|
Packit Service |
f2d567 |
0 if row_a == row_b
|
|
Packit Service |
f2d567 |
-1 if row_b < row_a
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
Note that the actual comparison direction depends on the
|
|
Packit Service |
f2d567 |
field definitions of the fields being compared, since
|
|
Packit Service |
f2d567 |
each sort key defines its own sort order.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
:param row_a: The first row to compare
|
|
Packit Service |
f2d567 |
:param row_b: The seconf row to compare
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
for cnt in range(0, row_a._report._keys_count):
|
|
Packit Service |
f2d567 |
sfa = row_a._sort_fields[cnt]
|
|
Packit Service |
f2d567 |
sfb = row_b._sort_fields[cnt]
|
|
Packit Service |
f2d567 |
if sfa._props.dtype == REP_NUM:
|
|
Packit Service |
f2d567 |
num_a = sfa.sort_value
|
|
Packit Service |
f2d567 |
num_b = sfb.sort_value
|
|
Packit Service |
f2d567 |
if num_a == num_b:
|
|
Packit Service |
f2d567 |
continue
|
|
Packit Service |
f2d567 |
if sfa._props.sort_dir == ASCENDING:
|
|
Packit Service |
f2d567 |
return 1 if num_a > num_b else -1
|
|
Packit Service |
f2d567 |
else:
|
|
Packit Service |
f2d567 |
return 1 if num_a < num_b else -1
|
|
Packit Service |
f2d567 |
else:
|
|
Packit Service |
f2d567 |
stra = sfa.sort_value
|
|
Packit Service |
f2d567 |
strb = sfb.sort_value
|
|
Packit Service |
f2d567 |
if stra == strb:
|
|
Packit Service |
f2d567 |
continue
|
|
Packit Service |
f2d567 |
if sfa._props.sort_dir == ASCENDING:
|
|
Packit Service |
f2d567 |
return 1 if stra > strb else -1
|
|
Packit Service |
f2d567 |
else:
|
|
Packit Service |
f2d567 |
return 1 if stra < strb else -1
|
|
Packit Service |
f2d567 |
return 0
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
class __RowKey(object):
|
|
Packit Service |
f2d567 |
"""__RowKey sort wrapper.
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
def __init__(self, obj, *args):
|
|
Packit Service |
f2d567 |
"""Initialise a new __RowKey object.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
:param obj: The object to be compared
|
|
Packit Service |
f2d567 |
:returns: None
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
self.obj = obj
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
def __lt__(self, other):
|
|
Packit Service |
f2d567 |
"""Test if less than.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
:param other: The other object to be compared
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
return _row_cmp(self.obj, other.obj) < 0
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
def __gt__(self, other):
|
|
Packit Service |
f2d567 |
"""Test if greater than.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
:param other: The other object to be compared
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
return _row_cmp(self.obj, other.obj) > 0
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
def __eq__(self, other):
|
|
Packit Service |
f2d567 |
"""Test if equal to.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
:param other: The other object to be compared
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
return _row_cmp(self.obj, other.obj) == 0
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
def __le__(self, other):
|
|
Packit Service |
f2d567 |
"""Test if less than or equal to.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
:param other: The other object to be compared
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
return _row_cmp(self.obj, other.obj) <= 0
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
def __ge__(self, other):
|
|
Packit Service |
f2d567 |
"""Test if greater than or equal to.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
:param other: The other object to be compared
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
return _row_cmp(self.obj, other.obj) >= 0
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
def __ne__(self, other):
|
|
Packit Service |
f2d567 |
"""Test if not equal to.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
:param other: The other object to be compared
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
return _row_cmp(self.obj, other.obj) != 0
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
return __RowKey
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
def _sort_rows(self):
|
|
Packit Service |
f2d567 |
"""Sort the rows of this BoomReport.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
Sort this report's rows, according to the configured sort
|
|
Packit Service |
f2d567 |
keys.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
:returns: None
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
self._rows.sort(key=self.__row_key_fn())
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
def report_object(self, obj):
|
|
Packit Service |
f2d567 |
"""Report data for object.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
Add a row of data to this ``BoomReport``. The ``data``
|
|
Packit Service |
f2d567 |
argument should be an object of the type understood by this
|
|
Packit Service |
f2d567 |
report's fields. It will be passed in turn to each field to
|
|
Packit Service |
f2d567 |
obtain data for the current row.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
:param obj: the object to report on for this row.
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
if obj is None:
|
|
Packit Service |
f2d567 |
raise ValueError("Cannot report NoneType object.")
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
if self._already_reported:
|
|
Packit Service |
f2d567 |
return
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
row = BoomRow(self)
|
|
Packit Service |
f2d567 |
fields = self._fields
|
|
Packit Service |
f2d567 |
if self._sort_required:
|
|
Packit Service |
f2d567 |
row._sort_fields = [-1] * self._keys_count
|
|
Packit Service |
f2d567 |
for fp in self._field_properties:
|
|
Packit Service |
f2d567 |
field = BoomField(self, fp)
|
|
Packit Service |
f2d567 |
data = fp.objtype.data_fn(obj)
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
if data is None:
|
|
Packit Service |
f2d567 |
raise ValueError("No data assigned to field %s" %
|
|
Packit Service |
f2d567 |
fields[fp.field_num].name)
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
try:
|
|
Packit Service |
f2d567 |
fields[fp.field_num].report_fn(field, data)
|
|
Packit Service |
f2d567 |
except ValueError:
|
|
Packit Service |
f2d567 |
raise ValueError("No value assigned to field %s" %
|
|
Packit Service |
f2d567 |
fields[fp.field_num].name)
|
|
Packit Service |
f2d567 |
row.add_field(field)
|
|
Packit Service |
f2d567 |
self._rows.append(row)
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
if not self.opts.buffered:
|
|
Packit Service |
f2d567 |
return self.report_output()
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
def _output_field(self, field):
|
|
Packit Service |
f2d567 |
"""Output field data.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
Generate string data for one field in a report row.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
:field: The field to be output
|
|
Packit Service |
f2d567 |
:returns: The output report string for this field
|
|
Packit Service |
f2d567 |
:rtype: str
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
fields = self._fields
|
|
Packit Service |
f2d567 |
prefix = self.opts.field_name_prefix
|
|
Packit Service |
f2d567 |
quote = "" if self.opts.unquoted else STANDARD_QUOTE
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
if prefix:
|
|
Packit Service |
f2d567 |
field_name = fields[field._props.field_num].name
|
|
Packit Service |
f2d567 |
prefix += "%s%s%s" % (field_name.upper(), STANDARD_PAIR,
|
|
Packit Service |
f2d567 |
STANDARD_QUOTE)
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
repstr = field.report_string
|
|
Packit Service |
f2d567 |
width = field._props.width
|
|
Packit Service |
f2d567 |
if self.opts.aligned:
|
|
Packit Service |
f2d567 |
align = field._props.align
|
|
Packit Service |
f2d567 |
if not align:
|
|
Packit Service |
f2d567 |
if field._props.dtype == REP_NUM:
|
|
Packit Service |
f2d567 |
align = ALIGN_RIGHT
|
|
Packit Service |
f2d567 |
else:
|
|
Packit Service |
f2d567 |
align = ALIGN_LEFT
|
|
Packit Service |
f2d567 |
reptuple = (width, width, repstr)
|
|
Packit Service |
f2d567 |
if align == ALIGN_LEFT:
|
|
Packit Service |
f2d567 |
repstr = "%-*.*s" % reptuple
|
|
Packit Service |
f2d567 |
else:
|
|
Packit Service |
f2d567 |
repstr = "%*.*s" % reptuple
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
suffix = quote
|
|
Packit Service |
f2d567 |
return prefix + repstr + suffix
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
def _output_as_rows(self):
|
|
Packit Service |
f2d567 |
"""Output this report in column format.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
Output the data contained in this ``BoomReport`` in column
|
|
Packit Service |
f2d567 |
format, one row per line. If column headings have not been
|
|
Packit Service |
f2d567 |
printed already they will be automatically displayed by this
|
|
Packit Service |
f2d567 |
call.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
:returns: None
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
for fp in self._field_properties:
|
|
Packit Service |
f2d567 |
if fp.hidden:
|
|
Packit Service |
f2d567 |
for row in self._rows:
|
|
Packit Service |
f2d567 |
row._fields = row._fields[1:]
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
fields = self._implicit_fields if fp.implicit else self._fields
|
|
Packit Service |
f2d567 |
line = ""
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
if self.opts.headings:
|
|
Packit Service |
f2d567 |
line += fields[fp.field_num].head + self.opts.separator
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
for row in self._rows:
|
|
Packit Service |
f2d567 |
field = row._fields[0]
|
|
Packit Service |
f2d567 |
line += self._output_field(field)
|
|
Packit Service |
f2d567 |
line += self.opts.separator
|
|
Packit Service |
f2d567 |
row._fields = row._fields[1:]
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
self.opts.report_file.write(line + "\n")
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
def _output_as_columns(self):
|
|
Packit Service |
f2d567 |
"""Output this report in column format.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
Output the data contained in this ``BoomReport`` in column
|
|
Packit Service |
f2d567 |
format, one row per line. If column headings have not been
|
|
Packit Service |
f2d567 |
printed already they will be automatically displayed by this
|
|
Packit Service |
f2d567 |
call.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
:returns: None
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
if not self._header_written:
|
|
Packit Service |
f2d567 |
self.__report_headings()
|
|
Packit Service |
f2d567 |
for row in self._rows:
|
|
Packit Service |
f2d567 |
do_field_delim = False
|
|
Packit Service |
f2d567 |
line = ""
|
|
Packit Service |
f2d567 |
for field in row._fields:
|
|
Packit Service |
f2d567 |
if field._props.hidden:
|
|
Packit Service |
f2d567 |
continue
|
|
Packit Service |
f2d567 |
if do_field_delim:
|
|
Packit Service |
f2d567 |
line += self.opts.separator
|
|
Packit Service |
f2d567 |
else:
|
|
Packit Service |
f2d567 |
do_field_delim = True
|
|
Packit Service |
f2d567 |
line += self._output_field(field)
|
|
Packit Service |
f2d567 |
self.opts.report_file.write(line + "\n")
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
def report_output(self):
|
|
Packit Service |
f2d567 |
"""Output report data.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
Output this report's data to the configured report file,
|
|
Packit Service |
f2d567 |
using the configured output controls and fields.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
On success the number of rows output is returned. On
|
|
Packit Service |
f2d567 |
error an exception is raised.
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
:returns: the number of rows of output written.
|
|
Packit Service |
f2d567 |
:rtype: ``int``
|
|
Packit Service |
f2d567 |
"""
|
|
Packit Service |
f2d567 |
if self._already_reported:
|
|
Packit Service |
f2d567 |
return
|
|
Packit Service |
f2d567 |
if self._field_calc_needed:
|
|
Packit Service |
f2d567 |
self.__recalculate_sha_width()
|
|
Packit Service |
f2d567 |
self.__recalculate_fields()
|
|
Packit Service |
f2d567 |
if self._sort_required:
|
|
Packit Service |
f2d567 |
self._sort_rows()
|
|
Packit Service |
f2d567 |
if self.opts.columns_as_rows:
|
|
Packit Service |
f2d567 |
return self._output_as_rows()
|
|
Packit Service |
f2d567 |
else:
|
|
Packit Service |
f2d567 |
return self._output_as_columns()
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
__all__ = [
|
|
Packit Service |
f2d567 |
# Module constants
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
'REP_NUM', 'REP_STR', 'REP_SHA',
|
|
Packit Service |
f2d567 |
'ALIGN_LEFT', 'ALIGN_RIGHT',
|
|
Packit Service |
f2d567 |
'ASCENDING', 'DESCENDING',
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
# Report objects
|
|
Packit Service |
f2d567 |
'BoomReportOpts', 'BoomReportObjType', 'BoomField', 'BoomFieldType',
|
|
Packit Service |
f2d567 |
'BoomFieldProperties', 'BoomReport'
|
|
Packit Service |
f2d567 |
]
|
|
Packit Service |
f2d567 |
|
|
Packit Service |
f2d567 |
# vim: set et ts=4 sw=4 :
|