Chromium Code Reviews| Index: utils/pub/version.dart |
| diff --git a/utils/pub/version.dart b/utils/pub/version.dart |
| index ee5aca0cc461c22d4feddf28a8214299bf8bf124..114fa0c3c04a314bdb8374fc72e07f4e2519e244 100644 |
| --- a/utils/pub/version.dart |
| +++ b/utils/pub/version.dart |
| @@ -13,7 +13,8 @@ |
| /** A parsed semantic version number. */ |
| class Version implements Comparable, Hashable, VersionConstraint { |
| - static get none() => new Version(0, 0, 0); |
| + /** No released version: i.e. "0.0.0". */ |
| + static Version get none() => new Version(0, 0, 0); |
| static final _PARSE_REGEX = const RegExp( |
| @'^' // Start at beginning. |
| @@ -71,7 +72,7 @@ class Version implements Comparable, Hashable, VersionConstraint { |
| } |
| } |
| - bool operator ==(Version other) { |
| + bool operator ==(other) { |
| if (other is! Version) return false; |
| return compareTo(other) == 0; |
| } |
| @@ -81,9 +82,24 @@ class Version implements Comparable, Hashable, VersionConstraint { |
| bool operator <=(Version other) => compareTo(other) <= 0; |
| bool operator >=(Version other) => compareTo(other) >= 0; |
| + bool get isEmpty() => false; |
| + |
| /** Tests if [other] matches this version exactly. */ |
| bool allows(Version other) => this == other; |
| + VersionConstraint intersect(VersionConstraint other) { |
| + if (other.isEmpty) return other; |
| + |
| + // Intersect a version and a range. |
| + if (other is VersionRange) return other.intersect(this); |
| + |
| + // Intersecting two versions only works if they are the same. |
| + if (other is Version) return this == other ? this : _emptyVersion; |
| + |
| + throw new IllegalArgumentException( |
| + 'Unknown VersionConstraint type $other.'); |
| + } |
| + |
| int compareTo(Version other) { |
| if (major != other.major) return major.compareTo(other.major); |
| if (minor != other.minor) return minor.compareTo(other.minor); |
| @@ -179,8 +195,52 @@ class Version implements Comparable, Hashable, VersionConstraint { |
| * version that is "2.0.0" or greater. Version objects themselves implement |
| * this to match a specific version. |
| */ |
| -interface VersionConstraint { |
| +interface VersionConstraint default _VersionConstraintFactory { |
| + /** |
| + * A [VersionConstraint] that allows no versions: i.e. the empty set. |
| + */ |
| + VersionConstraint.empty(); |
| + |
| + /** |
| + * Parses a version constraint. This string is a space-separated series of |
| + * version parts. Each part can be one of: |
| + * |
| + * * A version string like `1.2.3`. In other words, anything that can be |
| + * parsed by [Version.parse()]. |
| + * * A comparison operator (`<`, `>`, `<=`, or `>=`) followed by a version |
| + * string. There cannot be a space between the operator and the version. |
| + * |
| + * Examples: |
| + * |
| + * 1.2.3-alpha |
| + * <=5.1.4 |
| + * >2.0.4 <=2.4.6 |
| + */ |
| + VersionConstraint.parse(String text); |
| + |
| + /** |
| + * Creates a new version constraint that is the intersection of [constraints]. |
| + * It will only allow versions that all of those constraints allow. If |
| + * constraints is empty, then it returns a VersionConstraint that allows all |
| + * versions. |
| + */ |
| + VersionConstraint.intersect(Collection<VersionConstraint> constraints); |
| + |
| + /** |
| + * Returns `true` if this constraint allows no versions. |
| + */ |
| + bool get isEmpty(); |
| + |
| + /** |
| + * Returns `true` if this constraint allows [version]. |
| + */ |
| bool allows(Version version); |
| + |
| + /** |
| + * Creates a new [VersionConstraint] that only allows [Version]s allowed by |
| + * both this and [other]. |
| + */ |
| + VersionConstraint intersect(VersionConstraint other); |
| } |
| /** |
| @@ -192,18 +252,184 @@ interface VersionConstraint { |
| class VersionRange implements VersionConstraint { |
| final Version min; |
| final Version max; |
| + final bool includeMin; |
| + final bool includeMax; |
| - VersionRange([this.min, this.max]) { |
| + VersionRange([this.min, this.max, |
| + this.includeMin = false, this.includeMax = false]) { |
| if (min != null && max != null && min > max) { |
| throw new IllegalArgumentException( |
| - 'Maximum version ("$max") must be less than minimum ("$min").'); |
| + 'Minimum version ("$min") must be less than maximum ("$max").'); |
| } |
| } |
| + bool operator ==(other) { |
| + if (other is! VersionRange) return false; |
| + |
| + return min == other.min && |
| + max == other.max && |
| + includeMin == other.includeMin && |
| + includeMax == other.includeMax; |
| + } |
| + |
| + bool get isEmpty() => false; |
| + |
| /** Tests if [other] matches falls within this version range. */ |
| bool allows(Version other) { |
| if (min != null && other < min) return false; |
| - if (max != null && other >= max) return false; |
| + if (min != null && !includeMin && other == min) return false; |
| + if (max != null && other > max) return false; |
| + if (max != null && !includeMax && other == max) return false; |
| return true; |
| } |
| + |
| + VersionConstraint intersect(VersionConstraint other) { |
| + if (other.isEmpty) return other; |
| + |
| + // A range and a Version just yields the version if it's in the range. |
| + if (other is Version) return allows(other) ? other : _emptyVersion; |
| + |
| + if (other is VersionRange) { |
| + // Intersect the two ranges. |
| + var intersectMin = min; |
| + var intersectIncludeMin = includeMin; |
| + var intersectMax = max; |
| + var intersectIncludeMax = includeMax; |
| + |
| + if (other.min == null) { |
| + // Do nothing. |
| + } else if (intersectMin == null || intersectMin < other.min) { |
| + intersectMin = other.min; |
| + intersectIncludeMin = other.includeMin; |
| + } else if (intersectMin == other.min && !other.includeMin) { |
| + // The edges are the same, but one is exclusive, make it exclusive. |
| + intersectIncludeMin = false; |
| + } |
| + |
| + if (other.max == null) { |
| + // Do nothing. |
| + } else if (intersectMax == null || intersectMax > other.max) { |
| + intersectMax = other.max; |
| + intersectIncludeMax = other.includeMax; |
| + } else if (intersectMax == other.max && !other.includeMax) { |
| + // The edges are the same, but one is exclusive, make it exclusive. |
| + intersectIncludeMax = false; |
| + } |
| + |
| + if (intersectMin == null && intersectMax == null) { |
| + // Open range. |
| + return new VersionRange(); |
| + } |
| + |
| + // 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.
|
| + if (intersectMin == intersectMax) { |
| + // If both ends are inclusive, allow that version. |
| + if (intersectIncludeMin && intersectIncludeMax) return intersectMin; |
| + |
| + // Otherwise, no versions. |
| + return _emptyVersion; |
| + } |
| + |
| + if (intersectMin != null && intersectMax != null && |
| + intersectMin > intersectMax) { |
| + // Non-overlapping ranges, so empty. |
| + return _emptyVersion; |
| + } |
| + |
| + // If we got here, there is an actual range. |
| + return new VersionRange(intersectMin, intersectMax, |
| + intersectIncludeMin, intersectIncludeMax); |
| + } |
| + |
| + throw new IllegalArgumentException( |
| + 'Unknown VersionConstraint type $other.'); |
| + } |
| + |
| + String toString() { |
| + var buffer = new StringBuffer(); |
| + |
| + if (min != null) { |
| + buffer.add(includeMin ? '>=' : '>'); |
| + buffer.add(min); |
| + } |
| + |
| + if (max != null) { |
| + if (min != null) buffer.add(' '); |
| + buffer.add(includeMax ? '<=' : '<'); |
| + buffer.add(max); |
| + } |
| + |
| + 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
|
| + return buffer.toString(); |
| + } |
| +} |
| + |
| +class _EmptyVersion implements VersionConstraint { |
| + const _EmptyVersion(); |
| + |
| + bool get isEmpty() => true; |
| + bool allows(Version other) => false; |
| + VersionConstraint intersect(VersionConstraint other) => this; |
| + String toString() => '<empty>'; |
| +} |
| + |
| +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.
|
| + |
| +class _VersionConstraintFactory { |
| + factory VersionConstraint.empty() => _emptyVersion; |
| + |
| + factory VersionConstraint.parse(String text) { |
| + if (text.trim() == '') { |
| + throw new FormatException('Cannot parse an empty string.'); |
| + } |
| + |
| + // Split it into space-separated parts. |
| + var constraints = <VersionConstraint>[]; |
| + for (var part in text.split(' ')) { |
| + constraints.add(parseSingleConstraint(part)); |
| + } |
| + |
| + return new VersionConstraint.intersect(constraints); |
| + } |
| + |
| + factory VersionConstraint.intersect(Collection<VersionConstraint> constraints) { |
|
nweiz
2012/06/18 18:29:19
Line length.
Bob Nystrom
2012/06/20 01:40:04
Done.
|
| + var constraint = new VersionRange(); |
| + for (var other in constraints) { |
| + constraint = constraint.intersect(other); |
| + } |
| + return constraint; |
| + } |
| + |
| + static VersionConstraint parseSingleConstraint(String text) { |
| + // TODO(rnystrom): Consider other syntaxes for version constraints. This |
| + // 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.
|
| + // unfortunately meaningful in YAML, requiring it to be quoted in a |
| + // pubspec. |
| + 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.
|
| + // A maximum version (inclusive). |
| + var version = new Version.parse(text.substring('<='.length)); |
| + return new VersionRange(max: version, includeMax: true); |
| + } |
| + |
| + if (text.startsWith('<')) { |
| + // A maximum version (exclusive). |
| + var version = new Version.parse(text.substring('<'.length)); |
| + return new VersionRange(max: version, includeMax: false); |
| + } |
| + |
| + if (text.startsWith('>=')) { |
| + // A minimum version (inclusive). |
| + var version = new Version.parse(text.substring('>='.length)); |
| + return new VersionRange(min: version, includeMin: true); |
| + } |
| + |
| + if (text.startsWith('>')) { |
| + // A minimum version (exclusive). |
| + var version = new Version.parse(text.substring('>'.length)); |
| + return new VersionRange(min: version, includeMin: false); |
| + } |
| + |
| + // Otherwise, it must be an explicit version. |
| + return new Version.parse(text); |
| + } |
| } |