diff --git a/README.md b/README.md index 9e79130..9774893 100644 --- a/README.md +++ b/README.md @@ -222,25 +222,27 @@ You can also define custom patterns in the items of the `pycalver:file_patterns` 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 | +| 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` | 0011, 1001, 1002, .. | build number (lexid) | +| `BLD` | 11, 1001, 1002, .. | zero truncated `BUILD` | +| `TAG` | alpha, beta, rc | `--release=` | +| `PYTAG` | a, b, rc | `--release=` | +| `NUM` | 0, 1, 2... | release tag number | +| `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 @@ -252,17 +254,17 @@ Week numbering is a bit special, as it depends on your definition of "week": - 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 | +| 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 | `%V` iso_week | +| `GGGG` | 2019, 2020... | `strftime("%G")` ISO 8601 week-based year | +| `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 @@ -283,22 +285,34 @@ For example: 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 | +It may also be confusing to your users if they a list of version numbers, sorted lexiographically by some tool (e.g. a list of git tags) and a newer version is listed after older versions like this: + +``` +3.9.1 +3.8.1 +3.8.0 +3.10.0 +``` + +If you wish to avoid this, you should use a pattern which maintains lexiographical ordering. + + +| pattern | example | lexio. | PEP440 | lexio. | +|-----------------------|---------|--------|--------|--------| +| `YYYY0M.BUILD[-TAG]` | | yes | | yes | +| `YYYY.BUILD[-TAG]` | | yes | | yes | +| `YYYY0M.MINOR[-TAG]` | | yes² | | yes | +| `YY0M.BUILD[-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 | +| `YYYY.0M.0D` | | yes | | no | +| `YYYY.MM.DD` | | no | | no | +| `vYYYY.0W` | | yes | | no | +| `vYYYY.WW` | | no | | no | +| `YYYY.0M` | | yes | | no | +| `YYYY.MM` | | no | | 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` @@ -317,8 +331,6 @@ Available placeholders are: | placeholder | range / example(s) | comment | |---------------------|---------------------|-----------------| -| `{pycalver}` | v201902.0001-beta | | -| `{pep440_pycalver}` | 201902.1b0 | | | `{year}` | 2019... | `%Y` | | `{yy}` | 18, 19..99, 01, 02 | `%y` | | `{quarter}` | 1, 2, 3, 4 | | @@ -327,16 +339,22 @@ Available placeholders are: | `{us_week}` | 00..53 | `%U` | | `{dom}` | 01..31 | `%d` | | `{doy}` | 001..366 | `%j` | -| `{build}` | .0123 | lexical id | -| `{build_no}` | 0123, 12345 | ... | +| `{build}` | .1023 | lexical id | +| `{build_no}` | 1023, 20345 | ... | | `{release}` | -alpha, -beta, -rc | --release= | | `{release_tag}` | alpha, beta, rc | ... | -| `{semver}` | 1.2.3 | | -| `{MAJOR}` | 1..9, 10..99, 100.. | --major | -| `{MINOR}` | 1..9, 10..99, 100.. | --minor | -| `{PATCH}` | 1..9, 10..99, 100.. | --patch | + +| placeholder | range / example(s) | comment | +|---------------------|---------------------|-----------------| +| `{pycalver}` | v201902.1001-beta | | +| `{pep440_pycalver}` | 201902.1b0 | | +| `{semver}` | 1.2.3 | | + + +### Pattern Usage + There are some limitations to keep in mind: 1. A version string cannot span multiple lines. diff --git a/src/pycalver2/patterns.py b/src/pycalver2/patterns.py index d1a5097..c6081f8 100644 --- a/src/pycalver2/patterns.py +++ b/src/pycalver2/patterns.py @@ -86,6 +86,7 @@ PART_PATTERNS = { 'PATCH': r"[0-9]+", 'MICRO': r"[0-9]+", 'BUILD': r"[0-9]+", + 'BLD' : r"[1-9][0-9]*", 'TAG' : r"(?:alpha|beta|dev|pre|rc|post|final)", 'PYTAG': r"(?:a|b|dev|rc|post)", 'NUM' : r"[0-9]+", @@ -111,6 +112,7 @@ PATTERN_PART_FIELDS = { 'PATCH': 'patch', 'MICRO': 'patch', 'BUILD': 'bid', + 'BLD' : 'bid', 'TAG' : 'tag', 'PYTAG': 'pytag', 'NUM' : 'num', @@ -130,6 +132,10 @@ def _fmt_num(val: FieldValue) -> str: return str(val) +def _fmt_bld(val: FieldValue) -> str: + return str(int(val)) + + def _fmt_yy(year_y: FieldValue) -> str: return str(int(str(year_y)[-2:])) @@ -189,6 +195,7 @@ PART_FORMATS: typ.Dict[str, typ.Callable[[FieldValue], str]] = { 'PATCH': _fmt_num, 'MICRO': _fmt_num, 'BUILD': _fmt_num, + 'BLD' : _fmt_bld, 'TAG' : _fmt_num, 'PYTAG': _fmt_num, 'NUM' : _fmt_num, diff --git a/test/test_version.py b/test/test_version.py index 09fb0af..696fec7 100644 --- a/test/test_version.py +++ b/test/test_version.py @@ -10,9 +10,9 @@ import datetime as dt import pytest import pycalver.version as v1version +import pycalver2.version as v2version import pycalver.patterns as v1patterns -# import pycalver2.version as v2version # import pycalver2.patterns as v2patterns # pylint:disable=protected-access ; allowed for test code @@ -182,7 +182,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): +def test_v1_parse_versions(pattern_str, line, expected_vinfo): pattern = v1patterns.compile_pattern(pattern_str) version_match = pattern.regexp.search(line) @@ -196,3 +196,25 @@ def test_parse_versions(pattern_str, line, expected_vinfo): version_info = v1version.parse_version_info(version_str, pattern_str) assert version_info == expected_vinfo + + +# def test_v2_parse_versions(pattern_str, line, expected_vinfo): +def test_v2_parse_versions(): + vnfo = v2version.parse_version_info("v201712.0033", pattern="vYYYY0M.BUILD[-TAG[NUM]]") + fvals = {'year_y': 2017, 'month': 12, 'bid': "0033"} + assert vnfo == v2version._parse_version_info(fvals) + + +def test_v2_format_version(): + pattern = "vYYYY0M.BUILD[-TAG[NUM]]" + in_version = "v200701.0033-beta" + + vinfo = v2version.parse_version_info(in_version, pattern=pattern) + out_version = v2version.format_version(vinfo, pattern=pattern) + assert in_version == out_version + + result = v2version.format_version(vinfo, pattern="v0Y.BUILD[-TAG]") + assert result == "v07.0033-beta" + + result = v2version.format_version(vinfo, pattern="vYY.BLD[-TAG]") + assert result == "v7.33-beta"