Пример #1
0
	def merge_existing(self, ini_file: pathlib.Path):
		"""
		Merge existing sections in the configuration file into the new configuration.

		:param ini_file: The existing ``.ini`` file.
		"""

		if ini_file.is_file():
			existing_config = ConfigUpdater()
			existing_config.read(str(ini_file))
			for section in existing_config.sections_blocks():
				if section.name not in self.managed_sections:
					self._ini.add_section(section)
Пример #2
0
	def bump(self, new_version: Version, commit: Optional[bool], message: str):
		"""
		Bump to the given version.

		:param new_version:
		:param commit: Whether to commit automatically (:py:obj:`True`) or ask first (:py:obj:`None`).
		:param message: The commit message.

		.. versionchanged:: 2021.8.11

			Now takes a :class:`packaging.version.Version` rather than a
			:class:`domdf_python_tools.versions.Version`.
		"""

		new_version_str = str(new_version)

		dulwich_repo = Repo(self.repo.target_repo)

		if f"v{new_version_str}".encode("UTF-8") in dulwich_repo.refs.as_dict(b"refs/tags"):
			raise abort(f"The tag 'v{new_version_str}' already exists!")

		bumpversion_config = self.get_bumpversion_config(str(self.current_version), new_version_str)

		changed_files = [self.bumpversion_file.relative_to(self.repo.target_repo).as_posix()]

		for filename in bumpversion_config.keys():
			if not os.path.isfile(filename):
				raise FileNotFoundError(filename)

		for filename, config in bumpversion_config.items():
			self.bump_version_for_file(filename, config)
			changed_files.append(filename)

		# Update number in .bumpversion.cfg
		bv = ConfigUpdater()
		bv.read(self.bumpversion_file)
		bv["bumpversion"]["current_version"] = new_version_str
		self.bumpversion_file.write_clean(str(bv))

		commit_message = message.format(current_version=self.current_version, new_version=new_version)
		click.echo(commit_message)

		if commit_changed_files(
				self.repo.target_repo,
				managed_files=changed_files,
				commit=commit,
				message=commit_message.encode("UTF-8"),
				enable_pre_commit=False,
				):

			tag_create(dulwich_repo, f"v{new_version_str}")
Пример #3
0
	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)
Пример #4
0
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()]
Пример #5
0
    def merge_existing(self, ini_file):

        if ini_file.is_file():
            existing_config = ConfigUpdater()
            existing_config.read(str(ini_file))

            for section in existing_config.sections_blocks():
                if section.name == "options.packages.find" and "exclude" in section:

                    all_excludes = (
                        *section["exclude"].value.splitlines(),
                        *self._ini["options.packages.find"]
                        ["exclude"].value.splitlines(),
                    )

                    exclude_packages = sorted(
                        filter(bool, set(map(str.strip, all_excludes))))
                    self._ini["options.packages.find"][
                        "exclude"] = indent_join(exclude_packages)

                if section.name not in self.managed_sections:
                    self._ini.add_section(section)
                elif section.name == "mypy":
                    self.copy_existing_value(section, "incremental")

        if "options.entry_points" not in self._ini.sections():
            self._ini.add_section("options.entry_points")

        # if self["console_scripts"]:
        # 	self._ini["options.entry_points"]["console_scripts"] = self["console_scripts"]
        # else:
        if not self._ini["options.entry_points"].options():
            self._ini.remove_section("options.entry_points")

        if self["use_whey"]:
            self._ini.remove_section("metadata")
            self._ini.remove_section("options")
            self._ini.remove_section("options.packages.find")

        if float(self["mypy_version"]) >= 0.901:
            self._ini.remove_section("mypy")
Пример #6
0
	def get_bumpversion_config(
			self,
			current_version: str,
			new_version: str,
			) -> Dict[str, BumpversionFileConfig]:
		"""
		Returns the bumpversion config.

		:param current_version:
		:param new_version:
		"""

		bv = ConfigUpdater()
		bv.read(self.bumpversion_file)

		def default():
			return {"search": current_version, "replace": new_version}

		# populate with the sections which are managed by repo_helper
		config: Dict[str, BumpversionFileConfig] = {
				filename: default()
				for filename in get_bumpversion_filenames(self.repo.templates)
				}

		if self.repo.templates.globals["enable_docs"]:
			config[f"{self.repo.templates.globals['docs_dir']}/index.rst"] = default()

		for section in bv.sections():
			if not section.startswith("bumpversion:file:"):
				continue

			section_dict: Dict[str, str] = bv[section].to_dict()
			config[section[17:]] = dict(
					search=section_dict.get("search", "{current_version}").format(current_version=current_version),
					replace=section_dict.get("replace", "{new_version}").format(new_version=new_version),
					)

		return config
Пример #7
0
class IniConfigurator:
	"""
	Base class to generate ``.ini`` configuration files.

	:param base_path:
	"""

	managed_sections: List[str]
	_ini: ConfigUpdater
	_output: StringList
	managed_message: str = "This file is managed by 'repo_helper'."
	filename: str

	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 merge_existing(self, ini_file: pathlib.Path):
		"""
		Merge existing sections in the configuration file into the new configuration.

		:param ini_file: The existing ``.ini`` file.
		"""

		if ini_file.is_file():
			existing_config = ConfigUpdater()
			existing_config.read(str(ini_file))
			for section in existing_config.sections_blocks():
				if section.name not in self.managed_sections:
					self._ini.add_section(section)

	def write_out(self):
		"""
		Write out to the ``.ini`` file.
		"""

		ini_file = PathPlus(self.base_path / self.filename)

		for section_name in self.managed_sections:
			getattr(self, re.sub("[:.-]", '_', section_name))()

		self.merge_existing(ini_file)
		self._output.append(str(self._ini))
		ini_file.write_lines(self._output)

	def copy_existing_value(self, section: Section, key: str):
		"""
		Copy the existing value for ``key``, if present, to the new configuration.

		:param section:
		:param key:
		"""

		if key in section:
			self._ini[section.name][key] = section[key].value
Пример #8
0
def make_formate_toml(repo_path: pathlib.Path, templates: Environment) -> List[str]:
	"""
	Add configuration for ``formate``.

	https://formate.readthedocs.io

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

	known_third_party = set()

	isort_file = PathPlus(repo_path / ".isort.cfg")
	formate_file = PathPlus(repo_path / "formate.toml")

	isort_config = get_isort_config(repo_path, templates)
	known_third_party.update(isort_config["known_third_party"])

	if formate_file.is_file():
		formate_config = dom_toml.load(formate_file)
	else:
		formate_config = {}

	# Read the isort config file and get "known_third_party" from there
	if isort_file.is_file():
		isort = ConfigUpdater()
		isort.read(str(isort_file))

		if "settings" in isort.sections() and "known_third_party" in isort["settings"]:
			known_third_party.update(re.split(r"(\n|,\s*)", isort["settings"]["known_third_party"].value))

	isort_file.unlink(missing_ok=True)

	if "hooks" in formate_config and "isort" in formate_config["hooks"]:
		if "kwargs" in formate_config["hooks"]["isort"]:
			known_third_party.update(formate_config["hooks"]["isort"]["kwargs"].get("known_third_party", ()))

			for existing_key, value in formate_config["hooks"]["isort"]["kwargs"].items():
				if existing_key not in isort_config:
					isort_config[existing_key] = value

	def normalise_underscore(name: str) -> str:
		return normalize(name.strip()).replace('-', '_')

	isort_config["known_third_party"] = sorted(set(filter(bool, map(normalise_underscore, known_third_party))))

	hooks = {
			"dynamic_quotes": 10,
			"collections-import-rewrite": 20,
			"yapf": {"priority": 30, "kwargs": {"yapf_style": ".style.yapf"}},
			"reformat-generics": 40,
			"isort": {"priority": 50, "kwargs": isort_config},
			"noqa-reformat": 60,
			"ellipsis-reformat": 70,
			"squish_stubs": 80,
			}

	config = {"indent": '\t', "line_length": 115}

	formate_config["hooks"] = hooks
	formate_config["config"] = config

	formate_file = PathPlus(repo_path / "formate.toml")
	dom_toml.dump(formate_config, formate_file, encoder=dom_toml.TomlEncoder)

	return [formate_file.name, isort_file.name]
Пример #9
0
	def merge_existing(self, ini_file):
		"""
		Merge existing sections in the configuration file into the new configuration.

		:param ini_file: The existing ``.ini`` file.
		"""

		if ini_file.is_file():
			existing_config = ConfigUpdater()
			existing_config.read(str(ini_file))

			for section in existing_config.sections_blocks():
				if section.name not in self.managed_sections:
					self._ini.add_section(section)
				elif section.name == "coverage:report" and "omit" in section:
					self._ini["coverage:report"]["omit"] = section["omit"].value
				elif section.name == "flake8":
					if "rst-directives" in section:
						existing_directives = section["rst-directives"].value.splitlines()
						new_directives = self._ini["flake8"]["rst-directives"].value.splitlines()
						combined_directives = set(map(str.strip, (*new_directives, *existing_directives)))
						self._ini["flake8"]["rst-directives"] = indent_join(
								sorted(filter(bool, combined_directives))
								)

					if "rst-roles" in section:
						existing_roles = section["rst-roles"].value.splitlines()
						new_roles = self._ini["flake8"]["rst-roles"].value.splitlines()
						combined_roles = set(map(str.strip, (*new_roles, *existing_roles)))
						self._ini["flake8"]["rst-roles"] = indent_join(sorted(filter(bool, combined_roles)))

					if "per-file-ignores" in section:
						combined_ignores = {}

						# Existing first, so they're always overridden by our new ones
						for line in section["per-file-ignores"].value.splitlines():
							if not line.strip():
								continue
							glob, ignores = line.split(':', 1)
							combined_ignores[glob.strip()] = ignores.strip()

						for line in self._ini["flake8"]["per-file-ignores"].value.splitlines():
							if not line.strip():
								continue
							glob, ignores = line.split(':', 1)
							combined_ignores[glob.strip()] = ignores.strip()

						# Always put tests/* and */*.pyi first
						combined_ignores_strings = [
								f"tests/*: {combined_ignores.pop('tests/*')}",
								f"*/*.pyi: {combined_ignores.pop('*/*.pyi')}",
								]

						combined_ignores_strings.extend(
								sorted(filter(bool, (map(": ".join, combined_ignores.items()))))
								)
						self._ini["flake8"]["per-file-ignores"] = indent_join(combined_ignores_strings)
				elif section.name == "pytest":
					if "filterwarnings" in section:
						existing_value = set(map(str.strip, section["filterwarnings"].value.splitlines()))
						self._ini["pytest"]["filterwarnings"] = indent_join(sorted(filter(bool, existing_value)))
					if "markers" in section:
						existing_value = set(map(str.strip, section["markers"].value.splitlines()))
						self._ini["pytest"]["markers"] = indent_join(sorted(filter(bool, existing_value)))
Пример #10
0
def ensure_bumpversion(repo_path: pathlib.Path,
                       templates: Environment) -> List[str]:
    """
	Add configuration for ``bumpversion`` to the desired repo.

	https://pypi.org/project/bumpversion/

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

    bumpversion_file = PathPlus(repo_path / ".bumpversion.cfg")

    if not bumpversion_file.is_file():
        bumpversion_file.write_lines([
            "[bumpversion]",
            f"current_version = {templates.globals['version']}",
            "commit = True",
            "tag = True",
        ])

    bv = ConfigUpdater()
    bv.read(str(bumpversion_file))

    old_sections = [
        "bumpversion:file:git_helper.yml", "bumpversion:file:__pkginfo__.py"
    ]
    required_sections = {
        f"bumpversion:file:{filename}"
        for filename in get_bumpversion_filenames(templates)
    }

    if not templates.globals["enable_docs"]:
        old_sections.append(
            f"bumpversion:file:{templates.globals['docs_dir']}/index.rst")

    if not templates.globals["enable_conda"]:
        old_sections.append(f"bumpversion:file:.github/workflows/conda_ci.yml")

    if templates.globals["use_whey"]:
        old_sections.append("bumpversion:file:setup.cfg")

    for section in old_sections:
        if section in bv.sections():
            bv.remove_section(section)
        if section in required_sections:
            required_sections.remove(section)

    for section in sorted(required_sections):
        if section not in bv.sections():
            bv.add_section(section)

    init_filename = get_init_filename(templates)
    if init_filename is not None:
        init_section = bv[f"bumpversion:file:{init_filename}"]
        if "search" not in init_section:
            init_section["search"] = ': str = "{current_version}"'
            init_section["replace"] = ': str = "{new_version}"'

    if "bumpversion:file:setup.cfg" in bv.sections():
        setup_cfg_section = bv["bumpversion:file:setup.cfg"]
        if ("search" not in setup_cfg_section or
            ("search" in setup_cfg_section and
             setup_cfg_section["search"].value == "name = {current_version}")):
            setup_cfg_section["search"] = "version = {current_version}"
            setup_cfg_section["replace"] = "version = {new_version}"

    if "bumpversion:file:pyproject.toml" in bv.sections():
        pp_toml_section = bv["bumpversion:file:pyproject.toml"]
        if "search" not in pp_toml_section:
            pp_toml_section["search"] = 'version = "{current_version}"'
            pp_toml_section["replace"] = 'version = "{new_version}"'

    bv["bumpversion"]["current_version"] = templates.globals["version"]
    bv["bumpversion"]["commit"] = "True"
    bv["bumpversion"]["tag"] = "True"

    bumpversion_file.write_clean(str(bv))

    return [bumpversion_file.name]
Пример #11
0
def typed():
    """
	Add a 'py.typed' file and the associated trove classifier.
	"""

    # 3rd party
    from domdf_python_tools.paths import PathPlus
    from domdf_python_tools.stringlist import StringList
    from natsort import natsorted

    # this package
    from repo_helper.configupdater2 import ConfigUpdater
    from repo_helper.core import RepoHelper
    from repo_helper.utils import indent_join, stage_changes

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

    py_typed = rh.target_repo / rh.templates.globals["import_name"] / "py.typed"
    if not py_typed.is_file():
        py_typed.touch()

    stage_changes(rh.target_repo, [py_typed])

    setup_cfg = rh.target_repo / "setup.cfg"
    pyproject_file = rh.target_repo / "pyproject.toml"

    if setup_cfg.is_file() and not rh.templates.globals["use_whey"]:
        content = setup_cfg.read_text()

        config = ConfigUpdater()
        config.read_string(content)

        existing_classifiers = config["metadata"]["classifiers"]
        existing_classifiers_string = str(existing_classifiers)

        classifiers = set(
            map(str.strip, existing_classifiers.value.split('\n')))
        classifiers.add("Typing :: Typed")

        new_classifiers_lines = StringList(
            indent_join(natsorted(classifiers)).expandtabs(4))
        new_classifiers_lines[0] = "classifiers ="
        new_classifiers_lines.blankline(ensure_single=True)

        setup_cfg.write_clean(
            content.replace(existing_classifiers_string,
                            str(new_classifiers_lines)))

    if pyproject_file.is_file() and rh.templates.globals["use_whey"]:
        pyproject_config = dom_toml.load(pyproject_file)
        if "whey" in pyproject_config.get("tool", {}):
            classifiers = set(
                pyproject_config["tool"]["whey"]["base-classifiers"])
            classifiers.add("Typing :: Typed")
            pyproject_config["tool"]["whey"]["base-classifiers"] = natsorted(
                classifiers)

        dom_toml.dump(pyproject_config,
                      pyproject_file,
                      encoder=dom_toml.TomlEncoder)