From 33f9e43abc105f8597179bb5c4ad422a2dd4b2f6 Mon Sep 17 00:00:00 2001 From: Jan Safranek Date: Mon, 10 Dec 2012 12:52:14 +0100 Subject: Added class2rst.py and class2uml.py tools. --- tools/class2rst.py | 454 +++++++++++++++++++++++++++++++++++++++++++++++++++++ tools/class2uml.py | 217 +++++++++++++++++++++++++ 2 files changed, 671 insertions(+) create mode 100755 tools/class2rst.py create mode 100755 tools/class2uml.py (limited to 'tools') 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 +# + +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 +# + +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, "" + 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, "" + print >>self.file, "" % (direction, self.display_type(p), p.name) + print >>self.file, "" + print >>self.file, "" + print >>self.file, "
%s%s%s" + self.print_qualifiers(p.qualifiers.values()) + print >>self.file, "
" + + 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 = "
DEPRECATED
" + if q.name == "Description": + print >>self.file, "%s%s" % (deprecated, self.escape(q.value)) + if q.name == "Values": + print >>self.file, "Values
%s
" % ("".join(q.value)) + if q.name == "ValueMap": + print >>self.file, "ValueMap
%s
" % ("".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) + -- cgit