semver.py 35 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219
  1. # Copyright (c) Jupyter Development Team.
  2. # Distributed under the terms of the Modified BSD License.
  3. # This file comes from https://github.com/podhmo/python-semver/blob/b42e9896e391e086b773fc621b23fa299d16b874/semver/__init__.py
  4. #
  5. # It is licensed under the following license:
  6. #
  7. # MIT License
  8. # Copyright (c) 2016 podhmo
  9. # Permission is hereby granted, free of charge, to any person obtaining a copy
  10. # of this software and associated documentation files (the "Software"), to deal
  11. # in the Software without restriction, including without limitation the rights
  12. # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  13. # copies of the Software, and to permit persons to whom the Software is
  14. # furnished to do so, subject to the following conditions:
  15. # The above copyright notice and this permission notice shall be included in all
  16. # copies or substantial portions of the Software.
  17. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  23. # SOFTWARE.
  24. import logging
  25. import re
  26. logger = logging.getLogger(__name__)
  27. SEMVER_SPEC_VERSION = "2.0.0"
  28. string_type = str
  29. class _R:
  30. def __init__(self, i):
  31. self.i = i
  32. def __call__(self):
  33. v = self.i
  34. self.i += 1
  35. return v
  36. def value(self):
  37. return self.i
  38. class Extendlist(list):
  39. def __setitem__(self, i, v):
  40. try:
  41. list.__setitem__(self, i, v)
  42. except IndexError:
  43. if len(self) == i:
  44. self.append(v)
  45. else:
  46. raise
  47. def list_get(xs, i):
  48. try:
  49. return xs[i]
  50. except IndexError:
  51. return None
  52. R = _R(0)
  53. src = Extendlist()
  54. regexp = {}
  55. # The following Regular Expressions can be used for tokenizing,
  56. # validating, and parsing SemVer version strings.
  57. # ## Numeric Identifier
  58. # A single `0`, or a non-zero digit followed by zero or more digits.
  59. NUMERICIDENTIFIER = R()
  60. src[NUMERICIDENTIFIER] = "0|[1-9]\\d*"
  61. NUMERICIDENTIFIERLOOSE = R()
  62. src[NUMERICIDENTIFIERLOOSE] = "[0-9]+"
  63. # ## Non-numeric Identifier
  64. # Zero or more digits, followed by a letter or hyphen, and then zero or
  65. # more letters, digits, or hyphens.
  66. NONNUMERICIDENTIFIER = R()
  67. src[NONNUMERICIDENTIFIER] = "\\d*[a-zA-Z-][a-zA-Z0-9-]*"
  68. # ## Main Version
  69. # Three dot-separated numeric identifiers.
  70. MAINVERSION = R()
  71. src[MAINVERSION] = (
  72. "("
  73. + src[NUMERICIDENTIFIER]
  74. + ")\\."
  75. + "("
  76. + src[NUMERICIDENTIFIER]
  77. + ")\\."
  78. + "("
  79. + src[NUMERICIDENTIFIER]
  80. + ")"
  81. )
  82. MAINVERSIONLOOSE = R()
  83. src[MAINVERSIONLOOSE] = (
  84. "("
  85. + src[NUMERICIDENTIFIERLOOSE]
  86. + ")\\."
  87. + "("
  88. + src[NUMERICIDENTIFIERLOOSE]
  89. + ")\\."
  90. + "("
  91. + src[NUMERICIDENTIFIERLOOSE]
  92. + ")"
  93. )
  94. # ## Pre-release Version Identifier
  95. # A numeric identifier, or a non-numeric identifier.
  96. PRERELEASEIDENTIFIER = R()
  97. src[PRERELEASEIDENTIFIER] = "(?:" + src[NUMERICIDENTIFIER] + "|" + src[NONNUMERICIDENTIFIER] + ")"
  98. PRERELEASEIDENTIFIERLOOSE = R()
  99. src[PRERELEASEIDENTIFIERLOOSE] = (
  100. "(?:" + src[NUMERICIDENTIFIERLOOSE] + "|" + src[NONNUMERICIDENTIFIER] + ")"
  101. )
  102. # ## Pre-release Version
  103. # Hyphen, followed by one or more dot-separated pre-release version
  104. # identifiers.
  105. PRERELEASE = R()
  106. src[PRERELEASE] = (
  107. "(?:-(" + src[PRERELEASEIDENTIFIER] + "(?:\\." + src[PRERELEASEIDENTIFIER] + ")*))"
  108. )
  109. PRERELEASELOOSE = R()
  110. src[PRERELEASELOOSE] = (
  111. "(?:-?(" + src[PRERELEASEIDENTIFIERLOOSE] + "(?:\\." + src[PRERELEASEIDENTIFIERLOOSE] + ")*))"
  112. )
  113. # ## Build Metadata Identifier
  114. # Any combination of digits, letters, or hyphens.
  115. BUILDIDENTIFIER = R()
  116. src[BUILDIDENTIFIER] = "[0-9A-Za-z-]+"
  117. # ## Build Metadata
  118. # Plus sign, followed by one or more period-separated build metadata
  119. # identifiers.
  120. BUILD = R()
  121. src[BUILD] = "(?:\\+(" + src[BUILDIDENTIFIER] + "(?:\\." + src[BUILDIDENTIFIER] + ")*))"
  122. # ## Full Version String
  123. # A main version, followed optionally by a pre-release version and
  124. # build metadata.
  125. # Note that the only major, minor, patch, and pre-release sections of
  126. # the version string are capturing groups. The build metadata is not a
  127. # capturing group, because it should not ever be used in version
  128. # comparison.
  129. FULL = R()
  130. FULLPLAIN = "v?" + src[MAINVERSION] + src[PRERELEASE] + "?" + src[BUILD] + "?"
  131. src[FULL] = "^" + FULLPLAIN + "$"
  132. # like full, but allows v1.2.3 and =1.2.3, which people do sometimes.
  133. # also, 1.0.0alpha1 (prerelease without the hyphen) which is pretty
  134. # common in the npm registry.
  135. LOOSEPLAIN = "[v=\\s]*" + src[MAINVERSIONLOOSE] + src[PRERELEASELOOSE] + "?" + src[BUILD] + "?"
  136. LOOSE = R()
  137. src[LOOSE] = "^" + LOOSEPLAIN + "$"
  138. GTLT = R()
  139. src[GTLT] = "((?:<|>)?=?)"
  140. # Something like "2.*" or "1.2.x".
  141. # Note that "x.x" is a valid xRange identifier, meaning "any version"
  142. # Only the first item is strictly required.
  143. XRANGEIDENTIFIERLOOSE = R()
  144. src[XRANGEIDENTIFIERLOOSE] = src[NUMERICIDENTIFIERLOOSE] + "|x|X|\\*"
  145. XRANGEIDENTIFIER = R()
  146. src[XRANGEIDENTIFIER] = src[NUMERICIDENTIFIER] + "|x|X|\\*"
  147. XRANGEPLAIN = R()
  148. src[XRANGEPLAIN] = (
  149. "[v=\\s]*("
  150. + src[XRANGEIDENTIFIER]
  151. + ")"
  152. + "(?:\\.("
  153. + src[XRANGEIDENTIFIER]
  154. + ")"
  155. + "(?:\\.("
  156. + src[XRANGEIDENTIFIER]
  157. + ")"
  158. + "(?:"
  159. + src[PRERELEASE]
  160. + ")?"
  161. + src[BUILD]
  162. + "?"
  163. + ")?)?"
  164. )
  165. XRANGEPLAINLOOSE = R()
  166. src[XRANGEPLAINLOOSE] = (
  167. "[v=\\s]*("
  168. + src[XRANGEIDENTIFIERLOOSE]
  169. + ")"
  170. + "(?:\\.("
  171. + src[XRANGEIDENTIFIERLOOSE]
  172. + ")"
  173. + "(?:\\.("
  174. + src[XRANGEIDENTIFIERLOOSE]
  175. + ")"
  176. + "(?:"
  177. + src[PRERELEASELOOSE]
  178. + ")?"
  179. + src[BUILD]
  180. + "?"
  181. + ")?)?"
  182. )
  183. XRANGE = R()
  184. src[XRANGE] = "^" + src[GTLT] + "\\s*" + src[XRANGEPLAIN] + "$"
  185. XRANGELOOSE = R()
  186. src[XRANGELOOSE] = "^" + src[GTLT] + "\\s*" + src[XRANGEPLAINLOOSE] + "$"
  187. # Tilde ranges.
  188. # Meaning is "reasonably at or greater than"
  189. LONETILDE = R()
  190. src[LONETILDE] = "(?:~>?)"
  191. TILDETRIM = R()
  192. src[TILDETRIM] = "(\\s*)" + src[LONETILDE] + "\\s+"
  193. regexp[TILDETRIM] = re.compile(src[TILDETRIM], re.M)
  194. tildeTrimReplace = r"\1~"
  195. TILDE = R()
  196. src[TILDE] = "^" + src[LONETILDE] + src[XRANGEPLAIN] + "$"
  197. TILDELOOSE = R()
  198. src[TILDELOOSE] = "^" + src[LONETILDE] + src[XRANGEPLAINLOOSE] + "$"
  199. # Caret ranges.
  200. # Meaning is "at least and backwards compatible with"
  201. LONECARET = R()
  202. src[LONECARET] = "(?:\\^)"
  203. CARETTRIM = R()
  204. src[CARETTRIM] = "(\\s*)" + src[LONECARET] + "\\s+"
  205. regexp[CARETTRIM] = re.compile(src[CARETTRIM], re.M)
  206. caretTrimReplace = r"\1^"
  207. CARET = R()
  208. src[CARET] = "^" + src[LONECARET] + src[XRANGEPLAIN] + "$"
  209. CARETLOOSE = R()
  210. src[CARETLOOSE] = "^" + src[LONECARET] + src[XRANGEPLAINLOOSE] + "$"
  211. # A simple gt/lt/eq thing, or just "" to indicate "any version"
  212. COMPARATORLOOSE = R()
  213. src[COMPARATORLOOSE] = "^" + src[GTLT] + "\\s*(" + LOOSEPLAIN + ")$|^$"
  214. COMPARATOR = R()
  215. src[COMPARATOR] = "^" + src[GTLT] + "\\s*(" + FULLPLAIN + ")$|^$"
  216. # An expression to strip any whitespace between the gtlt and the thing
  217. # it modifies, so that `> 1.2.3` ==> `>1.2.3`
  218. COMPARATORTRIM = R()
  219. src[COMPARATORTRIM] = "(\\s*)" + src[GTLT] + "\\s*(" + LOOSEPLAIN + "|" + src[XRANGEPLAIN] + ")"
  220. # this one has to use the /g flag
  221. regexp[COMPARATORTRIM] = re.compile(src[COMPARATORTRIM], re.M)
  222. comparatorTrimReplace = r"\1\2\3"
  223. # Something like `1.2.3 - 1.2.4`
  224. # Note that these all use the loose form, because they'll be
  225. # checked against either the strict or loose comparator form
  226. # later.
  227. HYPHENRANGE = R()
  228. src[HYPHENRANGE] = (
  229. "^\\s*(" + src[XRANGEPLAIN] + ")" + "\\s+-\\s+" + "(" + src[XRANGEPLAIN] + ")" + "\\s*$"
  230. )
  231. HYPHENRANGELOOSE = R()
  232. src[HYPHENRANGELOOSE] = (
  233. "^\\s*("
  234. + src[XRANGEPLAINLOOSE]
  235. + ")"
  236. + "\\s+-\\s+"
  237. + "("
  238. + src[XRANGEPLAINLOOSE]
  239. + ")"
  240. + "\\s*$"
  241. )
  242. # Star ranges basically just allow anything at all.
  243. STAR = R()
  244. src[STAR] = "(<|>)?=?\\s*\\*"
  245. # version name recovery for convenient
  246. RECOVERYVERSIONNAME = R()
  247. _n = src[NUMERICIDENTIFIER]
  248. _pre = src[PRERELEASELOOSE]
  249. src[RECOVERYVERSIONNAME] = f"v?({_n})(?:\\.({_n}))?{_pre}?"
  250. # Compile to actual regexp objects.
  251. # All are flag-free, unless they were created above with a flag.
  252. for i in range(R.value()):
  253. logger.debug("genregxp %s %s", i, src[i])
  254. if i not in regexp:
  255. regexp[i] = re.compile(src[i])
  256. def parse(version, loose):
  257. r = regexp[LOOSE] if loose else regexp[FULL]
  258. m = r.search(version)
  259. if m:
  260. return semver(version, loose)
  261. else:
  262. return None
  263. def valid(version, loose):
  264. v = parse(version, loose)
  265. if v.version:
  266. return v
  267. else:
  268. return None
  269. def clean(version, loose):
  270. s = parse(version, loose)
  271. if s:
  272. return s.version
  273. else:
  274. return None
  275. NUMERIC = re.compile(r"^\d+$")
  276. def semver(version, loose):
  277. if isinstance(version, SemVer):
  278. if version.loose == loose:
  279. return version
  280. else:
  281. version = version.version
  282. elif not isinstance(version, string_type): # xxx:
  283. raise ValueError(f"Invalid Version: {version}")
  284. """
  285. if (!(this instanceof SemVer))
  286. return new SemVer(version, loose);
  287. """
  288. return SemVer(version, loose)
  289. make_semver = semver
  290. class SemVer:
  291. def __init__(self, version, loose):
  292. logger.debug("SemVer %s, %s", version, loose)
  293. self.loose = loose
  294. self.raw = version
  295. m = regexp[LOOSE if loose else FULL].search(version.strip())
  296. if not m:
  297. if not loose:
  298. raise ValueError(f"Invalid Version: {version}")
  299. m = regexp[RECOVERYVERSIONNAME].search(version.strip())
  300. self.major = int(m.group(1)) if m.group(1) else 0
  301. self.minor = int(m.group(2)) if m.group(2) else 0
  302. self.patch = 0
  303. if not m.group(3):
  304. self.prerelease = []
  305. else:
  306. self.prerelease = [
  307. (int(id_) if NUMERIC.search(id_) else id_) for id_ in m.group(3).split(".")
  308. ]
  309. else:
  310. # these are actually numbers
  311. self.major = int(m.group(1))
  312. self.minor = int(m.group(2))
  313. self.patch = int(m.group(3))
  314. # numberify any prerelease numeric ids
  315. if not m.group(4):
  316. self.prerelease = []
  317. else:
  318. self.prerelease = [
  319. (int(id_) if NUMERIC.search(id_) else id_) for id_ in m.group(4).split(".")
  320. ]
  321. if m.group(5):
  322. self.build = m.group(5).split(".")
  323. else:
  324. self.build = []
  325. self.format() # xxx:
  326. def format(self):
  327. self.version = f"{self.major}.{self.minor}.{self.patch}"
  328. if len(self.prerelease) > 0:
  329. self.version += "-{}".format(".".join(str(v) for v in self.prerelease))
  330. return self.version
  331. def __repr__(self):
  332. return f"<SemVer {self} >"
  333. def __str__(self):
  334. return self.version
  335. def compare(self, other):
  336. logger.debug("SemVer.compare %s %s %s", self.version, self.loose, other)
  337. if not isinstance(other, SemVer):
  338. other = make_semver(other, self.loose)
  339. result = self.compare_main(other) or self.compare_pre(other)
  340. logger.debug("compare result %s", result)
  341. return result
  342. def compare_main(self, other):
  343. if not isinstance(other, SemVer):
  344. other = make_semver(other, self.loose)
  345. return (
  346. compare_identifiers(str(self.major), str(other.major))
  347. or compare_identifiers(str(self.minor), str(other.minor))
  348. or compare_identifiers(str(self.patch), str(other.patch))
  349. )
  350. def compare_pre(self, other): # noqa PLR0911
  351. if not isinstance(other, SemVer):
  352. other = make_semver(other, self.loose)
  353. # NOT having a prerelease is > having one
  354. is_self_more_than_zero = len(self.prerelease) > 0
  355. is_other_more_than_zero = len(other.prerelease) > 0
  356. if not is_self_more_than_zero and is_other_more_than_zero:
  357. return 1
  358. elif is_self_more_than_zero and not is_other_more_than_zero:
  359. return -1
  360. elif not is_self_more_than_zero and not is_other_more_than_zero:
  361. return 0
  362. i = 0
  363. while True:
  364. a = list_get(self.prerelease, i)
  365. b = list_get(other.prerelease, i)
  366. logger.debug("prerelease compare %s: %s %s", i, a, b)
  367. i += 1
  368. if a is None and b is None:
  369. return 0
  370. elif b is None:
  371. return 1
  372. elif a is None:
  373. return -1
  374. elif a == b:
  375. continue
  376. else:
  377. return compare_identifiers(str(a), str(b))
  378. def inc(self, release, identifier=None): # noqa PLR0915
  379. logger.debug("inc release %s %s", self.prerelease, release)
  380. if release == "premajor":
  381. self.prerelease = []
  382. self.patch = 0
  383. self.minor = 0
  384. self.major += 1
  385. self.inc("pre", identifier=identifier)
  386. elif release == "preminor":
  387. self.prerelease = []
  388. self.patch = 0
  389. self.minor += 1
  390. self.inc("pre", identifier=identifier)
  391. elif release == "prepatch":
  392. # If this is already a prerelease, it will bump to the next version
  393. # drop any prereleases that might already exist, since they are not
  394. # relevant at this point.
  395. self.prerelease = []
  396. self.inc("patch", identifier=identifier)
  397. self.inc("pre", identifier=identifier)
  398. elif release == "prerelease":
  399. # If the input is a non-prerelease version, this acts the same as
  400. # prepatch.
  401. if len(self.prerelease) == 0:
  402. self.inc("patch", identifier=identifier)
  403. self.inc("pre", identifier=identifier)
  404. elif release == "major":
  405. # If this is a pre-major version, bump up to the same major version.
  406. # Otherwise increment major.
  407. # 1.0.0-5 bumps to 1.0.0
  408. # 1.1.0 bumps to 2.0.0
  409. if self.minor != 0 or self.patch != 0 or len(self.prerelease) == 0:
  410. self.major += 1
  411. self.minor = 0
  412. self.patch = 0
  413. self.prerelease = []
  414. elif release == "minor":
  415. # If this is a pre-minor version, bump up to the same minor version.
  416. # Otherwise increment minor.
  417. # 1.2.0-5 bumps to 1.2.0
  418. # 1.2.1 bumps to 1.3.0
  419. if self.patch != 0 or len(self.prerelease) == 0:
  420. self.minor += 1
  421. self.patch = 0
  422. self.prerelease = []
  423. elif release == "patch":
  424. # If this is not a pre-release version, it will increment the patch.
  425. # If it is a pre-release it will bump up to the same patch version.
  426. # 1.2.0-5 patches to 1.2.0
  427. # 1.2.0 patches to 1.2.1
  428. if len(self.prerelease) == 0:
  429. self.patch += 1
  430. self.prerelease = []
  431. elif release == "pre":
  432. # This probably shouldn't be used publicly.
  433. # 1.0.0 "pre" would become 1.0.0-0 which is the wrong direction.
  434. logger.debug("inc prerelease %s", self.prerelease)
  435. if len(self.prerelease) == 0:
  436. self.prerelease = [0]
  437. else:
  438. i = len(self.prerelease) - 1
  439. while i >= 0:
  440. if isinstance(self.prerelease[i], int):
  441. self.prerelease[i] += 1
  442. i -= 2
  443. i -= 1
  444. # ## this is needless code in python ##
  445. # if i == -1: # didn't increment anything
  446. # self.prerelease.append(0)
  447. if identifier is not None:
  448. # 1.2.0-beta.1 bumps to 1.2.0-beta.2,
  449. # 1.2.0-beta.fooblz or 1.2.0-beta bumps to 1.2.0-beta.0
  450. if self.prerelease[0] == identifier:
  451. if not isinstance(self.prerelease[1], int):
  452. self.prerelease = [identifier, 0]
  453. else:
  454. self.prerelease = [identifier, 0]
  455. else:
  456. raise ValueError(f"invalid increment argument: {release}")
  457. self.format()
  458. self.raw = self.version
  459. return self
  460. def inc(version, release, loose, identifier=None): # wow!
  461. try:
  462. return make_semver(version, loose).inc(release, identifier=identifier).version
  463. except Exception as e:
  464. logger.debug(e, exc_info=5)
  465. return None
  466. def compare_identifiers(a, b):
  467. anum = NUMERIC.search(a)
  468. bnum = NUMERIC.search(b)
  469. if anum and bnum:
  470. a = int(a)
  471. b = int(b)
  472. if anum and not bnum:
  473. return -1
  474. elif bnum and not anum:
  475. return 1
  476. elif a < b:
  477. return -1
  478. elif a > b:
  479. return 1
  480. else:
  481. return 0
  482. def rcompare_identifiers(a, b):
  483. return compare_identifiers(b, a)
  484. def compare(a, b, loose):
  485. return make_semver(a, loose).compare(b)
  486. def compare_loose(a, b):
  487. return compare(a, b, True)
  488. def rcompare(a, b, loose):
  489. return compare(b, a, loose)
  490. def make_key_function(loose):
  491. def key_function(version):
  492. v = make_semver(version, loose)
  493. key = (v.major, v.minor, v.patch)
  494. if v.prerelease: # noqa SIM108
  495. key = key + tuple(v.prerelease)
  496. else:
  497. # NOT having a prerelease is > having one
  498. key = (*key, float("inf"))
  499. return key
  500. return key_function
  501. loose_key_function = make_key_function(True)
  502. full_key_function = make_key_function(True)
  503. def sort(list_, loose):
  504. keyf = loose_key_function if loose else full_key_function
  505. list_.sort(key=keyf)
  506. return list_
  507. def rsort(list_, loose):
  508. keyf = loose_key_function if loose else full_key_function
  509. list_.sort(key=keyf, reverse=True)
  510. return list_
  511. def gt(a, b, loose):
  512. return compare(a, b, loose) > 0
  513. def lt(a, b, loose):
  514. return compare(a, b, loose) < 0
  515. def eq(a, b, loose):
  516. return compare(a, b, loose) == 0
  517. def neq(a, b, loose):
  518. return compare(a, b, loose) != 0
  519. def gte(a, b, loose):
  520. return compare(a, b, loose) >= 0
  521. def lte(a, b, loose):
  522. return compare(a, b, loose) <= 0
  523. def cmp(a, op, b, loose): # noqa PLR0911
  524. logger.debug("cmp: %s", op)
  525. if op == "===":
  526. return a == b
  527. elif op == "!==":
  528. return a != b
  529. elif op == "" or op == "=" or op == "==":
  530. return eq(a, b, loose)
  531. elif op == "!=":
  532. return neq(a, b, loose)
  533. elif op == ">":
  534. return gt(a, b, loose)
  535. elif op == ">=":
  536. return gte(a, b, loose)
  537. elif op == "<":
  538. return lt(a, b, loose)
  539. elif op == "<=":
  540. return lte(a, b, loose)
  541. else:
  542. raise ValueError(f"Invalid operator: {op}")
  543. def comparator(comp, loose):
  544. if isinstance(comp, Comparator):
  545. if comp.loose == loose:
  546. return comp
  547. else:
  548. comp = comp.value
  549. # if (!(this instanceof Comparator))
  550. # return new Comparator(comp, loose)
  551. return Comparator(comp, loose)
  552. make_comparator = comparator
  553. ANY = object()
  554. class Comparator:
  555. semver = None
  556. def __init__(self, comp, loose):
  557. logger.debug("comparator: %s %s", comp, loose)
  558. self.loose = loose
  559. self.parse(comp)
  560. if self.semver == ANY:
  561. self.value = ""
  562. else:
  563. self.value = self.operator + self.semver.version
  564. def parse(self, comp):
  565. r = regexp[COMPARATORLOOSE] if self.loose else regexp[COMPARATOR]
  566. logger.debug("parse comp=%s", comp)
  567. m = r.search(comp)
  568. if m is None:
  569. raise ValueError(f"Invalid comparator: {comp}")
  570. self.operator = m.group(1)
  571. # if it literally is just '>' or '' then allow anything.
  572. if m.group(2) is None:
  573. self.semver = ANY
  574. else:
  575. self.semver = semver(m.group(2), self.loose)
  576. def __repr__(self):
  577. return f'<SemVer Comparator "{self}">'
  578. def __str__(self):
  579. return self.value
  580. def test(self, version):
  581. logger.debug("Comparator, test %s, %s", version, self.loose)
  582. if self.semver == ANY:
  583. return True
  584. else:
  585. return cmp(version, self.operator, self.semver, self.loose)
  586. def make_range(range_, loose):
  587. if isinstance(range_, Range) and range_.loose == loose:
  588. return range_
  589. # if (!(this instanceof Range))
  590. # return new Range(range, loose);
  591. return Range(range_, loose)
  592. class Range:
  593. def __init__(self, range_, loose):
  594. self.loose = loose
  595. # First, split based on boolean or ||
  596. self.raw = range_
  597. xs = [self.parse_range(r.strip()) for r in re.split(r"\s*\|\|\s*", range_)]
  598. self.set = [r for r in xs if r]
  599. if not len(self.set):
  600. raise ValueError(f"Invalid SemVer Range: {range_}")
  601. self.format()
  602. def __repr__(self):
  603. return f'<SemVer Range "{self.range}">'
  604. def format(self):
  605. self.range = "||".join(
  606. [" ".join(c.value for c in comps).strip() for comps in self.set]
  607. ).strip()
  608. logger.debug("Range format %s", self.range)
  609. return self.range
  610. def __str__(self):
  611. return self.range
  612. def parse_range(self, range_):
  613. loose = self.loose
  614. logger.debug("range %s %s", range_, loose)
  615. # `1.2.3 - 1.2.4` => `>=1.2.3 <=1.2.4`
  616. hr = regexp[HYPHENRANGELOOSE] if loose else regexp[HYPHENRANGE]
  617. range_ = hr.sub(
  618. hyphen_replace,
  619. range_,
  620. )
  621. logger.debug("hyphen replace %s", range_)
  622. # `> 1.2.3 < 1.2.5` => `>1.2.3 <1.2.5`
  623. range_ = regexp[COMPARATORTRIM].sub(comparatorTrimReplace, range_)
  624. logger.debug("comparator trim %s, %s", range_, regexp[COMPARATORTRIM])
  625. # `~ 1.2.3` => `~1.2.3`
  626. range_ = regexp[TILDETRIM].sub(tildeTrimReplace, range_)
  627. # `^ 1.2.3` => `^1.2.3`
  628. range_ = regexp[CARETTRIM].sub(caretTrimReplace, range_)
  629. # normalize spaces
  630. range_ = " ".join(re.split(r"\s+", range_))
  631. # At this point, the range is completely trimmed and
  632. # ready to be split into comparators.
  633. comp_re = regexp[COMPARATORLOOSE] if loose else regexp[COMPARATOR]
  634. set_ = re.split(
  635. r"\s+", " ".join([parse_comparator(comp, loose) for comp in range_.split(" ")])
  636. )
  637. if self.loose:
  638. # in loose mode, throw out any that are not valid comparators
  639. set_ = [comp for comp in set_ if comp_re.search(comp)]
  640. set_ = [make_comparator(comp, loose) for comp in set_]
  641. return set_
  642. def test(self, version):
  643. if not version: # xxx
  644. return False
  645. if isinstance(version, string_type):
  646. version = make_semver(version, loose=self.loose)
  647. return any(test_set(e, version) for e in self.set)
  648. # Mostly just for testing and legacy API reasons
  649. def to_comparators(range_, loose):
  650. return [
  651. " ".join([c.value for c in comp]).strip().split(" ")
  652. for comp in make_range(range_, loose).set
  653. ]
  654. # comprised of xranges, tildes, stars, and gtlt's at this point.
  655. # already replaced the hyphen ranges
  656. # turn into a set of JUST comparators.
  657. def parse_comparator(comp, loose):
  658. logger.debug("comp %s", comp)
  659. comp = replace_carets(comp, loose)
  660. logger.debug("caret %s", comp)
  661. comp = replace_tildes(comp, loose)
  662. logger.debug("tildes %s", comp)
  663. comp = replace_xranges(comp, loose)
  664. logger.debug("xrange %s", comp)
  665. comp = replace_stars(comp, loose)
  666. logger.debug("stars %s", comp)
  667. return comp
  668. def is_x(id_):
  669. return id_ is None or id_ == "" or id_.lower() == "x" or id_ == "*"
  670. # ~, ~> --> * (any, kinda silly)
  671. # ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0 <3.0.0
  672. # ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0 <2.1.0
  673. # ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0 <1.3.0
  674. # ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0
  675. # ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0
  676. def replace_tildes(comp, loose):
  677. return " ".join([replace_tilde(c, loose) for c in re.split(r"\s+", comp.strip())])
  678. def replace_tilde(comp, loose):
  679. r = regexp[TILDELOOSE] if loose else regexp[TILDE]
  680. def repl(mob):
  681. _ = mob.group(0)
  682. M, m, p, pr, _ = mob.groups()
  683. logger.debug("tilde %s %s %s %s %s %s", comp, _, M, m, p, pr)
  684. if is_x(M):
  685. ret = ""
  686. elif is_x(m):
  687. ret = ">=" + M + ".0.0 <" + str(int(M) + 1) + ".0.0"
  688. elif is_x(p):
  689. # ~1.2 == >=1.2.0 <1.3.0
  690. ret = ">=" + M + "." + m + ".0 <" + M + "." + str(int(m) + 1) + ".0"
  691. elif pr:
  692. logger.debug("replaceTilde pr %s", pr)
  693. if pr[0] != "-":
  694. pr = "-" + pr
  695. ret = ">=" + M + "." + m + "." + p + pr + " <" + M + "." + str(int(m) + 1) + ".0"
  696. else:
  697. # ~1.2.3 == >=1.2.3 <1.3.0
  698. ret = ">=" + M + "." + m + "." + p + " <" + M + "." + str(int(m) + 1) + ".0"
  699. logger.debug("tilde return, %s", ret)
  700. return ret
  701. return r.sub(repl, comp)
  702. # ^ --> * (any, kinda silly)
  703. # ^2, ^2.x, ^2.x.x --> >=2.0.0 <3.0.0
  704. # ^2.0, ^2.0.x --> >=2.0.0 <3.0.0
  705. # ^1.2, ^1.2.x --> >=1.2.0 <2.0.0
  706. # ^1.2.3 --> >=1.2.3 <2.0.0
  707. # ^1.2.0 --> >=1.2.0 <2.0.0
  708. def replace_carets(comp, loose):
  709. return " ".join([replace_caret(c, loose) for c in re.split(r"\s+", comp.strip())])
  710. def replace_caret(comp, loose):
  711. r = regexp[CARETLOOSE] if loose else regexp[CARET]
  712. def repl(mob): # noqa PLR0911
  713. m0 = mob.group(0)
  714. M, m, p, pr, _ = mob.groups()
  715. logger.debug("caret %s %s %s %s %s %s", comp, m0, M, m, p, pr)
  716. if is_x(M):
  717. ret = ""
  718. elif is_x(m):
  719. ret = ">=" + M + ".0.0 <" + str(int(M) + 1) + ".0.0"
  720. elif is_x(p):
  721. if M == "0":
  722. ret = ">=" + M + "." + m + ".0 <" + M + "." + str(int(m) + 1) + ".0"
  723. else:
  724. ret = ">=" + M + "." + m + ".0 <" + str(int(M) + 1) + ".0.0"
  725. elif pr:
  726. logger.debug("replaceCaret pr %s", pr)
  727. if pr[0] != "-":
  728. pr = "-" + pr
  729. if M == "0":
  730. if m == "0":
  731. ret = (
  732. ">="
  733. + M
  734. + "."
  735. + m
  736. + "."
  737. + (p or "")
  738. + pr
  739. + " <"
  740. + M
  741. + "."
  742. + m
  743. + "."
  744. + str(int(p or 0) + 1)
  745. )
  746. else:
  747. ret = (
  748. ">="
  749. + M
  750. + "."
  751. + m
  752. + "."
  753. + (p or "")
  754. + pr
  755. + " <"
  756. + M
  757. + "."
  758. + str(int(m) + 1)
  759. + ".0"
  760. )
  761. else:
  762. ret = ">=" + M + "." + m + "." + (p or "") + pr + " <" + str(int(M) + 1) + ".0.0"
  763. else:
  764. if M == "0":
  765. if m == "0":
  766. ret = (
  767. ">="
  768. + M
  769. + "."
  770. + m
  771. + "."
  772. + (p or "")
  773. + " <"
  774. + M
  775. + "."
  776. + m
  777. + "."
  778. + str(int(p or 0) + 1)
  779. )
  780. else:
  781. ret = (
  782. ">="
  783. + M
  784. + "."
  785. + m
  786. + "."
  787. + (p or "")
  788. + " <"
  789. + M
  790. + "."
  791. + str(int(m) + 1)
  792. + ".0"
  793. )
  794. else:
  795. ret = ">=" + M + "." + m + "." + (p or "") + " <" + str(int(M) + 1) + ".0.0"
  796. logger.debug("caret return %s", ret)
  797. return ret
  798. return r.sub(repl, comp)
  799. def replace_xranges(comp, loose):
  800. logger.debug("replaceXRanges %s %s", comp, loose)
  801. return " ".join([replace_xrange(c, loose) for c in re.split(r"\s+", comp.strip())])
  802. def replace_xrange(comp, loose):
  803. comp = comp.strip()
  804. r = regexp[XRANGELOOSE] if loose else regexp[XRANGE]
  805. def repl(mob): # noqa PLR0911
  806. ret = mob.group(0)
  807. gtlt, M, m, p, pr, _ = mob.groups()
  808. logger.debug("xrange %s %s %s %s %s %s %s", comp, ret, gtlt, M, m, p, pr)
  809. xM = is_x(M)
  810. xm = xM or is_x(m)
  811. xp = xm or is_x(p)
  812. any_x = xp
  813. if gtlt == "=" and any_x:
  814. gtlt = ""
  815. logger.debug("xrange gtlt=%s any_x=%s", gtlt, any_x)
  816. if xM:
  817. if gtlt == ">" or gtlt == "<": # noqa SIM108
  818. # nothing is allowed
  819. ret = "<0.0.0"
  820. else:
  821. ret = "*"
  822. elif gtlt and any_x:
  823. # replace X with 0, and then append the -0 min-prerelease
  824. if xm:
  825. m = 0
  826. if xp:
  827. p = 0
  828. if gtlt == ">":
  829. # >1 => >=2.0.0
  830. # >1.2 => >=1.3.0
  831. # >1.2.3 => >= 1.2.4
  832. gtlt = ">="
  833. if xm:
  834. M = int(M) + 1
  835. m = 0
  836. p = 0
  837. elif xp:
  838. m = int(m) + 1
  839. p = 0
  840. elif gtlt == "<=":
  841. # <=0.7.x is actually <0.8.0, since any 0.7.x should
  842. # pass. Similarly, <=7.x is actually <8.0.0, etc.
  843. gtlt = "<"
  844. if xm:
  845. M = int(M) + 1
  846. else:
  847. m = int(m) + 1
  848. ret = gtlt + str(M) + "." + str(m) + "." + str(p)
  849. elif xm:
  850. ret = ">=" + M + ".0.0 <" + str(int(M) + 1) + ".0.0"
  851. elif xp:
  852. ret = ">=" + M + "." + m + ".0 <" + M + "." + str(int(m) + 1) + ".0"
  853. logger.debug("xRange return %s", ret)
  854. return ret
  855. return r.sub(repl, comp)
  856. # Because * is AND-ed with everything else in the comparator,
  857. # and '' means "any version", just remove the *s entirely.
  858. def replace_stars(comp, loose):
  859. logger.debug("replaceStars %s %s", comp, loose)
  860. # Looseness is ignored here. star is always as loose as it gets!
  861. return regexp[STAR].sub("", comp.strip())
  862. # This function is passed to string.replace(re[HYPHENRANGE])
  863. # M, m, patch, prerelease, build
  864. # 1.2 - 3.4.5 => >=1.2.0 <=3.4.5
  865. # 1.2.3 - 3.4 => >=1.2.0 <3.5.0 Any 3.4.x will do
  866. # 1.2 - 3.4 => >=1.2.0 <3.5.0
  867. def hyphen_replace(mob):
  868. from_, fM, fm, fp, fpr, fb, to, tM, tm, tp, tpr, tb = mob.groups()
  869. if is_x(fM):
  870. from_ = ""
  871. elif is_x(fm):
  872. from_ = ">=" + fM + ".0.0"
  873. elif is_x(fp):
  874. from_ = ">=" + fM + "." + fm + ".0"
  875. else:
  876. from_ = ">=" + from_
  877. if is_x(tM):
  878. to = ""
  879. elif is_x(tm):
  880. to = "<" + str(int(tM) + 1) + ".0.0"
  881. elif is_x(tp):
  882. to = "<" + tM + "." + str(int(tm) + 1) + ".0"
  883. elif tpr:
  884. to = "<=" + tM + "." + tm + "." + tp + "-" + tpr
  885. else:
  886. to = "<=" + to
  887. return (from_ + " " + to).strip()
  888. def test_set(set_, version):
  889. for e in set_:
  890. if not e.test(version):
  891. return False
  892. if len(version.prerelease) > 0:
  893. # Find the set of versions that are allowed to have prereleases
  894. # For example, ^1.2.3-pr.1 desugars to >=1.2.3-pr.1 <2.0.0
  895. # That should allow `1.2.3-pr.2` to pass.
  896. # However, `1.2.4-alpha.notready` should NOT be allowed,
  897. # even though it's within the range set by the comparators.
  898. for e in set_:
  899. if e.semver == ANY:
  900. continue
  901. if len(e.semver.prerelease) > 0:
  902. allowed = e.semver
  903. if (
  904. allowed.major == version.major
  905. and allowed.minor == version.minor
  906. and allowed.patch == version.patch
  907. ):
  908. return True
  909. # Version has a -pre, but it's not one of the ones we like.
  910. return False
  911. return True
  912. def satisfies(version, range_, loose=False):
  913. try:
  914. range_ = make_range(range_, loose)
  915. except Exception:
  916. return False
  917. return range_.test(version)
  918. def max_satisfying(versions, range_, loose=False):
  919. try:
  920. range_ob = make_range(range_, loose=loose)
  921. except Exception:
  922. return None
  923. max_ = None
  924. max_sv = None
  925. for v in versions:
  926. if range_ob.test(v): # noqa # satisfies(v, range_, loose=loose)
  927. if max_ is None or max_sv.compare(v) == -1: # compare(max, v, true)
  928. max_ = v
  929. max_sv = make_semver(max_, loose=loose)
  930. return max_
  931. def valid_range(range_, loose):
  932. try:
  933. # Return '*' instead of '' so that truthiness works.
  934. # This will throw if it's invalid anyway
  935. return make_range(range_, loose).range or "*"
  936. except Exception:
  937. return None
  938. # Determine if version is less than all the versions possible in the range
  939. def ltr(version, range_, loose):
  940. return outside(version, range_, "<", loose)
  941. # Determine if version is greater than all the versions possible in the range.
  942. def rtr(version, range_, loose):
  943. return outside(version, range_, ">", loose)
  944. def outside(version, range_, hilo, loose):
  945. version = make_semver(version, loose)
  946. range_ = make_range(range_, loose)
  947. if hilo == ">":
  948. gtfn = gt
  949. ltefn = lte
  950. ltfn = lt
  951. comp = ">"
  952. ecomp = ">="
  953. elif hilo == "<":
  954. gtfn = lt
  955. ltefn = gte
  956. ltfn = gt
  957. comp = "<"
  958. ecomp = "<="
  959. else:
  960. raise ValueError("Must provide a hilo val of '<' or '>'")
  961. # If it satisfies the range it is not outside
  962. if satisfies(version, range_, loose):
  963. return False
  964. # From now on, variable terms are as if we're in "gtr" mode.
  965. # but note that everything is flipped for the "ltr" function.
  966. for comparators in range_.set:
  967. high = None
  968. low = None
  969. for comparator in comparators:
  970. high = high or comparator
  971. low = low or comparator
  972. if gtfn(comparator.semver, high.semver, loose):
  973. high = comparator
  974. elif ltfn(comparator.semver, low.semver, loose):
  975. low = comparator
  976. # If the edge version comparator has a operator then our version
  977. # isn't outside it
  978. if high.operator == comp or high.operator == ecomp:
  979. return False
  980. # If the lowest version comparator has an operator and our version
  981. # is less than it then it isn't higher than the range
  982. if (not low.operator or low.operator == comp) and ltefn(version, low.semver): # noqa SIM114
  983. return False
  984. elif low.operator == ecomp and ltfn(version, low.semver):
  985. return False
  986. return True