mirror of
https://github.com/TECHNOFAB11/bumpver.git
synced 2025-12-12 14:30:09 +01:00
formatting with segment tree
This commit is contained in:
parent
8af5047244
commit
5a64983b8e
14 changed files with 325 additions and 154 deletions
|
|
@ -24,6 +24,7 @@ from . import rewrite
|
|||
from . import version
|
||||
from . import v1version
|
||||
from . import v2version
|
||||
from . import v1patterns
|
||||
|
||||
_VERBOSE = 0
|
||||
|
||||
|
|
@ -104,13 +105,14 @@ def test(
|
|||
) -> None:
|
||||
"""Increment a version number for demo purposes."""
|
||||
_configure_logging(verbose=max(_VERBOSE, verbose))
|
||||
raw_pattern = pattern
|
||||
|
||||
if release:
|
||||
_validate_release_tag(release)
|
||||
|
||||
new_version = _incr(
|
||||
old_version,
|
||||
raw_pattern=pattern,
|
||||
raw_pattern=raw_pattern,
|
||||
release=release,
|
||||
major=major,
|
||||
minor=minor,
|
||||
|
|
@ -118,7 +120,7 @@ def test(
|
|||
pin_date=pin_date,
|
||||
)
|
||||
if new_version is None:
|
||||
logger.error(f"Invalid version '{old_version}' and/or pattern '{pattern}'.")
|
||||
logger.error(f"Invalid version '{old_version}' and/or pattern '{raw_pattern}'.")
|
||||
sys.exit(1)
|
||||
|
||||
pep440_version = version.to_pep440(new_version)
|
||||
|
|
@ -185,7 +187,7 @@ def _print_diff(cfg: config.Config, new_version: str) -> None:
|
|||
|
||||
def _incr(
|
||||
old_version: str,
|
||||
raw_pattern: str = "{pycalver}",
|
||||
raw_pattern: str,
|
||||
*,
|
||||
release : str = None,
|
||||
major : bool = False,
|
||||
|
|
@ -193,9 +195,10 @@ def _incr(
|
|||
patch : bool = False,
|
||||
pin_date: bool = False,
|
||||
) -> typ.Optional[str]:
|
||||
is_new_pattern = "{" in raw_pattern and "}" in raw_pattern
|
||||
if is_new_pattern:
|
||||
return v2version.incr(
|
||||
v1_parts = list(v1patterns.PART_PATTERNS) + list(v1patterns.FULL_PART_FORMATS)
|
||||
has_v1_part = any("{" + part + "}" in raw_pattern for part in v1_parts)
|
||||
if has_v1_part:
|
||||
return v1version.incr(
|
||||
old_version,
|
||||
raw_pattern=raw_pattern,
|
||||
release=release,
|
||||
|
|
@ -205,7 +208,7 @@ def _incr(
|
|||
pin_date=pin_date,
|
||||
)
|
||||
else:
|
||||
return v1version.incr(
|
||||
return v2version.incr(
|
||||
old_version,
|
||||
raw_pattern=raw_pattern,
|
||||
release=release,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
# This file is part of the pycalver project
|
||||
# https://gitlab.com/mbarkhau/pycalver
|
||||
#
|
||||
# Copyright (c) 2019 Manuel Barkhau (mbarkhau@gmail.com) - MIT License
|
||||
# Copyright (c) 2018-2020 Manuel Barkhau (mbarkhau@gmail.com) - MIT License
|
||||
# SPDX-License-Identifier: MIT
|
||||
"""Parse setup.cfg or pycalver.cfg files."""
|
||||
|
||||
import re
|
||||
import glob
|
||||
import typing as typ
|
||||
import logging
|
||||
|
|
@ -125,7 +126,7 @@ def _debug_str(cfg: Config) -> str:
|
|||
"\n file_patterns={",
|
||||
]
|
||||
|
||||
for filepath, patterns in cfg.file_patterns.items():
|
||||
for filepath, patterns in sorted(cfg.file_patterns.items()):
|
||||
for pattern in patterns:
|
||||
cfg_str_parts.append(f"\n '{filepath}': '{pattern.raw_pattern}',")
|
||||
|
||||
|
|
@ -261,6 +262,14 @@ def _parse_config(raw_cfg: RawConfig) -> Config:
|
|||
|
||||
is_new_pattern = "{" not in version_pattern and "}" not in version_pattern
|
||||
|
||||
if is_new_pattern:
|
||||
invalid_chars = re.search(r"([\s]+)", raw_cfg['version_pattern'])
|
||||
if invalid_chars:
|
||||
raise ValueError(
|
||||
f"Invalid character(s) '{invalid_chars.group(1)}'"
|
||||
f" in pycalver.version_pattern = {raw_cfg['version_pattern']}"
|
||||
)
|
||||
|
||||
# TODO (mb 2020-09-18): Validate Pattern
|
||||
# detect YY with WW or UU -> suggest GG with VV
|
||||
# detect YYMM -> suggest YY0M
|
||||
|
|
@ -372,17 +381,17 @@ def _parse_raw_config(ctx: ProjectContext) -> RawConfig:
|
|||
|
||||
def parse(ctx: ProjectContext) -> MaybeConfig:
|
||||
"""Parse config file if available."""
|
||||
if not ctx.config_filepath.exists():
|
||||
if ctx.config_filepath.exists():
|
||||
try:
|
||||
raw_cfg = _parse_raw_config(ctx)
|
||||
return _parse_config(raw_cfg)
|
||||
except (TypeError, ValueError) as ex:
|
||||
logger.warning(f"Couldn't parse {ctx.config_rel_path}: {str(ex)}")
|
||||
return None
|
||||
else:
|
||||
logger.warning(f"File not found: {ctx.config_rel_path}")
|
||||
return None
|
||||
|
||||
try:
|
||||
raw_cfg = _parse_raw_config(ctx)
|
||||
return _parse_config(raw_cfg)
|
||||
except (TypeError, ValueError) as ex:
|
||||
logger.warning(f"Couldn't parse {ctx.config_rel_path}: {str(ex)}")
|
||||
return None
|
||||
|
||||
|
||||
DEFAULT_CONFIGPARSER_BASE_TMPL = """
|
||||
[pycalver]
|
||||
|
|
|
|||
|
|
@ -1,3 +1,8 @@
|
|||
# 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
|
||||
import typing as typ
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,8 @@
|
|||
# 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
|
||||
import typing as typ
|
||||
import difflib
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,8 @@ def update_cfg_from_vcs(cfg: config.Config, all_tags: typ.List[str]) -> config.C
|
|||
return cfg
|
||||
|
||||
version_tags.sort(reverse=True)
|
||||
logger.debug(f"found {len(version_tags)} tags: {version_tags[:2]}")
|
||||
_debug_tags = ", ".join(version_tags[:3])
|
||||
logger.debug(f"found tags: {_debug_tags} ... ({len(version_tags)} in total)")
|
||||
latest_version_tag = version_tags[0]
|
||||
latest_version_pep440 = version.to_pep440(latest_version_tag)
|
||||
if latest_version_tag <= cfg.current_version:
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import re
|
|||
import typing as typ
|
||||
import logging
|
||||
|
||||
from . import utils
|
||||
from .patterns import RE_PATTERN_ESCAPES
|
||||
from .patterns import Pattern
|
||||
|
||||
|
|
@ -80,8 +81,8 @@ PART_PATTERNS = {
|
|||
'month' : r"(?:0[0-9]|1[0-2])",
|
||||
'month_short': r"(?:1[0-2]|[1-9])",
|
||||
'build_no' : r"\d{4,}",
|
||||
'pep440_tag' : r"(?:a|b|dev|rc|post)?\d*",
|
||||
'tag' : r"(?:alpha|beta|dev|rc|post|final)",
|
||||
'pep440_tag' : r"(?:post|dev|rc|a|b)?\d*",
|
||||
'tag' : r"(?:preview|final|alpha|beta|post|pre|dev|rc|a|b|c|r)",
|
||||
'yy' : r"\d{2}",
|
||||
'yyyy' : r"\d{4}",
|
||||
'quarter' : r"[1-4]",
|
||||
|
|
@ -183,6 +184,7 @@ def _replace_pattern_parts(pattern: str) -> str:
|
|||
named_part_pattern = f"(?P<{part_name}>{part_pattern})"
|
||||
placeholder = "\u005c{" + part_name + "\u005c}"
|
||||
pattern = pattern.replace(placeholder, named_part_pattern)
|
||||
|
||||
return pattern
|
||||
|
||||
|
||||
|
|
@ -214,6 +216,7 @@ def _compile_pattern_re(version_pattern: str, raw_pattern: str) -> typ.Pattern[s
|
|||
return re.compile(pattern_str)
|
||||
|
||||
|
||||
@utils.memo
|
||||
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
|
||||
regexp = _compile_pattern_re(version_pattern, _raw_pattern)
|
||||
|
|
|
|||
|
|
@ -37,11 +37,11 @@ def rewrite_lines(
|
|||
>>> rewrite_lines(patterns, new_vinfo, ['__version__ = "201809.2b0"'])
|
||||
['__version__ = "201811.123b0"']
|
||||
"""
|
||||
new_lines = old_lines[:]
|
||||
found_patterns = set()
|
||||
found_patterns: typ.Set[Pattern] = set()
|
||||
|
||||
new_lines = old_lines[:]
|
||||
for match in parse.iter_matches(old_lines, patterns):
|
||||
found_patterns.add(match.pattern.raw_pattern)
|
||||
found_patterns.add(match.pattern)
|
||||
replacement = v1version.format_version(new_vinfo, match.pattern.raw_pattern)
|
||||
span_l, span_r = match.span
|
||||
new_line = match.line[:span_l] + replacement + match.line[span_r:]
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import typing as typ
|
|||
import logging
|
||||
import datetime as dt
|
||||
|
||||
import lexid
|
||||
|
||||
from . import version
|
||||
from . import v1patterns
|
||||
|
||||
|
|
@ -18,15 +20,19 @@ logger = logging.getLogger("pycalver.v1version")
|
|||
CalInfo = typ.Union[version.V1CalendarInfo, version.V1VersionInfo]
|
||||
|
||||
|
||||
def _is_later_than(old: CalInfo, new: CalInfo) -> bool:
|
||||
"""Is old > new based on non None fields."""
|
||||
def _is_cal_gt(left: CalInfo, right: CalInfo) -> bool:
|
||||
"""Is left > right for non-None fields."""
|
||||
|
||||
lvals = []
|
||||
rvals = []
|
||||
for field in version.V1CalendarInfo._fields:
|
||||
aval = getattr(old, field)
|
||||
bval = getattr(new, field)
|
||||
if not (aval is None or bval is None):
|
||||
if aval > bval:
|
||||
return True
|
||||
return False
|
||||
lval = getattr(left , field)
|
||||
rval = getattr(right, field)
|
||||
if not (lval is None or rval is None):
|
||||
lvals.append(lval)
|
||||
rvals.append(rval)
|
||||
|
||||
return lvals > rvals
|
||||
|
||||
|
||||
def _ver_to_cal_info(vnfo: version.V1VersionInfo) -> version.V1CalendarInfo:
|
||||
|
|
@ -235,7 +241,7 @@ def parse_version_info(version_str: str, raw_pattern: str = "{pycalver}") -> ver
|
|||
if match is None:
|
||||
err_msg = (
|
||||
f"Invalid version string '{version_str}' "
|
||||
f"for pattern '{raw_pattern}'/'{pattern.regexp}'"
|
||||
f"for pattern '{raw_pattern}'/'{pattern.regexp.pattern}'"
|
||||
)
|
||||
raise version.PatternError(err_msg)
|
||||
else:
|
||||
|
|
@ -386,19 +392,23 @@ def incr(
|
|||
|
||||
cur_cinfo = _ver_to_cal_info(old_vinfo) if pin_date else cal_info()
|
||||
|
||||
if _is_later_than(old_vinfo, cur_cinfo):
|
||||
cur_vinfo = old_vinfo._replace(**cur_cinfo._asdict())
|
||||
else:
|
||||
logger.warning(f"Version appears to be from the future '{old_version}'")
|
||||
if _is_cal_gt(old_vinfo, cur_cinfo):
|
||||
logger.warning(f"Old version appears to be from the future '{old_version}'")
|
||||
cur_vinfo = old_vinfo
|
||||
else:
|
||||
cur_vinfo = old_vinfo._replace(**cur_cinfo._asdict())
|
||||
|
||||
cur_vinfo = cur_vinfo._replace(bid=lexid.next_id(cur_vinfo.bid))
|
||||
|
||||
if release:
|
||||
cur_vinfo = cur_vinfo._replace(tag=release)
|
||||
if major:
|
||||
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)
|
||||
|
||||
cur_vinfo = version.incr_non_cal_parts(
|
||||
cur_vinfo,
|
||||
release,
|
||||
major,
|
||||
minor,
|
||||
patch,
|
||||
)
|
||||
new_version = format_version(cur_vinfo, raw_pattern)
|
||||
if new_version == old_version:
|
||||
logger.error("Invalid arguments or pattern, version did not change.")
|
||||
|
|
|
|||
|
|
@ -27,7 +27,8 @@ def update_cfg_from_vcs(cfg: config.Config, all_tags: typ.List[str]) -> config.C
|
|||
return cfg
|
||||
|
||||
version_tags.sort(reverse=True)
|
||||
logger.debug(f"found {len(version_tags)} tags: {version_tags[:2]}")
|
||||
_debug_tags = ", ".join(version_tags[:3])
|
||||
logger.debug(f"found tags: {_debug_tags} ... ({len(version_tags)} in total)")
|
||||
latest_version_tag = version_tags[0]
|
||||
latest_version_pep440 = version.to_pep440(latest_version_tag)
|
||||
if latest_version_tag <= cfg.current_version:
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import re
|
|||
import typing as typ
|
||||
import logging
|
||||
|
||||
from . import utils
|
||||
from .patterns import RE_PATTERN_ESCAPES
|
||||
from .patterns import Pattern
|
||||
|
||||
|
|
@ -84,8 +85,8 @@ PART_PATTERNS = {
|
|||
'PATCH': r"[0-9]+",
|
||||
'BUILD': r"[0-9]+",
|
||||
'BLD' : r"[1-9][0-9]*",
|
||||
'TAG' : r"(?:alpha|beta|dev|pre|rc|post|final)",
|
||||
'PYTAG': r"(?:a|b|dev|rc|post)",
|
||||
'TAG' : r"(?:preview|final|alpha|beta|post|pre|dev|rc|a|b|c|r)",
|
||||
'PYTAG': r"(?:post|dev|rc|a|b)",
|
||||
'NUM' : r"[0-9]+",
|
||||
}
|
||||
|
||||
|
|
@ -203,24 +204,56 @@ PART_FORMATS: typ.Dict[str, typ.Callable[[FieldValue], str]] = {
|
|||
}
|
||||
|
||||
|
||||
def _convert_to_pep440(version_pattern: str) -> str:
|
||||
# NOTE (mb 2020-09-20): This does not support some
|
||||
# corner cases as specified in PEP440, in particular
|
||||
# related to post and dev releases.
|
||||
|
||||
version_pattern = version_pattern.lstrip("v")
|
||||
|
||||
part_names = list(PATTERN_PART_FIELDS.keys())
|
||||
part_names.sort(key=len, reverse=True)
|
||||
if version_pattern == "vYYYY0M.BUILD[-TAG]":
|
||||
return "YYYY0M.BLD[PYTAGNUM]"
|
||||
|
||||
# TODO (mb 2020-09-20)
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def normalize_pattern(version_pattern: str, raw_pattern: str) -> str:
|
||||
normalized_pattern = raw_pattern
|
||||
if "{version}" in raw_pattern:
|
||||
normalized_pattern = normalized_pattern.replace("{version}", version_pattern)
|
||||
|
||||
if "{pep440_version}" in normalized_pattern:
|
||||
pep440_version_pattern = _convert_to_pep440(version_pattern)
|
||||
normalized_pattern = normalized_pattern.replace("{pep440_version}", pep440_version_pattern)
|
||||
|
||||
return normalized_pattern
|
||||
|
||||
|
||||
def _replace_pattern_parts(pattern: str) -> str:
|
||||
# The pattern is escaped, so that everything besides the format
|
||||
# string variables is treated literally.
|
||||
if "[" in pattern and "]" in pattern:
|
||||
pattern = pattern.replace("[", "(?:")
|
||||
pattern = pattern.replace("]", ")?")
|
||||
while True:
|
||||
new_pattern, n = re.subn(r"([^\\]|^)\[", r"\1(?:", pattern)
|
||||
new_pattern, m = re.subn(r"([^\\]|^)\]", r"\1)?" , new_pattern)
|
||||
pattern = new_pattern
|
||||
if n + m == 0:
|
||||
break
|
||||
|
||||
SortKey = typ.Tuple[int, int]
|
||||
PostitionedPart = typ.Tuple[int, int, str]
|
||||
part_patterns_by_index: typ.Dict[SortKey, PostitionedPart] = {}
|
||||
|
||||
part_patterns_by_index: typ.Dict[typ.Tuple[int, int], typ.Tuple[int, int, str]] = {}
|
||||
for part_name, part_pattern in PART_PATTERNS.items():
|
||||
start_idx = pattern.find(part_name)
|
||||
if start_idx < 0:
|
||||
continue
|
||||
|
||||
field = PATTERN_PART_FIELDS[part_name]
|
||||
named_part_pattern = f"(?P<{field}>{part_pattern})"
|
||||
end_idx = start_idx + len(part_name)
|
||||
sort_key = (-end_idx, -len(part_name))
|
||||
part_patterns_by_index[sort_key] = (start_idx, end_idx, named_part_pattern)
|
||||
if start_idx >= 0:
|
||||
field = PATTERN_PART_FIELDS[part_name]
|
||||
named_part_pattern = f"(?P<{field}>{part_pattern})"
|
||||
end_idx = start_idx + len(part_name)
|
||||
sort_key = (-end_idx, -len(part_name))
|
||||
part_patterns_by_index[sort_key] = (start_idx, end_idx, named_part_pattern)
|
||||
|
||||
# NOTE (mb 2020-09-17): The sorting is done so that we process items:
|
||||
# - right before left
|
||||
|
|
@ -238,26 +271,21 @@ def _replace_pattern_parts(pattern: str) -> str:
|
|||
|
||||
|
||||
def _compile_pattern_re(version_pattern: str, raw_pattern: str) -> typ.Pattern[str]:
|
||||
escaped_pattern = raw_pattern
|
||||
normalized_pattern = normalize_pattern(version_pattern, raw_pattern)
|
||||
escaped_pattern = normalized_pattern
|
||||
for char, escaped in RE_PATTERN_ESCAPES:
|
||||
# [] braces are used for optional parts, such as [-TAG]/[-beta]
|
||||
is_semantic_char = char in "[]"
|
||||
# and need to be escaped manually.
|
||||
is_semantic_char = char in "[]\\"
|
||||
if not is_semantic_char:
|
||||
# escape it so it is a literal in the re pattern
|
||||
escaped_pattern = escaped_pattern.replace(char, escaped)
|
||||
|
||||
escaped_pattern = raw_pattern.replace("[", "\u005c[").replace("]", "\u005c]")
|
||||
normalized_pattern = escaped_pattern.replace("{version}", version_pattern)
|
||||
print(">>>>", (raw_pattern ,))
|
||||
print("....", (escaped_pattern ,))
|
||||
print("....", (normalized_pattern,))
|
||||
print("<<<<", (normalized_pattern,))
|
||||
|
||||
# TODO (mb 2020-09-19): replace {version} etc with version_pattern
|
||||
pattern_str = _replace_pattern_parts(escaped_pattern)
|
||||
return re.compile(pattern_str)
|
||||
|
||||
|
||||
@utils.memo
|
||||
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
|
||||
regexp = _compile_pattern_re(version_pattern, _raw_pattern)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ from . import config
|
|||
from . import rewrite
|
||||
from . import version
|
||||
from . import v2version
|
||||
from . import v2patterns
|
||||
from .patterns import Pattern
|
||||
|
||||
logger = logging.getLogger("pycalver.v2rewrite")
|
||||
|
|
@ -41,12 +42,15 @@ def rewrite_lines(
|
|||
>>> rewrite_lines(patterns, new_vinfo, old_lines)
|
||||
['__version__ = "201811.123b0"']
|
||||
"""
|
||||
new_lines = old_lines[:]
|
||||
found_patterns = set()
|
||||
found_patterns: typ.Set[Pattern] = set()
|
||||
|
||||
new_lines = old_lines[:]
|
||||
for match in parse.iter_matches(old_lines, patterns):
|
||||
found_patterns.add(match.pattern.raw_pattern)
|
||||
replacement = v2version.format_version(new_vinfo, match.pattern.raw_pattern)
|
||||
found_patterns.add(match.pattern)
|
||||
normalized_pattern = v2patterns.normalize_pattern(
|
||||
match.pattern.version_pattern, match.pattern.raw_pattern
|
||||
)
|
||||
replacement = v2version.format_version(new_vinfo, normalized_pattern)
|
||||
span_l, span_r = match.span
|
||||
new_line = match.line[:span_l] + replacement + match.line[span_r:]
|
||||
new_lines[match.lineno] = new_line
|
||||
|
|
@ -93,6 +97,18 @@ def rfd_from_content(
|
|||
return rewrite.RewrittenFileData(path, line_sep, old_lines, new_lines)
|
||||
|
||||
|
||||
def _patterns_with_change(
|
||||
old_vinfo: version.V2VersionInfo, new_vinfo: version.V2VersionInfo, patterns: typ.List[Pattern]
|
||||
) -> int:
|
||||
patterns_with_change = 0
|
||||
for pattern in patterns:
|
||||
old_str = v2version.format_version(old_vinfo, pattern.raw_pattern)
|
||||
new_str = v2version.format_version(new_vinfo, pattern.raw_pattern)
|
||||
if old_str != new_str:
|
||||
patterns_with_change += 1
|
||||
return patterns_with_change
|
||||
|
||||
|
||||
def iter_rewritten(
|
||||
file_patterns: config.PatternsByFile,
|
||||
new_vinfo : version.V2VersionInfo,
|
||||
|
|
@ -159,13 +175,6 @@ def diff(
|
|||
with file_path.open(mode="rt", encoding="utf-8") as fobj:
|
||||
content = fobj.read()
|
||||
|
||||
patterns_with_change = 0
|
||||
for pattern in patterns:
|
||||
old_str = v2version.format_version(old_vinfo, pattern.raw_pattern)
|
||||
new_str = v2version.format_version(new_vinfo, pattern.raw_pattern)
|
||||
if old_str != new_str:
|
||||
patterns_with_change += 1
|
||||
|
||||
try:
|
||||
rfd = rfd_from_content(patterns, new_vinfo, content)
|
||||
except rewrite.NoPatternMatch:
|
||||
|
|
@ -175,6 +184,8 @@ def diff(
|
|||
|
||||
rfd = rfd._replace(path=str(file_path))
|
||||
lines = rewrite.diff_lines(rfd)
|
||||
|
||||
patterns_with_change = _patterns_with_change(old_vinfo, new_vinfo, patterns)
|
||||
if len(lines) == 0 and patterns_with_change > 0:
|
||||
errmsg = f"No patterns matched for '{file_path}'"
|
||||
raise rewrite.NoPatternMatch(errmsg)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import typing as typ
|
|||
import logging
|
||||
import datetime as dt
|
||||
|
||||
import lexid
|
||||
|
||||
from . import version
|
||||
from . import v2patterns
|
||||
|
||||
|
|
@ -18,15 +20,19 @@ logger = logging.getLogger("pycalver.v2version")
|
|||
CalInfo = typ.Union[version.V2CalendarInfo, version.V2VersionInfo]
|
||||
|
||||
|
||||
def _is_later_than(old: CalInfo, new: CalInfo) -> bool:
|
||||
"""Is old > new based on non None fields."""
|
||||
for field in version.V1CalendarInfo._fields:
|
||||
aval = getattr(old, field)
|
||||
bval = getattr(new, field)
|
||||
if not (aval is None or bval is None):
|
||||
if aval > bval:
|
||||
return True
|
||||
return False
|
||||
def _is_cal_gt(left: CalInfo, right: CalInfo) -> bool:
|
||||
"""Is left > right for non-None fields."""
|
||||
|
||||
lvals = []
|
||||
rvals = []
|
||||
for field in version.V2CalendarInfo._fields:
|
||||
lval = getattr(left , field)
|
||||
rval = getattr(right, field)
|
||||
if not (lval is None or rval is None):
|
||||
lvals.append(lval)
|
||||
rvals.append(rval)
|
||||
|
||||
return lvals > rvals
|
||||
|
||||
|
||||
def _ver_to_cal_info(vinfo: version.V2VersionInfo) -> version.V2CalendarInfo:
|
||||
|
|
@ -268,9 +274,10 @@ def is_valid(version_str: str, raw_pattern: str = "vYYYY0M.BUILD[-TAG]") -> bool
|
|||
|
||||
|
||||
TemplateKwargs = typ.Dict[str, typ.Union[str, int, None]]
|
||||
PartValues = typ.List[typ.Tuple[str, str]]
|
||||
|
||||
|
||||
def _format_part_values(vinfo: version.V2VersionInfo) -> typ.Dict[str, str]:
|
||||
def _format_part_values(vinfo: version.V2VersionInfo) -> PartValues:
|
||||
"""Generate kwargs for template from minimal V2VersionInfo.
|
||||
|
||||
The V2VersionInfo Tuple only has the minimal representation
|
||||
|
|
@ -279,14 +286,14 @@ def _format_part_values(vinfo: version.V2VersionInfo) -> typ.Dict[str, str]:
|
|||
representation '09' for '0M'.
|
||||
|
||||
>>> vinfo = parse_version_info("v200709.1033-beta", pattern="vYYYY0M.BUILD[-TAG]")
|
||||
>>> kwargs = _format_part_values(vinfo)
|
||||
>>> kwargs = dict(_format_part_values(vinfo))
|
||||
>>> (kwargs['YYYY'], kwargs['0M'], kwargs['BUILD'], kwargs['TAG'])
|
||||
('2007', '09', '1033', 'beta')
|
||||
>>> (kwargs['YY'], kwargs['0Y'], kwargs['MM'], kwargs['PYTAG'])
|
||||
('7', '07', '9', 'b')
|
||||
|
||||
>>> vinfo = parse_version_info("200709.1033b1", pattern="YYYY0M.BLD[PYTAGNUM]")
|
||||
>>> kwargs = _format_part_values(vinfo)
|
||||
>>> kwargs = dict(_format_part_values(vinfo))
|
||||
>>> (kwargs['YYYY'], kwargs['0M'], kwargs['BUILD'], kwargs['PYTAG'], kwargs['NUM'])
|
||||
('2007', '09', '1033', 'b', '1')
|
||||
"""
|
||||
|
|
@ -299,7 +306,7 @@ def _format_part_values(vinfo: version.V2VersionInfo) -> typ.Dict[str, str]:
|
|||
format_fn = v2patterns.PART_FORMATS[part]
|
||||
kwargs[part] = format_fn(field_val)
|
||||
|
||||
return kwargs
|
||||
return sorted(kwargs.items(), key=lambda item: -len(item[0]))
|
||||
|
||||
|
||||
def _make_segments(raw_pattern: str) -> typ.List[str]:
|
||||
|
|
@ -345,12 +352,63 @@ def _clear_zero_segments(
|
|||
return non_zero_segs
|
||||
|
||||
|
||||
Segment = str
|
||||
# mypy limitation wrt. cyclic definition
|
||||
# SegmentTree = typ.List[typ.Union[Segment, "SegmentTree"]]
|
||||
SegmentTree = typ.Any
|
||||
|
||||
|
||||
def _parse_segment_tree(raw_pattern: str) -> SegmentTree:
|
||||
"""Generate segment tree from pattern string.
|
||||
|
||||
>>> tree = _parse_segment_tree("aa[bb[cc]]")
|
||||
>>> assert tree == ["aa", ["bb", ["cc"]]]
|
||||
>>> tree = _parse_segment_tree("aa[bb[cc]dd[ee]ff]gg")
|
||||
>>> assert tree == ["aa", ["bb", ["cc"], "dd", ["ee"], "ff"], "gg"]
|
||||
"""
|
||||
|
||||
internal_root: SegmentTree = []
|
||||
branch_stack : typ.List[SegmentTree] = [internal_root]
|
||||
segment_start_index = -1
|
||||
|
||||
raw_pattern = "[" + raw_pattern + "]"
|
||||
|
||||
for i, char in enumerate(raw_pattern):
|
||||
is_escaped = i > 0 and raw_pattern[i - 1] == "\\"
|
||||
if char in "[]" and not is_escaped:
|
||||
start = segment_start_index + 1
|
||||
end = i
|
||||
if start < end:
|
||||
branch_stack[-1].append(raw_pattern[start:end])
|
||||
|
||||
if char == "[":
|
||||
new_branch: SegmentTree = []
|
||||
branch_stack[-1].append(new_branch)
|
||||
branch_stack.append(new_branch)
|
||||
segment_start_index = i
|
||||
elif char == "]":
|
||||
if len(branch_stack) == 1:
|
||||
err = f"Unbalanced brace(s) in '{raw_pattern}'"
|
||||
raise ValueError(err)
|
||||
|
||||
branch_stack.pop()
|
||||
segment_start_index = i
|
||||
else:
|
||||
raise NotImplementedError("Unreachable")
|
||||
|
||||
if len(branch_stack) > 1:
|
||||
err = f"Unclosed brace in '{raw_pattern}'"
|
||||
raise ValueError(err)
|
||||
|
||||
return internal_root[0]
|
||||
|
||||
|
||||
def _format_segments(
|
||||
vinfo : version.V2VersionInfo,
|
||||
pattern_segs: typ.List[str],
|
||||
part_values : PartValues,
|
||||
) -> typ.List[str]:
|
||||
kwargs = _format_part_values(vinfo)
|
||||
part_values = sorted(kwargs.items(), key=lambda item: -len(item[0]))
|
||||
# NOTE (mb 2020-09-21): Old implementaion that doesn't cover corner
|
||||
# cases relating to escaped braces.
|
||||
|
||||
is_zero_segment = [True] * len(pattern_segs)
|
||||
|
||||
|
|
@ -361,23 +419,25 @@ def _format_segments(
|
|||
idx_r = len(pattern_segs) - 1
|
||||
while idx_l <= idx_r:
|
||||
# NOTE (mb 2020-09-18): All segments are optional,
|
||||
# except the most left and the most right,
|
||||
# i.e the ones NOT surrounded by braces.
|
||||
# Empty string is a valid segment.
|
||||
is_optional = idx_l > 0
|
||||
# except the most left and the most right.
|
||||
# In other words the ones NOT surrounded by braces are
|
||||
# required. Empty string is a valid segment.
|
||||
is_required_seg = idx_l == 0
|
||||
|
||||
seg_l = pattern_segs[idx_l]
|
||||
seg_r = pattern_segs[idx_r]
|
||||
|
||||
for part, part_value in part_values:
|
||||
if part in seg_l:
|
||||
seg_l = seg_l.replace(part, part_value)
|
||||
if not (is_optional and str(part_value) == version.ZERO_VALUES.get(part)):
|
||||
seg_l = seg_l.replace(part, part_value)
|
||||
is_zero_seg = str(part_value) == version.ZERO_VALUES.get(part)
|
||||
if is_required_seg or not is_zero_seg:
|
||||
is_zero_segment[idx_l] = False
|
||||
|
||||
if part in seg_r:
|
||||
seg_r = seg_r.replace(part, part_value)
|
||||
if not (is_optional and str(part_value) == version.ZERO_VALUES[part]):
|
||||
seg_r = seg_r.replace(part, part_value)
|
||||
is_zero_seg = str(part_value) == version.ZERO_VALUES.get(part)
|
||||
if is_required_seg or not is_zero_seg:
|
||||
is_zero_segment[idx_r] = False
|
||||
|
||||
formatted_segs_l.append(seg_l)
|
||||
|
|
@ -391,6 +451,43 @@ def _format_segments(
|
|||
return _clear_zero_segments(formatted_segs, is_zero_segment)
|
||||
|
||||
|
||||
FormattedSegmentParts = typ.List[str]
|
||||
|
||||
|
||||
def _format_segment_tree(
|
||||
seg_tree: SegmentTree,
|
||||
part_values : PartValues,
|
||||
) -> FormattedSegmentParts:
|
||||
result_parts = []
|
||||
for seg in seg_tree:
|
||||
if isinstance(seg, list):
|
||||
result_parts.extend(_format_segment_tree(seg, part_values))
|
||||
else:
|
||||
# NOTE (mb 2020-09-24): If a segment has any zero parts,
|
||||
# the whole segment is skipped.
|
||||
is_zero_seg = False
|
||||
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:
|
||||
is_zero_seg = True
|
||||
else:
|
||||
formatted_seg = formatted_seg.replace(part, part_value)
|
||||
|
||||
if not is_zero_seg:
|
||||
result_parts.append(formatted_seg)
|
||||
|
||||
return result_parts
|
||||
|
||||
|
||||
def format_version(vinfo: version.V2VersionInfo, raw_pattern: str) -> str:
|
||||
"""Generate version string.
|
||||
|
||||
|
|
@ -477,10 +574,15 @@ def format_version(vinfo: version.V2VersionInfo, raw_pattern: str) -> str:
|
|||
>>> format_version(vinfo_d, pattern='__version__ = "vMAJOR[.MINOR[.PATCH[-TAG[NUM]]]]"')
|
||||
'__version__ = "v1.0.0-rc2"'
|
||||
"""
|
||||
pattern_segs = _make_segments(raw_pattern)
|
||||
formatted_segs = _format_segments(vinfo, pattern_segs)
|
||||
part_values = _format_part_values(vinfo)
|
||||
|
||||
return "".join(formatted_segs)
|
||||
# pattern_segs = _make_segments(raw_pattern)
|
||||
# formatted_segs = _format_segments(pattern_segs, part_values)
|
||||
# version_str = "".join(formatted_segs)
|
||||
|
||||
seg_tree = _parse_segment_tree(raw_pattern)
|
||||
version_str_parts = _format_segment_tree(seg_tree, part_values)
|
||||
return "".join(version_str_parts)
|
||||
|
||||
|
||||
def incr(
|
||||
|
|
@ -505,19 +607,30 @@ def incr(
|
|||
|
||||
cur_cinfo = _ver_to_cal_info(old_vinfo) if pin_date else cal_info()
|
||||
|
||||
if _is_later_than(old_vinfo, cur_cinfo):
|
||||
logger.warning(f"Version appears to be from the future '{old_version}'")
|
||||
if _is_cal_gt(old_vinfo, cur_cinfo):
|
||||
logger.warning(f"Old version appears to be from the future '{old_version}'")
|
||||
cur_vinfo = old_vinfo
|
||||
else:
|
||||
cur_vinfo = old_vinfo._replace(**cur_cinfo._asdict())
|
||||
|
||||
cur_vinfo = version.incr_non_cal_parts(
|
||||
cur_vinfo,
|
||||
release,
|
||||
major,
|
||||
minor,
|
||||
patch,
|
||||
)
|
||||
# prevent truncation of leading zeros
|
||||
if int(cur_vinfo.bid) < 1000:
|
||||
cur_vinfo = cur_vinfo._replace(bid=str(int(cur_vinfo.bid) + 1000))
|
||||
|
||||
cur_vinfo = cur_vinfo._replace(bid=lexid.next_id(cur_vinfo.bid))
|
||||
|
||||
if release:
|
||||
cur_vinfo = cur_vinfo._replace(tag=release)
|
||||
if major:
|
||||
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)
|
||||
|
||||
# TODO (mb 2020-09-20): New Rollover Behaviour:
|
||||
# Reset major, minor, patch to zero if any part to the left of it is incremented
|
||||
|
||||
new_version = format_version(cur_vinfo, raw_pattern)
|
||||
if new_version == old_version:
|
||||
logger.error("Invalid arguments or pattern, version did not change.")
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
# 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
|
||||
import typing as typ
|
||||
import datetime as dt
|
||||
|
||||
import lexid
|
||||
import pkg_resources
|
||||
|
||||
MaybeInt = typ.Optional[int]
|
||||
|
|
@ -71,9 +75,6 @@ class V2VersionInfo(typ.NamedTuple):
|
|||
pytag : str
|
||||
|
||||
|
||||
VersionInfoType = typ.TypeVar('VersionInfoType', V1VersionInfo, V2VersionInfo)
|
||||
|
||||
|
||||
# The test suite may replace this.
|
||||
TODAY = dt.datetime.utcnow().date()
|
||||
|
||||
|
|
@ -81,7 +82,7 @@ TODAY = dt.datetime.utcnow().date()
|
|||
TAG_BY_PEP440_TAG = {
|
||||
'a' : 'alpha',
|
||||
'b' : 'beta',
|
||||
"" : 'final',
|
||||
'' : 'final',
|
||||
'rc' : 'rc',
|
||||
'dev' : 'dev',
|
||||
'post': 'post',
|
||||
|
|
@ -89,13 +90,19 @@ TAG_BY_PEP440_TAG = {
|
|||
|
||||
|
||||
PEP440_TAG_BY_TAG = {
|
||||
'alpha': "a",
|
||||
'beta' : "b",
|
||||
'final': "",
|
||||
'pre' : "rc",
|
||||
'rc' : "rc",
|
||||
'dev' : "dev",
|
||||
'post' : "post",
|
||||
'a' : 'a',
|
||||
'b' : 'b',
|
||||
'dev' : 'dev',
|
||||
'alpha' : 'a',
|
||||
'beta' : 'b',
|
||||
'preview': 'rc',
|
||||
'pre' : 'rc',
|
||||
'rc' : 'rc',
|
||||
'c' : 'rc',
|
||||
'final' : '',
|
||||
'post' : 'post',
|
||||
'r' : 'post',
|
||||
'rev' : 'post',
|
||||
}
|
||||
|
||||
assert set(TAG_BY_PEP440_TAG.keys()) == set(PEP440_TAG_BY_TAG.values())
|
||||
|
|
@ -148,28 +155,3 @@ def to_pep440(version: str) -> str:
|
|||
'201811.7b0'
|
||||
"""
|
||||
return str(pkg_resources.parse_version(version))
|
||||
|
||||
|
||||
def incr_non_cal_parts(
|
||||
vinfo : VersionInfoType,
|
||||
release: typ.Optional[str],
|
||||
major : bool,
|
||||
minor : bool,
|
||||
patch : bool,
|
||||
) -> VersionInfoType:
|
||||
_bid = vinfo.bid
|
||||
if int(_bid) < 1000:
|
||||
# prevent truncation of leading zeros
|
||||
_bid = str(int(_bid) + 1000)
|
||||
|
||||
vinfo = vinfo._replace(bid=lexid.next_id(_bid))
|
||||
|
||||
if release:
|
||||
vinfo = vinfo._replace(tag=release)
|
||||
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)
|
||||
return vinfo
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue