Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/c7n/vendored/distutils/version.py: 39%

62 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-08 06:51 +0000

1# distutils/version.py 

2# 

3# Vendored from: 

4# https://github.com/python/cpython/blob/6fea61a9e02260648fbec204e9caac6d5176cc7b/Lib/distutils/version.py 

5 

6"""Provides classes to represent module version numbers (one class for 

7each style of version numbering). There are currently two such classes 

8implemented: StrictVersion and LooseVersion. 

9 

10Every version number class implements the following interface: 

11 * the 'parse' method takes a string and parses it to some internal 

12 representation; if the string is an invalid version number, 

13 'parse' raises a ValueError exception 

14 * the class constructor takes an optional string argument which, 

15 if supplied, is passed to 'parse' 

16 * __str__ reconstructs the string that was passed to 'parse' (or 

17 an equivalent string -- ie. one that will generate an equivalent 

18 version number instance) 

19 * __repr__ generates Python code to recreate the version number instance 

20 * _cmp compares the current instance with either another instance 

21 of the same class or a string (which will be parsed to an instance 

22 of the same class, thus must follow the same rules) 

23""" 

24 

25import re 

26 

27class Version: 

28 """Abstract base class for version numbering classes. Just provides 

29 constructor (__init__) and reproducer (__repr__), because those 

30 seem to be the same for all version numbering classes; and route 

31 rich comparisons to _cmp. 

32 """ 

33 

34 def __init__ (self, vstring=None): 

35 if vstring: 

36 self.parse(vstring) 

37 

38 def __repr__ (self): 

39 return "%s ('%s')" % (self.__class__.__name__, str(self)) 

40 

41 def __eq__(self, other): 

42 c = self._cmp(other) 

43 if c is NotImplemented: 

44 return c 

45 return c == 0 

46 

47 def __lt__(self, other): 

48 c = self._cmp(other) 

49 if c is NotImplemented: 

50 return c 

51 return c < 0 

52 

53 def __le__(self, other): 

54 c = self._cmp(other) 

55 if c is NotImplemented: 

56 return c 

57 return c <= 0 

58 

59 def __gt__(self, other): 

60 c = self._cmp(other) 

61 if c is NotImplemented: 

62 return c 

63 return c > 0 

64 

65 def __ge__(self, other): 

66 c = self._cmp(other) 

67 if c is NotImplemented: 

68 return c 

69 return c >= 0 

70 

71 

72# Interface for version-number classes -- must be implemented 

73# by the following classes (the concrete ones -- Version should 

74# be treated as an abstract class). 

75# __init__ (string) - create and take same action as 'parse' 

76# (string parameter is optional) 

77# parse (string) - convert a string representation to whatever 

78# internal representation is appropriate for 

79# this style of version numbering 

80# __str__ (self) - convert back to a string; should be very similar 

81# (if not identical to) the string supplied to parse 

82# __repr__ (self) - generate Python code to recreate 

83# the instance 

84# _cmp (self, other) - compare two version numbers ('other' may 

85# be an unparsed version string, or another 

86# instance of your version class) 

87 

88# The rules according to Greg Stein: 

89# 1) a version number has 1 or more numbers separated by a period or by 

90# sequences of letters. If only periods, then these are compared 

91# left-to-right to determine an ordering. 

92# 2) sequences of letters are part of the tuple for comparison and are 

93# compared lexicographically 

94# 3) recognize the numeric components may have leading zeroes 

95# 

96# The LooseVersion class below implements these rules: a version number 

97# string is split up into a tuple of integer and string components, and 

98# comparison is a simple tuple comparison. This means that version 

99# numbers behave in a predictable and obvious way, but a way that might 

100# not necessarily be how people *want* version numbers to behave. There 

101# wouldn't be a problem if people could stick to purely numeric version 

102# numbers: just split on period and compare the numbers as tuples. 

103# However, people insist on putting letters into their version numbers; 

104# the most common purpose seems to be: 

105# - indicating a "pre-release" version 

106# ('alpha', 'beta', 'a', 'b', 'pre', 'p') 

107# - indicating a post-release patch ('p', 'pl', 'patch') 

108# but of course this can't cover all version number schemes, and there's 

109# no way to know what a programmer means without asking him. 

110# 

111# The problem is what to do with letters (and other non-numeric 

112# characters) in a version number. The current implementation does the 

113# obvious and predictable thing: keep them as strings and compare 

114# lexically within a tuple comparison. This has the desired effect if 

115# an appended letter sequence implies something "post-release": 

116# eg. "0.99" < "0.99pl14" < "1.0", and "5.001" < "5.001m" < "5.002". 

117# 

118# However, if letters in a version number imply a pre-release version, 

119# the "obvious" thing isn't correct. Eg. you would expect that 

120# "1.5.1" < "1.5.2a2" < "1.5.2", but under the tuple/lexical comparison 

121# implemented here, this just isn't so. 

122# 

123# Two possible solutions come to mind. The first is to tie the 

124# comparison algorithm to a particular set of semantic rules, as has 

125# been done in the StrictVersion class above. This works great as long 

126# as everyone can go along with bondage and discipline. Hopefully a 

127# (large) subset of Python module programmers will agree that the 

128# particular flavour of bondage and discipline provided by StrictVersion 

129# provides enough benefit to be worth using, and will submit their 

130# version numbering scheme to its domination. The free-thinking 

131# anarchists in the lot will never give in, though, and something needs 

132# to be done to accommodate them. 

133# 

134# Perhaps a "moderately strict" version class could be implemented that 

135# lets almost anything slide (syntactically), and makes some heuristic 

136# assumptions about non-digits in version number strings. This could 

137# sink into special-case-hell, though; if I was as talented and 

138# idiosyncratic as Larry Wall, I'd go ahead and implement a class that 

139# somehow knows that "1.2.1" < "1.2.2a2" < "1.2.2" < "1.2.2pl3", and is 

140# just as happy dealing with things like "2g6" and "1.13++". I don't 

141# think I'm smart enough to do it right though. 

142# 

143# In any case, I've coded the test suite for this module (see 

144# ../test/test_version.py) specifically to fail on things like comparing 

145# "1.2a2" and "1.2". That's not because the *code* is doing anything 

146# wrong, it's because the simple, obvious design doesn't match my 

147# complicated, hairy expectations for real-world version numbers. It 

148# would be a snap to fix the test suite to say, "Yep, LooseVersion does 

149# the Right Thing" (ie. the code matches the conception). But I'd rather 

150# have a conception that matches common notions about version numbers. 

151 

152class LooseVersion (Version): 

153 

154 """Version numbering for anarchists and software realists. 

155 Implements the standard interface for version number classes as 

156 described above. A version number consists of a series of numbers, 

157 separated by either periods or strings of letters. When comparing 

158 version numbers, the numeric components will be compared 

159 numerically, and the alphabetic components lexically. The following 

160 are all valid version numbers, in no particular order: 

161 

162 1.5.1 

163 1.5.2b2 

164 161 

165 3.10a 

166 8.02 

167 3.4j 

168 1996.07.12 

169 3.2.pl0 

170 3.1.1.6 

171 2g6 

172 11g 

173 0.960923 

174 2.2beta29 

175 1.13++ 

176 5.5.kw 

177 2.0b1pl0 

178 

179 In fact, there is no such thing as an invalid version number under 

180 this scheme; the rules for comparison are simple and predictable, 

181 but may not always give the results you want (for some definition 

182 of "want"). 

183 """ 

184 

185 component_re = re.compile(r'(\d+ | [a-z]+ | \.)', re.VERBOSE) 

186 

187 def __init__ (self, vstring=None): 

188 if vstring: 

189 self.parse(vstring) 

190 

191 

192 def parse (self, vstring): 

193 # I've given up on thinking I can reconstruct the version string 

194 # from the parsed tuple -- so I just store the string here for 

195 # use by __str__ 

196 self.vstring = vstring 

197 components = [x for x in self.component_re.split(vstring) 

198 if x and x != '.'] 

199 for i, obj in enumerate(components): 

200 try: 

201 components[i] = int(obj) 

202 except ValueError: 

203 pass 

204 

205 self.version = components 

206 

207 

208 def __str__ (self): 

209 return self.vstring 

210 

211 

212 def __repr__ (self): 

213 return "LooseVersion ('%s')" % str(self) 

214 

215 

216 def _cmp (self, other): 

217 if isinstance(other, str): 

218 other = LooseVersion(other) 

219 elif not isinstance(other, LooseVersion): 

220 return NotImplemented 

221 

222 if self.version == other.version: 

223 return 0 

224 if self.version < other.version: 

225 return -1 

226 if self.version > other.version: 

227 return 1 

228 

229 

230# end class LooseVersion