Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(883)

Side by Side Diff: utils/pub/version.dart

Issue 10540151: First pass at version constraint solver. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 8 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 /** 5 /**
6 * Handles version numbers, following the [Semantic Versioning][semver] spec. 6 * Handles version numbers, following the [Semantic Versioning][semver] spec.
7 * 7 *
8 * [semver]: http://semver.org/ 8 * [semver]: http://semver.org/
9 */ 9 */
10 #library('version'); 10 #library('version');
11 11
12 #import('utils.dart'); 12 #import('utils.dart');
13 13
14 /** A parsed semantic version number. */ 14 /** A parsed semantic version number. */
15 class Version implements Comparable, Hashable, VersionConstraint { 15 class Version implements Comparable, Hashable, VersionConstraint {
16 static get none() => new Version(0, 0, 0); 16 /** No released version: i.e. "0.0.0". */
17 static Version get none() => new Version(0, 0, 0);
17 18
18 static final _PARSE_REGEX = const RegExp( 19 static final _PARSE_REGEX = const RegExp(
19 @'^' // Start at beginning. 20 @'^' // Start at beginning.
20 @'(\d+).(\d+).(\d+)' // Version number. 21 @'(\d+).(\d+).(\d+)' // Version number.
21 @'(-([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?' // Pre-release. 22 @'(-([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?' // Pre-release.
22 @'(\+([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?' // Build. 23 @'(\+([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?' // Build.
23 @'$'); // Consume entire string. 24 @'$'); // Consume entire string.
24 25
25 /** The major version number: "1" in "1.2.3". */ 26 /** The major version number: "1" in "1.2.3". */
26 final int major; 27 final int major;
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
64 65
65 String preRelease = match[5]; 66 String preRelease = match[5];
66 String build = match[8]; 67 String build = match[8];
67 68
68 return new Version(major, minor, patch, preRelease, build); 69 return new Version(major, minor, patch, preRelease, build);
69 } catch (BadNumberFormatException ex) { 70 } catch (BadNumberFormatException ex) {
70 throw new FormatException('Could not parse "$text".'); 71 throw new FormatException('Could not parse "$text".');
71 } 72 }
72 } 73 }
73 74
74 bool operator ==(Version other) { 75 bool operator ==(other) {
75 if (other is! Version) return false; 76 if (other is! Version) return false;
76 return compareTo(other) == 0; 77 return compareTo(other) == 0;
77 } 78 }
78 79
79 bool operator <(Version other) => compareTo(other) < 0; 80 bool operator <(Version other) => compareTo(other) < 0;
80 bool operator >(Version other) => compareTo(other) > 0; 81 bool operator >(Version other) => compareTo(other) > 0;
81 bool operator <=(Version other) => compareTo(other) <= 0; 82 bool operator <=(Version other) => compareTo(other) <= 0;
82 bool operator >=(Version other) => compareTo(other) >= 0; 83 bool operator >=(Version other) => compareTo(other) >= 0;
83 84
85 bool get isEmpty() => false;
86
84 /** Tests if [other] matches this version exactly. */ 87 /** Tests if [other] matches this version exactly. */
85 bool allows(Version other) => this == other; 88 bool allows(Version other) => this == other;
86 89
90 VersionConstraint intersect(VersionConstraint other) {
91 if (other.isEmpty) return other;
92
93 // Intersect a version and a range.
94 if (other is VersionRange) return other.intersect(this);
95
96 // Intersecting two versions only works if they are the same.
97 if (other is Version) return this == other ? this : _emptyVersion;
98
99 throw new IllegalArgumentException(
100 'Unknown VersionConstraint type $other.');
101 }
102
87 int compareTo(Version other) { 103 int compareTo(Version other) {
88 if (major != other.major) return major.compareTo(other.major); 104 if (major != other.major) return major.compareTo(other.major);
89 if (minor != other.minor) return minor.compareTo(other.minor); 105 if (minor != other.minor) return minor.compareTo(other.minor);
90 if (patch != other.patch) return patch.compareTo(other.patch); 106 if (patch != other.patch) return patch.compareTo(other.patch);
91 107
92 if (preRelease != other.preRelease) { 108 if (preRelease != other.preRelease) {
93 // Pre-releases always come before no pre-release string. 109 // Pre-releases always come before no pre-release string.
94 if (preRelease == null) return 1; 110 if (preRelease == null) return 1;
95 if (other.preRelease == null) return -1; 111 if (other.preRelease == null) return -1;
96 112
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after
172 }); 188 });
173 } 189 }
174 } 190 }
175 191
176 /** 192 /**
177 * A [VersionConstraint] is a predicate that can determine whether a given 193 * A [VersionConstraint] is a predicate that can determine whether a given
178 * version is valid or not. For example, a ">= 2.0.0" constraint allows any 194 * version is valid or not. For example, a ">= 2.0.0" constraint allows any
179 * version that is "2.0.0" or greater. Version objects themselves implement 195 * version that is "2.0.0" or greater. Version objects themselves implement
180 * this to match a specific version. 196 * this to match a specific version.
181 */ 197 */
182 interface VersionConstraint { 198 interface VersionConstraint default _VersionConstraintFactory {
199 /**
200 * A [VersionConstraint] that allows no versions: i.e. the empty set.
201 */
202 VersionConstraint.empty();
203
204 /**
205 * Parses a version constraint. This string is a space-separated series of
206 * version parts. Each part can be one of:
207 *
208 * * A version string like `1.2.3`. In other words, anything that can be
209 * parsed by [Version.parse()].
210 * * A comparison operator (`<`, `>`, `<=`, or `>=`) followed by a version
211 * string. There cannot be a space between the operator and the version.
212 *
213 * Examples:
214 *
215 * 1.2.3-alpha
216 * <=5.1.4
217 * >2.0.4 <=2.4.6
218 */
219 VersionConstraint.parse(String text);
220
221 /**
222 * Creates a new version constraint that is the intersection of [constraints].
223 * It will only allow versions that all of those constraints allow. If
224 * constraints is empty, then it returns a VersionConstraint that allows all
225 * versions.
226 */
227 VersionConstraint.intersect(Collection<VersionConstraint> constraints);
228
229 /**
230 * Returns `true` if this constraint allows no versions.
231 */
232 bool get isEmpty();
233
234 /**
235 * Returns `true` if this constraint allows [version].
236 */
183 bool allows(Version version); 237 bool allows(Version version);
238
239 /**
240 * Creates a new [VersionConstraint] that only allows [Version]s allowed by
241 * both this and [other].
242 */
243 VersionConstraint intersect(VersionConstraint other);
184 } 244 }
185 245
186 /** 246 /**
187 * Constrains versions to a fall within a given range. If there is a minimum, 247 * Constrains versions to a fall within a given range. If there is a minimum,
188 * then this only allows versions that are at that minimum or greater. If there 248 * then this only allows versions that are at that minimum or greater. If there
189 * is a maximum, then only versions less than that are allowed. In other words, 249 * is a maximum, then only versions less than that are allowed. In other words,
190 * this allows `>= min, < max`. 250 * this allows `>= min, < max`.
191 */ 251 */
192 class VersionRange implements VersionConstraint { 252 class VersionRange implements VersionConstraint {
193 final Version min; 253 final Version min;
194 final Version max; 254 final Version max;
255 final bool includeMin;
256 final bool includeMax;
195 257
196 VersionRange([this.min, this.max]) { 258 VersionRange([this.min, this.max,
259 this.includeMin = false, this.includeMax = false]) {
197 if (min != null && max != null && min > max) { 260 if (min != null && max != null && min > max) {
198 throw new IllegalArgumentException( 261 throw new IllegalArgumentException(
199 'Maximum version ("$max") must be less than minimum ("$min").'); 262 'Minimum version ("$min") must be less than maximum ("$max").');
200 } 263 }
201 } 264 }
202 265
266 bool operator ==(other) {
267 if (other is! VersionRange) return false;
268
269 return min == other.min &&
270 max == other.max &&
271 includeMin == other.includeMin &&
272 includeMax == other.includeMax;
273 }
274
275 bool get isEmpty() => false;
276
203 /** Tests if [other] matches falls within this version range. */ 277 /** Tests if [other] matches falls within this version range. */
204 bool allows(Version other) { 278 bool allows(Version other) {
205 if (min != null && other < min) return false; 279 if (min != null && other < min) return false;
206 if (max != null && other >= max) return false; 280 if (min != null && !includeMin && other == min) return false;
281 if (max != null && other > max) return false;
282 if (max != null && !includeMax && other == max) return false;
207 return true; 283 return true;
208 } 284 }
285
286 VersionConstraint intersect(VersionConstraint other) {
287 if (other.isEmpty) return other;
288
289 // A range and a Version just yields the version if it's in the range.
290 if (other is Version) return allows(other) ? other : _emptyVersion;
291
292 if (other is VersionRange) {
293 // Intersect the two ranges.
294 var intersectMin = min;
295 var intersectIncludeMin = includeMin;
296 var intersectMax = max;
297 var intersectIncludeMax = includeMax;
298
299 if (other.min == null) {
300 // Do nothing.
301 } else if (intersectMin == null || intersectMin < other.min) {
302 intersectMin = other.min;
303 intersectIncludeMin = other.includeMin;
304 } else if (intersectMin == other.min && !other.includeMin) {
305 // The edges are the same, but one is exclusive, make it exclusive.
306 intersectIncludeMin = false;
307 }
308
309 if (other.max == null) {
310 // Do nothing.
311 } else if (intersectMax == null || intersectMax > other.max) {
312 intersectMax = other.max;
313 intersectIncludeMax = other.includeMax;
314 } else if (intersectMax == other.max && !other.includeMax) {
315 // The edges are the same, but one is exclusive, make it exclusive.
316 intersectIncludeMax = false;
317 }
318
319 if (intersectMin == null && intersectMax == null) {
320 // Open range.
321 return new VersionRange();
322 }
323
324 // If the range is just a single version.
nweiz 2012/06/18 18:29:19 There are other cases where it could just be a sin
Bob Nystrom 2012/06/20 01:40:04 That can still have other versions, like: 0.0.1+ho
nweiz 2012/06/20 21:08:48 Good point.
325 if (intersectMin == intersectMax) {
326 // If both ends are inclusive, allow that version.
327 if (intersectIncludeMin && intersectIncludeMax) return intersectMin;
328
329 // Otherwise, no versions.
330 return _emptyVersion;
331 }
332
333 if (intersectMin != null && intersectMax != null &&
334 intersectMin > intersectMax) {
335 // Non-overlapping ranges, so empty.
336 return _emptyVersion;
337 }
338
339 // If we got here, there is an actual range.
340 return new VersionRange(intersectMin, intersectMax,
341 intersectIncludeMin, intersectIncludeMax);
342 }
343
344 throw new IllegalArgumentException(
345 'Unknown VersionConstraint type $other.');
346 }
347
348 String toString() {
349 var buffer = new StringBuffer();
350
351 if (min != null) {
352 buffer.add(includeMin ? '>=' : '>');
353 buffer.add(min);
354 }
355
356 if (max != null) {
357 if (min != null) buffer.add(' ');
358 buffer.add(includeMax ? '<=' : '<');
359 buffer.add(max);
360 }
361
362 if (min == null && max == null) buffer.add('any');
nweiz 2012/06/18 18:29:19 We should only print this if we parse "any" as all
Bob Nystrom 2012/06/20 01:40:04 Done. Added parse support for it. That should make
363 return buffer.toString();
364 }
209 } 365 }
366
367 class _EmptyVersion implements VersionConstraint {
368 const _EmptyVersion();
369
370 bool get isEmpty() => true;
371 bool allows(Version other) => false;
372 VersionConstraint intersect(VersionConstraint other) => this;
373 String toString() => '<empty>';
374 }
375
376 final _emptyVersion = const _EmptyVersion();
nweiz 2012/06/18 18:29:19 Why is this necessary? Can't const automatically c
Bob Nystrom 2012/06/20 01:40:04 Done.
377
378 class _VersionConstraintFactory {
379 factory VersionConstraint.empty() => _emptyVersion;
380
381 factory VersionConstraint.parse(String text) {
382 if (text.trim() == '') {
383 throw new FormatException('Cannot parse an empty string.');
384 }
385
386 // Split it into space-separated parts.
387 var constraints = <VersionConstraint>[];
388 for (var part in text.split(' ')) {
389 constraints.add(parseSingleConstraint(part));
390 }
391
392 return new VersionConstraint.intersect(constraints);
393 }
394
395 factory VersionConstraint.intersect(Collection<VersionConstraint> constraints) {
nweiz 2012/06/18 18:29:19 Line length.
Bob Nystrom 2012/06/20 01:40:04 Done.
396 var constraint = new VersionRange();
397 for (var other in constraints) {
398 constraint = constraint.intersect(other);
399 }
400 return constraint;
401 }
402
403 static VersionConstraint parseSingleConstraint(String text) {
404 // TODO(rnystrom): Consider other syntaxes for version constraints. This
405 // one is whitespace sensitive (you can't do "< 1.2.3") and "<" is
nweiz 2012/06/18 18:29:19 I'm coming around on quoting constraints in genera
Bob Nystrom 2012/06/20 01:40:04 Agreed, this is too finicky, but is it OK to be si
nweiz 2012/06/20 21:08:48 Yes, definitely.
406 // unfortunately meaningful in YAML, requiring it to be quoted in a
407 // pubspec.
408 if (text.startsWith('<=')) {
nweiz 2012/06/18 18:29:19 I think you could save some repetition here by par
Bob Nystrom 2012/06/20 01:40:04 Done. Good call.
409 // A maximum version (inclusive).
410 var version = new Version.parse(text.substring('<='.length));
411 return new VersionRange(max: version, includeMax: true);
412 }
413
414 if (text.startsWith('<')) {
415 // A maximum version (exclusive).
416 var version = new Version.parse(text.substring('<'.length));
417 return new VersionRange(max: version, includeMax: false);
418 }
419
420 if (text.startsWith('>=')) {
421 // A minimum version (inclusive).
422 var version = new Version.parse(text.substring('>='.length));
423 return new VersionRange(min: version, includeMin: true);
424 }
425
426 if (text.startsWith('>')) {
427 // A minimum version (exclusive).
428 var version = new Version.parse(text.substring('>'.length));
429 return new VersionRange(min: version, includeMin: false);
430 }
431
432 // Otherwise, it must be an explicit version.
433 return new Version.parse(text);
434 }
435 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698