From e1aaf7629b99dd0a4d821224db96d54fd4287c3b Mon Sep 17 00:00:00 2001 From: Manuel Barkhau Date: Fri, 18 Sep 2020 19:52:40 +0000 Subject: [PATCH] support for glob patterns --- CHANGELOG.md | 1 + LICENSE | 2 +- src/pycalver/__main__.py | 85 ++++++++++++++++++++++----------------- src/pycalver/config.py | 49 ++++++++++++++-------- src/pycalver2/patterns.py | 2 +- test/test_cli.py | 37 +++++++++-------- test/test_config.py | 43 +++++++++++++++++++- 7 files changed, 147 insertions(+), 72 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2f31a7..abdcaa6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Better support for optional parts. - New: Start `BUILD` parts at `1000` to avoid leading zero truncation. - New gitlab #10: `--pin-date` to keep date parts unchanged, and only increment non-date parts. + - New: enable globs for filenames in `pycalver:file_patterns` - Fix gitlab #8: Push tags only pushed tags, not actual commit. - Fix gitlab #9: Make commit message configurable. diff --git a/LICENSE b/LICENSE index 77be9d3..a7b7ab4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -MIT License Copyright (c) 2020 Manuel Barkhau (mbarkhau@gmail.com) +MIT License Copyright (c) 2018-2020 Manuel Barkhau (mbarkhau@gmail.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/pycalver/__main__.py b/src/pycalver/__main__.py index 54fc27d..bf7ae8e 100755 --- a/src/pycalver/__main__.py +++ b/src/pycalver/__main__.py @@ -17,13 +17,14 @@ import subprocess as sp import click import pycalver.cli as v1cli +import pycalver2.cli as v2cli import pycalver.version as v1version import pycalver2.version as v2version +import pycalver.rewrite as v1rewrite + from pycalver import vcs from pycalver import config -# import pycalver2.cli as v2cli - _VERBOSE = 0 @@ -149,31 +150,21 @@ def show(verbose: int = 0, fetch: bool = True) -> None: click.echo(f"PEP440 : {cfg.pep440_version}") -def _try_print_diff(cfg: config.Config, new_version: str) -> None: - try: - # TODO (mb 2020-09-05): version switch - diff = v1cli.get_diff(cfg, new_version) - # diff = v2cli.get_diff(cfg, new_version) - - if sys.stdout.isatty(): - for line in diff.splitlines(): - if line.startswith("+++") or line.startswith("---"): - click.echo(line) - elif line.startswith("+"): - click.echo("\u001b[32m" + line + "\u001b[0m") - elif line.startswith("-"): - click.echo("\u001b[31m" + line + "\u001b[0m") - elif line.startswith("@"): - click.echo("\u001b[36m" + line + "\u001b[0m") - else: - click.echo(line) - else: - click.echo(diff) - except Exception as ex: - # pylint:disable=broad-except; Mostly we expect IOError here, but - # could be other things and there's no option to recover anyway. - logger.error(str(ex), exc_info=True) - sys.exit(1) +def _print_diff(diff: str) -> None: + if sys.stdout.isatty(): + for line in diff.splitlines(): + if line.startswith("+++") or line.startswith("---"): + click.echo(line) + elif line.startswith("+"): + click.echo("\u001b[32m" + line + "\u001b[0m") + elif line.startswith("-"): + click.echo("\u001b[31m" + line + "\u001b[0m") + elif line.startswith("@"): + click.echo("\u001b[36m" + line + "\u001b[0m") + else: + click.echo(line) + else: + click.echo(diff) def _incr( @@ -229,10 +220,15 @@ def _bump( vcs.assert_not_dirty(vcs_api, filepaths, allow_dirty) try: - # TODO (mb 2020-09-05): version switch - v1cli.rewrite(cfg, new_version) - # v2cli.rewrite(cfg, new_version) + if cfg.is_new_pattern: + v2cli.rewrite(cfg, new_version) + else: + v1cli.rewrite(cfg, new_version) + except v1rewrite.NoPatternMatch as ex: + logger.error(str(ex)) + sys.exit(1) except Exception as ex: + # TODO (mb 2020-09-18): Investigate error messages logger.error(str(ex)) sys.exit(1) @@ -285,10 +281,10 @@ def init(verbose: int = 0, dry: bool = False) -> None: def _update_cfg_from_vcs(cfg: config.Config, fetch: bool) -> config.Config: all_tags = vcs.get_tags(fetch=fetch) - # TODO (mb 2020-09-05): version switch - cfg = v1cli.update_cfg_from_vcs(cfg, all_tags) - # cfg = v2cli.update_cfg_from_vcs(cfg, all_tags) - return cfg + if cfg.is_new_pattern: + return v2cli.update_cfg_from_vcs(cfg, all_tags) + else: + return v1cli.update_cfg_from_vcs(cfg, all_tags) @cli.command() @@ -375,7 +371,11 @@ def bump( ) if new_version is None: - is_semver = "{semver}" in cfg.version_pattern + is_semver = "{semver}" in cfg.version_pattern or ( + "MAJOR" in cfg.version_pattern + and "MAJOR" in cfg.version_pattern + and "PATCH" in cfg.version_pattern + ) has_semver_inc = major or minor or patch if is_semver and not has_semver_inc: logger.warning("bump --major/--minor/--patch required when using semver.") @@ -387,7 +387,20 @@ def bump( logger.info(f"New Version: {new_version}") if dry or verbose >= 2: - _try_print_diff(cfg, new_version) + try: + if cfg.is_new_pattern: + diff = v2cli.get_diff(cfg, new_version) + else: + diff = v1cli.get_diff(cfg, new_version) + _print_diff(diff) + except v1rewrite.NoPatternMatch as ex: + logger.error(str(ex)) + sys.exit(1) + except Exception as ex: + # pylint:disable=broad-except; Mostly we expect IOError here, but + # could be other things and there's no option to recover anyway. + logger.error(str(ex)) + sys.exit(1) if dry: return diff --git a/src/pycalver/config.py b/src/pycalver/config.py index bfe26f6..5576ac5 100644 --- a/src/pycalver/config.py +++ b/src/pycalver/config.py @@ -5,7 +5,7 @@ # SPDX-License-Identifier: MIT """Parse setup.cfg or pycalver.cfg files.""" -import os +import glob import typing as typ import logging import datetime as dt @@ -14,7 +14,8 @@ import configparser import toml import pathlib2 as pl -from . import version +import pycalver.version as v1version +import pycalver2.version as v2version logger = logging.getLogger("pycalver.config") @@ -82,9 +83,10 @@ class Config(typ.NamedTuple): pep440_version : str commit_message : str - commit: bool - tag : bool - push : bool + commit : bool + tag : bool + push : bool + is_new_pattern: bool file_patterns: PatternsByGlob @@ -93,12 +95,13 @@ def _debug_str(cfg: Config) -> str: cfg_str_parts = [ "Config Parsed: Config(", f"current_version='{cfg.current_version}'", - "version_pattern='{pycalver}'", + f"version_pattern='{cfg.version_pattern}'", f"pep440_version='{cfg.pep440_version}'", f"commit_message='{cfg.commit_message}'", f"commit={cfg.commit}", f"tag={cfg.tag}", f"push={cfg.push}", + f"is_new_pattern={cfg.is_new_pattern}", "file_patterns={", ] @@ -197,7 +200,7 @@ def _normalize_file_patterns(raw_cfg: RawConfig) -> FilePatterns: """ version_str : str = raw_cfg['current_version'] version_pattern: str = raw_cfg['version_pattern'] - pep440_version : str = version.to_pep440(version_str) + pep440_version : str = v1version.to_pep440(version_str) file_patterns: FilePatterns if 'file_patterns' in raw_cfg: @@ -205,9 +208,12 @@ def _normalize_file_patterns(raw_cfg: RawConfig) -> FilePatterns: else: file_patterns = {} - for filepath, patterns in list(file_patterns.items()): - if not os.path.exists(filepath): - logger.warning(f"Invalid config, no such file: {filepath}") + for filepath_glob, patterns in list(file_patterns.items()): + filepaths = glob.glob(filepath_glob) + if not filepaths: + logger.warning(f"Invalid config, no such file: {filepath_glob}") + # fallback to treating it as a simple path + filepaths = [filepath_glob] normalized_patterns: typ.List[str] = [] for pattern in patterns: @@ -219,11 +225,12 @@ def _normalize_file_patterns(raw_cfg: RawConfig) -> FilePatterns: elif version_pattern == "{semver}": normalized_pattern = normalized_pattern.replace("{pep440_version}", "{semver}") elif "{pep440_version}" in pattern: - logger.warning(f"Invalid config, cannot match '{pattern}' for '{filepath}'.") + logger.warning(f"Invalid config, cannot match '{pattern}' for '{filepath_glob}'.") logger.warning(f"No mapping of '{version_pattern}' to '{pep440_version}'") normalized_patterns.append(normalized_pattern) - file_patterns[filepath] = normalized_patterns + for filepath in filepaths: + file_patterns[filepath] = normalized_patterns return file_patterns @@ -237,18 +244,27 @@ def _parse_config(raw_cfg: RawConfig) -> Config: version_str: str = raw_cfg['current_version'] version_str = raw_cfg['current_version'] = version_str.strip("'\" ") - # TODO (mb 2020-09-06): new style pattern by default - # version_pattern: str = raw_cfg.get('version_pattern', "vYYYY0M.BUILD[-TAG]") version_pattern: str = raw_cfg.get('version_pattern', "{pycalver}") version_pattern = raw_cfg['version_pattern'] = version_pattern.strip("'\" ") commit_message: str = raw_cfg.get('commit_message', DEFAULT_COMMIT_MESSAGE) commit_message = raw_cfg['commit_message'] = commit_message.strip("'\" ") + + is_new_pattern = not ("{" in version_pattern or "}" in version_pattern) + + # TODO (mb 2020-09-18): Validate Pattern + # detect YY with WW or UU -> suggest GG with VV + # detect YYMM -> suggest YY0M + # detect YYWW -> suggest YY0W + # NOTE (mb 2019-01-05): Provoke ValueError if version_pattern # and current_version are not compatible. - version.parse_version_info(version_str, version_pattern) + if is_new_pattern: + v2version.parse_version_info(version_str, version_pattern) + else: + v1version.parse_version_info(version_str, version_pattern) - pep440_version = version.to_pep440(version_str) + pep440_version = v1version.to_pep440(version_str) commit = raw_cfg['commit'] tag = raw_cfg['tag'] @@ -275,6 +291,7 @@ def _parse_config(raw_cfg: RawConfig) -> Config: commit=commit, tag=tag, push=push, + is_new_pattern=is_new_pattern, file_patterns=file_patterns, ) logger.debug(_debug_str(cfg)) diff --git a/src/pycalver2/patterns.py b/src/pycalver2/patterns.py index 2b27506..8219422 100644 --- a/src/pycalver2/patterns.py +++ b/src/pycalver2/patterns.py @@ -236,7 +236,7 @@ def _replace_pattern_parts(pattern: str) -> str: ) last_start_idx = start_idx - return "(?P" + result_pattern + ")" + return result_pattern def compile_pattern_str(pattern: str) -> str: diff --git a/test/test_cli.py b/test/test_cli.py index 2e99fde..118ff55 100644 --- a/test/test_cli.py +++ b/test/test_cli.py @@ -98,7 +98,7 @@ def test_incr_default(runner): result = runner.invoke(cli, ['test', "-vv", old_version]) assert result.exit_code == 0 - new_version = initial_version.replace(".0001-alpha", ".11000-alpha") + new_version = initial_version.replace(".1001-alpha", ".11000-alpha") assert f"Version: {new_version}\n" in result.output @@ -152,7 +152,7 @@ def test_incr_to_beta(runner): result = runner.invoke(cli, ['test', old_version, "-vv", "--release", "beta"]) assert result.exit_code == 0 - new_version = initial_version.replace(".0001-alpha", ".11000-beta") + new_version = initial_version.replace(".1001-alpha", ".11000-beta") assert f"Version: {new_version}\n" in result.output @@ -162,7 +162,7 @@ def test_incr_to_final(runner): result = runner.invoke(cli, ['test', old_version, "-vv", "--release", "final"]) assert result.exit_code == 0 - new_version = initial_version.replace(".0001-alpha", ".11000") + new_version = initial_version.replace(".1001-alpha", ".11000") assert f"Version: {new_version}\n" in result.output @@ -178,8 +178,8 @@ def _add_project_files(*files): with pl.Path("README.md").open(mode="wt", encoding="utf-8") as fobj: fobj.write( """ - Hello World v201701.0002-alpha ! - aka. 201701.2a0 ! + Hello World v201701.1002-alpha ! + aka. 201701.1002a0 ! """ ) @@ -337,8 +337,8 @@ def test_git_tag_eval(runner): result = runner.invoke(cli, ['init', "-vv"]) assert result.exit_code == 0 initial_version = config._initial_version() - tag_version = initial_version.replace(".0001-alpha", ".0123-beta") - tag_version_pep440 = tag_version[1:7] + ".123b0" + tag_version = initial_version.replace(".1001-alpha", ".1123-beta") + tag_version_pep440 = tag_version[1:7] + ".1123b0" shell("git", "tag", "--annotate", tag_version, "--message", f"bump version to {tag_version}") @@ -357,8 +357,8 @@ def test_hg_tag_eval(runner): result = runner.invoke(cli, ['init', "-vv"]) assert result.exit_code == 0 initial_version = config._initial_version() - tag_version = initial_version.replace(".0001-alpha", ".0123-beta") - tag_version_pep440 = tag_version[1:7] + ".123b0" + tag_version = initial_version.replace(".1001-alpha", ".1123-beta") + tag_version_pep440 = tag_version[1:7] + ".1123b0" shell("hg", "tag", tag_version, "--message", f"bump version to {tag_version}") @@ -377,20 +377,20 @@ def test_novcs_bump(runner): result = runner.invoke(cli, ['bump', "-vv"]) assert result.exit_code == 0 - calver = config._initial_version()[:7] + calver = config._initial_version().split(".")[0] with pl.Path("README.md").open() as fobj: content = fobj.read() - assert calver + ".0002-alpha !\n" in content - assert calver[1:] + ".2a0 !\n" in content + assert calver + ".1002-alpha !\n" in content + assert calver[1:] + ".1002a0 !\n" in content result = runner.invoke(cli, ['bump', "-vv", "--release", "beta"]) assert result.exit_code == 0 with pl.Path("README.md").open() as fobj: content = fobj.read() - assert calver + ".0003-beta !\n" in content - assert calver[1:] + ".3b0 !\n" in content + assert calver + ".1003-beta !\n" in content + assert calver[1:] + ".1003b0 !\n" in content def test_git_bump(runner): @@ -410,7 +410,7 @@ def test_git_bump(runner): with pl.Path("README.md").open() as fobj: content = fobj.read() - assert calver + ".0002-alpha !\n" in content + assert calver + ".1002-alpha !\n" in content def test_hg_bump(runner): @@ -430,7 +430,7 @@ def test_hg_bump(runner): with pl.Path("README.md").open() as fobj: content = fobj.read() - assert calver + ".0002-alpha !\n" in content + assert calver + ".1002-alpha !\n" in content def test_empty_git_bump(runner, caplog): @@ -530,3 +530,8 @@ def test_bump_semver_diff(runner, caplog): assert "+++ setup.cfg" in out_lines assert "-current_version = \"0.1.0\"" in out_lines assert f"+current_version = \"{expected}\"" in out_lines + + +# def test_custom_commit_message(runner): +# # TODO (mb 2020-09-18): +# assert False diff --git a/test/test_config.py b/test/test_config.py index bae5ade..da611b1 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -63,6 +63,26 @@ setup.cfg = """ +NEW_PATTERN_CFG_FIXTURE = """ +[pycalver] +current_version = "v201808.1456-beta" +version_pattern = "vYYYY0M.BUILD[-TAG]" +commit_message = "bump version to {new_version}" +commit = True +tag = True +push = True + +[pycalver:file_patterns] +setup.py = + {version} + {pep440_version} +setup.cfg = + current_version = "{version}" +src/project/*.py = + Copyright (c) 2018-YYYY +""" + + def mk_buf(text): buf = io.StringIO() buf.write(text) @@ -104,7 +124,7 @@ def test_parse_toml_2(): assert cfg.file_patterns["pycalver.toml"] == ['current_version = "{semver}"'] -def test_parse_cfg(): +def test_parse_v1_cfg(): buf = mk_buf(SETUP_CFG_FIXTURE) raw_cfg = config._parse_cfg(buf) @@ -120,6 +140,25 @@ def test_parse_cfg(): assert cfg.file_patterns["setup.cfg"] == ['current_version = "{pycalver}"'] +def test_parse_v2_cfg(): + buf = mk_buf(NEW_PATTERN_CFG_FIXTURE) + + raw_cfg = config._parse_cfg(buf) + cfg = config._parse_config(raw_cfg) + assert cfg.current_version == "v201808.1456-beta" + assert cfg.commit_message == "bump version to {new_version}" + assert cfg.commit is True + assert cfg.tag is True + assert cfg.push is True + + assert "setup.py" in cfg.file_patterns + assert "setup.cfg" in cfg.file_patterns + # TODO (mb 2020-09-18): + # assert cfg.file_patterns["setup.py" ] == ["vYYYY0M.BUILD[-TAG]", "YYYY0M.BLD[PYTAGNUM]"] + # assert cfg.file_patterns["setup.cfg" ] == ['current_version = "vYYYY0M.BUILD[-TAG]"'] + # assert cfg.file_patterns["src/project/*.py"] == ['Copyright (c) 2018-YYYY"'] + + def test_parse_default_toml(): project_path = util.FIXTURES_DIR / "project_a" @@ -295,7 +334,7 @@ def test_parse_missing_version(tmpdir): "\n".join( ( "[pycalver]", - # f"current_version = v201808.0001-dev", + # f"current_version = v201808.1001-dev", "commit = False", ) )