much bugfixing

This commit is contained in:
Manuel Barkhau 2020-10-02 20:52:54 +00:00
parent 56c9f9b36c
commit 49e19fbf89
18 changed files with 687 additions and 451 deletions

View file

@ -23,7 +23,7 @@ The recommended approach to using `pylint-ignore` is:
# Overview # Overview
- [W0511: fixme (9x)](#w0511-fixme) - [W0511: fixme (8x)](#w0511-fixme)
- [W0703: broad-except (1x)](#w0703-broad-except) - [W0703: broad-except (1x)](#w0703-broad-except)
@ -44,37 +44,20 @@ The recommended approach to using `pylint-ignore` is:
``` ```
## File src/pycalver/vcs.py - Line 78 - W0511 (fixme) ## File src/pycalver/vcs.py - Line 80 - W0511 (fixme)
- `message: TODO (mb 2018-11-15): Detect encoding of output? Use chardet?` - `message: TODO (mb 2018-11-15): Detect encoding of output? Use chardet?`
- `author : Manuel Barkhau <mbarkhau@gmail.com>` - `author : Manuel Barkhau <mbarkhau@gmail.com>`
- `date : 2020-09-18T17:24:49` - `date : 2020-09-18T17:24:49`
``` ```
68: def __call__(self, cmd_name: str, env: Env = None, **kwargs: str) -> str: 69: def __call__(self, cmd_name: str, env: Env = None, **kwargs: str) -> str:
... ...
76: output_data: bytes = sp.check_output(cmd_str.split(), env=env, stderr=sp.STDOUT) 78: output_data: bytes = sp.check_output(cmd_parts, env=env, stderr=sp.STDOUT)
77: 79:
> 78: # TODO (mb 2018-11-15): Detect encoding of output? Use chardet? > 80: # TODO (mb 2018-11-15): Detect encoding of output? Use chardet?
79: _encoding = "utf-8" 81: _encoding = "utf-8"
80: return output_data.decode(_encoding) 82: return output_data.decode(_encoding)
```
## File test/test_config.py - Line 156 - W0511 (fixme)
- `message: TODO (mb 2020-09-18):`
- `author : Manuel Barkhau <mbarkhau@gmail.com>`
- `date : 2020-09-18T19:04:06`
```
143: def test_parse_v2_cfg():
...
154: assert "setup.py" in cfg.file_patterns
155: assert "setup.cfg" in cfg.file_patterns
> 156: # TODO (mb 2020-09-18):
157: # assert cfg.file_patterns["setup.py" ] == ["vYYYY0M.BUILD[-RELEASE]", "YYYY0M.BLD[PYTAGNUM]"]
158: # assert cfg.file_patterns["setup.cfg" ] == ['current_version = "vYYYY0M.BUILD[-RELEASE]"']
``` ```
@ -95,37 +78,37 @@ The recommended approach to using `pylint-ignore` is:
``` ```
## File src/pycalver/v1patterns.py - Line 214 - W0511 (fixme) ## File test/test_config.py - Line 170 - W0511 (fixme)
- `message: TODO (mb 2020-09-19): replace {version} etc with version_pattern` - `message: TODO (mb 2020-09-18):`
- `author : Manuel Barkhau <mbarkhau@gmail.com>` - `author : Manuel Barkhau <mbarkhau@gmail.com>`
- `date : 2020-09-19T16:24:10` - `date : 2020-09-18T19:04:06`
``` ```
201: def _compile_pattern_re(version_pattern: str, raw_pattern: str) -> typ.Pattern[str]: 156: def test_parse_v2_cfg():
... ...
212: escaped_pattern = escaped_pattern.replace(char, escaped) 168: assert "setup.cfg" in cfg.file_patterns
213: 169:
> 214: # TODO (mb 2020-09-19): replace {version} etc with version_pattern > 170: # TODO (mb 2020-09-18):
215: pattern_str = _replace_pattern_parts(escaped_pattern) 171: # raw_patterns_by_file = _parse_raw_patterns_by_file(cfg)
216: return re.compile(pattern_str) 172: # assert raw_patterns_by_file["setup.py" ] == ["vYYYY0M.BUILD[-RELEASE]", "YYYY0M.BLD[PYTAGNUM]"]
``` ```
## File src/pycalver/__main__.py - Line 250 - W0511 (fixme) ## File src/pycalver/__main__.py - Line 259 - W0511 (fixme)
- `message: TODO (mb 2020-09-18): Investigate error messages` - `message: TODO (mb 2020-09-18): Investigate error messages`
- `author : Manuel Barkhau <mbarkhau@gmail.com>` - `author : Manuel Barkhau <mbarkhau@gmail.com>`
- `date : 2020-09-19T16:24:10` - `date : 2020-09-19T16:24:10`
``` ```
222: def _bump( 231: def _bump(
... ...
248: sys.exit(1) 257: sys.exit(1)
249: except Exception as ex: 258: except Exception as ex:
> 250: # TODO (mb 2020-09-18): Investigate error messages > 259: # TODO (mb 2020-09-18): Investigate error messages
251: logger.error(str(ex)) 260: logger.error(str(ex))
252: sys.exit(1) 261: sys.exit(1)
``` ```
@ -146,53 +129,52 @@ The recommended approach to using `pylint-ignore` is:
``` ```
## File test/test_cli.py - Line 536 - W0511 (fixme) ## File test/test_cli.py - Line 599 - W0511 (fixme)
- `message: # TODO (mb 2020-09-18):` - `message: # TODO (mb 2020-09-18):`
- `author : Manuel Barkhau <mbarkhau@gmail.com>` - `author : Manuel Barkhau <mbarkhau@gmail.com>`
- `date : 2020-09-18T19:35:32` - `date : 2020-09-18T19:35:32`
``` ```
534: 597:
535: # def test_custom_commit_message(runner): 598: # def test_custom_commit_message(runner):
> 536: # # TODO (mb 2020-09-18): > 599: # # TODO (mb 2020-09-18):
537: # assert False 600: # assert False
``` ```
## File src/pycalver/v2version.py - Line 551 - W0511 (fixme) ## File src/pycalver/v2version.py - Line 616 - W0511 (fixme)
- `message: TODO (mb 2020-09-20): New Rollover Behaviour:` - `message: TODO (mb 2020-09-20): New Rollover Behaviour:`
- `author : Manuel Barkhau <mbarkhau@gmail.com>` - `author : Manuel Barkhau <mbarkhau@gmail.com>`
- `date : 2020-09-20T17:36:38` - `date : 2020-09-20T17:36:38`
``` ```
508: def incr( 578: def incr(
... ...
549: cur_vinfo = cur_vinfo._replace(patch=cur_vinfo.patch + 1) 614: )
550: 615:
> 551: # TODO (mb 2020-09-20): New Rollover Behaviour: > 616: # TODO (mb 2020-09-20): New Rollover Behaviour:
552: # Reset major, minor, patch to zero if any part to the left of it is incremented 617: # Reset major, minor, patch to zero if any part to the left of it is incremented
553: 618:
``` ```
# W0703: broad-except # W0703: broad-except
## File src/pycalver/__main__.py - Line 249 - W0703 (broad-except) ## File src/pycalver/__main__.py - Line 258 - W0703 (broad-except)
- `message: Catching too general exception Exception` - `message: Catching too general exception Exception`
- `author : Manuel Barkhau <mbarkhau@gmail.com>` - `author : Manuel Barkhau <mbarkhau@gmail.com>`
- `date : 2020-09-05T14:30:17` - `date : 2020-09-05T14:30:17`
``` ```
222: def _bump( 231: def _bump(
... ...
247: logger.error(str(ex)) 256: logger.error(str(ex))
248: sys.exit(1) 257: sys.exit(1)
> 249: except Exception as ex: > 258: except Exception as ex:
250: # TODO (mb 2020-09-18): Investigate error messages 259: # TODO (mb 2020-09-18): Investigate error messages
251: logger.error(str(ex)) 260: logger.error(str(ex))
``` ```

View file

@ -137,7 +137,7 @@ output-format = colorized
max-locals = 21 max-locals = 21
# Maximum number of arguments for function / method # Maximum number of arguments for function / method
max-args = 9 max-args = 12
good-names = logger,i,ex good-names = logger,i,ex

View file

@ -22,7 +22,9 @@ from . import v2cli
from . import config from . import config
from . import rewrite from . import rewrite
from . import version from . import version
from . import v1rewrite
from . import v1version from . import v1version
from . import v2rewrite
from . import v2version from . import v2version
from . import v1patterns from . import v1patterns
@ -93,10 +95,10 @@ def cli(verbose: int = 0) -> None:
f"{', '.join(VALID_RELEASE_VALUES)}." f"{', '.join(VALID_RELEASE_VALUES)}."
), ),
) )
@click.option("--major", is_flag=True, default=False, help="Increment major component.") @click.option("--major" , is_flag=True, default=False, help="Increment major component.")
@click.option("-m", "--minor", is_flag=True, default=False, help="Increment minor component.") @click.option("-m" , "--minor" , is_flag=True, default=False, help="Increment minor component.")
@click.option("-p", "--patch", is_flag=True, default=False, help="Increment patch component.") @click.option("-p" , "--patch" , is_flag=True, default=False, help="Increment patch component.")
@click.option("-r", "--release-num", is_flag=True, default=False, help="Increment release number.") @click.option("-r" , "--release-num", is_flag=True, default=False, help="Increment release number.")
@click.option("--pin-date", is_flag=True, default=False, help="Leave date components unchanged.") @click.option("--pin-date", is_flag=True, default=False, help="Leave date components unchanged.")
def test( def test(
old_version: str, old_version: str,
@ -145,8 +147,7 @@ def show(verbose: int = 0, fetch: bool = True) -> None:
"""Show current version of your project.""" """Show current version of your project."""
_configure_logging(verbose=max(_VERBOSE, verbose)) _configure_logging(verbose=max(_VERBOSE, verbose))
ctx: config.ProjectContext = config.init_project_ctx(project_path=".") _, cfg = config.init(project_path=".")
cfg: config.MaybeConfig = config.parse(ctx)
if cfg is None: if cfg is None:
logger.error("Could not parse configuration. Perhaps try 'pycalver init'.") logger.error("Could not parse configuration. Perhaps try 'pycalver init'.")
@ -188,7 +189,7 @@ def _print_diff(cfg: config.Config, new_version: str) -> None:
except Exception as ex: except Exception as ex:
# pylint:disable=broad-except; Mostly we expect IOError here, but # pylint:disable=broad-except; Mostly we expect IOError here, but
# could be other things and there's no option to recover anyway. # could be other things and there's no option to recover anyway.
logger.error(str(ex)) logger.error(str(ex), exc_info=True)
sys.exit(1) sys.exit(1)
@ -196,12 +197,12 @@ def _incr(
old_version: str, old_version: str,
raw_pattern: str, raw_pattern: str,
*, *,
release : str = None, release : str = None,
major : bool = False, major : bool = False,
minor : bool = False, minor : bool = False,
patch : bool = False, patch : bool = False,
release_num: bool = False, release_num: bool = False,
pin_date: bool = False, pin_date : bool = False,
) -> typ.Optional[str]: ) -> typ.Optional[str]:
v1_parts = list(v1patterns.PART_PATTERNS) + list(v1patterns.FULL_PART_FORMATS) v1_parts = list(v1patterns.PART_PATTERNS) + list(v1patterns.FULL_PART_FORMATS)
has_v1_part = any("{" + part + "}" in raw_pattern for part in v1_parts) has_v1_part = any("{" + part + "}" in raw_pattern for part in v1_parts)
@ -250,9 +251,11 @@ def _bump(
try: try:
if cfg.is_new_pattern: if cfg.is_new_pattern:
v2cli.rewrite_files(cfg, new_version) new_v2_vinfo = v2version.parse_version_info(new_version, cfg.version_pattern)
v2rewrite.rewrite_files(cfg.file_patterns, new_v2_vinfo)
else: else:
v1cli.rewrite_files(cfg, new_version) new_v1_vinfo = v1version.parse_version_info(new_version, cfg.version_pattern)
v1rewrite.rewrite_files(cfg.file_patterns, new_v1_vinfo)
except rewrite.NoPatternMatch as ex: except rewrite.NoPatternMatch as ex:
logger.error(str(ex)) logger.error(str(ex))
sys.exit(1) sys.exit(1)
@ -291,8 +294,7 @@ def init(verbose: int = 0, dry: bool = False) -> None:
"""Initialize [pycalver] configuration.""" """Initialize [pycalver] configuration."""
_configure_logging(verbose=max(_VERBOSE, verbose)) _configure_logging(verbose=max(_VERBOSE, verbose))
ctx: config.ProjectContext = config.init_project_ctx(project_path=".") ctx, cfg = config.init(project_path=".")
cfg: config.MaybeConfig = config.parse(ctx)
if cfg: if cfg:
logger.error(f"Configuration already initialized in {ctx.config_rel_path}") logger.error(f"Configuration already initialized in {ctx.config_rel_path}")
@ -355,10 +357,10 @@ def _update_cfg_from_vcs(cfg: config.Config, fetch: bool) -> config.Config:
"to files with version strings." "to files with version strings."
), ),
) )
@click.option("--major", is_flag=True, default=False, help="Increment major component.") @click.option("--major" , is_flag=True, default=False, help="Increment major component.")
@click.option("-m", "--minor", is_flag=True, default=False, help="Increment minor component.") @click.option("-m" , "--minor" , is_flag=True, default=False, help="Increment minor component.")
@click.option("-p", "--patch", is_flag=True, default=False, help="Increment patch component.") @click.option("-p" , "--patch" , is_flag=True, default=False, help="Increment patch component.")
@click.option("-r", "--release-num", is_flag=True, default=False, help="Increment release number.") @click.option("-r" , "--release-num", is_flag=True, default=False, help="Increment release number.")
@click.option("--pin-date", is_flag=True, default=False, help="Leave date components unchanged.") @click.option("--pin-date", is_flag=True, default=False, help="Leave date components unchanged.")
def bump( def bump(
release : typ.Optional[str] = None, release : typ.Optional[str] = None,
@ -379,8 +381,7 @@ def bump(
if release: if release:
_validate_release_tag(release) _validate_release_tag(release)
ctx: config.ProjectContext = config.init_project_ctx(project_path=".") _, cfg = config.init(project_path=".")
cfg: config.MaybeConfig = config.parse(ctx)
if cfg is None: if cfg is None:
logger.error("Could not parse configuration. Perhaps try 'pycalver init'.") logger.error("Could not parse configuration. Perhaps try 'pycalver init'.")

View file

@ -188,6 +188,8 @@ def _parse_cfg(cfg_buffer: typ.IO[str]) -> RawConfig:
raw_cfg['file_patterns'] = dict(_parse_cfg_file_patterns(cfg_parser)) raw_cfg['file_patterns'] = dict(_parse_cfg_file_patterns(cfg_parser))
_set_raw_config_defaults(raw_cfg)
return raw_cfg return raw_cfg
@ -198,6 +200,8 @@ def _parse_toml(cfg_buffer: typ.IO[str]) -> RawConfig:
for option, default_val in BOOL_OPTIONS.items(): for option, default_val in BOOL_OPTIONS.items():
raw_cfg[option] = raw_cfg.get(option, default_val) raw_cfg[option] = raw_cfg.get(option, default_val)
_set_raw_config_defaults(raw_cfg)
return raw_cfg return raw_cfg
@ -227,9 +231,7 @@ def _compile_v1_file_patterns(raw_cfg: RawConfig) -> typ.Iterable[FilePatternsIt
raw_patterns_by_file: RawPatternsByFile = raw_cfg['file_patterns'] raw_patterns_by_file: RawPatternsByFile = raw_cfg['file_patterns']
for filepath, raw_patterns in _iter_glob_expanded_file_patterns(raw_patterns_by_file): for filepath, raw_patterns in _iter_glob_expanded_file_patterns(raw_patterns_by_file):
compiled_patterns = [ compiled_patterns = v1patterns.compile_patterns(version_pattern, raw_patterns)
v1patterns.compile_pattern(version_pattern, raw_pattern) for raw_pattern in raw_patterns
]
yield filepath, compiled_patterns yield filepath, compiled_patterns
@ -242,9 +244,7 @@ def _compile_v2_file_patterns(raw_cfg: RawConfig) -> typ.Iterable[FilePatternsIt
raw_patterns_by_file: RawPatternsByFile = raw_cfg['file_patterns'] raw_patterns_by_file: RawPatternsByFile = raw_cfg['file_patterns']
for filepath, raw_patterns in _iter_glob_expanded_file_patterns(raw_patterns_by_file): for filepath, raw_patterns in _iter_glob_expanded_file_patterns(raw_patterns_by_file):
compiled_patterns = [ compiled_patterns = v2patterns.compile_patterns(version_pattern, raw_patterns)
v2patterns.compile_pattern(version_pattern, raw_pattern) for raw_pattern in raw_patterns
]
yield filepath, compiled_patterns yield filepath, compiled_patterns
@ -319,12 +319,7 @@ def _parse_config(raw_cfg: RawConfig) -> Config:
return cfg return cfg
def _parse_current_version_default_pattern(ctx: ProjectContext, raw_cfg: RawConfig) -> str: def _parse_current_version_default_pattern(raw_cfg: RawConfig, raw_cfg_text: str) -> str:
fobj: typ.IO[str]
with ctx.config_filepath.open(mode="rt", encoding="utf-8") as fobj:
raw_cfg_text = fobj.read()
is_pycalver_section = False is_pycalver_section = False
for line in raw_cfg_text.splitlines(): for line in raw_cfg_text.splitlines():
if is_pycalver_section and line.startswith("current_version"): if is_pycalver_section and line.startswith("current_version"):
@ -340,19 +335,7 @@ def _parse_current_version_default_pattern(ctx: ProjectContext, raw_cfg: RawConf
raise ValueError("Could not parse pycalver.current_version") raise ValueError("Could not parse pycalver.current_version")
def _parse_raw_config(ctx: ProjectContext) -> RawConfig: def _set_raw_config_defaults(raw_cfg: RawConfig) -> None:
with ctx.config_filepath.open(mode="rt", encoding="utf-8") as fobj:
if ctx.config_format == 'toml':
raw_cfg = _parse_toml(fobj)
elif ctx.config_format == 'cfg':
raw_cfg = _parse_cfg(fobj)
else:
err_msg = (
f"Invalid config_format='{ctx.config_format}'."
"Supported formats are 'setup.cfg' and 'pyproject.toml'"
)
raise RuntimeError(err_msg)
if 'current_version' in raw_cfg: if 'current_version' in raw_cfg:
if not isinstance(raw_cfg['current_version'], str): if not isinstance(raw_cfg['current_version'], str):
err = f"Invalid type for pycalver.current_version = {raw_cfg['current_version']}" err = f"Invalid type for pycalver.current_version = {raw_cfg['current_version']}"
@ -370,10 +353,27 @@ def _parse_raw_config(ctx: ProjectContext) -> RawConfig:
if 'file_patterns' not in raw_cfg: if 'file_patterns' not in raw_cfg:
raw_cfg['file_patterns'] = {} raw_cfg['file_patterns'] = {}
def _parse_raw_config(ctx: ProjectContext) -> RawConfig:
with ctx.config_filepath.open(mode="rt", encoding="utf-8") as fobj:
if ctx.config_format == 'toml':
raw_cfg = _parse_toml(fobj)
elif ctx.config_format == 'cfg':
raw_cfg = _parse_cfg(fobj)
else:
err_msg = (
f"Invalid config_format='{ctx.config_format}'."
"Supported formats are 'setup.cfg' and 'pyproject.toml'"
)
raise RuntimeError(err_msg)
if ctx.config_rel_path not in raw_cfg['file_patterns']: if ctx.config_rel_path not in raw_cfg['file_patterns']:
# NOTE (mb 2020-09-19): By default we always add # NOTE (mb 2020-09-19): By default we always add
# a pattern for the config section itself. # a pattern for the config section itself.
raw_version_pattern = _parse_current_version_default_pattern(ctx, raw_cfg) with ctx.config_filepath.open(mode="rt", encoding="utf-8") as fobj:
raw_cfg_text = fobj.read()
raw_version_pattern = _parse_current_version_default_pattern(raw_cfg, raw_cfg_text)
raw_cfg['file_patterns'][ctx.config_rel_path] = [raw_version_pattern] raw_cfg['file_patterns'][ctx.config_rel_path] = [raw_version_pattern]
return raw_cfg return raw_cfg
@ -393,6 +393,14 @@ def parse(ctx: ProjectContext) -> MaybeConfig:
return None return None
def init(
project_path: typ.Union[str, pl.Path, None] = "."
) -> typ.Tuple[ProjectContext, MaybeConfig]:
ctx = init_project_ctx(project_path)
cfg = parse(ctx)
return (ctx, cfg)
DEFAULT_CONFIGPARSER_BASE_TMPL = """ DEFAULT_CONFIGPARSER_BASE_TMPL = """
[pycalver] [pycalver]
current_version = "{initial_version}" current_version = "{initial_version}"

View file

@ -9,14 +9,43 @@ import typing as typ
from .patterns import Pattern from .patterns import Pattern
LineNo = int
Start = int
End = int
class LineSpan(typ.NamedTuple):
lineno: LineNo
start : Start
end : End
LineSpans = typ.List[LineSpan]
def _has_overlap(needle: LineSpan, haystack: LineSpans) -> bool:
for span in haystack:
# assume needle is in the center
has_overlap = (
span.lineno == needle.lineno
# needle starts before (or at) span end
and needle.start <= span.end
# needle ends after (or at) span start
and needle.end >= span.start
)
if has_overlap:
return True
return False
class PatternMatch(typ.NamedTuple): class PatternMatch(typ.NamedTuple):
"""Container to mark a version string in a file.""" """Container to mark a version string in a file."""
lineno : int # zero based lineno : LineNo # zero based
line : str line : str
pattern: Pattern pattern: Pattern
span : typ.Tuple[int, int] span : typ.Tuple[Start, End]
match : str match : str
@ -47,6 +76,10 @@ def iter_matches(lines: typ.List[str], patterns: typ.List[Pattern]) -> PatternMa
... match = "v201712.0002-alpha", ... match = "v201712.0002-alpha",
... ) ... )
""" """
matched_spans: LineSpans = []
for pattern in patterns: for pattern in patterns:
for match in _iter_for_pattern(lines, pattern): for match in _iter_for_pattern(lines, pattern):
yield match needle_span = LineSpan(match.lineno, *match.span)
if not _has_overlap(needle_span, matched_spans):
yield match
matched_spans.append(needle_span)

View file

@ -42,14 +42,6 @@ def update_cfg_from_vcs(cfg: config.Config, all_tags: typ.List[str]) -> config.C
) )
def rewrite_files(
cfg : config.Config,
new_version: str,
) -> None:
new_vinfo = v1version.parse_version_info(new_version, cfg.version_pattern)
v1rewrite.rewrite_files(cfg.file_patterns, new_vinfo)
def get_diff(cfg: config.Config, new_version: str) -> str: def get_diff(cfg: config.Config, new_version: str) -> str:
old_vinfo = v1version.parse_version_info(cfg.current_version, cfg.version_pattern) old_vinfo = v1version.parse_version_info(cfg.current_version, cfg.version_pattern)
new_vinfo = v1version.parse_version_info(new_version , cfg.version_pattern) new_vinfo = v1version.parse_version_info(new_version , cfg.version_pattern)

View file

@ -81,8 +81,8 @@ PART_PATTERNS = {
'month' : r"(?:0[0-9]|1[0-2])", 'month' : r"(?:0[0-9]|1[0-2])",
'month_short': r"(?:1[0-2]|[1-9])", 'month_short': r"(?:1[0-2]|[1-9])",
'build_no' : r"\d{4,}", 'build_no' : r"\d{4,}",
'pep440_tag' : r"(?:post|dev|rc|a|b)?\d*", 'pep440_tag' : r"(?:a|b|dev|rc|post)?\d*",
'tag' : r"(?:preview|final|alpha|beta|post|pre|dev|rc|a|b|c|r)", 'tag' : r"(?:alpha|beta|dev|rc|post|final)",
'yy' : r"\d{2}", 'yy' : r"\d{2}",
'yyyy' : r"\d{4}", 'yyyy' : r"\d{4}",
'quarter' : r"[1-4]", 'quarter' : r"[1-4]",
@ -198,26 +198,29 @@ def _init_composite_patterns() -> None:
_init_composite_patterns() _init_composite_patterns()
def _compile_pattern_re(version_pattern: str, raw_pattern: str) -> typ.Pattern[str]: def _compile_pattern_re(normalized_pattern: str) -> typ.Pattern[str]:
normalized_pattern = raw_pattern.replace(r"{version}", version_pattern)
if version_pattern == r"{pycalver}":
normalized_pattern = normalized_pattern.replace(r"{pep440_version}", r"{pep440_pycalver}")
elif version_pattern == r"{semver}":
normalized_pattern = normalized_pattern.replace(r"{pep440_version}", r"{semver}")
elif r"{pep440_version}" in raw_pattern:
logger.warning(f"No mapping of '{version_pattern}' to '{{pep440_version}}'")
escaped_pattern = normalized_pattern escaped_pattern = normalized_pattern
for char, escaped in RE_PATTERN_ESCAPES: for char, escaped in RE_PATTERN_ESCAPES:
escaped_pattern = escaped_pattern.replace(char, escaped) escaped_pattern = escaped_pattern.replace(char, escaped)
# TODO (mb 2020-09-19): replace {version} etc with version_pattern
pattern_str = _replace_pattern_parts(escaped_pattern) pattern_str = _replace_pattern_parts(escaped_pattern)
return re.compile(pattern_str) return re.compile(pattern_str)
@utils.memo @utils.memo
def compile_pattern(version_pattern: str, raw_pattern: typ.Optional[str] = None) -> Pattern: def compile_pattern(version_pattern: str, raw_pattern: typ.Optional[str] = None) -> Pattern:
_raw_pattern = version_pattern if raw_pattern is None else raw_pattern _raw_pattern = version_pattern if raw_pattern is None else raw_pattern
regexp = _compile_pattern_re(version_pattern, _raw_pattern) normalized_pattern = _raw_pattern.replace(r"{version}", version_pattern)
return Pattern(version_pattern, _raw_pattern, regexp) if version_pattern == r"{pycalver}":
normalized_pattern = normalized_pattern.replace(r"{pep440_version}", r"{pep440_pycalver}")
elif version_pattern == r"{semver}":
normalized_pattern = normalized_pattern.replace(r"{pep440_version}", r"{semver}")
elif r"{pep440_version}" in _raw_pattern:
logger.warning(f"No mapping of '{version_pattern}' to '{{pep440_version}}'")
regexp = _compile_pattern_re(normalized_pattern)
return Pattern(version_pattern, normalized_pattern, regexp)
def compile_patterns(version_pattern: str, raw_patterns: typ.List[str]) -> typ.List[Pattern]:
return [compile_pattern(version_pattern, raw_pattern) for raw_pattern in raw_patterns]

View file

@ -24,19 +24,7 @@ def rewrite_lines(
new_vinfo: version.V1VersionInfo, new_vinfo: version.V1VersionInfo,
old_lines: typ.List[str], old_lines: typ.List[str],
) -> typ.List[str]: ) -> typ.List[str]:
"""Replace occurances of patterns in old_lines with new_vinfo. """Replace occurances of patterns in old_lines with new_vinfo."""
>>> from .v1patterns import compile_pattern
>>> version_pattern = "{pycalver}"
>>> new_vinfo = v1version.parse_version_info("v201811.0123-beta", version_pattern)
>>> patterns = [compile_pattern(version_pattern, '__version__ = "{pycalver}"')]
>>> rewrite_lines(patterns, new_vinfo, ['__version__ = "v201809.0002-beta"'])
['__version__ = "v201811.0123-beta"']
>>> patterns = [compile_pattern(version_pattern, '__version__ = "{pep440_version}"')]
>>> rewrite_lines(patterns, new_vinfo, ['__version__ = "201809.2b0"'])
['__version__ = "201811.123b0"']
"""
found_patterns: typ.Set[Pattern] = set() found_patterns: typ.Set[Pattern] = set()
new_lines = old_lines[:] new_lines = old_lines[:]
@ -65,10 +53,12 @@ def rfd_from_content(
) -> rewrite.RewrittenFileData: ) -> rewrite.RewrittenFileData:
r"""Rewrite pattern occurrences with version string. r"""Rewrite pattern occurrences with version string.
>>> from .v1patterns import compile_pattern >>> version_pattern = "{pycalver}"
>>> patterns = [compile_pattern("{pycalver}", '__version__ = "{pycalver}"']
>>> new_vinfo = v1version.parse_version_info("v201809.0123") >>> new_vinfo = v1version.parse_version_info("v201809.0123")
>>> from .v1patterns import compile_pattern
>>> patterns = [compile_pattern(version_pattern, '__version__ = "{pycalver}"')]
>>> content = '__version__ = "v201809.0001-alpha"' >>> content = '__version__ = "v201809.0001-alpha"'
>>> rfd = rfd_from_content(patterns, new_vinfo, content) >>> rfd = rfd_from_content(patterns, new_vinfo, content)
>>> rfd.new_lines >>> rfd.new_lines
@ -92,26 +82,7 @@ def iter_rewritten(
file_patterns: config.PatternsByFile, file_patterns: config.PatternsByFile,
new_vinfo : version.V1VersionInfo, new_vinfo : version.V1VersionInfo,
) -> typ.Iterable[rewrite.RewrittenFileData]: ) -> typ.Iterable[rewrite.RewrittenFileData]:
r'''Iterate over files with version string replaced. """Iterate over files with version string replaced."""
>>> version_pattern = "{pycalver}"
>>> file_patterns = {"src/pycalver/__init__.py": ['__version__ = "{pycalver}"']}
>>> new_vinfo = v1version.parse_version_info("v201809.0123")
>>> rewritten_datas = iter_rewritten(version_pattern, file_patterns, new_vinfo)
>>> rfd = list(rewritten_datas)[0]
>>> expected = [
... '# 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"',
... '',
... ]
>>> assert rfd.new_lines == expected
'''
fobj: typ.IO[str] fobj: typ.IO[str]
@ -128,24 +99,7 @@ def diff(
new_vinfo : version.V1VersionInfo, new_vinfo : version.V1VersionInfo,
file_patterns: config.PatternsByFile, file_patterns: config.PatternsByFile,
) -> str: ) -> str:
r"""Generate diffs of rewritten files. """Generate diffs of rewritten files."""
>>> old_vinfo = v1version.parse_version_info("v201809.0123")
>>> new_vinfo = v1version.parse_version_info("v201810.1124")
>>> file_patterns = {"src/pycalver/__init__.py": ['__version__ = "{pycalver}"']}
>>> diff_str = diff(old_vinfo, 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"'
>>> file_patterns = {"LICENSE": ['Copyright (c) 2018-{year}']}
>>> diff_str = diff(old_vinfo, new_vinfo, file_patterns)
>>> assert not diff_str
"""
full_diff = "" full_diff = ""
fobj: typ.IO[str] fobj: typ.IO[str]
@ -165,13 +119,13 @@ def diff(
rfd = rfd_from_content(patterns, new_vinfo, content) rfd = rfd_from_content(patterns, new_vinfo, content)
except rewrite.NoPatternMatch: except rewrite.NoPatternMatch:
# pylint:disable=raise-missing-from ; we support py2, so not an option # pylint:disable=raise-missing-from ; we support py2, so not an option
errmsg = f"No patterns matched for '{file_path}'" errmsg = f"No patterns matched for file '{file_path}'"
raise rewrite.NoPatternMatch(errmsg) raise rewrite.NoPatternMatch(errmsg)
rfd = rfd._replace(path=str(file_path)) rfd = rfd._replace(path=str(file_path))
lines = rewrite.diff_lines(rfd) lines = rewrite.diff_lines(rfd)
if len(lines) == 0 and has_updated_version: if len(lines) == 0 and has_updated_version:
errmsg = f"No patterns matched for '{file_path}'" errmsg = f"No patterns matched for file '{file_path}'"
raise rewrite.NoPatternMatch(errmsg) raise rewrite.NoPatternMatch(errmsg)
full_diff += "\n".join(lines) + "\n" full_diff += "\n".join(lines) + "\n"

View file

@ -374,12 +374,12 @@ def incr(
old_version: str, old_version: str,
raw_pattern: str = "{pycalver}", raw_pattern: str = "{pycalver}",
*, *,
release : typ.Optional[str] = None, release : typ.Optional[str] = None,
major : bool = False, major : bool = False,
minor : bool = False, minor : bool = False,
patch : bool = False, patch : bool = False,
release_num: bool = False, release_num: bool = False,
pin_date: bool = False, pin_date : bool = False,
) -> typ.Optional[str]: ) -> typ.Optional[str]:
"""Increment version string. """Increment version string.
@ -408,7 +408,7 @@ def incr(
if patch: if patch:
cur_vinfo = cur_vinfo._replace(patch=cur_vinfo.patch + 1) cur_vinfo = cur_vinfo._replace(patch=cur_vinfo.patch + 1)
if release_num: if release_num:
cur_vinfo = cur_vinfo._replace(num=cur_vinfo.num + 1) raise NotImplementedError("--release-num not supported for old style patterns")
if release: if release:
cur_vinfo = cur_vinfo._replace(tag=release) cur_vinfo = cur_vinfo._replace(tag=release)

View file

@ -42,14 +42,6 @@ def update_cfg_from_vcs(cfg: config.Config, all_tags: typ.List[str]) -> config.C
) )
def rewrite_files(
cfg : config.Config,
new_version: str,
) -> None:
new_vinfo = v2version.parse_version_info(new_version, cfg.version_pattern)
v2rewrite.rewrite_files(cfg.file_patterns, new_vinfo)
def get_diff(cfg: config.Config, new_version: str) -> str: def get_diff(cfg: config.Config, new_version: str) -> str:
old_vinfo = v2version.parse_version_info(cfg.current_version, cfg.version_pattern) old_vinfo = v2version.parse_version_info(cfg.current_version, cfg.version_pattern)
new_vinfo = v2version.parse_version_info(new_version , cfg.version_pattern) new_vinfo = v2version.parse_version_info(new_version , cfg.version_pattern)

View file

@ -8,7 +8,6 @@
>>> pattern = compile_pattern("vYYYY0M.BUILD[-RELEASE]") >>> pattern = compile_pattern("vYYYY0M.BUILD[-RELEASE]")
>>> version_info = pattern.regexp.match("v201712.0123-alpha") >>> version_info = pattern.regexp.match("v201712.0123-alpha")
>>> assert version_info.groupdict() == { >>> assert version_info.groupdict() == {
... "version": "v201712.0123-alpha",
... "year_y" : "2017", ... "year_y" : "2017",
... "month" : "12", ... "month" : "12",
... "bid" : "0123", ... "bid" : "0123",
@ -23,7 +22,6 @@
>>> version_info = pattern.regexp.match("v201712.1234") >>> version_info = pattern.regexp.match("v201712.1234")
>>> assert version_info.groupdict() == { >>> assert version_info.groupdict() == {
... "version": "v201712.1234",
... "year_y" : "2017", ... "year_y" : "2017",
... "month" : "12", ... "month" : "12",
... "bid" : "1234", ... "bid" : "1234",
@ -251,6 +249,13 @@ def _convert_to_pep440(version_pattern: str) -> str:
else: else:
pep440_pattern = pep440_pattern.replace(part_name, substitution) pep440_pattern = pep440_pattern.replace(part_name, substitution)
# PYTAG and NUM must be adjacent and also be the last (optional) part
if 'PYTAGNUM' not in pep440_pattern:
pep440_pattern = pep440_pattern.replace("PYTAG", "")
pep440_pattern = pep440_pattern.replace("NUM" , "")
pep440_pattern = pep440_pattern.replace("[]" , "")
pep440_pattern += "[PYTAGNUM]"
return pep440_pattern return pep440_pattern
@ -304,9 +309,8 @@ def _replace_pattern_parts(pattern: str) -> str:
return result_pattern return result_pattern
def _compile_pattern_re(version_pattern: str, raw_pattern: str) -> typ.Pattern[str]: def _compile_pattern_re(normalized_pattern: str) -> typ.Pattern[str]:
normalized_pattern = normalize_pattern(version_pattern, raw_pattern) escaped_pattern = normalized_pattern
escaped_pattern = normalized_pattern
for char, escaped in RE_PATTERN_ESCAPES: for char, escaped in RE_PATTERN_ESCAPES:
# [] braces are used for optional parts, such as [-RELEASE]/[-beta] # [] braces are used for optional parts, such as [-RELEASE]/[-beta]
# and need to be escaped manually. # and need to be escaped manually.
@ -321,6 +325,11 @@ def _compile_pattern_re(version_pattern: str, raw_pattern: str) -> typ.Pattern[s
@utils.memo @utils.memo
def compile_pattern(version_pattern: str, raw_pattern: typ.Optional[str] = None) -> Pattern: def compile_pattern(version_pattern: str, raw_pattern: typ.Optional[str] = None) -> Pattern:
_raw_pattern = version_pattern if raw_pattern is None else raw_pattern _raw_pattern = version_pattern if raw_pattern is None else raw_pattern
regexp = _compile_pattern_re(version_pattern, _raw_pattern) normalized_pattern = normalize_pattern(version_pattern, _raw_pattern)
return Pattern(version_pattern, _raw_pattern, regexp) regexp = _compile_pattern_re(normalized_pattern)
return Pattern(version_pattern, normalized_pattern, regexp)
def compile_patterns(version_pattern: str, raw_patterns: typ.List[str]) -> typ.List[Pattern]:
return [compile_pattern(version_pattern, raw_pattern) for raw_pattern in raw_patterns]

View file

@ -25,23 +25,7 @@ def rewrite_lines(
new_vinfo: version.V2VersionInfo, new_vinfo: version.V2VersionInfo,
old_lines: typ.List[str], old_lines: typ.List[str],
) -> typ.List[str]: ) -> typ.List[str]:
"""Replace occurances of patterns in old_lines with new_vinfo. """Replace occurances of patterns in old_lines with new_vinfo."""
>>> from .v2patterns import compile_pattern
>>> version_pattern = "vYYYY0M.BUILD[-RELEASE]"
>>> new_vinfo = v2version.parse_version_info("v201811.0123-beta", version_pattern)
>>> patterns = [compile_pattern(version_pattern, '__version__ = "{version}"')]
>>> rewrite_lines(patterns, new_vinfo, ['__version__ = "v201809.0002-alpha" '])
['__version__ = "v201811.0123-beta" ']
>>> rewrite_lines(patterns, new_vinfo, ['__version__ = "v201809.0002-alpha" # comment'])
['__version__ = "v201811.0123-beta" # comment']
>>> patterns = [compile_pattern(version_pattern, '__version__ = "{pep440_version}"')]
>>> old_lines = ['__version__ = "201809.2a0"']
>>> rewrite_lines(patterns, new_vinfo, old_lines)
['__version__ = "201811.123b0"']
"""
found_patterns: typ.Set[Pattern] = set() found_patterns: typ.Set[Pattern] = set()
new_lines = old_lines[:] new_lines = old_lines[:]
@ -73,10 +57,10 @@ def rfd_from_content(
) -> rewrite.RewrittenFileData: ) -> rewrite.RewrittenFileData:
r"""Rewrite pattern occurrences with version string. r"""Rewrite pattern occurrences with version string.
>>> from .v2patterns import compile_pattern
>>> version_pattern = "vYYYY0M.BUILD[-RELEASE]" >>> version_pattern = "vYYYY0M.BUILD[-RELEASE]"
>>> new_vinfo = v2version.parse_version_info("v201809.0123", version_pattern) >>> new_vinfo = v2version.parse_version_info("v201809.0123", version_pattern)
>>> raw_patterns = ['__version__ = "vYYYY0M.BUILD[-RELEASE]"'] >>> patterns = [compile_pattern(version_pattern, '__version__ = "vYYYY0M.BUILD[-RELEASE]"')]
>>> patterns =
>>> content = '__version__ = "v201809.0001-alpha"' >>> content = '__version__ = "v201809.0001-alpha"'
>>> rfd = rfd_from_content(patterns, new_vinfo, content) >>> rfd = rfd_from_content(patterns, new_vinfo, content)
>>> rfd.new_lines >>> rfd.new_lines
@ -84,8 +68,7 @@ def rfd_from_content(
>>> version_pattern = "vMAJOR.MINOR.PATCH" >>> version_pattern = "vMAJOR.MINOR.PATCH"
>>> new_vinfo = v2version.parse_version_info("v1.2.3", version_pattern) >>> new_vinfo = v2version.parse_version_info("v1.2.3", version_pattern)
>>> raw_patterns = ['__version__ = "vMAJOR.MINOR.PATCH"'] >>> patterns = [compile_pattern(version_pattern, '__version__ = "vMAJOR.MINOR.PATCH"')]
>>> patterns =
>>> content = '__version__ = "v1.2.2"' >>> content = '__version__ = "v1.2.2"'
>>> rfd = rfd_from_content(patterns, new_vinfo, content) >>> rfd = rfd_from_content(patterns, new_vinfo, content)
>>> rfd.new_lines >>> rfd.new_lines
@ -113,26 +96,7 @@ def iter_rewritten(
file_patterns: config.PatternsByFile, file_patterns: config.PatternsByFile,
new_vinfo : version.V2VersionInfo, new_vinfo : version.V2VersionInfo,
) -> typ.Iterable[rewrite.RewrittenFileData]: ) -> typ.Iterable[rewrite.RewrittenFileData]:
r'''Iterate over files with version string replaced. """Iterate over files with version string replaced."""
>>> version_pattern = "vYYYY0M.BUILD[-RELEASE]"
>>> file_patterns = {"src/pycalver/__init__.py": ['__version__ = "vYYYY0M.BUILD[-RELEASE]"']}
>>> new_vinfo = v2version.parse_version_info("v201809.0123", version_pattern)
>>> rewritten_datas = iter_rewritten(file_patterns, new_vinfo)
>>> rfd = list(rewritten_datas)[0]
>>> expected = [
... '# 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"',
... '',
... ]
>>> assert rfd.new_lines == expected
'''
fobj: typ.IO[str] fobj: typ.IO[str]
@ -149,24 +113,7 @@ def diff(
new_vinfo : version.V2VersionInfo, new_vinfo : version.V2VersionInfo,
file_patterns: config.PatternsByFile, file_patterns: config.PatternsByFile,
) -> str: ) -> str:
r"""Generate diffs of rewritten files. r"""Generate diffs of rewritten files."""
>>> old_vinfo = v2version.parse_version_info("v201809.0123", version_pattern)
>>> new_vinfo = v2version.parse_version_info("v201810.1124", version_pattern)
>>> file_patterns = {"src/pycalver/__init__.py": ['__version__ = "vYYYY0M.BUILD[-RELEASE]"']}
>>> diff_str = diff(old_vinfo, 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__ = "v201810.1124"')
>>> lines[7]
'+__version__ = "v201810.1124"'
>>> file_patterns = {"LICENSE": ['Copyright (c) 2018-YYYY']}
>>> diff_str = diff(old_vinfo, new_vinfo, file_patterns)
>>> assert not diff_str
"""
full_diff = "" full_diff = ""
fobj: typ.IO[str] fobj: typ.IO[str]
@ -179,7 +126,7 @@ def diff(
rfd = rfd_from_content(patterns, new_vinfo, content) rfd = rfd_from_content(patterns, new_vinfo, content)
except rewrite.NoPatternMatch: except rewrite.NoPatternMatch:
# pylint:disable=raise-missing-from ; we support py2, so not an option # pylint:disable=raise-missing-from ; we support py2, so not an option
errmsg = f"No patterns matched for '{file_path}'" errmsg = f"No patterns matched for file '{file_path}'"
raise rewrite.NoPatternMatch(errmsg) raise rewrite.NoPatternMatch(errmsg)
rfd = rfd._replace(path=str(file_path)) rfd = rfd._replace(path=str(file_path))
@ -187,7 +134,7 @@ def diff(
patterns_with_change = _patterns_with_change(old_vinfo, new_vinfo, patterns) patterns_with_change = _patterns_with_change(old_vinfo, new_vinfo, patterns)
if len(lines) == 0 and patterns_with_change > 0: if len(lines) == 0 and patterns_with_change > 0:
errmsg = f"No patterns matched for '{file_path}'" errmsg = f"No patterns matched for file '{file_path}'"
raise rewrite.NoPatternMatch(errmsg) raise rewrite.NoPatternMatch(errmsg)
full_diff += "\n".join(lines) + "\n" full_diff += "\n".join(lines) + "\n"

View file

@ -145,7 +145,7 @@ def _parse_version_info(field_values: FieldValues) -> version.V2VersionInfo:
assert key in VALID_FIELD_KEYS, key assert key in VALID_FIELD_KEYS, key
fvals = field_values fvals = field_values
tag = fvals.get('tag' ) or "final" tag = fvals.get('tag' ) or ""
pytag = fvals.get('pytag') or "" pytag = fvals.get('pytag') or ""
if tag and not pytag: if tag and not pytag:
@ -153,6 +153,9 @@ def _parse_version_info(field_values: FieldValues) -> version.V2VersionInfo:
elif pytag and not tag: elif pytag and not tag:
tag = version.RELEASE_BY_PEP440_TAG[pytag] tag = version.RELEASE_BY_PEP440_TAG[pytag]
if not tag:
tag = "final"
date: typ.Optional[dt.date] = None date: typ.Optional[dt.date] = None
year_y: MaybeInt = int(fvals['year_y']) if 'year_y' in fvals else None year_y: MaybeInt = int(fvals['year_y']) if 'year_y' in fvals else None
@ -221,22 +224,22 @@ def _parse_version_info(field_values: FieldValues) -> version.V2VersionInfo:
def parse_version_info( def parse_version_info(
version_str: str, raw_pattern: str = "vYYYY0M.BUILD[-RELEASE[NUM]]" version_str: str, raw_pattern: str = "vYYYY0M.BUILD[-RELEASE]"
) -> version.V2VersionInfo: ) -> version.V2VersionInfo:
"""Parse normalized V2VersionInfo. """Parse normalized V2VersionInfo.
>>> vinfo = parse_version_info("v201712.0033-beta0", raw_pattern="vYYYY0M.BUILD[-RELEASE[NUM]]") >>> vinfo = parse_version_info("v201712.0033-beta", raw_pattern="vYYYY0M.BUILD[-RELEASE]")
>>> fvals = {'year_y': 2017, 'month': 12, 'bid': "0033", 'tag': "beta", 'num': 0}
>>> assert vinfo == _parse_version_info(fvals)
>>> vinfo = parse_version_info("v201712.0033-beta", raw_pattern="vYYYY0M.BUILD[-RELEASE[NUM]]")
>>> fvals = {'year_y': 2017, 'month': 12, 'bid': "0033", 'tag': "beta"} >>> fvals = {'year_y': 2017, 'month': 12, 'bid': "0033", 'tag': "beta"}
>>> assert vinfo == _parse_version_info(fvals) >>> assert vinfo == _parse_version_info(fvals)
>>> vinfo = parse_version_info("v201712.0033", raw_pattern="vYYYY0M.BUILD[-RELEASE[NUM]]") >>> vinfo = parse_version_info("v201712.0033", raw_pattern="vYYYY0M.BUILD[-RELEASE]")
>>> fvals = {'year_y': 2017, 'month': 12, 'bid': "0033"} >>> fvals = {'year_y': 2017, 'month': 12, 'bid': "0033"}
>>> assert vinfo == _parse_version_info(fvals) >>> assert vinfo == _parse_version_info(fvals)
>>> vinfo = parse_version_info("201712.33b0", raw_pattern="YYYY0M.BLD[PYTAGNUM]")
>>> fvals = {'year_y': 2017, 'month': 12, 'bid': "33", 'tag': "beta", 'num': 0}
>>> assert vinfo == _parse_version_info(fvals)
>>> vinfo = parse_version_info("1.23.456", raw_pattern="MAJOR.MINOR.PATCH") >>> vinfo = parse_version_info("1.23.456", raw_pattern="MAJOR.MINOR.PATCH")
>>> fvals = {'major': "1", 'minor': "23", 'patch': "456"} >>> fvals = {'major': "1", 'minor': "23", 'patch': "456"}
>>> assert vinfo == _parse_version_info(fvals) >>> assert vinfo == _parse_version_info(fvals)
@ -291,14 +294,14 @@ def _format_part_values(vinfo: version.V2VersionInfo) -> PartValues:
It may for example have month=9, but not the formatted It may for example have month=9, but not the formatted
representation '09' for '0M'. representation '09' for '0M'.
>>> vinfo = parse_version_info("v200709.1033-beta", pattern="vYYYY0M.BUILD[-RELEASE[NUM]]") >>> vinfo = parse_version_info("v200709.1033-beta", raw_pattern="vYYYY0M.BUILD[-RELEASE]")
>>> kwargs = dict(_format_part_values(vinfo)) >>> kwargs = dict(_format_part_values(vinfo))
>>> (kwargs['YYYY'], kwargs['0M'], kwargs['BUILD'], kwargs['RELEASE[NUM]']) >>> (kwargs['YYYY'], kwargs['0M'], kwargs['BUILD'], kwargs['RELEASE'])
('2007', '09', '1033', 'beta') ('2007', '09', '1033', 'beta')
>>> (kwargs['YY'], kwargs['0Y'], kwargs['MM'], kwargs['PYTAG']) >>> (kwargs['YY'], kwargs['0Y'], kwargs['MM'], kwargs['PYTAG'])
('7', '07', '9', 'b') ('7', '07', '9', 'b')
>>> vinfo = parse_version_info("200709.1033b1", pattern="YYYY0M.BLD[PYTAGNUM]") >>> vinfo = parse_version_info("200709.1033b1", raw_pattern="YYYY0M.BLD[PYTAGNUM]")
>>> kwargs = dict(_format_part_values(vinfo)) >>> kwargs = dict(_format_part_values(vinfo))
>>> (kwargs['YYYY'], kwargs['0M'], kwargs['BUILD'], kwargs['PYTAG'], kwargs['NUM']) >>> (kwargs['YYYY'], kwargs['0M'], kwargs['BUILD'], kwargs['PYTAG'], kwargs['NUM'])
('2007', '09', '1033', 'b', '1') ('2007', '09', '1033', 'b', '1')
@ -386,141 +389,202 @@ def _parse_segment_tree(raw_pattern: str) -> SegmentTree:
FormattedSegmentParts = typ.List[str] FormattedSegmentParts = typ.List[str]
class FormatedSeg(typ.NamedTuple):
is_literal: bool
is_zero : bool
result : str
def _format_segment(seg: Segment, part_values: PartValues) -> FormatedSeg:
zero_part_count = 0
# find all parts, regardless of zero value
used_parts: typ.List[typ.Tuple[str, str]] = []
for part, part_value in part_values:
if part in seg:
used_parts.append((part, part_value))
if version.is_zero_val(part, part_value):
zero_part_count += 1
result = seg
# unescape braces
result = result.replace(r"\[", r"[")
result = result.replace(r"\]", r"]")
for part, part_value in used_parts:
result = result.replace(part, part_value)
# If a segment has no parts at all, it is a literal string
# (typically a prefix or sufix) and should be output as is.
is_literal_seg = len(used_parts) == 0
if is_literal_seg:
return FormatedSeg(True, False, result)
elif zero_part_count > 0 and zero_part_count == len(used_parts):
# all zero, omit segment completely
return FormatedSeg(False, True, result)
else:
return FormatedSeg(False, False, result)
def _format_segment_tree( def _format_segment_tree(
seg_tree : SegmentTree, seg_tree : SegmentTree,
part_values: PartValues, part_values: PartValues,
) -> FormattedSegmentParts: ) -> FormatedSeg:
result_parts = [] # print("??>>>", seg_tree)
# NOTE (mb 2020-10-02): starting from the right, if there is any non-zero
# part, all further parts going left will be used. In other words, a part
# is only omitted, if all parts to the right of it were also omitted.
result_parts: typ.List[str] = []
is_zero = True
for seg in seg_tree: for seg in seg_tree:
if isinstance(seg, list): if isinstance(seg, list):
result_parts.extend(_format_segment_tree(seg, part_values)) formatted_seg = _format_segment_tree(seg, part_values)
else: else:
# If a segment has any non-zero parts, the whole segment is used. formatted_seg = _format_segment(seg, part_values)
non_zero_parts = 0
formatted_seg = seg
# unescape braces
formatted_seg = formatted_seg.replace(r"\[", r"[")
formatted_seg = formatted_seg.replace(r"\]", r"]")
# replace non zero parts
for part, part_value in part_values:
if part in formatted_seg:
is_zero_part = (
part in version.ZERO_VALUES and str(part_value) == version.ZERO_VALUES[part]
)
if is_zero_part:
formatted_seg = formatted_seg.replace(part, "")
else:
non_zero_parts += 1
formatted_seg = formatted_seg.replace(part, part_value)
if non_zero_parts: if formatted_seg.is_literal:
result_parts.append(formatted_seg) result_parts.append(formatted_seg.result)
else:
is_zero = is_zero and formatted_seg.is_zero
result_parts.append(formatted_seg.result)
return result_parts # print("<<<<", is_zero, result_parts)
result = "" if is_zero else "".join(result_parts)
return FormatedSeg(False, is_zero, result)
def format_version(vinfo: version.V2VersionInfo, raw_pattern: str) -> str: def format_version(vinfo: version.V2VersionInfo, raw_pattern: str) -> str:
"""Generate version string. """Generate version string.
>>> import datetime as dt >>> import datetime as dt
>>> vinfo = parse_version_info("v200712.0033-beta", pattern="vYYYY0M.BUILD[-RELEASE[NUM]]") >>> vinfo = parse_version_info("v200712.0033-beta", raw_pattern="vYYYY0M.BUILD[-RELEASE]")
>>> vinfo_a = vinfo._replace(**cal_info(date=dt.date(2007, 1, 1))._asdict()) >>> vinfo_a = vinfo._replace(**cal_info(date=dt.date(2007, 1, 1))._asdict())
>>> vinfo_b = vinfo._replace(**cal_info(date=dt.date(2007, 12, 31))._asdict()) >>> vinfo_b = vinfo._replace(**cal_info(date=dt.date(2007, 12, 31))._asdict())
>>> format_version(vinfo_a, pattern="vYY.BLD[-PYTAGNUM]") >>> format_version(vinfo_a, raw_pattern="vYY.BLD[-PYTAGNUM]")
'v7.33-b0' 'v7.33-b0'
>>> format_version(vinfo_a, pattern="YYYY0M.BUILD[PYTAG[NUM]]") >>> format_version(vinfo_a, raw_pattern="YYYY0M.BUILD[PYTAG[NUM]]")
'200701.0033b' '200701.0033b'
>>> format_version(vinfo_a, pattern="vYY.BLD[-PYTAGNUM]") >>> format_version(vinfo_a, raw_pattern="vYY.BLD[-PYTAGNUM]")
'v7.33-b0' 'v7.33-b0'
>>> format_version(vinfo_a, pattern="v0Y.BLD[-RELEASE[NUM]]") >>> format_version(vinfo_a, raw_pattern="v0Y.BLD[-RELEASE[NUM]]")
'v07.33-beta' 'v07.33-beta'
>>> format_version(vinfo_a, pattern="vYYYY0M.BUILD[-RELEASE[NUM]]") >>> format_version(vinfo_a, raw_pattern="vYYYY0M.BUILD[-RELEASE[NUM]]")
'v200701.0033-beta' 'v200701.0033-beta'
>>> format_version(vinfo_b, pattern="vYYYY0M.BUILD[-RELEASE[NUM]]") >>> format_version(vinfo_b, raw_pattern="vYYYY0M.BUILD[-RELEASE[NUM]]")
'v200712.0033-beta' 'v200712.0033-beta'
>>> format_version(vinfo_a, pattern="vYYYYw0W.BUILD[-RELEASE[NUM]]") >>> format_version(vinfo_a, raw_pattern="vYYYYw0W.BUILD[-RELEASE[NUM]]")
'v2007w01.0033-beta' 'v2007w01.0033-beta'
>>> format_version(vinfo_a, pattern="vYYYYwWW.BLD[-RELEASE[NUM]]") >>> format_version(vinfo_a, raw_pattern="vYYYYwWW.BLD[-RELEASE[NUM]]")
'v2007w1.33-beta' 'v2007w1.33-beta'
>>> format_version(vinfo_b, pattern="vYYYYw0W.BUILD[-RELEASE[NUM]]") >>> format_version(vinfo_b, raw_pattern="vYYYYw0W.BUILD[-RELEASE[NUM]]")
'v2007w53.0033-beta' 'v2007w53.0033-beta'
>>> format_version(vinfo_a, pattern="vYYYYd00J.BUILD[-RELEASE[NUM]]") >>> format_version(vinfo_a, raw_pattern="vYYYYd00J.BUILD[-RELEASE[NUM]]")
'v2007d001.0033-beta' 'v2007d001.0033-beta'
>>> format_version(vinfo_a, pattern="vYYYYdJJJ.BUILD[-RELEASE[NUM]]") >>> format_version(vinfo_a, raw_pattern="vYYYYdJJJ.BUILD[-RELEASE[NUM]]")
'v2007d1.0033-beta' 'v2007d1.0033-beta'
>>> format_version(vinfo_b, pattern="vYYYYd00J.BUILD[-RELEASE[NUM]]") >>> format_version(vinfo_b, raw_pattern="vYYYYd00J.BUILD[-RELEASE[NUM]]")
'v2007d365.0033-beta' 'v2007d365.0033-beta'
>>> format_version(vinfo_a, pattern="vGGGGwVV.BLD[PYTAGNUM]") >>> format_version(vinfo_a, raw_pattern="vGGGGwVV.BLD[PYTAGNUM]")
'v2007w1.33b0' 'v2007w1.33b0'
>>> format_version(vinfo_a, pattern="vGGGGw0V.BUILD[-RELEASE[NUM]]") >>> format_version(vinfo_a, raw_pattern="vGGGGw0V.BUILD[-RELEASE[NUM]]")
'v2007w01.0033-beta' 'v2007w01.0033-beta'
>>> format_version(vinfo_b, pattern="vGGGGw0V.BUILD[-RELEASE[NUM]]") >>> format_version(vinfo_b, raw_pattern="vGGGGw0V.BUILD[-RELEASE[NUM]]")
'v2008w01.0033-beta' 'v2008w01.0033-beta'
>>> vinfo_c = vinfo_b._replace(major=1, minor=2, patch=34, tag='final') >>> vinfo_c = vinfo_b._replace(major=1, minor=2, patch=34, tag='final')
>>> format_version(vinfo_c, pattern="vYYYYwWW.BUILD-RELEASE") >>> format_version(vinfo_c, raw_pattern="vYYYYwWW.BUILD-RELEASE")
'v2007w53.0033-final' 'v2007w53.0033-final'
>>> format_version(vinfo_c, pattern="vYYYYwWW.BUILD[-RELEASE[NUM]]") >>> format_version(vinfo_c, raw_pattern="vYYYYwWW.BUILD[-RELEASE[NUM]]")
'v2007w53.0033' 'v2007w53.0033'
>>> format_version(vinfo_c, pattern="vMAJOR.MINOR.PATCH") >>> format_version(vinfo_c, raw_pattern="vMAJOR.MINOR.PATCH")
'v1.2.34' 'v1.2.34'
>>> vinfo_d = vinfo_b._replace(major=1, minor=0, patch=0, tag='final') >>> vinfo_d = vinfo_b._replace(major=1, minor=0, patch=0, tag='final')
>>> format_version(vinfo_d, pattern="vMAJOR.MINOR.PATCH-RELEASENUM") >>> format_version(vinfo_d, raw_pattern="vMAJOR.MINOR.PATCH-RELEASENUM")
'v1.0.0-final0' 'v1.0.0-final0'
>>> format_version(vinfo_d, pattern="vMAJOR.MINOR.PATCH-RELEASE[NUM]") >>> format_version(vinfo_d, raw_pattern="vMAJOR.MINOR.PATCH-RELEASE[NUM]")
'v1.0.0-final' 'v1.0.0-final'
>>> format_version(vinfo_d, pattern="vMAJOR.MINOR.PATCH-RELEASE") >>> format_version(vinfo_d, raw_pattern="vMAJOR.MINOR.PATCH-RELEASE")
'v1.0.0-final' 'v1.0.0-final'
>>> format_version(vinfo_d, pattern="vMAJOR.MINOR.PATCH[-RELEASE[NUM]]") >>> format_version(vinfo_d, raw_pattern="vMAJOR.MINOR.PATCH[-RELEASE[NUM]]")
'v1.0.0' 'v1.0.0'
>>> format_version(vinfo_d, pattern="vMAJOR.MINOR[.PATCH[-RELEASE[NUM]]]") >>> format_version(vinfo_d, raw_pattern="vMAJOR.MINOR[.PATCH[-RELEASE[NUM]]]")
'v1.0' 'v1.0'
>>> format_version(vinfo_d, pattern="vMAJOR[.MINOR[.PATCH[-RELEASE[NUM]]]]") >>> format_version(vinfo_d, raw_pattern="vMAJOR[.MINOR[.PATCH[-RELEASE[NUM]]]]")
'v1' 'v1'
>>> vinfo_d = vinfo_b._replace(major=1, minor=0, patch=1, tag='rc', num=0) >>> vinfo_d = vinfo_b._replace(major=1, minor=0, patch=2, tag='rc', pytag='rc', num=0)
>>> format_version(vinfo_d, pattern="vMAJOR[.MINOR[.PATCH]]") >>> format_version(vinfo_d, raw_pattern="vMAJOR[.MINOR[.PATCH]]")
'v1.0.1' 'v1.0.2'
>>> format_version(vinfo_d, pattern="vMAJOR[.MINOR[.PATCH[-RELEASE[NUM]]]]") >>> format_version(vinfo_d, raw_pattern="vMAJOR[.MINOR[.PATCH[-RELEASE[NUM]]]]")
'v1.0.1-rc' 'v1.0.2-rc'
>>> format_version(vinfo_d, pattern="vMAJOR[.MINOR[.PATCH[-RELEASENUM]]]") >>> format_version(vinfo_d, raw_pattern="vMAJOR[.MINOR[.PATCH[PYTAGNUM]]]")
'v1.0.1-rc0' 'v1.0.2rc0'
>>> format_version(vinfo_d, pattern="vMAJOR[.MINOR[.PATCH]]") >>> format_version(vinfo_d, raw_pattern="vMAJOR[.MINOR[.PATCH]]")
'v1.0.1' 'v1.0.2'
>>> vinfo_d = vinfo_b._replace(major=1, minor=0, patch=0, tag='rc', num=2) >>> vinfo_d = vinfo_b._replace(major=1, minor=0, patch=0, tag='rc', num=2)
>>> format_version(vinfo_d, pattern="vMAJOR[.MINOR[.PATCH[-RELEASE[NUM]]]]") >>> format_version(vinfo_d, raw_pattern="vMAJOR[.MINOR[.PATCH[-RELEASE[NUM]]]]")
'v1.0.0-rc2' 'v1.0.0-rc2'
>>> vinfo_d = vinfo_b._replace(major=1, minor=0, patch=0, tag='rc', num=2) >>> vinfo_d = vinfo_b._replace(major=1, minor=0, patch=0, tag='rc', num=2)
>>> format_version(vinfo_d, pattern='__version__ = "vMAJOR[.MINOR[.PATCH[-RELEASE[NUM]]]]"') >>> format_version(vinfo_d, raw_pattern='__version__ = "vMAJOR[.MINOR[.PATCH[-RELEASE[NUM]]]]"')
'__version__ = "v1.0.0-rc2"' '__version__ = "v1.0.0-rc2"'
""" """
part_values = _format_part_values(vinfo) part_values = _format_part_values(vinfo)
seg_tree = _parse_segment_tree(raw_pattern) seg_tree = _parse_segment_tree(raw_pattern)
version_str_parts = _format_segment_tree(seg_tree, part_values) formatted_seg = _format_segment_tree(seg_tree, part_values)
return "".join(version_str_parts) return formatted_seg.result
def _incr_numeric(
vinfo : version.V2VersionInfo,
major : bool,
minor : bool,
patch : bool,
release : typ.Optional[str],
release_num: bool,
) -> version.V2VersionInfo:
# prevent truncation of leading zeros
if int(vinfo.bid) < 1000:
vinfo = vinfo._replace(bid=str(int(vinfo.bid) + 1000))
vinfo = vinfo._replace(bid=lexid.next_id(vinfo.bid))
if major:
vinfo = vinfo._replace(major=vinfo.major + 1, minor=0, patch=0)
if minor:
vinfo = vinfo._replace(minor=vinfo.minor + 1, patch=0)
if patch:
vinfo = vinfo._replace(patch=vinfo.patch + 1)
if release_num:
vinfo = vinfo._replace(num=vinfo.num + 1)
if release:
if release != vinfo.tag:
vinfo = vinfo._replace(num=0)
vinfo = vinfo._replace(tag=release)
return vinfo
def incr( def incr(
old_version: str, old_version: str,
raw_pattern: str = "vYYYY0M.BUILD[-RELEASE[NUM]]", raw_pattern: str = "vYYYY0M.BUILD[-RELEASE[NUM]]",
*, *,
release : typ.Optional[str] = None, release : typ.Optional[str] = None,
major : bool = False, major : bool = False,
minor : bool = False, minor : bool = False,
patch : bool = False, patch : bool = False,
release_num: bool = False, release_num: bool = False,
pin_date: bool = False, pin_date : bool = False,
) -> typ.Optional[str]: ) -> typ.Optional[str]:
"""Increment version string. """Increment version string.
@ -540,24 +604,14 @@ def incr(
else: else:
cur_vinfo = old_vinfo._replace(**cur_cinfo._asdict()) cur_vinfo = old_vinfo._replace(**cur_cinfo._asdict())
# prevent truncation of leading zeros cur_vinfo = _incr_numeric(
if int(cur_vinfo.bid) < 1000: cur_vinfo,
cur_vinfo = cur_vinfo._replace(bid=str(int(cur_vinfo.bid) + 1000)) major=major,
minor=minor,
cur_vinfo = cur_vinfo._replace(bid=lexid.next_id(cur_vinfo.bid)) patch=patch,
release=release,
if major: release_num=release_num,
cur_vinfo = cur_vinfo._replace(major=cur_vinfo.major + 1, minor=0, patch=0) )
if minor:
cur_vinfo = cur_vinfo._replace(minor=cur_vinfo.minor + 1, patch=0)
if patch:
cur_vinfo = cur_vinfo._replace(patch=cur_vinfo.patch + 1)
if release_num:
cur_vinfo = cur_vinfo._replace(num=cur_vinfo.num + 1)
if release:
if release != cur_vinfo.tag:
cur_vinfo = cur_vinfo._replace(num=0)
cur_vinfo = cur_vinfo._replace(tag=release)
# TODO (mb 2020-09-20): New Rollover Behaviour: # TODO (mb 2020-09-20): New Rollover Behaviour:
# Reset major, minor, patch to zero if any part to the left of it is incremented # Reset major, minor, patch to zero if any part to the left of it is incremented

View file

@ -16,6 +16,7 @@ mercurial, then the git terms are used. For example "fetch"
import os import os
import sys import sys
import shlex
import typing as typ import typing as typ
import logging import logging
import tempfile import tempfile
@ -73,7 +74,8 @@ class VCSAPI:
logger.info(cmd_str) logger.info(cmd_str)
else: else:
logger.debug(cmd_str) logger.debug(cmd_str)
output_data: bytes = sp.check_output(cmd_str.split(), env=env, stderr=sp.STDOUT) cmd_parts = shlex.split(cmd_str)
output_data: bytes = sp.check_output(cmd_parts, env=env, stderr=sp.STDOUT)
# TODO (mb 2018-11-15): Detect encoding of output? Use chardet? # TODO (mb 2018-11-15): Detect encoding of output? Use chardet?
_encoding = "utf-8" _encoding = "utf-8"

View file

@ -119,6 +119,10 @@ ZERO_VALUES = {
} }
def is_zero_val(part: str, part_value: str) -> bool:
return part in ZERO_VALUES and part_value == ZERO_VALUES[part]
class PatternError(Exception): class PatternError(Exception):
pass pass

View file

@ -4,7 +4,9 @@ from __future__ import print_function
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
import io
import os import os
import re
import time import time
import shutil import shutil
import subprocess as sp import subprocess as sp
@ -13,6 +15,8 @@ import pytest
import pathlib2 as pl import pathlib2 as pl
from click.testing import CliRunner from click.testing import CliRunner
from pycalver import v1cli
from pycalver import v2cli
from pycalver import config from pycalver import config
from pycalver import v1patterns from pycalver import v1patterns
from pycalver.__main__ import cli from pycalver.__main__ import cli
@ -21,6 +25,12 @@ from pycalver.__main__ import cli
# pylint:disable=protected-access ; allowed for test code # pylint:disable=protected-access ; allowed for test code
README_TEXT_FIXTURE = """
Hello World v201701.1002-alpha !
aka. 201701.1002a0 !
"""
SETUP_CFG_FIXTURE = """ SETUP_CFG_FIXTURE = """
[metadata] [metadata]
license_file = LICENSE license_file = LICENSE
@ -110,31 +120,33 @@ def test_incr_pin_date(runner):
def test_incr_semver(runner): def test_incr_semver(runner):
semver_pattern = "{MAJOR}.{MINOR}.{PATCH}" semver_patterns = [
old_version = "0.1.0" "{semver}",
new_version = "0.1.1" "{MAJOR}.{MINOR}.{PATCH}",
"MAJOR.MINOR.PATCH",
]
result = runner.invoke(cli, ['test', "-vv", "--patch", old_version, "{semver}"]) for semver_pattern in semver_patterns:
assert result.exit_code == 0 old_version = "0.1.0"
assert f"Version: {new_version}\n" in result.output new_version = "0.1.1"
result = runner.invoke(cli, ['test', "-vv", "--patch", old_version, semver_pattern]) result = runner.invoke(cli, ['test', "-vv", "--patch", old_version, semver_pattern])
assert result.exit_code == 0 assert result.exit_code == 0
assert f"Version: {new_version}\n" in result.output assert f"Version: {new_version}\n" in result.output
old_version = "0.1.1" old_version = "0.1.1"
new_version = "0.2.0" new_version = "0.2.0"
result = runner.invoke(cli, ['test', "-vv", "--minor", old_version, semver_pattern]) result = runner.invoke(cli, ['test', "-vv", "--minor", old_version, semver_pattern])
assert result.exit_code == 0 assert result.exit_code == 0
assert f"Version: {new_version}\n" in result.output assert f"Version: {new_version}\n" in result.output
old_version = "0.1.1" old_version = "0.1.1"
new_version = "1.0.0" new_version = "1.0.0"
result = runner.invoke(cli, ['test', "-vv", "--major", old_version, semver_pattern]) result = runner.invoke(cli, ['test', "-vv", "--major", old_version, semver_pattern])
assert result.exit_code == 0 assert result.exit_code == 0
assert f"Version: {new_version}\n" in result.output assert f"Version: {new_version}\n" in result.output
def test_incr_semver_invalid(runner, caplog): def test_incr_semver_invalid(runner, caplog):
@ -175,12 +187,8 @@ 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:
README_TEXT = """
Hello World v201701.1002-alpha !
aka. 201701.1002a0 !
"""
with pl.Path("README.md").open(mode="wt", encoding="utf-8") as fobj: with pl.Path("README.md").open(mode="wt", encoding="utf-8") as fobj:
fobj.write(README_TEXT) fobj.write(README_TEXT_FIXTURE)
if "setup.cfg" in files: if "setup.cfg" in files:
with pl.Path("setup.cfg").open(mode="wt", encoding="utf-8") as fobj: with pl.Path("setup.cfg").open(mode="wt", encoding="utf-8") as fobj:
@ -195,6 +203,22 @@ def _add_project_files(*files):
fobj.write(PYPROJECT_TOML_FIXTURE) fobj.write(PYPROJECT_TOML_FIXTURE)
def _update_config_val(filename, **kwargs):
with io.open(filename, mode="r", encoding="utf-8") as fobj:
old_cfg_text = fobj.read()
new_cfg_text = old_cfg_text
for key, val in kwargs.items():
replacement = "{} = {}\n".format(key, val)
if replacement not in new_cfg_text:
pattern = r"^{} = .*$".format(key)
new_cfg_text = re.sub(pattern, replacement, new_cfg_text, flags=re.MULTILINE)
assert old_cfg_text != new_cfg_text
with io.open(filename, mode="w", encoding="utf-8") as fobj:
fobj.write(new_cfg_text)
def test_nocfg(runner, caplog): def test_nocfg(runner, caplog):
_add_project_files("README.md") _add_project_files("README.md")
result = runner.invoke(cli, ['show', "-vv"]) result = runner.invoke(cli, ['show', "-vv"])
@ -491,7 +515,7 @@ setup.cfg =
""" """
def test_bump_semver_warning(runner, caplog): def test_v1_bump_semver_warning(runner, caplog):
_add_project_files("README.md") _add_project_files("README.md")
with pl.Path("setup.cfg").open(mode="w") as fobj: with pl.Path("setup.cfg").open(mode="w") as fobj:
@ -509,7 +533,7 @@ def test_bump_semver_warning(runner, caplog):
assert result.exit_code == 0 assert result.exit_code == 0
def test_bump_semver_diff(runner, caplog): def test_v1_bump_semver_diff(runner, caplog):
_add_project_files("README.md") _add_project_files("README.md")
with pl.Path("setup.cfg").open(mode="w") as fobj: with pl.Path("setup.cfg").open(mode="w") as fobj:
@ -531,6 +555,48 @@ def test_bump_semver_diff(runner, caplog):
assert f"+current_version = \"{expected}\"" in out_lines assert f"+current_version = \"{expected}\"" in out_lines
def test_v1_get_diff(runner):
_add_project_files("README.md", "setup.cfg")
result = runner.invoke(cli, ['init', "-vv"])
assert result.exit_code == 0
_update_config_val("setup.cfg", version_pattern='"{pycalver}"')
_, cfg = config.init()
new_version = "v202010.1003-beta"
diff_str = v1cli.get_diff(cfg, new_version)
diff_lines = set(diff_str.splitlines())
assert "- Hello World v201701.1002-alpha !" in diff_lines
assert "- aka. 201701.1002a0 !" in diff_lines
assert "+ Hello World v202010.1003-beta !" in diff_lines
assert "+ aka. 202010.1003b0 !" in diff_lines
assert '-current_version = "v202010.1001-alpha"' in diff_lines
assert '+current_version = "v202010.1003-beta"' in diff_lines
def test_v2_get_diff(runner):
_add_project_files("README.md", "setup.cfg")
result = runner.invoke(cli, ['init', "-vv"])
assert result.exit_code == 0
_update_config_val("setup.cfg", version_pattern='"vYYYY0M.BUILD[-RELEASE]"')
_, cfg = config.init()
new_version = "v202010.1003-beta"
diff_str = v2cli.get_diff(cfg, new_version)
diff_lines = set(diff_str.splitlines())
assert "- Hello World v201701.1002-alpha !" in diff_lines
assert "- aka. 201701.1002a0 !" in diff_lines
assert "+ Hello World v202010.1003-beta !" in diff_lines
assert "+ aka. 202010.1003b0 !" in diff_lines
assert '-current_version = "v202010.1001-alpha"' in diff_lines
assert '+current_version = "v202010.1003-beta"' in diff_lines
# def test_custom_commit_message(runner): # def test_custom_commit_message(runner):
# # TODO (mb 2020-09-18): # # TODO (mb 2020-09-18):
# assert False # assert False

View file

@ -90,6 +90,13 @@ def mk_buf(text):
return buf return buf
def _parse_raw_patterns_by_filepath(cfg):
return {
filepath: [pattern.raw_pattern for pattern in patterns]
for filepath, patterns in cfg.file_patterns.items()
}
def test_parse_toml_1(): def test_parse_toml_1():
buf = mk_buf(PYCALVER_TOML_FIXTURE_1) buf = mk_buf(PYCALVER_TOML_FIXTURE_1)
@ -103,8 +110,10 @@ def test_parse_toml_1():
assert cfg.push is True assert cfg.push is True
assert "pycalver.toml" in cfg.file_patterns assert "pycalver.toml" in cfg.file_patterns
assert cfg.file_patterns["README.md" ] == ["{pycalver}", "{pep440_pycalver}"]
assert cfg.file_patterns["pycalver.toml"] == ['current_version = "{pycalver}"'] raw_patterns_by_filepath = _parse_raw_patterns_by_filepath(cfg)
assert raw_patterns_by_filepath["README.md" ] == ["{pycalver}", "{pep440_pycalver}"]
assert raw_patterns_by_filepath["pycalver.toml"] == ['current_version = "{pycalver}"']
def test_parse_toml_2(): def test_parse_toml_2():
@ -120,8 +129,10 @@ def test_parse_toml_2():
assert cfg.push is False assert cfg.push is False
assert "pycalver.toml" in cfg.file_patterns assert "pycalver.toml" in cfg.file_patterns
assert cfg.file_patterns["README.md" ] == ["{semver}", "{semver}"]
assert cfg.file_patterns["pycalver.toml"] == ['current_version = "{semver}"'] raw_patterns_by_filepath = _parse_raw_patterns_by_filepath(cfg)
assert raw_patterns_by_filepath["README.md" ] == ["{semver}", "{semver}"]
assert raw_patterns_by_filepath["pycalver.toml"] == ['current_version = "{semver}"']
def test_parse_v1_cfg(): def test_parse_v1_cfg():
@ -136,8 +147,10 @@ def test_parse_v1_cfg():
assert cfg.push is True assert cfg.push is True
assert "setup.cfg" in cfg.file_patterns assert "setup.cfg" in cfg.file_patterns
assert cfg.file_patterns["setup.py" ] == ["{pycalver}", "{pep440_pycalver}"]
assert cfg.file_patterns["setup.cfg"] == ['current_version = "{pycalver}"'] raw_patterns_by_filepath = _parse_raw_patterns_by_filepath(cfg)
assert raw_patterns_by_filepath["setup.py" ] == ["{pycalver}", "{pep440_pycalver}"]
assert raw_patterns_by_filepath["setup.cfg"] == ['current_version = "{pycalver}"']
def test_parse_v2_cfg(): def test_parse_v2_cfg():
@ -153,10 +166,12 @@ def test_parse_v2_cfg():
assert "setup.py" in cfg.file_patterns assert "setup.py" in cfg.file_patterns
assert "setup.cfg" in cfg.file_patterns assert "setup.cfg" in cfg.file_patterns
# TODO (mb 2020-09-18): # TODO (mb 2020-09-18):
# assert cfg.file_patterns["setup.py" ] == ["vYYYY0M.BUILD[-RELEASE]", "YYYY0M.BLD[PYTAGNUM]"] # raw_patterns_by_filepath = _parse_raw_patterns_by_filepath(cfg)
# assert cfg.file_patterns["setup.cfg" ] == ['current_version = "vYYYY0M.BUILD[-RELEASE]"'] # assert raw_patterns_by_filepath["setup.py" ] == ["vYYYY0M.BUILD[-RELEASE]", "YYYY0M.BLD[PYTAGNUM]"]
# assert cfg.file_patterns["src/project/*.py"] == ['Copyright (c) 2018-YYYY"'] # assert raw_patterns_by_filepath["setup.cfg" ] == ['current_version = "vYYYY0M.BUILD[-RELEASE]"']
# assert raw_patterns_by_filepath["src/project/*.py"] == ['Copyright (c) 2018-YYYY"']
def test_parse_default_toml(): def test_parse_default_toml():
@ -186,8 +201,9 @@ def test_parse_default_cfg():
def test_parse_project_toml(): def test_parse_project_toml():
project_path = util.FIXTURES_DIR / "project_a" project_path = util.FIXTURES_DIR / "project_a"
config_path = util.FIXTURES_DIR / "project_a" / "pycalver.toml" config_path = util.FIXTURES_DIR / "project_a" / "pycalver.toml"
config_rel_path = "pycalver.toml"
with config_path.open() as fobj: with config_path.open() as fobj:
config_data = fobj.read() config_data = fobj.read()
@ -195,7 +211,7 @@ def test_parse_project_toml():
assert "v201710.0123-alpha" in config_data assert "v201710.0123-alpha" in config_data
ctx = config.init_project_ctx(project_path) ctx = config.init_project_ctx(project_path)
assert ctx == config.ProjectContext(project_path, config_path, "toml", None) assert ctx == config.ProjectContext(project_path, config_path, config_rel_path, "toml", None)
cfg = config.parse(ctx) cfg = config.parse(ctx)
@ -210,8 +226,9 @@ def test_parse_project_toml():
def test_parse_project_cfg(): def test_parse_project_cfg():
project_path = util.FIXTURES_DIR / "project_b" project_path = util.FIXTURES_DIR / "project_b"
config_path = util.FIXTURES_DIR / "project_b" / "setup.cfg" config_path = util.FIXTURES_DIR / "project_b" / "setup.cfg"
config_rel_path = "setup.cfg"
with config_path.open() as fobj: with config_path.open() as fobj:
config_data = fobj.read() config_data = fobj.read()
@ -219,7 +236,7 @@ def test_parse_project_cfg():
assert "v201307.0456-beta" in config_data assert "v201307.0456-beta" in config_data
ctx = config.init_project_ctx(project_path) ctx = config.init_project_ctx(project_path)
assert ctx == config.ProjectContext(project_path, config_path, 'cfg', None) assert ctx == config.ProjectContext(project_path, config_path, config_rel_path, 'cfg', None)
cfg = config.parse(ctx) cfg = config.parse(ctx)
@ -241,9 +258,10 @@ def test_parse_toml_file(tmpdir):
project_path = tmpdir.mkdir("minimal") project_path = tmpdir.mkdir("minimal")
setup_cfg = project_path.join("pycalver.toml") setup_cfg = project_path.join("pycalver.toml")
setup_cfg.write(PYCALVER_TOML_FIXTURE_1) setup_cfg.write(PYCALVER_TOML_FIXTURE_1)
setup_cfg_rel_path = "pycalver.toml"
ctx = config.init_project_ctx(project_path) ctx = config.init_project_ctx(project_path)
assert ctx == config.ProjectContext(project_path, setup_cfg, 'toml', None) assert ctx == config.ProjectContext(project_path, setup_cfg, setup_cfg_rel_path, 'toml', None)
cfg = config.parse(ctx) cfg = config.parse(ctx)
@ -253,19 +271,21 @@ def test_parse_toml_file(tmpdir):
assert cfg.commit is True assert cfg.commit is True
assert cfg.push is True assert cfg.push is True
assert cfg.file_patterns == { raw_patterns_by_filepath = _parse_raw_patterns_by_filepath(cfg)
assert raw_patterns_by_filepath == {
"README.md" : ["{pycalver}", "{pep440_pycalver}"], "README.md" : ["{pycalver}", "{pep440_pycalver}"],
"pycalver.toml": ['current_version = "{pycalver}"'], "pycalver.toml": ['current_version = "{pycalver}"'],
} }
def test_parse_default_pattern(): def test_parse_default_pattern():
project_path = util.FIXTURES_DIR / "project_c" project_path = util.FIXTURES_DIR / "project_c"
config_path = util.FIXTURES_DIR / "project_c" / "pyproject.toml" config_path = util.FIXTURES_DIR / "project_c" / "pyproject.toml"
config_rel_path = "pyproject.toml"
ctx = config.init_project_ctx(project_path) ctx = config.init_project_ctx(project_path)
assert ctx == config.ProjectContext(project_path, config_path, "toml", None) assert ctx == config.ProjectContext(project_path, config_path, config_rel_path, "toml", None)
cfg = config.parse(ctx) cfg = config.parse(ctx)
@ -276,7 +296,8 @@ def test_parse_default_pattern():
assert cfg.tag is True assert cfg.tag is True
assert cfg.push is True assert cfg.push is True
assert cfg.file_patterns == { raw_patterns_by_filepath = _parse_raw_patterns_by_filepath(cfg)
assert raw_patterns_by_filepath == {
"pyproject.toml": [r'current_version = "v{year}q{quarter}.{build_no}"'] "pyproject.toml": [r'current_version = "v{year}q{quarter}.{build_no}"']
} }
@ -285,9 +306,10 @@ def test_parse_cfg_file(tmpdir):
project_path = tmpdir.mkdir("minimal") project_path = tmpdir.mkdir("minimal")
setup_cfg = project_path.join("setup.cfg") setup_cfg = project_path.join("setup.cfg")
setup_cfg.write(SETUP_CFG_FIXTURE) setup_cfg.write(SETUP_CFG_FIXTURE)
setup_cfg_rel_path = "setup.cfg"
ctx = config.init_project_ctx(project_path) ctx = config.init_project_ctx(project_path)
assert ctx == config.ProjectContext(project_path, setup_cfg, 'cfg', None) assert ctx == config.ProjectContext(project_path, setup_cfg, setup_cfg_rel_path, 'cfg', None)
cfg = config.parse(ctx) cfg = config.parse(ctx)
@ -297,7 +319,8 @@ def test_parse_cfg_file(tmpdir):
assert cfg.commit is True assert cfg.commit is True
assert cfg.push is True assert cfg.push is True
assert cfg.file_patterns == { raw_patterns_by_filepath = _parse_raw_patterns_by_filepath(cfg)
assert raw_patterns_by_filepath == {
"setup.py" : ["{pycalver}", "{pep440_pycalver}"], "setup.py" : ["{pycalver}", "{pep440_pycalver}"],
"setup.cfg": ['current_version = "{pycalver}"'], "setup.cfg": ['current_version = "{pycalver}"'],
} }

View file

@ -4,6 +4,7 @@ from __future__ import print_function
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
import re
import copy import copy
from test import util from test import util
@ -13,35 +14,77 @@ from pycalver import v1rewrite
from pycalver import v1version from pycalver import v1version
from pycalver import v2rewrite from pycalver import v2rewrite
from pycalver import v2version from pycalver import v2version
from pycalver import v1patterns
from pycalver import v2patterns
# pylint:disable=protected-access ; allowed for test code # pylint:disable=protected-access ; allowed for test code
# Fix for Python<3.7
# https://stackoverflow.com/a/56935186/62997
copy._deepcopy_dispatch[type(re.compile(''))] = lambda r, _: r
REWRITE_FIXTURE = """ REWRITE_FIXTURE = """
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
__version__ = "v201809.0002-beta" __version__ = "v201809.0002-beta"
""" """
def test_rewrite_lines(): def test_v1_rewrite_lines_basic():
old_lines = REWRITE_FIXTURE.splitlines() pattern = v1patterns.compile_pattern("{pycalver}", '__version__ = "{pycalver}"')
patterns = ['__version__ = "{pycalver}"']
new_vinfo = v1version.parse_version_info("v201911.0003") new_vinfo = v1version.parse_version_info("v201911.0003")
new_lines = v1rewrite.rewrite_lines(patterns, new_vinfo, old_lines)
old_lines = REWRITE_FIXTURE.splitlines()
new_lines = v1rewrite.rewrite_lines([pattern], new_vinfo, old_lines)
assert len(new_lines) == len(old_lines) assert len(new_lines) == len(old_lines)
assert "v201911.0003" not in "\n".join(old_lines) assert "v201911.0003" not in "\n".join(old_lines)
assert "v201911.0003" in "\n".join(new_lines) assert "v201911.0003" in "\n".join(new_lines)
def test_rewrite_final(): def test_v1_rewrite_lines():
version_pattern = "{pycalver}"
new_vinfo = v1version.parse_version_info("v201811.0123-beta", version_pattern)
patterns = [v1patterns.compile_pattern(version_pattern, '__version__ = "{pycalver}"')]
lines = v1rewrite.rewrite_lines(patterns, new_vinfo, ['__version__ = "v201809.0002-beta"'])
assert lines == ['__version__ = "v201811.0123-beta"']
patterns = [v1patterns.compile_pattern(version_pattern, '__version__ = "{pep440_version}"')]
lines = v1rewrite.rewrite_lines(patterns, new_vinfo, ['__version__ = "201809.2b0"'])
assert lines == ['__version__ = "201811.123b0"']
def test_v2_rewrite_lines():
version_pattern = "vYYYY0M.BUILD[-RELEASE]"
new_vinfo = v2version.parse_version_info("v201811.0123-beta", version_pattern)
patterns = [v2patterns.compile_pattern(version_pattern, '__version__ = "{version}"')]
lines = v2rewrite.rewrite_lines(patterns, new_vinfo, ['__version__ = "v201809.0002-alpha" '])
assert lines == ['__version__ = "v201811.0123-beta" ']
lines = v2rewrite.rewrite_lines(
patterns, new_vinfo, ['__version__ = "v201809.0002-alpha" # comment']
)
assert lines == ['__version__ = "v201811.0123-beta" # comment']
patterns = [v2patterns.compile_pattern(version_pattern, '__version__ = "YYYY0M.BLD[PYTAGNUM]"')]
old_lines = ['__version__ = "201809.2a0"']
lines = v2rewrite.rewrite_lines(patterns, new_vinfo, old_lines)
assert lines == ['__version__ = "201811.123b0"']
def test_v1_rewrite_final():
# Patterns written with {release_tag} placeholder preserve # Patterns written with {release_tag} placeholder preserve
# the release tag even if the new version is -final # the release tag even if the new version is -final
old_lines = REWRITE_FIXTURE.splitlines() pattern = v1patterns.compile_pattern(
patterns = ['__version__ = "v{year}{month}.{build_no}-{release_tag}"'] "v{year}{month}.{build_no}-{release_tag}",
'__version__ = "v{year}{month}.{build_no}-{release_tag}"',
)
new_vinfo = v1version.parse_version_info("v201911.0003") new_vinfo = v1version.parse_version_info("v201911.0003")
new_lines = v1rewrite.rewrite_lines(patterns, new_vinfo, old_lines)
old_lines = REWRITE_FIXTURE.splitlines()
new_lines = v1rewrite.rewrite_lines([pattern], new_vinfo, old_lines)
assert len(new_lines) == len(old_lines) assert len(new_lines) == len(old_lines)
assert "v201911.0003" not in "\n".join(old_lines) assert "v201911.0003" not in "\n".join(old_lines)
@ -93,14 +136,19 @@ def test_error_bad_path():
assert "setup.py" in str(ex) assert "setup.py" in str(ex)
def test_error_bad_pattern(): def test_v1_error_bad_pattern():
with util.Project(project="b") as project: with util.Project(project="b") as project:
ctx = config.init_project_ctx(project.dir) ctx = config.init_project_ctx(project.dir)
cfg = config.parse(ctx) cfg = config.parse(ctx)
assert cfg assert cfg
patterns = copy.deepcopy(cfg.file_patterns) patterns = copy.deepcopy(cfg.file_patterns)
patterns["setup.py"] = patterns["setup.py"][0] + "invalid" original_pattern = patterns["setup.py"][0]
invalid_pattern = v1patterns.compile_pattern(
original_pattern.version_pattern,
original_pattern.raw_pattern + ".invalid",
)
patterns["setup.py"] = [invalid_pattern]
try: try:
old_vinfo = v1version.parse_version_info("v201808.0233") old_vinfo = v1version.parse_version_info("v201808.0233")
@ -118,45 +166,163 @@ __version__ = "2018.0002-beta"
def test_v1_optional_release(): def test_v1_optional_release():
old_lines = OPTIONAL_RELEASE_FIXTURE.splitlines() version_pattern = "{year}.{build_no}{release}"
pattern = "{year}.{build_no}{release}" new_vinfo = v1version.parse_version_info("2019.0003", version_pattern)
patterns = ['__version__ = "{year}.{build_no}{release}"']
new_vinfo = v1version.parse_version_info("2019.0003", pattern) raw_pattern = '__version__ = "{year}.{build_no}{release}"'
new_lines = v1rewrite.rewrite_lines(patterns, new_vinfo, old_lines) pattern = v1patterns.compile_pattern(version_pattern, raw_pattern)
old_lines = OPTIONAL_RELEASE_FIXTURE.splitlines()
new_lines = v1rewrite.rewrite_lines([pattern], new_vinfo, old_lines)
assert len(new_lines) == len(old_lines) assert len(new_lines) == len(old_lines)
assert "2019.0003" not in "\n".join(old_lines) assert "2019.0003" not in "\n".join(old_lines)
new_text = "\n".join(new_lines) assert "2019.0003" in "\n".join(new_lines)
assert "2019.0003" in new_text assert '__version__ = "2019.0003"' in "\n".join(new_lines)
new_vinfo = v1version.parse_version_info("2019.0004-beta", pattern) new_vinfo = v1version.parse_version_info("2019.0004-beta", version_pattern)
new_lines = v1rewrite.rewrite_lines(patterns, new_vinfo, old_lines) new_lines = v1rewrite.rewrite_lines([pattern], new_vinfo, old_lines)
# make sure optional release tag is added back on # make sure optional release tag is added back on
assert len(new_lines) == len(old_lines) assert len(new_lines) == len(old_lines)
assert "2019.0004-beta" not in "\n".join(old_lines) assert "2019.0004-beta" not in "\n".join(old_lines)
assert "2019.0004-beta" in "\n".join(new_lines) assert "2019.0004-beta" in "\n".join(new_lines)
assert '__version__ = "2019.0004-beta"' in "\n".join(new_lines)
def test_v2_optional_release(): def test_v2_optional_release():
old_lines = OPTIONAL_RELEASE_FIXTURE.splitlines() version_pattern = "YYYY.BUILD[-RELEASE]"
pattern = "YYYY.BUILD[-RELEASE]" new_vinfo = v2version.parse_version_info("2019.0003", version_pattern)
patterns = ['__version__ = "YYYY.BUILD[-RELEASE]"']
new_vinfo = v2version.parse_version_info("2019.0003", pattern) raw_pattern = '__version__ = "YYYY.BUILD[-RELEASE]"'
new_lines = v2rewrite.rewrite_lines(patterns, new_vinfo, old_lines) pattern = v2patterns.compile_pattern(version_pattern, raw_pattern)
old_lines = OPTIONAL_RELEASE_FIXTURE.splitlines()
new_lines = v2rewrite.rewrite_lines([pattern], new_vinfo, old_lines)
assert len(new_lines) == len(old_lines) assert len(new_lines) == len(old_lines)
assert "2019.0003" not in "\n".join(old_lines) assert "2019.0003" not in "\n".join(old_lines)
new_text = "\n".join(new_lines) assert "2019.0003" in "\n".join(new_lines)
assert "2019.0003" in new_text assert '__version__ = "2019.0003"' in "\n".join(new_lines)
assert '__version__ = "2019.0003"' in new_text
new_vinfo = v2version.parse_version_info("2019.0004-beta", pattern) new_vinfo = v2version.parse_version_info("2019.0004-beta", version_pattern)
new_lines = v2rewrite.rewrite_lines(patterns, new_vinfo, old_lines) new_lines = v2rewrite.rewrite_lines([pattern], new_vinfo, old_lines)
# make sure optional release tag is added back on # make sure optional release tag is added back on
assert len(new_lines) == len(old_lines) assert len(new_lines) == len(old_lines)
assert "2019.0004-beta" not in "\n".join(old_lines) assert "2019.0004-beta" not in "\n".join(old_lines)
assert "2019.0004-beta" in "\n".join(new_lines) assert "2019.0004-beta" in "\n".join(new_lines)
assert '__version__ = "2019.0004-beta"' in "\n".join(new_lines)
def test_v1_iter_rewritten():
version_pattern = "{pycalver}"
new_vinfo = v1version.parse_version_info("v201809.0123")
file_patterns = {
"src/pycalver/__init__.py": [
v1patterns.compile_pattern(version_pattern, '__version__ = "{pycalver}"'),
]
}
rewritten_datas = v1rewrite.iter_rewritten(file_patterns, new_vinfo)
rfd = list(rewritten_datas)[0]
expected = [
"# 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"',
'',
]
assert rfd.new_lines == expected
def test_v2_iter_rewritten():
version_pattern = "vYYYY0M.BUILD[-RELEASE]"
new_vinfo = v2version.parse_version_info("v201809.0123", version_pattern)
file_patterns = {
"src/pycalver/__init__.py": [
v2patterns.compile_pattern(version_pattern, '__version__ = "vYYYY0M.BUILD[-RELEASE]"'),
]
}
rewritten_datas = v2rewrite.iter_rewritten(file_patterns, new_vinfo)
rfd = list(rewritten_datas)[0]
expected = [
"# 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"',
'',
]
assert rfd.new_lines == expected
def test_v1_diff():
version_pattern = "{pycalver}"
raw_pattern = '__version__ = "{pycalver}"'
pattern = v1patterns.compile_pattern(version_pattern, raw_pattern)
file_patterns = {"src/pycalver/__init__.py": [pattern]}
old_vinfo = v1version.parse_version_info("v201809.0123")
new_vinfo = v1version.parse_version_info("v201910.1124")
diff_str = v1rewrite.diff(old_vinfo, new_vinfo, file_patterns)
lines = diff_str.split("\n")
assert lines[:2] == ["--- src/pycalver/__init__.py", "+++ src/pycalver/__init__.py"]
assert lines[6].startswith('-__version__ = "v20')
assert lines[7].startswith('+__version__ = "v20')
assert not lines[6].startswith('-__version__ = "v201809.0123"')
assert lines[7] == '+__version__ = "v201910.1124"'
raw_pattern = "Copyright (c) 2018-{year}"
pattern = v1patterns.compile_pattern(version_pattern, raw_pattern)
file_patterns = {'LICENSE': [pattern]}
diff_str = v1rewrite.diff(old_vinfo, new_vinfo, file_patterns)
lines = diff_str.split("\n")
assert lines[3].startswith("-MIT License Copyright (c) 2018-20")
assert lines[4].startswith("+MIT License Copyright (c) 2018-2019")
def test_v2_diff():
version_pattern = "vYYYY0M.BUILD[-RELEASE]"
raw_pattern = '__version__ = "vYYYY0M.BUILD[-RELEASE]"'
pattern = v2patterns.compile_pattern(version_pattern, raw_pattern)
file_patterns = {"src/pycalver/__init__.py": [pattern]}
old_vinfo = v2version.parse_version_info("v201809.0123", version_pattern)
new_vinfo = v2version.parse_version_info("v201910.1124", version_pattern)
diff_str = v2rewrite.diff(old_vinfo, new_vinfo, file_patterns)
lines = diff_str.split("\n")
assert lines[:2] == ["--- src/pycalver/__init__.py", "+++ src/pycalver/__init__.py"]
assert lines[6].startswith('-__version__ = "v20')
assert lines[7].startswith('+__version__ = "v20')
assert not lines[6].startswith('-__version__ = "v201809.0123"')
assert lines[7] == '+__version__ = "v201910.1124"'
raw_pattern = "Copyright (c) 2018-YYYY"
pattern = v2patterns.compile_pattern(version_pattern, raw_pattern)
file_patterns = {'LICENSE': [pattern]}
diff_str = v2rewrite.diff(old_vinfo, new_vinfo, file_patterns)
lines = diff_str.split("\n")
assert lines[3].startswith("-MIT License Copyright (c) 2018-20")
assert lines[4].startswith("+MIT License Copyright (c) 2018-2019")