def test_indent_type(self): sl = StringList(['', '', "hello", "world", '', '', '', "1234"]) assert sl.indent_type == '\t' with pytest.raises(ValueError, match="'type' cannot an empty string."): sl.indent_type = '' assert sl.indent_type == '\t' sl.indent_type = ' ' assert sl.indent_type == ' ' sl.set_indent_type('\t') assert sl.indent_type == '\t' sl.set_indent_type(' ') assert sl.indent_type == ' ' with pytest.raises(ValueError, match="'type' cannot an empty string."): sl.set_indent_type('') assert sl.indent_type == ' ' sl.set_indent_type() assert sl.indent_type == '\t'
def __repr__(self) -> str: buf = StringList() buf.indent_type = " " buf.append(f"{self.__class__.__module__}.{self.__class__.__qualname__}(") with buf.with_indent_size(1): for attrib in attr.fields(self.__class__): value = getattr(self, attrib.name) if isinstance(value, datetime): buf.append(f"{attrib.name}={value.isoformat()!r},") elif isinstance(value, str): lines = textwrap.wrap(value, width=80 - len(attrib.name) - 1) buf.append(f"{attrib.name}={lines.pop(0)!r}") for line in lines: buf.append(' ' * len(attrib.name) + ' ' + repr(line)) buf[-1] = f"{buf[-1][len(buf.indent_type) * buf.indent_size:]}," elif value is None: buf.append(f"{attrib.name}=None,") else: buf.append(f"{attrib.name}={prettyprinter.pformat(value)},") buf.append(')') return str(buf)
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 create_body_overloads(self) -> StringList: """ Create the overloaded implementations for insertion into to the body of the documenter's output. """ output = StringList() formatted_overloads = [] output.blankline() # output.append(":Overloaded Implementations:") output.append(":Overloads:") output.blankline() # Size varies depending on docutils config output.indent_type = ' ' output.indent_size = self.env.app.config.docutils_tab_width # type: ignore if self.analyzer and '.'.join(self.objpath) in self.analyzer.overloads: for overload in self.analyzer.overloads.get('.'.join(self.objpath)): # type: ignore overload = self.process_overload_signature(overload) buf = [format_annotation(self.object), r"\("] for name, param in overload.parameters.items(): buf.append(f"**{name}**") if param.annotation is not Parameter.empty: buf.append(r"\: ") buf.append(format_annotation(param.annotation)) if param.default is not Parameter.empty: buf.append(" = ") buf.append(param.default) buf.append(r"\, ") if buf[-2][-1] != '`': buf[-1] = r" )" else: buf[-1] = r")" if overload.return_annotation is not Parameter.empty: buf.append(" -> ") buf.append(format_annotation(overload.return_annotation)) formatted_overloads.append(''.join(buf)) if len(formatted_overloads) == 1: output.append(formatted_overloads[0]) else: for line in formatted_overloads: output.append(f"* {line}") output.blankline(ensure_single=True) return output return StringList()
def make_node_content( requirements: List[str], package_name: str, extra: str, scope: str = "module", ) -> str: """ Create the content of an extras_require node. :param requirements: List of additional :pep:`508` requirements. :param package_name: The name of the module/package on PyPI. :param extra: The name of the "extra". :param scope: The scope of the additional requirements, e.g. ``"module"``, ``"package"``. :return: The content of an extras_require node. """ content = StringList(convert_indents=True) content.indent_type = ' ' * 4 content.append( f"This {scope} has the following additional {_requirement(len(requirements))}:" ) content.blankline(ensure_single=True) with content.with_indent_size(content.indent_size + 1): content.append(".. code-block:: text") content.blankline(ensure_single=True) with content.with_indent_size(content.indent_size + 1): content.extend(requirements) content.blankline(ensure_single=True) if len(requirements) > 1: content.append("These can be installed as follows:") else: content.append("This can be installed as follows:") content.blankline(ensure_single=True) with content.with_indent_size(content.indent_size + 1): content.append(".. prompt:: bash") content.blankline(ensure_single=True) with content.with_indent_size(content.indent_size + 1): content.append(f"python -m pip install {package_name}[{extra}]") content.blankline(ensure_single=True) content.blankline() return str(content)
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 make_rest_example( options: Dict[str, Any], env: sphinx.environment.BuildEnvironment, content: Sequence[str], ) -> List[str]: """ Make the content of a reST Example node. :param options: :param content: The user-provided content of the directive. """ output = StringList(".. container:: rest-example") output.indent_type = ' ' * env.config.docutils_tab_width output.blankline() with output.with_indent_size(1): output.append(".. code-block:: rest") with output.with_indent_size(2): for option, value in options.items(): if value is None: output.append(f":{option}:") else: output.append(f":{option}: {value}") output.blankline() for line in content: output.append(line) output.blankline(ensure_single=True) for line in content: output.append(line) output.blankline(ensure_single=True) return list(output)
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 make_documentation(cls): """ Returns the reStructuredText documentation for the :class:`~.ConfigVar`. """ docstring = cls.__doc__ or '' docstring = (indent(dedent(docstring), tab)) if not docstring.startswith('\n'): docstring = '\n' + docstring buf = StringList() buf.indent_type = " " buf.blankline(ensure_single=True) buf.append(f".. conf:: {cls.__name__}") buf.append(docstring) buf.blankline() buf.indent_size += 1 buf.append(f"**Required**: {'yes' if cls.required else 'no'}") buf.blankline() buf.blankline() if not cls.required: if cls.default == []: buf.append("**Default**: [ ]") elif cls.default == {}: buf.append("**Default**: { }") elif isinstance(cls.default, Callable): # type: ignore buf.append( f"**Default**: The value of :conf:`{cls.default.__name__}`" ) elif isinstance(cls.default, bool): buf.append(f"**Default**: :py:obj:`{cls.default}`") elif isinstance(cls.default, str): if cls.default == '': buf.append("**Default**: <blank>") else: buf.append(f"**Default**: ``{cls.default}``") else: buf.append(f"**Default**: {cls.default}") buf.blankline() buf.blankline() buf.append(f"**Type**: {get_yaml_type(cls.dtype)}") if is_literal_type(cls.dtype): valid_values = ", ".join(f"``{x}``" for x in cls.dtype.__args__) buf.blankline() buf.blankline() buf.append(f"**Allowed values**: {valid_values}") elif hasattr(cls.dtype, "__args__") and is_literal_type( cls.dtype.__args__[0]): valid_values = ", ".join(f"``{x}``" for x in cls.dtype.__args__[0].__args__) buf.blankline() buf.blankline() buf.append(f"**Allowed values**: {valid_values}") buf.indent_size -= 1 return str(buf)
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 dump_sections(self, o, sup) -> Tuple[str, Mapping]: """ Serialise a dictionary into TOML sections. :param o: :param sup: """ retstr = '' if sup != '' and sup[-1] != '.': sup += '.' arraystr = StringList() arraystr.indent_type = ' ' * 4 retdict = self._dict() # type: ignore[attr-defined] for section in o: section = str(section) qsection = section if not _section_disallowed_re.match(section): qsection = _dump_str(section) if not isinstance(o[section], Mapping): arrayoftables = False if isinstance(o[section], Sequence): for a in o[section]: if isinstance(a, Mapping): arrayoftables = True if arrayoftables: for a in o[section]: arraytabstr = '\n' # if arraystr: arraystr.blankline(ensure_single=True) arraystr.append(f"[[{sup}{qsection}]]") s, d = self.dump_sections(a, sup + qsection) if s: if s[0] == '[': arraytabstr += s else: arraystr.append(s) while d: # pragma: no cover newd = self._dict() # type: ignore[attr-defined] for dsec in d: s1, d1 = self.dump_sections( d[dsec], f"{sup}{qsection}.{dsec}") if s1: arraytabstr += f'[{sup}{qsection}.{dsec}]\n{s1}' for s1 in d1: newd[f"{dsec}.{s1}"] = d1[s1] d = newd arraystr.append(arraytabstr) else: if o[section] is not None: retstr += f"{qsection} = {self.dump_value(o[section])}\n" elif self.preserve and isinstance(o[section], InlineTableDict): retstr += f"{qsection} = {self.dump_inline_table(o[section])}" else: retdict[qsection] = o[section] retstr += str(arraystr) return retstr.lstrip(), retdict
def run_html(self) -> List[nodes.Node]: """ Generate output for ``HTML`` builders. """ # colours = itertools.cycle(self.delimited_get("colours", "#6ab0de")) colours = itertools.cycle( get_random_sample(self.delimited_get("colours", "blue"))) classes = list( self.delimited_get( "class", "col-xl-6 col-lg-6 col-md-12 col-sm-12 col-xs-12 p-2")) content = StringList() content.append(".. panels::") content.indent_type = " " content.indent_size = 1 content.append(":container: container-xl pb-4 sphinx-highlights") content.blankline() 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]) colour_class = f"highlight-{next(colours)}" content.append( f":column: {DelimitedList((*classes, colour_class)): }") 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}>`") content.append('^' * len(content[-1])) content.blankline() # content.append(f".. function:: {name_parts[-1]} {stringify_signature(inspect.signature(obj))}") content.append(format_signature(obj)) content.blankline() content.append( inspect.cleandoc(obj.__doc__ or '').split("\n\n")[0]) content.blankline() content.append(f"See more in :mod:`{module.__name__}`.") content.append("---") content.pop(-1) targetid = f'sphinx-highlights-{self.env.new_serialno("sphinx-highlights"):d}' targetnode = nodes.target('', '', ids=[targetid]) view = ViewList(content) body_node = nodes.paragraph(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]