def pformat_tabs( obj: object, width: int = 80, depth: Optional[int] = None, *, compact: bool = False, ) -> str: """ Format a Python object into a pretty-printed representation. Indentation is set at one tab. :param obj: The object to format. :param width: The maximum width of the output. :param depth: :param compact: """ prettyprinter = FancyPrinter(indent=4, width=width, depth=depth, compact=compact) buf = StringList() for line in prettyprinter.pformat(obj).splitlines(): buf.append(re.sub("^ {4}", r"\t", line)) return str(buf)
def test_copy(self): sl = StringList(['', '', "hello", "world", '', '', '', "1234"]) sl2 = sl.copy() assert sl == sl2 assert sl2 == ['', '', "hello", "world", '', '', '', "1234"] assert isinstance(sl2, StringList)
def test_set_indent_error(self): sl = StringList() with pytest.raises( TypeError, match= "'size' argument cannot be used when providing an 'Indent' object." ): sl.set_indent(Indent(0, " "), 5)
def test_extend(self): sl = StringList(['', '', "hello", "world", '', '', '', "1234"]) sl.extend(["\nfoo\nbar\n baz"]) assert sl == [ '', '', "hello", "world", '', '', '', "1234", '', "foo", "bar", " baz" ]
def requirement(requirement: str, file: Optional[str] = None) -> int: """ Add a requirement. """ # 3rd party from consolekit.utils import abort from domdf_python_tools.paths import PathPlus, traverse_to_file from domdf_python_tools.stringlist import StringList from packaging.requirements import InvalidRequirement from packaging.specifiers import SpecifierSet from shippinglabel import normalize_keep_dot from shippinglabel.requirements import ComparableRequirement, combine_requirements, read_requirements repo_dir: PathPlus = traverse_to_file(PathPlus.cwd(), "repo_helper.yml", "git_helper.yml") if file is None: requirements_file = repo_dir / "requirements.txt" if not requirements_file.is_file(): raise abort(f"'{file}' not found.") else: requirements_file = PathPlus(file) if not requirements_file.is_file(): raise abort("'requirements.txt' not found.") try: req = ComparableRequirement(requirement) except InvalidRequirement as e: raise BadRequirement(requirement, e) response = (PYPI_API / req.name / "json/").get() if response.status_code != 200: raise click.BadParameter(f"No such project {req.name}") else: req.name = normalize(response.json()["info"]["name"]) if not req.specifier: req.specifier = SpecifierSet( f">={response.json()['info']['version']}") click.echo(f"Adding requirement '{req}'") requirements, comments, invalid_lines = read_requirements( req_file=requirements_file, include_invalid=True, normalize_func=normalize_keep_dot, ) requirements.add(req) buf = StringList([*comments, *invalid_lines]) buf.extend(str(req) for req in sorted(combine_requirements(requirements))) requirements_file.write_lines(buf) return 0
def sort_requirements(filename: PathLike, allow_git: bool = False) -> int: """ Sort the requirements in the given file alphabetically. :param filename: The file to sort the requirements in. :param allow_git: Whether to allow lines that start with ``git+``, which are allowed by pip but not :pep:`508`. """ ret = PASS filename = PathPlus(filename) comments: List[str] requirements: Set[ComparableRequirement] git_lines: List[str] = [] requirements, comments, invalid_lines = read_requirements( req_file=filename, include_invalid=True, normalize_func=normalize_keep_dot, ) for line in invalid_lines: if line.startswith("git+") and allow_git: git_lines.append(line) else: ret |= FAIL # find and remove pkg-resources==0.0.0 # which is automatically added by broken pip package under Debian if ComparableRequirement("pkg-resources==0.0.0") in requirements: requirements.remove(ComparableRequirement("pkg-resources==0.0.0")) ret |= FAIL sorted_requirements = sorted(requirements) buf = StringList( [*comments, *git_lines, *[str(req) for req in sorted_requirements]]) buf.blankline(ensure_single=True) if (requirements != sorted_requirements and buf != filename.read_lines()) or ret: print('\n'.join(buf)) # print(coloured_diff( # filename.read_lines(), # buf, # str(filename), # str(filename), # "(original)", # "(sorted)", # lineterm='', # )) ret |= FAIL filename.write_lines(buf) return ret
def clean_writer(string: str, fp: IO) -> None: """ Write string to ``fp`` without trailing spaces. :param string: :param fp: """ # this package from domdf_python_tools.stringlist import StringList buffer = StringList(string) buffer.blankline(ensure_single=True) fp.write(str(buffer))
def run(self) -> bool: """ Run the reformatter. :return: Whether the file was changed. """ hooks = parse_hooks(self.config) reformatted_source = StringList(call_hooks(hooks, self._unformatted_source, self.filename)) reformatted_source.blankline(ensure_single=True) self._reformatted_source = str(reformatted_source) return self._reformatted_source != self._unformatted_source
def configure(app: Sphinx, config: Config): """ Configure Sphinx Extension. :param app: The Sphinx application. :param config: """ latex_elements = getattr(config, "latex_elements", {}) latex_extrapackages = StringList(latex_elements.get("extrapackages", '')) latex_extrapackages.append(r"\usepackage{needspace}") latex_elements["extrapackages"] = str(latex_extrapackages) config.latex_elements = latex_elements # type: ignore
def __init__(self, base_path: pathlib.Path): self.base_path = base_path self._ini = ConfigUpdater() self._output = StringList([ f"# {self.managed_message}", "# You may add new sections, but any changes made to the following sections will be lost:", ]) self.managed_sections = self.managed_sections[:] for sec in self.managed_sections: self._ini.add_section(sec) self._output.append(f"# * {sec}") self._output.blankline(ensure_single=True)
def copy_assets(app: Sphinx, exception: Optional[Exception] = None) -> None: """ Copy asset files to the output. :param app: The Sphinx application. :param exception: Any exception which occurred and caused Sphinx to abort. """ if exception: # pragma: no cover return style = StringList([ ".docutils.container {", " padding-left: 0 !important;", " padding-right: 0 !important;", '}', '', # "div.sphinx-tabs.docutils.container {", # " padding-left: 0 !important;", # " padding-right: 0 !important;", # "}", # '', "div.ui.top.attached.tabular.menu.sphinx-menu.docutils.container {", # " padding-left: 0 !important;", # " padding-right: 0 !important;", " margin-left: 0 !important;", " margin-right: 0 !important;", '}', ]) css_dir = PathPlus(app.builder.outdir) / "_static" / "css" css_dir.maybe_make(parents=True) css_file = css_dir / "tabs_customise.css" css_file.write_lines(style)
def validate_config(app: Sphinx, config: Config): r""" Validate the provided configuration values. :param app: The Sphinx app. :param config: """ rst_prolog: Union[str, StringList] = config.rst_prolog or '' nbsp_sub = ".. |nbsp| unicode:: 0xA0\n :trim:" if nbsp_sub not in rst_prolog: rst_prolog = StringList(rst_prolog) rst_prolog.append(nbsp_sub) config.rst_prolog = str(rst_prolog) # type: ignore
def test_prompt(capsys, monkeypatch, data_regression: DataRegressionFixture): inputs = iter([ '', '', '', '', "24", "Bond007", "badpassword", "baspassword", "badpassword", "badpassword", "badpassword", "badpassword", ]) def fake_input(prompt): value = next(inputs) print(f"{prompt}{value}".rstrip()) return value monkeypatch.setattr(click.termui, "visible_prompt_func", fake_input) assert prompt(text="What is your age", prompt_suffix="? ", type=click.INT) == 24 assert prompt(text="Username", type=click.STRING) == "Bond007" assert prompt(text="Password", type=click.STRING, confirmation_prompt=True) == "badpassword" assert prompt( text="Password", type=click.STRING, confirmation_prompt="Are you sure about that? ", ) == "badpassword" data_regression.check(list(StringList(capsys.readouterr().out.splitlines())))
def check(self, page: BeautifulSoup, **kwargs): # type: ignore r""" Check an HTML page generated by Sphinx for regressions, using `pytest-regressions <https://pypi.org/project/pytest-regressions/>`_ :param page: The page to test. :param \*\*kwargs: Additional keyword arguments passed to :meth:`pytest_regressions.file_regression.FileRegressionFixture.check`. **Example usage** .. code-block:: python @pytest.mark.parametrize("page", ["index.html"], indirect=True) def test_page(page: BeautifulSoup, html_regression: HTMLRegressionFixture): html_regression.check(page, file_regression) """ # noqa D400 __tracebackhide__ = True page = remove_html_footer(page) page = remove_html_link_tags(page) for div in page.select("script"): if "_static/language_data.js" in str(div): div.extract() kwargs.pop("encoding", None) kwargs.pop("extension", None) super().check( str(StringList(page.prettify())), encoding="UTF-8", extension=".html", )
def check_html_regression(page: BeautifulSoup, file_regression: FileRegressionFixture): """ Check an HTML page generated by Sphinx for regressions, using `pytest-regressions <https://pypi.org/project/pytest-regressions/>`_ :param page: The page to test. :param file_regression: The file regression fixture. **Example usage** .. code-block:: python @pytest.mark.parametrize("page", ["index.html"], indirect=True) def test_page(page: BeautifulSoup, file_regression: FileRegressionFixture): check_html_regression(page, file_regression) """ # noqa D400 __tracebackhide__ = True page = remove_html_footer(page) page = remove_html_link_tags(page) for div in page.select("script"): if "_static/language_data.js" in str(div): div.extract() check_file_regression( StringList(page.prettify()), file_regression, extension=".html", )
def test_sort(self): sl = StringList(['', '', "hello", "world", '', '', '', "1234"]) sl.sort() assert sl == ['', '', '', '', '', "1234", "hello", "world"] assert isinstance(sl, StringList) sl = StringList(['', '', "hello", "world", '', '', '', "1234"]) sl.sort(reverse=True) assert sl == ["world", "hello", "1234", '', '', '', '', ''] assert isinstance(sl, StringList)
def dump_list(self, v) -> str: """ Serialize a list to TOML. :param v: :rtype: .. latex:clearpage:: """ single_line = super().dump_list(v) if len(single_line) <= self.max_width: return single_line retval = StringList(['[']) with retval.with_indent(" ", 1): for u in v: retval.append(f"{str(self.dump_value(u))},") retval.append(']') return str(retval)
def test_make_style(): style: css_parser.css.CSSStyleRule = make_style( "li p:last-child", {"max-width": (rem(1200), IMPORTANT)}) assert str(style.selectorText) == "li p:last-child" assert StringList(style.cssText) == [ "li p:last-child {", " max-width: 1200rem !important", " }", ] serializer = CSSSerializer(trailing_semicolon=True) with serializer.use(): assert StringList(style.cssText) == [ "li p:last-child {", "\tmax-width: 1200rem !important;", '}', ]
def get_linux_ci_requirements(self) -> List[str]: """ Returns the Python requirements to run tests for on Linux. """ dependency_lines = StringList( self.templates.globals["github_ci_requirements"]["Linux"]["pre"]) dependency_lines.extend(self.standard_python_install_lines) if self.templates.globals["enable_tests"]: dependency_lines.append( "python -m pip install --upgrade coverage_pyver_pragma") dependency_lines.extend(self._get_additional_requirements()) dependency_lines.extend( self.templates.globals["github_ci_requirements"]["Linux"]["post"]) return dependency_lines
def test_creation(self): assert not StringList() assert not StringList([]) assert not StringList(()) assert StringList([1]) == ['1'] assert StringList(['1']) == ['1'] assert StringList('1') == ['1'] assert StringList("1\n") == ['1', ''] with pytest.raises(TypeError, match="'int' object is not iterable"): StringList(1) # type: ignore
def test_negative_getitem(self): sl = StringList(['', '', "hello", "world", '', '', "abc", "1234"]) assert sl[-1] == "1234" sl[-1] += "5678" assert sl == ['', '', "hello", "world", '', '', "abc", "12345678"] assert sl[-2] == "abc" sl[-2] += "def" assert sl == ['', '', "hello", "world", '', '', "abcdef", "12345678"]
def test_latex_output(app, latex_regression: LaTeXRegressionFixture): assert app.builder.name.lower() == "latex" with pytest.warns(UserWarning, match="(No codes specified|No such code 'F401')"): app.build() output_file = PathPlus(app.outdir / "python.tex") latex_regression.check(StringList(output_file.read_lines()), jinja2=True)
def check_modules(self) -> Iterator[Tuple[str, int]]: """ Checks modules can be imported. :returns: An iterator of 2-element tuples comprising the name of the module and the import status: 0. The module was imported successfully. 1. The module could not be imported. If :attr:`~.show` is :py:obj:`True` the traceback will be shown. """ longest_name = 15 echo = functools.partial(click.echo, color=resolve_color_default(self.colour)) if self.modules: longest_name += max(map(len, self.modules)) else: return for module_name in self.modules: echo(Style.BRIGHT(f"Checking {module_name!r}".ljust( longest_name, '.')), nl=False) ret = check_module(module_name, combine_output=True) if ret: echo(Back.RED("Failed")) self.stats["failed"] += 1 # pylint: disable=loop-invariant-statement if self.show: echo(Style.BRIGHT("Captured output:")) stdout = StringList(ret.stdout) stdout.blankline(ensure_single=True) echo(stdout) yield module_name, 1 else: echo(Back.GREEN("Passed")) self.stats["passed"] += 1 # pylint: disable=loop-invariant-statement yield module_name, 0
def test_check_file_regression(tmp_pathplus: PathPlus, file_regression: FileRegressionFixture): with pytest.raises(FileNotFoundError, match=no_such_file_pattern): check_file_output(tmp_pathplus / "file.txt", file_regression) check_file_regression("Success!\n\nThis is a test.", file_regression) result = StringList("Success!") result.blankline() result.blankline(ensure_single=True) result.append("This is a test.") check_file_regression(result, file_regression)
def test_isort_stubs(advanced_file_regression: AdvancedFileRegressionFixture): source = StringList([ 'from natsort.natsort import as_ascii as as_ascii, as_utf8 as as_utf8, decoder as decoder, humansorted as ' 'humansorted, index_humansorted as index_humansorted, index_natsorted as index_natsorted, index_realsorted as ' 'index_realsorted, natsort_key as natsort_key, natsort_keygen as natsort_keygen, natsorted as natsorted, ns as ns, ' 'numeric_regex_chooser as numeric_regex_chooser, order_by_index as order_by_index, os_sort_key as os_sort_key, ' 'os_sort_keygen as os_sort_keygen, os_sorted as os_sorted, realsorted as realsorted', "from natsort.utils import chain_functions as chain_functions", '', ]) advanced_file_regression.check(isort_hook(str(source), "utils.pyi"))
def test_prompt_abort(capsys, monkeypatch, data_regression: DataRegressionFixture, exception): def fake_input(prompt): print(f"{prompt}", end='') raise exception monkeypatch.setattr(click.termui, "visible_prompt_func", fake_input) with pytest.raises(click.Abort, match="^$"): prompt(text="Password", type=click.STRING, confirmation_prompt=True) assert list(StringList(capsys.readouterr().out.splitlines())) == ["Password:"]
def test_generics_functions( advanced_file_regression: AdvancedFileRegressionFixture): code = StringList([ "def foo():", "\tdata: Tuple[int, int, str, float, str, int, bytes, int, int, str, float, str, int, bytes, int, int, str, float, str, int, bytes] = ()", '', "async def acync_foo():", "\tdata: Tuple[int, int, str, float, str, int, bytes, int, int, str, float, str, int, bytes, int, int, str, float, str, int, bytes] = ()", ]) advanced_file_regression.check(reformat_generics(str(code)), extension="._py")
def test_latex_output(app, advanced_file_regression: AdvancedFileRegressionFixture): random.seed("5678") assert app.builder.name.lower() == "latex" app.build() output_file = PathPlus(app.outdir / "sphinx-highlights-demo.tex") content = str(StringList(output_file.read_lines())).replace("\\sphinxAtStartPar\n", '') advanced_file_regression.check( re.sub(r"\\date{.*}", r"\\date{Mar 11, 2021}", content), extension=".tex", )
def run(self) -> bool: """ Run the reformatter. :return: Whether the file was changed. """ quote_formatted_code = reformat_quotes(self._unformatted_source) yapfed_code = FormatCode(quote_formatted_code, style_config=self.yapf_style)[0] generic_formatted_code = reformat_generics(yapfed_code) # TODO: support spaces try: isorted_code = StringList( isort.code(generic_formatted_code, config=self.isort_config)) except FileSkipComment: isorted_code = StringList(generic_formatted_code) isorted_code.blankline(ensure_single=True) self._reformatted_source = str(isorted_code) # Fix for noqa comments being pushed to new line self._reformatted_source = noqa_reformat(self._reformatted_source) return self._reformatted_source != self._unformatted_source
def document_keys(self, keys: List[str], types: Dict[str, Type], docstrings: Dict[str, List[str]]): """ Document keys in a :class:`typing.TypedDict`. :param keys: List of key names to document. :param types: Mapping of key names to types. :param docstrings: Mapping of key names to docstrings. """ content = StringList() for key in keys: if key in types: key_type = f"({format_annotation(types[key])}) " else: key_type = '' if key in docstrings: content.append( f" * **{key}** {key_type}-- {' '.join(docstrings.get(key, ''))}" ) else: content.append(f" * **{key}** {key_type}") for line in content: self.add_line(line, self.get_sourcename())