mirror of
https://github.com/TECHNOFAB11/bumpver.git
synced 2025-12-12 06:20:08 +01:00
Add more flexible parsing and formating
This commit is contained in:
parent
32447b03d4
commit
9eda61d95b
13 changed files with 932 additions and 359 deletions
10
setup.cfg
10
setup.cfg
|
|
@ -75,15 +75,15 @@ push = True
|
|||
|
||||
[pycalver:file_patterns]
|
||||
bootstrapit.sh =
|
||||
PACKAGE_VERSION="{version}"
|
||||
PACKAGE_VERSION="{pycalver}"
|
||||
setup.cfg =
|
||||
current_version = {version}
|
||||
current_version = {pycalver}
|
||||
setup.py =
|
||||
version="{pep440_version}"
|
||||
version="{pep440_pycalver}"
|
||||
src/pycalver/__init__.py =
|
||||
__version__ = "{version}"
|
||||
__version__ = "{pycalver}"
|
||||
src/pycalver/__main__.py =
|
||||
click.version_option(version="{version}")
|
||||
click.version_option(version="{pycalver}")
|
||||
README.md =
|
||||
[PyCalVer {version}]
|
||||
https://img.shields.io/badge/PyCalVer-{calver}{build}-blue.svg
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ import logging
|
|||
import typing as typ
|
||||
|
||||
from . import vcs
|
||||
from . import parse
|
||||
from . import config
|
||||
from . import version
|
||||
from . import rewrite
|
||||
|
|
@ -25,19 +24,22 @@ from . import rewrite
|
|||
_VERBOSE = 0
|
||||
|
||||
|
||||
try:
|
||||
import backtrace
|
||||
# try:
|
||||
# import backtrace
|
||||
|
||||
# To enable pretty tracebacks:
|
||||
# echo "export ENABLE_BACKTRACE=1;" >> ~/.bashrc
|
||||
backtrace.hook(align=True, strip_path=True, enable_on_envvar_only=True)
|
||||
except ImportError:
|
||||
pass
|
||||
# # To enable pretty tracebacks:
|
||||
# # echo "export ENABLE_BACKTRACE=1;" >> ~/.bashrc
|
||||
# backtrace.hook(align=True, strip_path=True, enable_on_envvar_only=True)
|
||||
# except ImportError:
|
||||
# pass
|
||||
|
||||
|
||||
click.disable_unicode_literals_warning = True
|
||||
|
||||
|
||||
VALID_RELEASE_VALUES = ("alpha", "beta", "dev", "rc", "post", "final")
|
||||
|
||||
|
||||
log = logging.getLogger("pycalver.cli")
|
||||
|
||||
|
||||
|
|
@ -57,11 +59,11 @@ def _init_logging(verbose: int = 0) -> None:
|
|||
|
||||
|
||||
def _validate_release_tag(release: str) -> None:
|
||||
if release in parse.VALID_RELEASE_VALUES:
|
||||
if release in VALID_RELEASE_VALUES:
|
||||
return
|
||||
|
||||
log.error(f"Invalid argument --release={release}")
|
||||
log.error(f"Valid arguments are: {', '.join(parse.VALID_RELEASE_VALUES)}")
|
||||
log.error(f"Valid arguments are: {', '.join(VALID_RELEASE_VALUES)}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
|
|
@ -77,22 +79,40 @@ def cli(verbose: int = 0):
|
|||
|
||||
@cli.command()
|
||||
@click.argument("old_version")
|
||||
@click.argument("pattern", default="{pycalver}")
|
||||
@click.option('-v', '--verbose', count=True, help="Control log level. -vv for debug level.")
|
||||
@click.option(
|
||||
"--release", default=None, metavar="<name>", help="Override release name of current_version"
|
||||
)
|
||||
def test(old_version: str, verbose: int = 0, release: str = None) -> None:
|
||||
@click.option("--major", is_flag=True, default=False, help="Increment major component.")
|
||||
@click.option("--minor", is_flag=True, default=False, help="Increment minor component.")
|
||||
@click.option("--patch", is_flag=True, default=False, help="Increment patch component.")
|
||||
def test(
|
||||
old_version: str,
|
||||
pattern : str = "{pycalver}",
|
||||
verbose : int = 0,
|
||||
release : str = None,
|
||||
major : bool = False,
|
||||
minor : bool = False,
|
||||
patch : bool = False,
|
||||
) -> None:
|
||||
"""Increment a version number for demo purposes."""
|
||||
_init_logging(verbose=max(_VERBOSE, verbose))
|
||||
|
||||
if release:
|
||||
_validate_release_tag(release)
|
||||
|
||||
new_version = version.incr(old_version, release=release)
|
||||
pep440_version = version.pycalver_to_pep440(new_version)
|
||||
new_version = version.incr(
|
||||
old_version, pattern=pattern, release=release, major=major, minor=minor, patch=patch
|
||||
)
|
||||
if new_version is None:
|
||||
log.error(f"Invalid version '{old_version}' and/or pattern '{pattern}'.")
|
||||
sys.exit(1)
|
||||
|
||||
print("PyCalVer Version:", new_version)
|
||||
print("PEP440 Version :", pep440_version)
|
||||
pep440_version = version.to_pep440(new_version)
|
||||
|
||||
print("New Version:", new_version)
|
||||
print("PEP440 :", pep440_version)
|
||||
|
||||
|
||||
def _update_cfg_from_vcs(cfg: config.Config, fetch: bool) -> config.Config:
|
||||
|
|
@ -103,12 +123,12 @@ def _update_cfg_from_vcs(cfg: config.Config, fetch: bool) -> config.Config:
|
|||
log.info(f"fetching tags from remote (to turn off use: -n / --no-fetch)")
|
||||
_vcs.fetch()
|
||||
|
||||
version_tags = [tag for tag in _vcs.ls_tags() if version.PYCALVER_RE.match(tag)]
|
||||
version_tags = [tag for tag in _vcs.ls_tags() if version.is_valid(tag, cfg.version_pattern)]
|
||||
if version_tags:
|
||||
version_tags.sort(reverse=True)
|
||||
log.debug(f"found {len(version_tags)} tags: {version_tags[:2]}")
|
||||
latest_version_tag = version_tags[0]
|
||||
latest_version_pep440 = version.pycalver_to_pep440(latest_version_tag)
|
||||
latest_version_pep440 = version.to_pep440(latest_version_tag)
|
||||
if latest_version_tag > cfg.current_version:
|
||||
log.info(f"Working dir version : {cfg.current_version}")
|
||||
log.info(f"Latest version from {_vcs.name:>3} tag: {latest_version_tag}")
|
||||
|
|
@ -143,7 +163,7 @@ def show(verbose: int = 0, fetch: bool = True) -> None:
|
|||
cfg = _update_cfg_from_vcs(cfg, fetch=fetch)
|
||||
|
||||
print(f"Current Version: {cfg.current_version}")
|
||||
print(f"PEP440 Version : {cfg.pep440_version}")
|
||||
print(f"PEP440 : {cfg.pep440_version}")
|
||||
|
||||
|
||||
@cli.command()
|
||||
|
|
@ -235,7 +255,7 @@ def _bump(cfg: config.Config, new_version: str, allow_dirty: bool = False) -> No
|
|||
metavar="<name>",
|
||||
help=(
|
||||
f"Override release name of current_version. Valid options are: "
|
||||
f"{', '.join(parse.VALID_RELEASE_VALUES)}."
|
||||
f"{', '.join(VALID_RELEASE_VALUES)}."
|
||||
),
|
||||
)
|
||||
@click.option(
|
||||
|
|
@ -248,12 +268,18 @@ def _bump(cfg: config.Config, new_version: str, allow_dirty: bool = False) -> No
|
|||
"to files with version strings."
|
||||
),
|
||||
)
|
||||
@click.option("--major", is_flag=True, default=False, help="Increment major component.")
|
||||
@click.option("--minor", is_flag=True, default=False, help="Increment minor component.")
|
||||
@click.option("--patch", is_flag=True, default=False, help="Increment patch component.")
|
||||
def bump(
|
||||
release : typ.Optional[str] = None,
|
||||
verbose : int = 0,
|
||||
dry : bool = False,
|
||||
allow_dirty: bool = False,
|
||||
fetch : bool = True,
|
||||
major : bool = False,
|
||||
minor : bool = False,
|
||||
patch : bool = False,
|
||||
) -> None:
|
||||
"""Increment the current version string and update project files."""
|
||||
verbose = max(_VERBOSE, verbose)
|
||||
|
|
@ -272,7 +298,17 @@ def bump(
|
|||
cfg = _update_cfg_from_vcs(cfg, fetch=fetch)
|
||||
|
||||
old_version = cfg.current_version
|
||||
new_version = version.incr(old_version, release=release)
|
||||
new_version = version.incr(
|
||||
old_version,
|
||||
pattern=cfg.version_pattern,
|
||||
release=release,
|
||||
major=major,
|
||||
minor=minor,
|
||||
patch=patch,
|
||||
)
|
||||
if new_version is None:
|
||||
log.error(f"Invalid version '{old_version}' and/or pattern '{cfg.version_pattern}'.")
|
||||
sys.exit(1)
|
||||
|
||||
log.info(f"Old Version: {old_version}")
|
||||
log.info(f"New Version: {new_version}")
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ class Config(typ.NamedTuple):
|
|||
"""Container for parameters parsed from a config file."""
|
||||
|
||||
current_version: str
|
||||
version_pattern: str
|
||||
pep440_version : str
|
||||
|
||||
commit: bool
|
||||
|
|
@ -90,6 +91,7 @@ def _debug_str(cfg: Config) -> str:
|
|||
cfg_str_parts = [
|
||||
f"Config Parsed: Config(",
|
||||
f"current_version='{cfg.current_version}'",
|
||||
f"version_pattern='{{pycalver}}'",
|
||||
f"pep440_version='{cfg.pep440_version}'",
|
||||
f"commit={cfg.commit}",
|
||||
f"tag={cfg.tag}",
|
||||
|
|
@ -179,17 +181,52 @@ def _parse_toml(cfg_buffer: typ.TextIO) -> RawConfig:
|
|||
return raw_cfg
|
||||
|
||||
|
||||
def _normalize_file_patterns(raw_cfg: RawConfig) -> FilePatterns:
|
||||
version_str = raw_cfg['current_version']
|
||||
version_pattern = raw_cfg['version_pattern']
|
||||
pep440_version = version.to_pep440(version_str)
|
||||
file_patterns = raw_cfg['file_patterns']
|
||||
|
||||
for filepath, patterns in list(file_patterns.items()):
|
||||
if not os.path.exists(filepath):
|
||||
log.warning(f"Invalid config, no such file: {filepath}")
|
||||
|
||||
normalized_patterns: typ.List[str] = []
|
||||
for pattern in patterns:
|
||||
normalized_pattern = pattern.replace("{version}", version_pattern)
|
||||
if version_pattern == "{pycalver}":
|
||||
normalized_pattern = normalized_pattern.replace(
|
||||
"{pep440_version}", "{pep440_pycalver}"
|
||||
)
|
||||
elif version_pattern == "{semver}":
|
||||
normalized_pattern = normalized_pattern.replace("{pep440_version}", "{semver}")
|
||||
elif "{pep440_version}" in pattern:
|
||||
log.warning(f"Invalid config, cannot match '{pattern}' for '{filepath}'.")
|
||||
log.warning(f"No mapping of '{version_pattern}' to '{pep440_version}'")
|
||||
normalized_patterns.append(normalized_pattern)
|
||||
|
||||
file_patterns[filepath] = normalized_patterns
|
||||
|
||||
return file_patterns
|
||||
|
||||
|
||||
def _parse_config(raw_cfg: RawConfig) -> Config:
|
||||
"""Parse configuration which was loaded from an .ini/.cfg or .toml file."""
|
||||
|
||||
if 'current_version' not in raw_cfg:
|
||||
raise ValueError("Missing 'pycalver.current_version'")
|
||||
|
||||
version_str = raw_cfg['current_version']
|
||||
version_str = raw_cfg['current_version'] = version_str.strip("'\" ")
|
||||
|
||||
if version.PYCALVER_RE.match(version_str) is None:
|
||||
raise ValueError(f"Invalid current_version = {version_str}")
|
||||
version_pattern = raw_cfg.get('version_pattern', "{pycalver}")
|
||||
version_pattern = raw_cfg['version_pattern'] = version_pattern.strip("'\" ")
|
||||
|
||||
pep440_version = version.pycalver_to_pep440(version_str)
|
||||
# NOTE (mb 2019-01-05): trigger ValueError if version_pattern
|
||||
# and current_version don't work together.
|
||||
version.parse_version_info(version_str, version_pattern)
|
||||
|
||||
pep440_version = version.to_pep440(version_str)
|
||||
|
||||
commit = raw_cfg['commit']
|
||||
tag = raw_cfg['tag']
|
||||
|
|
@ -206,13 +243,9 @@ def _parse_config(raw_cfg: RawConfig) -> Config:
|
|||
if push and not commit:
|
||||
raise ValueError("pycalver.commit = true required if pycalver.push = true")
|
||||
|
||||
file_patterns = raw_cfg['file_patterns']
|
||||
file_patterns = _normalize_file_patterns(raw_cfg)
|
||||
|
||||
for filepath in file_patterns.keys():
|
||||
if not os.path.exists(filepath):
|
||||
log.warning(f"Invalid configuration, no such file: {filepath}")
|
||||
|
||||
cfg = Config(version_str, pep440_version, tag, commit, push, file_patterns)
|
||||
cfg = Config(version_str, version_pattern, pep440_version, tag, commit, push, file_patterns)
|
||||
log.debug(_debug_str(cfg))
|
||||
return cfg
|
||||
|
||||
|
|
@ -241,6 +274,7 @@ def parse(ctx: ProjectContext) -> MaybeConfig:
|
|||
DEFAULT_CONFIGPARSER_BASE_TMPL = """
|
||||
[pycalver]
|
||||
current_version = "{initial_version}"
|
||||
version_pattern = "{{pycalver}}"
|
||||
commit = True
|
||||
tag = True
|
||||
push = True
|
||||
|
|
@ -279,6 +313,7 @@ README.md =
|
|||
DEFAULT_TOML_BASE_TMPL = """
|
||||
[pycalver]
|
||||
current_version = "{initial_version}"
|
||||
version_pattern = "{{pycalver}}"
|
||||
commit = true
|
||||
tag = true
|
||||
push = true
|
||||
|
|
|
|||
|
|
@ -5,50 +5,14 @@
|
|||
# SPDX-License-Identifier: MIT
|
||||
"""Parse PyCalVer strings from files."""
|
||||
|
||||
import re
|
||||
import logging
|
||||
import typing as typ
|
||||
|
||||
from . import patterns
|
||||
|
||||
log = logging.getLogger("pycalver.parse")
|
||||
|
||||
|
||||
VALID_RELEASE_VALUES = ("alpha", "beta", "dev", "rc", "post", "final")
|
||||
|
||||
|
||||
PATTERN_ESCAPES = [
|
||||
("\u005c", "\u005c\u005c"),
|
||||
("-" , "\u005c-"),
|
||||
("." , "\u005c."),
|
||||
("+" , "\u005c+"),
|
||||
("*" , "\u005c*"),
|
||||
("{" , "\u005c{{"),
|
||||
("}" , "\u005c}}"),
|
||||
("[" , "\u005c["),
|
||||
("]" , "\u005c]"),
|
||||
("(" , "\u005c("),
|
||||
(")" , "\u005c)"),
|
||||
]
|
||||
|
||||
# NOTE (mb 2018-09-03): These are matchers for parts, which are
|
||||
# used in the patterns, they're not for validation. This means
|
||||
# that they may find strings, which are not valid pycalver
|
||||
# strings, when parsed in their full context. For such cases,
|
||||
# the patterns should be expanded.
|
||||
|
||||
|
||||
RE_PATTERN_PARTS = {
|
||||
'pep440_version': r"\d{6}\.[1-9]\d*(a|b|dev|rc|post)?\d*",
|
||||
'version' : r"v\d{6}\.\d{4,}(\-(alpha|beta|dev|rc|post|final))?",
|
||||
'calver' : r"v\d{6}",
|
||||
'year' : r"\d{4}",
|
||||
'month' : r"\d{2}",
|
||||
'build' : r"\.\d{4,}",
|
||||
'build_no' : r"\d{4,}",
|
||||
'release' : r"(\-(alpha|beta|dev|rc|post|final))?",
|
||||
'release_tag' : r"(alpha|beta|dev|rc|post|final)?",
|
||||
}
|
||||
|
||||
|
||||
class PatternMatch(typ.NamedTuple):
|
||||
"""Container to mark a version string in a file."""
|
||||
|
||||
|
|
@ -62,26 +26,10 @@ class PatternMatch(typ.NamedTuple):
|
|||
PatternMatches = typ.Iterable[PatternMatch]
|
||||
|
||||
|
||||
def compile_pattern(pattern: str) -> typ.Pattern[str]:
|
||||
pattern_tmpl = pattern
|
||||
|
||||
for char, escaped in PATTERN_ESCAPES:
|
||||
pattern_tmpl = pattern_tmpl.replace(char, escaped)
|
||||
|
||||
# undo escaping only for valid part names
|
||||
for part_name in RE_PATTERN_PARTS.keys():
|
||||
pattern_tmpl = pattern_tmpl.replace(
|
||||
"\u005c{{" + part_name + "\u005c}}", "{" + part_name + "}"
|
||||
)
|
||||
|
||||
pattern_str = pattern_tmpl.format(**RE_PATTERN_PARTS)
|
||||
return re.compile(pattern_str)
|
||||
|
||||
|
||||
def _iter_for_pattern(lines: typ.List[str], pattern: str) -> PatternMatches:
|
||||
# The pattern is escaped, so that everything besides the format
|
||||
# string variables is treated literally.
|
||||
pattern_re = compile_pattern(pattern)
|
||||
pattern_re = patterns.compile_pattern(pattern)
|
||||
|
||||
for lineno, line in enumerate(lines):
|
||||
match = pattern_re.search(line)
|
||||
|
|
@ -93,12 +41,12 @@ def iter_matches(lines: typ.List[str], patterns: typ.List[str]) -> PatternMatche
|
|||
"""Iterate over all matches of any pattern on any line.
|
||||
|
||||
>>> lines = ["__version__ = 'v201712.0002-alpha'"]
|
||||
>>> patterns = ["{version}", "{pep440_version}"]
|
||||
>>> patterns = ["{pycalver}", "{pep440_pycalver}"]
|
||||
>>> matches = list(iter_matches(lines, patterns))
|
||||
>>> assert matches[0] == PatternMatch(
|
||||
... lineno = 0,
|
||||
... line = "__version__ = 'v201712.0002-alpha'",
|
||||
... pattern= "{version}",
|
||||
... pattern= "{pycalver}",
|
||||
... span = (15, 33),
|
||||
... match = "v201712.0002-alpha",
|
||||
... )
|
||||
|
|
|
|||
200
src/pycalver/patterns.py
Normal file
200
src/pycalver/patterns.py
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
# This file is part of the pycalver project
|
||||
# https://github.com/mbarkhau/pycalver
|
||||
#
|
||||
# Copyright (c) 2018 Manuel Barkhau (@mbarkhau) - MIT License
|
||||
# SPDX-License-Identifier: MIT
|
||||
"""Compose Regular Expressions from Patterns.
|
||||
|
||||
>>> version_info = PYCALVER_RE.match("v201712.0123-alpha").groupdict()
|
||||
>>> assert version_info == {
|
||||
... "pycalver" : "v201712.0123-alpha",
|
||||
... "vYYYYMM" : "v201712",
|
||||
... "year" : "2017",
|
||||
... "month" : "12",
|
||||
... "build" : ".0123",
|
||||
... "build_no" : "0123",
|
||||
... "release" : "-alpha",
|
||||
... "release_tag" : "alpha",
|
||||
... }
|
||||
>>>
|
||||
>>> version_info = PYCALVER_RE.match("v201712.0033").groupdict()
|
||||
>>> assert version_info == {
|
||||
... "pycalver" : "v201712.0033",
|
||||
... "vYYYYMM" : "v201712",
|
||||
... "year" : "2017",
|
||||
... "month" : "12",
|
||||
... "build" : ".0033",
|
||||
... "build_no" : "0033",
|
||||
... "release" : None,
|
||||
... "release_tag": None,
|
||||
... }
|
||||
"""
|
||||
|
||||
import re
|
||||
import typing as typ
|
||||
|
||||
# https://regex101.com/r/fnj60p/10
|
||||
PYCALVER_PATTERN = r"""
|
||||
\b
|
||||
(?P<pycalver>
|
||||
(?P<vYYYYMM>
|
||||
v # "v" version prefix
|
||||
(?P<year>\d{4})
|
||||
(?P<month>\d{2})
|
||||
)
|
||||
(?P<build>
|
||||
\. # "." build nr prefix
|
||||
(?P<build_no>\d{4,})
|
||||
)
|
||||
(?P<release>
|
||||
\- # "-" release prefix
|
||||
(?P<release_tag>alpha|beta|dev|rc|post)
|
||||
)?
|
||||
)(?:\s|$)
|
||||
"""
|
||||
|
||||
PYCALVER_RE: typ.Pattern[str] = re.compile(PYCALVER_PATTERN, flags=re.VERBOSE)
|
||||
|
||||
|
||||
PATTERN_ESCAPES = [
|
||||
("\u005c", "\u005c\u005c"),
|
||||
("-" , "\u005c-"),
|
||||
("." , "\u005c."),
|
||||
("+" , "\u005c+"),
|
||||
("*" , "\u005c*"),
|
||||
("{" , "\u005c{"),
|
||||
("}" , "\u005c}"),
|
||||
("[" , "\u005c["),
|
||||
("]" , "\u005c]"),
|
||||
("(" , "\u005c("),
|
||||
(")" , "\u005c)"),
|
||||
]
|
||||
|
||||
COMPOSITE_PART_PATTERNS = {
|
||||
'pep440_pycalver': r"{year}{month}\.{BID}(?:{pep440_tag})?",
|
||||
'pycalver' : r"v{year}{month}\.{bid}(?:-{tag})?",
|
||||
'calver' : r"v{year}{month}",
|
||||
'semver' : r"{MAJOR}\.{MINOR}\.{PATCH}",
|
||||
'release_tag' : r"{tag}",
|
||||
'build' : r"\.{bid}",
|
||||
'release' : r"-{tag}",
|
||||
# depricated
|
||||
'pep440_version': r"{year}{month}\.{BID}(?:{pep440_tag})?",
|
||||
}
|
||||
|
||||
|
||||
PART_PATTERNS = {
|
||||
'year' : r"\d{4}",
|
||||
'month' : r"(?:0[0-9]|1[0-2])",
|
||||
'build_no' : r"\d{4,}",
|
||||
'pep440_tag': r"(?:a|b|dev|rc|post)?\d*",
|
||||
'tag' : r"(?:alpha|beta|dev|rc|post|final)",
|
||||
'yy' : r"\d{2}",
|
||||
'yyyy' : r"\d{4}",
|
||||
'quarter' : r"[1-4]",
|
||||
'iso_week' : r"(?:[0-4]\d|5[0-3])",
|
||||
'us_week' : r"(?:[0-4]\d|5[0-3])",
|
||||
'dom' : r"(0[1-9]|[1-2][0-9]|3[0-1])",
|
||||
'doy' : r"(?:[0-2]\d\d|3[0-5][0-9]|36[0-6])",
|
||||
'MAJOR' : r"\d+",
|
||||
'MINOR' : r"\d+",
|
||||
'MM' : r"\d{2,}",
|
||||
'MMM' : r"\d{3,}",
|
||||
'MMMM' : r"\d{4,}",
|
||||
'MMMMM' : r"\d{5,}",
|
||||
'PATCH' : r"\d+",
|
||||
'PP' : r"\d{2,}",
|
||||
'PPP' : r"\d{3,}",
|
||||
'PPPP' : r"\d{4,}",
|
||||
'PPPPP' : r"\d{5,}",
|
||||
'bid' : r"\d{4,}",
|
||||
'BID' : r"[1-9]\d*",
|
||||
'BB' : r"[1-9]\d{1,}",
|
||||
'BBB' : r"[1-9]\d{2,}",
|
||||
'BBBB' : r"[1-9]\d{3,}",
|
||||
'BBBBB' : r"[1-9]\d{4,}",
|
||||
'BBBBBB' : r"[1-9]\d{5,}",
|
||||
'BBBBBBB' : r"[1-9]\d{6,}",
|
||||
}
|
||||
|
||||
|
||||
FULL_PART_FORMATS = {
|
||||
'pep440_pycalver': "{year}{month:02}.{BID}{pep440_tag}",
|
||||
'pycalver' : "v{year}{month:02}.{bid}{release}",
|
||||
'calver' : "v{year}{month:02}",
|
||||
'semver' : "{MAJOR}.{MINOR}.{PATCH}",
|
||||
'release_tag' : "{tag}",
|
||||
'build' : ".{bid}",
|
||||
# NOTE (mb 2019-01-04): since release is optional, it
|
||||
# is treates specially in version.format
|
||||
# 'release' : "-{tag}",
|
||||
'month' : "{month:02}",
|
||||
'build_no': "{bid}",
|
||||
'iso_week': "{iso_week:02}",
|
||||
'us_week' : "{us_week:02}",
|
||||
'dom' : "{dom:02}",
|
||||
'doy' : "{doy:03}",
|
||||
# depricated
|
||||
'pep440_version': "{year}{month:02}.{BID}{pep440_tag}",
|
||||
'version' : "v{year}{month:02}.{bid}{release}",
|
||||
}
|
||||
|
||||
|
||||
PART_FORMATS = {
|
||||
'major' : "[0-9]+",
|
||||
'minor' : "[0-9]{3,}",
|
||||
'patch' : "[0-9]{3,}",
|
||||
'bid' : "[0-9]{4,}",
|
||||
'MAJOR' : "[0-9]+",
|
||||
'MINOR' : "[0-9]+",
|
||||
'MM' : "[0-9]{2,}",
|
||||
'MMM' : "[0-9]{3,}",
|
||||
'MMMM' : "[0-9]{4,}",
|
||||
'MMMMM' : "[0-9]{5,}",
|
||||
'MMMMMM' : "[0-9]{6,}",
|
||||
'MMMMMMM': "[0-9]{7,}",
|
||||
'PATCH' : "[0-9]+",
|
||||
'PP' : "[0-9]{2,}",
|
||||
'PPP' : "[0-9]{3,}",
|
||||
'PPPP' : "[0-9]{4,}",
|
||||
'PPPPP' : "[0-9]{5,}",
|
||||
'PPPPPP' : "[0-9]{6,}",
|
||||
'PPPPPPP': "[0-9]{7,}",
|
||||
'BID' : "[1-9][0-9]*",
|
||||
'BB' : "[1-9][0-9]{1,}",
|
||||
'BBB' : "[1-9][0-9]{2,}",
|
||||
'BBBB' : "[1-9][0-9]{3,}",
|
||||
'BBBBB' : "[1-9][0-9]{4,}",
|
||||
'BBBBBB' : "[1-9][0-9]{5,}",
|
||||
'BBBBBBB': "[1-9][0-9]{6,}",
|
||||
}
|
||||
|
||||
|
||||
def _replace_pattern_parts(pattern: str) -> str:
|
||||
for part_name, part_pattern in PART_PATTERNS.items():
|
||||
named_part_pattern = f"(?P<{part_name}>{part_pattern})"
|
||||
placeholder = "\u005c{" + part_name + "\u005c}"
|
||||
pattern = pattern.replace(placeholder, named_part_pattern)
|
||||
return pattern
|
||||
|
||||
|
||||
def _compile_pattern(pattern: str) -> str:
|
||||
for char, escaped in PATTERN_ESCAPES:
|
||||
pattern = pattern.replace(char, escaped)
|
||||
|
||||
return _replace_pattern_parts(pattern)
|
||||
|
||||
|
||||
def compile_pattern(pattern: str) -> typ.Pattern[str]:
|
||||
pattern_str = _compile_pattern(pattern)
|
||||
return re.compile(pattern_str)
|
||||
|
||||
|
||||
def _init_composite_patterns() -> None:
|
||||
for part_name, part_pattern in COMPOSITE_PART_PATTERNS.items():
|
||||
part_pattern = part_pattern.replace("{", "\u005c{").replace("}", "\u005c}")
|
||||
pattern_str = _replace_pattern_parts(part_pattern)
|
||||
PART_PATTERNS[part_name] = pattern_str
|
||||
|
||||
|
||||
_init_composite_patterns()
|
||||
|
|
@ -43,18 +43,20 @@ def rewrite_lines(
|
|||
) -> typ.List[str]:
|
||||
"""Replace occurances of patterns in old_lines with new_version.
|
||||
|
||||
>>> old_lines = ['__version__ = "v201809.0002-beta"']
|
||||
>>> patterns = ['__version__ = "{version}"']
|
||||
>>> new_lines = rewrite_lines(patterns, "v201811.0123-beta", old_lines)
|
||||
>>> assert new_lines == ['__version__ = "v201811.0123-beta"']
|
||||
>>> patterns = ['__version__ = "{pycalver}"']
|
||||
>>> rewrite_lines(patterns, "v201811.0123-beta", ['__version__ = "v201809.0002-beta"'])
|
||||
['__version__ = "v201811.0123-beta"']
|
||||
|
||||
>>> patterns = ['__version__ = "{pep440_version}"']
|
||||
>>> rewrite_lines(patterns, "v201811.0123-beta", ['__version__ = "201809.2b0"'])
|
||||
['__version__ = "201811.123b0"']
|
||||
"""
|
||||
new_version_nfo = version.parse_version_info(new_version)
|
||||
new_version_fmt_kwargs = new_version_nfo._asdict()
|
||||
|
||||
new_lines = old_lines[:]
|
||||
|
||||
for m in parse.iter_matches(old_lines, patterns):
|
||||
replacement = m.pattern.format(**new_version_fmt_kwargs)
|
||||
replacement = version.format_version(new_version_nfo, m.pattern)
|
||||
span_l, span_r = m.span
|
||||
new_line = m.line[:span_l] + replacement + m.line[span_r:]
|
||||
new_lines[m.lineno] = new_line
|
||||
|
|
@ -74,10 +76,11 @@ class RewrittenFileData(typ.NamedTuple):
|
|||
def rfd_from_content(patterns: typ.List[str], new_version: str, content: str) -> RewrittenFileData:
|
||||
r"""Rewrite pattern occurrences with version string.
|
||||
|
||||
>>> patterns = ['__version__ = "{version}"']
|
||||
>>> patterns = ['__version__ = "{pycalver}"']
|
||||
>>> content = '__version__ = "v201809.0001-alpha"'
|
||||
>>> rfd = rfd_from_content(patterns, "v201809.0123", content)
|
||||
>>> assert rfd.new_lines == ['__version__ = "v201809.0123"']
|
||||
>>> rfd.new_lines
|
||||
['__version__ = "v201809.0123"']
|
||||
"""
|
||||
line_sep = detect_line_sep(content)
|
||||
old_lines = content.split(line_sep)
|
||||
|
|
@ -90,7 +93,7 @@ def iter_rewritten(
|
|||
) -> typ.Iterable[RewrittenFileData]:
|
||||
r'''Iterate over files with version string replaced.
|
||||
|
||||
>>> file_patterns = {"src/pycalver/__init__.py": ['__version__ = "{version}"']}
|
||||
>>> file_patterns = {"src/pycalver/__init__.py": ['__version__ = "{pycalver}"']}
|
||||
>>> rewritten_datas = iter_rewritten(file_patterns, "v201809.0123")
|
||||
>>> rfd = list(rewritten_datas)[0]
|
||||
>>> assert rfd.new_lines == [
|
||||
|
|
@ -135,7 +138,7 @@ def diff_lines(rfd: RewrittenFileData) -> typ.List[str]:
|
|||
def diff(new_version: str, file_patterns: config.PatternsByFilePath) -> str:
|
||||
r"""Generate diffs of rewritten files.
|
||||
|
||||
>>> file_patterns = {"src/pycalver/__init__.py": ['__version__ = "{version}"']}
|
||||
>>> file_patterns = {"src/pycalver/__init__.py": ['__version__ = "{pycalver}"']}
|
||||
>>> diff_str = diff("v201809.0123", file_patterns)
|
||||
>>> lines = diff_str.split("\n")
|
||||
>>> lines[:2]
|
||||
|
|
|
|||
|
|
@ -3,164 +3,470 @@
|
|||
#
|
||||
# Copyright (c) 2018 Manuel Barkhau (@mbarkhau) - MIT License
|
||||
# SPDX-License-Identifier: MIT
|
||||
"""Functions related to version string manipulation.
|
||||
"""Functions related to version string manipulation."""
|
||||
|
||||
>>> version_info = PYCALVER_RE.match("v201712.0123-alpha").groupdict()
|
||||
>>> assert version_info == {
|
||||
... "version" : "v201712.0123-alpha",
|
||||
... "calver" : "v201712",
|
||||
... "year" : "2017",
|
||||
... "month" : "12",
|
||||
... "build" : ".0123",
|
||||
... "build_no" : "0123",
|
||||
... "release" : "-alpha",
|
||||
... "release_tag" : "alpha",
|
||||
... }
|
||||
>>>
|
||||
>>> version_info = PYCALVER_RE.match("v201712.0033").groupdict()
|
||||
>>> assert version_info == {
|
||||
... "version" : "v201712.0033",
|
||||
... "calver" : "v201712",
|
||||
... "year" : "2017",
|
||||
... "month" : "12",
|
||||
... "build" : ".0033",
|
||||
... "build_no" : "0033",
|
||||
... "release" : None,
|
||||
... "release_tag": None,
|
||||
... }
|
||||
"""
|
||||
|
||||
import re
|
||||
import logging
|
||||
import pkg_resources
|
||||
import typing as typ
|
||||
import datetime as dt
|
||||
|
||||
from . import lex_id
|
||||
from . import patterns
|
||||
|
||||
log = logging.getLogger("pycalver.version")
|
||||
|
||||
|
||||
# https://regex101.com/r/fnj60p/10
|
||||
PYCALVER_PATTERN = r"""
|
||||
\b
|
||||
(?P<version>
|
||||
(?P<calver>
|
||||
v # "v" version prefix
|
||||
(?P<year>\d{4})
|
||||
(?P<month>\d{2})
|
||||
)
|
||||
(?P<build>
|
||||
\. # "." build nr prefix
|
||||
(?P<build_no>\d{4,})
|
||||
)
|
||||
(?P<release>
|
||||
\- # "-" release prefix
|
||||
(?P<release_tag>alpha|beta|dev|rc|post)
|
||||
)?
|
||||
)(?:\s|$)
|
||||
"""
|
||||
# The test suite may replace this.
|
||||
TODAY = dt.datetime.utcnow().date()
|
||||
|
||||
PYCALVER_RE: typ.Pattern[str] = re.compile(PYCALVER_PATTERN, flags=re.VERBOSE)
|
||||
|
||||
PATTERN_PART_FIELDS = {
|
||||
'year' : 'year',
|
||||
'month' : 'month',
|
||||
'pep440_tag': 'tag',
|
||||
'tag' : 'tag',
|
||||
'yy' : 'year',
|
||||
'yyyy' : 'year',
|
||||
'quarter' : 'quarter',
|
||||
'iso_week' : 'iso_week',
|
||||
'us_week' : 'us_week',
|
||||
'dom' : 'dom',
|
||||
'doy' : 'doy',
|
||||
'MAJOR' : 'major',
|
||||
'MINOR' : 'minor',
|
||||
'MM' : 'minor',
|
||||
'MMM' : 'minor',
|
||||
'MMMM' : 'minor',
|
||||
'MMMMM' : 'minor',
|
||||
'PP' : 'patch',
|
||||
'PPP' : 'patch',
|
||||
'PPPP' : 'patch',
|
||||
'PPPPP' : 'patch',
|
||||
'PATCH' : 'patch',
|
||||
'build_no' : 'bid',
|
||||
'bid' : 'bid',
|
||||
'BID' : 'bid',
|
||||
'BB' : 'bid',
|
||||
'BBB' : 'bid',
|
||||
'BBBB' : 'bid',
|
||||
'BBBBB' : 'bid',
|
||||
'BBBBBB' : 'bid',
|
||||
'BBBBBBB' : 'bid',
|
||||
}
|
||||
|
||||
|
||||
class CalendarInfo(typ.NamedTuple):
|
||||
"""Container for calendar components of version strings."""
|
||||
|
||||
year : int
|
||||
quarter : int
|
||||
month : int
|
||||
dom : int
|
||||
doy : int
|
||||
iso_week: int
|
||||
us_week : int
|
||||
|
||||
|
||||
def _date_from_doy(year: int, doy: int) -> dt.date:
|
||||
"""Parse date from year and day of year (1 indexed).
|
||||
|
||||
>>> cases = [
|
||||
... (2016, 1), (2016, 31), (2016, 31 + 1), (2016, 31 + 29), (2016, 31 + 30),
|
||||
... (2017, 1), (2017, 31), (2017, 31 + 1), (2017, 31 + 28), (2017, 31 + 29),
|
||||
... ]
|
||||
>>> dates = [_date_from_doy(year, month) for year, month in cases]
|
||||
>>> assert [(d.month, d.day) for d in dates] == [
|
||||
... (1, 1), (1, 31), (2, 1), (2, 29), (3, 1),
|
||||
... (1, 1), (1, 31), (2, 1), (2, 28), (3, 1),
|
||||
... ]
|
||||
"""
|
||||
return dt.date(year, 1, 1) + dt.timedelta(days=doy - 1)
|
||||
|
||||
|
||||
def _quarter_from_month(month: int) -> int:
|
||||
"""Calculate quarter (1 indexed) from month (1 indexed).
|
||||
|
||||
>>> [_quarter_from_month(month) for month in range(1, 13)]
|
||||
[1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4]
|
||||
"""
|
||||
return ((month - 1) // 3) + 1
|
||||
|
||||
|
||||
def cal_info(date: dt.date = None) -> CalendarInfo:
|
||||
"""Generate calendar components for current date.
|
||||
|
||||
>>> from datetime import date
|
||||
|
||||
>>> c = cal_info(date(2019, 1, 5))
|
||||
>>> (c.year, c.quarter, c.month, c.dom, c.doy, c.iso_week, c.us_week)
|
||||
(2019, 1, 1, 5, 5, 0, 0)
|
||||
|
||||
>>> c = cal_info(date(2019, 1, 6))
|
||||
>>> (c.year, c.quarter, c.month, c.dom, c.doy, c.iso_week, c.us_week)
|
||||
(2019, 1, 1, 6, 6, 0, 1)
|
||||
|
||||
>>> c = cal_info(date(2019, 1, 7))
|
||||
>>> (c.year, c.quarter, c.month, c.dom, c.doy, c.iso_week, c.us_week)
|
||||
(2019, 1, 1, 7, 7, 1, 1)
|
||||
|
||||
>>> c = cal_info(date(2019, 4, 7))
|
||||
>>> (c.year, c.quarter, c.month, c.dom, c.doy, c.iso_week, c.us_week)
|
||||
(2019, 2, 4, 7, 97, 13, 14)
|
||||
"""
|
||||
if date is None:
|
||||
date = TODAY
|
||||
|
||||
kw = {
|
||||
'year' : date.year,
|
||||
'quarter' : _quarter_from_month(date.month),
|
||||
'month' : date.month,
|
||||
'dom' : date.day,
|
||||
'doy' : int(date.strftime("%j"), base=10),
|
||||
'iso_week': int(date.strftime("%W"), base=10),
|
||||
'us_week' : int(date.strftime("%U"), base=10),
|
||||
}
|
||||
|
||||
return CalendarInfo(**kw)
|
||||
|
||||
|
||||
class VersionInfo(typ.NamedTuple):
|
||||
"""Container for parsed version string."""
|
||||
|
||||
version : str
|
||||
pep440_version: str
|
||||
calver : str
|
||||
year : str
|
||||
month : str
|
||||
build : str
|
||||
build_no : str
|
||||
release : typ.Optional[str]
|
||||
release_tag : typ.Optional[str]
|
||||
year : typ.Optional[int]
|
||||
quarter : typ.Optional[int]
|
||||
month : typ.Optional[int]
|
||||
dom : typ.Optional[int]
|
||||
doy : typ.Optional[int]
|
||||
iso_week: typ.Optional[int]
|
||||
us_week : typ.Optional[int]
|
||||
major : int
|
||||
minor : int
|
||||
patch : int
|
||||
bid : str
|
||||
tag : str
|
||||
|
||||
|
||||
def parse_version_info(version_str: str) -> VersionInfo:
|
||||
"""Parse a PyCalVer string.
|
||||
def _is_calver(nfo: typ.Union[CalendarInfo, VersionInfo]) -> bool:
|
||||
"""Check pattern for any calendar based parts.
|
||||
|
||||
>>> vnfo = parse_version_info("v201712.0033-beta")
|
||||
>>> assert vnfo == VersionInfo(
|
||||
... version ="v201712.0033-beta",
|
||||
... pep440_version="201712.33b0",
|
||||
... calver ="v201712",
|
||||
... year ="2017",
|
||||
... month ="12",
|
||||
... build =".0033",
|
||||
... build_no ="0033",
|
||||
... release ="-beta",
|
||||
... release_tag ="beta",
|
||||
... )
|
||||
>>> _is_calver(cal_info())
|
||||
True
|
||||
|
||||
>>> vnfo = _parse_version_info({'year': "2018", 'month': "11", 'bid': "0018"})
|
||||
>>> _is_calver(vnfo)
|
||||
True
|
||||
|
||||
>>> vnfo = _parse_version_info({'MAJOR': "1", 'MINOR': "023", 'PATCH': "45"})
|
||||
>>> _is_calver(vnfo)
|
||||
False
|
||||
"""
|
||||
match = PYCALVER_RE.match(version_str)
|
||||
if match is None:
|
||||
raise ValueError(f"Invalid PyCalVer string: {version_str}")
|
||||
for field in CalendarInfo._fields:
|
||||
if isinstance(getattr(nfo, field, None), int):
|
||||
return True
|
||||
|
||||
kwargs = match.groupdict()
|
||||
kwargs['pep440_version'] = pycalver_to_pep440(kwargs['version'])
|
||||
if kwargs['release'] is None:
|
||||
kwargs['release'] = "-final"
|
||||
if kwargs['release_tag'] is None:
|
||||
kwargs['release_tag'] = "final"
|
||||
return VersionInfo(**kwargs)
|
||||
return False
|
||||
|
||||
|
||||
def current_calver() -> str:
|
||||
"""Generate calver version string based on current date.
|
||||
|
||||
example result: "v201812"
|
||||
"""
|
||||
return dt.date.today().strftime("v%Y%m")
|
||||
TAG_ALIASES: typ.Dict[str, str] = {'a': "alpha", 'b': "beta", 'pre': "rc"}
|
||||
|
||||
|
||||
def incr(old_version: str, *, release: str = None) -> str:
|
||||
"""Increment a full PyCalVer version string.
|
||||
PEP440_TAGS: typ.Dict[str, str] = {'alpha': "a", 'beta': "b", 'final': "", 'rc': "rc", 'dev': "dev"}
|
||||
|
||||
Old_version is assumed to be a valid calver string,
|
||||
already validated in pycalver.config.parse.
|
||||
"""
|
||||
old_ver = parse_version_info(old_version)
|
||||
|
||||
new_calver = current_calver()
|
||||
VersionInfoKW = typ.Dict[str, typ.Union[str, int, None]]
|
||||
|
||||
if old_ver.calver > new_calver:
|
||||
log.warning(
|
||||
f"'version.incr' called with '{old_version}', "
|
||||
+ f"which is from the future, "
|
||||
+ f"maybe your system clock is out of sync."
|
||||
|
||||
def _parse_pattern_groups(pattern_groups: typ.Dict[str, str]) -> typ.Dict[str, str]:
|
||||
for part_name in pattern_groups.keys():
|
||||
is_valid_part_name = (
|
||||
part_name in patterns.COMPOSITE_PART_PATTERNS or part_name in PATTERN_PART_FIELDS
|
||||
)
|
||||
# leave calver as is (don't go back in time)
|
||||
new_calver = old_ver.calver
|
||||
if not is_valid_part_name:
|
||||
err_msg = f"Invalid part '{part_name}'"
|
||||
raise ValueError(err_msg)
|
||||
|
||||
new_build = lex_id.next_id(old_ver.build[1:])
|
||||
new_release: typ.Optional[str] = None
|
||||
items = [
|
||||
(field_name, pattern_groups[part_name])
|
||||
for part_name, field_name in PATTERN_PART_FIELDS.items()
|
||||
if part_name in pattern_groups.keys()
|
||||
]
|
||||
all_fields = [field_name for field_name, _ in items]
|
||||
unique_fields = set(all_fields)
|
||||
duplicate_fields = [f for f in unique_fields if all_fields.count(f) > 1]
|
||||
|
||||
if release is None:
|
||||
if old_ver.release:
|
||||
# preserve existing release
|
||||
new_release = old_ver.release[1:]
|
||||
if any(duplicate_fields):
|
||||
err_msg = f"Multiple parts for same field {duplicate_fields}."
|
||||
raise ValueError(err_msg)
|
||||
|
||||
return dict(items)
|
||||
|
||||
|
||||
def _parse_version_info(pattern_groups: typ.Dict[str, str]) -> VersionInfo:
|
||||
"""Parse normalized VersionInfo from groups of a matched pattern.
|
||||
|
||||
>>> vnfo = _parse_version_info({'year': "2018", 'month': "11", 'bid': "0099"})
|
||||
>>> (vnfo.year, vnfo.month, vnfo.quarter, vnfo.bid, vnfo.tag)
|
||||
(2018, 11, 4, '0099', 'final')
|
||||
|
||||
>>> vnfo = _parse_version_info({'year': "2018", 'doy': "11", 'bid': "099", 'tag': "b"})
|
||||
>>> (vnfo.year, vnfo.month, vnfo.dom, vnfo.bid, vnfo.tag)
|
||||
(2018, 1, 11, '099', 'beta')
|
||||
|
||||
>>> vnfo = _parse_version_info({'MAJOR': "1", 'MINOR': "23", 'PATCH': "45"})
|
||||
>>> (vnfo.major, vnfo.minor, vnfo.patch)
|
||||
(1, 23, 45)
|
||||
|
||||
>>> vnfo = _parse_version_info({'MAJOR': "1", 'MMM': "023", 'PPPP': "0045"})
|
||||
>>> (vnfo.major, vnfo.minor, vnfo.patch)
|
||||
(1, 23, 45)
|
||||
"""
|
||||
kw = _parse_pattern_groups(pattern_groups)
|
||||
|
||||
tag = kw.get('tag')
|
||||
if tag is None:
|
||||
tag = "final"
|
||||
tag = TAG_ALIASES.get(tag, tag)
|
||||
assert tag is not None
|
||||
|
||||
bid = kw['bid'] if 'bid' in kw else "0001"
|
||||
|
||||
year = int(kw['year']) if 'year' in kw else None
|
||||
doy = int(kw['doy' ]) if 'doy' in kw else None
|
||||
|
||||
month: typ.Optional[int]
|
||||
dom : typ.Optional[int]
|
||||
|
||||
if year and doy:
|
||||
date = _date_from_doy(year, doy)
|
||||
month = date.month
|
||||
dom = date.day
|
||||
else:
|
||||
new_release = None
|
||||
elif release == 'final':
|
||||
new_release = None
|
||||
month = int(kw['month']) if 'month' in kw else None
|
||||
dom = int(kw['dom' ]) if 'dom' in kw else None
|
||||
|
||||
iso_week: typ.Optional[int]
|
||||
us_week : typ.Optional[int]
|
||||
|
||||
if year and month and dom:
|
||||
date = dt.date(year, month, dom)
|
||||
doy = int(date.strftime("%j"), base=10)
|
||||
iso_week = int(date.strftime("%W"), base=10)
|
||||
us_week = int(date.strftime("%U"), base=10)
|
||||
else:
|
||||
new_release = release
|
||||
iso_week = None
|
||||
us_week = None
|
||||
|
||||
if new_release == 'final':
|
||||
new_release = None
|
||||
quarter = int(kw['quarter']) if 'quarter' in kw else None
|
||||
if quarter is None and month:
|
||||
quarter = _quarter_from_month(month)
|
||||
|
||||
new_version = new_calver + "." + new_build
|
||||
if new_release:
|
||||
new_version += "-" + new_release
|
||||
major = int(kw['major']) if 'major' in kw else 0
|
||||
minor = int(kw['minor']) if 'minor' in kw else 0
|
||||
patch = int(kw['patch']) if 'patch' in kw else 0
|
||||
|
||||
return VersionInfo(
|
||||
year=year,
|
||||
quarter=quarter,
|
||||
month=month,
|
||||
dom=dom,
|
||||
doy=doy,
|
||||
iso_week=iso_week,
|
||||
us_week=us_week,
|
||||
major=major,
|
||||
minor=minor,
|
||||
patch=patch,
|
||||
bid=bid,
|
||||
tag=tag,
|
||||
)
|
||||
|
||||
|
||||
def parse_version_info(version_str: str, pattern: str = "{pycalver}") -> VersionInfo:
|
||||
"""Parse normalized VersionInfo.
|
||||
|
||||
>>> vnfo = parse_version_info("v201712.0033-beta", pattern="{pycalver}")
|
||||
>>> assert vnfo == _parse_version_info({'year': 2017, 'month': 12, 'bid': "0033", 'tag': "beta"})
|
||||
|
||||
>>> vnfo = parse_version_info("1.23.456", pattern="{semver}")
|
||||
>>> assert vnfo == _parse_version_info({'MAJOR': "1", 'MINOR': "23", 'PATCH': "456"})
|
||||
"""
|
||||
regex = patterns.compile_pattern(pattern)
|
||||
match = regex.match(version_str)
|
||||
if match is None:
|
||||
err_msg = (
|
||||
f"Invalid version string '{version_str}' for pattern '{pattern}'/'{regex.pattern}'"
|
||||
)
|
||||
raise ValueError(err_msg)
|
||||
|
||||
return _parse_version_info(match.groupdict())
|
||||
|
||||
|
||||
def is_valid(version_str: str, pattern: str = "{pycalver}") -> bool:
|
||||
"""Check if a version matches a pattern.
|
||||
|
||||
>>> is_valid("v201712.0033-beta", pattern="{pycalver}")
|
||||
True
|
||||
>>> is_valid("v201712.0033-beta", pattern="{semver}")
|
||||
False
|
||||
>>> is_valid("1.2.3", pattern="{semver}")
|
||||
True
|
||||
>>> is_valid("v201712.0033-beta", pattern="{semver}")
|
||||
False
|
||||
"""
|
||||
try:
|
||||
parse_version_info(version_str, pattern)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
|
||||
ID_FIELDS_BY_PART = {
|
||||
'MAJOR' : 'major',
|
||||
'MINOR' : 'minor',
|
||||
'MM' : 'minor',
|
||||
'MMM' : 'minor',
|
||||
'MMMM' : 'minor',
|
||||
'MMMMM' : 'minor',
|
||||
'MMMMMM' : 'minor',
|
||||
'MMMMMMM': 'minor',
|
||||
'PATCH' : 'patch',
|
||||
'PP' : 'patch',
|
||||
'PPP' : 'patch',
|
||||
'PPPP' : 'patch',
|
||||
'PPPPP' : 'patch',
|
||||
'PPPPPP' : 'patch',
|
||||
'PPPPPPP': 'patch',
|
||||
'BID' : 'bid',
|
||||
'BB' : 'bid',
|
||||
'BBB' : 'bid',
|
||||
'BBBB' : 'bid',
|
||||
'BBBBB' : 'bid',
|
||||
'BBBBBB' : 'bid',
|
||||
'BBBBBBB': 'bid',
|
||||
}
|
||||
|
||||
|
||||
def format_version(ver_nfo: VersionInfo, pattern: str) -> str:
|
||||
"""Generate version string.
|
||||
|
||||
>>> import datetime as dt
|
||||
>>> ver_nfo = parse_version_info("v201712.0033-beta", pattern="{pycalver}")
|
||||
>>> ver_nfo_a = ver_nfo._replace(**cal_info(date=dt.date(2017, 1, 1))._asdict())
|
||||
>>> ver_nfo_b = ver_nfo._replace(**cal_info(date=dt.date(2017, 12, 31))._asdict())
|
||||
>>> ver_nfo_c = ver_nfo_b._replace(major=1, minor=2, patch=34, tag='final')
|
||||
|
||||
>>> format_version(ver_nfo_a, pattern="v{yy}.{BID}{release}")
|
||||
'v17.33-beta'
|
||||
>>> format_version(ver_nfo_a, pattern="{pep440_version}")
|
||||
'201701.33b0'
|
||||
|
||||
>>> format_version(ver_nfo_a, pattern="{pycalver}")
|
||||
'v201701.0033-beta'
|
||||
>>> format_version(ver_nfo_b, pattern="{pycalver}")
|
||||
'v201712.0033-beta'
|
||||
|
||||
>>> format_version(ver_nfo_a, pattern="v{year}w{iso_week}.{BID}{release}")
|
||||
'v2017w00.33-beta'
|
||||
>>> format_version(ver_nfo_b, pattern="v{year}w{iso_week}.{BID}{release}")
|
||||
'v2017w52.33-beta'
|
||||
|
||||
>>> format_version(ver_nfo_a, pattern="v{year}d{doy}.{bid}{release}")
|
||||
'v2017d001.0033-beta'
|
||||
>>> format_version(ver_nfo_b, pattern="v{year}d{doy}.{bid}{release}")
|
||||
'v2017d365.0033-beta'
|
||||
|
||||
>>> format_version(ver_nfo_c, pattern="v{year}w{iso_week}.{BID}-{tag}")
|
||||
'v2017w52.33-final'
|
||||
>>> format_version(ver_nfo_c, pattern="v{year}w{iso_week}.{BID}{release}")
|
||||
'v2017w52.33'
|
||||
|
||||
>>> format_version(ver_nfo_c, pattern="v{MAJOR}.{MINOR}.{PATCH}")
|
||||
'v1.2.34'
|
||||
>>> format_version(ver_nfo_c, pattern="v{MAJOR}.{MM}.{PPP}")
|
||||
'v1.02.034'
|
||||
"""
|
||||
full_pattern = pattern
|
||||
for part_name, full_part_format in patterns.FULL_PART_FORMATS.items():
|
||||
full_pattern = full_pattern.replace("{" + part_name + "}", full_part_format)
|
||||
|
||||
kw = ver_nfo._asdict()
|
||||
if kw['tag'] == 'final':
|
||||
kw['release' ] = ""
|
||||
kw['pep440_tag'] = ""
|
||||
else:
|
||||
kw['release' ] = "-" + kw['tag']
|
||||
kw['pep440_tag'] = PEP440_TAGS[kw['tag']] + "0"
|
||||
|
||||
kw['release_tag'] = kw['tag']
|
||||
|
||||
kw['yy' ] = str(kw['year'])[-2:]
|
||||
kw['yyyy'] = kw['year']
|
||||
kw['BID' ] = int(kw['bid'], 10)
|
||||
|
||||
for part_name, field in ID_FIELDS_BY_PART.items():
|
||||
val = kw[field]
|
||||
if part_name.lower() == field.lower():
|
||||
if isinstance(val, str):
|
||||
kw[part_name] = int(val, base=10)
|
||||
else:
|
||||
kw[part_name] = val
|
||||
else:
|
||||
assert len(set(part_name)) == 1
|
||||
padded_len = len(part_name)
|
||||
kw[part_name] = str(val).zfill(padded_len)
|
||||
|
||||
return full_pattern.format(**kw)
|
||||
|
||||
|
||||
def incr(
|
||||
old_version: str,
|
||||
pattern : str = "{pycalver}",
|
||||
*,
|
||||
release: str = None,
|
||||
major : bool = False,
|
||||
minor : bool = False,
|
||||
patch : bool = False,
|
||||
) -> typ.Optional[str]:
|
||||
"""Increment version string.
|
||||
|
||||
'old_version' is assumed to be a string that matches 'pattern'
|
||||
"""
|
||||
old_ver_nfo = parse_version_info(old_version, pattern)
|
||||
cur_ver_nfo = old_ver_nfo
|
||||
|
||||
cur_cal_nfo = cal_info()
|
||||
|
||||
old_date = (old_ver_nfo.year or 0, old_ver_nfo.month or 0, old_ver_nfo.dom or 0)
|
||||
cur_date = (cur_cal_nfo.year , cur_cal_nfo.month , cur_cal_nfo.dom)
|
||||
|
||||
if old_date <= cur_date:
|
||||
cur_ver_nfo = cur_ver_nfo._replace(**cur_cal_nfo._asdict())
|
||||
else:
|
||||
log.warning(f"Version appears to be from the future '{old_version}'")
|
||||
|
||||
cur_ver_nfo = cur_ver_nfo._replace(bid=lex_id.next_id(cur_ver_nfo.bid))
|
||||
|
||||
if major:
|
||||
cur_ver_nfo = cur_ver_nfo._replace(major=cur_ver_nfo.major + 1, minor=0, patch=0)
|
||||
if minor:
|
||||
cur_ver_nfo = cur_ver_nfo._replace(minor=cur_ver_nfo.minor + 1, patch=0)
|
||||
if patch:
|
||||
cur_ver_nfo = cur_ver_nfo._replace(patch=cur_ver_nfo.patch + 1)
|
||||
|
||||
if release:
|
||||
cur_ver_nfo = cur_ver_nfo._replace(tag=release)
|
||||
|
||||
new_version = format_version(cur_ver_nfo, pattern)
|
||||
if new_version == old_version:
|
||||
log.error("Invalid arguments or pattern, version did not change.")
|
||||
return None
|
||||
else:
|
||||
return new_version
|
||||
|
||||
|
||||
def pycalver_to_pep440(version: str) -> str:
|
||||
def to_pep440(version: str) -> str:
|
||||
"""Derive pep440 compliant version string from PyCalVer version string.
|
||||
|
||||
>>> pycalver_to_pep440("v201811.0007-beta")
|
||||
>>> to_pep440("v201811.0007-beta")
|
||||
'201811.7b0'
|
||||
"""
|
||||
return str(pkg_resources.parse_version(version))
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import pytest
|
|||
from click.testing import CliRunner
|
||||
|
||||
import pycalver.config as config
|
||||
import pycalver.version as version
|
||||
import pycalver.patterns as patterns
|
||||
import pycalver.__main__ as pycalver
|
||||
|
||||
|
||||
|
|
@ -80,18 +80,52 @@ def test_version(runner):
|
|||
result = runner.invoke(pycalver.cli, ['--version', "--verbose"])
|
||||
assert result.exit_code == 0
|
||||
assert " version v20" in result.output
|
||||
match = version.PYCALVER_RE.search(result.output)
|
||||
match = patterns.PYCALVER_RE.search(result.output)
|
||||
assert match
|
||||
|
||||
|
||||
def test_incr(runner):
|
||||
def test_incr_default(runner):
|
||||
old_version = "v201701.0999-alpha"
|
||||
initial_version = config._initial_version()
|
||||
|
||||
result = runner.invoke(pycalver.cli, ['test', old_version, "--verbose"])
|
||||
result = runner.invoke(pycalver.cli, ['test', "--verbose", old_version])
|
||||
assert result.exit_code == 0
|
||||
new_version = initial_version.replace(".0001-alpha", ".11000-alpha")
|
||||
assert f"PyCalVer Version: {new_version}\n" in result.output
|
||||
assert f"Version: {new_version}\n" in result.output
|
||||
|
||||
|
||||
def test_incr_semver(runner):
|
||||
semver_pattern = "{MAJOR}.{MINOR}.{PATCH}"
|
||||
old_version = "0.1.0"
|
||||
new_version = "0.1.1"
|
||||
|
||||
result = runner.invoke(pycalver.cli, ['test', "--verbose", "--patch", old_version, "{semver}"])
|
||||
assert result.exit_code == 0
|
||||
assert f"Version: {new_version}\n" in result.output
|
||||
|
||||
result = runner.invoke(
|
||||
pycalver.cli, ['test', "--verbose", "--patch", old_version, semver_pattern]
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
assert f"Version: {new_version}\n" in result.output
|
||||
|
||||
old_version = "0.1.1"
|
||||
new_version = "0.2.0"
|
||||
|
||||
result = runner.invoke(
|
||||
pycalver.cli, ['test', "--verbose", "--minor", old_version, semver_pattern]
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
assert f"Version: {new_version}\n" in result.output
|
||||
|
||||
old_version = "0.1.1"
|
||||
new_version = "1.0.0"
|
||||
|
||||
result = runner.invoke(
|
||||
pycalver.cli, ['test', "--verbose", "--major", old_version, semver_pattern]
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
assert f"Version: {new_version}\n" in result.output
|
||||
|
||||
|
||||
def test_incr_to_beta(runner):
|
||||
|
|
@ -101,7 +135,7 @@ def test_incr_to_beta(runner):
|
|||
result = runner.invoke(pycalver.cli, ['test', old_version, "--verbose", "--release", "beta"])
|
||||
assert result.exit_code == 0
|
||||
new_version = initial_version.replace(".0001-alpha", ".11000-beta")
|
||||
assert f"PyCalVer Version: {new_version}\n" in result.output
|
||||
assert f"Version: {new_version}\n" in result.output
|
||||
|
||||
|
||||
def test_incr_to_final(runner):
|
||||
|
|
@ -111,7 +145,7 @@ def test_incr_to_final(runner):
|
|||
result = runner.invoke(pycalver.cli, ['test', old_version, "--verbose", "--release", "final"])
|
||||
assert result.exit_code == 0
|
||||
new_version = initial_version.replace(".0001-alpha", ".11000")
|
||||
assert f"PyCalVer Version: {new_version}\n" in result.output
|
||||
assert f"Version: {new_version}\n" in result.output
|
||||
|
||||
|
||||
def test_incr_invalid(runner, caplog):
|
||||
|
|
@ -164,7 +198,7 @@ def test_novcs_nocfg_init(runner):
|
|||
result = runner.invoke(pycalver.cli, ['show', "--verbose"])
|
||||
assert result.exit_code == 0
|
||||
assert f"Current Version: {config._initial_version()}\n" in result.output
|
||||
assert f"PEP440 Version : {config._initial_version_pep440()}\n" in result.output
|
||||
assert f"PEP440 : {config._initial_version_pep440()}\n" in result.output
|
||||
|
||||
|
||||
def test_novcs_setupcfg_init(runner):
|
||||
|
|
@ -184,7 +218,7 @@ def test_novcs_setupcfg_init(runner):
|
|||
result = runner.invoke(pycalver.cli, ['show', "--verbose"])
|
||||
assert result.exit_code == 0
|
||||
assert f"Current Version: {config._initial_version()}\n" in result.output
|
||||
assert f"PEP440 Version : {config._initial_version_pep440()}\n" in result.output
|
||||
assert f"PEP440 : {config._initial_version_pep440()}\n" in result.output
|
||||
|
||||
|
||||
def test_novcs_pyproject_init(runner):
|
||||
|
|
@ -202,7 +236,7 @@ def test_novcs_pyproject_init(runner):
|
|||
result = runner.invoke(pycalver.cli, ['show'])
|
||||
assert result.exit_code == 0
|
||||
assert f"Current Version: {config._initial_version()}\n" in result.output
|
||||
assert f"PEP440 Version : {config._initial_version_pep440()}\n" in result.output
|
||||
assert f"PEP440 : {config._initial_version_pep440()}\n" in result.output
|
||||
|
||||
|
||||
def _vcs_init(vcs):
|
||||
|
|
@ -224,7 +258,7 @@ def test_git_init(runner):
|
|||
result = runner.invoke(pycalver.cli, ['show'])
|
||||
assert result.exit_code == 0
|
||||
assert f"Current Version: {config._initial_version()}\n" in result.output
|
||||
assert f"PEP440 Version : {config._initial_version_pep440()}\n" in result.output
|
||||
assert f"PEP440 : {config._initial_version_pep440()}\n" in result.output
|
||||
|
||||
|
||||
def test_hg_init(runner):
|
||||
|
|
@ -237,7 +271,7 @@ def test_hg_init(runner):
|
|||
result = runner.invoke(pycalver.cli, ['show'])
|
||||
assert result.exit_code == 0
|
||||
assert f"Current Version: {config._initial_version()}\n" in result.output
|
||||
assert f"PEP440 Version : {config._initial_version_pep440()}\n" in result.output
|
||||
assert f"PEP440 : {config._initial_version_pep440()}\n" in result.output
|
||||
|
||||
|
||||
def test_git_tag_eval(runner):
|
||||
|
|
@ -257,7 +291,7 @@ def test_git_tag_eval(runner):
|
|||
result = runner.invoke(pycalver.cli, ['show', "--verbose"])
|
||||
assert result.exit_code == 0
|
||||
assert f"Current Version: {tag_version}\n" in result.output
|
||||
assert f"PEP440 Version : {tag_version_pep440}\n" in result.output
|
||||
assert f"PEP440 : {tag_version_pep440}\n" in result.output
|
||||
|
||||
|
||||
def test_hg_tag_eval(runner):
|
||||
|
|
@ -277,7 +311,7 @@ def test_hg_tag_eval(runner):
|
|||
result = runner.invoke(pycalver.cli, ['show', "--verbose"])
|
||||
assert result.exit_code == 0
|
||||
assert f"Current Version: {tag_version}\n" in result.output
|
||||
assert f"PEP440 Version : {tag_version_pep440}\n" in result.output
|
||||
assert f"PEP440 : {tag_version_pep440}\n" in result.output
|
||||
|
||||
|
||||
def test_novcs_bump(runner):
|
||||
|
|
|
|||
|
|
@ -58,8 +58,8 @@ def test_parse_toml():
|
|||
assert cfg.push is True
|
||||
|
||||
assert "pycalver.toml" in cfg.file_patterns
|
||||
assert cfg.file_patterns["README.md" ] == ["{version}", "{pep440_version}"]
|
||||
assert cfg.file_patterns["pycalver.toml"] == ['current_version = "{version}"']
|
||||
assert cfg.file_patterns["README.md" ] == ["{pycalver}", "{pep440_pycalver}"]
|
||||
assert cfg.file_patterns["pycalver.toml"] == ['current_version = "{pycalver}"']
|
||||
|
||||
|
||||
def test_parse_cfg():
|
||||
|
|
@ -74,8 +74,8 @@ def test_parse_cfg():
|
|||
assert cfg.push is True
|
||||
|
||||
assert "setup.cfg" in cfg.file_patterns
|
||||
assert cfg.file_patterns["setup.py" ] == ["{version}", "{pep440_version}"]
|
||||
assert cfg.file_patterns["setup.cfg"] == ['current_version = "{version}"']
|
||||
assert cfg.file_patterns["setup.py" ] == ["{pycalver}", "{pep440_pycalver}"]
|
||||
assert cfg.file_patterns["setup.cfg"] == ['current_version = "{pycalver}"']
|
||||
|
||||
|
||||
def test_parse_default_toml():
|
||||
|
|
@ -168,8 +168,8 @@ def test_parse_toml_file(tmpdir):
|
|||
assert cfg.push is True
|
||||
|
||||
assert cfg.file_patterns == {
|
||||
"README.md" : ["{version}", "{pep440_version}"],
|
||||
"pycalver.toml": ['current_version = "{version}"'],
|
||||
"README.md" : ["{pycalver}", "{pep440_pycalver}"],
|
||||
"pycalver.toml": ['current_version = "{pycalver}"'],
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -190,8 +190,8 @@ def test_parse_cfg_file(tmpdir):
|
|||
assert cfg.push is True
|
||||
|
||||
assert cfg.file_patterns == {
|
||||
"setup.py" : ["{version}", "{pep440_version}"],
|
||||
"setup.cfg": ['current_version = "{version}"'],
|
||||
"setup.py" : ["{pycalver}", "{pep440_pycalver}"],
|
||||
"setup.cfg": ['current_version = "{pycalver}"'],
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,50 +1,6 @@
|
|||
import re
|
||||
from pycalver import parse
|
||||
|
||||
|
||||
def test_re_pattern_parts():
|
||||
part_re_by_name = {
|
||||
part_name: re.compile(part_re_str)
|
||||
for part_name, part_re_str in parse.RE_PATTERN_PARTS.items()
|
||||
}
|
||||
|
||||
cases = [
|
||||
("pep440_version", "201712.31" , "201712.31"),
|
||||
("pep440_version", "v201712.0032" , None),
|
||||
("pep440_version", "201712.0033-alpha" , None),
|
||||
("version" , "v201712.0034" , "v201712.0034"),
|
||||
("version" , "v201712.0035-alpha" , "v201712.0035-alpha"),
|
||||
("version" , "v201712.0036-alpha0", "v201712.0036-alpha"),
|
||||
("version" , "v201712.0037-pre" , "v201712.0037"),
|
||||
("version" , "201712.38a0" , None),
|
||||
("version" , "201712.0039" , None),
|
||||
("calver" , "v201712" , "v201712"),
|
||||
("calver" , "v201799" , "v201799"), # maybe date validation should be a thing
|
||||
("calver" , "201712" , None),
|
||||
("calver" , "v20171" , None),
|
||||
("build" , ".0012" , ".0012"),
|
||||
("build" , ".11012" , ".11012"),
|
||||
("build" , ".012" , None),
|
||||
("build" , "11012" , None),
|
||||
("release" , "-alpha" , "-alpha"),
|
||||
("release" , "-beta" , "-beta"),
|
||||
("release" , "-dev" , "-dev"),
|
||||
("release" , "-rc" , "-rc"),
|
||||
("release" , "-post" , "-post"),
|
||||
("release" , "-pre" , ""),
|
||||
("release" , "alpha" , ""),
|
||||
]
|
||||
|
||||
for part_name, line, expected in cases:
|
||||
part_re = part_re_by_name[part_name]
|
||||
result = part_re.search(line)
|
||||
if result is None:
|
||||
assert expected is None, (part_name, line)
|
||||
else:
|
||||
result_val = result.group(0)
|
||||
assert result_val == expected, (part_name, line)
|
||||
|
||||
|
||||
SETUP_PY_FIXTURE = """
|
||||
# setup.py
|
||||
import setuptools
|
||||
|
|
@ -57,7 +13,7 @@ setuptools.setup(
|
|||
|
||||
def test_default_parse_patterns():
|
||||
lines = SETUP_PY_FIXTURE.splitlines()
|
||||
patterns = ["{version}", "{pep440_version}"]
|
||||
patterns = ["{pycalver}", "{pep440_pycalver}"]
|
||||
|
||||
matches = list(parse.iter_matches(lines, patterns))
|
||||
assert len(matches) == 2
|
||||
|
|
@ -75,7 +31,7 @@ def test_default_parse_patterns():
|
|||
def test_explicit_parse_patterns():
|
||||
lines = SETUP_PY_FIXTURE.splitlines()
|
||||
|
||||
patterns = ["__version__ = '{version}'", "version='{pep440_version}'"]
|
||||
patterns = ["__version__ = '{pycalver}'", "version='{pep440_pycalver}'"]
|
||||
|
||||
matches = list(parse.iter_matches(lines, patterns))
|
||||
assert len(matches) == 2
|
||||
|
|
@ -102,7 +58,7 @@ README_RST_FIXTURE = """
|
|||
def test_badge_parse_patterns():
|
||||
lines = README_RST_FIXTURE.splitlines()
|
||||
|
||||
patterns = ["badge/CalVer-{calver}{build}-{release}-blue.svg", ":alt: CalVer {version}"]
|
||||
patterns = ["badge/CalVer-{calver}{build}-{release}-blue.svg", ":alt: CalVer {pycalver}"]
|
||||
|
||||
matches = list(parse.iter_matches(lines, patterns))
|
||||
assert len(matches) == 2
|
||||
|
|
@ -115,30 +71,3 @@ def test_badge_parse_patterns():
|
|||
|
||||
assert matches[0].match == "badge/CalVer-v201809.0002--beta-blue.svg"
|
||||
assert matches[1].match == ":alt: CalVer v201809.0002-beta"
|
||||
|
||||
|
||||
CLI_MAIN_FIXTURE = """
|
||||
@click.group()
|
||||
@click.version_option(version="v201812.0123-beta")
|
||||
@click.help_option()
|
||||
"""
|
||||
|
||||
|
||||
def test_pattern_escapes():
|
||||
pattern_re = parse.compile_pattern(r'click.version_option(version="{version}")')
|
||||
match = pattern_re.search(CLI_MAIN_FIXTURE)
|
||||
assert match.group(0) == 'click.version_option(version="v201812.0123-beta")'
|
||||
|
||||
|
||||
CURLY_BRACE_FIXTURE = """
|
||||
package_metadata = {"name": "mypackage", "version": "v201812.0123-beta"}
|
||||
"""
|
||||
|
||||
|
||||
def test_curly_escapes():
|
||||
pattern = r'package_metadata = {"name": "mypackage", "version": "{version}"}'
|
||||
pattern_re = parse.compile_pattern(pattern)
|
||||
match = pattern_re.search(CURLY_BRACE_FIXTURE)
|
||||
assert (
|
||||
match.group(0) == 'package_metadata = {"name": "mypackage", "version": "v201812.0123-beta"}'
|
||||
)
|
||||
|
|
|
|||
81
test/test_patterns.py
Normal file
81
test/test_patterns.py
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
import re
|
||||
import pytest
|
||||
|
||||
from pycalver import patterns
|
||||
|
||||
|
||||
def _part_re_by_name(name):
|
||||
return re.compile(patterns.PART_PATTERNS[name])
|
||||
|
||||
|
||||
@pytest.mark.parametrize("part_name", patterns.PART_PATTERNS.keys())
|
||||
def test_part_compilation(part_name):
|
||||
assert _part_re_by_name(part_name)
|
||||
|
||||
|
||||
PATTERN_PART_CASES = [
|
||||
("pep440_pycalver", "201712.31" , "201712.31"),
|
||||
("pep440_pycalver", "v201712.0032" , None),
|
||||
("pep440_pycalver", "201712.0033-alpha" , None),
|
||||
("pycalver" , "v201712.0034" , "v201712.0034"),
|
||||
("pycalver" , "v201712.0035-alpha" , "v201712.0035-alpha"),
|
||||
("pycalver" , "v201712.0036-alpha0", "v201712.0036-alpha"),
|
||||
("pycalver" , "v201712.0037-pre" , "v201712.0037"),
|
||||
("pycalver" , "201712.38a0" , None),
|
||||
("pycalver" , "201712.0039" , None),
|
||||
("semver" , "1.23.456" , "1.23.456"),
|
||||
("calver" , "v201712" , "v201712"),
|
||||
("calver" , "v201799" , None), # invalid date
|
||||
("calver" , "201712" , None),
|
||||
("calver" , "v20171" , None),
|
||||
("build" , ".0012" , ".0012"),
|
||||
("build" , ".11012" , ".11012"),
|
||||
("build" , ".012" , None),
|
||||
("build" , "11012" , None),
|
||||
("release" , "-alpha" , "-alpha"),
|
||||
("release" , "-beta" , "-beta"),
|
||||
("release" , "-dev" , "-dev"),
|
||||
("release" , "-rc" , "-rc"),
|
||||
("release" , "-post" , "-post"),
|
||||
("release" , "-pre" , None),
|
||||
("release" , "alpha" , None),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("part_name, line, expected", PATTERN_PART_CASES)
|
||||
def test_re_pattern_parts(part_name, line, expected):
|
||||
part_re = _part_re_by_name(part_name)
|
||||
result = part_re.search(line)
|
||||
if result is None:
|
||||
assert expected is None, (part_name, line)
|
||||
else:
|
||||
result_val = result.group(0)
|
||||
assert result_val == expected, (part_name, line)
|
||||
|
||||
|
||||
CLI_MAIN_FIXTURE = """
|
||||
@click.group()
|
||||
@click.version_option(version="v201812.0123-beta")
|
||||
@click.help_option()
|
||||
"""
|
||||
|
||||
|
||||
def test_pattern_escapes():
|
||||
pattern = 'click.version_option(version="{pycalver}")'
|
||||
pattern_re = patterns.compile_pattern(pattern)
|
||||
match = pattern_re.search(CLI_MAIN_FIXTURE)
|
||||
expected = 'click.version_option(version="v201812.0123-beta")'
|
||||
assert match.group(0) == expected
|
||||
|
||||
|
||||
CURLY_BRACE_FIXTURE = """
|
||||
package_metadata = {"name": "mypackage", "version": "v201812.0123-beta"}
|
||||
"""
|
||||
|
||||
|
||||
def test_curly_escapes():
|
||||
pattern = 'package_metadata = {"name": "mypackage", "version": "{pycalver}"}'
|
||||
pattern_re = patterns.compile_pattern(pattern)
|
||||
match = pattern_re.search(CURLY_BRACE_FIXTURE)
|
||||
expected = 'package_metadata = {"name": "mypackage", "version": "v201812.0123-beta"}'
|
||||
assert match.group(0) == expected
|
||||
|
|
@ -9,7 +9,7 @@ __version__ = "v201809.0002-beta"
|
|||
|
||||
def test_rewrite_lines():
|
||||
old_lines = REWRITE_FIXTURE.splitlines()
|
||||
patterns = ['__version__ = "{version}"']
|
||||
patterns = ['__version__ = "{pycalver}"']
|
||||
new_lines = rewrite.rewrite_lines(patterns, "v201911.0003", old_lines)
|
||||
|
||||
assert len(new_lines) == len(old_lines)
|
||||
|
|
|
|||
|
|
@ -2,18 +2,11 @@ import random
|
|||
import datetime as dt
|
||||
|
||||
from pycalver import version
|
||||
|
||||
|
||||
def test_current_calver():
|
||||
v = version.current_calver()
|
||||
assert len(v) == 7
|
||||
assert v.startswith("v")
|
||||
assert v[1:].isdigit()
|
||||
from pycalver import patterns
|
||||
|
||||
|
||||
def test_bump_beta():
|
||||
calver = version.current_calver()
|
||||
cur_version = calver + ".0001-beta"
|
||||
cur_version = "v201712.0001-beta"
|
||||
assert cur_version < version.incr(cur_version)
|
||||
assert version.incr(cur_version).endswith("-beta")
|
||||
assert version.incr(cur_version, release="alpha").endswith("-alpha")
|
||||
|
|
@ -21,8 +14,7 @@ def test_bump_beta():
|
|||
|
||||
|
||||
def test_bump_final():
|
||||
calver = version.current_calver()
|
||||
cur_version = calver + ".0001"
|
||||
cur_version = "v201712.0001"
|
||||
assert cur_version < version.incr(cur_version)
|
||||
assert version.incr(cur_version).endswith(".0002")
|
||||
assert version.incr(cur_version, release="alpha").endswith("-alpha")
|
||||
|
|
@ -34,20 +26,19 @@ def test_bump_final():
|
|||
|
||||
|
||||
def test_bump_future():
|
||||
"""Test that versions don't go back in time."""
|
||||
future_date = dt.datetime.today() + dt.timedelta(days=300)
|
||||
future_calver = future_date.strftime("v%Y%m")
|
||||
cur_version = future_calver + ".0001"
|
||||
assert cur_version < version.incr(cur_version)
|
||||
new_version = version.incr(cur_version)
|
||||
assert cur_version < new_version
|
||||
|
||||
|
||||
def test_bump_random(monkeypatch):
|
||||
cur_date = dt.date.today()
|
||||
cur_date = dt.date(2016, 1, 1) + dt.timedelta(days=random.randint(1, 2000))
|
||||
cur_version = cur_date.strftime("v%Y%m") + ".0001-dev"
|
||||
|
||||
def _mock_current_calver():
|
||||
return cur_date.strftime("v%Y%m")
|
||||
|
||||
monkeypatch.setattr(version, 'current_calver', _mock_current_calver)
|
||||
monkeypatch.setattr(version, 'TODAY', cur_date)
|
||||
|
||||
for i in range(1000):
|
||||
cur_date += dt.timedelta(days=int((1 + random.random()) ** 10))
|
||||
|
|
@ -62,37 +53,31 @@ def test_parse_version_info():
|
|||
version_str = "v201712.0001-alpha"
|
||||
version_nfo = version.parse_version_info(version_str)
|
||||
|
||||
assert version_nfo.pep440_version == "201712.1a0"
|
||||
assert version_nfo.version == "v201712.0001-alpha"
|
||||
assert version_nfo.calver == "v201712"
|
||||
assert version_nfo.year == "2017"
|
||||
assert version_nfo.month == "12"
|
||||
assert version_nfo.build == ".0001"
|
||||
assert version_nfo.release == "-alpha"
|
||||
assert version_nfo.build_no == "0001"
|
||||
assert version_nfo.release_tag == "alpha"
|
||||
# assert version_nfo.pep440_version == "201712.1a0"
|
||||
# assert version_nfo.version == "v201712.0001-alpha"
|
||||
assert version_nfo.year == 2017
|
||||
assert version_nfo.month == 12
|
||||
assert version_nfo.bid == "0001"
|
||||
assert version_nfo.tag == "alpha"
|
||||
|
||||
version_str = "v201712.0001"
|
||||
version_nfo = version.parse_version_info(version_str)
|
||||
|
||||
assert version_nfo.pep440_version == "201712.1"
|
||||
assert version_nfo.version == "v201712.0001"
|
||||
assert version_nfo.calver == "v201712"
|
||||
assert version_nfo.year == "2017"
|
||||
assert version_nfo.month == "12"
|
||||
assert version_nfo.build == ".0001"
|
||||
assert version_nfo.release == "-final"
|
||||
assert version_nfo.build_no == "0001"
|
||||
assert version_nfo.release_tag == "final"
|
||||
# assert version_nfo.pep440_version == "201712.1"
|
||||
# assert version_nfo.version == "v201712.0001"
|
||||
assert version_nfo.year == 2017
|
||||
assert version_nfo.month == 12
|
||||
assert version_nfo.bid == "0001"
|
||||
assert version_nfo.tag == "final"
|
||||
|
||||
|
||||
def test_readme_pycalver1():
|
||||
version_str = "v201712.0001-alpha"
|
||||
version_info = version.PYCALVER_RE.match(version_str).groupdict()
|
||||
version_info = patterns.PYCALVER_RE.match(version_str).groupdict()
|
||||
|
||||
assert version_info == {
|
||||
'version' : "v201712.0001-alpha",
|
||||
'calver' : "v201712",
|
||||
'pycalver' : "v201712.0001-alpha",
|
||||
'vYYYYMM' : "v201712",
|
||||
'year' : "2017",
|
||||
'month' : "12",
|
||||
'build' : ".0001",
|
||||
|
|
@ -104,11 +89,11 @@ def test_readme_pycalver1():
|
|||
|
||||
def test_readme_pycalver2():
|
||||
version_str = "v201712.0033"
|
||||
version_info = version.PYCALVER_RE.match(version_str).groupdict()
|
||||
version_info = patterns.PYCALVER_RE.match(version_str).groupdict()
|
||||
|
||||
assert version_info == {
|
||||
'version' : "v201712.0033",
|
||||
'calver' : "v201712",
|
||||
'pycalver' : "v201712.0033",
|
||||
'vYYYYMM' : "v201712",
|
||||
'year' : "2017",
|
||||
'month' : "12",
|
||||
'build' : ".0033",
|
||||
|
|
@ -140,3 +125,19 @@ def test_parse_error_nopadding():
|
|||
assert False
|
||||
except ValueError as err:
|
||||
pass
|
||||
|
||||
|
||||
def test_part_field_mapping():
|
||||
a_names = set(version.PATTERN_PART_FIELDS.keys())
|
||||
b_names = set(patterns.PART_PATTERNS.keys())
|
||||
c_names = set(patterns.COMPOSITE_PART_PATTERNS.keys())
|
||||
|
||||
extra_names = a_names - b_names
|
||||
assert not any(extra_names)
|
||||
missing_names = b_names - a_names
|
||||
assert missing_names == c_names
|
||||
|
||||
a_fields = set(version.PATTERN_PART_FIELDS.values())
|
||||
b_fields = set(version.VersionInfo._fields)
|
||||
|
||||
assert a_fields == b_fields
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue