| Index: sdk/lib/_internal/pub/lib/src/version.dart
|
| diff --git a/sdk/lib/_internal/pub/lib/src/version.dart b/sdk/lib/_internal/pub/lib/src/version.dart
|
| index 969d268039b972fece4d1b6d858d46f3203bb7ca..dcba9d447b5459c3f3a8d36c2620ece0028a50a9 100644
|
| --- a/sdk/lib/_internal/pub/lib/src/version.dart
|
| +++ b/sdk/lib/_internal/pub/lib/src/version.dart
|
| @@ -9,6 +9,8 @@ library pub.version;
|
|
|
| import 'dart:math';
|
|
|
| +import 'package:collection_helpers/equality.dart';
|
| +
|
| /// Regex that matches a version number at the beginning of a string.
|
| final _START_VERSION = new RegExp(
|
| r'^' // Start at beginning.
|
| @@ -23,10 +25,14 @@ final _COMPLETE_VERSION = new RegExp("${_START_VERSION.pattern}\$");
|
| /// a string.
|
| final _START_COMPARISON = new RegExp(r"^[<>]=?");
|
|
|
| +/// The equality operator to use for comparing version components.
|
| +final _equality = const IterableEquality();
|
| +
|
| /// A parsed semantic version number.
|
| class Version implements Comparable<Version>, VersionConstraint {
|
| /// No released version: i.e. "0.0.0".
|
| static Version get none => new Version(0, 0, 0);
|
| +
|
| /// The major version number: "1" in "1.2.3".
|
| final int major;
|
|
|
| @@ -36,15 +42,30 @@ class Version implements Comparable<Version>, VersionConstraint {
|
| /// The patch version number: "3" in "1.2.3".
|
| final int patch;
|
|
|
| - /// The pre-release identifier: "foo" in "1.2.3-foo". May be `null`.
|
| - final String preRelease;
|
| + /// The pre-release identifier: "foo" in "1.2.3-foo".
|
| + ///
|
| + /// This is split into a list of components, each of which may be either a
|
| + /// string or a non-negative integer. It may also be empty, indicating that
|
| + /// this version has no pre-release identifier.
|
| + final List preRelease;
|
|
|
| - /// The build identifier: "foo" in "1.2.3+foo". May be `null`.
|
| - final String build;
|
| + /// The build identifier: "foo" in "1.2.3+foo".
|
| + ///
|
| + /// This is split into a list of components, each of which may be either a
|
| + /// string or a non-negative integer. It may also be empty, indicating that
|
| + /// this version has no build identifier.
|
| + final List build;
|
|
|
| - /// Creates a new [Version] object.
|
| - Version(this.major, this.minor, this.patch, {String pre, this.build})
|
| - : preRelease = pre {
|
| + /// The original string representation of the version number.
|
| + ///
|
| + /// This preserves textual artifacts like leading zeros that may be left out
|
| + /// of the parsed version.
|
| + final String _text;
|
| +
|
| + Version._(this.major, this.minor, this.patch, String preRelease, String build,
|
| + this._text)
|
| + : preRelease = preRelease == null ? [] : _splitParts(preRelease),
|
| + build = build == null ? [] : _splitParts(build) {
|
| if (major < 0) throw new ArgumentError(
|
| 'Major version must be non-negative.');
|
| if (minor < 0) throw new ArgumentError(
|
| @@ -53,6 +74,15 @@ class Version implements Comparable<Version>, VersionConstraint {
|
| 'Patch version must be non-negative.');
|
| }
|
|
|
| + /// Creates a new [Version] object.
|
| + factory Version(int major, int minor, int patch, {String pre, String build}) {
|
| + var text = "$major.$minor.$patch";
|
| + if (pre != null) text += "-$pre";
|
| + if (build != null) text += "+$build";
|
| +
|
| + return new Version._(major, minor, patch, pre, build, text);
|
| + }
|
| +
|
| /// Creates a new [Version] by parsing [text].
|
| factory Version.parse(String text) {
|
| final match = _COMPLETE_VERSION.firstMatch(text);
|
| @@ -68,7 +98,7 @@ class Version implements Comparable<Version>, VersionConstraint {
|
| String preRelease = match[5];
|
| String build = match[8];
|
|
|
| - return new Version(major, minor, patch, pre: preRelease, build: build);
|
| + return new Version._(major, minor, patch, preRelease, build, text);
|
| } on FormatException catch (ex) {
|
| throw new FormatException('Could not parse "$text".');
|
| }
|
| @@ -88,11 +118,31 @@ class Version implements Comparable<Version>, VersionConstraint {
|
| return primary;
|
| }
|
|
|
| + /// Splits a string of dot-delimited identifiers into their component parts.
|
| + ///
|
| + /// Identifiers that are numeric are converted to numbers.
|
| + static List _splitParts(String text) {
|
| + return text.split('.').map((part) {
|
| + try {
|
| + return int.parse(part);
|
| + } on FormatException catch (ex) {
|
| + // Not a number.
|
| + return part;
|
| + }
|
| + }).toList();
|
| + }
|
| +
|
| bool operator ==(other) {
|
| if (other is! Version) return false;
|
| - return compareTo(other) == 0;
|
| + return major == other.major && minor == other.minor &&
|
| + patch == other.patch &&
|
| + _equality.equals(preRelease, other.preRelease) &&
|
| + _equality.equals(build, other.build);
|
| }
|
|
|
| + int get hashCode => major ^ minor ^ patch ^ _equality.hash(preRelease) ^
|
| + _equality.hash(build);
|
| +
|
| bool operator <(Version other) => compareTo(other) < 0;
|
| bool operator >(Version other) => compareTo(other) > 0;
|
| bool operator <=(Version other) => compareTo(other) <= 0;
|
| @@ -102,7 +152,7 @@ class Version implements Comparable<Version>, VersionConstraint {
|
| bool get isEmpty => false;
|
|
|
| /// Whether or not this is a pre-release version.
|
| - bool get isPreRelease => preRelease != null;
|
| + bool get isPreRelease => preRelease.isNotEmpty;
|
|
|
| /// Tests if [other] matches this version exactly.
|
| bool allows(Version other) => this == other;
|
| @@ -127,83 +177,57 @@ class Version implements Comparable<Version>, VersionConstraint {
|
| if (minor != other.minor) return minor.compareTo(other.minor);
|
| if (patch != other.patch) return patch.compareTo(other.patch);
|
|
|
| - if (preRelease != other.preRelease) {
|
| - // Pre-releases always come before no pre-release string.
|
| - if (preRelease == null) return 1;
|
| - if (other.preRelease == null) return -1;
|
| + // Pre-releases always come before no pre-release string.
|
| + if (!isPreRelease && other.isPreRelease) return 1;
|
| + if (!other.isPreRelease && isPreRelease) return -1;
|
|
|
| - return _compareStrings(preRelease, other.preRelease);
|
| - }
|
| -
|
| - if (build != other.build) {
|
| - // Builds always come after no build string.
|
| - if (build == null) return -1;
|
| - if (other.build == null) return 1;
|
| -
|
| - return _compareStrings(build, other.build);
|
| - }
|
| + var comparison = _compareLists(preRelease, other.preRelease);
|
| + if (comparison != 0) return comparison;
|
|
|
| - return 0;
|
| + // Builds always come after no build string.
|
| + if (build.isEmpty && other.build.isNotEmpty) return -1;
|
| + if (other.build.isEmpty && build.isNotEmpty) return 1;
|
| + return _compareLists(build, other.build);
|
| }
|
|
|
| - int get hashCode => toString().hashCode;
|
| + String toString() => _text;
|
|
|
| - String toString() {
|
| - var buffer = new StringBuffer();
|
| - buffer.write('$major.$minor.$patch');
|
| - if (preRelease != null) buffer.write('-$preRelease');
|
| - if (build != null) buffer.write('+$build');
|
| - return buffer.toString();
|
| - }
|
| -
|
| - /// Compares the string part of two versions. This is used for the pre-release
|
| - /// and build version parts. This follows Rule 12. of the Semantic Versioning
|
| - /// spec.
|
| - int _compareStrings(String a, String b) {
|
| - var aParts = _splitParts(a);
|
| - var bParts = _splitParts(b);
|
| -
|
| - for (int i = 0; i < max(aParts.length, bParts.length); i++) {
|
| - var aPart = (i < aParts.length) ? aParts[i] : null;
|
| - var bPart = (i < bParts.length) ? bParts[i] : null;
|
| -
|
| - if (aPart != bPart) {
|
| - // Missing parts come before present ones.
|
| - if (aPart == null) return -1;
|
| - if (bPart == null) return 1;
|
| -
|
| - if (aPart is num) {
|
| - if (bPart is num) {
|
| - // Compare two numbers.
|
| - return aPart.compareTo(bPart);
|
| - } else {
|
| - // Numbers come before strings.
|
| - return -1;
|
| - }
|
| + /// Compares a dot-separated component of two versions.
|
| + ///
|
| + /// This is used for the pre-release and build version parts. This follows
|
| + /// Rule 12 of the Semantic Versioning spec (v2.0.0-rc.1).
|
| + int _compareLists(List a, List b) {
|
| + for (var i = 0; i < max(a.length, b.length); i++) {
|
| + var aPart = (i < a.length) ? a[i] : null;
|
| + var bPart = (i < b.length) ? b[i] : null;
|
| +
|
| + if (aPart == bPart) continue;
|
| +
|
| + // Missing parts come before present ones.
|
| + if (aPart == null) return -1;
|
| + if (bPart == null) return 1;
|
| +
|
| + if (aPart is num) {
|
| + if (bPart is num) {
|
| + // Compare two numbers.
|
| + return aPart.compareTo(bPart);
|
| + } else {
|
| + // Numbers come before strings.
|
| + return -1;
|
| + }
|
| + } else {
|
| + if (bPart is num) {
|
| + // Strings come after numbers.
|
| + return 1;
|
| } else {
|
| - if (bPart is num) {
|
| - // Strings come after numbers.
|
| - return 1;
|
| - } else {
|
| - // Compare two strings.
|
| - return aPart.compareTo(bPart);
|
| - }
|
| + // Compare two strings.
|
| + return aPart.compareTo(bPart);
|
| }
|
| }
|
| }
|
| - }
|
|
|
| - /// Splits a string of dot-delimited identifiers into their component parts.
|
| - /// Identifiers that are numeric are converted to numbers.
|
| - List _splitParts(String text) {
|
| - return text.split('.').map((part) {
|
| - try {
|
| - return int.parse(part);
|
| - } on FormatException catch (ex) {
|
| - // Not a number.
|
| - return part;
|
| - }
|
| - }).toList();
|
| + // The lists are entirely equal.
|
| + return 0;
|
| }
|
| }
|
|
|
|
|