1
1
from __future__ import annotations
2
2
3
- from dataclasses import dataclass
4
3
from functools import total_ordering
5
4
import re
6
- from typing import List , Optional , Protocol , runtime_checkable , Union
7
-
8
-
9
- _MODDED_PATTERN = "[0-9a-f]+-modded"
10
-
5
+ from typing import List , Union
11
6
12
7
@total_ordering
13
- @dataclass
14
8
class NodeVersion :
15
9
"""NodeVersion
16
10
@@ -30,195 +24,46 @@ class NodeVersion:
30
24
- `v23.11` < `v24.02`
31
25
The oldest version is the smallest
32
26
"""
33
-
34
- version : str
35
-
36
- def to_parts (self ) -> List [_NodeVersionPart ]:
37
- parts = self .version [1 :].split ("." )
38
- # If the first part contains a v we will ignore it
39
- if not parts [0 ][0 ].isdigit ():
40
- parts [0 ] = parts [1 :]
41
-
42
- return [_NodeVersionPart .parse (p ) for p in parts ]
43
-
44
- def strict_equal (self , other : NodeVersion ) -> bool :
45
- if not isinstance (other , NodeVersion ):
46
- raise TypeError (
47
- "`other` is expected to be of type `NodeVersion` but is `{type(other)}`"
48
- )
49
- else :
50
- return self .version == other .version
27
+ def __init__ (self , version : str ):
28
+ # e.g. v24.11-225-gda793e66b9
29
+ if version .startswith ('v' ):
30
+ version = version [1 :]
31
+ version = version .split ('-' )[0 ]
32
+ parts = version .split ('.' )
33
+ # rc is considered "close enough"
34
+ if 'rc' in parts [- 1 ]:
35
+ parts [- 1 ] = parts [- 1 ].split ('rc' )[0 ]
36
+
37
+ self .parts : int = []
38
+ for p in parts :
39
+ self .parts .append (int (p ))
51
40
52
41
def __eq__ (self , other : Union [NodeVersion , str ]) -> bool :
53
42
if isinstance (other , str ):
54
43
other = NodeVersion (other )
55
44
if not isinstance (other , NodeVersion ):
56
45
return False
57
46
58
- if self .strict_equal (other ):
59
- return True
60
- elif re .match (_MODDED_PATTERN , self .version ):
47
+ if len (self .parts ) != len (other .parts ):
61
48
return False
62
- else :
63
- self_parts = [p .num for p in self .to_parts ()]
64
- other_parts = [p .num for p in other .to_parts ()]
65
-
66
- if len (self_parts ) != len (other_parts ):
49
+ for a , b in zip (self .parts , other .parts ):
50
+ if a != b :
67
51
return False
68
-
69
- for ps , po in zip (self_parts , other_parts ):
70
- if ps != po :
71
- return False
72
- return True
52
+ return True
73
53
74
54
def __lt__ (self , other : Union [NodeVersion , str ]) -> bool :
75
55
if isinstance (other , str ):
76
56
other = NodeVersion (other )
77
57
if not isinstance (other , NodeVersion ):
78
58
return NotImplemented
79
59
80
- # If we are in CI the version will by a hex ending on modded
81
- # We will assume it is the latest version
82
- if re .match (_MODDED_PATTERN , self .version ):
83
- return False
84
- elif re .match (_MODDED_PATTERN , other .version ):
85
- return True
86
- else :
87
- self_parts = [p .num for p in self .to_parts ()]
88
- other_parts = [p .num for p in other .to_parts ()]
89
-
90
- # zip truncates to shortes length
91
- for sp , op in zip (self_parts , other_parts ):
92
- if sp < op :
93
- return True
94
- if sp > op :
95
- return False
96
-
97
- # If the initial parts are all equal the longest version is the biggest
98
- #
99
- # self = 'v24.02'
100
- # other = 'v24.02.1'
101
- return len (self_parts ) < len (other_parts )
102
-
103
- def matches (self , version_spec : VersionSpecLike ) -> bool :
104
- """Returns True if the version matches the spec
105
-
106
- The `version_spec` can be represented as a string and has 8 operators
107
- which are `=`, `===`, `!=`, `!===`, `<`, `<=`, `>`, `>=`.
108
-
109
- The `=` is the equality operator. The verson_spec `=v24.02` matches
110
- all versions that equal `v24.02` including release candidates such as `v24.02rc1`.
111
- You can use the strict-equality operator `===` if strict equality is required.
112
-
113
- Specifiers can be combined by separating the with a comma ','. The `version_spec`
114
- `>=v23.11, <v24.02" includes any version which is greater than or equal to `v23.11`
115
- and smaller than `v24.02`.
116
- """
117
- spec = VersionSpec .parse (version_spec )
118
- return spec .matches (self )
119
-
120
-
121
- @dataclass
122
- class _NodeVersionPart :
123
- num : int
124
- text : Optional [str ] = None
125
-
126
- @classmethod
127
- def parse (cls , part : str ) -> _NodeVersionPart :
128
- # We assume all parts start with a number and are followed by a text
129
- # E.g: v24.01rc2 has two parts
130
- # - "24" -> num = 24, text = None
131
- # - "01rc" -> num = 01, text = "rc"
132
-
133
- number = re .search (r"\d+" , part ).group ()
134
- text = part [len (number ):]
135
- text_opt = text if text != "" else None
136
- return _NodeVersionPart (int (number ), text_opt )
137
-
138
-
139
- @runtime_checkable
140
- class VersionSpec (Protocol ):
141
- def matches (self , other : NodeVersionLike ) -> bool :
142
- ...
143
-
144
- @classmethod
145
- def parse (cls , spec : VersionSpecLike ) -> VersionSpec :
146
- if isinstance (spec , VersionSpec ):
147
- return spec
148
- else :
149
- parts = [p .strip () for p in spec .split ("," )]
150
- subspecs = [_CompareSpec .parse (p ) for p in parts ]
151
- return _AndVersionSpecifier (subspecs )
152
-
153
-
154
- @dataclass
155
- class _AndVersionSpecifier (VersionSpec ):
156
- specs : List [VersionSpec ]
157
-
158
- def matches (self , other : NodeVersionLike ) -> bool :
159
- for spec in self .specs :
160
- if not spec .matches (other ):
60
+ # We want a zero-padded zip. Pad both to make one.
61
+ totlen = max (len (self .parts ), len (other .parts ))
62
+ for a , b in zip (self .parts + [0 ] * totlen , other .parts + [0 ] * totlen ):
63
+ if a < b :
64
+ return True
65
+ if a < b :
161
66
return False
162
- return True
163
-
164
-
165
- _OPERATORS = [
166
- "===" , # Strictly equal
167
- "!===" , # not strictly equal
168
- "=" , # Equal
169
- ">=" , # Greater or equal
170
- "<=" , # Less or equal
171
- "<" , # less
172
- ">" , # greater than
173
- "!=" , # not equal
174
- ]
175
-
176
-
177
- @dataclass
178
- class _CompareSpec (VersionSpec ):
179
- operator : str
180
- version : NodeVersion
181
-
182
- def __post_init__ (self ):
183
- if self .operator not in _OPERATORS :
184
- raise ValueError (f"Invalid operator '{ self .operator } '" )
185
-
186
- def matches (self , other : NodeVersionLike ):
187
- if isinstance (other , str ):
188
- other = NodeVersion (other )
189
- if self .operator == "===" :
190
- return other .strict_equal (self .version )
191
- if self .operator == "!===" :
192
- return not other .strict_equal (self .version )
193
- if self .operator == "=" :
194
- return other == self .version
195
- if self .operator == ">=" :
196
- return other >= self .version
197
- if self .operator == "<=" :
198
- return other <= self .version
199
- if self .operator == "<" :
200
- return other < self .version
201
- if self .operator == ">" :
202
- return other > self .version
203
- if self .operator == "!=" :
204
- return other != self .version
205
- else :
206
- ValueError ("Unknown operator" )
207
-
208
- @classmethod
209
- def parse (cls , spec_string : str ) -> _CompareSpec :
210
- spec_string = spec_string .strip ()
211
-
212
- for op in _OPERATORS :
213
- if spec_string .startswith (op ):
214
- version = spec_string [len (op ):]
215
- version = version .strip ()
216
- return _CompareSpec (op , NodeVersion (version ))
217
-
218
- raise ValueError (f"Failed to parse '{ spec_string } '" )
219
-
220
-
221
- NodeVersionLike = Union [NodeVersion , str ]
222
- VersionSpecLike = Union [VersionSpec , str ]
67
+ return False
223
68
224
- __all__ = [NodeVersion , NodeVersionLike , VersionSpec , VersionSpecLike ]
69
+ __all__ = [NodeVersion ]
0 commit comments