def test_attributes_docstring(self): config = Config() actual = str( NumpyDocstring(cleandoc(NamedtupleSubclass.__doc__), config=config, app=None, what='class', name='NamedtupleSubclass', obj=NamedtupleSubclass)) expected = """\ Sample namedtuple subclass .. attribute:: attr1 Quick description of attr1 :type: Arbitrary type .. attribute:: attr2 Quick description of attr2 :type: Another arbitrary type .. attribute:: attr3 Adds a newline after the type :type: Type """ self.assertEqual(expected, actual)
def make_rst(self): env = self.state.document.settings.env path = self.arguments[0] parsed = parse_jinja_comment( os.path.join(env.config["jinja_template_path"], path)) if parsed: config = Config(napoleon_use_param=True, napoleon_use_rtype=True) for comment, macro_function in parsed: if macro_function.startswith( "{{%- macro ") or macro_function.startswith( "{{% macro "): macro_function_signature = macro_function.replace( "{{% macro ", "").replace("{{%- macro ", "").replace(" -%}}", "").replace(" %}}", "") macro_function_name = macro_function_signature.split( "(")[0] docstring = GoogleDocstring(comment, config).lines() docstring.append(macro_function_signature) if docstring is not None and env.config[ "jinja_template_path"]: for line in jinja_directive(macro_function_name, docstring): yield line yield ""
def _getparams(self, docstring: str) -> Dict[str, str]: """ Parse the given documentation string and extract the Args parameters. Returns a dictionary mapping the parameter name to the parameter text. Args: docstring: The given documentation string to extract the Args parameters from. Returns: The dictionary mapping the parameter name to the parameter text. """ if docstring is None: return {} def elementByTagName(tagname: str): return lambda n: isinstance(n, Element) and n.tagname == tagname def getvalue(node, tagname: str) -> str: return node.traverse(elementByTagName(tagname))[0].astext() def param(node): return (getvalue(node, 'field_name'), getvalue(node, 'field_body')) params = {} config = Config(napoleon_use_param=True, napoleon_use_rtype=True) document = _parse_rst(GoogleDocstring(_deindent(docstring), config).lines()) for node in document.traverse(elementByTagName('field')): name, value = param(node) for keyword in ['param', 'keyword']: if name.startswith(keyword + ' '): name = name[len(keyword):].lstrip() params[name] = value.replace('\n', ' ') return params
def __init__(self, docstring, config=None, app=None, what='', name='', obj=None, options=None): # type: (Union[unicode, List[unicode]], SphinxConfig, Sphinx, unicode, unicode, Any, Any) -> None # NOQA self._config = config self._app = app if not self._config: from sphinxcontrib.napoleon import Config self._config = self._app and self._app.config or Config() # type: ignore if not what: if inspect.isclass(obj): what = 'class' elif inspect.ismodule(obj): what = 'module' elif isinstance(obj, collections.Callable): # type: ignore what = 'function' else: what = 'object' self._what = what self._name = name self._obj = obj self._opt = options if isinstance(docstring, string_types): docstring = docstring.splitlines() # type: ignore self._lines = docstring self._line_iter = modify_iter(docstring, modifier=lambda s: s.rstrip()) self._parsed_lines = [] # type: List[unicode] self._is_in_section = False self._section_indent = 0 if not hasattr(self, '_directive_sections'): self._directive_sections = [] # type: List[unicode] if not hasattr(self, '_sections'): self._sections = { 'args': self._parse_parameters_section, 'arguments': self._parse_parameters_section, 'attributes': self._parse_attributes_section, 'example': self._parse_examples_section, 'examples': self._parse_examples_section, 'keyword args': self._parse_keyword_arguments_section, 'keyword arguments': self._parse_keyword_arguments_section, 'methods': self._parse_methods_section, 'note': self._parse_note_section, 'notes': self._parse_notes_section, 'other parameters': self._parse_other_parameters_section, 'parameters': self._parse_parameters_section, 'return': self._parse_returns_section, 'returns': self._parse_returns_section, 'raises': self._parse_raises_section, 'references': self._parse_references_section, 'see also': self._parse_see_also_section, 'todo': self._parse_todo_section, 'warning': self._parse_warning_section, 'warnings': self._parse_warning_section, 'warns': self._parse_warns_section, 'yield': self._parse_yields_section, 'yields': self._parse_yields_section, } # type: Dict[unicode, Callable] self._parse()
def test_docstrings(self): config = Config(napoleon_use_param=False, napoleon_use_rtype=False, napoleon_use_keyword=False) for docstring, expected in self.docstrings: actual = str(GoogleDocstring(dedent(docstring), config)) expected = dedent(expected) self.assertEqual(expected, actual)
def test_modify_in_place(self): lines = ['Summary line.', '', 'Args:', ' arg1: arg1 description'] app = Mock() app.config = Config() _process_docstring(app, 'class', 'SampleClass', SampleClass, Mock(), lines) expected = ['Summary line.', '', ':param arg1: arg1 description', ''] self.assertEqual(expected, lines)
def parse_docstring(f): if f.__doc__ is None: f.__doc__ = 'TODO\n TODO' doc = inspect.cleandoc(f.__doc__) config = Config() google_doc = GoogleDocstring(doc, config) rst = str(google_doc) param_regex = r':param (?P<param>\w+): (?P<doc>.*)' m = re.findall(param_regex, rst) args_help = dict((k, v) for k, v in m) return args_help
def __get_class_docs__(cls): output = list() class_docs = {} if cls.__doc__: lines = inspect.getdoc(cls).split('\n') lines = list(filter(lambda x: len(x.strip()) > 0, lines)) section = 'Desc' class_docs[section] = [] for i in range(len(lines)): if '---' in lines[i]: del class_docs[section][-1] section = lines[i - 1].strip() class_docs[section] = [] line = lines[i].strip() class_docs[section].append(line) class_docs['Parameters'] = [] docparam = '' for key in cls.SECTIONS: if key in class_docs.keys(): value = class_docs[key] if key == 'Desc': docparam += '\n'.join(value) elif key == 'Parameters': params = cls.__parameters__() if len(params) > 0: docparam += '\nParameters\n' docparam += '---------\n' for p in params: docparam += p + ' : ' + str(type(p)) + '\n' if hasattr(cls, p): param_attr = getattr(cls, p) if hasattr(param_attr, '__doc__'): docparam += '\t' + param_attr.__doc__ + '\n' else: docparam += '{}\n{}\n'.format(key, '\n'.join(value)) docparam += '\n' from sphinxcontrib.napoleon import Config config = Config(napoleon_use_param=True, napoleon_use_rtype=True) from sphinxcontrib.napoleon.docstring import NumpyDocstring lines = NumpyDocstring(docparam, config).lines() output.extend(lines) return output
def test_parameters_with_class_reference(self): docstring = """\ Parameters ---------- param1 : :class:`MyClass <name.space.MyClass>` instance """ config = Config(napoleon_use_param=False) actual = str(NumpyDocstring(docstring, config)) expected = """\ :Parameters: **param1** (:class:`MyClass <name.space.MyClass>` instance) """ self.assertEqual(expected, actual) config = Config(napoleon_use_param=True) actual = str(NumpyDocstring(docstring, config)) expected = """\ :param param1: :type param1: :class:`MyClass <name.space.MyClass>` instance """ self.assertEqual(expected, actual)
def test_parameters_without_class_reference(self): docstring = """\ Parameters ---------- param1 : MyClass instance """ config = Config(napoleon_use_param=False) actual = str(NumpyDocstring(docstring, config)) expected = """\ :Parameters: **param1** (*MyClass instance*) """ self.assertEqual(expected, actual) config = Config(napoleon_use_param=True) actual = str(NumpyDocstring(dedent(docstring), config)) expected = """\ :param param1: :type param1: MyClass instance """ self.assertEqual(expected, actual)
def assertSkip(self, what, member, obj, expect_skip, config_name): skip = 'default skip' app = Mock() app.config = Config() setattr(app.config, config_name, True) if expect_skip: self.assertEqual( skip, _skip_member(app, what, member, obj, skip, Mock())) else: self.assertFalse(_skip_member(app, what, member, obj, skip, Mock())) setattr(app.config, config_name, False) self.assertEqual(skip, _skip_member(app, what, member, obj, skip, Mock()))
def assertSkip(self, what, member, obj, expect_default_skip, config_name): skip = True app = mock.Mock() app.config = Config() setattr(app.config, config_name, True) if expect_default_skip: self.assertEqual( None, _skip_member(app, what, member, obj, skip, mock.Mock())) else: self.assertFalse( _skip_member(app, what, member, obj, skip, mock.Mock())) setattr(app.config, config_name, False) self.assertEqual( None, _skip_member(app, what, member, obj, skip, mock.Mock()))
def test_see_also_refs(self): docstring = """\ numpy.multivariate_normal(mean, cov, shape=None, spam=None) See Also -------- some, other, funcs otherfunc : relationship """ actual = str(NumpyDocstring(docstring)) expected = """\ numpy.multivariate_normal(mean, cov, shape=None, spam=None) .. seealso:: :obj:`some`, :obj:`other`, :obj:`funcs` \n\ :obj:`otherfunc` relationship """ self.assertEqual(expected, actual) docstring = """\ numpy.multivariate_normal(mean, cov, shape=None, spam=None) See Also -------- some, other, funcs otherfunc : relationship """ config = Config() app = mock.Mock() actual = str(NumpyDocstring(docstring, config, app, "method")) expected = """\ numpy.multivariate_normal(mean, cov, shape=None, spam=None) .. seealso:: :meth:`some`, :meth:`other`, :meth:`funcs` \n\ :meth:`otherfunc` relationship """ self.assertEqual(expected, actual)
def parse_header(f): if f.__doc__ is None: f.__doc__ = 'TODO\n TODO' doc = inspect.cleandoc(f.__doc__) config = Config() google_doc = GoogleDocstring(doc, config) rst = str(google_doc) lines = [l for l in rst.splitlines() if len(l) > 0] if len(lines) >= 2: return lines[:2] elif len(lines) == 1: return lines[0] else: return None, None
def test_class_data_member_inline(self): config = Config() docstring = """b: data member description with :ref:`reference`""" actual = str( GoogleDocstring(docstring, config=config, app=None, what='attribute', name='some_data', obj=0)) expected = """data member description with :ref:`reference` :type: b""" self.assertEqual(expected, actual)
def __init__(self, docstring, config=None, app=None, what='', name='', obj=None, options=None): self._config = config self._app = app if not self._config: from sphinxcontrib.napoleon import Config self._config = self._app and self._app.config or Config() self._what = what self._name = name self._obj = obj self._opt = options if isinstance(docstring, str): docstring = docstring.splitlines() self._lines = docstring self._line_iter = modify_iter(docstring, modifier=lambda s: s.rstrip()) self._parsed_lines = [] self._is_in_section = False self._section_indent = 0 if not hasattr(self, '_directive_sections'): self._directive_sections = [] if not hasattr(self, '_sections'): self._sections = { 'args': self._parse_parameters_section, 'arguments': self._parse_parameters_section, 'attributes': self._parse_attributes_section, 'example': self._parse_examples_section, 'examples': self._parse_examples_section, 'keyword args': self._parse_keyword_arguments_section, 'keyword arguments': self._parse_keyword_arguments_section, 'methods': self._parse_methods_section, 'note': self._parse_note_section, 'notes': self._parse_notes_section, 'other parameters': self._parse_other_parameters_section, 'parameters': self._parse_parameters_section, 'return': self._parse_returns_section, 'returns': self._parse_returns_section, 'raises': self._parse_raises_section, 'references': self._parse_references_section, 'see also': self._parse_see_also_section, 'warning': self._parse_warning_section, 'warnings': self._parse_warning_section, 'warns': self._parse_warns_section, 'yields': self._parse_yields_section, } self._parse()
def test_class_data_member(self): config = Config() docstring = """data member description: - a: b """ actual = str( GoogleDocstring(docstring, config=config, app=None, what='attribute', name='some_data', obj=0)) expected = """data member description: - a: b""" self.assertEqual(expected, actual)
def test_underscore_in_attribute(self): docstring = """ Attributes ---------- arg_ : type some description """ expected = """ :ivar arg_: some description :vartype arg_: type """ config = Config(napoleon_use_ivar=True) app = mock.Mock() actual = str(NumpyDocstring(docstring, config, app, "class")) self.assertEqual(expected, actual)
def test_custom_generic_sections(self): docstrings = (("""\ Really Important Details: You should listen to me! """, """.. rubric:: Really Important Details You should listen to me! """), ("""\ Sooper Warning: Stop hitting yourself! """, """:Warns: **Stop hitting yourself!** """)) testConfig = Config(napoleon_custom_sections=[ 'Really Important Details', ('Sooper Warning', 'warns') ]) for docstring, expected in docstrings: actual = str(GoogleDocstring(docstring, testConfig)) self.assertEqual(expected, actual)
def test_xrefs_in_return_type(self): docstring = """ Example Function Returns ------- :class:`numpy.ndarray` A :math:`n \\times 2` array containing a bunch of math items """ expected = """ Example Function :returns: A :math:`n \\times 2` array containing a bunch of math items :rtype: :class:`numpy.ndarray` """ config = Config() app = mock.Mock() actual = str(NumpyDocstring(docstring, config, app, "method")) self.assertEqual(expected, actual)
def test_colon_in_return_type(self): docstring = """ Summary Returns ------- :py:class:`~my_mod.my_class` an instance of :py:class:`~my_mod.my_class` """ expected = """ Summary :returns: an instance of :py:class:`~my_mod.my_class` :rtype: :py:class:`~my_mod.my_class` """ config = Config() app = mock.Mock() actual = str(NumpyDocstring(docstring, config, app, "method")) self.assertEqual(expected, actual)
def test_sphinx_admonitions(self): admonition_map = { 'Attention': 'attention', 'Caution': 'caution', 'Danger': 'danger', 'Error': 'error', 'Hint': 'hint', 'Important': 'important', 'Note': 'note', 'Tip': 'tip', 'Todo': 'todo', 'Warning': 'warning', 'Warnings': 'warning', } config = Config() for section, admonition in admonition_map.items(): # Multiline actual = str( NumpyDocstring(("{}\n" "{}\n" " this is the first line\n" "\n" " and this is the second line\n").format( section, '-' * len(section)), config)) expect = (".. {}::\n" "\n" " this is the first line\n" " \n" " and this is the second line\n").format(admonition) self.assertEqual(expect, actual) # Single line actual = str( NumpyDocstring(("{}\n" "{}\n" " this is a single line\n").format( section, '-' * len(section)), config)) expect = (".. {}:: this is a single line\n").format(admonition) self.assertEqual(expect, actual)
def test_list_in_parameter_description(self): docstring = """One line summary. Parameters: no_list (int): one_bullet_empty (int): * one_bullet_single_line (int): - first line one_bullet_two_lines (int): + first line continued two_bullets_single_line (int): - first line - second line two_bullets_two_lines (int): * first line continued * second line continued one_enumeration_single_line (int): 1. first line one_enumeration_two_lines (int): 1) first line continued two_enumerations_one_line (int): (iii) first line (iv) second line two_enumerations_two_lines (int): a. first line continued b. second line continued one_definition_one_line (int): item 1 first line one_definition_two_lines (int): item 1 first line continued two_definitions_one_line (int): item 1 first line item 2 second line two_definitions_two_lines (int): item 1 first line continued item 2 second line continued one_definition_blank_line (int): item 1 first line extra first line two_definitions_blank_lines (int): item 1 first line extra first line item 2 second line extra second line definition_after_inline_text (int): text line item 1 first line definition_after_normal_text (int): text line item 1 first line """ expected = """One line summary. :param no_list: :type no_list: int :param one_bullet_empty: * :type one_bullet_empty: int :param one_bullet_single_line: - first line :type one_bullet_single_line: int :param one_bullet_two_lines: + first line continued :type one_bullet_two_lines: int :param two_bullets_single_line: - first line - second line :type two_bullets_single_line: int :param two_bullets_two_lines: * first line continued * second line continued :type two_bullets_two_lines: int :param one_enumeration_single_line: 1. first line :type one_enumeration_single_line: int :param one_enumeration_two_lines: 1) first line continued :type one_enumeration_two_lines: int :param two_enumerations_one_line: (iii) first line (iv) second line :type two_enumerations_one_line: int :param two_enumerations_two_lines: a. first line continued b. second line continued :type two_enumerations_two_lines: int :param one_definition_one_line: item 1 first line :type one_definition_one_line: int :param one_definition_two_lines: item 1 first line continued :type one_definition_two_lines: int :param two_definitions_one_line: item 1 first line item 2 second line :type two_definitions_one_line: int :param two_definitions_two_lines: item 1 first line continued item 2 second line continued :type two_definitions_two_lines: int :param one_definition_blank_line: item 1 first line extra first line :type one_definition_blank_line: int :param two_definitions_blank_lines: item 1 first line extra first line item 2 second line extra second line :type two_definitions_blank_lines: int :param definition_after_inline_text: text line item 1 first line :type definition_after_inline_text: int :param definition_after_normal_text: text line item 1 first line :type definition_after_normal_text: int """ config = Config(napoleon_use_param=True) actual = str(GoogleDocstring(docstring, config)) self.assertEqual(expected, actual) expected = """One line summary. :Parameters: * **no_list** (*int*) * **one_bullet_empty** (*int*) -- * * **one_bullet_single_line** (*int*) -- - first line * **one_bullet_two_lines** (*int*) -- + first line continued * **two_bullets_single_line** (*int*) -- - first line - second line * **two_bullets_two_lines** (*int*) -- * first line continued * second line continued * **one_enumeration_single_line** (*int*) -- 1. first line * **one_enumeration_two_lines** (*int*) -- 1) first line continued * **two_enumerations_one_line** (*int*) -- (iii) first line (iv) second line * **two_enumerations_two_lines** (*int*) -- a. first line continued b. second line continued * **one_definition_one_line** (*int*) -- item 1 first line * **one_definition_two_lines** (*int*) -- item 1 first line continued * **two_definitions_one_line** (*int*) -- item 1 first line item 2 second line * **two_definitions_two_lines** (*int*) -- item 1 first line continued item 2 second line continued * **one_definition_blank_line** (*int*) -- item 1 first line extra first line * **two_definitions_blank_lines** (*int*) -- item 1 first line extra first line item 2 second line extra second line * **definition_after_inline_text** (*int*) -- text line item 1 first line * **definition_after_normal_text** (*int*) -- text line item 1 first line """ config = Config(napoleon_use_param=False) actual = str(GoogleDocstring(docstring, config)) self.assertEqual(expected, actual)
def test_raises_types(self): docstrings = [ (""" Example Function Raises ------ InvalidDimensionsError """, """ Example Function :raises: :exc:`InvalidDimensionsError` """), ################################ (""" Example Function Raises ------ Invalid Dimensions Error """, """ Example Function :raises: Invalid Dimensions Error """), ################################ (""" Example Function Raises ------ Invalid Dimensions Error With description """, """ Example Function :raises: *Invalid Dimensions Error* -- With description """), ################################ (""" Example Function Raises ------ InvalidDimensionsError If the dimensions couldn't be parsed. """, """ Example Function :raises: :exc:`InvalidDimensionsError` -- If the dimensions couldn't be parsed. """), ################################ (""" Example Function Raises ------ Invalid Dimensions Error If the dimensions couldn't be parsed. """, """ Example Function :raises: *Invalid Dimensions Error* -- If the dimensions couldn't be parsed. """), ################################ (""" Example Function Raises ------ If the dimensions couldn't be parsed. """, """ Example Function :raises: If the dimensions couldn't be parsed. """), ################################ (""" Example Function Raises ------ :class:`exc.InvalidDimensionsError` """, """ Example Function :raises: :class:`exc.InvalidDimensionsError` """), ################################ (""" Example Function Raises ------ :class:`exc.InvalidDimensionsError` If the dimensions couldn't be parsed. """, """ Example Function :raises: :class:`exc.InvalidDimensionsError` -- If the dimensions couldn't be parsed. """), ################################ (""" Example Function Raises ------ :class:`exc.InvalidDimensionsError` If the dimensions couldn't be parsed, then a :class:`exc.InvalidDimensionsError` will be raised. """, """ Example Function :raises: :class:`exc.InvalidDimensionsError` -- If the dimensions couldn't be parsed, then a :class:`exc.InvalidDimensionsError` will be raised. """), ################################ (""" Example Function Raises ------ :class:`exc.InvalidDimensionsError` If the dimensions couldn't be parsed. :class:`exc.InvalidArgumentsError` If the arguments are invalid. """, """ Example Function :raises: * :class:`exc.InvalidDimensionsError` -- If the dimensions couldn't be parsed. * :class:`exc.InvalidArgumentsError` -- If the arguments are invalid. """), ################################ (""" Example Function Raises ------ :class:`exc.InvalidDimensionsError` :class:`exc.InvalidArgumentsError` """, """ Example Function :raises: * :class:`exc.InvalidDimensionsError` * :class:`exc.InvalidArgumentsError` """) ] for docstring, expected in docstrings: config = Config() app = Mock() actual = str(NumpyDocstring(docstring, config, app, "method")) self.assertEqual(expected, actual)
#%% import json import itertools import os import os.path import sys import subprocess as sp import collections from pprint import pprint from sphinxcontrib.napoleon.docstring import GoogleDocstring from sphinxcontrib.napoleon import Config napoleon_config = Config(napoleon_use_param=True, napoleon_use_rtype=True) root = os.path.abspath(sys.argv[1]) print(f"Generating documentation for {root}...") # 1. Call seqc -docstr and get a documentation in JSON format def load_json(directory): # Get all seq files in the directory files = [] for root, _, items in os.walk(directory): for f in items: if f.endswith('.seq'): files.append(os.path.abspath(os.path.join(root, f))) files = '\n'.join(files) s = sp.run(['../../build/seqc', '-docstr'], stdout=sp.PIPE, input=files.encode('utf-8')) if s.returncode != 0:
#!/usr/bin/env python3 try: from sphinxcontrib.napoleon import Config from sphinxcontrib.napoleon.docstring import (GoogleDocstring, NumpyDocstring) except ImportError: def convert_docstring(docstring): return docstring else: CONFIG = Config(napoleon_use_param=True, napoleon_use_rtype=True, napoleon_use_ivar=True, napoleon_include_special_with_doc=True) def convert_docstring(docstring): if is_google_style(docstring): return str(GoogleDocstring(docstring, CONFIG)) elif is_numpy_style(docstring): return str(NumpyDocstring(docstring, CONFIG)) return docstring PARAGRAPHS = ('Args', 'Arguments', 'Attributes', 'Example', 'Examples', 'Keyword Args', 'Keyword Arguments', 'Methods', 'Note', 'Notes', 'Other Parameters', 'Parameters', 'Return', 'Returns', 'Raises', 'References', 'See Also', 'Warning', 'Warnings', 'Warns', 'Yield', 'Yields')
def test_raises_types(self): docstrings = [ (""" Example Function Raises ------ RuntimeError A setting wasn't specified, or was invalid. ValueError Something something value error. """, """ Example Function :raises RuntimeError: A setting wasn't specified, or was invalid. :raises ValueError: Something something value error. """), ################################ (""" Example Function Raises ------ InvalidDimensionsError """, """ Example Function :raises InvalidDimensionsError: """), ################################ (""" Example Function Raises ------ Invalid Dimensions Error """, """ Example Function :raises Invalid Dimensions Error: """), ################################ (""" Example Function Raises ------ Invalid Dimensions Error With description """, """ Example Function :raises Invalid Dimensions Error: With description """), ################################ (""" Example Function Raises ------ InvalidDimensionsError If the dimensions couldn't be parsed. """, """ Example Function :raises InvalidDimensionsError: If the dimensions couldn't be parsed. """), ################################ (""" Example Function Raises ------ Invalid Dimensions Error If the dimensions couldn't be parsed. """, """ Example Function :raises Invalid Dimensions Error: If the dimensions couldn't be parsed. """), ################################ (""" Example Function Raises ------ If the dimensions couldn't be parsed. """, """ Example Function :raises If the dimensions couldn't be parsed.: """), ################################ (""" Example Function Raises ------ :class:`exc.InvalidDimensionsError` """, """ Example Function :raises exc.InvalidDimensionsError: """), ################################ (""" Example Function Raises ------ :class:`exc.InvalidDimensionsError` If the dimensions couldn't be parsed. """, """ Example Function :raises exc.InvalidDimensionsError: If the dimensions couldn't be parsed. """), ################################ (""" Example Function Raises ------ :class:`exc.InvalidDimensionsError` If the dimensions couldn't be parsed, then a :class:`exc.InvalidDimensionsError` will be raised. """, """ Example Function :raises exc.InvalidDimensionsError: If the dimensions couldn't be parsed, then a :class:`exc.InvalidDimensionsError` will be raised. """), ################################ (""" Example Function Raises ------ :class:`exc.InvalidDimensionsError` If the dimensions couldn't be parsed. :class:`exc.InvalidArgumentsError` If the arguments are invalid. """, """ Example Function :raises exc.InvalidDimensionsError: If the dimensions couldn't be parsed. :raises exc.InvalidArgumentsError: If the arguments are invalid. """), ################################ (""" Example Function Raises ------ :class:`exc.InvalidDimensionsError` :class:`exc.InvalidArgumentsError` """, """ Example Function :raises exc.InvalidDimensionsError: :raises exc.InvalidArgumentsError: """) ] for docstring, expected in docstrings: config = Config() app = mock.Mock() actual = str(NumpyDocstring(docstring, config, app, "method")) self.assertEqual(expected, actual)
def _parse_docstring(doc): """Extract documentation from a function's docstring.""" if doc is None: return _Doc('', '', {}, []) # Convert Google- or Numpy-style docstrings to RST. # (Should do nothing if not in either style.) # use_ivar avoids generating an unhandled .. attribute:: directive for # Attribute blocks, preferring a benign :ivar: field. cfg = Config(napoleon_use_ivar=True) doc = str(GoogleDocstring(doc, cfg)) doc = str(NumpyDocstring(doc, cfg)) with _sphinx_common_roles(): tree = docutils.core.publish_doctree(doc) class Visitor(NodeVisitor): optional = [ 'document', 'docinfo', 'field_list', 'field_body', 'literal', 'problematic', # Introduced by our custom passthrough handlers, but the Visitor # will recurse into the inner text node by itself. 'TextElement', ] def __init__(self, document): super().__init__(document) self.paragraphs = [] self.start_lines = [] self.params = defaultdict(dict) self.raises = [] self._current_paragraph = None self._indent_iterator_stack = [] self._indent_stack = [] def _do_nothing(self, node): pass def visit_paragraph(self, node): self.start_lines.append(node.line) self._current_paragraph = [] def depart_paragraph(self, node): text = ''.join(self._current_paragraph) text = ''.join(self._indent_stack) + text self._indent_stack = [ ' ' * len(item) for item in self._indent_stack] text = text.replace('\n', '\n' + ''.join(self._indent_stack)) self.paragraphs.append(text) self._current_paragraph = None visit_block_quote = visit_doctest_block = visit_paragraph depart_block_quote = depart_doctest_block = depart_paragraph def visit_Text(self, node): self._current_paragraph.append(node) depart_Text = _do_nothing def visit_emphasis(self, node): self._current_paragraph.append('\033[3m') # *foo*: italic def visit_strong(self, node): self._current_paragraph.append('\033[1m') # **foo**: bold def visit_title_reference(self, node): self._current_paragraph.append('\033[4m') # `foo`: underlined def _depart_markup(self, node): self._current_paragraph.append('\033[0m') depart_emphasis = depart_strong = depart_title_reference = \ _depart_markup def visit_rubric(self, node): self.visit_paragraph(node) def depart_rubric(self, node): # Style consistent with "usage:", "positional arguments:", etc. self._current_paragraph[:] = [ (t.lower() if t == t.title() else t) + ":" for t in self._current_paragraph] self.depart_paragraph(node) def visit_literal_block(self, node): text, = node self.start_lines.append(node.line) self.paragraphs.append( re.sub('^|\n', r'\g<0> ', text)) # indent raise SkipNode def visit_bullet_list(self, node): self._indent_iterator_stack.append( (node['bullet'] + ' ' for _ in range(len(node)))) def depart_bullet_list(self, node): self._indent_iterator_stack.pop() def visit_enumerated_list(self, node): enumtype = node['enumtype'] fmt = {('(', ')'): 'parens', ('', ')'): 'rparen', ('', '.'): 'period'}[node['prefix'], node['suffix']] start = node.get('start', 1) enumerators = [Body(None).make_enumerator(i, enumtype, fmt)[0] for i in range(start, start + len(node))] width = max(map(len, enumerators)) enumerators = [enum.ljust(width) for enum in enumerators] self._indent_iterator_stack.append(iter(enumerators)) def depart_enumerated_list(self, node): self._indent_iterator_stack.pop() def visit_list_item(self, node): self._indent_stack.append(next(self._indent_iterator_stack[-1])) def depart_list_item(self, node): self._indent_stack.pop() def visit_field(self, node): field_name_node, field_body_node = node field_name, = field_name_node parts = field_name.split() if len(parts) == 2: doctype, name = parts # docutils>=0.16 represents \* as \0* in the doctree. name = name.lstrip('*\0') elif len(parts) == 3: doctype, type_, name = parts name = name.lstrip('*\0') if doctype not in _PARAM_TYPES: raise SkipNode if 'type' in self.params[name]: raise ValueError('type defined twice for {}'.format(name)) self.params[name]['type'] = type_ else: raise SkipNode if doctype in _PARAM_TYPES: doctype = 'param' if doctype in _TYPE_NAMES: doctype = 'type' if doctype in ['param', 'type'] and doctype in self.params[name]: raise ValueError( '{} defined twice for {}'.format(doctype, name)) visitor = Visitor(self.document) field_body_node.walkabout(visitor) if doctype in ['param', 'type']: self.params[name][doctype] = ''.join(visitor.paragraphs) elif doctype in ['raises']: self.raises.append(name) raise SkipNode def visit_comment(self, node): self.paragraphs.append(comment_token) # Comments report their line as the *end* line of the comment. self.start_lines.append( node.line - node.children[0].count('\n') - 1) raise SkipNode def visit_system_message(self, node): raise SkipNode comment_token = object() visitor = Visitor(tree) tree.walkabout(visitor) tuples = {name: _Param(values.get('param'), values.get('type')) for name, values in visitor.params.items()} if visitor.paragraphs: text = [] for start, paragraph, next_start in zip( visitor.start_lines, visitor.paragraphs, visitor.start_lines[1:] + [0]): if paragraph is comment_token: continue text.append(paragraph) # We insert a space before each newline to prevent argparse # from stripping consecutive newlines down to just two # (http://bugs.python.org/issue31330). # FIXME: but napoleon inserts far too many newlines :/ text.append(' \n' * (next_start - start - paragraph.count('\n'))) parsed = _Doc(text[0], ''.join(text), tuples, visitor.raises) else: parsed = _Doc('', '', tuples, visitor.raises) return parsed