Source code for lmi.shell.LMIFormatter

# Copyright (C) 2012-2014 Peter Hatina <phatina@redhat.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.

import os
import abc
import sys
import pywbem
import tempfile
import subprocess
from textwrap import TextWrapper
from xml.dom.minicompat import NodeList

from LMIUtil import lmi_raise_or_dump_exception

from LMIExceptions import LMINoPagerError

def _is_executable(fpath):
    """
    :param string fpath: path of executable
    :returns: True, if provided path is executable, False otherwise
    """
    return os.path.exists(fpath) and os.access(fpath, os.X_OK)

def _which(program):
    """
    :param string program: executable name
    :returns: full path of a selected executable
    :rtype: string
    """
    (fpath, fname) = os.path.split(program)
    if fpath:
        if _is_executable(program):
            return program
    else:
        for path in os.environ["PATH"].split(os.pathsep):
            exe_file = os.path.join(path, program)
            if _is_executable(exe_file):
                return exe_file
    return None

def _get_pager_with_params():
    """
    :returns: list containing a executable with its CLI arguments
    """
    if "PAGER" in os.environ:
        path = _which(os.environ["PAGER"])
        if path:
            return [path]
    for p in ["less", "more"]:
        path = _which(p)
        if not path:
            continue
        elif p == "less":
            return [path, "-S"]
        return [path]
    lmi_raise_or_dump_exception(LMINoPagerError("No default pager found"))
    return []

[docs]class LMITextFormatter(object): """ Text formatter class. Used when printing a block of text to output stream. :param string text: text to be formatted """ def __init__(self, text): self._text = text
[docs] def format(self, indent=0, sub_indent=0, width=80, f=sys.stdout, **kwargs): """ Formats a block of text and prints it to the output stream. :param int indent: number of spaces to indent the text block :param int sub_indent: number of spaces for the second and other lines of the text block :param int width: total text block width :param f: output stream :param dictionary kwargs: supported keyword arguments * **separator** (*bool*) -- if True, there will be a new line appended after the formatted text """ if indent > width: return # NOTE: this is wrong! wrapper = TextWrapper() wrapper.width = width - indent wrapper.subsequent_indent = " " * sub_indent for l in wrapper.wrap(self._text): f.write("%s%s\n" % (" " * indent, l)) if "separator" in kwargs and kwargs["separator"]: f.write("\n")
[docs]class LMIFormatter(object): """ Abstract class for XML formatter and MOF formatter. """ __metaclass__ = abc.ABCMeta def __init__(self): stty_dimensions = os.popen("stty size 2> /dev/null", "r").read().split() self._width = int(stty_dimensions[1]) if stty_dimensions else 80 @abc.abstractmethod
[docs] def format(self, indent=0, sub_indent=0, width=80, f=sys.stdout): """ Formats a block of text and prints it to the output stream. :param int indent: number of spaces to indent the text block :param int sub_indent: number of spaces for the second and other lines of the text block :param int width: total text block width :param f: output stream """ pass
[docs] def fancy_format(self, interactive): """ Formats a block of text. If the LMIShell is running in interactive mode, pager will be used, otherwise the output will be written to standard output. :param bool interactive: defines, if to use pager """ if interactive: tmpfile = tempfile.mkstemp() f = os.fdopen(tmpfile[0], "w") self.format(0, 0, self._width, f) f.close() subprocess_params = _get_pager_with_params() if not subprocess_params: return subprocess_params.append(tmpfile[1]) subprocess.call(subprocess_params) os.remove(tmpfile[1]) else: self.format(0, 0, self._width)
[docs]class LMIXmlFormatter(LMIFormatter): """ XML formatter used for :py:meth:`CIMClass.doc` to print pretty verbose help message. :param pywbem.cim_xml.CLASS xml: -- class element """ def __init__(self, xml): super(LMIXmlFormatter, self).__init__() self._xml = xml def __format_class_property_content(self, node, indent=0, sub_indent=0, width=80, f=sys.stdout): """ Formats a class property. :param pywbem.cim_xml.PROPERTY node: property element :param int indent: number of spaces to indent the text block :param int sub_indent: number of spaces for the second and other lines of the text block :param int width: total text block width :param f: -- output stream """ children = node.childNodes children_len = len(children) for (i, n) in enumerate(children): if isinstance(n, pywbem.cim_xml.QUALIFIER): self.__format_qualifier(n, indent, sub_indent, width, f) elif isinstance(n, pywbem.cim_xml.PROPERTY): self.__format_class_property(n, indent, sub_indent, width, f) elif isinstance(n, pywbem.cim_xml.PROPERTY_ARRAY): self.__format_class_property_array(n, indent, sub_indent, width, f) elif isinstance(n, pywbem.cim_xml.METHOD): self.__format_method(n, indent, sub_indent, width, f) elif isinstance(n, pywbem.cim_xml.VALUE): # NOTE: we skip default value for the class pass def __format_qualifier(self, node, indent=0, sub_indent=0, width=80, f=sys.stdout): """ Formats a qualifier. :param pywbem.cim_xml.QUALIFIER node: qualifier element :param int indent: number of spaces to indent the text block :param int sub_indent: number of spaces for the second and other lines of the text block :param int width: total text block width :param f: -- output stream """ is_array = isinstance(node.firstChild, pywbem.cim_xml.VALUE_ARRAY) val = "[qualifier] %s%s %s" % (node.getAttribute("TYPE"), " []" if is_array else "", node.getAttribute("NAME")) values = node.childNodes if values and isinstance(values[0], pywbem.cim_xml.VALUE_ARRAY): val += ": { " + ", ".join(["'" + v.firstChild.nodeValue + "'" for v in values[0].childNodes]) + " }\n" else: if values: val += ": '%s'\n" % values[0].firstChild.nodeValue LMITextFormatter(val).format(indent, sub_indent + 4, width, f, separator=True) def __format_class_property(self, node, indent=0, sub_indent=0, width=80, f=sys.stdout): """ Formats a class property. :param pywbem.cim_xml.PROPERTY node: property element :param int indent: number of spaces to indent the text block :param int sub_indent: number of spaces for the second and other lines of the text block :param int width: total text block width :param f: -- output stream """ val = "[property] %s %s" % (node.getAttribute("TYPE"), node.getAttribute("NAME")) LMITextFormatter(val).format(indent, sub_indent+4, width, f, separator=True) self.__format_class_property_content(node, indent+4, sub_indent, width, f) def __format_class_property_array(self, node, indent=0, sub_indent=0, width=80, f=sys.stdout): """ Formats a class property array. :param pywbem.cim_xml.PROPERTY_ARRAY node: property array element :param int indent: number of spaces to indent the text block :param int sub_indent: number of spaces for the second and other lines of the text block :param int width: total text block width :param f: -- output stream """ val = "[property array] %s [] %s" % (node.getAttribute("TYPE"), node.getAttribute("TYPE")) LMITextFormatter(val).format(indent, sub_indent+4, width, f, separator=True) self.__format_class_property_content(node, indent+4, sub_indent, width, f) def __format_class_property_reference(self, node, indent=0, sub_indent=0, width=80, f=sys.stdout): """ Formats a class property reference. :param pywbem.cim_xml.PROPERTY_REFERENCE node: property reference element :param int indent: number of spaces to indent the text block :param int sub_indent: number of spaces for the second and other lines of the text block :param int width: total text block width :param f: -- output stream """ val = "[property ref] %s %s" % (node.getAttribute("REFERENCECLASS"), node.getAttribute("NAME")) LMITextFormatter(val).format(indent, sub_indent+4, width, f, separator=True) self.__format_class_property_content(node, indent+4, sub_indent, width, f) def __format_instance_property(self, node, indent=0, sub_indent=0, width=80, f=sys.stdout): """ Formats a instance property. :param pywbem.cim_xml.PROPERTY node: property element :param int indent: number of spaces to indent the text block :param int sub_indent: number of spaces for the second and other lines of the text block :param int width: total text block width :param f: -- output stream """ val = node.firstChild prop = "[property] %s %s%s" % (node.getAttribute("TYPE"), node.getAttribute("NAME"), " = '%s'" % val.firstChild.nodeValue if val else "") LMITextFormatter(prop).format(indent, sub_indent+4, width, f, separator=True) def __format_instance_property_array(self, node, indent=0, sub_indent=0, width=80, f=sys.stdout): """ Formats a instance property array. :param pywbem.cim_xml.PROPERTY_ARRAY node: property array element :param int indent: number of spaces to indent the text block :param int sub_indent: number of spaces for the second and other lines of the text block :param int width: total text block width :param f: -- output stream """ prop = "[property array] %s [] %s" % (node.getAttribute("TYPE"), node.getAttribute("NAME")) val = node.firstChild if val: prop += " = { " + ", ".join(["'" + v.firstChild.nodeValue + "'" for v in val.childNodes]) + " }" LMITextFormatter(prop).format(indent, sub_indent+4, width, f, separator=True) def __format_instance_property_reference(self, node, indent=0, sub_indent=0, width=80, f=sys.stdout): """ Formats a instance property reference. :param pywbem.cim_xml.PROPERTY_REFERENCE node: property reference element :param int indent: number of spaces to indent the text block :param int sub_indent: number of spaces for the second and other lines of the text block :param int width: total text block width :param f: -- output stream """ val = "[property ref] %s %s" % (node.getAttribute("REFERENCECLASS"), node.getAttribute("NAME")) LMITextFormatter(val).format(indent, sub_indent+4, width, f, separator=True) def __format_method(self, node, indent=0, sub_indent=0, width=80, f=sys.stdout): """ Formats a class method. :param pywbem.cim_xml.METHOD node: method element :param int indent: number of spaces to indent the text block :param int sub_indent: number of spaces for the second and other lines of the text block :param int width: total text block width :param f: output stream """ qualifiers = [x for x in node.childNodes if isinstance(x, pywbem.cim_xml.QUALIFIER)] parameters = [x for x in node.childNodes if isinstance(x, pywbem.cim_xml.PARAMETER)] parameters_arr = [x for x in node.childNodes if isinstance(x, pywbem.cim_xml.PARAMETER_ARRAY)] parameters_ref = [x for x in node.childNodes if isinstance(x, pywbem.cim_xml.PARAMETER_REFERENCE)] parameters_ref_arr = [x for x in node.childNodes if isinstance(x, pywbem.cim_xml.PARAMETER_REFARRAY)] has_args = parameters != [] or parameters_arr != [] or parameters_ref != [] or parameters_ref_arr != [] val = "[method] %s %s%s" % (node.getAttribute("TYPE"), node.getAttribute("NAME"), "(...)" if has_args else "()") LMITextFormatter(val).format(indent, sub_indent, width, f, separator=True) for q in qualifiers: self.__format_qualifier(q, indent+4, sub_indent+4, width, f) for p in parameters: val = "[param] %s %s" % (p.getAttribute("TYPE"), p.getAttribute("NAME")) LMITextFormatter(val).format(indent+4, sub_indent+4, width, f, separator=True) for p in parameters_arr: val = "[param array] %s (ref) %s" % (p.getAttribute("TYPE"), p.getAttribute("NAME")) LMITextFormatter(val).format(indent+4, sub_indent+4, width, f, separator=True) for p in parameters_ref: val = "[param ref] %s (ref) %s" % (p.getAttribute("REFERENCECLASS"), p.getAttribute("NAME")) LMITextFormatter(val).format(indent+4, sub_indent+4, width, f, separator=True) for p in parameters_ref_arr: val = "[param ref array] %s (ref) %s" % (p.getAttribute("REFERENCECLASS"), p.getAttribute("NAME")) LMITextFormatter(val).format(indent+4, sub_indent+4, width, f, separator=True) def __format_class(self, node, indent=0, sub_indent=0, width=80, f=sys.stdout): """ Formats a class. :param pywbem.cim_xml.CLASS node: class element :param int indent: number of spaces to indent the text block :param int sub_indent: number of spaces for the second and other lines of the text block :param int width: total text block width :param f: output stream """ LMITextFormatter("Class: %s" % node.getAttribute("NAME")).format(indent, sub_indent, width, f) LMITextFormatter("SuperClass: %s" % node.getAttribute("SUPERCLASS")).format(indent+4, sub_indent, width, f) for n in node.childNodes: if isinstance(n, pywbem.cim_xml.QUALIFIER): self.__format_qualifier(n, indent+4, sub_indent, width, f) elif isinstance(n, pywbem.cim_xml.PROPERTY): self.__format_class_property(n, indent+4, sub_indent, width, f) elif isinstance(n, pywbem.cim_xml.PROPERTY_ARRAY): self.__format_class_property_array(n, indent+4, sub_indent, width, f) elif isinstance(n, pywbem.cim_xml.PROPERTY_REFERENCE): self.__format_class_property_reference(n, indent+4, sub_indent, width, f) elif isinstance(n, pywbem.cim_xml.METHOD): self.__format_method(n, indent+4, sub_indent, width, f) def __format_instance(self, node, indent=0, sub_indent=0, width=80, f=sys.stdout): """ Formats an instance. :param pywbem.cim_xml.VALUE_NAMEDINSTANCE node: instance element :param int indent: number of spaces to indent the text block :param int sub_indent: number of spaces for the second and other lines of the text block :param int width: total text block width :param f: -- output stream """ instance = node.firstChild.nextSibling LMITextFormatter("Instance of %s" % instance.getAttribute("CLASSNAME")).format(indent, sub_indent, width, f) for n in instance.childNodes: if isinstance(n, pywbem.cim_xml.PROPERTY): self.__format_instance_property(n, indent+4, sub_indent, width, f) elif isinstance(n, pywbem.cim_xml.PROPERTY_ARRAY): self.__format_instance_property_array(n, indent+4, sub_indent, width, f) elif isinstance(n, pywbem.cim_xml.PROPERTY_REFERENCE): self.__format_instance_property_reference(n, indent+4, sub_indent, width, f)
[docs] def format(self, indent=0, sub_indent=0, width=80, f=sys.stdout): """ Formats a XML object and prints it to the output stream. :param int indent: number of spaces to indent the text block :param int sub_indent: number of spaces for the second and other lines of the text block :param int width: total text block width :param f: output stream """ if isinstance(self._xml, pywbem.cim_xml.CLASS): self.__format_class(self._xml, indent, sub_indent, width, f) elif isinstance(self._xml, pywbem.cim_xml.VALUE_NAMEDINSTANCE): self.__format_instance(self._xml, indent, sub_indent, width, f) elif isinstance(self._xml, pywbem.cim_xml.METHOD): self.__format_method(self._xml, indent, sub_indent, width, f)
[docs]class LMIMofFormatter(LMIFormatter): """ MOF formatter used for :py:meth:`CIMInstance.doc` to print pretty verbose help message. :param string mof: MOF representation of a object """ def __init__(self, mof): super(LMIMofFormatter, self).__init__() self._mof = mof
[docs] def format(self, indent=0, sub_indent=0, width=80, f=sys.stdout): """ Formats a MOF object and prints it to the output stream. :param int indent: number of spaces to indent the text block :param int sub_indent: number of spaces for the second and other lines of the text block :param int width: total text block width :param f: output stream """ f.write(self._mof)