[Epydoc-commits] SF.net SVN: epydoc: [1444] trunk/epydoc/src/epydoc
Brought to you by:
edloper
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. |