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