mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-18 00:20:54 +07:00
2204 lines
90 KiB
Python
2204 lines
90 KiB
Python
# epydoc -- API Documentation Classes
|
|
#
|
|
# Copyright (C) 2005 Edward Loper
|
|
# Author: Edward Loper <edloper@loper.org>
|
|
# URL: <http://epydoc.sf.net>
|
|
#
|
|
# $Id: apidoc.py 1675 2008-01-29 17:12:56Z edloper $
|
|
|
|
"""
|
|
Classes for encoding API documentation about Python programs.
|
|
These classes are used as a common representation for combining
|
|
information derived from introspection and from parsing.
|
|
|
|
The API documentation for a Python program is encoded using a graph of
|
|
L{APIDoc} objects, each of which encodes information about a single
|
|
Python variable or value. C{APIDoc} has two direct subclasses:
|
|
L{VariableDoc}, for documenting variables; and L{ValueDoc}, for
|
|
documenting values. The C{ValueDoc} class is subclassed further, to
|
|
define the different pieces of information that should be recorded
|
|
about each value type:
|
|
|
|
G{classtree: APIDoc}
|
|
|
|
The distinction between variables and values is intentionally made
|
|
explicit. This allows us to distinguish information about a variable
|
|
itself (such as whether it should be considered 'public' in its
|
|
containing namespace) from information about the value it contains
|
|
(such as what type the value has). This distinction is also important
|
|
because several variables can contain the same value: each variable
|
|
should be described by a separate C{VariableDoc}; but we only need one
|
|
C{ValueDoc}, since they share a single value.
|
|
|
|
@todo: Add a cache to canonical name lookup?
|
|
"""
|
|
__docformat__ = 'epytext en'
|
|
|
|
######################################################################
|
|
## Imports
|
|
######################################################################
|
|
|
|
import types, re, os.path, pickle
|
|
from epydoc import log
|
|
import epydoc
|
|
import __builtin__
|
|
from epydoc.compat import * # Backwards compatibility
|
|
from epydoc.util import decode_with_backslashreplace, py_src_filename
|
|
import epydoc.markup.pyval_repr
|
|
|
|
######################################################################
|
|
# Dotted Names
|
|
######################################################################
|
|
|
|
class DottedName:
|
|
"""
|
|
A sequence of identifiers, separated by periods, used to name a
|
|
Python variable, value, or argument. The identifiers that make up
|
|
a dotted name can be accessed using the indexing operator:
|
|
|
|
>>> name = DottedName('epydoc', 'api_doc', 'DottedName')
|
|
>>> print name
|
|
epydoc.apidoc.DottedName
|
|
>>> name[1]
|
|
'api_doc'
|
|
"""
|
|
UNREACHABLE = "??"
|
|
_IDENTIFIER_RE = re.compile("""(?x)
|
|
(%s | # UNREACHABLE marker, or..
|
|
(script-)? # Prefix: script (not a module)
|
|
\w+ # Identifier (yes, identifiers starting with a
|
|
# digit are allowed. See SF bug #1649347)
|
|
'?) # Suffix: submodule that is shadowed by a var
|
|
(-\d+)? # Suffix: unreachable vals with the same name
|
|
$"""
|
|
% re.escape(UNREACHABLE))
|
|
|
|
class InvalidDottedName(ValueError):
|
|
"""
|
|
An exception raised by the DottedName constructor when one of
|
|
its arguments is not a valid dotted name.
|
|
"""
|
|
|
|
_ok_identifiers = set()
|
|
"""A cache of identifier strings that have been checked against
|
|
_IDENTIFIER_RE and found to be acceptable."""
|
|
|
|
def __init__(self, *pieces, **options):
|
|
"""
|
|
Construct a new dotted name from the given sequence of pieces,
|
|
each of which can be either a C{string} or a C{DottedName}.
|
|
Each piece is divided into a sequence of identifiers, and
|
|
these sequences are combined together (in order) to form the
|
|
identifier sequence for the new C{DottedName}. If a piece
|
|
contains a string, then it is divided into substrings by
|
|
splitting on periods, and each substring is checked to see if
|
|
it is a valid identifier.
|
|
|
|
As an optimization, C{pieces} may also contain a single tuple
|
|
of values. In that case, that tuple will be used as the
|
|
C{DottedName}'s identifiers; it will I{not} be checked to
|
|
see if it's valid.
|
|
|
|
@kwparam strict: if true, then raise an L{InvalidDottedName}
|
|
if the given name is invalid.
|
|
"""
|
|
if len(pieces) == 1 and isinstance(pieces[0], tuple):
|
|
self._identifiers = pieces[0] # Optimization
|
|
return
|
|
if len(pieces) == 0:
|
|
raise DottedName.InvalidDottedName('Empty DottedName')
|
|
self._identifiers = []
|
|
for piece in pieces:
|
|
if isinstance(piece, DottedName):
|
|
self._identifiers += piece._identifiers
|
|
elif isinstance(piece, basestring):
|
|
for subpiece in piece.split('.'):
|
|
if piece not in self._ok_identifiers:
|
|
if not self._IDENTIFIER_RE.match(subpiece):
|
|
if options.get('strict'):
|
|
raise DottedName.InvalidDottedName(
|
|
'Bad identifier %r' % (piece,))
|
|
else:
|
|
log.warning("Identifier %r looks suspicious; "
|
|
"using it anyway." % piece)
|
|
self._ok_identifiers.add(piece)
|
|
self._identifiers.append(subpiece)
|
|
else:
|
|
raise TypeError('Bad identifier %r: expected '
|
|
'DottedName or str' % (piece,))
|
|
self._identifiers = tuple(self._identifiers)
|
|
|
|
def __repr__(self):
|
|
idents = [`ident` for ident in self._identifiers]
|
|
return 'DottedName(' + ', '.join(idents) + ')'
|
|
|
|
def __str__(self):
|
|
"""
|
|
Return the dotted name as a string formed by joining its
|
|
identifiers with periods:
|
|
|
|
>>> print DottedName('epydoc', 'api_doc', DottedName')
|
|
epydoc.apidoc.DottedName
|
|
"""
|
|
return '.'.join(self._identifiers)
|
|
|
|
def __add__(self, other):
|
|
"""
|
|
Return a new C{DottedName} whose identifier sequence is formed
|
|
by adding C{other}'s identifier sequence to C{self}'s.
|
|
"""
|
|
if isinstance(other, (basestring, DottedName)):
|
|
return DottedName(self, other)
|
|
else:
|
|
return DottedName(self, *other)
|
|
|
|
def __radd__(self, other):
|
|
"""
|
|
Return a new C{DottedName} whose identifier sequence is formed
|
|
by adding C{self}'s identifier sequence to C{other}'s.
|
|
"""
|
|
if isinstance(other, (basestring, DottedName)):
|
|
return DottedName(other, self)
|
|
else:
|
|
return DottedName(*(list(other)+[self]))
|
|
|
|
def __getitem__(self, i):
|
|
"""
|
|
Return the C{i}th identifier in this C{DottedName}. If C{i} is
|
|
a non-empty slice, then return a C{DottedName} built from the
|
|
identifiers selected by the slice. If C{i} is an empty slice,
|
|
return an empty list (since empty C{DottedName}s are not valid).
|
|
"""
|
|
if isinstance(i, types.SliceType):
|
|
pieces = self._identifiers[i.start:i.stop]
|
|
if pieces: return DottedName(pieces)
|
|
else: return []
|
|
else:
|
|
return self._identifiers[i]
|
|
|
|
def __hash__(self):
|
|
return hash(self._identifiers)
|
|
|
|
def __cmp__(self, other):
|
|
"""
|
|
Compare this dotted name to C{other}. Two dotted names are
|
|
considered equal if their identifier subsequences are equal.
|
|
Ordering between dotted names is lexicographic, in order of
|
|
identifier from left to right.
|
|
"""
|
|
if not isinstance(other, DottedName):
|
|
return -1
|
|
return cmp(self._identifiers, other._identifiers)
|
|
|
|
def __len__(self):
|
|
"""
|
|
Return the number of identifiers in this dotted name.
|
|
"""
|
|
return len(self._identifiers)
|
|
|
|
def container(self):
|
|
"""
|
|
Return the DottedName formed by removing the last identifier
|
|
from this dotted name's identifier sequence. If this dotted
|
|
name only has one name in its identifier sequence, return
|
|
C{None} instead.
|
|
"""
|
|
if len(self._identifiers) == 1:
|
|
return None
|
|
else:
|
|
return DottedName(*self._identifiers[:-1])
|
|
|
|
def dominates(self, name, strict=False):
|
|
"""
|
|
Return true if this dotted name is equal to a prefix of
|
|
C{name}. If C{strict} is true, then also require that
|
|
C{self!=name}.
|
|
|
|
>>> DottedName('a.b').dominates(DottedName('a.b.c.d'))
|
|
True
|
|
"""
|
|
len_self = len(self._identifiers)
|
|
len_name = len(name._identifiers)
|
|
|
|
if (len_self > len_name) or (strict and len_self == len_name):
|
|
return False
|
|
# The following is redundant (the first clause is implied by
|
|
# the second), but is done as an optimization.
|
|
return ((self._identifiers[0] == name._identifiers[0]) and
|
|
self._identifiers == name._identifiers[:len_self])
|
|
|
|
def contextualize(self, context):
|
|
"""
|
|
If C{self} and C{context} share a common ancestor, then return
|
|
a name for C{self}, relative to that ancestor. If they do not
|
|
share a common ancestor (or if C{context} is C{UNKNOWN}), then
|
|
simply return C{self}.
|
|
|
|
This is used to generate shorter versions of dotted names in
|
|
cases where users can infer the intended target from the
|
|
context.
|
|
|
|
@type context: L{DottedName}
|
|
@rtype: L{DottedName}
|
|
"""
|
|
if context is UNKNOWN or not context or len(self) <= 1:
|
|
return self
|
|
if self[0] == context[0]:
|
|
return self[1:].contextualize(context[1:])
|
|
else:
|
|
return self
|
|
|
|
# Find the first index where self & context differ.
|
|
for i in range(min(len(context), len(self))):
|
|
if self._identifiers[i] != context._identifiers[i]:
|
|
first_difference = i
|
|
break
|
|
else:
|
|
first_difference = i+1
|
|
|
|
# Strip off anything before that index.
|
|
if first_difference == 0:
|
|
return self
|
|
elif first_difference == len(self):
|
|
return self[-1:]
|
|
else:
|
|
return self[first_difference:]
|
|
|
|
######################################################################
|
|
# UNKNOWN Value
|
|
######################################################################
|
|
|
|
class _Sentinel:
|
|
"""
|
|
A unique value that won't compare equal to any other value. This
|
|
class is used to create L{UNKNOWN}.
|
|
"""
|
|
def __init__(self, name):
|
|
self.name = name
|
|
def __repr__(self):
|
|
return '<%s>' % self.name
|
|
def __nonzero__(self):
|
|
raise ValueError('Sentinel value <%s> can not be used as a boolean' %
|
|
self.name)
|
|
|
|
UNKNOWN = _Sentinel('UNKNOWN')
|
|
"""A special value used to indicate that a given piece of
|
|
information about an object is unknown. This is used as the
|
|
default value for all instance variables."""
|
|
|
|
######################################################################
|
|
# API Documentation Objects: Abstract Base Classes
|
|
######################################################################
|
|
|
|
class APIDoc(object):
|
|
"""
|
|
API documentation information for a single element of a Python
|
|
program. C{APIDoc} itself is an abstract base class; subclasses
|
|
are used to specify what information should be recorded about each
|
|
type of program element. In particular, C{APIDoc} has two direct
|
|
subclasses, C{VariableDoc} for documenting variables and
|
|
C{ValueDoc} for documenting values; and the C{ValueDoc} class is
|
|
subclassed further for different value types.
|
|
|
|
Each C{APIDoc} subclass specifies the set of attributes that
|
|
should be used to record information about the corresponding
|
|
program element type. The default value for each attribute is
|
|
stored in the class; these default values can then be overridden
|
|
with instance variables. Most attributes use the special value
|
|
L{UNKNOWN} as their default value, to indicate that the correct
|
|
value for that attribute has not yet been determined. This makes
|
|
it easier to merge two C{APIDoc} objects that are documenting the
|
|
same element (in particular, to merge information about an element
|
|
that was derived from parsing with information that was derived
|
|
from introspection).
|
|
|
|
For all attributes with boolean values, use only the constants
|
|
C{True} and C{False} to designate true and false. In particular,
|
|
do I{not} use other values that evaluate as true or false, such as
|
|
C{2} or C{()}. This restriction makes it easier to handle
|
|
C{UNKNOWN} values. For example, to test if a boolean attribute is
|
|
C{True} or C{UNKNOWN}, use 'C{attrib in (True, UNKNOWN)}' or
|
|
'C{attrib is not False}'.
|
|
|
|
Two C{APIDoc} objects describing the same object can be X{merged},
|
|
using the method L{merge_and_overwrite(other)}. After two
|
|
C{APIDoc}s are merged, any changes to one will be reflected in the
|
|
other. This is accomplished by setting the two C{APIDoc} objects
|
|
to use a shared instance dictionary. See the documentation for
|
|
L{merge_and_overwrite} for more information, and some important
|
|
caveats about hashing.
|
|
"""
|
|
#{ Docstrings
|
|
docstring = UNKNOWN
|
|
"""@ivar: The documented item's docstring.
|
|
@type: C{string} or C{None}"""
|
|
|
|
docstring_lineno = UNKNOWN
|
|
"""@ivar: The line number on which the documented item's docstring
|
|
begins.
|
|
@type: C{int}"""
|
|
#} end of "docstrings" group
|
|
|
|
#{ Information Extracted from Docstrings
|
|
descr = UNKNOWN
|
|
"""@ivar: A description of the documented item, extracted from its
|
|
docstring.
|
|
@type: L{ParsedDocstring<epydoc.markup.ParsedDocstring>}"""
|
|
|
|
summary = UNKNOWN
|
|
"""@ivar: A summary description of the documented item, extracted from
|
|
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
|
|
C{(field, arg, descr)}. But that may change.
|
|
@type: C{(str, str, L{ParsedDocstring<markup.ParsedDocstring>})}"""
|
|
|
|
extra_docstring_fields = UNKNOWN
|
|
"""@ivar: A list of new docstring fields tags that are defined by the
|
|
documented item's docstring. These new field tags can be used by
|
|
this item or by any item it contains.
|
|
@type: L{DocstringField <epydoc.docstringparser.DocstringField>}"""
|
|
#} end of "information extracted from docstrings" group
|
|
|
|
#{ Source Information
|
|
docs_extracted_by = UNKNOWN # 'parser' or 'introspecter' or 'both'
|
|
"""@ivar: Information about where the information contained by this
|
|
C{APIDoc} came from. Can be one of C{'parser'},
|
|
C{'introspector'}, or C{'both'}.
|
|
@type: C{str}"""
|
|
#} end of "source information" group
|
|
|
|
def __init__(self, **kwargs):
|
|
"""
|
|
Construct a new C{APIDoc} object. Keyword arguments may be
|
|
used to initialize the new C{APIDoc}'s attributes.
|
|
|
|
@raise TypeError: If a keyword argument is specified that does
|
|
not correspond to a valid attribute for this (sub)class of
|
|
C{APIDoc}.
|
|
"""
|
|
if epydoc.DEBUG:
|
|
for key in kwargs:
|
|
if key[0] != '_' and not hasattr(self.__class__, key):
|
|
raise TypeError('%s got unexpected arg %r' %
|
|
(self.__class__.__name__, key))
|
|
self.__dict__.update(kwargs)
|
|
|
|
def _debug_setattr(self, attr, val):
|
|
"""
|
|
Modify an C{APIDoc}'s attribute. This is used when
|
|
L{epydoc.DEBUG} is true, to make sure we don't accidentally
|
|
set any inappropriate attributes on C{APIDoc} objects.
|
|
|
|
@raise AttributeError: If C{attr} is not a valid attribute for
|
|
this (sub)class of C{APIDoc}. (C{attr} is considered a
|
|
valid attribute iff C{self.__class__} defines an attribute
|
|
with that name.)
|
|
"""
|
|
# Don't intercept special assignments like __class__, or
|
|
# assignments to private variables.
|
|
if attr.startswith('_'):
|
|
return object.__setattr__(self, attr, val)
|
|
if not hasattr(self, attr):
|
|
raise AttributeError('%s does not define attribute %r' %
|
|
(self.__class__.__name__, attr))
|
|
self.__dict__[attr] = val
|
|
|
|
if epydoc.DEBUG:
|
|
__setattr__ = _debug_setattr
|
|
|
|
def __repr__(self):
|
|
return '<%s>' % self.__class__.__name__
|
|
|
|
def pp(self, doublespace=0, depth=5, exclude=(), include=()):
|
|
"""
|
|
Return a pretty-printed string representation for the
|
|
information contained in this C{APIDoc}.
|
|
"""
|
|
return pp_apidoc(self, doublespace, depth, exclude, include)
|
|
__str__ = pp
|
|
|
|
def specialize_to(self, cls):
|
|
"""
|
|
Change C{self}'s class to C{cls}. C{cls} must be a subclass
|
|
of C{self}'s current class. For example, if a generic
|
|
C{ValueDoc} was created for a value, and it is determined that
|
|
the value is a routine, you can update its class with:
|
|
|
|
>>> valdoc.specialize_to(RoutineDoc)
|
|
"""
|
|
if not issubclass(cls, self.__class__):
|
|
raise ValueError('Can not specialize to %r' % cls)
|
|
# Update the class.
|
|
self.__class__ = cls
|
|
# Update the class of any other apidoc's in the mergeset.
|
|
if self.__mergeset is not None:
|
|
for apidoc in self.__mergeset:
|
|
apidoc.__class__ = cls
|
|
# Re-initialize self, in case the subclass constructor does
|
|
# any special processing on its arguments.
|
|
self.__init__(**self.__dict__)
|
|
|
|
__has_been_hashed = False
|
|
"""True iff L{self.__hash__()} has ever been called."""
|
|
|
|
def __hash__(self):
|
|
self.__has_been_hashed = True
|
|
return id(self.__dict__)
|
|
|
|
def __cmp__(self, other):
|
|
if not isinstance(other, APIDoc): return -1
|
|
if self.__dict__ is other.__dict__: return 0
|
|
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}
|
|
in this set shares a common instance dictionary (C{__dict__})."""
|
|
|
|
def merge_and_overwrite(self, other, ignore_hash_conflict=False):
|
|
"""
|
|
Combine C{self} and C{other} into a X{merged object}, such
|
|
that any changes made to one will affect the other. Any
|
|
attributes that C{other} had before merging will be discarded.
|
|
This is accomplished by copying C{self.__dict__} over
|
|
C{other.__dict__} and C{self.__class__} over C{other.__class__}.
|
|
|
|
Care must be taken with this method, since it modifies the
|
|
hash value of C{other}. To help avoid the problems that this
|
|
can cause, C{merge_and_overwrite} will raise an exception if
|
|
C{other} has ever been hashed, unless C{ignore_hash_conflict}
|
|
is True. Note that adding C{other} to a dictionary, set, or
|
|
similar data structure will implicitly cause it to be hashed.
|
|
If you do set C{ignore_hash_conflict} to True, then any
|
|
existing data structures that rely on C{other}'s hash staying
|
|
constant may become corrupted.
|
|
|
|
@return: C{self}
|
|
@raise ValueError: If C{other} has ever been hashed.
|
|
"""
|
|
# If we're already merged, then there's nothing to do.
|
|
if (self.__dict__ is other.__dict__ and
|
|
self.__class__ is other.__class__): return self
|
|
|
|
if other.__has_been_hashed and not ignore_hash_conflict:
|
|
raise ValueError("%r has already been hashed! Merging it "
|
|
"would cause its has value to change." % other)
|
|
|
|
# If other was itself already merged with anything,
|
|
# then we need to merge those too.
|
|
a,b = (self.__mergeset, other.__mergeset)
|
|
mergeset = (self.__mergeset or [self]) + (other.__mergeset or [other])
|
|
other.__dict__.clear()
|
|
for apidoc in mergeset:
|
|
#if apidoc is self: pass
|
|
apidoc.__class__ = self.__class__
|
|
apidoc.__dict__ = self.__dict__
|
|
self.__mergeset = mergeset
|
|
# Sanity chacks.
|
|
assert self in mergeset and other in mergeset
|
|
for apidoc in mergeset:
|
|
assert apidoc.__dict__ is self.__dict__
|
|
# Return self.
|
|
return self
|
|
|
|
def apidoc_links(self, **filters):
|
|
"""
|
|
Return a list of all C{APIDoc}s that are directly linked from
|
|
this C{APIDoc} (i.e., are contained or pointed to by one or
|
|
more of this C{APIDoc}'s attributes.)
|
|
|
|
Keyword argument C{filters} can be used to selectively exclude
|
|
certain categories of attribute value. For example, using
|
|
C{includes=False} will exclude variables that were imported
|
|
from other modules; and C{subclasses=False} will exclude
|
|
subclasses. The filter categories currently supported by
|
|
epydoc are:
|
|
- C{imports}: Imported variables.
|
|
- C{packages}: Containing packages for modules.
|
|
- C{submodules}: Contained submodules for packages.
|
|
- C{bases}: Bases for classes.
|
|
- C{subclasses}: Subclasses for classes.
|
|
- C{variables}: All variables.
|
|
- C{private}: Private variables.
|
|
- C{overrides}: Points from class variables to the variables
|
|
they override. This filter is False by default.
|
|
"""
|
|
return []
|
|
|
|
def reachable_valdocs(root, **filters):
|
|
"""
|
|
Return a list of all C{ValueDoc}s that can be reached, directly or
|
|
indirectly from the given root list of C{ValueDoc}s.
|
|
|
|
@param filters: A set of filters that can be used to prevent
|
|
C{reachable_valdocs} from following specific link types when
|
|
looking for C{ValueDoc}s that can be reached from the root
|
|
set. See C{APIDoc.apidoc_links} for a more complete
|
|
description.
|
|
"""
|
|
apidoc_queue = list(root)
|
|
val_set = set()
|
|
var_set = set()
|
|
while apidoc_queue:
|
|
api_doc = apidoc_queue.pop()
|
|
if isinstance(api_doc, ValueDoc):
|
|
val_set.add(api_doc)
|
|
else:
|
|
var_set.add(api_doc)
|
|
apidoc_queue.extend([v for v in api_doc.apidoc_links(**filters)
|
|
if v not in val_set and v not in var_set])
|
|
return val_set
|
|
|
|
######################################################################
|
|
# Variable Documentation Objects
|
|
######################################################################
|
|
|
|
class VariableDoc(APIDoc):
|
|
"""
|
|
API documentation information about a single Python variable.
|
|
|
|
@note: The only time a C{VariableDoc} will have its own docstring
|
|
is if that variable was created using an assignment statement, and
|
|
that assignment statement had a docstring-comment or was followed
|
|
by a pseudo-docstring.
|
|
"""
|
|
#{ Basic Variable Information
|
|
name = UNKNOWN
|
|
"""@ivar: The name of this variable in its containing namespace.
|
|
@type: C{str}"""
|
|
|
|
container = UNKNOWN
|
|
"""@ivar: API documentation for the namespace that contains this
|
|
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}"""
|
|
#}
|
|
|
|
#{ Information Extracted from Docstrings
|
|
type_descr = UNKNOWN
|
|
"""@ivar: A description of the variable's expected type, extracted from
|
|
its docstring.
|
|
@type: L{ParsedDocstring<epydoc.markup.ParsedDocstring>}"""
|
|
#} end of "information extracted from docstrings" group
|
|
|
|
#{ Information about Imported Variables
|
|
imported_from = UNKNOWN
|
|
"""@ivar: The fully qualified dotted name of the variable that this
|
|
variable's value was imported from. This attribute should only
|
|
be defined if C{is_instvar} is true.
|
|
@type: L{DottedName}"""
|
|
|
|
is_imported = UNKNOWN
|
|
"""@ivar: Was this variable's value imported from another module?
|
|
(Exception: variables that are explicitly included in __all__ have
|
|
C{is_imported} set to C{False}, even if they are in fact
|
|
imported.)
|
|
@type: C{bool}"""
|
|
#} end of "information about imported variables" group
|
|
|
|
#{ Information about Variables in Classes
|
|
is_instvar = UNKNOWN
|
|
"""@ivar: If true, then this variable is an instance variable; if false,
|
|
then this variable is a class variable. This attribute should
|
|
only be defined if the containing namespace is a class
|
|
@type: C{bool}"""
|
|
|
|
overrides = UNKNOWN # [XXX] rename -- don't use a verb.
|
|
"""@ivar: The API documentation for the variable that is overridden by
|
|
this variable. This attribute should only be defined if the
|
|
containing namespace is a class.
|
|
@type: L{VariableDoc}"""
|
|
#} end of "information about variables in classes" group
|
|
|
|
#{ Flags
|
|
is_alias = UNKNOWN
|
|
"""@ivar: Is this variable an alias for another variable with the same
|
|
value? If so, then this variable will be dispreferred when
|
|
assigning canonical names.
|
|
@type: C{bool}"""
|
|
|
|
is_public = UNKNOWN
|
|
"""@ivar: Is this variable part of its container's public API?
|
|
@type: C{bool}"""
|
|
#} end of "flags" group
|
|
|
|
def __init__(self, **kwargs):
|
|
APIDoc.__init__(self, **kwargs)
|
|
if self.is_public is UNKNOWN and self.name is not UNKNOWN:
|
|
self.is_public = (not self.name.startswith('_') or
|
|
self.name.endswith('_'))
|
|
|
|
def __repr__(self):
|
|
if self.canonical_name is not UNKNOWN:
|
|
return '<%s %s>' % (self.__class__.__name__, self.canonical_name)
|
|
if self.name is not UNKNOWN:
|
|
return '<%s %s>' % (self.__class__.__name__, self.name)
|
|
else:
|
|
return '<%s>' % self.__class__.__name__
|
|
|
|
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):
|
|
# nb: overrides filter is *False* by default.
|
|
if (filters.get('overrides', False) and
|
|
(self.overrides not in (None, UNKNOWN))):
|
|
overrides = [self.overrides]
|
|
else:
|
|
overrides = []
|
|
if self.value in (None, UNKNOWN):
|
|
return []+overrides
|
|
else:
|
|
return [self.value]+overrides
|
|
|
|
def is_detailed(self):
|
|
pval = super(VariableDoc, self).is_detailed()
|
|
if pval or self.value in (None, UNKNOWN):
|
|
return pval
|
|
|
|
if (self.overrides not in (None, UNKNOWN) and
|
|
isinstance(self.value, RoutineDoc)):
|
|
return True
|
|
|
|
if isinstance(self.value, GenericValueDoc):
|
|
# [XX] This is a little hackish -- we assume that the
|
|
# summary lines will have SUMMARY_REPR_LINELEN chars,
|
|
# that len(name) of those will be taken up by the name,
|
|
# and that 3 of those will be taken up by " = " between
|
|
# the name & val. Note that if any docwriter uses a
|
|
# different formula for maxlen for this, then it will
|
|
# not get the right value for is_detailed().
|
|
maxlen = self.value.SUMMARY_REPR_LINELEN-3-len(self.name)
|
|
return (not self.value.summary_pyval_repr(maxlen).is_complete)
|
|
else:
|
|
return self.value.is_detailed()
|
|
|
|
######################################################################
|
|
# Value Documentation Objects
|
|
######################################################################
|
|
|
|
class ValueDoc(APIDoc):
|
|
"""
|
|
API documentation information about a single Python value.
|
|
"""
|
|
canonical_name = UNKNOWN
|
|
"""@ivar: A dotted name that serves as a unique identifier for
|
|
this C{ValueDoc}'s value. If the value can be reached using a
|
|
single sequence of identifiers (given the appropriate imports),
|
|
then that sequence of identifiers is used as its canonical name.
|
|
If the value can be reached by multiple sequences of identifiers
|
|
(i.e., if it has multiple aliases), then one of those sequences of
|
|
identifiers is used. If the value cannot be reached by any
|
|
sequence of identifiers (e.g., if it was used as a base class but
|
|
then its variable was deleted), then its canonical name will start
|
|
with C{'??'}. If necessary, a dash followed by a number will be
|
|
appended to the end of a non-reachable identifier to make its
|
|
canonical name unique.
|
|
|
|
When possible, canonical names are chosen when new C{ValueDoc}s
|
|
are created. However, this is sometimes not possible. If a
|
|
canonical name can not be chosen when the C{ValueDoc} is created,
|
|
then one will be assigned by L{assign_canonical_names()
|
|
<docbuilder.assign_canonical_names>}.
|
|
|
|
@type: L{DottedName}"""
|
|
|
|
#{ Value Representation
|
|
pyval = UNKNOWN
|
|
"""@ivar: A pointer to the actual Python object described by this
|
|
C{ValueDoc}. This is used to display the value (e.g., when
|
|
describing a variable.) Use L{pyval_repr()} to generate a
|
|
plaintext string representation of this value.
|
|
@type: Python object"""
|
|
|
|
parse_repr = UNKNOWN
|
|
"""@ivar: A text representation of this value, extracted from
|
|
parsing its source code. This representation may not accurately
|
|
reflect the actual value (e.g., if the value was modified after
|
|
the initial assignment).
|
|
@type: C{unicode}"""
|
|
|
|
REPR_MAXLINES = 5
|
|
"""@cvar: The maximum number of lines of text that should be
|
|
generated by L{pyval_repr()}. If the string representation does
|
|
not fit in this number of lines, an ellpsis marker (...) will
|
|
be placed at the end of the formatted representation."""
|
|
|
|
REPR_LINELEN = 75
|
|
"""@cvar: The maximum number of characters for lines of text that
|
|
should be generated by L{pyval_repr()}. Any lines that exceed
|
|
this number of characters will be line-wrappped; The S{crarr}
|
|
symbol will be used to indicate that the line was wrapped."""
|
|
|
|
SUMMARY_REPR_LINELEN = 75
|
|
"""@cvar: The maximum number of characters for the single-line
|
|
text representation generated by L{summary_pyval_repr()}. If
|
|
the value's representation does not fit in this number of
|
|
characters, an ellipsis marker (...) will be placed at the end
|
|
of the formatted representation."""
|
|
|
|
REPR_MIN_SCORE = 0
|
|
"""@cvar: The minimum score that a value representation based on
|
|
L{pyval} should have in order to be used instead of L{parse_repr}
|
|
as the canonical representation for this C{ValueDoc}'s value.
|
|
@see: L{epydoc.markup.pyval_repr}"""
|
|
#} end of "value representation" group
|
|
|
|
#{ Context
|
|
defining_module = UNKNOWN
|
|
"""@ivar: The documentation for the module that defines this
|
|
value. This is used, e.g., to lookup the appropriate markup
|
|
language for docstrings. For a C{ModuleDoc},
|
|
C{defining_module} should be C{self}.
|
|
@type: L{ModuleDoc}"""
|
|
#} end of "context group"
|
|
|
|
#{ Information about Imported Variables
|
|
proxy_for = None # [xx] in progress.
|
|
"""@ivar: If C{proxy_for} is not None, then this value was
|
|
imported from another file. C{proxy_for} is the dotted name of
|
|
the variable that this value was imported from. If that
|
|
variable is documented, then its C{value} may contain more
|
|
complete API documentation about this value. The C{proxy_for}
|
|
attribute is used by the source code parser to link imported
|
|
values to their source values (in particular, for base
|
|
classes). When possible, these proxy C{ValueDoc}s are replaced
|
|
by the imported value's C{ValueDoc} by
|
|
L{link_imports()<docbuilder.link_imports>}.
|
|
@type: L{DottedName}"""
|
|
#} end of "information about imported variables" group
|
|
|
|
#: @ivar:
|
|
#: This is currently used to extract values from __all__, etc, in
|
|
#: the docparser module; maybe I should specialize
|
|
#: process_assignment and extract it there? Although, for __all__,
|
|
#: it's not clear where I'd put the value, since I just use it to
|
|
#: set private/public/imported attribs on other vars (that might not
|
|
#: exist yet at the time.)
|
|
toktree = UNKNOWN
|
|
|
|
def __repr__(self):
|
|
if self.canonical_name is not UNKNOWN:
|
|
return '<%s %s>' % (self.__class__.__name__, self.canonical_name)
|
|
else:
|
|
return '<%s %s>' % (self.__class__.__name__,
|
|
self.summary_pyval_repr().to_plaintext(None))
|
|
|
|
def __setstate__(self, state):
|
|
self.__dict__ = state
|
|
|
|
def __getstate__(self):
|
|
"""
|
|
State serializer for the pickle module. This is necessary
|
|
because sometimes the C{pyval} attribute contains an
|
|
un-pickleable value.
|
|
"""
|
|
# Construct our pickled dictionary. Maintain this dictionary
|
|
# as a private attribute, so we can reuse it later, since
|
|
# merged objects need to share a single dictionary.
|
|
if not hasattr(self, '_ValueDoc__pickle_state'):
|
|
# Make sure __pyval_repr & __summary_pyval_repr are cached:
|
|
self.pyval_repr(), self.summary_pyval_repr()
|
|
# Construct the dictionary; leave out 'pyval'.
|
|
self.__pickle_state = self.__dict__.copy()
|
|
self.__pickle_state['pyval'] = UNKNOWN
|
|
|
|
if not isinstance(self, GenericValueDoc):
|
|
assert self.__pickle_state != {}
|
|
# Return the pickle state.
|
|
return self.__pickle_state
|
|
|
|
#{ Value Representation
|
|
def pyval_repr(self):
|
|
"""
|
|
Return a formatted representation of the Python object
|
|
described by this C{ValueDoc}. This representation may
|
|
include data from introspection or parsing, and is authorative
|
|
as 'the best way to represent a Python value.' Any lines that
|
|
go beyond L{REPR_LINELEN} characters will be wrapped; and if
|
|
the representation as a whole takes more than L{REPR_MAXLINES}
|
|
lines, then it will be truncated (with an ellipsis marker).
|
|
This function will never return L{UNKNOWN} or C{None}.
|
|
|
|
@rtype: L{ColorizedPyvalRepr}
|
|
"""
|
|
# Use self.__pyval_repr to cache the result.
|
|
if not hasattr(self, '_ValueDoc__pyval_repr'):
|
|
self.__pyval_repr = epydoc.markup.pyval_repr.colorize_pyval(
|
|
self.pyval, self.parse_repr, self.REPR_MIN_SCORE,
|
|
self.REPR_LINELEN, self.REPR_MAXLINES, linebreakok=True)
|
|
return self.__pyval_repr
|
|
|
|
def summary_pyval_repr(self, max_len=None):
|
|
"""
|
|
Return a single-line formatted representation of the Python
|
|
object described by this C{ValueDoc}. This representation may
|
|
include data from introspection or parsing, and is authorative
|
|
as 'the best way to summarize a Python value.' If the
|
|
representation takes more then L{SUMMARY_REPR_LINELEN}
|
|
characters, then it will be truncated (with an ellipsis
|
|
marker). This function will never return L{UNKNOWN} or
|
|
C{None}.
|
|
|
|
@rtype: L{ColorizedPyvalRepr}
|
|
"""
|
|
# If max_len is specified, then do *not* cache the result.
|
|
if max_len is not None:
|
|
return epydoc.markup.pyval_repr.colorize_pyval(
|
|
self.pyval, self.parse_repr, self.REPR_MIN_SCORE,
|
|
max_len, maxlines=1, linebreakok=False)
|
|
|
|
# Use self.__summary_pyval_repr to cache the result.
|
|
if not hasattr(self, '_ValueDoc__summary_pyval_repr'):
|
|
self.__summary_pyval_repr = epydoc.markup.pyval_repr.colorize_pyval(
|
|
self.pyval, self.parse_repr, self.REPR_MIN_SCORE,
|
|
self.SUMMARY_REPR_LINELEN, maxlines=1, linebreakok=False)
|
|
return self.__summary_pyval_repr
|
|
#} end of "value representation" group
|
|
|
|
def apidoc_links(self, **filters):
|
|
return []
|
|
|
|
class GenericValueDoc(ValueDoc):
|
|
"""
|
|
API documentation about a 'generic' value, i.e., one that does not
|
|
have its own docstring or any information other than its value and
|
|
parse representation. C{GenericValueDoc}s do not get assigned
|
|
cannonical names.
|
|
"""
|
|
canonical_name = None
|
|
|
|
def is_detailed(self):
|
|
return (not self.summary_pyval_repr().is_complete)
|
|
|
|
class NamespaceDoc(ValueDoc):
|
|
"""
|
|
API documentation information about a singe Python namespace
|
|
value. (I.e., a module or a class).
|
|
"""
|
|
#{ Information about Variables
|
|
variables = UNKNOWN
|
|
"""@ivar: The contents of the namespace, encoded as a
|
|
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{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
|
|
namespace, in sorted order. The elements of this list should
|
|
exactly match the values of L{variables}. The sort order for
|
|
this list is defined as follows:
|
|
- Any variables listed in a C{@sort} docstring field are
|
|
listed in the order given by that field.
|
|
- These are followed by any variables that were found while
|
|
parsing the source code, in the order in which they were
|
|
defined in the source file.
|
|
- Finally, any remaining variables are listed in
|
|
alphabetical order.
|
|
@type: C{list} of L{VariableDoc}"""
|
|
sort_spec = UNKNOWN
|
|
"""@ivar: The order in which variables should be listed,
|
|
encoded as a list of names. Any variables whose names are not
|
|
included in this list should be listed alphabetically,
|
|
following the variables that are included.
|
|
@type: C{list} of C{str}"""
|
|
group_specs = UNKNOWN
|
|
"""@ivar: The groups that are defined by this namespace's
|
|
docstrings. C{group_specs} is encoded as an ordered list of
|
|
tuples C{(group_name, elt_names)}, where C{group_name} is the
|
|
|
|
name of a group and C{elt_names} is a list of element names in
|
|
that group. (An element can be a variable or a submodule.) A
|
|
'*' in an element name will match any string of characters.
|
|
@type: C{list} of C{(str,list)}"""
|
|
variable_groups = UNKNOWN
|
|
"""@ivar: A dictionary specifying what group each
|
|
variable belongs to. The keys of the dictionary are group
|
|
names, and the values are lists of C{VariableDoc}s. The order
|
|
that groups should be listed in should be taken from
|
|
L{group_specs}.
|
|
@type: C{dict} from C{str} to C{list} of L{VariableDoc}"""
|
|
#} end of group "information about variables"
|
|
|
|
def __init__(self, **kwargs):
|
|
kwargs.setdefault('variables', {})
|
|
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)
|
|
private = filters.get('private', True)
|
|
if variables and imports and private:
|
|
return self.variables.values() # list the common case first.
|
|
elif not variables:
|
|
return []
|
|
elif not imports and not private:
|
|
return [v for v in self.variables.values() if
|
|
v.is_imported != True and v.is_public != False]
|
|
elif not private:
|
|
return [v for v in self.variables.values() if
|
|
v.is_public != False]
|
|
elif not imports:
|
|
return [v for v in self.variables.values() if
|
|
v.is_imported != True]
|
|
assert 0, 'this line should be unreachable'
|
|
|
|
def init_sorted_variables(self):
|
|
"""
|
|
Initialize the L{sorted_variables} attribute, based on the
|
|
L{variables} and L{sort_spec} attributes. This should usually
|
|
be called after all variables have been added to C{variables}
|
|
(including any inherited variables for classes).
|
|
"""
|
|
unsorted = self.variables.copy()
|
|
self.sorted_variables = []
|
|
|
|
# Add any variables that are listed in sort_spec
|
|
if self.sort_spec is not UNKNOWN:
|
|
unused_idents = set(self.sort_spec)
|
|
for ident in self.sort_spec:
|
|
if ident in unsorted:
|
|
self.sorted_variables.append(unsorted.pop(ident))
|
|
unused_idents.discard(ident)
|
|
elif '*' in ident:
|
|
regexp = re.compile('^%s$' % ident.replace('*', '(.*)'))
|
|
# sort within matching group?
|
|
for name, var_doc in unsorted.items():
|
|
if regexp.match(name):
|
|
self.sorted_variables.append(unsorted.pop(name))
|
|
unused_idents.discard(ident)
|
|
for ident in unused_idents:
|
|
if ident not in ['__all__', '__docformat__', '__path__']:
|
|
log.warning("@sort: %s.%s not found" %
|
|
(self.canonical_name, ident))
|
|
|
|
|
|
# Add any remaining variables in alphabetical order.
|
|
var_docs = unsorted.items()
|
|
var_docs.sort()
|
|
for name, var_doc in var_docs:
|
|
self.sorted_variables.append(var_doc)
|
|
|
|
def init_variable_groups(self):
|
|
"""
|
|
Initialize the L{variable_groups} attribute, based on the
|
|
L{sorted_variables} and L{group_specs} attributes.
|
|
"""
|
|
if self.sorted_variables is UNKNOWN:
|
|
self.init_sorted_variables()
|
|
assert len(self.sorted_variables) == len(self.variables)
|
|
|
|
elts = [(v.name, v) for v in self.sorted_variables]
|
|
self._unused_groups = dict([(n,set(i)) for (n,i) in self.group_specs])
|
|
self.variable_groups = self._init_grouping(elts)
|
|
|
|
def group_names(self):
|
|
"""
|
|
Return a list of the group names defined by this namespace, in
|
|
the order in which they should be listed, with no duplicates.
|
|
"""
|
|
name_list = ['']
|
|
name_set = set()
|
|
for name, spec in self.group_specs:
|
|
if name not in name_set:
|
|
name_set.add(name)
|
|
name_list.append(name)
|
|
return name_list
|
|
|
|
def _init_grouping(self, elts):
|
|
"""
|
|
Divide a given a list of APIDoc objects into groups, as
|
|
specified by L{self.group_specs}.
|
|
|
|
@param elts: A list of tuples C{(name, apidoc)}.
|
|
|
|
@return: A list of tuples C{(groupname, elts)}, where
|
|
C{groupname} is the name of a group and C{elts} is a list of
|
|
C{APIDoc}s in that group. The first tuple has name C{''}, and
|
|
is used for ungrouped elements. The remaining tuples are
|
|
listed in the order that they appear in C{self.group_specs}.
|
|
Within each tuple, the elements are listed in the order that
|
|
they appear in C{api_docs}.
|
|
"""
|
|
# Make the common case fast.
|
|
if len(self.group_specs) == 0:
|
|
return {'': [elt[1] for elt in elts]}
|
|
|
|
ungrouped = set([elt_doc for (elt_name, elt_doc) in elts])
|
|
|
|
ungrouped = dict(elts)
|
|
groups = {}
|
|
for elt_name, elt_doc in elts:
|
|
for (group_name, idents) in self.group_specs:
|
|
group = groups.setdefault(group_name, [])
|
|
unused_groups = self._unused_groups[group_name]
|
|
for ident in idents:
|
|
if re.match('^%s$' % ident.replace('*', '(.*)'), elt_name):
|
|
unused_groups.discard(ident)
|
|
if elt_name in ungrouped:
|
|
group.append(ungrouped.pop(elt_name))
|
|
else:
|
|
log.warning("%s.%s in multiple groups" %
|
|
(self.canonical_name, elt_name))
|
|
|
|
# Convert ungrouped from an unordered set to an ordered list.
|
|
groups[''] = [elt_doc for (elt_name, elt_doc) in elts
|
|
if elt_name in ungrouped]
|
|
return groups
|
|
|
|
def report_unused_groups(self):
|
|
"""
|
|
Issue a warning for any @group items that were not used by
|
|
L{_init_grouping()}.
|
|
"""
|
|
for (group, unused_idents) in self._unused_groups.items():
|
|
for ident in unused_idents:
|
|
log.warning("@group %s: %s.%s not found" %
|
|
(group, self.canonical_name, ident))
|
|
|
|
class ModuleDoc(NamespaceDoc):
|
|
"""
|
|
API documentation information about a single module.
|
|
"""
|
|
#{ Information about the Module
|
|
filename = UNKNOWN
|
|
"""@ivar: The name of the file that defines the module.
|
|
@type: C{string}"""
|
|
docformat = UNKNOWN
|
|
"""@ivar: The markup language used by docstrings in this module.
|
|
@type: C{string}"""
|
|
#{ Information about Submodules
|
|
submodules = UNKNOWN
|
|
"""@ivar: Modules contained by this module (if this module
|
|
is a package). (Note: on rare occasions, a module may have a
|
|
submodule that is shadowed by a variable with the same name.)
|
|
@type: C{list} of L{ModuleDoc}"""
|
|
submodule_groups = UNKNOWN
|
|
"""@ivar: A dictionary specifying what group each
|
|
submodule belongs to. The keys of the dictionary are group
|
|
names, and the values are lists of C{ModuleDoc}s. The order
|
|
that groups should be listed in should be taken from
|
|
L{group_specs}.
|
|
@type: C{dict} from C{str} to C{list} of L{ModuleDoc}"""
|
|
#{ Information about Packages
|
|
package = UNKNOWN
|
|
"""@ivar: API documentation for the module's containing package.
|
|
@type: L{ModuleDoc}"""
|
|
is_package = UNKNOWN
|
|
"""@ivar: True if this C{ModuleDoc} describes a package.
|
|
@type: C{bool}"""
|
|
path = UNKNOWN
|
|
"""@ivar: If this C{ModuleDoc} describes a package, then C{path}
|
|
contains a list of directories that constitute its path (i.e.,
|
|
the value of its C{__path__} variable).
|
|
@type: C{list} of C{str}"""
|
|
#{ Information about Imported Variables
|
|
imports = UNKNOWN
|
|
"""@ivar: A list of the source names of variables imported into
|
|
this module. This is used to construct import graphs.
|
|
@type: C{list} of L{DottedName}"""
|
|
#}
|
|
|
|
def apidoc_links(self, **filters):
|
|
val_docs = NamespaceDoc.apidoc_links(self, **filters)
|
|
if (filters.get('packages', True) and
|
|
self.package not in (None, UNKNOWN)):
|
|
val_docs.append(self.package)
|
|
if (filters.get('submodules', True) and
|
|
self.submodules not in (None, UNKNOWN)):
|
|
val_docs += self.submodules
|
|
return val_docs
|
|
|
|
def init_submodule_groups(self):
|
|
"""
|
|
Initialize the L{submodule_groups} attribute, based on the
|
|
L{submodules} and L{group_specs} attributes.
|
|
"""
|
|
if self.submodules in (None, UNKNOWN):
|
|
return
|
|
self.submodules = sorted(self.submodules,
|
|
key=lambda m:m.canonical_name)
|
|
elts = [(m.canonical_name[-1], m) for m in self.submodules]
|
|
self.submodule_groups = self._init_grouping(elts)
|
|
|
|
def select_variables(self, group=None, value_type=None, public=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
|
|
whose values have the specified type. If C{group} is given,
|
|
then only return variables that belong to the specified group.
|
|
|
|
@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()},
|
|
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:
|
|
- 'class' - variables whose values are classes or types.
|
|
- 'function' - variables whose values are functions.
|
|
- 'other' - variables whose values are not classes,
|
|
exceptions, types, or functions.
|
|
@type value_type: C{string}
|
|
|
|
@param group: The name of the group for which variables should
|
|
be returned. A complete list of the groups defined by
|
|
this C{ModuleDoc} is available in the L{group_names}
|
|
instance variable. The first element of this list is
|
|
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):
|
|
raise ValueError('sorted_variables and variable_groups '
|
|
'must be initialized first.')
|
|
|
|
if group is None: var_list = self.sorted_variables
|
|
else:
|
|
var_list = self.variable_groups.get(group, self.sorted_variables)
|
|
|
|
# Public/private filter (Count UNKNOWN as public)
|
|
if public is True:
|
|
var_list = [v for v in var_list if v.is_public is not False]
|
|
elif public is False:
|
|
var_list = [v for v in var_list if v.is_public is False]
|
|
|
|
# Imported filter (Count UNKNOWN as non-imported)
|
|
if imported is True:
|
|
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]
|
|
|
|
# [xx] Modules are not currently included in any of these
|
|
# value types.
|
|
if value_type is None:
|
|
return var_list
|
|
elif value_type == 'class':
|
|
return [var_doc for var_doc in var_list
|
|
if (isinstance(var_doc.value, ClassDoc))]
|
|
elif value_type == 'function':
|
|
return [var_doc for var_doc in var_list
|
|
if isinstance(var_doc.value, RoutineDoc)]
|
|
elif value_type == 'other':
|
|
return [var_doc for var_doc in var_list
|
|
if not isinstance(var_doc.value,
|
|
(ClassDoc, RoutineDoc, ModuleDoc))]
|
|
else:
|
|
raise ValueError('Bad value type %r' % value_type)
|
|
|
|
class ClassDoc(NamespaceDoc):
|
|
"""
|
|
API documentation information about a single class.
|
|
"""
|
|
#{ Information about Base Classes
|
|
bases = UNKNOWN
|
|
"""@ivar: API documentation for the class's base classes.
|
|
@type: C{list} of L{ClassDoc}"""
|
|
#{ Information about Subclasses
|
|
subclasses = UNKNOWN
|
|
"""@ivar: API documentation for the class's known subclasses.
|
|
@type: C{list} of L{ClassDoc}"""
|
|
#}
|
|
|
|
def apidoc_links(self, **filters):
|
|
val_docs = NamespaceDoc.apidoc_links(self, **filters)
|
|
if (filters.get('bases', True) and
|
|
self.bases not in (None, UNKNOWN)):
|
|
val_docs += self.bases
|
|
if (filters.get('subclasses', True) and
|
|
self.subclasses not in (None, UNKNOWN)):
|
|
val_docs += self.subclasses
|
|
return val_docs
|
|
|
|
def is_type(self):
|
|
if self.canonical_name == DottedName('type'): return True
|
|
if self.bases is UNKNOWN: return False
|
|
for base in self.bases:
|
|
if isinstance(base, ClassDoc) and base.is_type():
|
|
return True
|
|
return False
|
|
|
|
def is_exception(self):
|
|
if self.canonical_name == DottedName('Exception'): return True
|
|
if self.bases is UNKNOWN: return False
|
|
for base in self.bases:
|
|
if isinstance(base, ClassDoc) and base.is_exception():
|
|
return True
|
|
return False
|
|
|
|
def is_newstyle_class(self):
|
|
if self.canonical_name == DottedName('object'): return True
|
|
if self.bases is UNKNOWN: return False
|
|
for base in self.bases:
|
|
if isinstance(base, ClassDoc) and base.is_newstyle_class():
|
|
return True
|
|
return False
|
|
|
|
def mro(self, warn_about_bad_bases=False):
|
|
if self.is_newstyle_class():
|
|
return self._c3_mro(warn_about_bad_bases)
|
|
else:
|
|
return self._dfs_bases([], set(), warn_about_bad_bases)
|
|
|
|
def _dfs_bases(self, mro, seen, warn_about_bad_bases):
|
|
if self in seen: return mro
|
|
mro.append(self)
|
|
seen.add(self)
|
|
if self.bases is not UNKNOWN:
|
|
for base in self.bases:
|
|
if isinstance(base, ClassDoc) and base.proxy_for is None:
|
|
base._dfs_bases(mro, seen, warn_about_bad_bases)
|
|
elif warn_about_bad_bases:
|
|
self._report_bad_base(base)
|
|
return mro
|
|
|
|
def _c3_mro(self, warn_about_bad_bases):
|
|
"""
|
|
Compute the class precedence list (mro) according to C3.
|
|
@seealso: U{http://www.python.org/2.3/mro.html}
|
|
"""
|
|
bases = [base for base in self.bases if isinstance(base, ClassDoc)]
|
|
if len(bases) != len(self.bases) and warn_about_bad_bases:
|
|
for base in self.bases:
|
|
if (not isinstance(base, ClassDoc) or
|
|
base.proxy_for is not None):
|
|
self._report_bad_base(base)
|
|
w = [warn_about_bad_bases]*len(bases)
|
|
return self._c3_merge([[self]] + map(ClassDoc._c3_mro, bases, w) +
|
|
[list(bases)])
|
|
|
|
def _report_bad_base(self, base):
|
|
if not isinstance(base, ClassDoc):
|
|
if not isinstance(base, GenericValueDoc):
|
|
base_name = base.canonical_name
|
|
elif base.parse_repr is not UNKNOWN:
|
|
base_name = base.parse_repr
|
|
else:
|
|
base_name = '%r' % base
|
|
log.warning("%s's base %s is not a class" %
|
|
(self.canonical_name, base_name))
|
|
elif base.proxy_for is not None:
|
|
log.warning("No information available for %s's base %s" %
|
|
(self.canonical_name, base.proxy_for))
|
|
|
|
def _c3_merge(self, seqs):
|
|
"""
|
|
Helper function for L{_c3_mro}.
|
|
"""
|
|
res = []
|
|
while 1:
|
|
nonemptyseqs=[seq for seq in seqs if seq]
|
|
if not nonemptyseqs: return res
|
|
for seq in nonemptyseqs: # find merge candidates among seq heads
|
|
cand = seq[0]
|
|
nothead=[s for s in nonemptyseqs if cand in s[1:]]
|
|
if nothead: cand=None #reject candidate
|
|
else: break
|
|
if not cand: raise "Inconsistent hierarchy"
|
|
res.append(cand)
|
|
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, detailed=None):
|
|
"""
|
|
Return a specified subset of this class's L{sorted_variables}
|
|
list. If C{value_type} is given, then only return variables
|
|
whose values have the specified type. If C{group} is given,
|
|
then only return variables that belong to the specified group.
|
|
If C{inherited} is True, then only return inherited variables;
|
|
if C{inherited} is False, then only return local variables.
|
|
|
|
@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_variable_groups()}.
|
|
|
|
@param value_type: A string specifying the value type for
|
|
which variables should be returned. Valid values are:
|
|
- 'instancemethod' - variables whose values are
|
|
instance methods.
|
|
- 'classmethod' - variables whose values are class
|
|
methods.
|
|
- 'staticmethod' - variables whose values are static
|
|
methods.
|
|
- 'properties' - variables whose values are properties.
|
|
- 'class' - variables whose values are nested classes
|
|
(including exceptions and types).
|
|
- 'instancevariable' - instance variables. This includes
|
|
any variables that are explicitly marked as instance
|
|
variables with docstring fields; and variables with
|
|
docstrings that are initialized in the constructor.
|
|
- 'classvariable' - class variables. This includes any
|
|
variables that are not included in any of the above
|
|
categories.
|
|
@type value_type: C{string}
|
|
|
|
@param group: The name of the group for which variables should
|
|
be returned. A complete list of the groups defined by
|
|
this C{ClassDoc} is available in the L{group_names}
|
|
instance variable. The first element of this list is
|
|
always the special group name C{''}, which is used for
|
|
variables that do not belong to any group.
|
|
@type group: C{string}
|
|
|
|
@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):
|
|
raise ValueError('sorted_variables and variable_groups '
|
|
'must be initialized first.')
|
|
|
|
if group is None: var_list = self.sorted_variables
|
|
else: var_list = self.variable_groups[group]
|
|
|
|
# Public/private filter (Count UNKNOWN as public)
|
|
if public is True:
|
|
var_list = [v for v in var_list if v.is_public is not False]
|
|
elif public is False:
|
|
var_list = [v for v in var_list if v.is_public is False]
|
|
|
|
# Inherited filter (Count UNKNOWN as non-inherited)
|
|
if inherited is None: pass
|
|
elif inherited:
|
|
var_list = [v for v in var_list if v.container != self]
|
|
else:
|
|
var_list = [v for v in var_list if v.container == self ]
|
|
|
|
# Imported filter (Count UNKNOWN as non-imported)
|
|
if imported is True:
|
|
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':
|
|
return [var_doc for var_doc in var_list
|
|
if (isinstance(var_doc.value, RoutineDoc) and
|
|
var_doc.is_instvar in (False, UNKNOWN))]
|
|
elif value_type == 'instancemethod':
|
|
return [var_doc for var_doc in var_list
|
|
if (isinstance(var_doc.value, RoutineDoc) and
|
|
not isinstance(var_doc.value, ClassMethodDoc) and
|
|
not isinstance(var_doc.value, StaticMethodDoc) and
|
|
var_doc.is_instvar in (False, UNKNOWN))]
|
|
elif value_type == 'classmethod':
|
|
return [var_doc for var_doc in var_list
|
|
if (isinstance(var_doc.value, ClassMethodDoc) and
|
|
var_doc.is_instvar in (False, UNKNOWN))]
|
|
elif value_type == 'staticmethod':
|
|
return [var_doc for var_doc in var_list
|
|
if (isinstance(var_doc.value, StaticMethodDoc) and
|
|
var_doc.is_instvar in (False, UNKNOWN))]
|
|
elif value_type == 'property':
|
|
return [var_doc for var_doc in var_list
|
|
if (isinstance(var_doc.value, PropertyDoc) and
|
|
var_doc.is_instvar in (False, UNKNOWN))]
|
|
elif value_type == 'class':
|
|
return [var_doc for var_doc in var_list
|
|
if (isinstance(var_doc.value, ClassDoc) and
|
|
var_doc.is_instvar in (False, UNKNOWN))]
|
|
elif value_type == 'instancevariable':
|
|
return [var_doc for var_doc in var_list
|
|
if var_doc.is_instvar is True]
|
|
elif value_type == 'classvariable':
|
|
return [var_doc for var_doc in var_list
|
|
if (var_doc.is_instvar in (False, UNKNOWN) and
|
|
not isinstance(var_doc.value,
|
|
(RoutineDoc, ClassDoc, PropertyDoc)))]
|
|
else:
|
|
raise ValueError('Bad value type %r' % value_type)
|
|
|
|
class RoutineDoc(ValueDoc):
|
|
"""
|
|
API documentation information about a single routine.
|
|
"""
|
|
#{ Signature
|
|
posargs = UNKNOWN
|
|
"""@ivar: The names of the routine's positional arguments.
|
|
If an argument list contains \"unpacking\" arguments, then
|
|
their names will be specified using nested lists. E.g., if
|
|
a function's argument list is C{((x1,y1), (x2,y2))}, then
|
|
posargs will be C{[['x1','y1'], ['x2','y2']]}.
|
|
@type: C{list}"""
|
|
posarg_defaults = UNKNOWN
|
|
"""@ivar: API documentation for the positional arguments'
|
|
default values. This list has the same length as C{posargs}, and
|
|
each element of C{posarg_defaults} describes the corresponding
|
|
argument in C{posargs}. For positional arguments with no default,
|
|
C{posargs_defaults} will contain None.
|
|
@type: C{list} of C{ValueDoc} or C{None}"""
|
|
vararg = UNKNOWN
|
|
"""@ivar: The name of the routine's vararg argument, or C{None} if
|
|
it has no vararg argument.
|
|
@type: C{string} or C{None}"""
|
|
kwarg = UNKNOWN
|
|
"""@ivar: The name of the routine's keyword argument, or C{None} if
|
|
it has no keyword argument.
|
|
@type: C{string} or C{None}"""
|
|
lineno = UNKNOWN # used to look up profiling info from pstats.
|
|
"""@ivar: The line number of the first line of the function's
|
|
signature. For Python functions, this is equal to
|
|
C{func.func_code.co_firstlineno}. The first line of a file
|
|
is considered line 1.
|
|
@type: C{int}"""
|
|
#} end of "signature" group
|
|
|
|
#{ Decorators
|
|
decorators = UNKNOWN
|
|
"""@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 C{string}"""
|
|
#} end of "decorators" group
|
|
|
|
#{ Information Extracted from Docstrings
|
|
arg_descrs = UNKNOWN
|
|
"""@ivar: A list of descriptions of the routine's
|
|
arguments. Each element of this list is a tuple C{(args,
|
|
descr)}, where C{args} is a list of argument names; and
|
|
C{descr} is a L{ParsedDocstring
|
|
<epydoc.markup.ParsedDocstring>} describing the argument(s)
|
|
specified by C{arg}.
|
|
@type: C{list}"""
|
|
arg_types = UNKNOWN
|
|
"""@ivar: Descriptions of the expected types for the
|
|
routine's arguments, encoded as a dictionary mapping from
|
|
argument names to type descriptions.
|
|
@type: C{dict} from C{string} to L{ParsedDocstring
|
|
<epydoc.markup.ParsedDocstring>}"""
|
|
return_descr = UNKNOWN
|
|
"""@ivar: A description of the value returned by this routine.
|
|
@type: L{ParsedDocstring<epydoc.markup.ParsedDocstring>}"""
|
|
return_type = UNKNOWN
|
|
"""@ivar: A description of expected type for the value
|
|
returned by this routine.
|
|
@type: L{ParsedDocstring<epydoc.markup.ParsedDocstring>}"""
|
|
exception_descrs = UNKNOWN
|
|
"""@ivar: A list of descriptions of exceptions
|
|
that the routine might raise. Each element of this list is a
|
|
tuple C{(exc, descr)}, where C{exc} is a string contianing the
|
|
exception name; and C{descr} is a L{ParsedDocstring
|
|
<epydoc.markup.ParsedDocstring>} describing the circumstances
|
|
under which the exception specified by C{exc} is raised.
|
|
@type: C{list}"""
|
|
#} end of "information extracted from docstrings" group
|
|
callgraph_uid = None
|
|
"""@ivar: L{DotGraph}.uid of the call graph for the function.
|
|
@type: C{str}"""
|
|
|
|
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,
|
|
vararg, and keyword), in order. If a positional argument
|
|
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)
|
|
if self.kwarg not in (None, UNKNOWN):
|
|
all_args.append(self.kwarg)
|
|
return all_args
|
|
|
|
def _flatten(lst, out=None):
|
|
"""
|
|
Return a flattened version of C{lst}.
|
|
"""
|
|
if out is None: out = []
|
|
for elt in lst:
|
|
if isinstance(elt, (list,tuple)):
|
|
_flatten(elt, out)
|
|
else:
|
|
out.append(elt)
|
|
return out
|
|
|
|
class ClassMethodDoc(RoutineDoc): pass
|
|
class StaticMethodDoc(RoutineDoc): pass
|
|
|
|
class PropertyDoc(ValueDoc):
|
|
"""
|
|
API documentation information about a single property.
|
|
"""
|
|
#{ Property Access Functions
|
|
fget = UNKNOWN
|
|
"""@ivar: API documentation for the property's get function.
|
|
@type: L{RoutineDoc}"""
|
|
fset = UNKNOWN
|
|
"""@ivar: API documentation for the property's set function.
|
|
@type: L{RoutineDoc}"""
|
|
fdel = UNKNOWN
|
|
"""@ivar: API documentation for the property's delete function.
|
|
@type: L{RoutineDoc}"""
|
|
#}
|
|
#{ Information Extracted from Docstrings
|
|
type_descr = UNKNOWN
|
|
"""@ivar: A description of the property's expected type, extracted
|
|
from its docstring.
|
|
@type: L{ParsedDocstring<epydoc.markup.ParsedDocstring>}"""
|
|
#} end of "information extracted from docstrings" group
|
|
|
|
def apidoc_links(self, **filters):
|
|
val_docs = []
|
|
if self.fget not in (None, UNKNOWN): val_docs.append(self.fget)
|
|
if self.fset not in (None, UNKNOWN): val_docs.append(self.fset)
|
|
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
|
|
######################################################################
|
|
|
|
class DocIndex:
|
|
"""
|
|
[xx] out of date.
|
|
|
|
An index that .. hmm... it *can't* be used to access some things,
|
|
cuz they're not at the root level. Do I want to add them or what?
|
|
And if so, then I have a sort of a new top level. hmm.. so
|
|
basically the question is what to do with a name that's not in the
|
|
root var's name space. 2 types:
|
|
- entirely outside (eg os.path)
|
|
- inside but not known (eg a submodule that we didn't look at?)
|
|
- container of current thing not examined?
|
|
|
|
An index of all the C{APIDoc} objects that can be reached from a
|
|
root set of C{ValueDoc}s.
|
|
|
|
The members of this index can be accessed by dotted name. In
|
|
particular, C{DocIndex} defines two mappings, accessed via the
|
|
L{get_vardoc()} and L{get_valdoc()} methods, which can be used to
|
|
access C{VariableDoc}s or C{ValueDoc}s respectively by name. (Two
|
|
separate mappings are necessary because a single name can be used
|
|
to refer to both a variable and to the value contained by that
|
|
variable.)
|
|
|
|
Additionally, the index defines two sets of C{ValueDoc}s:
|
|
\"reachable C{ValueDoc}s\" and \"contained C{ValueDoc}s\". The
|
|
X{reachable C{ValueDoc}s} are defined as the set of all
|
|
C{ValueDoc}s that can be reached from the root set by following
|
|
I{any} sequence of pointers to C{ValueDoc}s or C{VariableDoc}s.
|
|
The X{contained C{ValueDoc}s} are defined as the set of all
|
|
C{ValueDoc}s that can be reached from the root set by following
|
|
only the C{ValueDoc} pointers defined by non-imported
|
|
C{VariableDoc}s. For example, if the root set contains a module
|
|
C{m}, then the contained C{ValueDoc}s includes the C{ValueDoc}s
|
|
for any functions, variables, or classes defined in that module,
|
|
as well as methods and variables defined in classes defined in the
|
|
module. The reachable C{ValueDoc}s includes all of those
|
|
C{ValueDoc}s, as well as C{ValueDoc}s for any values imported into
|
|
the module, and base classes for classes defined in the module.
|
|
"""
|
|
|
|
def __init__(self, root):
|
|
"""
|
|
Create a new documentation index, based on the given root set
|
|
of C{ValueDoc}s. If any C{APIDoc}s reachable from the root
|
|
set does not have a canonical name, then it will be assigned
|
|
one. etc.
|
|
|
|
@param root: A list of C{ValueDoc}s.
|
|
"""
|
|
for apidoc in root:
|
|
if apidoc.canonical_name in (None, UNKNOWN):
|
|
raise ValueError("All APIdocs passed to DocIndexer "
|
|
"must already have canonical names.")
|
|
|
|
# Initialize the root items list. We sort them by length in
|
|
# ascending order. (This ensures that variables will shadow
|
|
# submodules when appropriate.)
|
|
# When the elements name is the same, list in alphabetical order:
|
|
# this is needed by the check for duplicates below.
|
|
self.root = sorted(root,
|
|
key=lambda d: (len(d.canonical_name), d.canonical_name))
|
|
"""The list of C{ValueDoc}s to document.
|
|
@type: C{list}"""
|
|
|
|
# Drop duplicated modules
|
|
# [xx] maybe what causes duplicates should be fixed instead.
|
|
# If fixed, adjust the sort here above: sorting by names will not
|
|
# be required anymore
|
|
i = 1
|
|
while i < len(self.root):
|
|
if self.root[i-1] is self.root[i]:
|
|
del self.root[i]
|
|
else:
|
|
i += 1
|
|
|
|
self.mlclasses = self._get_module_classes(self.root)
|
|
"""A mapping from class names to L{ClassDoc}. Contains
|
|
classes defined at module level for modules in L{root}
|
|
and which can be used as fallback by L{find()} if looking
|
|
in containing namespaces fails.
|
|
@type: C{dict} from C{str} to L{ClassDoc} or C{list}"""
|
|
|
|
self.callers = None
|
|
"""A dictionary mapping from C{RoutineDoc}s in this index
|
|
to lists of C{RoutineDoc}s for the routine's callers.
|
|
This dictionary is initialized by calling
|
|
L{read_profiling_info()}.
|
|
@type: C{list} of L{RoutineDoc}"""
|
|
|
|
self.callees = None
|
|
"""A dictionary mapping from C{RoutineDoc}s in this index
|
|
to lists of C{RoutineDoc}s for the routine's callees.
|
|
This dictionary is initialized by calling
|
|
L{read_profiling_info()}.
|
|
@type: C{list} of L{RoutineDoc}"""
|
|
|
|
self._funcid_to_doc = {}
|
|
"""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{callees} variables."""
|
|
|
|
self._container_cache = {}
|
|
"""A cache for the L{container()} method, to increase speed."""
|
|
|
|
self._get_cache = {}
|
|
"""A cache for the L{get_vardoc()} and L{get_valdoc()} methods,
|
|
to increase speed."""
|
|
|
|
#////////////////////////////////////////////////////////////
|
|
# Lookup methods
|
|
#////////////////////////////////////////////////////////////
|
|
# [xx]
|
|
# Currently these only work for things reachable from the
|
|
# root... :-/ I might want to change this so that imported
|
|
# values can be accessed even if they're not contained.
|
|
# Also, I might want canonical names to not start with ??
|
|
# if the thing is a top-level imported module..?
|
|
|
|
def get_vardoc(self, name):
|
|
"""
|
|
Return the C{VariableDoc} with the given name, or C{None} if this
|
|
index does not contain a C{VariableDoc} with the given name.
|
|
"""
|
|
var, val = self._get(name)
|
|
return var
|
|
|
|
def get_valdoc(self, name):
|
|
"""
|
|
Return the C{ValueDoc} with the given name, or C{None} if this
|
|
index does not contain a C{ValueDoc} with the given name.
|
|
"""
|
|
var, val = self._get(name)
|
|
return val
|
|
|
|
def _get(self, name):
|
|
"""
|
|
A helper function that's used to implement L{get_vardoc()}
|
|
and L{get_valdoc()}.
|
|
"""
|
|
# Convert name to a DottedName, if necessary.
|
|
if not isinstance(name, DottedName):
|
|
name = DottedName(name)
|
|
|
|
# Check if the result is cached.
|
|
val = self._get_cache.get(name)
|
|
if val is not None: return val
|
|
|
|
# Look for an element in the root set whose name is a prefix
|
|
# of `name`. If we can't find one, then return None.
|
|
for root_valdoc in self.root:
|
|
if root_valdoc.canonical_name.dominates(name):
|
|
# Starting at the root valdoc, walk down the variable/
|
|
# submodule chain until we find the requested item.
|
|
var_doc = None
|
|
val_doc = root_valdoc
|
|
for identifier in name[len(root_valdoc.canonical_name):]:
|
|
if val_doc is None: break
|
|
var_doc, val_doc = self._get_from(val_doc, identifier)
|
|
else:
|
|
# If we found it, then return.
|
|
if var_doc is not None or val_doc is not None:
|
|
self._get_cache[name] = (var_doc, val_doc)
|
|
return var_doc, val_doc
|
|
|
|
# We didn't find it.
|
|
self._get_cache[name] = (None, None)
|
|
return None, None
|
|
|
|
def _get_from(self, val_doc, identifier):
|
|
if isinstance(val_doc, NamespaceDoc):
|
|
child_var = val_doc.variables.get(identifier)
|
|
if child_var is not None:
|
|
child_val = child_var.value
|
|
if child_val is UNKNOWN: child_val = None
|
|
return child_var, child_val
|
|
|
|
# If that fails, then see if it's a submodule.
|
|
if (isinstance(val_doc, ModuleDoc) and
|
|
val_doc.submodules is not UNKNOWN):
|
|
for submodule in val_doc.submodules:
|
|
if submodule.canonical_name[-1] == identifier:
|
|
var_doc = None
|
|
val_doc = submodule
|
|
if val_doc is UNKNOWN: val_doc = None
|
|
return var_doc, val_doc
|
|
|
|
return None, None
|
|
|
|
def find(self, name, context):
|
|
"""
|
|
Look for an C{APIDoc} named C{name}, relative to C{context}.
|
|
Return the C{APIDoc} if one is found; otherwise, return
|
|
C{None}. C{find} looks in the following places, in order:
|
|
- Function parameters (if one matches, return C{None})
|
|
- All enclosing namespaces, from closest to furthest.
|
|
- If C{name} starts with C{'self'}, then strip it off and
|
|
look for the remaining part of the name using C{find}
|
|
- Builtins
|
|
- Parameter attributes
|
|
- Classes at module level (if the name is not ambiguous)
|
|
|
|
@type name: C{str} or L{DottedName}
|
|
@type context: L{APIDoc}
|
|
"""
|
|
if isinstance(name, basestring):
|
|
name = re.sub(r'\(.*\)$', '', name.strip())
|
|
if re.match('^([a-zA-Z_]\w*)(\.[a-zA-Z_]\w*)*$', name):
|
|
name = DottedName(name)
|
|
else:
|
|
return None
|
|
elif not isinstance(name, DottedName):
|
|
raise TypeError("'name' should be a string or DottedName")
|
|
|
|
if context is None or context.canonical_name is None:
|
|
container_name = []
|
|
else:
|
|
container_name = context.canonical_name
|
|
|
|
# Check for the name in all containing namespaces, starting
|
|
# with the closest one.
|
|
for i in range(len(container_name), -1, -1):
|
|
relative_name = container_name[:i]+name
|
|
# Is `name` the absolute name of a documented value?
|
|
# (excepting GenericValueDoc values.)
|
|
val_doc = self.get_valdoc(relative_name)
|
|
if (val_doc is not None and
|
|
not isinstance(val_doc, GenericValueDoc)):
|
|
return val_doc
|
|
# Is `name` the absolute name of a documented variable?
|
|
var_doc = self.get_vardoc(relative_name)
|
|
if var_doc is not None: return var_doc
|
|
|
|
# If the name begins with 'self', then try stripping that off
|
|
# and see if we can find the variable.
|
|
if name[0] == 'self':
|
|
doc = self.find('.'.join(name[1:]), context)
|
|
if doc is not None: return doc
|
|
|
|
# Is it the name of a builtin?
|
|
if len(name)==1 and hasattr(__builtin__, name[0]):
|
|
return None
|
|
|
|
# Is it a parameter's name or an attribute of a parameter?
|
|
if isinstance(context, RoutineDoc):
|
|
all_args = context.all_args()
|
|
if all_args is not UNKNOWN and name[0] in all_args:
|
|
return None
|
|
|
|
# Is this an object directly contained by any module?
|
|
doc = self.mlclasses.get(name[-1])
|
|
if isinstance(doc, APIDoc):
|
|
return doc
|
|
elif isinstance(doc, list):
|
|
log.warning("%s is an ambiguous name: it may be %s" % (
|
|
name[-1],
|
|
", ".join([ "'%s'" % d.canonical_name for d in doc ])))
|
|
|
|
# Drop this item so that the warning is reported only once.
|
|
# fail() will fail anyway.
|
|
del self.mlclasses[name[-1]]
|
|
|
|
def _get_module_classes(self, docs):
|
|
"""
|
|
Gather all the classes defined in a list of modules.
|
|
|
|
Very often people refers to classes only by class name,
|
|
even if they are not imported in the namespace. Linking
|
|
to such classes will fail if we look for them only in nested
|
|
namespaces. Allow them to retrieve only by name.
|
|
|
|
@param docs: containers of the objects to collect
|
|
@type docs: C{list} of C{APIDoc}
|
|
@return: mapping from objects name to the object(s) with that name
|
|
@rtype: C{dict} from C{str} to L{ClassDoc} or C{list}
|
|
"""
|
|
classes = {}
|
|
for doc in docs:
|
|
if not isinstance(doc, ModuleDoc):
|
|
continue
|
|
|
|
for var in doc.variables.values():
|
|
if not isinstance(var.value, ClassDoc):
|
|
continue
|
|
|
|
val = var.value
|
|
if val in (None, UNKNOWN) or val.defining_module is not doc:
|
|
continue
|
|
if val.canonical_name in (None, UNKNOWN):
|
|
continue
|
|
|
|
name = val.canonical_name[-1]
|
|
vals = classes.get(name)
|
|
if vals is None:
|
|
classes[name] = val
|
|
elif not isinstance(vals, list):
|
|
classes[name] = [ vals, val ]
|
|
else:
|
|
vals.append(val)
|
|
|
|
return classes
|
|
|
|
#////////////////////////////////////////////////////////////
|
|
# etc
|
|
#////////////////////////////////////////////////////////////
|
|
|
|
def reachable_valdocs(self, **filters):
|
|
"""
|
|
Return a list of all C{ValueDoc}s that can be reached,
|
|
directly or indirectly from this C{DocIndex}'s root set.
|
|
|
|
@param filters: A set of filters that can be used to prevent
|
|
C{reachable_valdocs} from following specific link types
|
|
when looking for C{ValueDoc}s that can be reached from the
|
|
root set. See C{APIDoc.apidoc_links} for a more complete
|
|
description.
|
|
"""
|
|
return reachable_valdocs(self.root, **filters)
|
|
|
|
def container(self, api_doc):
|
|
"""
|
|
Return the C{ValueDoc} that contains the given C{APIDoc}, or
|
|
C{None} if its container is not in the index.
|
|
"""
|
|
# Check if the result is cached.
|
|
val = self._container_cache.get(api_doc)
|
|
if val is not None: return val
|
|
|
|
if isinstance(api_doc, GenericValueDoc):
|
|
self._container_cache[api_doc] = None
|
|
return None # [xx] unknown.
|
|
if isinstance(api_doc, VariableDoc):
|
|
self._container_cache[api_doc] = api_doc.container
|
|
return api_doc.container
|
|
if len(api_doc.canonical_name) == 1:
|
|
self._container_cache[api_doc] = None
|
|
return None
|
|
elif isinstance(api_doc, ModuleDoc) and api_doc.package is not UNKNOWN:
|
|
self._container_cache[api_doc] = api_doc.package
|
|
return api_doc.package
|
|
else:
|
|
parent = self.get_valdoc(api_doc.canonical_name.container())
|
|
self._container_cache[api_doc] = parent
|
|
return parent
|
|
|
|
#////////////////////////////////////////////////////////////
|
|
# Profiling information
|
|
#////////////////////////////////////////////////////////////
|
|
|
|
def read_profiling_info(self, profile_stats):
|
|
"""
|
|
Initialize the L{callers} and L{callees} variables, given a
|
|
C{Stat} object from the C{pstats} module.
|
|
|
|
@warning: This method uses undocumented data structures inside
|
|
of C{profile_stats}.
|
|
"""
|
|
if self.callers is None: self.callers = {}
|
|
if self.callees is None: self.callees = {}
|
|
|
|
# The Stat object encodes functions using `funcid`s, or
|
|
# tuples of (filename, lineno, funcname). Create a mapping
|
|
# from these `funcid`s to `RoutineDoc`s.
|
|
self._update_funcid_to_doc(profile_stats)
|
|
|
|
for callee, (cc, nc, tt, ct, callers) in profile_stats.stats.items():
|
|
callee = self._funcid_to_doc.get(callee)
|
|
if callee is None: continue
|
|
for caller in callers:
|
|
caller = self._funcid_to_doc.get(caller)
|
|
if caller is None: continue
|
|
self.callers.setdefault(callee, []).append(caller)
|
|
self.callees.setdefault(caller, []).append(callee)
|
|
|
|
def _update_funcid_to_doc(self, profile_stats):
|
|
"""
|
|
Update the dictionary mapping from C{pstat.Stat} funciton ids to
|
|
C{RoutineDoc}s. C{pstat.Stat} function ids are tuples of
|
|
C{(filename, lineno, funcname)}.
|
|
"""
|
|
# Maps (filename, lineno, funcname) -> RoutineDoc
|
|
for val_doc in self.reachable_valdocs():
|
|
# We only care about routines.
|
|
if not isinstance(val_doc, RoutineDoc): continue
|
|
# Get the filename from the defining module.
|
|
module = val_doc.defining_module
|
|
if module is UNKNOWN or module.filename is UNKNOWN: continue
|
|
# Normalize the filename.
|
|
filename = os.path.abspath(module.filename)
|
|
try: filename = py_src_filename(filename)
|
|
except: pass
|
|
# Look up the stat_func_id
|
|
funcid = (filename, val_doc.lineno, val_doc.canonical_name[-1])
|
|
if funcid in profile_stats.stats:
|
|
self._funcid_to_doc[funcid] = val_doc
|
|
|
|
######################################################################
|
|
## Pretty Printing
|
|
######################################################################
|
|
|
|
def pp_apidoc(api_doc, doublespace=0, depth=5, exclude=(), include=(),
|
|
backpointers=None):
|
|
"""
|
|
@return: A multiline pretty-printed string representation for the
|
|
given C{APIDoc}.
|
|
@param doublespace: If true, then extra lines will be
|
|
inserted to make the output more readable.
|
|
@param depth: The maximum depth that pp_apidoc will descend
|
|
into descendent VarDocs. To put no limit on
|
|
depth, use C{depth=-1}.
|
|
@param exclude: A list of names of attributes whose values should
|
|
not be shown.
|
|
@param backpointers: For internal use.
|
|
"""
|
|
pyid = id(api_doc.__dict__)
|
|
if backpointers is None: backpointers = {}
|
|
if (hasattr(api_doc, 'canonical_name') and
|
|
api_doc.canonical_name not in (None, UNKNOWN)):
|
|
name = '%s for %s' % (api_doc.__class__.__name__,
|
|
api_doc.canonical_name)
|
|
elif getattr(api_doc, 'name', None) not in (UNKNOWN, None):
|
|
if (getattr(api_doc, 'container', None) not in (UNKNOWN, None) and
|
|
getattr(api_doc.container, 'canonical_name', None)
|
|
not in (UNKNOWN, None)):
|
|
name ='%s for %s' % (api_doc.__class__.__name__,
|
|
api_doc.container.canonical_name+
|
|
api_doc.name)
|
|
else:
|
|
name = '%s for %s' % (api_doc.__class__.__name__, api_doc.name)
|
|
else:
|
|
name = api_doc.__class__.__name__
|
|
|
|
if pyid in backpointers:
|
|
return '%s [%s] (defined above)' % (name, backpointers[pyid])
|
|
|
|
if depth == 0:
|
|
if hasattr(api_doc, 'name') and api_doc.name is not None:
|
|
return '%s...' % api_doc.name
|
|
else:
|
|
return '...'
|
|
|
|
backpointers[pyid] = len(backpointers)
|
|
s = '%s [%s]' % (name, backpointers[pyid])
|
|
|
|
# Only print non-empty fields:
|
|
fields = [field for field in api_doc.__dict__.keys()
|
|
if (field in include or
|
|
(getattr(api_doc, field) is not UNKNOWN
|
|
and field not in exclude))]
|
|
if include:
|
|
fields = [field for field in dir(api_doc)
|
|
if field in include]
|
|
else:
|
|
fields = [field for field in api_doc.__dict__.keys()
|
|
if (getattr(api_doc, field) is not UNKNOWN
|
|
and field not in exclude)]
|
|
fields.sort()
|
|
|
|
for field in fields:
|
|
fieldval = getattr(api_doc, field)
|
|
if doublespace: s += '\n |'
|
|
s += '\n +- %s' % field
|
|
|
|
if (isinstance(fieldval, types.ListType) and
|
|
len(fieldval)>0 and
|
|
isinstance(fieldval[0], APIDoc)):
|
|
s += _pp_list(api_doc, fieldval, doublespace, depth,
|
|
exclude, include, backpointers,
|
|
(field is fields[-1]))
|
|
elif (isinstance(fieldval, types.DictType) and
|
|
len(fieldval)>0 and
|
|
isinstance(fieldval.values()[0], APIDoc)):
|
|
s += _pp_dict(api_doc, fieldval, doublespace,
|
|
depth, exclude, include, backpointers,
|
|
(field is fields[-1]))
|
|
elif isinstance(fieldval, APIDoc):
|
|
s += _pp_apidoc(api_doc, fieldval, doublespace, depth,
|
|
exclude, include, backpointers,
|
|
(field is fields[-1]))
|
|
else:
|
|
s += ' = ' + _pp_val(api_doc, fieldval, doublespace,
|
|
depth, exclude, include, backpointers)
|
|
|
|
return s
|
|
|
|
def _pp_list(api_doc, items, doublespace, depth, exclude, include,
|
|
backpointers, is_last):
|
|
line1 = (is_last and ' ') or '|'
|
|
s = ''
|
|
for item in items:
|
|
line2 = ((item is items[-1]) and ' ') or '|'
|
|
joiner = '\n %s %s ' % (line1, line2)
|
|
if doublespace: s += '\n %s |' % line1
|
|
s += '\n %s +- ' % line1
|
|
valstr = _pp_val(api_doc, item, doublespace, depth, exclude, include,
|
|
backpointers)
|
|
s += joiner.join(valstr.split('\n'))
|
|
return s
|
|
|
|
def _pp_dict(api_doc, dict, doublespace, depth, exclude, include,
|
|
backpointers, is_last):
|
|
items = dict.items()
|
|
items.sort()
|
|
line1 = (is_last and ' ') or '|'
|
|
s = ''
|
|
for item in items:
|
|
line2 = ((item is items[-1]) and ' ') or '|'
|
|
joiner = '\n %s %s ' % (line1, line2)
|
|
if doublespace: s += '\n %s |' % line1
|
|
s += '\n %s +- ' % line1
|
|
valstr = _pp_val(api_doc, item[1], doublespace, depth, exclude,
|
|
include, backpointers)
|
|
s += joiner.join(('%s => %s' % (item[0], valstr)).split('\n'))
|
|
return s
|
|
|
|
def _pp_apidoc(api_doc, val, doublespace, depth, exclude, include,
|
|
backpointers, is_last):
|
|
line1 = (is_last and ' ') or '|'
|
|
s = ''
|
|
if doublespace: s += '\n %s | ' % line1
|
|
s += '\n %s +- ' % line1
|
|
joiner = '\n %s ' % line1
|
|
childstr = pp_apidoc(val, doublespace, depth-1, exclude,
|
|
include, backpointers)
|
|
return s + joiner.join(childstr.split('\n'))
|
|
|
|
def _pp_val(api_doc, val, doublespace, depth, exclude, include, backpointers):
|
|
from epydoc import markup
|
|
if isinstance(val, APIDoc):
|
|
return pp_apidoc(val, doublespace, depth-1, exclude,
|
|
include, backpointers)
|
|
elif isinstance(val, markup.ParsedDocstring):
|
|
valrepr = `val.to_plaintext(None)`
|
|
if len(valrepr) < 40: return valrepr
|
|
else: return valrepr[:37]+'...'
|
|
else:
|
|
valrepr = repr(val)
|
|
if len(valrepr) < 40: return valrepr
|
|
else: return valrepr[:37]+'...'
|
|
|