Code quality updates

This commit is contained in:
Manuel Barkhau 2018-11-15 22:16:16 +01:00
parent 95234dfd0b
commit 54a681bf34
14 changed files with 413 additions and 187 deletions

View file

@ -84,7 +84,7 @@ regular expression:
import re
# https://regex101.com/r/fnj60p/10
pycalver_re = re.compile(r"""
PYCALVER_PATTERN = r"""
\b
(?P<version>
(?P<calver>
@ -101,10 +101,11 @@ pycalver_re = re.compile(r"""
(?:alpha|beta|dev|rc|post)
)?
)(?:\s|$)
""", flags=re.VERBOSE)
"""
PYCALVER_RE = re.compile(PYCALVER_PATTERN, flags=re.VERBOSE)
version_str = "v201712.0001-alpha"
version_info = pycalver_re.match(version_str).groupdict()
version_info = PYCALVER_RE.match(version_str).groupdict()
assert version_info == {
"version" : "v201712.0001-alpha",
@ -116,7 +117,7 @@ assert version_info == {
}
version_str = "v201712.0033"
version_info = pycalver_re.match(version_str).groupdict()
version_info = PYCALVER_RE.match(version_str).groupdict()
assert version_info == {
"version" : "v201712.0033",

View file

@ -17,7 +17,8 @@ ENV CONDA_DIR /opt/conda
ENV PATH $CONDA_DIR/bin:$PATH
ENV SHELL /bin/bash
RUN apk add --no-cache bash make sed grep gawk curl git bzip2 unzip
RUN apk add --no-cache bash make sed grep gawk curl bzip2 unzip
RUN apk add --no-cache git mercurial
CMD [ "/bin/bash" ]

View file

@ -320,6 +320,7 @@ check: fmt lint mypy test
env:
@bash --init-file <(echo '\
source $$HOME/.bashrc; \
source $(CONDA_ROOT)/etc/profile.d/conda.sh \
export ENV=${ENV-dev}; \
export PYTHONPATH="src/:vendor/:$$PYTHONPATH"; \
conda activate $(DEV_ENV_NAME) \

View file

@ -43,6 +43,8 @@ ignore =
# D101
# Missing docstring on __init__
D107
# No blank lines allowed after function docstring
D202
# First line should be in imperative mood
D401
select = A,AAA,D,C,E,F,W,H,B,D212,D404,D405,D406,B901,B950

View file

@ -30,7 +30,7 @@ _VERBOSE = 0
log = logging.getLogger("pycalver.cli")
def _init_loggers(verbose: int = 0) -> None:
def _init_logging(verbose: int = 0) -> None:
if verbose >= 2:
log_format = "%(asctime)s.%(msecs)03d %(levelname)-7s %(name)-15s - %(message)s"
log_level = logging.DEBUG
@ -42,7 +42,7 @@ def _init_loggers(verbose: int = 0) -> None:
log_level = logging.WARNING
logging.basicConfig(level=log_level, format=log_format, datefmt="%Y-%m-%dT%H:%M:%S")
log.debug("Loggers initialized.")
log.debug("Logging initialized.")
@click.group()
@ -84,7 +84,7 @@ def _update_cfg_from_vcs(cfg: config.Config, fetch: bool) -> config.Config:
def show(verbose: int = 0, fetch: bool = True) -> None:
"""Show current version."""
verbose = max(_VERBOSE, verbose)
_init_loggers(verbose=verbose)
_init_logging(verbose=verbose)
cfg: config.MaybeConfig = config.parse()
if cfg is None:
@ -106,7 +106,7 @@ def show(verbose: int = 0, fetch: bool = True) -> None:
def incr(old_version: str, verbose: int = 0, release: str = None) -> None:
"""Increment a version number for demo purposes."""
verbose = max(_VERBOSE, verbose)
_init_loggers(verbose)
_init_logging(verbose)
if release and release not in parse.VALID_RELESE_VALUES:
log.error(f"Invalid argument --release={release}")
@ -114,7 +114,7 @@ def incr(old_version: str, verbose: int = 0, release: str = None) -> None:
sys.exit(1)
new_version = version.incr(old_version, release=release)
new_version_nfo = parse.parse_version_info(new_version)
new_version_nfo = parse.VersionInfo.parse(new_version)
print("PyCalVer Version:", new_version)
print("PEP440 Version:" , new_version_nfo.pep440_version)
@ -128,7 +128,7 @@ def incr(old_version: str, verbose: int = 0, release: str = None) -> None:
def init(verbose: int = 0, dry: bool = False) -> None:
"""Initialize [pycalver] configuration."""
verbose = max(_VERBOSE, verbose)
_init_loggers(verbose)
_init_logging(verbose)
cfg : config.MaybeConfig = config.parse()
if cfg:
@ -174,6 +174,35 @@ def _assert_not_dirty(vcs, filepaths: typ.Set[str], allow_dirty: bool):
sys.exit(1)
def _bump(cfg: config.Config, new_version: str, allow_dirty: bool = False) -> None:
_vcs: typ.Optional[vcs.VCS]
try:
_vcs = vcs.get_vcs()
except OSError:
log.warn("Version Control System not found, aborting commit.")
_vcs = None
filepaths = set(cfg.file_patterns.keys())
if _vcs:
_assert_not_dirty(_vcs, filepaths, allow_dirty)
rewrite.rewrite(new_version, cfg.file_patterns)
if _vcs is None or not cfg.commit:
return
for filepath in filepaths:
_vcs.add(filepath)
_vcs.commit(f"bump version to {new_version}")
if cfg.tag:
_vcs.tag(new_version)
_vcs.push(new_version)
@cli.command()
@click.option("-v", "--verbose" , count=True , help="Control log level. -vv for debug level.")
@click.option('-f', "--fetch/--no-fetch", is_flag=True, default=True)
@ -202,7 +231,7 @@ def bump(
) -> None:
"""Increment the current version string and update project files."""
verbose = max(_VERBOSE, verbose)
_init_loggers(verbose)
_init_logging(verbose)
if release and release not in parse.VALID_RELESE_VALUES:
log.error(f"Invalid argument --release={release}")
@ -223,33 +252,10 @@ def bump(
log.info(f"Old Version: {old_version}")
log.info(f"New Version: {new_version}")
if dry or verbose:
print(rewrite.diff(new_version, cfg.file_patterns))
if dry:
log.info("Running with '--dry', showing diffs instead of updating files.")
file_patterns = cfg.file_patterns
filepaths = set(file_patterns.keys())
_vcs: typ.Optional[vcs.VCS]
try:
_vcs = vcs.get_vcs()
except OSError:
log.warn("Version Control System not found, aborting commit.")
_vcs = None
# if _vcs:
# _assert_not_dirty(_vcs, filepaths, allow_dirty)
rewrite.rewrite(new_version, file_patterns, dry=dry, verbose=verbose)
if dry or _vcs is None or not cfg.commit:
return
for filepath in filepaths:
_vcs.add(filepath)
_vcs.commit(f"bump version to {new_version}")
if cfg.tag:
_vcs.tag(new_version)
_vcs.push(new_version)
_bump(cfg, new_version, allow_dirty)

View file

@ -3,7 +3,7 @@
#
# Copyright (c) 2018 Manuel Barkhau (@mbarkhau) - MIT License
# SPDX-License-Identifier: MIT
"""Parsing code for setup.cfg or pycalver.cfg"""
"""Parse setup.cfg or pycalver.cfg files."""
import io
import os
@ -18,15 +18,18 @@ from .parse import PYCALVER_RE
log = logging.getLogger("pycalver.config")
PatternsByFilePath = typ.Dict[str, typ.List[str]]
class Config(typ.NamedTuple):
"""Represents a parsed config."""
current_version: str
tag : bool
commit: bool
file_patterns: typ.Dict[str, typ.List[str]]
file_patterns: PatternsByFilePath
def _debug_str(self) -> str:
cfg_str_parts = [
@ -46,6 +49,12 @@ class Config(typ.NamedTuple):
@property
def pep440_version(self) -> str:
"""Derive pep440 compliant version string from PyCalVer version string.
>>> cfg = Config("v201811.0007-beta", True, True, [])
>>> cfg.pep440_version
'201811.7b0'
"""
return str(pkg_resources.parse_version(self.current_version))
@ -131,26 +140,27 @@ def _parse_buffer(cfg_buffer: io.StringIO, config_filename: str = "<pycalver.cfg
return cfg
def parse(config_filename: str = None) -> MaybeConfig:
if config_filename is None:
def parse(config_filepath: str = None) -> MaybeConfig:
"""Parse config file using configparser."""
if config_filepath is None:
if os.path.exists("pycalver.cfg"):
config_filename = "pycalver.cfg"
config_filepath = "pycalver.cfg"
elif os.path.exists("setup.cfg"):
config_filename = "setup.cfg"
config_filepath = "setup.cfg"
else:
log.error("File not found: pycalver.cfg or setup.cfg")
return None
if not os.path.exists(config_filename):
log.error(f"File not found: {config_filename}")
if not os.path.exists(config_filepath):
log.error(f"File not found: {config_filepath}")
return None
cfg_buffer = io.StringIO()
with io.open(config_filename, mode="rt", encoding="utf-8") as fh:
with io.open(config_filepath, mode="rt", encoding="utf-8") as fh:
cfg_buffer.write(fh.read())
cfg_buffer.seek(0)
return _parse_buffer(cfg_buffer, config_filename)
return _parse_buffer(cfg_buffer, config_filepath)
DEFAULT_CONFIG_BASE_STR = """
@ -190,6 +200,7 @@ patterns =
def default_config_lines() -> typ.List[str]:
"""Generate initial default config based on PWD and current date."""
initial_version = dt.datetime.now().strftime("v%Y%m.0001-dev")
cfg_str = DEFAULT_CONFIG_BASE_STR.format(initial_version=initial_version)

View file

@ -4,9 +4,7 @@
# Copyright (c) 2018 Manuel Barkhau (@mbarkhau) - MIT License
# SPDX-License-Identifier: MIT
"""
This is a simple scheme for numerical ids which are ordered both
numerically and lexically.
"""A scheme for lexically ordered numerical ids.
Throughout the sequence this expression remains true, whether you
are dealing with integers or strings:
@ -84,25 +82,56 @@ MINIMUM_ID = "0"
def next_id(prev_id: str) -> str:
"""Generate next lexical id.
Increments by one and adds padding if required.
>>> next_id("0098")
'0099'
>>> next_id("0099")
'0100'
>>> next_id("0999")
'11000'
>>> next_id("11000")
'11001'
"""
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}}"
if prev_id[0] != next_id[0]:
next_id = str(_next_id * 11)
return next_id
_prev_id_val = int(prev_id, 10)
_next_id_val = int(_prev_id_val) + 1
_next_id_str = f"{_next_id_val:0{num_digits}}"
if prev_id[0] != _next_id_str[0]:
_next_id_str = str(_next_id_val * 11)
return _next_id_str
def ord_val(lex_id: str) -> int:
"""Parse the ordinal value of a lexical id.
The ordinal value is the position in the sequence,
from repeated calls to next_id.
>>> ord_val("0098")
98
>>> ord_val("0099")
99
>>> ord_val("0100")
100
>>> ord_val("11000")
1000
>>> ord_val("11001")
1001
"""
if len(lex_id) == 1:
return int(lex_id, 10)
return int(lex_id[1:], 10)
def main() -> None:
def _main() -> None:
_curr_id = "01"
print(f"{'lexical':<13} {'numerical':>12}")
@ -130,4 +159,4 @@ def main() -> None:
if __name__ == '__main__':
main()
_main()

View file

@ -3,6 +3,28 @@
#
# Copyright (c) 2018 Manuel Barkhau (@mbarkhau) - MIT License
# SPDX-License-Identifier: MIT
"""Parse PyCalVer strings.
>>> version_info = PYCALVER_RE.match("v201712.0123-alpha").groupdict()
>>> assert version_info == {
... "version" : "v201712.0123-alpha",
... "calver" : "v201712",
... "year" : "2017",
... "month" : "12",
... "build" : ".0123",
... "release" : "-alpha",
... }
>>>
>>> version_info = PYCALVER_RE.match("v201712.0033").groupdict()
>>> assert version_info == {
... "version" : "v201712.0033",
... "calver" : "v201712",
... "year" : "2017",
... "month" : "12",
... "build" : ".0033",
... "release" : None,
... }
"""
import re
import logging
@ -64,7 +86,49 @@ RE_PATTERN_PARTS = {
}
class VersionInfo(typ.NamedTuple):
"""Container for parsed version string."""
version: str
calver : str
year : str
month : str
build : str
release: typ.Optional[str]
@property
def pep440_version(self) -> str:
"""Generate pep440 compliant version string.
>>> vnfo = VersionInfo.parse("v201712.0033-beta")
>>> vnfo.pep440_version
'201712.33b0'
"""
return str(pkg_resources.parse_version(self.version))
@staticmethod
def parse(version: str) -> 'VersionInfo':
"""Parse a PyCalVer string.
>>> vnfo = VersionInfo.parse("v201712.0033-beta")
>>> assert vnfo == VersionInfo(
... version="v201712.0033-beta",
... calver="v201712",
... year="2017",
... month="12",
... build=".0033",
... release="-beta",
... )
"""
match = PYCALVER_RE.match(version)
if match is None:
raise ValueError(f"Invalid pycalver: {version}")
return VersionInfo(**match.groupdict())
class PatternMatch(typ.NamedTuple):
"""Container to mark a version string in a file."""
lineno : int # zero based
line : str
@ -72,27 +136,8 @@ class PatternMatch(typ.NamedTuple):
span : typ.Tuple[int, int]
match : str
class VersionInfo(typ.NamedTuple):
pep440_version: str
version : str
calver : str
year : str
month : str
build : str
release : typ.Optional[str]
def parse_version_info(version: str) -> VersionInfo:
match = PYCALVER_RE.match(version)
if match is None:
raise ValueError(f"Invalid pycalver: {version}")
pep440_version = str(pkg_resources.parse_version(version))
return VersionInfo(pep440_version=pep440_version, **match.groupdict())
def _iter_pattern_matches(lines: typ.List[str], pattern: str) -> typ.Iterable[PatternMatch]:
@staticmethod
def _iter_for_pattern(lines: typ.List[str], pattern: str) -> typ.Iterable['PatternMatch']:
# The pattern is escaped, so that everything besides the format
# string variables is treated literally.
@ -108,9 +153,21 @@ def _iter_pattern_matches(lines: typ.List[str], pattern: str) -> typ.Iterable[Pa
if match:
yield PatternMatch(lineno, line, pattern, match.span(), match.group(0))
@staticmethod
def iter_matches(lines: typ.List[str], patterns: typ.List[str]) -> typ.Iterable['PatternMatch']:
"""Iterate over all matches of any pattern on any line.
def parse_patterns(lines: typ.List[str], patterns: typ.List[str]) -> typ.List[PatternMatch]:
all_matches: typ.List[PatternMatch] = []
>>> lines = ["__version__ = 'v201712.0002-alpha'"]
>>> patterns = ["{version}", "{pep440_version}"]
>>> matches = list(PatternMatch.iter_matches(lines, patterns))
>>> assert matches[0] == PatternMatch(
... lineno = 0,
... line = "__version__ = 'v201712.0002-alpha'",
... pattern= "{version}",
... span = (15, 33),
... match = "v201712.0002-alpha",
... )
"""
for pattern in patterns:
all_matches.extend(_iter_pattern_matches(lines, pattern))
return all_matches
for match in PatternMatch._iter_for_pattern(lines, pattern):
yield match

View file

@ -3,6 +3,7 @@
#
# (C) 2018 Manuel Barkhau (@mbarkhau)
# SPDX-License-Identifier: MIT
"""Rewrite files, updating occurences of version strings."""
import io
import difflib
@ -10,11 +11,23 @@ import logging
import typing as typ
from . import parse
from . import config
log = logging.getLogger("pycalver.rewrite")
def _detect_line_sep(content: str) -> str:
def detect_line_sep(content: str) -> str:
r"""Parse line separator from content.
>>> detect_line_sep('\r\n')
'\r\n'
>>> detect_line_sep('\r')
'\r'
>>> detect_line_sep('\n')
'\n'
>>> detect_line_sep('')
'\n'
"""
if "\r\n" in content:
return "\r\n"
elif "\r" in content:
@ -24,16 +37,21 @@ def _detect_line_sep(content: str) -> str:
def rewrite_lines(
old_lines: typ.List[str], patterns: typ.List[str], new_version: str
patterns: typ.List[str], new_version: str, old_lines: typ.List[str]
) -> typ.List[str]:
new_version_nfo = parse.parse_version_info(new_version)
"""Replace occurances of patterns in old_lines with new_version.
>>> old_lines = ['__version__ = "v201809.0002-beta"']
>>> patterns = ['__version__ = "{version}"']
>>> new_lines = rewrite_lines(patterns, "v201811.0123-beta", old_lines)
>>> assert new_lines == ['__version__ = "v201811.0123-beta"']
"""
new_version_nfo = parse.VersionInfo.parse(new_version)
new_version_fmt_kwargs = new_version_nfo._asdict()
new_lines = old_lines.copy()
matches: typ.List[parse.PatternMatch] = parse.parse_patterns(old_lines, patterns)
for m in matches:
for m in parse.PatternMatch.iter_matches(old_lines, patterns):
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:]
@ -42,26 +60,107 @@ def rewrite_lines(
return new_lines
def rewrite(
new_version: str, file_patterns: typ.Dict[str, typ.List[str]], dry=False, verbose: int = 0
) -> None:
class RewrittenFileData(typ.NamedTuple):
"""Container for line-wise content of rewritten files."""
path : str
line_sep : str
old_lines: typ.List[str]
new_lines: typ.List[str]
@property
def diff_lines(self) -> typ.List[str]:
r"""Generate unified diff.
>>> rwd = RewrittenFileData(
... path = "<path>",
... line_sep = "\n",
... old_lines = ["foo"],
... new_lines = ["bar"],
... )
>>> rwd.diff_lines
['--- <path>', '+++ <path>', '@@ -1 +1 @@', '-foo', '+bar']
"""
return list(
difflib.unified_diff(
a=self.old_lines,
b=self.new_lines,
lineterm="",
fromfile=self.path,
tofile=self.path,
)
)
@staticmethod
def from_content(
patterns: typ.List[str], new_version: str, content: str
) -> 'RewrittenFileData':
r"""Rewrite pattern occurrences with version string.
>>> patterns = ['__version__ = "{version}"']
>>> content = '__version__ = "v201809.0001-alpha"'
>>> rwd = RewrittenFileData.from_content(patterns, "v201809.0123", content)
>>> assert rwd.new_lines == ['__version__ = "v201809.0123"']
"""
line_sep = detect_line_sep(content)
old_lines = content.split(line_sep)
new_lines = rewrite_lines(patterns, new_version, old_lines)
return RewrittenFileData("<path>", line_sep, old_lines, new_lines)
@staticmethod
def iter_rewritten(
file_patterns: config.PatternsByFilePath, new_version: str
) -> typ.Iterable['RewrittenFileData']:
r'''Iterate over files with version string replaced.
>>> file_patterns = {"src/pycalver/__init__.py": ['__version__ = "{version}"']}
>>> rewritten_datas = RewrittenFileData.iter_rewritten(file_patterns, "v201809.0123")
>>> rwd = list(rewritten_datas)[0]
>>> assert rwd.new_lines == [
... '# This file is part of the pycalver project',
... '# https://gitlab.com/mbarkhau/pycalver',
... '#',
... '# Copyright (c) 2018 Manuel Barkhau (@mbarkhau) - MIT License',
... '# SPDX-License-Identifier: MIT',
... '"""PyCalVer: Automatic CalVer Versioning for Python Packages."""',
... '',
... '__version__ = "v201809.0123"',
... '',
... ]
>>>
'''
for filepath, patterns in file_patterns.items():
with io.open(filepath, mode="rt", encoding="utf-8") as fh:
content = fh.read()
line_sep = _detect_line_sep(content)
old_lines = content.split(line_sep)
new_lines = rewrite_lines(old_lines, patterns, new_version)
rfd = RewrittenFileData.from_content(patterns, new_version, content)
yield rfd._replace(path=filepath)
if dry or verbose:
diff_lines = difflib.unified_diff(
old_lines, new_lines, lineterm="", fromfile="a/" + filepath, tofile="b/" + filepath
)
print("\n".join(diff_lines))
if dry:
continue
def diff(new_version: str, file_patterns: config.PatternsByFilePath) -> str:
r"""Generate diffs of rewritten files.
new_content = line_sep.join(new_lines)
with io.open(filepath, mode="wt", encoding="utf-8") as fh:
>>> file_patterns = {"src/pycalver/__init__.py": ['__version__ = "{version}"']}
>>> diff_lines = diff("v201809.0123", file_patterns).split("\n")
>>> diff_lines[:2]
['--- src/pycalver/__init__.py', '+++ src/pycalver/__init__.py']
>>> assert diff_lines[6].startswith('-__version__ = "v2')
>>> assert not diff_lines[6].startswith('-__version__ = "v201809.0123"')
>>> diff_lines[7]
'+__version__ = "v201809.0123"'
"""
diff_lines: typ.List[str] = []
for rwd in RewrittenFileData.iter_rewritten(file_patterns, new_version):
diff_lines += rwd.diff_lines
return "\n".join(diff_lines)
def rewrite(new_version: str, file_patterns: config.PatternsByFilePath) -> None:
"""Rewrite project files, updating each with the new version."""
for file_data in RewrittenFileData.iter_rewritten(file_patterns, new_version):
new_content = file_data.line_sep.join(file_data.new_lines)
with io.open(file_data.path, mode="wt", encoding="utf-8") as fh:
fh.write(new_content)

View file

@ -7,6 +7,12 @@
# pycalver/vcs.py (this file) is based on code from the
# bumpversion project: https://github.com/peritus/bumpversion
# Copyright (c) 2013-2014 Filip Noetzel - MIT License
"""Minimal Git and Mercirial API.
If terminology for similar concepts differs between git and
mercurial, then the git terms are used. For example "fetch"
(git) instead of "pull" (hg) .
"""
import os
import logging
@ -42,7 +48,7 @@ VCS_SUBCOMMANDS_BY_NAME = {
class VCS:
"""Version Control System absraction for git and mercurial"""
"""VCS absraction for git and mercurial."""
def __init__(self, name: str, subcommands: typ.Dict[str, str] = None):
self.name = name
@ -51,13 +57,19 @@ class VCS:
else:
self.subcommands = subcommands
def __call__(self, cmd_name: str, env=None, **kwargs: str) -> bytes:
def __call__(self, cmd_name: str, env=None, **kwargs: str) -> str:
"""Invoke subcommand and return output."""
cmd_str = self.subcommands[cmd_name]
cmd_parts = cmd_str.format(**kwargs).split()
return sp.check_output(cmd_parts, env=env)
output_data = sp.check_output(cmd_parts, env=env)
# TODO (mb 2018-11-15): Detect encoding of output?
_encoding = "utf-8"
return output_data.decode(_encoding)
@property
def is_usable(self) -> bool:
"""Detect availability of subcommand."""
cmd = self.subcommands['is_usable'].split()
try:
@ -70,28 +82,31 @@ class VCS:
raise
def fetch(self) -> None:
"""Fetch updates from remote origin."""
self('fetch')
def status(self) -> typ.List[str]:
"""Get status lines."""
status_output = self('status')
return [
line.decode("utf-8")[2:].strip()
line[2:].strip()
for line in status_output.splitlines()
if not line.strip().startswith(b"??")
if not line.strip().startswith("??")
]
def ls_tags(self) -> typ.List[str]:
"""List vcs tags on all branches."""
ls_tag_lines = self('ls_tags').splitlines()
log.debug(f"ls_tags output {ls_tag_lines}")
return [
line.decode("utf-8").strip() for line in ls_tag_lines if line.strip().startswith(b"v")
]
return [line.strip() for line in ls_tag_lines if line.strip().startswith("v")]
def add(self, path) -> None:
def add(self, path: str) -> None:
"""Add updates to be included in next commit."""
log.info(f"{self.name} add {path}")
self('add_path', path=path)
def commit(self, message: str) -> None:
"""Commit added files."""
log.info(f"{self.name} commit -m '{message}'")
message_data = message.encode("utf-8")
@ -108,17 +123,24 @@ class VCS:
self('commit', env=env, path=tmp_file.name)
os.unlink(tmp_file.name)
def tag(self, tag_name) -> None:
def tag(self, tag_name: str) -> None:
"""Create an annotated tag."""
self('tag', tag=tag_name)
def push(self, tag_name) -> None:
def push(self, tag_name: str) -> None:
"""Push changes to origin."""
self('push_tag', tag=tag_name)
def __repr__(self) -> str:
"""Generate string representation."""
return f"VCS(name='{self.name}')"
def get_vcs() -> VCS:
"""Detect the appropriate VCS for a repository.
raises OSError if the directory doesn't use a supported VCS.
"""
for vcs_name in VCS_SUBCOMMANDS_BY_NAME.keys():
vcs = VCS(name=vcs_name)
if vcs.is_usable:

View file

@ -29,7 +29,7 @@ def incr(old_version: str, *, release: str = None) -> str:
Old_version is assumed to be a valid calver string,
already validated in pycalver.config.parse.
"""
old_ver = parse.parse_version_info(old_version)
old_ver = parse.VersionInfo.parse(old_version)
new_calver = current_calver()

View file

@ -35,7 +35,7 @@ def test_ord_val():
def test_main(capsys):
lex_id.main()
lex_id._main()
captured = capsys.readouterr()
assert len(captured.err) == 0

View file

@ -75,7 +75,7 @@ def test_re_pattern_parts():
def test_parse_version_info():
version_str = "v201712.0001-alpha"
version_nfo = parse.parse_version_info(version_str)
version_nfo = parse.VersionInfo.parse(version_str)
assert version_nfo.pep440_version == "201712.1a0"
assert version_nfo.version == "v201712.0001-alpha"
@ -86,7 +86,7 @@ def test_parse_version_info():
assert version_nfo.release == "-alpha"
version_str = "v201712.0001"
version_nfo = parse.parse_version_info(version_str)
version_nfo = parse.VersionInfo.parse(version_str)
assert version_nfo.pep440_version == "201712.1"
assert version_nfo.version == "v201712.0001"
@ -97,23 +97,25 @@ def test_parse_version_info():
assert version_nfo.release is None
def test_default_parse_patterns():
lines = [
"# setup.py",
"import setuptools",
"__version__ = 'v201712.0002-alpha'",
"setuptools.setup(",
"...",
" version='201712.2a0',",
]
SETUP_PY_FIXTURE = """
# setup.py
import setuptools
__version__ = 'v201712.0002-alpha'
setuptools.setup(
...
version='201712.2a0',
"""
def test_default_parse_patterns():
lines = SETUP_PY_FIXTURE.splitlines()
patterns = ["{version}", "{pep440_version}"]
matches = parse.parse_patterns(lines, patterns)
matches = list(parse.PatternMatch.iter_matches(lines, patterns))
assert len(matches) == 2
assert matches[0].lineno == 2
assert matches[1].lineno == 5
assert matches[0].lineno == 3
assert matches[1].lineno == 6
assert matches[0].pattern == patterns[0]
assert matches[1].pattern == patterns[1]
@ -123,22 +125,15 @@ def test_default_parse_patterns():
def test_explicit_parse_patterns():
lines = [
"# setup.py",
"import setuptools",
"__version__ = 'v201712.0002-alpha'",
"setuptools.setup(",
"...",
" version='201712.2a0',",
]
lines = SETUP_PY_FIXTURE.splitlines()
patterns = ["__version__ = '{version}'", "version='{pep440_version}'"]
matches = parse.parse_patterns(lines, patterns)
matches = list(parse.PatternMatch.iter_matches(lines, patterns))
assert len(matches) == 2
assert matches[0].lineno == 2
assert matches[1].lineno == 5
assert matches[0].lineno == 3
assert matches[1].lineno == 6
assert matches[0].pattern == patterns[0]
assert matches[1].pattern == patterns[1]
@ -147,23 +142,25 @@ def test_explicit_parse_patterns():
assert matches[1].match == "version='201712.2a0'"
README_RST_FIXTURE = """
:alt: PyPI version
.. |version| image:: https://img.shields.io/badge/CalVer-v201809.0002--beta-blue.svg
:target: https://calver.org/
:alt: CalVer v201809.0002-beta
"""
def test_badge_parse_patterns():
lines = [
":alt: PyPI version",
"",
".. |version| image:: https://img.shields.io/badge/CalVer-v201809.0002--beta-blue.svg",
":target: https://calver.org/",
":alt: CalVer v201809.0002-beta",
"",
]
lines = README_RST_FIXTURE.splitlines()
patterns = ["badge/CalVer-{calver}{build}-{release}-blue.svg", ":alt: CalVer {version}"]
matches = parse.parse_patterns(lines, patterns)
matches = list(parse.PatternMatch.iter_matches(lines, patterns))
assert len(matches) == 2
assert matches[0].lineno == 2
assert matches[1].lineno == 4
assert matches[0].lineno == 3
assert matches[1].lineno == 5
assert matches[0].pattern == patterns[0]
assert matches[1].pattern == patterns[1]
@ -174,19 +171,19 @@ def test_badge_parse_patterns():
def test_parse_error():
try:
parse.parse_version_info("")
parse.VersionInfo.parse("")
assert False
except ValueError as err:
pass
try:
parse.parse_version_info("201809.0002")
parse.VersionInfo.parse("201809.0002")
assert False
except ValueError as err:
pass
try:
parse.parse_version_info("v201809.2b0")
parse.VersionInfo.parse("v201809.2b0")
assert False
except ValueError as err:
pass

View file

@ -1,22 +1,22 @@
from pycalver import rewrite
REWRITE_FIXTURE = """
# This file is part of the pycalver project
# https://github.com/mbarkhau/pycalver
#
# (C) 2018 Manuel Barkhau (@mbarkhau)
# SPDX-License-Identifier: MIT
__version__ = "v201809.0002-beta"
"""
def test_rewrite_lines():
old_lines = [
"# 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.0002-beta"',
'DEBUG = os.environ.get("PYDEBUG", "0") == "1"',
]
old_lines = REWRITE_FIXTURE.splitlines()
patterns = ['__version__ = "{version}"']
new_version = "v201809.0003"
new_lines = rewrite.rewrite_lines(old_lines, patterns, new_version)
new_lines = rewrite.rewrite_lines(patterns, new_version, old_lines)
assert len(new_lines) == len(old_lines)
assert new_version not in "\n".join(old_lines)