def replace_emoji(app: Sphinx, exception: Optional[Exception] = None):
    if exception:
        return

    if app.builder.name.lower() != "latex":
        return

    output_file = PathPlus(
        app.builder.outdir) / f"{app.builder.titles[0][1]}.tex"

    output_content = output_file.read_text()

    # Documentation summary emoji
    output_content = output_content.replace(" 🐍 🛠️", '')
    output_content = output_content.replace('🐍', '')
    output_content = output_content.replace('🛠', '')
    output_content = output_content.replace('️', '')  # Variation Selector-16

    output_content = output_content.replace(
        '≈', r" $\approx$ ")  # coming in sphinx-toolbox 2.12
    output_content = output_content.replace(
        'μ', r"\textmu ")  # fixed in sphinx-toolbox 2.12
    output_content = output_content.replace(
        r"\textmum", r"\textmu m")  # fixed in sphinx-toolbox 2.12
    output_content = output_content.replace(
        '\u205f', r"\medspace ")  # medium mathematical space

    # in words.py
    output_content = output_content.replace(r'A\sphinxhyphen{}Ω',
                                            r"A\sphinxhyphen{}\textOmega")
    output_content = output_content.replace(
        r'α\sphinxhyphen{}ϖ', r"\textalpha\sphinxhyphen{}\textomega")

    output_file.write_clean(output_content)
Esempio n. 2
0
def rewrite_readme(repo_path: pathlib.Path,
                   templates: Environment) -> List[str]:
    """
	Update blocks in the ``README.rst`` file.

	:param repo_path: Path to the repository root.
	:param templates:
	"""

    # TODO: link to documentation below installation

    readme_file = PathPlus(repo_path / "README.rst")

    shields_block = 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"],
        docs_url=templates.globals["docs_url"],
        primary_conda_channel=templates.globals["primary_conda_channel"],
    ).make()

    if templates.globals["on_pypi"]:
        install_block = create_readme_install_block(
            templates.globals["modname"],
            templates.globals["username"],
            templates.globals["enable_conda"],
            templates.globals["on_pypi"],
            templates.globals["pypi_name"],
            templates.globals["conda_channels"],
        )
    else:
        install_block = get_readme_installation_block_no_pypi_template(
        ).render(
            modname=templates.globals["modname"],
            username=templates.globals["username"],
            repo_name=templates.globals["repo_name"],
        )

    readme = readme_file.read_text(encoding="UTF-8")
    readme = shields_regex.sub(str(shields_block), readme)
    readme = installation_regex.sub(install_block + '\n', readme)
    short_desc_block = create_short_desc_block(
        templates.globals["short_desc"], )
    readme = short_desc_regex.sub(short_desc_block, readme)

    readme_file.write_clean(readme)

    return [readme_file.name]
Esempio n. 3
0
def replace_unknown_unicode(app: Sphinx,
                            exception: Optional[Exception] = None):
    r"""
	Replaces certain unknown unicode characters in the Sphinx LaTeX output with the best equivalents.

	.. only:: html

		The mapping is as follows:

		* ♠ -- \spadesuit
		* ♥ -- \heartsuit
		* ♦ -- \diamondsuit
		* ♣ -- \clubsuit
		* Zero width space -- \hspace{0pt}
		* μ -- \textmu
		* ≡ -- \equiv (new in version 2.11.0)
		* ≈ -- \approx (new in version 2.12.0)
		* ≥ -- \geq (new in version 2.13.0)
		* ≤ -- \leq (new in version 2.13.0)

	This function can be hooked into the :event:`build-finished` event as follows:

	.. code-block:: python

		app.connect("build-finished", replace_unknown_unicode)

	.. versionadded:: 2.9.0

	:param app: The Sphinx application.
	:param exception: Any exception which occurred and caused Sphinx to abort.
	"""

    if exception:  # pragma: no cover
        return

    if app.builder is None or app.builder.name.lower() != "latex":
        return

    builder = cast(LaTeXBuilder, app.builder)
    output_file = PathPlus(
        builder.outdir) / f"{builder.titles[0][1].lower()}.tex"

    output_content = output_file.read_text()

    output_content = output_content.replace('♠', r' $\spadesuit$ ')
    output_content = output_content.replace('♥', r' $\heartsuit$ ')
    output_content = output_content.replace('♦', r' $\diamondsuit$ ')
    output_content = output_content.replace('♣', r' $\clubsuit$ ')
    output_content = output_content.replace(
        '\u200b', r'\hspace{0pt}')  # Zero width space
    output_content = output_content.replace('μ', r"\textmu{}")
    output_content = output_content.replace('≡', r" $\equiv$ ")
    output_content = output_content.replace('≈', r" $\approx$ ")
    output_content = output_content.replace('≥', r" $\geq$ ")
    output_content = output_content.replace('≤', r" $\leq$ ")

    output_file.write_clean(output_content)
def replace_geq(app: Sphinx, exception: Optional[Exception] = None):
    if exception:
        return

    if app.builder.name.lower() != "latex":
        return

    output_file = PathPlus(
        app.builder.outdir) / f"{app.builder.titles[0][1]}.tex"
    output_content = output_file.read_text()
    output_content = output_content.replace('≥', r" $\geq$ ")
    output_file.write_clean(output_content)
Esempio n. 5
0
def replace_unicode(app, exception):
    if exception:
        return

    if app.builder.name.lower() != "latex":
        return

    output_file = PathPlus(
        app.builder.outdir) / f"{app.builder.titles[0][1]}.tex"

    output_content = output_file.read_text()
    output_content = output_content.replace('➞', r" $\rightarrow$ ")
    output_file.write_clean(output_content)
Esempio n. 6
0
        def check_file(filename: PathPlus, extension: Optional[str] = None):

            data = filename.read_text(encoding="UTF-8")

            assert expected_version in data

            extension = extension or filename.suffix

            if extension == ".py":
                extension = "._py_"

            return check_file_regression(data, advanced_file_regression,
                                         extension)
Esempio n. 7
0
    def check_fn(obtained_filename, expected_filename):
        print(obtained_filename, expected_filename)
        expected_filename = PathPlus(expected_filename)
        template = Template(expected_filename.read_text())

        expected_filename.write_text(
            template.render(
                sphinx_version=sphinx.version_info,
                python_version=sys.version_info,
            ))

        return check_text_files(obtained_filename,
                                expected_filename,
                                encoding="UTF-8")
Esempio n. 8
0
            def check_fn(obtained_filename, expected_filename):
                __tracebackhide__ = True

                expected_filename = PathPlus(expected_filename)
                template = Template(expected_filename.read_text())

                expected_filename.write_text(
                    template.render(
                        sphinx_version=sphinx.version_info,
                        python_version=sys.version_info,
                        docutils_version=docutils_version,
                        **jinja2_namespace or {},
                    ))

                return check_text_files(obtained_filename,
                                        expected_filename,
                                        encoding="UTF-8")
Esempio n. 9
0
def replace_environment_variables_header(app: Sphinx, exception: Optional[Exception] = None):
	if exception:
		return

	if app.builder.name.lower() != "latex":
		return

	output_file = PathPlus(app.builder.outdir) / f"{app.builder.titles[0][1]}.tex"

	output_content = output_file.read_text()

	output_content = output_content.replace(
			r"\subsubsection*{Environment variables}",
			r"\vspace{20px}{\textcolor{TitleColor}{\sffamily\bfseries Environment variables}}",
			)

	output_file.write_clean(output_content)
Esempio n. 10
0
def replace_latex(app: Sphinx, exception: Optional[Exception] = None):
	if exception:
		return

	if app.builder.name.lower() != "latex":
		return

	output_file = PathPlus(app.builder.outdir) / f"{app.builder.titles[0][1]}.tex"

	output_content = output_file.read_text()

	output_content = output_content.replace('≡', r"$\equiv$")
	output_content = output_content.replace(
			r"@\spxentry{verify}\spxextra{SlumberURL attribute}}\needspace{5\baselineskip}",
			r"@\spxentry{verify}\spxextra{SlumberURL attribute}}",
			)

	output_file.write_clean(output_content)
Esempio n. 11
0
def demo_environment(tmp_pathplus):

    example_formate_toml = PathPlus(__file__).parent / "example_formate.toml"
    (tmp_pathplus / "formate.toml").write_text(
        example_formate_toml.read_text())

    code = [
        "class F:",
        "\tfrom collections import (",
        "Iterable,",
        "\tCounter,",
        "\t\t)",
        '',
        "\tdef foo(self):",
        "\t\tpass",
        '',
        "print('hello world')",
    ]

    (tmp_pathplus / "code.py").write_lines(code, trailing_whitespace=True)
Esempio n. 12
0
def replace_emoji(app: Sphinx, exception: Optional[Exception] = None):
    if exception:
        return

    if app.builder.name.lower() != "latex":
        return

    output_file = PathPlus(
        app.builder.outdir) / f"{app.builder.titles[0][1]}.tex"

    output_content = output_file.read_text()

    output_content = output_content.replace('🧰', '')
    output_content = output_content.replace('📔', '')
    output_content = output_content.replace(
        r"\sphinxcode{\sphinxupquote{\textbackslash{}vspace\{\}}}",
        r"\mbox{\sphinxcode{\sphinxupquote{\textbackslash{}vspace\{\}}}}",
    )

    output_file.write_clean(output_content)
Esempio n. 13
0
class Reformatter:
	"""
	Reformat a Python source file.

	:param filename: The filename to reformat.
	:param config: The ``formate`` configuration, parsed from a TOML file (or similar).

	.. autosummary-widths:: 5/16 11/16
	"""

	#: The filename being reformatted.
	filename: str

	#: The filename being reformatted, as a POSIX-style path.
	file_to_format: PathPlus

	#: The ``formate`` configuration, parsed from a TOML file (or similar).
	config: FormateConfigDict

	def __init__(self, filename: PathLike, config: FormateConfigDict):
		self.file_to_format = PathPlus(filename)
		self.filename = self.file_to_format.as_posix()
		self.config = config
		self._unformatted_source = self.file_to_format.read_text()
		self._reformatted_source: Optional[str] = None

	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 get_diff(self) -> str:
		"""
		Returns the diff between the original and reformatted file content.
		"""

		# Based on yapf
		# Apache 2.0 License

		after = self.to_string().split('\n')
		before = self._unformatted_source.split('\n')
		return coloured_diff(
				before,
				after,
				self.filename,
				self.filename,
				"(original)",
				"(reformatted)",
				lineterm='',
				)

	def to_string(self) -> str:
		"""
		Return the reformatted file as a string.

		:rtype:

		.. latex:clearpage::
		"""

		if self._reformatted_source is None:
			raise ValueError("'Reformatter.run()' must be called first!")

		return self._reformatted_source

	def to_file(self) -> None:
		"""
		Write the reformatted source to the original file.
		"""

		self.file_to_format.write_text(self.to_string())
Esempio n. 14
0
 def test_append_pathplus(self):
     file = PathPlus("paths_append_test_file.txt")
     file.write_text("initial content\n")
     file.append_text("appended content")
     assert file.read_text() == "initial content\nappended content"
     file.unlink()
Esempio n. 15
0
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()]
Esempio n. 16
0
def make_pre_commit(repo_path: pathlib.Path,
                    templates: Environment) -> List[str]:
    """
	Add configuration for ``pre-commit``.

	https://github.com/pre-commit/pre-commit

	# See https://pre-commit.com for more information
	# See https://pre-commit.com/hooks.html for more hooks

	:param repo_path: Path to the repository root.
	:param templates:
	"""

    docs_dir = templates.globals["docs_dir"]
    import_name = templates.globals["import_name"]
    stubs_package = templates.globals["stubs_package"]

    non_source_files = [
        posixpath.join(docs_dir, "conf"), "__pkginfo__", "setup"
    ]

    domdfcoding_hooks = Repo(
        repo=make_github_url("domdfcoding", "pre-commit-hooks"),
        rev="v0.3.0",
        hooks=[
            {
                "id": "requirements-txt-sorter",
                "args": ["--allow-git"]
            },
            {
                "id":
                "check-docstring-first",
                "exclude":
                fr"^({'|'.join(non_source_files)}|{templates.globals['tests_dir']}/.*)\.py$"
            },
            "bind-requirements",
        ])

    flake8_dunder_all = Repo(
        repo=make_github_url("domdfcoding", "flake8-dunder-all"),
        rev="v0.1.8",
        hooks=[{
            "id":
            "ensure-dunder-all",
            "files":
            fr"^{import_name}{'-stubs' if stubs_package else ''}/.*\.py$"
        }])

    snippet_fmt = Repo(
        repo=make_github_url("python-formate", "snippet-fmt"),
        rev="v0.1.4",
        hooks=["snippet-fmt"],
    )

    formate_excludes = fr"^({'|'.join([*templates.globals['yapf_exclude'], *non_source_files])})\.(_)?py$"

    formate = Repo(
        repo=make_github_url("python-formate", "formate"),
        rev="v0.4.9",
        hooks=[{
            "id": "formate",
            "exclude": formate_excludes
        }],
    )

    dep_checker_args = [templates.globals["import_name"].replace('.', '/')]

    if templates.globals["source_dir"]:
        dep_checker_args.extend(
            ["--work-dir", templates.globals["source_dir"]])

    dep_checker = Repo(repo=make_github_url("domdfcoding", "dep_checker"),
                       rev="v0.6.2",
                       hooks=[{
                           "id": "dep_checker",
                           "args": dep_checker_args
                       }])

    pre_commit_file = PathPlus(repo_path / ".pre-commit-config.yaml")

    if not pre_commit_file.is_file():
        pre_commit_file.touch()

    dumper = ruamel.yaml.YAML()
    dumper.indent(mapping=2, sequence=3, offset=1)

    output = StringList([
        f"# {templates.globals['managed_message']}",
        "---",
        '',
        f"exclude: {templates.globals['pre_commit_exclude']}",
        '',
        "repos:",
    ])

    indent_re = re.compile("^ {3}")

    managed_hooks = [
        pyproject_parser,
        pre_commit_hooks,
        domdfcoding_hooks,
        flake8_dunder_all,
        flake2lint,
        pygrep_hooks,
        pyupgrade,
        lucas_c_hooks,
        snippet_fmt,
        formate,
    ]

    if not templates.globals["stubs_package"]:
        managed_hooks.append(dep_checker)

    managed_hooks_urls = [str(hook.repo) for hook in managed_hooks]

    custom_hooks_comment = "# Custom hooks can be added below this comment"

    for hook in managed_hooks:
        buf = StringIO()
        dumper.dump(hook.to_dict(), buf)
        output.append(indent_re.sub(" - ", indent(buf.getvalue(), "   ")))
        output.blankline(ensure_single=True)
    output.append(custom_hooks_comment)
    output.blankline(ensure_single=True)

    raw_yaml = pre_commit_file.read_text()

    if custom_hooks_comment in raw_yaml:
        custom_hooks_yaml = pre_commit_file.read_text().split(
            custom_hooks_comment)[1]

        custom_hooks = []
        local_hooks = []

        for repo in yaml_safe_loader.load(custom_hooks_yaml) or []:
            if repo["repo"] == "local":
                local_hooks.append(repo)

            elif repo["repo"] not in managed_hooks_urls:
                custom_hooks.append(Repo(**repo))

        for hook in custom_hooks:
            buf = StringIO()
            dumper.dump(hook.to_dict(), buf)
            output.append(indent_re.sub(" - ", indent(buf.getvalue(), "   ")))
            output.blankline(ensure_single=True)

        for hook in local_hooks:
            buf = StringIO()
            dumper.dump(hook, buf)
            output.append(indent_re.sub(" - ", indent(buf.getvalue(), "   ")))
            output.blankline(ensure_single=True)

    pre_commit_file.write_lines(output)

    return [pre_commit_file.name]
Esempio n. 17
0
class Reformatter:
    """
	Reformat a Python source file.

	:param filename:
	:param yapf_style: The name of the yapf style, or the path to the yapf style file.
	:param isort_config: The filename of the isort configuration file.
	"""
    def __init__(self, filename: PathLike, yapf_style: str,
                 isort_config: Config):
        self.file_to_format = PathPlus(filename)
        self.filename = self.file_to_format.as_posix()
        self.yapf_style = yapf_style
        self.isort_config = isort_config
        self._unformatted_source = self.file_to_format.read_text()
        self._reformatted_source: Optional[str] = None

    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 get_diff(self) -> str:
        """
		Returns the diff between the original and reformatted file content.
		"""

        # Based on yapf
        # Apache 2.0 License

        if self._reformatted_source is None:
            raise ValueError("'Reformatter.run()' must be called first!")

        before = self._unformatted_source.splitlines()
        after = self._reformatted_source.splitlines()
        return coloured_diff(
            before,
            after,
            self.filename,
            self.filename,
            "(original)",
            "(reformatted)",
            lineterm='',
        )

    def to_string(self) -> str:
        """
		Return the reformatted file as a string.
		"""

        if self._reformatted_source is None:
            raise ValueError("'Reformatter.run()' must be called first!")

        return self._reformatted_source

    def to_file(self) -> None:
        """
		Write the reformatted source to the original file.
		"""

        if self._reformatted_source is None:
            raise ValueError("'Reformatter.run()' must be called first!")

        self.file_to_format.write_text(self._reformatted_source)
Esempio n. 18
0
def demo_pyproject_environment(demo_environment, tmp_pathplus):
    example_formate_toml = PathPlus(__file__).parent / "example_pyproject.toml"
    (tmp_pathplus / "pyproject.toml").write_text(
        example_formate_toml.read_text())