diff --git a/README.md b/README.md index 4c52521..2627d77 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ setup.cfg = current_version = {pycalver} setup.py = version="{pep440_pycalver}" -myproject/__init__.py = +src/myproject_v*/__init__.py = __version__ = "{pycalver}" README.md = [PyCalVer {calver}{build}{release}] @@ -167,8 +167,14 @@ INFO - New Version: v201902.0002-beta +[![Version v201902.0002][version_img]][version_ref] [![PyPI Releases][pypi_img]][pypi_ref] ---- myproject/__init__.py -+++ myproject/__init__.py +--- src/myproject_v1/__init__.py ++++ src/myproject_v1/__init__.py +@@ -1,1 +1,1 @@ +-__version__ = "v201901.0001-beta" ++__version__ = "v201902.0002-beta" + +--- src/myproject_v2/__init__.py ++++ src/myproject_v2/__init__.py @@ -1,1 +1,1 @@ -__version__ = "v201901.0001-beta" +__version__ = "v201902.0002-beta" diff --git a/src/pycalver/config.py b/src/pycalver/config.py index 574e5bd..585771b 100644 --- a/src/pycalver/config.py +++ b/src/pycalver/config.py @@ -20,7 +20,8 @@ from . import version log = logging.getLogger("pycalver.config") -PatternsByFilePath = typ.Dict[str, typ.List[str]] +Patterns = typ.List[str] +PatternsByGlob = typ.Dict[str, Patterns] SUPPORTED_CONFIGS = ["setup.cfg", "pyproject.toml", "pycalver.toml"] @@ -84,7 +85,7 @@ class Config(typ.NamedTuple): tag : bool push : bool - file_patterns: PatternsByFilePath + file_patterns: PatternsByGlob def _debug_str(cfg: Config) -> str: @@ -182,6 +183,10 @@ def _parse_toml(cfg_buffer: typ.IO[str]) -> RawConfig: def _normalize_file_patterns(raw_cfg: RawConfig) -> FilePatterns: + """Create consistent representation of file_patterns. + + The result the same, regardless of the config format. + """ version_str : str = raw_cfg['current_version'] version_pattern: str = raw_cfg['version_pattern'] pep440_version : str = version.to_pep440(version_str) diff --git a/src/pycalver/rewrite.py b/src/pycalver/rewrite.py index fb291a9..ca5da21 100644 --- a/src/pycalver/rewrite.py +++ b/src/pycalver/rewrite.py @@ -6,9 +6,11 @@ """Rewrite files, updating occurences of version strings.""" import io +import glob import difflib import logging import typing as typ +import pathlib2 as pl from . import parse from . import config @@ -88,8 +90,17 @@ def rfd_from_content(patterns: typ.List[str], new_version: str, content: str) -> return RewrittenFileData("", line_sep, old_lines, new_lines) +def _iter_file_paths( + file_patterns: config.PatternsByGlob +) -> typ.Iterable[typ.Tuple[pl.Path, config.Patterns]]: + for globstr, patterns in file_patterns.items(): + for file_path_str in glob.glob(globstr): + file_path = pl.Path(file_path_str) + yield (file_path, patterns) + + def iter_rewritten( - file_patterns: config.PatternsByFilePath, new_version: str + file_patterns: config.PatternsByGlob, new_version: str ) -> typ.Iterable[RewrittenFileData]: r'''Iterate over files with version string replaced. @@ -111,12 +122,12 @@ def iter_rewritten( ''' fh: typ.IO[str] - for filepath, patterns in file_patterns.items(): - with io.open(filepath, mode="rt", encoding="utf-8") as fh: + for file_path, patterns in _iter_file_paths(file_patterns): + with file_path.open(mode="rt", encoding="utf-8") as fh: content = fh.read() rfd = rfd_from_content(patterns, new_version, content) - yield rfd._replace(path=filepath) + yield rfd._replace(path=str(file_path)) def diff_lines(rfd: RewrittenFileData) -> typ.List[str]: @@ -137,7 +148,7 @@ def diff_lines(rfd: RewrittenFileData) -> typ.List[str]: return list(lines) -def diff(new_version: str, file_patterns: config.PatternsByFilePath) -> str: +def diff(new_version: str, file_patterns: config.PatternsByGlob) -> str: r"""Generate diffs of rewritten files. >>> file_patterns = {"src/pycalver/__init__.py": ['__version__ = "{pycalver}"']} @@ -152,22 +163,21 @@ def diff(new_version: str, file_patterns: config.PatternsByFilePath) -> str: """ full_diff = "" - file_path: str - fh : typ.IO[str] + fh: typ.IO[str] - for file_path, patterns in sorted(file_patterns.items()): - with io.open(file_path, mode="rt", encoding="utf-8") as fh: + for file_path, patterns in sorted(_iter_file_paths(file_patterns)): + with file_path.open(mode="rt", encoding="utf-8") as fh: content = fh.read() rfd = rfd_from_content(patterns, new_version, content) - rfd = rfd._replace(path=file_path) + rfd = rfd._replace(path=str(file_path)) full_diff += "\n".join(diff_lines(rfd)) + "\n" full_diff = full_diff.rstrip("\n") return full_diff -def rewrite(new_version: str, file_patterns: config.PatternsByFilePath) -> None: +def rewrite(new_version: str, file_patterns: config.PatternsByGlob) -> None: """Rewrite project files, updating each with the new version.""" fh: typ.IO[str] diff --git a/test/fixtures/project_b/setup.cfg b/test/fixtures/project_b/setup.cfg index 049bad6..3f80d12 100644 --- a/test/fixtures/project_b/setup.cfg +++ b/test/fixtures/project_b/setup.cfg @@ -11,3 +11,5 @@ setup.py = version="{pep440_version}" README.rst = img.shields.io/badge/PyCalVer-{calver}{build}--{release}-blue +src/module_v*/__init__.py = + __version__ = "{version}" diff --git a/test/fixtures/project_b/src/module_v1/__init__.py b/test/fixtures/project_b/src/module_v1/__init__.py new file mode 100644 index 0000000..155d8a7 --- /dev/null +++ b/test/fixtures/project_b/src/module_v1/__init__.py @@ -0,0 +1,2 @@ + +__version__ = "v201307.0456-beta" diff --git a/test/fixtures/project_b/src/module_v2/__init__.py b/test/fixtures/project_b/src/module_v2/__init__.py new file mode 100644 index 0000000..155d8a7 --- /dev/null +++ b/test/fixtures/project_b/src/module_v2/__init__.py @@ -0,0 +1,2 @@ + +__version__ = "v201307.0456-beta" diff --git a/test/test_cli.py b/test/test_cli.py index 4807bc3..ccb172f 100644 --- a/test/test_cli.py +++ b/test/test_cli.py @@ -1,5 +1,4 @@ import os -import io import time import shutil import pathlib2 as pl @@ -166,19 +165,19 @@ def test_incr_invalid(runner): def _add_project_files(*files): if "README.md" in files: - with io.open("README.md", mode="wt", encoding="utf-8") as fh: + with pl.Path("README.md").open(mode="wt", encoding="utf-8") as fh: fh.write("Hello World v201701.0002-alpha !\n") if "setup.cfg" in files: - with io.open("setup.cfg", mode="wt", encoding="utf-8") as fh: + with pl.Path("setup.cfg").open(mode="wt", encoding="utf-8") as fh: fh.write(SETUP_CFG_FIXTURE) if "pycalver.toml" in files: - with io.open("pycalver.toml", mode="wt", encoding="utf-8") as fh: + with pl.Path("pycalver.toml").open(mode="wt", encoding="utf-8") as fh: fh.write(PYCALVER_TOML_FIXTURE) if "pyproject.toml" in files: - with io.open("pyproject.toml", mode="wt", encoding="utf-8") as fh: + with pl.Path("pyproject.toml").open(mode="wt", encoding="utf-8") as fh: fh.write(PYPROJECT_TOML_FIXTURE) @@ -221,7 +220,7 @@ def test_novcs_nocfg_init(runner, caplog, capsys): assert "File not found" in log.message assert os.path.exists("pycalver.toml") - with io.open("pycalver.toml", mode="r", encoding="utf-8") as fh: + with pl.Path("pycalver.toml").open(mode="r", encoding="utf-8") as fh: cfg_content = fh.read() base_str = config.DEFAULT_TOML_BASE_TMPL.format(initial_version=config._initial_version()) @@ -248,7 +247,7 @@ def test_novcs_setupcfg_init(runner): result = runner.invoke(pycalver.cli, ['init', "--verbose"]) assert result.exit_code == 0 - with io.open("setup.cfg", mode="r", encoding="utf-8") as fh: + with pl.Path("setup.cfg").open(mode="r", encoding="utf-8") as fh: cfg_content = fh.read() base_str = config.DEFAULT_CONFIGPARSER_BASE_TMPL.format( @@ -268,7 +267,7 @@ def test_novcs_pyproject_init(runner): result = runner.invoke(pycalver.cli, ['init', "--verbose"]) assert result.exit_code == 0 - with io.open("pyproject.toml", mode="r", encoding="utf-8") as fh: + with pl.Path("pyproject.toml").open(mode="r", encoding="utf-8") as fh: cfg_content = fh.read() base_str = config.DEFAULT_TOML_BASE_TMPL.format(initial_version=config._initial_version()) @@ -367,14 +366,14 @@ def test_novcs_bump(runner): calver = config._initial_version()[:7] - with io.open("README.md") as fh: + with pl.Path("README.md").open() as fh: content = fh.read() assert calver + ".0002-alpha !\n" in content result = runner.invoke(pycalver.cli, ['bump', "--verbose", "--release", "beta"]) assert result.exit_code == 0 - with io.open("README.md") as fh: + with pl.Path("README.md").open() as fh: content = fh.read() assert calver + ".0003-beta !\n" in content @@ -391,7 +390,7 @@ def test_git_bump(runner): calver = config._initial_version()[:7] - with io.open("README.md") as fh: + with pl.Path("README.md").open() as fh: content = fh.read() assert calver + ".0002-alpha !\n" in content @@ -408,6 +407,6 @@ def test_hg_bump(runner): calver = config._initial_version()[:7] - with io.open("README.md") as fh: + with pl.Path("README.md").open() as fh: content = fh.read() assert calver + ".0002-alpha !\n" in content diff --git a/test/test_config.py b/test/test_config.py index 830d386..775b3e9 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -182,7 +182,12 @@ def test_parse_project_cfg(): assert cfg.tag is True assert cfg.push is True - assert set(cfg.file_patterns.keys()) == {"setup.py", "README.rst", "setup.cfg"} + assert set(cfg.file_patterns.keys()) == { + "setup.py", + "README.rst", + "setup.cfg", + "src/module_v*/__init__.py", + } def test_parse_toml_file(tmpdir): diff --git a/test/test_rewrite.py b/test/test_rewrite.py index 747bc05..61cad1c 100644 --- a/test/test_rewrite.py +++ b/test/test_rewrite.py @@ -1,5 +1,8 @@ +from pycalver import config from pycalver import rewrite +from . import util + REWRITE_FIXTURE = """ # SPDX-License-Identifier: MIT @@ -29,3 +32,38 @@ def test_rewrite_final(): assert "v201911.0003" not in "\n".join(old_lines) assert "None" not in "\n".join(new_lines) assert "v201911.0003-final" in "\n".join(new_lines) + + +def test_iter_file_paths(): + with util.Project(project="a") as project: + ctx = config.init_project_ctx(project.dir) + cfg = config.parse(ctx) + assert cfg + + file_paths = { + str(file_path) for file_path, patterns in rewrite._iter_file_paths(cfg.file_patterns) + } + + assert file_paths == { + "pycalver.toml", + "README.md", + } + + +def test_iter_file_globs(): + with util.Project(project="b") as project: + ctx = config.init_project_ctx(project.dir) + cfg = config.parse(ctx) + assert cfg + + file_paths = { + str(file_path) for file_path, patterns in rewrite._iter_file_paths(cfg.file_patterns) + } + + assert file_paths == { + "setup.cfg", + "setup.py", + "README.rst", + "src/module_v1/__init__.py", + "src/module_v2/__init__.py", + } diff --git a/test/util.py b/test/util.py index c921aa1..4ccf6ae 100644 --- a/test/util.py +++ b/test/util.py @@ -1,3 +1,4 @@ +import os import shlex import shutil import tempfile @@ -21,44 +22,67 @@ class Shell: _MODULE_PATH = pl.Path(__file__) FIXTURES_DIR = _MODULE_PATH.parent / "fixtures" +FIXTURE_PATH_PARTS = [ + ["README.rst"], + ["README.md"], + ["setup.cfg"], + ["setup.py"], + ["pycalver.toml"], + ["src", "module_v1", "__init__.py"], + ["src", "module_v2", "__init__.py"], +] + class Project: - def __init__(self, project_prefix="a"): - if not project_prefix.endswith("_"): - project_prefix += "_" + def __init__(self, project="a"): + if not project.startswith("project_"): + project = "project_" + project tmpdir = pl.Path(tempfile.mkdtemp(prefix="pytest_")) self.tmpdir = tmpdir - _project_dir = tmpdir / "pycalver_project" - _project_dir.mkdir() + self.dir = tmpdir / "pycalver_project" + self.dir.mkdir() - for fpath in FIXTURES_DIR.glob(project_prefix + "*"): - fname = fpath.name[len(project_prefix) :] - shutil.copy(fpath, _project_dir / fname) + fixtures_subdir = FIXTURES_DIR / project - self.dir = _project_dir + for path_parts in FIXTURE_PATH_PARTS: + fixture_fpath = fixtures_subdir.joinpath(*path_parts) + if fixture_fpath.exists(): + project_fpath = self.dir.joinpath(*path_parts) + project_fpath.parent.mkdir(parents=True, exist_ok=True) + shutil.copy(str(fixture_fpath), str(project_fpath)) def __enter__(self): + self.prev_cwd = os.getcwd() + os.chdir(self.dir) return self def __exit__(self, *exc): shutil.rmtree(str(self.tmpdir)) + os.chdir(self.prev_cwd) return False def sh(self, cmd): shell = Shell(str(self.dir)) return shell(cmd) + def _vcs_addall(self, cmd): + added_file_paths = [] + for path_parts in FIXTURE_PATH_PARTS: + maybe_file_path = self.dir.joinpath(*path_parts) + if maybe_file_path.exists(): + self.sh(f"{cmd} add {str(maybe_file_path)}") + added_file_paths.append(maybe_file_path) + + assert len(added_file_paths) >= 2 + def git_init(self): self.sh("git init") - self.sh("git add pycalver.toml") - self.sh("git add README.md") + self._vcs_addall(cmd="git") self.sh("git commit -m 'initial commit'") def hg_init(self): - self.sh = Shell(str(self.dir)) self.sh("hg init") - self.sh("hg add pycalver.toml") - self.sh("hg add README.md") + self._vcs_addall(cmd="hg") self.sh("hg commit -m 'initial commit'")