setup to reduce code duplication

This commit is contained in:
Manuel Barkhau 2020-09-08 20:59:52 +00:00
parent e053af589e
commit 3cbf0e82b1
10 changed files with 271 additions and 267 deletions

View file

@ -7,7 +7,7 @@
import typing as typ import typing as typ
from .patterns import compile_pattern import pycalver.patterns as v1patterns
class PatternMatch(typ.NamedTuple): class PatternMatch(typ.NamedTuple):
@ -15,37 +15,33 @@ class PatternMatch(typ.NamedTuple):
lineno : int # zero based lineno : int # zero based
line : str line : str
pattern: str pattern: v1patterns.Pattern
span : typ.Tuple[int, int] span : typ.Tuple[int, int]
match : str match : str
PatternMatches = typ.Iterable[PatternMatch] 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
# string variables is treated literally.
pattern_re = compile_pattern(pattern)
def _iter_for_pattern(lines: typ.List[str], pattern: v1patterns.Pattern) -> PatternMatches:
for lineno, line in enumerate(lines): for lineno, line in enumerate(lines):
match = pattern_re.search(line) match = pattern.regexp.search(line)
if match: if match:
yield PatternMatch(lineno, line, pattern, match.span(), match.group(0)) yield PatternMatch(lineno, line, pattern, match.span(), match.group(0))
def iter_matches(lines: typ.List[str], patterns: typ.List[str]) -> PatternMatches: def iter_matches(lines: typ.List[str], patterns: typ.List[v1patterns.Pattern]) -> PatternMatches:
"""Iterate over all matches of any pattern on any line. """Iterate over all matches of any pattern on any line.
>>> import pycalver.patterns as v1patterns
>>> lines = ["__version__ = 'v201712.0002-alpha'"] >>> lines = ["__version__ = 'v201712.0002-alpha'"]
>>> patterns = ["{pycalver}", "{pep440_pycalver}"] >>> patterns = ["{pycalver}", "{pep440_pycalver}"]
>>> patterns = [v1patterns.compile_pattern(p) for p in patterns]
>>> matches = list(iter_matches(lines, patterns)) >>> matches = list(iter_matches(lines, patterns))
>>> assert matches[0] == PatternMatch( >>> assert matches[0] == PatternMatch(
... lineno = 0, ... lineno = 0,
... line = "__version__ = 'v201712.0002-alpha'", ... line = "__version__ = 'v201712.0002-alpha'",
... pattern= "{pycalver}", ... pattern= v1patterns.compile_pattern("{pycalver}"),
... span = (15, 33), ... span = (15, 33),
... match = "v201712.0002-alpha", ... match = "v201712.0002-alpha",
... ) ... )

View file

@ -177,7 +177,18 @@ PART_FORMATS = {
} }
class Pattern(typ.NamedTuple):
raw : str # "{pycalver}", "{year}.{month}", "YYYY0M.BUILD"
regexp: typ.Pattern[str]
Patterns = typ.List[typ.Pattern[str]]
def _replace_pattern_parts(pattern: str) -> str: def _replace_pattern_parts(pattern: str) -> str:
# The pattern is escaped, so that everything besides the format
# string variables is treated literally.
for part_name, part_pattern in PART_PATTERNS.items(): for part_name, part_pattern in PART_PATTERNS.items():
named_part_pattern = f"(?P<{part_name}>{part_pattern})" named_part_pattern = f"(?P<{part_name}>{part_pattern})"
placeholder = "\u005c{" + part_name + "\u005c}" placeholder = "\u005c{" + part_name + "\u005c}"
@ -192,9 +203,10 @@ def compile_pattern_str(pattern: str) -> str:
return _replace_pattern_parts(pattern) return _replace_pattern_parts(pattern)
def compile_pattern(pattern: str) -> typ.Pattern[str]: def compile_pattern(pattern: str) -> Pattern:
pattern_str = compile_pattern_str(pattern) pattern_str = compile_pattern_str(pattern)
return re.compile(pattern_str) pattern_re = re.compile(pattern_str)
return Pattern(pattern, pattern_re)
def _init_composite_patterns() -> None: def _init_composite_patterns() -> None:

View file

@ -94,9 +94,11 @@ def rewrite_lines(
new_lines = old_lines[:] new_lines = old_lines[:]
found_patterns = set() found_patterns = set()
for match in parse.iter_matches(old_lines, pattern_strs): patterns = [v1patterns.compile_pattern(p) for p in pattern_strs]
found_patterns.add(match.pattern) matches = parse.iter_matches(old_lines, patterns)
replacement = v1version.format_version(new_vinfo, match.pattern) for match in matches:
found_patterns.add(match.pattern.raw)
replacement = v1version.format_version(new_vinfo, match.pattern.raw)
span_l, span_r = match.span span_l, span_r = match.span
new_line = match.line[:span_l] + replacement + match.line[span_r:] new_line = match.line[:span_l] + replacement + match.line[span_r:]
new_lines[match.lineno] = new_line new_lines[match.lineno] = new_line

View file

@ -318,11 +318,12 @@ def parse_version_info(version_str: str, pattern: str = "{pycalver}") -> Version
>>> vnfo = parse_version_info("1.23.456", pattern="{semver}") >>> vnfo = parse_version_info("1.23.456", pattern="{semver}")
>>> assert vnfo == _parse_version_info({'MAJOR': "1", 'MINOR': "23", 'PATCH': "456"}) >>> assert vnfo == _parse_version_info({'MAJOR': "1", 'MINOR': "23", 'PATCH': "456"})
""" """
regex = v1patterns.compile_pattern(pattern) pattern_tup = v1patterns.compile_pattern(pattern)
match = regex.match(version_str) match = pattern_tup.regexp.match(version_str)
if match is None: if match is None:
err_msg = ( err_msg = (
f"Invalid version string '{version_str}' for pattern '{pattern}'/'{regex.pattern}'" f"Invalid version string '{version_str}' "
f"for pattern '{pattern}'/'{pattern_tup.regexp.pattern}'"
) )
raise PatternError(err_msg) raise PatternError(err_msg)

View file

@ -33,6 +33,8 @@
import re import re
import typing as typ import typing as typ
import pycalver.patterns as v1patterns
# https://regex101.com/r/fnj60p/10 # https://regex101.com/r/fnj60p/10
PYCALVER_PATTERN = r""" PYCALVER_PATTERN = r"""
\b \b
@ -192,9 +194,10 @@ def compile_pattern_str(pattern: str) -> str:
return _replace_pattern_parts(pattern) return _replace_pattern_parts(pattern)
def compile_pattern(pattern: str) -> typ.Pattern[str]: def compile_pattern(pattern: str) -> v1patterns.Pattern:
pattern_str = compile_pattern_str(pattern) pattern_str = compile_pattern_str(pattern)
return re.compile(pattern_str) pattern_re = re.compile(pattern_str)
return v1patterns.Pattern(pattern, pattern_re)
def _init_composite_patterns() -> None: def _init_composite_patterns() -> None:

View file

@ -9,43 +9,40 @@ import io
import typing as typ import typing as typ
import logging import logging
import pycalver2.version as v2version
import pycalver2.patterns as v2patterns
from pycalver import parse from pycalver import parse
from pycalver import config from pycalver import config
from pycalver import rewrite as v1rewrite from pycalver import rewrite as v1rewrite
from pycalver2 import version
from pycalver2 import patterns
logger = logging.getLogger("pycalver2.rewrite") logger = logging.getLogger("pycalver2.rewrite")
def rewrite_lines( def rewrite_lines(
pattern_strs: typ.List[str], pattern_strs: typ.List[str],
new_vinfo : version.VersionInfo, new_vinfo : v2version.VersionInfo,
old_lines : typ.List[str], old_lines : typ.List[str],
) -> typ.List[str]: ) -> typ.List[str]:
"""TODO reenable doctest""" # TODO reenable doctest
pass # """Replace occurances of pattern_strs in old_lines with new_vinfo.
"""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"']
>>> new_vinfo = version.parse_version_info("v201811.0123-beta") # >>> pattern_strs = ['__version__ = "{pep440_version}"']
>>> pattern_strs = ['__version__ = "{pycalver}"'] # >>> rewrite_lines(pattern_strs, new_vinfo, ['__version__ = "201809.2b0"'])
>>> rewrite_lines(pattern_strs, new_vinfo, ['__version__ = "v201809.0002-beta"']) # ['__version__ = "201811.123b0"']
['__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[:] new_lines = old_lines[:]
found_patterns = set() found_patterns = set()
# re_patterns = [patterns.compile_pattern(p) for p in pattern_strs] patterns = [v2patterns.compile_pattern(p) for p in pattern_strs]
# matches = parse.iter_matches(old_lines, re_patterns) matches = parse.iter_matches(old_lines, patterns)
matches = parse.iter_matches(old_lines, pattern_strs)
for match in matches: for match in matches:
found_patterns.add(match.pattern) found_patterns.add(match.pattern.raw)
replacement = version.format_version(new_vinfo, match.pattern) replacement = v2version.format_version(new_vinfo, match.pattern.raw)
span_l, span_r = match.span span_l, span_r = match.span
new_line = match.line[:span_l] + replacement + match.line[span_r:] new_line = match.line[:span_l] + replacement + match.line[span_r:]
new_lines[match.lineno] = new_line new_lines[match.lineno] = new_line
@ -54,7 +51,7 @@ def rewrite_lines(
if non_matched_patterns: if non_matched_patterns:
for non_matched_pattern in non_matched_patterns: for non_matched_pattern in non_matched_patterns:
logger.error(f"No match for pattern '{non_matched_pattern}'") logger.error(f"No match for pattern '{non_matched_pattern}'")
compiled_pattern_str = patterns.compile_pattern_str(non_matched_pattern) compiled_pattern_str = v2patterns.compile_pattern_str(non_matched_pattern)
logger.error(f"Pattern compiles to regex '{compiled_pattern_str}'") logger.error(f"Pattern compiles to regex '{compiled_pattern_str}'")
raise v1rewrite.NoPatternMatch("Invalid pattern(s)") raise v1rewrite.NoPatternMatch("Invalid pattern(s)")
else: else:
@ -63,28 +60,26 @@ def rewrite_lines(
def rfd_from_content( def rfd_from_content(
pattern_strs: typ.List[str], pattern_strs: typ.List[str],
new_vinfo : version.VersionInfo, new_vinfo : v2version.VersionInfo,
content : str, content : str,
) -> v1rewrite.RewrittenFileData: ) -> v1rewrite.RewrittenFileData:
"""TODO reenable doctest""" # TODO reenable doctest
pass # r"""Rewrite pattern occurrences with version string.
r"""Rewrite pattern occurrences with version string. # >>> new_vinfo = version.parse_version_info("v201809.0123")
# >>> pattern_strs = ['__version__ = "{pycalver}"']
>>> new_vinfo = version.parse_version_info("v201809.0123") # >>> content = '__version__ = "v201809.0001-alpha"'
>>> pattern_strs = ['__version__ = "{pycalver}"'] # >>> rfd = rfd_from_content(pattern_strs, new_vinfo, content)
>>> content = '__version__ = "v201809.0001-alpha"' # >>> rfd.new_lines
>>> rfd = rfd_from_content(pattern_strs, new_vinfo, content) # ['__version__ = "v201809.0123"']
>>> rfd.new_lines # >>>
['__version__ = "v201809.0123"'] # >>> new_vinfo = version.parse_version_info("v1.2.3", "v{semver}")
>>> # >>> pattern_strs = ['__version__ = "v{semver}"']
>>> new_vinfo = version.parse_version_info("v1.2.3", "v{semver}") # >>> content = '__version__ = "v1.2.2"'
>>> pattern_strs = ['__version__ = "v{semver}"'] # >>> rfd = rfd_from_content(pattern_strs, new_vinfo, content)
>>> content = '__version__ = "v1.2.2"' # >>> rfd.new_lines
>>> rfd = rfd_from_content(pattern_strs, new_vinfo, content) # ['__version__ = "v1.2.3"']
>>> rfd.new_lines # """
['__version__ = "v1.2.3"']
"""
line_sep = v1rewrite.detect_line_sep(content) line_sep = v1rewrite.detect_line_sep(content)
old_lines = content.split(line_sep) old_lines = content.split(line_sep)
new_lines = rewrite_lines(pattern_strs, new_vinfo, old_lines) new_lines = rewrite_lines(pattern_strs, new_vinfo, old_lines)
@ -93,30 +88,28 @@ def rfd_from_content(
def iter_rewritten( def iter_rewritten(
file_patterns: config.PatternsByGlob, file_patterns: config.PatternsByGlob,
new_vinfo : version.VersionInfo, new_vinfo : v2version.VersionInfo,
) -> typ.Iterable[v1rewrite.RewrittenFileData]: ) -> typ.Iterable[v1rewrite.RewrittenFileData]:
"""TODO reenable doctest""" # TODO reenable doctest
pass # r'''Iterate over files with version string replaced.
r'''Iterate over files with version string replaced. # >>> file_patterns = {"src/pycalver/__init__.py": ['__version__ = "{pycalver}"']}
# >>> new_vinfo = version.parse_version_info("v201809.0123")
>>> file_patterns = {"src/pycalver/__init__.py": ['__version__ = "{pycalver}"']} # >>> rewritten_datas = iter_rewritten(file_patterns, new_vinfo)
>>> new_vinfo = version.parse_version_info("v201809.0123") # >>> rfd = list(rewritten_datas)[0]
>>> rewritten_datas = iter_rewritten(file_patterns, new_vinfo) # >>> assert rfd.new_lines == [
>>> rfd = list(rewritten_datas)[0] # ... '# This file is part of the pycalver project',
>>> assert rfd.new_lines == [ # ... '# https://gitlab.com/mbarkhau/pycalver',
... '# 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',
... '# Copyright (c) 2019 Manuel Barkhau (mbarkhau@gmail.com) - MIT License', # ... '"""PyCalVer: CalVer for Python Packages."""',
... '# SPDX-License-Identifier: MIT', # ... '',
... '"""PyCalVer: CalVer for Python Packages."""', # ... '__version__ = "v201809.0123"',
... '', # ... '',
... '__version__ = "v201809.0123"', # ... ]
... '', # >>>
... ] # '''
>>>
'''
fobj: typ.IO[str] fobj: typ.IO[str]
@ -128,23 +121,24 @@ def iter_rewritten(
yield rfd._replace(path=str(file_path)) yield rfd._replace(path=str(file_path))
def diff(new_vinfo: version.VersionInfo, file_patterns: config.PatternsByGlob) -> str: def diff(
"""TODO reenable doctest""" new_vinfo : v2version.VersionInfo,
pass file_patterns: config.PatternsByGlob,
) -> str:
# TODO reenable doctest
# r"""Generate diffs of rewritten files.
r"""Generate diffs of rewritten files. # >>> new_vinfo = version.parse_version_info("v201809.0123")
# >>> file_patterns = {"src/pycalver/__init__.py": ['__version__ = "{pycalver}"']}
>>> new_vinfo = version.parse_version_info("v201809.0123") # >>> diff_str = diff(new_vinfo, file_patterns)
>>> file_patterns = {"src/pycalver/__init__.py": ['__version__ = "{pycalver}"']} # >>> lines = diff_str.split("\n")
>>> diff_str = diff(new_vinfo, file_patterns) # >>> lines[:2]
>>> lines = diff_str.split("\n") # ['--- src/pycalver/__init__.py', '+++ src/pycalver/__init__.py']
>>> lines[:2] # >>> assert lines[6].startswith('-__version__ = "v2')
['--- src/pycalver/__init__.py', '+++ src/pycalver/__init__.py'] # >>> assert not lines[6].startswith('-__version__ = "v201809.0123"')
>>> assert lines[6].startswith('-__version__ = "v2') # >>> lines[7]
>>> assert not lines[6].startswith('-__version__ = "v201809.0123"') # '+__version__ = "v201809.0123"'
>>> lines[7] # """
'+__version__ = "v201809.0123"'
"""
full_diff = "" full_diff = ""
fobj: typ.IO[str] fobj: typ.IO[str]
@ -172,7 +166,7 @@ def diff(new_vinfo: version.VersionInfo, file_patterns: config.PatternsByGlob) -
return full_diff return full_diff
def rewrite(file_patterns: config.PatternsByGlob, new_vinfo: version.VersionInfo) -> None: def rewrite(file_patterns: config.PatternsByGlob, new_vinfo: v2version.VersionInfo) -> None:
"""Rewrite project files, updating each with the new version.""" """Rewrite project files, updating each with the new version."""
fobj: typ.IO[str] fobj: typ.IO[str]

View file

@ -108,29 +108,27 @@ def _quarter_from_month(month: int) -> int:
def cal_info(date: dt.date = None) -> CalendarInfo: def cal_info(date: dt.date = None) -> CalendarInfo:
"""TODO reenable doctest""" # TODO reenable doctest
pass # """Generate calendar components for current date.
"""Generate calendar components for current date. # >>> from datetime import 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, 5)) # >>> c = cal_info(date(2019, 1, 6))
>>> (c.year_y, c.quarter, c.month, c.dom, c.doy, c.iso_week, c.us_week) # >>> (c.year_y, c.quarter, c.month, c.dom, c.doy, c.iso_week, c.us_week)
(2019, 1, 1, 5, 5, 0, 0) # (2019, 1, 1, 6, 6, 0, 1)
>>> c = cal_info(date(2019, 1, 6)) # >>> c = cal_info(date(2019, 1, 7))
>>> (c.year_y, c.quarter, c.month, c.dom, c.doy, c.iso_week, c.us_week) # >>> (c.year_y, c.quarter, c.month, c.dom, c.doy, c.iso_week, c.us_week)
(2019, 1, 1, 6, 6, 0, 1) # (2019, 1, 1, 7, 7, 1, 1)
>>> c = cal_info(date(2019, 1, 7)) # >>> c = cal_info(date(2019, 4, 7))
>>> (c.year_y, c.quarter, c.month, c.dom, c.doy, c.iso_week, c.us_week) # >>> (c.year_y, c.quarter, c.month, c.dom, c.doy, c.iso_week, c.us_week)
(2019, 1, 1, 7, 7, 1, 1) # (2019, 2, 4, 7, 97, 13, 14)
# """
>>> 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: if date is None:
date = TODAY date = TODAY
@ -250,22 +248,20 @@ def _parse_field_values(field_values: FieldValues) -> VersionInfo:
def _is_calver(nfo: typ.Union[CalendarInfo, VersionInfo]) -> bool: def _is_calver(nfo: typ.Union[CalendarInfo, VersionInfo]) -> bool:
"""TODO reenable doctest""" # TODO reenable doctest
pass # """Check pattern for any calendar based parts.
"""Check pattern for any calendar based parts. # >>> _is_calver(cal_info())
# True
>>> _is_calver(cal_info()) # >>> vnfo = _parse_version_info({'year': "2018", 'month': "11", 'bid': "0018"})
True # >>> _is_calver(vnfo)
# True
>>> vnfo = _parse_version_info({'year': "2018", 'month': "11", 'bid': "0018"}) # >>> vnfo = _parse_version_info({'MAJOR': "1", 'MINOR': "023", 'PATCH': "45"})
>>> _is_calver(vnfo) # >>> _is_calver(vnfo)
True # False
# """
>>> vnfo = _parse_version_info({'MAJOR': "1", 'MINOR': "023", 'PATCH': "45"})
>>> _is_calver(vnfo)
False
"""
for field in CalendarInfo._fields: for field in CalendarInfo._fields:
maybe_val: typ.Any = getattr(nfo, field, None) maybe_val: typ.Any = getattr(nfo, field, None)
if isinstance(maybe_val, int): if isinstance(maybe_val, int):
@ -325,48 +321,45 @@ def _parse_pattern_groups(pattern_groups: PatternGroups) -> FieldValues:
def _parse_version_info(pattern_groups: PatternGroups) -> VersionInfo: def _parse_version_info(pattern_groups: PatternGroups) -> VersionInfo:
"""TODO reenable doctest""" # TODO reenable doctest
pass # """Parse normalized VersionInfo from groups of a matched pattern.
"""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", 'month': "11", 'bid': "0099"}) # >>> vnfo = _parse_version_info({'year': "2018", 'doy': "11", 'bid': "099", 'tag': "b"})
>>> (vnfo.year_y, vnfo.month, vnfo.quarter, vnfo.bid, vnfo.tag) # >>> (vnfo.year_y, vnfo.month, vnfo.dom, vnfo.bid, vnfo.tag)
(2018, 11, 4, '0099', 'final') # (2018, 1, 11, '099', 'beta')
>>> vnfo = _parse_version_info({'year': "2018", 'doy': "11", 'bid': "099", 'tag': "b"}) # >>> vnfo = _parse_version_info({'MAJOR': "1", 'MINOR': "23", 'PATCH': "45"})
>>> (vnfo.year_y, vnfo.month, vnfo.dom, vnfo.bid, vnfo.tag) # >>> (vnfo.major, vnfo.minor, vnfo.patch)
(2018, 1, 11, '099', 'beta') # (1, 23, 45)
>>> vnfo = _parse_version_info({'MAJOR': "1", 'MINOR': "23", 'PATCH': "45"}) # >>> vnfo = _parse_version_info({'MAJOR': "1", 'MMM': "023", 'PPPP': "0045"})
>>> (vnfo.major, vnfo.minor, vnfo.patch) # >>> (vnfo.major, vnfo.minor, vnfo.patch)
(1, 23, 45) # (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) field_values = _parse_pattern_groups(pattern_groups)
return _parse_field_values(field_values) return _parse_field_values(field_values)
def parse_version_info(version_str: str, pattern: str = "{pycalver}") -> VersionInfo: def parse_version_info(version_str: str, pattern: str = "{pycalver}") -> VersionInfo:
"""TODO reenable doctest""" # TODO reenable doctest
pass # """Parse normalized VersionInfo.
"""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("v201712.0033-beta", pattern="{pycalver}") # >>> vnfo = parse_version_info("1.23.456", pattern="{semver}")
>>> assert vnfo == _parse_version_info({'year': 2017, 'month': 12, 'bid': "0033", 'tag': "beta"}) # >>> assert vnfo == _parse_version_info({'MAJOR': "1", 'MINOR': "23", 'PATCH': "456"})
# """
>>> vnfo = parse_version_info("1.23.456", pattern="{semver}") pattern_tup = v2patterns.compile_pattern(pattern)
>>> assert vnfo == _parse_version_info({'MAJOR': "1", 'MINOR': "23", 'PATCH': "456"}) match = pattern_tup.regexp.match(version_str)
"""
regex = v2patterns.compile_pattern(pattern)
match = regex.match(version_str)
if match is None: if match is None:
err_msg = ( err_msg = (
f"Invalid version string '{version_str}' for pattern '{pattern}'/'{regex.pattern}'" f"Invalid version string '{version_str}' "
f"for pattern '{pattern}'/'{pattern_tup.regexp.pattern}'"
) )
raise PatternError(err_msg) raise PatternError(err_msg)
@ -374,20 +367,18 @@ def parse_version_info(version_str: str, pattern: str = "{pycalver}") -> Version
def is_valid(version_str: str, pattern: str = "{pycalver}") -> bool: def is_valid(version_str: str, pattern: str = "{pycalver}") -> bool:
"""TODO reenable doctest""" # TODO reenable doctest
pass # """Check if a version matches a pattern.
"""Check if a version matches a pattern. # >>> is_valid("v201712.0033-beta", pattern="{pycalver}")
# True
>>> is_valid("v201712.0033-beta", pattern="{pycalver}") # >>> is_valid("v201712.0033-beta", pattern="{semver}")
True # False
>>> is_valid("v201712.0033-beta", pattern="{semver}") # >>> is_valid("1.2.3", pattern="{semver}")
False # True
>>> is_valid("1.2.3", pattern="{semver}") # >>> is_valid("v201712.0033-beta", pattern="{semver}")
True # False
>>> is_valid("v201712.0033-beta", pattern="{semver}") # """
False
"""
try: try:
parse_version_info(version_str, pattern) parse_version_info(version_str, pattern)
return True return True
@ -454,77 +445,75 @@ def _compile_format_template(pattern: str, kwargs: TemplateKwargs) -> str:
def format_version(vinfo: VersionInfo, pattern: str) -> str: def format_version(vinfo: VersionInfo, pattern: str) -> str:
"""TODO reenable doctest""" # TODO reenable doctest
pass # """Generate version string.
"""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())
>>> import datetime as dt # >>> format_version(vinfo_a, pattern="v{yy}.{BID}{release}")
>>> vinfo = parse_version_info("v201712.0033-beta", pattern="{pycalver}") # 'v17.33-beta'
>>> vinfo_a = vinfo._replace(**cal_info(date=dt.date(2017, 1, 1))._asdict()) # >>> format_version(vinfo_a, pattern="vYY.BUILD[-TAG]")
>>> vinfo_b = vinfo._replace(**cal_info(date=dt.date(2017, 12, 31))._asdict()) # 'v17.33-beta'
# >>> format_version(vinfo_a, pattern="YYYY0M.BUILD[PYTAG]")
# '201701.33b0'
>>> format_version(vinfo_a, pattern="v{yy}.{BID}{release}") # >>> format_version(vinfo_a, pattern="{pycalver}")
'v17.33-beta' # 'v201701.0033-beta'
>>> format_version(vinfo_a, pattern="vYY.BUILD[-TAG]") # >>> format_version(vinfo_b, pattern="{pycalver}")
'v17.33-beta' # 'v201712.0033-beta'
>>> format_version(vinfo_a, pattern="YYYY0M.BUILD[PYTAG]")
'201701.33b0'
>>> format_version(vinfo_a, pattern="{pycalver}") # >>> format_version(vinfo_a, pattern="v{year}w{iso_week}.{BID}{release}")
'v201701.0033-beta' # 'v2017w00.33-beta'
>>> format_version(vinfo_b, pattern="{pycalver}") # >>> format_version(vinfo_a, pattern="vYYYYwWW.BUILD[-TAG]")
'v201712.0033-beta' # '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}w{iso_week}.{BID}{release}") # >>> format_version(vinfo_a, pattern="v{year}d{doy}.{bid}{release}")
'v2017w00.33-beta' # 'v2017d001.0033-beta'
>>> format_version(vinfo_a, pattern="vYYYYwWW.BUILD[-TAG]") # >>> format_version(vinfo_b, pattern="v{year}d{doy}.{bid}{release}")
'v2017w00.33-beta' # 'v2017d365.0033-beta'
>>> format_version(vinfo_b, pattern="v{year}w{iso_week}.{BID}{release}") # >>> format_version(vinfo_a, pattern="vYYYYdJJJ.BUILD[-TAG]")
'v2017w52.33-beta' # 'v2017d001.0033-beta'
>>> format_version(vinfo_b, pattern="vYYYYwWW.BUILD[-TAG]") # >>> format_version(vinfo_b, pattern="vYYYYdJJJ.BUILD[-TAG]")
'v2017w52.33-beta' # 'v2017d365.0033-beta'
>>> format_version(vinfo_a, pattern="v{year}d{doy}.{bid}{release}") # >>> format_version(vinfo_a, pattern="vGGGGwVV.BUILD[-TAG]")
'v2017d001.0033-beta' # 'v2016w52.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]") # >>> vinfo_c = vinfo_b._replace(major=1, minor=2, patch=34, tag='final')
'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{year}w{iso_week}.{BID}-{tag}") # >>> format_version(vinfo_c, pattern="v{MAJOR}.{MINOR}.{PATCH}")
'v2017w52.33-final' # 'v1.2.34'
>>> format_version(vinfo_c, pattern="v{year}w{iso_week}.{BID}{release}") # >>> format_version(vinfo_c, pattern="vMAJOR.MINOR.PATCH")
'v2017w52.33' # 'v1.2.34'
>>> 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}") # >>> vinfo_d = vinfo_b._replace(major=1, minor=0, patch=0, tag='final')
'v1.2.34' # >>> format_version(vinfo_d, pattern="vMAJOR.MINOR.PATCH-TAG")
>>> format_version(vinfo_c, pattern="vMAJOR.MINOR.PATCH") # 'v1.0.0-final'
'v1.2.34' # >>> format_version(vinfo_d, pattern="vMAJOR.MINOR.PATCH[-TAG]")
# 'v1.0.0'
>>> vinfo_d = vinfo_b._replace(major=1, minor=0, patch=0, tag='final') # >>> format_version(vinfo_d, pattern="vMAJOR.MINOR[.PATCH[-TAG]]")
>>> format_version(vinfo_d, pattern="vMAJOR.MINOR.PATCH-TAG") # 'v1.0'
'v1.0.0-final' # >>> format_version(vinfo_d, pattern="vMAJOR.MINOR[.MICRO[-TAG]]")
>>> format_version(vinfo_d, pattern="vMAJOR.MINOR.PATCH[-TAG]") # 'v1.0'
'v1.0.0' # >>> format_version(vinfo_d, pattern="vMAJOR[.MINOR[.PATCH[-TAG]]]")
>>> format_version(vinfo_d, pattern="vMAJOR.MINOR[.PATCH[-TAG]]") # 'v1'
'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) kwargs = _derive_template_kwargs(vinfo)
format_tmpl = _compile_format_template(pattern, kwargs) format_tmpl = _compile_format_template(pattern, kwargs)

View file

@ -1,3 +1,4 @@
import pycalver.patterns as v1patterns
from pycalver import parse from pycalver import parse
SETUP_PY_FIXTURE = """ SETUP_PY_FIXTURE = """
@ -11,17 +12,17 @@ setuptools.setup(
def test_default_parse_patterns(): def test_default_parse_patterns():
lines = SETUP_PY_FIXTURE.splitlines() lines = SETUP_PY_FIXTURE.splitlines()
patterns = ["{pycalver}", "{pep440_pycalver}"] patterns = ["{pycalver}", "{pep440_pycalver}"]
re_patterns = [v1patterns.compile_pattern(p) for p in patterns]
matches = list(parse.iter_matches(lines, patterns)) matches = list(parse.iter_matches(lines, re_patterns))
assert len(matches) == 2 assert len(matches) == 2
assert matches[0].lineno == 3 assert matches[0].lineno == 3
assert matches[1].lineno == 6 assert matches[1].lineno == 6
assert matches[0].pattern == patterns[0] assert matches[0].pattern == re_patterns[0]
assert matches[1].pattern == patterns[1] assert matches[1].pattern == re_patterns[1]
assert matches[0].match == "v201712.0002-alpha" assert matches[0].match == "v201712.0002-alpha"
assert matches[1].match == "201712.2a0" assert matches[1].match == "201712.2a0"
@ -30,16 +31,16 @@ def test_default_parse_patterns():
def test_explicit_parse_patterns(): def test_explicit_parse_patterns():
lines = SETUP_PY_FIXTURE.splitlines() lines = SETUP_PY_FIXTURE.splitlines()
patterns = ["__version__ = '{pycalver}'", "version='{pep440_pycalver}'"] patterns = ["__version__ = '{pycalver}'", "version='{pep440_pycalver}'"]
re_patterns = [v1patterns.compile_pattern(p) for p in patterns]
matches = list(parse.iter_matches(lines, patterns)) matches = list(parse.iter_matches(lines, re_patterns))
assert len(matches) == 2 assert len(matches) == 2
assert matches[0].lineno == 3 assert matches[0].lineno == 3
assert matches[1].lineno == 6 assert matches[1].lineno == 6
assert matches[0].pattern == patterns[0] assert matches[0].pattern == re_patterns[0]
assert matches[1].pattern == patterns[1] assert matches[1].pattern == re_patterns[1]
assert matches[0].match == "__version__ = 'v201712.0002-alpha'" assert matches[0].match == "__version__ = 'v201712.0002-alpha'"
assert matches[1].match == "version='201712.2a0'" assert matches[1].match == "version='201712.2a0'"
@ -57,16 +58,17 @@ README_RST_FIXTURE = """
def test_badge_parse_patterns(): def test_badge_parse_patterns():
lines = README_RST_FIXTURE.splitlines() lines = README_RST_FIXTURE.splitlines()
patterns = ["badge/CalVer-{calver}{build}-{release}-blue.svg", ":alt: CalVer {pycalver}"] patterns = ["badge/CalVer-{calver}{build}-{release}-blue.svg", ":alt: CalVer {pycalver}"]
re_patterns = [v1patterns.compile_pattern(p) for p in patterns]
matches = list(parse.iter_matches(lines, re_patterns))
matches = list(parse.iter_matches(lines, patterns))
assert len(matches) == 2 assert len(matches) == 2
assert matches[0].lineno == 3 assert matches[0].lineno == 3
assert matches[1].lineno == 5 assert matches[1].lineno == 5
assert matches[0].pattern == patterns[0] assert matches[0].pattern == re_patterns[0]
assert matches[1].pattern == patterns[1] assert matches[1].pattern == re_patterns[1]
assert matches[0].match == "badge/CalVer-v201809.0002--beta-blue.svg" assert matches[0].match == "badge/CalVer-v201809.0002--beta-blue.svg"
assert matches[1].match == ":alt: CalVer v201809.0002-beta" assert matches[1].match == ":alt: CalVer v201809.0002-beta"

View file

@ -67,8 +67,8 @@ PATTERN_CASES = [
@pytest.mark.parametrize("pattern_str, line, expected", PATTERN_CASES) @pytest.mark.parametrize("pattern_str, line, expected", PATTERN_CASES)
def test_patterns(pattern_str, line, expected): def test_patterns(pattern_str, line, expected):
pattern_re = v1patterns.compile_pattern(pattern_str) pattern = v1patterns.compile_pattern(pattern_str)
result = pattern_re.search(line) result = pattern.regexp.search(line)
if result is None: if result is None:
assert expected is None, (pattern_str, line) assert expected is None, (pattern_str, line)
else: else:
@ -84,10 +84,10 @@ CLI_MAIN_FIXTURE = """
def test_pattern_escapes(): def test_pattern_escapes():
pattern = 'click.version_option(version="{pycalver}")' pattern_str = 'click.version_option(version="{pycalver}")'
pattern_re = v1patterns.compile_pattern(pattern) pattern = v1patterns.compile_pattern(pattern_str)
match = pattern_re.search(CLI_MAIN_FIXTURE) match = pattern.regexp.search(CLI_MAIN_FIXTURE)
expected = 'click.version_option(version="v201812.0123-beta")' expected = 'click.version_option(version="v201812.0123-beta")'
assert match.group(0) == expected assert match.group(0) == expected
@ -97,8 +97,8 @@ package_metadata = {"name": "mypackage", "version": "v201812.0123-beta"}
def test_curly_escapes(): def test_curly_escapes():
pattern = 'package_metadata = {"name": "mypackage", "version": "{pycalver}"}' pattern_str = 'package_metadata = {"name": "mypackage", "version": "{pycalver}"}'
pattern_re = v1patterns.compile_pattern(pattern) pattern = v1patterns.compile_pattern(pattern_str)
match = pattern_re.search(CURLY_BRACE_FIXTURE) match = pattern.regexp.search(CURLY_BRACE_FIXTURE)
expected = 'package_metadata = {"name": "mypackage", "version": "v201812.0123-beta"}' expected = 'package_metadata = {"name": "mypackage", "version": "v201812.0123-beta"}'
assert match.group(0) == expected assert match.group(0) == expected

View file

@ -136,15 +136,18 @@ def test_part_field_mapping():
b_names = set(patterns.PART_PATTERNS.keys()) b_names = set(patterns.PART_PATTERNS.keys())
c_names = set(patterns.COMPOSITE_PART_PATTERNS.keys()) c_names = set(patterns.COMPOSITE_PART_PATTERNS.keys())
extra_names = a_names - b_names a_extra_names = a_names - b_names
assert not any(extra_names) assert not any(a_extra_names), sorted(a_extra_names)
missing_names = b_names - a_names b_extra_names = b_names - (a_names | c_names)
assert missing_names == c_names assert not any(b_extra_names), sorted(b_extra_names)
a_fields = set(version.PATTERN_PART_FIELDS.values()) a_fields = set(version.PATTERN_PART_FIELDS.values())
b_fields = set(version.VersionInfo._fields) b_fields = set(version.VersionInfo._fields)
assert a_fields == b_fields a_extra_fields = a_fields - b_fields
b_extra_fields = b_fields - a_fields
assert not any(a_extra_fields), sorted(a_extra_fields)
assert not any(b_extra_fields), sorted(b_extra_fields)
def vnfo(**field_values): def vnfo(**field_values):
@ -152,6 +155,8 @@ def vnfo(**field_values):
PARSE_VERSION_TEST_CASES = [ PARSE_VERSION_TEST_CASES = [
# TODO (mb 2020-09-06): add tests for new style patterns
# ["YYYY.MM.DD" , "2017.06.07", vnfo(year="2017", month="06", dom="07")],
["{year}.{month}.{dom}" , "2017.06.07", vnfo(year="2017", month="06", dom="07")], ["{year}.{month}.{dom}" , "2017.06.07", vnfo(year="2017", month="06", dom="07")],
["{year}.{month}.{dom_short}" , "2017.06.7" , vnfo(year="2017", month="06", dom="7" )], ["{year}.{month}.{dom_short}" , "2017.06.7" , vnfo(year="2017", month="06", dom="7" )],
["{year}.{month}.{dom_short}" , "2017.06.7" , vnfo(year="2017", month="06", dom="7" )], ["{year}.{month}.{dom_short}" , "2017.06.7" , vnfo(year="2017", month="06", dom="7" )],
@ -169,8 +174,8 @@ PARSE_VERSION_TEST_CASES = [
@pytest.mark.parametrize("pattern_str, line, expected_vinfo", PARSE_VERSION_TEST_CASES) @pytest.mark.parametrize("pattern_str, line, expected_vinfo", PARSE_VERSION_TEST_CASES)
def test_parse_versions(pattern_str, line, expected_vinfo): def test_parse_versions(pattern_str, line, expected_vinfo):
pattern_re = patterns.compile_pattern(pattern_str) pattern = patterns.compile_pattern(pattern_str)
version_match = pattern_re.search(line) version_match = pattern.regexp.search(line)
if expected_vinfo is None: if expected_vinfo is None:
assert version_match is None assert version_match is None