summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorJan Safranek <jsafrane@redhat.com>2012-12-10 12:52:14 +0100
committerJan Safranek <jsafrane@redhat.com>2012-12-10 12:52:14 +0100
commit33f9e43abc105f8597179bb5c4ad422a2dd4b2f6 (patch)
treeeb2521bb107a6393eed0db0e080e74d17421c1dd /tools
parente1772137de5be91aba29fc3920861b26c1f7a4fb (diff)
downloadopenlmi-providers-33f9e43abc105f8597179bb5c4ad422a2dd4b2f6.tar.gz
openlmi-providers-33f9e43abc105f8597179bb5c4ad422a2dd4b2f6.tar.xz
openlmi-providers-33f9e43abc105f8597179bb5c4ad422a2dd4b2f6.zip
Added class2rst.py and class2uml.py tools.
Diffstat (limited to 'tools')
-rwxr-xr-xtools/class2rst.py454
-rwxr-xr-xtools/class2uml.py217
2 files changed, 671 insertions, 0 deletions
diff --git a/tools/class2rst.py b/tools/class2rst.py
new file mode 100755
index 0000000..828745e
--- /dev/null
+++ b/tools/class2rst.py
@@ -0,0 +1,454 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2012 Red Hat, Inc. All rights reserved.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Authors: Jan Safranek <jsafrane@redhat.com>
+#
+
+import os;
+import pywbem;
+import optparse
+import re
+import cgi
+import sys
+
+class HtmlExporter(object):
+
+ def __init__(self, cliconn):
+ self.classcache = {}
+ self.cliconn = cliconn
+ # original list of classes
+ self.classes = []
+ # set of all classes to print, i.e. incl. all parents
+ self.queue = set()
+ self.file = None
+
+ def link(self, classname, attributename = ""):
+ if attributename:
+ link = classname + "-" + attributename
+ else:
+ link = classname
+ link = link.replace("_","-")
+ return link
+
+ def print_escaped(self, string, prefix = ""):
+ # replace \n with prefixes
+ lines = string.split("\\n")
+ for line in lines:
+ print >>self.file, prefix + line
+ print >>self.file, ""
+
+ def load_class(self, classname):
+ if classname in self.classcache:
+ return self.classcache[classname]
+
+ c = self.cliconn.GetClass(classname)
+ self.classcache[classname] = c
+ return c
+
+ def source_class(self, c, param):
+ """
+ Find a class where a CIMMethod or CIMProperty is first defined.
+ Start at class c and inspect all parents.
+ """
+
+ while True:
+
+ if isinstance(param, pywbem.CIMMethod):
+ if not c.methods.has_key(param.name):
+ break
+ else:
+ if not c.properties.has_key(param.name):
+ break
+
+ if not c.superclass:
+ # we're at the top class
+ return c.classname
+
+ parent = c
+ c = self.load_class(c.superclass)
+ return parent.classname
+
+ def display_type(self, param):
+ """
+ Return displayable type of given parameter.
+ It adds [] if it's array and class name of referenced classes.
+ """
+ url = False
+ if param.reference_class:
+ ptype = param.reference_class
+ url = True
+ else:
+ ptype = param.type
+ if param.is_array:
+ ptype = ptype + "[]"
+ if url:
+ ptype = ":ref:`%s <%s>`" % (ptype, self.link(ptype))
+ else:
+ ptype = "``" + ptype + "``"
+ return ptype
+
+ def print_parameters(self, params):
+ """
+ Print table of method parametes.
+ """
+ function_indent = " "*4
+ if not params:
+ print >>self.file, "*None*"
+ return
+
+ for p in params.values():
+ param_indent = " "*8
+ direction = set()
+ if p.qualifiers.has_key("Out"):
+ direction.add("*OUT*")
+ if p.qualifiers.has_key("In"):
+ if p.qualifiers["In"].value:
+ direction.add("*IN*")
+ if not direction:
+ direction.add("*IN*")
+ direction = ", ".join(sorted(direction))
+
+ print >>self.file, param_indent + "%s %s **%s**" % (direction, self.display_type(p), p.name)
+ self.print_qualifiers(p.qualifiers.values(), param_indent+ " "*4)
+ print >>self.file, param_indent
+ print >>self.file, function_indent
+
+ def print_prototype(self, method):
+ """ Print function prototype """
+ self.file.write("``" + method.return_type + "`` ")
+ self.file.write("**" + method.name + "** (")
+ params = []
+ for p in method.parameters.values():
+ params.append((self.display_type(p) + " " + p.name))
+ self.file.write(", ".join(params))
+ self.file.write("\n")
+
+ def print_table(self, col1, col2, indent):
+ """
+ Print table with two columns.
+ """
+ len1 = max(max(map(len, col1)), len("ValueMap"))
+ len2 = max(max(map(len, col2)), len("Values"))
+ separator = "="*len1 + " " + "="*len2
+ print >>self.file, indent + separator
+ print >>self.file, indent + format("ValueMap", str(len1)) + " " + format("Values", str(len2))
+ print >>self.file, indent + separator
+ for i in range(len(col1)):
+ print >>self.file, indent + format(col1[i], str(len1)) + " " + format(col2[i], str(len2))
+ print >>self.file, indent + separator
+
+ def print_qualifiers(self, qualifiers, indent):
+ """
+ Print content of table of qualifiers.
+ Only Deprecated, Description, Values and ValueMap are recognized.
+ """
+ deprecated = ""
+ values = None
+ maps = None
+ for q in sorted(qualifiers):
+ if q.name == "Deprecated":
+ deprecated = "**Deprecated!** "
+ if q.name == "Description":
+ if deprecated:
+ print >>self.file, indent + deprecated
+ self.print_escaped(q.value, indent)
+ if q.name == "Values":
+ values = q.value
+ if q.name == "ValueMap":
+ maps = q.value
+ print >>self.file, indent
+ if maps and values:
+ self.print_table(maps, values, indent)
+ print >>self.file, indent
+
+ def compare_properties(self, p1, p2):
+ """
+ Compare two properties. Only Name and Description are checked.
+ """
+ if p1.name != p2.name:
+ return False
+ d1 = p1.qualifiers.get("Description", None)
+ d2 = p2.qualifiers.get("Description", None)
+ if d1.value != d2.value:
+ return False
+ return True
+
+ def print_keys(self, c):
+ print >>self.file, "Key properties"
+ print >>self.file, "^^^^^^^^^^^^^^"
+ print >>self.file, ""
+ for prop in c.properties.values():
+ if prop.qualifiers.has_key('Key'):
+ src = self.source_class(c, prop)
+ link = self.link(src, prop.name)
+ print >>self.file, "| :ref:`%s <%s>`" % (prop.name, link)
+ print >>self.file, ""
+
+ def print_class(self, c):
+ """
+ Print one class, inc. header.
+ """
+ parent = None
+ if c.superclass:
+ parent = self.load_class(c.superclass)
+ print >>self.file, "Subclass of :ref:`%s <%s>`" % (c.superclass, self.link(c.superclass))
+ print >>self.file, ""
+
+ description = c.qualifiers.get("Description", None)
+ if not description:
+ description = parent.qualifiers.get("Description", None)
+ if description:
+ self.print_escaped(description.value, "")
+ print >>self.file, ""
+
+ self.print_keys(c)
+
+ local_props = []
+ inherited_props = []
+ for name in sorted(c.properties.keys()):
+ if parent and parent.properties.has_key(name):
+ inherited_props.append(c.properties[name])
+ if not self.compare_properties(c.properties[name], parent.properties[name]):
+ # the property was overridden
+ local_props.append(c.properties[name])
+ else:
+ local_props.append(c.properties[name])
+
+ local_methods = []
+ inherited_methods = []
+ for name in sorted(c.methods.keys()):
+ if parent and parent.methods.has_key(name):
+ inherited_methods.append(c.methods[name])
+ if not self.compare_properties(c.methods[name], parent.methods[name]):
+ # the property was overridden
+ local_methods.append(c.methods[name])
+ else:
+ local_methods.append(c.methods[name])
+
+ print >>self.file, "Local properties"
+ print >>self.file, "^^^^^^^^^^^^^^^^"
+ print >>self.file, ""
+ if local_props:
+ for prop in local_props:
+ prop_link = self.link(c.classname, prop.name)
+ print >>self.file, ".. _%s:" % (prop_link)
+ print >>self.file, ""
+ print >>self.file, "%s **%s**" % (self.display_type(prop), prop.name)
+ print >>self.file, ""
+ self.print_qualifiers(prop.qualifiers.values(), " "*4)
+ print >>self.file, ""
+ else:
+ print >>self.file, "*None*"
+ print >>self.file, ""
+
+ print >>self.file, "Local methods"
+ print >>self.file, "^^^^^^^^^^^^^"
+ print >>self.file, ""
+ if local_methods:
+ for m in local_methods:
+ link = self.link(c.classname, m.name)
+ print >>self.file, " .. _%s:" % (link)
+ print >>self.file, ""
+ self.print_prototype(m)
+ print >>self.file, ""
+ self.print_qualifiers(m.qualifiers.values(), " "*4)
+ print >>self.file, " **Parameters**"
+ print >>self.file, " "
+ self.print_parameters(m.parameters)
+ print >>self.file, " "
+ else:
+ print >>self.file, "*None*"
+ print >>self.file, ""
+
+ print >>self.file, "Inherited properties"
+ print >>self.file, "^^^^^^^^^^^^^^^^^^^^"
+ print >>self.file, ""
+ if inherited_props:
+ for p in inherited_props:
+ src = self.source_class(c, p)
+ link = self.link(src, p.name)
+ print >>self.file, "| %s :ref:`%s <%s>`" % (self.display_type(p), p.name, link)
+ print >>self.file, ""
+ else:
+ print >>self.file, "*None*"
+ print >>self.file, ""
+
+ print >>self.file, "Inherited methods"
+ print >>self.file, "^^^^^^^^^^^^^^^^^"
+ print >>self.file, ""
+ if inherited_methods:
+ for m in inherited_methods:
+ src = self.source_class(c, m)
+ link = self.link(src, m.name)
+ print >>self.file, "| :ref:`%s <%s>`" % (m.name, link)
+ print >>self.file, ""
+ else:
+ print >>self.file, "*None*"
+ print >>self.file, ""
+
+
+ def print_file(self, filename):
+ f = open(filename, "r")
+ data = f.read()
+ self.file.write(data)
+ f.close()
+
+ def print_page(self, c, header, footer):
+ print "exporting ", c.classname
+ self.file = open(c.classname + ".rst", "w")
+ print >>self.file, ".. _%s:" % self.link(c.classname)
+ print >>self.file, ""
+ print >>self.file, c.classname
+ print >>self.file, "-"*len(c.classname)
+ print >>self.file, ""
+# if header:
+# self.print_file(header)
+ self.print_class(c)
+# if footer:
+# self.print_file(footer)
+ self.file.close()
+
+ def add_class(self, classname):
+ self.classes.append(classname)
+ print "adding", classname
+ self.queue.add(classname)
+ # add all parents
+ c = self.load_class(classname)
+ parentname = c.superclass
+ while parentname:
+ if parentname in self.queue:
+ break
+ print "adding", parentname
+ self.queue.add(parentname)
+ c = self.load_class(parentname)
+ parentname = c.superclass
+
+ def export(self, header=None, footer=None):
+ """
+ Print everything, i.e. index.rst + all classes + all their parents.
+ """
+ # remove duplicate classes from the queue (and sort them)
+ self.queue = sorted(self.queue)
+ self.print_tree_rst(header, footer)
+ self.print_index_rst(header, footer)
+ while self.queue:
+ c = self.queue.pop()
+ self.print_page(self.load_class(c), header, footer)
+
+ def _do_print_tree(self, name, subtree, level):
+ nbsp=" |nbsp| "
+ if level:
+ print >>self.file, "| " + ("\\|"+nbsp*7)*(level-1) + "\\|---",
+ else:
+ print >>self.file, "|",
+ if name in self.classes:
+ print >>self.file, "\*",
+ link = self.link(name)
+ print >>self.file, ":ref:`%s <%s>`" % (name, link)
+ for n in subtree.keys():
+ self._do_print_tree(n, subtree[n], level+1)
+
+ def print_tree(self):
+ """
+ Create inheritance tree of classes.
+ """
+ # hash classname -> list of hash of (direct) sublasses
+ subclasses = {}
+ # hash classname -> nr. of its parents
+ parents = {}
+ # initialize the hash
+ for cname in self.queue:
+ subclasses[cname] = {}
+ parents[cname] = 0
+
+ # fill the hash
+ for cname in self.queue:
+ c = self.load_class(cname)
+ if c.superclass:
+ subclasses[c.superclass][cname] = subclasses[cname]
+ parents[cname] += 1
+
+ print >>self.file, ".. |nbsp| unicode:: 0xA0"
+ print >>self.file, " :trim:"
+ print >>self.file, ""
+ # print all top classes
+ for cname in self.queue:
+ if parents[cname] == 0:
+ self._do_print_tree(cname, subclasses[cname], 0)
+
+ def print_index(self):
+ """
+ Create table of content.
+ """
+ # hash classname -> list of hash of (direct) sublasses
+ print >>self.file, ".. toctree::"
+ print >>self.file, " :maxdepth: 1"
+ print >>self.file, ""
+ # print all top classes
+ for cname in self.queue:
+ print >>self.file, " " + cname
+
+
+ def print_tree_rst(self, header, footer):
+ print "exporting tree"
+ self.file = open("tree.rst", "w")
+ print >>self.file, "Inheritance tree"
+ print >>self.file, "================"
+ print >>self.file, ""
+
+ self.print_tree()
+ self.file.close()
+
+ def print_index_rst(self, header, footer):
+ print "exporting index"
+ self.file = open("index.rst", "w")
+ if header:
+ self.print_file(header)
+ self.print_index()
+ if footer:
+ self.print_file(footer)
+ self.file.close()
+
+description = """
+Generate RST documentation for given CIM classes. The tool connects to specified CIMOM
+and reads class definition from there. It generates separate RST file for each class
+specified on command line and for all it's parents.
+
+The tool also generates tree.rst with inheritance tree and index.rst with table of content.
+
+The index.rst will optionally contain header and/or footer. Both must be valid
+RST code snippets and will be inserted just at the beginning or at the end of the file.
+
+Use Sphinx to generate html documentation from the RST files.
+"""
+
+parser = optparse.OptionParser(usage="usage: %prog [options] classname [classname ...]", description=description)
+parser.add_option('-u', '--url', action='store', dest='addr', default='https://localhost:5989', help='URL of CIM server, default: https://localhost:5989')
+parser.add_option('-U', '--user', action='store', dest='user', default=None, help='CIM user name')
+parser.add_option('-P', '--password', action='store', dest='password', default=None, help='CIM password')
+parser.add_option('-H', '--header', action='store', dest='header', default=None, help='File with HTML page header')
+parser.add_option('-F', '--footer', action='store', dest='footer', default=None, help='File with HTML page footer')
+
+(options, args) = parser.parse_args()
+
+cliconn = pywbem.WBEMConnection(options.addr, (options.user, options.password))
+exporter = HtmlExporter(cliconn)
+for c in args:
+ exporter.add_class(c)
+exporter.export(options.header, options.footer)
+
diff --git a/tools/class2uml.py b/tools/class2uml.py
new file mode 100755
index 0000000..d46bcc5
--- /dev/null
+++ b/tools/class2uml.py
@@ -0,0 +1,217 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2012 Red Hat, Inc. All rights reserved.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Authors: Jan Safranek <jsafrane@redhat.com>
+#
+
+import os;
+import pywbem;
+import optparse
+import re
+import cgi
+import sys
+
+class UmlExporter(object):
+
+ def __init__(self, cliconn):
+ self.classcache = {}
+ self.cliconn = cliconn
+ # original list of classes
+ self.classes = set()
+ self.file = sys.stdout
+
+ def load_class(self, classname):
+ if classname in self.classcache:
+ return self.classcache[classname]
+
+ c = self.cliconn.GetClass(classname)
+ self.classcache[classname] = c
+ return c
+
+ def display_type(self, param):
+ """
+ Return displayable type of given parameter.
+ It adds [] if it's array and class name of referenced classes.
+ """
+ if param.reference_class:
+ ptype = param.reference_class
+ else:
+ ptype = param.type
+ if param.is_array:
+ ptype = ptype + "[]"
+ return ptype
+
+ def print_parameters(self, params):
+ """
+ Print table of method parametes.
+ """
+ if not params:
+ print >>self.file, "None"
+ return
+
+ print >>self.file, "<table class=\"parameters\">"
+ for p in params.values():
+ direction = set()
+ if p.qualifiers.has_key("Out"):
+ direction.add("OUT")
+ if p.qualifiers.has_key("In"):
+ direction.add("IN")
+ if not direction:
+ direction.add("IN")
+ direction = "/".join(sorted(direction))
+
+ print >>self.file, "<tr>"
+ print >>self.file, "<td>%s</td><td>%s</td><td><b>%s</b></td>" % (direction, self.display_type(p), p.name)
+ print >>self.file, "<td><table class=\"qualifiers\">"
+ self.print_qualifiers(p.qualifiers.values())
+ print >>self.file, "</table></td>"
+ print >>self.file, "</tr>"
+ print >>self.file, "</table>"
+
+ def print_qualifiers(self, qualifiers):
+ """
+ Print contenf of table of qualifiers.
+ Only Deprecated, Description, Values and ValueMap are recognized.
+ """
+ deprecated = ""
+ for q in sorted(qualifiers):
+ if q.name == "Deprecated":
+ deprecated = "<div class=\"deprecated\">DEPRECATED</div> "
+ if q.name == "Description":
+ print >>self.file, "<tr><td class=\"qualifiers\" colspan=\"2\">%s%s</td></tr>" % (deprecated, self.escape(q.value))
+ if q.name == "Values":
+ print >>self.file, "<tr><td class=\"qualifiers\"><b>Values</b></td> <td class=\"qualifiers\"><table><tr><td class=\"qualifiers\">%s</td></tr></table></td></tr>" % ("</td></tr><tr><td class=\"qualifiers\">".join(q.value))
+ if q.name == "ValueMap":
+ print >>self.file, "<tr><td class=\"qualifiers\"><b>ValueMap</b></td> <td class=\"qualifiers\"><table><tr><td class=\"qualifiers\">%s</td></tr></table></td></tr>" % ("</td></tr><tr><td class=\"qualifiers\">".join(q.value))
+
+ def compare_properties(self, p1, p2):
+ """
+ Compare two properties. Only Name and Description are checked.
+ """
+ if p1.name != p2.name:
+ return False
+ i1 = p1.qualifiers.get("Implemented", None)
+ i2 = p2.qualifiers.get("Implemented", None)
+ if i1 and i1.value and not (i2 and i2.value):
+ return False
+ d1 = p1.qualifiers.get("Description", None)
+ d2 = p2.qualifiers.get("Description", None)
+ if d1.value != d2.value:
+ return False
+ return True
+
+ def print_class(self, c, display_local = True, box_only = False):
+ """
+ Print one class, inc. header.
+ """
+ parent = None
+ if c.superclass:
+ parent = self.load_class(c.superclass)
+
+ if c.superclass:
+ # draw arrow to parent
+ print >>self.file, "%s -down-|> %s" % (c.classname, c.superclass)
+
+ if box_only:
+ self.file.write("class %s\n\n" % c.classname)
+ return
+
+ local_props = []
+ for name in sorted(c.properties.keys()):
+ if parent and parent.properties.has_key(name):
+ if not self.compare_properties(c.properties[name], parent.properties[name]):
+ # the property was overridden
+ local_props.append(c.properties[name])
+ else:
+ local_props.append(c.properties[name])
+
+ local_methods = []
+ for name in sorted(c.methods.keys()):
+ if parent and parent.methods.has_key(name):
+ if not self.compare_properties(c.methods[name], parent.methods[name]):
+ # the property was overridden
+ local_methods.append(c.methods[name])
+ else:
+ local_methods.append(c.methods[name])
+ self.file.write("class %s {\n" % c.classname)
+
+ if display_local:
+ for prop in local_props:
+ self.file.write(" %s %s\n" % (self.display_type(prop), prop.name))
+
+
+ if local_methods:
+ for m in local_methods:
+ self.file.write(" %s()\n" % (m.name))
+
+ self.file.write("}\n")
+ self.file.write("url of %s is [[%s.html]]\n" % (c.classname, c.classname))
+
+ def add_class(self, classname):
+ self.classes.add(classname)
+
+ def export(self, shrink, noassoc = False):
+ """
+ Print all classes and their parents.
+ """
+ print >>self.file, "@startuml"
+ while self.classes:
+ c = self.classes.pop()
+
+ cl = self.load_class(c)
+ if noassoc and cl.qualifiers.get("Association", False):
+ continue
+
+ if shrink and shrink.match(c):
+
+ self.print_class(cl, box_only = True)
+ else:
+ self.print_class(cl)
+
+ print >>self.file, "@enduml"
+
+description = """
+Generate UML image for given classes. The tool connects to specified CIMOM
+and reads class definition from there. Each class specified on command line
+will be drawn as one box, containing locally defined or re-defined properties
+and methods. Inheritance will be shown as arrow between a parent class and a
+subclass.
+
+The generated file can be coverted to a picture by PlantUML tool.
+"""
+
+parser = optparse.OptionParser(usage="usage: %prog [options] classname [classname ...]", description=description)
+parser.add_option('-u', '--url', action='store', dest='addr', default='https://localhost:5989', help='URL of CIM server, default: https://localhost:5989')
+parser.add_option('-U', '--user', action='store', dest='user', default=None, help='CIM user name')
+parser.add_option('-s', '--shrink', action='store', dest='shrink', default=None, help='Regular expression pattern of CIM classes, which will be drawn only as boxes, without properties.')
+parser.add_option('-A', '--no-associations', action='store_true', dest='noassoc', default=False, help='Skip association classes.')
+parser.add_option('-P', '--password', action='store', dest='password', default=None, help='CIM password')
+(options, args) = parser.parse_args()
+
+sys.stdout.softspace=0
+
+shrink = None
+if options.shrink:
+ shrink = re.compile(options.shrink)
+
+cliconn = pywbem.WBEMConnection(options.addr, (options.user, options.password))
+exporter = UmlExporter(cliconn)
+for c in args:
+ exporter.add_class(c)
+exporter.export(shrink = shrink, noassoc = options.noassoc)
+