Hide keyboard shortcuts

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 

3 

4class PreRelease: 

5 """A semantic version pre-release. 

6 

7 """ 

8 

9 _value: string 

10 

11 func __init__(self, value: string): 

12 """Initialize a pre-release object from given string `value`. 

13 

14 Raises SemVerError if `value` is not a valid semantic version 

15 pre-release. 

16 

17 """ 

18 

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-]*))*)") 

22 

23 if mo is None: 

24 raise SemVerError(f"invalid semantic version pre-release '{value}'") 

25 

26 self._value = mo.group(1) 

27 

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". 

31 

32 """ 

33 

34 return self._value.split(".") 

35 

36 func ==(self, other: PreRelease) -> bool: 

37 return self._value == str(other) 

38 

39 func !=(self, other: PreRelease) -> bool: 

40 return not (self == other) 

41 

42 func <(self, other: PreRelease) -> bool: 

43 identifiers = self.identifiers() 

44 other_identifiers = other.identifiers() 

45 compare_count = min(identifiers.length(), other_identifiers.length()) 

46 

47 for i in range(compare_count): 

48 is_numeric = identifiers[i].is_numeric() 

49 is_other_numeric = other_identifiers[i].is_numeric() 

50 

51 if is_numeric and is_other_numeric: 

52 numeric = i64(identifiers[i]) 

53 other_numeric = i64(other_identifiers[i]) 

54 

55 if numeric < other_numeric: 

56 return True 

57 

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 

68 

69 return identifiers.length() < other_identifiers.length() 

70 

71 func >(self, other: PreRelease) -> bool: 

72 return not (self <= other) 

73 

74 func <=(self, other: PreRelease) -> bool: 

75 return self < other or self == other 

76 

77 func >=(self, other: PreRelease) -> bool: 

78 return not (self < other) 

79 

80 func __str__(self) -> string: 

81 return self._value 

82 

83class BuildMetadata: 

84 """A semantic version build metadata. 

85 

86 """ 

87 

88 _value: string 

89 

90 func __init__(self, value: string): 

91 """Initialize a build metadata object from given string `value`. 

92 

93 Raises SemVerError if `value` is not a valid semantic version 

94 build metadata. 

95 

96 """ 

97 

98 mo = value.match(re"^([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*)$") 

99 

100 if mo is None: 

101 raise SemVerError(f"invalid semantic version build metadata '{value}'") 

102 

103 self._value = mo.group(1) 

104 

105 func __str__(self) -> string: 

106 return self._value 

107 

108class Version: 

109 """A semantic version. 

110 

111 """ 

112 

113 major: i64 

114 minor: i64 

115 patch: i64 

116 pre_release: PreRelease? 

117 build_metadata: BuildMetadata? 

118 

119 func __init__(self, version: string): 

120 """Initialize a version object from given string `version`. 

121 

122 Raises SemVerError if `version` is not a valid semantic 

123 version. 

124 

125 """ 

126 

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-]+)*))?$") 

135 

136 if mo is None: 

137 raise SemVerError(f"invalid semantic version '{version}'") 

138 

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}'") 

145 

146 value = mo.group(4) 

147 

148 if value is not None: 

149 self.pre_release = PreRelease(value) 

150 else: 

151 self.pre_release = None 

152 

153 value = mo.group(5) 

154 

155 if value is not None: 

156 self.build_metadata = BuildMetadata(value) 

157 else: 

158 self.build_metadata = None 

159 

160 func increment_major(self): 

161 """Increment major by one. Reset pre-release, build metadata, minor 

162 and patch. 

163 

164 """ 

165 

166 self.pre_release = None 

167 self.build_metadata = None 

168 self.major += 1 

169 self.minor = 0 

170 self.patch = 0 

171 

172 func increment_minor(self): 

173 """Increment minor by one. Reset pre-release, build metadata and 

174 patch. 

175 

176 """ 

177 

178 self.pre_release = None 

179 self.build_metadata = None 

180 self.minor += 1 

181 self.patch = 0 

182 

183 func increment_patch(self): 

184 """Increment patch by one. Reset pre-release and build metadata. 

185 

186 """ 

187 

188 self.pre_release = None 

189 self.build_metadata = None 

190 self.patch += 1 

191 

192 func ==(self, other: Version) -> bool: 

193 if self.major != other.major: 

194 return False 

195 

196 if self.minor != other.minor: 

197 return False 

198 

199 if self.patch != other.patch: 

200 return False 

201 

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 

205 

206 if self.pre_release is not None and other.pre_release is None: 

207 return False 

208 

209 if self.pre_release is None and other.pre_release is not None: 

210 return False 

211 

212 return True 

213 

214 func !=(self, other: Version) -> bool: 

215 return not (self == other) 

216 

217 func <(self, other: Version) -> bool: 

218 if self.major < other.major: 

219 return True 

220 

221 if self.major > other.major: 

222 return False 

223 

224 if self.minor < other.minor: 

225 return True 

226 

227 if self.minor > other.minor: 

228 return False 

229 

230 if self.patch < other.patch: 

231 return True 

232 

233 if self.patch > other.patch: 

234 return False 

235 

236 if self.pre_release is None and other.pre_release is None: 

237 return False 

238 

239 if self.pre_release is not None and other.pre_release is None: 

240 return True 

241 

242 if self.pre_release is None and other.pre_release is not None: 

243 return False 

244 

245 return self.pre_release < other.pre_release 

246 

247 func >(self, other: Version) -> bool: 

248 return not (self <= other) 

249 

250 func <=(self, other: Version) -> bool: 

251 return self < other or self == other 

252 

253 func >=(self, other: Version) -> bool: 

254 return not (self < other) 

255 

256 func __str__(self) -> string: 

257 version = f"{self.major}.{self.minor}.{self.patch}" 

258 

259 if self.pre_release is not None: 

260 version += f"-{self.pre_release}" 

261 

262 if self.build_metadata is not None: 

263 version += f"+{self.build_metadata}" 

264 

265 return version 

266 

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 

276 

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 

285 

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 

294 

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'" 

301 

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'" 

307 

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'") 

314 

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")) 

320 

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")) 

325 

326 assert Version("0.1.0") == Version("0.1.0") 

327 assert Version("0.1.0") != Version("0.2.0") 

328 

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") 

333 

334 assert str(Version("1.0.0-rc4").pre_release) == "rc4" 

335 assert str(Version("1.0.0+2fd3493b").build_metadata) == "2fd3493b" 

336 

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" 

343 

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") 

352 

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") 

358 

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") 

367 

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") 

376 

377test compare_build_metadata(): 

378 version_1 = Version("0.1.0") 

379 version_2 = Version("0.1.0+981238912") 

380 

381 assert version_1 == version_2 

382 

383test pre_release_identifiers(): 

384 assert PreRelease("alpha.1").identifiers() == ["alpha", "1"] 

385 

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 ''" 

392 

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 ''" 

399 

400test increment_major(): 

401 datas = [ 

402 ("0.1.0", "1.0.0"), 

403 ("1.1.1-123+45", "2.0.0") 

404 ] 

405 

406 for current_version, expected_version in datas: 

407 version = Version(current_version) 

408 version.increment_major() 

409 assert version == Version(expected_version) 

410 

411test increment_minor(): 

412 datas = [ 

413 ("0.1.0", "0.2.0"), 

414 ("1.1.1-123+45", "1.2.0") 

415 ] 

416 

417 for current_version, expected_version in datas: 

418 version = Version(current_version) 

419 version.increment_minor() 

420 assert version == Version(expected_version) 

421 

422test increment_patch(): 

423 datas = [ 

424 ("0.1.0", "0.1.1"), 

425 ("1.1.1-123+45", "1.1.2") 

426 ] 

427 

428 for current_version, expected_version in datas: 

429 version = Version(current_version) 

430 version.increment_patch() 

431 assert version == Version(expected_version)