From 9c232e45afc2224ed4cce6f4dbb1aae92b1078f1 Mon Sep 17 00:00:00 2001 From: Joel Nothman Date: Sun, 20 Nov 2016 23:56:56 +1100 Subject: [PATCH 1/2] Render numpydoc strings from a template --- numpydoc/docscrape_sphinx.py | 67 ++++++++++++++++------- numpydoc/numpydoc.py | 3 +- numpydoc/templates/numpydoc_docstring.rst | 16 ++++++ numpydoc/tests/test_docscrape.py | 33 +++++++++-- 4 files changed, 92 insertions(+), 27 deletions(-) create mode 100644 numpydoc/templates/numpydoc_docstring.rst diff --git a/numpydoc/docscrape_sphinx.py b/numpydoc/docscrape_sphinx.py index 482978c3..d8a495e0 100644 --- a/numpydoc/docscrape_sphinx.py +++ b/numpydoc/docscrape_sphinx.py @@ -5,8 +5,13 @@ import inspect import textwrap import pydoc -import sphinx import collections +import os + +from jinja2 import FileSystemLoader +from jinja2.sandbox import SandboxedEnvironment +import sphinx +from sphinx.jinja2glue import BuiltinTemplateLoader from .docscrape import NumpyDocString, FunctionDoc, ClassDoc @@ -24,6 +29,12 @@ def __init__(self, docstring, config={}): def load_config(self, config): self.use_plots = config.get('use_plots', False) self.class_members_toctree = config.get('class_members_toctree', True) + self.template = config.get('template', None) + if self.template is None: + template_dirs = [os.path.join(os.path.dirname(__file__), 'templates')] + template_loader = FileSystemLoader(template_dirs) + template_env = SandboxedEnvironment(loader=template_loader) + self.template = template_env.get_template('numpydoc_docstring.rst') # string conversion routines def _str_header(self, name, symbol='`'): @@ -223,25 +234,29 @@ def _str_examples(self): return self._str_section('Examples') def __str__(self, indent=0, func_role="obj"): - out = [] - out += self._str_signature() - out += self._str_index() + [''] - out += self._str_summary() - out += self._str_extended_summary() - 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) - out += self._str_section('Notes') - out += self._str_references() - out += self._str_examples() - for param_list in ('Attributes', 'Methods'): - out += self._str_member_list(param_list) - out = self._str_indent(out, indent) - return '\n'.join(out) + ns = { + 'signature': self._str_signature(), + 'index': self._str_index(), + 'summary': self._str_summary(), + 'extended_summary': self._str_extended_summary(), + 'parameters': self._str_param_list('Parameters'), + 'returns': self._str_returns('Returns'), + 'yields': self._str_returns('Yields'), + 'other_parameters': self._str_param_list('Other Parameters'), + 'raises': self._str_param_list('Raises'), + 'warns': self._str_param_list('Warns'), + 'warnings': self._str_warnings(), + 'see_also': self._str_see_also(func_role), + 'notes': self._str_section('Notes'), + 'references': self._str_references(), + 'examples': self._str_examples(), + 'attributes': self._str_member_list('Attributes'), + 'methods': self._str_member_list('Methods'), + } + ns = dict((k, '\n'.join(v)) for k, v in ns.items()) + + rendered = self.template.render(**ns) + return '\n'.join(self._str_indent(rendered.split('\n'), indent)) class SphinxFunctionDoc(SphinxDocString, FunctionDoc): @@ -263,7 +278,7 @@ def __init__(self, obj, doc=None, config={}): SphinxDocString.__init__(self, doc, config=config) -def get_doc_object(obj, what=None, doc=None, config={}): +def get_doc_object(obj, what=None, doc=None, config={}, builder=None): if what is None: if inspect.isclass(obj): what = 'class' @@ -273,6 +288,16 @@ def get_doc_object(obj, what=None, doc=None, config={}): what = 'function' else: what = 'object' + + template_dirs = [os.path.join(os.path.dirname(__file__), 'templates')] + if builder is not None: + template_loader = BuiltinTemplateLoader() + template_loader.init(builder, dirs=template_dirs) + else: + template_loader = FileSystemLoader(template_dirs) + template_env = SandboxedEnvironment(loader=template_loader) + config['template'] = template_env.get_template('numpydoc_docstring.rst') + if what == 'class': return SphinxClassDoc(obj, func_doc=SphinxFunctionDoc, doc=doc, config=config) diff --git a/numpydoc/numpydoc.py b/numpydoc/numpydoc.py index 2fcf1894..eab47569 100644 --- a/numpydoc/numpydoc.py +++ b/numpydoc/numpydoc.py @@ -77,7 +77,8 @@ def mangle_docstrings(app, what, name, obj, options, lines): title_re = re.compile(sixu(pattern), re.I | re.S) lines[:] = title_re.sub(sixu(''), u_NL.join(lines)).split(u_NL) else: - doc = get_doc_object(obj, what, u_NL.join(lines), config=cfg) + doc = get_doc_object(obj, what, u_NL.join(lines), config=cfg, + builder=app.builder) if sys.version_info[0] >= 3: doc = str(doc) else: diff --git a/numpydoc/templates/numpydoc_docstring.rst b/numpydoc/templates/numpydoc_docstring.rst new file mode 100644 index 00000000..1900db53 --- /dev/null +++ b/numpydoc/templates/numpydoc_docstring.rst @@ -0,0 +1,16 @@ +{{index}} +{{summary}} +{{extended_summary}} +{{parameters}} +{{returns}} +{{yields}} +{{other_parameters}} +{{raises}} +{{warns}} +{{warnings}} +{{see_also}} +{{notes}} +{{references}} +{{examples}} +{{attributes}} +{{methods}} diff --git a/numpydoc/tests/test_docscrape.py b/numpydoc/tests/test_docscrape.py index 3484ea2d..b71d7bf6 100644 --- a/numpydoc/tests/test_docscrape.py +++ b/numpydoc/tests/test_docscrape.py @@ -2,6 +2,9 @@ from __future__ import division, absolute_import, print_function import sys, textwrap +import io + +import jinja2 from numpydoc.docscrape import NumpyDocString, FunctionDoc, ClassDoc from numpydoc.docscrape_sphinx import SphinxDocString, SphinxClassDoc @@ -248,11 +251,8 @@ def non_blank_line_by_line_compare(a,b): b = textwrap.dedent(b) a = [l.rstrip() for l in a.split('\n') if l.strip()] b = [l.rstrip() for l in b.split('\n') if l.strip()] - for n,line in enumerate(a): - if not line == b[n]: - raise AssertionError("Lines %s of a and b differ: " - "\n>>> %s\n<<< %s\n" % - (n,line,b[n])) + assert_list_equal(a, b) + def test_str(): # doc_txt has the order of Notes and See Also sections flipped. # This should be handled automatically, and so, one thing this test does @@ -924,6 +924,29 @@ def x(self): """) +def test_templated_sections(): + doc = SphinxClassDoc(None, class_doc_txt, + config={'template': jinja2.Template('{{examples}}{{parameters}}')}) + non_blank_line_by_line_compare(str(doc), + """ + .. rubric:: Examples + + For usage examples, see `ode`. + + + :Parameters: + + **f** : callable ``f(t, y, *f_args)`` + + Aaa. + + **jac** : callable ``jac(t, y, *jac_args)`` + + Bbb. + + """) + + if __name__ == "__main__": import nose nose.run() From 6c2e67f72bc269efab578e23c1be2d890b1d16fb Mon Sep 17 00:00:00 2001 From: Joel Nothman Date: Wed, 30 Nov 2016 06:32:15 +1100 Subject: [PATCH 2/2] Add dependency on jinja2 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e27d9aa5..05ea9eea 100644 --- a/setup.py +++ b/setup.py @@ -38,7 +38,7 @@ author_email="pav@iki.fi", url="https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt", license="BSD", - install_requires=["sphinx >= 1.0.1"], + install_requires=["sphinx >= 1.0.1", 'Jinja2>=2.3'], package_data={'numpydoc': ['tests/test_*.py']}, test_suite = 'nose.collector', cmdclass={"sdist": sdist},