ps-version 0.2.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ps_version-0.2.8/PKG-INFO +10 -0
- ps_version-0.2.8/pyproject.toml +15 -0
- ps_version-0.2.8/src/ps/version/__init__.py +20 -0
- ps_version-0.2.8/src/ps/version/_version.py +287 -0
- ps_version-0.2.8/src/ps/version/_version_constraint.py +10 -0
- ps_version-0.2.8/src/ps/version/_version_metadata.py +28 -0
- ps_version-0.2.8/src/ps/version/_version_prerelease.py +37 -0
- ps_version-0.2.8/src/ps/version/_version_standard.py +9 -0
- ps_version-0.2.8/src/ps/version/parsers/__init__.py +13 -0
- ps_version-0.2.8/src/ps/version/parsers/_base_parser.py +10 -0
- ps_version-0.2.8/src/ps/version/parsers/_calver_parser.py +38 -0
- ps_version-0.2.8/src/ps/version/parsers/_loose_parser.py +34 -0
- ps_version-0.2.8/src/ps/version/parsers/_nuget_parser.py +42 -0
- ps_version-0.2.8/src/ps/version/parsers/_pep440_parser.py +45 -0
- ps_version-0.2.8/src/ps/version/parsers/_semver_parser.py +42 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ps-version
|
|
3
|
+
Version: 0.2.8
|
|
4
|
+
Summary:
|
|
5
|
+
Requires-Python: >=3.10,<3.14
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "ps-version"
|
|
3
|
+
description = ""
|
|
4
|
+
requires-python = ">=3.10,<3.14"
|
|
5
|
+
version = "0.2.8"
|
|
6
|
+
|
|
7
|
+
[tool.poetry]
|
|
8
|
+
packages = [ { include = "ps/version", from = "src" } ]
|
|
9
|
+
|
|
10
|
+
[tool.ps-plugin]
|
|
11
|
+
host-project = "../.."
|
|
12
|
+
|
|
13
|
+
[build-system]
|
|
14
|
+
requires = ["poetry-core>=1.0.0"]
|
|
15
|
+
build-backend = "poetry.core.masonry.api"
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from ._version import Version, VersionFormatter
|
|
2
|
+
from ._version_constraint import VersionConstraint
|
|
3
|
+
from ._version_metadata import VersionMetadata
|
|
4
|
+
from ._version_prerelease import VersionPreRelease
|
|
5
|
+
from ._version_standard import VersionStandard
|
|
6
|
+
from .parsers import CalVerParser, LooseParser, NuGetParser, PEP440Parser, SemVerParser
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"Version",
|
|
10
|
+
"VersionFormatter",
|
|
11
|
+
"VersionConstraint",
|
|
12
|
+
"VersionMetadata",
|
|
13
|
+
"VersionPreRelease",
|
|
14
|
+
"VersionStandard",
|
|
15
|
+
"CalVerParser",
|
|
16
|
+
"LooseParser",
|
|
17
|
+
"NuGetParser",
|
|
18
|
+
"PEP440Parser",
|
|
19
|
+
"SemVerParser",
|
|
20
|
+
]
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
# ruff: noqa: PLC0415
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from functools import lru_cache, total_ordering
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from ._version_metadata import VersionMetadata
|
|
7
|
+
from ._version_prerelease import VersionPreRelease
|
|
8
|
+
from ._version_constraint import VersionConstraint
|
|
9
|
+
from ._version_standard import VersionStandard
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
_PEP440_CANONICAL_LABELS: dict[str, str] = {
|
|
13
|
+
"alpha": "a",
|
|
14
|
+
"beta": "b",
|
|
15
|
+
"c": "rc",
|
|
16
|
+
"pre": "rc",
|
|
17
|
+
"preview": "rc",
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@lru_cache
|
|
22
|
+
def _get_parsers() -> list:
|
|
23
|
+
from .parsers import CalVerParser, LooseParser, NuGetParser, PEP440Parser, SemVerParser
|
|
24
|
+
|
|
25
|
+
return [
|
|
26
|
+
PEP440Parser(),
|
|
27
|
+
SemVerParser(),
|
|
28
|
+
NuGetParser(),
|
|
29
|
+
CalVerParser(),
|
|
30
|
+
LooseParser(),
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@total_ordering
|
|
35
|
+
@dataclass(slots=True)
|
|
36
|
+
class Version:
|
|
37
|
+
major: int = 0
|
|
38
|
+
minor: Optional[int] = None
|
|
39
|
+
patch: Optional[int] = None
|
|
40
|
+
rev: Optional[int] = None
|
|
41
|
+
pre: Optional[VersionPreRelease] = None
|
|
42
|
+
post: Optional[int] = None
|
|
43
|
+
dev: Optional[int] = None
|
|
44
|
+
metadata: Optional[VersionMetadata] = None
|
|
45
|
+
|
|
46
|
+
def __post_init__(self) -> None:
|
|
47
|
+
if self.major < 0:
|
|
48
|
+
raise ValueError(f"major must be non-negative, got {self.major}")
|
|
49
|
+
if self.minor is not None and self.minor < 0:
|
|
50
|
+
raise ValueError(f"minor must be non-negative, got {self.minor}")
|
|
51
|
+
if self.patch is not None and self.patch < 0:
|
|
52
|
+
raise ValueError(f"patch must be non-negative, got {self.patch}")
|
|
53
|
+
if self.rev is not None and self.rev < 0:
|
|
54
|
+
raise ValueError(f"rev must be non-negative, got {self.rev}")
|
|
55
|
+
if self.post is not None and self.post < 0:
|
|
56
|
+
raise ValueError(f"post must be non-negative, got {self.post}")
|
|
57
|
+
if self.dev is not None and self.dev < 0:
|
|
58
|
+
raise ValueError(f"dev must be non-negative, got {self.dev}")
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def core(self) -> str:
|
|
62
|
+
parts = [str(self.major)]
|
|
63
|
+
if self.minor is not None:
|
|
64
|
+
parts.append(str(self.minor))
|
|
65
|
+
if self.patch is not None:
|
|
66
|
+
parts.append(str(self.patch))
|
|
67
|
+
if self.rev is not None and self.rev > 0:
|
|
68
|
+
parts.append(str(self.rev))
|
|
69
|
+
return ".".join(parts)
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def standards(self) -> list[VersionStandard]:
|
|
73
|
+
compatible = []
|
|
74
|
+
|
|
75
|
+
# SEMVER: requires minor and patch, no rev/post/dev
|
|
76
|
+
if (
|
|
77
|
+
self.minor is not None
|
|
78
|
+
and self.patch is not None
|
|
79
|
+
and self.rev is None
|
|
80
|
+
and self.post is None
|
|
81
|
+
and self.dev is None
|
|
82
|
+
):
|
|
83
|
+
compatible.append(VersionStandard.SEMVER)
|
|
84
|
+
|
|
85
|
+
# NUGET: requires minor and patch, no metadata/post/dev
|
|
86
|
+
if (
|
|
87
|
+
self.minor is not None
|
|
88
|
+
and self.patch is not None
|
|
89
|
+
and self.metadata is None
|
|
90
|
+
and self.post is None
|
|
91
|
+
and self.dev is None
|
|
92
|
+
):
|
|
93
|
+
compatible.append(VersionStandard.NUGET)
|
|
94
|
+
|
|
95
|
+
# CALVER: requires minor, major >= 20, no pre/post/dev
|
|
96
|
+
if (
|
|
97
|
+
self.minor is not None
|
|
98
|
+
and self.major >= 20
|
|
99
|
+
and self.pre is None
|
|
100
|
+
and self.post is None
|
|
101
|
+
and self.dev is None
|
|
102
|
+
):
|
|
103
|
+
compatible.append(VersionStandard.CALVER)
|
|
104
|
+
|
|
105
|
+
# LOOSE: no pre/post/dev
|
|
106
|
+
if self.pre is None and self.post is None and self.dev is None:
|
|
107
|
+
compatible.append(VersionStandard.LOOSE)
|
|
108
|
+
|
|
109
|
+
# PEP440 is always compatible
|
|
110
|
+
compatible.append(VersionStandard.PEP440)
|
|
111
|
+
|
|
112
|
+
return compatible
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def format(self) -> "VersionFormatter":
|
|
116
|
+
return VersionFormatter(self)
|
|
117
|
+
|
|
118
|
+
def _compare_core(self, other: "Version") -> int:
|
|
119
|
+
self_parts = (self.major, self.minor or 0, self.patch or 0, self.rev or 0)
|
|
120
|
+
other_parts = (other.major, other.minor or 0, other.patch or 0, other.rev or 0)
|
|
121
|
+
if self_parts < other_parts:
|
|
122
|
+
return -1
|
|
123
|
+
if self_parts > other_parts:
|
|
124
|
+
return 1
|
|
125
|
+
return 0
|
|
126
|
+
|
|
127
|
+
def _compare_pre(self, other: "Version") -> int:
|
|
128
|
+
if self.pre is None and other.pre is None:
|
|
129
|
+
return 0
|
|
130
|
+
if self.pre is None:
|
|
131
|
+
return 1
|
|
132
|
+
if other.pre is None:
|
|
133
|
+
return -1
|
|
134
|
+
|
|
135
|
+
self_parts = (self.pre.name.casefold(), self.pre.number or 0)
|
|
136
|
+
other_parts = (other.pre.name.casefold(), other.pre.number or 0)
|
|
137
|
+
if self_parts < other_parts:
|
|
138
|
+
return -1
|
|
139
|
+
if self_parts > other_parts:
|
|
140
|
+
return 1
|
|
141
|
+
return 0
|
|
142
|
+
|
|
143
|
+
def __eq__(self, other: object) -> bool:
|
|
144
|
+
if not isinstance(other, Version):
|
|
145
|
+
return NotImplemented
|
|
146
|
+
return (
|
|
147
|
+
self._compare_core(other) == 0
|
|
148
|
+
and self._compare_pre(other) == 0
|
|
149
|
+
and (self.post or 0) == (other.post or 0)
|
|
150
|
+
and (self.dev or 0) == (other.dev or 0)
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
def __hash__(self) -> int:
|
|
154
|
+
return hash((
|
|
155
|
+
self.major,
|
|
156
|
+
self.minor,
|
|
157
|
+
self.patch,
|
|
158
|
+
self.rev,
|
|
159
|
+
self.pre.name.casefold() if self.pre else None,
|
|
160
|
+
self.pre.number if self.pre else None,
|
|
161
|
+
self.post,
|
|
162
|
+
self.dev,
|
|
163
|
+
))
|
|
164
|
+
|
|
165
|
+
def __lt__(self, other: object) -> bool:
|
|
166
|
+
if not isinstance(other, Version):
|
|
167
|
+
return NotImplemented
|
|
168
|
+
|
|
169
|
+
core_cmp = self._compare_core(other)
|
|
170
|
+
if core_cmp != 0:
|
|
171
|
+
return core_cmp < 0
|
|
172
|
+
|
|
173
|
+
dev_cmp = (self.dev or 0, other.dev or 0)
|
|
174
|
+
if dev_cmp[0] != dev_cmp[1]:
|
|
175
|
+
if self.dev is None:
|
|
176
|
+
return False
|
|
177
|
+
if other.dev is None:
|
|
178
|
+
return True
|
|
179
|
+
return self.dev < other.dev
|
|
180
|
+
|
|
181
|
+
pre_cmp = self._compare_pre(other)
|
|
182
|
+
if pre_cmp != 0:
|
|
183
|
+
return pre_cmp < 0
|
|
184
|
+
|
|
185
|
+
return (self.post or 0) < (other.post or 0)
|
|
186
|
+
|
|
187
|
+
def __str__(self) -> str:
|
|
188
|
+
standards = self.standards
|
|
189
|
+
return self.format(standards[0] if standards else VersionStandard.PEP440)
|
|
190
|
+
|
|
191
|
+
def get_constraint(self, constraint: VersionConstraint) -> str:
|
|
192
|
+
major = self.major
|
|
193
|
+
minor = self.minor or 0
|
|
194
|
+
patch = self.patch or 0
|
|
195
|
+
full_version = str(self)
|
|
196
|
+
|
|
197
|
+
if constraint == VersionConstraint.EXACT:
|
|
198
|
+
return f"=={full_version}"
|
|
199
|
+
if constraint == VersionConstraint.MINIMUM_ONLY:
|
|
200
|
+
return f">={full_version}"
|
|
201
|
+
if constraint == VersionConstraint.RANGE_NEXT_MAJOR:
|
|
202
|
+
return f">={full_version},<{major + 1}.0.0"
|
|
203
|
+
if constraint == VersionConstraint.RANGE_NEXT_MINOR:
|
|
204
|
+
return f">={full_version},<{major}.{minor + 1}.0"
|
|
205
|
+
if constraint == VersionConstraint.RANGE_NEXT_PATCH:
|
|
206
|
+
return f">={full_version},<{major}.{minor}.{patch + 1}"
|
|
207
|
+
if major > 0:
|
|
208
|
+
upper = f"{major + 1}.0.0"
|
|
209
|
+
elif minor > 0:
|
|
210
|
+
upper = f"0.{minor + 1}.0"
|
|
211
|
+
else:
|
|
212
|
+
upper = f"0.0.{patch + 1}"
|
|
213
|
+
return f">={full_version},<{upper}"
|
|
214
|
+
|
|
215
|
+
@staticmethod
|
|
216
|
+
def parse(version_string: Optional[str]) -> Optional["Version"]:
|
|
217
|
+
if version_string:
|
|
218
|
+
for parser in _get_parsers():
|
|
219
|
+
result = parser.parse(version_string.strip())
|
|
220
|
+
if result:
|
|
221
|
+
return result
|
|
222
|
+
return None
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
@dataclass(slots=True)
|
|
226
|
+
class VersionFormatter:
|
|
227
|
+
version: Version
|
|
228
|
+
|
|
229
|
+
def __call__(self, standard: VersionStandard) -> str:
|
|
230
|
+
if standard == VersionStandard.PEP440:
|
|
231
|
+
parts = [self.version.core]
|
|
232
|
+
if self.version.pre:
|
|
233
|
+
canonical = _PEP440_CANONICAL_LABELS.get(self.version.pre.name.casefold(), self.version.pre.name)
|
|
234
|
+
num = str(self.version.pre.number) if self.version.pre.number is not None else ""
|
|
235
|
+
parts.append(f"{canonical}{num}")
|
|
236
|
+
if self.version.post is not None:
|
|
237
|
+
parts.append(f".post{self.version.post}")
|
|
238
|
+
if self.version.dev is not None:
|
|
239
|
+
parts.append(f".dev{self.version.dev}")
|
|
240
|
+
if self.version.metadata:
|
|
241
|
+
parts.append(f"+{self.version.metadata}")
|
|
242
|
+
return "".join(parts)
|
|
243
|
+
|
|
244
|
+
if standard == VersionStandard.SEMVER:
|
|
245
|
+
parts = [self.version.core]
|
|
246
|
+
if self.version.pre:
|
|
247
|
+
parts.append(f"-{self.version.pre.name}")
|
|
248
|
+
if self.version.pre.number is not None:
|
|
249
|
+
parts.append(f".{self.version.pre.number}")
|
|
250
|
+
if self.version.metadata:
|
|
251
|
+
parts.append(f"+{self.version.metadata}")
|
|
252
|
+
return "".join(parts)
|
|
253
|
+
|
|
254
|
+
if standard == VersionStandard.NUGET:
|
|
255
|
+
parts = [self.version.core]
|
|
256
|
+
if self.version.pre:
|
|
257
|
+
parts.append(f"-{self.version.pre.name}")
|
|
258
|
+
if self.version.pre.number is not None:
|
|
259
|
+
parts.append(f".{self.version.pre.number}")
|
|
260
|
+
return "".join(parts)
|
|
261
|
+
|
|
262
|
+
if standard in (VersionStandard.CALVER, VersionStandard.LOOSE):
|
|
263
|
+
if self.version.metadata:
|
|
264
|
+
return f"{self.version.core}-{self.version.metadata}"
|
|
265
|
+
return self.version.core
|
|
266
|
+
|
|
267
|
+
return self(VersionStandard.PEP440)
|
|
268
|
+
|
|
269
|
+
@property
|
|
270
|
+
def pep440(self) -> str:
|
|
271
|
+
return self(VersionStandard.PEP440)
|
|
272
|
+
|
|
273
|
+
@property
|
|
274
|
+
def semver(self) -> str:
|
|
275
|
+
return self(VersionStandard.SEMVER)
|
|
276
|
+
|
|
277
|
+
@property
|
|
278
|
+
def nuget(self) -> str:
|
|
279
|
+
return self(VersionStandard.NUGET)
|
|
280
|
+
|
|
281
|
+
@property
|
|
282
|
+
def calver(self) -> str:
|
|
283
|
+
return self(VersionStandard.CALVER)
|
|
284
|
+
|
|
285
|
+
@property
|
|
286
|
+
def loose(self) -> str:
|
|
287
|
+
return self(VersionStandard.LOOSE)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class VersionConstraint(str, Enum):
|
|
5
|
+
EXACT = "exact"
|
|
6
|
+
MINIMUM_ONLY = "minimum-only"
|
|
7
|
+
RANGE_NEXT_MAJOR = "range-next-major"
|
|
8
|
+
RANGE_NEXT_MINOR = "range-next-minor"
|
|
9
|
+
RANGE_NEXT_PATCH = "range-next-patch"
|
|
10
|
+
COMPATIBLE = "compatible"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from functools import total_ordering
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@total_ordering
|
|
6
|
+
@dataclass(slots=True)
|
|
7
|
+
class VersionMetadata:
|
|
8
|
+
value: str
|
|
9
|
+
|
|
10
|
+
def __str__(self) -> str:
|
|
11
|
+
return self.value
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def parts(self) -> list[str]:
|
|
15
|
+
return self.value.split(".")
|
|
16
|
+
|
|
17
|
+
def __eq__(self, other: object) -> bool:
|
|
18
|
+
if not isinstance(other, VersionMetadata):
|
|
19
|
+
return NotImplemented
|
|
20
|
+
return self.value == other.value
|
|
21
|
+
|
|
22
|
+
def __lt__(self, other: object) -> bool:
|
|
23
|
+
if not isinstance(other, VersionMetadata):
|
|
24
|
+
return NotImplemented
|
|
25
|
+
return self.value < other.value
|
|
26
|
+
|
|
27
|
+
def __hash__(self) -> int:
|
|
28
|
+
return hash(self.value)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from functools import total_ordering
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@total_ordering
|
|
7
|
+
@dataclass(slots=True)
|
|
8
|
+
class VersionPreRelease:
|
|
9
|
+
name: str
|
|
10
|
+
number: Optional[int] = None
|
|
11
|
+
|
|
12
|
+
def __post_init__(self) -> None:
|
|
13
|
+
if self.number is not None and self.number < 0:
|
|
14
|
+
raise ValueError(f"Pre-release number must be non-negative, got {self.number}")
|
|
15
|
+
|
|
16
|
+
def __str__(self) -> str:
|
|
17
|
+
result = self.name
|
|
18
|
+
if self.number is not None:
|
|
19
|
+
result += str(self.number)
|
|
20
|
+
return result
|
|
21
|
+
|
|
22
|
+
def __eq__(self, other: object) -> bool:
|
|
23
|
+
if not isinstance(other, VersionPreRelease):
|
|
24
|
+
return NotImplemented
|
|
25
|
+
return self.name.casefold() == other.name.casefold() and (self.number or 0) == (other.number or 0)
|
|
26
|
+
|
|
27
|
+
def __lt__(self, other: object) -> bool:
|
|
28
|
+
if not isinstance(other, VersionPreRelease):
|
|
29
|
+
return NotImplemented
|
|
30
|
+
self_name = self.name.casefold()
|
|
31
|
+
other_name = other.name.casefold()
|
|
32
|
+
if self_name != other_name:
|
|
33
|
+
return self_name < other_name
|
|
34
|
+
return (self.number or 0) < (other.number or 0)
|
|
35
|
+
|
|
36
|
+
def __hash__(self) -> int:
|
|
37
|
+
return hash((self.name.casefold(), self.number))
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from ._calver_parser import CalVerParser
|
|
2
|
+
from ._loose_parser import LooseParser
|
|
3
|
+
from ._nuget_parser import NuGetParser
|
|
4
|
+
from ._pep440_parser import PEP440Parser
|
|
5
|
+
from ._semver_parser import SemVerParser
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"CalVerParser",
|
|
9
|
+
"LooseParser",
|
|
10
|
+
"NuGetParser",
|
|
11
|
+
"PEP440Parser",
|
|
12
|
+
"SemVerParser",
|
|
13
|
+
]
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Optional, cast
|
|
3
|
+
|
|
4
|
+
from .. import Version, VersionMetadata
|
|
5
|
+
from ._base_parser import BaseParser
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CalVerParser(BaseParser):
|
|
9
|
+
PATTERN = re.compile(
|
|
10
|
+
r"^(?P<major>20\d{2}|\d{2})"
|
|
11
|
+
r"\.(?P<minor>\d+)"
|
|
12
|
+
r"(?:\.(?P<patch>\d+))?"
|
|
13
|
+
r"(?:\.(?P<rev>\d+))?"
|
|
14
|
+
r"(?:[.\-+](?P<suffix>.+))?$"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
def parse(self, version_string: str) -> Optional[Version]:
|
|
18
|
+
match = self.PATTERN.match(version_string)
|
|
19
|
+
if not match:
|
|
20
|
+
return None
|
|
21
|
+
|
|
22
|
+
groups = match.groupdict()
|
|
23
|
+
major = int(groups["major"])
|
|
24
|
+
|
|
25
|
+
if major < 20 or (major > 99 and major < 2020):
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
patch = groups["patch"]
|
|
29
|
+
rev = groups["rev"]
|
|
30
|
+
suffix = groups["suffix"]
|
|
31
|
+
|
|
32
|
+
return Version(
|
|
33
|
+
major=major,
|
|
34
|
+
minor=int(groups["minor"]),
|
|
35
|
+
patch=int(patch) if patch else None,
|
|
36
|
+
rev=int(rev) if rev else None,
|
|
37
|
+
metadata=VersionMetadata(cast(str, suffix)) if suffix else None,
|
|
38
|
+
)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Optional, cast
|
|
3
|
+
|
|
4
|
+
from .. import Version, VersionMetadata
|
|
5
|
+
from ._base_parser import BaseParser
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class LooseParser(BaseParser):
|
|
9
|
+
PATTERN = re.compile(
|
|
10
|
+
r"^(?P<major>\d+)"
|
|
11
|
+
r"(?:\.(?P<minor>\d+))?"
|
|
12
|
+
r"(?:\.(?P<patch>\d+))?"
|
|
13
|
+
r"(?:\.(?P<rev>\d+))?"
|
|
14
|
+
r"(?:[.\-+](?P<suffix>.+))?$"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
def parse(self, version_string: str) -> Optional[Version]:
|
|
18
|
+
match = self.PATTERN.match(version_string)
|
|
19
|
+
if not match:
|
|
20
|
+
return None
|
|
21
|
+
|
|
22
|
+
groups = match.groupdict()
|
|
23
|
+
minor = groups["minor"]
|
|
24
|
+
patch = groups["patch"]
|
|
25
|
+
rev = groups["rev"]
|
|
26
|
+
suffix = groups["suffix"]
|
|
27
|
+
|
|
28
|
+
return Version(
|
|
29
|
+
major=int(groups["major"]),
|
|
30
|
+
minor=int(minor) if minor else None,
|
|
31
|
+
patch=int(patch) if patch else None,
|
|
32
|
+
rev=int(rev) if rev else None,
|
|
33
|
+
metadata=VersionMetadata(cast(str, suffix)) if suffix else None,
|
|
34
|
+
)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Optional, cast
|
|
3
|
+
|
|
4
|
+
from .. import Version, VersionPreRelease
|
|
5
|
+
from ._base_parser import BaseParser
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class NuGetParser(BaseParser):
|
|
9
|
+
PATTERN = re.compile(
|
|
10
|
+
r"^(?P<major>\d+)"
|
|
11
|
+
r"\.(?P<minor>\d+)"
|
|
12
|
+
r"\.(?P<patch>\d+)"
|
|
13
|
+
r"(?:\.(?P<rev>\d+))?"
|
|
14
|
+
r"(?:-(?P<pre>[0-9A-Za-z\-]+(?:\.[0-9A-Za-z\-]+)*))?$"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
def parse(self, version_string: str) -> Optional[Version]:
|
|
18
|
+
match = self.PATTERN.match(version_string)
|
|
19
|
+
if not match:
|
|
20
|
+
return None
|
|
21
|
+
|
|
22
|
+
groups = match.groupdict()
|
|
23
|
+
pre_str = cast(Optional[str], groups["pre"])
|
|
24
|
+
pre_release: Optional[VersionPreRelease] = None
|
|
25
|
+
|
|
26
|
+
if pre_str:
|
|
27
|
+
pre_parts = re.match(r"^([A-Za-z]+)\.?(\d+)?", pre_str)
|
|
28
|
+
if pre_parts:
|
|
29
|
+
pre_num = pre_parts.group(2)
|
|
30
|
+
pre_release = VersionPreRelease(
|
|
31
|
+
cast(str, pre_parts.group(1)),
|
|
32
|
+
int(pre_num) if pre_num else None,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
rev = groups["rev"]
|
|
36
|
+
return Version(
|
|
37
|
+
major=int(groups["major"]),
|
|
38
|
+
minor=int(groups["minor"]),
|
|
39
|
+
patch=int(groups["patch"]),
|
|
40
|
+
rev=int(rev) if rev else None,
|
|
41
|
+
pre=pre_release,
|
|
42
|
+
)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Optional, cast
|
|
3
|
+
|
|
4
|
+
from .. import Version, VersionMetadata, VersionPreRelease
|
|
5
|
+
from ._base_parser import BaseParser
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class PEP440Parser(BaseParser):
|
|
9
|
+
PATTERN = re.compile(
|
|
10
|
+
r"^(?P<major>\d+)"
|
|
11
|
+
r"(?:\.(?P<minor>\d+))?"
|
|
12
|
+
r"(?:\.(?P<patch>\d+))?"
|
|
13
|
+
r"(?:\.(?P<rev>\d+))?"
|
|
14
|
+
r"(?:(?P<pre_label>a|alpha|b|beta|rc|c|pre|preview)(?P<pre_num>\d+))?"
|
|
15
|
+
r"(?:\.post(?P<post>\d+))?"
|
|
16
|
+
r"(?:\.dev(?P<dev>\d+))?"
|
|
17
|
+
r"(?:\+(?P<meta>[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*))?$",
|
|
18
|
+
re.IGNORECASE,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
def parse(self, version_string: str) -> Optional[Version]:
|
|
22
|
+
match = self.PATTERN.match(version_string)
|
|
23
|
+
if not match:
|
|
24
|
+
return None
|
|
25
|
+
|
|
26
|
+
groups = match.groupdict()
|
|
27
|
+
minor = groups["minor"]
|
|
28
|
+
patch = groups["patch"]
|
|
29
|
+
rev = groups["rev"]
|
|
30
|
+
pre_label = groups["pre_label"]
|
|
31
|
+
pre_num = groups["pre_num"]
|
|
32
|
+
post = groups["post"]
|
|
33
|
+
dev = groups["dev"]
|
|
34
|
+
meta = groups["meta"]
|
|
35
|
+
|
|
36
|
+
return Version(
|
|
37
|
+
major=int(groups["major"]),
|
|
38
|
+
minor=int(minor) if minor else None,
|
|
39
|
+
patch=int(patch) if patch else None,
|
|
40
|
+
rev=int(rev) if rev else None,
|
|
41
|
+
pre=VersionPreRelease(pre_label, int(pre_num)) if pre_label and pre_num else None,
|
|
42
|
+
post=int(post) if post else None,
|
|
43
|
+
dev=int(dev) if dev else None,
|
|
44
|
+
metadata=VersionMetadata(cast(str, meta)) if meta else None,
|
|
45
|
+
)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Optional, cast
|
|
3
|
+
|
|
4
|
+
from .. import Version, VersionMetadata, VersionPreRelease
|
|
5
|
+
from ._base_parser import BaseParser
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SemVerParser(BaseParser):
|
|
9
|
+
PATTERN = re.compile(
|
|
10
|
+
r"^(?P<major>\d+)"
|
|
11
|
+
r"\.(?P<minor>\d+)"
|
|
12
|
+
r"\.(?P<patch>\d+)"
|
|
13
|
+
r"(?:-(?P<pre>[0-9A-Za-z\-]+(?:\.[0-9A-Za-z\-]+)*))?"
|
|
14
|
+
r"(?:\+(?P<meta>[0-9A-Za-z\-]+(?:\.[0-9A-Za-z\-]+)*))?$"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
def parse(self, version_string: str) -> Optional[Version]:
|
|
18
|
+
match = self.PATTERN.match(version_string)
|
|
19
|
+
if not match:
|
|
20
|
+
return None
|
|
21
|
+
|
|
22
|
+
groups = match.groupdict()
|
|
23
|
+
pre_str = cast(Optional[str], groups["pre"])
|
|
24
|
+
pre_release: Optional[VersionPreRelease] = None
|
|
25
|
+
|
|
26
|
+
if pre_str:
|
|
27
|
+
pre_parts = re.match(r"^([A-Za-z]+)\.?(\d+)?", pre_str)
|
|
28
|
+
if pre_parts:
|
|
29
|
+
pre_num = pre_parts.group(2)
|
|
30
|
+
pre_release = VersionPreRelease(
|
|
31
|
+
cast(str, pre_parts.group(1)),
|
|
32
|
+
int(pre_num) if pre_num else None,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
meta = groups["meta"]
|
|
36
|
+
return Version(
|
|
37
|
+
major=int(groups["major"]),
|
|
38
|
+
minor=int(groups["minor"]),
|
|
39
|
+
patch=int(groups["patch"]),
|
|
40
|
+
pre=pre_release,
|
|
41
|
+
metadata=VersionMetadata(cast(str, meta)) if meta else None,
|
|
42
|
+
)
|