You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

166 lines
5.0 KiB

  1. """Module for parsing and testing package version predicate strings.
  2. """
  3. import re
  4. import distutils.version
  5. import operator
  6. re_validPackage = re.compile(r"(?i)^\s*([a-z_]\w*(?:\.[a-z_]\w*)*)(.*)",
  7. re.ASCII)
  8. # (package) (rest)
  9. re_paren = re.compile(r"^\s*\((.*)\)\s*$") # (list) inside of parentheses
  10. re_splitComparison = re.compile(r"^\s*(<=|>=|<|>|!=|==)\s*([^\s,]+)\s*$")
  11. # (comp) (version)
  12. def splitUp(pred):
  13. """Parse a single version comparison.
  14. Return (comparison string, StrictVersion)
  15. """
  16. res = re_splitComparison.match(pred)
  17. if not res:
  18. raise ValueError("bad package restriction syntax: %r" % pred)
  19. comp, verStr = res.groups()
  20. return (comp, distutils.version.StrictVersion(verStr))
  21. compmap = {"<": operator.lt, "<=": operator.le, "==": operator.eq,
  22. ">": operator.gt, ">=": operator.ge, "!=": operator.ne}
  23. class VersionPredicate:
  24. """Parse and test package version predicates.
  25. >>> v = VersionPredicate('pyepat.abc (>1.0, <3333.3a1, !=1555.1b3)')
  26. The `name` attribute provides the full dotted name that is given::
  27. >>> v.name
  28. 'pyepat.abc'
  29. The str() of a `VersionPredicate` provides a normalized
  30. human-readable version of the expression::
  31. >>> print(v)
  32. pyepat.abc (> 1.0, < 3333.3a1, != 1555.1b3)
  33. The `satisfied_by()` method can be used to determine with a given
  34. version number is included in the set described by the version
  35. restrictions::
  36. >>> v.satisfied_by('1.1')
  37. True
  38. >>> v.satisfied_by('1.4')
  39. True
  40. >>> v.satisfied_by('1.0')
  41. False
  42. >>> v.satisfied_by('4444.4')
  43. False
  44. >>> v.satisfied_by('1555.1b3')
  45. False
  46. `VersionPredicate` is flexible in accepting extra whitespace::
  47. >>> v = VersionPredicate(' pat( == 0.1 ) ')
  48. >>> v.name
  49. 'pat'
  50. >>> v.satisfied_by('0.1')
  51. True
  52. >>> v.satisfied_by('0.2')
  53. False
  54. If any version numbers passed in do not conform to the
  55. restrictions of `StrictVersion`, a `ValueError` is raised::
  56. >>> v = VersionPredicate('p1.p2.p3.p4(>=1.0, <=1.3a1, !=1.2zb3)')
  57. Traceback (most recent call last):
  58. ...
  59. ValueError: invalid version number '1.2zb3'
  60. It the module or package name given does not conform to what's
  61. allowed as a legal module or package name, `ValueError` is
  62. raised::
  63. >>> v = VersionPredicate('foo-bar')
  64. Traceback (most recent call last):
  65. ...
  66. ValueError: expected parenthesized list: '-bar'
  67. >>> v = VersionPredicate('foo bar (12.21)')
  68. Traceback (most recent call last):
  69. ...
  70. ValueError: expected parenthesized list: 'bar (12.21)'
  71. """
  72. def __init__(self, versionPredicateStr):
  73. """Parse a version predicate string.
  74. """
  75. # Fields:
  76. # name: package name
  77. # pred: list of (comparison string, StrictVersion)
  78. versionPredicateStr = versionPredicateStr.strip()
  79. if not versionPredicateStr:
  80. raise ValueError("empty package restriction")
  81. match = re_validPackage.match(versionPredicateStr)
  82. if not match:
  83. raise ValueError("bad package name in %r" % versionPredicateStr)
  84. self.name, paren = match.groups()
  85. paren = paren.strip()
  86. if paren:
  87. match = re_paren.match(paren)
  88. if not match:
  89. raise ValueError("expected parenthesized list: %r" % paren)
  90. str = match.groups()[0]
  91. self.pred = [splitUp(aPred) for aPred in str.split(",")]
  92. if not self.pred:
  93. raise ValueError("empty parenthesized list in %r"
  94. % versionPredicateStr)
  95. else:
  96. self.pred = []
  97. def __str__(self):
  98. if self.pred:
  99. seq = [cond + " " + str(ver) for cond, ver in self.pred]
  100. return self.name + " (" + ", ".join(seq) + ")"
  101. else:
  102. return self.name
  103. def satisfied_by(self, version):
  104. """True if version is compatible with all the predicates in self.
  105. The parameter version must be acceptable to the StrictVersion
  106. constructor. It may be either a string or StrictVersion.
  107. """
  108. for cond, ver in self.pred:
  109. if not compmap[cond](version, ver):
  110. return False
  111. return True
  112. _provision_rx = None
  113. def split_provision(value):
  114. """Return the name and optional version number of a provision.
  115. The version number, if given, will be returned as a `StrictVersion`
  116. instance, otherwise it will be `None`.
  117. >>> split_provision('mypkg')
  118. ('mypkg', None)
  119. >>> split_provision(' mypkg( 1.2 ) ')
  120. ('mypkg', StrictVersion ('1.2'))
  121. """
  122. global _provision_rx
  123. if _provision_rx is None:
  124. _provision_rx = re.compile(
  125. "([a-zA-Z_]\w*(?:\.[a-zA-Z_]\w*)*)(?:\s*\(\s*([^)\s]+)\s*\))?$",
  126. re.ASCII)
  127. value = value.strip()
  128. m = _provision_rx.match(value)
  129. if not m:
  130. raise ValueError("illegal provides specification: %r" % value)
  131. ver = m.group(2) or None
  132. if ver:
  133. ver = distutils.version.StrictVersion(ver)
  134. return m.group(1), ver