def make_installation_instructions(options: Dict[str, Any], env: BuildEnvironment) -> List[str]:
	"""
	Make the content of an installation node.

	:param options:
	:param env: The Sphinx build environment.
	"""

	tabs: Dict[str, List[str]] = _get_installation_instructions(options, env)

	if not tabs:
		warnings.warn("No installation source specified. No installation instructions will be shown.")
		return []

	content = StringList([".. tabs::", ''])
	content.set_indent_type("    ")

	for tab_name, tab_content in tabs.items():
		with content.with_indent_size(1):
			content.append(f".. tab:: {tab_name}")
			content.blankline(ensure_single=True)

		with content.with_indent_size(2):
			content.extend([f"{line}" if line else '' for line in tab_content])

	return list(content)
    def test_extend(self):
        sl = StringList(['', '', "hello", "world", '', '', '', "1234"])
        sl.extend(["\nfoo\nbar\n    baz"])

        assert sl == [
            '', '', "hello", "world", '', '', '', "1234", '', "foo", "bar",
            "    baz"
        ]
Beispiel #3
0
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
Beispiel #4
0
    def get_linux_mypy_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)
        dependency_lines.extend(
            self.templates.globals["github_ci_requirements"]["Linux"]["post"])

        return dependency_lines
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)
Beispiel #6
0
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
Beispiel #7
0
    def make_mypy(self) -> PathPlus:
        """
		Create, update or remove the mypy action, as appropriate.

		.. versionadded:: 2020.1.27
		"""

        ci_file = self.workflows_dir / "mypy.yml"
        template = self.templates.get_template(ci_file.name)
        # TODO: handle case where Linux is not a supported platform

        platforms = set(self.templates.globals["platforms"])
        if "macOS" in platforms:
            platforms.remove("macOS")

        platforms = set(
            filter(None, (platform_ci_names.get(p, None) for p in platforms)))

        dependency_lines = self.get_linux_mypy_requirements()
        linux_platform = platform_ci_names["Linux"]

        if dependency_lines == self.standard_python_install_lines:
            dependencies_block = StringList([
                "- name: Install dependencies 🔧",
                "  run: |",
            ])
            with dependencies_block.with_indent("  ", 2):
                dependencies_block.extend(self.standard_python_install_lines)
        else:
            dependencies_block = StringList([
                "- name: Install dependencies (Linux) 🔧",
                f"  if: ${{{{ matrix.os == '{linux_platform}' && steps.changes.outputs.code == 'true' }}}}",
                "  run: |",
            ])
            with dependencies_block.with_indent("  ", 2):
                dependencies_block.extend(dependency_lines)

            if self.templates.globals["platforms"] != ["Linux"]:
                dependencies_block.blankline(ensure_single=True)
                dependencies_block.extend([
                    "- name: Install dependencies (Win/mac) 🔧",
                    f"  if: ${{{{ matrix.os != '{linux_platform}' && steps.changes.outputs.code == 'true' }}}}",
                    "  run: |",
                ])
                with dependencies_block.with_indent("  ", 2):
                    dependencies_block.extend(
                        self.standard_python_install_lines)

        ci_file.write_clean(
            template.render(
                platforms=sorted(platforms),
                linux_platform=platform_ci_names["Linux"],
                dependencies_block=indent(str(dependencies_block), "      "),
                code_file_filter=self._code_file_filter,
            ))

        return ci_file
    def run(self) -> List[nodes.Node]:
        """
		Create the installation node.
		"""

        if self.env.docname != self.env.config.master_doc:  # pragma: no cover
            warnings.warn(
                "The 'sidebar-links' directive can only be used on the Sphinx master doc. "
                "No links will be shown.",
                UserWarning,
            )
            return []

        body = StringList([
            ".. toctree::",
            "    :hidden:",
        ])

        with body.with_indent("    ", 1):
            if "caption" in self.options:
                body.append(f":caption: {self.options['caption']}")
            else:  # pragma: no cover
                body.append(":caption: Links")

            body.blankline()

            if "github" in self.options:
                body.append(self.process_github_option())
            if "pypi" in self.options:
                body.append(
                    f"PyPI <https://pypi.org/project/{self.options['pypi']}>")

            body.extend(self.content)

        body.blankline()
        body.blankline()

        only_node = addnodes.only(expr="html")
        content_node = nodes.paragraph(rawsource=str(body))
        only_node += content_node
        self.state.nested_parse(docutils.statemachine.StringList(body),
                                self.content_offset, content_node)

        return [only_node]
Beispiel #9
0
def make_pr_details() -> str:
	"""
	Returns the body of a pull request.
	"""

	buf = StringList()
	buf.extend([
			"<details>",
			"  <summary>Commands</summary>",
			'',
			"  * `@repo-helper recreate` will recreate the pull request by checking"
			" out the current master branch and running `repo-helper` on that.",
			"</details>",
			])

	buf.blankline(ensure_single=True)

	buf.append("---")
	buf.blankline(ensure_single=True)

	buf.append(make_footer_links("repo-helper", "repo-helper-bot", event_date=date.today(), type="app"))
	return str(buf)
Beispiel #10
0
    def get_macos_ci_requirements(self) -> List[str]:
        """
		Returns the Python requirements to run tests for on macOS.
		"""

        dependency_lines = StringList(
            self.templates.globals["github_ci_requirements"]["macOS"]["pre"])
        dependency_lines.extend(self.standard_python_install_lines)

        dependency_lines.extend(self._get_additional_requirements())
        dependency_lines.extend(
            self.templates.globals["github_ci_requirements"]["macOS"]["post"])

        return dependency_lines
Beispiel #11
0
    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
Beispiel #12
0
    def make(self) -> StringList:
        """
		Constructs the contents of the shields block.
		"""

        buf = StringList()
        sections = {}
        substitutions = {}

        repo_name = self.repo_name
        username = self.username
        pypi_name = self.pypi_name

        if self.unique_name:
            buf.append(f".. start shields {self.unique_name.lstrip('_')}")
        else:
            buf.append(f".. start shields")

        buf.blankline(ensure_single=True)

        buf.extend(
            [".. list-table::", "\t:stub-columns: 1", "\t:widths: 10 90"])
        buf.blankline(ensure_single=True)

        sections["Activity"] = [
            "commits-latest", "commits-since", "maintained"
        ]
        substitutions["commits-since"] = self.make_activity_shield(
            repo_name, username, self.version)
        substitutions["commits-latest"] = self.make_last_commit_shield(
            repo_name, username)
        substitutions["maintained"] = self.make_maintained_shield()

        sections["Other"] = ["license", "language", "requires"]
        substitutions["requires"] = self.make_requires_shield(
            repo_name, username)
        substitutions["license"] = self.make_license_shield(
            repo_name, username)
        substitutions["language"] = self.make_language_shield(
            repo_name, username)

        sections["QA"] = ["codefactor", "actions_flake8", "actions_mypy"]
        substitutions["codefactor"] = self.make_codefactor_shield(
            repo_name, username)
        substitutions["actions_flake8"] = self.make_actions_shield(
            repo_name, username, "Flake8", "Flake8 Status")
        substitutions["actions_mypy"] = self.make_actions_shield(
            repo_name, username, "mypy", "mypy status")

        if self.docs:
            sections["Docs"] = ["docs", "docs_check"]
            substitutions["docs"] = self.make_rtfd_shield(
                repo_name, self.docs_url)
            substitutions["docs_check"] = self.make_docs_check_shield(
                repo_name, username)

        sections["Tests"] = []

        if "Linux" in self.platforms:
            sections["Tests"].append("actions_linux")
            substitutions["actions_linux"] = self.make_actions_shield(
                repo_name,
                username,
                "Linux",
                "Linux Test Status",
            )
        if "Windows" in self.platforms:
            sections["Tests"].append("actions_windows")
            substitutions["actions_windows"] = self.make_actions_shield(
                repo_name,
                username,
                "Windows",
                "Windows Test Status",
            )
        if "macOS" in self.platforms:
            sections["Tests"].append("actions_macos")
            substitutions["actions_macos"] = self.make_actions_shield(
                repo_name,
                username,
                "macOS",
                "macOS Test Status",
            )

        if self.tests:
            sections["Tests"].append("coveralls")
            substitutions["coveralls"] = self.make_coveralls_shield(
                repo_name, username)

        if self.on_pypi:
            sections["PyPI"] = [
                "pypi-version", "supported-versions",
                "supported-implementations", "wheel"
            ]
            substitutions["pypi-version"] = self.make_pypi_version_shield(
                pypi_name)
            substitutions[
                "supported-versions"] = self.make_python_versions_shield(
                    pypi_name)
            substitutions[
                "supported-implementations"] = self.make_python_implementations_shield(
                    pypi_name)
            substitutions["wheel"] = self.make_wheel_shield(pypi_name)

            sections["Activity"].append("pypi-downloads")
            substitutions["pypi-downloads"] = self.make_pypi_downloads_shield(
                pypi_name)

        if self.conda:
            sections["Anaconda"] = ["conda-version", "conda-platform"]
            substitutions["conda-version"] = self.make_conda_version_shield(
                pypi_name, self.primary_conda_channel)
            substitutions["conda-platform"] = self.make_conda_platform_shield(
                pypi_name, self.primary_conda_channel)

        if self.docker_shields:
            docker_name = self.docker_name
            sections["Docker"] = [
                "docker_build", "docker_automated", "docker_size"
            ]
            substitutions[
                "docker_build"] = self.make_docker_build_status_shield(
                    docker_name, username)
            substitutions[
                "docker_automated"] = self.make_docker_automated_build_shield(
                    docker_name, username)
            substitutions["docker_size"] = self.make_docker_size_shield(
                docker_name, username)

        for section in self.sections:
            if section not in sections or not sections[section]:
                continue

            images = DelimitedList(
                [f"|{name}{self.unique_name}|" for name in sections[section]])
            buf.extend([f"	* - {section}", f"	  - {images: }"])

        for sub_name in self.substitutions:
            if sub_name not in substitutions:
                continue

            buf.blankline(ensure_single=True)
            buf.append(
                f".. |{sub_name}{self.unique_name}| {substitutions[sub_name][3:]}"
            )

        buf.blankline(ensure_single=True)

        buf.append(".. end shields")
        # buf.blankline(ensure_single=True)

        return buf
Beispiel #13
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()]
from apeye.requests_url import RequestsURL
from domdf_python_tools.paths import PathPlus
from domdf_python_tools.stringlist import StringList
from shippinglabel.requirements import read_requirements

head_sha = RequestsURL(
    "https://api.github.com/repos/domdfcoding/repo_helper/commits/master").get(
    ).json()["sha"]

requirements, comments, invalid = read_requirements("requirements.txt",
                                                    include_invalid=True)

sorted_requirements = sorted(requirements)

buf = StringList(comments)

for line in invalid:
    if line.startswith("git+https://github.com/domdfcoding/repo_helper@"):
        buf.append(
            f"git+https://github.com/domdfcoding/repo_helper@{head_sha}")
    else:
        buf.append(line)

buf.extend(str(req) for req in sorted_requirements)

PathPlus("requirements.txt").write_lines(buf)

os.system("pre-commit")
os.system("git stage requirements.txt")
os.system("git commit -m 'Bump repo-helper version'")
Beispiel #15
0
])

with extra_imports.with_indent("    ", 2):
    extra_imports.extend([
        "DeviceType,",
        "StoredDataType,",
        "DataUnit,",
        "DataValueType,",
        "ChromType,",
        "ChromSubType,",
        "MSLevel,",
        "MSScanType,",
        "MSStorageMode,",
        "SpecType,",
        "SpecSubType,",
        "SampleCategory,",
        "IonizationMode,",
        "TofMsProcessingMode,",
        "DataFileValueDataType,",
        "PointValueStorageScheme,",
        "ImsFrameType,",
        "DesiredMSStorageType,",
        "ApseBackgroundSource,",
        "IonDetectorGain,",
        "FragmentationOpMode,",
        "FragmentationClass,",
        ')',
    ])

extra_imports.blankline(ensure_single=True)
extra_imports.extend(["IonPolarity = int", "SmoothingFunctionType = Any"])
Beispiel #16
0
def requirements(
    no_pager: bool = False,
    depth: int = -1,
    concise: bool = False,
    no_venv: bool = False,
):
    """
	Lists the requirements of this library, and their dependencies.
	"""

    # stdlib
    import re
    import shutil

    # 3rd party
    from domdf_python_tools.compat import importlib_metadata
    from domdf_python_tools.iterative import make_tree
    from domdf_python_tools.paths import PathPlus, in_directory
    from domdf_python_tools.stringlist import StringList
    from packaging.requirements import Requirement
    from shippinglabel.requirements import (ComparableRequirement,
                                            combine_requirements,
                                            list_requirements,
                                            read_requirements)

    # this package
    from repo_helper.core import RepoHelper

    rh = RepoHelper(PathPlus.cwd())
    rh.load_settings(allow_unknown_keys=True)

    with in_directory(rh.target_repo):

        buf = StringList([
            f"{rh.templates.globals['pypi_name']}=={rh.templates.globals['version']}"
        ])
        raw_requirements = sorted(read_requirements("requirements.txt")[0])
        tree: List[Union[str, List[str], List[Union[str, List]]]] = []
        venv_dir = (rh.target_repo / "venv")

        if venv_dir.is_dir() and not no_venv:
            # Use virtualenv as it exists
            search_path = []

            for directory in (venv_dir / "lib").glob("python3.*"):
                search_path.append(str(directory / "site-packages"))

            importlib_metadata.DistributionFinder.Context.path = search_path  # type: ignore

        if concise:
            concise_requirements = []

            def flatten(iterable: Iterable[Union[Requirement, Iterable]]):
                for item in iterable:
                    if isinstance(item, str):
                        yield item
                    else:
                        yield from flatten(item)  # type: ignore

            for requirement in raw_requirements:
                concise_requirements.append(requirement)
                # TODO: remove "extra == " marker
                for req in flatten(
                        list_requirements(str(requirement), depth=depth - 1)):
                    concise_requirements.append(
                        ComparableRequirement(
                            re.sub('; extra == ".*"', '', req)))

            concise_requirements = sorted(
                set(combine_requirements(concise_requirements)))
            tree = list(map(str, concise_requirements))

        else:
            for requirement in raw_requirements:
                tree.append(str(requirement))
                deps = list(
                    list_requirements(str(requirement), depth=depth - 1))
                if deps:
                    tree.append(deps)

        buf.extend(make_tree(tree))

        if shutil.get_terminal_size().lines >= len(buf):
            # Don't use pager if fewer lines that terminal height
            no_pager = True

        if no_pager:
            click.echo(str(buf))
        else:
            click.echo_via_pager(str(buf))