From 28e09fd60a04a36e3d5401366b1fd3256ae00239 Mon Sep 17 00:00:00 2001 From: Manuel Barkhau Date: Thu, 17 Sep 2020 16:24:21 +0000 Subject: [PATCH] readme updates --- CHANGELOG.md | 10 ++ README.md | 144 ++++++++++++++++++++++----- src/pycalver2/patterns.py | 199 +++++++++----------------------------- src/pycalver2/version.py | 7 +- test/test_cli.py | 4 +- test/test_patterns.py | 128 ++++++++++++++++++++++++ test/test_version.py | 78 ++++++++------- 7 files changed, 356 insertions(+), 214 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff167b2..957b86c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,16 @@ # Changelog for https://gitlab.com/mbarkhau/pycalver +## NEXT + + - New gitlab #7: New style patterns, to be in line with CalVer.org + - Better support for week numbering. + - Better support for optional parts. + - New gitlab #10: `--pin-date` to keep date parts unchanged, and only increment non-date parts. + - Fix gitlab #8: Push tags only pushed tags, not actual commit. + - Fix gitlab #9: Make commit message configurable. + + ## v201907.0036 - Fix: Don't use git/hg command if `commit=False` is configured (thanks @valentin87) diff --git a/README.md b/README.md index 09343eb..9e79130 100644 --- a/README.md +++ b/README.md @@ -212,10 +212,108 @@ The internally used regular expression is also shown, which you can use to debug ### Pattern Search and Replacement -The `pycalver:file_patterns` section of the configuration is used both to search -and also to replace version strings in your projects files. Everything except +The neat thing about PyCalVer is that you don't have to separately declare the search pattern and the replacement template. You declare one pattern that is used to derive both. + for valid placeholders is treated as literal text. Available placeholders are: +You define your pattern in the `pycalver:version_pattern` option of your config. + +You can also define custom patterns in the items of the `pycalver:file_patterns` section of your configuration, but usually it will be easier to just use the special `{version}` and `{pep440_version}` patterns, which are derived from what you configure as your `version_pattern`. is used both to search and also to replace version strings in your projects files. Everything except for valid placeholders is treated as literal text. Available placeholders are: + +These patterns are closely based on https://calver.org/ + +| placeholder | range / example(s) | comment | +|-------------|---------------------|----------------------| +| YYYY | 2019, 2020... | `%Y` | +| YY | 18, 19..99, 1, 2 | `int(%y)` | +| 0Y | 18, 19..99, 01, 02 | `%y` | +| Q | 1, 2, 3, 4 | quarter | +| MM | 9, 10, 11, 12 | `int(%m)` | +| 0M | 09, 10, 11, 12 | `%m` | +| DD | 1, 2, 3..31 | `int(%d)` | +| 0D | 01, 02, 03..31 | `%d` | +| JJJ | 1,2,3..366 | `int(%j)` | +| 00J | 001, 002..366 | `%j` | +| BUILD | 1001, 1002, 23456 | build number (lexid) | +| TAG | alpha, beta, rc | `--release=` | +| PYTAG | a0, b0, rc | `--release=` | +| MAJOR | 0..9, 10..99, 100.. | `--major` | +| MINOR | 0..9, 10..99, 100.. | `--minor` | +| PATCH | 0..9, 10..99, 100.. | `--patch` | +| MICRO | 0..9, 10..99, 100.. | Synonym for PATCH | + + +### Week Numbering + +Week numbering is a bit special, as it depends on your definition of "week": + +- Does it start on a Monday or a Sunday? +- Range from 0-52 or 1-53 ? +- At the beginning/end of the year, do you have partial weeks or do you have a week that span mutliple years? +- If a week spans multiple years, what is the year number? + +| placeholder | range / example(s) | comment | +|-------------|---------------------|------------------------------------------------------------| +| `WW` | 0, 1, 2..52 | `int(%W)` | +| `0W` | 00, 01, 02..52 | `%W` | +| `UU` | 0, 1, 2..52 | `int(%U)` us_week | +| `0U` | 00, 01, 02..52 | `%U` us_week | +| `VV` | 1, 2..53 | `int(%V)` iso week | +| `0V` | 01, 02..53 | `%U` iso_week | +| `GGGG` | 2019, 2020... | ISO 8601 week-based year (corresponds to `strftime("%G")`) | +| `GG` | 19, 20...99, 0, 1 | Short ISO 8601 week-based year | +| `0G` | 19, 20...99, 00, 01 | Zero-padded ISO 8601 week-based year | + + +### Normalization Caveats + +Since other tools parse your version numbers, they may not care about your choice of formatting. In the case of Python, the packaging tools (such as pypi.org) follow [PEP440 normalization rules][pep_440_normalzation_ref]. + +According to these rules: + +- Any non-numerical prefix (such as `v`) is removed +- Leading zeros in parts are truncated `XX.08` -> `XX.8` +- Tags are converted to a short form (`-alpha` -> `a0`) + +For example: + +- Pattern: `vYY.0M.0D[-TAG]` +- Version: `v20.08.02-beta` +- PEP440 : `20.8.2b0` + +It may be confusing to your users to see versions displayed in two different forms. It is not immediately obvious that `v20.08.02-beta` is the same `20.8.2b0` on pypi. If you wish to avoid this, you should usa a pattern which is as close as possible to the normalized form of your version. + +| pattern | example | lexical | PEP440 | lexical | +|-----------------------|---------|---------|--------|---------| +| `YYYY.0M` | | yes | | no | +| `YYYY.MM` | | no | | no | +| `vYYYY.0W` | | yes | | no | +| `vYYYY.WW` | | no | | no | +| `YYYY.0M.0D` | | yes | | no | +| `YYYY.MM.DD` | | no | | no | +| `YYYY0M.BUILD[-TAG]` | | yes | | yes | +| `YY0M.BUILD[-TAG]` | | yes¹ | | yes¹ | +| `YYYY.BUILD[-TAG]` | | yes | | yes | +| `YYYY0M.MINOR[-TAG]` | | yes² | | yes | +| `YYYY.MM.MINOR[-TAG]` | | no | | no | +| `YYYY.0M.MINOR[-TAG]` | | yes² | | no | +| `YYYY.WW.MINOR[-TAG]` | | no | | no | +| `YYYY.0W.MINOR[-TAG]` | | yes² | | no | + +- ¹ Until 2099. If your project has new releases after 2099, future maintainers can change `YY`/`0Y` -> `YYYY` so that they don't release `00.xx`. +- ² As long as `MINOR <= 9` + + +### Legacy Patterns + +> These patterns use curly braces `{}` and were the initial implementation. They are still supported and still follow their original semantics. + +The `pycalver:file_patterns` section of the configuration uses a different set +of placeholders and does not use curly braces to mark placeholders. It is still +supported, but we don't recomend you use it. + +Available placeholders are: + | placeholder | range / example(s) | comment | |---------------------|---------------------|-----------------| @@ -298,29 +396,29 @@ bump` to update the components that are not updated automatically (eg. based on the calendar). ```shell -$ pycalver test 'v18.1.1' 'v{yy}.{MINOR}.{PATCH}' +$ pycalver test 'v18.1.1' 'vYY.MINOR.PATCH' New Version: v19.1.1 PEP440 : 19.1.1 -$ pycalver test 'v18.1.1' 'v{yy}.{MINOR}.{PATCH}' --patch +$ pycalver test 'v18.1.1' 'vYY.MINOR.PATCH' --patch New Version: v19.1.2 PEP440 : 19.1.2 -$ pycalver test 'v18.1.2' 'v{yy}.{MINOR}.{PATCH}' --minor +$ pycalver test 'v18.1.2' 'vYY.MINOR.PATCH' --minor New Version: v19.2.0 PEP440 : 19.2.0 -$ pycalver test 'v201811.0051-beta' '{pycalver}' -New Version: v201902.0052-beta -PEP440 : 201902.52b0 +$ pycalver test 'v201811.1051-beta' 'vYYYYMM.BUILD[-TAG]' +New Version: v201902.1052-beta +PEP440 : 201902.1052b0 -$ pycalver test 'v201811.0051-beta' '{pycalver}' --release rc -New Version: v201902.0052-rc -PEP440 : 201902.52rc0 +$ pycalver test 'v201811.0051-beta' 'vYYYYMM.BUILD[-TAG]' --release rc +New Version: v201902.1052-rc +PEP440 : 201902.1052rc0 -$ pycalver test 'v201811.0051-beta' '{pycalver}' --release final -New Version: v201902.0052 -PEP440 : 201902.52 +$ pycalver test 'v201811.0051-beta' 'vYYYYMM.BUILD[-TAG]' --release final +New Version: v201902.1052 +PEP440 : 201902.1052 ``` Note that pypi/setuptools/pip will normalize version strings to a format @@ -391,8 +489,8 @@ section: ```ini [pycalver] -current_version = "v201812.0006-beta" -version_pattern = "{pycalver}" +current_version = "202008.1006-beta" +version_pattern = "YYYY0M.BUILD[-TAG]" commit = True tag = True push = True @@ -429,8 +527,8 @@ $ pycalver bump --dry @@ -65,7 +65,7 @@ [pycalver] --current_version = v201812.0005-beta -+current_version = v201812.0006-beta +-current_version = v202008.1005-beta ++current_version = v202008.1006-beta commit = True tag = True push = True @@ -442,11 +540,11 @@ If everything looks OK, you can do `pycalver bump`. ``` $ pycalver bump --verbose INFO - fetching tags from remote (to turn off use: -n / --no-fetch) -INFO - Old Version: v201812.0005-beta -INFO - New Version: v201812.0006-beta +INFO - Old Version: v202008.0005-beta +INFO - New Version: v202008.0006-beta INFO - git commit --file /tmp/tmpph_npey9 -INFO - git tag --annotate v201812.0006-beta --message v201812.0006-beta -INFO - git push origin v201812.0006-beta +INFO - git tag --annotate v202008.0006-beta --message v202008.0006-beta +INFO - git push origin v202008.0006-beta ``` @@ -461,7 +559,7 @@ The PyCalVer format for version strings has three parts: | | o Release Tag (optional) | | | ---+--- --+-- --+-- - v201812 .0123 -beta + v202008 .0123 -beta ``` diff --git a/src/pycalver2/patterns.py b/src/pycalver2/patterns.py index 3db6736..1217493 100644 --- a/src/pycalver2/patterns.py +++ b/src/pycalver2/patterns.py @@ -3,61 +3,36 @@ # # Copyright (c) 2018-2020 Manuel Barkhau (mbarkhau@gmail.com) - MIT License # SPDX-License-Identifier: MIT -"""Compose Regular Expressions from Patterns. +# """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, -... } -""" +# >>> pattern = compile_pattern("vYYYY0M.BUILD[-TAG]") +# >>> version_info = pattern.regexp.match("v201712.0123-alpha") +# >>> assert version_info == { +# ... "version": "v201712.0123-alpha", +# ... "YYYY" : "2017", +# ... "0M" : "12", +# ... "BUILD" : "0123", +# ... "TAG" : "alpha", +# ... } +# >>> +# >>> version_info = pattern.regexp.match("201712.1234") +# >>> assert version_info is None + +# >>> version_info = pattern.regexp.match("v201712.1234") +# >>> assert version_info == { +# ... "version": "v201712.0123-alpha", +# ... "YYYY" : "2017", +# ... "0M" : "12", +# ... "BUILD" : "0123", +# ... "TAG" : None, +# ... } +# """ import re import typing as typ import pycalver.patterns as v1patterns -# https://regex101.com/r/fnj60p/10 -PYCALVER_PATTERN = r""" -\b -(?P - (?P - v # "v" version prefix - (?P\d{4}) - (?P\d{2}) - ) - (?P - \. # "." build nr prefix - (?P\d{4,}) - ) - (?P - \- # "-" release prefix - (?Palpha|beta|dev|rc|post) - )? -)(?:\s|$) -""" - -PYCALVER_RE: typ.Pattern[str] = re.compile(PYCALVER_PATTERN, flags=re.VERBOSE) - - PATTERN_ESCAPES = [ ("\u005c", "\u005c\u005c"), ("-" , "\u005c-"), @@ -73,107 +48,37 @@ PATTERN_ESCAPES = [ (")" , "\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}", + # Based on calver.org + 'YYYY': r"[1-9][0-9]{3}", + 'YY' : r"[1-9][0-9]?", + '0Y' : r"[0-9]{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}", + '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]|[1-9][0-9]|[1-2][0-9][0-9]|3[0-5][0-9]|36[0-6])", + '00J' : r"(?:00[1-9]|0[1-9][0-9]|[1-2][0-9][0-9]|3[0-5][0-9]|36[0-6])", + # week numbering parts + 'WW' : r"(?:[0-9]|[1-4][0-9]|5[0-2])", + '0W' : r"(?:[0-4][0-9]|5[0-2])", + 'UU' : r"(?:[0-9]|[1-4][0-9]|5[0-2])", + '0U' : r"(?:[0-4][0-9]|5[0-2])", + 'VV' : r"(?:[1-9]|[1-4][0-9]|5[0-3])", + '0V' : r"(?:0[1-9]|[1-4][0-9]|5[0-3])", + 'GGGG': r"[1-9][0-9]{3}", + 'GG' : r"[1-9][0-9]?", + '0G' : r"[0-9]{2}", # non calver parts - 'MAJOR': r"\d+", - 'MINOR': r"\d+", - 'PATCH': r"\d+", - 'MICRO': r"\d+", - 'BUILD': r"\d+", + 'MAJOR': r"[0-9]+", + 'MINOR': r"[0-9]+", + 'PATCH': r"[0-9]+", + 'MICRO': r"[0-9]+", + 'BUILD': r"[0-9]+", '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}", + 'PYTAG': r"(?:a|b|dev|rc|post)?[0-9]*", } @@ -198,13 +103,3 @@ def compile_pattern(pattern: str) -> v1patterns.Pattern: pattern_str = compile_pattern_str(pattern) pattern_re = re.compile(pattern_str) return v1patterns.Pattern(pattern, pattern_re) - - -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() diff --git a/src/pycalver2/version.py b/src/pycalver2/version.py index e16484b..0f28b72 100644 --- a/src/pycalver2/version.py +++ b/src/pycalver2/version.py @@ -344,14 +344,13 @@ def _parse_version_info(pattern_groups: PatternGroups) -> VersionInfo: return _parse_field_values(field_values) -def parse_version_info(version_str: str, pattern: str = "{pycalver}") -> VersionInfo: - # TODO reenable doctest +def parse_version_info(version_str: str, pattern: str = "vYYYY0M.BUILD[-TAG]") -> VersionInfo: # """Parse normalized VersionInfo. - # >>> vnfo = parse_version_info("v201712.0033-beta", pattern="{pycalver}") + # >>> vnfo = parse_version_info("v201712.0033-beta", pattern="vYYYY0M.BUILD[-TAG]") # >>> assert vnfo == _parse_version_info({'year': 2017, 'month': 12, 'bid': "0033", 'tag': "beta"}) - # >>> vnfo = parse_version_info("1.23.456", pattern="{semver}") + # >>> vnfo = parse_version_info("1.23.456", pattern="MAJOR.MINOR.PATCH") # >>> assert vnfo == _parse_version_info({'MAJOR': "1", 'MINOR': "23", 'PATCH': "456"}) # """ pattern_tup = v2patterns.compile_pattern(pattern) diff --git a/test/test_cli.py b/test/test_cli.py index 38355a2..a5d494d 100644 --- a/test/test_cli.py +++ b/test/test_cli.py @@ -11,7 +11,7 @@ import pathlib2 as pl from click.testing import CliRunner import pycalver.config as config -import pycalver2.patterns as patterns +import pycalver.patterns as v1patterns from pycalver.__main__ import cli SETUP_CFG_FIXTURE = """ @@ -81,7 +81,7 @@ def test_version(runner): result = runner.invoke(cli, ['--version', "-vv"]) assert result.exit_code == 0 assert " version v20" in result.output - match = patterns.PYCALVER_RE.search(result.output) + match = v1patterns.PYCALVER_RE.search(result.output) assert match diff --git a/test/test_patterns.py b/test/test_patterns.py index 14f6012..66c7503 100644 --- a/test/test_patterns.py +++ b/test/test_patterns.py @@ -7,6 +7,134 @@ import pycalver2.patterns as v2patterns # TODO (mb 2020-09-06): test for v2patterns +V2_PART_PATTERN_CASES = [ + (['YYYY', 'GGGG'], "2020" , "2020"), + (['YYYY', 'GGGG'], "" , None), + (['YYYY', 'GGGG'], "A020" , None), + (['YYYY', 'GGGG'], "020" , None), + (['YYYY', 'GGGG'], "12020", None), + (['YY' , 'GG' ], "20" , "20"), + (['YY' , 'GG' ], "3" , "3"), + (['YY' , 'GG' ], "03" , None), + (['YY' , 'GG' ], "2X" , None), + (['YY' , 'GG' ], "" , None), + (['0Y' , '0G' ], "20" , "20"), + (['0Y' , '0G' ], "03" , "03"), + (['0Y' , '0G' ], "3" , None), + (['0Y' , '0G' ], "2X" , None), + (['0Y' , '0G' ], "" , None), + # quarter + (['Q'], "0", None), + (['Q'], "1", "1"), + (['Q'], "2", "2"), + (['Q'], "3", "3"), + (['Q'], "4", "4"), + (['Q'], "5", None), + (['Q'], "X", None), + # months + (['MM'], "0" , None), + (['MM'], "01", None), + (['MM'], "1" , "1"), + (['MM'], "10", "10"), + (['MM'], "12", "12"), + (['MM'], "13", None), + (['0M'], "00", None), + (['0M'], "1" , None), + (['0M'], "01", "01"), + (['MM'], "10", "10"), + (['MM'], "12", "12"), + (['MM'], "13", None), + # day of month + (['DD'], "0" , None), + (['DD'], "01", None), + (['DD'], "1" , "1"), + (['DD'], "10", "10"), + (['DD'], "31", "31"), + (['DD'], "32", None), + (['0D'], "00", None), + (['0D'], "1" , None), + (['0D'], "01", "01"), + (['0D'], "10", "10"), + (['0D'], "31", "31"), + (['0D'], "32", None), + (['DD'], "0" , None), + (['DD'], "01", None), + (['DD'], "1" , "1"), + (['DD'], "10", "10"), + (['DD'], "31", "31"), + (['DD'], "32", None), + (['0D'], "00", None), + (['0D'], "1" , None), + (['0D'], "01", "01"), + (['0D'], "10", "10"), + (['0D'], "31", "31"), + (['0D'], "32", None), + # day of year + (['JJJ'], "0" , None), + (['JJJ'], "01" , None), + (['JJJ'], "1" , "1"), + (['JJJ'], "10" , "10"), + (['JJJ'], "31" , "31"), + (['JJJ'], "32" , "32"), + (['JJJ'], "100", "100"), + (['JJJ'], "365", "365"), + (['JJJ'], "366", "366"), + (['JJJ'], "367", None), + (['00J'], "000", None), + (['00J'], "01" , None), + (['00J'], "1" , None), + (['00J'], "001", "001"), + (['00J'], "010", "010"), + (['00J'], "031", "031"), + (['00J'], "032", "032"), + (['00J'], "100", "100"), + (['00J'], "365", "365"), + (['00J'], "366", "366"), + (['00J'], "367", None), + # week numbers + (['WW', 'UU'], "00", None), + (['WW', 'UU'], "01", None), + (['WW', 'UU'], "0" , "0"), + (['WW', 'UU'], "1" , "1"), + (['WW', 'UU'], "10", "10"), + (['WW', 'UU'], "52", "52"), + (['WW', 'UU'], "53", None), + (['0W', '0U'], "00", "00"), + (['0W', '0U'], "01", "01"), + (['0W', '0U'], "0" , None), + (['0W', '0U'], "1" , None), + (['0W', '0U'], "10", "10"), + (['0W', '0U'], "52", "52"), + (['0W', '0U'], "53", None), + (['VV'], "00", None), + (['VV'], "01", None), + (['VV'], "0" , None), + (['VV'], "1" , "1"), + (['VV'], "10", "10"), + (['VV'], "52", "52"), + (['VV'], "53", "53"), + (['VV'], "54", None), + (['0V'], "00", None), + (['0V'], "01", "01"), + (['0V'], "0" , None), + (['0V'], "1" , None), + (['0V'], "10", "10"), + (['0V'], "52", "52"), + (['0V'], "53", "53"), + (['0V'], "54", None), + (['MAJOR', 'MINOR', 'PATCH', 'MICRO'], "0", "0"), + # ('TAG', ""), + # ('PYTAG', ""), +] + + +@pytest.mark.parametrize("parts, testcase, expected", V2_PART_PATTERN_CASES) +def test_part_patterns(parts, testcase, expected): + for part in parts: + pattern_str = v2patterns.PART_PATTERNS[part] + match = re.match("^" + pattern_str + "$", testcase) + assert (match is None and expected is None) or (match.group(0) == expected) + def _part_re_by_name(name): return re.compile(v1patterns.PART_PATTERNS[name]) diff --git a/test/test_version.py b/test/test_version.py index a5e2767..2cef58a 100644 --- a/test/test_version.py +++ b/test/test_version.py @@ -5,28 +5,30 @@ import datetime as dt import pytest -from pycalver import version -from pycalver import patterns +import pycalver.version as v1version +import pycalver2.version as v2version +import pycalver.patterns as v1patterns +import pycalver2.patterns as v2patterns def test_bump_beta(): cur_version = "v201712.0001-beta" - assert cur_version < version.incr(cur_version) - assert version.incr(cur_version).endswith("-beta") - assert version.incr(cur_version, release="alpha").endswith("-alpha") - assert version.incr(cur_version, release="final").endswith("0002") + assert cur_version < v1version.incr(cur_version) + assert v1version.incr(cur_version).endswith("-beta") + assert v1version.incr(cur_version, release="alpha").endswith("-alpha") + assert v1version.incr(cur_version, release="final").endswith("0002") def test_bump_final(): cur_version = "v201712.0001" - assert cur_version < version.incr(cur_version) - assert version.incr(cur_version).endswith(".0002") - assert version.incr(cur_version, release="alpha").endswith("-alpha") + assert cur_version < v1version.incr(cur_version) + assert v1version.incr(cur_version).endswith(".0002") + assert v1version.incr(cur_version, release="alpha").endswith("-alpha") - assert version.incr(cur_version, release="final").endswith(".0002") + assert v1version.incr(cur_version, release="final").endswith(".0002") pre_version = cur_version + "-beta" - assert version.incr(pre_version, release="final").endswith(".0002") + assert v1version.incr(pre_version, release="final").endswith(".0002") def test_bump_future(): @@ -34,7 +36,7 @@ def test_bump_future(): future_date = dt.datetime.today() + dt.timedelta(days=300) future_calver = future_date.strftime("v%Y%m") cur_version = future_calver + ".0001" - new_version = version.incr(cur_version) + new_version = v1version.incr(cur_version) assert cur_version < new_version @@ -42,11 +44,11 @@ def test_bump_random(monkeypatch): cur_date = dt.date(2016, 1, 1) + dt.timedelta(days=random.randint(1, 2000)) cur_version = cur_date.strftime("v%Y%m") + ".0001-dev" - monkeypatch.setattr(version, 'TODAY', cur_date) + monkeypatch.setattr(v1version, 'TODAY', cur_date) for _ in range(1000): cur_date += dt.timedelta(days=int((1 + random.random()) ** 10)) - new_version = version.incr( + new_version = v1version.incr( cur_version, release=random.choice([None, "alpha", "beta", "rc", "final", "post"]) ) assert cur_version < new_version @@ -55,7 +57,7 @@ def test_bump_random(monkeypatch): def test_parse_version_info(): version_str = "v201712.0001-alpha" - version_info = version.parse_version_info(version_str) + version_info = v1version.parse_version_info(version_str) # assert version_info.pep440_version == "201712.1a0" # assert version_info.version == "v201712.0001-alpha" @@ -65,7 +67,7 @@ def test_parse_version_info(): assert version_info.tag == "alpha" version_str = "v201712.0001" - version_info = version.parse_version_info(version_str) + version_info = v1version.parse_version_info(version_str) # assert version_info.pep440_version == "201712.1" # assert version_info.version == "v201712.0001" @@ -77,7 +79,7 @@ def test_parse_version_info(): def test_readme_pycalver1(): version_str = "v201712.0001-alpha" - version_info = patterns.PYCALVER_RE.match(version_str).groupdict() + version_info = v1patterns.PYCALVER_RE.match(version_str).groupdict() assert version_info == { 'pycalver' : "v201712.0001-alpha", @@ -93,7 +95,7 @@ def test_readme_pycalver1(): def test_readme_pycalver2(): version_str = "v201712.0033" - version_info = patterns.PYCALVER_RE.match(version_str).groupdict() + version_info = v1patterns.PYCALVER_RE.match(version_str).groupdict() assert version_info == { 'pycalver' : "v201712.0033", @@ -109,40 +111,40 @@ def test_readme_pycalver2(): def test_parse_error_empty(): try: - version.parse_version_info("") + v1version.parse_version_info("") assert False - except version.PatternError as err: + except v1version.PatternError as err: assert "Invalid version string" in str(err) def test_parse_error_noprefix(): try: - version.parse_version_info("201809.0002") + v1version.parse_version_info("201809.0002") assert False - except version.PatternError as err: + except v1version.PatternError as err: assert "Invalid version string" in str(err) def test_parse_error_nopadding(): try: - version.parse_version_info("v201809.2b0") + v1version.parse_version_info("v201809.2b0") assert False - except version.PatternError as err: + except v1version.PatternError as err: assert "Invalid version string" in str(err) -def test_part_field_mapping(): - a_names = set(version.PATTERN_PART_FIELDS.keys()) - b_names = set(patterns.PART_PATTERNS.keys()) - c_names = set(patterns.COMPOSITE_PART_PATTERNS.keys()) +def test_part_field_mapping_v1(): + a_names = set(v1version.PATTERN_PART_FIELDS.keys()) + b_names = set(v1patterns.PART_PATTERNS.keys()) + c_names = set(v1patterns.COMPOSITE_PART_PATTERNS.keys()) a_extra_names = a_names - b_names assert not any(a_extra_names), sorted(a_extra_names) b_extra_names = b_names - (a_names | c_names) assert not any(b_extra_names), sorted(b_extra_names) - a_fields = set(version.PATTERN_PART_FIELDS.values()) - b_fields = set(version.VersionInfo._fields) + a_fields = set(v1version.PATTERN_PART_FIELDS.values()) + b_fields = set(v1version.VersionInfo._fields) a_extra_fields = a_fields - b_fields b_extra_fields = b_fields - a_fields @@ -150,8 +152,18 @@ def test_part_field_mapping(): assert not any(b_extra_fields), sorted(b_extra_fields) +def test_part_field_mapping_v2(): + a_names = set(v2version.PATTERN_PART_FIELDS.keys()) + b_names = set(v2patterns.PART_PATTERNS.keys()) + + a_extra_names = a_names - b_names + assert not any(a_extra_names), sorted(a_extra_names) + b_extra_names = b_names - a_names + assert not any(b_extra_names), sorted(b_extra_names) + + def vnfo(**field_values): - return version._parse_field_values(field_values) + return v1version._parse_field_values(field_values) PARSE_VERSION_TEST_CASES = [ @@ -174,7 +186,7 @@ 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): - pattern = patterns.compile_pattern(pattern_str) + pattern = v1patterns.compile_pattern(pattern_str) version_match = pattern.regexp.search(line) if expected_vinfo is None: @@ -184,6 +196,6 @@ def test_parse_versions(pattern_str, line, expected_vinfo): assert version_match is not None version_str = version_match.group(0) - version_info = version.parse_version_info(version_str, pattern_str) + version_info = v1version.parse_version_info(version_str, pattern_str) assert version_info == expected_vinfo