Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2012, 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 /** | |
| 6 * Handles version numbers, following the [Semantic Versioning][semver] spec. | |
| 7 * | |
| 8 * [semver]: http://semver.org/ | |
| 9 */ | |
| 10 #library('pub_version'); | |
| 11 | |
| 12 /** A parsed semantic version number. */ | |
| 13 class Version implements Comparable, VersionConstraint { | |
| 14 static final _PARSE_REGEX = const RegExp( | |
| 15 @'^' // Start at beginning. | |
| 16 @'(\d+).(\d+).(\d+)' // Version number. | |
| 17 @'(-([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?' // Pre-release. | |
| 18 @'(\+([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?' // Build. | |
| 19 @'$'); // Consume entire string. | |
| 20 | |
| 21 /** The major version number: "1" in "1.2.3". */ | |
| 22 final int major; | |
| 23 | |
| 24 /** The minor version number: "2" in "1.2.3". */ | |
| 25 final int minor; | |
| 26 | |
| 27 /** The patch version number: "3" in "1.2.3". */ | |
| 28 final int patch; | |
| 29 | |
| 30 /** The pre-release identifier: "foo" in "1.2.3-foo". May be `null`. */ | |
| 31 final String preRelease; | |
| 32 | |
| 33 /** The build identifier: "foo" in "1.2.3+foo". May be `null`. */ | |
| 34 final String build; | |
| 35 | |
| 36 /** Creates a new [Version] object. */ | |
| 37 Version(this.major, this.minor, this.patch, [String pre, this.build]) | |
| 38 : preRelease = pre { | |
| 39 if (major < 0) throw new IllegalArgumentException( | |
| 40 'Major version must be non-negative.'); | |
| 41 if (minor < 0) throw new IllegalArgumentException( | |
| 42 'Minor version must be non-negative.'); | |
| 43 if (patch < 0) throw new IllegalArgumentException( | |
| 44 'Patch version must be non-negative.'); | |
| 45 } | |
| 46 | |
| 47 /** | |
| 48 * Creates a new [Version] by parsing [text]. | |
| 49 */ | |
| 50 factory Version.parse(String text) { | |
| 51 final match = _PARSE_REGEX.firstMatch(text); | |
| 52 if (match == null) { | |
| 53 throw new FormatException('Could not parse "$text".'); | |
| 54 } | |
| 55 | |
| 56 try { | |
| 57 int major = Math.parseInt(match[1]); | |
| 58 int minor = Math.parseInt(match[2]); | |
| 59 int patch = Math.parseInt(match[3]); | |
| 60 | |
| 61 String preRelease = match[5]; | |
| 62 String build = match[8]; | |
| 63 | |
| 64 return new Version(major, minor, patch, preRelease, build); | |
| 65 } catch (BadNumberFormatException ex) { | |
| 66 throw new FormatException('Could not parse "$text".'); | |
| 67 } | |
| 68 } | |
| 69 | |
| 70 bool operator ==(Version other) { | |
| 71 if (other is! Version) return false; | |
| 72 return compareTo(other) == 0; | |
| 73 } | |
| 74 | |
| 75 bool operator <(Version other) => compareTo(other) < 0; | |
| 76 bool operator >(Version other) => compareTo(other) > 0; | |
| 77 bool operator <=(Version other) => compareTo(other) <= 0; | |
| 78 bool operator >=(Version other) => compareTo(other) >= 0; | |
| 79 | |
| 80 /** Tests if [other] matches this version exactly. */ | |
| 81 bool allows(Version other) => this == other; | |
| 82 | |
| 83 int compareTo(Version other) { | |
| 84 if (major != other.major) return major.compareTo(other.major); | |
| 85 if (minor != other.minor) return minor.compareTo(other.minor); | |
| 86 if (patch != other.patch) return patch.compareTo(other.patch); | |
| 87 | |
| 88 if (preRelease != other.preRelease) { | |
| 89 // Pre-releases always come before no pre-release string. | |
| 90 if (preRelease == null) return 1; | |
| 91 if (other.preRelease == null) return -1; | |
| 92 | |
| 93 return _compareStrings(preRelease, other.preRelease); | |
| 94 } | |
| 95 | |
| 96 if (build != other.build) { | |
| 97 // Builds always come after no build string. | |
| 98 if (build == null) return -1; | |
| 99 if (other.build == null) return 1; | |
| 100 | |
| 101 return _compareStrings(build, other.build); | |
| 102 } | |
| 103 | |
| 104 return 0; | |
| 105 } | |
| 106 | |
| 107 String toString() { | |
| 108 var buffer = new StringBuffer(); | |
| 109 buffer.add('$major.$minor.$patch'); | |
| 110 if (preRelease != null) buffer.add('-$preRelease'); | |
| 111 if (build != null) buffer.add('+$build'); | |
| 112 return buffer.toString(); | |
| 113 } | |
| 114 | |
| 115 /** | |
| 116 * Compares the string part of two versions. This is used for the pre-release | |
| 117 * and build version parts. This follows Rule 12. of the Semantic Versioning | |
| 118 * spec. | |
| 119 */ | |
| 120 int _compareStrings(String a, String b) { | |
| 121 var aParts = _splitParts(a); | |
| 122 var bParts = _splitParts(b); | |
| 123 | |
| 124 for (int i = 0; i < Math.max(aParts.length, bParts.length); i++) { | |
| 125 var aPart = (i < aParts.length) ? aParts[i] : null; | |
| 126 var bPart = (i < bParts.length) ? bParts[i] : null; | |
| 127 | |
| 128 if (aPart != bPart) { | |
| 129 // Missing parts come before present ones. | |
| 130 if (aPart == null) return -1; | |
| 131 if (bPart == null) return 1; | |
| 132 | |
| 133 if (aPart is int) { | |
| 134 if (bPart is int) { | |
| 135 // Compare two numbers. | |
| 136 return aPart.compareTo(bPart); | |
| 137 } else { | |
| 138 // Numbers come before strings. | |
| 139 return -1; | |
| 140 } | |
| 141 } else { | |
| 142 if (bPart is int) { | |
| 143 // Strings come after numbers. | |
| 144 return 1; | |
| 145 } else { | |
| 146 // Compare two strings. | |
| 147 return aPart.compareTo(bPart); | |
| 148 } | |
| 149 } | |
| 150 } | |
| 151 } | |
| 152 } | |
| 153 | |
| 154 /** | |
| 155 * Splits a string of dot-delimited identifiers into their component parts. | |
| 156 * Identifiers that are numeric are converted to numbers. | |
| 157 */ | |
| 158 List _splitParts(String text) { | |
| 159 return text.split('.').map((part) { | |
| 160 try { | |
| 161 return Math.parseInt(part); | |
| 162 } catch (BadNumberFormatException ex) { | |
| 163 // Not a number. | |
| 164 return part; | |
| 165 } | |
| 166 }); | |
| 167 } | |
| 168 } | |
| 169 | |
| 170 /** | |
| 171 * A [VersionConstraint] is a predicate that can determine whether a given | |
| 172 * version is valid or not. For example, a ">= 2.0.0" constraint allows any | |
| 173 * version that is "2.0.0" or greater. Version objects themselves implement | |
| 174 * this to match a specific version. | |
| 175 */ | |
| 176 interface VersionConstraint { | |
|
Sean Eagan
2012/05/17 15:35:39
couldn't this just be:
typedef bool VersionConstr
Bob Nystrom
2012/05/17 16:48:30
It could be right now, but later patches will be a
| |
| 177 bool allows(Version version); | |
| 178 } | |
| 179 | |
| 180 /** | |
| 181 * Constrains versions to a fall within a given range. If there is a minimum, | |
| 182 * then this only allows versions that are at that minimum or greater. If there | |
| 183 * is a maximum, then only versions less than that are allowed. In other words, | |
| 184 * this allows `>= min, < max`. | |
| 185 */ | |
| 186 class VersionRange implements VersionConstraint { | |
|
Sean Eagan
2012/05/17 15:35:39
There should be a generic interface for intervals
Bob Nystrom
2012/05/17 16:48:30
I like this idea, but sometimes I think you can be
| |
| 187 final Version min; | |
| 188 final Version max; | |
| 189 | |
| 190 VersionRange([this.min, this.max]) { | |
|
Sean Eagan
2012/05/17 15:35:39
As in the above linked gist, you can determine the
Bob Nystrom
2012/05/17 16:48:30
At least in the use case of versions, my hunch is
| |
| 191 if (min != null && max != null && min > max) { | |
| 192 throw new IllegalArgumentException( | |
| 193 'Maximum version ("$max") must be less than minimum ("$min").'); | |
| 194 } | |
| 195 } | |
| 196 | |
| 197 /** Tests if [other] matches falls within this version range. */ | |
| 198 bool allows(Version other) { | |
| 199 if (min != null && other < min) return false; | |
| 200 if (max != null && other >= max) return false; | |
| 201 return true; | |
| 202 } | |
| 203 } | |
| 204 | |
| 205 /** Thrown by [Version.parse()] if the argument isn't a valid version string. */ | |
| 206 class FormatException implements Exception { | |
| 207 final String message; | |
| 208 | |
| 209 FormatException(this.message); | |
| 210 | |
| 211 String toString() => message; | |
| 212 } | |
| OLD | NEW |