OLD | NEW |
| (Empty) |
1 library pub.version; | |
2 import 'dart:math'; | |
3 import 'package:collection/equality.dart'; | |
4 final _START_VERSION = new RegExp( | |
5 r'^' r'(\d+).(\d+).(\d+)' r'(-([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?' | |
6 r'(\+([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?'); | |
7 final _COMPLETE_VERSION = new RegExp("${_START_VERSION.pattern}\$"); | |
8 final _START_COMPARISON = new RegExp(r"^[<>]=?"); | |
9 final _equality = const IterableEquality(); | |
10 class Version implements Comparable<Version>, VersionConstraint { | |
11 static Version get none => new Version(0, 0, 0); | |
12 static int prioritize(Version a, Version b) { | |
13 if (a.isPreRelease && !b.isPreRelease) return -1; | |
14 if (!a.isPreRelease && b.isPreRelease) return 1; | |
15 return a.compareTo(b); | |
16 } | |
17 static int antiPrioritize(Version a, Version b) { | |
18 if (a.isPreRelease && !b.isPreRelease) return -1; | |
19 if (!a.isPreRelease && b.isPreRelease) return 1; | |
20 return b.compareTo(a); | |
21 } | |
22 final int major; | |
23 final int minor; | |
24 final int patch; | |
25 final List preRelease; | |
26 final List build; | |
27 final String _text; | |
28 Version._(this.major, this.minor, this.patch, String preRelease, String build, | |
29 this._text) | |
30 : preRelease = preRelease == null ? [] : _splitParts(preRelease), | |
31 build = build == null ? [] : _splitParts(build) { | |
32 if (major < | |
33 0) throw new ArgumentError('Major version must be non-negative.'); | |
34 if (minor < | |
35 0) throw new ArgumentError('Minor version must be non-negative.'); | |
36 if (patch < | |
37 0) throw new ArgumentError('Patch version must be non-negative.'); | |
38 } | |
39 factory Version(int major, int minor, int patch, {String pre, String build}) { | |
40 var text = "$major.$minor.$patch"; | |
41 if (pre != null) text += "-$pre"; | |
42 if (build != null) text += "+$build"; | |
43 return new Version._(major, minor, patch, pre, build, text); | |
44 } | |
45 factory Version.parse(String text) { | |
46 final match = _COMPLETE_VERSION.firstMatch(text); | |
47 if (match == null) { | |
48 throw new FormatException('Could not parse "$text".'); | |
49 } | |
50 try { | |
51 int major = int.parse(match[1]); | |
52 int minor = int.parse(match[2]); | |
53 int patch = int.parse(match[3]); | |
54 String preRelease = match[5]; | |
55 String build = match[8]; | |
56 return new Version._(major, minor, patch, preRelease, build, text); | |
57 } on FormatException catch (ex) { | |
58 throw new FormatException('Could not parse "$text".'); | |
59 } | |
60 } | |
61 static Version primary(List<Version> versions) { | |
62 var primary; | |
63 for (var version in versions) { | |
64 if (primary == null || | |
65 (!version.isPreRelease && primary.isPreRelease) || | |
66 (version.isPreRelease == primary.isPreRelease && version > primary)) { | |
67 primary = version; | |
68 } | |
69 } | |
70 return primary; | |
71 } | |
72 static List _splitParts(String text) { | |
73 return text.split('.').map((part) { | |
74 try { | |
75 return int.parse(part); | |
76 } on FormatException catch (ex) { | |
77 return part; | |
78 } | |
79 }).toList(); | |
80 } | |
81 bool operator ==(other) { | |
82 if (other is! Version) return false; | |
83 return major == other.major && | |
84 minor == other.minor && | |
85 patch == other.patch && | |
86 _equality.equals(preRelease, other.preRelease) && | |
87 _equality.equals(build, other.build); | |
88 } | |
89 int get hashCode => | |
90 major ^ minor ^ patch ^ _equality.hash(preRelease) ^ _equality.hash(build)
; | |
91 bool operator <(Version other) => compareTo(other) < 0; | |
92 bool operator >(Version other) => compareTo(other) > 0; | |
93 bool operator <=(Version other) => compareTo(other) <= 0; | |
94 bool operator >=(Version other) => compareTo(other) >= 0; | |
95 bool get isAny => false; | |
96 bool get isEmpty => false; | |
97 bool get isPreRelease => preRelease.isNotEmpty; | |
98 Version get nextMajor { | |
99 if (isPreRelease && minor == 0 && patch == 0) { | |
100 return new Version(major, minor, patch); | |
101 } | |
102 return new Version(major + 1, 0, 0); | |
103 } | |
104 Version get nextMinor { | |
105 if (isPreRelease && patch == 0) { | |
106 return new Version(major, minor, patch); | |
107 } | |
108 return new Version(major, minor + 1, 0); | |
109 } | |
110 Version get nextPatch { | |
111 if (isPreRelease) { | |
112 return new Version(major, minor, patch); | |
113 } | |
114 return new Version(major, minor, patch + 1); | |
115 } | |
116 bool allows(Version other) => this == other; | |
117 VersionConstraint intersect(VersionConstraint other) { | |
118 if (other.isEmpty) return other; | |
119 if (other is VersionRange) return other.intersect(this); | |
120 if (other is Version) { | |
121 return this == other ? this : VersionConstraint.empty; | |
122 } | |
123 throw new ArgumentError('Unknown VersionConstraint type $other.'); | |
124 } | |
125 int compareTo(Version other) { | |
126 if (major != other.major) return major.compareTo(other.major); | |
127 if (minor != other.minor) return minor.compareTo(other.minor); | |
128 if (patch != other.patch) return patch.compareTo(other.patch); | |
129 if (!isPreRelease && other.isPreRelease) return 1; | |
130 if (!other.isPreRelease && isPreRelease) return -1; | |
131 var comparison = _compareLists(preRelease, other.preRelease); | |
132 if (comparison != 0) return comparison; | |
133 if (build.isEmpty && other.build.isNotEmpty) return -1; | |
134 if (other.build.isEmpty && build.isNotEmpty) return 1; | |
135 return _compareLists(build, other.build); | |
136 } | |
137 String toString() => _text; | |
138 int _compareLists(List a, List b) { | |
139 for (var i = 0; i < max(a.length, b.length); i++) { | |
140 var aPart = (i < a.length) ? a[i] : null; | |
141 var bPart = (i < b.length) ? b[i] : null; | |
142 if (aPart == bPart) continue; | |
143 if (aPart == null) return -1; | |
144 if (bPart == null) return 1; | |
145 if (aPart is num) { | |
146 if (bPart is num) { | |
147 return aPart.compareTo(bPart); | |
148 } else { | |
149 return -1; | |
150 } | |
151 } else { | |
152 if (bPart is num) { | |
153 return 1; | |
154 } else { | |
155 return aPart.compareTo(bPart); | |
156 } | |
157 } | |
158 } | |
159 return 0; | |
160 } | |
161 } | |
162 abstract class VersionConstraint { | |
163 static VersionConstraint any = new VersionRange(); | |
164 static VersionConstraint empty = const _EmptyVersion(); | |
165 factory VersionConstraint.parse(String text) { | |
166 if (text.trim() == "any") return new VersionRange(); | |
167 var originalText = text; | |
168 var constraints = <VersionConstraint>[]; | |
169 void skipWhitespace() { | |
170 text = text.trim(); | |
171 } | |
172 Version matchVersion() { | |
173 var version = _START_VERSION.firstMatch(text); | |
174 if (version == null) return null; | |
175 text = text.substring(version.end); | |
176 return new Version.parse(version[0]); | |
177 } | |
178 VersionConstraint matchComparison() { | |
179 var comparison = _START_COMPARISON.firstMatch(text); | |
180 if (comparison == null) return null; | |
181 var op = comparison[0]; | |
182 text = text.substring(comparison.end); | |
183 skipWhitespace(); | |
184 var version = matchVersion(); | |
185 if (version == null) { | |
186 throw new FormatException( | |
187 'Expected version number after "$op" in ' '"$originalText", got "$te
xt".'); | |
188 } | |
189 switch (op) { | |
190 case '<=': | |
191 return new VersionRange(max: version, includeMax: true); | |
192 case '<': | |
193 return new VersionRange(max: version, includeMax: false); | |
194 case '>=': | |
195 return new VersionRange(min: version, includeMin: true); | |
196 case '>': | |
197 return new VersionRange(min: version, includeMin: false); | |
198 } | |
199 throw "Unreachable."; | |
200 } | |
201 while (true) { | |
202 skipWhitespace(); | |
203 if (text.isEmpty) break; | |
204 var version = matchVersion(); | |
205 if (version != null) { | |
206 constraints.add(version); | |
207 continue; | |
208 } | |
209 var comparison = matchComparison(); | |
210 if (comparison != null) { | |
211 constraints.add(comparison); | |
212 continue; | |
213 } | |
214 throw new FormatException( | |
215 'Could not parse version "$originalText". ' 'Unknown text at "$text".'
); | |
216 } | |
217 if (constraints.isEmpty) { | |
218 throw new FormatException('Cannot parse an empty string.'); | |
219 } | |
220 return new VersionConstraint.intersection(constraints); | |
221 } | |
222 factory | |
223 VersionConstraint.intersection(Iterable<VersionConstraint> constraints) { | |
224 var constraint = new VersionRange(); | |
225 for (var other in constraints) { | |
226 constraint = constraint.intersect(other); | |
227 } | |
228 return constraint; | |
229 } | |
230 bool get isEmpty; | |
231 bool get isAny; | |
232 bool allows(Version version); | |
233 VersionConstraint intersect(VersionConstraint other); | |
234 } | |
235 class VersionRange implements VersionConstraint { | |
236 final Version min; | |
237 final Version max; | |
238 final bool includeMin; | |
239 final bool includeMax; | |
240 VersionRange({this.min, this.max, this.includeMin: false, this.includeMax: | |
241 false}) { | |
242 if (min != null && max != null && min > max) { | |
243 throw new ArgumentError( | |
244 'Minimum version ("$min") must be less than maximum ("$max").'); | |
245 } | |
246 } | |
247 bool operator ==(other) { | |
248 if (other is! VersionRange) return false; | |
249 return min == other.min && | |
250 max == other.max && | |
251 includeMin == other.includeMin && | |
252 includeMax == other.includeMax; | |
253 } | |
254 bool get isEmpty => false; | |
255 bool get isAny => min == null && max == null; | |
256 bool allows(Version other) { | |
257 if (min != null) { | |
258 if (other < min) return false; | |
259 if (!includeMin && other == min) return false; | |
260 } | |
261 if (max != null) { | |
262 if (other > max) return false; | |
263 if (!includeMax && other == max) return false; | |
264 if (!includeMax && | |
265 !max.isPreRelease && | |
266 other.isPreRelease && | |
267 other.major == max.major && | |
268 other.minor == max.minor && | |
269 other.patch == max.patch) { | |
270 return false; | |
271 } | |
272 } | |
273 return true; | |
274 } | |
275 VersionConstraint intersect(VersionConstraint other) { | |
276 if (other.isEmpty) return other; | |
277 if (other is Version) { | |
278 return allows(other) ? other : VersionConstraint.empty; | |
279 } | |
280 if (other is VersionRange) { | |
281 var intersectMin = min; | |
282 var intersectIncludeMin = includeMin; | |
283 var intersectMax = max; | |
284 var intersectIncludeMax = includeMax; | |
285 if (other.min == | |
286 null) {} else if (intersectMin == null || intersectMin < other.min) { | |
287 intersectMin = other.min; | |
288 intersectIncludeMin = other.includeMin; | |
289 } else if (intersectMin == other.min && !other.includeMin) { | |
290 intersectIncludeMin = false; | |
291 } | |
292 if (other.max == | |
293 null) {} else if (intersectMax == null || intersectMax > other.max) { | |
294 intersectMax = other.max; | |
295 intersectIncludeMax = other.includeMax; | |
296 } else if (intersectMax == other.max && !other.includeMax) { | |
297 intersectIncludeMax = false; | |
298 } | |
299 if (intersectMin == null && intersectMax == null) { | |
300 return new VersionRange(); | |
301 } | |
302 if (intersectMin == intersectMax) { | |
303 if (intersectIncludeMin && intersectIncludeMax) return intersectMin; | |
304 return VersionConstraint.empty; | |
305 } | |
306 if (intersectMin != null && | |
307 intersectMax != null && | |
308 intersectMin > intersectMax) { | |
309 return VersionConstraint.empty; | |
310 } | |
311 return new VersionRange( | |
312 min: intersectMin, | |
313 max: intersectMax, | |
314 includeMin: intersectIncludeMin, | |
315 includeMax: intersectIncludeMax); | |
316 } | |
317 throw new ArgumentError('Unknown VersionConstraint type $other.'); | |
318 } | |
319 String toString() { | |
320 var buffer = new StringBuffer(); | |
321 if (min != null) { | |
322 buffer.write(includeMin ? '>=' : '>'); | |
323 buffer.write(min); | |
324 } | |
325 if (max != null) { | |
326 if (min != null) buffer.write(' '); | |
327 buffer.write(includeMax ? '<=' : '<'); | |
328 buffer.write(max); | |
329 } | |
330 if (min == null && max == null) buffer.write('any'); | |
331 return buffer.toString(); | |
332 } | |
333 } | |
334 class _EmptyVersion implements VersionConstraint { | |
335 const _EmptyVersion(); | |
336 bool get isEmpty => true; | |
337 bool get isAny => false; | |
338 bool allows(Version other) => false; | |
339 VersionConstraint intersect(VersionConstraint other) => this; | |
340 String toString() => '<empty>'; | |
341 } | |
OLD | NEW |