def run_generic(self) -> List[nodes.Node]: """ Generate generic reStructuredText output. """ content = StringList() content.indent_type = ' ' for obj_name in get_random_sample(sorted(set(self.content))): if self.options.get("module", '') and obj_name.startswith('.'): obj_name = obj_name.replace('.', f"{self.options['module']}.", 1) name_parts = obj_name.split('.') module = import_module('.'.join(name_parts[:-1])) obj = getattr(module, name_parts[-1]) if isinstance(obj, FunctionType): content.append( f"* :func:`{'.'.join(name_parts[1:])}() <.{obj_name}>`") elif isinstance(obj, type): content.append( f"* :class:`{'.'.join(name_parts[1:])} <.{obj_name}>`") else: content.append( f"* :py:obj:`{'.'.join(name_parts[1:])} <.{obj_name}>`") with content.with_indent_size(2): content.blankline() content.append(format_signature(obj)) content.blankline() content.append( inspect.cleandoc(obj.__doc__ or '').split("\n\n")[0]) content.blankline() targetid = f'sphinx-highlights-{self.env.new_serialno("sphinx-highlights"):d}' targetnode = nodes.target('', '', ids=[targetid]) view = ViewList(content) body_node = nodes.container(rawsource=str(content)) self.state.nested_parse(view, self.content_offset, body_node) # type: ignore sphinx_highlights_purger.add_node(self.env, body_node, targetnode, self.lineno) return [targetnode, body_node]
def format_signature(obj: Union[type, FunctionType]) -> StringList: """ Format the signature of the given object, for insertion into the highlight panel. :param obj: :return: A list of reStructuredText lines. """ with monkeypatcher(): obj.__annotations__ = get_type_hints(obj) signature: inspect.Signature = inspect.signature(obj) buf = StringList(".. parsed-literal::") buf.blankline() buf.indent_type = " " buf.indent_size = 1 if signature.return_annotation is not inspect.Signature.empty and not isinstance( obj, type): return_annotation = f") -> {format_annotation(signature.return_annotation)}" else: return_annotation = f")" total_length = len(obj.__name__) + len(return_annotation) arguments_buf: DelimitedList[str] = DelimitedList() param: inspect.Parameter for param in signature.parameters.values(): arguments_buf.append(f"{format_parameter(param)}") total_length += len(arguments_buf[-1]) if total_length <= 60: signature_buf = StringList(''.join( [f"{obj.__name__}(", f"{arguments_buf:, }", return_annotation])) else: signature_buf = StringList([f"{obj.__name__}("]) signature_buf.indent_type = " " with signature_buf.with_indent_size(1): signature_buf.extend( [f"{arguments_buf:,\n}" + ',', return_annotation]) buf.extend(signature_buf) return buf
def add_autosummary(self): """ Add the :rst:dir:`autosummary` table of this documenter. """ if not self.options.get("autosummary", False): return content = StringList() content.indent_type = ' ' * 4 sourcename = self.get_sourcename() grouped_documenters = self.get_grouped_documenters() for section, documenters in grouped_documenters.items(): if not self.options.get("autosummary-no-titles", False): content.append(f"**{section}:**") content.blankline(ensure_single=True) content.append(".. autosummary::") content.blankline(ensure_single=True) member_order = get_first_matching( lambda x: x != "groupwise", [ self.options.get("member-order", ''), self.env.config.autodocsumm_member_order, self.env.config.autodoc_member_order, ], default="alphabetical", ) with content.with_indent_size(content.indent_size + 1): for documenter, _ in self.sort_members(documenters, member_order): content.append(f"~{documenter.fullname}") content.blankline() for line in content: self.add_line(line, sourcename)
def rewrite_docs_index(repo_path: pathlib.Path, templates: Environment) -> List[str]: """ Update blocks in the documentation ``index.rst`` file. :param repo_path: Path to the repository root. :param templates: """ index_rst_file = PathPlus(repo_path / templates.globals["docs_dir"] / "index.rst") index_rst_file.parent.maybe_make() # Set up the blocks sb = ShieldsBlock( username=templates.globals["username"], repo_name=templates.globals["repo_name"], version=templates.globals["version"], conda=templates.globals["enable_conda"], tests=templates.globals["enable_tests"] and not templates.globals["stubs_package"], docs=templates.globals["enable_docs"], pypi_name=templates.globals["pypi_name"], docker_shields=templates.globals["docker_shields"], docker_name=templates.globals["docker_name"], platforms=templates.globals["platforms"], pre_commit=templates.globals["enable_pre_commit"], on_pypi=templates.globals["on_pypi"], primary_conda_channel=templates.globals["primary_conda_channel"], ) sb.set_docs_mode() make_out = sb.make() shield_block_list = StringList([*make_out[0:2], ".. only:: html"]) with shield_block_list.with_indent_size(1): shield_block_list.extend(make_out[1:-1]) shield_block_list.append(make_out[-1]) shields_block = str(shield_block_list) if templates.globals["license"] == "GNU General Public License v2 (GPLv2)": source = f"https://img.shields.io/github/license/{templates.globals['username']}/{templates.globals['repo_name']}" shields_block.replace( source, "https://img.shields.io/badge/license-GPLv2-orange") # .. image:: https://img.shields.io/badge/License-LGPL%20v3-blue.svg install_block = create_docs_install_block( templates.globals["repo_name"], templates.globals["username"], templates.globals["enable_conda"], templates.globals["on_pypi"], templates.globals["pypi_name"], templates.globals["conda_channels"], ) + '\n' links_block = create_docs_links_block( templates.globals["username"], templates.globals["repo_name"], ) # Do the replacement index_rst = index_rst_file.read_text(encoding="UTF-8") index_rst = shields_regex.sub(shields_block, index_rst) index_rst = installation_regex.sub(install_block, index_rst) index_rst = links_regex.sub(links_block, index_rst) index_rst = short_desc_regex.sub( ".. start short_desc\n\n.. documentation-summary::\n\t:meta:\n\n.. end short_desc", index_rst, ) if ":caption: Links" not in index_rst and not templates.globals[ "preserve_custom_theme"]: index_rst = index_rst.replace( ".. start links", '\n'.join([ ".. sidebar-links::", "\t:caption: Links", "\t:github:", (f" :pypi: {templates.globals['pypi_name']}" if templates.globals["on_pypi"] else ''), '', '', ".. start links", ])) index_rst_file.write_clean(index_rst) return [index_rst_file.relative_to(repo_path).as_posix()]
def walk_attrs(module: ModuleType, attr_name, converter=Converter()) -> str: """ Create stubs for given class, including all attributes. :param module: :param attr_name: :param converter: :return: """ buf = StringList(convert_indents=True) buf.indent_type = " " if not is_dunder(attr_name): obj = getattr(module, attr_name) # TODO: case where obj is not a class if not isinstance(obj, FunctionType): bases = [] for base in obj.__bases__: if base not in {System.Object, object}: if base.__name__ in converter.type_mapping: bases.append(converter.type_mapping[base.__name__]) else: bases.append(base.__name__) bases = list(filter(lambda x: x is Any, bases)) if bases: buf.append(f"class {attr_name}({', '.join(bases)}):\n") else: buf.append(f"class {attr_name}:\n") for child_attr_name in get_child_attrs(obj): try: child_obj = getattr(obj, child_attr_name) except TypeError as e: if str(e) in { "instance property must be accessed through a class instance", "property cannot be read", }: make_property(buf, child_attr_name) continue elif str( e ) == "instance attribute must be accessed through a class instance": print( f"{e.__class__.__name__}: '{e}' occurred for {attr_name}.{child_attr_name}" ) continue else: raise e # TODO: if isinstance(child_obj, FunctionType): return_type, arguments = get_signature(child_obj, child_attr_name, converter) with buf.with_indent_size(buf.indent_size + 1): if arguments is not None and arguments: signature = [] for idx, argument in enumerate(arguments.split(", ")): signature.append( f"{'_' * (idx + 1)}: {converter.convert_type(argument)}" ) line = f"def {child_attr_name}(self, {', '.join(signature)}) -> {return_type}: ..." if len(line) > 88: buf.blankline(ensure_single=True) buf.append(f"def {child_attr_name}(") with buf.with_indent_size(buf.indent_size + 2): buf.append("self,") for line in signature: buf.append(f"{line},") buf.append(f") -> {return_type}: ...\n") else: buf.append(line) elif arguments is None: buf.append( f"def {child_attr_name}(self, *args, **kwargs) -> {return_type}: ..." ) elif not arguments: # i.e. takes no arguments buf.append( f"def {child_attr_name}(self) -> {return_type}: ..." ) buf.blankline(ensure_single=True) return str(buf) return ''
def test_indent(self): sl = StringList() sl.set_indent_size(1) sl.append("Indented") assert sl == ["\tIndented"] sl.set_indent_type(" ") sl.append("Indented") assert sl == ["\tIndented", " Indented"] expected_list = [ "class Foo:", '', "\tdef bar(self, listicle: List[Item]):", "\t\t...", '', "\tdef __repr__(self) -> str:", '\t\treturn "Foo()"', '', ] expected_string = dedent("""\ class Foo: def bar(self, listicle: List[Item]): ... def __repr__(self) -> str: return "Foo()" """) sl = StringList() sl.append("class Foo:") sl.blankline(True) sl.set_indent_size(1) sl.append("def bar(self, listicle: List[Item]):") sl.indent_size += 1 sl.append("...") sl.indent_size -= 1 sl.blankline(True) sl.append("def __repr__(self) -> str:") sl.indent_size += 1 sl.append('return "Foo()"') sl.indent_size -= 1 sl.blankline(True) sl.set_indent_size(0) assert sl == expected_list assert str(sl) == expected_string assert sl == expected_string sl = StringList() sl.append("class Foo:") sl.blankline(True) with sl.with_indent('\t', 1): sl.append("def bar(self, listicle: List[Item]):") with sl.with_indent('\t', 2): sl.append("...") sl.blankline(True) sl.append("def __repr__(self) -> str:") with sl.with_indent('\t', 2): sl.append('return "Foo()"') sl.blankline(True) assert sl.indent_size == 0 assert sl == expected_list assert str(sl) == expected_string assert sl == expected_string sl = StringList() sl.append("class Foo:") sl.blankline(True) with sl.with_indent_size(1): sl.append("def bar(self, listicle: List[Item]):") with sl.with_indent_size(2): sl.append("...") sl.blankline(True) sl.append("def __repr__(self) -> str:") with sl.with_indent_size(2): sl.append('return "Foo()"') sl.blankline(True) assert sl.indent_size == 0 assert sl == expected_list assert str(sl) == expected_string assert sl == expected_string sl = StringList() sl.append("class Foo:") sl.set_indent(Indent(0, " ")) sl.blankline(True) with sl.with_indent_size(1): sl.append("def bar(self, listicle: List[Item]):") with sl.with_indent_size(2): sl.append("...") sl.blankline(True) sl.append("def __repr__(self) -> str:") with sl.with_indent_size(2): sl.append('return "Foo()"') sl.blankline(True) assert sl.indent_size == 0 assert sl == [x.expandtabs(4) for x in expected_list] assert str(sl) == expected_string.expandtabs(4) assert sl == expected_string.expandtabs(4) sl = StringList() sl.append("class Foo:") sl.set_indent(" ", 0) sl.blankline(True) with sl.with_indent_size(1): sl.append("def bar(self, listicle: List[Item]):") with sl.with_indent_size(2): sl.append("...") sl.blankline(True) sl.append("def __repr__(self) -> str:") with sl.with_indent_size(2): sl.append('return "Foo()"') sl.blankline(True) assert sl.indent_size == 0 assert sl == [x.expandtabs(4) for x in expected_list] assert str(sl) == expected_string.expandtabs(4) assert sl == expected_string.expandtabs(4) sl = StringList() sl.append("class Foo:") sl.blankline(True) with sl.with_indent_size(1): sl.append("def bar(self, listicle: List[Item]):") with sl.with_indent_size(2): sl.append("...") sl.blankline(True) sl.append("def __repr__(self) -> str:") with sl.with_indent_size(2): with sl.with_indent_type(" "): sl.append('return "Foo()"') sl.blankline(True) assert sl.indent_size == 0 expected_list[-2] = ' return "Foo()"' assert sl == expected_list assert str(sl) == expected_string.replace('\t\treturn "Foo()"', ' return "Foo()"') assert sl == expected_string.replace('\t\treturn "Foo()"', ' return "Foo()"')