(root)/
Python-3.11.7/
Lib/
distutils/
version.py
       1  #
       2  # distutils/version.py
       3  #
       4  # Implements multiple version numbering conventions for the
       5  # Python Module Distribution Utilities.
       6  #
       7  # $Id$
       8  #
       9  
      10  """Provides classes to represent module version numbers (one class for
      11  each style of version numbering).  There are currently two such classes
      12  implemented: StrictVersion and LooseVersion.
      13  
      14  Every version number class implements the following interface:
      15    * the 'parse' method takes a string and parses it to some internal
      16      representation; if the string is an invalid version number,
      17      'parse' raises a ValueError exception
      18    * the class constructor takes an optional string argument which,
      19      if supplied, is passed to 'parse'
      20    * __str__ reconstructs the string that was passed to 'parse' (or
      21      an equivalent string -- ie. one that will generate an equivalent
      22      version number instance)
      23    * __repr__ generates Python code to recreate the version number instance
      24    * _cmp compares the current instance with either another instance
      25      of the same class or a string (which will be parsed to an instance
      26      of the same class, thus must follow the same rules)
      27  """
      28  
      29  import re
      30  
      31  class ESC[4;38;5;81mVersion:
      32      """Abstract base class for version numbering classes.  Just provides
      33      constructor (__init__) and reproducer (__repr__), because those
      34      seem to be the same for all version numbering classes; and route
      35      rich comparisons to _cmp.
      36      """
      37  
      38      def __init__ (self, vstring=None):
      39          if vstring:
      40              self.parse(vstring)
      41  
      42      def __repr__ (self):
      43          return "%s ('%s')" % (self.__class__.__name__, str(self))
      44  
      45      def __eq__(self, other):
      46          c = self._cmp(other)
      47          if c is NotImplemented:
      48              return c
      49          return c == 0
      50  
      51      def __lt__(self, other):
      52          c = self._cmp(other)
      53          if c is NotImplemented:
      54              return c
      55          return c < 0
      56  
      57      def __le__(self, other):
      58          c = self._cmp(other)
      59          if c is NotImplemented:
      60              return c
      61          return c <= 0
      62  
      63      def __gt__(self, other):
      64          c = self._cmp(other)
      65          if c is NotImplemented:
      66              return c
      67          return c > 0
      68  
      69      def __ge__(self, other):
      70          c = self._cmp(other)
      71          if c is NotImplemented:
      72              return c
      73          return c >= 0
      74  
      75  
      76  # Interface for version-number classes -- must be implemented
      77  # by the following classes (the concrete ones -- Version should
      78  # be treated as an abstract class).
      79  #    __init__ (string) - create and take same action as 'parse'
      80  #                        (string parameter is optional)
      81  #    parse (string)    - convert a string representation to whatever
      82  #                        internal representation is appropriate for
      83  #                        this style of version numbering
      84  #    __str__ (self)    - convert back to a string; should be very similar
      85  #                        (if not identical to) the string supplied to parse
      86  #    __repr__ (self)   - generate Python code to recreate
      87  #                        the instance
      88  #    _cmp (self, other) - compare two version numbers ('other' may
      89  #                        be an unparsed version string, or another
      90  #                        instance of your version class)
      91  
      92  
      93  class ESC[4;38;5;81mStrictVersion (ESC[4;38;5;149mVersion):
      94  
      95      """Version numbering for anal retentives and software idealists.
      96      Implements the standard interface for version number classes as
      97      described above.  A version number consists of two or three
      98      dot-separated numeric components, with an optional "pre-release" tag
      99      on the end.  The pre-release tag consists of the letter 'a' or 'b'
     100      followed by a number.  If the numeric components of two version
     101      numbers are equal, then one with a pre-release tag will always
     102      be deemed earlier (lesser) than one without.
     103  
     104      The following are valid version numbers (shown in the order that
     105      would be obtained by sorting according to the supplied cmp function):
     106  
     107          0.4       0.4.0  (these two are equivalent)
     108          0.4.1
     109          0.5a1
     110          0.5b3
     111          0.5
     112          0.9.6
     113          1.0
     114          1.0.4a3
     115          1.0.4b1
     116          1.0.4
     117  
     118      The following are examples of invalid version numbers:
     119  
     120          1
     121          2.7.2.2
     122          1.3.a4
     123          1.3pl1
     124          1.3c4
     125  
     126      The rationale for this version numbering system will be explained
     127      in the distutils documentation.
     128      """
     129  
     130      version_re = re.compile(r'^(\d+) \. (\d+) (\. (\d+))? ([ab](\d+))?$',
     131                              re.VERBOSE | re.ASCII)
     132  
     133  
     134      def parse (self, vstring):
     135          match = self.version_re.match(vstring)
     136          if not match:
     137              raise ValueError("invalid version number '%s'" % vstring)
     138  
     139          (major, minor, patch, prerelease, prerelease_num) = \
     140              match.group(1, 2, 4, 5, 6)
     141  
     142          if patch:
     143              self.version = tuple(map(int, [major, minor, patch]))
     144          else:
     145              self.version = tuple(map(int, [major, minor])) + (0,)
     146  
     147          if prerelease:
     148              self.prerelease = (prerelease[0], int(prerelease_num))
     149          else:
     150              self.prerelease = None
     151  
     152  
     153      def __str__ (self):
     154  
     155          if self.version[2] == 0:
     156              vstring = '.'.join(map(str, self.version[0:2]))
     157          else:
     158              vstring = '.'.join(map(str, self.version))
     159  
     160          if self.prerelease:
     161              vstring = vstring + self.prerelease[0] + str(self.prerelease[1])
     162  
     163          return vstring
     164  
     165  
     166      def _cmp (self, other):
     167          if isinstance(other, str):
     168              other = StrictVersion(other)
     169          elif not isinstance(other, StrictVersion):
     170              return NotImplemented
     171  
     172          if self.version != other.version:
     173              # numeric versions don't match
     174              # prerelease stuff doesn't matter
     175              if self.version < other.version:
     176                  return -1
     177              else:
     178                  return 1
     179  
     180          # have to compare prerelease
     181          # case 1: neither has prerelease; they're equal
     182          # case 2: self has prerelease, other doesn't; other is greater
     183          # case 3: self doesn't have prerelease, other does: self is greater
     184          # case 4: both have prerelease: must compare them!
     185  
     186          if (not self.prerelease and not other.prerelease):
     187              return 0
     188          elif (self.prerelease and not other.prerelease):
     189              return -1
     190          elif (not self.prerelease and other.prerelease):
     191              return 1
     192          elif (self.prerelease and other.prerelease):
     193              if self.prerelease == other.prerelease:
     194                  return 0
     195              elif self.prerelease < other.prerelease:
     196                  return -1
     197              else:
     198                  return 1
     199          else:
     200              assert False, "never get here"
     201  
     202  # end class StrictVersion
     203  
     204  
     205  # The rules according to Greg Stein:
     206  # 1) a version number has 1 or more numbers separated by a period or by
     207  #    sequences of letters. If only periods, then these are compared
     208  #    left-to-right to determine an ordering.
     209  # 2) sequences of letters are part of the tuple for comparison and are
     210  #    compared lexicographically
     211  # 3) recognize the numeric components may have leading zeroes
     212  #
     213  # The LooseVersion class below implements these rules: a version number
     214  # string is split up into a tuple of integer and string components, and
     215  # comparison is a simple tuple comparison.  This means that version
     216  # numbers behave in a predictable and obvious way, but a way that might
     217  # not necessarily be how people *want* version numbers to behave.  There
     218  # wouldn't be a problem if people could stick to purely numeric version
     219  # numbers: just split on period and compare the numbers as tuples.
     220  # However, people insist on putting letters into their version numbers;
     221  # the most common purpose seems to be:
     222  #   - indicating a "pre-release" version
     223  #     ('alpha', 'beta', 'a', 'b', 'pre', 'p')
     224  #   - indicating a post-release patch ('p', 'pl', 'patch')
     225  # but of course this can't cover all version number schemes, and there's
     226  # no way to know what a programmer means without asking him.
     227  #
     228  # The problem is what to do with letters (and other non-numeric
     229  # characters) in a version number.  The current implementation does the
     230  # obvious and predictable thing: keep them as strings and compare
     231  # lexically within a tuple comparison.  This has the desired effect if
     232  # an appended letter sequence implies something "post-release":
     233  # eg. "0.99" < "0.99pl14" < "1.0", and "5.001" < "5.001m" < "5.002".
     234  #
     235  # However, if letters in a version number imply a pre-release version,
     236  # the "obvious" thing isn't correct.  Eg. you would expect that
     237  # "1.5.1" < "1.5.2a2" < "1.5.2", but under the tuple/lexical comparison
     238  # implemented here, this just isn't so.
     239  #
     240  # Two possible solutions come to mind.  The first is to tie the
     241  # comparison algorithm to a particular set of semantic rules, as has
     242  # been done in the StrictVersion class above.  This works great as long
     243  # as everyone can go along with bondage and discipline.  Hopefully a
     244  # (large) subset of Python module programmers will agree that the
     245  # particular flavour of bondage and discipline provided by StrictVersion
     246  # provides enough benefit to be worth using, and will submit their
     247  # version numbering scheme to its domination.  The free-thinking
     248  # anarchists in the lot will never give in, though, and something needs
     249  # to be done to accommodate them.
     250  #
     251  # Perhaps a "moderately strict" version class could be implemented that
     252  # lets almost anything slide (syntactically), and makes some heuristic
     253  # assumptions about non-digits in version number strings.  This could
     254  # sink into special-case-hell, though; if I was as talented and
     255  # idiosyncratic as Larry Wall, I'd go ahead and implement a class that
     256  # somehow knows that "1.2.1" < "1.2.2a2" < "1.2.2" < "1.2.2pl3", and is
     257  # just as happy dealing with things like "2g6" and "1.13++".  I don't
     258  # think I'm smart enough to do it right though.
     259  #
     260  # In any case, I've coded the test suite for this module (see
     261  # ../test/test_version.py) specifically to fail on things like comparing
     262  # "1.2a2" and "1.2".  That's not because the *code* is doing anything
     263  # wrong, it's because the simple, obvious design doesn't match my
     264  # complicated, hairy expectations for real-world version numbers.  It
     265  # would be a snap to fix the test suite to say, "Yep, LooseVersion does
     266  # the Right Thing" (ie. the code matches the conception).  But I'd rather
     267  # have a conception that matches common notions about version numbers.
     268  
     269  class ESC[4;38;5;81mLooseVersion (ESC[4;38;5;149mVersion):
     270  
     271      """Version numbering for anarchists and software realists.
     272      Implements the standard interface for version number classes as
     273      described above.  A version number consists of a series of numbers,
     274      separated by either periods or strings of letters.  When comparing
     275      version numbers, the numeric components will be compared
     276      numerically, and the alphabetic components lexically.  The following
     277      are all valid version numbers, in no particular order:
     278  
     279          1.5.1
     280          1.5.2b2
     281          161
     282          3.10a
     283          8.02
     284          3.4j
     285          1996.07.12
     286          3.2.pl0
     287          3.1.1.6
     288          2g6
     289          11g
     290          0.960923
     291          2.2beta29
     292          1.13++
     293          5.5.kw
     294          2.0b1pl0
     295  
     296      In fact, there is no such thing as an invalid version number under
     297      this scheme; the rules for comparison are simple and predictable,
     298      but may not always give the results you want (for some definition
     299      of "want").
     300      """
     301  
     302      component_re = re.compile(r'(\d+ | [a-z]+ | \.)', re.VERBOSE)
     303  
     304      def __init__ (self, vstring=None):
     305          if vstring:
     306              self.parse(vstring)
     307  
     308  
     309      def parse (self, vstring):
     310          # I've given up on thinking I can reconstruct the version string
     311          # from the parsed tuple -- so I just store the string here for
     312          # use by __str__
     313          self.vstring = vstring
     314          components = [x for x in self.component_re.split(vstring)
     315                                if x and x != '.']
     316          for i, obj in enumerate(components):
     317              try:
     318                  components[i] = int(obj)
     319              except ValueError:
     320                  pass
     321  
     322          self.version = components
     323  
     324  
     325      def __str__ (self):
     326          return self.vstring
     327  
     328  
     329      def __repr__ (self):
     330          return "LooseVersion ('%s')" % str(self)
     331  
     332  
     333      def _cmp (self, other):
     334          if isinstance(other, str):
     335              other = LooseVersion(other)
     336          elif not isinstance(other, LooseVersion):
     337              return NotImplemented
     338  
     339          if self.version == other.version:
     340              return 0
     341          if self.version < other.version:
     342              return -1
     343          if self.version > other.version:
     344              return 1
     345  
     346  
     347  # end class LooseVersion