wip: add v2 module placeholders

This commit is contained in:
Manuel Barkhau 2020-09-06 20:20:36 +00:00
parent 7962a7cb9f
commit 669e8944e9
27 changed files with 1361 additions and 393 deletions

View file

@ -1,9 +1,9 @@
Individual files contain the following tag instead of the full license text.
This file is part of the pycalver project
https://gitlab.com/mbarkhau/pycalver
https://github.com/mbarkhau/pycalver
Copyright (c) 2020 Manuel Barkhau (mbarkhau@gmail.com) - MIT License
Copyright (c) 2018-2020 Manuel Barkhau (mbarkhau@gmail.com) - MIT License
SPDX-License-Identifier: MIT
This enables machine processing of license information based on the SPDX

View file

@ -61,3 +61,5 @@ test_compat: $(COMPAT_TEST_FILES)
ENABLE_BACKTRACE=0 PYTHONPATH="" ENV=$${ENV-dev} \
$${env_py} -m pytest --verbose compat_test/; \
done;
rm -rf compat_test/

View file

@ -14,8 +14,8 @@
version="1.1"
id="svg8"
inkscape:version="1.0 (b51213c273, 2020-08-10)"
sodipodi:docname="pycalver_1k.svg"
inkscape:export-filename="C:\Users\ManuelBarkhau\Dropbox\projects\pycalver\pycalver_128.png"
sodipodi:docname="pycalver1k.svg"
inkscape:export-filename="/home/mbarkhau/foss/pycalver/pycalver1k2_128.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96">
<defs
@ -147,7 +147,7 @@
x2="17.63979"
y2="282.83472"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.0831145,0,0,1.325042,-1.407405,-355.71527)" />
gradientTransform="matrix(1.0541065,0,0,0.87055183,-0.916204,-223.64659)" />
<mask
maskUnits="userSpaceOnUse"
id="mask1129">
@ -201,6 +201,59 @@
</g>
</g>
</mask>
<mask
maskUnits="userSpaceOnUse"
id="mask1129-3">
<g
id="g1145-6"
transform="translate(-1.5416733,-2.3386129)">
<rect
y="267.51743"
x="-0.8018086"
height="9.5214758"
width="38.553631"
id="rect1131-7"
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#aa8800;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers" />
<g
transform="translate(0.07525963,4.2889947)"
id="g1137-5"
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">
<ellipse
cy="268.73132"
cx="10.111843"
id="ellipse1133-3"
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
rx="2.2523122"
ry="2.629046" />
<rect
y="263.9899"
x="8.7405252"
height="6.5171237"
width="2.7426364"
id="rect1135-5"
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
</g>
<g
transform="translate(16.669414,4.2889947)"
id="g1143-6"
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">
<ellipse
cy="268.73132"
cx="10.111843"
id="ellipse1139-2"
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
rx="2.2523122"
ry="2.629046" />
<rect
y="263.9899"
x="8.7405252"
height="6.5171237"
width="2.7426364"
id="rect1141-9"
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
</g>
</g>
</mask>
</defs>
<sodipodi:namedview
id="base"
@ -209,11 +262,11 @@
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="9.2734375"
inkscape:cx="56.807022"
inkscape:cy="64"
inkscape:zoom="8"
inkscape:cx="61.591322"
inkscape:cy="64.987937"
inkscape:document-units="mm"
inkscape:current-layer="layer5"
inkscape:current-layer="layer8"
showgrid="false"
units="px"
inkscape:window-width="2512"
@ -250,44 +303,76 @@
</g>
<g
inkscape:groupmode="layer"
id="layer4"
inkscape:label="gradient"
style="display:inline"
sodipodi:insensitive="true">
<rect
style="display:inline;opacity:1;fill:url(#linearGradient1165-3);fill-opacity:1;stroke:#000000;stroke-width:1.52114;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="rect815"
width="30.280628"
height="20.917326"
x="1.7930192"
y="10.965927" />
id="layer6"
inkscape:label="bg"
style="display:inline">
<path
id="rect880"
style="display:inline;fill:#ffffff;stroke-width:7.16557"
d="m 1.585409,3.7099638 30.618783,-0.052171 0.725957,0.7967657 -0.0046,27.3188725 -0.528162,0.557572 L 1.1974464,32.32312 0.94904837,32.088188 0.93651797,4.3514519 Z"
sodipodi:nodetypes="ccccccccc" />
</g>
<g
inkscape:label="text"
inkscape:groupmode="layer"
id="layer4"
inkscape:label="gradient"
style="display:inline">
<rect
style="display:inline;opacity:1;fill:url(#linearGradient1165-3);fill-opacity:1;stroke:#000000;stroke-width:1.21634;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="rect815"
width="29.469652"
height="13.742671"
x="2.1985073"
y="17.262745" />
</g>
<g
inkscape:label="text bottom"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-263.13332)"
style="display:inline"
sodipodi:insensitive="true">
style="display:inline">
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.2889px;line-height:0.85;font-family:Monoid;-inkscape-font-specification:Monoid;letter-spacing:-0.111125px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
x="3.0023723"
y="283.79349"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:14.8167px;line-height:0.85;font-family:Monoid;-inkscape-font-specification:Monoid;letter-spacing:-0.111125px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
x="2.2388713"
y="292.59897"
id="text859"><tspan
sodipodi:role="line"
x="3.0023723"
y="283.79349"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:11.2889px;font-family:'Iosevka Term SS05';-inkscape-font-specification:'Iosevka Term SS05 Bold';stroke-width:0.264583"
id="tspan886"><tspan
dy="0.58999997"
id="tspan884"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:11.2889px;font-family:'Iosevka Term SS05';-inkscape-font-specification:'Iosevka Term SS05 Bold';stroke-width:0.264583">v2020</tspan></tspan><tspan
x="2.2388713"
y="292.59897"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:14.8167px;font-family:'Iosevka Term SS05';-inkscape-font-specification:'Iosevka Term SS05 Bold';stroke-width:0.264583"
id="tspan953">2020</tspan></text>
</g>
<g
inkscape:groupmode="layer"
id="layer9"
inkscape:label="frame top"
style="display:inline">
<rect
style="display:inline;opacity:1;fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="rect815-0"
width="30.546177"
height="13.588312"
x="1.6602445"
y="-15.922193"
transform="scale(1,-1)" />
</g>
<g
inkscape:groupmode="layer"
id="layer8"
inkscape:label="text top"
style="display:inline">
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:16.9333px;line-height:1.25;font-family:sans-serif;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.265;stroke-miterlimit:4;stroke-dasharray:none"
x="4.7286582"
y="13.507061"
id="text921"><tspan
sodipodi:role="line"
x="3.0023723"
y="293.7308"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:11.2889px;font-family:'Iosevka Term SS05';-inkscape-font-specification:'Iosevka Term SS05 Bold';stroke-width:0.264583"
id="tspan953">.1001</tspan></text>
id="tspan919"
x="4.7286582"
y="13.507061"
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:16.9333px;font-family:'Iosevka Fixed SS05';-inkscape-font-specification:'Iosevka Fixed SS05 Medium';fill:#ffffff;stroke-width:0.265;stroke-miterlimit:4;stroke-dasharray:none">ver</tspan></text>
</g>
<g
inkscape:groupmode="layer"
@ -297,10 +382,17 @@
<rect
style="display:inline;opacity:1;fill:#000000;fill-opacity:1;stroke:#aa8800;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="rect1398"
width="31.802925"
width="30.684868"
height="0.76833469"
x="1.0316887"
y="10.203551" />
x="1.5910856"
y="16.622082" />
</g>
<g
inkscape:groupmode="layer"
id="layer7"
inkscape:label="top frame"
sodipodi:insensitive="true"
style="display:none">
<rect
style="display:inline;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:2.32913;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
id="rect900"
@ -309,27 +401,28 @@
x="3.5457754"
y="268.57437"
clip-path="none"
mask="url(#mask1129)"
transform="matrix(1.0938376,0,0,0.89104584,-1.5990177,-235.29985)" />
mask="url(#mask1129-3)"
transform="matrix(1.0545213,0,0,0.89332824,-0.92322741,-236.38373)" />
</g>
<g
inkscape:groupmode="layer"
id="layer5"
inkscape:label="pegs"
style="display:inline">
style="display:none"
sodipodi:insensitive="true">
<rect
style="display:inline;opacity:1;fill:#000000;fill-opacity:1;stroke:#aa8800;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
id="rect1400"
width="1.5874993"
height="5.499999"
x="7.0579548"
y="1.2687082" />
x="7.3927369"
y="1.7978847" />
<rect
style="display:inline;opacity:1;fill:#000000;fill-opacity:1;stroke:#aa8800;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
id="rect1400-6"
width="1.5874993"
height="5.499999"
x="25.221212"
y="1.39138" />
x="24.886431"
y="1.7978847" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Before After
Before After

BIN
pycalver1k2_128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

View file

@ -12,3 +12,4 @@ typing; python_version < "3.5"
click
toml
six
lexid

View file

@ -1,3 +0,0 @@
import sys
print(sys.version)

View file

@ -1,7 +1,7 @@
# This file is part of the pycalver project
# https://gitlab.com/mbarkhau/pycalver
# https://github.com/mbarkhau/pycalver
#
# Copyright (c) 2019 Manuel Barkhau (mbarkhau@gmail.com) - MIT License
# Copyright (c) 2018-2020 Manuel Barkhau (mbarkhau@gmail.com) - MIT License
# SPDX-License-Identifier: MIT
import os
@ -26,48 +26,11 @@ install_requires = [
]
package_dir = {"": "src"}
if any(arg.startswith("bdist") for arg in sys.argv):
try:
import lib3to6
package_dir = lib3to6.fix(package_dir)
except ImportError:
if sys.version_info < (3, 6):
raise
else:
sys.stderr.write((
"WARNING: Creating non-universal bdist of pycalver, "
"this should only be used for development.\n"
))
long_description = "\n\n".join((read("README.md"), read("CHANGELOG.md")))
setuptools.setup(
name="pycalver",
license="MIT",
author="Manuel Barkhau",
author_email="mbarkhau@gmail.com",
url="https://gitlab.com/mbarkhau/pycalver",
version="202007.36",
keywords="version versioning bumpversion calver",
description="CalVer for python libraries.",
long_description=long_description,
long_description_content_type="text/markdown",
packages=['pycalver'],
package_dir=package_dir,
install_requires=install_requires,
entry_points="""
[console_scripts]
pycalver=pycalver.cli:cli
""",
zip_safe=True,
# See https://pypi.python.org/pypi?%3Aaction=list_classifiers
classifiers=[
# See https://pypi.python.org/pypi?%3Aaction=list_classifiers
classifiers = [
"Development Status :: 4 - Beta",
"Environment :: Console",
"Environment :: Other Environment",
@ -84,5 +47,53 @@ setuptools.setup(
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Software Development :: Libraries",
"Topic :: Software Development :: Libraries :: Python Modules",
],
]
package_dir = {"": "src"}
is_lib3to6_fix_required = (
any(arg.startswith("bdist") for arg in sys.argv)
and (
"Programming Language :: Python :: 2.7" in classifiers
or "Programming Language :: Python :: 2" in classifiers
)
)
if is_lib3to6_fix_required:
try:
import lib3to6
package_dir = lib3to6.fix(package_dir)
except ImportError:
if sys.version_info < (3, 6):
raise
else:
sys.stderr.write((
"WARNING: Creating non-universal bdist of pycalver, "
"this should only be used for development.\n"
))
setuptools.setup(
name="pycalver",
license="MIT",
author="Manuel Barkhau",
author_email="mbarkhau@gmail.com",
url="https://gitlab.com/mbarkhau/pycalver",
version="202007.36",
keywords="version versioning bumpversion calver",
description="CalVer for python libraries.",
long_description=long_description,
long_description_content_type="text/markdown",
packages=setuptools.find_packages("src/"),
package_dir=package_dir,
install_requires=install_requires,
entry_points="""
[console_scripts]
pycalver=pycalver.cli:cli
""",
python_requires=">=2.7",
zip_safe=True,
classifiers=classifiers,
)

View file

@ -1,7 +1,7 @@
# This file is part of the pycalver project
# https://gitlab.com/mbarkhau/pycalver
# https://github.com/mbarkhau/pycalver
#
# Copyright (c) 2019 Manuel Barkhau (mbarkhau@gmail.com) - MIT License
# Copyright (c) 2018-2020 Manuel Barkhau (mbarkhau@gmail.com) - MIT License
# SPDX-License-Identifier: MIT
"""PyCalVer: CalVer for Python Packages."""

View file

@ -1,8 +1,8 @@
#!/usr/bin/env python
# This file is part of the pycalver project
# https://gitlab.com/mbarkhau/pycalver
# https://github.com/mbarkhau/pycalver
#
# Copyright (c) 2019 Manuel Barkhau (mbarkhau@gmail.com) - MIT License
# Copyright (c) 2018-2020 Manuel Barkhau (mbarkhau@gmail.com) - MIT License
# SPDX-License-Identifier: MIT
"""
__main__ module for PyCalVer.

View file

@ -1,8 +1,8 @@
#!/usr/bin/env python
# This file is part of the pycalver project
# https://gitlab.com/mbarkhau/pycalver
# https://github.com/mbarkhau/pycalver
#
# Copyright (c) 2019 Manuel Barkhau (mbarkhau@gmail.com) - MIT License
# Copyright (c) 2018-2020 Manuel Barkhau (mbarkhau@gmail.com) - MIT License
# SPDX-License-Identifier: MIT
"""
CLI module for PyCalVer.

View file

@ -234,6 +234,8 @@ def _parse_config(raw_cfg: RawConfig) -> Config:
version_str: str = raw_cfg['current_version']
version_str = raw_cfg['current_version'] = version_str.strip("'\" ")
# TODO (mb 2020-09-06): new style pattern by default
# version_pattern: str = raw_cfg.get('version_pattern', "vYYYY0M.BUILD[-TAG]")
version_pattern: str = raw_cfg.get('version_pattern', "{pycalver}")
version_pattern = raw_cfg['version_pattern'] = version_pattern.strip("'\" ")

View file

@ -1,169 +0,0 @@
# This file is part of the pycalver project
# https://gitlab.com/mbarkhau/pycalver
#
# Copyright (c) 2019 Manuel Barkhau (mbarkhau@gmail.com) - MIT License
# SPDX-License-Identifier: MIT
"""A scheme for lexically ordered numerical ids.
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:
"""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_val = int(prev_id, 10)
_maybe_next_id_val = int(_prev_id_val) + 1
_maybe_next_id_str = f"{_maybe_next_id_val:0{num_digits}}"
_is_padding_ok = prev_id[0] == _maybe_next_id_str[0]
_next_id_str: str
if _is_padding_ok:
_next_id_str = _maybe_next_id_str
else:
_next_id_str = str(_maybe_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)
else:
return int(lex_id[1:], 10)
def _main() -> None:
_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)
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()

View file

@ -1,7 +1,7 @@
# This file is part of the pycalver project
# https://gitlab.com/mbarkhau/pycalver
# https://github.com/mbarkhau/pycalver
#
# Copyright (c) 2019 Manuel Barkhau (mbarkhau@gmail.com) - MIT License
# Copyright (c) 2018-2020 Manuel Barkhau (mbarkhau@gmail.com) - MIT License
# SPDX-License-Identifier: MIT
"""Parse PyCalVer strings from files."""
@ -22,6 +22,8 @@ class PatternMatch(typ.NamedTuple):
PatternMatches = typ.Iterable[PatternMatch]
RegexpPatterns = typ.List[typ.Pattern[str]]
def _iter_for_pattern(lines: typ.List[str], pattern: str) -> PatternMatches:
# The pattern is escaped, so that everything besides the format

View file

@ -1,7 +1,7 @@
# This file is part of the pycalver project
# https://gitlab.com/mbarkhau/pycalver
# https://github.com/mbarkhau/pycalver
#
# Copyright (c) 2019 Manuel Barkhau (mbarkhau@gmail.com) - MIT License
# Copyright (c) 2018-2020 Manuel Barkhau (mbarkhau@gmail.com) - MIT License
# SPDX-License-Identifier: MIT
"""Compose Regular Expressions from Patterns.

View file

@ -1,7 +1,7 @@
# This file is part of the pycalver project
# https://gitlab.com/mbarkhau/pycalver
# https://github.com/mbarkhau/pycalver
#
# Copyright (c) 2019 Manuel Barkhau (mbarkhau@gmail.com) - MIT License
# Copyright (c) 2018-2020 Manuel Barkhau (mbarkhau@gmail.com) - MIT License
# SPDX-License-Identifier: MIT
"""Rewrite files, updating occurences of version strings."""
@ -13,8 +13,9 @@ import logging
import pathlib2 as pl
from . import parse
from . import config
from pycalver import parse
from pycalver import config
from . import version
from . import patterns
@ -53,6 +54,28 @@ class NoPatternMatch(Exception):
"""
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]
def iter_file_paths(
file_patterns: config.PatternsByGlob,
) -> typ.Iterable[typ.Tuple[pl.Path, config.Patterns]]:
for globstr, pattern_strs in file_patterns.items():
file_paths = glob.glob(globstr)
if not any(file_paths):
errmsg = f"No files found for path/glob '{globstr}'"
raise IOError(errmsg)
for file_path_str in file_paths:
file_path = pl.Path(file_path_str)
yield (file_path, pattern_strs)
def rewrite_lines(
pattern_strs: typ.List[str], new_vinfo: version.VersionInfo, old_lines: typ.List[str]
) -> typ.List[str]:
@ -88,15 +111,6 @@ def rewrite_lines(
return new_lines
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]
def rfd_from_content(
pattern_strs: typ.List[str], new_vinfo: version.VersionInfo, content: str
) -> RewrittenFileData:
@ -122,19 +136,6 @@ def rfd_from_content(
return RewrittenFileData("<path>", line_sep, old_lines, new_lines)
def _iter_file_paths(
file_patterns: config.PatternsByGlob,
) -> typ.Iterable[typ.Tuple[pl.Path, config.Patterns]]:
for globstr, pattern_strs in file_patterns.items():
file_paths = glob.glob(globstr)
if not any(file_paths):
errmsg = f"No files found for path/glob '{globstr}'"
raise IOError(errmsg)
for file_path_str in file_paths:
file_path = pl.Path(file_path_str)
yield (file_path, pattern_strs)
def iter_rewritten(
file_patterns: config.PatternsByGlob, new_vinfo: version.VersionInfo
) -> typ.Iterable[RewrittenFileData]:
@ -144,23 +145,23 @@ def iter_rewritten(
>>> new_vinfo = version.parse_version_info("v201809.0123")
>>> rewritten_datas = iter_rewritten(file_patterns, new_vinfo)
>>> rfd = list(rewritten_datas)[0]
>>> assert rfd.new_lines == [
>>> expected = [
... '# This file is part of the pycalver project',
... '# https://gitlab.com/mbarkhau/pycalver',
... '# https://github.com/mbarkhau/pycalver',
... '#',
... '# Copyright (c) 2019 Manuel Barkhau (mbarkhau@gmail.com) - MIT License',
... '# Copyright (c) 2018-2020 Manuel Barkhau (mbarkhau@gmail.com) - MIT License',
... '# SPDX-License-Identifier: MIT',
... '"""PyCalVer: CalVer for Python Packages."""',
... '',
... '__version__ = "v201809.0123"',
... '',
... ]
>>>
>>> assert rfd.new_lines[:len(expected)] == expected
'''
fobj: typ.IO[str]
for file_path, pattern_strs in _iter_file_paths(file_patterns):
for file_path, pattern_strs in iter_file_paths(file_patterns):
with file_path.open(mode="rt", encoding="utf-8") as fobj:
content = fobj.read()
@ -204,7 +205,7 @@ def diff(new_vinfo: version.VersionInfo, file_patterns: config.PatternsByGlob) -
full_diff = ""
fobj: typ.IO[str]
for file_path, pattern_strs in sorted(_iter_file_paths(file_patterns)):
for file_path, pattern_strs in sorted(iter_file_paths(file_patterns)):
with file_path.open(mode="rt", encoding="utf-8") as fobj:
content = fobj.read()

View file

@ -1,7 +1,7 @@
# This file is part of the pycalver project
# https://gitlab.com/mbarkhau/pycalver
# https://github.com/mbarkhau/pycalver
#
# Copyright (c) 2019 Manuel Barkhau (mbarkhau@gmail.com) - MIT License
# Copyright (c) 2018-2020 Manuel Barkhau (mbarkhau@gmail.com) - MIT License
# SPDX-License-Identifier: MIT
#
# pycalver/vcs.py (this file) is based on code from the
@ -15,11 +15,14 @@ mercurial, then the git terms are used. For example "fetch"
"""
import os
import sys
import typing as typ
import logging
import tempfile
import subprocess as sp
from pycalver import config
logger = logging.getLogger("pycalver.vcs")
@ -179,3 +182,57 @@ def get_vcs_api() -> VCSAPI:
return vcs_api
raise OSError("No such directory .git/ or .hg/ ")
# cli helper methods
def assert_not_dirty(vcs_api: VCSAPI, filepaths: typ.Set[str], allow_dirty: bool) -> None:
dirty_files = vcs_api.status(required_files=filepaths)
if dirty_files:
logger.warning(f"{vcs_api.name} working directory is not clean. Uncomitted file(s):")
for dirty_file in dirty_files:
logger.warning(" " + dirty_file)
if not allow_dirty and dirty_files:
sys.exit(1)
dirty_pattern_files = set(dirty_files) & filepaths
if dirty_pattern_files:
logger.error("Not commiting when pattern files are dirty:")
for dirty_file in dirty_pattern_files:
logger.warning(" " + dirty_file)
sys.exit(1)
def commit(
cfg : config.Config,
vcs_api : VCSAPI,
filepaths : typ.Set[str],
new_version : str,
commit_message: str,
) -> None:
for filepath in filepaths:
vcs_api.add(filepath)
vcs_api.commit(commit_message)
if cfg.commit and cfg.tag:
vcs_api.tag(new_version)
if cfg.commit and cfg.tag and cfg.push:
vcs_api.push(new_version)
def get_tags(fetch: bool) -> typ.List[str]:
try:
vcs_api = get_vcs_api()
logger.debug(f"vcs found: {vcs_api.name}")
if fetch:
logger.info("fetching tags from remote (to turn off use: -n / --no-fetch)")
vcs_api.fetch()
return vcs_api.ls_tags()
except OSError:
logger.debug("No vcs found")
return []

View file

@ -1,7 +1,7 @@
# This file is part of the pycalver project
# https://gitlab.com/mbarkhau/pycalver
# https://github.com/mbarkhau/pycalver
#
# Copyright (c) 2019 Manuel Barkhau (mbarkhau@gmail.com) - MIT License
# Copyright (c) 2018-2020 Manuel Barkhau (mbarkhau@gmail.com) - MIT License
# SPDX-License-Identifier: MIT
"""Functions related to version string manipulation."""
@ -9,9 +9,9 @@ import typing as typ
import logging
import datetime as dt
import lexid
import pkg_resources
from . import lex_id
from . import patterns
logger = logging.getLogger("pycalver.version")
@ -482,7 +482,7 @@ def incr(
else:
logger.warning(f"Version appears to be from the future '{old_version}'")
cur_vinfo = cur_vinfo._replace(bid=lex_id.next_id(cur_vinfo.bid))
cur_vinfo = cur_vinfo._replace(bid=lexid.next_id(cur_vinfo.bid))
if major:
cur_vinfo = cur_vinfo._replace(major=cur_vinfo.major + 1, minor=0, patch=0)

View file

@ -0,0 +1,8 @@
# This file is part of the pycalver project
# https://github.com/mbarkhau/pycalver
#
# Copyright (c) 2018-2020 Manuel Barkhau (mbarkhau@gmail.com) - MIT License
# SPDX-License-Identifier: MIT
"""PyCalVer: CalVer for Python Packages."""
__version__ = "v202007.1036"

53
src/pycalver2/cli.py Normal file
View file

@ -0,0 +1,53 @@
#!/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 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 = v2version.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)

205
src/pycalver2/patterns.py Normal file
View file

@ -0,0 +1,205 @@
# 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.
>>> version_info = PYCALVER_RE.match("v201712.0123-alpha").groupdict()
>>> assert version_info == {
... "pycalver" : "v201712.0123-alpha",
... "vYYYYMM" : "v201712",
... "year" : "2017",
... "month" : "12",
... "build" : ".0123",
... "build_no" : "0123",
... "release" : "-alpha",
... "release_tag" : "alpha",
... }
>>>
>>> version_info = PYCALVER_RE.match("v201712.0033").groupdict()
>>> assert version_info == {
... "pycalver" : "v201712.0033",
... "vYYYYMM" : "v201712",
... "year" : "2017",
... "month" : "12",
... "build" : ".0033",
... "build_no" : "0033",
... "release" : None,
... "release_tag": None,
... }
"""
import re
import typing as typ
# https://regex101.com/r/fnj60p/10
PYCALVER_PATTERN = r"""
\b
(?P<pycalver>
(?P<vYYYYMM>
v # "v" version prefix
(?P<year>\d{4})
(?P<month>\d{2})
)
(?P<build>
\. # "." build nr prefix
(?P<build_no>\d{4,})
)
(?P<release>
\- # "-" release prefix
(?P<release_tag>alpha|beta|dev|rc|post)
)?
)(?:\s|$)
"""
PYCALVER_RE: typ.Pattern[str] = re.compile(PYCALVER_PATTERN, flags=re.VERBOSE)
PATTERN_ESCAPES = [
("\u005c", "\u005c\u005c"),
("-" , "\u005c-"),
("." , "\u005c."),
("+" , "\u005c+"),
("*" , "\u005c*"),
("?" , "\u005c?"),
("{" , "\u005c{"),
("}" , "\u005c}"),
("[" , "\u005c["),
("]" , "\u005c]"),
("(" , "\u005c("),
(")" , "\u005c)"),
]
# NOTE (mb 2020-09-04): These are depricated in favour of explicit patterns
COMPOSITE_PART_PATTERNS = {
'pep440_pycalver': r"{year}{month}\.{BID}(?:{pep440_tag})?",
'pycalver' : r"v{year}{month}\.{bid}(?:-{tag})?",
'calver' : r"v{year}{month}",
'semver' : r"{MAJOR}\.{MINOR}\.{PATCH}",
'release_tag' : r"{tag}",
'build' : r"\.{bid}",
'release' : r"(?:-{tag})?",
# depricated
'pep440_version': r"{year}{month}\.{BID}(?:{pep440_tag})?",
}
PART_PATTERNS = {
# recommended (based on calver.org)
'YYYY': r"[1-9]\d{3}",
'YY' : r"\d{1,2}",
'0Y' : r"\d{2}",
'Q' : r"[1-4]",
'MM' : r"(?:[1-9]|1[0-2])",
'0M' : r"(?:0[1-9]|1[0-2])",
'DD' : r"([1-9]|[1-2][0-9]|3[0-1])",
'0D' : r"(0[1-9]|[1-2][0-9]|3[0-1])",
'JJJ' : r"(?:[1-9]\d|[1-9]|[1-2]\d\d|3[0-5][0-9]|36[0-6])",
'00J' : r"(?:[0-2]\d\d|3[0-5][0-9]|36[0-6])",
'WW' : r"(?:[1-9]|[1-4]\d|5[0-2])",
'0W' : r"(?:[0-4]\d|5[0-2])",
'UU' : r"(?:[1-9]|[0-4]\d|5[0-2])",
'0U' : r"(?:[0-4]\d|5[0-2])",
'VV' : r"(?:[1-9]|[1-4]\d|5[0-3])",
'0V' : r"(?:[0-4]\d|5[0-3])",
'GGGG': r"[1-9]\d{3}",
'GG' : r"\d{1,2}",
'0G' : r"\d{2}",
# non calver parts
'MAJOR': r"\d+",
'MINOR': r"\d+",
'PATCH': r"\d+",
'MICRO': r"\d+",
'BUILD': r"\d+",
'TAG' : r"(?:alpha|beta|dev|rc|post|final)",
'PYTAG': r"(?:a|b|dev|rc|post)?\d*",
# supported (but legacy)
'year' : r"\d{4}",
'month' : r"(?:0[0-9]|1[0-2])",
'month_short': r"(?:1[0-2]|[1-9])",
'build_no' : r"\d{4,}",
'pep440_tag' : r"(?:a|b|dev|rc|post)?\d*",
'tag' : r"(?:alpha|beta|dev|rc|post|final)",
'yy' : r"\d{2}",
'yyyy' : r"\d{4}",
'quarter' : r"[1-4]",
'iso_week' : r"(?:[0-4]\d|5[0-3])",
'us_week' : r"(?:[0-4]\d|5[0-3])",
'dom' : r"(0[1-9]|[1-2][0-9]|3[0-1])",
'dom_short' : r"([1-9]|[1-2][0-9]|3[0-1])",
'doy' : r"(?:[0-2]\d\d|3[0-5][0-9]|36[0-6])",
'doy_short' : r"(?:[0-2]\d\d|3[0-5][0-9]|36[0-6])",
'bid' : r"\d{4,}",
# dropped support (never documented)
# 'BID' : r"[1-9]\d*",
# 'MM' : r"\d{2,}",
# 'MMM' : r"\d{3,}",
# 'MMMM' : r"\d{4,}",
# 'MMMMM' : r"\d{5,}",
# 'PP' : r"\d{2,}",
# 'PPP' : r"\d{3,}",
# 'PPPP' : r"\d{4,}",
# 'PPPPP' : r"\d{5,}",
# 'BB' : r"[1-9]\d{1,}",
# 'BBB' : r"[1-9]\d{2,}",
# 'BBBB' : r"[1-9]\d{3,}",
# 'BBBBB' : r"[1-9]\d{4,}",
# 'BBBBBB' : r"[1-9]\d{5,}",
# 'BBBBBBB' : r"[1-9]\d{6,}",
}
FULL_PART_FORMATS = {
'pep440_pycalver': "{year}{month:02}.{BID}{pep440_tag}",
'pycalver' : "v{year}{month:02}.{bid}{release}",
'calver' : "v{year}{month:02}",
'semver' : "{MAJOR}.{MINOR}.{PATCH}",
'release_tag' : "{tag}",
'build' : ".{bid}",
# NOTE (mb 2019-01-04): since release is optional, it
# is treated specially in version.format
# 'release' : "-{tag}",
'month' : "{month:02}",
'month_short': "{month}",
'build_no' : "{bid}",
'iso_week' : "{iso_week:02}",
'us_week' : "{us_week:02}",
'dom' : "{dom:02}",
'doy' : "{doy:03}",
'dom_short' : "{dom}",
'doy_short' : "{doy}",
# depricated
'pep440_version': "{year}{month:02}.{BID}{pep440_tag}",
'version' : "v{year}{month:02}.{bid}{release}",
}
def _replace_pattern_parts(pattern: str) -> str:
for part_name, part_pattern in PART_PATTERNS.items():
named_part_pattern = f"(?P<{part_name}>{part_pattern})"
placeholder = "\u005c{" + part_name + "\u005c}"
pattern = pattern.replace(placeholder, named_part_pattern)
return 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) -> typ.Pattern[str]:
pattern_str = compile_pattern_str(pattern)
return re.compile(pattern_str)
def _init_composite_patterns() -> None:
for part_name, part_pattern in COMPOSITE_PART_PATTERNS.items():
part_pattern = part_pattern.replace("{", "\u005c{").replace("}", "\u005c}")
pattern_str = _replace_pattern_parts(part_pattern)
PART_PATTERNS[part_name] = pattern_str
_init_composite_patterns()

175
src/pycalver2/rewrite.py Normal file
View file

@ -0,0 +1,175 @@
# 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
from pycalver import parse
from pycalver import config
from pycalver import rewrite as v1rewrite
from pycalver2 import version
from pycalver2 import patterns
logger = logging.getLogger("pycalver2.rewrite")
def rewrite_lines(
pattern_strs: typ.List[str], new_vinfo: version.VersionInfo, old_lines: typ.List[str]
) -> typ.List[str]:
"""TODO reenable doctest"""
pass
"""Replace occurances of pattern_strs in old_lines with new_vinfo.
>>> new_vinfo = version.parse_version_info("v201811.0123-beta")
>>> pattern_strs = ['__version__ = "{pycalver}"']
>>> rewrite_lines(pattern_strs, new_vinfo, ['__version__ = "v201809.0002-beta"'])
['__version__ = "v201811.0123-beta"']
>>> pattern_strs = ['__version__ = "{pep440_version}"']
>>> rewrite_lines(pattern_strs, new_vinfo, ['__version__ = "201809.2b0"'])
['__version__ = "201811.123b0"']
"""
new_lines = old_lines[:]
found_patterns = set()
re_patterns = [patterns.compile_pattern(p) for p in pattern_strs]
for match in parse.iter_matches(old_lines, re_patterns):
found_patterns.add(match.pattern)
replacement = version.format_version(new_vinfo, match.pattern)
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 = patterns.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: version.VersionInfo, content: str
) -> v1rewrite.RewrittenFileData:
"""TODO reenable doctest"""
pass
r"""Rewrite pattern occurrences with version string.
>>> new_vinfo = version.parse_version_info("v201809.0123")
>>> pattern_strs = ['__version__ = "{pycalver}"']
>>> content = '__version__ = "v201809.0001-alpha"'
>>> rfd = rfd_from_content(pattern_strs, new_vinfo, content)
>>> rfd.new_lines
['__version__ = "v201809.0123"']
>>>
>>> new_vinfo = version.parse_version_info("v1.2.3", "v{semver}")
>>> pattern_strs = ['__version__ = "v{semver}"']
>>> 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: version.VersionInfo
) -> typ.Iterable[v1rewrite.RewrittenFileData]:
"""TODO reenable doctest"""
pass
r'''Iterate over files with version string replaced.
>>> file_patterns = {"src/pycalver/__init__.py": ['__version__ = "{pycalver}"']}
>>> new_vinfo = version.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://gitlab.com/mbarkhau/pycalver',
... '#',
... '# Copyright (c) 2019 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: version.VersionInfo, file_patterns: config.PatternsByGlob) -> str:
"""TODO reenable doctest"""
pass
r"""Generate diffs of rewritten files.
>>> new_vinfo = version.parse_version_info("v201809.0123")
>>> file_patterns = {"src/pycalver/__init__.py": ['__version__ = "{pycalver}"']}
>>> 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: version.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)

590
src/pycalver2/version.py Normal file
View file

@ -0,0 +1,590 @@
# 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 pkg_resources
from . import patterns
logger = logging.getLogger("pycalver.version")
# The test suite may replace this.
TODAY = dt.datetime.utcnow().date()
PATTERN_PART_FIELDS = {
'YYYY' : 'year_y',
'YY' : 'year_y',
'0Y' : 'year_y',
'Q' : 'quarter',
'MM' : 'month',
'0M' : 'month',
'DD' : 'dom',
'0D' : 'dom',
'JJJ' : 'doy',
'00J' : 'doy',
'MAJOR': 'major',
'MINOR': 'minor',
'PATCH': 'patch',
'MICRO': 'patch',
'BUILD': 'bid',
'TAG' : 'tag',
'PYTAG': 'pytag',
'WW' : 'week_w',
'0W' : 'week_w',
'UU' : 'week_u',
'0U' : 'week_u',
'VV' : 'week_v',
'0V' : 'week_v',
'GGGG' : 'year_g',
'GG' : 'year_g',
'0G' : 'year_g',
}
ID_FIELDS_BY_PART = {
'MAJOR': 'major',
'MINOR': 'minor',
'PATCH': 'patch',
'MICRO': 'patch',
}
ZERO_VALUES = {
'major': "0",
'minor': "0",
'patch': "0",
'TAG' : "final",
'PYTAG': "",
}
class CalendarInfo(typ.NamedTuple):
"""Container for calendar components of version strings."""
year_y : int
year_g : int
quarter: int
month : int
dom : int
doy : int
week_w : int
week_u : int
week_v : int
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:
"""TODO reenable doctest"""
pass
"""Generate calendar components for current date.
>>> from datetime import date
>>> c = cal_info(date(2019, 1, 5))
>>> (c.year_y, c.quarter, c.month, c.dom, c.doy, c.iso_week, c.us_week)
(2019, 1, 1, 5, 5, 0, 0)
>>> c = cal_info(date(2019, 1, 6))
>>> (c.year_y, c.quarter, c.month, c.dom, c.doy, c.iso_week, c.us_week)
(2019, 1, 1, 6, 6, 0, 1)
>>> c = cal_info(date(2019, 1, 7))
>>> (c.year_y, c.quarter, c.month, c.dom, c.doy, c.iso_week, c.us_week)
(2019, 1, 1, 7, 7, 1, 1)
>>> c = cal_info(date(2019, 4, 7))
>>> (c.year_y, c.quarter, c.month, c.dom, c.doy, c.iso_week, c.us_week)
(2019, 2, 4, 7, 97, 13, 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)
class VersionInfo(typ.NamedTuple):
"""Container for parsed version string."""
year_y : typ.Optional[int]
year_g : typ.Optional[int]
quarter: typ.Optional[int]
month : typ.Optional[int]
dom : typ.Optional[int]
doy : typ.Optional[int]
week_w : typ.Optional[int]
week_u : typ.Optional[int]
week_v : typ.Optional[int]
major : int
minor : int
patch : int
bid : str
tag : str
pytag : str
FieldKey = str
MatchGroupKey = str
MatchGroupStr = str
PatternGroups = typ.Dict[MatchGroupKey, MatchGroupStr]
FieldValues = typ.Dict[FieldKey , MatchGroupStr]
def _parse_field_values(field_values: FieldValues) -> VersionInfo:
fvals = field_values
tag = fvals.get('tag')
if tag is None:
tag = "final"
tag = TAG_ALIASES.get(tag, tag)
assert tag is not None
# TODO (mb 2020-09-06): parts of course
pytag = "TODO"
bid = fvals['bid'] if 'bid' in fvals else "1001"
year_y = int(fvals['year_y']) if 'year_y' in fvals else None
year_g = int(fvals['year_g']) if 'year_g' in fvals else None
doy = int(fvals['doy' ]) if 'doy' in fvals else None
date: typ.Optional[dt.date] = None
month: typ.Optional[int] = None
dom : typ.Optional[int] = None
week_w: typ.Optional[int] = None
week_u: typ.Optional[int] = None
week_v: typ.Optional[int] = 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
quarter = int(fvals['quarter']) if 'quarter' in fvals else None
if quarter is None and month:
quarter = _quarter_from_month(month)
if year_y and month and dom:
date = dt.date(year_y, month, dom)
if date:
# derive all fields from other previous values
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)
year_g = int(date.strftime("%G"), base=10)
major = int(fvals['major']) if 'major' in fvals else 0
minor = int(fvals['minor']) if 'minor' in fvals else 0
patch = int(fvals['patch']) if 'patch' in fvals else 0
return 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,
bid=bid,
tag=tag,
pytag=pytag,
)
def _is_calver(nfo: typ.Union[CalendarInfo, VersionInfo]) -> bool:
"""TODO reenable doctest"""
pass
"""Check pattern for any calendar based parts.
>>> _is_calver(cal_info())
True
>>> vnfo = _parse_version_info({'year': "2018", 'month': "11", 'bid': "0018"})
>>> _is_calver(vnfo)
True
>>> vnfo = _parse_version_info({'MAJOR': "1", 'MINOR': "023", 'PATCH': "45"})
>>> _is_calver(vnfo)
False
"""
for field in CalendarInfo._fields:
maybe_val: typ.Any = getattr(nfo, field, None)
if isinstance(maybe_val, int):
return True
return False
TAG_ALIASES: typ.Dict[str, str] = {
'a' : "alpha",
'b' : "beta",
'pre': "rc",
}
PEP440_TAGS: typ.Dict[str, str] = {
'alpha': "a",
'beta' : "b",
'final': "",
'rc' : "rc",
'dev' : "dev",
'post' : "post",
}
VersionInfoKW = typ.Dict[str, typ.Union[str, int, None]]
class PatternError(Exception):
pass
def _parse_pattern_groups(pattern_groups: PatternGroups) -> FieldValues:
for part_name in pattern_groups.keys():
is_valid_part_name = (
part_name in patterns.COMPOSITE_PART_PATTERNS or part_name in PATTERN_PART_FIELDS
)
if not is_valid_part_name:
err_msg = f"Invalid part '{part_name}'"
raise PatternError(err_msg)
field_value_items = [
(field_name, pattern_groups[part_name])
for part_name, field_name in PATTERN_PART_FIELDS.items()
if part_name in pattern_groups.keys()
]
all_fields = [field_name for field_name, _ in field_value_items]
unique_fields = set(all_fields)
duplicate_fields = [f for f in unique_fields if all_fields.count(f) > 1]
if any(duplicate_fields):
err_msg = f"Multiple parts for same field {duplicate_fields}."
raise PatternError(err_msg)
return dict(field_value_items)
def _parse_version_info(pattern_groups: PatternGroups) -> VersionInfo:
"""TODO reenable doctest"""
pass
"""Parse normalized VersionInfo from groups of a matched pattern.
>>> vnfo = _parse_version_info({'year': "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': "2018", 'doy': "11", 'bid': "099", 'tag': "b"})
>>> (vnfo.year_y, vnfo.month, vnfo.dom, vnfo.bid, vnfo.tag)
(2018, 1, 11, '099', 'beta')
>>> 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", 'MMM': "023", 'PPPP': "0045"})
>>> (vnfo.major, vnfo.minor, vnfo.patch)
(1, 23, 45)
"""
field_values = _parse_pattern_groups(pattern_groups)
return _parse_field_values(field_values)
def parse_version_info(version_str: str, pattern: str = "{pycalver}") -> VersionInfo:
"""TODO reenable doctest"""
pass
"""Parse normalized VersionInfo.
>>> vnfo = parse_version_info("v201712.0033-beta", pattern="{pycalver}")
>>> assert vnfo == _parse_version_info({'year': 2017, 'month': 12, 'bid': "0033", 'tag': "beta"})
>>> vnfo = parse_version_info("1.23.456", pattern="{semver}")
>>> assert vnfo == _parse_version_info({'MAJOR': "1", 'MINOR': "23", 'PATCH': "456"})
"""
regex = patterns.compile_pattern(pattern)
match = regex.match(version_str)
if match is None:
err_msg = (
f"Invalid version string '{version_str}' for pattern '{pattern}'/'{regex.pattern}'"
)
raise PatternError(err_msg)
return _parse_version_info(match.groupdict())
def is_valid(version_str: str, pattern: str = "{pycalver}") -> bool:
"""TODO reenable doctest"""
pass
"""Check if a version matches a pattern.
>>> is_valid("v201712.0033-beta", pattern="{pycalver}")
True
>>> is_valid("v201712.0033-beta", pattern="{semver}")
False
>>> is_valid("1.2.3", pattern="{semver}")
True
>>> is_valid("v201712.0033-beta", pattern="{semver}")
False
"""
try:
parse_version_info(version_str, pattern)
return True
except PatternError:
return False
TemplateKwargs = typ.Dict[str, typ.Union[str, int, None]]
def _derive_template_kwargs(vinfo: VersionInfo) -> TemplateKwargs:
"""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'.
"""
kwargs: TemplateKwargs = vinfo._asdict()
tag = vinfo.tag
kwargs['TAG'] = tag
if tag == 'final':
kwargs['PYTAG'] = ""
else:
kwargs['PYTAG'] = PEP440_TAGS[tag] + "0"
year_y = vinfo.year_y
if year_y:
kwargs['0Y' ] = str(year_y)[-2:]
kwargs['YY' ] = int(str(year_y)[-2:])
kwargs['YYYY'] = year_y
year_g = vinfo.year_g
if year_g:
kwargs['0G' ] = str(year_g)[-2:]
kwargs['GG' ] = int(str(year_g)[-2:])
kwargs['GGGG'] = year_g
kwargs['BUILD'] = int(vinfo.bid, 10)
for part_name, field in ID_FIELDS_BY_PART.items():
val = kwargs[field]
if part_name.lower() == field.lower():
if isinstance(val, str):
kwargs[part_name] = int(val, base=10)
else:
kwargs[part_name] = val
else:
assert len(set(part_name)) == 1
padded_len = len(part_name)
kwargs[part_name] = str(val).zfill(padded_len)
return kwargs
def _compile_format_template(pattern: str, kwargs: TemplateKwargs) -> str:
# NOTE (mb 2020-09-04): Some parts are optional, we need the kwargs to
# determine if part is set to its zero value
format_tmpl = pattern
for part_name, full_part_format in patterns.FULL_PART_FORMATS.items():
format_tmpl = format_tmpl.replace("{" + part_name + "}", full_part_format)
return format_tmpl
def format_version(vinfo: VersionInfo, pattern: str) -> str:
"""TODO reenable doctest"""
pass
"""Generate version string.
>>> import datetime as dt
>>> vinfo = parse_version_info("v201712.0033-beta", pattern="{pycalver}")
>>> vinfo_a = vinfo._replace(**cal_info(date=dt.date(2017, 1, 1))._asdict())
>>> vinfo_b = vinfo._replace(**cal_info(date=dt.date(2017, 12, 31))._asdict())
>>> format_version(vinfo_a, pattern="v{yy}.{BID}{release}")
'v17.33-beta'
>>> format_version(vinfo_a, pattern="vYY.BUILD[-TAG]")
'v17.33-beta'
>>> format_version(vinfo_a, pattern="YYYY0M.BUILD[PYTAG]")
'201701.33b0'
>>> format_version(vinfo_a, pattern="{pycalver}")
'v201701.0033-beta'
>>> format_version(vinfo_b, pattern="{pycalver}")
'v201712.0033-beta'
>>> format_version(vinfo_a, pattern="v{year}w{iso_week}.{BID}{release}")
'v2017w00.33-beta'
>>> format_version(vinfo_a, pattern="vYYYYwWW.BUILD[-TAG]")
'v2017w00.33-beta'
>>> format_version(vinfo_b, pattern="v{year}w{iso_week}.{BID}{release}")
'v2017w52.33-beta'
>>> format_version(vinfo_b, pattern="vYYYYwWW.BUILD[-TAG]")
'v2017w52.33-beta'
>>> format_version(vinfo_a, pattern="v{year}d{doy}.{bid}{release}")
'v2017d001.0033-beta'
>>> format_version(vinfo_b, pattern="v{year}d{doy}.{bid}{release}")
'v2017d365.0033-beta'
>>> format_version(vinfo_a, pattern="vYYYYdJJJ.BUILD[-TAG]")
'v2017d001.0033-beta'
>>> format_version(vinfo_b, pattern="vYYYYdJJJ.BUILD[-TAG]")
'v2017d365.0033-beta'
>>> format_version(vinfo_a, pattern="vGGGGwVV.BUILD[-TAG]")
'v2016w52.0033-beta'
>>> vinfo_c = vinfo_b._replace(major=1, minor=2, patch=34, tag='final')
>>> format_version(vinfo_c, pattern="v{year}w{iso_week}.{BID}-{tag}")
'v2017w52.33-final'
>>> format_version(vinfo_c, pattern="v{year}w{iso_week}.{BID}{release}")
'v2017w52.33'
>>> format_version(vinfo_c, pattern="vYYYYwWW.BUILD-TAG")
'v2017w52.33-final'
>>> format_version(vinfo_c, pattern="vYYYYwWW.BUILD[-TAG]")
'v2017w52.33'
>>> format_version(vinfo_c, pattern="v{MAJOR}.{MINOR}.{PATCH}")
'v1.2.34'
>>> 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-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[.MICRO[-TAG]]")
'v1.0'
>>> format_version(vinfo_d, pattern="vMAJOR[.MINOR[.PATCH[-TAG]]]")
'v1'
"""
kwargs = _derive_template_kwargs(vinfo)
format_tmpl = _compile_format_template(pattern, kwargs)
return format_tmpl.format(**kwargs)
def incr(
old_version: str,
pattern : str = "{pycalver}",
*,
release: str = None,
major : bool = False,
minor : bool = False,
patch : 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 = 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 , cur_cal_nfo.month , cur_cal_nfo.dom)
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}'")
cur_vinfo = cur_vinfo._replace(bid=lexid.incr(cur_vinfo.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
def to_pep440(version: str) -> str:
"""Derive pep440 compliant version string from PyCalVer version string.
>>> to_pep440("v201811.0007-beta")
'201811.7b0'
"""
return str(pkg_resources.parse_version(version))

View file

@ -0,0 +1,6 @@
[pycalver]
current_version = "v2017q1.54321"
version_pattern = "vYYYYqQ.BUILD"
commit = true
tag = true
push = true

View file

@ -2,11 +2,10 @@
# pylint:disable=protected-access ; allowed for test code
import io
from test import util
from pycalver import config
from . import util
PYCALVER_TOML_FIXTURE_1 = """
[pycalver]
current_version = "v201808.0123-alpha"

View file

@ -1,67 +0,0 @@
# pylint:disable=protected-access ; allowed for test code
import random
from pycalver import lex_id
def test_next_id_basic():
assert lex_id.next_id("01") == "02"
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 _ in range(1000):
prev_id = str(random.randint(1, 100 * 1000))
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():
assert lex_id.ord_val("1" ) == 1
assert lex_id.ord_val("01" ) == 1
assert lex_id.ord_val("02" ) == 2
assert lex_id.ord_val("09" ) == 9
assert lex_id.ord_val("110") == 10
def test_main(capsys):
lex_id._main()
captured = capsys.readouterr()
assert len(captured.err) == 0
lines = iter(captured.out.splitlines())
header = next(lines)
assert "lexical" in header
assert "numerical" in header
ids = []
ord_vals = []
for line in lines:
if "..." in line:
continue
_id, _ord_val = line.split()
assert _id.endswith(_ord_val)
assert int(_ord_val) == int(_ord_val, 10)
ids.append(_id.strip())
ord_vals.append(int(_ord_val.strip()))
assert len(ids) > 0
assert sorted(ids) == ids
assert sorted(ord_vals) == ord_vals

View file

@ -2,14 +2,17 @@ import re
import pytest
from pycalver import patterns
import pycalver.patterns as v1patterns
import pycalver2.patterns as v2patterns
# TODO (mb 2020-09-06): test for v2patterns
def _part_re_by_name(name):
return re.compile(patterns.PART_PATTERNS[name])
return re.compile(v1patterns.PART_PATTERNS[name])
@pytest.mark.parametrize("part_name", patterns.PART_PATTERNS.keys())
@pytest.mark.parametrize("part_name", v1patterns.PART_PATTERNS.keys())
def test_part_compilation(part_name):
assert _part_re_by_name(part_name)
@ -64,7 +67,7 @@ PATTERN_CASES = [
@pytest.mark.parametrize("pattern_str, line, expected", PATTERN_CASES)
def test_patterns(pattern_str, line, expected):
pattern_re = patterns.compile_pattern(pattern_str)
pattern_re = v1patterns.compile_pattern(pattern_str)
result = pattern_re.search(line)
if result is None:
assert expected is None, (pattern_str, line)
@ -82,7 +85,7 @@ CLI_MAIN_FIXTURE = """
def test_pattern_escapes():
pattern = 'click.version_option(version="{pycalver}")'
pattern_re = patterns.compile_pattern(pattern)
pattern_re = v1patterns.compile_pattern(pattern)
match = pattern_re.search(CLI_MAIN_FIXTURE)
expected = 'click.version_option(version="v201812.0123-beta")'
assert match.group(0) == expected
@ -95,7 +98,7 @@ package_metadata = {"name": "mypackage", "version": "v201812.0123-beta"}
def test_curly_escapes():
pattern = 'package_metadata = {"name": "mypackage", "version": "{pycalver}"}'
pattern_re = patterns.compile_pattern(pattern)
pattern_re = v1patterns.compile_pattern(pattern)
match = pattern_re.search(CURLY_BRACE_FIXTURE)
expected = 'package_metadata = {"name": "mypackage", "version": "v201812.0123-beta"}'
assert match.group(0) == expected

View file

@ -1,12 +1,13 @@
# pylint:disable=protected-access ; allowed for test code
import copy
from test import util
from pycalver import config
from pycalver import rewrite
from pycalver import version
from . import util
from pycalver import rewrite as v1rewrite
from pycalver import version as v1version
from pycalver2 import rewrite as v2rewrite
from pycalver2 import version as v2version
REWRITE_FIXTURE = """
# SPDX-License-Identifier: MIT
@ -17,8 +18,8 @@ __version__ = "v201809.0002-beta"
def test_rewrite_lines():
old_lines = REWRITE_FIXTURE.splitlines()
patterns = ['__version__ = "{pycalver}"']
new_vinfo = version.parse_version_info("v201911.0003")
new_lines = rewrite.rewrite_lines(patterns, new_vinfo, old_lines)
new_vinfo = v1version.parse_version_info("v201911.0003")
new_lines = v1rewrite.rewrite_lines(patterns, new_vinfo, old_lines)
assert len(new_lines) == len(old_lines)
assert "v201911.0003" not in "\n".join(old_lines)
@ -31,8 +32,8 @@ def test_rewrite_final():
old_lines = REWRITE_FIXTURE.splitlines()
patterns = ['__version__ = "v{year}{month}.{build_no}-{release_tag}"']
new_vinfo = version.parse_version_info("v201911.0003")
new_lines = rewrite.rewrite_lines(patterns, new_vinfo, old_lines)
new_vinfo = v1version.parse_version_info("v201911.0003")
new_lines = v1rewrite.rewrite_lines(patterns, new_vinfo, old_lines)
assert len(new_lines) == len(old_lines)
assert "v201911.0003" not in "\n".join(old_lines)
@ -46,9 +47,8 @@ def test_iter_file_paths():
cfg = config.parse(ctx)
assert cfg
file_paths = {
str(file_path) for file_path, patterns in rewrite._iter_file_paths(cfg.file_patterns)
}
_paths_and_patterns = v1rewrite.iter_file_paths(cfg.file_patterns)
file_paths = {str(file_path) for file_path, patterns in _paths_and_patterns}
assert file_paths == {"pycalver.toml", "README.md"}
@ -59,9 +59,8 @@ def test_iter_file_globs():
cfg = config.parse(ctx)
assert cfg
file_paths = {
str(file_path) for file_path, patterns in rewrite._iter_file_paths(cfg.file_patterns)
}
_paths_and_patterns = v1rewrite.iter_file_paths(cfg.file_patterns)
file_paths = {str(file_path) for file_path, patterns in _paths_and_patterns}
assert file_paths == {
"setup.cfg",
@ -80,7 +79,7 @@ def test_error_bad_path():
(project.dir / "setup.py").unlink()
try:
list(rewrite._iter_file_paths(cfg.file_patterns))
list(v1rewrite.iter_file_paths(cfg.file_patterns))
assert False, "expected IOError"
except IOError as ex:
assert "setup.py" in str(ex)
@ -96,10 +95,10 @@ def test_error_bad_pattern():
patterns["setup.py"] = patterns["setup.py"][0] + "invalid"
try:
new_vinfo = version.parse_version_info("v201809.1234")
list(rewrite.diff(new_vinfo, patterns))
assert False, "expected rewrite.NoPatternMatch"
except rewrite.NoPatternMatch as ex:
new_vinfo = v1version.parse_version_info("v201809.1234")
list(v1rewrite.diff(new_vinfo, patterns))
assert False, "expected v1rewrite.NoPatternMatch"
except v1rewrite.NoPatternMatch as ex:
assert "setup.py" in str(ex)
@ -109,21 +108,21 @@ __version__ = "2018.0002-beta"
"""
def test_optional_release():
def test_v1_optional_release():
old_lines = OPTIONAL_RELEASE_FIXTURE.splitlines()
pattern = "{year}.{build_no}{release}"
patterns = ['__version__ = "{year}.{build_no}{release}"']
new_vinfo = version.parse_version_info("2019.0003", pattern)
new_lines = rewrite.rewrite_lines(patterns, new_vinfo, old_lines)
new_vinfo = v1version.parse_version_info("2019.0003", pattern)
new_lines = v1rewrite.rewrite_lines(patterns, new_vinfo, old_lines)
assert len(new_lines) == len(old_lines)
assert "2019.0003" not in "\n".join(old_lines)
new_text = "\n".join(new_lines)
assert "2019.0003" in new_text
new_vinfo = version.parse_version_info("2019.0004-beta", pattern)
new_lines = rewrite.rewrite_lines(patterns, new_vinfo, old_lines)
new_vinfo = v1version.parse_version_info("2019.0004-beta", pattern)
new_lines = v1rewrite.rewrite_lines(patterns, new_vinfo, old_lines)
# make sure optional release tag is added back on
assert len(new_lines) == len(old_lines)