mirror of
https://github.com/TECHNOFAB11/bumpver.git
synced 2025-12-12 22:40:09 +01:00
WIP: mostly scratch code still
This commit is contained in:
parent
e2e218bce9
commit
3471560eaa
20 changed files with 1757 additions and 1 deletions
11
src/pycalver/__init__.py
Normal file
11
src/pycalver/__init__.py
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# This file is part of the pycalver project
|
||||
# https://github.com/mbarkhau/pycalver
|
||||
#
|
||||
# (C) 2018 Manuel Barkhau (@mbarkhau)
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import os
|
||||
|
||||
__version__ = "v201809.0001-beta"
|
||||
|
||||
DEBUG = os.environ.get("PYCALVER_DEBUG", "0") == "1"
|
||||
146
src/pycalver/__main__.py
Normal file
146
src/pycalver/__main__.py
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
#!/usr/bin/env python
|
||||
# This file is part of the pycalver project
|
||||
# https://github.com/mbarkhau/pycalver
|
||||
#
|
||||
# (C) 2018 Manuel Barkhau (@mbarkhau)
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
import click
|
||||
import logging
|
||||
import typing as typ
|
||||
|
||||
from . import DEBUG
|
||||
from . import vcs
|
||||
from . import parse
|
||||
from . import config
|
||||
from . import version
|
||||
from . import rewrite
|
||||
|
||||
|
||||
log = logging.getLogger("pycalver.__main__")
|
||||
|
||||
|
||||
def _init_loggers(verbose: bool) -> None:
|
||||
if DEBUG:
|
||||
log_formatter = logging.Formatter('%(levelname)s - %(name)s - %(message)s')
|
||||
log_level = logging.DEBUG
|
||||
elif verbose:
|
||||
log_formatter = logging.Formatter('%(levelname)s - %(message)s')
|
||||
log_level = logging.INFO
|
||||
else:
|
||||
log_formatter = logging.Formatter('%(message)s')
|
||||
log_level = logging.WARNING
|
||||
|
||||
loggers = [log, vcs.log, parse.log, config.log, rewrite.log, version.log]
|
||||
|
||||
for logger in loggers:
|
||||
if len(logger.handlers) == 0:
|
||||
ch = logging.StreamHandler(sys.stderr)
|
||||
ch.setFormatter(log_formatter)
|
||||
logger.addHandler(ch)
|
||||
logger.setLevel(log_level)
|
||||
|
||||
log.debug("Loggers initialized.")
|
||||
|
||||
|
||||
@click.group()
|
||||
def cli():
|
||||
"""parse and update project versions automatically."""
|
||||
|
||||
|
||||
@cli.command()
|
||||
def show() -> None:
|
||||
_init_loggers(verbose=False)
|
||||
|
||||
cfg = config.parse()
|
||||
if cfg is None:
|
||||
return
|
||||
|
||||
print(f"Current Version: {cfg['current_version']}")
|
||||
print(f"PEP440 Version: {cfg['pep440_version']}")
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option(
|
||||
"--dry",
|
||||
default=False,
|
||||
is_flag=True,
|
||||
help="Display diff of changes, don't rewrite files.",
|
||||
)
|
||||
def init(dry: bool) -> None:
|
||||
"""Initialize [pycalver] configuration in setup.cfg"""
|
||||
_init_loggers(verbose=False)
|
||||
|
||||
cfg = config.parse()
|
||||
if cfg:
|
||||
log.error("Configuration already initialized in setup.cfg")
|
||||
return
|
||||
|
||||
cfg_lines = config.default_config_lines()
|
||||
|
||||
if dry:
|
||||
print("Exiting because of '--dry'. Would have written to setup.cfg:")
|
||||
print("\n " + "\n ".join(cfg_lines))
|
||||
return
|
||||
|
||||
if os.path.exists("setup.cfg"):
|
||||
cfg_content = "\n" + "\n".join(cfg_lines)
|
||||
with io.open("setup.cfg", mode="at", encoding="utf-8") as fh:
|
||||
fh.write(cfg_content)
|
||||
print("Updated setup.cfg")
|
||||
else:
|
||||
cfg_content = "\n".join(cfg_lines)
|
||||
with io.open("setup.cfg", mode="at", encoding="utf-8") as fh:
|
||||
fh.write(cfg_content)
|
||||
print("Created setup.cfg")
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option(
|
||||
"--verbose",
|
||||
default=False,
|
||||
is_flag=True,
|
||||
help="Log applied changes to stderr",
|
||||
)
|
||||
@click.option(
|
||||
"--dry",
|
||||
default=False,
|
||||
is_flag=True,
|
||||
help="Display diff of changes, don't rewrite files.",
|
||||
)
|
||||
@click.option(
|
||||
"--release",
|
||||
default=None,
|
||||
metavar="<name>",
|
||||
help="Override release name of current_version",
|
||||
)
|
||||
def bump(verbose: bool, dry: bool, release: typ.Optional[str] = None) -> None:
|
||||
_init_loggers(verbose)
|
||||
if release and release not in parse.VALID_RELESE_VALUES:
|
||||
log.error(f"Invalid argument --release={release}")
|
||||
log.error(f"Valid arguments are: {', '.join(parse.VALID_RELESE_VALUES)}")
|
||||
return
|
||||
|
||||
cfg = config.parse()
|
||||
|
||||
if cfg is None:
|
||||
log.error("Unable to parse pycalver configuration from setup.cfg")
|
||||
return
|
||||
|
||||
old_version = cfg["current_version"]
|
||||
new_version = version.bump(old_version, release=release)
|
||||
|
||||
log.info(f"Old Version: {old_version}")
|
||||
log.info(f"New Version: {new_version}")
|
||||
|
||||
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()
|
||||
lines = content.splitlines()
|
||||
matches = parse.parse_patterns(lines, patterns)
|
||||
for m in matches:
|
||||
print(m)
|
||||
122
src/pycalver/config.py
Normal file
122
src/pycalver/config.py
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
# This file is part of the pycalver project
|
||||
# https://github.com/mbarkhau/pycalver
|
||||
#
|
||||
# (C) 2018 Manuel Barkhau (@mbarkhau)
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import io
|
||||
import os
|
||||
import configparser
|
||||
import pkg_resources
|
||||
import typing as typ
|
||||
import datetime as dt
|
||||
|
||||
import logging
|
||||
|
||||
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
|
||||
|
||||
cfg_parser = configparser.RawConfigParser("")
|
||||
with io.open(config_file, mode="rt", encoding="utf-8") as fh:
|
||||
cfg_parser.readfp(fh)
|
||||
|
||||
if "pycalver" not in cfg_parser:
|
||||
log.error("setup.cfg does not contain a [pycalver] section.")
|
||||
return None
|
||||
|
||||
cfg = dict(cfg_parser.items("pycalver"))
|
||||
|
||||
if "current_version" not in cfg:
|
||||
log.error("setup.cfg does not have 'pycalver.current_version'")
|
||||
return None
|
||||
|
||||
current_version = 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))
|
||||
|
||||
cfg["tag"] = cfg.get("tag", "").lower() in ("yes", "true", "1", "on")
|
||||
cfg["commit"] = cfg.get("commit", "").lower() in ("yes", "true", "1", "on")
|
||||
|
||||
cfg["file_patterns"] = {}
|
||||
|
||||
for section_name in cfg_parser.sections():
|
||||
if not section_name.startswith("pycalver:file:"):
|
||||
continue
|
||||
|
||||
filepath = section_name.split(":", 2)[-1]
|
||||
if not os.path.exists(filepath):
|
||||
log.error(f"No such file: {filepath} from {section_name} in setup.cfg")
|
||||
return None
|
||||
|
||||
section = dict(cfg_parser.items(section_name))
|
||||
|
||||
if "patterns" in section:
|
||||
cfg["file_patterns"][filepath] = [
|
||||
line.strip()
|
||||
for line in section["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}"]
|
||||
|
||||
log.debug(f"Config Parsed: {cfg}")
|
||||
|
||||
return cfg
|
||||
|
||||
|
||||
def default_config_lines() -> typ.List[str]:
|
||||
initial_version = dt.datetime.now().strftime("v%Y%m.0001-dev")
|
||||
|
||||
cfg_lines = [
|
||||
"[pycalver]",
|
||||
f"current_version = {initial_version}",
|
||||
"commit = True",
|
||||
"tag = True",
|
||||
"",
|
||||
"[pycalver:file:setup.cfg]",
|
||||
"patterns = ",
|
||||
" current_version = {version}",
|
||||
"",
|
||||
]
|
||||
|
||||
if os.path.exists("setup.py"):
|
||||
cfg_lines.extend([
|
||||
"[pycalver:file:setup.py]",
|
||||
"patterns = ",
|
||||
" \"{version}\"",
|
||||
" \"{pep440_version}\"",
|
||||
"",
|
||||
])
|
||||
|
||||
if os.path.exists("README.rst"):
|
||||
cfg_lines.extend([
|
||||
"[pycalver:file:README.rst]",
|
||||
"patterns = ",
|
||||
" {version}",
|
||||
" {pep440_version}",
|
||||
"",
|
||||
])
|
||||
|
||||
if os.path.exists("README.md"):
|
||||
cfg_lines.extend([
|
||||
"[pycalver:file:README.md]",
|
||||
" {version}",
|
||||
" {pep440_version}",
|
||||
"",
|
||||
])
|
||||
|
||||
return cfg_lines
|
||||
137
src/pycalver/lex_id.py
Normal file
137
src/pycalver/lex_id.py
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
# This file is part of the pycalver project
|
||||
# https://github.com/mbarkhau/pycalver
|
||||
#
|
||||
# (C) 2018 Manuel Barkhau (@mbarkhau)
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
"""
|
||||
This is a simple scheme for numerical ids which are ordered both
|
||||
numerically and lexically.
|
||||
|
||||
Throughout the sequence this expression remains true, whether you
|
||||
are dealing with integers or strings:
|
||||
|
||||
older_id < newer_id
|
||||
|
||||
The left most character/digit is only used to maintain lexical
|
||||
order, so that the position in the sequence is maintained in the
|
||||
remaining digits.
|
||||
|
||||
sequence_pos = int(idval[1:], 10)
|
||||
|
||||
lexical sequence_pos
|
||||
0 0
|
||||
11 1
|
||||
12 2
|
||||
...
|
||||
19 9
|
||||
220 20
|
||||
221 21
|
||||
...
|
||||
298 98
|
||||
299 99
|
||||
3300 300
|
||||
3301 301
|
||||
...
|
||||
3998 998
|
||||
3999 999
|
||||
44000 4000
|
||||
44001 4001
|
||||
...
|
||||
899999998 99999998
|
||||
899999999 99999999
|
||||
9900000000 900000000
|
||||
9900000001 900000001
|
||||
...
|
||||
9999999998 999999998
|
||||
9999999999 999999999 # maximum value
|
||||
|
||||
You can add leading zeros to delay the expansion and/or increase
|
||||
the maximum possible value.
|
||||
|
||||
lexical sequence_pos
|
||||
0001 1
|
||||
0002 2
|
||||
0003 3
|
||||
...
|
||||
0999 999
|
||||
11000 1000
|
||||
11001 1001
|
||||
11002 1002
|
||||
...
|
||||
19998 9998
|
||||
19999 9999
|
||||
220000 20000
|
||||
220001 20001
|
||||
...
|
||||
899999999998 99999999998
|
||||
899999999999 99999999999
|
||||
9900000000000 900000000000
|
||||
9900000000001 900000000001
|
||||
...
|
||||
9999999999998 999999999998
|
||||
9999999999999 999999999999 # maximum value
|
||||
|
||||
This scheme is useful when you just want an ordered sequence of
|
||||
numbers, but the numbers don't have any particular meaning or
|
||||
arithmetical relation. The only relation they have to each other
|
||||
is that numbers generated later in the sequence are greater than
|
||||
ones generated earlier.
|
||||
"""
|
||||
|
||||
|
||||
MINIMUM_ID = "0"
|
||||
|
||||
|
||||
def next_id(prev_id: str) -> str:
|
||||
num_digits = len(prev_id)
|
||||
_prev_id = int(prev_id, 10)
|
||||
_next_id = int(_prev_id) + 1
|
||||
next_id = f"{_next_id:0{num_digits}}"
|
||||
if prev_id[0] != next_id[0]:
|
||||
next_id = str(_next_id * 11)
|
||||
return next_id
|
||||
|
||||
|
||||
def ord_val(lex_id: str) -> int:
|
||||
if len(lex_id) == 1:
|
||||
return int(lex_id, 10)
|
||||
return int(lex_id[1:], 10)
|
||||
|
||||
|
||||
def main():
|
||||
_curr_id = "01"
|
||||
print(f"{'lexical':<13} {'numerical':>12}")
|
||||
|
||||
while True:
|
||||
print(f"{_curr_id:<13} {ord_val(_curr_id):>12}")
|
||||
_next_id = next_id(_curr_id)
|
||||
|
||||
assert _curr_id < next_id
|
||||
assert int(_curr_id, 10) < int(next_id, 10)
|
||||
assert ord_val(_curr_id) < ord_val(next_id)
|
||||
|
||||
# while next_id.startswith("0") and int(next_id) < 1000:
|
||||
# _next_id = next_id(_next_id)
|
||||
|
||||
if next_id.count("9") == len(next_id):
|
||||
# all nines, we're done
|
||||
print(f"{next_id:<13} {ord_val(next_id):>12}")
|
||||
break
|
||||
|
||||
if _next_id[0] != _curr_id[0] and len(_curr_id) > 1:
|
||||
print(f"{_next_id:<13} {ord_val(_next_id):>12}")
|
||||
_next_id = next_id(_next_id)
|
||||
print(f"{_next_id:<13} {ord_val(_next_id):>12}")
|
||||
_next_id = next_id(_next_id)
|
||||
|
||||
print("...")
|
||||
|
||||
# skip ahead
|
||||
_next_id = _next_id[:1] + "9" * (len(_next_id) - 2) + "8"
|
||||
|
||||
_curr_id = _next_id
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
94
src/pycalver/parse.py
Normal file
94
src/pycalver/parse.py
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
# This file is part of the pycalver project
|
||||
# https://github.com/mbarkhau/pycalver
|
||||
#
|
||||
# (C) 2018 Manuel Barkhau (@mbarkhau)
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import re
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import typing as typ
|
||||
import datetime as dt
|
||||
|
||||
from pkg_resources import parse_version
|
||||
|
||||
from . import lex_id
|
||||
|
||||
log = logging.getLogger("pycalver.parse")
|
||||
|
||||
|
||||
VALID_RELESE_VALUES = ("alpha", "beta", "dev", "rc", "post")
|
||||
|
||||
|
||||
PYCALVER_RE: typ.re.Pattern[str] = re.compile(r"""
|
||||
\b
|
||||
(?P<version>
|
||||
(?P<calver>
|
||||
v # "v" version prefix
|
||||
(?P<year>\d{4})
|
||||
(?P<month>\d{2})
|
||||
)
|
||||
(?:
|
||||
\. # "." build nr prefix
|
||||
(?P<build>\d{4,})
|
||||
)
|
||||
(?:
|
||||
\- # "-" release prefix
|
||||
(?P<release>
|
||||
alpha|beta|dev|rc|post
|
||||
)
|
||||
)?
|
||||
)(?:\s|$)
|
||||
""", flags=re.VERBOSE)
|
||||
|
||||
|
||||
RE_PATTERN_PARTS = {
|
||||
"pep440_version" : r"\d{6}\.\d+(a|b|dev|rc|post)?\d*",
|
||||
"version" : r"v\d{6}\.\d{4,}\-(?:alpha|beta|dev|rc|post)",
|
||||
"calver" : r"v\d{6}",
|
||||
"build" : r"\.\d{4,}",
|
||||
"release" : r"(\-(?:alpha|beta|dev|rc|post))?",
|
||||
}
|
||||
|
||||
|
||||
class PatternMatch(typ.NamedTuple):
|
||||
|
||||
lineno : int
|
||||
line : str
|
||||
pattern : str
|
||||
span : typ.Tuple[int, int]
|
||||
match : str
|
||||
|
||||
|
||||
MaybeMatch = typ.Optional[typ.re.Match[str]]
|
||||
PyCalVerInfo = typ.Dict[str, str]
|
||||
|
||||
|
||||
def iter_pattern_matches(lines: typ.List[str], pattern: str) -> typ.Iterable[PatternMatch]:
|
||||
# The pattern is escaped, so that everything besides the format
|
||||
# string variables is treated literally.
|
||||
pattern_re = re.compile(
|
||||
pattern
|
||||
.replace("\\", "\\\\")
|
||||
.replace("-", "\\-")
|
||||
.replace(".", "\\.")
|
||||
.replace("+", "\\+")
|
||||
.replace("*", "\\*")
|
||||
.replace("[", "\\[")
|
||||
.replace("(", "\\(")
|
||||
.format(**RE_PATTERN_PARTS)
|
||||
)
|
||||
for i, line in enumerate(lines):
|
||||
match = pattern_re.search(line)
|
||||
if match:
|
||||
lineno = i + 1
|
||||
yield PatternMatch(lineno, line, pattern, match.span(), match.group(0))
|
||||
|
||||
|
||||
def parse_patterns(lines: typ.List[str], patterns: typ.List[str]) -> typ.List[PatternMatch]:
|
||||
all_matches: typ.List[PatternMatch] = []
|
||||
for pattern in patterns:
|
||||
all_matches.extend(iter_pattern_matches(lines, pattern))
|
||||
return all_matches
|
||||
21
src/pycalver/rewrite.py
Normal file
21
src/pycalver/rewrite.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#!/usr/bin/env python
|
||||
# This file is part of the pycalver project
|
||||
# https://github.com/mbarkhau/pycalver
|
||||
#
|
||||
# (C) 2018 Manuel Barkhau (@mbarkhau)
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import logging
|
||||
import difflib
|
||||
|
||||
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,
|
||||
)
|
||||
111
src/pycalver/vcs.py
Normal file
111
src/pycalver/vcs.py
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
# This file is part of the pycalver project
|
||||
# https://github.com/mbarkhau/pycalver
|
||||
#
|
||||
# (C) 2018 Manuel Barkhau (@mbarkhau)
|
||||
# SPDX-License-Identifier: MIT
|
||||
#
|
||||
# pycalver/vcs.py (this file) is based on code from the
|
||||
# bumpversion project: https://github.com/peritus/bumpversion
|
||||
# MIT License - (C) 2013-2014 Filip Noetzel
|
||||
|
||||
import os
|
||||
import logging
|
||||
import tempfile
|
||||
import subprocess as sp
|
||||
|
||||
|
||||
log = logging.getLogger("pycalver.vcs")
|
||||
|
||||
|
||||
class WorkingDirectoryIsDirtyException(Exception):
|
||||
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
|
||||
|
||||
class BaseVCS:
|
||||
|
||||
@classmethod
|
||||
def commit(cls, message):
|
||||
f = tempfile.NamedTemporaryFile("wb", delete=False)
|
||||
f.write(message.encode("utf-8"))
|
||||
f.close()
|
||||
cmd = cls._COMMIT_COMMAND + [f.name]
|
||||
env_items = list(os.environ.items()) + [(b"HGENCODING", b"utf-8")]
|
||||
sp.check_output(cmd, env=dict(env_items))
|
||||
os.unlink(f.name)
|
||||
|
||||
@classmethod
|
||||
def is_usable(cls):
|
||||
try:
|
||||
return sp.call(
|
||||
cls._TEST_USABLE_COMMAND,
|
||||
stderr=sp.PIPE,
|
||||
stdout=sp.PIPE,
|
||||
) == 0
|
||||
except OSError as e:
|
||||
if e.errno == 2:
|
||||
# mercurial is not installed then, ok.
|
||||
return False
|
||||
raise
|
||||
|
||||
@classmethod
|
||||
def assert_nondirty(cls):
|
||||
status_output = sp.check_output(cls._STATUS_COMMAND)
|
||||
lines = [
|
||||
line.strip()
|
||||
for line in status_output.splitlines()
|
||||
if not line.strip().startswith(b"??")
|
||||
]
|
||||
|
||||
if lines:
|
||||
cleaned_output = b"\n".join(lines)
|
||||
cls_name = cls.__name__
|
||||
raise WorkingDirectoryIsDirtyException(
|
||||
f"{cls_name} working directory is not clean:\n{cleaned_output}"
|
||||
)
|
||||
|
||||
|
||||
class Git(BaseVCS):
|
||||
|
||||
_TEST_USABLE_COMMAND = ["git", "rev-parse", "--git-dir"]
|
||||
_COMMIT_COMMAND = ["git", "commit", "-F"]
|
||||
_STATUS_COMMAND = ["git", "status", "--porcelain"]
|
||||
|
||||
@classmethod
|
||||
def tag(cls, name):
|
||||
sp.check_output(["git", "tag", name])
|
||||
|
||||
|
||||
class Mercurial(BaseVCS):
|
||||
|
||||
_TEST_USABLE_COMMAND = ["hg", "root"]
|
||||
_COMMIT_COMMAND = ["hg", "commit", "--logfile"]
|
||||
_STATUS_COMMAND = ["hg", "status", "-mard"]
|
||||
|
||||
@classmethod
|
||||
def tag(cls, name):
|
||||
sp.check_output(["hg", "tag", name])
|
||||
|
||||
|
||||
VCS = [Git, Mercurial]
|
||||
|
||||
|
||||
def get_vcs(allow_dirty=False):
|
||||
for vcs in VCS:
|
||||
if not vcs.is_usable():
|
||||
continue
|
||||
|
||||
if not allow_dirty:
|
||||
try:
|
||||
vcs.assert_nondirty()
|
||||
except WorkingDirectoryIsDirtyException as e:
|
||||
log.warn(
|
||||
f"{e.message}\n\n"
|
||||
f"Use --allow-dirty to override this if you know what you're doing."
|
||||
)
|
||||
raise
|
||||
|
||||
return vcs
|
||||
|
||||
return None
|
||||
71
src/pycalver/version.py
Normal file
71
src/pycalver/version.py
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
# This file is part of the pycalver project
|
||||
# https://github.com/mbarkhau/pycalver
|
||||
#
|
||||
# (C) 2018 Manuel Barkhau (@mbarkhau)
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import logging
|
||||
import datetime as dt
|
||||
|
||||
from . import lex_id
|
||||
log = logging.getLogger("pycalver.version")
|
||||
|
||||
|
||||
def current_calver() -> str:
|
||||
return dt.datetime.utcnow().strftime("v%Y%m")
|
||||
|
||||
|
||||
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_calver, rest = old_version.split(".")
|
||||
old_build, old_release = rest.split("-")
|
||||
|
||||
new_calver = current_calver()
|
||||
new_build = lex_id.next_id(old_build)
|
||||
if release is None:
|
||||
# preserve existing release
|
||||
new_release = old_release
|
||||
else:
|
||||
new_release = release
|
||||
|
||||
new_version = new_calver + "." + new_build
|
||||
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
|
||||
Loading…
Add table
Add a link
Reference in a new issue