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" ]
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 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)
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 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]
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)
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
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 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
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'")
]) 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"])
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))