Skip to content

Commit

Permalink
Merge pull request #9904 from aktech/numpydoc
Browse files Browse the repository at this point in the history
Build docs on Python3 : Update numpydoc
  • Loading branch information
debugger22 committed Oct 2, 2015
2 parents fde31b4 + 2ac5f17 commit 7c4c825
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 85 deletions.
104 changes: 78 additions & 26 deletions doc/ext/docscrape.py
@@ -1,12 +1,14 @@
"""
Extract reference documentation from the NumPy source tree.
"""
from __future__ import division, absolute_import, print_function

import inspect
import textwrap
import re
import pydoc
from StringIO import StringIO
import collections
import sys


class Reader(object):
Expand Down Expand Up @@ -67,6 +69,7 @@ def read_to_next_empty_line(self):

def is_empty(line):
return not line.strip()

return self.read_to_condition(is_empty)

def read_to_next_unindented_line(self):
Expand All @@ -84,7 +87,7 @@ def is_empty(self):
return not ''.join(self._str).strip()


class NumpyDocString(object):
class NumpyDocString(collections.Mapping):
def __init__(self, docstring, config={}):
docstring = textwrap.dedent(docstring).split('\n')

Expand All @@ -95,6 +98,7 @@ def __init__(self, docstring, config={}):
'Extended Summary': [],
'Parameters': [],
'Returns': [],
'Yields': [],
'Raises': [],
'Warns': [],
'Other Parameters': [],
Expand All @@ -120,6 +124,12 @@ def __setitem__(self, key, val):

self._parsed_data[key] = val

def __iter__(self):
return iter(self._parsed_data)

def __len__(self):
return len(self._parsed_data)

def _is_at_section(self):
self._doc.seek_next_non_empty_line()

Expand Down Expand Up @@ -270,13 +280,17 @@ def _parse_summary(self):
if self._is_at_section():
return

summary = self._doc.read_to_next_empty_line()
summary_str = " ".join([s.strip() for s in summary]).strip()
if re.compile('^([\w., ]+=)?\s*[\w\.]+\(.*\)$').match(summary_str):
self['Signature'] = summary_str
if not self._is_at_section():
self['Summary'] = self._doc.read_to_next_empty_line()
else:
# If several signatures present, take the last one
while True:
summary = self._doc.read_to_next_empty_line()
summary_str = " ".join([s.strip() for s in summary]).strip()
if re.compile('^([\w., ]+=)?\s*[\w\.]+\(.*\)$').match(summary_str):
self['Signature'] = summary_str
if not self._is_at_section():
continue
break

if summary is not None:
self['Summary'] = summary

if not self._is_at_section():
Expand All @@ -286,11 +300,23 @@ def _parse(self):
self._doc.reset()
self._parse_summary()

for (section, content) in self._read_sections():
sections = list(self._read_sections())
section_names = set([section for section, content in sections])

has_returns = 'Returns' in section_names
has_yields = 'Yields' in section_names
# We could do more tests, but we are not. Arbitrarily.
if has_returns and has_yields:
msg = 'Docstring contains both a Returns and Yields section.'
raise ValueError(msg)

for (section, content) in sections:
if not section.startswith('..'):
section = ' '.join([s.capitalize() for s in section.split(' ')])
if section in ('Parameters', 'Returns', 'Raises', 'Warns',
'Other Parameters', 'Attributes', 'Methods'):
section = (s.capitalize() for s in section.split(' '))
section = ' '.join(section)
if section in ('Parameters', 'Returns', 'Yields', 'Raises',
'Warns', 'Other Parameters', 'Attributes',
'Methods'):
self[section] = self._parse_param_list(content)
elif section.startswith('.. index::'):
self['index'] = self._parse_index(section, content)
Expand Down Expand Up @@ -333,7 +359,10 @@ def _str_param_list(self, name):
if self[name]:
out += self._str_header(name)
for param, param_type, desc in self[name]:
out += ['%s : %s' % (param, param_type)]
if param_type:
out += ['%s : %s' % (param, param_type)]
else:
out += [param]
out += self._str_indent(desc)
out += ['']
return out
Expand Down Expand Up @@ -387,8 +416,8 @@ def __str__(self, func_role=''):
out += self._str_signature()
out += self._str_summary()
out += self._str_extended_summary()
for param_list in ('Parameters', 'Returns', 'Other Parameters',
'Raises', 'Warns'):
for param_list in ('Parameters', 'Returns', 'Yields',
'Other Parameters', 'Raises', 'Warns'):
out += self._str_param_list(param_list)
out += self._str_section('Warnings')
out += self._str_see_also(func_role)
Expand Down Expand Up @@ -432,7 +461,10 @@ def __init__(self, func, role='func', doc=None, config={}):
func, func_name = self.get_func()
try:
# try to read signature
argspec = inspect.getargspec(func)
if sys.version_info[0] >= 3:
argspec = inspect.getfullargspec(func)
else:
argspec = inspect.getargspec(func)
argspec = inspect.formatargspec(*argspec)
argspec = argspec.replace('*', '\*')
signature = '%s%s' % (func_name, argspec)
Expand Down Expand Up @@ -477,6 +509,9 @@ def __init__(self, cls, doc=None, modulename='', func_doc=FunctionDoc,
raise ValueError("Expected a class or None, but got %r" % cls)
self._cls = cls

self.show_inherited_members = config.get(
'show_inherited_class_members', True)

if modulename and not modulename.endswith('.'):
modulename += '.'
self._mod = modulename
Expand All @@ -489,12 +524,23 @@ def __init__(self, cls, doc=None, modulename='', func_doc=FunctionDoc,
NumpyDocString.__init__(self, doc)

if config.get('show_class_members', True):
if not self['Methods']:
self['Methods'] = [(name, '', '')
for name in sorted(self.methods)]
if not self['Attributes']:
self['Attributes'] = [(name, '', '')
for name in sorted(self.properties)]
def splitlines_x(s):
if not s:
return []
else:
return s.splitlines()

for field, items in [('Methods', self.methods),
('Attributes', self.properties)]:
if not self[field]:
doc_list = []
for name in sorted(items):
try:
doc_item = pydoc.getdoc(getattr(self._cls, name))
doc_list.append((name, '', splitlines_x(doc_item)))
except AttributeError:
pass # method doesn't exist
self[field] = doc_list

@property
def methods(self):
Expand All @@ -518,11 +564,10 @@ def properties(self):
# try/except AttributeError clause added, which catches exceptions like this
# one: https://gist.github.com/1471949
def inspect_getmembers(object, predicate=None):
"""Return all members of an object as (name, value) pairs sorted by name.
"""
Return all members of an object as (name, value) pairs sorted by name.
Optionally, only return members that satisfy a given predicate.
"""

results = []
for key in dir(object):
try:
Expand All @@ -533,3 +578,10 @@ def inspect_getmembers(object, predicate=None):
results.append((key, value))
results.sort()
return results

def _is_show_member(self, name):
if self.show_inherited_members:
return True # show all class members
if name not in self._cls.__dict__:
return False # class member is inherited, we do not show it
return True
105 changes: 77 additions & 28 deletions doc/ext/docscrape_sphinx.py
@@ -1,15 +1,29 @@
from __future__ import division, absolute_import, print_function

import sys
import re
import inspect
import textwrap
import pydoc
import sphinx
import collections

from docscrape import NumpyDocString, FunctionDoc, ClassDoc

if sys.version_info[0] >= 3:
sixu = lambda s: s
else:
sixu = lambda s: unicode(s, 'unicode_escape')


class SphinxDocString(NumpyDocString):
def __init__(self, docstring, config={}):
self.use_plots = config.get('use_plots', False)
NumpyDocString.__init__(self, docstring, config=config)
self.load_config(config)

def load_config(self, config):
self.use_plots = config.get('use_plots', False)
self.class_members_toctree = config.get('class_members_toctree', True)

# string conversion routines
def _str_header(self, name, symbol='`'):
Expand Down Expand Up @@ -37,16 +51,37 @@ def _str_summary(self):
def _str_extended_summary(self):
return self['Extended Summary'] + ['']

def _str_param_list(self, name):
def _str_returns(self, name='Returns'):
out = []
if self[name]:
out += self._str_field_list(name)
out += ['']
for param, param_type, desc in self[name]:
out += self._str_indent(['**%s** : %s' % (param.strip(),
param_type)])
if param_type:
out += self._str_indent(['**%s** : %s' % (param.strip(),
param_type)])
else:
out += self._str_indent([param.strip()])
if desc:
out += ['']
out += self._str_indent(desc, 8)
out += ['']
out += self._str_indent(desc, 8)
return out

def _str_param_list(self, name):
out = []
if self[name]:
out += self._str_field_list(name)
out += ['']
for param, param_type, desc in self[name]:
if param_type:
out += self._str_indent(['**%s** : %s' % (param.strip(),
param_type)])
else:
out += self._str_indent(['**%s**' % param.strip()])
if desc:
out += ['']
out += self._str_indent(desc, 8)
out += ['']
return out

Expand All @@ -72,33 +107,44 @@ def _str_member_list(self, name):
if prefix:
prefix = '~%s.' % prefix

## Lines that are commented out are used to make the
## autosummary:: table. Since SymPy does not use the
## autosummary:: functionality, it is easiest to just comment it
## out.
#autosum = []
# Lines that are commented out are used to make the
# autosummary:: table. Since SymPy does not use the
# autosummary:: functionality, it is easiest to just comment it
# out.
# autosum = []
others = []
for param, param_type, desc in self[name]:
param = param.strip()
#if not self._obj or hasattr(self._obj, param):
# autosum += [" %s%s" % (prefix, param)]
#else:

# Check if the referenced member can have a docstring or not
param_obj = getattr(self._obj, param, None)
if not (callable(param_obj)
or isinstance(param_obj, property)
or inspect.isgetsetdescriptor(param_obj)):
param_obj = None

# if param_obj and (pydoc.getdoc(param_obj) or not desc):
# # Referenced object has a docstring
# autosum += [" %s%s" % (prefix, param)]
# else:
others.append((param, param_type, desc))

#if autosum:
# out += ['.. autosummary::', ' :toctree:', '']
# out += autosum
# if autosum:
# out += ['.. autosummary::']
# if self.class_members_toctree:
# out += [' :toctree:']
# out += [''] + autosum

if others:
maxlen_0 = max([len(x[0]) for x in others])
maxlen_1 = max([len(x[1]) for x in others])
hdr = "="*maxlen_0 + " " + "="*maxlen_1 + " " + "="*10
fmt = '%%%ds %%%ds ' % (maxlen_0, maxlen_1)
n_indent = maxlen_0 + maxlen_1 + 4
out += [hdr]
maxlen_0 = max(3, max([len(x[0]) for x in others]))
hdr = sixu("=")*maxlen_0 + sixu(" ") + sixu("=")*10
fmt = sixu('%%%ds %%s ') % (maxlen_0,)
out += ['', '', hdr]
for param, param_type, desc in others:
out += [fmt % (param.strip(), param_type)]
out += self._str_indent(desc, n_indent)
desc = sixu(" ").join(x.strip() for x in desc).strip()
if param_type:
desc = "(%s) %s" % (param_type, desc)
out += [fmt % (param.strip(), desc)]
out += [hdr]
out += ['']
return out
Expand Down Expand Up @@ -186,8 +232,10 @@ def __str__(self, indent=0, func_role="obj"):
out += self._str_index() + ['']
out += self._str_summary()
out += self._str_extended_summary()
for param_list in ('Parameters', 'Returns', 'Other Parameters',
'Raises', 'Warns'):
out += self._str_param_list('Parameters')
out += self._str_returns('Returns')
out += self._str_returns('Yields')
for param_list in ('Other Parameters', 'Raises', 'Warns'):
out += self._str_param_list(param_list)
out += self._str_warnings()
out += self._str_see_also(func_role)
Expand All @@ -203,19 +251,20 @@ def __str__(self, indent=0, func_role="obj"):

class SphinxFunctionDoc(SphinxDocString, FunctionDoc):
def __init__(self, obj, doc=None, config={}):
self.use_plots = config.get('use_plots', False)
self.load_config(config)
FunctionDoc.__init__(self, obj, doc=doc, config=config)


class SphinxClassDoc(SphinxDocString, ClassDoc):
def __init__(self, obj, doc=None, func_doc=None, config={}):
self.use_plots = config.get('use_plots', False)
self.load_config(config)
ClassDoc.__init__(self, obj, doc=doc, func_doc=None, config=config)


class SphinxObjDoc(SphinxDocString):
def __init__(self, obj, doc=None, config={}):
self._f = obj
self.load_config(config)
SphinxDocString.__init__(self, doc, config=config)


Expand Down

0 comments on commit 7c4c825

Please sign in to comment.