mirror of
https://github.com/TECHNOFAB11/bumpver.git
synced 2025-12-16 08:13:52 +01:00
module reorg
This commit is contained in:
parent
e1aaf7629b
commit
8af5047244
23 changed files with 1658 additions and 1532 deletions
|
|
@ -1,8 +0,0 @@
|
|||
# This file is part of the pycalver project
|
||||
# https://github.com/mbarkhau/pycalver
|
||||
#
|
||||
# Copyright (c) 2018-2020 Manuel Barkhau (mbarkhau@gmail.com) - MIT License
|
||||
# SPDX-License-Identifier: MIT
|
||||
"""PyCalVer: CalVer for Python Packages."""
|
||||
|
||||
__version__ = "v202007.1036"
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# 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
|
||||
"""
|
||||
CLI module for PyCalVer.
|
||||
|
||||
Provided subcommands: show, test, init, bump
|
||||
"""
|
||||
import typing as typ
|
||||
import logging
|
||||
|
||||
import pycalver.version as v1version
|
||||
import pycalver2.rewrite as v2rewrite
|
||||
import pycalver2.version as v2version
|
||||
from pycalver import config
|
||||
|
||||
logger = logging.getLogger("pycalver2.cli")
|
||||
|
||||
|
||||
def update_cfg_from_vcs(cfg: config.Config, all_tags: typ.List[str]) -> config.Config:
|
||||
version_tags = [tag for tag in all_tags if v2version.is_valid(tag, cfg.version_pattern)]
|
||||
if not version_tags:
|
||||
logger.debug("no vcs tags found")
|
||||
return cfg
|
||||
|
||||
version_tags.sort(reverse=True)
|
||||
logger.debug(f"found {len(version_tags)} tags: {version_tags[:2]}")
|
||||
latest_version_tag = version_tags[0]
|
||||
latest_version_pep440 = v1version.to_pep440(latest_version_tag)
|
||||
if latest_version_tag <= cfg.current_version:
|
||||
return cfg
|
||||
|
||||
logger.info(f"Working dir version : {cfg.current_version}")
|
||||
logger.info(f"Latest version from VCS tag: {latest_version_tag}")
|
||||
return cfg._replace(
|
||||
current_version=latest_version_tag,
|
||||
pep440_version=latest_version_pep440,
|
||||
)
|
||||
|
||||
|
||||
def rewrite(
|
||||
cfg : config.Config,
|
||||
new_version: str,
|
||||
) -> None:
|
||||
new_vinfo = v2version.parse_version_info(new_version, cfg.version_pattern)
|
||||
v2rewrite.rewrite(cfg.file_patterns, new_vinfo)
|
||||
|
||||
|
||||
def get_diff(cfg: config.Config, new_version: str) -> str:
|
||||
new_vinfo = v2version.parse_version_info(new_version, cfg.version_pattern)
|
||||
return v2rewrite.diff(new_vinfo, cfg.file_patterns)
|
||||
|
|
@ -1,252 +0,0 @@
|
|||
# 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
|
||||
"""Compose Regular Expressions from Patterns.
|
||||
|
||||
>>> pattern = compile_pattern("vYYYY0M.BUILD[-TAG]")
|
||||
>>> version_info = pattern.regexp.match("v201712.0123-alpha")
|
||||
>>> assert version_info.groupdict() == {
|
||||
... "version": "v201712.0123-alpha",
|
||||
... "year_y" : "2017",
|
||||
... "month" : "12",
|
||||
... "bid" : "0123",
|
||||
... "tag" : "alpha",
|
||||
... }
|
||||
>>>
|
||||
>>> version_info = pattern.regexp.match("201712.1234")
|
||||
>>> assert version_info is None
|
||||
|
||||
>>> version_info = pattern.regexp.match("v201713.1234")
|
||||
>>> assert version_info is None
|
||||
|
||||
>>> version_info = pattern.regexp.match("v201712.1234")
|
||||
>>> assert version_info.groupdict() == {
|
||||
... "version": "v201712.1234",
|
||||
... "year_y" : "2017",
|
||||
... "month" : "12",
|
||||
... "bid" : "1234",
|
||||
... "tag" : None,
|
||||
... }
|
||||
"""
|
||||
|
||||
import re
|
||||
import typing as typ
|
||||
|
||||
import pycalver.patterns as v1patterns
|
||||
|
||||
PATTERN_ESCAPES = [
|
||||
("\u005c", "\u005c\u005c"),
|
||||
("-" , "\u005c-"),
|
||||
("." , "\u005c."),
|
||||
("+" , "\u005c+"),
|
||||
("*" , "\u005c*"),
|
||||
("?" , "\u005c?"),
|
||||
("{" , "\u005c{"),
|
||||
("}" , "\u005c}"),
|
||||
# ("[" , "\u005c["), # [braces] are used for optional parts
|
||||
# ("]" , "\u005c]"),
|
||||
("(", "\u005c("),
|
||||
(")", "\u005c)"),
|
||||
]
|
||||
|
||||
# NOTE (mb 2020-09-17): For patterns with different options, the longer
|
||||
# patterns should be first/left (e.g. for 'MM', `1[0-2]` before `[1-9]`).
|
||||
# This ensures that the longest match is done rather than the shortest.
|
||||
# To have a consistent ordering, we always put the pattern that matches
|
||||
# the larger number first (even if the patterns would otherwise be the
|
||||
# same size).
|
||||
|
||||
PART_PATTERNS = {
|
||||
# Based on calver.org
|
||||
'YYYY': r"[1-9][0-9]{3}",
|
||||
'YY' : r"[1-9][0-9]?",
|
||||
'0Y' : r"[0-9]{2}",
|
||||
'GGGG': r"[1-9][0-9]{3}",
|
||||
'GG' : r"[1-9][0-9]?",
|
||||
'0G' : r"[0-9]{2}",
|
||||
'Q' : r"[1-4]",
|
||||
'MM' : r"(?:1[0-2]|[1-9])",
|
||||
'0M' : r"(?:1[0-2]|0[1-9])",
|
||||
'DD' : r"(?:3[0-1]|[1-2][0-9]|[1-9])",
|
||||
'0D' : r"(?:3[0-1]|[1-2][0-9]|0[1-9])",
|
||||
'JJJ' : r"(?:36[0-6]|3[0-5][0-9]|[1-2][0-9][0-9]|[1-9][0-9]|[1-9])",
|
||||
'00J' : r"(?:36[0-6]|3[0-5][0-9]|[1-2][0-9][0-9]|0[1-9][0-9]|00[1-9])",
|
||||
# week numbering parts
|
||||
'WW': r"(?:5[0-2]|[1-4][0-9]|[0-9])",
|
||||
'0W': r"(?:5[0-2]|[0-4][0-9])",
|
||||
'UU': r"(?:5[0-2]|[1-4][0-9]|[0-9])",
|
||||
'0U': r"(?:5[0-2]|[0-4][0-9])",
|
||||
'VV': r"(?:5[0-3]|[1-4][0-9]|[1-9])",
|
||||
'0V': r"(?:5[0-3]|[1-4][0-9]|0[1-9])",
|
||||
# non calver parts
|
||||
'MAJOR': r"[0-9]+",
|
||||
'MINOR': r"[0-9]+",
|
||||
'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)",
|
||||
'NUM' : r"[0-9]+",
|
||||
}
|
||||
|
||||
|
||||
PATTERN_PART_FIELDS = {
|
||||
'YYYY' : 'year_y',
|
||||
'YY' : 'year_y',
|
||||
'0Y' : 'year_y',
|
||||
'GGGG' : 'year_g',
|
||||
'GG' : 'year_g',
|
||||
'0G' : 'year_g',
|
||||
'Q' : 'quarter',
|
||||
'MM' : 'month',
|
||||
'0M' : 'month',
|
||||
'DD' : 'dom',
|
||||
'0D' : 'dom',
|
||||
'JJJ' : 'doy',
|
||||
'00J' : 'doy',
|
||||
'MAJOR': 'major',
|
||||
'MINOR': 'minor',
|
||||
'PATCH': 'patch',
|
||||
'BUILD': 'bid',
|
||||
'BLD' : 'bid',
|
||||
'TAG' : 'tag',
|
||||
'PYTAG': 'pytag',
|
||||
'NUM' : 'num',
|
||||
'WW' : 'week_w',
|
||||
'0W' : 'week_w',
|
||||
'UU' : 'week_u',
|
||||
'0U' : 'week_u',
|
||||
'VV' : 'week_v',
|
||||
'0V' : 'week_v',
|
||||
}
|
||||
|
||||
|
||||
FieldValue = typ.Union[str, int]
|
||||
|
||||
|
||||
def _fmt_num(val: FieldValue) -> str:
|
||||
return str(val)
|
||||
|
||||
|
||||
def _fmt_bld(val: FieldValue) -> str:
|
||||
return str(int(val))
|
||||
|
||||
|
||||
def _fmt_yy(year_y: FieldValue) -> str:
|
||||
return str(int(str(year_y)[-2:]))
|
||||
|
||||
|
||||
def _fmt_0y(year_y: FieldValue) -> str:
|
||||
return "{0:02}".format(int(str(year_y)[-2:]))
|
||||
|
||||
|
||||
def _fmt_gg(year_g: FieldValue) -> str:
|
||||
return str(int(str(year_g)[-2:]))
|
||||
|
||||
|
||||
def _fmt_0g(year_g: FieldValue) -> str:
|
||||
return "{0:02}".format(int(str(year_g)[-2:]))
|
||||
|
||||
|
||||
def _fmt_0m(month: FieldValue) -> str:
|
||||
return "{0:02}".format(int(month))
|
||||
|
||||
|
||||
def _fmt_0d(dom: FieldValue) -> str:
|
||||
return "{0:02}".format(int(dom))
|
||||
|
||||
|
||||
def _fmt_00j(doy: FieldValue) -> str:
|
||||
return "{0:03}".format(int(doy))
|
||||
|
||||
|
||||
def _fmt_0w(week_w: FieldValue) -> str:
|
||||
return "{0:02}".format(int(week_w))
|
||||
|
||||
|
||||
def _fmt_0u(week_u: FieldValue) -> str:
|
||||
return "{0:02}".format(int(week_u))
|
||||
|
||||
|
||||
def _fmt_0v(week_v: FieldValue) -> str:
|
||||
return "{0:02}".format(int(week_v))
|
||||
|
||||
|
||||
PART_FORMATS: typ.Dict[str, typ.Callable[[FieldValue], str]] = {
|
||||
'YYYY' : _fmt_num,
|
||||
'YY' : _fmt_yy,
|
||||
'0Y' : _fmt_0y,
|
||||
'GGGG' : _fmt_num,
|
||||
'GG' : _fmt_gg,
|
||||
'0G' : _fmt_0g,
|
||||
'Q' : _fmt_num,
|
||||
'MM' : _fmt_num,
|
||||
'0M' : _fmt_0m,
|
||||
'DD' : _fmt_num,
|
||||
'0D' : _fmt_0d,
|
||||
'JJJ' : _fmt_num,
|
||||
'00J' : _fmt_00j,
|
||||
'MAJOR': _fmt_num,
|
||||
'MINOR': _fmt_num,
|
||||
'PATCH': _fmt_num,
|
||||
'BUILD': _fmt_num,
|
||||
'BLD' : _fmt_bld,
|
||||
'TAG' : _fmt_num,
|
||||
'PYTAG': _fmt_num,
|
||||
'NUM' : _fmt_num,
|
||||
'WW' : _fmt_num,
|
||||
'0W' : _fmt_0w,
|
||||
'UU' : _fmt_num,
|
||||
'0U' : _fmt_0u,
|
||||
'VV' : _fmt_num,
|
||||
'0V' : _fmt_0v,
|
||||
}
|
||||
|
||||
|
||||
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("]", ")?")
|
||||
|
||||
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)
|
||||
|
||||
# NOTE (mb 2020-09-17): The sorting is done so that we process items:
|
||||
# - right before left
|
||||
# - longer before shorter
|
||||
last_start_idx = len(pattern) + 1
|
||||
result_pattern = pattern
|
||||
for _, (start_idx, end_idx, named_part_pattern) in sorted(part_patterns_by_index.items()):
|
||||
if end_idx <= last_start_idx:
|
||||
result_pattern = (
|
||||
result_pattern[:start_idx] + named_part_pattern + result_pattern[end_idx:]
|
||||
)
|
||||
last_start_idx = start_idx
|
||||
|
||||
return result_pattern
|
||||
|
||||
|
||||
def compile_pattern_str(pattern: str) -> str:
|
||||
for char, escaped in PATTERN_ESCAPES:
|
||||
pattern = pattern.replace(char, escaped)
|
||||
|
||||
return _replace_pattern_parts(pattern)
|
||||
|
||||
|
||||
def compile_pattern(pattern: str) -> v1patterns.Pattern:
|
||||
pattern_str = compile_pattern_str(pattern)
|
||||
pattern_re = re.compile(pattern_str)
|
||||
return v1patterns.Pattern(pattern, pattern_re)
|
||||
|
|
@ -1,178 +0,0 @@
|
|||
# This file is part of the pycalver project
|
||||
# https://github.com/mbarkhau/pycalver
|
||||
#
|
||||
# Copyright (c) 2018-2020 Manuel Barkhau (mbarkhau@gmail.com) - MIT License
|
||||
# SPDX-License-Identifier: MIT
|
||||
"""Rewrite files, updating occurences of version strings."""
|
||||
|
||||
import io
|
||||
import typing as typ
|
||||
import logging
|
||||
|
||||
import pycalver.rewrite as v1rewrite
|
||||
import pycalver2.version as v2version
|
||||
import pycalver2.patterns as v2patterns
|
||||
from pycalver import parse
|
||||
from pycalver import config
|
||||
|
||||
logger = logging.getLogger("pycalver2.rewrite")
|
||||
|
||||
|
||||
def rewrite_lines(
|
||||
pattern_strs: typ.List[str],
|
||||
new_vinfo : v2version.VersionInfo,
|
||||
old_lines : typ.List[str],
|
||||
) -> typ.List[str]:
|
||||
"""Replace occurances of pattern_strs in old_lines with new_vinfo.
|
||||
|
||||
>>> new_vinfo = v2version.parse_version_info("v201811.0123-beta")
|
||||
>>> pattern_strs = ['__version__ = "vYYYY0M.BUILD[-TAG]"']
|
||||
>>> old_lines = ['__version__ = "v201809.0002-alpha" ']
|
||||
>>> rewrite_lines(pattern_strs, new_vinfo, old_lines)
|
||||
['__version__ = "v201811.0123-beta" ']
|
||||
|
||||
>>> old_lines = ['__version__ = "v201809.0002-alpha" # comment']
|
||||
>>> rewrite_lines(pattern_strs, new_vinfo, old_lines)
|
||||
['__version__ = "v201811.0123-beta" # comment']
|
||||
|
||||
>>> pattern_strs = ['__version__ = "YYYY0M.BLD[PYTAGNUM]"']
|
||||
>>> old_lines = ['__version__ = "201809.2a0"']
|
||||
>>> rewrite_lines(pattern_strs, new_vinfo, old_lines)
|
||||
['__version__ = "201811.123b0"']
|
||||
"""
|
||||
new_lines = old_lines[:]
|
||||
found_patterns = set()
|
||||
|
||||
patterns = [v2patterns.compile_pattern(p) for p in pattern_strs]
|
||||
matches = parse.iter_matches(old_lines, patterns)
|
||||
for match in matches:
|
||||
found_patterns.add(match.pattern.raw)
|
||||
replacement = v2version.format_version(new_vinfo, match.pattern.raw)
|
||||
span_l, span_r = match.span
|
||||
new_line = match.line[:span_l] + replacement + match.line[span_r:]
|
||||
new_lines[match.lineno] = new_line
|
||||
|
||||
non_matched_patterns = set(pattern_strs) - found_patterns
|
||||
if non_matched_patterns:
|
||||
for non_matched_pattern in non_matched_patterns:
|
||||
logger.error(f"No match for pattern '{non_matched_pattern}'")
|
||||
compiled_pattern_str = v2patterns.compile_pattern_str(non_matched_pattern)
|
||||
logger.error(f"Pattern compiles to regex '{compiled_pattern_str}'")
|
||||
raise v1rewrite.NoPatternMatch("Invalid pattern(s)")
|
||||
else:
|
||||
return new_lines
|
||||
|
||||
|
||||
def rfd_from_content(
|
||||
pattern_strs: typ.List[str],
|
||||
new_vinfo : v2version.VersionInfo,
|
||||
content : str,
|
||||
) -> v1rewrite.RewrittenFileData:
|
||||
r"""Rewrite pattern occurrences with version string.
|
||||
|
||||
>>> new_vinfo = v2version.parse_version_info("v201809.0123")
|
||||
>>> pattern_strs = ['__version__ = "vYYYY0M.BUILD[-TAG]"']
|
||||
>>> content = '__version__ = "v201809.0001-alpha"'
|
||||
>>> rfd = rfd_from_content(pattern_strs, new_vinfo, content)
|
||||
>>> rfd.new_lines
|
||||
['__version__ = "v201809.0123"']
|
||||
>>>
|
||||
>>> new_vinfo = v2version.parse_version_info("v1.2.3", "vMAJOR.MINOR.PATCH")
|
||||
>>> pattern_strs = ['__version__ = "vMAJOR.MINOR.PATCH"']
|
||||
>>> content = '__version__ = "v1.2.2"'
|
||||
>>> rfd = rfd_from_content(pattern_strs, new_vinfo, content)
|
||||
>>> rfd.new_lines
|
||||
['__version__ = "v1.2.3"']
|
||||
"""
|
||||
line_sep = v1rewrite.detect_line_sep(content)
|
||||
old_lines = content.split(line_sep)
|
||||
new_lines = rewrite_lines(pattern_strs, new_vinfo, old_lines)
|
||||
return v1rewrite.RewrittenFileData("<path>", line_sep, old_lines, new_lines)
|
||||
|
||||
|
||||
def iter_rewritten(
|
||||
file_patterns: config.PatternsByGlob,
|
||||
new_vinfo : v2version.VersionInfo,
|
||||
) -> typ.Iterable[v1rewrite.RewrittenFileData]:
|
||||
r'''Iterate over files with version string replaced.
|
||||
|
||||
>>> file_patterns = {"src/pycalver/__init__.py": ['__version__ = "vYYYY0M.BUILD[-TAG]"']}
|
||||
>>> new_vinfo = v2version.parse_version_info("v201809.0123")
|
||||
>>> rewritten_datas = iter_rewritten(file_patterns, new_vinfo)
|
||||
>>> rfd = list(rewritten_datas)[0]
|
||||
>>> assert rfd.new_lines == [
|
||||
... '# This file is part of the pycalver project',
|
||||
... '# https://github.com/mbarkhau/pycalver',
|
||||
... '#',
|
||||
... '# Copyright (c) 2018-2020 Manuel Barkhau (mbarkhau@gmail.com) - MIT License',
|
||||
... '# SPDX-License-Identifier: MIT',
|
||||
... '"""PyCalVer: CalVer for Python Packages."""',
|
||||
... '',
|
||||
... '__version__ = "v201809.0123"',
|
||||
... '',
|
||||
... ]
|
||||
>>>
|
||||
'''
|
||||
|
||||
fobj: typ.IO[str]
|
||||
|
||||
for file_path, pattern_strs in v1rewrite.iter_file_paths(file_patterns):
|
||||
with file_path.open(mode="rt", encoding="utf-8") as fobj:
|
||||
content = fobj.read()
|
||||
|
||||
rfd = rfd_from_content(pattern_strs, new_vinfo, content)
|
||||
yield rfd._replace(path=str(file_path))
|
||||
|
||||
|
||||
def diff(
|
||||
new_vinfo : v2version.VersionInfo,
|
||||
file_patterns: config.PatternsByGlob,
|
||||
) -> str:
|
||||
r"""Generate diffs of rewritten files.
|
||||
|
||||
>>> new_vinfo = v2version.parse_version_info("v201809.0123")
|
||||
>>> file_patterns = {"src/pycalver/__init__.py": ['__version__ = "vYYYY0M.BUILD[-TAG]"']}
|
||||
>>> diff_str = diff(new_vinfo, file_patterns)
|
||||
>>> lines = diff_str.split("\n")
|
||||
>>> lines[:2]
|
||||
['--- src/pycalver/__init__.py', '+++ src/pycalver/__init__.py']
|
||||
>>> assert lines[6].startswith('-__version__ = "v2')
|
||||
>>> assert not lines[6].startswith('-__version__ = "v201809.0123"')
|
||||
>>> lines[7]
|
||||
'+__version__ = "v201809.0123"'
|
||||
"""
|
||||
|
||||
full_diff = ""
|
||||
fobj: typ.IO[str]
|
||||
|
||||
for file_path, pattern_strs in sorted(v1rewrite.iter_file_paths(file_patterns)):
|
||||
with file_path.open(mode="rt", encoding="utf-8") as fobj:
|
||||
content = fobj.read()
|
||||
|
||||
try:
|
||||
rfd = rfd_from_content(pattern_strs, new_vinfo, content)
|
||||
except v1rewrite.NoPatternMatch:
|
||||
# pylint:disable=raise-missing-from ; we support py2, so not an option
|
||||
errmsg = f"No patterns matched for '{file_path}'"
|
||||
raise v1rewrite.NoPatternMatch(errmsg)
|
||||
|
||||
rfd = rfd._replace(path=str(file_path))
|
||||
lines = v1rewrite.diff_lines(rfd)
|
||||
if len(lines) == 0:
|
||||
errmsg = f"No patterns matched for '{file_path}'"
|
||||
raise v1rewrite.NoPatternMatch(errmsg)
|
||||
|
||||
full_diff += "\n".join(lines) + "\n"
|
||||
|
||||
full_diff = full_diff.rstrip("\n")
|
||||
return full_diff
|
||||
|
||||
|
||||
def rewrite(file_patterns: config.PatternsByGlob, new_vinfo: v2version.VersionInfo) -> None:
|
||||
"""Rewrite project files, updating each with the new version."""
|
||||
fobj: typ.IO[str]
|
||||
|
||||
for file_data in iter_rewritten(file_patterns, new_vinfo):
|
||||
new_content = file_data.line_sep.join(file_data.new_lines)
|
||||
with io.open(file_data.path, mode="wt", encoding="utf-8") as fobj:
|
||||
fobj.write(new_content)
|
||||
|
|
@ -1,638 +0,0 @@
|
|||
# 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
|
||||
"""Functions related to version string manipulation."""
|
||||
|
||||
import typing as typ
|
||||
import logging
|
||||
import datetime as dt
|
||||
|
||||
import lexid
|
||||
|
||||
import pycalver2.patterns as v2patterns
|
||||
|
||||
# import pycalver.version as v1version
|
||||
# import pycalver.patterns as v1patterns
|
||||
|
||||
logger = logging.getLogger("pycalver.version")
|
||||
|
||||
|
||||
# The test suite may replace this.
|
||||
TODAY = dt.datetime.utcnow().date()
|
||||
|
||||
|
||||
ZERO_VALUES = {
|
||||
'MAJOR': "0",
|
||||
'MINOR': "0",
|
||||
'PATCH': "0",
|
||||
'TAG' : "final",
|
||||
'PYTAG': "",
|
||||
'NUM' : "0",
|
||||
}
|
||||
|
||||
|
||||
TAG_BY_PEP440_TAG = {
|
||||
'a' : 'alpha',
|
||||
'b' : 'beta',
|
||||
"" : 'final',
|
||||
'rc' : 'rc',
|
||||
'dev' : 'dev',
|
||||
'post': 'post',
|
||||
}
|
||||
|
||||
|
||||
PEP440_TAG_BY_TAG = {
|
||||
'alpha': "a",
|
||||
'beta' : "b",
|
||||
'final': "",
|
||||
'pre' : "rc",
|
||||
'rc' : "rc",
|
||||
'dev' : "dev",
|
||||
'post' : "post",
|
||||
}
|
||||
|
||||
assert set(TAG_BY_PEP440_TAG.keys()) == set(PEP440_TAG_BY_TAG.values())
|
||||
assert set(TAG_BY_PEP440_TAG.values()) < set(PEP440_TAG_BY_TAG.keys())
|
||||
|
||||
# PEP440_TAGS_REVERSE = {
|
||||
# "a" : 'alpha',
|
||||
# "b" : 'beta',
|
||||
# "rc" : 'rc',
|
||||
# "dev" : 'dev',
|
||||
# "post": 'post',
|
||||
# }
|
||||
|
||||
|
||||
MaybeInt = typ.Optional[int]
|
||||
|
||||
|
||||
class CalendarInfo(typ.NamedTuple):
|
||||
"""Container for calendar components of version strings."""
|
||||
|
||||
year_y : MaybeInt
|
||||
year_g : MaybeInt
|
||||
quarter: MaybeInt
|
||||
month : MaybeInt
|
||||
dom : MaybeInt
|
||||
doy : MaybeInt
|
||||
week_w : MaybeInt
|
||||
week_u : MaybeInt
|
||||
week_v : MaybeInt
|
||||
|
||||
|
||||
class VersionInfo(typ.NamedTuple):
|
||||
"""Container for parsed version string."""
|
||||
|
||||
year_y : MaybeInt
|
||||
year_g : MaybeInt
|
||||
quarter: MaybeInt
|
||||
month : MaybeInt
|
||||
dom : MaybeInt
|
||||
doy : MaybeInt
|
||||
week_w : MaybeInt
|
||||
week_u : MaybeInt
|
||||
week_v : MaybeInt
|
||||
major : int
|
||||
minor : int
|
||||
patch : int
|
||||
num : int
|
||||
bid : str
|
||||
tag : str
|
||||
pytag : str
|
||||
|
||||
|
||||
def _ver_to_cal_info(vinfo: VersionInfo) -> CalendarInfo:
|
||||
return CalendarInfo(
|
||||
vinfo.year_y,
|
||||
vinfo.year_g,
|
||||
vinfo.quarter,
|
||||
vinfo.month,
|
||||
vinfo.dom,
|
||||
vinfo.doy,
|
||||
vinfo.week_w,
|
||||
vinfo.week_u,
|
||||
vinfo.week_v,
|
||||
)
|
||||
|
||||
|
||||
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.
|
||||
|
||||
>>> import datetime as dt
|
||||
|
||||
>>> c = cal_info(dt.date(2019, 1, 5))
|
||||
>>> (c.year_y, c.quarter, c.month, c.dom, c.doy, c.week_w, c.week_u, c.week_v)
|
||||
(2019, 1, 1, 5, 5, 0, 0, 1)
|
||||
|
||||
>>> c = cal_info(dt.date(2019, 1, 6))
|
||||
>>> (c.year_y, c.quarter, c.month, c.dom, c.doy, c.week_w, c.week_u, c.week_v)
|
||||
(2019, 1, 1, 6, 6, 0, 1, 1)
|
||||
|
||||
>>> c = cal_info(dt.date(2019, 1, 7))
|
||||
>>> (c.year_y, c.quarter, c.month, c.dom, c.doy, c.week_w, c.week_u, c.week_v)
|
||||
(2019, 1, 1, 7, 7, 1, 1, 2)
|
||||
|
||||
>>> c = cal_info(dt.date(2019, 4, 7))
|
||||
>>> (c.year_y, c.quarter, c.month, c.dom, c.doy, c.week_w, c.week_u, c.week_v)
|
||||
(2019, 2, 4, 7, 97, 13, 14, 14)
|
||||
"""
|
||||
if date is None:
|
||||
date = TODAY
|
||||
|
||||
kwargs = {
|
||||
'year_y' : date.year,
|
||||
'year_g' : int(date.strftime("%G"), base=10),
|
||||
'quarter': _quarter_from_month(date.month),
|
||||
'month' : date.month,
|
||||
'dom' : date.day,
|
||||
'doy' : int(date.strftime("%j"), base=10),
|
||||
'week_w' : int(date.strftime("%W"), base=10),
|
||||
'week_u' : int(date.strftime("%U"), base=10),
|
||||
'week_v' : int(date.strftime("%V"), base=10),
|
||||
}
|
||||
|
||||
return CalendarInfo(**kwargs)
|
||||
|
||||
|
||||
VALID_FIELD_KEYS = set(VersionInfo._fields) | {'version'}
|
||||
|
||||
FieldKey = str
|
||||
MatchGroupKey = str
|
||||
MatchGroupStr = str
|
||||
|
||||
PatternGroups = typ.Dict[FieldKey, MatchGroupStr]
|
||||
FieldValues = typ.Dict[FieldKey, MatchGroupStr]
|
||||
|
||||
|
||||
def _parse_version_info(field_values: FieldValues) -> VersionInfo:
|
||||
"""Parse normalized VersionInfo from groups of a matched pattern.
|
||||
|
||||
>>> vnfo = _parse_version_info({'year_y': "2018", 'month': "11", 'bid': "0099"})
|
||||
>>> (vnfo.year_y, vnfo.month, vnfo.quarter, vnfo.bid, vnfo.tag)
|
||||
(2018, 11, 4, '0099', 'final')
|
||||
|
||||
>>> vnfo = _parse_version_info({'year_y': "2018", 'doy': "11", 'bid': "099", 'tag': "beta"})
|
||||
>>> (vnfo.year_y, vnfo.month, vnfo.dom, vnfo.doy, vnfo.bid, vnfo.tag)
|
||||
(2018, 1, 11, 11, '099', 'beta')
|
||||
|
||||
>>> vnfo = _parse_version_info({'year_y': "2018", 'month': "6", 'dom': "15"})
|
||||
>>> (vnfo.year_y, vnfo.month, vnfo.dom, vnfo.doy)
|
||||
(2018, 6, 15, 166)
|
||||
|
||||
>>> 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", 'minor': "023", 'patch': "0045"})
|
||||
>>> (vnfo.major, vnfo.minor, vnfo.patch)
|
||||
(1, 23, 45)
|
||||
|
||||
>>> vnfo = _parse_version_info({'year_y': "2021", 'week_w': "02"})
|
||||
>>> (vnfo.year_y, vnfo.week_w)
|
||||
(2021, 2)
|
||||
>>> vnfo = _parse_version_info({'year_y': "2021", 'week_u': "02"})
|
||||
>>> (vnfo.year_y, vnfo.week_u)
|
||||
(2021, 2)
|
||||
>>> vnfo = _parse_version_info({'year_g': "2021", 'week_v': "02"})
|
||||
>>> (vnfo.year_g, vnfo.week_v)
|
||||
(2021, 2)
|
||||
|
||||
>>> vnfo = _parse_version_info({'year_y': "2021", 'month': "01", 'dom': "03"})
|
||||
>>> (vnfo.year_y, vnfo.month, vnfo.dom, vnfo.tag)
|
||||
(2021, 1, 3, 'final')
|
||||
>>> (vnfo.year_y, vnfo.week_w, vnfo.year_y, vnfo.week_u,vnfo.year_g, vnfo.week_v)
|
||||
(2021, 0, 2021, 1, 2020, 53)
|
||||
"""
|
||||
for key in field_values:
|
||||
assert key in VALID_FIELD_KEYS, key
|
||||
|
||||
fvals = field_values
|
||||
tag = fvals.get('tag' ) or "final"
|
||||
pytag = fvals.get('pytag') or ""
|
||||
|
||||
if tag and not pytag:
|
||||
pytag = PEP440_TAG_BY_TAG[tag]
|
||||
elif pytag and not tag:
|
||||
tag = TAG_BY_PEP440_TAG[pytag]
|
||||
|
||||
date: typ.Optional[dt.date] = None
|
||||
|
||||
year_y: MaybeInt = int(fvals['year_y']) if 'year_y' in fvals else None
|
||||
year_g: MaybeInt = int(fvals['year_g']) if 'year_g' in fvals else None
|
||||
|
||||
month: MaybeInt = int(fvals['month']) if 'month' in fvals else None
|
||||
doy : MaybeInt = int(fvals['doy' ]) if 'doy' in fvals else None
|
||||
dom : MaybeInt = int(fvals['dom' ]) if 'dom' in fvals else None
|
||||
|
||||
week_w: MaybeInt = int(fvals['week_w']) if 'week_w' in fvals else None
|
||||
week_u: MaybeInt = int(fvals['week_u']) if 'week_u' in fvals else None
|
||||
week_v: MaybeInt = int(fvals['week_v']) if 'week_v' in fvals else None
|
||||
|
||||
if year_y and doy:
|
||||
date = _date_from_doy(year_y, doy)
|
||||
month = date.month
|
||||
dom = date.day
|
||||
else:
|
||||
month = int(fvals['month']) if 'month' in fvals else None
|
||||
dom = int(fvals['dom' ]) if 'dom' in fvals else None
|
||||
|
||||
if year_y and month and dom:
|
||||
date = dt.date(year_y, month, dom)
|
||||
|
||||
if date:
|
||||
# derive all fields from other previous values
|
||||
year_y = int(date.strftime("%Y"), base=10)
|
||||
year_g = int(date.strftime("%G"), base=10)
|
||||
month = int(date.strftime("%m"), base=10)
|
||||
dom = int(date.strftime("%d"), base=10)
|
||||
doy = int(date.strftime("%j"), base=10)
|
||||
week_w = int(date.strftime("%W"), base=10)
|
||||
week_u = int(date.strftime("%U"), base=10)
|
||||
week_v = int(date.strftime("%V"), base=10)
|
||||
|
||||
quarter = int(fvals['quarter']) if 'quarter' in fvals else None
|
||||
if quarter is None and month:
|
||||
quarter = _quarter_from_month(month)
|
||||
|
||||
# NOTE (mb 2020-09-18): If a part is optional, fvals[<field>] may be None
|
||||
major = int(fvals.get('major') or 0)
|
||||
minor = int(fvals.get('minor') or 0)
|
||||
patch = int(fvals.get('patch') or 0)
|
||||
num = int(fvals.get('num' ) or 0)
|
||||
bid = fvals['bid'] if 'bid' in fvals else "1000"
|
||||
|
||||
vnfo = VersionInfo(
|
||||
year_y=year_y,
|
||||
year_g=year_g,
|
||||
quarter=quarter,
|
||||
month=month,
|
||||
dom=dom,
|
||||
doy=doy,
|
||||
week_w=week_w,
|
||||
week_u=week_u,
|
||||
week_v=week_v,
|
||||
major=major,
|
||||
minor=minor,
|
||||
patch=patch,
|
||||
num=num,
|
||||
bid=bid,
|
||||
tag=tag,
|
||||
pytag=pytag,
|
||||
)
|
||||
return vnfo
|
||||
|
||||
|
||||
VersionInfoKW = typ.Dict[str, typ.Union[str, int, None]]
|
||||
|
||||
|
||||
class PatternError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def parse_version_info(version_str: str, pattern: str = "vYYYY0M.BUILD[-TAG[NUM]]") -> VersionInfo:
|
||||
"""Parse normalized VersionInfo.
|
||||
|
||||
>>> vnfo = parse_version_info("v201712.0033-beta0", pattern="vYYYY0M.BUILD[-TAG[NUM]]")
|
||||
>>> fvals = {'year_y': 2017, 'month': 12, 'bid': "0033", 'tag': "beta", 'num': 0}
|
||||
>>> assert vnfo == _parse_version_info(fvals)
|
||||
|
||||
>>> vnfo = parse_version_info("v201712.0033-beta", pattern="vYYYY0M.BUILD[-TAG[NUM]]")
|
||||
>>> fvals = {'year_y': 2017, 'month': 12, 'bid': "0033", 'tag': "beta"}
|
||||
>>> assert vnfo == _parse_version_info(fvals)
|
||||
|
||||
>>> vnfo = parse_version_info("v201712.0033", pattern="vYYYY0M.BUILD[-TAG[NUM]]")
|
||||
>>> fvals = {'year_y': 2017, 'month': 12, 'bid': "0033"}
|
||||
>>> assert vnfo == _parse_version_info(fvals)
|
||||
|
||||
>>> vnfo = parse_version_info("1.23.456", pattern="MAJOR.MINOR.PATCH")
|
||||
>>> fvals = {'major': "1", 'minor': "23", 'patch': "456"}
|
||||
>>> assert vnfo == _parse_version_info(fvals)
|
||||
"""
|
||||
pattern_tup = v2patterns.compile_pattern(pattern)
|
||||
match = pattern_tup.regexp.match(version_str)
|
||||
if match is None:
|
||||
err_msg = (
|
||||
f"Invalid version string '{version_str}' "
|
||||
f"for pattern '{pattern}'/'{pattern_tup.regexp.pattern}'"
|
||||
)
|
||||
raise PatternError(err_msg)
|
||||
else:
|
||||
field_values = match.groupdict()
|
||||
return _parse_version_info(field_values)
|
||||
|
||||
|
||||
def is_valid(version_str: str, pattern: str = "vYYYY0M.BUILD[-TAG]") -> bool:
|
||||
"""Check if a version matches a pattern.
|
||||
|
||||
>>> is_valid("v201712.0033-beta", pattern="vYYYY0M.BUILD[-TAG]")
|
||||
True
|
||||
>>> is_valid("v201712.0033-beta", pattern="MAJOR.MINOR.PATCH")
|
||||
False
|
||||
>>> is_valid("1.2.3", pattern="MAJOR.MINOR.PATCH")
|
||||
True
|
||||
>>> is_valid("v201712.0033-beta", pattern="MAJOR.MINOR.PATCH")
|
||||
False
|
||||
"""
|
||||
try:
|
||||
parse_version_info(version_str, pattern)
|
||||
return True
|
||||
except PatternError:
|
||||
return False
|
||||
|
||||
|
||||
TemplateKwargs = typ.Dict[str, typ.Union[str, int, None]]
|
||||
|
||||
|
||||
def _format_part_values(vinfo: VersionInfo) -> typ.Dict[str, str]:
|
||||
"""Generate kwargs for template from minimal VersionInfo.
|
||||
|
||||
The VersionInfo Tuple only has the minimal representation
|
||||
of a parsed version, not the values suitable for formatting.
|
||||
It may for example have month=9, but not the formatted
|
||||
representation '09' for '0M'.
|
||||
|
||||
>>> vinfo = parse_version_info("v200709.1033-beta", pattern="vYYYY0M.BUILD[-TAG]")
|
||||
>>> kwargs = _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['YYYY'], kwargs['0M'], kwargs['BUILD'], kwargs['PYTAG'], kwargs['NUM'])
|
||||
('2007', '09', '1033', 'b', '1')
|
||||
"""
|
||||
vnfo_kwargs: TemplateKwargs = vinfo._asdict()
|
||||
kwargs : typ.Dict[str, str] = {}
|
||||
|
||||
for part, field in v2patterns.PATTERN_PART_FIELDS.items():
|
||||
field_val = vnfo_kwargs[field]
|
||||
if field_val is not None:
|
||||
format_fn = v2patterns.PART_FORMATS[part]
|
||||
kwargs[part] = format_fn(field_val)
|
||||
|
||||
return kwargs
|
||||
|
||||
|
||||
def _make_segments(pattern: str) -> typ.List[str]:
|
||||
pattern_segs_l: typ.List[str] = []
|
||||
pattern_segs_r: typ.List[str] = []
|
||||
|
||||
pattern_rest = pattern
|
||||
while "[" in pattern_rest and "]" in pattern_rest:
|
||||
try:
|
||||
seg_l , pattern_rest = pattern_rest.split("[", 1)
|
||||
pattern_rest, seg_r = pattern_rest.rsplit("]", 1)
|
||||
except ValueError as val_err:
|
||||
if "values to unpack" in str(val_err):
|
||||
pat_err = PatternError(f"Unbalanced braces [] in '{pattern}'")
|
||||
pat_err.__cause__ = val_err
|
||||
raise pat_err
|
||||
else:
|
||||
raise
|
||||
|
||||
pattern_segs_l.append(seg_l)
|
||||
pattern_segs_r.append(seg_r)
|
||||
|
||||
pattern_segs_l.append(pattern_rest)
|
||||
return pattern_segs_l + list(reversed(pattern_segs_r))
|
||||
|
||||
|
||||
def _clear_zero_segments(
|
||||
formatted_segs: typ.List[str], is_zero_segment: typ.List[bool]
|
||||
) -> typ.List[str]:
|
||||
non_zero_segs = list(formatted_segs)
|
||||
|
||||
has_val_to_right = False
|
||||
for idx, is_zero in reversed(list(enumerate(is_zero_segment))):
|
||||
is_optional = 0 < idx < len(formatted_segs) - 1
|
||||
if is_optional:
|
||||
if is_zero and not has_val_to_right:
|
||||
non_zero_segs[idx] = ""
|
||||
else:
|
||||
has_val_to_right = True
|
||||
|
||||
return non_zero_segs
|
||||
|
||||
|
||||
def _format_segments(
|
||||
vinfo : VersionInfo,
|
||||
pattern_segs: typ.List[str],
|
||||
) -> typ.List[str]:
|
||||
kwargs = _format_part_values(vinfo)
|
||||
part_values = sorted(kwargs.items(), key=lambda item: -len(item[0]))
|
||||
|
||||
is_zero_segment = [True] * len(pattern_segs)
|
||||
|
||||
formatted_segs_l: typ.List[str] = []
|
||||
formatted_segs_r: typ.List[str] = []
|
||||
|
||||
idx_l = 0
|
||||
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
|
||||
|
||||
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) == ZERO_VALUES.get(part)):
|
||||
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) == ZERO_VALUES[part]):
|
||||
is_zero_segment[idx_r] = False
|
||||
|
||||
formatted_segs_l.append(seg_l)
|
||||
if idx_l < idx_r:
|
||||
formatted_segs_r.append(seg_r)
|
||||
|
||||
idx_l += 1
|
||||
idx_r -= 1
|
||||
|
||||
formatted_segs = formatted_segs_l + list(reversed(formatted_segs_r))
|
||||
return _clear_zero_segments(formatted_segs, is_zero_segment)
|
||||
|
||||
|
||||
def format_version(vinfo: VersionInfo, pattern: str) -> str:
|
||||
"""Generate version string.
|
||||
|
||||
>>> import datetime as dt
|
||||
>>> vinfo = parse_version_info("v200712.0033-beta", pattern="vYYYY0M.BUILD[-TAG[NUM]]")
|
||||
>>> vinfo_a = vinfo._replace(**cal_info(date=dt.date(2007, 1, 1))._asdict())
|
||||
>>> vinfo_b = vinfo._replace(**cal_info(date=dt.date(2007, 12, 31))._asdict())
|
||||
|
||||
>>> format_version(vinfo_a, pattern="vYY.BLD[-PYTAGNUM]")
|
||||
'v7.33-b0'
|
||||
|
||||
>>> format_version(vinfo_a, pattern="YYYY0M.BUILD[PYTAG[NUM]]")
|
||||
'200701.0033b'
|
||||
>>> format_version(vinfo_a, pattern="vYY.BLD[-PYTAGNUM]")
|
||||
'v7.33-b0'
|
||||
>>> format_version(vinfo_a, pattern="v0Y.BLD[-TAG]")
|
||||
'v07.33-beta'
|
||||
|
||||
>>> format_version(vinfo_a, pattern="vYYYY0M.BUILD[-TAG]")
|
||||
'v200701.0033-beta'
|
||||
>>> format_version(vinfo_b, pattern="vYYYY0M.BUILD[-TAG]")
|
||||
'v200712.0033-beta'
|
||||
|
||||
>>> format_version(vinfo_a, pattern="vYYYYw0W.BUILD[-TAG]")
|
||||
'v2007w01.0033-beta'
|
||||
>>> format_version(vinfo_a, pattern="vYYYYwWW.BLD[-TAG]")
|
||||
'v2007w1.33-beta'
|
||||
>>> format_version(vinfo_b, pattern="vYYYYw0W.BUILD[-TAG]")
|
||||
'v2007w53.0033-beta'
|
||||
|
||||
>>> format_version(vinfo_a, pattern="vYYYYd00J.BUILD[-TAG]")
|
||||
'v2007d001.0033-beta'
|
||||
>>> format_version(vinfo_a, pattern="vYYYYdJJJ.BUILD[-TAG]")
|
||||
'v2007d1.0033-beta'
|
||||
>>> format_version(vinfo_b, pattern="vYYYYd00J.BUILD[-TAG]")
|
||||
'v2007d365.0033-beta'
|
||||
|
||||
>>> format_version(vinfo_a, pattern="vGGGGwVV.BLD[PYTAGNUM]")
|
||||
'v2007w1.33b0'
|
||||
>>> format_version(vinfo_a, pattern="vGGGGw0V.BUILD[-TAG]")
|
||||
'v2007w01.0033-beta'
|
||||
>>> format_version(vinfo_b, pattern="vGGGGw0V.BUILD[-TAG]")
|
||||
'v2008w01.0033-beta'
|
||||
|
||||
>>> vinfo_c = vinfo_b._replace(major=1, minor=2, patch=34, tag='final')
|
||||
|
||||
>>> format_version(vinfo_c, pattern="vYYYYwWW.BUILD-TAG")
|
||||
'v2007w53.0033-final'
|
||||
>>> format_version(vinfo_c, pattern="vYYYYwWW.BUILD[-TAG]")
|
||||
'v2007w53.0033'
|
||||
|
||||
>>> format_version(vinfo_c, pattern="vMAJOR.MINOR.PATCH")
|
||||
'v1.2.34'
|
||||
|
||||
>>> vinfo_d = vinfo_b._replace(major=1, minor=0, patch=0, tag='final')
|
||||
>>> format_version(vinfo_d, pattern="vMAJOR.MINOR.PATCH-TAGNUM")
|
||||
'v1.0.0-final0'
|
||||
>>> format_version(vinfo_d, pattern="vMAJOR.MINOR.PATCH-TAG[NUM]")
|
||||
'v1.0.0-final'
|
||||
>>> format_version(vinfo_d, pattern="vMAJOR.MINOR.PATCH-TAG")
|
||||
'v1.0.0-final'
|
||||
>>> format_version(vinfo_d, pattern="vMAJOR.MINOR.PATCH[-TAG]")
|
||||
'v1.0.0'
|
||||
>>> format_version(vinfo_d, pattern="vMAJOR.MINOR[.PATCH[-TAG]]")
|
||||
'v1.0'
|
||||
>>> format_version(vinfo_d, pattern="vMAJOR[.MINOR[.PATCH[-TAG]]]")
|
||||
'v1'
|
||||
|
||||
>>> vinfo_d = vinfo_b._replace(major=1, minor=0, patch=1, tag='rc', num=0)
|
||||
>>> format_version(vinfo_d, pattern="vMAJOR[.MINOR[.PATCH]]")
|
||||
'v1.0.1'
|
||||
>>> format_version(vinfo_d, pattern="vMAJOR[.MINOR[.PATCH[-TAG[NUM]]]]")
|
||||
'v1.0.1-rc'
|
||||
>>> format_version(vinfo_d, pattern="vMAJOR[.MINOR[.PATCH[-TAGNUM]]]")
|
||||
'v1.0.1-rc0'
|
||||
>>> format_version(vinfo_d, pattern="vMAJOR[.MINOR[.PATCH]]")
|
||||
'v1.0.1'
|
||||
|
||||
>>> vinfo_d = vinfo_b._replace(major=1, minor=0, patch=0, tag='rc', num=2)
|
||||
>>> format_version(vinfo_d, pattern="vMAJOR[.MINOR[.PATCH[-TAG[NUM]]]]")
|
||||
'v1.0.0-rc2'
|
||||
|
||||
>>> vinfo_d = vinfo_b._replace(major=1, minor=0, patch=0, tag='rc', num=2)
|
||||
>>> format_version(vinfo_d, pattern='__version__ = "vMAJOR[.MINOR[.PATCH[-TAG[NUM]]]]"')
|
||||
'__version__ = "v1.0.0-rc2"'
|
||||
"""
|
||||
pattern_segs = _make_segments(pattern)
|
||||
formatted_segs = _format_segments(vinfo, pattern_segs)
|
||||
|
||||
return "".join(formatted_segs)
|
||||
|
||||
|
||||
def incr(
|
||||
old_version: str,
|
||||
pattern : str = "vYYYY0M.BUILD[-TAG]",
|
||||
*,
|
||||
release : str = None,
|
||||
major : bool = False,
|
||||
minor : bool = False,
|
||||
patch : bool = False,
|
||||
pin_date: bool = False,
|
||||
) -> typ.Optional[str]:
|
||||
"""Increment version string.
|
||||
|
||||
'old_version' is assumed to be a string that matches 'pattern'
|
||||
"""
|
||||
try:
|
||||
old_vinfo = parse_version_info(old_version, pattern)
|
||||
except PatternError as ex:
|
||||
logger.error(str(ex))
|
||||
return None
|
||||
|
||||
cur_vinfo = old_vinfo
|
||||
|
||||
cur_cal_nfo = _ver_to_cal_info(old_vinfo) if pin_date else cal_info()
|
||||
|
||||
old_date = (old_vinfo.year_y or 0 , old_vinfo.month or 0 , old_vinfo.dom or 0)
|
||||
cur_date = (cur_cal_nfo.year_y or 0, cur_cal_nfo.month or 0, cur_cal_nfo.dom or 0)
|
||||
|
||||
if old_date <= cur_date:
|
||||
cur_vinfo = cur_vinfo._replace(**cur_cal_nfo._asdict())
|
||||
else:
|
||||
logger.warning(f"Version appears to be from the future '{old_version}'")
|
||||
|
||||
_bid = cur_vinfo.bid
|
||||
if int(_bid) < 1000:
|
||||
# prevent truncation of leading zeros
|
||||
_bid = str(int(_bid) + 1000)
|
||||
|
||||
cur_vinfo = cur_vinfo._replace(bid=lexid.incr(_bid))
|
||||
|
||||
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)
|
||||
|
||||
if release:
|
||||
cur_vinfo = cur_vinfo._replace(tag=release)
|
||||
|
||||
new_version = format_version(cur_vinfo, pattern)
|
||||
if new_version == old_version:
|
||||
logger.error("Invalid arguments or pattern, version did not change.")
|
||||
return None
|
||||
else:
|
||||
return new_version
|
||||
Loading…
Add table
Add a link
Reference in a new issue