allow globs in file_patterns

This commit is contained in:
Manuel Barkhau 2019-02-21 15:41:06 +01:00
parent 710019ee44
commit 1e633a2a7d
10 changed files with 136 additions and 43 deletions

View file

@ -143,7 +143,7 @@ setup.cfg =
current_version = {pycalver} current_version = {pycalver}
setup.py = setup.py =
version="{pep440_pycalver}" version="{pep440_pycalver}"
myproject/__init__.py = src/myproject_v*/__init__.py =
__version__ = "{pycalver}" __version__ = "{pycalver}"
README.md = README.md =
[PyCalVer {calver}{build}{release}] [PyCalVer {calver}{build}{release}]
@ -167,8 +167,14 @@ INFO - New Version: v201902.0002-beta
+[![Version v201902.0002][version_img]][version_ref] +[![Version v201902.0002][version_img]][version_ref]
[![PyPI Releases][pypi_img]][pypi_ref] [![PyPI Releases][pypi_img]][pypi_ref]
--- myproject/__init__.py --- src/myproject_v1/__init__.py
+++ myproject/__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 @@ @@ -1,1 +1,1 @@
-__version__ = "v201901.0001-beta" -__version__ = "v201901.0001-beta"
+__version__ = "v201902.0002-beta" +__version__ = "v201902.0002-beta"

View file

@ -20,7 +20,8 @@ from . import version
log = logging.getLogger("pycalver.config") 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"] SUPPORTED_CONFIGS = ["setup.cfg", "pyproject.toml", "pycalver.toml"]
@ -84,7 +85,7 @@ class Config(typ.NamedTuple):
tag : bool tag : bool
push : bool push : bool
file_patterns: PatternsByFilePath file_patterns: PatternsByGlob
def _debug_str(cfg: Config) -> str: 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: 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_str : str = raw_cfg['current_version']
version_pattern: str = raw_cfg['version_pattern'] version_pattern: str = raw_cfg['version_pattern']
pep440_version : str = version.to_pep440(version_str) pep440_version : str = version.to_pep440(version_str)

View file

@ -6,9 +6,11 @@
"""Rewrite files, updating occurences of version strings.""" """Rewrite files, updating occurences of version strings."""
import io import io
import glob
import difflib import difflib
import logging import logging
import typing as typ import typing as typ
import pathlib2 as pl
from . import parse from . import parse
from . import config from . import config
@ -88,8 +90,17 @@ def rfd_from_content(patterns: typ.List[str], new_version: str, content: str) ->
return RewrittenFileData("<path>", line_sep, old_lines, new_lines) return RewrittenFileData("<path>", 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( def iter_rewritten(
file_patterns: config.PatternsByFilePath, new_version: str file_patterns: config.PatternsByGlob, new_version: str
) -> typ.Iterable[RewrittenFileData]: ) -> typ.Iterable[RewrittenFileData]:
r'''Iterate over files with version string replaced. r'''Iterate over files with version string replaced.
@ -111,12 +122,12 @@ def iter_rewritten(
''' '''
fh: typ.IO[str] fh: typ.IO[str]
for filepath, patterns in file_patterns.items(): for file_path, patterns in _iter_file_paths(file_patterns):
with io.open(filepath, mode="rt", encoding="utf-8") as fh: with file_path.open(mode="rt", encoding="utf-8") as fh:
content = fh.read() content = fh.read()
rfd = rfd_from_content(patterns, new_version, content) 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]: def diff_lines(rfd: RewrittenFileData) -> typ.List[str]:
@ -137,7 +148,7 @@ def diff_lines(rfd: RewrittenFileData) -> typ.List[str]:
return list(lines) 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. r"""Generate diffs of rewritten files.
>>> file_patterns = {"src/pycalver/__init__.py": ['__version__ = "{pycalver}"']} >>> file_patterns = {"src/pycalver/__init__.py": ['__version__ = "{pycalver}"']}
@ -152,22 +163,21 @@ def diff(new_version: str, file_patterns: config.PatternsByFilePath) -> str:
""" """
full_diff = "" full_diff = ""
file_path: str
fh: typ.IO[str] fh: typ.IO[str]
for file_path, patterns in sorted(file_patterns.items()): for file_path, patterns in sorted(_iter_file_paths(file_patterns)):
with io.open(file_path, mode="rt", encoding="utf-8") as fh: with file_path.open(mode="rt", encoding="utf-8") as fh:
content = fh.read() content = fh.read()
rfd = rfd_from_content(patterns, new_version, content) 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 += "\n".join(diff_lines(rfd)) + "\n"
full_diff = full_diff.rstrip("\n") full_diff = full_diff.rstrip("\n")
return full_diff 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.""" """Rewrite project files, updating each with the new version."""
fh: typ.IO[str] fh: typ.IO[str]

View file

@ -11,3 +11,5 @@ setup.py =
version="{pep440_version}" version="{pep440_version}"
README.rst = README.rst =
img.shields.io/badge/PyCalVer-{calver}{build}--{release}-blue img.shields.io/badge/PyCalVer-{calver}{build}--{release}-blue
src/module_v*/__init__.py =
__version__ = "{version}"

View file

@ -0,0 +1,2 @@
__version__ = "v201307.0456-beta"

View file

@ -0,0 +1,2 @@
__version__ = "v201307.0456-beta"

View file

@ -1,5 +1,4 @@
import os import os
import io
import time import time
import shutil import shutil
import pathlib2 as pl import pathlib2 as pl
@ -166,19 +165,19 @@ def test_incr_invalid(runner):
def _add_project_files(*files): def _add_project_files(*files):
if "README.md" in 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") fh.write("Hello World v201701.0002-alpha !\n")
if "setup.cfg" in files: 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) fh.write(SETUP_CFG_FIXTURE)
if "pycalver.toml" in files: 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) fh.write(PYCALVER_TOML_FIXTURE)
if "pyproject.toml" in files: 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) 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 "File not found" in log.message
assert os.path.exists("pycalver.toml") 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() cfg_content = fh.read()
base_str = config.DEFAULT_TOML_BASE_TMPL.format(initial_version=config._initial_version()) 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"]) result = runner.invoke(pycalver.cli, ['init', "--verbose"])
assert result.exit_code == 0 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() cfg_content = fh.read()
base_str = config.DEFAULT_CONFIGPARSER_BASE_TMPL.format( 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"]) result = runner.invoke(pycalver.cli, ['init', "--verbose"])
assert result.exit_code == 0 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() cfg_content = fh.read()
base_str = config.DEFAULT_TOML_BASE_TMPL.format(initial_version=config._initial_version()) 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] calver = config._initial_version()[:7]
with io.open("README.md") as fh: with pl.Path("README.md").open() as fh:
content = fh.read() content = fh.read()
assert calver + ".0002-alpha !\n" in content assert calver + ".0002-alpha !\n" in content
result = runner.invoke(pycalver.cli, ['bump', "--verbose", "--release", "beta"]) result = runner.invoke(pycalver.cli, ['bump', "--verbose", "--release", "beta"])
assert result.exit_code == 0 assert result.exit_code == 0
with io.open("README.md") as fh: with pl.Path("README.md").open() as fh:
content = fh.read() content = fh.read()
assert calver + ".0003-beta !\n" in content assert calver + ".0003-beta !\n" in content
@ -391,7 +390,7 @@ def test_git_bump(runner):
calver = config._initial_version()[:7] calver = config._initial_version()[:7]
with io.open("README.md") as fh: with pl.Path("README.md").open() as fh:
content = fh.read() content = fh.read()
assert calver + ".0002-alpha !\n" in content assert calver + ".0002-alpha !\n" in content
@ -408,6 +407,6 @@ def test_hg_bump(runner):
calver = config._initial_version()[:7] calver = config._initial_version()[:7]
with io.open("README.md") as fh: with pl.Path("README.md").open() as fh:
content = fh.read() content = fh.read()
assert calver + ".0002-alpha !\n" in content assert calver + ".0002-alpha !\n" in content

View file

@ -182,7 +182,12 @@ def test_parse_project_cfg():
assert cfg.tag is True assert cfg.tag is True
assert cfg.push 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): def test_parse_toml_file(tmpdir):

View file

@ -1,5 +1,8 @@
from pycalver import config
from pycalver import rewrite from pycalver import rewrite
from . import util
REWRITE_FIXTURE = """ REWRITE_FIXTURE = """
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
@ -29,3 +32,38 @@ def test_rewrite_final():
assert "v201911.0003" not in "\n".join(old_lines) assert "v201911.0003" not in "\n".join(old_lines)
assert "None" not in "\n".join(new_lines) assert "None" not in "\n".join(new_lines)
assert "v201911.0003-final" 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",
}

View file

@ -1,3 +1,4 @@
import os
import shlex import shlex
import shutil import shutil
import tempfile import tempfile
@ -21,44 +22,67 @@ class Shell:
_MODULE_PATH = pl.Path(__file__) _MODULE_PATH = pl.Path(__file__)
FIXTURES_DIR = _MODULE_PATH.parent / "fixtures" 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: class Project:
def __init__(self, project_prefix="a"): def __init__(self, project="a"):
if not project_prefix.endswith("_"): if not project.startswith("project_"):
project_prefix += "_" project = "project_" + project
tmpdir = pl.Path(tempfile.mkdtemp(prefix="pytest_")) tmpdir = pl.Path(tempfile.mkdtemp(prefix="pytest_"))
self.tmpdir = tmpdir self.tmpdir = tmpdir
_project_dir = tmpdir / "pycalver_project" self.dir = tmpdir / "pycalver_project"
_project_dir.mkdir() self.dir.mkdir()
for fpath in FIXTURES_DIR.glob(project_prefix + "*"): fixtures_subdir = FIXTURES_DIR / project
fname = fpath.name[len(project_prefix) :]
shutil.copy(fpath, _project_dir / fname)
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): def __enter__(self):
self.prev_cwd = os.getcwd()
os.chdir(self.dir)
return self return self
def __exit__(self, *exc): def __exit__(self, *exc):
shutil.rmtree(str(self.tmpdir)) shutil.rmtree(str(self.tmpdir))
os.chdir(self.prev_cwd)
return False return False
def sh(self, cmd): def sh(self, cmd):
shell = Shell(str(self.dir)) shell = Shell(str(self.dir))
return shell(cmd) 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): def git_init(self):
self.sh("git init") self.sh("git init")
self.sh("git add pycalver.toml") self._vcs_addall(cmd="git")
self.sh("git add README.md")
self.sh("git commit -m 'initial commit'") self.sh("git commit -m 'initial commit'")
def hg_init(self): def hg_init(self):
self.sh = Shell(str(self.dir))
self.sh("hg init") self.sh("hg init")
self.sh("hg add pycalver.toml") self._vcs_addall(cmd="hg")
self.sh("hg add README.md")
self.sh("hg commit -m 'initial commit'") self.sh("hg commit -m 'initial commit'")