| // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'dart:math' as math; |
| |
| import 'package:collection/collection.dart'; |
| |
| import 'patterns.dart'; |
| import 'version_constraint.dart'; |
| import 'version_range.dart'; |
| |
| /// The equality operator to use for comparing version components. |
| final _equality = const IterableEquality(); |
| |
| /// A parsed semantic version number. |
| class Version implements VersionConstraint, VersionRange { |
| /// No released version: i.e. "0.0.0". |
| static Version get none => Version(0, 0, 0); |
| |
| /// Compares [a] and [b] to see which takes priority over the other. |
| /// |
| /// Returns `1` if [a] takes priority over [b] and `-1` if vice versa. If |
| /// [a] and [b] are equivalent, returns `0`. |
| /// |
| /// Unlike [compareTo], which *orders* versions, this determines which |
| /// version a user is likely to prefer. In particular, it prioritizes |
| /// pre-release versions lower than stable versions, regardless of their |
| /// version numbers. Pub uses this when determining which version to prefer |
| /// when a number of versions are allowed. In that case, it will always |
| /// choose a stable version when possible. |
| /// |
| /// When used to sort a list, orders in ascending priority so that the |
| /// highest priority version is *last* in the result. |
| static int prioritize(Version a, Version b) { |
| // Sort all prerelease versions after all normal versions. This way |
| // the solver will prefer stable packages over unstable ones. |
| if (a.isPreRelease && !b.isPreRelease) return -1; |
| if (!a.isPreRelease && b.isPreRelease) return 1; |
| |
| return a.compareTo(b); |
| } |
| |
| /// Like [prioritize], but lower version numbers are considered greater than |
| /// higher version numbers. |
| /// |
| /// This still considers prerelease versions to be lower than non-prerelease |
| /// versions. Pub uses this when downgrading -- it chooses the lowest version |
| /// but still excludes pre-release versions when possible. |
| static int antiprioritize(Version a, Version b) { |
| if (a.isPreRelease && !b.isPreRelease) return -1; |
| if (!a.isPreRelease && b.isPreRelease) return 1; |
| |
| return b.compareTo(a); |
| } |
| |
| /// The major version number: "1" in "1.2.3". |
| final int major; |
| |
| /// The minor version number: "2" in "1.2.3". |
| final int minor; |
| |
| /// The patch version number: "3" in "1.2.3". |
| final int patch; |
| |
| /// 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". |
| /// |
| /// 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; |
| |
| /// 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; |
| |
| @override |
| Version get min => this; |
| @override |
| Version get max => this; |
| @override |
| bool get includeMin => true; |
| @override |
| bool get includeMax => true; |
| |
| 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 ArgumentError('Major version must be non-negative.'); |
| if (minor < 0) throw ArgumentError('Minor version must be non-negative.'); |
| if (patch < 0) throw ArgumentError('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 Version._(major, minor, patch, pre, build, text); |
| } |
| |
| /// Creates a new [Version] by parsing [text]. |
| factory Version.parse(String text) { |
| final match = completeVersion.firstMatch(text); |
| if (match == null) { |
| throw FormatException('Could not parse "$text".'); |
| } |
| |
| try { |
| var major = int.parse(match[1]); |
| var minor = int.parse(match[2]); |
| var patch = int.parse(match[3]); |
| |
| var preRelease = match[5]; |
| var build = match[8]; |
| |
| return Version._(major, minor, patch, preRelease, build, text); |
| } on FormatException { |
| throw FormatException('Could not parse "$text".'); |
| } |
| } |
| |
| /// Returns the primary version out of [versions]. |
| /// |
| /// This is the highest-numbered stable (non-prerelease) version. If there |
| /// are no stable versions, it's just the highest-numbered version. |
| /// |
| /// If [versions] is empty, returns `null`. |
| static Version primary(List<Version> versions) { |
| Version primary; |
| for (var version in versions) { |
| if (primary == null || |
| (!version.isPreRelease && primary.isPreRelease) || |
| (version.isPreRelease == primary.isPreRelease && version > primary)) { |
| primary = version; |
| } |
| } |
| 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) { |
| // Return an integer part if possible, otherwise return the string as-is |
| return int.tryParse(part) ?? part; |
| }).toList(); |
| } |
| |
| @override |
| bool operator ==(Object other) => |
| other is Version && |
| major == other.major && |
| minor == other.minor && |
| patch == other.patch && |
| _equality.equals(preRelease, other.preRelease) && |
| _equality.equals(build, other.build); |
| |
| @override |
| 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; |
| bool operator >=(Version other) => compareTo(other) >= 0; |
| |
| @override |
| bool get isAny => false; |
| @override |
| bool get isEmpty => false; |
| |
| /// Whether or not this is a pre-release version. |
| bool get isPreRelease => preRelease.isNotEmpty; |
| |
| /// Gets the next major version number that follows this one. |
| /// |
| /// If this version is a pre-release of a major version release (i.e. the |
| /// minor and patch versions are zero), then it just strips the pre-release |
| /// suffix. Otherwise, it increments the major version and resets the minor |
| /// and patch. |
| Version get nextMajor { |
| if (isPreRelease && minor == 0 && patch == 0) { |
| return Version(major, minor, patch); |
| } |
| |
| return _incrementMajor(); |
| } |
| |
| /// Gets the next minor version number that follows this one. |
| /// |
| /// If this version is a pre-release of a minor version release (i.e. the |
| /// patch version is zero), then it just strips the pre-release suffix. |
| /// Otherwise, it increments the minor version and resets the patch. |
| Version get nextMinor { |
| if (isPreRelease && patch == 0) { |
| return Version(major, minor, patch); |
| } |
| |
| return _incrementMinor(); |
| } |
| |
| /// Gets the next patch version number that follows this one. |
| /// |
| /// If this version is a pre-release, then it just strips the pre-release |
| /// suffix. Otherwise, it increments the patch version. |
| Version get nextPatch { |
| if (isPreRelease) { |
| return Version(major, minor, patch); |
| } |
| |
| return _incrementPatch(); |
| } |
| |
| /// Gets the next breaking version number that follows this one. |
| /// |
| /// Increments [major] if it's greater than zero, otherwise [minor], resets |
| /// subsequent digits to zero, and strips any [preRelease] or [build] |
| /// suffix. |
| Version get nextBreaking { |
| if (major == 0) { |
| return _incrementMinor(); |
| } |
| |
| return _incrementMajor(); |
| } |
| |
| /// Returns the first possible pre-release of this version. |
| Version get firstPreRelease => Version(major, minor, patch, pre: '0'); |
| |
| /// Returns whether this is the first possible pre-release of its version. |
| bool get isFirstPreRelease => preRelease.length == 1 && preRelease.first == 0; |
| |
| Version _incrementMajor() => Version(major + 1, 0, 0); |
| Version _incrementMinor() => Version(major, minor + 1, 0); |
| Version _incrementPatch() => Version(major, minor, patch + 1); |
| |
| /// Tests if [other] matches this version exactly. |
| @override |
| bool allows(Version other) => this == other; |
| |
| @override |
| bool allowsAll(VersionConstraint other) => other.isEmpty || other == this; |
| |
| @override |
| bool allowsAny(VersionConstraint other) => other.allows(this); |
| |
| @override |
| VersionConstraint intersect(VersionConstraint other) => |
| other.allows(this) ? this : VersionConstraint.empty; |
| |
| @override |
| VersionConstraint union(VersionConstraint other) { |
| if (other.allows(this)) return other; |
| |
| if (other is VersionRange) { |
| if (other.min == this) { |
| return VersionRange( |
| min: other.min, |
| max: other.max, |
| includeMin: true, |
| includeMax: other.includeMax, |
| alwaysIncludeMaxPreRelease: true); |
| } |
| |
| if (other.max == this) { |
| return VersionRange( |
| min: other.min, |
| max: other.max, |
| includeMin: other.includeMin, |
| includeMax: true, |
| alwaysIncludeMaxPreRelease: true); |
| } |
| } |
| |
| return VersionConstraint.unionOf([this, other]); |
| } |
| |
| @override |
| VersionConstraint difference(VersionConstraint other) => |
| other.allows(this) ? VersionConstraint.empty : this; |
| |
| @override |
| int compareTo(VersionRange other) { |
| if (other is Version) { |
| if (major != other.major) return major.compareTo(other.major); |
| if (minor != other.minor) return minor.compareTo(other.minor); |
| if (patch != other.patch) return patch.compareTo(other.patch); |
| |
| // Pre-releases always come before no pre-release string. |
| if (!isPreRelease && other.isPreRelease) return 1; |
| if (!other.isPreRelease && isPreRelease) return -1; |
| |
| var comparison = _compareLists(preRelease, other.preRelease); |
| if (comparison != 0) return comparison; |
| |
| // 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); |
| } else { |
| return -other.compareTo(this); |
| } |
| } |
| |
| @override |
| String toString() => _text; |
| |
| /// 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 < math.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 { |
| // Compare two strings. |
| return (aPart as String).compareTo(bPart as String); |
| } |
| } |
| } |
| |
| // The lists are entirely equal. |
| return 0; |
| } |
| } |