Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for local sections #8

Merged
merged 6 commits into from
Dec 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like an accidental find/replace 🙃 But I need to fix up the reference to post/dev anyway, so I can take care of it.

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