def generate_module_rst(self, module, membersByType: Dict[str, List[Tuple[str,object,str]]]): """ Generates the RST for a Python module. :param membersByType: The filtered set of members to be documented, keyed by member type. """ if not membersByType: return None module_fullname = module.__name__ self.documented_items.add(f'{module_fullname} (module)') title = self.config['module_title_decider'](module_fullname) output = """ {module_title} {module_title_underline} .. automodule:: {module_fullname} .. currentmodule:: {module_fullname} """.format(module_fullname=module_fullname, module_title=rst.escape(title), module_title_underline=module_underline*len(rst.escape(title))) for memberType, members in membersByType.items(): if not members: continue # don't show empty sections extra = self.generate_member_type_rst(module, memberType, members) if extra: output += '\n'+extra return output
def get_module_member_rst(self, memberType, qualifiedName, obj, docstring): name = qualifiedName.split('.')[-1] result = """ {name_escaped} {name_underline} .. auto{type}:: {name} """.format( qualified_name=qualifiedName, name=name, name_escaped=rst.escape(name), name_underline=member_underline*len(rst.escape(name)), type=memberType ) defaults = {'members':bool(memberType in {'class', 'exception'})} # TODO: could get this from the autodoc default autodocoptions = self.config['autodoc_options_decider'](self.app, memberType, qualifiedName, obj, docstring, defaults, None) if autodocoptions is None: autodocoptions = defaults for k, v in autodocoptions.items(): if v is False: continue result +=f' :{k}:' if not (v is None or v is True): result += ' '+v result += '\n' return result
def format_heading(level, text, escape=True): # type: (int, unicode, bool) -> unicode """Create a heading of <level> [1, 2 or 3 supported].""" if escape: text = rst.escape(text) underlining = ['=', '-', '~', ][level - 1] * len(text) return '%s\n%s\n\n' % (text, underlining)
def _fields(self, obj): """Return an iterable of "info fields" to be included in the directive, like params, return values, and exceptions. Each field consists of a tuple ``(heads, tail)``, where heads are words that go between colons (as in ``:param string href:``) and tail comes after. """ FIELD_TYPES = [('params', _param_formatter), ('params', _param_type_formatter), ('properties', _param_formatter), ('properties', _param_type_formatter), ('exceptions', _exception_formatter), ('returns', _return_formatter)] for collection_attr, callback in FIELD_TYPES: for instance in getattr(obj, collection_attr, []): result = callback(instance) if result: heads, tail = result # If there are line breaks in the tail, the RST parser will # end the field list prematurely. # # TODO: Instead, indent multi-line tails juuuust right, and # we can enjoy block-level constructs within tails: # https://docutils.sourceforge.io/docs/ref/rst/ # restructuredtext.html#field-lists. yield [rst.escape(h) for h in heads], unwrapped(tail)
def _get_row(self, obj): template = ':{}:`{} <{}>`\\ {}' if 'nosignatures' in self.options: template = ':{}:`{} <{}>`' col1 = template.format( 'obj', obj.short_name, obj.name, escape(obj.signature), ) col2 = obj.summary row = nodes.row('') for text in (col1, col2): node = nodes.paragraph('') view_list = ViewList() view_list.append(text, '<autosummary>') self.state.nested_parse(view_list, 0, node) try: if isinstance(node[0], nodes.paragraph): node = node[0] except IndexError: pass row.append(nodes.entry('', node)) return row
def _param_type_formatter(param): """Generate types for function parameters specified in field.""" if not param.type: return None heads = ['type', param.name] tail = rst.escape(param.type) return heads, tail
def format_heading(level: int, text: str, escape: bool = True) -> str: """Create a heading of <level> [1, 2 or 3 supported].""" warnings.warn('format_warning() is deprecated.', RemovedInSphinx40Warning) if escape: text = rst.escape(text) underlining = ['=', '-', '~', ][level - 1] * len(text) return '%s\n%s\n\n' % (text, underlining)
def _or_types(field): """Return all the types in a doclet subfield like "params" or "returns" with vertical bars between them, like "number|string". ReST-escape the types. """ return rst.escape('|'.join(field.get('type', {}).get('names', [])))
def _params_formatter(field, description): """Derive heads and tail from ``@param`` blocks.""" heads = ['param'] types = _or_types(field) if types: heads.append(types) heads.append(rst.escape(field['name'])) tail = description return heads, tail
def get_table(self, items: List[Tuple[str, str, str, str]]) -> List[nodes.Node]: """ Generate a proper list of table nodes for autosummary:: directive. :param items: A a list produced by :meth:`~.get_items`. """ table_spec = addnodes.tabular_col_spec() # table_spec['spec'] = r'\Xx{1}{3}\Xx{2}{3}' # table_spec['spec'] = r'\Xx{3}{8}\Xx{5}{8}' # table_spec['spec'] = r'\Xx{7}{16}\Xx{9}{16}' widths = chain.from_iterable( getattr(self.state.document, "autosummary_widths", ((1, 2), (1, 2)))) table_spec["spec"] = r'\Xx{{{}}}{{{}}}\Xx{{{}}}{{{}}}'.format(*widths) table = autosummary_table('') real_table = nodes.table('', classes=["longtable"]) table.append(real_table) group = nodes.tgroup('', cols=2) real_table.append(group) group.append(nodes.colspec('', colwidth=10)) group.append(nodes.colspec('', colwidth=90)) body = nodes.tbody('') group.append(body) def append_row(*column_texts: str) -> None: row = nodes.row('') source, line = self.state_machine.get_source_and_line() for text in column_texts: node = nodes.paragraph('') vl = StringList() vl.append(text, "%s:%d:<autosummary>" % (source, line)) with switch_source_input(self.state, vl): self.state.nested_parse(vl, 0, node) try: if isinstance(node[0], nodes.paragraph): node = node[0] except IndexError: pass row.append(nodes.entry('', node)) body.append(row) for name, sig, summary, real_name in items: qualifier = "obj" if "nosignatures" not in self.options: col1 = ":{}:`{} <{}>`\\ {}".format( qualifier, name, real_name, rst.escape(sig).replace('(', "(")) else: col1 = f":{qualifier}:`{name} <{real_name}>`" col2 = summary append_row(col1, col2) return [table_spec, table]
def _param_type_formatter(param): """Generate types for function parameters specified in field.""" if not param.type: return None heads = ['type', param.name] tail = [] tail.append(rst.escape(param.type)) if param.is_optional: tail.append(param.optional) return heads, ', '.join(tail)
def format_table(self, items): """Generate a proper list of table nodes for autosummary:: directive. *items* is a list produced by :meth:`get_items`. """ table_spec = addnodes.tabular_col_spec() table_spec["spec"] = r"\X{1}{2}\X{1}{2}" table = autosummary_table("") real_table = nodes.table("", classes=["longtable"]) table.append(real_table) group = nodes.tgroup("", cols=2) real_table.append(group) group.append(nodes.colspec("", colwidth=10)) group.append(nodes.colspec("", colwidth=90)) body = nodes.tbody("") group.append(body) def append_row(*column_texts: str) -> None: row = nodes.row("") source, line = self.state_machine.get_source_and_line() for text in column_texts: node = nodes.paragraph("") vl = StringList() vl.append(text, "%s:%d:<autosummary>" % (source, line)) with switch_source_input(self.state, vl): self.state.nested_parse(vl, 0, node) try: if isinstance(node[0], nodes.paragraph): node = node[0] except IndexError: pass row.append(nodes.entry("", node)) body.append(row) for prefix, name, sig, summary, real_name in items: qualifier = "any" # <== Only thing changed from autosummary version if "nosignatures" not in self.options: col1 = "%s:%s:`%s <%s>`\\ %s" % ( prefix, qualifier, name, real_name, rst.escape(sig), ) else: col1 = "%s:%s:`%s <%s>`" % (prefix, qualifier, name, real_name) col2 = summary append_row(col1, col2) return [table_spec, table]
def get_table(self, items): # type: (List[Tuple[unicode, unicode, unicode, unicode]]) -> List[Union[addnodes.tabular_col_spec, autosummary_table]] # NOQA """Generate a proper list of table nodes for autosummary:: directive. *items* is a list produced by :meth:`get_items`. """ table_spec = addnodes.tabular_col_spec() table_spec['spec'] = r'\X{1}{2}\X{1}{2}' table = autosummary_table('') real_table = nodes.table('', classes=['longtable']) table.append(real_table) group = nodes.tgroup('', cols=2) real_table.append(group) group.append(nodes.colspec('', colwidth=10)) group.append(nodes.colspec('', colwidth=90)) body = nodes.tbody('') group.append(body) def append_row(*column_texts): # type: (unicode) -> None row = nodes.row('') source, line = self.state_machine.get_source_and_line() for text in column_texts: node = nodes.paragraph('') vl = ViewList() vl.append(text, '%s:%d:<autosummary>' % (source, line)) with switch_source_input(self.state, vl): self.state.nested_parse(vl, 0, node) try: if isinstance(node[0], nodes.paragraph): node = node[0] except IndexError: pass row.append(nodes.entry('', node)) body.append(row) for name, sig, summary, real_name in items: qualifier = 'obj' if 'nosignatures' not in self.options: col1 = ':%s:`%s <%s>`\\ %s' % ( qualifier, name, real_name, rst.escape(sig) ) # type: unicode # NOQA else: col1 = ':%s:`%s <%s>`' % (qualifier, name, real_name) col2 = summary append_row(col1, col2) return [table_spec, table]
def get_table(self, items): """ Subclass to get support for `hidesummary` as options to enable displaying the short summary in the table """ hidesummary = 'hidesummary' in self.options table_spec = addnodes.tabular_col_spec() table_spec['spec'] = 'p{0.5\linewidth}p{0.5\linewidth}' table = autosummary_table('') real_table = nodes.table('', classes=['longtable']) table.append(real_table) group = nodes.tgroup('', cols=2) real_table.append(group) group.append(nodes.colspec('', colwidth=10)) group.append(nodes.colspec('', colwidth=90)) body = nodes.tbody('') group.append(body) def append_row(*column_texts): row = nodes.row('') for text in column_texts: node = nodes.paragraph('') vl = ViewList() vl.append(text, '<autosummary>') self.state.nested_parse(vl, 0, node) try: if isinstance(node[0], nodes.paragraph): node = node[0] except IndexError: pass row.append(nodes.entry('', node)) body.append(row) for name, sig, summary, real_name in items: qualifier = 'obj' if 'nosignatures' not in self.options: col1 = ':%s:`%s <%s>`\ %s' % (qualifier, name, real_name, rst.escape(sig)) else: col1 = ':%s:`%s <%s>`' % (qualifier, name, real_name) col2 = summary if hidesummary: append_row(col1) else: append_row(col1, col2) return [table_spec, table]
def get_table(self, items): # type: (List[Tuple[unicode, unicode, unicode, unicode]]) -> List[Union[addnodes.tabular_col_spec, autosummary_table]] # NOQA """Generate a proper list of table nodes for autosummary:: directive. *items* is a list produced by :meth:`get_items`. """ table_spec = addnodes.tabular_col_spec() table_spec['spec'] = r'\X{1}{2}\X{1}{2}' table = autosummary_table('') real_table = nodes.table('', classes=['longtable']) table.append(real_table) group = nodes.tgroup('', cols=2) real_table.append(group) group.append(nodes.colspec('', colwidth=10)) group.append(nodes.colspec('', colwidth=90)) body = nodes.tbody('') group.append(body) def append_row(*column_texts): # type: (unicode) -> None row = nodes.row('') source, line = self.state_machine.get_source_and_line() for text in column_texts: node = nodes.paragraph('') vl = ViewList() vl.append(text, '%s:%d:<autosummary>' % (source, line)) with switch_source_input(self.state, vl): self.state.nested_parse(vl, 0, node) try: if isinstance(node[0], nodes.paragraph): node = node[0] except IndexError: pass row.append(nodes.entry('', node)) body.append(row) for name, sig, summary, real_name in items: qualifier = 'obj' if 'nosignatures' not in self.options: col1 = ':%s:`%s <%s>`\\ %s' % (qualifier, name, real_name, rst.escape(sig)) # type: unicode # NOQA else: col1 = ':%s:`%s <%s>`' % (qualifier, name, real_name) col2 = summary append_row(col1, col2) return [table_spec, table]
def get_table(self, items): """Generate a proper list of table nodes for autosummary:: directive. *items* is a list produced by :meth:`get_items`. """ table_spec = addnodes.tabular_col_spec() table_spec['spec'] = 'p{0.5\linewidth}p{0.5\linewidth}' table = autosummary_table('') real_table = nodes.table('', classes=['longtable']) table.append(real_table) group = nodes.tgroup('', cols=2) real_table.append(group) group.append(nodes.colspec('', colwidth=10)) group.append(nodes.colspec('', colwidth=90)) body = nodes.tbody('') group.append(body) def append_row(*column_texts): row = nodes.row('') for text in column_texts: node = nodes.paragraph('') vl = ViewList() vl.append(text, '<autosummary>') self.state.nested_parse(vl, 0, node) try: if isinstance(node[0], nodes.paragraph): node = node[0] except IndexError: pass row.append(nodes.entry('', node)) body.append(row) for name, sig, summary, real_name in items: qualifier = 'obj' if 'nosignatures' not in self.options: col1 = ':%s:`%s <%s>`\ %s' % (qualifier, name, real_name, rst.escape(sig)) else: col1 = ':%s:`%s <%s>`' % (qualifier, name, real_name) col2 = summary append_row(col1, col2) return [table_spec, table]
def _get_row(self, obj): template = ":{}:`{} <{}>`\\ {}" if "nosignatures" in self.options: template = ":{}:`{} <{}>`" col1 = template.format("obj", obj.short_name, obj.name, escape("({})".format(obj.args))) col2 = obj.summary row = nodes.row("") for text in (col1, col2): node = nodes.paragraph("") view_list = ViewList() view_list.append(text, "<autosummary>") self.state.nested_parse(view_list, 0, node) try: if isinstance(node[0], nodes.paragraph): node = node[0] except IndexError: pass row.append(nodes.entry("", node)) return row
def _get_row(self, obj): template = ":{}:`{} <{}>`\\ {}" if "nosignatures" in self.options: template = ":{}:`{} <{}>`" col1 = template.format( "obj", obj.short_name, obj.name, escape("({})".format(obj.args)) ) col2 = obj.summary row = nodes.row("") for text in (col1, col2): node = nodes.paragraph("") view_list = ViewList() view_list.append(text, "<autosummary>") self.state.nested_parse(view_list, 0, node) try: if isinstance(node[0], nodes.paragraph): node = node[0] except IndexError: pass row.append(nodes.entry("", node)) return row
def get_table(self, items: List[Tuple[str, str, str, str, str]]) -> List[Node]: """Generate a proper list of table nodes for autosummary:: directive. *items* is a list produced by :meth:`get_items`. """ has_config_type = any([item[-1] is not None for item in items]) if has_config_type: n_cols = 3 else: n_cols = 2 table_spec = addnodes.tabular_col_spec() table_spec["spec"] = r"\X{1}{2}\X{1}{2}" table = autosummary_table("") real_table = nodes.table("", classes=["longtable"]) table.append(real_table) group = nodes.tgroup("", cols=n_cols) real_table.append(group) group.append(nodes.colspec("", colwidth=10)) if has_config_type: group.append(nodes.colspec("", colwidth=10)) group.append(nodes.colspec("", colwidth=90)) head = nodes.thead("") cols = ["Class/method name", "type", "Summary"] if not has_config_type: del cols[1] row = nodes.row("") source, line = self.state_machine.get_source_and_line() for text in cols: node = nodes.paragraph("") vl = StringList() vl.append(text, "%s:%d:<autosummary>" % (source, line)) with switch_source_input(self.state, vl): self.state.nested_parse(vl, 0, node) try: if isinstance(node[0], nodes.paragraph): node = node[0] except IndexError: pass row.append(nodes.entry("", node)) head.append(row) group.append(head) body = nodes.tbody("") group.append(body) def append_row(*column_texts: str) -> None: row = nodes.row("") source, line = self.state_machine.get_source_and_line() for text in column_texts: node = nodes.paragraph("") vl = StringList() vl.append(text, "%s:%d:<autosummary>" % (source, line)) with switch_source_input(self.state, vl): self.state.nested_parse(vl, 0, node) try: if isinstance(node[0], nodes.paragraph): node = node[0] except IndexError: pass row.append(nodes.entry("", node)) body.append(row) for name, sig, summary, real_name, config_type in items: qualifier = "obj" if "nosignatures" not in self.options: col1 = ":%s:`%s <%s>`\\ %s" % ( qualifier, name, real_name, rst.escape(sig), ) else: col1 = ":%s:`%s <%s>`" % (qualifier, name, real_name) col2 = summary if has_config_type: col3 = config_type if config_type else "" append_row(col1, col3, col2) else: append_row(col1, col2) return [table_spec, table]
def _return_formatter(return_): """Derive heads and tail from ``@returns`` blocks.""" tail = ('**%s** -- ' % rst.escape(return_.type)) if return_.type else '' tail += return_.description return ['returns'], tail
def test_escape(): assert escape(':ref:`id`') == r'\:ref\:\`id\`' assert escape('footnote [#]_') == r'footnote \[\#\]\_'
def test_escape(): assert escape(':ref:`id`') == '\:ref\:\`id\`' assert escape('footnote [#]_') == 'footnote \[\#\]\_'
def test_escape(): assert escape(':ref:`id`') == r'\:ref\:\`id\`' assert escape('footnote [#]_') == r'footnote \[\#\]\_' assert escape('sphinx.application') == r'sphinx.application' assert escape('.. toctree::') == r'\.. toctree\:\:'
def _formal_params(self, doclet): """Return the JS function or class params, looking first to any explicit params written into the directive and falling back to those in the JS code. Return a ReST-escaped string ready for substitution into the template. """ def format_default_according_to_type_hints(value, declared_types): """Return the default value for a param, formatted as a string ready to be used in a formal parameter list. JSDoc is a mess at extracting default values. It can unambiguously extract only a few simple types from the function signature, and ambiguity is even more rife when extracting from doclets. So we use any declared types to resolve the ambiguity. :arg value: The extracted value, which may be of the right or wrong type :arg declared_types: A list of types declared in the doclet for this param. For example ``{string|number}`` would yield ['string', 'number']. """ def first(list, default): try: return list[0] except IndexError: return default declared_type_implies_string = first(declared_types, '') == 'string' # If the first item of the type disjunction is "string", we treat # the default value as a string. Otherwise, we don't. So, if you # want your ambiguously documented default like ``@param # {string|Array} [foo=[]]`` to be treated as a string, make sure # "string" comes first. if isinstance(value, string_types ): # JSDoc threw it to us as a string in the JSON. if declared_types and not declared_type_implies_string: # It's a spurious string, like ``() => 5`` or a variable name. # Let it through verbatim. return value else: # It's a real string. return dumps(value) # Escape any contained quotes. else: # It came in as a non-string. if declared_type_implies_string: # It came in as an int, null, or bool, and we have to # convert it back to a string. return '"%s"' % (dumps(value), ) else: # It's fine as the type it is. return dumps(value) if self._explicit_formal_params: return self._explicit_formal_params # Harvest params from the @param tag unless they collide with an # explicit formal param. Even look at params that are really # documenting subproperties of formal params. Also handle param default # values. params = [] used_names = [] MARKER = object() for param in doclet.get('params', []): name = param['name'].split('.')[0] default = param.get('defaultvalue', MARKER) type = param.get('type', {'names': []}) # Add '...' to the parameter name if it's a variadic argument if param.get('variable'): name = '...' + name if name not in used_names: params.append( rst.escape(name) if default is MARKER else '%s=%s' % (rst.escape(name), rst.escape( format_default_according_to_type_hints( default, type['names'])))) used_names.append(name) # Use params from JS code if there are no documented params: if not params: params = [ rst.escape(p) for p in doclet['meta']['code'].get('paramnames', []) ] return '(%s)' % ', '.join(params)