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' as 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, VersionRange { |
| 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 [prioritize], 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 get min => this; |
| 89 Version get max => this; |
| 90 bool get includeMin => true; |
| 91 bool get includeMax => true; |
| 92 |
| 93 Version._(this.major, this.minor, this.patch, String preRelease, String build, |
| 94 this._text) |
| 95 : preRelease = preRelease == null ? [] : _splitParts(preRelease), |
| 96 build = build == null ? [] : _splitParts(build) { |
| 97 if (major < 0) throw new ArgumentError( |
| 98 'Major version must be non-negative.'); |
| 99 if (minor < 0) throw new ArgumentError( |
| 100 'Minor version must be non-negative.'); |
| 101 if (patch < 0) throw new ArgumentError( |
| 102 'Patch version must be non-negative.'); |
| 103 } |
| 104 |
| 105 /// Creates a new [Version] object. |
| 106 factory Version(int major, int minor, int patch, {String pre, String build}) { |
| 107 var text = "$major.$minor.$patch"; |
| 108 if (pre != null) text += "-$pre"; |
| 109 if (build != null) text += "+$build"; |
| 110 |
| 111 return new Version._(major, minor, patch, pre, build, text); |
| 112 } |
| 113 |
| 114 /// Creates a new [Version] by parsing [text]. |
| 115 factory Version.parse(String text) { |
| 116 final match = COMPLETE_VERSION.firstMatch(text); |
| 117 if (match == null) { |
| 118 throw new FormatException('Could not parse "$text".'); |
| 119 } |
| 120 |
| 121 try { |
| 122 int major = int.parse(match[1]); |
| 123 int minor = int.parse(match[2]); |
| 124 int patch = int.parse(match[3]); |
| 125 |
| 126 String preRelease = match[5]; |
| 127 String build = match[8]; |
| 128 |
| 129 return new Version._(major, minor, patch, preRelease, build, text); |
| 130 } on FormatException { |
| 131 throw new FormatException('Could not parse "$text".'); |
| 132 } |
| 133 } |
| 134 |
| 135 /// Returns the primary version out of a list of candidates. |
| 136 /// |
| 137 /// This is the highest-numbered stable (non-prerelease) version. If there |
| 138 /// are no stable versions, it's just the highest-numbered version. |
| 139 static Version primary(List<Version> versions) { |
| 140 var primary; |
| 141 for (var version in versions) { |
| 142 if (primary == null || (!version.isPreRelease && primary.isPreRelease) || |
| 143 (version.isPreRelease == primary.isPreRelease && version > primary)) { |
| 144 primary = version; |
| 145 } |
| 146 } |
| 147 return primary; |
| 148 } |
| 149 |
| 150 /// Splits a string of dot-delimited identifiers into their component parts. |
| 151 /// |
| 152 /// Identifiers that are numeric are converted to numbers. |
| 153 static List _splitParts(String text) { |
| 154 return text.split('.').map((part) { |
| 155 try { |
| 156 return int.parse(part); |
| 157 } on FormatException { |
| 158 // Not a number. |
| 159 return part; |
| 160 } |
| 161 }).toList(); |
| 162 } |
| 163 |
| 164 bool operator ==(other) { |
| 165 if (other is! Version) return false; |
| 166 return major == other.major && minor == other.minor && |
| 167 patch == other.patch && |
| 168 _equality.equals(preRelease, other.preRelease) && |
| 169 _equality.equals(build, other.build); |
| 170 } |
| 171 |
| 172 int get hashCode => major ^ minor ^ patch ^ _equality.hash(preRelease) ^ |
| 173 _equality.hash(build); |
| 174 |
| 175 bool operator <(Version other) => compareTo(other) < 0; |
| 176 bool operator >(Version other) => compareTo(other) > 0; |
| 177 bool operator <=(Version other) => compareTo(other) <= 0; |
| 178 bool operator >=(Version other) => compareTo(other) >= 0; |
| 179 |
| 180 bool get isAny => false; |
| 181 bool get isEmpty => false; |
| 182 |
| 183 /// Whether or not this is a pre-release version. |
| 184 bool get isPreRelease => preRelease.isNotEmpty; |
| 185 |
| 186 /// Gets the next major version number that follows this one. |
| 187 /// |
| 188 /// If this version is a pre-release of a major version release (i.e. the |
| 189 /// minor and patch versions are zero), then it just strips the pre-release |
| 190 /// suffix. Otherwise, it increments the major version and resets the minor |
| 191 /// and patch. |
| 192 Version get nextMajor { |
| 193 if (isPreRelease && minor == 0 && patch == 0) { |
| 194 return new Version(major, minor, patch); |
| 195 } |
| 196 |
| 197 return _incrementMajor(); |
| 198 } |
| 199 |
| 200 /// Gets the next minor version number that follows this one. |
| 201 /// |
| 202 /// If this version is a pre-release of a minor version release (i.e. the |
| 203 /// patch version is zero), then it just strips the pre-release suffix. |
| 204 /// Otherwise, it increments the minor version and resets the patch. |
| 205 Version get nextMinor { |
| 206 if (isPreRelease && patch == 0) { |
| 207 return new Version(major, minor, patch); |
| 208 } |
| 209 |
| 210 return _incrementMinor(); |
| 211 } |
| 212 |
| 213 /// Gets the next patch version number that follows this one. |
| 214 /// |
| 215 /// If this version is a pre-release, then it just strips the pre-release |
| 216 /// suffix. Otherwise, it increments the patch version. |
| 217 Version get nextPatch { |
| 218 if (isPreRelease) { |
| 219 return new Version(major, minor, patch); |
| 220 } |
| 221 |
| 222 return _incrementPatch(); |
| 223 } |
| 224 |
| 225 /// Gets the next breaking version number that follows this one. |
| 226 /// |
| 227 /// Increments [major] if it's greater than zero, otherwise [minor], resets |
| 228 /// subsequent digits to zero, and strips any [preRelease] or [build] |
| 229 /// suffix. |
| 230 Version get nextBreaking { |
| 231 if (major == 0) { |
| 232 return _incrementMinor(); |
| 233 } |
| 234 |
| 235 return _incrementMajor(); |
| 236 } |
| 237 |
| 238 Version _incrementMajor() => new Version(major + 1, 0, 0); |
| 239 Version _incrementMinor() => new Version(major, minor + 1, 0); |
| 240 Version _incrementPatch() => new Version(major, minor, patch + 1); |
| 241 |
| 242 /// Tests if [other] matches this version exactly. |
| 243 bool allows(Version other) => this == other; |
| 244 |
| 245 bool allowsAll(VersionConstraint other) => other.isEmpty || other == this; |
| 246 |
| 247 bool allowsAny(VersionConstraint other) => other.allows(this); |
| 248 |
| 249 VersionConstraint intersect(VersionConstraint other) => |
| 250 other.allows(this) ? this : VersionConstraint.empty; |
| 251 |
| 252 VersionConstraint union(VersionConstraint other) { |
| 253 if (other.allows(this)) return other; |
| 254 |
| 255 if (other is VersionRange) { |
| 256 if (other.min == this) { |
| 257 return new VersionRange( |
| 258 min: other.min, max: other.max, |
| 259 includeMin: true, includeMax: other.includeMax); |
| 260 } |
| 261 |
| 262 if (other.max == this) { |
| 263 return new VersionRange( |
| 264 min: other.min, max: other.max, |
| 265 includeMin: other.includeMin, includeMax: true); |
| 266 } |
| 267 } |
| 268 |
| 269 return new VersionConstraint.unionOf([this, other]); |
| 270 } |
| 271 |
| 272 int compareTo(Version other) { |
| 273 if (major != other.major) return major.compareTo(other.major); |
| 274 if (minor != other.minor) return minor.compareTo(other.minor); |
| 275 if (patch != other.patch) return patch.compareTo(other.patch); |
| 276 |
| 277 // Pre-releases always come before no pre-release string. |
| 278 if (!isPreRelease && other.isPreRelease) return 1; |
| 279 if (!other.isPreRelease && isPreRelease) return -1; |
| 280 |
| 281 var comparison = _compareLists(preRelease, other.preRelease); |
| 282 if (comparison != 0) return comparison; |
| 283 |
| 284 // Builds always come after no build string. |
| 285 if (build.isEmpty && other.build.isNotEmpty) return -1; |
| 286 if (other.build.isEmpty && build.isNotEmpty) return 1; |
| 287 return _compareLists(build, other.build); |
| 288 } |
| 289 |
| 290 String toString() => _text; |
| 291 |
| 292 /// Compares a dot-separated component of two versions. |
| 293 /// |
| 294 /// This is used for the pre-release and build version parts. This follows |
| 295 /// Rule 12 of the Semantic Versioning spec (v2.0.0-rc.1). |
| 296 int _compareLists(List a, List b) { |
| 297 for (var i = 0; i < math.max(a.length, b.length); i++) { |
| 298 var aPart = (i < a.length) ? a[i] : null; |
| 299 var bPart = (i < b.length) ? b[i] : null; |
| 300 |
| 301 if (aPart == bPart) continue; |
| 302 |
| 303 // Missing parts come before present ones. |
| 304 if (aPart == null) return -1; |
| 305 if (bPart == null) return 1; |
| 306 |
| 307 if (aPart is num) { |
| 308 if (bPart is num) { |
| 309 // Compare two numbers. |
| 310 return aPart.compareTo(bPart); |
| 311 } else { |
| 312 // Numbers come before strings. |
| 313 return -1; |
| 314 } |
| 315 } else { |
| 316 if (bPart is num) { |
| 317 // Strings come after numbers. |
| 318 return 1; |
| 319 } else { |
| 320 // Compare two strings. |
| 321 return aPart.compareTo(bPart); |
| 322 } |
| 323 } |
| 324 } |
| 325 |
| 326 // The lists are entirely equal. |
| 327 return 0; |
| 328 } |
| 329 } |
OLD | NEW |