Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1class SemVerError(Error):
2 message: string
4class PreRelease:
5 """A semantic version pre-release.
7 """
9 _value: string
11 func __init__(self, value: string):
12 """Initialize a pre-release object from given string `value`.
14 Raises SemVerError if `value` is not a valid semantic version
15 pre-release.
17 """
19 mo = value.match(
20 re"((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)"
21 re"(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*)")
23 if mo is None:
24 raise SemVerError(f"invalid semantic version pre-release '{value}'")
26 self._value = mo.group(1)
28 func identifiers(self) -> [string]:
29 """Returns the idenetifiers (dot separated values). For example, the
30 pre-release "alpha.1" has two identifiers, "alpha" and "1".
32 """
34 return self._value.split(".")
36 func ==(self, other: PreRelease) -> bool:
37 return self._value == str(other)
39 func !=(self, other: PreRelease) -> bool:
40 return not (self == other)
42 func <(self, other: PreRelease) -> bool:
43 identifiers = self.identifiers()
44 other_identifiers = other.identifiers()
45 compare_count = min(identifiers.length(), other_identifiers.length())
47 for i in range(compare_count):
48 is_numeric = identifiers[i].is_numeric()
49 is_other_numeric = other_identifiers[i].is_numeric()
51 if is_numeric and is_other_numeric:
52 numeric = i64(identifiers[i])
53 other_numeric = i64(other_identifiers[i])
55 if numeric < other_numeric:
56 return True
58 if numeric > other_numeric:
59 return False
60 elif is_numeric and not is_other_numeric:
61 return True
62 elif not is_numeric and is_other_numeric:
63 return False
64 elif identifiers[i] < other_identifiers[i]:
65 return True
66 elif identifiers[i] > other_identifiers[i]:
67 return False
69 return identifiers.length() < other_identifiers.length()
71 func >(self, other: PreRelease) -> bool:
72 return not (self <= other)
74 func <=(self, other: PreRelease) -> bool:
75 return self < other or self == other
77 func >=(self, other: PreRelease) -> bool:
78 return not (self < other)
80 func __str__(self) -> string:
81 return self._value
83class BuildMetadata:
84 """A semantic version build metadata.
86 """
88 _value: string
90 func __init__(self, value: string):
91 """Initialize a build metadata object from given string `value`.
93 Raises SemVerError if `value` is not a valid semantic version
94 build metadata.
96 """
98 mo = value.match(re"^([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*)$")
100 if mo is None:
101 raise SemVerError(f"invalid semantic version build metadata '{value}'")
103 self._value = mo.group(1)
105 func __str__(self) -> string:
106 return self._value
108class Version:
109 """A semantic version.
111 """
113 major: i64
114 minor: i64
115 patch: i64
116 pre_release: PreRelease?
117 build_metadata: BuildMetadata?
119 func __init__(self, version: string):
120 """Initialize a version object from given string `version`.
122 Raises SemVerError if `version` is not a valid semantic
123 version.
125 """
127 mo = version.match(
128 # major, minor and patch
129 re"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)"
130 # pre release
131 re"(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)"
132 re"(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?"
133 # build metadata
134 re"(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$")
136 if mo is None:
137 raise SemVerError(f"invalid semantic version '{version}'")
139 try:
140 self.major = i64(mo.group(1))
141 self.minor = i64(mo.group(2))
142 self.patch = i64(mo.group(3))
143 except ValueError:
144 raise SemVerError(f"invalid semantic version '{version}'")
146 value = mo.group(4)
148 if value is not None:
149 self.pre_release = PreRelease(value)
150 else:
151 self.pre_release = None
153 value = mo.group(5)
155 if value is not None:
156 self.build_metadata = BuildMetadata(value)
157 else:
158 self.build_metadata = None
160 func increment_major(self):
161 """Increment major by one. Reset pre-release, build metadata, minor
162 and patch.
164 """
166 self.pre_release = None
167 self.build_metadata = None
168 self.major += 1
169 self.minor = 0
170 self.patch = 0
172 func increment_minor(self):
173 """Increment minor by one. Reset pre-release, build metadata and
174 patch.
176 """
178 self.pre_release = None
179 self.build_metadata = None
180 self.minor += 1
181 self.patch = 0
183 func increment_patch(self):
184 """Increment patch by one. Reset pre-release and build metadata.
186 """
188 self.pre_release = None
189 self.build_metadata = None
190 self.patch += 1
192 func ==(self, other: Version) -> bool:
193 if self.major != other.major:
194 return False
196 if self.minor != other.minor:
197 return False
199 if self.patch != other.patch:
200 return False
202 if self.pre_release is not None and other.pre_release is not None:
203 if self.pre_release != other.pre_release:
204 return False
206 if self.pre_release is not None and other.pre_release is None:
207 return False
209 if self.pre_release is None and other.pre_release is not None:
210 return False
212 return True
214 func !=(self, other: Version) -> bool:
215 return not (self == other)
217 func <(self, other: Version) -> bool:
218 if self.major < other.major:
219 return True
221 if self.major > other.major:
222 return False
224 if self.minor < other.minor:
225 return True
227 if self.minor > other.minor:
228 return False
230 if self.patch < other.patch:
231 return True
233 if self.patch > other.patch:
234 return False
236 if self.pre_release is None and other.pre_release is None:
237 return False
239 if self.pre_release is not None and other.pre_release is None:
240 return True
242 if self.pre_release is None and other.pre_release is not None:
243 return False
245 return self.pre_release < other.pre_release
247 func >(self, other: Version) -> bool:
248 return not (self <= other)
250 func <=(self, other: Version) -> bool:
251 return self < other or self == other
253 func >=(self, other: Version) -> bool:
254 return not (self < other)
256 func __str__(self) -> string:
257 version = f"{self.major}.{self.minor}.{self.patch}"
259 if self.pre_release is not None:
260 version += f"-{self.pre_release}"
262 if self.build_metadata is not None:
263 version += f"+{self.build_metadata}"
265 return version
267test version():
268 version_string = "1.2.0-rc-4+meta"
269 version = Version(version_string)
270 assert version.major == 1
271 assert version.minor == 2
272 assert version.patch == 0
273 assert str(version.pre_release) == "rc-4"
274 assert str(version.build_metadata) == "meta"
275 assert str(version) == version_string
277 version_string = "3.1.2"
278 version = Version(version_string)
279 assert version.major == 3
280 assert version.minor == 1
281 assert version.patch == 2
282 assert version.pre_release is None
283 assert version.build_metadata is None
284 assert str(version) == version_string
286 version_string = "100.200.300+foo"
287 version = Version(version_string)
288 assert version.major == 100
289 assert version.minor == 200
290 assert version.patch == 300
291 assert version.pre_release is None
292 assert str(version.build_metadata) == "foo"
293 assert str(version) == version_string
295test bad_versions():
296 try:
297 Version("1.0")
298 assert False
299 except SemVerError as error:
300 assert error.message == "invalid semantic version '1.0'"
302 try:
303 Version("0.0.0.1")
304 assert False
305 except SemVerError as error:
306 assert error.message == "invalid semantic version '0.0.0.1'"
308 try:
309 Version("1000000000000000000000000000000000000000.0.0")
310 assert False
311 except SemVerError as error:
312 assert error.message == (
313 "invalid semantic version '1000000000000000000000000000000000000000.0.0'")
315test compare():
316 assert Version("1.0.0") > Version("0.0.0")
317 assert Version("1.1.0") > Version("1.0.0")
318 assert Version("1.1.1") > Version("1.1.0")
319 assert not (Version("1.0.0") > Version("1.0.0"))
321 assert Version("0.0.0") < Version("1.0.0")
322 assert Version("1.0.0") < Version("1.1.0")
323 assert Version("1.1.0") < Version("1.1.1")
324 assert not (Version("1.0.0") < Version("1.0.0"))
326 assert Version("0.1.0") == Version("0.1.0")
327 assert Version("0.1.0") != Version("0.2.0")
329 assert Version("1.1.0") <= Version("1.1.1")
330 assert Version("1.1.1") <= Version("1.1.1")
331 assert Version("1.1.1") >= Version("0.1.1")
332 assert Version("2.1.1") >= Version("2.1.1")
334 assert str(Version("1.0.0-rc4").pre_release) == "rc4"
335 assert str(Version("1.0.0+2fd3493b").build_metadata) == "2fd3493b"
337 version = Version("1.0.0-rc5+1fd3493b")
338 assert version.major == 1
339 assert version.minor == 0
340 assert version.patch == 0
341 assert str(version.pre_release) == "rc5"
342 assert str(version.build_metadata) == "1fd3493b"
344test compare_pre_release():
345 assert Version("1.0.0-alpha") < Version("1.0.0-alpha.1")
346 assert Version("1.0.0-alpha.1") < Version("1.0.0-alpha.beta")
347 assert Version("1.0.0-alpha.beta") < Version("1.0.0-beta")
348 assert Version("1.0.0-beta") < Version("1.0.0-beta.2")
349 assert Version("1.0.0-beta.2") < Version("1.0.0-beta.11")
350 assert Version("1.0.0-beta.11") < Version("1.0.0-rc.1")
351 assert Version("1.0.0-rc.1") < Version("1.0.0")
353 assert Version("1.0.0-alpha") != Version("1.0.0-alpha.1")
354 assert not (Version("1.0.0-alpha") == Version("1.0.0-alpha.1"))
355 assert Version("1.0.0") != Version("1.0.0-alpha")
356 assert Version("1.0.0-1") != Version("1.0.0")
357 assert Version("1.0.0") > Version("1.0.0-rc.1")
359 assert PreRelease("alpha") < PreRelease("alpha.1")
360 assert PreRelease("alpha.1") < PreRelease("alpha.beta")
361 assert PreRelease("alpha.beta") > PreRelease("alpha.1")
362 assert PreRelease("alpha.beta") < PreRelease("beta")
363 assert PreRelease("beta") > PreRelease("alpha.beta")
364 assert PreRelease("beta") < PreRelease("beta.2")
365 assert PreRelease("beta.2") < PreRelease("beta.11")
366 assert PreRelease("beta.11") < PreRelease("rc.1")
368 assert PreRelease("rc.1") == PreRelease("rc.1")
369 assert PreRelease("rc.1") != PreRelease("rc.2")
370 assert PreRelease("rc.1") < PreRelease("rc.2")
371 assert PreRelease("rc.2") > PreRelease("rc.1")
372 assert PreRelease("rc.1") <= PreRelease("rc.1")
373 assert PreRelease("rc.1") <= PreRelease("rc.2")
374 assert PreRelease("rc.2") >= PreRelease("rc.1")
375 assert PreRelease("rc.1") >= PreRelease("rc.1")
377test compare_build_metadata():
378 version_1 = Version("0.1.0")
379 version_2 = Version("0.1.0+981238912")
381 assert version_1 == version_2
383test pre_release_identifiers():
384 assert PreRelease("alpha.1").identifiers() == ["alpha", "1"]
386test bad_pre_release():
387 try:
388 PreRelease("")
389 assert False
390 except SemVerError as error:
391 assert error.message == "invalid semantic version pre-release ''"
393test bad_build_metadata():
394 try:
395 BuildMetadata("")
396 assert False
397 except SemVerError as error:
398 assert error.message == "invalid semantic version build metadata ''"
400test increment_major():
401 datas = [
402 ("0.1.0", "1.0.0"),
403 ("1.1.1-123+45", "2.0.0")
404 ]
406 for current_version, expected_version in datas:
407 version = Version(current_version)
408 version.increment_major()
409 assert version == Version(expected_version)
411test increment_minor():
412 datas = [
413 ("0.1.0", "0.2.0"),
414 ("1.1.1-123+45", "1.2.0")
415 ]
417 for current_version, expected_version in datas:
418 version = Version(current_version)
419 version.increment_minor()
420 assert version == Version(expected_version)
422test increment_patch():
423 datas = [
424 ("0.1.0", "0.1.1"),
425 ("1.1.1-123+45", "1.1.2")
426 ]
428 for current_version, expected_version in datas:
429 version = Version(current_version)
430 version.increment_patch()
431 assert version == Version(expected_version)