[Epydoc-commits] SF.net SVN: epydoc: [1169] trunk/epydoc/src/epydoc
Brought to you by:
edloper
From: <ed...@us...> - 2006-04-05 16:58:50
|
Revision: 1169 Author: edloper Date: 2006-04-05 09:58:42 -0700 (Wed, 05 Apr 2006) ViewCVS: http://svn.sourceforge.net/epydoc/?rev=1169&view=rev Log Message: ----------- - Added new graph type: callgraph. - Added -pstat option, which reads profiling information from pstat file(s) and stores it in the DocIndex - Renamed --profile to --profile-epydoc, and disabled it when epydoc.DEBUG=false (to avoid confusion with -pstat) - In cli._profile, explicilty specify the globals to use. - dotgraph.add_valdoc_nodes now adds () to the labels of routines - Fixed bug in HTMLWriter.pysrc_url Modified Paths: -------------- trunk/epydoc/src/epydoc/cli.py trunk/epydoc/src/epydoc/docwriter/dotgraph.py trunk/epydoc/src/epydoc/docwriter/html.py trunk/epydoc/src/epydoc/docwriter/html_css.py trunk/epydoc/src/epydoc/markup/epytext.py Modified: trunk/epydoc/src/epydoc/cli.py =================================================================== --- trunk/epydoc/src/epydoc/cli.py 2006-04-05 16:52:56 UTC (rev 1168) +++ trunk/epydoc/src/epydoc/cli.py 2006-04-05 16:58:42 UTC (rev 1169) @@ -59,7 +59,7 @@ """ __docformat__ = 'epytext en' -import sys, os, time, re +import sys, os, time, re, pstats from glob import glob from optparse import OptionParser, OptionGroup import epydoc @@ -69,7 +69,7 @@ import ConfigParser INHERITANCE_STYLES = ('grouped', 'listed', 'included') -GRAPH_TYPES = ('classtree',) +GRAPH_TYPES = ('classtree', 'callgraph') ###################################################################### #{ Argument & Config File Parsing @@ -175,10 +175,12 @@ options_group.add_option( # --introspect-only "--introspect-only", action="store_false", dest="parse", help="Get all information from introspecting (don't parse)") + if epydoc.DEBUG: + # this option is for developers, not users. + options_group.add_option( + "--profile-epydoc", action="store_true", dest="profile", + help="Run the profiler. Output will be written to profile.out") options_group.add_option( - "--profile", action="store_true", dest="profile", - help="Run the profiler. Output will be written to profile.out") - options_group.add_option( "--dotpath", dest="dotpath", metavar='PATH', help="The path to the Graphviz 'dot' executable.") options_group.add_option( @@ -207,6 +209,9 @@ '--no-sourcecode', action='store_false', dest='include_source_code', help=("Do not include source code with syntax highlighting in the " "HTML output.")) + options_group.add_option( + '--pstat', action='append', dest='pstat_files', metavar='FILE', + help="A pstat output file, to be used in generating call graphs.") # Add the option groups. optparser.add_option_group(action_group) @@ -221,7 +226,7 @@ parse=True, introspect=True, debug=epydoc.DEBUG, profile=False, graphs=[], list_classes_separately=False, - include_source_code=True) + include_source_code=True, pstat_files=[]) # Parse the arguments. options, names = optparser.parse_args() @@ -407,6 +412,19 @@ if docindex is None: return # docbuilder already logged an error. + # Load profile information, if it was given. + if options.pstat_files: + try: + profile_stats = pstats.Stats(options.pstat_files[0]) + for filename in options.pstat_files[1:]: + profile_stats.add(filename) + except KeyboardInterrupt: raise + except Exception, e: + log.error("Error reading pstat file: %s" % e) + profile_stats = None + if profile_stats is not None: + docindex.read_profiling_info(profile_stats) + # Perform the specified action. if options.action == 'html': write_html(docindex, options) @@ -559,8 +577,13 @@ print >>sys.stderr, 'Use --debug to see trace information.' def _profile(): - import profile, pstats, code - profile.run('main(*parse_arguments())', 'profile.out') + import profile, pstats + try: + prof = profile.Profile() + prof = prof.runctx('main(*parse_arguments())', globals(), {}) + except SystemExit: + pass + prof.dump_stats('profile.out') # Use the pstats statistical browser. This is made unnecessarily # difficult because the whole browser is wrapped in an Modified: trunk/epydoc/src/epydoc/docwriter/dotgraph.py =================================================================== --- trunk/epydoc/src/epydoc/docwriter/dotgraph.py 2006-04-05 16:52:56 UTC (rev 1168) +++ trunk/epydoc/src/epydoc/docwriter/dotgraph.py 2006-04-05 16:58:42 UTC (rev 1169) @@ -382,15 +382,88 @@ return graph +def call_graph(api_docs, docindex, linker, context=None, **options): + """ + @param options: + - C{dir}: rankdir for the graph. (default=LR) + - C{add_callers}: also include callers for any of the + routines in C{api_docs}. (default=False) + - C{add_callees}: also include callees for any of the + routines in C{api_docs}. (default=False) + @todo: Add an C{exclude} option? + """ + if docindex.callers is None: + log.warning("No profiling information for call graph!") + return DotGraph('Call Graph') # return None instead? + + if isinstance(context, VariableDoc): + context = context.value + + # Get the set of requested functions. + functions = [] + for api_doc in api_docs: + # If it's a variable, get its value. + if isinstance(api_doc, VariableDoc): + api_doc = api_doc.value + # Add the value to the functions list. + if isinstance(api_doc, RoutineDoc): + functions.append(api_doc) + elif isinstance(api_doc, NamespaceDoc): + for vardoc in api_doc.variables.values(): + if isinstance(vardoc.value, RoutineDoc): + functions.append(vardoc.value) + + # Filter out functions with no callers/callees? + # [xx] this isnt' quite right, esp if add_callers or add_callees + # options are fales. + functions = [f for f in functions if + (f in docindex.callers) or (f in docindex.callees)] + + # Add any callers/callees of the selected functions + func_set = set(functions) + if options.get('add_callers', False) or options.get('add_callees', False): + for func_doc in functions: + if options.get('add_callers', False): + func_set.update(docindex.callers.get(func_doc, ())) + if options.get('add_callees', False): + func_set.update(docindex.callees.get(func_doc, ())) + + graph = DotGraph('Call Graph for %s' % name_list(api_docs, context), + node_defaults={'shape':'box', 'width': 0, 'height': 0}, + edge_defaults={'sametail':True}) + + # Options + if options.get('dir', 'LR') != 'TB': # default: left-to-right + graph.body += 'rankdir=%s\n' % options.get('dir', 'LR') + + nodes = add_valdoc_nodes(graph, func_set, linker, context) + + # Find the edges. + edges = set() + for func_doc in functions: + for caller in docindex.callers.get(func_doc, ()): + if caller in nodes: + edges.add( (nodes[caller], nodes[func_doc]) ) + for callee in docindex.callees.get(func_doc, ()): + if callee in nodes: + edges.add( (nodes[func_doc], nodes[callee]) ) + graph.edges = [DotGraphEdge(src,dst) for (src,dst) in edges] + + return graph + ###################################################################### #{ Helper Functions ###################################################################### def add_valdoc_nodes(graph, val_docs, linker, context): + """ + @todo: Use different node styles for different subclasses of APIDoc + """ nodes = {} val_docs = sorted(val_docs, key=lambda d:d.canonical_name) for i, val_doc in enumerate(val_docs): label = val_doc.canonical_name.contextualize(context.canonical_name) + if isinstance(val_doc, RoutineDoc): label = '%s()' % label node = nodes[val_doc] = DotGraphNode(label) graph.nodes.append(node) if val_doc == context: Modified: trunk/epydoc/src/epydoc/docwriter/html.py =================================================================== --- trunk/epydoc/src/epydoc/docwriter/html.py 2006-04-05 16:52:56 UTC (rev 1168) +++ trunk/epydoc/src/epydoc/docwriter/html.py 2006-04-05 16:58:42 UTC (rev 1169) @@ -697,7 +697,7 @@ if 'classtree' in self._graph_types: linker = _HTMLDocstringLinker(self, doc) graph = class_tree_graph([doc], linker, doc) - self.write_graph(out, graph) + out('<center>\n%s</center>\n' % self.render_graph(graph)) # Otherwise, use ascii-art. else: @@ -1214,6 +1214,7 @@ print >> jsfile, self.GET_COOKIE_JS print >> jsfile, self.SET_FRAME_JS print >> jsfile, self.HIDE_PRIVATE_JS + print >> jsfile, self.TOGGLE_CALLGRAPH_JS jsfile.close() #: A javascript that is used to show or hide the API documentation @@ -1308,22 +1309,49 @@ } '''.strip() + TOGGLE_CALLGRAPH_JS = ''' + function toggleCallGraph(id) { + var elt = document.getElementById(id); + if (elt.style.display == "none") + elt.style.display = "block"; + else + elt.style.display = "none"; + } + '''.strip() + #//////////////////////////////////////////////////////////// #{ 2.10. Graphs #//////////////////////////////////////////////////////////// - def write_graph(self, out, graph): + def render_graph(self, graph, css='graph-without-title'): + if graph is None: return '' # Write the graph's image to a file path = os.path.join(self._directory, graph.uid) if not graph.write('%s.gif' % path, 'gif'): - return + return '' # Generate the image map. cmapx = graph.render('cmapx') or '' # Display the graph. - out('%s\n<center>\n<img src="%s.gif" alt="%s" usemap="#%s" ' - 'ismap="ismap" class="graph-without-title"/>\n</center>' % - (cmapx, graph.uid, graph.uid, graph.uid)) + uid = graph.uid + return ('%s\n<img src="%s.gif" alt="%s" usemap="#%s" ismap="ismap" ' + 'class="%s"/>\n' % (cmapx, uid, uid, uid, css)) + + def render_callgraph(self, callgraph): + graph_html = self.render_graph(callgraph, css='callgraph') + if graph_html == '': return '' + return ('<div style="display:none" id="%s-div"><center>\n' + '<table border="0" cellpadding="0" cellspacing="0">\n' + ' <tr><td>%s</td></tr>\n' + ' <tr><th>Call Graph</th></tr>\n' + '</table><br />\n</center></div>\n' % + (callgraph.uid, graph_html)) + def callgraph_link(self, callgraph): + if callgraph is None: return '' + return ('<br /><span class="codelink"><a href="#" ' + 'onclick="toggleCallGraph(\'%s-div\');return false;">' + 'call graph</a></span> ' % callgraph.uid) + #//////////////////////////////////////////////////////////// #{ 3.1. Page Header #//////////////////////////////////////////////////////////// @@ -1794,8 +1822,19 @@ for n in arg_names]) rhs = self.docstring_to_html(arg_descr, var_doc.value, 10) arg_descrs.append( (lhs, rhs) ) - self.write_function_details_entry(out, var_doc, descr, rtype, - rdescr, arg_descrs, div_class) + # Perpare the call-graph, if requested + if 'callgraph' in self._graph_types: + linker = _HTMLDocstringLinker(self, var_doc.value) + callgraph = call_graph([var_doc.value], self.docindex, + linker, var_doc, add_callers=True, + add_callees=True) + if callgraph is not None and len(callgraph.nodes) == 0: + callgraph = None + else: + callgraph = None + self.write_function_details_entry(out, var_doc, descr, callgraph, + rtype, rdescr, arg_descrs, + div_class) # Properties elif isinstance(var_doc.value, PropertyDoc): @@ -1858,7 +1897,7 @@ write_function_details_entry = compile_template( ''' - write_function_details_entry(self, out, var_doc, descr, \ + write_function_details_entry(self, out, var_doc, descr, callgraph, \ rtype, rdescr, arg_descrs, div_class) ''', # /------------------------- Template -------------------------\ @@ -1875,8 +1914,10 @@ >>> #endif </h3> </td><td align="right" valign="top" - >$self.pysrc_link(func_doc)$ </span></td> + >$self.pysrc_link(func_doc)$ </span + >$self.callgraph_link(callgraph)$</td> </table> + $self.render_callgraph(callgraph)$ $descr$ <dl><dt></dt><dd> >>> # === parameters === @@ -2758,7 +2799,7 @@ else: return None else: - module = self.docindex.defining_module + module = api_doc.defining_module if module == UNKNOWN: return None module_pysrc_url = self.pysrc_url(module) if module_pysrc_url is None: return None Modified: trunk/epydoc/src/epydoc/docwriter/html_css.py =================================================================== --- trunk/epydoc/src/epydoc/docwriter/html_css.py 2006-04-05 16:52:56 UTC (rev 1168) +++ trunk/epydoc/src/epydoc/docwriter/html_css.py 2006-04-05 16:58:42 UTC (rev 1169) @@ -139,6 +139,7 @@ .graph-without-title { border: none; } .graph-with-title { border: 1px solid black; } .graph-title { font-weight: bold; } +.callgraph { border: 1px solid black; } /* Lists */ ul { margin-top: 0; } Modified: trunk/epydoc/src/epydoc/markup/epytext.py =================================================================== --- trunk/epydoc/src/epydoc/markup/epytext.py 2006-04-05 16:52:56 UTC (rev 1168) +++ trunk/epydoc/src/epydoc/markup/epytext.py 2006-04-05 16:58:42 UTC (rev 1169) @@ -1067,7 +1067,7 @@ return stack[0] -GRAPH_TYPES = ['classtree', 'packagetree', 'importgraph'] +GRAPH_TYPES = ['classtree', 'packagetree', 'importgraph', 'callgraph'] def _colorize_graph(doc, graph, token, end, errors): """ @@ -1082,7 +1082,6 @@ for child in children: graph.removeChild(child) if len(children) != 1 or not isinstance(children[0], Text): - print len(children), children[0] bad_graph_spec = "Bad graph specification" else: pieces = children[0].data.split(None, 1) @@ -1872,6 +1871,11 @@ elif graph_type == 'importgraph': modules = [d for d in docindex.root if isinstance(d, ModuleDoc)] return import_graph(modules, docindex, linker, context) + + elif graph_type == 'callgraph': + docs = [docindex.find(name, context) for name in graph_args] + docs = [doc for doc in docs if doc is not None] + return call_graph(docs, docindex, linker, context) else: log.warning("Unknown graph type %s" % graph_type) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |