Modules | Files | Inheritance Tree | Inheritance Graph | Name Index | Config
File: Synopsis/Formatter/Dot.py
    1| # $Id: Dot.py,v 1.34 2002/11/20 15:18:41 stefan Exp $
    2| #
    3| # This file is a part of Synopsis.
    4| # Copyright (C) 2000, 2001 Stefan Seefeld
    5| #
    6| # Synopsis is free software; you can redistribute it and/or modify it
    7| # under the terms of the GNU General Public License as published by
    8| # the Free Software Foundation; either version 2 of the License, or
    9| # (at your option) any later version.
   10| #
   11| # This program is distributed in the hope that it will be useful,
   12| # but WITHOUT ANY WARRANTY; without even the implied warranty of
   13| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   14| # General Public License for more details.
   15| #
   16| # You should have received a copy of the GNU General Public License
   17| # along with this program; if not, write to the Free Software
   18| # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
   19| # 02111-1307, USA.
   20| #
   21| # $Log: Dot.py,v $
   22| # Revision 1.34  2002/11/20 15:18:41  stefan
   23| # don't quote dirname part of a file name
   24| #
   25| # Revision 1.33  2002/11/01 04:26:34  chalky
   26| # Fix wrong-ordered imagemap coords
   27| #
   28| # Revision 1.32  2002/11/01 03:39:20  chalky
   29| # Cleaning up HTML after using 'htmltidy'
   30| #
   31| # Revision 1.31  2002/10/29 15:01:21  chalky
   32| # Better display of template types, and support names with spaces
   33| #
   34| # Revision 1.30  2002/10/28 16:27:22  chalky
   35| # Support horizontal inheritance graphs
   36| #
   37| # Revision 1.29  2002/10/28 11:44:16  chalky
   38| # Support being given a prefix name to strip from class names.
   39| # Display template parameters in class labels
   40| #
   41| # Revision 1.28  2002/10/20 15:38:08  chalky
   42| # Much improved template support, including Function Templates.
   43| #
   44| # Revision 1.27  2002/10/20 02:21:50  chalky
   45| # Fix up quoting (thanks David).
   46| #
   47| # Revision 1.26  2002/08/21 03:36:54  stefan
   48| # * use UML inheritance arrows
   49| # * display operations and attributes if requested
   50| #
   51| # Revision 1.25  2002/07/11 02:09:34  chalky
   52| # Patch from Patrick Mauritz: Use png support in latest graphviz. If dot not
   53| # present, don't subvert what the user asked for but instead tell them.
   54| #
   55| # Revision 1.24  2002/04/26 01:21:14  chalky
   56| # Bugs and cleanups
   57| #
   58| # Revision 1.23  2002/01/09 11:43:41  chalky
   59| # Inheritance pics
   60| #
   61| # Revision 1.22  2001/07/19 04:03:05  chalky
   62| # New .syn file format.
   63| #
   64| # Revision 1.21  2001/07/12 00:53:12  stefan
   65| # ...wasn't meant to be left for the commit
   66| #
   67| # Revision 1.20  2001/07/11 01:45:02  stefan
   68| # fix Dot and HTML formatters to adjust the references depending on the filename of the output
   69| #
   70| # Revision 1.19  2001/06/28 07:22:18  stefan
   71| # more refactoring/cleanup in the HTML formatter
   72| #
   73| # Revision 1.18  2001/06/26 04:32:15  stefan
   74| # A whole slew of changes mostly to fix the HTML formatter's output generation,
   75| # i.e. to make the output more robust towards changes in the layout of files.
   76| #
   77| # the rpm script now works, i.e. it generates source and binary packages.
   78| #
   79| # Revision 1.17  2001/06/06 04:44:54  uid20151
   80| # Prune names of each class to the base which is the most common of its parents.
   81| # Makes for simpler graphs with short names :)
   82| #
   83| # Revision 1.16  2001/05/25 13:45:49  stefan
   84| # fix problem with getopt error reporting
   85| #
   86| # Revision 1.15  2001/04/06 02:38:14  chalky
   87| # Dont reset toc
   88| #
   89| # Revision 1.14  2001/04/06 01:40:19  chalky
   90| # Fixed the unknown nodes bug! Wasn't clearing the nodes dict on each run!
   91| #
   92| # Revision 1.13  2001/03/19 07:53:45  chalky
   93| # Small fixes.
   94| #
   95| # Revision 1.12  2001/02/13 06:55:23  chalky
   96| # Made synopsis -l work again
   97| #
   98| # Revision 1.11  2001/02/06 16:54:15  chalky
   99| # Added -n to Dot which stops those nested classes
  100| #
  101| # Revision 1.10  2001/02/06 15:00:42  stefan
  102| # Dot.py now references the template, not the last parameter, when displaying a Parametrized; replaced logo with a simple link
  103| #
  104| # Revision 1.9  2001/02/05 05:25:08  chalky
  105| # Added hspace and vspace in _format_html
  106| #
  107| # Revision 1.8  2001/02/02 17:42:50  stefan
  108| # cleanup in the Makefiles, more work on the Dot formatter
  109| #
  110| # Revision 1.7  2001/02/02 02:01:01  stefan
  111| # synopsis now supports inlined inheritance tree generation
  112| #
  113| # Revision 1.6  2001/02/01 04:04:23  stefan
  114| # added support for html page generation
  115| #
  116| # Revision 1.5  2001/01/31 21:53:11  stefan
  117| # some more work on the dot formatter
  118| #
  119| # Revision 1.4  2001/01/31 06:51:24  stefan
  120| # add support for '-v' to all modules; modified toc lookup to use additional url as prefix
  121| #
  122| # Revision 1.3  2001/01/24 01:38:36  chalky
  123| # Added docstrings to all modules
  124| #
  125| # Revision 1.2  2001/01/23 21:31:36  stefan
  126| # bug fixes
  127| #
  128| # Revision 1.1  2001/01/23 19:50:42  stefan
  129| # Dot: an inheritance/collaboration graph generator
  130| #
  131| #
  132| 
  133| """
  134| Uses 'dot' from graphviz to generate various graphs.
  135| """
  136| # THIS-IS-A-FORMATTER
  137| 
  138| import sys, tempfile, getopt, os, os.path, string, types, errno, re
  139| from Synopsis.Core import AST, Type, Util
  140| from Synopsis.Formatter import TOC
  141| 
  142| verbose = 0
  143| toc = None
  144| nodes = {}
  145| name_prefix = None
  146| direction = 'vertical'
  147| 
  148| class SystemError:
  149|     """Error thrown by the system() function. Attributes are 'retval', encoded
  150|     as per os.wait(): low-byte is killing signal number, high-byte is return
  151|     value of command."""
  152|     def __init__(self, retval, command):
  153|         self.retval = retval
  154|         self.command = command
  155|     def __repr__(self):
  156|         return "SystemError: %(retval)x\"%(command)s\" failed."%self.__dict__
  157| 
  158| def system(command):
  159|     """Run the command. If the command fails, an exception SystemError is
  160|     thrown."""
  161|     ret = os.system(command)
  162|     if (ret>>8) != 0:
  163|         raise SystemError(ret, command)
  164| 
  165| class InheritanceFormatter(AST.Visitor, Type.Visitor):
  166|     """A Formatter that generates an inheritance graph"""
  167|     def __init__(self, os, operations, attributes):
  168|         self.__os = os
  169|         if operations: self.__operations = []
  170|         else: self.__operations = None
  171|         if attributes: self.__attributes = []
  172|         else: self.__attributes = None
  173|         self.__scope = []
  174|         if name_prefix: self.__scope = string.split(name_prefix, '::')
  175|         self.__type_ref = None
  176|         self.__type_label = ''
  177|     def scope(self): return self.__scope
  178|     def write(self, text): self.__os.write(text)
  179|     def type_ref(self): return self.__type_ref
  180|     def type_label(self): return self.__type_label
  181|     def parameter(self): return self.__parameter
  182| 
  183|     def formatType(self, typeObj):
  184|         "Returns a reference string for the given type object"
  185|         if typeObj is None: return "(unknown)"
  186|         typeObj.accept(self)
  187|         return self.type_label()
  188| 
  189|     def clearType(self):
  190|         self.__type_ref = None
  191|         self.__type_label = ''
  192|         
  193|     def writeNode(self, ref, name, label, **attr):
  194|         """helper method to generate output for a given node"""
  195|         if nodes.has_key(name): return
  196|         nodes[name] = len(nodes)
  197|         number = nodes[name]
  198| 
  199|         # Quote to remove characters that dot can't handle
  200|         label = re.sub('<',r'\<',label)
  201|         label = re.sub('>',r'\>',label)
  202|         label = re.sub('{',r'\{',label)
  203|         label = re.sub('}',r'\}',label)
  204| 
  205|         self.write("Node" + str(number) + " [shape=\"record\", label=\"" + label + "\"")
  206|         self.write(", fontSize = 10, height = 0.2, width = 0.4")
  207|         self.write(string.join(map(lambda item:', %s="%s"'%item, attr.items())))
  208|         if ref: self.write(", URL=\"" + ref + "\"")
  209|         self.write("];\n")
  210| 
  211|     def writeEdge(self, parent, child, label, **attr):
  212|         self.write("Node" + str(nodes[parent]) + " -> ")
  213|         self.write("Node" + str(nodes[child]))
  214|         self.write("[ color=\"black\", fontsize=10, dir=back, arrowtail=empty, " + string.join(map(lambda item:', %s="%s"'%item, attr.items())) + "];\n")
  215| 
  216|     def getClassName(self, node):
  217|         """Returns the name of the given class node, relative to all its
  218|         parents. This makes the graph simpler by making the names shorter"""
  219|         base = node.name()
  220|         for i in node.parents():
  221|         try:
  222|                parent = i.parent()
  223|                pname = parent.name()
  224|                for j in range(len(base)):
  225|                    if j > len(pname) or pname[j] != base[j]:
  226|                       # Base is longer than parent name, or found a difference
  227|                base[j:] = []
  228|         break
  229|             except: pass # typedefs etc may cause errors here.. ignore
  230|         if not node.parents():
  231|             base = self.scope()
  232|         return Util.ccolonName(node.name(), base)
  233| 
  234|     #################### Type Visitor ###########################################
  235|     def visitModifier(self, type):
  236|         self.formatType(type.alias())
  237|         self.__type_label = string.join(type.premod()) + self.__type_label
  238|         self.__type_label = self.__type_label + string.join(type.postmod())
  239| 
  240|     def visitUnknown(self, type):
  241|         self.__type_ref = toc[type.link()]
  242|         self.__type_label = Util.ccolonName(type.name(), self.scope())
  243|         
  244|     def visitBase(self, type):
  245|         self.__type_ref = None
  246|         self.__type_label = type.name()[-1]
  247| 
  248|     def visitDependent(self, type):
  249|         self.__type_ref = None
  250|         self.__type_label = type.name()[-1]
  251|         
  252|     def visitDeclared(self, type):
  253|         self.__type_ref = toc[type.declaration().name()]
  254|         if isinstance(type.declaration(), AST.Class):
  255|             self.__type_label = self.getClassName(type.declaration())
  256|         else:
  257|             self.__type_label = Util.ccolonName(type.declaration().name(), self.scope())
  258| 
  259|     def visitParametrized(self, type):
  260|         if type.template():
  261|             type_ref = toc[type.template().name()]
  262|             type_label = Util.ccolonName(type.template().name(), self.scope())
  263|         else:
  264|             type_ref = None
  265|             type_label = "(unknown)"
  266|         parameters_label = []
  267|         for p in type.parameters():
  268|             parameters_label.append(self.formatType(p))
  269|         self.__type_ref = type_ref
  270|         self.__type_label = type_label + "<" + string.join(parameters_label, ",") + ">"
  271| 
  272|     def visitTemplate(self, type):
  273|         self.__type_ref = None
  274|         def clip(x, max=20):
  275|             if len(x) > max: return '...'
  276|             return x
  277|         self.__type_label = "template<%s>"%(clip(string.join(map(clip, map(self.formatType, type.parameters())), ","),40))
  278| 
  279|     #################### AST Visitor ############################################
  280|         
  281|     def visitInheritance(self, node):
  282|         self.formatType(node.parent())
  283|         if self.type_ref():
  284|             self.writeNode(self.type_ref().link, self.type_label(), self.type_label())
  285|         else:
  286|             self.writeNode('', self.type_label(), self.type_label(), color='gray75', fontcolor='gray75')
  287|         
  288|     def visitClass(self, node):
  289|         if self.__operations is not None: self.__operations.append([])
  290|         if self.__attributes is not None: self.__attributes.append([])
  291|         name = self.getClassName(node)
  292|         ref = toc[node.name()]
  293|         for d in node.declarations(): d.accept(self)
  294|         # NB: old version of dot needed the label surrounded in {}'s (?)
  295|         label = name
  296|         if node.template():
  297|             if direction == 'vertical':
  298|                 label = self.formatType(node.template()) + '\\n' + label
  299|             else:
  300|                 label = self.formatType(node.template()) + ' ' + label
  301|         if self.__operations or self.__attributes:
  302|             label = label + '\\n'
  303|             if self.__operations:
  304|                 label = label + '|' + string.join(map(lambda x:x[-1] + '()\\l', self.__operations[-1]))
  305|             if self.__attributes:
  306|                 label = label + '|' + string.join(map(lambda x:x[-1] + '\\l', self.__attributes[-1]))
  307|         if ref:
  308|             self.writeNode(ref.link, name, label)
  309|         else:
  310|             self.writeNode('', name, label, color='gray75', fontcolor='gray75')
  311|         for inheritance in node.parents():
  312|             inheritance.accept(self)
  313|             self.writeEdge(self.type_label(), name, None)
  314|         if no_descend: return
  315|         if self.__operations: self.__operations = self.__operations[:-1]
  316|         if self.__attributes: self.__attributes = self.__attributes[:-1]
  317| 
  318|     def visitOperation(self, operation):
  319|         if self.__operations:
  320|             self.__operations[-1].append(operation.realname())
  321| 
  322|     def visitVariable(self, variable):
  323|         if self.__attributes:
  324|             self.__attributes[-1].append(variable.name())
  325| 
  326| class SingleInheritanceFormatter(InheritanceFormatter):
  327|     """A Formatter that generates an inheritance graph for a specific class.
  328|     This Visitor visits the AST upwards, i.e. following the inheritance links, instead of
  329|     the declarations contained in a given scope."""
  330|     #base = InheritanceFormatter
  331|     def __init__(self, os, operations, attributes, levels, types):
  332|         InheritanceFormatter.__init__(self, os, operations, attributes)
  333|         self.__levels = levels
  334|         self.__types = types
  335|         self.__current = 1
  336|         self.__visited_classes = {} # classes already visited, to prevent recursion
  337| 
  338|     #################### Type Visitor ###########################################
  339| 
  340|     def visitDeclared(self, type):
  341|         if self.__current < self.__levels or self.__levels == -1:
  342|             self.__current = self.__current + 1
  343|             type.declaration().accept(self)
  344|             self.__current = self.__current - 1
  345|         # to restore the ref/label...
  346|         InheritanceFormatter.visitDeclared(self, type)
  347|     #################### AST Visitor ############################################
  348|         
  349|     def visitInheritance(self, node):
  350|         node.parent().accept(self)
  351|         if self.type_label():
  352|             if self.type_ref():
  353|                 self.writeNode(self.type_ref().link, self.type_label(), self.type_label())
  354|             else:
  355|                 self.writeNode('', self.type_label(), self.type_label(), color='gray75', fontcolor='gray75')
  356|         
  357|     def visitClass(self, node):
  358|         # Prevent recursion
  359|         if self.__visited_classes.has_key(id(node)): return
  360|         self.__visited_classes[id(node)] = None
  361| 
  362|         name = self.getClassName(node)
  363|         if self.__current == 1:
  364|             self.writeNode('', name, name, style='filled', color='lightgrey')
  365|         else:
  366|             ref = toc[node.name()]
  367|             if ref:
  368|                 self.writeNode(ref.link, name, name)
  369|             else:
  370|                 self.writeNode('', name, name, color='gray75', fontcolor='gray75')
  371|         for inheritance in node.parents():
  372|             inheritance.accept(self)
  373|             if nodes.has_key(self.type_label()):
  374|                 self.writeEdge(self.type_label(), name, None)
  375|         # if this is the main class and if there is a type dictionary,
  376|         # look for classes that are derived from this class
  377| 
  378|         # if this is the main class
  379|         if self.__current == 1 and self.__types:
  380|             # fool the visitDeclared method to stop walking upwards
  381|             self.__levels = 0
  382|             for t in self.__types.values():
  383|                 if isinstance(t, Type.Declared):
  384|                     child = t.declaration()
  385|                     if isinstance(child, AST.Class):
  386|                         for inheritance in child.parents():
  387|                             type = inheritance.parent()
  388|                             type.accept(self)
  389|                             if self.type_ref():
  390|                                 if self.type_ref().name == node.name():
  391|                                     child_label = self.getClassName(child)
  392|                                     ref = toc[child.name()]
  393|                                     if ref:
  394|                                         self.writeNode(ref.link, child_label, child_label)
  395|                                     else:
  396|                                         self.writeNode('', child_label, child_label, color='gray75', fontcolor='gray75')
  397|                                     self.writeEdge(name, child_label, None)
  398| 
  399| class CollaborationFormatter(AST.Visitor, Type.Visitor):
  400|     """A Formatter that generates a collaboration graph"""
  401|     def __init__(self, output):
  402|         self.__output = output
  403|     def write(self, text): self.__output.write(text)
  404|     def visitClass(self, node):
  405|         name = node.name()
  406|         for inheritance in node.parents():
  407|             parent = inheritance.parent()
  408|             if hasattr(parent, 'name'):
  409|                 self.write(parent.name()[-1] + " -> " + name[-1] + ";\n")
  410|         for d in node.declarations(): d.accept(self)
  411|     def visitVariable(self, node):
  412|         node.vtype().accept(self)
  413|         print node.name(), self.__type_label
  414| 
  415|     def visitBaseType(self, type):
  416|         self.__type_label = type.name()
  417|     def visitUnknown(self, type):
  418|         self.__type_label = type.name()
  419|     def visitDeclared(self, type):
  420|         self.__type_label = type.name()
  421|     def visitModifier(self, type):
  422|         alias = type.alias()
  423|         pre = string.join(map(lambda x:x+"&nbsp;", type.premod()), '')
  424|         post = string.join(type.postmod(), '')
  425|         self.__type_label = "%s%s%s"%(pre,alias,post)
  426|     def visitTemplate(self, type):
  427|         self.__type_label = "<template>"
  428|     def visitParametrized(self, type):
  429|         self.__type_label = "<parametrized>"
  430|     def visitFunctionType(self, type):
  431|         self.__type_label = "<function>"
  432| 
  433| 
  434| def usage():
  435|     """Print usage to stdout"""
  436|     print \
  437| """
  438|   -o <filename>         Output directory, created if it doesn't exist.
  439|   -t <title>            Associate <title> with the generated graph
  440|   -i                    Generate an inheritance graph
  441|   -s                    Generate an inheritance graph for a single class
  442|   -O                    show operations
  443|   -A                    show attributs
  444|   -c                    Generate a collaboration graph
  445|   -f <format>           Generate output in format 'dot', 'ps' (default), 'png', 'gif', 'map', 'html'
  446|   -r <filename>         Read in toc for an external data base that is to be referenced (for map/html output)
  447|   -R <filename>         Provide a reference URL which is used to compute relative links from the toc entries (for map/html output)    
  448|   -n                    Don't descend AST (for internal use)
  449|   -p <prefix>           Specify a prefix to strip from all class names
  450|   -d <direction>        Direction of graph: 'vertical' or 'horizontal'"""
  451| 
  452| formats = {
  453|     'dot' : 'dot',
  454|     'ps' : 'ps',
  455|     'png' : 'png',
  456|     'gif' : 'gif',
  457|     'map' : 'imap',
  458|     'html' : 'html',
  459| }
  460| 
  461| def __parseArgs(args, config_obj):
  462|     global output, title, type, operations, attributes, oformat, verbose
  463|     global toc_in, origin, no_descend, nodes, name_prefix, direction
  464|     output = ''
  465|     title = 'NoTitle'
  466|     type = ''
  467|     operations = 0
  468|     attributes = 0
  469|     oformat = 'ps'
  470|     toc_in = []
  471|     origin = ''
  472|     no_descend = 0
  473|     nodes = {}
  474|     name_prefix = None
  475|     direction = 'vertical'
  476|     try:
  477|         opts,remainder = Util.getopt_spec(args, "o:t:OAf:r:R:p:d:icsvn")
  478|     except Util.getopt.error, e:
  479|         sys.stderr.write("Error in arguments: " + str(e) + "\n")
  480|         sys.exit(1)
  481| 
  482|     for opt in opts:
  483|         o,a = opt
  484|         if o == "-o": output = a
  485|         elif o == "-t": title = a
  486|         elif o == "-i": type = "inheritance"
  487|         elif o == "-s": type = "single"
  488|         elif o == "-O": operations = 1
  489|         elif o == "-A": attributes = 1
  490|         elif o == "-c":
  491|             type = "collaboration"
  492|             sys.stderr.write("sorry, collaboration diagrams not yet implemented\n");
  493|             sys.exit(-1)
  494|         elif o == "-f":
  495|             if formats.has_key(a): oformat = formats[a]
  496|          else:
  497|                print "Error: Unknown format. Available formats are:",string.join(formats.keys(), ', ')
  498|                sys.exit(1)
  499|         elif o == "-r": toc_in.append(a)
  500|         elif o == "-R": origin = a
  501|         elif o == "-v": verbose = 1
  502|         elif o == "-n": no_descend = 1
  503|         elif o == "-p": name_prefix = a
  504|         elif o == "-d": direction = a
  505| 
  506| def _rel(frm, to):
  507|     "Find link to to relative to frm"
  508|     frm = string.split(frm, '/'); to = string.split(to, '/')
  509|     for l in range((len(frm)<len(to)) and len(frm)-1 or len(to)-1):
  510|         if to[0] == frm[0]: del to[0]; del frm[0]
  511|         else: break
  512|     if frm: to = ['..'] * (len(frm) - 1) + to
  513|     return string.join(to,'/')
  514| 
  515| def _convert_map(input, output):
  516|     """convert map generated from Dot to a html region map.
  517|     input and output are (open) streams"""
  518|     line = input.readline()
  519|     while line:
  520|         line = line[:-1]
  521|         if line[0:4] == "rect":
  522|             url, x1y1, x2y2 = string.split(line[4:])
  523|             x1, y1 = string.split(x1y1, ",")
  524|             x2, y2 = string.split(x2y2, ",")
  525|             output.write('<area alt="'+url+'" href="' + _rel(origin, url) + '" shape="rect" coords="')
  526|             output.write(str(x1) + ", " + str(y1) + ", " + str(x2) + ", " + str(y2) + '">\n')
  527|         line = input.readline()
  528| 
  529| def _format(input, output, format):
  530|     command = 'dot -T%s -o "%s" "%s"'%(format, output, input)
  531|     if verbose: print "Dot Formatter: running command '" + command + "'"
  532|     system(command)
  533| 
  534| def _format_png(input, output):
  535|     _format(input, output, "png")
  536| 
  537| def _format_html(input, output):
  538|     """generate (active) image for html.
  539|     input and output are file names. If output ends
  540|     in '.html', its stem is used with an '.png' suffix for the
  541|     actual image."""
  542|     if output[-5:] == ".html": output = output[:-5]
  543|     _format_png(input, output + ".png")
  544|     _format(input, output + ".map", "imap")
  545|     prefix, name = os.path.split(output)
  546|     reference = name + ".png"
  547|     html = Util.open(output + ".html")
  548|     html.write('<img alt="'+name+'" src="' + reference + '" hspace="8" vspace="8" border="0" usemap="#')
  549|     html.write(name + "_map\">\n")
  550|     html.write("<map name=\"" + name + "_map\">")
  551|     dotmap = open(output + ".map", "r+")
  552|     _convert_map(dotmap, html)
  553|     dotmap.close()
  554|     os.remove(output + ".map")
  555|     html.write("</map>\n")
  556| 
  557| def format(args, ast, config_obj):
  558|     global output, title, type, operations, attributes, oformat, verbose, toc, toc_in
  559|     __parseArgs(args, config_obj)
  560| 
  561|     # Create table of contents index
  562|     if not toc: toc = TOC.TableOfContents(TOC.Linker())
  563|     for t in toc_in: toc.load(t)
  564| 
  565|     head, tail = os.path.split(output)
  566|     tmpfile = os.path.join(head, Util.quote(tail)) + ".dot"
  567|     if verbose: print "Dot Formatter: Writing dot file..."
  568|     dotfile = Util.open(tmpfile)
  569|     dotfile.write("digraph \"%s\" {\n"%(title))
  570|     if direction == 'horizontal':
  571|         dotfile.write('rankdir="LR";\n')
  572|         dotfile.write('ranksep="1.0";\n')
  573|     dotfile.write("node[shape=record, fontsize=10, height=0.2, width=0.4, color=black]\n")
  574|     if type == "inheritance":
  575|         generator = InheritanceFormatter(dotfile, operations, attributes)
  576|     elif type == "single":
  577|         generator = SingleInheritanceFormatter(dotfile, operations, attributes, -1, ast.types())
  578|     else:
  579|         generator = CollaborationFormatter(dotfile)
  580|     for d in ast.declarations():
  581|         d.accept(generator)
  582|     dotfile.write("}\n")
  583|     dotfile.close()
  584|     if oformat == "dot":
  585|         os.rename(tmpfile, output)
  586|     elif oformat == "png":
  587|         _format_png(tmpfile, output)
  588|         #os.remove(tmpfile)
  589|     elif oformat == "html":
  590|         _format_html(tmpfile, output)
  591|         #os.remove(tmpfile)
  592|     else:
  593|         _format(tmpfile, output, oformat)
  594|         os.remove(tmpfile)