summaryrefslogtreecommitdiffstats
path: root/tools/class2uml.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/class2uml.py')
-rwxr-xr-xtools/class2uml.py217
1 files changed, 217 insertions, 0 deletions
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)
+