Thread: [Epydoc-commits] SF.net SVN: epydoc: [1154] trunk/epydoc/src/epydoc
Brought to you by:
edloper
From: <ed...@us...> - 2006-04-04 01:50:04
|
Revision: 1154 Author: edloper Date: 2006-04-03 18:49:57 -0700 (Mon, 03 Apr 2006) ViewCVS: http://svn.sourceforge.net/epydoc/?rev=1154&view=rev Log Message: ----------- - Renamed DottedName.Invalid -> DottedName.InvalidDottedName Modified Paths: -------------- trunk/epydoc/src/epydoc/apidoc.py trunk/epydoc/src/epydoc/docbuilder.py trunk/epydoc/src/epydoc/docintrospecter.py trunk/epydoc/src/epydoc/docstringparser.py Modified: trunk/epydoc/src/epydoc/apidoc.py =================================================================== --- trunk/epydoc/src/epydoc/apidoc.py 2006-04-04 01:35:13 UTC (rev 1153) +++ trunk/epydoc/src/epydoc/apidoc.py 2006-04-04 01:49:57 UTC (rev 1154) @@ -74,7 +74,7 @@ $""" % re.escape(UNREACHABLE)) - class Invalid(ValueError): + class InvalidDottedName(ValueError): """ An exception raised by the DottedName constructor when one of its arguments is not a valid dotted name. @@ -92,7 +92,7 @@ it is a valid identifier. """ if len(pieces) == 0: - raise DottedName.Invalid('Empty DottedName') + raise DottedName.InvalidDottedName('Empty DottedName') self._identifiers = [] for piece in pieces: if isinstance(piece, DottedName): @@ -101,11 +101,13 @@ for subpiece in piece.split('.'): if not self._IDENTIFIER_RE.match(subpiece): log.debug('Bad identifier %r' % (piece,)) - raise DottedName.Invalid('Bad identifier %r' % (piece,)) + raise DottedName.InvalidDottedName( + 'Bad identifier %r' % (piece,)) self._identifiers.append(subpiece) else: log.debug('Bad identifier %r' % (piece,)) - raise DottedName.Invalid('Bad identifier %r' % (piece,)) + raise DottedName.InvalidDottedName( + 'Bad identifier %r' % (piece,)) self._identifiers = tuple(self._identifiers) def __repr__(self): Modified: trunk/epydoc/src/epydoc/docbuilder.py =================================================================== --- trunk/epydoc/src/epydoc/docbuilder.py 2006-04-04 01:35:13 UTC (rev 1153) +++ trunk/epydoc/src/epydoc/docbuilder.py 2006-04-04 01:49:57 UTC (rev 1154) @@ -1058,7 +1058,7 @@ try: name = DottedName(DottedName.UNREACHABLE, val_doc.pyval.__name__) - except DottedName.Invalid: + except DottedName.InvalidDottedName: name = DottedName(DottedName.UNREACHABLE) else: name = DottedName(DottedName.UNREACHABLE) Modified: trunk/epydoc/src/epydoc/docintrospecter.py =================================================================== --- trunk/epydoc/src/epydoc/docintrospecter.py 2006-04-04 01:35:13 UTC (rev 1153) +++ trunk/epydoc/src/epydoc/docintrospecter.py 2006-04-04 01:49:57 UTC (rev 1154) @@ -180,7 +180,7 @@ val_doc = _valuedoc_cache.get(pyid) if val_doc is None: try: canonical_name = get_canonical_name(value) - except DottedName.Invalid: canonical_name = UNKNOWN + except DottedName.InvalidDottedName: canonical_name = UNKNOWN val_doc = ValueDoc(pyval=value, canonical_name = canonical_name, docs_extracted_by='introspecter') _valuedoc_cache[pyid] = val_doc Modified: trunk/epydoc/src/epydoc/docstringparser.py =================================================================== --- trunk/epydoc/src/epydoc/docstringparser.py 2006-04-04 01:35:13 UTC (rev 1153) +++ trunk/epydoc/src/epydoc/docstringparser.py 2006-04-04 01:49:57 UTC (rev 1154) @@ -465,7 +465,7 @@ C{tag} in C{api_doc.exception_descrs}.""" _check(api_doc, tag, arg, context=RoutineDoc, expect_arg='single') try: name = DottedName(arg) - except DottedName.Invalid: name = arg + except DottedName.InvalidDottedName: name = arg api_doc.exception_descrs.append( (name, descr) ) def process_sort_field(api_doc, docindex, tag, arg, descr): This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ed...@us...> - 2006-04-04 15:19:32
|
Revision: 1158 Author: edloper Date: 2006-04-04 08:19:28 -0700 (Tue, 04 Apr 2006) ViewCVS: http://svn.sourceforge.net/epydoc/?rev=1158&view=rev Log Message: ----------- - Fixed exception handling bug in cli - Changed several 'except' clauses to only propagate KeyboardInterrupt, not SystemExit. Modified Paths: -------------- trunk/epydoc/src/epydoc/apidoc.py trunk/epydoc/src/epydoc/cli.py trunk/epydoc/src/epydoc/docintrospecter.py Modified: trunk/epydoc/src/epydoc/apidoc.py =================================================================== --- trunk/epydoc/src/epydoc/apidoc.py 2006-04-04 04:45:28 UTC (rev 1157) +++ trunk/epydoc/src/epydoc/apidoc.py 2006-04-04 15:19:28 UTC (rev 1158) @@ -690,7 +690,7 @@ if isinstance(s, str): s = decode_with_backslashreplace(s) return s - except KeyboardInterrupt, SystemExit: raise + except KeyboardInterrupt: raise except: return UNKNOWN def apidoc_links(self, **filters): Modified: trunk/epydoc/src/epydoc/cli.py =================================================================== --- trunk/epydoc/src/epydoc/cli.py 2006-04-04 04:45:28 UTC (rev 1157) +++ trunk/epydoc/src/epydoc/cli.py 2006-04-04 15:19:28 UTC (rev 1158) @@ -213,7 +213,7 @@ if options.configfiles: try: parse_configfiles(options.configfiles, options, names) - except KeyboardInterrupt,SystemExit: raise + except (KeyboardInterrupt,SystemExit): raise except Exception, e: optparser.error('Error reading config file:\n %s' % e) Modified: trunk/epydoc/src/epydoc/docintrospecter.py =================================================================== --- trunk/epydoc/src/epydoc/docintrospecter.py 2006-04-04 04:45:28 UTC (rev 1157) +++ trunk/epydoc/src/epydoc/docintrospecter.py 2006-04-04 15:19:28 UTC (rev 1158) @@ -223,7 +223,7 @@ # Record the module's filename if hasattr(module, '__file__'): try: module_doc.filename = unicode(module.__file__) - except KeyboardInterrupt, SystemExit: raise + except KeyboardInterrupt: raise except: pass # If this is just a preliminary introspection, then don't do @@ -241,7 +241,7 @@ if hasattr(module, '__path__'): module_doc.is_package = True try: module_doc.path = [unicode(p) for p in module.__path__] - except KeyboardInterrupt, SystemExit: raise + except KeyboardInterrupt: raise except: pass else: module_doc.is_package = False @@ -313,7 +313,7 @@ var_doc.is_imported = False else: var_doc.is_public = False - except KeyboardInterrupt, SystemExit: raise + except KeyboardInterrupt: raise except: pass return module_doc @@ -342,7 +342,7 @@ try: public_names = [str(name) for name in cls.__all__] class_doc.public_names = public_names - except KeyboardInterrupt, SystemExit: raise + except KeyboardInterrupt: raise except: pass # Start a list of subclasses. @@ -635,7 +635,7 @@ try: module = inspect.getmodule(func) if module: return module.__name__ - except KeyboardInterrupt, SystemExit: raise + except KeyboardInterrupt: raise except: pass # This fallback shouldn't usually be needed. But it is needed in @@ -823,8 +823,7 @@ else: # For importing scripts: return imp.load_source(name, filename) - except KeyboardInterrupt: - raise # don't capture keyboard interrupts! + except KeyboardInterrupt: raise except: exc_typ, exc_val, exc_tb = sys.exc_info() if exc_val is None: This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ed...@us...> - 2006-04-05 01:58:45
|
Revision: 1162 Author: edloper Date: 2006-04-04 18:58:40 -0700 (Tue, 04 Apr 2006) ViewCVS: http://svn.sourceforge.net/epydoc/?rev=1162&view=rev Log Message: ----------- - Added VariableDoc.defining_module property (based on its container) - Removed DocIndex.module_that_defines; use the defining_module property instead. Modified Paths: -------------- trunk/epydoc/src/epydoc/apidoc.py trunk/epydoc/src/epydoc/docstringparser.py trunk/epydoc/src/epydoc/docwriter/html.py Modified: trunk/epydoc/src/epydoc/apidoc.py =================================================================== --- trunk/epydoc/src/epydoc/apidoc.py 2006-04-05 01:51:56 UTC (rev 1161) +++ trunk/epydoc/src/epydoc/apidoc.py 2006-04-05 01:58:40 UTC (rev 1162) @@ -612,6 +612,15 @@ container's cannonical name, and adding the variable's name to it.""") + def _get_defining_module(self): + if self.container is UNKNOWN: + return UNKNOWN + return self.container.defining_module + defining_module = property(_get_defining_module, doc=""" + A read-only property that can be used to get the variable's + defining module. This is defined as the defining module + of the variable's container.""") + def apidoc_links(self, **filters): if self.value in (None, UNKNOWN): return [] @@ -1602,23 +1611,6 @@ parent = api_doc.canonical_name.container() return self.get_valdoc(parent) - # [xx] this could be moved out of docindex and/or removed. - def module_that_defines(self, api_doc): - """ - Return the C{ModuleDoc} of the module that defines C{api_doc}, - or C{None} if that module is not in the index. If C{api_doc} - is itself a module, then C{api_doc} will be returned. - """ - if isinstance(api_doc, VariableDoc): - api_doc = api_doc.container - if api_doc.defining_module == UNKNOWN: return None - if not isinstance(api_doc.defining_module, ModuleDoc): - log.error("Internal error -- %r claims that its defining " - "module is %r, but the latter is not a module." % - (api_doc, api_doc.defining_module)) - return None - return api_doc.defining_module - ###################################################################### ## Pretty Printing ###################################################################### Modified: trunk/epydoc/src/epydoc/docstringparser.py =================================================================== --- trunk/epydoc/src/epydoc/docstringparser.py 2006-04-05 01:51:56 UTC (rev 1161) +++ trunk/epydoc/src/epydoc/docstringparser.py 2006-04-05 01:58:40 UTC (rev 1162) @@ -250,7 +250,7 @@ # Get the name of the item containing the error, and the # filename of its containing module. name = api_doc.canonical_name - module = docindex.module_that_defines(api_doc) + module = api_doc.defining_module if module is not None and module.filename not in (None, UNKNOWN): try: filename = py_src_filename(module.filename) except: filename = module.filename @@ -627,7 +627,7 @@ parse the API documentation for the given object. """ # Find the module that defines api_doc. - module = docindex.module_that_defines(api_doc) + module = api_doc.defining_module # Look up its docformat. if module is not None and module.docformat not in (None, UNKNOWN): docformat = module.docformat Modified: trunk/epydoc/src/epydoc/docwriter/html.py =================================================================== --- trunk/epydoc/src/epydoc/docwriter/html.py 2006-04-05 01:51:56 UTC (rev 1161) +++ trunk/epydoc/src/epydoc/docwriter/html.py 2006-04-05 01:58:40 UTC (rev 1162) @@ -2758,8 +2758,8 @@ else: return None else: - module = self.docindex.module_that_defines(api_doc) - if module is None: return None + module = self.docindex.defining_module + if module == UNKNOWN: return None module_pysrc_url = self.pysrc_url(module) if module_pysrc_url is None: return None module_name = module.canonical_name This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ed...@us...> - 2006-04-05 16:53:00
|
Revision: 1168 Author: edloper Date: 2006-04-05 09:52:56 -0700 (Wed, 05 Apr 2006) ViewCVS: http://svn.sourceforge.net/epydoc/?rev=1168&view=rev Log Message: ----------- - Added RoutineDoc.lineno Modified Paths: -------------- trunk/epydoc/src/epydoc/docintrospecter.py trunk/epydoc/src/epydoc/docparser.py Modified: trunk/epydoc/src/epydoc/docintrospecter.py =================================================================== --- trunk/epydoc/src/epydoc/docintrospecter.py 2006-04-05 16:52:41 UTC (rev 1167) +++ trunk/epydoc/src/epydoc/docintrospecter.py 2006-04-05 16:52:56 UTC (rev 1168) @@ -443,6 +443,10 @@ routine_doc.posargs = routine_doc.posargs[1:] routine_doc.posarg_defaults = routine_doc.posarg_defaults[1:] + # Set the routine's line number. + if hasattr(func, 'func_code'): + routine_doc.lineno = func.func_code.co_firstlineno + else: # [XX] I should probably use UNKNOWN here?? routine_doc.posargs = ['...'] Modified: trunk/epydoc/src/epydoc/docparser.py =================================================================== --- trunk/epydoc/src/epydoc/docparser.py 2006-04-05 16:52:41 UTC (rev 1167) +++ trunk/epydoc/src/epydoc/docparser.py 2006-04-05 16:52:56 UTC (rev 1168) @@ -1360,7 +1360,7 @@ # Create the function's RoutineDoc. func_doc = RoutineDoc(canonical_name=canonical_name, defining_module=parent_docs[0], - docs_extracted_by='parser') + lineno=lineno, docs_extracted_by='parser') # Process the signature. init_arglist(func_doc, line[2]) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
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. |
From: <ed...@us...> - 2006-04-09 18:16:00
|
Revision: 1196 Author: edloper Date: 2006-04-09 11:15:55 -0700 (Sun, 09 Apr 2006) ViewCVS: http://svn.sourceforge.net/epydoc/?rev=1196&view=rev Log Message: ----------- - Added support for drawing uml class graphs (many thanks to Daniele Varrazzo for the initial code!) - Changed default node/edge defaults to use 10pt helvetica Modified Paths: -------------- trunk/epydoc/src/epydoc/cli.py trunk/epydoc/src/epydoc/docwriter/dotgraph.py Modified: trunk/epydoc/src/epydoc/cli.py =================================================================== --- trunk/epydoc/src/epydoc/cli.py 2006-04-09 18:12:40 UTC (rev 1195) +++ trunk/epydoc/src/epydoc/cli.py 2006-04-09 18:15:55 UTC (rev 1196) @@ -69,7 +69,7 @@ import ConfigParser INHERITANCE_STYLES = ('grouped', 'listed', 'included') -GRAPH_TYPES = ('classtree', 'callgraph') +GRAPH_TYPES = ('classtree', 'callgraph', 'umlclasstree') ACTIONS = ('html', 'text', 'latex', 'dvi', 'ps', 'pdf', 'check') DEFAULT_DOCFORMAT = 'epytext' Modified: trunk/epydoc/src/epydoc/docwriter/dotgraph.py =================================================================== --- trunk/epydoc/src/epydoc/docwriter/dotgraph.py 2006-04-09 18:12:40 UTC (rev 1195) +++ trunk/epydoc/src/epydoc/docwriter/dotgraph.py 2006-04-09 18:15:55 UTC (rev 1196) @@ -27,6 +27,15 @@ from epydoc.util import * from epydoc.compat import * # Backwards compatibility +# colors for graphs of APIDocs +MODULE_BG = '#d8e8ff' +CLASS_BG = '#d8ffe8' +SELECTED_BG = '#ffd0d0' +BASECLASS_BG = '#e0b0a0' +SUBCLASS_BG = '#e0b0a0' +ROUTINE_BG = '#e8d0b0' # maybe? +INH_LINK_COLOR = '#800000' + ###################################################################### #{ Dot Graphs ###################################################################### @@ -64,6 +73,9 @@ """A set of all uids that that have been generated, used to ensure that each new graph has a unique uid.""" + DEFAULT_NODE_DEFAULTS={'fontsize':10, 'fontname': 'helvetica'} + DEFAULT_EDGE_DEFAULTS={'fontsize':10, 'fontname': 'helvetica'} + def __init__(self, title, body='', node_defaults=None, edge_defaults=None, caption=None): """ @@ -78,12 +90,12 @@ self.nodes = [] """A list of the nodes that are present in the graph. - :type: `list` of `DocGraphNode`""" + :type: `list` of `DotGraphNode`""" self.edges = [] """A list of the edges that are present in the graph. - :type: `list` of `DocGraphEdge`""" + :type: `list` of `DotGraphEdge`""" self.body = body """A string that should be included as-is in the body of the @@ -91,10 +103,10 @@ :type: `str`""" - self.node_defaults = node_defaults or {} + self.node_defaults = node_defaults or self.DEFAULT_NODE_DEFAULTS """Default attribute values for nodes.""" - self.edge_defaults = edge_defaults or {} + self.edge_defaults = edge_defaults or self.DEFAULT_EDGE_DEFAULTS """Default attribute values for edges.""" self.uid = re.sub(r'\W', '_', title).lower() @@ -277,7 +289,8 @@ """ Return the dot commands that should be used to render this node. """ - attribs = ['%s="%s"' % (k,v) for (k,v) in self._attribs.items()] + attribs = ['%s="%s"' % (k,v) for (k,v) in self._attribs.items() + if v is not None] if self._html_label: attribs.insert(0, 'label=<%s>' % (self._html_label,)) if attribs: attribs = ' [%s]' % (','.join(attribs)) @@ -289,6 +302,8 @@ :type start: `DotGraphNode` :type end: `DotGraphNode` """ + assert isinstance(start, DotGraphNode) + assert isinstance(end, DotGraphNode) if label is not None: attribs['label'] = label self.start = start #: :type: `DotGraphNode` self.end = end #: :type: `DotGraphNode` @@ -311,12 +326,559 @@ if (self.end.port is not None and 'tailport' not in attribs): attribs['tailport'] = self.end.port # Convert attribs to a string - attribs = ','.join(['%s="%s"' % (k,v) for (k,v) in attribs.items()]) + attribs = ','.join(['%s="%s"' % (k,v) for (k,v) in attribs.items() + if v is not None]) if attribs: attribs = ' [%s]' % attribs # Return the dotfile edge. return 'node%d -> node%d%s' % (self.start.id, self.end.id, attribs) ###################################################################### +#{ Specialized Nodes for UML Graphs +###################################################################### + +class DotGraphUmlClassNode(DotGraphNode): + """ + A specialized dot graph node used to display C{ClassDoc}s using + UML notation. The node is rendered as a table with three cells: + the top cell contains the class name; the middle cell contains a + list of attributes; and the bottom cell contains a list of + operations:: + + +-------------+ + | ClassName | + +-------------+ + | x: int | + | ... | + +-------------+ + | f(self, x) | + | ... | + +-------------+ + + `DotGraphUmlClassNode`s may be *collapsed*, in which case they are + drawn as a simple box containing the class name:: + + +-------------+ + | ClassName | + +-------------+ + + Attributes with types corresponding to documented classes can + optionally be converted into edges, using `link_attributes()`. + + :todo: Add more options? + - show/hide operation signature + - show/hide operation signature types + - show/hide operation signature return type + - show/hide attribute types + - use qualifiers + """ + + def __init__(self, class_doc, linker, context, collapsed=False, + bgcolor=CLASS_BG, **options): + """ + Create a new `DotGraphUmlClassNode` based on the class + `class_doc`. + + :Parameters: + linker: `DocstringLinker<markup.DocstringLinker>` + Used to look up URLs for classes. + context: `APIDoc` + The context in which this node will be drawn; dotted + names will be contextualized to this context. + collapsed: ``bool`` + If true, then display this node as a simple box. + bgcolor: ``str`` + The background color for this node. + options: ``dict`` + A set of options used to control how the node should + be displayed. + + :Keywords: + - `show_private_vars`: If false, then private variables + are filtered out of the attributes & operations lists. + (Default: *False*) + - `show_magic_vars`: If false, then magic variables + (such as ``__init__`` and ``__add__``) are filtered out of + the attributes & operations lists. (Default: *True*) + - `show_inherited_vars`: If false, then inherited variables + are filtered out of the attributes & operations lists. + (Default: *False*) + - `max_attributes`: The maximum number of attributes that + should be listed in the attribute box. If the class has + more than this number of attributes, some will be + ellided. Ellipsis is marked with ``'...'``. + - `max_operations`: The maximum number of operations that + should be listed in the operation box. + - `add_nodes_for_linked_attributes`: If true, then + `link_attributes()` will create new a collapsed node for + the types of a linked attributes if no node yet exists for + that type. + """ + self.class_doc = class_doc + """The class represented by this node.""" + + self.linker = linker + """Used to look up URLs for classes.""" + + self.context = context + """The context in which the node will be drawn.""" + + self.bgcolor = bgcolor + """The background color of the node.""" + + self.options = options + """Options used to control how the node is displayed.""" + + self.collapsed = collapsed + """If true, then draw this node as a simple box.""" + + self.attributes = [] + """The list of VariableDocs for attributes""" + + self.operations = [] + """The list of VariableDocs for operations""" + + self.qualifiers = [] + """List of (key_label, port) tuples.""" + + self.edges = [] + """List of edges used to represent this node's attributes. + These should not be added to the `DotGraph`; this node will + generate their dotfile code directly.""" + + # Initialize operations & attributes lists. + show_private = options.get('show_private_vars', False) + show_magic = options.get('show_magic_vars', True) + show_inherited = options.get('show_inherited_vars', False) + for name, var in class_doc.variables.iteritems(): + if ((not show_private and var.is_public == False) or + (not show_magic and re.match('__\w+__$', name)) or + (not show_inherited and var.container != class_doc)): + pass + elif isinstance(var.value, RoutineDoc): + self.operations.append(var) + else: + self.attributes.append(var) + + # Initialize our dot node settings. + DotGraphNode.__init__(self, tooltip=class_doc.canonical_name, + width=0, height=0, shape='plaintext', + href=linker.url_for(class_doc) or NOOP_URL) + + #///////////////////////////////////////////////////////////////// + #{ Attribute Linking + #///////////////////////////////////////////////////////////////// + + SIMPLE_TYPE_RE = re.compile( + r'^([\w\.]+)$') + """A regular expression that matches descriptions of simple types.""" + + COLLECTION_TYPE_RE = re.compile( + r'^(list|set|sequence|tuple|collection) of ([\w\.]+)$') + """A regular expression that matches descriptions of collection types.""" + + MAPPING_TYPE_RE = re.compile( + r'^(dict|dictionary|map|mapping) from ([\w\.]+) to ([\w\.]+)$') + """A regular expression that matches descriptions of mapping types.""" + + MAPPING_TO_COLLECTION_TYPE_RE = re.compile( + r'^(dict|dictionary|map|mapping) from ([\w\.]+) to ' + r'(list|set|sequence|tuple|collection) of ([\w\.]+)$') + """A regular expression that matches descriptions of mapping types + whose value type is a collection.""" + + OPTIONAL_TYPE_RE = re.compile( + r'^(None or|optional) ([\w\.]+)$|^([\w\.]+) or None$') + """A regular expression that matches descriptions of optional types.""" + + def link_attributes(self, nodes): + """ + Convert any attributes with type descriptions corresponding to + documented classes to edges. The following type descriptions + are currently handled: + + - Dotted names: Create an attribute edge to the named type, + labelled with the variable name. + - Collections: Create an attribute edge to the named type, + labelled with the variable name, and marked with '*' at the + type end of the edge. + - Mappings: Create an attribute edge to the named type, + labelled with the variable name, connected to the class by + a qualifier box that contains the key type description. + - Optional: Create an attribute edge to the named type, + labelled with the variable name, and marked with '0..1' at + the type end of the edge. + + The edges created by `link_attribute()` are handled internally + by `DotGraphUmlClassNode`; they should *not* be added directly + to the `DotGraph`. + + :param nodes: A dictionary mapping from `ClassDoc`s to + `DotGraphUmlClassNode`s, used to look up the nodes for + attribute types. If the ``add_nodes_for_linked_attributes`` + option is used, then new nodes will be added to this + dictionary for any types that are not already listed. + These added nodes must be added to the `DotGraph`. + """ + # Try to convert each attribute var into a graph edge. If + # _link_attribute returns true, then it succeeded, so remove + # that var from our attribute list; otherwise, leave that var + # in our attribute list. + self.attributes = [var for var in self.attributes + if not self._link_attribute(var, nodes)] + + def _link_attribute(self, var, nodes): + """ + Helper for `link_attributes()`: try to convert the attribute + variable `var` into an edge, and add that edge to + `self.edges`. Return ``True`` iff the variable was + successfully converted to an edge (in which case, it should be + removed from the attributes list). + """ + type_descr = self._type_descr(var) or self._type_descr(var.value) + + # Simple type. + m = self.SIMPLE_TYPE_RE.match(type_descr) + if m and self._add_attribute_edge(var, nodes, m.group(1)): + return True + + # Collection type. + m = self.COLLECTION_TYPE_RE.match(type_descr) + if m and self._add_attribute_edge(var, nodes, m.group(2), + headlabel='*'): + return True + + # Optional type. + m = self.OPTIONAL_TYPE_RE.match(type_descr) + if m and self._add_attribute_edge(var, nodes, m.group(2) or m.group(3), + headlabel='0..1'): + return True + + # Mapping type. + m = self.MAPPING_TYPE_RE.match(type_descr) + if m: + port = 'qualifier_%s' % var.name + if self._add_attribute_edge(var, nodes, m.group(3), + tailport='%s:e' % port): + self.qualifiers.append( (m.group(2), port) ) + return True + + # Mapping to collection type. + m = self.MAPPING_TO_COLLECTION_TYPE_RE.match(type_descr) + if m: + port = 'qualifier_%s' % var.name + if self._add_attribute_edge(var, nodes, m.group(4), headlabel='*', + tailport='%s:e' % port): + self.qualifiers.append( (m.group(2), port) ) + return True + + # We were unable to link this attribute. + return False + + def _add_attribute_edge(self, var, nodes, type_str, **attribs): + """ + Helper for `link_attribute()`: try to add an edge for the + given attribute variable `var`. Return ``True`` if + successful. + """ + # Use the type string to look up a corresponding ValueDoc. + type_doc = self.linker.docindex.find(type_str, var) + if not type_doc: return False + + # Get the type ValueDoc's node. If it doesn't have one (and + # add_nodes_for_linked_attributes=True), then create it. + type_node = nodes.get(type_doc) + if not type_node: + if self.options.get('add_nodes_for_linked_attributes', True): + type_node = DotGraphUmlClassNode(type_doc, self.linker, + self.context, collapsed=True) + nodes[type_doc] = type_node + else: + return False + + # Add an edge from self to the target type node. + # [xx] should I set constraint=false here? + attribs.setdefault('headport', 'body') + attribs.setdefault('tailport', 'body') + url = self.linker.url_for(var) or NOOP_URL + self.edges.append(DotGraphEdge(self, type_node, label=var.name, + arrowhead='open', href=url, + tooltip=var.canonical_name, labeldistance=1.5, + **attribs)) + return True + + #///////////////////////////////////////////////////////////////// + #{ Helper Methods + #///////////////////////////////////////////////////////////////// + def _summary(self, api_doc): + """Return a plaintext summary for `api_doc`""" + if not isinstance(api_doc, APIDoc): return '' + if api_doc.summary in (None, UNKNOWN): return '' + summary = api_doc.summary.to_plaintext(self.linker).strip() + return plaintext_to_html(summary) + + def _type_descr(self, api_doc): + """Return a plaintext type description for `api_doc`""" + if not hasattr(api_doc, 'type_descr'): return '' + if api_doc.type_descr in (None, UNKNOWN): return '' + type_descr = api_doc.type_descr.to_plaintext(self.linker).strip() + return plaintext_to_html(type_descr) + + def _tooltip(self, var_doc): + """Return a tooltip for `var_doc`.""" + return (self._summary(var_doc) or + self._summary(var_doc.value) or + var_doc.canonical_name) + + #///////////////////////////////////////////////////////////////// + #{ Rendering + #///////////////////////////////////////////////////////////////// + + def _attribute_cell(self, var_doc): + # Construct the label + label = var_doc.name + type_descr = (self._type_descr(var_doc) or + self._type_descr(var_doc.value)) + if type_descr: label += ': %s' % type_descr + # Get the URL + url = self.linker.url_for(var_doc) or NOOP_URL + # Construct & return the pseudo-html code + return self._ATTRIBUTE_CELL % (url, self._tooltip(var_doc), label) + + def _operation_cell(self, var_doc): + """ + :todo: do 'word wrapping' on the signature, by starting a new + row in the table, if necessary. How to indent the new + line? Maybe use align=right? I don't think dot has a + . + :todo: Optionally add return type info? + """ + # Construct the label (aka function signature) + func_doc = var_doc.value + args = [self._operation_arg(n, d, func_doc) for (n, d) + in zip(func_doc.posargs, func_doc.posarg_defaults)] + args = [plaintext_to_html(arg) for arg in args] + if func_doc.vararg: args.append('*'+func_doc.vararg) + if func_doc.kwarg: args.append('**'+func_doc.kwarg) + label = '%s(%s)' % (var_doc.name, ', '.join(args)) + # Get the URL + url = self.linker.url_for(var_doc) or NOOP_URL + # Construct & return the pseudo-html code + return self._OPERATION_CELL % (url, self._tooltip(var_doc), label) + + def _operation_arg(self, name, default, func_doc): + """ + :todo: Handle tuple args better + :todo: Optionally add type info? + """ + if default is None: + return '%s' % name + elif default.parse_repr is not UNKNOWN: + return '%s=%s' % (name, default.parse_repr) + else: + pyval_repr = default.pyval_repr() + if pyval_repr is not UNKNOWN: + return '%s=%s' % (name, pyval_repr) + else: + return '%s=??' % name + + def _qualifier_cell(self, key_label, port): + return self._QUALIFIER_CELL % (port, self.bgcolor, key_label) + + #: args: (url, tooltip, label) + _ATTRIBUTE_CELL = ''' + <TR><TD ALIGN="LEFT" HREF="%s" TOOLTIP="%s">%s</TD></TR> + ''' + + #: args: (url, tooltip, label) + _OPERATION_CELL = ''' + <TR><TD ALIGN="LEFT" HREF="%s" TOOLTIP="%s">%s</TD></TR> + ''' + + #: args: (port, bgcolor, label) + _QUALIFIER_CELL = ''' + <TR><TD VALIGN="BOTTOM" PORT="%s" BGCOLOR="%s" BORDER="1">%s</TD></TR> + ''' + + _QUALIFIER_DIV = ''' + <TR><TD VALIGN="BOTTOM" HEIGHT="10" WIDTH="10" FIXEDSIZE="TRUE"></TD></TR> + ''' + + #: Args: (rowspan, bgcolor, classname, attributes, operations, qualifiers) + _LABEL = ''' + <TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" CELLPADDING="0"> + <TR><TD ROWSPAN="%s"> + <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" + CELLPADDING="0" PORT="body" BGCOLOR="%s"> + <TR><TD>%s</TD></TR> + <TR><TD><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0"> + %s</TABLE></TD></TR> + <TR><TD><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0"> + %s</TABLE></TD></TR> + </TABLE> + </TD></TR> + %s + </TABLE>''' + + _COLLAPSED_LABEL = ''' + <TABLE CELLBORDER="0" BGCOLOR="%s" PORT="body"> + <TR><TD>%s</TD></TR> + </TABLE>''' + + def _get_html_label(self): + # Get the class name & contextualize it. + classname = self.class_doc.canonical_name + classname = classname.contextualize(self.context.canonical_name) + + # If we're collapsed, display the node as a single box. + if self.collapsed: + return self._COLLAPSED_LABEL % (self.bgcolor, classname) + + # Construct the attribute list. (If it's too long, truncate) + attrib_cells = [self._attribute_cell(a) for a in self.attributes] + if len(attrib_cells) == 0: + attrib_cells = ['<TR><TD></TD></TR>'] + elif len(attrib_cells) > self.options.get('max_attributes', 15): + attrib_cells[max_attributes-2:-1] = ['<TR><TD>...</TD></TR>'] + attributes = ''.join(attrib_cells) + + # Construct the operation list. (If it's too long, truncate) + oper_cells = [self._operation_cell(a) for a in self.operations] + if len(oper_cells) == 0: + oper_cells = ['<TR><TD></TD></TR>'] + elif len(oper_cells) > self.options.get('max_operations', 15): + oper_cells[max_operations-2:-1] = ['<TR><TD>...</TD></TR>'] + operations = ''.join(oper_cells) + + # Construct the qualifier list & determine the rowspan. + if self.qualifiers: + rowspan = len(self.qualifiers)*2+2 + div = self._QUALIFIER_DIV + qualifiers = div+div.join([self._qualifier_cell(l,p) for + (l,p) in self.qualifiers])+div + else: + rowspan = 1 + qualifiers = '' + + # Put it all together. + return self._LABEL % (rowspan, self.bgcolor, classname, + attributes, operations, qualifiers) + + def to_dotfile(self): + attribs = ['%s="%s"' % (k,v) for (k,v) in self._attribs.items()] + attribs.append('label=<%s>' % self._get_html_label()) + s = 'node%d%s' % (self.id, ' [%s]' % (','.join(attribs))) + if not self.collapsed: + for edge in self.edges: + s += '\n' + edge.to_dotfile() + return s + +# [XX] Not used yet. +class DotGraphUmlModuleNode(DotGraphNode): + """ + A specialized dot grah node used to display C{ModuleDoc}s using + UML notation. Simple module nodes look like:: + + .----. + +------------+ + | modulename | + +------------+ + + Packages nodes are drawn with their modules & subpackages nested + inside:: + + .----. + +----------------------------------------+ + | packagename | + | | + | .----. .----. .----. | + | +---------+ +---------+ +---------+ | + | | module1 | | module2 | | module3 | | + | +---------+ +---------+ +---------+ | + | | + +----------------------------------------+ + + """ + def __init__(self, module_doc, linker, context, collapsed=False, + **options): + self.module_doc = module_doc + self.linker = linker + self.context = context + + def _nested_package_label(package): + """ + :Return: (label, depth, width) where: + + - `label` is the HTML label + - `depth` is the depth of the package tree (for coloring) + - `width` is the max width of the HTML label, roughly in + units of characters. + """ + MAX_ROW_WIDTH = 80 # unit is roughly characters. + pkg_name = package.canonical_name + pkg_url = self.linker.url_for(package) or NOOP_URL + + if not package.is_package or len(package.submodules) == 0: + pkg_color = _nested_uml_package_color(package, self.context, 1) + label = MODULE_NODE_HTML % (pkg_color, pkg_color, pkg_url, + pkg_name, pkg_name[-1]) + return (label, 1, len(pkg_name[-1])+3) + + submodule_labels = [self._nested_package_label(submodule, self.linker, + self.context) + for submodule in package.submodules] + + ROW_HDR = '<TABLE BORDER="0" CELLBORDER="0"><TR>' + # Build the body of the package's icon. + body = '<TABLE BORDER="0" CELLBORDER="0">' + body += '<TR><TD ALIGN="LEFT">%s</TD></TR>' % pkg_name[-1] + body += '<TR><TD>%s' % ROW_HDR + row_width = [0] + for i, (label, depth, width) in enumerate(submodule_labels): + if row_width[-1] > 0 and width+row_width[-1] > MAX_ROW_WIDTH: + body += '</TR></TABLE></TD></TR>' + body += '<TR><TD>%s' % ROW_HDR + row_width.append(0) + #submodule_url = self.linker.url_for(package.submodules[i]) or '#' + #submodule_name = package.submodules[i].canonical_name + body += '<TD ALIGN="LEFT">%s</TD>' % label + row_width [-1] += width + body += '</TR></TABLE></TD></TR></TABLE>' + + # Put together our return value. + depth = max([d for (l,d,w) in submodule_labels])+1 + pkg_color = self._color(package, depth) + label = ('<TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0"><TR>' + '<TD ALIGN="LEFT" HEIGHT="8" WIDTH="16" FIXEDSIZE="true" ' + 'BORDER="1" VALIGN="BOTTOM" BGCOLOR="%s"></TD></TR><TR>' + '<TD COLSPAN="5" VALIGN="TOP" ALIGN="LEFT" BORDER="1" ' + 'BGCOLOR="%s" HREF="%s" TOOLTIP="%s">%s</TD></TR></TABLE>' % + (pkg_color, pkg_color, pkg_url, pkg_name, body)) + width = max(max(row_width), len(pkg_name[-1])+3) + return label, depth, width + + def _color(package, depth): + if package == self.context: return SELECTED_BG + else: + # Parse the base color. + if re.match(MODULE_BG, 'r#[0-9a-fA-F]{6}$'): + base = int(MODULE_BG[1:], 16) + else: + base = int('d8e8ff', 16) + red = (base & 0xff0000) >> 16 + green = (base & 0x00ff00) >> 8 + blue = (base & 0x0000ff) + # Make it darker with each level of depth. (but not *too* + # dark -- package name needs to be readable) + red = max(64, red-(depth-1)*10) + green = max(64, green-(depth-1)*10) + blue = max(64, blue-(depth-1)*10) + # Convert it back to a color string + return '#%06x' % ((red<<16)+(green<<8)+blue) + + + + +###################################################################### #{ Graph Generation Functions ###################################################################### @@ -381,7 +943,7 @@ for package in root_packages: html_label, _, _ = _nested_uml_package_label(package, linker, context) node = DotGraphNode(html_label=html_label, shape='plaintext', - url=linker.url_for(package), + href=linker.url_for(package) or NOOP_URL, tooltip=package.canonical_name) graph.nodes.append(node) return graph @@ -500,6 +1062,88 @@ return graph ###################################################################### +def uml_class_tree_graph(class_doc, linker, context=None, **options): + """ + Return a `DotGraph` that graphically displays the class hierarchy + for the given class, using UML notation. Options: + - max_attributes + - max_operations + - show_private_vars + - show_magic_vars + - link_attributes + """ + nodes = {} # ClassDoc -> DotGraphUmlClassNode + + # Create nodes for class_doc and all its bases. + for cls in class_doc.mro(): + if cls.pyval is object: continue # don't include `object`. + if cls == class_doc: color = SELECTED_BG + else: color = BASECLASS_BG + nodes[cls] = DotGraphUmlClassNode(cls, linker, context, + show_inherited_vars=False, + collapsed=False, bgcolor=color) + + # Create nodes for all class_doc's subclasses. + queue = [class_doc] + for cls in queue: + if cls.subclasses not in (None, UNKNOWN): + queue.extend(cls.subclasses) + for cls in cls.subclasses: + if cls not in nodes: + nodes[cls] = DotGraphUmlClassNode(cls, linker, context, + collapsed=True, + bgcolor=SUBCLASS_BG) + + # Only show variables in the class where they're defined for + # *class_doc*. + mro = class_doc.mro() + for name, var in class_doc.variables.items(): + i = mro.index(var.container) + for base in mro[i+1:]: + if base.pyval is object: continue # don't include `object`. + overridden_var = base.variables.get(name) + if overridden_var and overridden_var.container == base: + try: + if isinstance(overridden_var.value, RoutineDoc): + nodes[base].operations.remove(overridden_var) + else: + nodes[base].attributes.remove(overridden_var) + except ValueError: + pass # var is filtered (eg private or magic) + + # Keep track of which nodes are part of the inheritance graph + # (since link_attributes might add new nodes) + inheritance_nodes = set(nodes.values()) + + # Turn attributes into links. + if options.get('link_attributes', True): + for node in nodes.values(): + node.link_attributes(nodes) + # Make sure that none of the new attribute edges break the + # rank ordering assigned by inheritance. + for edge in node.edges: + if edge.end in inheritance_nodes: + edge['constraint'] = 'False' + + # Construct the graph. + graph = DotGraph('UML class diagram for %s' % class_doc, + body='ranksep=.2\n;nodesep=.3\n') + graph.nodes = nodes.values() + + # Add inheritance edges. + for node in inheritance_nodes: + for base in node.class_doc.bases: + if base in nodes: + graph.edges.append(DotGraphEdge(nodes[base], node, + dir='back', arrowtail='empty', + headport='body', tailport='body', + color=INH_LINK_COLOR, weight=100, + style='bold')) + + # And we're done! + return graph + +###################################################################### def import_graph(modules, docindex, linker, context=None, **options): graph = DotGraph('Import Graph', body='ranksep=.3\n;nodesep=.3\n') @@ -633,9 +1277,7 @@ specialize_valdoc_node(node, val_doc, context, linker.url_for(val_doc)) return nodes -NOOP_URL = '#' -NOOP_URL = 'javascript:;' # this option is more evil. - +NOOP_URL = 'javascript: void(0);' MODULE_NODE_HTML = ''' <TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" CELLPADDING="0" PORT="table" ALIGN="LEFT"> @@ -644,8 +1286,6 @@ <TR><TD ALIGN="LEFT" VALIGN="TOP" BGCOLOR="%s" BORDER="1" PORT="body" HREF="%s" TOOLTIP="%s">%s</TD></TR> </TABLE>'''.strip() -MODULE_BG = '#d8e8ff' -SELECTED_BG = '#ffd0d0' def specialize_valdoc_node(node, val_doc, context, url): """ This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <dva...@us...> - 2006-07-11 13:41:44
|
Revision: 1246 Author: dvarrazzo Date: 2006-07-11 06:41:33 -0700 (Tue, 11 Jul 2006) ViewCVS: http://svn.sourceforge.net/epydoc/?rev=1246&view=rev Log Message: ----------- - PyGetSetDef generated attributes handled as properties. Modified Paths: -------------- trunk/epydoc/src/epydoc/docintrospecter.py trunk/epydoc/src/epydoc/docwriter/html.py Modified: trunk/epydoc/src/epydoc/docintrospecter.py =================================================================== --- trunk/epydoc/src/epydoc/docintrospecter.py 2006-07-11 13:40:17 UTC (rev 1245) +++ trunk/epydoc/src/epydoc/docintrospecter.py 2006-07-11 13:41:33 UTC (rev 1246) @@ -475,10 +475,11 @@ prop_doc.docstring = get_docstring(prop) # Record the property's access functions. - prop_doc.fget = introspect_docs(prop.fget) - prop_doc.fset = introspect_docs(prop.fset) - prop_doc.fdel = introspect_docs(prop.fdel) - + if hasattr(prop, 'fget'): + prop_doc.fget = introspect_docs(prop.fget) + prop_doc.fset = introspect_docs(prop.fset) + prop_doc.fdel = introspect_docs(prop.fdel) + return prop_doc #//////////////////////////////////////////////////////////// @@ -696,6 +697,15 @@ register_introspecter(inspect.isroutine, introspect_routine, priority=28) register_introspecter(is_property, introspect_property, priority=30) +try: + import array + attribute = type(array.array.typecode) + del array + def is_attribute(v): return isinstance(v, attribute) + register_introspecter(is_attribute, introspect_property, priority=32) +except: + pass + #//////////////////////////////////////////////////////////// # Import support #//////////////////////////////////////////////////////////// Modified: trunk/epydoc/src/epydoc/docwriter/html.py =================================================================== --- trunk/epydoc/src/epydoc/docwriter/html.py 2006-07-11 13:40:17 UTC (rev 1245) +++ trunk/epydoc/src/epydoc/docwriter/html.py 2006-07-11 13:41:33 UTC (rev 1246) @@ -1930,7 +1930,8 @@ accessors = [(name, self.property_accessor_to_html(val_doc), self.summary(val_doc)) for (name, val_doc) in [('Get', prop_doc.fget), ('Set', prop_doc.fset), - ('Delete', prop_doc.fdel)]] + ('Delete', prop_doc.fdel)] + if val_doc is not UNKNOWN ] self.write_property_details_entry(out, var_doc, descr, accessors, div_class) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ed...@us...> - 2006-08-21 07:16:21
|
Revision: 1253 Author: edloper Date: 2006-08-21 00:16:15 -0700 (Mon, 21 Aug 2006) ViewCVS: http://svn.sourceforge.net/epydoc/?rev=1253&view=rev Log Message: ----------- - Changed all cases where UNKNOWN was tested with == or != to use "is" and "is not" instead. (UNKNOWN is defined to be a unique value, so it should always be tested by identity, like None). This should fix sf bug #1506850. Modified Paths: -------------- trunk/epydoc/src/epydoc/apidoc.py trunk/epydoc/src/epydoc/cli.py trunk/epydoc/src/epydoc/docbuilder.py trunk/epydoc/src/epydoc/docintrospecter.py trunk/epydoc/src/epydoc/docparser.py trunk/epydoc/src/epydoc/docstringparser.py Modified: trunk/epydoc/src/epydoc/apidoc.py =================================================================== --- trunk/epydoc/src/epydoc/apidoc.py 2006-08-21 07:04:18 UTC (rev 1252) +++ trunk/epydoc/src/epydoc/apidoc.py 2006-08-21 07:16:15 UTC (rev 1253) @@ -722,7 +722,7 @@ or UNKNOWN if we don't succeed. This should probably eventually be replaced by more of a safe-repr variant. """ - if self.pyval == UNKNOWN: + if self.pyval is UNKNOWN: return UNKNOWN try: s = '%r' % self.pyval @@ -798,7 +798,7 @@ def __init__(self, **kwargs): kwargs.setdefault('variables', {}) APIDoc.__init__(self, **kwargs) - assert self.variables != UNKNOWN + assert self.variables is not UNKNOWN def apidoc_links(self, **filters): variables = filters.get('variables', True) @@ -854,7 +854,7 @@ Initialize the L{variable_groups} attribute, based on the L{sorted_variables} and L{group_specs} attributes. """ - if self.sorted_variables == UNKNOWN: + if self.sorted_variables is UNKNOWN: self.init_sorted_variables assert len(self.sorted_variables) == len(self.variables) @@ -1008,8 +1008,8 @@ variables that do not belong to any group. @type group: C{string} """ - if (self.sorted_variables == UNKNOWN or - self.variable_groups == UNKNOWN): + if (self.sorted_variables is UNKNOWN or + self.variable_groups is UNKNOWN): raise ValueError('sorted_variables and variable_groups ' 'must be initialized first.') @@ -1199,8 +1199,8 @@ local variables; if C{True}, then return only inherited variables; if C{False}, then return only local variables. """ - if (self.sorted_variables == UNKNOWN or - self.variable_groups == UNKNOWN): + if (self.sorted_variables is UNKNOWN or + self.variable_groups is UNKNOWN): raise ValueError('sorted_variables and variable_groups ' 'must be initialized first.') @@ -1528,7 +1528,7 @@ child_var = val_doc.variables.get(identifier) if child_var is not None: child_val = child_var.value - if child_val == UNKNOWN: child_val = None + if child_val is UNKNOWN: child_val = None return child_var, child_val # If that fails, then see if it's a submodule. @@ -1627,7 +1627,7 @@ return api_doc.container if len(api_doc.canonical_name) == 1: return None - elif isinstance(api_doc, ModuleDoc) and api_doc.package != UNKNOWN: + elif isinstance(api_doc, ModuleDoc) and api_doc.package is not UNKNOWN: return api_doc.package else: parent = api_doc.canonical_name.container() Modified: trunk/epydoc/src/epydoc/cli.py =================================================================== --- trunk/epydoc/src/epydoc/cli.py 2006-08-21 07:04:18 UTC (rev 1252) +++ trunk/epydoc/src/epydoc/cli.py 2006-08-21 07:16:15 UTC (rev 1253) @@ -939,7 +939,7 @@ p = ((sum(self.stages[:i]) + percent*self.stages[i]) / float(sum(self.stages))) - if message == UNKNOWN: message = None + if message is UNKNOWN: message = None if message: message = '%s: %s' % (self.task, message) ConsoleLogger.progress(self, p, message) Modified: trunk/epydoc/src/epydoc/docbuilder.py =================================================================== --- trunk/epydoc/src/epydoc/docbuilder.py 2006-08-21 07:04:18 UTC (rev 1252) +++ trunk/epydoc/src/epydoc/docbuilder.py 2006-08-21 07:16:15 UTC (rev 1253) @@ -218,7 +218,7 @@ def _report_valdoc_progress(i, val_doc, val_docs): if (isinstance(val_doc, (ModuleDoc, ClassDoc)) and - val_doc.canonical_name != UNKNOWN and + val_doc.canonical_name is not UNKNOWN and not val_doc.canonical_name[0].startswith('??')): log.progress(float(i)/len(val_docs), val_doc.canonical_name) @@ -731,7 +731,7 @@ def _merge_posargs_and_defaults(introspect_doc, parse_doc, path): # If either is unknown, then let merge_attrib handle it. - if introspect_doc.posargs == UNKNOWN or parse_doc.posargs == UNKNOWN: + if introspect_doc.posargs is UNKNOWN or parse_doc.posargs is UNKNOWN: return # If the introspected doc just has '...', then trust the parsed doc. @@ -877,7 +877,7 @@ return defaults def merge_docstring(docstring1, docstring2, precedence, cyclecheck, path): - if docstring1 in (None, UNKNOWN) or precedence=='parse': + if docstring1 is None or docstring1 is UNKNOWN or precedence=='parse': return docstring2 else: return docstring1 @@ -1116,7 +1116,7 @@ # local, then record the fact that it overrides # var_doc. elif (class_doc.variables[name].container==class_doc and - class_doc.variables[name].overrides==UNKNOWN): + class_doc.variables[name].overrides is UNKNOWN): class_doc.variables[name].overrides = var_doc _inherit_info(class_doc.variables[name]) Modified: trunk/epydoc/src/epydoc/docintrospecter.py =================================================================== --- trunk/epydoc/src/epydoc/docintrospecter.py 2006-08-21 07:04:18 UTC (rev 1252) +++ trunk/epydoc/src/epydoc/docintrospecter.py 2006-08-21 07:16:15 UTC (rev 1253) @@ -149,7 +149,7 @@ introspect_func(value, val_doc) # Set canonical name, if it was given - if val_doc.canonical_name == UNKNOWN and name is not None: + if val_doc.canonical_name is UNKNOWN and name is not None: val_doc.canonical_name = DottedName(name) # If we were given a filename, but didn't manage to get a @@ -160,7 +160,7 @@ if is_script and filename is not None: val_doc.canonical_name = DottedName(munge_script_name(str(filename))) - if val_doc.canonical_name == UNKNOWN and filename is not None: + if val_doc.canonical_name is UNKNOWN and filename is not None: shadowed_name = DottedName(value.__name__) log.warning("Module %s is shadowed by a variable with " "the same name." % shadowed_name) @@ -583,7 +583,7 @@ Verify the name. E.g., if it's a nested class, then we won't be able to find it with the name we constructed. """ - if dotted_name == UNKNOWN: return UNKNOWN + if dotted_name is UNKNOWN: return UNKNOWN if len(dotted_name) == 1 and hasattr(__builtin__, dotted_name[0]): return dotted_name named_value = sys.modules.get(dotted_name[0]) @@ -864,7 +864,7 @@ """ if api_doc.docstring_lineno is not UNKNOWN: return api_doc.docstring_lineno - if isinstance(api_doc, ValueDoc) and api_doc.pyval != UNKNOWN: + if isinstance(api_doc, ValueDoc) and api_doc.pyval is not UNKNOWN: try: lines, lineno = inspect.findsource(api_doc.pyval) if not isinstance(api_doc, ModuleDoc): lineno += 1 Modified: trunk/epydoc/src/epydoc/docparser.py =================================================================== --- trunk/epydoc/src/epydoc/docparser.py 2006-08-21 07:04:18 UTC (rev 1252) +++ trunk/epydoc/src/epydoc/docparser.py 2006-08-21 07:16:15 UTC (rev 1253) @@ -234,7 +234,7 @@ "with filename, not with name.") name = DottedName(name) val_doc = _find(name) - if val_doc.canonical_name == UNKNOWN: + if val_doc.canonical_name is UNKNOWN: val_doc.canonical_name = name return val_doc @@ -451,7 +451,7 @@ raise ImportError('Could not find value') def _get_filename(identifier, path=None): - if path == UNKNOWN: path = None + if path is UNKNOWN: path = None try: fp, filename, (s,m,typ) = imp.find_module(identifier, path) if fp is not None: fp.close() @@ -672,7 +672,7 @@ decorators = [] def add_to_group(container, api_doc, group_name): - if container.group_specs == UNKNOWN: + if container.group_specs is UNKNOWN: container.group_specs = [] if isinstance(api_doc, VariableDoc): @@ -1545,7 +1545,7 @@ #raise ParseError("Could not find %s" % name) # If the variable has a value, return that value. - if base_var.value != UNKNOWN: + if base_var.value is not UNKNOWN: return base_var.value # Otherwise, if BASE_HANDLING is 'parse', try parsing the docs for @@ -1766,7 +1766,7 @@ namespace.sort_spec.remove(var_doc.name) old_var_doc = namespace.variables[var_doc.name] if (old_var_doc.is_alias == False and - old_var_doc.value != UNKNOWN): + old_var_doc.value is not UNKNOWN): old_var_doc.value.canonical_name = UNKNOWN if (preserve_docstring and var_doc.docstring in (None, UNKNOWN) and old_var_doc.docstring not in (None, UNKNOWN)): @@ -1860,7 +1860,7 @@ if isinstance(var_doc.value, NamespaceDoc): var_dict = var_doc.value.variables - elif (var_doc.value == UNKNOWN and + elif (var_doc.value is UNKNOWN and var_doc.imported_from not in (None, UNKNOWN)): src_name = var_doc.imported_from + dotted_name[i:] # [xx] do I want to create a proxy here?? Modified: trunk/epydoc/src/epydoc/docstringparser.py =================================================================== --- trunk/epydoc/src/epydoc/docstringparser.py 2006-08-21 07:04:18 UTC (rev 1252) +++ trunk/epydoc/src/epydoc/docstringparser.py 2006-08-21 07:16:15 UTC (rev 1253) @@ -251,7 +251,7 @@ # filename of its containing module. name = api_doc.canonical_name module = api_doc.defining_module - if module != UNKNOWN and module.filename not in (None, UNKNOWN): + if module is not UNKNOWN and module.filename not in (None, UNKNOWN): try: filename = py_src_filename(module.filename) except: filename = module.filename else: @@ -629,7 +629,7 @@ # Find the module that defines api_doc. module = api_doc.defining_module # Look up its docformat. - if module != UNKNOWN and module.docformat not in (None, UNKNOWN): + if module is not UNKNOWN and module.docformat not in (None, UNKNOWN): docformat = module.docformat else: docformat = DEFAULT_DOCFORMAT This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ed...@us...> - 2006-08-22 00:53:11
|
Revision: 1281 Author: edloper Date: 2006-08-21 17:53:05 -0700 (Mon, 21 Aug 2006) ViewCVS: http://svn.sourceforge.net/epydoc/?rev=1281&view=rev Log Message: ----------- - Applied patches from SF bug [ 1482145 ] "Cannot introspect or parse Schevo project code". These all basically have to do with handling the case when a RoutineDoc's posargs, varargs, or kwargs attributes are UNKNOWN. Modified Paths: -------------- trunk/epydoc/src/epydoc/apidoc.py trunk/epydoc/src/epydoc/docparser.py trunk/epydoc/src/epydoc/docwriter/html.py Modified: trunk/epydoc/src/epydoc/apidoc.py =================================================================== --- trunk/epydoc/src/epydoc/apidoc.py 2006-08-21 14:13:15 UTC (rev 1280) +++ trunk/epydoc/src/epydoc/apidoc.py 2006-08-22 00:53:05 UTC (rev 1281) @@ -1347,6 +1347,9 @@ consists of a tuple of names, then that tuple will be flattened. """ + if self.posargs is UNKNOWN: + return UNKNOWN + all_args = _flatten(self.posargs) if self.vararg not in (None, UNKNOWN): all_args.append(self.vararg) @@ -1603,9 +1606,10 @@ return None # Is it a parameter's name or an attribute of a parameter? - if (isinstance(context, RoutineDoc) and - name[0] in context.all_args()): - return None + if isinstance(context, RoutineDoc): + all_args = context.all_args() + if all_args is not UNKNOWN and name[0] in all_args: + return None #//////////////////////////////////////////////////////////// # etc Modified: trunk/epydoc/src/epydoc/docparser.py =================================================================== --- trunk/epydoc/src/epydoc/docparser.py 2006-08-21 14:13:15 UTC (rev 1280) +++ trunk/epydoc/src/epydoc/docparser.py 2006-08-22 00:53:05 UTC (rev 1281) @@ -1151,6 +1151,7 @@ # the name of the first arg to the containing routinedoc, and # <name> is a simple name. posargs = parent_docs[-1].posargs + if posargs is UNKNOWN: return False if not (len(lhs_pieces)==1 and len(posargs) > 0 and len(lhs_pieces[0]) == 3 and lhs_pieces[0][0] == (token.NAME, posargs[0]) and Modified: trunk/epydoc/src/epydoc/docwriter/html.py =================================================================== --- trunk/epydoc/src/epydoc/docwriter/html.py 2006-08-21 14:13:15 UTC (rev 1280) +++ trunk/epydoc/src/epydoc/docwriter/html.py 2006-08-22 00:53:05 UTC (rev 1281) @@ -2424,13 +2424,13 @@ else: args = [self.func_arg(n, d, css_class) for (n, d) in zip(func_doc.posargs, func_doc.posarg_defaults)] - if func_doc.vararg: + if func_doc.vararg not in (None, UNKNOWN): if func_doc.vararg == '...': args.append('<span class="%s-arg">...</span>' % css_class) else: args.append('<span class="%s-arg">*%s</span>' % (css_class, func_doc.vararg)) - if func_doc.kwarg: + if func_doc.kwarg not in (None, UNKNOWN): args.append('<span class="%s-arg">**%s</span>' % (css_class, func_doc.kwarg)) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ed...@us...> - 2006-08-24 18:56:49
|
Revision: 1324 Author: edloper Date: 2006-08-24 11:56:43 -0700 (Thu, 24 Aug 2006) ViewCVS: http://svn.sourceforge.net/epydoc/?rev=1324&view=rev Log Message: ----------- - Updated bug reports in code Modified Paths: -------------- trunk/epydoc/src/epydoc/__init__.py trunk/epydoc/src/epydoc/docstringparser.py Modified: trunk/epydoc/src/epydoc/__init__.py =================================================================== --- trunk/epydoc/src/epydoc/__init__.py 2006-08-24 18:47:28 UTC (rev 1323) +++ trunk/epydoc/src/epydoc/__init__.py 2006-08-24 18:56:43 UTC (rev 1324) @@ -183,8 +183,6 @@ not necessary) :bug: UserDict.* is interpreted as imported .. why?? -:bug: Nested functions break source colorizer (and *why* is the source - colorizer being so slow on the stdlib???) :license: IBM Open Source License :copyright: |copy| 2006 Edward Loper Modified: trunk/epydoc/src/epydoc/docstringparser.py =================================================================== --- trunk/epydoc/src/epydoc/docstringparser.py 2006-08-24 18:47:28 UTC (rev 1323) +++ trunk/epydoc/src/epydoc/docstringparser.py 2006-08-24 18:56:43 UTC (rev 1324) @@ -375,10 +375,13 @@ Return a list of user defined fields that can be used for the given object. This list is taken from the given C{api_doc}, and any of its containing C{NamepaceDoc}s. - - @bug: If a child's docstring is parsed before its parents, then - its parent won't yet have had its C{extra_docstring_fields} - attribute initialized. + + @note: We assume here that a parent's docstring will always be + parsed before its childrens'. This is indeed the case when we + are called via L{docbuilder.build_doc_index()}. If a child's + docstring is parsed before its parents, then its parent won't + yet have had its C{extra_docstring_fields} attribute + initialized. """ docfields = [] # Get any docfields from `api_doc` itself This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ed...@us...> - 2006-09-02 01:40:41
|
Revision: 1344 http://svn.sourceforge.net/epydoc/?rev=1344&view=rev Author: edloper Date: 2006-09-01 18:40:35 -0700 (Fri, 01 Sep 2006) Log Message: ----------- - Replaced xml.dom.minidom with a *very* simple tree representation for parsed epytext. (Using the new Element class). This should significantly speed up some of the epytext processing steps. Modified Paths: -------------- trunk/epydoc/src/epydoc/markup/epytext.py trunk/epydoc/src/epydoc/test/epytext.doctest Modified: trunk/epydoc/src/epydoc/markup/epytext.py =================================================================== --- trunk/epydoc/src/epydoc/markup/epytext.py 2006-09-02 01:23:23 UTC (rev 1343) +++ trunk/epydoc/src/epydoc/markup/epytext.py 2006-09-02 01:40:35 UTC (rev 1344) @@ -9,7 +9,8 @@ """ Parser for epytext strings. Epytext is a lightweight markup whose primary intended application is Python documentation strings. This -parser converts Epytext strings to a XML/DOM representation. Epytext +parser converts Epytext strings to a simple DOM-like representation +(encoded as a tree of L{Element} objects and strings). Epytext strings can contain the following X{structural blocks}: - X{epytext}: The top-level element of the DOM tree. @@ -105,13 +106,48 @@ # 5. testing import re, string, types, sys, os.path -from xml.dom.minidom import Document, Text -import xml.dom.minidom from epydoc.markup import * from epydoc.util import wordwrap, plaintext_to_html, plaintext_to_latex from epydoc.markup.doctest import doctest_to_html, doctest_to_latex ################################################## +## DOM-Like Encoding +################################################## + +class Element: + """ + A very simple DOM-like representation for parsed epytext + documents. Each epytext document is encoded as a tree whose nodes + are L{Element} objects, and whose leaves are C{string}s. Each + node is marked by a I{tag} and zero or more I{attributes}. Each + attribute is a mapping from a string key to a string value. + """ + def __init__(self, tag, *children, **attribs): + self.tag = tag + """A string tag indicating the type of this element. + @type: C{string}""" + + self.children = list(children) + """A list of the children of this element. + @type: C{list} of (C{string} or C{Element})""" + + self.attribs = attribs + """A dictionary mapping attribute names to attribute values + for this element. + @type: C{dict} from C{string} to C{string}""" + + def __str__(self): + """ + Return a string representation of this element, using XML + notation. + @bug: Doesn't escape '<' or '&' or '>'. + """ + attribs = ''.join([' %s=%r' % t for t in self.attribs.items()]) + return ('<%s%s>' % (self.tag, attribs) + + ''.join([str(child) for child in self.children]) + + '</%s>' % self.tag) + +################################################## ## Constants ################################################## @@ -196,7 +232,7 @@ ignored. @type errors: C{list} of L{ParseError} @return: a DOM tree encoding the contents of an epytext string. - @rtype: L{xml.dom.minidom.Document} + @rtype: C{Element} @raise ParseError: If C{errors} is C{None} and an error is encountered while parsing. """ @@ -217,8 +253,8 @@ # Have we encountered a field yet? encountered_field = 0 - # Create an XML document to hold the epytext. - doc = Document() + # Create an document to hold the epytext. + doc = Element('epytext') # Maintain two parallel stacks: one contains DOM elements, and # gives the ancestors of the current block. The other contains @@ -230,13 +266,13 @@ # corresponds to). No 2 consecutive indent_stack values will be # ever be "None." Use initial dummy elements in the stack, so we # don't have to worry about bounds checking. - stack = [None, doc.createElement('epytext')] + stack = [None, doc] indent_stack = [-1, None] for token in tokens: # Uncomment this for debugging: #print ('%s: %s\n%s: %s\n' % - # (''.join(['%-11s' % (t and t.tagName) for t in stack]), + # (''.join(['%-11s' % (t and t.tag) for t in stack]), # token.tag, ''.join(['%-11s' % i for i in indent_stack]), # token.indent)) @@ -253,11 +289,11 @@ # If Token has type LBLOCK, add the new literal block elif token.tag == Token.LBLOCK: - stack[-1].appendChild(token.to_dom(doc)) + stack[-1].children.append(token.to_dom(doc)) # If Token has type DTBLOCK, add the new doctest block elif token.tag == Token.DTBLOCK: - stack[-1].appendChild(token.to_dom(doc)) + stack[-1].children.append(token.to_dom(doc)) # If Token has type BULLET, add the new list/list item/field elif token.tag == Token.BULLET: @@ -266,7 +302,7 @@ assert 0, 'Unknown token type: '+token.tag # Check if the DOM element we just added was a field.. - if stack[-1].tagName == 'field': + if stack[-1].tag == 'field': encountered_field = 1 elif encountered_field == 1: if len(stack) <= 3: @@ -282,7 +318,6 @@ return None # Return the top-level epytext DOM element. - doc.appendChild(stack[1]) return doc def _pop_completed_blocks(token, stack, indent_stack): @@ -305,10 +340,10 @@ # Dedent to a list item, if it is follwed by another list # item with the same indentation. elif (token.tag == 'bullet' and indent==indent_stack[-2] and - stack[-1].tagName in ('li', 'field')): pop=1 + stack[-1].tag in ('li', 'field')): pop=1 # End of a list (no more list items available) - elif (stack[-1].tagName in ('ulist', 'olist') and + elif (stack[-1].tag in ('ulist', 'olist') and (token.tag != 'bullet' or token.contents[-1] == ':')): pop=1 @@ -326,7 +361,7 @@ if para_token.indent == indent_stack[-1]: # Colorize the paragraph and add it. para = _colorize(doc, para_token, errors) - stack[-1].appendChild(para) + stack[-1].children.append(para) else: estr = "Improper paragraph indentation." errors.append(StructuringError(estr, para_token.startline)) @@ -341,7 +376,7 @@ # Check for errors. for tok in stack[2:]: - if tok.tagName != "section": + if tok.tag != "section": estr = "Headings must occur at the top level." errors.append(StructuringError(estr, heading_token.startline)) break @@ -358,10 +393,10 @@ head = _colorize(doc, heading_token, errors, 'heading') # Add the section's and heading's DOM elements. - sec = doc.createElement("section") - stack[-1].appendChild(sec) + sec = Element("section") + stack[-1].children.append(sec) stack.append(sec) - sec.appendChild(head) + sec.children.append(head) indent_stack.append(None) def _add_list(doc, bullet_token, stack, indent_stack, errors): @@ -382,11 +417,11 @@ # Is this a new list? newlist = 0 - if stack[-1].tagName != list_type: + if stack[-1].tag != list_type: newlist = 1 - elif list_type == 'olist' and stack[-1].tagName == 'olist': - old_listitem = stack[-1].childNodes[-1] - old_bullet = old_listitem.getAttribute("bullet").split('.')[:-1] + elif list_type == 'olist' and stack[-1].tag == 'olist': + old_listitem = stack[-1].children[-1] + old_bullet = old_listitem.attribs.get("bullet").split('.')[:-1] new_bullet = bullet_token.contents.split('.')[:-1] if (new_bullet[:-1] != old_bullet[:-1] or int(new_bullet[-1]) != int(old_bullet[-1])+1): @@ -394,7 +429,7 @@ # Create the new list. if newlist: - if stack[-1].tagName is 'fieldlist': + if stack[-1].tag is 'fieldlist': # The new list item is not a field list item (since this # is a new list); but it's indented the same as the field # list. This either means that they forgot to indent the @@ -403,7 +438,7 @@ # just warn about that (to avoid confusion). estr = "Lists must be indented." errors.append(StructuringError(estr, bullet_token.startline)) - if stack[-1].tagName in ('ulist', 'olist', 'fieldlist'): + if stack[-1].tag in ('ulist', 'olist', 'fieldlist'): stack.pop() indent_stack.pop() @@ -419,7 +454,7 @@ if list_type == 'fieldlist': # Fieldlist should be at the top-level. for tok in stack[2:]: - if tok.tagName != "section": + if tok.tag != "section": estr = "Fields must be at the top level." errors.append( StructuringError(estr, bullet_token.startline)) @@ -428,41 +463,40 @@ indent_stack[2:] = [] # Add the new list. - lst = doc.createElement(list_type) - stack[-1].appendChild(lst) + lst = Element(list_type) + stack[-1].children.append(lst) stack.append(lst) indent_stack.append(bullet_token.indent) if list_type == 'olist': start = bullet_token.contents.split('.')[:-1] if start != '1': - lst.setAttribute("start", start[-1]) + lst.attribs["start"] = start[-1] # Fields are treated somewhat specially: A "fieldlist" # node is created to make the parsing simpler, but fields # are adjoined directly into the "epytext" node, not into # the "fieldlist" node. if list_type == 'fieldlist': - li = doc.createElement("field") + li = Element("field") token_words = bullet_token.contents[1:-1].split(None, 1) - tag_elt = doc.createElement("tag") - tag_elt.appendChild(doc.createTextNode(token_words[0])) - li.appendChild(tag_elt) + tag_elt = Element("tag") + tag_elt.children.append(token_words[0]) + li.children.append(tag_elt) if len(token_words) > 1: - arg_elt = doc.createElement("arg") - arg_elt.appendChild(doc.createTextNode(token_words[1])) - li.appendChild(arg_elt) + arg_elt = Element("arg") + arg_elt.children.append(token_words[1]) + li.children.append(arg_elt) else: - li = doc.createElement("li") + li = Element("li") if list_type == 'olist': - li.setAttribute("bullet", bullet_token.contents) + li.attribs["bullet"] = bullet_token.contents # Add the bullet. - stack[-1].appendChild(li) + stack[-1].children.append(li) stack.append(li) indent_stack.append(None) - ################################################## ## Tokenization ################################################## @@ -570,10 +604,10 @@ def to_dom(self, doc): """ @return: a DOM representation of this C{Token}. - @rtype: L{xml.dom.minidom.Element} + @rtype: L{Element} """ - e = doc.createElement(self.tag) - e.appendChild(doc.createTextNode(self.contents)) + e = Element(self.tag) + e.children.append(self.contents) return e # Construct regular expressions for recognizing bullets. These are @@ -941,7 +975,7 @@ # the text currently being analyzed. New elements are pushed when # "{" is encountered, and old elements are popped when "}" is # encountered. - stack = [doc.createElement(tagName)] + stack = [Element(tagName)] # This is just used to make error-reporting friendlier. It's a # stack parallel to "stack" containing the index of each element's @@ -967,20 +1001,20 @@ if match.group() == '{': if (end>0) and 'A' <= str[end-1] <= 'Z': if (end-1) > start: - stack[-1].appendChild(doc.createTextNode(str[start:end-1])) + stack[-1].children.append(str[start:end-1]) if not _COLORIZING_TAGS.has_key(str[end-1]): estr = "Unknown inline markup tag." errors.append(ColorizingError(estr, token, end-1)) - stack.append(doc.createElement('unknown')) + stack.append(Element('unknown')) else: tag = _COLORIZING_TAGS[str[end-1]] - stack.append(doc.createElement(tag)) + stack.append(Element(tag)) else: if end > start: - stack[-1].appendChild(doc.createTextNode(str[start:end])) - stack.append(doc.createElement('litbrace')) + stack[-1].children.append(str[start:end]) + stack.append(Element('litbrace')) openbrace_stack.append(end) - stack[-2].appendChild(stack[-1]) + stack[-2].children.append(stack[-1]) # Close braces end colorizing elements. elif match.group() == '}': @@ -993,62 +1027,51 @@ # Add any remaining text. if end > start: - stack[-1].appendChild(doc.createTextNode(str[start:end])) + stack[-1].children.append(str[start:end]) # Special handling for symbols: - if stack[-1].tagName == 'symbol': - if (len(stack[-1].childNodes) != 1 or - not isinstance(stack[-1].childNodes[0], Text)): + if stack[-1].tag == 'symbol': + if (len(stack[-1].children) != 1 or + not isinstance(stack[-1].children[0], basestring)): estr = "Invalid symbol code." errors.append(ColorizingError(estr, token, end)) else: - symb = stack[-1].childNodes[0].data + symb = stack[-1].children[0] if _SYMBOLS.has_key(symb): # It's a symbol - symbol = doc.createElement('symbol') - stack[-2].removeChild(stack[-1]) - stack[-2].appendChild(symbol) - symbol.appendChild(doc.createTextNode(symb)) + stack[-2].children[-1] = Element('symbol', symb) else: estr = "Invalid symbol code." errors.append(ColorizingError(estr, token, end)) # Special handling for escape elements: - if stack[-1].tagName == 'escape': - if (len(stack[-1].childNodes) != 1 or - not isinstance(stack[-1].childNodes[0], Text)): + if stack[-1].tag == 'escape': + if (len(stack[-1].children) != 1 or + not isinstance(stack[-1].children[0], basestring)): estr = "Invalid escape code." errors.append(ColorizingError(estr, token, end)) else: - escp = stack[-1].childNodes[0].data + escp = stack[-1].children[0] if _ESCAPES.has_key(escp): # It's an escape from _ESCPAES - stack[-2].removeChild(stack[-1]) - escp = _ESCAPES[escp] - stack[-2].appendChild(doc.createTextNode(escp)) + stack[-2].children[-1] = _ESCAPES[escp] elif len(escp) == 1: # It's a single-character escape (eg E{.}) - stack[-2].removeChild(stack[-1]) - stack[-2].appendChild(doc.createTextNode(escp)) + stack[-2].children[-1] = escp else: estr = "Invalid escape code." errors.append(ColorizingError(estr, token, end)) # Special handling for literal braces elements: - if stack[-1].tagName == 'litbrace': - variables = stack[-1].childNodes - stack[-2].removeChild(stack[-1]) - stack[-2].appendChild(doc.createTextNode('{')) - for child in variables: - stack[-2].appendChild(child) - stack[-2].appendChild(doc.createTextNode('}')) + if stack[-1].tag == 'litbrace': + stack[-2].children = ['{'] + stack[-1].children + ['}'] # Special handling for graphs: - if stack[-1].tagName == 'graph': + if stack[-1].tag == 'graph': _colorize_graph(doc, stack[-1], token, end, errors) # Special handling for link-type elements: - if stack[-1].tagName in _LINK_COLORIZING_TAGS: + if stack[-1].tag in _LINK_COLORIZING_TAGS: _colorize_link(doc, stack[-1], token, end, errors) # Pop the completed element. @@ -1059,7 +1082,7 @@ # Add any final text. if start < len(str): - stack[-1].appendChild(doc.createTextNode(str[start:])) + stack[-1].children.append(str[start:]) if len(stack) != 1: estr = "Unbalanced '{'." @@ -1078,13 +1101,13 @@ """ bad_graph_spec = False - children = graph.childNodes[:] - for child in children: graph.removeChild(child) + children = graph.children[:] + graph.children = [] - if len(children) != 1 or not isinstance(children[0], Text): + if len(children) != 1 or not isinstance(children[0], basestring): bad_graph_spec = "Bad graph specification" else: - pieces = children[0].data.split(None, 1) + pieces = children[0].split(None, 1) graphtype = pieces[0].replace(':','').strip().lower() if graphtype in GRAPH_TYPES: if len(pieces) == 2: @@ -1100,51 +1123,49 @@ if bad_graph_spec: errors.append(ColorizingError(bad_graph_spec, token, end)) - graph.appendChild(doc.createTextNode('none')) - graph.appendChild(doc.createTextNode('')) + graph.children.append('none') + graph.children.append('') return - graph.appendChild(doc.createTextNode(graphtype)) + graph.children.append(graphtype) for arg in args: - graph.appendChild(doc.createTextNode(arg)) + graph.children.append(arg) def _colorize_link(doc, link, token, end, errors): - variables = link.childNodes[:] + variables = link.children[:] # If the last child isn't text, we know it's bad. - if len(variables)==0 or not isinstance(variables[-1], Text): - estr = "Bad %s target." % link.tagName + if len(variables)==0 or not isinstance(variables[-1], basestring): + estr = "Bad %s target." % link.tag errors.append(ColorizingError(estr, token, end)) return # Did they provide an explicit target? - match2 = _TARGET_RE.match(variables[-1].data) + match2 = _TARGET_RE.match(variables[-1]) if match2: (text, target) = match2.groups() - variables[-1].data = text + variables[-1] = text # Can we extract an implicit target? elif len(variables) == 1: - target = variables[0].data + target = variables[0] else: - estr = "Bad %s target." % link.tagName + estr = "Bad %s target." % link.tag errors.append(ColorizingError(estr, token, end)) return # Construct the name element. - name_elt = doc.createElement('name') - for child in variables: - name_elt.appendChild(link.removeChild(child)) + name_elt = Element('name', *variables) # Clean up the target. For URIs, assume http or mailto if they # don't specify (no relative urls) target = re.sub(r'\s', '', target) - if link.tagName=='uri': + if link.tag=='uri': if not re.match(r'\w+:', target): if re.match(r'\w+@(\w+)(\.\w+)*', target): target = 'mailto:' + target else: target = 'http://'+target - elif link.tagName=='link': + elif link.tag=='link': # Remove arg lists for functions (e.g., L{_colorize_link()}) target = re.sub(r'\(.*\)$', '', target) if not re.match(r'^[a-zA-Z_]\w*(\.[a-zA-Z_]\w*)*$', target): @@ -1153,12 +1174,10 @@ return # Construct the target element. - target_elt = doc.createElement('target') - target_elt.appendChild(doc.createTextNode(target)) + target_elt = Element('target', target) # Add them to the link element. - link.appendChild(name_elt) - link.appendChild(target_elt) + link.children = [name_elt, target_elt] ################################################## ## Formatters @@ -1176,7 +1195,7 @@ - C{to_epytext(parse(str)) == str} (approximately) @param tree: A DOM document encoding of an epytext string. - @type tree: L{xml.dom.minidom.Document} + @type tree: C{Element} @param indent: The indentation for the string representation of C{tree}. Each line of the returned string will begin with C{indent} space characters. @@ -1187,22 +1206,20 @@ @return: The epytext string corresponding to C{tree}. @rtype: C{string} """ - if isinstance(tree, Document): - return to_epytext(tree.childNodes[0], indent, seclevel) - if isinstance(tree, Text): - str = re.sub(r'\{', '\0', tree.data) + if isinstance(tree, basestring): + str = re.sub(r'\{', '\0', tree) str = re.sub(r'\}', '\1', str) return str - if tree.tagName == 'epytext': indent -= 2 - if tree.tagName == 'section': seclevel += 1 - variables = [to_epytext(c, indent+2, seclevel) for c in tree.childNodes] + if tree.tag == 'epytext': indent -= 2 + if tree.tag == 'section': seclevel += 1 + variables = [to_epytext(c, indent+2, seclevel) for c in tree.children] childstr = ''.join(variables) # Clean up for literal blocks (add the double "::" back) childstr = re.sub(':(\s*)\2', '::\\1', childstr) - if tree.tagName == 'para': + if tree.tag == 'para': str = wordwrap(childstr, indent)+'\n' str = re.sub(r'((^|\n)\s*\d+)\.', r'\1E{.}', str) str = re.sub(r'((^|\n)\s*)-', r'\1E{-}', str) @@ -1211,49 +1228,47 @@ str = re.sub('\0', 'E{lb}', str) str = re.sub('\1', 'E{rb}', str) return str - elif tree.tagName == 'li': - bulletAttr = tree.getAttributeNode('bullet') - if bulletAttr: bullet = bulletAttr.value - else: bullet = '-' + elif tree.tag == 'li': + bullet = tree.attribs.get('bullet') or '-' return indent*' '+ bullet + ' ' + childstr.lstrip() - elif tree.tagName == 'heading': + elif tree.tag == 'heading': str = re.sub('\0', 'E{lb}',childstr) str = re.sub('\1', 'E{rb}', str) uline = len(childstr)*_HEADING_CHARS[seclevel-1] return (indent-2)*' ' + str + '\n' + (indent-2)*' '+uline+'\n' - elif tree.tagName == 'doctestblock': + elif tree.tag == 'doctestblock': str = re.sub('\0', '{', childstr) str = re.sub('\1', '}', str) lines = [' '+indent*' '+line for line in str.split('\n')] return '\n'.join(lines) + '\n\n' - elif tree.tagName == 'literalblock': + elif tree.tag == 'literalblock': str = re.sub('\0', '{', childstr) str = re.sub('\1', '}', str) lines = [(indent+1)*' '+line for line in str.split('\n')] return '\2' + '\n'.join(lines) + '\n\n' - elif tree.tagName == 'field': + elif tree.tag == 'field': numargs = 0 - while tree.childNodes[numargs+1].tagName == 'arg': numargs += 1 + while tree.children[numargs+1].tag == 'arg': numargs += 1 tag = variables[0] args = variables[1:1+numargs] body = variables[1+numargs:] str = (indent)*' '+'@'+variables[0] if args: str += '(' + ', '.join(args) + ')' return str + ':\n' + ''.join(body) - elif tree.tagName == 'target': + elif tree.tag == 'target': return '<%s>' % childstr - elif tree.tagName in ('fieldlist', 'tag', 'arg', 'epytext', + elif tree.tag in ('fieldlist', 'tag', 'arg', 'epytext', 'section', 'olist', 'ulist', 'name'): return childstr - elif tree.tagName == 'symbol': + elif tree.tag == 'symbol': return 'E{%s}' % childstr - elif tree.tagName == 'graph': + elif tree.tag == 'graph': return 'G{%s}' % ' '.join(variables) else: for (tag, name) in _COLORIZING_TAGS.items(): - if name == tree.tagName: + if name == tree.tag: return '%s{%s}' % (tag, childstr) - raise ValueError('Unknown DOM element %r' % tree.tagName) + raise ValueError('Unknown DOM element %r' % tree.tag) def to_plaintext(tree, indent=0, seclevel=0): """ @@ -1263,7 +1278,7 @@ escaped characters in unescaped form, etc. @param tree: A DOM document encoding of an epytext string. - @type tree: L{xml.dom.minidom.Document} + @type tree: C{Element} @param indent: The indentation for the string representation of C{tree}. Each line of the returned string will begin with C{indent} space characters. @@ -1274,67 +1289,63 @@ @return: The epytext string corresponding to C{tree}. @rtype: C{string} """ - if isinstance(tree, Document): - return to_plaintext(tree.childNodes[0], indent, seclevel) - if isinstance(tree, Text): return tree.data + if isinstance(tree, basestring): return tree - if tree.tagName == 'section': seclevel += 1 + if tree.tag == 'section': seclevel += 1 # Figure out the child indent level. - if tree.tagName == 'epytext': cindent = indent - elif tree.tagName == 'li' and tree.getAttributeNode('bullet'): - cindent = indent + 1 + len(tree.getAttributeNode('bullet').value) + if tree.tag == 'epytext': cindent = indent + elif tree.tag == 'li' and tree.attribs.get('bullet'): + cindent = indent + 1 + len(tree.attribs.get('bullet')) else: cindent = indent + 2 - variables = [to_plaintext(c, cindent, seclevel) for c in tree.childNodes] + variables = [to_plaintext(c, cindent, seclevel) for c in tree.children] childstr = ''.join(variables) - if tree.tagName == 'para': + if tree.tag == 'para': return wordwrap(childstr, indent)+'\n' - elif tree.tagName == 'li': + elif tree.tag == 'li': # We should be able to use getAttribute here; but there's no # convenient way to test if an element has an attribute.. - bulletAttr = tree.getAttributeNode('bullet') - if bulletAttr: bullet = bulletAttr.value - else: bullet = '-' + bullet = tree.attribs.get('bullet') or '-' return indent*' ' + bullet + ' ' + childstr.lstrip() - elif tree.tagName == 'heading': + elif tree.tag == 'heading': uline = len(childstr)*_HEADING_CHARS[seclevel-1] return ((indent-2)*' ' + childstr + '\n' + (indent-2)*' ' + uline + '\n') - elif tree.tagName == 'doctestblock': + elif tree.tag == 'doctestblock': lines = [(indent+2)*' '+line for line in childstr.split('\n')] return '\n'.join(lines) + '\n\n' - elif tree.tagName == 'literalblock': + elif tree.tag == 'literalblock': lines = [(indent+1)*' '+line for line in childstr.split('\n')] return '\n'.join(lines) + '\n\n' - elif tree.tagName == 'fieldlist': + elif tree.tag == 'fieldlist': return childstr - elif tree.tagName == 'field': + elif tree.tag == 'field': numargs = 0 - while tree.childNodes[numargs+1].tagName == 'arg': numargs += 1 + while tree.children[numargs+1].tag == 'arg': numargs += 1 tag = variables[0] args = variables[1:1+numargs] body = variables[1+numargs:] str = (indent)*' '+'@'+variables[0] if args: str += '(' + ', '.join(args) + ')' return str + ':\n' + ''.join(body) - elif tree.tagName == 'uri': + elif tree.tag == 'uri': if len(variables) != 2: raise ValueError('Bad URI ') elif variables[0] == variables[1]: return '<%s>' % variables[1] else: return '%r<%s>' % (variables[0], variables[1]) - elif tree.tagName == 'link': + elif tree.tag == 'link': if len(variables) != 2: raise ValueError('Bad Link') return '%s' % variables[0] - elif tree.tagName in ('olist', 'ulist'): + elif tree.tag in ('olist', 'ulist'): # [xx] always use condensed lists. ## Use a condensed list if each list item is 1 line long. #for child in variables: # if child.count('\n') > 2: return childstr return childstr.replace('\n\n', '\n')+'\n' - elif tree.tagName == 'symbol': + elif tree.tag == 'symbol': return '%s' % childstr - elif tree.tagName == 'graph': + elif tree.tag == 'graph': return '<<%s graph: %s>>' % (variables[0], ', '.join(variables[1:])) else: # Assume that anything else can be passed through. @@ -1348,7 +1359,7 @@ where different blocks begin, along the left margin. @param tree: A DOM document encoding of an epytext string. - @type tree: L{xml.dom.minidom.Document} + @type tree: C{Element} @param indent: The indentation for the string representation of C{tree}. Each line of the returned string will begin with C{indent} space characters. @@ -1359,21 +1370,19 @@ @return: The epytext string corresponding to C{tree}. @rtype: C{string} """ - if isinstance(tree, Document): - return to_debug(tree.childNodes[0], indent, seclevel) - if isinstance(tree, Text): - str = re.sub(r'\{', '\0', tree.data) + if isinstance(tree, basestring): + str = re.sub(r'\{', '\0', tree) str = re.sub(r'\}', '\1', str) return str - if tree.tagName == 'section': seclevel += 1 - variables = [to_debug(c, indent+2, seclevel) for c in tree.childNodes] + if tree.tag == 'section': seclevel += 1 + variables = [to_debug(c, indent+2, seclevel) for c in tree.children] childstr = ''.join(variables) # Clean up for literal blocks (add the double "::" back) childstr = re.sub(':( *\n \|\n)\2', '::\\1', childstr) - if tree.tagName == 'para': + if tree.tag == 'para': str = wordwrap(childstr, indent-6, 69)+'\n' str = re.sub(r'((^|\n)\s*\d+)\.', r'\1E{.}', str) str = re.sub(r'((^|\n)\s*)-', r'\1E{-}', str) @@ -1385,54 +1394,52 @@ lines[0] = ' P>|' + lines[0] lines[1:] = [' |'+l for l in lines[1:]] return '\n'.join(lines)+'\n |\n' - elif tree.tagName == 'li': - bulletAttr = tree.getAttributeNode('bullet') - if bulletAttr: bullet = bulletAttr.value - else: bullet = '-' + elif tree.tag == 'li': + bullet = tree.attribs.get('bullet') or '-' return ' LI>|'+ (indent-6)*' '+ bullet + ' ' + childstr[6:].lstrip() - elif tree.tagName in ('olist', 'ulist'): + elif tree.tag in ('olist', 'ulist'): return 'LIST>|'+(indent-4)*' '+childstr[indent+2:] - elif tree.tagName == 'heading': + elif tree.tag == 'heading': str = re.sub('\0', 'E{lb}', childstr) str = re.sub('\1', 'E{rb}', str) uline = len(childstr)*_HEADING_CHARS[seclevel-1] return ('SEC'+`seclevel`+'>|'+(indent-8)*' ' + str + '\n' + ' |'+(indent-8)*' ' + uline + '\n') - elif tree.tagName == 'doctestblock': + elif tree.tag == 'doctestblock': str = re.sub('\0', '{', childstr) str = re.sub('\1', '}', str) lines = [' |'+(indent-4)*' '+line for line in str.split('\n')] lines[0] = 'DTST>'+lines[0][5:] return '\n'.join(lines) + '\n |\n' - elif tree.tagName == 'literalblock': + elif tree.tag == 'literalblock': str = re.sub('\0', '{', childstr) str = re.sub('\1', '}', str) lines = [' |'+(indent-5)*' '+line for line in str.split('\n')] lines[0] = ' LIT>'+lines[0][5:] return '\2' + '\n'.join(lines) + '\n |\n' - elif tree.tagName == 'field': + elif tree.tag == 'field': numargs = 0 - while tree.childNodes[numargs+1].tagName == 'arg': numargs += 1 + while tree.children[numargs+1].tag == 'arg': numargs += 1 tag = variables[0] args = variables[1:1+numargs] body = variables[1+numargs:] str = ' FLD>|'+(indent-6)*' '+'@'+variables[0] if args: str += '(' + ', '.join(args) + ')' return str + ':\n' + ''.join(body) - elif tree.tagName == 'target': + elif tree.tag == 'target': return '<%s>' % childstr - elif tree.tagName in ('fieldlist', 'tag', 'arg', 'epytext', + elif tree.tag in ('fieldlist', 'tag', 'arg', 'epytext', 'section', 'olist', 'ulist', 'name'): return childstr - elif tree.tagName == 'symbol': + elif tree.tag == 'symbol': return 'E{%s}' % childstr - elif tree.tagName == 'graph': + elif tree.tag == 'graph': return 'G{%s}' % ' '.join(variables) else: for (tag, name) in _COLORIZING_TAGS.items(): - if name == tree.tagName: + if name == tree.tag: return '%s{%s}' % (tag, childstr) - raise ValueError('Unknown DOM element %r' % tree.tagName) + raise ValueError('Unknown DOM element %r' % tree.tag) ################################################## ## Top-Level Wrapper function @@ -1455,7 +1462,7 @@ written to. @type stream: C{stream} @return: a DOM document encoding the contents of C{str}. - @rtype: L{xml.dom.minidom.Document} + @rtype: C{Element} @raise SyntaxError: If any fatal errors were encountered. """ errors = [] @@ -1556,15 +1563,9 @@ @return: A DOM document containing C{str} in a single literal block. - @rtype: L{xml.dom.minidom.Document} + @rtype: C{Element} """ - doc = Document() - epytext = doc.createElement('epytext') - lit = doc.createElement('literalblock') - doc.appendChild(epytext) - epytext.appendChild(lit) - lit.appendChild(doc.createTextNode(str)) - return doc + return Element('epytext', Element('literalblock', str)) def parse_as_para(str): """ @@ -1578,15 +1579,9 @@ @type str: C{string} @return: A DOM document containing C{str} in a single paragraph. - @rtype: L{xml.dom.minidom.Document} + @rtype: C{Element} """ - doc = Document() - epytext = doc.createElement('epytext') - para = doc.createElement('para') - doc.appendChild(epytext) - epytext.appendChild(para) - para.appendChild(doc.createTextNode(str)) - return doc + return Element('epytext', Element('para', str)) ################################################################# ## SUPPORT FOR EPYDOC @@ -1710,8 +1705,6 @@ } def __init__(self, dom_tree): - if isinstance(dom_tree, Document): - dom_tree = dom_tree.childNodes[0] self._tree = dom_tree # Caching: self._html = self._latex = self._plaintext = None @@ -1752,84 +1745,81 @@ def _to_html(self, tree, linker, directory, docindex, context, indent=0, seclevel=0): - if isinstance(tree, Text): - return plaintext_to_html(tree.data) + if isinstance(tree, basestring): + return plaintext_to_html(tree) - if tree.tagName == 'epytext': indent -= 2 - if tree.tagName == 'section': seclevel += 1 + if tree.tag == 'epytext': indent -= 2 + if tree.tag == 'section': seclevel += 1 # Process the variables first. variables = [self._to_html(c, linker, directory, docindex, context, indent+2, seclevel) - for c in tree.childNodes] + for c in tree.children] # Get rid of unnecessary <P>...</P> tags; they introduce extra # space on most browsers that we don't want. for i in range(len(variables)-1): - if (not isinstance(tree.childNodes[i], Text) and - tree.childNodes[i].tagName == 'para' and - (isinstance(tree.childNodes[i+1], Text) or - tree.childNodes[i+1].tagName != 'para')): + if (not isinstance(tree.children[i], basestring) and + tree.children[i].tag == 'para' and + (isinstance(tree.children[i+1], basestring) or + tree.children[i+1].tag != 'para')): variables[i] = ' '*(indent+2)+variables[i][5+indent:-5]+'\n' - if (tree.hasChildNodes() and - not isinstance(tree.childNodes[-1], Text) and - tree.childNodes[-1].tagName == 'para'): + if (tree.children and + not isinstance(tree.children[-1], basestring) and + tree.children[-1].tag == 'para'): variables[-1] = ' '*(indent+2)+variables[-1][5+indent:-5]+'\n' # Construct the HTML string for the variables. childstr = ''.join(variables) # Perform the approriate action for the DOM tree type. - if tree.tagName == 'para': + if tree.tag == 'para': return wordwrap('<p>%s</p>' % childstr, indent) - elif tree.tagName == 'code': + elif tree.tag == 'code': return '<code>%s</code>' % childstr - elif tree.tagName == 'uri': + elif tree.tag == 'uri': return ('<a href="%s" target="_top">%s</a>' % (variables[1], variables[0])) - elif tree.tagName == 'link': + elif tree.tag == 'link': return linker.translate_identifier_xref(variables[1], variables[0]) - elif tree.tagName == 'italic': + elif tree.tag == 'italic': return '<i>%s</i>' % childstr - elif tree.tagName == 'math': + elif tree.tag == 'math': return '<i class="math">%s</i>' % childstr - elif tree.tagName == 'indexed': - term = tree.cloneNode(1) - term.tagName = 'epytext' + elif tree.tag == 'indexed': + term = Element('epytext', *tree.children, **tree.attribs) return linker.translate_indexterm(ParsedEpytextDocstring(term)) #term_key = self._index_term_key(tree) #return linker.translate_indexterm(childstr, term_key) - elif tree.tagName == 'bold': + elif tree.tag == 'bold': return '<b>%s</b>' % childstr - elif tree.tagName == 'ulist': + elif tree.tag == 'ulist': return '%s<ul>\n%s%s</ul>\n' % (indent*' ', childstr, indent*' ') - elif tree.tagName == 'olist': - startAttr = tree.getAttributeNode('start') - if startAttr: start = ' start="%s"' % startAttr.value - else: start = '' + elif tree.tag == 'olist': + start = tree.attribs.get('start') or '' return ('%s<ol%s>\n%s%s</ol>\n' % (indent*' ', start, childstr, indent*' ')) - elif tree.tagName == 'li': + elif tree.tag == 'li': return indent*' '+'<li>\n%s%s</li>\n' % (childstr, indent*' ') - elif tree.tagName == 'heading': + elif tree.tag == 'heading': return ('%s<h%s class="heading">%s</h%s>\n' % ((indent-2)*' ', seclevel, childstr, seclevel)) - elif tree.tagName == 'literalblock': + elif tree.tag == 'literalblock': return '<pre class="literalblock">\n%s\n</pre>\n' % childstr - elif tree.tagName == 'doctestblock': - return doctest_to_html(tree.childNodes[0].data.strip()) - elif tree.tagName == 'fieldlist': + elif tree.tag == 'doctestblock': + return doctest_to_html(tree.children[0].strip()) + elif tree.tag == 'fieldlist': raise AssertionError("There should not be any field lists left") - elif tree.tagName in ('epytext', 'section', 'tag', 'arg', + elif tree.tag in ('epytext', 'section', 'tag', 'arg', 'name', 'target', 'html'): return childstr - elif tree.tagName == 'symbol': - symbol = tree.childNodes[0].data + elif tree.tag == 'symbol': + symbol = tree.children[0] if self.SYMBOL_TO_HTML.has_key(symbol): return '&%s;' % self.SYMBOL_TO_HTML[symbol] else: return '[??]' - elif tree.tagName == 'graph': + elif tree.tag == 'graph': # Generate the graph. graph = self._build_graph(variables[0], variables[1:], linker, docindex, context) @@ -1839,7 +1829,7 @@ image_file = os.path.join(directory, image_url) return graph.to_html(image_file, image_url) else: - raise ValueError('Unknown epytext DOM element %r' % tree.tagName) + raise ValueError('Unknown epytext DOM element %r' % tree.tag) #GRAPH_TYPES = ['classtree', 'packagetree', 'importgraph'] def _build_graph(self, graph_type, graph_args, linker, @@ -1883,27 +1873,27 @@ def _to_latex(self, tree, linker, indent=0, seclevel=0, breakany=0): - if isinstance(tree, Text): - return plaintext_to_latex(tree.data, breakany=breakany) + if isinstance(tree, basestring): + return plaintext_to_latex(tree, breakany=breakany) - if tree.tagName == 'section': seclevel += 1 + if tree.tag == 'section': seclevel += 1 # Figure out the child indent level. - if tree.tagName == 'epytext': cindent = indent + if tree.tag == 'epytext': cindent = indent else: cindent = indent + 2 variables = [self._to_latex(c, linker, cindent, seclevel, breakany) - for c in tree.childNodes] + for c in tree.children] childstr = ''.join(variables) - if tree.tagName == 'para': + if tree.tag == 'para': return wordwrap(childstr, indent)+'\n' - elif tree.tagName == 'code': + elif tree.tag == 'code': return '\\texttt{%s}' % childstr - elif tree.tagName == 'uri': + elif tree.tag == 'uri': if len(variables) != 2: raise ValueError('Bad URI ') if self._hyperref: # ~ and # should not be escaped in the URI. - uri = tree.childNodes[1].childNodes[0].data + uri = tree.children[1].children[0] uri = uri.replace('{\\textasciitilde}', '~') uri = uri.replace('\\#', '#') if variables[0] == variables[1]: @@ -1916,46 +1906,45 @@ return '\\textit{%s}' % variables[1] else: return '%s\\footnote{%s}' % (variables[0], variables[1]) - elif tree.tagName == 'link': + elif tree.tag == 'link': if len(variables) != 2: raise ValueError('Bad Link') return linker.translate_identifier_xref(variables[1], variables[0]) - elif tree.tagName == 'italic': + elif tree.tag == 'italic': return '\\textit{%s}' % childstr - elif tree.tagName == 'math': + elif tree.tag == 'math': return '\\textit{%s}' % childstr - elif tree.tagName == 'indexed': - term = tree.cloneNode(1) - term.tagName = 'epytext' + elif tree.tag == 'indexed': + term = Element('epytext', *tree.children, **tree.attribs) return linker.translate_indexterm(ParsedEpytextDocstring(term)) - elif tree.tagName == 'bold': + elif tree.tag == 'bold': return '\\textbf{%s}' % childstr - elif tree.tagName == 'li': + elif tree.tag == 'li': return indent*' ' + '\\item ' + childstr.lstrip() - elif tree.tagName == 'heading': + elif tree.tag == 'heading': return ' '*(indent-2) + '(section) %s\n\n' % childstr - elif tree.tagName == 'doctestblock': - return doctest_to_latex(tree.childNodes[0].data.strip()) - elif tree.tagName == 'literalblock': + elif tree.tag == 'doctestblock': + return doctest_to_latex(tree.children[0].strip()) + elif tree.tag == 'literalblock': return '\\begin{alltt}\n%s\\end{alltt}\n\n' % childstr - elif tree.tagName == 'fieldlist': + elif tree.tag == 'fieldlist': return indent*' '+'{omitted fieldlist}\n' - elif tree.tagName == 'olist': + elif tree.tag == 'olist': return (' '*indent + '\\begin{enumerate}\n\n' + ' '*indent + '\\setlength{\\parskip}{0.5ex}\n' + childstr + ' '*indent + '\\end{enumerate}\n\n') - elif tree.tagName == 'ulist': + elif tree.tag == 'ulist': return (' '*indent + '\\begin{itemize}\n' + ' '*indent + '\\setlength{\\parskip}{0.6ex}\n' + childstr + ' '*indent + '\\end{itemize}\n\n') - elif tree.tagName == 'symbol': - symbol = tree.childNodes[0].data + elif tree.tag == 'symbol': + symbol = tree.children[0] if self.SYMBOL_TO_LATEX.has_key(symbol): return r'%s' % self.SYMBOL_TO_LATEX[symbol] else: return '[??]' - elif tree.tagName == 'graph': + elif tree.tag == 'graph': return '(GRAPH)' #raise ValueError, 'graph not implemented yet for latex' else: @@ -1964,78 +1953,73 @@ def summary(self): if self._tree is None: return self - - # Is the cloning that happens here safe/proper? (Cloning - # between 2 different documents) tree = self._tree - - doc = Document() - epytext = doc.createElement('epytext') - doc.appendChild(epytext) + doc = Element('epytext') # Find the first paragraph. - variables = tree.childNodes - while (len(variables) > 0) and (variables[0].tagName != 'para'): - if variables[0].tagName in ('section', 'ulist', 'olist', 'li'): - variables = variables[0].childNodes + variables = tree.children + while (len(variables) > 0) and (variables[0].tag != 'para'): + if variables[0].tag in ('section', 'ulist', 'olist', 'li'): + variables = variables[0].children else: variables = variables[1:] # Special case: if the docstring contains a single literal block, # then try extracting the summary from it. - if (len(variables) == 0 and len(tree.childNodes) == 1 and - tree.childNodes[0].tagName == 'literalblock'): + if (len(variables) == 0 and len(tree.children) == 1 and + tree.children[0].tag == 'literalblock'): str = re.split(r'\n\s*(\n|$).*', - tree.childNodes[0].childNodes[0].data, 1)[0] - variables = [doc.createElement('para')] - variables[0].appendChild(doc.createTextNode(str)) + tree.children[0].children[0], 1)[0] + variables = [Element('para')] + variables[0].children.append(str) # If we didn't find a paragraph, return an empty epytext. if len(variables) == 0: return ParsedEpytextDocstring(doc) # Extract the first sentence. - parachildren = variables[0].childNodes - para = doc.createElement('para') - epytext.appendChild(para) + parachildren = variables[0].children + para = Element('para') + doc.children.append(para) for parachild in parachildren: - if isinstance(parachild, Text): - m = re.match(r'(\s*[\w\W]*?\.)(\s|$)', parachild.data) + if isinstance(parachild, basestring): + m = re.match(r'(\s*[\w\W]*?\.)(\s|$)', parachild) if m: - para.appendChild(doc.createTextNode(m.group(1))) + para.children.append(m.group(1)) return ParsedEpytextDocstring(doc) - para.appendChild(parachild.cloneNode(1)) + para.children.append(parachild) return ParsedEpytextDocstring(doc) def split_fields(self, errors=None): if self._tree is None: return (self, ()) - tree = self._tree.cloneNode(1) # Hmm.. + tree = Element(self._tree.tag, *self._tree.children, + **self._tree.attribs) fields = [] - if (tree.hasChildNodes() and - tree.childNodes[-1].tagName == 'fieldlist' and - tree.childNodes[-1].hasChildNodes()): - field_nodes = tree.childNodes[-1].childNodes - tree.removeChild(tree.childNodes[-1]) + if (tree.children and + tree.children[-1].tag == 'fieldlist' and + tree.children[-1].children): + field_nodes = tree.children[-1].children + del tree.children[-1] for field in field_nodes: # Get the tag - tag = field.childNodes[0].childNodes[0].data.lower() - field.removeChild(field.childNodes[0]) + tag = field.children[0].children[0].lower() + del field.children[0] # Get the argument. - if field.childNodes and field.childNodes[0].tagName == 'arg': - arg = field.childNodes[0].childNodes[0].data - field.removeChild(field.childNodes[0]) + if field.children and field.children[0].tag == 'arg': + arg = field.children[0].children[0] + del field.children[0] else: arg = None # Process the field. - field.tagName = 'epytext' + field.tag = 'epytext' fields.append(Field(tag, arg, ParsedEpytextDocstring(field))) # Save the remaining docstring as the description.. - if tree.hasChildNodes() and tree.childNodes[0].hasChildNodes(): + if tree.children and tree.children[0].children: descr = tree else: descr = None @@ -2049,14 +2033,13 @@ return self._terms def _index_terms(self, tree, terms): - if tree is None or isinstance(tree, Text): + if tree is None or isinstance(tree, basestring): return - if tree.tagName == 'indexed': - term = tree.cloneNode(1) - term.tagName = 'epytext' + if tree.tag == 'indexed': + term = Element('epytext', *tree.children, **tree.attribs) terms.append(ParsedEpytextDocstring(term)) # Look for index items in child nodes. - for child in tree.childNodes: + for child in tree.children: self._index_terms(child, terms) Modified: trunk/epydoc/src/epydoc/test/epytext.doctest =================================================================== --- trunk/epydoc/src/epydoc/test/epytext.doctest 2006-09-02 01:23:23 UTC (rev 1343) +++ trunk/epydoc/src/epydoc/test/epytext.doctest 2006-09-02 01:40:35 UTC (rev 1344) @@ -9,16 +9,15 @@ >>> import re >>> def testparse(s): ... # this strips off the <epytext>...</epytext> - ... out = ''.join([n.toxml() for n in - ... epytext.parse(s).childNodes[0].childNodes]) + ... out = ''.join([str(n) for n in + ... epytext.parse(s).children]) ... # This is basically word-wrapping: ... out = re.sub(r'((</\w+>)+)', r'\1\n', out).rstrip() ... out = re.sub(r'(?m)^(.{50,70}>)(.)', r'\1\n\2', out).rstrip() ... return out >>> def checkparse(s, expect): ... # this strips off the <epytext>...</epytext> - ... got = ''.join([n.toxml() for n in - ... epytext.parse(s).childNodes[0].childNodes]) + ... got = ''.join([str(n) for n in epytext.parse(s).children]) ... if got != expect: ... raise ValueError('mismatch: %r %r' % (expect, got)) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ed...@us...> - 2006-09-06 15:49:53
|
Revision: 1351 http://svn.sourceforge.net/epydoc/?rev=1351&view=rev Author: edloper Date: 2006-09-06 08:49:48 -0700 (Wed, 06 Sep 2006) Log Message: ----------- - Replaced VariableDoc._get_canonical_name property with a simple canonical_name attribute. This now gets assigned by docbuilder at the same time as ValueDoc attributes. - When assigning canonical names, don't bother to recurse to GenericValueDoc variables. - Fixed bug in code to merge parsed & introspected GenericValueDocs, which was sometimes causing value repr's to not propagate correctly. Modified Paths: -------------- trunk/epydoc/src/epydoc/apidoc.py trunk/epydoc/src/epydoc/docbuilder.py trunk/epydoc/src/epydoc/docstringparser.py Modified: trunk/epydoc/src/epydoc/apidoc.py =================================================================== --- trunk/epydoc/src/epydoc/apidoc.py 2006-09-04 04:09:52 UTC (rev 1350) +++ trunk/epydoc/src/epydoc/apidoc.py 2006-09-06 15:49:48 UTC (rev 1351) @@ -568,6 +568,12 @@ variable. @type: L{ValueDoc}""" + canonical_name = UNKNOWN + """@ivar: A dotted name that serves as a unique identifier for + this C{VariableDoc}. It should be formed by concatenating + the C{VariableDoc}'s C{container} with its C{name}. + @type: L{DottedName}""" + value = UNKNOWN """@ivar: The API documentation for this variable's value. @type: L{ValueDoc}""" @@ -635,24 +641,6 @@ else: return '<%s>' % self.__class__.__name__ - def _get_canonical_name(self): - # Check cache. - canonical_name = getattr(self, '_canonical_name', None) - if canonical_name is not None: return canonical_name - # Otherwise, compute it. - if (self.container is UNKNOWN or - self.container.canonical_name is UNKNOWN): - return UNKNOWN - else: - self._canonical_name = self.container.canonical_name + self.name - return self._canonical_name - canonical_name = property(_get_canonical_name, - doc="""A read-only property that can be used to get the variable's - canonical name. This is formed by taking the varaible's - container's cannonical name, and adding the variable's name - to it. (The value is cached upon the first successful - look-up.)""") - def _get_defining_module(self): if self.container is UNKNOWN: return UNKNOWN Modified: trunk/epydoc/src/epydoc/docbuilder.py =================================================================== --- trunk/epydoc/src/epydoc/docbuilder.py 2006-09-04 04:09:52 UTC (rev 1350) +++ trunk/epydoc/src/epydoc/docbuilder.py 2006-09-06 15:49:48 UTC (rev 1351) @@ -678,9 +678,12 @@ # them. E.g., we don't want to merge 2+2 with 4. So just copy # the inspect_doc's pyval to the parse_doc, and return the parse_doc. if type(introspect_doc) == type(parse_doc) == GenericValueDoc: - parse_doc.pyval = introspect_doc.pyval + if introspect_doc.pyval is not UNKNOWN: + parse_doc.pyval = introspect_doc.pyval + if introspect_doc.parse_repr is not UNKNOWN: + parse_doc.parse_repr = introspect_doc.parse_repr parse_doc.docs_extracted_by = 'both' - return parse_doc + return parse_doc.merge_and_overwrite(introspect_doc) # Perform several sanity checks here -- if we accidentally # merge values that shouldn't get merged, then bad things can @@ -1008,9 +1011,16 @@ # Recurse to any contained values. if isinstance(val_doc, NamespaceDoc): for var_doc in val_doc.variables.values(): - if var_doc.value is UNKNOWN: continue + # Set the variable's canonical name. varname = DottedName(name, var_doc.name) - + var_doc.canonical_name = varname + + # If the value is unknown, or is a generic value doc, then + # the valuedoc doesn't get assigned a name; move on. + if (var_doc.value is UNKNOWN + or isinstance(var_doc.value, GenericValueDoc)): + continue + # This check is for cases like curses.wrapper, where an # imported variable shadows its value's "real" location. if _var_shadows_self(var_doc, varname): Modified: trunk/epydoc/src/epydoc/docstringparser.py =================================================================== --- trunk/epydoc/src/epydoc/docstringparser.py 2006-09-04 04:09:52 UTC (rev 1350) +++ trunk/epydoc/src/epydoc/docstringparser.py 2006-09-06 15:49:48 UTC (rev 1351) @@ -205,6 +205,9 @@ field.arg(), field.body()) except ValueError, e: field_warnings.append(str(e)) + # [XX] If descr is empty but we have a return field, + # generate a descr from it.. + # Extract a summary if api_doc.summary is None and api_doc.descr is not None: api_doc.summary = api_doc.descr.summary() @@ -607,8 +610,9 @@ def set_var_descr(api_doc, ident, descr): if ident not in api_doc.variables: - api_doc.variables[ident] = VariableDoc(container=api_doc, - name=ident) + api_doc.variables[ident] = VariableDoc( + container=api_doc, name=ident, + canonical_name=api_doc.canonical_name+ident) var_doc = api_doc.variables[ident] if var_doc.descr not in (None, UNKNOWN): @@ -619,8 +623,10 @@ def set_var_type(api_doc, ident, descr): if ident not in api_doc.variables: - api_doc.variables[ident] = VariableDoc(container=api_doc, - name=ident) + api_doc.variables[ident] = VariableDoc( + container=api_doc, name=ident, + canonical_name=api_doc.canonical_name+ident) + var_doc = api_doc.variables[ident] if var_doc.type_descr not in (None, UNKNOWN): raise ValueError(REDEFINED % ('type for '+ident)) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ed...@us...> - 2006-09-06 19:08:25
|
Revision: 1354 http://svn.sourceforge.net/epydoc/?rev=1354&view=rev Author: edloper Date: 2006-09-06 12:08:13 -0700 (Wed, 06 Sep 2006) Log Message: ----------- - Fixed broken crossref links in various docstrings - Updated module docstrings in docparser & docintrospector - Removed unused method HTMLWriter._get_index_terms() - Don't report a failed xref if the identifier in question is a parameter of the current function. - If _HTMLDocstringLinker.translate_identifier_xref can't find a target, then try checking in the contexts of the ancestor classes (in case the docstring was inherited). Modified Paths: -------------- trunk/epydoc/src/epydoc/apidoc.py trunk/epydoc/src/epydoc/checker.py trunk/epydoc/src/epydoc/docbuilder.py trunk/epydoc/src/epydoc/docintrospecter.py trunk/epydoc/src/epydoc/docparser.py trunk/epydoc/src/epydoc/docwriter/dotgraph.py trunk/epydoc/src/epydoc/docwriter/html.py trunk/epydoc/src/epydoc/docwriter/latex.py trunk/epydoc/src/epydoc/gui.py trunk/epydoc/src/epydoc/log.py Modified: trunk/epydoc/src/epydoc/apidoc.py =================================================================== --- trunk/epydoc/src/epydoc/apidoc.py 2006-09-06 15:51:15 UTC (rev 1353) +++ trunk/epydoc/src/epydoc/apidoc.py 2006-09-06 19:08:13 UTC (rev 1354) @@ -813,8 +813,8 @@ dictionary mapping from identifiers to C{VariableDoc}s. This dictionary contains all names defined by the namespace, including imported variables, aliased variables, and variables - inherited from base classes (once L{DocInheriter - <epydoc.docinheriter.DocInheriter>} has added them). + inherited from base classes (once L{inherit_docs() + <epydoc.docbuilder.inherit_docs>} has added them). @type: C{dict} from C{string} to L{VariableDoc}""" sorted_variables = UNKNOWN """@ivar: A list of all variables defined by this @@ -1048,8 +1048,8 @@ @require: The L{sorted_variables}, L{variable_groups}, and L{submodule_groups} attributes must be initialized before - this method can be used. See L{init_sorted_variables()} - and L{init_groups()}. + this method can be used. See L{init_sorted_variables()}, + L{init_variable_groups()}, and L{init_submodule_groups()}. @param value_type: A string specifying the value type for which variables should be returned. Valid values are: @@ -1225,7 +1225,7 @@ @require: The L{sorted_variables} and L{variable_groups} attributes must be initialized before this method can be used. See L{init_sorted_variables()} and - L{init_groups()}. + L{init_variable_groups()}. @param value_type: A string specifying the value type for which variables should be returned. Valid values are: @@ -1365,7 +1365,7 @@ """@ivar: A list of names of decorators that were applied to this routine, in the order that they are listed in the source code. (I.e., in the reverse of the order that they were applied in.) - @type: C{list} of L{string}""" + @type: C{list} of C{string}""" #} end of "decorators" group #{ Information Extracted from Docstrings @@ -1543,7 +1543,7 @@ """A mapping from C{profile} function ids to corresponding C{APIDoc} objects. A function id is a tuple of the form C{(filename, lineno, funcname)}. This is used to update - the L{callers} and L{calleeds} variables.""" + the L{callers} and L{callees} variables.""" self._container_cache = {} """A cache for the L{container()} method, to increase speed.""" Modified: trunk/epydoc/src/epydoc/checker.py =================================================================== --- trunk/epydoc/src/epydoc/checker.py 2006-09-06 15:51:15 UTC (rev 1353) +++ trunk/epydoc/src/epydoc/checker.py 2006-09-06 19:08:13 UTC (rev 1354) @@ -53,7 +53,7 @@ - Public/private specifiers indicate whether public or private objects should be checked: L{PRIVATE}. - Check specifiers indicate what checks should be run on the - objects: L{TYPE}; L{DESCR}; L{DESCR_LAZY}; L{AUTHOR}; + objects: L{TYPE}; L{DESCR}; L{AUTHOR}; and L{VERSION}. The L{check} method is used to perform a check on the @@ -66,7 +66,7 @@ values together: >>> checker.check(DocChecker.MODULE | DocChecker.CLASS | - ... DocChecker.FUNC | DocChecker.DESCR_LAZY) + ... DocChecker.FUNC ) @group Types: MODULE, CLASS, FUNC, VAR, IVAR, CVAR, PARAM, RETURN, ALL_T @@ -98,7 +98,7 @@ @cvar ALL_T: Type specifier that indicates that the documentation of all objects should be checked. - @group Checks: TYPE, AUTHOR, VERSION, DESCR_LAZY, DESCR, ALL_C + @group Checks: TYPE, AUTHOR, VERSION, DESCR, ALL_C @type TYPE: C{int} @cvar TYPE: Check specifier that indicates that every variable and parameter should have a C{@type} field. @@ -235,7 +235,7 @@ L{_check_class}, and L{_check_func}. @param doc: The documentation that should be checked. - @type doc: L{ObjDoc} + @type doc: L{APIDoc} @rtype: C{None} """ if ((self._checks & DocChecker.DESCR) and @@ -287,7 +287,7 @@ whose name is C{name}. @param doc: The documentation for the variable to check. - @type doc: L{Doc} + @type doc: L{APIDoc} @param check_type: Whether or not the variable's type should be checked. This is used to allow varargs and keyword parameters to have no type specified. Modified: trunk/epydoc/src/epydoc/docbuilder.py =================================================================== --- trunk/epydoc/src/epydoc/docbuilder.py 2006-09-06 15:51:15 UTC (rev 1353) +++ trunk/epydoc/src/epydoc/docbuilder.py 2006-09-06 19:08:13 UTC (rev 1354) @@ -36,7 +36,7 @@ The main interface to C{epydoc.docbuilder} consists of two functions: - - L{build_docs()} -- Builds documentation for a single item, and + - L{build_doc()} -- Builds documentation for a single item, and returns it as an L{APIDoc} object. - L{build_doc_index()} -- Builds documentation for a collection of items, and returns it as a L{DocIndex} object. @@ -57,7 +57,7 @@ ###################################################################### ## Contents ###################################################################### -## 1. build_docs() -- the main interface. +## 1. build_doc() & build_doc_index() -- the main interface. ## 2. merge_docs() -- helper, used to merge parse & introspect info ## 3. link_imports() -- helper, used to connect imported vars w/ values ## 4. assign_canonical_names() -- helper, used to set canonical names @@ -77,7 +77,7 @@ from epydoc.compat import * # Backwards compatibility ###################################################################### -## 1. build_docs() +## 1. build_doc() ###################################################################### def build_doc(item, introspect=True, parse=True, add_submodules=True): Modified: trunk/epydoc/src/epydoc/docintrospecter.py =================================================================== --- trunk/epydoc/src/epydoc/docintrospecter.py 2006-09-06 15:51:15 UTC (rev 1353) +++ trunk/epydoc/src/epydoc/docintrospecter.py 2006-09-06 19:08:13 UTC (rev 1354) @@ -10,12 +10,14 @@ Extract API documentation about python objects by directly introspecting their values. -L{DocIntrospecter} is a processing class that examines Python objects via -introspection, and uses the information it finds to create L{APIDoc} -objects containing the API documentation for those objects. +The function L{introspect_docs()}, which provides the main interface +of this module, examines a Python objects via introspection, and uses +the information it finds to create an L{APIDoc} objects containing the +API documentation for that objects. -C{DocIntrospecter} can be subclassed to extend the set of object types -that it supports. +The L{register_introspecter()} method can be used to extend the +functionality of C{docintrospector}, by providing methods that handle +special value types. """ __docformat__ = 'epytext en' @@ -61,41 +63,6 @@ ## Introspection ###################################################################### -# [xx] old: -""" -An API documentation extractor based on introspecting python values. -C{DocIntrospecter} examines Python objects via introspection, and uses -the information it finds to create L{APIDoc} objects containing -the API documentation for those objects. The main interface to -the C{DocIntrospecter} class is the L{introspect} method, which takes a -Python value, and returns an L{APIDoc} that describes it. - -Currently, C{DocIntrospecter} can extract documentation information -from the following object types: - - - modules - - classes - - functions - - methods - - class methods - - static methods - - builtin routines - - properties - -Subclassing -=========== -C{DocIntrospecter} can be subclassed, to extend the set of object -types that it supports. C{DocIntrospecter} can be extended in -several different ways: - - - A subclass can override one of the existing introspection methods - to modify the behavior for a currently supported object type. - - - A subclass can add support for a new type by adding a new - introspection method; and extending L{introspecter_method()} to - return that method for values of the new type. -""" - def introspect_docs(value=None, name=None, filename=None, context=None, is_script=False): """ Modified: trunk/epydoc/src/epydoc/docparser.py =================================================================== --- trunk/epydoc/src/epydoc/docparser.py 2006-09-06 15:51:15 UTC (rev 1353) +++ trunk/epydoc/src/epydoc/docparser.py 2006-09-06 19:08:13 UTC (rev 1354) @@ -10,13 +10,42 @@ Extract API documentation about python objects by parsing their source code. -L{DocParser} is a processing class that reads the Python source code -for one or more modules, and uses it to create L{APIDoc} objects -containing the API documentation for the variables and values defined -in those modules. +The function L{parse_docs()}, which provides the main interface +of this module, reads and parses the Python source code for a +module, and uses it to create an L{APIDoc} object containing +the API documentation for the variables and values defined in +that modules. -C{DocParser} can be subclassed to extend the set of source code -constructions that it supports. +Currently, C{parse_docs()} extracts documentation from the following +source code constructions: + + - module docstring + - import statements + - class definition blocks + - function definition blocks + - assignment statements + - simple assignment statements + - assignment statements with multiple C{'='}s + - assignment statements with unpacked left-hand sides + - assignment statements that wrap a function in classmethod + or staticmethod. + - assignment to special variables __path__, __all__, and + __docformat__. + - delete statements + +C{parse_docs()} does not yet support the following source code +constructions: + + - assignment statements that create properties + +By default, C{parse_docs()} will expore the contents of top-level +C{try} and C{if} blocks. If desired, C{parse_docs()} can also +be configured to explore the contents of C{while} and C{for} blocks. +(See the configuration constants, below.) + +@todo: Make it possible to extend the functionality of C{parse_docs()}, + by replacing process_line with a dispatch table that can be + customized (similarly to C{docintrospector.register_introspector()}). """ __docformat__ = 'epytext en' @@ -59,52 +88,6 @@ C{ValueDoc} objects. @type: C{dict}""" -# [xx] outdated: -""" -An API documentation extractor based on source code parsing. -C{DocParser} reads and parses the Python source code for one or -more modules, and uses it to create L{APIDoc} objects containing -the API documentation for the variables and values defined in -those modules. The main interface method is L{parse()}, which -returns the documentation for an object with a given dotted name, -or a module with a given filename. - -Currently, C{DocParser} extracts documentation from the following -source code constructions: - - - module docstring - - import statements - - class definition blocks - - function definition blocks - - assignment statements - - simple assignment statements - - assignment statements with multiple C{'='}s - - assignment statements with unpacked left-hand sides - - assignment statements that wrap a function in classmethod - or staticmethod. - - assignment to special variables __path__, __all__, and - __docformat__. - - delete statements - -C{DocParser} does not yet support the following source code -constructions: - - - assignment statements that create properties - -By default, C{DocParser} will expore the contents of top-level -C{try} and C{if} blocks. If desired, C{DocParser} can also -be told to explore the contents of C{while} and C{for} blocks. - -Subclassing -=========== -C{DocParser} can be subclassed, to extend the set of source code -constructions that it supports. C{DocParser} can be extended in -several different ways: - - - [XX] fill this in! - -""" - #//////////////////////////////////////////////////////////// # Configuration Constants #//////////////////////////////////////////////////////////// @@ -211,7 +194,7 @@ C{ModuleDoc} describing its contents. @param name: The fully-qualified python dotted name of any value (including packages, modules, classes, and - functions). C{DocParser} will automatically figure out + functions). C{parse_docs()} will automatically figure out which module(s) it needs to parse in order to find the documentation for the specified object. @param context: The API documentation for the package that Modified: trunk/epydoc/src/epydoc/docwriter/dotgraph.py =================================================================== --- trunk/epydoc/src/epydoc/docwriter/dotgraph.py 2006-09-06 15:51:15 UTC (rev 1353) +++ trunk/epydoc/src/epydoc/docwriter/dotgraph.py 2006-09-06 19:08:13 UTC (rev 1354) @@ -45,7 +45,7 @@ class DotGraph: """ - A `dot` directed graph. The contents of the graph are + A ``dot`` directed graph. The contents of the graph are constructed from the following instance variables: - `nodes`: A list of `DotGraphNode`\\s, encoding the nodes @@ -62,7 +62,7 @@ The `link()` method can be used to resolve crossreference links within the graph. In particular, if the 'href' attribute of any - node or edge is assigned a value of the form `<name>`, then it + node or edge is assigned a value of the form ``<name>``, then it will be replaced by the URL of the object with that name. This applies to the `body` as well as the `nodes` and `edges`. @@ -190,8 +190,8 @@ def link(self, docstring_linker): """ - Replace any href attributes whose value is <name> with - the url of the object whose name is <name>. + Replace any href attributes whose value is ``<name>`` with + the url of the object whose name is ``<name>``. """ # Link xrefs in nodes self._link_href(self.node_defaults, docstring_linker) @@ -237,7 +237,7 @@ def render(self, language='gif'): """ Use the ``dot`` command to render this graph, using the output - format `language`. Return the result as a string, or `None` + format `language`. Return the result as a string, or ``None`` if the rendering failed. """ return self._run_dot('-T%s' % language) @@ -394,7 +394,7 @@ `class_doc`. :Parameters: - `linker` : `DocstringLinker<markup.DocstringLinker>` + `linker` : `markup.DocstringLinker` Used to look up URLs for classes. `context` : `APIDoc` The context in which this node will be drawn; dotted @@ -526,7 +526,7 @@ labelled with the variable name, and marked with '0..1' at the type end of the edge. - The edges created by `link_attribute()` are handled internally + The edges created by `link_attributes()` are handled internally by `DotGraphUmlClassNode`; they should *not* be added directly to the `DotGraph`. @@ -594,7 +594,7 @@ def _add_attribute_edge(self, var, nodes, type_str, **attribs): """ - Helper for `link_attribute()`: try to add an edge for the + Helper for `link_attributes()`: try to add an edge for the given attribute variable `var`. Return ``True`` if successful. """ @@ -1156,12 +1156,12 @@ def call_graph(api_docs, docindex, linker, context=None, **options): """ :param options: - - `dir`: rankdir for the graph. (default=LR) - - `add_callers`: also include callers for any of the - routines in `api_docs`. (default=False) - - `add_callees`: also include callees for any of the - routines in `api_docs`. (default=False) - :todo: Add an `exclude` option? + - ``dir``: rankdir for the graph. (default=LR) + - ``add_callers``: also include callers for any of the + routines in ``api_docs``. (default=False) + - ``add_callees``: also include callees for any of the + routines in ``api_docs``. (default=False) + :todo: Add an ``exclude`` option? """ if docindex.callers is None: log.warning("No profiling information for call graph!") Modified: trunk/epydoc/src/epydoc/docwriter/html.py =================================================================== --- trunk/epydoc/src/epydoc/docwriter/html.py 2006-09-06 15:51:15 UTC (rev 1353) +++ trunk/epydoc/src/epydoc/docwriter/html.py 2006-09-06 19:08:13 UTC (rev 1354) @@ -2488,7 +2488,7 @@ def contextual_label(self, doc, context): """ - Return the label for L{doc} to be shown in C{context}. + Return the label for C{doc} to be shown in C{context}. """ if doc.canonical_name is None: if doc.parse_repr is not None: @@ -2819,23 +2819,6 @@ index.setdefault(arg, []).append( (doc, descr_list) ) return index - def _get_index_terms(self, parsed_docstring, link, terms, links): - """ - A helper function for L{_extract_term_index}. - - For each index term M{t} with key M{k} in C{parsed_docstring}, - modify C{terms} and C{links} as follows: - - Set C{terms[M{k}] = t} (if C{terms[M{k}]} doesn't exist). - - Append C{link} to C{links[M{k}]}. - """ - if parsed_docstring in (None, UNKNOWN): return - for term in parsed_docstring.index_terms(): - key = self._term_index_to_anchor(term) - if not terms.has_key(key): - terms[key] = term - links[key] = [] - links[key].append(link) - def _term_index_to_anchor(self, term): """ Given the name of an inline index item, construct a URI anchor. @@ -3169,6 +3152,16 @@ # Find the APIDoc for it (if it's available). doc = self.docindex.find(identifier, self.container) + # If we didn't find a target, then try checking in the contexts + # of the ancestor classes. + if doc is None and isinstance(self.container, RoutineDoc): + container = self.docindex.get_vardoc( + self.container.canonical_name) + while (doc is None and container not in (None, UNKNOWN) + and container.overrides not in (None, UNKNOWN)): + container = container.overrides + doc = self.docindex.find(identifier, container) + # Translate it into HTML. if doc is None: self._failed_xref(identifier) @@ -3196,6 +3189,12 @@ def _failed_xref(self, identifier): """Add an identifier to the htmlwriter's failed crossreference list.""" + # Don't count it as a failed xref if it's a parameter of the + # current function. + if (isinstance(self.container, RoutineDoc) and + identifier in self.container.all_args()): + return + failed_xrefs = self.htmlwriter._failed_xrefs context = self.container.canonical_name failed_xrefs.setdefault(identifier,{})[context] = 1 Modified: trunk/epydoc/src/epydoc/docwriter/latex.py =================================================================== --- trunk/epydoc/src/epydoc/docwriter/latex.py 2006-09-06 15:51:15 UTC (rev 1353) +++ trunk/epydoc/src/epydoc/docwriter/latex.py 2006-09-06 19:08:13 UTC (rev 1354) @@ -428,7 +428,7 @@ def write_module_tree_item(self, out, doc, depth=0): """ - Helper function for L{_module_tree} and L{_module_list}. + Helper function for L{write_module_tree} and L{write_module_list}. @rtype: C{string} """ Modified: trunk/epydoc/src/epydoc/gui.py =================================================================== --- trunk/epydoc/src/epydoc/gui.py 2006-09-06 15:51:15 UTC (rev 1353) +++ trunk/epydoc/src/epydoc/gui.py 2006-09-06 19:08:13 UTC (rev 1354) @@ -208,7 +208,7 @@ @param options: The options to use for generating documentation. This includes keyword options that can be given to - L{html.HTMLFormatter}, as well as the option C{target}, which + L{docwriter.html.HTMLWriter}, as well as the option C{target}, which controls where the output is written to. @type options: C{dictionary} """ Modified: trunk/epydoc/src/epydoc/log.py =================================================================== --- trunk/epydoc/src/epydoc/log.py 2006-09-06 15:51:15 UTC (rev 1353) +++ trunk/epydoc/src/epydoc/log.py 2006-09-06 19:08:13 UTC (rev 1354) @@ -70,13 +70,13 @@ def start_block(self, header): """ - Start a new message block. Any calls to L{info}, L{warn}, or - L{error} that occur between a call to C{start_block} and a - corresponding call to C{end_block} will be grouped together, - and displayed with a common header. C{start_block} can be - called multiple times (to form nested blocks), but every call - to C{start_block} I{must} be balanced by a call to - C{end_block}. + Start a new message block. Any calls to L{info()}, + L{warning()}, or L{error()} that occur between a call to + C{start_block} and a corresponding call to C{end_block} will + be grouped together, and displayed with a common header. + C{start_block} can be called multiple times (to form nested + blocks), but every call to C{start_block} I{must} be balanced + by a call to C{end_block}. """ def end_block(self): This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ed...@us...> - 2006-09-06 20:06:27
|
Revision: 1358 http://svn.sourceforge.net/epydoc/?rev=1358&view=rev Author: edloper Date: 2006-09-06 13:06:18 -0700 (Wed, 06 Sep 2006) Log Message: ----------- - If the summary is empty, but the return field is not, then generate a summary from 'Return'+return_descr - Fixed bug in details display w/ empty descr - split_fields() now returns None for the body if the body is empty (i.e., the dosctring contained only fields) Modified Paths: -------------- trunk/epydoc/src/epydoc/docstringparser.py trunk/epydoc/src/epydoc/docwriter/html.py trunk/epydoc/src/epydoc/docwriter/html_css.py trunk/epydoc/src/epydoc/markup/__init__.py trunk/epydoc/src/epydoc/markup/epytext.py trunk/epydoc/src/epydoc/markup/javadoc.py trunk/epydoc/src/epydoc/markup/restructuredtext.py Modified: trunk/epydoc/src/epydoc/docstringparser.py =================================================================== --- trunk/epydoc/src/epydoc/docstringparser.py 2006-09-06 19:29:30 UTC (rev 1357) +++ trunk/epydoc/src/epydoc/docstringparser.py 2006-09-06 20:06:18 UTC (rev 1358) @@ -205,13 +205,16 @@ field.arg(), field.body()) except ValueError, e: field_warnings.append(str(e)) - # [XX] If descr is empty but we have a return field, - # generate a descr from it.. - # Extract a summary if api_doc.summary is None and api_doc.descr is not None: api_doc.summary = api_doc.descr.summary() + # If the summary is empty, but the return field is not, then use + # the return field to generate a summary description. + if (isinstance(api_doc, RoutineDoc) and api_doc.summary is None and + api_doc.return_descr is not None): + api_doc.summary = RETURN_PDS + api_doc.return_descr.summary() + # [XX] Make sure we don't have types/param descrs for unknown # vars/params? @@ -321,6 +324,11 @@ # End the message block. log.end_block() +RETURN_PDS = markup.parse('Returns', markup='epytext') +"""A ParsedDocstring containing the text 'Returns'. This is used to +construct summary descriptions for routines that have empty C{descr}, +but non-empty C{return_descr}.""" + ###################################################################### #{ Field Processing Error Messages ###################################################################### Modified: trunk/epydoc/src/epydoc/docwriter/html.py =================================================================== --- trunk/epydoc/src/epydoc/docwriter/html.py 2006-09-06 19:29:30 UTC (rev 1357) +++ trunk/epydoc/src/epydoc/docwriter/html.py 2006-09-06 20:06:18 UTC (rev 1358) @@ -1974,6 +1974,8 @@ def write_details_entry(self, out, var_doc): descr = self.descr(var_doc, indent=2) + if descr: descr = '<br />'+descr + else: descr = '' if var_doc.is_public: div_class = '' else: div_class = ' class="private"' Modified: trunk/epydoc/src/epydoc/docwriter/html_css.py =================================================================== --- trunk/epydoc/src/epydoc/docwriter/html_css.py 2006-09-06 19:29:30 UTC (rev 1357) +++ trunk/epydoc/src/epydoc/docwriter/html_css.py 2006-09-06 20:06:18 UTC (rev 1358) @@ -84,6 +84,8 @@ h1.epydoc { margin: 0; font-size: +140%; font-weight: bold; } h2.epydoc { font-size: +130%; font-weight: bold; } h3.epydoc { font-size: +115%; font-weight: bold; } +td h3.epydoc { font-size: +115%; font-weight: bold; + margin-bottom: 0; } table.navbar { background: #a0c0ff; color: #000000; border: 2px groove #c0d0d0; } table.navbar table { color: #000000; } Modified: trunk/epydoc/src/epydoc/markup/__init__.py =================================================================== --- trunk/epydoc/src/epydoc/markup/__init__.py 2006-09-06 19:29:30 UTC (rev 1357) +++ trunk/epydoc/src/epydoc/markup/__init__.py 2006-09-06 20:06:18 UTC (rev 1358) @@ -245,7 +245,8 @@ @return: A tuple C{(M{body}, M{fields})}, where C{M{body}} is the main body of this docstring, and C{M{fields}} is a list - of its fields. + of its fields. If the resulting body is empty, return + C{None} for the body. @rtype: C{(L{ParsedDocstring}, list of L{Field})} @param errors: A list where any errors generated during splitting will be stored. If no list is specified, then @@ -336,7 +337,8 @@ ################################################## class ConcatenatedDocstring: def __init__(self, *parsed_docstrings): - self._parsed_docstrings = parsed_docstrings + self._parsed_docstrings = [pds for pds in parsed_docstrings + if pds is not None] def split_fields(self, errors=None): bodies = [] Modified: trunk/epydoc/src/epydoc/markup/epytext.py =================================================================== --- trunk/epydoc/src/epydoc/markup/epytext.py 2006-09-06 19:29:30 UTC (rev 1357) +++ trunk/epydoc/src/epydoc/markup/epytext.py 2006-09-06 20:06:18 UTC (rev 1358) @@ -2020,11 +2020,10 @@ # Save the remaining docstring as the description.. if tree.children and tree.children[0].children: - descr = tree + return ParsedEpytextDocstring(tree), fields else: - descr = None + return None, fields - return ParsedEpytextDocstring(descr), fields def index_terms(self): if self._terms is None: Modified: trunk/epydoc/src/epydoc/markup/javadoc.py =================================================================== --- trunk/epydoc/src/epydoc/markup/javadoc.py 2006-09-06 19:29:30 UTC (rev 1357) +++ trunk/epydoc/src/epydoc/markup/javadoc.py 2006-09-06 20:06:18 UTC (rev 1358) @@ -146,7 +146,10 @@ parsed_body = ParsedJavadocDocstring(body) fields.append(Field(tag, arg, parsed_body)) - return (descr, fields) + if pieces[0].strip(): + return (descr, fields) + else: + return (None, fields) #//////////////////////////////////////////////////////////// # HTML Output. Modified: trunk/epydoc/src/epydoc/markup/restructuredtext.py =================================================================== --- trunk/epydoc/src/epydoc/markup/restructuredtext.py 2006-09-06 19:29:30 UTC (rev 1357) +++ trunk/epydoc/src/epydoc/markup/restructuredtext.py 2006-09-06 20:06:18 UTC (rev 1358) @@ -164,7 +164,10 @@ # Inherit docs visitor = _SplitFieldsTranslator(self._document, errors) self._document.walk(visitor) - return self, visitor.fields + if len(self._document.children) > 0: + return self, visitor.fields + else: + return None, visitor.fields def summary(self): # Inherit docs This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ed...@us...> - 2006-09-07 15:55:46
|
Revision: 1368 http://svn.sourceforge.net/epydoc/?rev=1368&view=rev Author: edloper Date: 2006-09-07 08:55:42 -0700 (Thu, 07 Sep 2006) Log Message: ----------- - Docstring fixes Modified Paths: -------------- trunk/epydoc/src/epydoc/docintrospecter.py trunk/epydoc/src/epydoc/docwriter/html.py trunk/epydoc/src/epydoc/log.py Modified: trunk/epydoc/src/epydoc/docintrospecter.py =================================================================== --- trunk/epydoc/src/epydoc/docintrospecter.py 2006-09-07 15:55:15 UTC (rev 1367) +++ trunk/epydoc/src/epydoc/docintrospecter.py 2006-09-07 15:55:42 UTC (rev 1368) @@ -758,7 +758,7 @@ """ Given a name, return the corresponding value. - @param globals: A namespace to check for the value, if there is no + @param globs: A namespace to check for the value, if there is no module containing the named value. Defaults to __builtin__. """ name = DottedName(name) Modified: trunk/epydoc/src/epydoc/docwriter/html.py =================================================================== --- trunk/epydoc/src/epydoc/docwriter/html.py 2006-09-07 15:55:15 UTC (rev 1367) +++ trunk/epydoc/src/epydoc/docwriter/html.py 2006-09-07 15:55:42 UTC (rev 1368) @@ -208,7 +208,7 @@ Construct a new HTML writer, using the given documentation index. - @param docmap: The documentation index. + @param docindex: The documentation index. @type prj_name: C{string} @keyword prj_name: The name of the project. Defaults to @@ -286,7 +286,7 @@ inherited objects are mixed in with non-inherited objects. The default is 'grouped'. @type include_source_code: C{boolean} - @param include_sourc_ecode: If true, then generate colorized + @keyword include_source_code: If true, then generate colorized source code files for each python module. """ self.docindex = docindex Modified: trunk/epydoc/src/epydoc/log.py =================================================================== --- trunk/epydoc/src/epydoc/log.py 2006-09-07 15:55:15 UTC (rev 1367) +++ trunk/epydoc/src/epydoc/log.py 2006-09-07 15:55:42 UTC (rev 1368) @@ -107,7 +107,7 @@ """ Update the progress display. - @param progress: A float from 0.0 to 1.0, indicating how much + @param percent: A float from 0.0 to 1.0, indicating how much progress has been made. @param message: A message indicating the most recent action that contributed towards that progress. This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <dva...@us...> - 2006-09-07 18:41:29
|
Revision: 1370 http://svn.sourceforge.net/epydoc/?rev=1370&view=rev Author: dvarrazzo Date: 2006-09-07 11:41:20 -0700 (Thu, 07 Sep 2006) Log Message: ----------- Documentation for __slots__ class variable is not generated. Modified Paths: -------------- trunk/epydoc/src/epydoc/docintrospecter.py trunk/epydoc/src/epydoc/docparser.py trunk/epydoc/src/epydoc/test/docintrospecter.doctest trunk/epydoc/src/epydoc/test/docparser.doctest Modified: trunk/epydoc/src/epydoc/docintrospecter.py =================================================================== --- trunk/epydoc/src/epydoc/docintrospecter.py 2006-09-07 15:56:54 UTC (rev 1369) +++ trunk/epydoc/src/epydoc/docintrospecter.py 2006-09-07 18:41:20 UTC (rev 1370) @@ -295,7 +295,7 @@ #: A list of class variables that should not be included in a #: class's API documentation. UNDOCUMENTED_CLASS_VARS = ( - '__doc__', '__module__', '__dict__', '__weakref__') + '__doc__', '__module__', '__dict__', '__weakref__', '__slots__') def introspect_class(cls, class_doc): """ Modified: trunk/epydoc/src/epydoc/docparser.py =================================================================== --- trunk/epydoc/src/epydoc/docparser.py 2006-09-07 15:56:54 UTC (rev 1369) +++ trunk/epydoc/src/epydoc/docparser.py 2006-09-07 18:41:20 UTC (rev 1370) @@ -1078,6 +1078,11 @@ if lhs_name is not None: lhs_parent = get_lhs_parent(lhs_name, parent_docs) if lhs_parent is None: continue + + # Skip a special class variable. + if lhs_name[-1] == '__slots__': + continue + # Create the VariableDoc. var_doc = VariableDoc(name=lhs_name[-1], value=rhs_val, is_imported=False, is_alias=is_alias, Modified: trunk/epydoc/src/epydoc/test/docintrospecter.doctest =================================================================== --- trunk/epydoc/src/epydoc/test/docintrospecter.doctest 2006-09-07 15:56:54 UTC (rev 1369) +++ trunk/epydoc/src/epydoc/test/docintrospecter.doctest 2006-09-07 18:41:20 UTC (rev 1370) @@ -374,7 +374,28 @@ +- docstring = u'calculated base' +- variables = {} +Some class variable have a special meaning. The ``__slots__`` variable isn't +very useful and should be discarded. + >>> runintrospecter(s=""" + ... class Foo: + ... __slots__ = ['bar'] + ... def __init__(self): + ... self.bar = 0 + ... """, + ... attribs="variables name value") + ModuleDoc for epydoc_test [0] + +- variables + +- Foo => VariableDoc for epydoc_test.Foo [1] + +- name = 'Foo' + +- value + +- ClassDoc for epydoc_test.Foo [2] + +- variables + +- __init__ => VariableDoc for epydoc_test.Foo.__init__ [3] + +- name = '__init__' + +- value + +- RoutineDoc [4] + Delete Statements ================= Modified: trunk/epydoc/src/epydoc/test/docparser.doctest =================================================================== --- trunk/epydoc/src/epydoc/test/docparser.doctest 2006-09-07 15:56:54 UTC (rev 1369) +++ trunk/epydoc/src/epydoc/test/docparser.doctest 2006-09-07 18:41:20 UTC (rev 1370) @@ -229,6 +229,28 @@ +- GenericValueDoc [2] +- parse_repr = u'33' +Some class variable have a special meaning. The ``__slots__`` variable isn't +very useful and should be discarded. + + >>> runparser(s=""" + ... class Foo: + ... __slots__ = ['bar'] + ... def __init__(self): + ... self.bar = 0 + ... """, + ... attribs="variables name value") + ModuleDoc for test [0] + +- variables + +- Foo => VariableDoc for test.Foo [1] + +- name = u'Foo' + +- value + +- ClassDoc for test.Foo [2] + +- variables + +- __init__ => VariableDoc for test.Foo.__init__ [3] + +- name = u'__init__' + +- value + +- RoutineDoc for test.Foo.__init__ [4] + Module Control Blocks ===================== DocParser will look inside certain types of module-level control This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <dva...@us...> - 2006-09-11 13:42:21
|
Revision: 1384 http://svn.sourceforge.net/epydoc/?rev=1384&view=rev Author: dvarrazzo Date: 2006-09-11 06:42:10 -0700 (Mon, 11 Sep 2006) Log Message: ----------- - Introspecter doesn't document the object resulting from a "from __future__" statement. Because the check deals with the implementation of a quasi-magic module, guard from unexpected implementation changes. In such case, issue a single warning and don't barf. The current implementation is tested with Python 2.4.3. Modified Paths: -------------- trunk/epydoc/src/epydoc/docintrospecter.py trunk/epydoc/src/epydoc/test/docintrospecter.doctest Modified: trunk/epydoc/src/epydoc/docintrospecter.py =================================================================== --- trunk/epydoc/src/epydoc/docintrospecter.py 2006-09-11 12:34:31 UTC (rev 1383) +++ trunk/epydoc/src/epydoc/docintrospecter.py 2006-09-11 13:42:10 UTC (rev 1384) @@ -255,6 +255,10 @@ container=module_doc, docs_extracted_by='introspecter') elif container is None or module_doc.canonical_name is UNKNOWN: + + # Don't introspect stuff "from __future__" + if is_future_feature(child): continue + # Possibly imported variable. child_val_doc = introspect_docs(child, context=module_doc) child_var_doc = VariableDoc(name=child_name, @@ -483,6 +487,31 @@ """ return isinstance(object, (TypeType, ClassType)) +__future_check_works = None + +def is_future_feature(object): + """ + Return True if C{object} results from a C{from __future__ import feature"} + statement. + """ + # Guard from unexpected implementation changes of the __future__ module. + global __future_check_works + if __future_check_works is not None: + if __future_check_works: + import __future__ + return isinstance(object, __future__._Feature) + else: + return False + else: + __future_check_works = True + try: + return is_future_feature(object) + except: + __future_check_works = False + log.warning("Troubles inspecting __future__. Python implementation" + " may have been changed.") + return False + def get_docstring(value): """ Return the docstring for the given value; or C{None} if it Modified: trunk/epydoc/src/epydoc/test/docintrospecter.doctest =================================================================== --- trunk/epydoc/src/epydoc/test/docintrospecter.doctest 2006-09-11 12:34:31 UTC (rev 1383) +++ trunk/epydoc/src/epydoc/test/docintrospecter.doctest 2006-09-11 13:42:10 UTC (rev 1384) @@ -520,6 +520,16 @@ +- ModuleDoc for re [4] +- variables = {} + >>> runintrospecter(s=""" + ... from __future__ import division + ... from re import match + ... """, + ... attribs='variables value') + ModuleDoc for epydoc_test [0] + +- variables + +- match => VariableDoc for epydoc_test.match [1] + +- value + +- ValueDoc for sre.match [2] Unicode ======= This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <dva...@us...> - 2006-09-11 16:13:01
|
Revision: 1386 http://svn.sourceforge.net/epydoc/?rev=1386&view=rev Author: dvarrazzo Date: 2006-09-11 09:12:51 -0700 (Mon, 11 Sep 2006) Log Message: ----------- - Fixed parsing of import statements in the form:: from mod import a as b Modified Paths: -------------- trunk/epydoc/src/epydoc/docparser.py trunk/epydoc/src/epydoc/test/docparser.doctest Modified: trunk/epydoc/src/epydoc/docparser.py =================================================================== --- trunk/epydoc/src/epydoc/docparser.py 2006-09-11 13:45:12 UTC (rev 1385) +++ trunk/epydoc/src/epydoc/docparser.py 2006-09-11 16:12:51 UTC (rev 1386) @@ -846,12 +846,24 @@ # >>> from os.path import join, split else: src_name = parse_dotted_name(lhs) - for elt in rhs: - if elt != (token.OP, ','): - var_name = parse_name(elt) + parts = split_on(rhs, (token.OP, ',')) + for part in parts: + # from m import x + if len(part) == 1: + var_name = parse_name(part[0]) _import_var_as(DottedName(src_name, var_name), var_name, parent_docs) - + + # from m import x as y + elif len(part) == 3 and part[1] == (token.NAME, 'as'): + orig_name = parse_name(part[0]) + var_name = parse_name(part[2]) + _import_var_as(DottedName(src_name, orig_name), + var_name, parent_docs) + + else: + ParseError("Bad from-import") + def _process_fromstar_import(src, parent_docs): """ Handle a statement of the form: Modified: trunk/epydoc/src/epydoc/test/docparser.doctest =================================================================== --- trunk/epydoc/src/epydoc/test/docparser.doctest 2006-09-11 13:45:12 UTC (rev 1385) +++ trunk/epydoc/src/epydoc/test/docparser.doctest 2006-09-11 16:12:51 UTC (rev 1386) @@ -710,6 +710,21 @@ +- is_imported = True +- value = <UNKNOWN> + >>> runparser(s=""" + ... from re import match as much, split, sub as scuba + ... """, + ... attribs='variables name imported_from') + ModuleDoc for test [0] + +- variables + +- much => VariableDoc for test.much [1] + | +- imported_from = DottedName(u're', u'match') + | +- name = u'much' + +- scuba => VariableDoc for test.scuba [2] + | +- imported_from = DottedName(u're', u'sub') + | +- name = u'scuba' + +- split => VariableDoc for test.split [3] + +- imported_from = DottedName(u're', u'split') + +- name = u'split' Unicode ======= This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <dva...@us...> - 2006-09-16 13:48:40
|
Revision: 1396 http://svn.sourceforge.net/epydoc/?rev=1396&view=rev Author: dvarrazzo Date: 2006-09-16 06:48:32 -0700 (Sat, 16 Sep 2006) Log Message: ----------- - Class docstrings can describe the constructor signature, parameters and raised exceptions. Merged revisions 1373-1375,1377-1388,1390-1395 via svnmerge from https://svn.sourceforge.net/svnroot/epydoc/branches/exp-args_in_class ........ r1373 | dvarrazzo | 2006-09-08 12:38:55 +0200 (Fri, 08 Sep 2006) | 10 lines - Allow parameter-related fields in a class docstring: use them to extend the matching __init__ docstring. - Enforce docstring parsing in dotted name order to ensure class docstring have already been parsed at __init__ docstring parsing time. - "type" fields are used to specify both class/instance variables types and constructor arguments types. In case a type is expressed in both class and __init__ docstring, the latter wins (e.g. the __init__ may receive an arg, parse it and store it in the instance state with the same name but different type) ........ r1374 | dvarrazzo | 2006-09-08 17:40:13 +0200 (Fri, 08 Sep 2006) | 8 lines - __init__ signature can be described in the class docstring also when there is no __init__.__doc__ at all. - parse_function_signature() can receive two arguments: one whose docstring is to be parsed and one to be populated. So the constructor signature can be parsed from the class docstring. - Dropped generation of variables when there is a type w/o matching var. The issue is still to be defined consistently anyway. ........ r1390 | dvarrazzo | 2006-09-15 03:47:53 +0200 (Fri, 15 Sep 2006) | 5 lines - Checked in a new algorithm to split fields in the class docstring between class documentation and constructor documentation. The algorithm requires the `type` fields to appear after the related fields. - Added more test. ........ r1391 | dvarrazzo | 2006-09-15 04:24:11 +0200 (Fri, 15 Sep 2006) | 2 lines - Splitting argument refactored into a single distinct function. ........ r1392 | dvarrazzo | 2006-09-16 01:36:23 +0200 (Sat, 16 Sep 2006) | 1 line - Be more lenient about types specified before matching objects. ........ r1393 | dvarrazzo | 2006-09-16 02:02:10 +0200 (Sat, 16 Sep 2006) | 5 lines - Reverted updates to `process_arg_field()` handler: is context is always a function, not the class docstring. - Exceptions are handled in the class docstring as well. - More explicit name of some variables. ........ r1394 | dvarrazzo | 2006-09-16 02:20:06 +0200 (Sat, 16 Sep 2006) | 1 line - Reverting type fields handling to trunk behavior. ........ r1395 | dvarrazzo | 2006-09-16 14:57:14 +0200 (Sat, 16 Sep 2006) | 4 lines - Class docstring fields are passed to __init__ without using an extra ClassDoc attribute. - Some docstring fixed. ........ Modified Paths: -------------- trunk/epydoc/src/epydoc/docstringparser.py trunk/epydoc/src/epydoc/test/docbuilder.doctest Modified: trunk/epydoc/src/epydoc/docstringparser.py =================================================================== --- trunk/epydoc/src/epydoc/docstringparser.py 2006-09-16 12:57:14 UTC (rev 1395) +++ trunk/epydoc/src/epydoc/docstringparser.py 2006-09-16 13:48:32 UTC (rev 1396) @@ -168,7 +168,9 @@ user docfields defined by containing objects. """ if api_doc.metadata is not UNKNOWN: - log.debug("%s's docstring processed twice" % api_doc.canonical_name) + if not (isinstance(api_doc, RoutineDoc) + and api_doc.canonical_name[-1] == '__init__'): + log.debug("%s's docstring processed twice" % api_doc.canonical_name) return initialize_api_doc(api_doc) @@ -197,8 +199,30 @@ descr, fields = parsed_docstring.split_fields(parse_errors) api_doc.descr = descr + field_warnings = [] + + # Handle the constructor fields that have been defined in the class + # docstring. This code assumes that a class docstring is parsed before + # the same class __init__ docstring. + if isinstance(api_doc, ClassDoc): + + # Parse ahead the __init__ docstring for this class + initvar = api_doc.variables.get('__init__') + if initvar and initvar.value not in (None, UNKNOWN): + init_api_doc = initvar.value + parse_docstring(init_api_doc, docindex) + + parse_function_signature(init_api_doc, api_doc) + init_fields = split_init_fields(fields, field_warnings) + + # Process fields + for field in init_fields: + try: + process_field(init_api_doc, docindex, field.tag(), + field.arg(), field.body()) + except ValueError, e: field_warnings.append(str(e)) + # Process fields - field_warnings = [] for field in fields: try: process_field(api_doc, docindex, field.tag(), @@ -254,6 +278,91 @@ if api_doc.sort_spec is UNKNOWN: api_doc.sort_spec = [] +def split_init_fields(fields, warnings): + """ + Remove the fields related to the constructor from a class docstring + fields list. + + @param fields: The fields to process. The list will be modified in place + @type fields: C{list} of L{markup.Field} + @param warnings: A list to emit processing warnings + @type warnings: C{list} + @return: The C{fields} items to be applied to the C{__init__} method + @rtype: C{list} of L{markup.Field} + """ + init_fields = [] + + # Split fields in lists according to their argument, keeping order. + arg_fields = {} + args_order = [] + i = 0 + while i < len(fields): + field = fields[i] + + # gather together all the fields with the same arg + if field.arg() is not None: + arg_fields.setdefault(field.arg(), []).append(fields.pop(i)) + args_order.append(field.arg()) + else: + i += 1 + + # Now check that for each argument there is at most a single variable + # and a single parameter, and at most a single type for each of them. + for arg in args_order: + ff = arg_fields.pop(arg, None) + if ff is None: + continue + + var = tvar = par = tpar = None + for field in ff: + if field.tag() in VARIABLE_TAGS: + if var is None: + var = field + fields.append(field) + else: + warnings.append( + "There is more than one variable named '%s'" + % arg) + elif field.tag() in PARAMETER_TAGS: + if par is None: + par = field + init_fields.append(field) + else: + warnings.append( + "There is more than one parameter named '%s'" + % arg) + + elif field.tag() == 'type': + if var is None and par is None: + # type before obj + tvar = tpar = field + else: + if var is not None and tvar is None: + tvar = field + if par is not None and tpar is None: + tpar = field + + elif field.tag() in EXCEPTION_TAGS: + init_fields.append(field) + + else: # Unespected field + fields.append(field) + + # Put selected types into the proper output lists + if tvar is not None: + if var is not None: + fields.append(tvar) + else: + pass # [xx] warn about type w/o object? + + if tpar is not None: + if par is not None: + init_fields.append(tpar) + else: + pass # [xx] warn about type w/o object? + + return init_fields + def report_errors(api_doc, docindex, parse_errors, field_warnings): """A helper function for L{parse_docstring()} that reports any markup warnings and field warnings that we encountered while @@ -620,6 +729,16 @@ register_field_handler(process_raise_field, 'raise', 'raises', 'except', 'exception') +# Tags related to function parameters +PARAMETER_TAGS = ('arg', 'argument', 'parameter', 'param', + 'kwarg', 'keyword', 'kwparam') + +# Tags related to variables in a class +VARIABLE_TAGS = ('cvar', 'cvariable', 'ivar', 'ivariable') + +# Tags related to exceptions +EXCEPTION_TAGS = ('raise', 'raises', 'except', 'exception') + ###################################################################### #{ Helper Functions ###################################################################### @@ -697,7 +816,7 @@ # [xx] copied from inspect.getdoc(); we can't use inspect.getdoc() # itself, since it expects an object, not a string. - if docstring == '': return '' + if not docstring: return '' lines = docstring.expandtabs().split('\n') # Find minimum indentation of any non-blank lines after first line. @@ -780,7 +899,7 @@ """A regular expression that is used to extract signatures from docstrings.""" -def parse_function_signature(func_doc): +def parse_function_signature(func_doc, doc_source=None): """ Construct the signature for a builtin function or method from its docstring. If the docstring uses the standard convention @@ -790,15 +909,26 @@ Otherwise, the signature will be set to a single varargs variable named C{"..."}. + @param func_doc: The target object where to store parsed signature. Also + container of the docstring to parse if doc_source is C{None} + @type func_doc: L{RoutineDoc} + @param doc_source: Contains the docstring to parse. If C{None}, parse + L{func_doc} docstring instead + @type doc_source: L{APIDoc} @rtype: C{None} """ + if doc_source is None: + doc_source = func_doc + # If there's no docstring, then don't do anything. - if not func_doc.docstring: return False + if not doc_source.docstring: return False - m = _SIGNATURE_RE.match(func_doc.docstring) + m = _SIGNATURE_RE.match(doc_source.docstring) if m is None: return False # Do I want to be this strict? + # Notice that __init__ must match the class name instead, if the signature + # comes from the class docstring # if not (m.group('func') == func_doc.canonical_name[-1] or # '_'+m.group('func') == func_doc.canonical_name[-1]): # log.warning("Not extracting function signature from %s's " @@ -857,7 +987,7 @@ func_doc.posarg_defaults.insert(0, None) # Remove the signature from the docstring. - func_doc.docstring = func_doc.docstring[m.end():] + doc_source.docstring = doc_source.docstring[m.end():] # We found a signature. return True Modified: trunk/epydoc/src/epydoc/test/docbuilder.doctest =================================================================== --- trunk/epydoc/src/epydoc/test/docbuilder.doctest 2006-09-16 12:57:14 UTC (rev 1395) +++ trunk/epydoc/src/epydoc/test/docbuilder.doctest 2006-09-16 13:48:32 UTC (rev 1396) @@ -1,5 +1,5 @@ Regression Testing for epydoc.docbuilder -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Test Function ============= @@ -14,6 +14,22 @@ >>> import tempfile, re, os, os.path, textwrap, sys >>> from epydoc.docbuilder import build_doc + >>> from epydoc.apidoc import ClassDoc, RoutineDoc + >>> from epydoc.markup import ParsedDocstring + + >>> def to_plain(docstring): + ... """Conver a parsed docstring into plain text""" + ... if isinstance(docstring, ParsedDocstring): + ... docstring = docstring.to_plaintext(None) + ... return docstring.rsplit() + + >>> def fun_to_plain(val_doc): + ... """Convert parsed docstrings in text from a RoutineDoc""" + ... for k, v in val_doc.arg_types.items(): + ... val_doc.arg_types[k] = to_plain(v) + ... for i, (k, v) in enumerate(val_doc.arg_descrs): + ... val_doc.arg_descrs[i] = (k, to_plain(v)) + >>> def runbuilder(s, attribs='', build=None, exclude=''): ... # Write it to a temp file. ... tmp_dir = tempfile.mkdtemp() @@ -24,6 +40,10 @@ ... val_doc = build_doc(os.path.join(tmp_dir, 'epydoc_test.py')) ... if build: val_doc = val_doc.variables[build].value ... # Display it. + ... if isinstance(val_doc, ClassDoc): + ... for val in val_doc.variables.values(): + ... if isinstance(val.value, RoutineDoc): + ... fun_to_plain(val.value) ... s = val_doc.pp(include=attribs.split(),exclude=exclude.split()) ... s = re.sub(r"(filename = ).*", r"\1...", s) ... s = re.sub(r"(<module 'epydoc_test' from ).*", r'\1...', s) @@ -115,3 +135,291 @@ +- PropertyDoc for epydoc_test.Foo.y [9] +- descr = u'A property has no defining module' +- type_descr = u'int' + +Stuff from future doesn't appear as variable. + + >>> runbuilder(s=""" + ... from __future__ import division + ... from re import match + ... """, + ... attribs='variables value') + ModuleDoc for epydoc_test [0] + +- variables + +- match => VariableDoc for epydoc_test.match [1] + +- value + +- ValueDoc for sre.match [2] + + +Specifying constructor signature in class docstring +=================================================== + +The class signature can be specified in the class docstring instead of __init__ + + >>> runbuilder(s=''' + ... class Foo: + ... """This is the object docstring + ... + ... @param a: init param. + ... @ivar a: instance var. + ... @type a: date + ... """ + ... def __init__(self, a): + ... """The ctor docstring. + ... + ... @type a: str + ... """ + ... pass + ... ''', + ... build="Foo", + ... attribs="variables name value " + ... "posargs vararg kwarg type arg_types arg_descrs") + ClassDoc for epydoc_test.Foo [0] + +- variables + +- __init__ => VariableDoc for epydoc_test.Foo.__init__ [1] + | +- name = '__init__' + | +- value + | +- RoutineDoc for epydoc_test.Foo.__init__ [2] + | +- arg_descrs = [([u'a'], ... + | +- arg_types = {u'a': ... + | +- kwarg = None + | +- posargs = ['self', 'a'] + | +- vararg = None + +- a => VariableDoc for epydoc_test.Foo.a [3] + +- name = u'a' + +- value = <UNKNOWN> + +Also keywords arguments can be put in the constructor + + >>> runbuilder(s=''' + ... class Foo: + ... """This is the object docstring + ... + ... @keyword a: a kwarg. + ... @type a: str + ... """ + ... def __init__(self, **kwargs): + ... """The ctor docstring. + ... + ... @type a: str + ... """ + ... ''', + ... build="Foo", + ... attribs="variables name value " + ... "posargs vararg kwarg type arg_types arg_descrs") + ClassDoc for epydoc_test.Foo [0] + +- variables + +- __init__ => VariableDoc for epydoc_test.Foo.__init__ [1] + +- name = '__init__' + +- value + +- RoutineDoc for epydoc_test.Foo.__init__ [2] + +- arg_descrs = [([u'a'], ... + +- arg_types = {u'a': ... + +- kwarg = 'kwargs' + +- posargs = ['self'] + +- vararg = None + +A missing docstring on the __init__ is not an issue. + + >>> runbuilder(s=''' + ... class Foo: + ... """This is the object docstring + ... + ... @param a: a param. + ... @type a: str + ... """ + ... def __init__(self): + ... pass + ... ''', + ... build="Foo", + ... attribs="variables name value " + ... "posargs vararg kwarg type arg_types arg_descrs") + ClassDoc for epydoc_test.Foo [0] + +- variables + +- __init__ => VariableDoc for epydoc_test.Foo.__init__ [1] + +- name = '__init__' + +- value + +- RoutineDoc for epydoc_test.Foo.__init__ [2] + +- arg_descrs = [([u'a'], ... + +- arg_types = {u'a': ... + +- kwarg = None + +- posargs = ['self'] + +- vararg = None + +Exceptions can be put in the docstring class, and they are assigned to the +constructor too. + >>> runbuilder(s=''' + ... class Foo: + ... """Foo(x, y) + ... + ... A class to ship rockets in outer space. + ... + ... @param x: first param + ... @param y: second param + ... @except ValueError: frobnication error + ... """ + ... def __init__(self, a, b): + ... """__init__ doc""" + ... pass + ... ''', + ... build="Foo", + ... attribs="variables name value exception_descrs " + ... "posargs vararg kwarg type arg_types arg_descrs docstring") + ClassDoc for epydoc_test.Foo [0] + +- docstring = u'A class to ship rockets in outer sp... + +- variables + +- __init__ => VariableDoc for epydoc_test.Foo.__init__ [1] + +- docstring = <UNKNOWN> + +- name = '__init__' + +- value + +- RoutineDoc for epydoc_test.Foo.__init__ [2] + +- arg_descrs = [([u'x'], [u'first', u'param']), ... + +- arg_types = {} + +- docstring = u'__init__ doc' + +- exception_descrs = [(DottedName(u'ValueError'), ... + +- kwarg = None + +- posargs = [u'x', u'y'] + +- vararg = None + + +Epydoc can also grok the constructor signature from the class docstring + + >>> runbuilder(s=''' + ... class Foo: + ... """Foo(x, y) + ... + ... A class to ship rockets in outer space. + ... + ... @param x: first param + ... @param y: second param + ... """ + ... def __init__(self, a, b): + ... """__init__ doc""" + ... pass + ... ''', + ... build="Foo", + ... attribs="variables name value " + ... "posargs vararg kwarg type arg_types arg_descrs docstring") + ClassDoc for epydoc_test.Foo [0] + +- docstring = u'A class to ship rockets ... + +- variables + +- __init__ => VariableDoc for epydoc_test.Foo.__init__ [1] + +- docstring = <UNKNOWN> + +- name = '__init__' + +- value + +- RoutineDoc for epydoc_test.Foo.__init__ [2] + +- arg_descrs = [([u'x'], ... + +- arg_types = {} + +- docstring = u'__init__ doc' + +- kwarg = None + +- posargs = [u'x', u'y'] + +- vararg = None + +A type can apply to both a param and a variable + + >>> runbuilder(s=''' + ... class Foo: + ... """This is the object docstring + ... + ... @param a: init param. + ... @ivar a: instance var. + ... @type a: date + ... """ + ... def __init__(self, a): + ... pass + ... ''', + ... build="Foo", + ... attribs="variables name value " + ... "posargs vararg kwarg type_descr arg_types arg_descrs") + ClassDoc for epydoc_test.Foo [0] + +- variables + +- __init__ => VariableDoc for epydoc_test.Foo.__init__ [1] + | +- name = '__init__' + | +- type_descr = None + | +- value + | +- RoutineDoc for epydoc_test.Foo.__init__ [2] + | +- arg_descrs = [([u'a'], [u'init', u'param.'])] + | +- arg_types = {u'a': [u'date']} + | +- kwarg = None + | +- posargs = ['self', 'a'] + | +- vararg = None + +- a => VariableDoc for epydoc_test.Foo.a [3] + +- name = u'a' + +- type_descr = u'date\n\n' + +- value = <UNKNOWN> + +But there can also be two different types + + >>> runbuilder(s=''' + ... class Foo: + ... """This is the object docstring + ... + ... @param a: init param. + ... @type a: string + ... @ivar a: instance var. + ... @type a: date + ... """ + ... def __init__(self, a): + ... pass + ... ''', + ... build="Foo", + ... attribs="variables name value " + ... "posargs vararg kwarg type_descr arg_types arg_descrs") + ClassDoc for epydoc_test.Foo [0] + +- variables + +- __init__ => VariableDoc for epydoc_test.Foo.__init__ [1] + | +- name = '__init__' + | +- type_descr = None + | +- value + | +- RoutineDoc for epydoc_test.Foo.__init__ [2] + | +- arg_descrs = [([u'a'], [u'init', u'param.'])] + | +- arg_types = {u'a': [u'string']} + | +- kwarg = None + | +- posargs = ['self', 'a'] + | +- vararg = None + +- a => VariableDoc for epydoc_test.Foo.a [3] + +- name = u'a' + +- type_descr = u'date\n\n' + +- value = <UNKNOWN> + +Also reST consolidated fields are not a problem. + + >>> runbuilder(s=''' + ... __docformat__ = 'restructuredtext' + ... class Foo: + ... """This is the object docstring + ... + ... :Parameters: + ... `a` : string + ... init param. + ... + ... :Exceptions: + ... * `ValueError`: frobnication error + ... init param. + ... + ... :IVariables: + ... `a` : date + ... instance var. + ... """ + ... def __init__(self, a): + ... pass + ... ''', + ... build="Foo", + ... attribs="variables name value exception_descrs " + ... "posargs vararg kwarg type_descr arg_types arg_descrs") + ClassDoc for epydoc_test.Foo [0] + +- variables + +- __init__ => VariableDoc for epydoc_test.Foo.__init__ [1] + | +- name = '__init__' + | +- type_descr = None + | +- value + | +- RoutineDoc for epydoc_test.Foo.__init__ [2] + | +- arg_descrs = [([u'a'], [u'init', u'param.'])] + | +- arg_types = {u'a': [u'string']} + | +- exception_descrs = [(DottedName(u'ValueError'), ... + | +- kwarg = None + | +- posargs = ['self', 'a'] + | +- vararg = None + +- a => VariableDoc for epydoc_test.Foo.a [3] + +- name = u'a' + +- type_descr = u'date' + +- value = <UNKNOWN> This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <dva...@us...> - 2007-01-28 15:23:09
|
Revision: 1421 http://svn.sourceforge.net/epydoc/?rev=1421&view=rev Author: dvarrazzo Date: 2007-01-28 07:23:07 -0800 (Sun, 28 Jan 2007) Log Message: ----------- - Using 'repr()' instead of the backtick operator, which fell in BDFL disgrace. Modified Paths: -------------- trunk/epydoc/src/epydoc/docbuilder.py trunk/epydoc/src/epydoc/docintrospecter.py Modified: trunk/epydoc/src/epydoc/docbuilder.py =================================================================== --- trunk/epydoc/src/epydoc/docbuilder.py 2007-01-28 14:19:30 UTC (rev 1420) +++ trunk/epydoc/src/epydoc/docbuilder.py 2007-01-28 15:23:07 UTC (rev 1421) @@ -283,7 +283,7 @@ def _get_docs_from_pyobject(obj, introspect, parse, progress_estimator): progress_estimator.complete += 1 - log.progress(progress_estimator.progress(), `obj`) + log.progress(progress_estimator.progress(), repr(obj)) if not introspect: log.error("Cannot get docs for Python objects without " Modified: trunk/epydoc/src/epydoc/docintrospecter.py =================================================================== --- trunk/epydoc/src/epydoc/docintrospecter.py 2007-01-28 14:19:30 UTC (rev 1420) +++ trunk/epydoc/src/epydoc/docintrospecter.py 2007-01-28 15:23:07 UTC (rev 1421) @@ -537,7 +537,7 @@ except KeyboardInterrupt: raise except Exception: pass if hasattr(value, '__name__'): name = value.__name__ - else: name = `value` + else: name = repr(value) log.warning("%s's docstring is not a unicode string, but it " "contains non-ascii data -- treating it as " "latin-1." % name) @@ -548,7 +548,7 @@ return None else: if hasattr(value, '__name__'): name = value.__name__ - else: name = `value` + else: name = repr(value) log.warning("%s's docstring is not a string -- ignoring it." % name) return None This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <dva...@us...> - 2007-01-29 00:14:39
|
Revision: 1426 http://svn.sourceforge.net/epydoc/?rev=1426&view=rev Author: dvarrazzo Date: 2007-01-28 16:14:38 -0800 (Sun, 28 Jan 2007) Log Message: ----------- - Warning about unknown parameters suppressed if the function couldn't be introspected, e.g. for builtins. Fixes SF bug #1556024. Modified Paths: -------------- trunk/epydoc/src/epydoc/docintrospecter.py trunk/epydoc/src/epydoc/docstringparser.py Modified: trunk/epydoc/src/epydoc/docintrospecter.py =================================================================== --- trunk/epydoc/src/epydoc/docintrospecter.py 2007-01-28 20:58:50 UTC (rev 1425) +++ trunk/epydoc/src/epydoc/docintrospecter.py 2007-01-29 00:14:38 UTC (rev 1426) @@ -435,6 +435,9 @@ else: # [XX] I should probably use UNKNOWN here?? + # dvarrazzo: if '...' is to be changed, also check that + # `docstringparser.process_arg_field()` works correctly. + # See SF bug #1556024. routine_doc.posargs = ['...'] routine_doc.posarg_defaults = [None] routine_doc.kwarg = None Modified: trunk/epydoc/src/epydoc/docstringparser.py =================================================================== --- trunk/epydoc/src/epydoc/docstringparser.py 2007-01-28 20:58:50 UTC (rev 1425) +++ trunk/epydoc/src/epydoc/docstringparser.py 2007-01-29 00:14:38 UTC (rev 1426) @@ -700,9 +700,11 @@ api_doc.arg_descrs.append( (idents, descr) ) # Check to make sure that the documented parameter(s) are # actually part of the function signature. - bad_params = ['"%s"' % i for i in idents if i not in api_doc.all_args()] - if bad_params: - raise ValueError(BAD_PARAM % (tag, ', '.join(bad_params))) + all_args = api_doc.all_args() + if all_args not in (['...'], UNKNOWN): + bad_params = ['"%s"' % i for i in idents if i not in all_args] + if bad_params: + raise ValueError(BAD_PARAM % (tag, ', '.join(bad_params))) def process_kwarg_field(api_doc, docindex, tag, arg, descr): # [xx] these should -not- be checked if they exist.. This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <dva...@us...> - 2007-02-05 02:22:17
|
Revision: 1436 http://svn.sourceforge.net/epydoc/?rev=1436&view=rev Author: dvarrazzo Date: 2007-02-04 18:22:16 -0800 (Sun, 04 Feb 2007) Log Message: ----------- - Added --exclude-introspect and --exclude-parse options. Modified Paths: -------------- trunk/epydoc/src/epydoc/cli.py trunk/epydoc/src/epydoc/docbuilder.py Modified: trunk/epydoc/src/epydoc/cli.py =================================================================== --- trunk/epydoc/src/epydoc/cli.py 2007-02-05 02:02:05 UTC (rev 1435) +++ trunk/epydoc/src/epydoc/cli.py 2007-02-05 02:22:16 UTC (rev 1436) @@ -144,6 +144,14 @@ generation_group.add_option( # --introspect-only "--introspect-only", action="store_false", dest="parse", help="Get all information from introspecting (don't parse)") + generation_group.add_option( # --exclude-introspect + "--exclude-introspect", dest="exclude_introspect", metavar="PATTERN", + help="Exclude introspection of modules whose dotted name matches " + "the regular expression PATTERN") + generation_group.add_option( # --exclude-parse + "--exclude-parse", dest="exclude_parse", metavar="PATTERN", + help="Exclude parsing of modules whose dotted name matches " + "the regular expression PATTERN") generation_group.add_option( # --inheritance "--inheritance", dest="inheritance", metavar="STYLE", help="The format for showing inheritance objects. STYLE " @@ -504,7 +512,9 @@ # Build docs for the named values. from epydoc.docbuilder import build_doc_index docindex = build_doc_index(names, options.introspect, options.parse, - add_submodules=(options.action!='text')) + add_submodules=(options.action!='text'), + exclude_introspect=options.exclude_introspect, + exclude_parse=options.exclude_parse) if docindex is None: return # docbuilder already logged an error. Modified: trunk/epydoc/src/epydoc/docbuilder.py =================================================================== --- trunk/epydoc/src/epydoc/docbuilder.py 2007-02-05 02:02:05 UTC (rev 1435) +++ trunk/epydoc/src/epydoc/docbuilder.py 2007-02-05 02:22:16 UTC (rev 1436) @@ -67,7 +67,7 @@ ## Imports ###################################################################### -import sys, os, os.path, __builtin__, imp +import sys, os, os.path, __builtin__, imp, re from epydoc.apidoc import * from epydoc.docintrospecter import introspect_docs from epydoc.docparser import parse_docs, ParseError @@ -80,8 +80,64 @@ ## 1. build_doc() ###################################################################### -def build_doc(item, introspect=True, parse=True, add_submodules=True): +class BuildOptions: """ + Holds the parameters for a documentation building process. + """ + def __init__(self, introspect=True, parse=True, + exclude_introspect=None, exclude_parse=None, + add_submodules=True): + self.introspect = introspect + self.parse = parse + self.exclude_introspect = exclude_introspect + self.exclude_parse = exclude_parse + self.add_submodules = add_submodules + + def must_introspect(self, name): + """ + Return C{True} if a module is to be introsepcted with the current + settings. + + @param name: The name of the module to test + @type name: L{DottedName} or C{str} + """ + return self.introspect \ + and not self._matches_filter(name, self.exclude_introspect) + + def must_parse(self, name): + """ + Return C{True} if a module is to be parsed with the current settings. + + @param name: The name of the module to test + @type name: L{DottedName} or C{str} + """ + return self.parse \ + and not self._matches_filter(name, self.exclude_parse) + + def _matches_filter(self, name, pattern): + """ + Test if a module name matches a pattern. + + @param name: The name of the module to test + @type name: L{DottedName} or C{str} + @param pattern: The pattern to match C{name} againts. + If C{None}, return C{False} + @type pattern: C{str} + @return: C{True} if C{name} in dotted format matches C{pattern}, + else C{False} + @rtype: C{bool} + """ + if not pattern: return False + + if isinstance(name, DottedName): + name = str(name) + + return bool(re.search(pattern, name)) + + +def build_doc(item, introspect=True, parse=True, add_submodules=True, + exclude_introspect=None, exclude_parse=None): + """ Build API documentation for a given item, and return it as an L{APIDoc} object. @@ -101,11 +157,13 @@ @param parse: If true, then use parsing to examine the specified items. Otherwise, just use introspection. """ - docindex = build_doc_index([item], introspect, parse, add_submodules) + docindex = build_doc_index([item], introspect, parse, add_submodules, + exclude_introspect=exclude_introspect, + exclude_parse=exclude_parse) return docindex.root[0] -def build_doc_index(items, introspect=True, parse=True, - add_submodules=True): +def build_doc_index(items, introspect=True, parse=True, add_submodules=True, + exclude_introspect=None, exclude_parse=None): """ Build API documentation for the given list of items, and return it in the form of a L{DocIndex}. @@ -126,11 +184,15 @@ @param parse: If true, then use parsing to examine the specified items. Otherwise, just use introspection. """ + options = BuildOptions(parse=parse, introspect=introspect, + exclude_introspect=exclude_introspect, exclude_parse=exclude_parse, + add_submodules=add_submodules) + # Get the basic docs for each item. - doc_pairs = _get_docs_from_items(items, introspect, parse, add_submodules) + doc_pairs = _get_docs_from_items(items, options) # Merge the introspection & parse docs. - if parse and introspect: + if options.parse and options.introspect: log.start_progress('Merging parsed & introspected information') docs = [] for i, (introspect_doc, parse_doc) in enumerate(doc_pairs): @@ -146,7 +208,7 @@ elif parse_doc is not None: docs.append(parse_doc) log.end_progress() - elif introspect: + elif options.introspect: docs = [doc_pair[0] for doc_pair in doc_pairs if doc_pair[0]] else: docs = [doc_pair[1] for doc_pair in doc_pairs if doc_pair[1]] @@ -160,7 +222,7 @@ # Replace any proxy valuedocs that we got from importing with # their targets. - if parse: + if options.parse: log.start_progress('Linking imported variables') valdocs = sorted(docindex.reachable_valdocs( imports=False, submodules=False, packages=False, subclasses=False)) @@ -230,7 +292,8 @@ # Documentation Generation #///////////////////////////////////////////////////////////////// -def _get_docs_from_items(items, introspect, parse, add_submodules): +def _get_docs_from_items(items, options): + # Start the progress bar. log.start_progress('Building documentation') progress_estimator = _ProgressEstimator(items) @@ -241,21 +304,21 @@ if isinstance(item, basestring): if is_module_file(item): doc_pairs.append(_get_docs_from_module_file( - item, introspect, parse, progress_estimator)) + item, options, progress_estimator)) elif is_package_dir(item): pkgfile = os.path.abspath(os.path.join(item, '__init__')) doc_pairs.append(_get_docs_from_module_file( - pkgfile, introspect, parse, progress_estimator)) + pkgfile, options, progress_estimator)) elif os.path.isfile(item): doc_pairs.append(_get_docs_from_pyscript( - item, introspect, parse, progress_estimator)) + item, options, progress_estimator)) elif hasattr(__builtin__, item): val = getattr(__builtin__, item) doc_pairs.append(_get_docs_from_pyobject( - val, introspect, parse, progress_estimator)) + val, options, progress_estimator)) elif is_pyname(item): doc_pairs.append(_get_docs_from_pyname( - item, introspect, parse, progress_estimator)) + item, options, progress_estimator)) elif os.path.isdir(item): log.error("Directory %r is not a package" % item) continue @@ -268,24 +331,24 @@ continue else: doc_pairs.append(_get_docs_from_pyobject( - item, introspect, parse, progress_estimator)) + item, options, progress_estimator)) # This will only have an effect if doc_pairs[-1] contains a # package's docs. The 'not is_module_file(item)' prevents # us from adding subdirectories if they explicitly specify # a package's __init__.py file. - if add_submodules and not is_module_file(item): + if options.add_submodules and not is_module_file(item): doc_pairs += _get_docs_from_submodules( - item, doc_pairs[-1], introspect, parse, progress_estimator) + item, doc_pairs[-1], options, progress_estimator) log.end_progress() return doc_pairs -def _get_docs_from_pyobject(obj, introspect, parse, progress_estimator): +def _get_docs_from_pyobject(obj, options, progress_estimator): progress_estimator.complete += 1 log.progress(progress_estimator.progress(), repr(obj)) - if not introspect: + if not options.introspect: log.error("Cannot get docs for Python objects without " "introspecting them.") @@ -296,11 +359,17 @@ except ImportError, e: log.error(e) return (None, None) - if parse: + if options.parse: if introspect_doc.canonical_name is not None: - _, parse_docs = _get_docs_from_pyname( - str(introspect_doc.canonical_name), False, True, - progress_estimator, supress_warnings=True) + prev_introspect = options.introspect + options.introspect = False + try: + _, parse_docs = _get_docs_from_pyname( + str(introspect_doc.canonical_name), options, + progress_estimator, supress_warnings=True) + finally: + options.introspect = prev_introspect + # We need a name: if introspect_doc.canonical_name in (None, UNKNOWN): if hasattr(obj, '__name__'): @@ -311,19 +380,19 @@ DottedName.UNREACHABLE) return (introspect_doc, parse_doc) -def _get_docs_from_pyname(name, introspect, parse, progress_estimator, +def _get_docs_from_pyname(name, options, progress_estimator, supress_warnings=False): progress_estimator.complete += 1 log.progress(progress_estimator.progress(), name) introspect_doc = parse_doc = None introspect_error = parse_error = None - if introspect: + if options.must_introspect(name): try: introspect_doc = introspect_docs(name=name) except ImportError, e: introspect_error = str(e) - if parse: + if options.must_parse(name): try: parse_doc = parse_docs(name=name) except ParseError, e: @@ -341,18 +410,18 @@ # Return the docs we found. return (introspect_doc, parse_doc) -def _get_docs_from_pyscript(filename, introspect, parse, progress_estimator): +def _get_docs_from_pyscript(filename, options, progress_estimator): # [xx] I should be careful about what names I allow as filenames, # and maybe do some munging to prevent problems. introspect_doc = parse_doc = None introspect_error = parse_error = None - if introspect: + if options.introspect: try: introspect_doc = introspect_docs(filename=filename, is_script=True) except ImportError, e: introspect_error = str(e) - if parse: + if options.parse: try: parse_doc = parse_docs(filename=filename, is_script=True) except ParseError, e: @@ -367,7 +436,7 @@ # Return the docs we found. return (introspect_doc, parse_doc) -def _get_docs_from_module_file(filename, introspect, parse, progress_estimator, +def _get_docs_from_module_file(filename, options, progress_estimator, parent_docs=(None,None)): """ Construct and return the API documentation for the python @@ -406,13 +475,13 @@ # Get the introspected & parsed docs (as appropriate) introspect_doc = parse_doc = None introspect_error = parse_error = None - if introspect: + if options.must_introspect(modulename): try: introspect_doc = introspect_docs( filename=filename, context=parent_docs[0]) except ImportError, e: introspect_error = str(e) - if parse and src_file_available: + if src_file_available and options.must_parse(modulename): try: parse_doc = parse_docs( filename=filename, context=parent_docs[1]) @@ -428,8 +497,7 @@ # Return the docs we found. return (introspect_doc, parse_doc) -def _get_docs_from_submodules(item, pkg_docs, introspect, parse, - progress_estimator): +def _get_docs_from_submodules(item, pkg_docs, options, progress_estimator): # Extract the package's __path__. if isinstance(pkg_docs[0], ModuleDoc) and pkg_docs[0].is_package: pkg_path = pkg_docs[0].path @@ -462,14 +530,14 @@ docs = [pkg_docs] for module_filename in module_filenames.values(): d = _get_docs_from_module_file( - module_filename, introspect, parse, progress_estimator, pkg_docs) + module_filename, options, progress_estimator, pkg_docs) docs.append(d) for subpackage_dir in subpackage_dirs: subpackage_file = os.path.join(subpackage_dir, '__init__') docs.append(_get_docs_from_module_file( - subpackage_file, introspect, parse, progress_estimator, pkg_docs)) + subpackage_file, options, progress_estimator, pkg_docs)) docs += _get_docs_from_submodules( - subpackage_dir, docs[-1], introspect, parse, progress_estimator) + subpackage_dir, docs[-1], options, progress_estimator) return docs def _report_errors(name, introspect_doc, parse_doc, This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <dva...@us...> - 2007-02-10 22:19:37
|
Revision: 1447 http://svn.sourceforge.net/epydoc/?rev=1447&view=rev Author: dvarrazzo Date: 2007-02-10 14:19:35 -0800 (Sat, 10 Feb 2007) Log Message: ----------- - Added 'python' directive to reporesent colourized Python code using reST markup. Modified Paths: -------------- trunk/epydoc/src/epydoc/markup/restructuredtext.py trunk/epydoc/src/epydoc/test/restructuredtext.doctest Modified: trunk/epydoc/src/epydoc/markup/restructuredtext.py =================================================================== --- trunk/epydoc/src/epydoc/markup/restructuredtext.py 2007-02-10 20:34:07 UTC (rev 1446) +++ trunk/epydoc/src/epydoc/markup/restructuredtext.py 2007-02-10 22:19:35 UTC (rev 1447) @@ -77,7 +77,7 @@ from docutils.nodes import NodeVisitor, Text, SkipChildren from docutils.nodes import SkipNode, TreeCopyVisitor from docutils.frontend import OptionParser -from docutils.parsers.rst import directives +from docutils.parsers.rst import directives, Directive import docutils.nodes import docutils.transforms.frontmatter import docutils.transforms @@ -87,7 +87,8 @@ from epydoc.markup import * from epydoc.apidoc import ModuleDoc, ClassDoc from epydoc.docwriter.dotgraph import * -from epydoc.markup.doctest import doctest_to_html, doctest_to_latex +from epydoc.markup.doctest import doctest_to_html, doctest_to_latex, \ + HTMLDoctestColorizer #: A dictionary whose keys are the "consolidated fields" that are #: recognized by epydoc; and whose values are the corresponding epydoc @@ -619,9 +620,30 @@ raise SkipNode() def visit_doctest_block(self, node): - self.body.append(doctest_to_html(str(node[0]))) + pysrc = str(node[0]) + if node.get('codeblock'): + self.body.append(HTMLDoctestColorizer().colorize_codeblock(pysrc)) + else: + self.body.append(doctest_to_html(str(node[0]))) raise SkipNode() + +class PythonCodeDirective(Directive): + required_arguments = 0 + optional_arguments = 0 + has_content = True + + def run(self): + self.assert_has_content() + text = '\n'.join(self.content) + + #node = docutils.nodes.doctest_block(rawsource=text) + #self.state.nested_parse(self.content, self.content_offset, node) + node = docutils.nodes.doctest_block(text, text, codeblock=True) + return [ node ] + +directives.register_directive('python', PythonCodeDirective) + ###################################################################### #{ Graph Generation Directives ###################################################################### Modified: trunk/epydoc/src/epydoc/test/restructuredtext.doctest =================================================================== --- trunk/epydoc/src/epydoc/test/restructuredtext.doctest 2007-02-10 20:34:07 UTC (rev 1446) +++ trunk/epydoc/src/epydoc/test/restructuredtext.doctest 2007-02-10 22:19:35 UTC (rev 1447) @@ -75,3 +75,36 @@ ... Other stuff after a tag. ... """) (u'This is the first line.', True) + +Python code +=========== +reStructuredText markup defines a ``python`` directive to represent a block +as colorized Python code. + +>>> err = [] +>>> p = restructuredtext.parse_docstring( +... """A test module +... +... .. python:: +... +... # This is some Python code +... def foo(): +... pass +... +... class Foo: +... def __init__(self): +... pass +... """, err) +>>> err +[] +>>> print p.to_html(None) +<p>A test module</p> +<pre class="py-doctest"> +<span class="py-comment"># This is some Python code</span> +<span class="py-keyword">def</span> <span class="py-defname">foo</span>(): + <span class="py-keyword">pass</span> +<BLANKLINE> +<span class="py-keyword">class</span> <span class="py-defname">Foo</span>: + <span class="py-keyword">def</span> <span class="py-defname">__init__</span>(self): + <span class="py-keyword">pass</span></pre> +<BLANKLINE> This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <dva...@us...> - 2007-02-10 19:26:57
|
Revision: 1444 http://svn.sourceforge.net/epydoc/?rev=1444&view=rev Author: dvarrazzo Date: 2007-02-10 11:26:54 -0800 (Sat, 10 Feb 2007) Log Message: ----------- - Added regression tests for sumarization for each markup. - Added APIDoc.is_detailed() method, with implementation for relevant subclasses. - APIDoc.pyval_repr() now always return the best representation for the object; single writers don't have to choose between parsed or introspected representation. - Added APIDoc.summary_pyval_repr() method to return a short version of the Python value. - select_variables() method can filter out (or in, if it cares) objects requiring extra details. - Summary lines in HTML output can be not linked to a detail box. If so, they gain an anchor to permit being referred in place of the detail box. - Added a link to source in summary items, which can be used for object whose details box is missing. - Classes, modules and variables names in the summary rendered in monotype font as function names. Modified Paths: -------------- trunk/epydoc/src/epydoc/apidoc.py trunk/epydoc/src/epydoc/docstringparser.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/docwriter/latex.py trunk/epydoc/src/epydoc/docwriter/plaintext.py trunk/epydoc/src/epydoc/markup/__init__.py trunk/epydoc/src/epydoc/markup/epytext.py trunk/epydoc/src/epydoc/markup/javadoc.py trunk/epydoc/src/epydoc/markup/plaintext.py trunk/epydoc/src/epydoc/markup/restructuredtext.py trunk/epydoc/src/epydoc/test/epytext.doctest Added Paths: ----------- trunk/epydoc/src/epydoc/test/javadoc.doctest trunk/epydoc/src/epydoc/test/plaintext.doctest trunk/epydoc/src/epydoc/test/restructuredtext.doctest Modified: trunk/epydoc/src/epydoc/apidoc.py =================================================================== --- trunk/epydoc/src/epydoc/apidoc.py 2007-02-08 04:17:45 UTC (rev 1443) +++ trunk/epydoc/src/epydoc/apidoc.py 2007-02-10 19:26:54 UTC (rev 1444) @@ -341,6 +341,11 @@ its docstring. @type: L{ParsedDocstring<epydoc.markup.ParsedDocstring>}""" + other_docs = UNKNOWN + """@ivar: A flag indicating if the entire L{docstring} body (except tags + if any) is entirely included in the L{summary}. + @type: C{bool}""" + metadata = UNKNOWN """@ivar: Metadata about the documented item, extracted from fields in its docstring. I{Currently} this is encoded as a list of tuples @@ -446,7 +451,20 @@ name_cmp = cmp(self.canonical_name, other.canonical_name) if name_cmp == 0: return -1 else: return name_cmp - + + def is_detailed(self): + """ + Does this object deserve a box with extra details? + + @return: True if the object needs extra details, else False. + @rtype: C{bool} + """ + if self.other_docs is True: + return True + + if self.metadata is not UNKNOWN: + return bool(self.metadata) + __mergeset = None """The set of all C{APIDoc} objects that have been merged with this C{APIDoc} (using L{merge_and_overwrite()}). Each C{APIDoc} @@ -655,6 +673,12 @@ else: return [self.value] + def is_detailed(self): + if (self.value in (None, UNKNOWN)): + return super(VariableDoc, self).is_detailed() + else: + return self.value.is_detailed() + ###################################################################### # Value Documentation Objects ###################################################################### @@ -699,6 +723,9 @@ reflect the actual value (e.g., if the value was modified after the initial assignment). @type: C{unicode}""" + + summary_linelen = 75 + """@cvar: The maximum length of a row to fit in a summary.""" #} end of "value representation" group #{ Context @@ -737,12 +764,8 @@ def __repr__(self): if self.canonical_name is not UNKNOWN: return '<%s %s>' % (self.__class__.__name__, self.canonical_name) - elif self.pyval_repr() is not UNKNOWN: + else: return '<%s %s>' % (self.__class__.__name__, self.pyval_repr()) - elif self.parse_repr is not UNKNOWN: - return '<%s %s>' % (self.__class__.__name__, self.parse_repr) - else: - return '<%s>' % self.__class__.__name__ def __setstate__(self, state): self.__dict__ = state @@ -766,11 +789,69 @@ # Return the pickle state. return self.__pickle_state + UNKNOWN_REPR = "??" + """@cvar: The string representation of an unknown value.""" + def pyval_repr(self): - if not hasattr(self, '_pyval_repr'): - self._pyval_repr = self._get_pyval_repr() - return self._pyval_repr - + """Return a string representation of the python value. + + The string representation may include data from introspection, parsing + and is authoritative as "the best way to represent a Python value." + + @return: A nice string representation. Never C{None} nor L{UNKNOWN} + @rtype: C{str} + + @todo: String representation can be made better here. + """ + if hasattr(self, '_pyval_repr'): + return self._pyval_repr + + rv = self._get_pyval_repr() + if rv is UNKNOWN: + rv = self.parse_repr + + if rv is UNKNOWN: + rv = self.UNKNOWN_REPR + + assert isinstance(rv, basestring) + self._pyval_repr = rv + return rv + + def summary_pyval_repr(self, max_len=None): + """Return a short version of L{pyval_repr}, fitting on a single line. + + Notice that L{GenericValueDoc.is_detailed()} uses this function to + return an answer leavling C{max_len} to the default value. So, if + a writer is to decide whether to emit a complete representation or + limit itself to the summary, it should call this function leaving + C{max_len} to its default value too, if it wants to generate consistent + results. + + @param max_len: The maximum length allowed. If None, use + L{summary_linelen} + + @return: the short representation and a boolean value stating if there + is further value to represent after such representation or not. + + @rtype: C{(str, bool)} + """ + ro = self.pyval_repr() + lo = False + + # Reduce to a single line + if "\n" in ro: + ro = ro.split("\n",1)[0] + lo = True + + # Truncate a long line + if max_len is None: + max_len = self.summary_linelen + if len(ro) > max_len: + ro = ro[:max_len-3]+'...' + lo = True + + return (ro, lo) + def _get_pyval_repr(self): """ Return a string representation of this value based on its pyval; @@ -801,6 +882,9 @@ """ canonical_name = None + def is_detailed(self): + return self.summary_pyval_repr()[1] + class NamespaceDoc(ValueDoc): """ API documentation information about a singe Python namespace @@ -857,6 +941,9 @@ APIDoc.__init__(self, **kwargs) assert self.variables is not UNKNOWN + def is_detailed(self): + return True + def apidoc_links(self, **filters): variables = filters.get('variables', True) imports = filters.get('imports', True) @@ -1038,7 +1125,7 @@ self.submodule_groups = self._init_grouping(elts) def select_variables(self, group=None, value_type=None, public=None, - imported=None): + imported=None, detailed=None): """ Return a specified subset of this module's L{sorted_variables} list. If C{value_type} is given, then only return variables @@ -1065,6 +1152,11 @@ always the special group name C{''}, which is used for variables that do not belong to any group. @type group: C{string} + + @param detailed: If True (False), return only the variables + deserving (not deserving) a detailed informative box. + If C{None}, don't care. + @type detailed: C{bool} """ if (self.sorted_variables is UNKNOWN or self.variable_groups is UNKNOWN): @@ -1087,6 +1179,12 @@ elif imported is False: var_list = [v for v in var_list if v.is_imported is not True] + # Detailed filter + if detailed is True: + var_list = [v for v in var_list if v.is_detailed() is True] + elif detailed is False: + var_list = [v for v in var_list if v.is_detailed() is not True] + # [xx] Modules are not currently included in any of these # value types. if value_type is None: @@ -1211,8 +1309,8 @@ for seq in nonemptyseqs: # remove cand if seq[0] == cand: del seq[0] - def select_variables(self, group=None, value_type=None, - inherited=None, public=None, imported=None): + def select_variables(self, group=None, value_type=None, inherited=None, + public=None, imported=None, detailed=None): """ Return a specified subset of this class's L{sorted_variables} list. If C{value_type} is given, then only return variables @@ -1257,6 +1355,11 @@ @param inherited: If C{None}, then return both inherited and local variables; if C{True}, then return only inherited variables; if C{False}, then return only local variables. + + @param detailed: If True (False), return only the variables + deserving (not deserving) a detailed informative box. + If C{None}, don't care. + @type detailed: C{bool} """ if (self.sorted_variables is UNKNOWN or self.variable_groups is UNKNOWN): @@ -1284,7 +1387,13 @@ var_list = [v for v in var_list if v.is_imported is True] elif imported is False: var_list = [v for v in var_list if v.is_imported is not True] - + + # Detailed filter + if detailed is True: + var_list = [v for v in var_list if v.is_detailed() is True] + elif detailed is False: + var_list = [v for v in var_list if v.is_detailed() is not True] + if value_type is None: return var_list elif value_type == 'method': @@ -1399,6 +1508,29 @@ @type: C{list}""" #} end of "information extracted from docstrings" group + def is_detailed(self): + if super(RoutineDoc, self).is_detailed(): + return True + + if self.arg_descrs not in (None, UNKNOWN) and self.arg_descrs: + return True + + if self.arg_types not in (None, UNKNOWN) and self.arg_types: + return True + + if self.return_descr not in (None, UNKNOWN): + return True + + if self.exception_descrs not in (None, UNKNOWN) and self.exception_descrs: + return True + + if (self.decorators not in (None, UNKNOWN) + and [ d for d in self.decorators + if d not in ('classmethod', 'staticmethod') ]): + return True + + return False + def all_args(self): """ @return: A list of the names of all arguments (positional, @@ -1460,6 +1592,19 @@ if self.fdel not in (None, UNKNOWN): val_docs.append(self.fdel) return val_docs + def is_detailed(self): + if super(PropertyDoc, self).is_detailed(): + return True + + if self.fget not in (None, UNKNOWN) and self.fget.pyval is not None: + return True + if self.fset not in (None, UNKNOWN) and self.fset.pyval is not None: + return True + if self.fdel not in (None, UNKNOWN) and self.fdel.pyval is not None: + return True + + return False + ###################################################################### ## Index ###################################################################### Modified: trunk/epydoc/src/epydoc/docstringparser.py =================================================================== --- trunk/epydoc/src/epydoc/docstringparser.py 2007-02-08 04:17:45 UTC (rev 1443) +++ trunk/epydoc/src/epydoc/docstringparser.py 2007-02-10 19:26:54 UTC (rev 1444) @@ -235,13 +235,15 @@ # Extract a summary if api_doc.summary is None and api_doc.descr is not None: - api_doc.summary = api_doc.descr.summary() + api_doc.summary, api_doc.other_docs = api_doc.descr.summary() # If the summary is empty, but the return field is not, then use # the return field to generate a summary description. if (isinstance(api_doc, RoutineDoc) and api_doc.summary is None and api_doc.return_descr is not None): - api_doc.summary = RETURN_PDS + api_doc.return_descr.summary() + s, o = api_doc.return_descr.summary() + api_doc.summary = RETURN_PDS + s + api_doc.other_docs = o # [XX] Make sure we don't have types/param descrs for unknown # vars/params? @@ -653,7 +655,7 @@ _check(api_doc, tag, arg, expect_arg=False) api_doc.is_instvar = False api_doc.descr = markup.ConcatenatedDocstring(api_doc.descr, descr) - api_doc.summary = descr.summary() + api_doc.summary, api_doc.other_docs = descr.summary() # Otherwise, @cvar should be used in a class. else: @@ -671,7 +673,7 @@ # require that there be no other descr? api_doc.is_instvar = True api_doc.descr = markup.ConcatenatedDocstring(api_doc.descr, descr) - api_doc.summary = descr.summary() + api_doc.summary, api_doc.other_docs = descr.summary() # Otherwise, @ivar should be used in a class. else: @@ -768,7 +770,7 @@ raise ValueError(REDEFINED % ('description for '+ident)) var_doc.descr = descr if var_doc.summary in (None, UNKNOWN): - var_doc.summary = var_doc.descr.summary() + var_doc.summary, var_doc.other_docs = var_doc.descr.summary() def set_var_type(api_doc, ident, descr): if ident not in api_doc.variables: Modified: trunk/epydoc/src/epydoc/docwriter/dotgraph.py =================================================================== --- trunk/epydoc/src/epydoc/docwriter/dotgraph.py 2007-02-08 04:17:45 UTC (rev 1443) +++ trunk/epydoc/src/epydoc/docwriter/dotgraph.py 2007-02-10 19:26:54 UTC (rev 1444) @@ -693,14 +693,9 @@ """ if default is None: return '%s' % name - elif default.parse_repr is not UNKNOWN: - return '%s=%s' % (name, default.parse_repr) else: - pyval_repr = default.pyval_repr() - if pyval_repr is not UNKNOWN: - return '%s=%s' % (name, pyval_repr) - else: - return '%s=??' % name + pyval_repr = default.summary_pyval_repr()[0] + return '%s=%s' % (name, pyval_repr) def _qualifier_cell(self, key_label, port): return self._QUALIFIER_CELL % (port, self.bgcolor, key_label) Modified: trunk/epydoc/src/epydoc/docwriter/html.py =================================================================== --- trunk/epydoc/src/epydoc/docwriter/html.py 2007-02-08 04:17:45 UTC (rev 1443) +++ trunk/epydoc/src/epydoc/docwriter/html.py 2007-02-10 19:26:54 UTC (rev 1444) @@ -400,7 +400,7 @@ if not isinstance(d, GenericValueDoc)] for doc in valdocs: if isinstance(doc, NamespaceDoc): - # add any vars with generic vlaues; but don't include + # add any vars with generic values; but don't include # inherited vars. self.indexed_docs += [d for d in doc.variables.values() if isinstance(d.value, GenericValueDoc) @@ -509,6 +509,11 @@ self._mkdir(directory) self._directory = directory + # Set the default value for L{ValueDoc.summary_linelen} so that + # L{ValueDoc.summary_pyval_repr()} return value is consistent with + # L{ValueDoc.is_detailed()} + ValueDoc.summary_linelen = self._variable_summary_linelen + # Write the CSS file. self._files_written += 1 log.progress(self._files_written/self._num_files, 'epydoc.css') @@ -1898,6 +1903,8 @@ @param container: The API documentation for the class or module whose summary table we're writing. """ + link = None # link to the source code + # If it's a private variable, then mark its <tr>. if var_doc.is_public: tr_class = '' else: tr_class = ' class="private"' @@ -1907,24 +1914,18 @@ if isinstance(var_doc.value, RoutineDoc): typ = self.return_type(var_doc, indent=6) description = self.function_signature(var_doc, True, True) + link = self.pysrc_link(var_doc.value) else: typ = self.type_descr(var_doc, indent=6) - description = self.href(var_doc) + description = self.summary_name(var_doc, link_name=True) if isinstance(var_doc.value, GenericValueDoc): - pyval_repr = var_doc.value.pyval_repr() - if pyval_repr is not UNKNOWN: - val_repr = pyval_repr - else: - val_repr = var_doc.value.parse_repr - if val_repr is not UNKNOWN: - val_repr = ' '.join(val_repr.strip().split()) - maxlen = self._variable_summary_linelen - if len(val_repr) > maxlen: - val_repr = val_repr[:maxlen-3]+'...' - val_repr = plaintext_to_html(val_repr) - tooltip = self.variable_tooltip(var_doc) - description += (' = <code%s>%s</code>' % - (tooltip, val_repr)) + # The summary max length has been chosen setting + # L{ValueDoc.summary_linelen} when L{write()} was called + val_repr = var_doc.value.summary_pyval_repr()[0] + val_repr = plaintext_to_html(val_repr) + tooltip = self.variable_tooltip(var_doc) + description += (' = <code%s>%s</code>' % + (tooltip, val_repr)) # Add the summary to the description (if there is one). summary = self.summary(var_doc, indent=6) @@ -1936,11 +1937,11 @@ self.href(var_doc.container) + ")</em>") # Write the summary line. - self._write_summary_line(out, typ, description, tr_class) + self._write_summary_line(out, typ, description, tr_class, link) _write_summary_line = compile_template( """ - _write_summary_line(self, out, typ, description, tr_class) + _write_summary_line(self, out, typ, description, tr_class, link) """, # /------------------------- Template -------------------------\ ''' @@ -1948,7 +1949,17 @@ <td width="15%" align="right" valign="top" class="summary"> <span class="summary-type">$typ or " "$</span> </td><td class="summary"> - $description$ + >>> if link is not None: + <table width="100%" cellpadding="0" cellspacing="0" border="0"> + <tr> + <td>$description$</td> + <td align="right" valign="top">$link$</td> + </tr> + </table> + >>> #endif + >>> if link is None: + $description$ + >>> #endif </td> </tr> ''') @@ -1963,11 +1974,13 @@ if isinstance(doc, ClassDoc): var_docs = doc.select_variables(value_type=value_type, imported=False, inherited=False, - public=self._public_filter) + public=self._public_filter, + detailed=True) else: var_docs = doc.select_variables(value_type=value_type, imported=False, - public=self._public_filter) + public=self._public_filter, + detailed=True) if not var_docs: return # Write a header @@ -2273,15 +2286,8 @@ def variable_tooltip(self, var_doc): if var_doc.value in (None, UNKNOWN): return '' - else: - pyval_repr = var_doc.value.pyval_repr() - if pyval_repr is not UNKNOWN: - s = pyval_repr - elif var_doc.value.parse_repr is not UNKNOWN: - s = var_doc.value.parse_repr - else: - return '' - + + s = var_doc.value.pyval_repr() if len(s) > self._variable_tooltip_linelen: s = s[:self._variable_tooltip_linelen-3]+'...' return ' title="%s"' % plaintext_to_html(s) @@ -2290,15 +2296,8 @@ if val_doc is UNKNOWN: return '' if val_doc.pyval is not UNKNOWN: return self.pprint_pyval(val_doc.pyval) - elif val_doc.pyval_repr() is not UNKNOWN: - s = plaintext_to_html(val_doc.pyval_repr()) - elif val_doc.parse_repr is not UNKNOWN: - s = plaintext_to_html(val_doc.parse_repr) elif isinstance(val_doc, GenericValueDoc): - # This *should* never happen -- GenericValueDoc's should always - # have a pyval_repr or a parse_repr. - log.debug('pprint_value() got GenericValueDoc w/ UNKNOWN repr') - return '' + s = plaintext_to_html(val_doc.pyval_repr()) else: s = self.href(val_doc) return self._linewrap_html(s, self._variable_linelen, @@ -2537,15 +2536,12 @@ '</span>(...)</span>') % (css_class, css_class, api_doc.name)) # Get the function's name. - if link_name: - name = self.href(api_doc, css_class=css_class+'-name') - else: - name = ('<span class="%s-name">%s</span>' % - (css_class, api_doc.name)) + name = self.summary_name(api_doc, css_class=css_class+'-name', + link_name=link_name) else: func_doc = api_doc name = self.href(api_doc, css_class=css_class+'-name') - + if func_doc.posargs == UNKNOWN: args = ['...'] else: @@ -2564,6 +2560,13 @@ return ('<span class="%s">%s(%s)</span>' % (css_class, name, ',\n '.join(args))) + def summary_name(self, api_doc, css_class='summary-name', link_name=False): + if link_name and api_doc.is_detailed(): + return self.href(api_doc, css_class=css_class) + else: + return '<a name="%s" /><span class="%s">%s</span>' % \ + (api_doc.name, css_class, api_doc.name) + # [xx] tuple args??? def func_arg(self, name, default, css_class): name = self._arg_name(name) @@ -2573,12 +2576,8 @@ s += ('=<span class="%s-default">%s</span>' % (css_class, plaintext_to_html(default.parse_repr))) else: - pyval_repr = default.pyval_repr() - if pyval_repr is not UNKNOWN: - s += ('=<span class="%s-default">%s</span>' % - (css_class, plaintext_to_html(pyval_repr))) - else: - s += '=<span class="%s-default">??</span>' % css_class + s += ('=<span class="%s-default">%s</span>' % + (css_class, plaintext_to_html(default.pyval_repr()))) return s def _arg_name(self, arg): Modified: trunk/epydoc/src/epydoc/docwriter/html_css.py =================================================================== --- trunk/epydoc/src/epydoc/docwriter/html_css.py 2007-02-08 04:17:45 UTC (rev 1443) +++ trunk/epydoc/src/epydoc/docwriter/html_css.py 2007-02-10 19:26:54 UTC (rev 1444) @@ -187,6 +187,10 @@ .summary-sig-arg { color: $summary_sig_arg; } .summary-sig-default { color: $summary_sig_default; } +/* To render variables, classes etc. like functions */ +.summary-name { color: $summary_sig_name; font-weight: bold; + font-family: monospace; } + /* Variable values * - In the 'variable details' sections, each varaible's value is * listed in a 'pre.variable' box. The width of this box is Modified: trunk/epydoc/src/epydoc/docwriter/latex.py =================================================================== --- trunk/epydoc/src/epydoc/docwriter/latex.py 2007-02-08 04:17:45 UTC (rev 1443) +++ trunk/epydoc/src/epydoc/docwriter/latex.py 2007-02-10 19:26:54 UTC (rev 1444) @@ -727,12 +727,7 @@ def func_arg(self, name, default): s = '\\textit{%s}' % plaintext_to_latex(self._arg_name(name)) if default is not None: - if default.parse_repr is not UNKNOWN: - s += '=\\texttt{%s}' % plaintext_to_latex(default.parse_repr) - elif default.pyval_repr() is not UNKNOWN: - s += '=\\texttt{%s}' % plaintext_to_latex(default.pyval_repr()) - else: - s += '=\\texttt{??}' + s += '=\\texttt{%s}' % default.summary_pyval_repr()[0] return s def _arg_name(self, arg): @@ -806,8 +801,7 @@ has_descr = var_doc.descr not in (None, UNKNOWN) has_type = var_doc.type_descr not in (None, UNKNOWN) has_repr = (var_doc.value not in (None, UNKNOWN) and - (var_doc.value.parse_repr is not UNKNOWN or - var_doc.value.pyval_repr() is not UNKNOWN)) + var_doc.value.pyval_repr() != var_doc.value.UNKNOWN_REPR) if has_descr or has_type: out('\\raggedright ') if has_descr: @@ -816,10 +810,7 @@ if has_repr: out('\\textbf{Value:} \n') pyval_repr = var_doc.value.pyval_repr() - if pyval_repr is not UNKNOWN: - out(self._pprint_var_value(pyval_repr, 80)) - elif var_doc.value.parse_repr is not UNKNOWN: - out(self._pprint_var_value(var_doc.value.parse_repr, 80)) + out(self._pprint_var_value(pyval_repr, 80)) if has_type: ptype = self.docstring_to_latex(var_doc.type_descr, 12).strip() out('%s\\textit{(type=%s)}' % (' '*12, ptype)) Modified: trunk/epydoc/src/epydoc/docwriter/plaintext.py =================================================================== --- trunk/epydoc/src/epydoc/docwriter/plaintext.py 2007-02-08 04:17:45 UTC (rev 1443) +++ trunk/epydoc/src/epydoc/docwriter/plaintext.py 2007-02-10 19:26:54 UTC (rev 1444) @@ -137,16 +137,8 @@ var_doc.value.canonical_name not in (None, UNKNOWN)): out(' = %s' % var_doc.value.canonical_name) elif var_doc.value not in (UNKNOWN, None): - pyval_repr = var_doc.value.pyval_repr() - if pyval_repr is not UNKNOWN: - val_repr = pyval_repr.expandtabs() - else: - val_repr = var_doc.value.parse_repr - if val_repr is not UNKNOWN: - if len(val_repr)+len(name) > 75: - val_repr = '%s...' % val_repr[:75-len(name)-3] - if '\n' in val_repr: val_repr = '%s...' % (val_repr.split()[0]) - out(' = %s' % val_repr) + val_repr = var_doc.value.summary_pyval_repr(max_len=len(name)-75)[0] + out(' = %s' % val_repr.expandtabs()) out('\n') if not verbose: return prefix += ' ' # indent the body. @@ -206,14 +198,8 @@ def fmt_arg(self, name, default): if default is None: return '%s' % name - elif default.parse_repr is not UNKNOWN: - return '%s=%s' % (name, default.parse_repr) else: - pyval_repr = default.pyval_repr() - if pyval_repr is not UNKNOWN: - return '%s=%s' % (name, pyval_repr) - else: - return '%s=??' % name + return '%s=%s' % (name, default.summary_pyval_repr()[0]) def write_list(self, out, heading, doc, value_type=None, imported=False, inherited=False, prefix='', noindent=False, Modified: trunk/epydoc/src/epydoc/markup/__init__.py =================================================================== --- trunk/epydoc/src/epydoc/markup/__init__.py 2007-02-08 04:17:45 UTC (rev 1443) +++ trunk/epydoc/src/epydoc/markup/__init__.py 2007-02-10 19:26:54 UTC (rev 1444) @@ -258,12 +258,14 @@ def summary(self): """ - @return: A short summary of this docstring. Typically, the - summary consists of the first sentence of the docstring. - @rtype: L{ParsedDocstring} + @return: A pair consisting of a short summary of this docstring and a + boolean value indicating whether there is further documentation + in addition to the summary. Typically, the summary consists of the + first sentence of the docstring. + @rtype: (L{ParsedDocstring}, C{bool}) """ # Default behavior: - return self + return self, False def concatenate(self, other): """ Modified: trunk/epydoc/src/epydoc/markup/epytext.py =================================================================== --- trunk/epydoc/src/epydoc/markup/epytext.py 2007-02-08 04:17:45 UTC (rev 1443) +++ trunk/epydoc/src/epydoc/markup/epytext.py 2007-02-10 19:26:54 UTC (rev 1444) @@ -1952,7 +1952,7 @@ return childstr def summary(self): - if self._tree is None: return self + if self._tree is None: return self, False tree = self._tree doc = Element('epytext') @@ -1974,8 +1974,16 @@ variables[0].children.append(str) # If we didn't find a paragraph, return an empty epytext. - if len(variables) == 0: return ParsedEpytextDocstring(doc) + if len(variables) == 0: return ParsedEpytextDocstring(doc), False + # Is there anything else, excluding tags, after the first variable? + long_docs = False + for var in variables[1:]: + if isinstance(var, Element) and var.tag == 'fieldlist': + continue + long_docs = True + break + # Extract the first sentence. parachildren = variables[0].children para = Element('para') @@ -1985,10 +1993,15 @@ m = re.match(r'(\s*[\w\W]*?\.)(\s|$)', parachild) if m: para.children.append(m.group(1)) - return ParsedEpytextDocstring(doc) + long_docs |= parachild is not parachildren[-1] + if not long_docs: + other = parachild[m.end():] + if other and not other.isspace(): + long_docs = True + return ParsedEpytextDocstring(doc), long_docs para.children.append(parachild) - return ParsedEpytextDocstring(doc) + return ParsedEpytextDocstring(doc), long_docs def split_fields(self, errors=None): if self._tree is None: return (self, ()) Modified: trunk/epydoc/src/epydoc/markup/javadoc.py =================================================================== --- trunk/epydoc/src/epydoc/markup/javadoc.py 2007-02-08 04:17:45 UTC (rev 1443) +++ trunk/epydoc/src/epydoc/markup/javadoc.py 2007-02-10 19:26:54 UTC (rev 1444) @@ -221,12 +221,26 @@ # Jeff's hack to get summary working def summary(self): - m = re.match(r'(\s*[\w\W]*?\.)(\s|$)', self._docstring) + # Drop tags + doc = "\n".join([ row for row in self._docstring.split('\n') + if not row.lstrip().startswith('@') ]) + + m = re.match(r'(\s*[\w\W]*?\.)(\s|$)', doc) if m: - return ParsedJavadocDocstring(m.group(1)) + other = doc[m.end():] + return (ParsedJavadocDocstring(m.group(1)), + other != '' and not other.isspace()) + else: - summary = self._docstring.split('\n', 1)[0]+'...' - return ParsedJavadocDocstring(summary) + parts = doc.strip('\n').split('\n', 1) + if len(parts) == 1: + summary = parts[0] + other = False + else: + summary = parts[0] + '...' + other = True + + return ParsedJavadocDocstring(summary), other # def concatenate(self, other): # if not isinstance(other, ParsedJavadocDocstring): Modified: trunk/epydoc/src/epydoc/markup/plaintext.py =================================================================== --- trunk/epydoc/src/epydoc/markup/plaintext.py 2007-02-08 04:17:45 UTC (rev 1443) +++ trunk/epydoc/src/epydoc/markup/plaintext.py 2007-02-10 19:26:54 UTC (rev 1444) @@ -53,10 +53,19 @@ def summary(self): m = re.match(r'(\s*[\w\W]*?\.)(\s|$)', self._text) if m: - return ParsedPlaintextDocstring(m.group(1), verbatim=0) + other = self._text[m.end():] + return (ParsedPlaintextDocstring(m.group(1), verbatim=0), + other != '' and not other.isspace()) else: - summary = self._text.split('\n', 1)[0]+'...' - return ParsedPlaintextDocstring(summary, verbatim=0) + parts = self._text.strip('\n').split('\n', 1) + if len(parts) == 1: + summary = parts[0] + other = False + else: + summary = parts[0] + '...' + other = True + + return ParsedPlaintextDocstring(summary, verbatim=0), other # def concatenate(self, other): # if not isinstance(other, ParsedPlaintextDocstring): Modified: trunk/epydoc/src/epydoc/markup/restructuredtext.py =================================================================== --- trunk/epydoc/src/epydoc/markup/restructuredtext.py 2007-02-08 04:17:45 UTC (rev 1443) +++ trunk/epydoc/src/epydoc/markup/restructuredtext.py 2007-02-10 19:26:54 UTC (rev 1444) @@ -174,7 +174,7 @@ visitor = _SummaryExtractor(self._document) try: self._document.walk(visitor) except docutils.nodes.NodeFound: pass - return visitor.summary + return visitor.summary, bool(visitor.other_docs) # def concatenate(self, other): # result = self._document.copy() @@ -278,12 +278,16 @@ def __init__(self, document): NodeVisitor.__init__(self, document) self.summary = None + self.other_docs = None def visit_document(self, node): self.summary = None def visit_paragraph(self, node): - if self.summary is not None: return + if self.summary is not None: + # found a paragraph after the first one + self.other_docs = True + raise docutils.nodes.NodeFound('Found summary') summary_pieces = [] @@ -293,6 +297,9 @@ m = re.match(r'(\s*[\w\W]*?\.)(\s|$)', child.data) if m: summary_pieces.append(docutils.nodes.Text(m.group(1))) + other = child.data[m.end():] + if other and not other.isspace(): + self.other_docs = True break summary_pieces.append(child) @@ -301,8 +308,10 @@ summary_doc[:] = [summary_para] summary_para[:] = summary_pieces self.summary = ParsedRstDocstring(summary_doc) - raise docutils.nodes.NodeFound('Found summary') + def visit_field(self, node): + raise SkipNode + def unknown_visit(self, node): 'Ignore all unknown nodes' Modified: trunk/epydoc/src/epydoc/test/epytext.doctest =================================================================== --- trunk/epydoc/src/epydoc/test/epytext.doctest 2007-02-08 04:17:45 UTC (rev 1443) +++ trunk/epydoc/src/epydoc/test/epytext.doctest 2007-02-10 19:26:54 UTC (rev 1444) @@ -25,10 +25,10 @@ >>> print testparse(""" ... this is one paragraph. - ... + ... ... This is ... another. - ... + ... ... This is a third""") <para>this is one paragraph.</para> <para>This is another.</para> @@ -38,7 +38,7 @@ >>> print testparse(""" ... This is a paragraph. - ... + ... ... @foo: This is a field.""") <para>This is a paragraph.</para> <fieldlist><field><tag>foo</tag> @@ -79,7 +79,7 @@ >>> print testparse(""" ... This is a paragraph. - ... + ... ... - This is a list item.""") Traceback (most recent call last): StructuringError: Line 4: Lists must be indented. @@ -102,7 +102,7 @@ ... This is a paragraph. ... - This is a list item. ... Hello. - ... + ... ... - Sublist item""") Traceback (most recent call last): StructuringError: Line 6: Lists must be indented. @@ -137,9 +137,9 @@ >>> print testparse(""" ... This is a paragraph. - ... + ... ... - This is a list item. - ... + ... ... This is a paragraph""") <para>This is a paragraph.</para> <ulist><li><para>This is a list item.</para></li></ulist> @@ -147,7 +147,7 @@ >>> print testparse(""" ... This is a paragraph. - ... + ... ... - This is a list item. ... This is a paragraph""") <para>This is a paragraph.</para> @@ -186,7 +186,7 @@ ... checkparse('%s\n%s' % (li1, p), ONELIST+PARA) ... checkparse('%s\n%s\n%s' % (p, li1, p), ... PARA+ONELIST+PARA) - ... + ... ... for li2 in (LI1, LI2, LI3, LI4): ... checkparse('%s\n%s' % (li1, li2), TWOLIST) ... checkparse('%s\n%s\n%s' % (p, li1, li2), PARA+TWOLIST) @@ -223,3 +223,62 @@ ... checkparse(nl1+indent+LI, ONELIST) ... for nl2 in ('\n', '\n\n'): ... checkparse(nl1+indent+LI+nl2+indent+LI, TWOLIST) + +Summary +======= +The implementation of the summaization function works as expected. + +>>> from epydoc.markup import epytext +>>> def getsummary(s): +... p = epytext.parse_docstring(s, []) +... s, o = p.summary() +... s = s.to_plaintext(None).strip() +... return s, o + +Let's not lose anything! + +>>> getsummary("Single line") +('Single line', False) + +>>> getsummary("Single line.") +('Single line.', False) + +>>> getsummary(""" +... Single line C{with} period. +... """) +('Single line with period.', False) + +>>> getsummary(""" +... Single line C{with }period. +... +... @type: Also with a tag. +... """) +('Single line with period.', False) + +>>> getsummary(""" +... Other lines C{with} period. +... This is attached +... """) +('Other lines with period.', True) + +>>> getsummary(""" +... Other lines C{with} period. +... +... This is detached +... +... @type: Also with a tag. +... """) +('Other lines with period.', True) + +>>> getsummary(""" +... Other lines without period +... This is attached +... """) +('Other lines without period This is attached', False) + +>>> getsummary(""" +... Other lines without period +... +... This is detached +... """) +('Other lines without period...', True) Added: trunk/epydoc/src/epydoc/test/javadoc.doctest =================================================================== --- trunk/epydoc/src/epydoc/test/javadoc.doctest (rev 0) +++ trunk/epydoc/src/epydoc/test/javadoc.doctest 2007-02-10 19:26:54 UTC (rev 1444) @@ -0,0 +1,68 @@ +Regression Testing for plaintext +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Summary +======= +The implementation of the summaization function works as expected. + +>>> from epydoc.markup import javadoc +>>> def getsummary(s): +... p = javadoc.parse_docstring(s, []) +... s, o = p.summary() +... s = s.to_plaintext(None).strip() +... return s, o + +#Let's not lose anything! + +>>> getsummary("Single line") +('Single line', False) + +>>> getsummary("Single line.") +('Single line.', False) + +>>> getsummary(""" +... Single line <i>with</i> period. +... """) +('Single line <i>with</i> period.', False) + +>>> getsummary(""" +... Single line <i>with</i> period. +... +... @type Also with a tag. +... """) +('Single line <i>with</i> period.', False) + +>>> getsummary(""" +... Other lines <i>with</i> period. +... This is attached +... """) +('Other lines <i>with</i> period.', True) + +>>> getsummary(""" +... Other lines <i>with</i> period. +... +... This is detached +... +... @type Also with a tag. +... """) +('Other lines <i>with</i> period.', True) + +>>> getsummary(""" +... Other lines without period +... This is attached +... """) +('Other lines without period...', True) + +>>> getsummary(""" +... Other lines without period +... +... This is detached +... """) +('Other lines without period...', True) + +>>> getsummary(""" +... Single line <i>without</i> period +... +... @type Also with a tag. +... """) +('Single line <i>without</i> period', False) Added: trunk/epydoc/src/epydoc/test/plaintext.doctest =================================================================== --- trunk/epydoc/src/epydoc/test/plaintext.doctest (rev 0) +++ trunk/epydoc/src/epydoc/test/plaintext.doctest 2007-02-10 19:26:54 UTC (rev 1444) @@ -0,0 +1,52 @@ +Regression Testing for plaintext +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Summary +======= +The implementation of the summaization function works as expected. + +>>> from epydoc.markup import plaintext +>>> def getsummary(s): +... p = plaintext.parse_docstring(s, []) +... s, o = p.summary() +... s = s.to_plaintext(None).strip() +... return s, o + +Let's not lose anything! + +>>> getsummary("Single line") +('Single line', False) + +>>> getsummary("Single line.") +('Single line.', False) + +>>> getsummary(""" +... Single line with period. +... """) +('Single line with period.', False) + +>>> getsummary(""" +... Other lines with period. +... This is attached +... """) +('Other lines with period.', True) + +>>> getsummary(""" +... Other lines with period. +... +... This is detached +... """) +('Other lines with period.', True) + +>>> getsummary(""" +... Other lines without period +... This is attached +... """) +('Other lines without period...', True) + +>>> getsummary(""" +... Other lines without period +... +... This is detached +... """) +('Other lines without period...', True) Added: trunk/epydoc/src/epydoc/test/restructuredtext.doctest =================================================================== --- trunk/epydoc/src/epydoc/test/restructuredtext.doctest (rev 0) +++ trunk/epydoc/src/epydoc/test/restructuredtext.doctest 2007-02-10 19:26:54 UTC (rev 1444) @@ -0,0 +1,77 @@ +Regression Testing for plaintext +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Summary +======= +The implementation of the summaization function works as expected. + +>>> from epydoc.markup import restructuredtext +>>> def getsummary(s): +... p = restructuredtext.parse_docstring(s, []) +... s, o = p.summary() +... s = s.to_plaintext(None).strip() +... return s, o + +#Let's not lose anything! + +>>> getsummary("Single line") +(u'Single line', False) + +>>> getsummary("Single line.") +(u'Single line.', False) + +>>> getsummary(""" +... Single line *with* period. +... """) +(u'Single line with period.', False) + +>>> getsummary(""" +... Single line `with` period. +... +... :type: Also with a tag. +... """) +(u'Single line with period.', False) + +>>> getsummary(""" +... Other lines **with** period. +... This is attached +... """) +(u'Other lines with period.', True) + +>>> getsummary(""" +... Other lines *with* period. +... +... This is detached +... +... :type: Also with a tag. +... """) +(u'Other lines with period.', True) + +>>> getsummary(""" +... Other lines without period +... This is attached +... """) +(u'Other lines without period\nThis is attached', False) + +>>> getsummary(""" +... Other lines without period +... +... This is detached +... """) +(u'Other lines without period...', True) + +>>> getsummary(""" +... Single line *without* period +... +... :type: Also with a tag. +... """) +(u'Single line without period', False) + +>>> getsummary(""" +... This is the first line. +... +... :type: Also with a tag. +... +... Other stuff after a tag. +... """) +(u'This is the first line.', True) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ed...@us...> - 2007-02-11 04:04:29
|
Revision: 1450 http://svn.sourceforge.net/epydoc/?rev=1450&view=rev Author: edloper Date: 2007-02-10 20:04:28 -0800 (Sat, 10 Feb 2007) Log Message: ----------- - Fixed SF bug 1657050, which was caused because the introspector decided not to introspect values that it knew were imported; but it then turned out that it should have introspected them, because they were explicitly listed in __all__. The patch changes the docintrospector to introspect all values listed in __all__. Modified Paths: -------------- trunk/epydoc/src/epydoc/docintrospecter.py trunk/epydoc/src/epydoc/test/docintrospecter.doctest Modified: trunk/epydoc/src/epydoc/docintrospecter.py =================================================================== --- trunk/epydoc/src/epydoc/docintrospecter.py 2007-02-11 03:18:26 UTC (rev 1449) +++ trunk/epydoc/src/epydoc/docintrospecter.py 2007-02-11 04:04:28 UTC (rev 1450) @@ -240,6 +240,14 @@ if module_doc.package not in (None, UNKNOWN): module_doc.package.submodules.append(module_doc) + # Look up the module's __all__ attribute (public names). + public_names = None + if hasattr(module, '__all__'): + try: + public_names = set([str(name) for name in module.__all__]) + except KeyboardInterrupt: raise + except: pass + # Record the module's variables. module_doc.variables = {} for child_name in dir(module): @@ -249,7 +257,10 @@ # Create a VariableDoc for the child, and introspect its # value if it's defined in this module. container = get_containing_module(child) - if container is not None and container == module_doc.canonical_name: + if ((container is not None and + container == module_doc.canonical_name) or + (public_names is not None and + child_name in public_names)): # Local variable. child_val_doc = introspect_docs(child, context=module_doc, module_name=dotted_name) @@ -278,22 +289,18 @@ container=module_doc, docs_extracted_by='introspecter') + # If the module's __all__ attribute is set, use it to set the + # variables public/private status and imported status. + if public_names is not None: + if child_name in public_names: + child_var_doc.is_public = True + if not isinstance(child_var_doc, ModuleDoc): + child_var_doc.is_imported = False + else: + child_var_doc.is_public = False + module_doc.variables[child_name] = child_var_doc - # Record the module's __all__ attribute (public names). - if hasattr(module, '__all__'): - try: - public_names = set([str(name) for name in module.__all__]) - for name, var_doc in module_doc.variables.items(): - if name in public_names: - var_doc.is_public = True - if not isinstance(var_doc, ModuleDoc): - var_doc.is_imported = False - else: - var_doc.is_public = False - except KeyboardInterrupt: raise - except: pass - return module_doc #//////////////////////////////////////////////////////////// Modified: trunk/epydoc/src/epydoc/test/docintrospecter.doctest =================================================================== --- trunk/epydoc/src/epydoc/test/docintrospecter.doctest 2007-02-11 03:18:26 UTC (rev 1449) +++ trunk/epydoc/src/epydoc/test/docintrospecter.doctest 2007-02-11 04:04:28 UTC (rev 1450) @@ -614,3 +614,24 @@ +- name = 'b' +- value +- ClassDoc for epydoc_test.B [1] (defined above) + +Closed Bugs +=========== + +SF Bug [ 1657050 ] Builtins not resolved in "os" +------------------------------------------------ +If a variable is listed in __all__, then we need to introspect it, +even if we know for certain that it's imported. Before this bug +was fixed, the following test generated a generic 'ValueDoc' value +instead of a 'RoutineDoc' value, because it didn't introspect the +value of getcwd. + + >>> x = runintrospecter(s=""" + ... __all__ = ['getcwd'] + ... from os import getcwd + ... """, attribs='variables value') + ModuleDoc for epydoc_test [0] + +- variables + +- getcwd => VariableDoc for epydoc_test.getcwd [1] + +- value + +- RoutineDoc [2] This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |