summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGuillaume Seguin <guillaume@segu.in>2009-10-24 02:06:25 +0200
committerGuillaume Seguin <guillaume@segu.in>2009-10-24 02:06:25 +0200
commit8d3ee91dc6345cc360519c6ce77177332a612299 (patch)
treebe8c22181c474cf98abf45e1cabbffa131369d0c
parent66514114b9b43e08d59204392f5702138f8f5007 (diff)
downloadumlpy-8d3ee91dc6345cc360519c6ce77177332a612299.tar.gz
umlpy-8d3ee91dc6345cc360519c6ce77177332a612299.tar.bz2
Major rework
-rw-r--r--umlpy.py621
1 files changed, 402 insertions, 219 deletions
diff --git a/umlpy.py b/umlpy.py
index 98923e9..3ac4436 100644
--- a/umlpy.py
+++ b/umlpy.py
@@ -79,147 +79,22 @@ parser.add_option("--pdf", dest = "format", action = "store_const",
parser.add_option("--jpg", dest = "format", action = "store_const",
const = "jpg", help = "output as jpg")
-(options, args) = parser.parse_args ()
-
-excludes = reduce (lambda x, y: x + y,
- map (lambda s: s.split(","), options.excludes), [])
-includes = reduce (lambda x, y: x + y,
- map (lambda s: s.split(","), options.includes), [])
-forces = reduce (lambda x, y: x + y,
- map (lambda s: s.split(","), options.forces), [])
-prefix = options.prefix
-all_methods = options.all_methods
-all_properties = options.all_properties
-no_method = options.no_method
-no_property = options.no_property
-output = options.output
-format = options.format
-if not output:
- output = "uml." + format
-
-print "Settings"
-print "--------"
-print "Output file :", output
-print "Prefix :", prefix
-print "Show all methods :", all_methods
-print "Show all properties :", all_properties
-print "Show no method :", no_method
-print "Show no propertie :", no_property
-print "Excludes :", excludes
-print "Includes :", includes
-print "Forces :", forces
-
-excludes = [re.compile (exp)
- for exp in excludes]
-includes = [re.compile (exp)
- for exp in includes]
-forces = [re.compile (exp)
- for exp in forces]
-
-def is_excluded (class_name):
- for exp in excludes:
- if exp.match (class_name):
- for exp in includes:
- if exp.match (class_name):
- return False
- for exp in forces:
- if exp.match (class_name):
- return False
- return True
- else:
- return False
-
-docs = []
-
-def get_var_type (var):
- if var.type_descr != None:
- return str (var.type_descr.to_plaintext ("").strip ())
- else:
- return None
-
-for path in args:
- if os.path.isdir (path):
- paths = [os.path.join (path, file)
- for file in os.listdir (path) if file.endswith (".py")]
- else:
- paths = [path]
- for path in paths:
- docs.append (epydoc.docparser.parse_docs (path))
-
-classes = []
-bases_dict = {}
-uses_dict = {}
-methods_dict = {}
-vars_dict = {}
-
-for doc in docs:
- for var in doc.variables.values ():
- if type (var.value) != epydoc.apidoc.ClassDoc:
- continue
- var_val = var.value
- var_name = str (var_val.canonical_name)
- var_name = var_name.replace (prefix, "")
- classes.append (var_name)
- if options.debug:
- print var_name
- var_vars = var_val.variables.values ()
- vars_dict[var_name] = []
- methods_dict[var_name] = []
- uses_dict[var_name] = []
- if str (var_val.docstring) != "<UNKNOWN>":
- bits = str (var_val.docstring).split ("\n")
- for bit in bits:
- if bit.strip ().startswith ("@uses:"):
- bit = bit.replace ("@uses:", "").strip ()
- uses_dict[var_name].append (bit)
- for var_var in var_vars:
- if type (var_var.value) == epydoc.apidoc.GenericValueDoc:
- if no_property:
- continue
- epydoc.docstringparser.parse_docstring (var_var, None)
- if options.debug:
- print var_name, var_var.name, get_var_type (var_var)
- var_type = get_var_type (var_var)
- if (all_properties and not "@nodoc" in str (var_var.docstring)) \
- or "@doc" in str (var_var.docstring) or var_type:
- var_var_name = str (var_var.name.replace (prefix, ""))
- vars_dict[var_name].append ((var_var_name, get_var_type (var_var)))
- elif type (var_var.value) in (epydoc.apidoc.RoutineDoc,
- epydoc.apidoc.StaticMethodDoc):
- if no_method:
- continue
- if (all_methods or "@doc" in str (var_var.value.docstring)) \
- and not "@nodoc" in str (var_var.value.docstring):
- methods_dict[var_name].append (str (var_var.name))
- else:
- print type (var_var.value)
- bases = [str (base.canonical_name).replace (prefix, "")
- for base in var_val.bases
- if str (base.canonical_name) not in ("object", "<UNKNOWN>")]
- if options.debug:
- print bases
- bases_dict[var_name] = bases
-
-nodes_dict = {}
-var_fields_dict = {}
-method_fields_dict = {}
-graph = gv.digraph ('g')
-gv.setv (graph, 'charset', 'utf-8')
-gv.setv (graph, 'overlap', 'false')
-gv.setv (graph, 'splines', 'true')
-gv.setv (graph, 'rankdir', 'BT')
-item = gv.protoedge (graph)
-gv.setv (item, 'len', '2')
-item = gv.protonode (graph)
-gv.setv (item, 'shape', 'plaintext')
-
-CLASS_COLOR = "#FF6262"
-VAR_FIELD_COLOR = "#63BDFF"
+DEFAULT_OUTPUT = "uml."
+
+# Graph functions
+
+CLASSNAME_FIELD_COLOR = "#FF6262"
+PROPERTY_FIELD_COLOR = "#63BDFF"
METHOD_FIELD_COLOR = "#6EFF62"
SUPER_EDGE_COLOR = "#AD0006"
-VAR_EDGE_COLOR = "#002990"
+SUPER_EDGE_ARROWHEAD = "normal"
+
+PROPERTY_EDGE_COLOR = "#002990"
+PROPERTY_EDGE_ARROWHEAD = "diamond"
+
USE_EDGE_COLOR = "#05C800"
+USE_EDGE_ARROWHEAD = "box"
LABEL_BASE = """<
<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
@@ -233,85 +108,393 @@ FIELD_FORMATTER = """ <TR>
<TD BGCOLOR="%s" PORT="%s">%s</TD>
</TR>"""
-def build_record_label (class_name):
- fields = "".join ([FIELD_FORMATTER % (VAR_FIELD_COLOR, field, field)
- for field in sorted (var_fields_dict[class_name])])
- fields += "".join ([FIELD_FORMATTER % (METHOD_FIELD_COLOR, field, field)
- for field in sorted (method_fields_dict[class_name])])
- return LABEL_BASE % (CLASS_COLOR, class_name, class_name, fields)
-
-def build_simple_label (class_name):
- return LABEL_BASE % (CLASS_COLOR, class_name, class_name, "")
-
-def check_class_node (name):
- if name not in nodes_dict:
- node = gv.node (graph, name)
- gv.setv (node, 'shape', 'plaintext')
- gv.setv (node, 'style', 'invis')
- if name in var_fields_dict or name in method_fields_dict:
- gv.setv (node, 'label', build_record_label (name))
- else:
- gv.setv (node, 'label', build_simple_label (name))
- nodes_dict[name] = node
- return nodes_dict[name]
-
-def add_super_edge (class_name, base_name):
- check_class_node (class_name)
- check_class_node (base_name)
- edge = gv.edge (graph, class_name, base_name)
- gv.setv (edge, 'arrowhead', 'normal')
- gv.setv (edge, 'color', SUPER_EDGE_COLOR)
-
-def add_var_edge (class_name, var_name, type_name):
- check_class_node (class_name)
- check_class_node (type_name)
- edge = gv.edge (graph, type_name, class_name)
- gv.setv (edge, 'headport', "%s:e" % var_name)
- gv.setv (edge, 'arrowhead', 'diamond')
- gv.setv (edge, 'color', VAR_EDGE_COLOR)
-
-def add_use_edge (class_name, base_name):
- check_class_node (class_name)
- check_class_node (base_name)
- edge = gv.edge (graph, class_name, base_name)
- gv.setv (edge, 'arrowhead', 'box')
- gv.setv (edge, 'color', USE_EDGE_COLOR)
-
-for class_name in classes:
- if is_excluded (class_name):
- continue
- for (var_name, type_name) in vars_dict[class_name]:
- if class_name not in var_fields_dict:
- var_fields_dict[class_name] = []
- method_fields_dict[class_name] = []
- var_fields_dict[class_name].append (var_name)
- for method_name in methods_dict[class_name]:
- if class_name not in var_fields_dict:
- var_fields_dict[class_name] = []
- method_fields_dict[class_name] = []
- method_fields_dict[class_name].append (method_name)
-
-for class_name in classes:
- if is_excluded (class_name):
- continue
- for (var_name, type_name) in vars_dict[class_name]:
- if not type_name or is_excluded (type_name):
- continue
- add_var_edge (class_name, var_name, type_name)
- for base_name in bases_dict[class_name]:
- if is_excluded (base_name):
- continue
- add_super_edge (class_name, base_name)
- for used_name in uses_dict[class_name]:
- if is_excluded (used_name):
- continue
- add_use_edge (class_name, used_name)
-
-for class_name in classes:
- for exp in forces:
- if exp.match (class_name):
- check_class_node (class_name)
- break
-
-gv.layout (graph, 'dot')
-gv.render (graph, format, output)
+class Graph (object):
+
+ graph = None
+ """@type: gv.digraph"""
+
+ def __init__ (self):
+ """@nodoc"""
+ self.graph = gv.digraph ('g')
+ gv.setv (self.graph, 'charset', 'utf-8')
+ gv.setv (self.graph, 'overlap', 'false')
+ gv.setv (self.graph, 'splines', 'true')
+ gv.setv (self.graph, 'rankdir', 'BT')
+ item = gv.protoedge (self.graph)
+ gv.setv (item, 'len', '2')
+ item = gv.protonode (self.graph)
+ gv.setv (item, 'shape', 'plaintext')
+ gv.setv (item, 'style', 'invis')
+
+ def add_node (self, node):
+ """@doc"""
+ node.create (self.graph)
+
+ def add_edge (self, edge):
+ """@doc"""
+ edge.create (self.graph)
+
+ def render (self, filename, format):
+ gv.layout (self.graph, 'dot')
+ gv.render (self.graph, format, filename)
+
+class Node (object):
+ """@uses: umlpy.Graph"""
+
+ class_name = None
+ """@doc"""
+ label = None
+ """@doc"""
+
+ def __init__ (self, class_name, properties, methods):
+ """@nodoc"""
+ self.class_name = class_name
+ fields = [FIELD_FORMATTER % (PROPERTY_FIELD_COLOR, field, field)
+ for field in sorted (properties)]
+ fields += [FIELD_FORMATTER % (METHOD_FIELD_COLOR, field, field)
+ for field in sorted (methods)]
+ fields = "".join (fields)
+ self.label = LABEL_BASE % (CLASSNAME_FIELD_COLOR,
+ class_name,
+ class_name,
+ fields)
+
+ def create (self, graph):
+ """@doc"""
+ node = gv.node (graph, self.class_name)
+ gv.setv (node, 'label', self.label)
+ return node
+
+class SimpleNode (Node):
+
+ def __init__ (self, class_name):
+ """@nodoc"""
+ super (SimpleNode, self).__init__ (class_name, [], [])
+ self.label = LABEL_BASE % (CLASSNAME_FIELD_COLOR,
+ class_name,
+ class_name,
+ "")
+
+class Edge (object):
+ """@uses: umlpy.Graph"""
+
+ color = None
+ """@doc"""
+ arrowhead = None
+ """@doc"""
+ head = None
+ """@doc"""
+ tail = None
+ """@doc"""
+
+ def __init__ (self, head, tail):
+ """@nodoc"""
+ self.head = head
+ self.tail = tail
+
+ def create (self, graph):
+ """@doc"""
+ edge = gv.edge (graph, self.head, self.tail)
+ gv.setv (edge, 'color', self.color)
+ gv.setv (edge, 'arrowhead', self.arrowhead)
+ return edge
+
+class SuperEdge (Edge):
+
+ color = SUPER_EDGE_COLOR
+ arrowhead = SUPER_EDGE_ARROWHEAD
+
+class UseEdge (Edge):
+
+ color = USE_EDGE_COLOR
+ arrowhead = USE_EDGE_ARROWHEAD
+
+class PropertyEdge (Edge):
+
+ color = PROPERTY_EDGE_COLOR
+ arrowhead = PROPERTY_EDGE_ARROWHEAD
+
+ property = None
+ """@doc"""
+
+ def __init__ (self, class_name, property_name, type_name):
+ """@nodoc"""
+ super (PropertyEdge, self).__init__ (type_name, class_name)
+ self.property = property_name
+
+ def create (self, graph):
+ """@nodoc"""
+ edge = super (PropertyEdge, self).create (graph)
+ gv.setv (edge, 'headport', "%s:e" % self.property)
+ return edge
+
+class UmlPy (object):
+ """@uses: umlpy.Graph"""
+
+ targets = None
+ """@doc"""
+
+ excludes = None
+ """@doc"""
+ includes = None
+ """@doc"""
+ forces = None
+ """@doc"""
+ prefix = None
+ """@doc"""
+ all_methods = None
+ """@doc"""
+ all_properties = None
+ """@doc"""
+ no_method = None
+ """@doc"""
+ no_property = None
+ """@doc"""
+ output = None
+ """@doc"""
+ format = None
+ """@doc"""
+
+ debug = False
+ """@doc"""
+
+ classes = []
+ bases_dict = {}
+ uses_dict = {}
+ methods_dict = {}
+ vars_dict = {}
+
+ def __init__ (self, targets, options):
+ """@nodoc"""
+ self.targets = targets
+ self.excludes = reduce (lambda x, y: x + y,
+ map (lambda s: s.split (","), options.excludes),
+ [])
+ self.includes = reduce (lambda x, y: x + y,
+ map (lambda s: s.split (","), options.includes),
+ [])
+ self.forces = reduce (lambda x, y: x + y,
+ map (lambda s: s.split (","), options.forces),
+ [])
+ self.real_excludes = [re.compile (exp)
+ for exp in self.excludes]
+ self.real_includes = [re.compile (exp)
+ for exp in self.includes]
+ self.real_forces = [re.compile (exp)
+ for exp in self.forces]
+ self.prefix = options.prefix
+ self.all_methods = options.all_methods
+ self.all_properties = options.all_properties
+ self.no_method = options.no_method
+ self.no_property = options.no_property
+ self.output = options.output
+ self.format = options.format
+ if not self.output:
+ self.output = DEFAULT_OUTPUT + self.format
+ pass
+ self.debug = options.debug
+ self.print_settings ()
+ self.classes = []
+ self.bases_dict = {}
+ self.uses_dict = {}
+ self.methods_dict = {}
+ self.properties_dict = {}
+ self.run ()
+
+ def print_settings (self):
+ """@nodoc"""
+ print "Settings"
+ print "--------"
+ print "Targets :", ", ".join (self.targets)
+ print "Output file :", self.output
+ print "Prefix :", self.prefix
+ print "Show all methods :", self.all_methods
+ print "Show all properties :", self.all_properties
+ print "Show no method :", self.no_method
+ print "Show no propertie :", self.no_property
+ print "Excludes :", self.excludes
+ print "Includes :", self.includes
+ print "Forces :", self.forces
+
+ def is_excluded (self, class_name):
+ """@nodoc"""
+ for exp in self.real_excludes:
+ if exp.match (class_name):
+ for exp in [self.real_includes + self.real_forces]:
+ if exp.match (class_name):
+ return False
+ return True
+ else:
+ return False
+
+ def is_forced (self, class_name):
+ """@nodoc"""
+ for exp in self.real_forces:
+ if exp.match (class_name):
+ return True
+ else:
+ return False
+
+ def run (self):
+ """@nodoc"""
+ docs = self.load_targets ()
+ for doc in docs:
+ self.process_doc (doc)
+ self.render_graph ()
+
+ def load_targets (self):
+ """@nodoc"""
+ paths = []
+ for target in self.targets:
+ for base in sys.path:
+ path = os.path.join (base, target)
+ path2 = os.path.join (path, "__init__.py")
+ path3 = path + ".py"
+ if os.path.isdir (path) and os.path.exists (path2):
+ paths.append ([os.path.join (path, file)
+ for file in os.listdir (path)
+ if file.endswith (".py")])
+ break
+ elif os.path.exists (path3):
+ paths.append (path3)
+ break
+ return [epydoc.docparser.parse_docs (path) for path in paths]
+
+ def process_doc (self, doc):
+ """@nodoc"""
+ for var in doc.variables.values ():
+ if type (var.value) != epydoc.apidoc.ClassDoc:
+ continue
+ self.process_class (var)
+
+ def process_class (self, variable):
+ """@nodoc"""
+ value = variable.value
+ name = str (value.canonical_name)
+ name = name.replace (self.prefix, "", 1)
+ if self.debug:
+ print "Processing class", name
+ self.classes.append (name)
+ self.properties_dict[name] = []
+ self.methods_dict[name] = []
+ self.uses_dict[name] = []
+ self.process_class_supers (name, value.bases)
+ docstring = str (value.docstring)
+ self.process_class_docstring (name, docstring)
+ class_variables = value.variables.values ()
+ for class_variable in class_variables:
+ self.process_class_variable (name, class_variable)
+
+ def process_class_supers (self, class_name, bases):
+ """@nodoc"""
+ bases = [str (base.canonical_name).replace (self.prefix, "", 1)
+ for base in bases
+ if str (base.canonical_name) not in ("object", "<UNKNOWN>")]
+ if self.debug:
+ if bases:
+ print "Class %s has %s as supers" % (class_name,
+ ", ".join (bases))
+ else:
+ print "Class %s has no super" % class_name
+ self.bases_dict[class_name] = bases
+
+ def process_class_docstring (self, class_name, docstring):
+ """@nodoc"""
+ if docstring != "<UNKNOWN>":
+ bits = docstring.split ("\n")
+ for bit in bits:
+ if bit.strip ().startswith ("@uses:"):
+ bit = str (bit.replace ("@uses:", "", 1).strip ())
+ self.uses_dict[class_name].append (bit)
+ if self.debug:
+ print "Class %s uses class %s" % (class_name, bit)
+
+ def process_class_variable (self, class_name, variable):
+ """@nodoc"""
+ var_type = type (variable.value)
+ if var_type == epydoc.apidoc.GenericValueDoc:
+ if self.no_property:
+ return
+ self.process_class_property (class_name, variable)
+ elif var_type in (epydoc.apidoc.RoutineDoc,
+ epydoc.apidoc.StaticMethodDoc):
+ if self.no_method:
+ return
+ self.process_class_method (class_name, variable)
+ else:
+ raise ValueError, ("Unknown class variable type :" + var_type)
+
+ @staticmethod
+ def get_property_type (property):
+ """@nodoc"""
+ if property.type_descr != None:
+ return str (property.type_descr.to_plaintext ("").strip ())
+ else:
+ return None
+
+ def process_class_property (self, class_name, property):
+ """@nodoc"""
+ epydoc.docstringparser.parse_docstring (property, None)
+ property_type = UmlPy.get_property_type (property)
+ property_name = str (property.name.replace (self.prefix, "", 1))
+ if self.debug:
+ print "Found property %s of class %s of type %s" % \
+ (class_name, property_name, property_type)
+ docstring = str (property.docstring)
+ if (self.all_properties and not "@nodoc" in docstring) \
+ or "@doc" in docstring or property_type:
+ self.properties_dict[class_name].append ((property_name,
+ property_type))
+
+ def process_class_method (self, class_name, method):
+ """@nodoc"""
+ docstring = str (method.value.docstring)
+ method_name = str (method.name)
+ if self.debug:
+ print "Found method %s of class %s" % (class_name, method_name)
+ if (self.all_methods and not "@nodoc" in docstring) \
+ or "@doc" in docstring :
+ self.methods_dict[class_name].append (method_name)
+
+ def render_graph (self):
+ other_classes = []
+ graph = Graph ()
+ for class_name in self.classes:
+ if self.is_excluded (class_name):
+ continue
+ properties = self.properties_dict[class_name]
+ property_names = [name for (name, type_name) in properties]
+ property_types = [type_name
+ for (name, type_name) in properties
+ if type_name]
+ methods = self.methods_dict[class_name]
+ uses = self.uses_dict[class_name]
+ bases = self.bases_dict[class_name]
+ if properties or methods or uses or bases:
+ graph.add_node (Node (class_name, property_names, methods))
+ elif self.is_forced (class_name):
+ graph.add_node (SimpleNode (class_name))
+ for other_name in property_types + uses + bases:
+ if not other_name or self.is_excluded (other_name):
+ continue
+ if other_name not in other_classes \
+ and other_name not in self.classes:
+ other_classes.append (other_name)
+ graph.add_node (SimpleNode (other_name))
+ for (property, type_name) in properties:
+ if not type_name or self.is_excluded (type_name):
+ continue
+ graph.add_edge (PropertyEdge (class_name, property, type_name))
+ for base_name in bases:
+ if self.is_excluded (base_name):
+ continue
+ graph.add_edge (SuperEdge (class_name, base_name))
+ for used_name in uses:
+ if self.is_excluded (used_name):
+ continue
+ graph.add_edge (UseEdge (class_name, used_name))
+ graph.render (self.output, self.format)
+
+if __name__ == "__main__":
+ (options, args) = parser.parse_args ()
+ UmlPy (args, options)