Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# -*- coding: utf-8 -*- 

2# Copyright (c) The python-semanticversion project 

3# This code is distributed under the two-clause BSD License. 

4 

5import functools 

6import re 

7import warnings 

8 

9 

10def _has_leading_zero(value): 

11 return (value 

12 and value[0] == '0' 

13 and value.isdigit() 

14 and value != '0') 

15 

16 

17class MaxIdentifier(object): 

18 __slots__ = [] 

19 

20 def __repr__(self): 

21 return 'MaxIdentifier()' 

22 

23 def __eq__(self, other): 

24 return isinstance(other, self.__class__) 

25 

26 

27@functools.total_ordering 

28class NumericIdentifier(object): 

29 __slots__ = ['value'] 

30 

31 def __init__(self, value): 

32 self.value = int(value) 

33 

34 def __repr__(self): 

35 return 'NumericIdentifier(%r)' % self.value 

36 

37 def __eq__(self, other): 

38 if isinstance(other, NumericIdentifier): 

39 return self.value == other.value 

40 return NotImplemented 

41 

42 def __lt__(self, other): 

43 if isinstance(other, MaxIdentifier): 

44 return True 

45 elif isinstance(other, AlphaIdentifier): 

46 return True 

47 elif isinstance(other, NumericIdentifier): 

48 return self.value < other.value 

49 else: 

50 return NotImplemented 

51 

52 

53@functools.total_ordering 

54class AlphaIdentifier(object): 

55 __slots__ = ['value'] 

56 

57 def __init__(self, value): 

58 self.value = value.encode('ascii') 

59 

60 def __repr__(self): 

61 return 'AlphaIdentifier(%r)' % self.value 

62 

63 def __eq__(self, other): 

64 if isinstance(other, AlphaIdentifier): 

65 return self.value == other.value 

66 return NotImplemented 

67 

68 def __lt__(self, other): 

69 if isinstance(other, MaxIdentifier): 

70 return True 

71 elif isinstance(other, NumericIdentifier): 

72 return False 

73 elif isinstance(other, AlphaIdentifier): 

74 return self.value < other.value 

75 else: 

76 return NotImplemented 

77 

78 

79class Version(object): 

80 

81 version_re = re.compile(r'^(\d+)\.(\d+)\.(\d+)(?:-([0-9a-zA-Z.-]+))?(?:\+([0-9a-zA-Z.-]+))?$') 

82 partial_version_re = re.compile(r'^(\d+)(?:\.(\d+)(?:\.(\d+))?)?(?:-([0-9a-zA-Z.-]*))?(?:\+([0-9a-zA-Z.-]*))?$') 

83 

84 def __init__( 

85 self, 

86 version_string=None, 

87 major=None, 

88 minor=None, 

89 patch=None, 

90 prerelease=None, 

91 build=None, 

92 partial=False): 

93 if partial: 

94 warnings.warn( 

95 "Partial versions will be removed in 3.0; use SimpleSpec('1.x.x') instead.", 

96 DeprecationWarning, 

97 stacklevel=2, 

98 ) 

99 has_text = version_string is not None 

100 has_parts = not (major is minor is patch is prerelease is build is None) 

101 if not has_text ^ has_parts: 

102 raise ValueError("Call either Version('1.2.3') or Version(major=1, ...).") 

103 

104 if has_text: 

105 major, minor, patch, prerelease, build = self.parse(version_string, partial) 

106 else: 

107 # Convenience: allow to omit prerelease/build. 

108 prerelease = tuple(prerelease or ()) 

109 if not partial: 

110 build = tuple(build or ()) 

111 self._validate_kwargs(major, minor, patch, prerelease, build, partial) 

112 

113 self.major = major 

114 self.minor = minor 

115 self.patch = patch 

116 self.prerelease = prerelease 

117 self.build = build 

118 

119 self.partial = partial 

120 

121 @classmethod 

122 def _coerce(cls, value, allow_none=False): 

123 if value is None and allow_none: 

124 return value 

125 return int(value) 

126 

127 def next_major(self): 

128 if self.prerelease and self.minor == self.patch == 0: 

129 return Version( 

130 major=self.major, 

131 minor=0, 

132 patch=0, 

133 partial=self.partial, 

134 ) 

135 else: 

136 return Version( 

137 major=self.major + 1, 

138 minor=0, 

139 patch=0, 

140 partial=self.partial, 

141 ) 

142 

143 def next_minor(self): 

144 if self.prerelease and self.patch == 0: 

145 return Version( 

146 major=self.major, 

147 minor=self.minor, 

148 patch=0, 

149 partial=self.partial, 

150 ) 

151 else: 

152 return Version( 

153 major=self.major, 

154 minor=self.minor + 1, 

155 patch=0, 

156 partial=self.partial, 

157 ) 

158 

159 def next_patch(self): 

160 if self.prerelease: 

161 return Version( 

162 major=self.major, 

163 minor=self.minor, 

164 patch=self.patch, 

165 partial=self.partial, 

166 ) 

167 else: 

168 return Version( 

169 major=self.major, 

170 minor=self.minor, 

171 patch=self.patch + 1, 

172 partial=self.partial, 

173 ) 

174 

175 def truncate(self, level='patch'): 

176 """Return a new Version object, truncated up to the selected level.""" 

177 if level == 'build': 

178 return self 

179 elif level == 'prerelease': 

180 return Version( 

181 major=self.major, 

182 minor=self.minor, 

183 patch=self.patch, 

184 prerelease=self.prerelease, 

185 partial=self.partial, 

186 ) 

187 elif level == 'patch': 

188 return Version( 

189 major=self.major, 

190 minor=self.minor, 

191 patch=self.patch, 

192 partial=self.partial, 

193 ) 

194 elif level == 'minor': 

195 return Version( 

196 major=self.major, 

197 minor=self.minor, 

198 patch=None if self.partial else 0, 

199 partial=self.partial, 

200 ) 

201 elif level == 'major': 

202 return Version( 

203 major=self.major, 

204 minor=None if self.partial else 0, 

205 patch=None if self.partial else 0, 

206 partial=self.partial, 

207 ) 

208 else: 

209 raise ValueError("Invalid truncation level `%s`." % level) 

210 

211 @classmethod 

212 def coerce(cls, version_string, partial=False): 

213 """Coerce an arbitrary version string into a semver-compatible one. 

214 

215 The rule is: 

216 - If not enough components, fill minor/patch with zeroes; unless 

217 partial=True 

218 - If more than 3 dot-separated components, extra components are "build" 

219 data. If some "build" data already appeared, append it to the 

220 extra components 

221 

222 Examples: 

223 >>> Version.coerce('0.1') 

224 Version(0, 1, 0) 

225 >>> Version.coerce('0.1.2.3') 

226 Version(0, 1, 2, (), ('3',)) 

227 >>> Version.coerce('0.1.2.3+4') 

228 Version(0, 1, 2, (), ('3', '4')) 

229 >>> Version.coerce('0.1+2-3+4_5') 

230 Version(0, 1, 0, (), ('2-3', '4-5')) 

231 """ 

232 base_re = re.compile(r'^\d+(?:\.\d+(?:\.\d+)?)?') 

233 

234 match = base_re.match(version_string) 

235 if not match: 

236 raise ValueError( 

237 "Version string lacks a numerical component: %r" 

238 % version_string 

239 ) 

240 

241 version = version_string[:match.end()] 

242 if not partial: 

243 # We need a not-partial version. 

244 while version.count('.') < 2: 

245 version += '.0' 

246 

247 # Strip leading zeros in components 

248 # Version is of the form nn, nn.pp or nn.pp.qq 

249 version = '.'.join( 

250 # If the part was '0', we end up with an empty string. 

251 part.lstrip('0') or '0' 

252 for part in version.split('.') 

253 ) 

254 

255 if match.end() == len(version_string): 

256 return Version(version, partial=partial) 

257 

258 rest = version_string[match.end():] 

259 

260 # Cleanup the 'rest' 

261 rest = re.sub(r'[^a-zA-Z0-9+.-]', '-', rest) 

262 

263 if rest[0] == '+': 

264 # A 'build' component 

265 prerelease = '' 

266 build = rest[1:] 

267 elif rest[0] == '.': 

268 # An extra version component, probably 'build' 

269 prerelease = '' 

270 build = rest[1:] 

271 elif rest[0] == '-': 

272 rest = rest[1:] 

273 if '+' in rest: 

274 prerelease, build = rest.split('+', 1) 

275 else: 

276 prerelease, build = rest, '' 

277 elif '+' in rest: 

278 prerelease, build = rest.split('+', 1) 

279 else: 

280 prerelease, build = rest, '' 

281 

282 build = build.replace('+', '.') 

283 

284 if prerelease: 

285 version = '%s-%s' % (version, prerelease) 

286 if build: 

287 version = '%s+%s' % (version, build) 

288 

289 return cls(version, partial=partial) 

290 

291 @classmethod 

292 def parse(cls, version_string, partial=False, coerce=False): 

293 """Parse a version string into a Version() object. 

294 

295 Args: 

296 version_string (str), the version string to parse 

297 partial (bool), whether to accept incomplete input 

298 coerce (bool), whether to try to map the passed in string into a 

299 valid Version. 

300 """ 

301 if not version_string: 

302 raise ValueError('Invalid empty version string: %r' % version_string) 

303 

304 if partial: 

305 version_re = cls.partial_version_re 

306 else: 

307 version_re = cls.version_re 

308 

309 match = version_re.match(version_string) 

310 if not match: 

311 raise ValueError('Invalid version string: %r' % version_string) 

312 

313 major, minor, patch, prerelease, build = match.groups() 

314 

315 if _has_leading_zero(major): 

316 raise ValueError("Invalid leading zero in major: %r" % version_string) 

317 if _has_leading_zero(minor): 

318 raise ValueError("Invalid leading zero in minor: %r" % version_string) 

319 if _has_leading_zero(patch): 

320 raise ValueError("Invalid leading zero in patch: %r" % version_string) 

321 

322 major = int(major) 

323 minor = cls._coerce(minor, partial) 

324 patch = cls._coerce(patch, partial) 

325 

326 if prerelease is None: 

327 if partial and (build is None): 

328 # No build info, strip here 

329 return (major, minor, patch, None, None) 

330 else: 

331 prerelease = () 

332 elif prerelease == '': 

333 prerelease = () 

334 else: 

335 prerelease = tuple(prerelease.split('.')) 

336 cls._validate_identifiers(prerelease, allow_leading_zeroes=False) 

337 

338 if build is None: 

339 if partial: 

340 build = None 

341 else: 

342 build = () 

343 elif build == '': 

344 build = () 

345 else: 

346 build = tuple(build.split('.')) 

347 cls._validate_identifiers(build, allow_leading_zeroes=True) 

348 

349 return (major, minor, patch, prerelease, build) 

350 

351 @classmethod 

352 def _validate_identifiers(cls, identifiers, allow_leading_zeroes=False): 

353 for item in identifiers: 

354 if not item: 

355 raise ValueError( 

356 "Invalid empty identifier %r in %r" 

357 % (item, '.'.join(identifiers)) 

358 ) 

359 

360 if item[0] == '0' and item.isdigit() and item != '0' and not allow_leading_zeroes: 

361 raise ValueError("Invalid leading zero in identifier %r" % item) 

362 

363 @classmethod 

364 def _validate_kwargs(cls, major, minor, patch, prerelease, build, partial): 

365 if ( 

366 major != int(major) 

367 or minor != cls._coerce(minor, partial) 

368 or patch != cls._coerce(patch, partial) 

369 or prerelease is None and not partial 

370 or build is None and not partial 

371 ): 

372 raise ValueError( 

373 "Invalid kwargs to Version(major=%r, minor=%r, patch=%r, " 

374 "prerelease=%r, build=%r, partial=%r" % ( 

375 major, minor, patch, prerelease, build, partial 

376 )) 

377 if prerelease is not None: 

378 cls._validate_identifiers(prerelease, allow_leading_zeroes=False) 

379 if build is not None: 

380 cls._validate_identifiers(build, allow_leading_zeroes=True) 

381 

382 def __iter__(self): 

383 return iter((self.major, self.minor, self.patch, self.prerelease, self.build)) 

384 

385 def __str__(self): 

386 version = '%d' % self.major 

387 if self.minor is not None: 

388 version = '%s.%d' % (version, self.minor) 

389 if self.patch is not None: 

390 version = '%s.%d' % (version, self.patch) 

391 

392 if self.prerelease or (self.partial and self.prerelease == () and self.build is None): 

393 version = '%s-%s' % (version, '.'.join(self.prerelease)) 

394 if self.build or (self.partial and self.build == ()): 

395 version = '%s+%s' % (version, '.'.join(self.build)) 

396 return version 

397 

398 def __repr__(self): 

399 return '%s(%r%s)' % ( 

400 self.__class__.__name__, 

401 str(self), 

402 ', partial=True' if self.partial else '', 

403 ) 

404 

405 def __hash__(self): 

406 # We don't include 'partial', since this is strictly equivalent to having 

407 # at least a field being `None`. 

408 return hash((self.major, self.minor, self.patch, self.prerelease, self.build)) 

409 

410 @property 

411 def precedence_key(self): 

412 if self.prerelease: 

413 prerelease_key = tuple( 

414 NumericIdentifier(part) if re.match(r'^[0-9]+$', part) else AlphaIdentifier(part) 

415 for part in self.prerelease 

416 ) 

417 else: 

418 prerelease_key = ( 

419 MaxIdentifier(), 

420 ) 

421 

422 return ( 

423 self.major, 

424 self.minor, 

425 self.patch, 

426 prerelease_key, 

427 ) 

428 

429 def __cmp__(self, other): 

430 if not isinstance(other, self.__class__): 

431 return NotImplemented 

432 if self < other: 

433 return -1 

434 elif self > other: 

435 return 1 

436 elif self == other: 

437 return 0 

438 else: 

439 return NotImplemented 

440 

441 def __eq__(self, other): 

442 if not isinstance(other, self.__class__): 

443 return NotImplemented 

444 return ( 

445 self.major == other.major 

446 and self.minor == other.minor 

447 and self.patch == other.patch 

448 and (self.prerelease or ()) == (other.prerelease or ()) 

449 and (self.build or ()) == (other.build or ()) 

450 ) 

451 

452 def __ne__(self, other): 

453 if not isinstance(other, self.__class__): 

454 return NotImplemented 

455 return tuple(self) != tuple(other) 

456 

457 def __lt__(self, other): 

458 if not isinstance(other, self.__class__): 

459 return NotImplemented 

460 return self.precedence_key < other.precedence_key 

461 

462 def __le__(self, other): 

463 if not isinstance(other, self.__class__): 

464 return NotImplemented 

465 return self.precedence_key <= other.precedence_key 

466 

467 def __gt__(self, other): 

468 if not isinstance(other, self.__class__): 

469 return NotImplemented 

470 return self.precedence_key > other.precedence_key 

471 

472 def __ge__(self, other): 

473 if not isinstance(other, self.__class__): 

474 return NotImplemented 

475 return self.precedence_key >= other.precedence_key 

476 

477 

478class SpecItem(object): 

479 """A requirement specification.""" 

480 

481 KIND_ANY = '*' 

482 KIND_LT = '<' 

483 KIND_LTE = '<=' 

484 KIND_EQUAL = '==' 

485 KIND_SHORTEQ = '=' 

486 KIND_EMPTY = '' 

487 KIND_GTE = '>=' 

488 KIND_GT = '>' 

489 KIND_NEQ = '!=' 

490 KIND_CARET = '^' 

491 KIND_TILDE = '~' 

492 KIND_COMPATIBLE = '~=' 

493 

494 # Map a kind alias to its full version 

495 KIND_ALIASES = { 

496 KIND_SHORTEQ: KIND_EQUAL, 

497 KIND_EMPTY: KIND_EQUAL, 

498 } 

499 

500 re_spec = re.compile(r'^(<|<=||=|==|>=|>|!=|\^|~|~=)(\d.*)$') 

501 

502 def __init__(self, requirement_string, _warn=True): 

503 if _warn: 

504 warnings.warn( 

505 "The `SpecItem` class will be removed in 3.0.", 

506 DeprecationWarning, 

507 stacklevel=2, 

508 ) 

509 kind, spec = self.parse(requirement_string) 

510 self.kind = kind 

511 self.spec = spec 

512 self._clause = Spec(requirement_string).clause 

513 

514 @classmethod 

515 def parse(cls, requirement_string): 

516 if not requirement_string: 

517 raise ValueError("Invalid empty requirement specification: %r" % requirement_string) 

518 

519 # Special case: the 'any' version spec. 

520 if requirement_string == '*': 

521 return (cls.KIND_ANY, '') 

522 

523 match = cls.re_spec.match(requirement_string) 

524 if not match: 

525 raise ValueError("Invalid requirement specification: %r" % requirement_string) 

526 

527 kind, version = match.groups() 

528 if kind in cls.KIND_ALIASES: 

529 kind = cls.KIND_ALIASES[kind] 

530 

531 spec = Version(version, partial=True) 

532 if spec.build is not None and kind not in (cls.KIND_EQUAL, cls.KIND_NEQ): 

533 raise ValueError( 

534 "Invalid requirement specification %r: build numbers have no ordering." 

535 % requirement_string 

536 ) 

537 return (kind, spec) 

538 

539 @classmethod 

540 def from_matcher(cls, matcher): 

541 if matcher == Always(): 

542 return cls('*', _warn=False) 

543 elif matcher == Never(): 

544 return cls('<0.0.0-', _warn=False) 

545 elif isinstance(matcher, Range): 

546 return cls('%s%s' % (matcher.operator, matcher.target), _warn=False) 

547 

548 def match(self, version): 

549 return self._clause.match(version) 

550 

551 def __str__(self): 

552 return '%s%s' % (self.kind, self.spec) 

553 

554 def __repr__(self): 

555 return '<SpecItem: %s %r>' % (self.kind, self.spec) 

556 

557 def __eq__(self, other): 

558 if not isinstance(other, SpecItem): 

559 return NotImplemented 

560 return self.kind == other.kind and self.spec == other.spec 

561 

562 def __hash__(self): 

563 return hash((self.kind, self.spec)) 

564 

565 

566def compare(v1, v2): 

567 return Version(v1).__cmp__(Version(v2)) 

568 

569 

570def match(spec, version): 

571 return Spec(spec).match(Version(version)) 

572 

573 

574def validate(version_string): 

575 """Validates a version string againt the SemVer specification.""" 

576 try: 

577 Version.parse(version_string) 

578 return True 

579 except ValueError: 

580 return False 

581 

582 

583DEFAULT_SYNTAX = 'simple' 

584 

585 

586class BaseSpec(object): 

587 """A specification of compatible versions. 

588 

589 Usage: 

590 >>> Spec('>=1.0.0', syntax='npm') 

591 

592 A version matches a specification if it matches any 

593 of the clauses of that specification. 

594 

595 Internally, a Spec is AnyOf( 

596 AllOf(Matcher, Matcher, Matcher), 

597 AllOf(...), 

598 ) 

599 """ 

600 SYNTAXES = {} 

601 

602 @classmethod 

603 def register_syntax(cls, subclass): 

604 syntax = subclass.SYNTAX 

605 if syntax is None: 

606 raise ValueError("A Spec needs its SYNTAX field to be set.") 

607 elif syntax in cls.SYNTAXES: 

608 raise ValueError( 

609 "Duplicate syntax for %s: %r, %r" 

610 % (syntax, cls.SYNTAXES[syntax], subclass) 

611 ) 

612 cls.SYNTAXES[syntax] = subclass 

613 return subclass 

614 

615 def __init__(self, expression): 

616 super(BaseSpec, self).__init__() 

617 self.expression = expression 

618 self.clause = self._parse_to_clause(expression) 

619 

620 @classmethod 

621 def parse(cls, expression, syntax=DEFAULT_SYNTAX): 

622 """Convert a syntax-specific expression into a BaseSpec instance.""" 

623 return cls.SYNTAXES[syntax](expression) 

624 

625 @classmethod 

626 def _parse_to_clause(cls, expression): 

627 """Converts an expression to a clause.""" 

628 raise NotImplementedError() 

629 

630 def filter(self, versions): 

631 """Filter an iterable of versions satisfying the Spec.""" 

632 for version in versions: 

633 if self.match(version): 

634 yield version 

635 

636 def match(self, version): 

637 """Check whether a Version satisfies the Spec.""" 

638 return self.clause.match(version) 

639 

640 def select(self, versions): 

641 """Select the best compatible version among an iterable of options.""" 

642 options = list(self.filter(versions)) 

643 if options: 

644 return max(options) 

645 return None 

646 

647 def __contains__(self, version): 

648 """Whether `version in self`.""" 

649 if isinstance(version, Version): 

650 return self.match(version) 

651 return False 

652 

653 def __eq__(self, other): 

654 if not isinstance(other, self.__class__): 

655 return NotImplemented 

656 

657 return self.clause == other.clause 

658 

659 def __hash__(self): 

660 return hash(self.clause) 

661 

662 def __str__(self): 

663 return self.expression 

664 

665 def __repr__(self): 

666 return '<%s: %r>' % (self.__class__.__name__, self.expression) 

667 

668 

669class Clause(object): 

670 __slots__ = [] 

671 

672 def match(self, version): 

673 raise NotImplementedError() 

674 

675 def __and__(self, other): 

676 raise NotImplementedError() 

677 

678 def __or__(self, other): 

679 raise NotImplementedError() 

680 

681 def __eq__(self, other): 

682 raise NotImplementedError() 

683 

684 def prettyprint(self, indent='\t'): 

685 """Pretty-print the clause. 

686 """ 

687 return '\n'.join(self._pretty()).replace('\t', indent) 

688 

689 def _pretty(self): 

690 """Actual pretty-printing logic. 

691 

692 Yields: 

693 A list of string. Indentation is performed with \t. 

694 """ 

695 yield repr(self) 

696 

697 def __ne__(self, other): 

698 return not self == other 

699 

700 def simplify(self): 

701 return self 

702 

703 

704class AnyOf(Clause): 

705 __slots__ = ['clauses'] 

706 

707 def __init__(self, *clauses): 

708 super(AnyOf, self).__init__() 

709 self.clauses = frozenset(clauses) 

710 

711 def match(self, version): 

712 return any(c.match(version) for c in self.clauses) 

713 

714 def simplify(self): 

715 subclauses = set() 

716 for clause in self.clauses: 

717 simplified = clause.simplify() 

718 if isinstance(simplified, AnyOf): 

719 subclauses |= simplified.clauses 

720 elif simplified == Never(): 

721 continue 

722 else: 

723 subclauses.add(simplified) 

724 if len(subclauses) == 1: 

725 return subclauses.pop() 

726 return AnyOf(*subclauses) 

727 

728 def __hash__(self): 

729 return hash((AnyOf, self.clauses)) 

730 

731 def __iter__(self): 

732 return iter(self.clauses) 

733 

734 def __eq__(self, other): 

735 return isinstance(other, self.__class__) and self.clauses == other.clauses 

736 

737 def __and__(self, other): 

738 if isinstance(other, AllOf): 

739 return other & self 

740 elif isinstance(other, Matcher) or isinstance(other, AnyOf): 

741 return AllOf(self, other) 

742 else: 

743 return NotImplemented 

744 

745 def __or__(self, other): 

746 if isinstance(other, AnyOf): 

747 clauses = list(self.clauses | other.clauses) 

748 elif isinstance(other, Matcher) or isinstance(other, AllOf): 

749 clauses = list(self.clauses | set([other])) 

750 else: 

751 return NotImplemented 

752 return AnyOf(*clauses) 

753 

754 def __repr__(self): 

755 return 'AnyOf(%s)' % ', '.join(sorted(repr(c) for c in self.clauses)) 

756 

757 def _pretty(self): 

758 yield 'AnyOF(' 

759 for clause in self.clauses: 

760 lines = list(clause._pretty()) 

761 for line in lines[:-1]: 

762 yield '\t' + line 

763 yield '\t' + lines[-1] + ',' 

764 yield ')' 

765 

766 

767class AllOf(Clause): 

768 __slots__ = ['clauses'] 

769 

770 def __init__(self, *clauses): 

771 super(AllOf, self).__init__() 

772 self.clauses = frozenset(clauses) 

773 

774 def match(self, version): 

775 return all(clause.match(version) for clause in self.clauses) 

776 

777 def simplify(self): 

778 subclauses = set() 

779 for clause in self.clauses: 

780 simplified = clause.simplify() 

781 if isinstance(simplified, AllOf): 

782 subclauses |= simplified.clauses 

783 elif simplified == Always(): 

784 continue 

785 else: 

786 subclauses.add(simplified) 

787 if len(subclauses) == 1: 

788 return subclauses.pop() 

789 return AllOf(*subclauses) 

790 

791 def __hash__(self): 

792 return hash((AllOf, self.clauses)) 

793 

794 def __iter__(self): 

795 return iter(self.clauses) 

796 

797 def __eq__(self, other): 

798 return isinstance(other, self.__class__) and self.clauses == other.clauses 

799 

800 def __and__(self, other): 

801 if isinstance(other, Matcher) or isinstance(other, AnyOf): 

802 clauses = list(self.clauses | set([other])) 

803 elif isinstance(other, AllOf): 

804 clauses = list(self.clauses | other.clauses) 

805 else: 

806 return NotImplemented 

807 return AllOf(*clauses) 

808 

809 def __or__(self, other): 

810 if isinstance(other, AnyOf): 

811 return other | self 

812 elif isinstance(other, Matcher): 

813 return AnyOf(self, AllOf(other)) 

814 elif isinstance(other, AllOf): 

815 return AnyOf(self, other) 

816 else: 

817 return NotImplemented 

818 

819 def __repr__(self): 

820 return 'AllOf(%s)' % ', '.join(sorted(repr(c) for c in self.clauses)) 

821 

822 def _pretty(self): 

823 yield 'AllOF(' 

824 for clause in self.clauses: 

825 lines = list(clause._pretty()) 

826 for line in lines[:-1]: 

827 yield '\t' + line 

828 yield '\t' + lines[-1] + ',' 

829 yield ')' 

830 

831 

832class Matcher(Clause): 

833 __slots__ = [] 

834 

835 def __and__(self, other): 

836 if isinstance(other, AllOf): 

837 return other & self 

838 elif isinstance(other, Matcher) or isinstance(other, AnyOf): 

839 return AllOf(self, other) 

840 else: 

841 return NotImplemented 

842 

843 def __or__(self, other): 

844 if isinstance(other, AnyOf): 

845 return other | self 

846 elif isinstance(other, Matcher) or isinstance(other, AllOf): 

847 return AnyOf(self, other) 

848 else: 

849 return NotImplemented 

850 

851 

852class Never(Matcher): 

853 __slots__ = [] 

854 

855 def match(self, version): 

856 return False 

857 

858 def __hash__(self): 

859 return hash((Never,)) 

860 

861 def __eq__(self, other): 

862 return isinstance(other, self.__class__) 

863 

864 def __and__(self, other): 

865 return self 

866 

867 def __or__(self, other): 

868 return other 

869 

870 def __repr__(self): 

871 return 'Never()' 

872 

873 

874class Always(Matcher): 

875 __slots__ = [] 

876 

877 def match(self, version): 

878 return True 

879 

880 def __hash__(self): 

881 return hash((Always,)) 

882 

883 def __eq__(self, other): 

884 return isinstance(other, self.__class__) 

885 

886 def __and__(self, other): 

887 return other 

888 

889 def __or__(self, other): 

890 return self 

891 

892 def __repr__(self): 

893 return 'Always()' 

894 

895 

896class Range(Matcher): 

897 OP_EQ = '==' 

898 OP_GT = '>' 

899 OP_GTE = '>=' 

900 OP_LT = '<' 

901 OP_LTE = '<=' 

902 OP_NEQ = '!=' 

903 

904 # <1.2.3 matches 1.2.3-a1 

905 PRERELEASE_ALWAYS = 'always' 

906 # <1.2.3 does not match 1.2.3-a1 

907 PRERELEASE_NATURAL = 'natural' 

908 # 1.2.3-a1 is only considered if target == 1.2.3-xxx 

909 PRERELEASE_SAMEPATCH = 'same-patch' 

910 

911 # 1.2.3 matches 1.2.3+* 

912 BUILD_IMPLICIT = 'implicit' 

913 # 1.2.3 matches only 1.2.3, not 1.2.3+4 

914 BUILD_STRICT = 'strict' 

915 

916 __slots__ = ['operator', 'target', 'prerelease_policy', 'build_policy'] 

917 

918 def __init__(self, operator, target, prerelease_policy=PRERELEASE_NATURAL, build_policy=BUILD_IMPLICIT): 

919 super(Range, self).__init__() 

920 if target.build and operator not in (self.OP_EQ, self.OP_NEQ): 

921 raise ValueError( 

922 "Invalid range %s%s: build numbers have no ordering." 

923 % (operator, target)) 

924 self.operator = operator 

925 self.target = target 

926 self.prerelease_policy = prerelease_policy 

927 self.build_policy = self.BUILD_STRICT if target.build else build_policy 

928 

929 def match(self, version): 

930 if self.build_policy != self.BUILD_STRICT: 

931 version = version.truncate('prerelease') 

932 

933 if version.prerelease: 

934 same_patch = self.target.truncate() == version.truncate() 

935 

936 if self.prerelease_policy == self.PRERELEASE_SAMEPATCH and not same_patch: 

937 return False 

938 

939 if self.operator == self.OP_EQ: 

940 if self.build_policy == self.BUILD_STRICT: 

941 return ( 

942 self.target.truncate('prerelease') == version.truncate('prerelease') 

943 and version.build == self.target.build 

944 ) 

945 return version == self.target 

946 elif self.operator == self.OP_GT: 

947 return version > self.target 

948 elif self.operator == self.OP_GTE: 

949 return version >= self.target 

950 elif self.operator == self.OP_LT: 

951 if ( 

952 version.prerelease 

953 and self.prerelease_policy == self.PRERELEASE_NATURAL 

954 and version.truncate() == self.target.truncate() 

955 and not self.target.prerelease 

956 ): 

957 return False 

958 return version < self.target 

959 elif self.operator == self.OP_LTE: 

960 return version <= self.target 

961 else: 

962 assert self.operator == self.OP_NEQ 

963 if self.build_policy == self.BUILD_STRICT: 

964 return not ( 

965 self.target.truncate('prerelease') == version.truncate('prerelease') 

966 and version.build == self.target.build 

967 ) 

968 

969 if ( 

970 version.prerelease 

971 and self.prerelease_policy == self.PRERELEASE_NATURAL 

972 and version.truncate() == self.target.truncate() 

973 and not self.target.prerelease 

974 ): 

975 return False 

976 return version != self.target 

977 

978 def __hash__(self): 

979 return hash((Range, self.operator, self.target, self.prerelease_policy)) 

980 

981 def __eq__(self, other): 

982 return ( 

983 isinstance(other, self.__class__) 

984 and self.operator == other.operator 

985 and self.target == other.target 

986 and self.prerelease_policy == other.prerelease_policy 

987 ) 

988 

989 def __str__(self): 

990 return '%s%s' % (self.operator, self.target) 

991 

992 def __repr__(self): 

993 policy_part = ( 

994 '' if self.prerelease_policy == self.PRERELEASE_NATURAL 

995 else ', prerelease_policy=%r' % self.prerelease_policy 

996 ) + ( 

997 '' if self.build_policy == self.BUILD_IMPLICIT 

998 else ', build_policy=%r' % self.build_policy 

999 ) 

1000 return 'Range(%r, %r%s)' % ( 

1001 self.operator, 

1002 self.target, 

1003 policy_part, 

1004 ) 

1005 

1006 

1007@BaseSpec.register_syntax 

1008class SimpleSpec(BaseSpec): 

1009 

1010 SYNTAX = 'simple' 

1011 

1012 @classmethod 

1013 def _parse_to_clause(cls, expression): 

1014 return cls.Parser.parse(expression) 

1015 

1016 class Parser: 

1017 NUMBER = r'\*|0|[1-9][0-9]*' 

1018 NAIVE_SPEC = re.compile(r"""^ 

1019 (?P<op><|<=||=|==|>=|>|!=|\^|~|~=) 

1020 (?P<major>{nb})(?:\.(?P<minor>{nb})(?:\.(?P<patch>{nb}))?)? 

1021 (?:-(?P<prerel>[a-z0-9A-Z.-]*))? 

1022 (?:\+(?P<build>[a-z0-9A-Z.-]*))? 

1023 $ 

1024 """.format(nb=NUMBER), 

1025 re.VERBOSE, 

1026 ) 

1027 

1028 @classmethod 

1029 def parse(cls, expression): 

1030 blocks = expression.split(',') 

1031 clause = Always() 

1032 for block in blocks: 

1033 if not cls.NAIVE_SPEC.match(block): 

1034 raise ValueError("Invalid simple block %r" % block) 

1035 clause &= cls.parse_block(block) 

1036 

1037 return clause 

1038 

1039 PREFIX_CARET = '^' 

1040 PREFIX_TILDE = '~' 

1041 PREFIX_COMPATIBLE = '~=' 

1042 PREFIX_EQ = '==' 

1043 PREFIX_NEQ = '!=' 

1044 PREFIX_GT = '>' 

1045 PREFIX_GTE = '>=' 

1046 PREFIX_LT = '<' 

1047 PREFIX_LTE = '<=' 

1048 

1049 PREFIX_ALIASES = { 

1050 '=': PREFIX_EQ, 

1051 '': PREFIX_EQ, 

1052 } 

1053 

1054 EMPTY_VALUES = ['*', 'x', 'X', None] 

1055 

1056 @classmethod 

1057 def parse_block(cls, expr): 

1058 if not cls.NAIVE_SPEC.match(expr): 

1059 raise ValueError("Invalid simple spec component: %r" % expr) 

1060 prefix, major_t, minor_t, patch_t, prerel, build = cls.NAIVE_SPEC.match(expr).groups() 

1061 prefix = cls.PREFIX_ALIASES.get(prefix, prefix) 

1062 

1063 major = None if major_t in cls.EMPTY_VALUES else int(major_t) 

1064 minor = None if minor_t in cls.EMPTY_VALUES else int(minor_t) 

1065 patch = None if patch_t in cls.EMPTY_VALUES else int(patch_t) 

1066 

1067 if major is None: # '*' 

1068 target = Version(major=0, minor=0, patch=0) 

1069 if prefix not in (cls.PREFIX_EQ, cls.PREFIX_GTE): 

1070 raise ValueError("Invalid simple spec: %r" % expr) 

1071 elif minor is None: 

1072 target = Version(major=major, minor=0, patch=0) 

1073 elif patch is None: 

1074 target = Version(major=major, minor=minor, patch=0) 

1075 else: 

1076 target = Version( 

1077 major=major, 

1078 minor=minor, 

1079 patch=patch, 

1080 prerelease=prerel.split('.') if prerel else (), 

1081 build=build.split('.') if build else (), 

1082 ) 

1083 

1084 if (major is None or minor is None or patch is None) and (prerel or build): 

1085 raise ValueError("Invalid simple spec: %r" % expr) 

1086 

1087 if build is not None and prefix not in (cls.PREFIX_EQ, cls.PREFIX_NEQ): 

1088 raise ValueError("Invalid simple spec: %r" % expr) 

1089 

1090 if prefix == cls.PREFIX_CARET: 

1091 # Accept anything with the same most-significant digit 

1092 if target.major: 

1093 high = target.next_major() 

1094 elif target.minor: 

1095 high = target.next_minor() 

1096 else: 

1097 high = target.next_patch() 

1098 return Range(Range.OP_GTE, target) & Range(Range.OP_LT, high) 

1099 

1100 elif prefix == cls.PREFIX_TILDE: 

1101 assert major is not None 

1102 # Accept any higher patch in the same minor 

1103 # Might go higher if the initial version was a partial 

1104 if minor is None: 

1105 high = target.next_major() 

1106 else: 

1107 high = target.next_minor() 

1108 return Range(Range.OP_GTE, target) & Range(Range.OP_LT, high) 

1109 

1110 elif prefix == cls.PREFIX_COMPATIBLE: 

1111 assert major is not None 

1112 # ~1 is 1.0.0..2.0.0; ~=2.2 is 2.2.0..3.0.0; ~=1.4.5 is 1.4.5..1.5.0 

1113 if minor is None or patch is None: 

1114 # We got a partial version 

1115 high = target.next_major() 

1116 else: 

1117 high = target.next_minor() 

1118 return Range(Range.OP_GTE, target) & Range(Range.OP_LT, high) 

1119 

1120 elif prefix == cls.PREFIX_EQ: 

1121 if major is None: 

1122 return Range(Range.OP_GTE, target) 

1123 elif minor is None: 

1124 return Range(Range.OP_GTE, target) & Range(Range.OP_LT, target.next_major()) 

1125 elif patch is None: 

1126 return Range(Range.OP_GTE, target) & Range(Range.OP_LT, target.next_minor()) 

1127 elif build == '': 

1128 return Range(Range.OP_EQ, target, build_policy=Range.BUILD_STRICT) 

1129 else: 

1130 return Range(Range.OP_EQ, target) 

1131 

1132 elif prefix == cls.PREFIX_NEQ: 

1133 assert major is not None 

1134 if minor is None: 

1135 # !=1.x => <1.0.0 || >=2.0.0 

1136 return Range(Range.OP_LT, target) | Range(Range.OP_GTE, target.next_major()) 

1137 elif patch is None: 

1138 # !=1.2.x => <1.2.0 || >=1.3.0 

1139 return Range(Range.OP_LT, target) | Range(Range.OP_GTE, target.next_minor()) 

1140 elif prerel == '': 

1141 # !=1.2.3- 

1142 return Range(Range.OP_NEQ, target, prerelease_policy=Range.PRERELEASE_ALWAYS) 

1143 elif build == '': 

1144 # !=1.2.3+ or !=1.2.3-a2+ 

1145 return Range(Range.OP_NEQ, target, build_policy=Range.BUILD_STRICT) 

1146 else: 

1147 return Range(Range.OP_NEQ, target) 

1148 

1149 elif prefix == cls.PREFIX_GT: 

1150 assert major is not None 

1151 if minor is None: 

1152 # >1.x => >=2.0 

1153 return Range(Range.OP_GTE, target.next_major()) 

1154 elif patch is None: 

1155 return Range(Range.OP_GTE, target.next_minor()) 

1156 else: 

1157 return Range(Range.OP_GT, target) 

1158 

1159 elif prefix == cls.PREFIX_GTE: 

1160 return Range(Range.OP_GTE, target) 

1161 

1162 elif prefix == cls.PREFIX_LT: 

1163 assert major is not None 

1164 if prerel == '': 

1165 # <1.2.3- 

1166 return Range(Range.OP_LT, target, prerelease_policy=Range.PRERELEASE_ALWAYS) 

1167 return Range(Range.OP_LT, target) 

1168 

1169 else: 

1170 assert prefix == cls.PREFIX_LTE 

1171 assert major is not None 

1172 if minor is None: 

1173 # <=1.x => <2.0 

1174 return Range(Range.OP_LT, target.next_major()) 

1175 elif patch is None: 

1176 return Range(Range.OP_LT, target.next_minor()) 

1177 else: 

1178 return Range(Range.OP_LTE, target) 

1179 

1180 

1181class LegacySpec(SimpleSpec): 

1182 def __init__(self, *expressions): 

1183 warnings.warn( 

1184 "The Spec() class will be removed in 3.1; use SimpleSpec() instead.", 

1185 PendingDeprecationWarning, 

1186 stacklevel=2, 

1187 ) 

1188 

1189 if len(expressions) > 1: 

1190 warnings.warn( 

1191 "Passing 2+ arguments to SimpleSpec will be removed in 3.0; concatenate them with ',' instead.", 

1192 DeprecationWarning, 

1193 stacklevel=2, 

1194 ) 

1195 expression = ','.join(expressions) 

1196 super(LegacySpec, self).__init__(expression) 

1197 

1198 @property 

1199 def specs(self): 

1200 return list(self) 

1201 

1202 def __iter__(self): 

1203 warnings.warn( 

1204 "Iterating over the components of a SimpleSpec object will be removed in 3.0.", 

1205 DeprecationWarning, 

1206 stacklevel=2, 

1207 ) 

1208 try: 

1209 clauses = list(self.clause) 

1210 except TypeError: # Not an iterable 

1211 clauses = [self.clause] 

1212 for clause in clauses: 

1213 yield SpecItem.from_matcher(clause) 

1214 

1215 

1216Spec = LegacySpec 

1217 

1218 

1219@BaseSpec.register_syntax 

1220class NpmSpec(BaseSpec): 

1221 SYNTAX = 'npm' 

1222 

1223 @classmethod 

1224 def _parse_to_clause(cls, expression): 

1225 return cls.Parser.parse(expression) 

1226 

1227 class Parser: 

1228 JOINER = '||' 

1229 HYPHEN = ' - ' 

1230 

1231 NUMBER = r'x|X|\*|0|[1-9][0-9]*' 

1232 PART = r'[a-zA-Z0-9.-]*' 

1233 NPM_SPEC_BLOCK = re.compile(r""" 

1234 ^(?:v)? # Strip optional initial v 

1235 (?P<op><|<=|>=|>|=|\^|~|) # Operator, can be empty 

1236 (?P<major>{nb})(?:\.(?P<minor>{nb})(?:\.(?P<patch>{nb}))?)? 

1237 (?:-(?P<prerel>{part}))? # Optional re-release 

1238 (?:\+(?P<build>{part}))? # Optional build 

1239 $""".format(nb=NUMBER, part=PART), 

1240 re.VERBOSE, 

1241 ) 

1242 

1243 @classmethod 

1244 def range(cls, operator, target): 

1245 return Range(operator, target, prerelease_policy=Range.PRERELEASE_SAMEPATCH) 

1246 

1247 @classmethod 

1248 def parse(cls, expression): 

1249 result = Never() 

1250 groups = expression.split(cls.JOINER) 

1251 for group in groups: 

1252 group = group.strip() 

1253 if not group: 

1254 group = '>=0.0.0' 

1255 

1256 subclauses = [] 

1257 if cls.HYPHEN in group: 

1258 low, high = group.split(cls.HYPHEN, 2) 

1259 subclauses = cls.parse_simple('>=' + low) + cls.parse_simple('<=' + high) 

1260 

1261 else: 

1262 blocks = group.split(' ') 

1263 for block in blocks: 

1264 if not cls.NPM_SPEC_BLOCK.match(block): 

1265 raise ValueError("Invalid NPM block in %r: %r" % (expression, block)) 

1266 

1267 subclauses.extend(cls.parse_simple(block)) 

1268 

1269 prerelease_clauses = [] 

1270 non_prerel_clauses = [] 

1271 for clause in subclauses: 

1272 if clause.target.prerelease: 

1273 if clause.operator in (Range.OP_GT, Range.OP_GTE): 

1274 prerelease_clauses.append(Range( 

1275 operator=Range.OP_LT, 

1276 target=Version( 

1277 major=clause.target.major, 

1278 minor=clause.target.minor, 

1279 patch=clause.target.patch + 1, 

1280 ), 

1281 prerelease_policy=Range.PRERELEASE_ALWAYS, 

1282 )) 

1283 elif clause.operator in (Range.OP_LT, Range.OP_LTE): 

1284 prerelease_clauses.append(Range( 

1285 operator=Range.OP_GTE, 

1286 target=Version( 

1287 major=clause.target.major, 

1288 minor=clause.target.minor, 

1289 patch=0, 

1290 prerelease=(), 

1291 ), 

1292 prerelease_policy=Range.PRERELEASE_ALWAYS, 

1293 )) 

1294 prerelease_clauses.append(clause) 

1295 non_prerel_clauses.append(cls.range( 

1296 operator=clause.operator, 

1297 target=clause.target.truncate(), 

1298 )) 

1299 else: 

1300 non_prerel_clauses.append(clause) 

1301 if prerelease_clauses: 

1302 result |= AllOf(*prerelease_clauses) 

1303 result |= AllOf(*non_prerel_clauses) 

1304 

1305 return result 

1306 

1307 PREFIX_CARET = '^' 

1308 PREFIX_TILDE = '~' 

1309 PREFIX_EQ = '=' 

1310 PREFIX_GT = '>' 

1311 PREFIX_GTE = '>=' 

1312 PREFIX_LT = '<' 

1313 PREFIX_LTE = '<=' 

1314 

1315 PREFIX_ALIASES = { 

1316 '': PREFIX_EQ, 

1317 } 

1318 

1319 PREFIX_TO_OPERATOR = { 

1320 PREFIX_EQ: Range.OP_EQ, 

1321 PREFIX_LT: Range.OP_LT, 

1322 PREFIX_LTE: Range.OP_LTE, 

1323 PREFIX_GTE: Range.OP_GTE, 

1324 PREFIX_GT: Range.OP_GT, 

1325 } 

1326 

1327 EMPTY_VALUES = ['*', 'x', 'X', None] 

1328 

1329 @classmethod 

1330 def parse_simple(cls, simple): 

1331 match = cls.NPM_SPEC_BLOCK.match(simple) 

1332 

1333 prefix, major_t, minor_t, patch_t, prerel, build = match.groups() 

1334 

1335 prefix = cls.PREFIX_ALIASES.get(prefix, prefix) 

1336 major = None if major_t in cls.EMPTY_VALUES else int(major_t) 

1337 minor = None if minor_t in cls.EMPTY_VALUES else int(minor_t) 

1338 patch = None if patch_t in cls.EMPTY_VALUES else int(patch_t) 

1339 

1340 if build is not None and prefix not in [cls.PREFIX_EQ]: 

1341 # Ignore the 'build' part when not comparing to a specific part. 

1342 build = None 

1343 

1344 if major is None: # '*', 'x', 'X' 

1345 target = Version(major=0, minor=0, patch=0) 

1346 if prefix not in [cls.PREFIX_EQ, cls.PREFIX_GTE]: 

1347 raise ValueError("Invalid expression %r" % simple) 

1348 prefix = cls.PREFIX_GTE 

1349 elif minor is None: 

1350 target = Version(major=major, minor=0, patch=0) 

1351 elif patch is None: 

1352 target = Version(major=major, minor=minor, patch=0) 

1353 else: 

1354 target = Version( 

1355 major=major, 

1356 minor=minor, 

1357 patch=patch, 

1358 prerelease=prerel.split('.') if prerel else (), 

1359 build=build.split('.') if build else (), 

1360 ) 

1361 

1362 if (major is None or minor is None or patch is None) and (prerel or build): 

1363 raise ValueError("Invalid NPM spec: %r" % simple) 

1364 

1365 if prefix == cls.PREFIX_CARET: 

1366 if target.major: # ^1.2.4 => >=1.2.4 <2.0.0 ; ^1.x => >=1.0.0 <2.0.0 

1367 high = target.truncate().next_major() 

1368 elif target.minor: # ^0.1.2 => >=0.1.2 <0.2.0 

1369 high = target.truncate().next_minor() 

1370 elif minor is None: # ^0.x => >=0.0.0 <1.0.0 

1371 high = target.truncate().next_major() 

1372 elif patch is None: # ^0.2.x => >=0.2.0 <0.3.0 

1373 high = target.truncate().next_minor() 

1374 else: # ^0.0.1 => >=0.0.1 <0.0.2 

1375 high = target.truncate().next_patch() 

1376 return [cls.range(Range.OP_GTE, target), cls.range(Range.OP_LT, high)] 

1377 

1378 elif prefix == cls.PREFIX_TILDE: 

1379 assert major is not None 

1380 if minor is None: # ~1.x => >=1.0.0 <2.0.0 

1381 high = target.next_major() 

1382 else: # ~1.2.x => >=1.2.0 <1.3.0; ~1.2.3 => >=1.2.3 <1.3.0 

1383 high = target.next_minor() 

1384 return [cls.range(Range.OP_GTE, target), cls.range(Range.OP_LT, high)] 

1385 

1386 elif prefix == cls.PREFIX_EQ: 

1387 if major is None: 

1388 return [cls.range(Range.OP_GTE, target)] 

1389 elif minor is None: 

1390 return [cls.range(Range.OP_GTE, target), cls.range(Range.OP_LT, target.next_major())] 

1391 elif patch is None: 

1392 return [cls.range(Range.OP_GTE, target), cls.range(Range.OP_LT, target.next_minor())] 

1393 else: 

1394 return [cls.range(Range.OP_EQ, target)] 

1395 

1396 elif prefix == cls.PREFIX_GT: 

1397 assert major is not None 

1398 if minor is None: # >1.x 

1399 return [cls.range(Range.OP_GTE, target.next_major())] 

1400 elif patch is None: # >1.2.x => >=1.3.0 

1401 return [cls.range(Range.OP_GTE, target.next_minor())] 

1402 else: 

1403 return [cls.range(Range.OP_GT, target)] 

1404 

1405 elif prefix == cls.PREFIX_GTE: 

1406 return [cls.range(Range.OP_GTE, target)] 

1407 

1408 elif prefix == cls.PREFIX_LT: 

1409 assert major is not None 

1410 return [cls.range(Range.OP_LT, target)] 

1411 

1412 else: 

1413 assert prefix == cls.PREFIX_LTE 

1414 assert major is not None 

1415 if minor is None: # <=1.x => <2.0.0 

1416 return [cls.range(Range.OP_LT, target.next_major())] 

1417 elif patch is None: # <=1.2.x => <1.3.0 

1418 return [cls.range(Range.OP_LT, target.next_minor())] 

1419 else: 

1420 return [cls.range(Range.OP_LTE, target)]