semver.py 32 KB

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