mirror of
https://github.com/TECHNOFAB11/bumpver.git
synced 2025-12-12 14:30:09 +01:00
WIP: refactoring and testing
This commit is contained in:
parent
8189075385
commit
5108837f45
11 changed files with 203 additions and 151 deletions
10
README.rst
10
README.rst
|
|
@ -52,8 +52,8 @@ which is compatible with python packaging software
|
|||
|
||||
|
||||
The PyCalVer package provides the ``pycalver`` command and
|
||||
module to generate version strings which follow the format:
|
||||
``v{calendar_version}.{build_number}[-{release_tag}]``
|
||||
module to generate version strings which follow the following
|
||||
format: ``v{calendar_version}.{build_number}[-{release_tag}]``
|
||||
|
||||
Some examples:
|
||||
|
||||
|
|
@ -69,9 +69,9 @@ Some examples:
|
|||
v202207.18134
|
||||
|
||||
|
||||
The ``pycalver bump`` command will parse specified/configured
|
||||
files for such strings and rewrite them with an updated version
|
||||
string.
|
||||
The ``pycalver bump`` command will parse the files you configure
|
||||
in ``setup.cfg`` for such strings and rewrite them with an
|
||||
updated version string.
|
||||
|
||||
The format accepted by PyCalVer can be parsed with this regular
|
||||
expression:
|
||||
|
|
|
|||
|
|
@ -10,8 +10,6 @@ import os
|
|||
import sys
|
||||
import click
|
||||
import logging
|
||||
import difflib
|
||||
import typing as typ
|
||||
|
||||
from . import DEBUG
|
||||
from . import vcs
|
||||
|
|
@ -56,13 +54,13 @@ def cli():
|
|||
def show() -> None:
|
||||
_init_loggers(verbose=False)
|
||||
|
||||
cfg = config.parse()
|
||||
cfg: config.MaybeConfig = config.parse()
|
||||
if cfg is None:
|
||||
log.error("Could not parse configuration from setup.cfg")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"Current Version: {cfg['current_version']}")
|
||||
print(f"PEP440 Version: {cfg['pep440_version']}")
|
||||
print(f"Current Version: {cfg.current_version}")
|
||||
print(f"PEP440 Version: {cfg.pep440_version}")
|
||||
|
||||
|
||||
@cli.command()
|
||||
|
|
@ -99,7 +97,7 @@ def init(dry: bool) -> None:
|
|||
"""Initialize [pycalver] configuration in setup.cfg"""
|
||||
_init_loggers(verbose=False)
|
||||
|
||||
cfg = config.parse()
|
||||
cfg: config.MaybeConfig = config.parse()
|
||||
if cfg:
|
||||
log.error("Configuration already initialized in setup.cfg")
|
||||
sys.exit(1)
|
||||
|
|
@ -179,16 +177,14 @@ def bump(
|
|||
log.error(f"Valid arguments are: {', '.join(parse.VALID_RELESE_VALUES)}")
|
||||
sys.exit(1)
|
||||
|
||||
cfg = config.parse()
|
||||
cfg: config.MaybeConfig = config.parse()
|
||||
|
||||
if cfg is None:
|
||||
log.error("Could not parse configuration from setup.cfg")
|
||||
sys.exit(1)
|
||||
|
||||
old_version = cfg["current_version"]
|
||||
old_version = cfg.current_version
|
||||
new_version = version.bump(old_version, release=release)
|
||||
new_version_nfo = parse.parse_version_info(new_version)
|
||||
new_version_fmt_kwargs = new_version_nfo._asdict()
|
||||
|
||||
log.info(f"Old Version: {old_version}")
|
||||
log.info(f"New Version: {new_version}")
|
||||
|
|
@ -196,53 +192,12 @@ def bump(
|
|||
if dry:
|
||||
log.info("Running with '--dry', showing diffs instead of updating files.")
|
||||
|
||||
file_patterns = cfg.file_patterns
|
||||
filepaths = set(file_patterns.keys())
|
||||
|
||||
_vcs = vcs.get_vcs()
|
||||
dirty_files = _vcs.dirty_files()
|
||||
|
||||
if dirty_files:
|
||||
log.warn(f"{_vcs.__name__} working directory is not clean:")
|
||||
for file in dirty_files:
|
||||
log.warn(" " + file)
|
||||
|
||||
if not allow_dirty and dirty_files:
|
||||
sys.exit(1)
|
||||
|
||||
pattern_files = cfg["file_patterns"].keys()
|
||||
dirty_pattern_files = set(dirty_files) & set(pattern_files)
|
||||
if dirty_pattern_files:
|
||||
log.error("Not commiting when pattern files are dirty:")
|
||||
for file in dirty_pattern_files:
|
||||
log.warn(" " + file)
|
||||
sys.exit(1)
|
||||
|
||||
matches: typ.List[parse.PatternMatch]
|
||||
for filepath, patterns in cfg["file_patterns"].items():
|
||||
with io.open(filepath, mode="rt", encoding="utf-8") as fh:
|
||||
content = fh.read()
|
||||
|
||||
old_lines = content.splitlines()
|
||||
new_lines = old_lines.copy()
|
||||
|
||||
matches = parse.parse_patterns(old_lines, patterns)
|
||||
for m in matches:
|
||||
replacement = m.pattern.format(**new_version_fmt_kwargs)
|
||||
span_l, span_r = m.span
|
||||
new_line = m.line[:span_l] + replacement + m.line[span_r:]
|
||||
new_lines[m.lineno] = new_line
|
||||
|
||||
if dry or verbose:
|
||||
print("\n".join(difflib.unified_diff(
|
||||
old_lines,
|
||||
new_lines,
|
||||
lineterm="",
|
||||
fromfile="a/" + filepath,
|
||||
tofile="b/" + filepath,
|
||||
)))
|
||||
|
||||
if not dry:
|
||||
new_content = "\n".join(new_lines)
|
||||
with io.open(filepath, mode="wt", encoding="utf-8") as fh:
|
||||
fh.write(new_content)
|
||||
_vcs.assert_not_dirty(filepaths, allow_dirty)
|
||||
rewrite.rewrite(new_version, file_patterns, dry, verbose)
|
||||
|
||||
if dry:
|
||||
return
|
||||
|
|
|
|||
|
|
@ -18,38 +18,48 @@ from .parse import PYCALVER_RE
|
|||
log = logging.getLogger("pycalver.config")
|
||||
|
||||
|
||||
def parse(config_file="setup.cfg") -> typ.Optional[typ.Dict[str, typ.Any]]:
|
||||
if not os.path.exists(config_file):
|
||||
log.error("File not found: setup.cfg")
|
||||
return None
|
||||
class Config(typ.NamedTuple):
|
||||
|
||||
current_version : str
|
||||
pep440_version : str
|
||||
|
||||
tag : bool
|
||||
commit : bool
|
||||
|
||||
file_patterns : typ.Dict[str, typ.List[str]]
|
||||
|
||||
|
||||
MaybeConfig = typ.Optional[Config]
|
||||
|
||||
|
||||
def _parse_buffer(cfg_buffer: io.StringIO) -> MaybeConfig:
|
||||
cfg_parser = configparser.RawConfigParser("")
|
||||
with io.open(config_file, mode="rt", encoding="utf-8") as fh:
|
||||
cfg_parser.readfp(fh)
|
||||
cfg_parser.readfp(cfg_buffer)
|
||||
|
||||
if "pycalver" not in cfg_parser:
|
||||
log.error("setup.cfg does not contain a [pycalver] section.")
|
||||
return None
|
||||
|
||||
cfg = dict(cfg_parser.items("pycalver"))
|
||||
base_cfg = dict(cfg_parser.items("pycalver"))
|
||||
|
||||
if "current_version" not in cfg:
|
||||
if "current_version" not in base_cfg:
|
||||
log.error("setup.cfg does not have 'pycalver.current_version'")
|
||||
return None
|
||||
|
||||
current_version = cfg["current_version"]
|
||||
current_version = base_cfg["current_version"]
|
||||
if PYCALVER_RE.match(current_version) is None:
|
||||
log.error(f"setup.cfg 'pycalver.current_version is invalid")
|
||||
log.error(f"current_version = {current_version}")
|
||||
return None
|
||||
|
||||
cfg["pep440_version"] = str(pkg_resources.parse_version(current_version))
|
||||
pep440_version = str(pkg_resources.parse_version(current_version))
|
||||
|
||||
cfg["tag"] = cfg.get("tag", "").lower() in ("yes", "true", "1", "on")
|
||||
cfg["commit"] = cfg.get("commit", "").lower() in ("yes", "true", "1", "on")
|
||||
tag = base_cfg.get("tag", "").lower() in ("yes", "true", "1", "on")
|
||||
commit = base_cfg.get("commit", "").lower() in ("yes", "true", "1", "on")
|
||||
|
||||
cfg["file_patterns"] = {}
|
||||
file_patterns: typ.Dict[str, typ.List[str]] = {}
|
||||
|
||||
section_name: str
|
||||
for section_name in cfg_parser.sections():
|
||||
if not section_name.startswith("pycalver:file:"):
|
||||
continue
|
||||
|
|
@ -59,25 +69,39 @@ def parse(config_file="setup.cfg") -> typ.Optional[typ.Dict[str, typ.Any]]:
|
|||
log.error(f"No such file: {filepath} from {section_name} in setup.cfg")
|
||||
return None
|
||||
|
||||
section = dict(cfg_parser.items(section_name))
|
||||
section: typ.Dict[str, str] = dict(cfg_parser.items(section_name))
|
||||
patterns = section.get("patterns")
|
||||
|
||||
if "patterns" in section:
|
||||
cfg["file_patterns"][filepath] = [
|
||||
if patterns is None:
|
||||
file_patterns[filepath] = ["{version}", "{pep440_version}"]
|
||||
else:
|
||||
file_patterns[filepath] = [
|
||||
line.strip()
|
||||
for line in section["patterns"].splitlines()
|
||||
for line in patterns.splitlines()
|
||||
if line.strip()
|
||||
]
|
||||
else:
|
||||
cfg["file_patterns"][filepath] = ["{version}", "{pep440_version}"]
|
||||
|
||||
if not cfg["file_patterns"]:
|
||||
cfg["file_patterns"]["setup.cfg"] = ["{version}", "{pep440_version}"]
|
||||
if not file_patterns:
|
||||
file_patterns["setup.cfg"] = ["{version}", "{pep440_version}"]
|
||||
|
||||
cfg = Config(current_version, pep440_version, tag, commit, file_patterns)
|
||||
log.debug(f"Config Parsed: {cfg}")
|
||||
|
||||
return cfg
|
||||
|
||||
|
||||
def parse(config_file="setup.cfg") -> MaybeConfig:
|
||||
if not os.path.exists(config_file):
|
||||
log.error("File not found: setup.cfg")
|
||||
return None
|
||||
|
||||
cfg_buffer = io.StringIO()
|
||||
with io.open(config_file, mode="rt", encoding="utf-8") as fh:
|
||||
cfg_buffer.write(fh.read())
|
||||
|
||||
cfg_buffer.seek(0)
|
||||
return _parse_buffer(cfg_buffer)
|
||||
|
||||
|
||||
def default_config_lines() -> typ.List[str]:
|
||||
initial_version = dt.datetime.now().strftime("v%Y%m.0001-dev")
|
||||
|
||||
|
|
|
|||
|
|
@ -85,6 +85,9 @@ MINIMUM_ID = "0"
|
|||
|
||||
def next_id(prev_id: str) -> str:
|
||||
num_digits = len(prev_id)
|
||||
if prev_id.count("9") == num_digits:
|
||||
raise OverflowError("max lexical version reached: " + prev_id)
|
||||
|
||||
_prev_id = int(prev_id, 10)
|
||||
_next_id = int(_prev_id) + 1
|
||||
next_id = f"{_next_id:0{num_digits}}"
|
||||
|
|
|
|||
|
|
@ -5,17 +5,52 @@
|
|||
# (C) 2018 Manuel Barkhau (@mbarkhau)
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import logging
|
||||
import io
|
||||
import difflib
|
||||
import logging
|
||||
import typing as typ
|
||||
|
||||
from . import parse
|
||||
|
||||
log = logging.getLogger("pycalver.rewrite")
|
||||
|
||||
|
||||
def rewrite_file(file: str, pattern: str, dry=False) -> None:
|
||||
difflib.unified_diff(
|
||||
file_content_before.splitlines(),
|
||||
file_content_after.splitlines(),
|
||||
lineterm="",
|
||||
fromfile="a/" + file,
|
||||
tofile="b/" + file,
|
||||
)
|
||||
def rewrite(
|
||||
new_version: str,
|
||||
file_patterns: typ.Dict[str, str],
|
||||
dry=False,
|
||||
verbose=False,
|
||||
) -> None:
|
||||
new_version_nfo = parse.parse_version_info(new_version)
|
||||
new_version_fmt_kwargs = new_version_nfo._asdict()
|
||||
|
||||
matches: typ.List[parse.PatternMatch]
|
||||
for filepath, patterns in file_patterns.items():
|
||||
with io.open(filepath, mode="rt", encoding="utf-8") as fh:
|
||||
content = fh.read()
|
||||
|
||||
old_lines = content.splitlines()
|
||||
new_lines = old_lines.copy()
|
||||
|
||||
matches = parse.parse_patterns(old_lines, patterns)
|
||||
for m in matches:
|
||||
replacement = m.pattern.format(**new_version_fmt_kwargs)
|
||||
span_l, span_r = m.span
|
||||
new_line = m.line[:span_l] + replacement + m.line[span_r:]
|
||||
new_lines[m.lineno] = new_line
|
||||
|
||||
if dry or verbose:
|
||||
print("\n".join(difflib.unified_diff(
|
||||
old_lines,
|
||||
new_lines,
|
||||
lineterm="",
|
||||
fromfile="a/" + filepath,
|
||||
tofile="b/" + filepath,
|
||||
)))
|
||||
|
||||
if dry:
|
||||
continue
|
||||
|
||||
new_content = "\n".join(new_lines)
|
||||
with io.open(filepath, mode="wt", encoding="utf-8") as fh:
|
||||
fh.write(new_content)
|
||||
|
|
|
|||
|
|
@ -9,8 +9,10 @@
|
|||
# MIT License - (C) 2013-2014 Filip Noetzel
|
||||
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import tempfile
|
||||
import typing as typ
|
||||
import subprocess as sp
|
||||
|
||||
|
||||
|
|
@ -52,6 +54,25 @@ class BaseVCS:
|
|||
if not line.strip().startswith(b"??")
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def assert_not_dirty(cls, filepaths: typ.Set[str], allow_dirty=False) -> None:
|
||||
dirty_files = cls.dirty_files()
|
||||
|
||||
if dirty_files:
|
||||
log.warn(f"{cls.__name__} working directory is not clean:")
|
||||
for file in dirty_files:
|
||||
log.warn(" " + file)
|
||||
|
||||
if not allow_dirty and dirty_files:
|
||||
sys.exit(1)
|
||||
|
||||
dirty_pattern_files = set(dirty_files) & filepaths
|
||||
if dirty_pattern_files:
|
||||
log.error("Not commiting when pattern files are dirty:")
|
||||
for file in dirty_pattern_files:
|
||||
log.warn(" " + file)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
class Git(BaseVCS):
|
||||
|
||||
|
|
|
|||
|
|
@ -14,15 +14,25 @@ log = logging.getLogger("pycalver.version")
|
|||
|
||||
|
||||
def current_calver() -> str:
|
||||
return dt.datetime.utcnow().strftime("v%Y%m")
|
||||
return dt.date.today().strftime("v%Y%m")
|
||||
|
||||
|
||||
def bump(old_version: str, release: str=None) -> str:
|
||||
def bump(old_version: str, *, release: str=None) -> str:
|
||||
# old_version is assumed to be a valid calver string,
|
||||
# validated in pycalver.config.parse.
|
||||
old_ver = parse.parse_version_info(old_version)
|
||||
|
||||
new_calver = current_calver()
|
||||
|
||||
if old_ver.calver > new_calver:
|
||||
log.warning(
|
||||
f"'version.bump' called with '{old_version}', " +
|
||||
f"which is from the future, " +
|
||||
f"maybe your system clock is out of sync."
|
||||
)
|
||||
# leave calver as is (don't go back in time)
|
||||
new_calver = old_ver.calver
|
||||
|
||||
new_build = lex_id.next_id(old_ver.build[1:])
|
||||
if release is None:
|
||||
if old_ver.release:
|
||||
|
|
@ -30,6 +40,8 @@ def bump(old_version: str, release: str=None) -> str:
|
|||
new_release = old_ver.release[1:]
|
||||
else:
|
||||
new_release = None
|
||||
elif release == "final":
|
||||
new_release = None
|
||||
else:
|
||||
new_release = release
|
||||
|
||||
|
|
@ -37,38 +49,3 @@ def bump(old_version: str, release: str=None) -> str:
|
|||
if new_release:
|
||||
new_version += "-" + new_release
|
||||
return new_version
|
||||
|
||||
|
||||
def incr_version(old_version: str, *, tag: str="__sentinel__") -> str:
|
||||
maybe_match: MaybeMatch = VERSION_RE.search(old_version)
|
||||
|
||||
if maybe_match is None:
|
||||
raise ValueError(f"Invalid version string: {old_version}")
|
||||
|
||||
prev_version_info: PyCalVerInfo = maybe_match.groupdict()
|
||||
|
||||
prev_calver: str = prev_version_info["calver"]
|
||||
next_calver: str = current_calver()
|
||||
|
||||
prev_build: str = prev_version_info["build"]
|
||||
if prev_calver > next_calver:
|
||||
log.warning(
|
||||
f"'incr_version' called with '{old_version}', " +
|
||||
f"which is from the future, " +
|
||||
f"maybe your system clock is out of sync."
|
||||
)
|
||||
next_calver = prev_calver # leave calver as is
|
||||
|
||||
next_build = lex_id.next_id(prev_build)
|
||||
new_version = f"{next_calver}.{next_build}"
|
||||
if tag != "__sentinel__":
|
||||
if tag is None:
|
||||
pass # tag explicitly ignored/removed
|
||||
else:
|
||||
new_version += "-" + tag
|
||||
elif "tag" in prev_version_info:
|
||||
# preserve previous tag
|
||||
new_version += "-" + prev_version_info["tag"]
|
||||
|
||||
assert old_version < new_version, f"{old_version} {new_version}"
|
||||
return new_version
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
from pycalver import config
|
||||
|
||||
|
||||
def test_parse():
|
||||
pass
|
||||
|
|
@ -7,11 +7,23 @@ def test_next_id_basic():
|
|||
assert lex_id.next_id("09") == "110"
|
||||
|
||||
|
||||
def test_next_id_overflow():
|
||||
try:
|
||||
prev_id = "9999"
|
||||
next_id = lex_id.next_id(prev_id)
|
||||
assert False, (prev_id, "->", next_id)
|
||||
except OverflowError:
|
||||
pass
|
||||
|
||||
|
||||
def test_next_id_random():
|
||||
for i in range(1000):
|
||||
prev_id = str(random.randint(1, 100000))
|
||||
next_id = lex_id.next_id(prev_id)
|
||||
assert prev_id < next_id
|
||||
try:
|
||||
next_id = lex_id.next_id(prev_id)
|
||||
assert prev_id < next_id
|
||||
except OverflowError:
|
||||
assert len(prev_id) == prev_id.count("9")
|
||||
|
||||
|
||||
def test_ord_val():
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import random
|
||||
import datetime as dt
|
||||
|
||||
from pycalver import version
|
||||
|
||||
|
||||
|
|
@ -9,29 +11,47 @@ def test_current_calver():
|
|||
assert v[1:].isdigit()
|
||||
|
||||
|
||||
# def test_calver():
|
||||
# import random
|
||||
def test_bump_beta():
|
||||
calver = version.current_calver()
|
||||
cur_version = calver + ".0001-beta"
|
||||
assert cur_version < version.bump(cur_version)
|
||||
assert version.bump(cur_version).endswith("-beta")
|
||||
assert version.bump(cur_version, release="alpha").endswith("-alpha")
|
||||
assert version.bump(cur_version, release="final").endswith("0002")
|
||||
|
||||
# first_version_str = "v201808.0001-dev"
|
||||
# padding = len(first_version_str) + 3
|
||||
# version_str = first_version_str
|
||||
|
||||
# def _current_calver() -> str:
|
||||
# _current_calver.delta += dt.timedelta(days=int(random.random() * 5))
|
||||
def test_bump_final():
|
||||
calver = version.current_calver()
|
||||
cur_version = calver + ".0001"
|
||||
assert cur_version < version.bump(cur_version)
|
||||
assert version.bump(cur_version, release="alpha").endswith("-alpha")
|
||||
assert version.bump(cur_version, release="final").endswith("0002")
|
||||
assert version.bump(cur_version).endswith("0002")
|
||||
|
||||
# return (dt.datetime.utcnow() + _current_calver.delta).strftime("v%Y%m")
|
||||
|
||||
# _current_calver.delta = dt.timedelta(days=1)
|
||||
def test_bump_future():
|
||||
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.bump(cur_version)
|
||||
|
||||
# global current_calver
|
||||
# current_calver = _current_calver
|
||||
|
||||
# for i in range(1050):
|
||||
# version_str = incr_version(version_str, tag=random.choice([
|
||||
# None, "alpha", "beta", "rc"
|
||||
# ]))
|
||||
# print(f"{version_str:<{padding}}", end=" ")
|
||||
# if (i + 1) % 8 == 0:
|
||||
# print()
|
||||
def test_bump_random():
|
||||
cur_date = dt.date.today()
|
||||
cur_version = cur_date.strftime("v%Y%m") + ".0001-dev"
|
||||
|
||||
# print()
|
||||
def _mock_current_calver():
|
||||
return cur_date.strftime("v%Y%m")
|
||||
|
||||
_orig_current_calver = version.current_calver
|
||||
version.current_calver = _mock_current_calver
|
||||
try:
|
||||
for i in range(1000):
|
||||
cur_date += dt.timedelta(days=int((1 + random.random()) ** 10))
|
||||
new_version = version.bump(cur_version, release=random.choice([
|
||||
None, "alpha", "beta", "rc", "final"
|
||||
]))
|
||||
assert cur_version < new_version
|
||||
cur_version = new_version
|
||||
finally:
|
||||
version.current_calver = _orig_current_calver
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue