def make_actions_deploy_conda(repo_path: pathlib.Path, templates: Environment) -> List[str]: """ Add script to build Conda package and deploy to Anaconda. :param repo_path: Path to the repository root. :param templates: """ old_deploy_file = PathPlus(repo_path / ".ci" / "actions_deploy_conda.sh") old_build_file = PathPlus(repo_path / ".ci" / "actions_build_conda.sh") old_deploy_file.unlink(missing_ok=True) old_build_file.unlink(missing_ok=True) deploy_file = PathPlus(repo_path / ".github" / "actions_deploy_conda.sh") build_file = PathPlus(repo_path / ".github" / "actions_build_conda.sh") deploy_file.parent.maybe_make() build_file.write_clean(templates.get_template(build_file.name).render()) build_file.make_executable() deploy_file.write_clean(templates.get_template(deploy_file.name).render()) deploy_file.make_executable() return [ build_file.relative_to(repo_path).as_posix(), old_build_file.relative_to(repo_path).as_posix(), deploy_file.relative_to(repo_path).as_posix(), old_deploy_file.relative_to(repo_path).as_posix(), ]
def make_docutils_conf(repo_path: pathlib.Path, templates: Environment) -> List[str]: """ Add configuration for ``Docutils``. :param repo_path: Path to the repository root. :param templates: """ file = PathPlus(repo_path / templates.globals["docs_dir"] / "docutils.conf") file.parent.maybe_make(parents=True) if not file.is_file(): file.write_text('\n'.join([ "[restructuredtext parser]", "tab_width = 4", '', '', ])) conf = ConfigUpdater() conf.read(str(file)) required_sections = ["restructuredtext parser"] for section in required_sections: if section not in conf.sections(): conf.add_section(section) conf["restructuredtext parser"]["tab_width"] = 4 file.write_clean(str(conf)) return [file.relative_to(repo_path).as_posix()]
def make_dependabotv2(repo_path: pathlib.Path, templates: Environment) -> List[str]: """ Add configuration for ``dependabot`` to the desired repo. https://dependabot.com/ :param repo_path: Path to the repository root. :param templates: .. versionadded:: 2020.12.11 """ dependabot_file = PathPlus(repo_path / ".github" / "dependabot.yml") dependabot_file.parent.maybe_make() updates = { "package-ecosystem": "pip", "directory": '/', "schedule": {"interval": "weekly"}, "reviewers": [templates.globals["assignee"]], } config = {"version": 2, "updates": [updates]} dependabot_file.write_lines([ f"# {templates.globals['managed_message']}", "---", _round_trip_dump(config), ]) return [dependabot_file.relative_to(repo_path).as_posix()]
def make_github_docs_test(repo_path: pathlib.Path, templates: Environment) -> List[str]: """ Add configuration for GitHub Actions documentation check to the desired repo. :param repo_path: Path to the repository root. :param templates: """ file = PathPlus(repo_path / ".github" / "workflows" / "docs_test_action.yml") file.parent.maybe_make(parents=True) if templates.globals["enable_docs"]: if templates.globals["docs_fail_on_warning"]: build_command = "tox -e docs -- -W " else: build_command = "tox -e docs -- " file.write_clean( templates.get_template( file.name).render(build_command=build_command)) else: file.unlink(missing_ok=True) return [file.relative_to(repo_path).as_posix()]
def make_dependabot(repo_path: pathlib.Path, templates: Environment) -> List[str]: """ Add configuration for ``dependabot`` to the desired repo. https://dependabot.com/ :param repo_path: Path to the repository root. :param templates: .. deprecated:: 2020.12.11 """ dependabot_file = PathPlus(repo_path / ".dependabot" / "config.yml") dependabot_file.parent.maybe_make() update_configs = { "package_manager": "python", "directory": '/', "update_schedule": "weekly", "default_reviewers": [templates.globals["assignee"]], } config = {"version": 1, "update_configs": [update_configs]} dependabot_file.write_lines([ f"# {templates.globals['managed_message']}", "---", _round_trip_dump(config), ]) return [dependabot_file.relative_to(repo_path).as_posix()]
def test_iterchildren_match( advanced_data_regression: AdvancedDataRegressionFixture, absolute: bool): repo_path = PathPlus(__file__).parent.parent with in_directory(repo_path.parent): assert repo_path.is_dir() if not absolute: repo_path = repo_path.relative_to(repo_path.parent) if (repo_path / "build").is_dir(): shutil.rmtree(repo_path / "build") children = list(repo_path.iterchildren(match="**/*.py")) assert children child_paths = sorted( p.relative_to(repo_path).as_posix() for p in children) for exclude_filename in { ".coverage", "pathtype_demo.py", "dist", "htmlcov", "conda", ".idea", "mutdef.py" }: if exclude_filename in child_paths: child_paths.remove(exclude_filename) advanced_data_regression.check(child_paths, basename="test_iterchildren_match")
def make_stale_bot(repo_path: pathlib.Path, templates: Environment) -> List[str]: """ Add configuration for ``stale`` to the desired repo. https://probot.github.io/apps/stale/ :param repo_path: Path to the repository root. :param templates: """ stale_file = PathPlus(repo_path) / ".github" / "stale.yml" stale_file.parent.maybe_make() stale_file.write_clean(templates.get_template("stale_bot.yaml").render()) return [stale_file.relative_to(repo_path).as_posix()]
def remove_copy_pypi_2_github(repo_path: pathlib.Path, templates: Environment) -> List[str]: """ Remove deprecated copy_pypi_2_github.py script. Uue octocheese and its GitHub Action instead. :param repo_path: Path to the repository root. :param templates: """ copier = PathPlus(repo_path / ".ci" / "copy_pypi_2_github.py") if copier.is_file(): copier.unlink() return [copier.relative_to(repo_path).as_posix()]
def travis_bad(repo_path: pathlib.Path, templates: Environment) -> List[str]: """ Removes Travis CI configuration. :param repo_path: Path to the repository root. :param templates: """ if PathPlus(repo_path / ".travis.yml").is_file(): PathPlus(repo_path / ".travis.yml").unlink() conda_file = PathPlus(repo_path / ".ci" / "travis_deploy_conda.sh") if conda_file.is_file(): conda_file.unlink() return [".travis.yml", conda_file.relative_to(repo_path).as_posix()]
def remove_autodoc_augment_defaults(repo_path: pathlib.Path, templates: Environment) -> List[str]: """ Remove the redundant "autodoc_augment_defaults" extension. :param repo_path: Path to the repository root. :param templates: """ target_file = PathPlus(repo_path / templates.globals["docs_dir"] / "autodoc_augment_defaults.py") if target_file.is_file(): target_file.unlink() return [target_file.relative_to(repo_path).as_posix()]
def make_github_octocheese(repo_path: pathlib.Path, templates: Environment) -> List[str]: """ Add configuration for the OctoCheese GitHub Action. :param repo_path: Path to the repository root. :param templates: """ file = PathPlus(repo_path / ".github" / "workflows" / "octocheese.yml") file.parent.maybe_make(parents=True) if templates.globals["on_pypi"]: file.write_clean(templates.get_template(file.name).render()) elif file.is_file(): file.unlink() return [file.relative_to(repo_path).as_posix()]
def stage_changes( repo: Union[PathLike, dulwich.repo.Repo], files: Iterable[PathLike], ) -> List[PathPlus]: """ Stage any files that have been updated, added or removed. :param repo: The repository. :param files: List of files to stage. :returns: A list of staged files. Not all files in ``files`` will have been changed, and only changes are staged. .. versionadded:: 2020.11.23 """ with open_repo_closing(repo) as repo: stat = status(repo) unstaged_changes = stat.unstaged untracked_files = stat.untracked staged_files = [] for filename in files: filename = PathPlus(filename) if filename.is_absolute(): filename = filename.relative_to(repo.path) if filename in unstaged_changes or filename in untracked_files: repo.stage(os.path.normpath(filename)) staged_files.append(filename) elif ( filename in stat.staged["add"] or filename in stat.staged["modify"] or filename in stat.staged["delete"] ): staged_files.append(filename) return staged_files
def make_github_manylinux(repo_path: pathlib.Path, templates: Environment) -> List[str]: """ Add configuration for `GitHub Actions` manylinux wheel builds the desired repo. :param repo_path: Path to the repository root. :param templates: """ # TODO: deploys from other supported platforms for not pure python file = PathPlus(repo_path / ".github" / "workflows" / "manylinux_build.yml") file.parent.maybe_make(parents=True) if not templates.globals["pure_python"] and "Linux" in templates.globals[ "platforms"]: actions = templates.get_template(file.name) wheel_py_versions = [] PYVERSIONS = [] for pyver in range(6, 8): if f"3.{pyver}" in templates.globals["python_versions"]: wheel_py_versions.append(f"cp3{pyver}-cp3{pyver}m") PYVERSIONS.append(f'"3{pyver}"') for pyver in range(8, 10): if f"3.{pyver}" in templates.globals["python_versions"]: wheel_py_versions.append(f"cp3{pyver}-cp3{pyver}") PYVERSIONS.append(f'"3{pyver}"') file.write_clean( actions.render( wheel_py_versions=wheel_py_versions, PYVERSIONS=' '.join(PYVERSIONS), )) elif file.is_file(): file.unlink() return [file.relative_to(repo_path).as_posix()]
def make_docs_contributing(repo_path: pathlib.Path, templates: Environment) -> List[str]: """ Add CONTRIBUTING.rst to the documentation directory of the repo. :param repo_path: Path to the repository root. :param templates: """ file = PathPlus(repo_path / templates.globals["docs_dir"] / "contributing.rst") file.parent.maybe_make(parents=True) contributing = templates.get_template("CONTRIBUTING.rst") if templates.globals["standalone_contrib_guide"]: file.write_clean(contributing.render(bash_block=sphinx_bash_block)) else: file.write_lines([ "Overview", "---------", *contributing.render(bash_block=sphinx_bash_block).splitlines()[3:], ]) return [file.relative_to(repo_path).as_posix()]
def make_conf(repo_path: pathlib.Path, templates: Environment) -> List[str]: """ Add ``conf.py`` configuration file for ``Sphinx``. https://www.sphinx-doc.org/en/master/index.html :param repo_path: Path to the repository root. :param templates: """ conf = templates.get_template("conf._py") file = PathPlus(repo_path / templates.globals["docs_dir"] / "conf.py") file.parent.maybe_make(parents=True) username = templates.globals["username"] repo_name = templates.globals["repo_name"] if templates.globals["sphinx_html_theme"] in { "sphinx-rtd-theme", "domdf-sphinx-theme" }: style = { "display_github": True, # Integrate GitHub "github_user": username, # Username "github_repo": repo_name, # Repo name "github_version": "master", # Version "conf_py_path": f"/{templates.globals['docs_dir']}/", # Path in the checkout to the docs root } for key, val in style.items(): if key not in templates.globals["html_context"]: templates.globals["html_context"][key] = val options = { # 'logo': 'logo.png', "logo_only": False, # True will show just the logo } for key, val in options.items(): if key not in templates.globals["html_theme_options"]: templates.globals["html_theme_options"][key] = val elif templates.globals["sphinx_html_theme"] in { "alabaster", "repo-helper-sphinx-theme" }: # See https://github.com/bitprophet/alabaster/blob/master/alabaster/theme.conf # and https://alabaster.readthedocs.io/en/latest/customization.html style = { # 'logo': 'logo.png', "page_width": "1200px", "logo_name": "true", "github_user": username, # Username "github_repo": repo_name, # Repo name "description": templates.globals["short_desc"], "github_banner": "true", "github_type": "star", "badge_branch": "master", "fixed_sidebar": "true", } for key, val in style.items(): if key not in templates.globals["html_theme_options"]: templates.globals["html_theme_options"][key] = val elif templates.globals["sphinx_html_theme"] in {"furo"}: # See https://github.com/bitprophet/alabaster/blob/master/alabaster/theme.conf # and https://alabaster.readthedocs.io/en/latest/customization.html style = { "toc-title-font-size": "12pt", "toc-font-size": "12pt", "admonition-font-size": "12pt", } for key in ["light_css_variables", "dark_css_variables"]: if key not in templates.globals["html_theme_options"]: templates.globals["html_theme_options"][key] = style else: templates.globals["html_theme_options"][key] = { **style, **templates.globals["html_theme_options"][key], } file.write_clean( conf.render(pformat=pformat_tabs, enquote_value=enquote_value)) with resource(repo_helper.files, "isort.cfg") as isort_config: yapf_style = PathPlus( isort_config).parent.parent / "templates" / "style.yapf" reformat_file(file, yapf_style=str(yapf_style), isort_config_file=str(isort_config)) return [file.relative_to(repo_path).as_posix()]
def make_rtfd(repo_path: pathlib.Path, templates: Environment) -> List[str]: """ Add configuration for ``ReadTheDocs``. https://readthedocs.org/ See https://docs.readthedocs.io/en/stable/config-file/v2.html for details :param repo_path: Path to the repository root. :param templates: """ file = PathPlus(repo_path / ".readthedocs.yml") docs_dir = PathPlus(repo_path / templates.globals["docs_dir"]) sphinx_config = { "builder": "html", "configuration": f"{templates.globals['docs_dir']}/conf.py", } install_requirements = [ "requirements.txt", f"{templates.globals['docs_dir']}/requirements.txt", *templates.globals["additional_requirements_files"], ] install_config: List[Dict] = [{ "requirements": r } for r in install_requirements] if (docs_dir / "rtd-extra-deps.txt").is_file(): install_config.append({ "requirements": f"{templates.globals['docs_dir']}/rtd-extra-deps.txt" }) elif templates.globals["tox_testenv_extras"]: install_config.append({ "method": "pip", "path": '.', "extra_requirements": [templates.globals["tox_testenv_extras"]], }) else: install_config.append({"method": "pip", "path": '.'}) python_config = {"version": 3.8, "install": install_config} # Formats: Optionally build your docs in additional formats such as PDF and ePub config = { "version": 2, "sphinx": sphinx_config, "formats": ["pdf", "htmlzip"], "python": python_config } # TODO: support user customisation of search rankings # https://docs.readthedocs.io/en/stable/config-file/v2.html#search-ranking dumper = yaml.YAML() dumper.indent(mapping=2, sequence=3, offset=1) yaml_buf = yaml.StringIO() dumper.dump(config, yaml_buf) file.write_lines([ f"# {templates.globals['managed_message']}", "# Read the Docs configuration file", "---", yaml_buf.getvalue() ]) return [file.relative_to(repo_path).as_posix()]
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()]