diff options
Diffstat (limited to 'numpydoc/docscrape.py')
-rw-r--r-- | numpydoc/docscrape.py | 365 |
1 files changed, 196 insertions, 169 deletions
diff --git a/numpydoc/docscrape.py b/numpydoc/docscrape.py index 1587797..6bdaa84 100644 --- a/numpydoc/docscrape.py +++ b/numpydoc/docscrape.py @@ -22,9 +22,8 @@ def strip_blank_lines(l): class Reader: - """A line-based string reader. + """A line-based string reader.""" - """ def __init__(self, data): """ Parameters @@ -36,7 +35,7 @@ class Reader: if isinstance(data, list): self._str = data else: - self._str = data.split('\n') # store string as list of lines + self._str = data.split("\n") # store string as list of lines self.reset() @@ -52,10 +51,10 @@ class Reader: self._l += 1 return out else: - return '' + return "" def seek_next_non_empty_line(self): - for l in self[self._l:]: + for l in self[self._l :]: if l.strip(): break else: @@ -68,10 +67,10 @@ class Reader: start = self._l for line in self[start:]: if condition_func(line): - return self[start:self._l] + return self[start : self._l] self._l += 1 if self.eof(): - return self[start:self._l+1] + return self[start : self._l + 1] return [] def read_to_next_empty_line(self): @@ -84,28 +83,29 @@ class Reader: def read_to_next_unindented_line(self): def is_unindented(line): - return (line.strip() and (len(line.lstrip()) == len(line))) + return line.strip() and (len(line.lstrip()) == len(line)) + return self.read_to_condition(is_unindented) def peek(self, n=0): if self._l + n < len(self._str): return self[self._l + n] else: - return '' + return "" def is_empty(self): - return not ''.join(self._str).strip() + return not "".join(self._str).strip() class ParseError(Exception): def __str__(self): message = self.args[0] - if hasattr(self, 'docstring'): + if hasattr(self, "docstring"): message = f"{message} in {self.docstring!r}" return message -Parameter = namedtuple('Parameter', ['name', 'type', 'desc']) +Parameter = namedtuple("Parameter", ["name", "type", "desc"]) class NumpyDocString(Mapping): @@ -116,29 +116,29 @@ class NumpyDocString(Mapping): """ sections = { - 'Signature': '', - 'Summary': [''], - 'Extended Summary': [], - 'Parameters': [], - 'Returns': [], - 'Yields': [], - 'Receives': [], - 'Raises': [], - 'Warns': [], - 'Other Parameters': [], - 'Attributes': [], - 'Methods': [], - 'See Also': [], - 'Notes': [], - 'Warnings': [], - 'References': '', - 'Examples': '', - 'index': {} + "Signature": "", + "Summary": [""], + "Extended Summary": [], + "Parameters": [], + "Returns": [], + "Yields": [], + "Receives": [], + "Raises": [], + "Warns": [], + "Other Parameters": [], + "Attributes": [], + "Methods": [], + "See Also": [], + "Notes": [], + "Warnings": [], + "References": "", + "Examples": "", + "index": {}, } def __init__(self, docstring, config=None): orig_docstring = docstring - docstring = textwrap.dedent(docstring).split('\n') + docstring = textwrap.dedent(docstring).split("\n") self._doc = Reader(docstring) self._parsed_data = copy.deepcopy(self.sections) @@ -172,15 +172,17 @@ class NumpyDocString(Mapping): l1 = self._doc.peek().strip() # e.g. Parameters - if l1.startswith('.. index::'): + if l1.startswith(".. index::"): return True l2 = self._doc.peek(1).strip() # ---------- or ========== - if len(l2) >= 3 and (set(l2) in ({'-'}, {'='}) ) and len(l2) != len(l1): - snip = '\n'.join(self._doc._str[:2])+'...' - self._error_location("potentially wrong underline length... \n%s \n%s in \n%s"\ - % (l1, l2, snip), error=False) - return l2.startswith('-'*len(l1)) or l2.startswith('='*len(l1)) + if len(l2) >= 3 and (set(l2) in ({"-"}, {"="})) and len(l2) != len(l1): + snip = "\n".join(self._doc._str[:2]) + "..." + self._error_location( + f"potentially wrong underline length... \n{l1} \n{l2} in \n{snip}", + error=False, + ) + return l2.startswith("-" * len(l1)) or l2.startswith("=" * len(l1)) def _strip(self, doc): i = 0 @@ -193,14 +195,14 @@ class NumpyDocString(Mapping): if line.strip(): break - return doc[i:len(doc)-j] + return doc[i : len(doc) - j] def _read_to_next_section(self): section = self._doc.read_to_next_empty_line() while not self._is_at_section() and not self._doc.eof(): if not self._doc.peek(-1).strip(): # previous line was empty - section += [''] + section += [""] section += self._doc.read_to_next_empty_line() @@ -211,7 +213,7 @@ class NumpyDocString(Mapping): data = self._read_to_next_section() name = data[0].strip() - if name.startswith('..'): # index section + if name.startswith(".."): # index section yield name, data[1:] elif len(data) < 2: yield StopIteration @@ -224,14 +226,14 @@ class NumpyDocString(Mapping): params = [] while not r.eof(): header = r.read().strip() - if ' :' in header: - arg_name, arg_type = header.split(' :', maxsplit=1) + if " :" in header: + arg_name, arg_type = header.split(" :", maxsplit=1) arg_name, arg_type = arg_name.strip(), arg_type.strip() else: if single_element_is_type: - arg_name, arg_type = '', header + arg_name, arg_type = "", header else: - arg_name, arg_type = header, '' + arg_name, arg_type = header, "" desc = r.read_to_next_unindented_line() desc = dedent_lines(desc) @@ -261,21 +263,24 @@ class NumpyDocString(Mapping): _funcbacktick = r"`(?P<name>(?:~\w+\.)?[a-zA-Z0-9_\.-]+)`" _funcplain = r"(?P<name2>[a-zA-Z0-9_\.-]+)" _funcname = r"(" + _role + _funcbacktick + r"|" + _funcplain + r")" - _funcnamenext = _funcname.replace('role', 'rolenext') - _funcnamenext = _funcnamenext.replace('name', 'namenext') + _funcnamenext = _funcname.replace("role", "rolenext") + _funcnamenext = _funcnamenext.replace("name", "namenext") _description = r"(?P<description>\s*:(\s+(?P<desc>\S+.*))?)?\s*$" _func_rgx = re.compile(r"^\s*" + _funcname + r"\s*") _line_rgx = re.compile( - r"^\s*" + - r"(?P<allfuncs>" + # group for all function names - _funcname + - r"(?P<morefuncs>([,]\s+" + _funcnamenext + r")*)" + - r")" + # end of "allfuncs" - r"(?P<trailing>[,\.])?" + # Some function lists have a trailing comma (or period) '\s*' - _description) + r"^\s*" + + r"(?P<allfuncs>" + + _funcname # group for all function names + + r"(?P<morefuncs>([,]\s+" + + _funcnamenext + + r")*)" + + r")" + + r"(?P<trailing>[,\.])?" # end of "allfuncs" + + _description # Some function lists have a trailing comma (or period) '\s*' + ) # Empty <DESC> elements are replaced with '..' - empty_description = '..' + empty_description = ".." def _parse_see_also(self, content): """ @@ -295,8 +300,8 @@ class NumpyDocString(Mapping): m = self._func_rgx.match(text) if not m: self._error_location(f"Error parsing See Also entry {line!r}") - role = m.group('role') - name = m.group('name') if role else m.group('name2') + role = m.group("role") + name = m.group("name") if role else m.group("name2") return name, role, m.end() rest = [] @@ -307,24 +312,25 @@ class NumpyDocString(Mapping): line_match = self._line_rgx.match(line) description = None if line_match: - description = line_match.group('desc') - if line_match.group('trailing') and description: + description = line_match.group("desc") + if line_match.group("trailing") and description: self._error_location( - 'Unexpected comma or period after function list at index %d of ' - 'line "%s"' % (line_match.end('trailing'), line), - error=False) - if not description and line.startswith(' '): + "Unexpected comma or period after function list at index %d of " + 'line "%s"' % (line_match.end("trailing"), line), + error=False, + ) + if not description and line.startswith(" "): rest.append(line.strip()) elif line_match: funcs = [] - text = line_match.group('allfuncs') + text = line_match.group("allfuncs") while True: if not text.strip(): break name, role, match_end = parse_item_name(text) funcs.append((name, role)) text = text[match_end:].strip() - if text and text[0] == ',': + if text and text[0] == ",": text = text[1:].strip() rest = list(filter(None, [description])) items.append((funcs, rest)) @@ -338,17 +344,18 @@ class NumpyDocString(Mapping): :refguide: something, else, and more """ + def strip_each_in(lst): return [s.strip() for s in lst] out = {} - section = section.split('::') + section = section.split("::") if len(section) > 1: - out['default'] = strip_each_in(section[1].split(','))[0] + out["default"] = strip_each_in(section[1].split(","))[0] for line in content: - line = line.split(':') + line = line.split(":") if len(line) > 2: - out[line[1]] = strip_each_in(line[2].split(',')) + out[line[1]] = strip_each_in(line[2].split(",")) return out def _parse_summary(self): @@ -360,18 +367,18 @@ class NumpyDocString(Mapping): while True: summary = self._doc.read_to_next_empty_line() summary_str = " ".join([s.strip() for s in summary]).strip() - compiled = re.compile(r'^([\w., ]+=)?\s*[\w\.]+\(.*\)$') + compiled = re.compile(r"^([\w., ]+=)?\s*[\w\.]+\(.*\)$") if compiled.match(summary_str): - self['Signature'] = summary_str + self["Signature"] = summary_str if not self._is_at_section(): continue break if summary is not None: - self['Summary'] = summary + self["Summary"] = summary if not self._is_at_section(): - self['Extended Summary'] = self._read_to_next_section() + self["Extended Summary"] = self._read_to_next_section() def _parse(self): self._doc.reset() @@ -380,42 +387,44 @@ class NumpyDocString(Mapping): sections = list(self._read_sections()) section_names = {section for section, content in sections} - has_returns = 'Returns' in section_names - has_yields = 'Yields' in section_names + 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.' + msg = "Docstring contains both a Returns and Yields section." raise ValueError(msg) - if not has_yields and 'Receives' in section_names: - msg = 'Docstring contains a Receives section but not Yields.' + if not has_yields and "Receives" in section_names: + msg = "Docstring contains a Receives section but not Yields." raise ValueError(msg) for (section, content) in sections: - if not section.startswith('..'): - section = (s.capitalize() for s in section.split(' ')) - section = ' '.join(section) + if not section.startswith(".."): + section = (s.capitalize() for s in section.split(" ")) + section = " ".join(section) if self.get(section): - self._error_location("The section %s appears twice in %s" - % (section, '\n'.join(self._doc._str))) + self._error_location( + "The section %s appears twice in %s" + % (section, "\n".join(self._doc._str)) + ) - if section in ('Parameters', 'Other Parameters', 'Attributes', - 'Methods'): + if section in ("Parameters", "Other Parameters", "Attributes", "Methods"): self[section] = self._parse_param_list(content) - elif section in ('Returns', 'Yields', 'Raises', 'Warns', 'Receives'): + elif section in ("Returns", "Yields", "Raises", "Warns", "Receives"): self[section] = self._parse_param_list( - content, single_element_is_type=True) - elif section.startswith('.. index::'): - self['index'] = self._parse_index(section, content) - elif section == 'See Also': - self['See Also'] = self._parse_see_also(content) + content, single_element_is_type=True + ) + elif section.startswith(".. index::"): + self["index"] = self._parse_index(section, content) + elif section == "See Also": + self["See Also"] = self._parse_see_also(content) else: self[section] = content @property def _obj(self): - if hasattr(self, '_cls'): + if hasattr(self, "_cls"): return self._cls - elif hasattr(self, '_f'): + elif hasattr(self, "_f"): return self._f return None @@ -428,9 +437,9 @@ class NumpyDocString(Mapping): filename = None # Make UserWarning more descriptive via object introspection. # Skip if introspection fails - name = getattr(self._obj, '__name__', None) + name = getattr(self._obj, "__name__", None) if name is None: - name = getattr(getattr(self._obj, '__class__', None), '__name__', None) + name = getattr(getattr(self._obj, "__class__", None), "__name__", None) if name is not None: msg += f" in the docstring of {name}" msg += f" in {filename}." if filename else "" @@ -441,25 +450,25 @@ class NumpyDocString(Mapping): # string conversion routines - def _str_header(self, name, symbol='-'): - return [name, len(name)*symbol] + def _str_header(self, name, symbol="-"): + return [name, len(name) * symbol] def _str_indent(self, doc, indent=4): - return [' '*indent + line for line in doc] + return [" " * indent + line for line in doc] def _str_signature(self): - if self['Signature']: - return [self['Signature'].replace('*', r'\*')] + [''] - return [''] + if self["Signature"]: + return [self["Signature"].replace("*", r"\*")] + [""] + return [""] def _str_summary(self): - if self['Summary']: - return self['Summary'] + [''] + if self["Summary"]: + return self["Summary"] + [""] return [] def _str_extended_summary(self): - if self['Extended Summary']: - return self['Extended Summary'] + [''] + if self["Extended Summary"]: + return self["Extended Summary"] + [""] return [] def _str_param_list(self, name): @@ -472,10 +481,10 @@ class NumpyDocString(Mapping): parts.append(param.name) if param.type: parts.append(param.type) - out += [' : '.join(parts)] - if param.desc and ''.join(param.desc).strip(): + out += [" : ".join(parts)] + if param.desc and "".join(param.desc).strip(): out += self._str_indent(param.desc) - out += [''] + out += [""] return out def _str_section(self, name): @@ -483,74 +492,81 @@ class NumpyDocString(Mapping): if self[name]: out += self._str_header(name) out += self[name] - out += [''] + out += [""] return out def _str_see_also(self, func_role): - if not self['See Also']: + if not self["See Also"]: return [] out = [] out += self._str_header("See Also") - out += [''] + out += [""] last_had_desc = True - for funcs, desc in self['See Also']: + for funcs, desc in self["See Also"]: assert isinstance(funcs, list) links = [] for func, role in funcs: if role: - link = f':{role}:`{func}`' + link = f":{role}:`{func}`" elif func_role: - link = f':{func_role}:`{func}`' + link = f":{func_role}:`{func}`" else: link = f"`{func}`_" links.append(link) - link = ', '.join(links) + link = ", ".join(links) out += [link] if desc: - out += self._str_indent([' '.join(desc)]) + out += self._str_indent([" ".join(desc)]) last_had_desc = True else: last_had_desc = False out += self._str_indent([self.empty_description]) if last_had_desc: - out += [''] - out += [''] + out += [""] + out += [""] return out def _str_index(self): - idx = self['index'] + idx = self["index"] out = [] output_index = False - default_index = idx.get('default', '') + default_index = idx.get("default", "") if default_index: output_index = True - out += [f'.. index:: {default_index}'] + out += [f".. index:: {default_index}"] for section, references in idx.items(): - if section == 'default': + if section == "default": continue output_index = True out += [f" :{section}: {', '.join(references)}"] if output_index: return out - return '' + return "" - def __str__(self, func_role=''): + def __str__(self, func_role=""): out = [] out += self._str_signature() out += self._str_summary() out += self._str_extended_summary() - for param_list in ('Parameters', 'Returns', 'Yields', 'Receives', - 'Other Parameters', 'Raises', 'Warns'): + for param_list in ( + "Parameters", + "Returns", + "Yields", + "Receives", + "Other Parameters", + "Raises", + "Warns", + ): out += self._str_param_list(param_list) - out += self._str_section('Warnings') + out += self._str_section("Warnings") out += self._str_see_also(func_role) - for s in ('Notes', 'References', 'Examples'): + for s in ("Notes", "References", "Examples"): out += self._str_section(s) - for param_list in ('Attributes', 'Methods'): + for param_list in ("Attributes", "Methods"): out += self._str_param_list(param_list) out += self._str_index() - return '\n'.join(out) + return "\n".join(out) def dedent_lines(lines): @@ -559,33 +575,32 @@ def dedent_lines(lines): class FunctionDoc(NumpyDocString): - def __init__(self, func, role='func', doc=None, config=None): + def __init__(self, func, role="func", doc=None, config=None): self._f = func self._role = role # e.g. "func" or "meth" if doc is None: if func is None: raise ValueError("No function or docstring given") - doc = inspect.getdoc(func) or '' + doc = inspect.getdoc(func) or "" if config is None: config = {} NumpyDocString.__init__(self, doc, config) def get_func(self): - func_name = getattr(self._f, '__name__', self.__class__.__name__) + func_name = getattr(self._f, "__name__", self.__class__.__name__) if inspect.isclass(self._f): - func = getattr(self._f, '__call__', self._f.__init__) + func = getattr(self._f, "__call__", self._f.__init__) else: func = self._f return func, func_name def __str__(self): - out = '' + out = "" func, func_name = self.get_func() - roles = {'func': 'function', - 'meth': 'method'} + roles = {"func": "function", "meth": "method"} if self._role: if self._role not in roles: @@ -606,26 +621,24 @@ class ObjDoc(NumpyDocString): class ClassDoc(NumpyDocString): - extra_public_methods = ['__call__'] + extra_public_methods = ["__call__"] - def __init__(self, cls, doc=None, modulename='', func_doc=FunctionDoc, - config=None): + def __init__(self, cls, doc=None, modulename="", func_doc=FunctionDoc, config=None): if not inspect.isclass(cls) and cls is not None: raise ValueError(f"Expected a class or None, but got {cls!r}") self._cls = cls - if 'sphinx' in sys.modules: + if "sphinx" in sys.modules: from sphinx.ext.autodoc import ALL else: ALL = object() if config is None: config = {} - self.show_inherited_members = config.get( - 'show_inherited_class_members', True) + self.show_inherited_members = config.get("show_inherited_class_members", True) - if modulename and not modulename.endswith('.'): - modulename += '.' + if modulename and not modulename.endswith("."): + modulename += "." self._mod = modulename if doc is None: @@ -635,29 +648,31 @@ class ClassDoc(NumpyDocString): NumpyDocString.__init__(self, doc) - _members = config.get('members', []) + _members = config.get("members", []) if _members is ALL: _members = None - _exclude = config.get('exclude-members', []) + _exclude = config.get("exclude-members", []) + + if config.get("show_class_members", True) and _exclude is not ALL: - if config.get('show_class_members', True) and _exclude is not ALL: def splitlines_x(s): if not s: return [] else: return s.splitlines() - for field, items in [('Methods', self.methods), - ('Attributes', self.properties)]: + + for field, items in [ + ("Methods", self.methods), + ("Attributes", self.properties), + ]: if not self[field]: doc_list = [] for name in sorted(items): - if (name in _exclude or - (_members and name not in _members)): + if name in _exclude or (_members and name not in _members): continue try: doc_item = pydoc.getdoc(getattr(self._cls, name)) - doc_list.append( - Parameter(name, '', splitlines_x(doc_item))) + doc_list.append(Parameter(name, "", splitlines_x(doc_item))) except AttributeError: pass # method doesn't exist self[field] = doc_list @@ -666,21 +681,33 @@ class ClassDoc(NumpyDocString): def methods(self): if self._cls is None: return [] - return [name for name, func in inspect.getmembers(self._cls) - if ((not name.startswith('_') - or name in self.extra_public_methods) - and isinstance(func, Callable) - and self._is_show_member(name))] + return [ + name + for name, func in inspect.getmembers(self._cls) + if ( + (not name.startswith("_") or name in self.extra_public_methods) + and isinstance(func, Callable) + and self._is_show_member(name) + ) + ] @property def properties(self): if self._cls is None: return [] - return [name for name, func in inspect.getmembers(self._cls) - if (not name.startswith('_') and - (func is None or isinstance(func, property) or - inspect.isdatadescriptor(func)) - and self._is_show_member(name))] + return [ + name + for name, func in inspect.getmembers(self._cls) + if ( + not name.startswith("_") + and ( + func is None + or isinstance(func, property) + or inspect.isdatadescriptor(func) + ) + and self._is_show_member(name) + ) + ] def _is_show_member(self, name): if self.show_inherited_members: @@ -693,19 +720,19 @@ class ClassDoc(NumpyDocString): def get_doc_object(obj, what=None, doc=None, config=None): if what is None: if inspect.isclass(obj): - what = 'class' + what = "class" elif inspect.ismodule(obj): - what = 'module' + what = "module" elif isinstance(obj, Callable): - what = 'function' + what = "function" else: - what = 'object' + what = "object" if config is None: config = {} - if what == 'class': + if what == "class": return ClassDoc(obj, func_doc=FunctionDoc, doc=doc, config=config) - elif what in ('function', 'method'): + elif what in ("function", "method"): return FunctionDoc(obj, doc=doc, config=config) else: if doc is None: |