OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
| 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. |
| 4 |
| 5 library pub_semver.src.version; |
| 6 |
| 7 import 'dart:math'; |
| 8 |
| 9 import 'package:collection/equality.dart'; |
| 10 |
| 11 import 'patterns.dart'; |
| 12 import 'version_constraint.dart'; |
| 13 import 'version_range.dart'; |
| 14 |
| 15 /// The equality operator to use for comparing version components. |
| 16 final _equality = const IterableEquality(); |
| 17 |
| 18 /// A parsed semantic version number. |
| 19 class Version implements Comparable<Version>, VersionConstraint { |
| 20 /// No released version: i.e. "0.0.0". |
| 21 static Version get none => new Version(0, 0, 0); |
| 22 |
| 23 /// Compares [a] and [b] to see which takes priority over the other. |
| 24 /// |
| 25 /// Returns `1` if [a] takes priority over [b] and `-1` if vice versa. If |
| 26 /// [a] and [b] are equivalent, returns `0`. |
| 27 /// |
| 28 /// Unlike [compareTo], which *orders* versions, this determines which |
| 29 /// version a user is likely to prefer. In particular, it prioritizes |
| 30 /// pre-release versions lower than stable versions, regardless of their |
| 31 /// version numbers. Pub uses this when determining which version to prefer |
| 32 /// when a number of versions are allowed. In that case, it will always |
| 33 /// choose a stable version when possible. |
| 34 /// |
| 35 /// When used to sort a list, orders in ascending priority so that the |
| 36 /// highest priority version is *last* in the result. |
| 37 static int prioritize(Version a, Version b) { |
| 38 // Sort all prerelease versions after all normal versions. This way |
| 39 // the solver will prefer stable packages over unstable ones. |
| 40 if (a.isPreRelease && !b.isPreRelease) return -1; |
| 41 if (!a.isPreRelease && b.isPreRelease) return 1; |
| 42 |
| 43 return a.compareTo(b); |
| 44 } |
| 45 |
| 46 /// Like [proiritize], but lower version numbers are considered greater than |
| 47 /// higher version numbers. |
| 48 /// |
| 49 /// This still considers prerelease versions to be lower than non-prerelease |
| 50 /// versions. Pub uses this when downgrading -- it chooses the lowest version |
| 51 /// but still excludes pre-release versions when possible. |
| 52 static int antiprioritize(Version a, Version b) { |
| 53 if (a.isPreRelease && !b.isPreRelease) return -1; |
| 54 if (!a.isPreRelease && b.isPreRelease) return 1; |
| 55 |
| 56 return b.compareTo(a); |
| 57 } |
| 58 |
| 59 /// The major version number: "1" in "1.2.3". |
| 60 final int major; |
| 61 |
| 62 /// The minor version number: "2" in "1.2.3". |
| 63 final int minor; |
| 64 |
| 65 /// The patch version number: "3" in "1.2.3". |
| 66 final int patch; |
| 67 |
| 68 /// The pre-release identifier: "foo" in "1.2.3-foo". |
| 69 /// |
| 70 /// This is split into a list of components, each of which may be either a |
| 71 /// string or a non-negative integer. It may also be empty, indicating that |
| 72 /// this version has no pre-release identifier. |
| 73 final List preRelease; |
| 74 |
| 75 /// The build identifier: "foo" in "1.2.3+foo". |
| 76 /// |
| 77 /// This is split into a list of components, each of which may be either a |
| 78 /// string or a non-negative integer. It may also be empty, indicating that |
| 79 /// this version has no build identifier. |
| 80 final List build; |
| 81 |
| 82 /// The original string representation of the version number. |
| 83 /// |
| 84 /// This preserves textual artifacts like leading zeros that may be left out |
| 85 /// of the parsed version. |
| 86 final String _text; |
| 87 |
| 88 Version._(this.major, this.minor, this.patch, String preRelease, String build, |
| 89 this._text) |
| 90 : preRelease = preRelease == null ? [] : _splitParts(preRelease), |
| 91 build = build == null ? [] : _splitParts(build) { |
| 92 if (major < 0) throw new ArgumentError( |
| 93 'Major version must be non-negative.'); |
| 94 if (minor < 0) throw new ArgumentError( |
| 95 'Minor version must be non-negative.'); |
| 96 if (patch < 0) throw new ArgumentError( |
| 97 'Patch version must be non-negative.'); |
| 98 } |
| 99 |
| 100 /// Creates a new [Version] object. |
| 101 factory Version(int major, int minor, int patch, {String pre, String build}) { |
| 102 var text = "$major.$minor.$patch"; |
| 103 if (pre != null) text += "-$pre"; |
| 104 if (build != null) text += "+$build"; |
| 105 |
| 106 return new Version._(major, minor, patch, pre, build, text); |
| 107 } |
| 108 |
| 109 /// Creates a new [Version] by parsing [text]. |
| 110 factory Version.parse(String text) { |
| 111 final match = COMPLETE_VERSION.firstMatch(text); |
| 112 if (match == null) { |
| 113 throw new FormatException('Could not parse "$text".'); |
| 114 } |
| 115 |
| 116 try { |
| 117 int major = int.parse(match[1]); |
| 118 int minor = int.parse(match[2]); |
| 119 int patch = int.parse(match[3]); |
| 120 |
| 121 String preRelease = match[5]; |
| 122 String build = match[8]; |
| 123 |
| 124 return new Version._(major, minor, patch, preRelease, build, text); |
| 125 } on FormatException catch (ex) { |
| 126 throw new FormatException('Could not parse "$text".'); |
| 127 } |
| 128 } |
| 129 |
| 130 /// Returns the primary version out of a list of candidates. |
| 131 /// |
| 132 /// This is the highest-numbered stable (non-prerelease) version. If there |
| 133 /// are no stable versions, it's just the highest-numbered version. |
| 134 static Version primary(List<Version> versions) { |
| 135 var primary; |
| 136 for (var version in versions) { |
| 137 if (primary == null || (!version.isPreRelease && primary.isPreRelease) || |
| 138 (version.isPreRelease == primary.isPreRelease && version > primary)) { |
| 139 primary = version; |
| 140 } |
| 141 } |
| 142 return primary; |
| 143 } |
| 144 |
| 145 /// Splits a string of dot-delimited identifiers into their component parts. |
| 146 /// |
| 147 /// Identifiers that are numeric are converted to numbers. |
| 148 static List _splitParts(String text) { |
| 149 return text.split('.').map((part) { |
| 150 try { |
| 151 return int.parse(part); |
| 152 } on FormatException catch (ex) { |
| 153 // Not a number. |
| 154 return part; |
| 155 } |
| 156 }).toList(); |
| 157 } |
| 158 |
| 159 bool operator ==(other) { |
| 160 if (other is! Version) return false; |
| 161 return major == other.major && minor == other.minor && |
| 162 patch == other.patch && |
| 163 _equality.equals(preRelease, other.preRelease) && |
| 164 _equality.equals(build, other.build); |
| 165 } |
| 166 |
| 167 int get hashCode => major ^ minor ^ patch ^ _equality.hash(preRelease) ^ |
| 168 _equality.hash(build); |
| 169 |
| 170 bool operator <(Version other) => compareTo(other) < 0; |
| 171 bool operator >(Version other) => compareTo(other) > 0; |
| 172 bool operator <=(Version other) => compareTo(other) <= 0; |
| 173 bool operator >=(Version other) => compareTo(other) >= 0; |
| 174 |
| 175 bool get isAny => false; |
| 176 bool get isEmpty => false; |
| 177 |
| 178 /// Whether or not this is a pre-release version. |
| 179 bool get isPreRelease => preRelease.isNotEmpty; |
| 180 |
| 181 /// Gets the next major version number that follows this one. |
| 182 /// |
| 183 /// If this version is a pre-release of a major version release (i.e. the |
| 184 /// minor and patch versions are zero), then it just strips the pre-release |
| 185 /// suffix. Otherwise, it increments the major version and resets the minor |
| 186 /// and patch. |
| 187 Version get nextMajor { |
| 188 if (isPreRelease && minor == 0 && patch == 0) { |
| 189 return new Version(major, minor, patch); |
| 190 } |
| 191 |
| 192 return new Version(major + 1, 0, 0); |
| 193 } |
| 194 |
| 195 /// Gets the next minor version number that follows this one. |
| 196 /// |
| 197 /// If this version is a pre-release of a minor version release (i.e. the |
| 198 /// patch version is zero), then it just strips the pre-release suffix. |
| 199 /// Otherwise, it increments the minor version and resets the patch. |
| 200 Version get nextMinor { |
| 201 if (isPreRelease && patch == 0) { |
| 202 return new Version(major, minor, patch); |
| 203 } |
| 204 |
| 205 return new Version(major, minor + 1, 0); |
| 206 } |
| 207 |
| 208 /// Gets the next patch version number that follows this one. |
| 209 /// |
| 210 /// If this version is a pre-release, then it just strips the pre-release |
| 211 /// suffix. Otherwise, it increments the patch version. |
| 212 Version get nextPatch { |
| 213 if (isPreRelease) { |
| 214 return new Version(major, minor, patch); |
| 215 } |
| 216 |
| 217 return new Version(major, minor, patch + 1); |
| 218 } |
| 219 |
| 220 /// Tests if [other] matches this version exactly. |
| 221 bool allows(Version other) => this == other; |
| 222 |
| 223 VersionConstraint intersect(VersionConstraint other) { |
| 224 if (other.isEmpty) return other; |
| 225 |
| 226 // Intersect a version and a range. |
| 227 if (other is VersionRange) return other.intersect(this); |
| 228 |
| 229 // Intersecting two versions only works if they are the same. |
| 230 if (other is Version) { |
| 231 return this == other ? this : VersionConstraint.empty; |
| 232 } |
| 233 |
| 234 throw new ArgumentError( |
| 235 'Unknown VersionConstraint type $other.'); |
| 236 } |
| 237 |
| 238 int compareTo(Version other) { |
| 239 if (major != other.major) return major.compareTo(other.major); |
| 240 if (minor != other.minor) return minor.compareTo(other.minor); |
| 241 if (patch != other.patch) return patch.compareTo(other.patch); |
| 242 |
| 243 // Pre-releases always come before no pre-release string. |
| 244 if (!isPreRelease && other.isPreRelease) return 1; |
| 245 if (!other.isPreRelease && isPreRelease) return -1; |
| 246 |
| 247 var comparison = _compareLists(preRelease, other.preRelease); |
| 248 if (comparison != 0) return comparison; |
| 249 |
| 250 // Builds always come after no build string. |
| 251 if (build.isEmpty && other.build.isNotEmpty) return -1; |
| 252 if (other.build.isEmpty && build.isNotEmpty) return 1; |
| 253 return _compareLists(build, other.build); |
| 254 } |
| 255 |
| 256 String toString() => _text; |
| 257 |
| 258 /// Compares a dot-separated component of two versions. |
| 259 /// |
| 260 /// This is used for the pre-release and build version parts. This follows |
| 261 /// Rule 12 of the Semantic Versioning spec (v2.0.0-rc.1). |
| 262 int _compareLists(List a, List b) { |
| 263 for (var i = 0; i < max(a.length, b.length); i++) { |
| 264 var aPart = (i < a.length) ? a[i] : null; |
| 265 var bPart = (i < b.length) ? b[i] : null; |
| 266 |
| 267 if (aPart == bPart) continue; |
| 268 |
| 269 // Missing parts come before present ones. |
| 270 if (aPart == null) return -1; |
| 271 if (bPart == null) return 1; |
| 272 |
| 273 if (aPart is num) { |
| 274 if (bPart is num) { |
| 275 // Compare two numbers. |
| 276 return aPart.compareTo(bPart); |
| 277 } else { |
| 278 // Numbers come before strings. |
| 279 return -1; |
| 280 } |
| 281 } else { |
| 282 if (bPart is num) { |
| 283 // Strings come after numbers. |
| 284 return 1; |
| 285 } else { |
| 286 // Compare two strings. |
| 287 return aPart.compareTo(bPart); |
| 288 } |
| 289 } |
| 290 } |
| 291 |
| 292 // The lists are entirely equal. |
| 293 return 0; |
| 294 } |
| 295 } |
OLD | NEW |