Skip to content

Commit

Permalink
Merge pull request #8 from mariusvniekerk/parse-local-segments
Browse files Browse the repository at this point in the history
Add support for local sections
  • Loading branch information
mtkennerly authored Dec 3, 2020
2 parents 7fb70af + 99781df commit 7825eaf
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 22 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ build/
dist/
htmlcov/
pip-wheel-metadata/
# Used by github codespaces
pythonenv*/
6 changes: 6 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,10 @@
"python.linting.pylintEnabled": false,
"python.pythonPath": "${workspaceFolder}/.venv",
"yaml.format.enable": true,
"python.testing.pytestArgs": [
"."
],
"python.testing.unittestEnabled": false,
"python.testing.nosetestsEnabled": false,
"python.testing.pytestEnabled": true,
}
122 changes: 104 additions & 18 deletions dunamai/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,15 @@
from enum import Enum
from functools import total_ordering
from pathlib import Path
from typing import Any, Callable, Mapping, Optional, Sequence, Tuple, TypeVar, Union
from typing import Any, Callable, Mapping, Optional, Sequence, Tuple, TypeVar, Union, NamedTuple

_VERSION_PATTERN = r"""
(?x) (?# ignore whitespace)
^v(?P<base>\d+\.\d+\.\d+) (?# v1.2.3)
(-?((?P<stage>[a-zA-Z]+)\.?(?P<revision>\d+)?))? (?# b0)
(\+(?P<tagged_metadata>.+))?$ (?# +linux)
"""

_VERSION_PATTERN = r"^v(?P<base>\d+\.\d+\.\d+)(-?((?P<stage>[a-zA-Z]+)\.?(?P<revision>\d+)?))?$"
# PEP 440: [N!]N(.N)*[{a|b|rc}N][.postN][.devN][+<local version label>]
_VALID_PEP440 = r"^(\d!)?\d+(\.\d+)*((a|b|rc)\d+)?(\.post\d+)?(\.dev\d+)?(\+.+)?$"
_VALID_SEMVER = (
Expand Down Expand Up @@ -59,9 +65,21 @@ def _run_cmd(
return (result.returncode, output)


_match_version_pattern_res = NamedTuple(
"_match_version_pattern_res",
[
("matched_tag", str),
("base", str),
("stage_revision", Optional[Tuple[str, Optional[int]]]),
("newer_tags", Sequence[str]),
("tagged_metadata", Optional[str]),
],
)


def _match_version_pattern(
pattern: str, sources: Sequence[str], latest_source: bool
) -> Tuple[str, str, Optional[Tuple[str, Optional[int]]], Sequence[str]]:
) -> _match_version_pattern_res:
"""
:return: Tuple of:
* matched tag
Expand All @@ -70,11 +88,13 @@ def _match_version_pattern(
* stage
* revision
* any newer unmatched tags
* tagged_metadata matched section
"""
pattern_match = None
base = None
stage_revision = None
newer_unmatched_tags = []
tagged_metadata = None

for source in sources[:1] if latest_source else sources:
pattern_match = re.search(pattern, source)
Expand Down Expand Up @@ -102,12 +122,15 @@ def _match_version_pattern(
try:
stage = pattern_match.group("stage")
revision = pattern_match.group("revision")
tagged_metadata = pattern_match.group("tagged_metadata")
if stage is not None:
stage_revision = (stage, None) if revision is None else (stage, int(revision))
except IndexError:
pass

return (source, base, stage_revision, newer_unmatched_tags)
return _match_version_pattern_res(
source, base, stage_revision, newer_unmatched_tags, tagged_metadata
)


def _blank(value: Optional[_T], default: _T) -> _T:
Expand Down Expand Up @@ -155,7 +178,8 @@ def __init__(
stage: Tuple[str, Optional[int]] = None,
distance: int = 0,
commit: str = None,
dirty: bool = None
dirty: bool = None,
tagged_metadata: Optional[str] = None
) -> None:
"""
:param base: Release segment, such as 0.1.0.
Expand All @@ -179,6 +203,8 @@ def __init__(
self.commit = commit
#: Whether there are uncommitted changes.
self.dirty = dirty
#: The version contains baked in tagged_metadata metadata
self.tagged_metadata = tagged_metadata

self._matched_tag = None # type: Optional[str]
self._newer_unmatched_tags = None # type: Optional[Sequence[str]]
Expand Down Expand Up @@ -227,12 +253,13 @@ def serialize(
format: str = None,
style: Style = None,
bump: bool = False,
tagged_metadata: bool = False,
) -> str:
"""
Create a string from the version info.
:param metadata: Metadata (commit, dirty) is normally included in
the local version part if post or dev are set. Set this to True to
the tagged_metadata version part if post or dev are set. Set this to True to
always include metadata, or set it to False to always exclude it.
:param dirty: Set this to True to include a dirty flag in the
metadata if applicable. Inert when metadata=False.
Expand All @@ -244,6 +271,7 @@ def serialize(
* {revision}
* {distance}
* {commit}
* {tagged_metadata}
* {dirty} which expands to either "dirty" or "clean"
:param style: Built-in output formats. Will default to PEP 440 if not
set and no custom format given. If you specify both a style and a
Expand All @@ -252,6 +280,8 @@ def serialize(
:param bump: If true, increment the last part of the `base` by 1,
unless `stage` is set, in which case either increment `revision`
by 1 or set it to a default of 2 if there was no revision.
:param tagged_metadata: If true use the tagged_metadata in the version as the first
segment of metadata.
"""
base = self.base
revision = self.revision
Expand All @@ -271,6 +301,7 @@ def serialize(
revision=_blank(revision, ""),
distance=_blank(self.distance, ""),
commit=_blank(self.commit, ""),
tagged_metadata=_blank(self.tagged_metadata, ""),
dirty="dirty" if self.dirty else "clean",
)
if style is not None:
Expand All @@ -283,6 +314,9 @@ def serialize(

meta_parts = []
if metadata is not False:
# treat tagged_metadata segments as the first meta_part
if tagged_metadata and self.tagged_metadata:
meta_parts.append(self.tagged_metadata)
if (metadata or self.distance > 0) and self.commit is not None:
meta_parts.append(self.commit)
if dirty and self.dirty:
Expand Down Expand Up @@ -374,12 +408,21 @@ def from_git(cls, pattern: str = _VERSION_PATTERN, latest_tag: bool = False) ->
t[0]
for t in reversed(sorted(detailed_tags, key=lambda x: x[1] if x[2] is None else x[2]))
]
tag, base, stage, unmatched = _match_version_pattern(pattern, tags, latest_tag)
tag, base, stage, unmatched, tagged_metadata = _match_version_pattern(
pattern, tags, latest_tag
)

code, msg = _run_cmd("git rev-list --count refs/tags/{}..HEAD".format(tag))
distance = int(msg)

version = cls(base, stage=stage, distance=distance, commit=commit, dirty=dirty)
version = cls(
base,
stage=stage,
distance=distance,
commit=commit,
dirty=dirty,
tagged_metadata=tagged_metadata,
)
version._matched_tag = tag
version._newer_unmatched_tags = unmatched
return version
Expand Down Expand Up @@ -420,13 +463,22 @@ def from_mercurial(cls, pattern: str = _VERSION_PATTERN, latest_tag: bool = Fals
distance = 0
return cls("0.0.0", distance=distance, commit=commit, dirty=dirty)
tags = [tag for tags in [line.split(":") for line in msg.splitlines()] for tag in tags]
tag, base, stage, unmatched = _match_version_pattern(pattern, tags, latest_tag)
tag, base, stage, unmatched, tagged_metadata = _match_version_pattern(
pattern, tags, latest_tag
)

code, msg = _run_cmd('hg log -r "{0}::{1} - {0}" --template "."'.format(tag, commit))
# The tag itself is in the list, so offset by 1.
distance = max(len(msg) - 1, 0)

version = cls(base, stage=stage, distance=distance, commit=commit, dirty=dirty)
version = cls(
base,
stage=stage,
distance=distance,
commit=commit,
dirty=dirty,
tagged_metadata=tagged_metadata,
)
version._matched_tag = tag
version._newer_unmatched_tags = unmatched
return version
Expand Down Expand Up @@ -463,13 +515,22 @@ def from_darcs(cls, pattern: str = _VERSION_PATTERN, latest_tag: bool = False) -
distance = 0
return cls("0.0.0", distance=distance, commit=commit, dirty=dirty)
tags = msg.splitlines()
tag, base, stage, unmatched = _match_version_pattern(pattern, tags, latest_tag)
tag, base, stage, unmatched, tagged_metadata = _match_version_pattern(
pattern, tags, latest_tag
)

code, msg = _run_cmd("darcs log --from-tag {} --count".format(tag))
# The tag itself is in the list, so offset by 1.
distance = int(msg) - 1

version = cls(base, stage=stage, distance=distance, commit=commit, dirty=dirty)
version = cls(
base,
stage=stage,
distance=distance,
commit=commit,
dirty=dirty,
tagged_metadata=tagged_metadata,
)
version._matched_tag = tag
version._newer_unmatched_tags = unmatched
return version
Expand Down Expand Up @@ -528,13 +589,22 @@ def from_subversion(
source = int(match.group(1))
tags_to_sources_revs[tag] = (source, rev)
tags = sorted(tags_to_sources_revs, key=lambda x: tags_to_sources_revs[x], reverse=True)
tag, base, stage, unmatched = _match_version_pattern(pattern, tags, latest_tag)
tag, base, stage, unmatched, tagged_metadata = _match_version_pattern(
pattern, tags, latest_tag
)

source, rev = tags_to_sources_revs[tag]
# The tag itself is in the list, so offset by 1.
distance = int(commit) - 1 - source

version = cls(base, stage=stage, distance=distance, commit=commit, dirty=dirty)
version = cls(
base,
stage=stage,
distance=distance,
commit=commit,
dirty=dirty,
tagged_metadata=tagged_metadata,
)
version._matched_tag = tag
version._newer_unmatched_tags = unmatched
return version
Expand Down Expand Up @@ -575,11 +645,20 @@ def from_bazaar(cls, pattern: str = _VERSION_PATTERN, latest_tag: bool = False)
if line.split()[1] != "?"
}
tags = [x[1] for x in sorted([(v, k) for k, v in tags_to_revs.items()], reverse=True)]
tag, base, stage, unmatched = _match_version_pattern(pattern, tags, latest_tag)
tag, base, stage, unmatched, tagged_metadata = _match_version_pattern(
pattern, tags, latest_tag
)

distance = int(commit) - tags_to_revs[tag]

version = cls(base, stage=stage, distance=distance, commit=commit, dirty=dirty)
version = cls(
base,
stage=stage,
distance=distance,
commit=commit,
dirty=dirty,
tagged_metadata=tagged_metadata,
)
version._matched_tag = tag
version._newer_unmatched_tags = unmatched
return version
Expand Down Expand Up @@ -651,12 +730,19 @@ def from_fossil(cls, pattern: str = _VERSION_PATTERN, latest_tag: bool = False)
(line.rsplit(",", 1)[0][5:-1], int(line.rsplit(",", 1)[1]) - 1)
for line in msg.splitlines()
]
tag, base, stage, unmatched = _match_version_pattern(
tag, base, stage, unmatched, tagged_metadata = _match_version_pattern(
pattern, [t for t, d in tags_to_distance], latest_tag
)
distance = dict(tags_to_distance)[tag]

version = cls(base, stage=stage, distance=distance, commit=commit, dirty=dirty)
version = cls(
base,
stage=stage,
distance=distance,
commit=commit,
dirty=dirty,
tagged_metadata=tagged_metadata,
)
version._matched_tag = tag
version._newer_unmatched_tags = unmatched
return version
Expand Down
10 changes: 9 additions & 1 deletion dunamai/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@
"dest": "dirty",
"help": "Include dirty flag if applicable",
},
{
"triggers": ["--tagged-metadata"],
"action": "store_true",
"dest": "tagged_metadata",
"help": "Include tagged metadata if applicable",
},
{
"triggers": ["--pattern"],
"default": _VERSION_PATTERN,
Expand Down Expand Up @@ -202,9 +208,10 @@ def from_vcs(
tag_dir: str,
debug: bool,
bump: bool,
tagged_metadata: bool,
) -> None:
version = Version.from_vcs(vcs, pattern, latest_tag, tag_dir)
print(version.serialize(metadata, dirty, format, style, bump))
print(version.serialize(metadata, dirty, format, style, bump, tagged_metadata=tagged_metadata))
if debug:
print("# Matched tag: {}".format(version._matched_tag), file=sys.stderr)
print("# Newer unmatched tags: {}".format(version._newer_unmatched_tags), file=sys.stderr)
Expand All @@ -226,6 +233,7 @@ def main() -> None:
tag_dir,
args.debug,
args.bump,
args.tagged_metadata,
)
elif args.command == "check":
version = from_stdin(args.version)
Expand Down
20 changes: 17 additions & 3 deletions tests/unit/test_dunamai.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,14 @@ def inner(*args, **kwargs):


def test__version__init() -> None:
v = Version("1", stage=("a", 2), distance=3, commit="abc", dirty=True)
v = Version("1", stage=("a", 2), distance=3, commit="abc", dirty=True, tagged_metadata="def")
assert v.base == "1"
assert v.stage == "a"
assert v.revision == 2
assert v.distance == 3
assert v.commit == "abc"
assert v.dirty
assert v.tagged_metadata == "def"


def test__version__str() -> None:
Expand Down Expand Up @@ -270,6 +271,10 @@ def test__version__serialize__pep440_metadata() -> None:
Version("0.1.0", distance=1, commit="abc").serialize(metadata=False) == "0.1.0.post1.dev0"
)

v = Version("0.1.0", distance=1, commit="abc", tagged_metadata="def")
serialized = v.serialize(tagged_metadata=True)
assert serialized == "0.1.0.post1.dev0+def.abc"


def test__version__serialize__semver_with_metadata() -> None:
style = Style.SemVer
Expand Down Expand Up @@ -490,15 +495,22 @@ def test__check_version__pvp() -> None:


def test__default_version_pattern() -> None:
def check_re(tag: str, base: str = None, stage: str = None, revision: str = None) -> None:
def check_re(
tag: str,
base: str = None,
stage: str = None,
revision: str = None,
tagged_metadata: str = None,
) -> None:
result = re.search(_VERSION_PATTERN, tag)
if result is None:
if any(x is not None for x in [base, stage, revision]):
raise ValueError("Pattern did not match")
raise ValueError("Pattern did not match, {tag}".format(tag=tag))
else:
assert result.group("base") == base
assert result.group("stage") == stage
assert result.group("revision") == revision
assert result.group("tagged_metadata") == tagged_metadata

check_re("v0.1.0", "0.1.0")
check_re("av0.1.0")
Expand All @@ -515,6 +527,8 @@ def check_re(tag: str, base: str = None, stage: str = None, revision: str = None
check_re("v0.1.0rc.4", "0.1.0", "rc", "4")
check_re("v0.1.0-beta", "0.1.0", "beta")

check_re("v0.1.0rc.4+specifier", "0.1.0", "rc", "4", tagged_metadata="specifier")


def test__serialize_pep440():
assert serialize_pep440("1.2.3") == "1.2.3"
Expand Down
Loading

0 comments on commit 7825eaf

Please sign in to comment.