semver.py 32 KB

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