# This file is part of the pycalver project # https://github.com/mbarkhau/pycalver # # Copyright (c) 2018-2020 Manuel Barkhau (mbarkhau@gmail.com) - MIT License # SPDX-License-Identifier: MIT """Rewrite files, updating occurences of version strings.""" import io import typing as typ import logging import pycalver.rewrite as v1rewrite import pycalver2.version as v2version import pycalver2.patterns as v2patterns from pycalver import parse from pycalver import config logger = logging.getLogger("pycalver2.rewrite") def rewrite_lines( pattern_strs: typ.List[str], new_vinfo : v2version.VersionInfo, old_lines : typ.List[str], ) -> typ.List[str]: """Replace occurances of pattern_strs in old_lines with new_vinfo. >>> new_vinfo = v2version.parse_version_info("v201811.0123-beta") >>> pattern_strs = ['__version__ = "vYYYY0M.BUILD[-TAG]"'] >>> old_lines = ['__version__ = "v201809.0002-alpha" '] >>> rewrite_lines(pattern_strs, new_vinfo, old_lines) ['__version__ = "v201811.0123-beta" '] >>> old_lines = ['__version__ = "v201809.0002-alpha" # comment'] >>> rewrite_lines(pattern_strs, new_vinfo, old_lines) ['__version__ = "v201811.0123-beta" # comment'] >>> pattern_strs = ['__version__ = "YYYY0M.BLD[PYTAGNUM]"'] >>> old_lines = ['__version__ = "201809.2a0"'] >>> rewrite_lines(pattern_strs, new_vinfo, old_lines) ['__version__ = "201811.123b0"'] """ new_lines = old_lines[:] found_patterns = set() patterns = [v2patterns.compile_pattern(p) for p in pattern_strs] matches = parse.iter_matches(old_lines, patterns) for match in matches: found_patterns.add(match.pattern.raw) replacement = v2version.format_version(new_vinfo, match.pattern.raw) span_l, span_r = match.span new_line = match.line[:span_l] + replacement + match.line[span_r:] new_lines[match.lineno] = new_line non_matched_patterns = set(pattern_strs) - found_patterns if non_matched_patterns: for non_matched_pattern in non_matched_patterns: logger.error(f"No match for pattern '{non_matched_pattern}'") compiled_pattern_str = v2patterns.compile_pattern_str(non_matched_pattern) logger.error(f"Pattern compiles to regex '{compiled_pattern_str}'") raise v1rewrite.NoPatternMatch("Invalid pattern(s)") else: return new_lines def rfd_from_content( pattern_strs: typ.List[str], new_vinfo : v2version.VersionInfo, content : str, ) -> v1rewrite.RewrittenFileData: r"""Rewrite pattern occurrences with version string. >>> new_vinfo = v2version.parse_version_info("v201809.0123") >>> pattern_strs = ['__version__ = "vYYYY0M.BUILD[-TAG]"'] >>> content = '__version__ = "v201809.0001-alpha"' >>> rfd = rfd_from_content(pattern_strs, new_vinfo, content) >>> rfd.new_lines ['__version__ = "v201809.0123"'] >>> >>> new_vinfo = v2version.parse_version_info("v1.2.3", "vMAJOR.MINOR.PATCH") >>> pattern_strs = ['__version__ = "vMAJOR.MINOR.PATCH"'] >>> content = '__version__ = "v1.2.2"' >>> rfd = rfd_from_content(pattern_strs, new_vinfo, content) >>> rfd.new_lines ['__version__ = "v1.2.3"'] """ line_sep = v1rewrite.detect_line_sep(content) old_lines = content.split(line_sep) new_lines = rewrite_lines(pattern_strs, new_vinfo, old_lines) return v1rewrite.RewrittenFileData("", line_sep, old_lines, new_lines) def iter_rewritten( file_patterns: config.PatternsByGlob, new_vinfo : v2version.VersionInfo, ) -> typ.Iterable[v1rewrite.RewrittenFileData]: r'''Iterate over files with version string replaced. >>> file_patterns = {"src/pycalver/__init__.py": ['__version__ = "vYYYY0M.BUILD[-TAG]"']} >>> new_vinfo = v2version.parse_version_info("v201809.0123") >>> rewritten_datas = iter_rewritten(file_patterns, new_vinfo) >>> rfd = list(rewritten_datas)[0] >>> assert rfd.new_lines == [ ... '# This file is part of the pycalver project', ... '# https://github.com/mbarkhau/pycalver', ... '#', ... '# Copyright (c) 2018-2020 Manuel Barkhau (mbarkhau@gmail.com) - MIT License', ... '# SPDX-License-Identifier: MIT', ... '"""PyCalVer: CalVer for Python Packages."""', ... '', ... '__version__ = "v201809.0123"', ... '', ... ] >>> ''' fobj: typ.IO[str] for file_path, pattern_strs in v1rewrite.iter_file_paths(file_patterns): with file_path.open(mode="rt", encoding="utf-8") as fobj: content = fobj.read() rfd = rfd_from_content(pattern_strs, new_vinfo, content) yield rfd._replace(path=str(file_path)) def diff( new_vinfo : v2version.VersionInfo, file_patterns: config.PatternsByGlob, ) -> str: r"""Generate diffs of rewritten files. >>> new_vinfo = v2version.parse_version_info("v201809.0123") >>> file_patterns = {"src/pycalver/__init__.py": ['__version__ = "vYYYY0M.BUILD[-TAG]"']} >>> diff_str = diff(new_vinfo, file_patterns) >>> lines = diff_str.split("\n") >>> lines[:2] ['--- src/pycalver/__init__.py', '+++ src/pycalver/__init__.py'] >>> assert lines[6].startswith('-__version__ = "v2') >>> assert not lines[6].startswith('-__version__ = "v201809.0123"') >>> lines[7] '+__version__ = "v201809.0123"' """ full_diff = "" fobj: typ.IO[str] for file_path, pattern_strs in sorted(v1rewrite.iter_file_paths(file_patterns)): with file_path.open(mode="rt", encoding="utf-8") as fobj: content = fobj.read() try: rfd = rfd_from_content(pattern_strs, new_vinfo, content) except v1rewrite.NoPatternMatch: # pylint:disable=raise-missing-from ; we support py2, so not an option errmsg = f"No patterns matched for '{file_path}'" raise v1rewrite.NoPatternMatch(errmsg) rfd = rfd._replace(path=str(file_path)) lines = v1rewrite.diff_lines(rfd) if len(lines) == 0: errmsg = f"No patterns matched for '{file_path}'" raise v1rewrite.NoPatternMatch(errmsg) full_diff += "\n".join(lines) + "\n" full_diff = full_diff.rstrip("\n") return full_diff def rewrite(file_patterns: config.PatternsByGlob, new_vinfo: v2version.VersionInfo) -> None: """Rewrite project files, updating each with the new version.""" fobj: typ.IO[str] for file_data in iter_rewritten(file_patterns, new_vinfo): new_content = file_data.line_sep.join(file_data.new_lines) with io.open(file_data.path, mode="wt", encoding="utf-8") as fobj: fobj.write(new_content)