Revision: 5601
          http://matplotlib.svn.sourceforge.net/matplotlib/?rev=5601&view=rev
Author:   mdboom
Date:     2008-06-19 12:55:41 -0700 (Thu, 19 Jun 2008)

Log Message:
-----------
Add initial support for inheritance diagrams.

Modified Paths:
--------------
    trunk/matplotlib/doc/api/artist_api.rst
    trunk/matplotlib/doc/api/collections_api.rst
    trunk/matplotlib/doc/conf.py
    trunk/matplotlib/doc/devel/transformations.rst

Added Paths:
-----------
    trunk/matplotlib/doc/sphinxext/inheritance_diagram.py

Modified: trunk/matplotlib/doc/api/artist_api.rst
===================================================================
--- trunk/matplotlib/doc/api/artist_api.rst     2008-06-19 19:53:12 UTC (rev 
5600)
+++ trunk/matplotlib/doc/api/artist_api.rst     2008-06-19 19:55:41 UTC (rev 
5601)
@@ -2,6 +2,8 @@
 matplotlib artists
 *******************
 
+.. inheritance-diagram:: matplotlib.patches matplotlib.lines matplotlib.text
+
 :mod:`matplotlib.artist`
 =============================
 

Modified: trunk/matplotlib/doc/api/collections_api.rst
===================================================================
--- trunk/matplotlib/doc/api/collections_api.rst        2008-06-19 19:53:12 UTC 
(rev 5600)
+++ trunk/matplotlib/doc/api/collections_api.rst        2008-06-19 19:55:41 UTC 
(rev 5601)
@@ -2,6 +2,7 @@
 matplotlib collections
 **********************
 
+.. inheritance-diagram:: matplotlib.collections
 
 :mod:`matplotlib.collections`
 =============================

Modified: trunk/matplotlib/doc/conf.py
===================================================================
--- trunk/matplotlib/doc/conf.py        2008-06-19 19:53:12 UTC (rev 5600)
+++ trunk/matplotlib/doc/conf.py        2008-06-19 19:55:41 UTC (rev 5601)
@@ -28,7 +28,7 @@
 # Add any Sphinx extension module names here, as strings. They can be 
extensions
 # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
 extensions = ['mathpng', 'math_symbol_table', 'sphinx.ext.autodoc',
-              'only_directives', 'plot_directive']
+              'only_directives', 'plot_directive', 'inheritance_diagram']
 
 # Add any paths that contain templates here, relative to this directory.
 templates_path = ['_templates']

Modified: trunk/matplotlib/doc/devel/transformations.rst
===================================================================
--- trunk/matplotlib/doc/devel/transformations.rst      2008-06-19 19:53:12 UTC 
(rev 5600)
+++ trunk/matplotlib/doc/devel/transformations.rst      2008-06-19 19:55:41 UTC 
(rev 5601)
@@ -2,6 +2,8 @@
  Working with transformations
 ==============================
 
+.. inheritance-diagram:: matplotlib.transforms matplotlib.path
+
 :mod:`matplotlib.transforms`
 =============================
 

Added: trunk/matplotlib/doc/sphinxext/inheritance_diagram.py
===================================================================
--- trunk/matplotlib/doc/sphinxext/inheritance_diagram.py                       
        (rev 0)
+++ trunk/matplotlib/doc/sphinxext/inheritance_diagram.py       2008-06-19 
19:55:41 UTC (rev 5601)
@@ -0,0 +1,414 @@
+"""
+Defines a docutils directive for inserting inheritance diagrams.
+
+Provide the directive with one or more classes or modules (separated
+by whitespace).  For modules, all of the classes in that module will
+be used.
+
+Example::
+
+   Given the following classes:
+
+   class A: pass
+   class B(A): pass
+   class C(A): pass
+   class D(B, C): pass
+   class E(B): pass
+
+   .. inheritance-diagram: D E
+
+   Produces a graph like the following:
+
+               A
+              / \
+             B   C
+            / \ /
+           E   D
+
+The graph is inserted as a PNG+image map into HTML and a PDF in
+LaTeX.
+"""
+
+import inspect
+import os
+import subprocess
+try:
+    from hashlib import md5
+except ImportError:
+    from md5 import md5
+
+from docutils.nodes import Body, Element
+from docutils.writers.html4css1 import HTMLTranslator
+from sphinx.latexwriter import LaTeXTranslator
+from docutils.parsers.rst import directives
+from sphinx.roles import xfileref_role
+from sphinx.directives.desc import py_sig_re
+
+class DotException(Exception):
+    pass
+
+class InheritanceGraph(object):
+    """
+    Given a list of classes, determines the set of classes that
+    they inherit from all the way to the root "object", and then
+    is able to generate a graphviz dot graph from them.
+    """
+    def __init__(self, class_names, show_builtins=False):
+        """
+        *class_names* is a list of child classes to show bases from.
+
+        If *show_builtins* is True, then Python builtins will be shown
+        in the graph.
+        """
+        self.class_names = class_names
+        self.classes = self._import_classes(class_names)
+        self.all_classes = self._all_classes(self.classes)
+        if len(self.all_classes) == 0:
+            raise ValueError("No classes found for inheritance diagram")
+        self.show_builtins = show_builtins
+
+    def _import_class_or_module(self, name):
+        """
+        Import a class using its fully-qualified *name*.
+        """
+        try:
+            path, base, signature = py_sig_re.match(name).groups()
+        except:
+            raise ValueError(
+                "Invalid class '%s' specified for inheritance diagram" % name)
+        fullname = (path or '') + base
+        path = path and path.rstrip('.')
+        if not path:
+            raise ValueError(
+                "Invalid class '%s' specified for inheritance diagram" % name)
+        try:
+            module = __import__(path, None, None, [])
+        except ImportError:
+            raise ValueError(
+                "Could not import class '%s' specified for inheritance 
diagram" % name)
+
+        try:
+            todoc = module
+            for comp in fullname.split('.')[1:]:
+                todoc = getattr(todoc, comp)
+        except AttributeError:
+            raise ValueError(
+                "Could not find class '%s' specified for inheritance diagram" 
% name)
+
+        # If a class, just return it
+        if inspect.isclass(todoc):
+            return [todoc]
+        elif inspect.ismodule(todoc):
+            classes = []
+            for cls in todoc.__dict__.values():
+                if inspect.isclass(cls) and cls.__module__ == todoc.__name__:
+                    classes.append(cls)
+            return classes
+        raise ValueError(
+            "'%s' does not resolve to a class or module" % name)
+
+    def _import_classes(self, class_names):
+        """
+        Import a list of classes.
+        """
+        classes = []
+        for name in class_names:
+            classes.extend(self._import_class_or_module(name))
+        return classes
+
+    def _all_classes(self, classes):
+        """
+        Return a list of all classes that are ancestors of *classes*.
+        """
+        all_classes = {}
+
+        def recurse(cls):
+            all_classes[cls] = None
+            for c in cls.__bases__:
+                if c not in all_classes:
+                    recurse(c)
+
+        for cls in classes:
+            recurse(cls)
+
+        return all_classes.keys()
+
+    def class_name(self, cls):
+        """
+        Given a class object, return a fully-qualified name.  This
+        works for things I've tested in matplotlib so far, but may
+        not be completely general.
+        """
+        module = cls.__module__
+        if module == '__builtin__':
+            return cls.__name__
+        return '.'.join([module, cls.__name__])
+
+    def get_all_class_names(self):
+        """
+        Get all of the class names involved in the graph.
+        """
+        return [self.class_name(x) for x in self.all_classes]
+
+    # These are the default options for
+    default_graph_options = {
+        "rankdir": "LR",
+        "size": '"11.0, 11.0"'
+        }
+    default_node_options = {
+        "shape": "box",
+        "fontsize": 10,
+        "height": 0.25,
+        "fontname": "sans",
+        "style": '"setlinewidth(0.5)"'
+        }
+    default_edge_options = {
+        "arrowsize": 0.5,
+        "style": '"setlinewidth(0.5)"'
+        }
+
+    def _format_node_options(self, options):
+        return ','.join(["%s=%s" % x for x in options.items()])
+    def _format_graph_options(self, options):
+        return ''.join(["%s=%s;\n" % x for x in options.items()])
+
+    def generate_dot(self, fd, name, urls={},
+                     graph_options={}, node_options={},
+                     edge_options={}):
+        """
+        Generate a graphviz dot graph from the classes that
+        were passed in to __init__.
+
+        *fd* is a Python file-like object to write to.
+
+        *name* is the name of the graph
+
+        *urls* is a dictionary mapping class names to http urls
+
+        *graph_options*, *node_options*, *edge_options* are
+        dictionaries containing key/value pairs to pass on as graphviz
+        properties.
+        """
+        g_options = self.default_graph_options.copy()
+        g_options.update(graph_options)
+        n_options = self.default_node_options.copy()
+        n_options.update(node_options)
+        e_options = self.default_edge_options.copy()
+        e_options.update(edge_options)
+
+        fd.write('digraph %s {\n' % name)
+        fd.write(self._format_graph_options(g_options))
+
+        for cls in self.all_classes:
+            if not self.show_builtins and cls in __builtins__.values():
+                continue
+
+            name = self.class_name(cls)
+
+            # Write the node
+            this_node_options = n_options.copy()
+            url = urls.get(name)
+            if url is not None:
+                this_node_options['URL'] = '"%s"' % url
+            fd.write('  "%s" [%s];\n' %
+                     (name, self._format_node_options(this_node_options)))
+
+            # Write the edges
+            for base in cls.__bases__:
+                if not self.show_builtins and base in __builtins__.values():
+                    continue
+
+                base_name = self.class_name(base)
+                fd.write('  "%s" -> "%s" [%s];\n' %
+                         (self.class_name(base), name,
+                          self._format_node_options(e_options)))
+        fd.write('}\n')
+
+    def run_dot(self, args, name, urls={},
+                graph_options={}, node_options={}, edge_options={}):
+        """
+        Run graphviz 'dot' over this graph, returning whatever 'dot'
+        writes to stdout.
+
+        *args* will be passed along as commandline arguments.
+
+        *name* is the name of the graph
+
+        *urls* is a dictionary mapping class names to http urls
+
+        Raises DotException for any of the many os and
+        installation-related errors that may occur.
+        """
+        try:
+            dot = subprocess.Popen(['dot'] + list(args),
+                                   stdin=subprocess.PIPE, 
stdout=subprocess.PIPE,
+                                   close_fds=True)
+        except OSError:
+            raise DotException("Could not execute 'dot'.  Are you sure you 
have 'graphviz' installed?")
+        except ValueError:
+            raise DotException("'dot' called with invalid arguments")
+        except:
+            raise DotException("Unexpected error calling 'dot'")
+
+        self.generate_dot(dot.stdin, name, urls, graph_options, node_options,
+                          edge_options)
+        dot.stdin.close()
+        result = dot.stdout.read()
+        returncode = dot.wait()
+        if returncode != 0:
+            raise DotException("'dot' returned the errorcode %d" % returncode)
+        return result
+
+class inheritance_diagram(Body, Element):
+    """
+    A docutils node to use as a placeholder for the inheritance
+    diagram.
+    """
+    pass
+
+def inheritance_diagram_directive_run(clstexts, state):
+    """
+    Run when the inheritance_diagram directive is first encountered.
+    """
+    node = inheritance_diagram()
+
+    # Create a graph starting with the list of classes
+    graph = InheritanceGraph(clstexts)
+
+    # Create xref nodes for each target of the graph's image map and
+    # add them to the doc tree so that Sphinx can resolve the
+    # references to real URLs later.  These nodes will eventually be
+    # removed from the doctree after we're done with them.
+    for name in graph.get_all_class_names():
+        refnodes, x = xfileref_role(
+            'class', ':class:`%s`' % name, name, 0, state)
+        node.extend(refnodes)
+    # Store the graph object so we can use it to generate the
+    # dot file later
+    node['graph'] = graph
+    # Store the original content for use as a hash
+    node['content'] = " ".join(clstexts)
+    return [node]
+
+def html_output_graph(self, node):
+    """
+    Output the graph for HTML.  This will insert a PNG with clickable
+    image map.
+    """
+    graph = node['graph']
+
+    # Determine where to write the PNG to.  This follows
+    # the same procedure as mathpng.py
+    name = 'inheritance%s' % md5(node['content']).hexdigest()[-10:]
+    png_path = '_static/%s.png' % name
+
+    path = '_static'
+    source = self.document.attributes['source']
+    count = source.split('/doc/')[-1].count('/')
+    for i in range(count):
+        if os.path.exists(path): break
+        path = '../'+path
+    path = '../'+path #specifically added for matplotlib
+
+    # Create a mapping from fully-qualified class names to URLs.
+    urls = {}
+    for child in node:
+        try:
+            urls[child['reftitle']] = child['refuri']
+        except KeyError:
+            try:
+                urls[child['reftitle']] = '#' + child['refid']
+            except KeyError:
+                pass
+
+    # These arguments to dot will save a PNG file to disk and write
+    # an HTML image map to stdout.
+    image_map = graph.run_dot(['-Tpng', '-o%s' % png_path, '-Tcmapx'],
+                              name, urls)
+    return ('<img src="%s/%s.png" usemap="#%s"/>%s' %
+            (path, name, name, image_map))
+
+def latex_output_graph(self, node):
+    """
+    Output the graph for LaTeX.  This will insert a PDF.
+    """
+    graph = node['graph']
+
+    # Determine where to write the PNG to.  This follows
+    # the same procedure as mathpng.py
+    name = 'inheritance%s' % md5(node['content']).hexdigest()[-10:]
+    pdf_path = '_static/%s.pdf' % name
+
+    path = '_static'
+    source = self.document.attributes['source']
+    count = source.split('/doc/')[-1].count('/')
+    for i in range(count):
+        if os.path.exists(path): break
+        path = '../'+path
+    path = '../'+path #specifically added for matplotlib
+
+    graph.run_dot(['-Tpdf', '-o%s' % pdf_path], name,
+                  graph_options={'size': '"6.0,6.0"'})
+    return '\\includegraphics{../../_static/%s.pdf}' % name
+
+def visit_inheritance_diagram(inner_func):
+    """
+    This is just a wrapper around html/latex_output_graph to make it
+    easier to handle errors and insert warnings.
+    """
+    def visitor(self, node):
+        try:
+            content = inner_func(self, node)
+        except DotException, e:
+            # Insert the exception as a warning in the document
+            warning = self.document.reporter.warning(str(e), line=node.line)
+            warning.parent = node
+            node.children = [warning]
+        else:
+            source = self.document.attributes['source']
+            self.body.append(content)
+            node.children = []
+    return visitor
+
+def do_nothing(self, node):
+    pass
+
+# Deal with the old and new way of registering directives
+try:
+    from docutils.parsers.rst import Directive
+except ImportError:
+    from docutils.parsers.rst.directives import _directives
+    def inheritance_diagram_directive(name, arguments, options, content, 
lineno,
+                                      content_offset, block_text, state,
+                                      state_machine):
+        return inheritance_diagram_directive_run(arguments, state)
+    inheritance_diagram_directive.__doc__ = __doc__
+    inheritance_diagram_directive.arguments = (1, 100, 0)
+    inheritance_diagram_directive.options = {}
+    inheritance_diagram_directive.content = 0
+    _directives['inheritance-diagram'] = inheritance_diagram_directive
+else:
+    class inheritance_diagram_directive(Directive):
+        has_content = False
+        required_arguments = 1
+        optional_arguments = 100
+        final_argument_whitespace = False
+        option_spec = {}
+
+        def run(self):
+            return inheritance_diagram_directive_run(self.arguments, 
self.state)
+    inheritance_diagram_directive.__doc__ = __doc__
+
+    directives.register_directive('inheritance-diagram',
+                                  inheritance_diagram_directive)
+
+def setup(app):
+    app.add_node(inheritance_diagram)
+
+    HTMLTranslator.visit_inheritance_diagram = \
+        visit_inheritance_diagram(html_output_graph)
+    HTMLTranslator.depart_inheritance_diagram = do_nothing
+
+    LaTeXTranslator.visit_inheritance_diagram = \
+        visit_inheritance_diagram(latex_output_graph)
+    LaTeXTranslator.depart_inheritance_diagram = do_nothing


This was sent by the SourceForge.net collaborative development platform, the 
world's largest Open Source development site.

-------------------------------------------------------------------------
Check out the new SourceForge.net Marketplace.
It's the best place to buy or sell services for
just about anything Open Source.
http://sourceforge.net/services/buy/index.php
_______________________________________________
Matplotlib-checkins mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/matplotlib-checkins

Reply via email to