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'); | |
|
nweiz
2012/05/15 23:12:19
Why is this a separate library? Are you testing ou
Bob Nystrom
2012/05/16 00:27:27
Sort of. It doesn't depend on anything in pub, so
nweiz
2012/05/16 18:28:05
Neither does source.dart. I think if we want to go
Bob Nystrom
2012/05/16 18:39:52
I'm OK with that if you are, but I think we should
nweiz
2012/05/16 21:02:07
Sounds good.
| |
| 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. | |
|
nweiz
2012/05/15 23:12:19
I believe the way this is currnetly written, the f
Bob Nystrom
2012/05/16 00:27:27
Exactly right. Accidentally left that in from befo
| |
| 18 @'(\+([0-9A-Za-z-.]+(\.[0-9A-Za-z-.]+)*))?' // Build. | |
| 19 @'$'); // Consume entire string. | |
| 20 | |
| 21 /** | |
| 22 * Creates a new [Version] by parsing [text]. | |
| 23 */ | |
| 24 static Version parse(String text) { | |
|
nweiz
2012/05/15 23:12:19
Factory constructor?
Bob Nystrom
2012/05/16 00:27:27
Done.
| |
| 25 ensure(bool predicate) { | |
| 26 if (!predicate) throw new FormatException('Could not parse "$text".'); | |
| 27 } | |
| 28 | |
| 29 final match = _PARSE_REGEX.firstMatch(text); | |
| 30 ensure(match != null); | |
| 31 | |
| 32 try { | |
| 33 int major = Math.parseInt(match[1]); | |
| 34 int minor = Math.parseInt(match[2]); | |
| 35 int patch = Math.parseInt(match[3]); | |
| 36 | |
| 37 String preRelease = match[5]; | |
| 38 String build = match[8]; | |
| 39 | |
| 40 ensure(major >= 0); | |
| 41 ensure(minor >= 0); | |
| 42 ensure(patch >= 0); | |
|
nweiz
2012/05/15 23:12:19
I don't think it's possible for these to be < 0.
Bob Nystrom
2012/05/16 00:27:27
Done.
| |
| 43 | |
| 44 return new Version(major, minor, patch, pre: preRelease, build: build); | |
|
nweiz
2012/05/15 23:12:19
I don't like using explicit keyword arguments when
Bob Nystrom
2012/05/16 00:27:27
Done.
| |
| 45 } catch (BadNumberFormatException ex) { | |
| 46 ensure(false); | |
| 47 } | |
| 48 } | |
| 49 | |
| 50 /** The major version number: "1" in "1.2.3". */ | |
| 51 final int major; | |
| 52 | |
| 53 /** The minor version number: "2" in "1.2.3". */ | |
| 54 final int minor; | |
| 55 | |
| 56 /** The patch version number: "3" in "1.2.3". */ | |
| 57 final int patch; | |
| 58 | |
| 59 /** The pre-release identifier: "foo" in "1.2.3-foo". May be `null`. */ | |
| 60 final String preRelease; | |
| 61 | |
| 62 /** The build identifier: "foo" in "1.2.3+foo". May be `null`. */ | |
| 63 final String build; | |
| 64 | |
| 65 /** Creates a new [Version] object. */ | |
| 66 Version(this.major, this.minor, this.patch, [String pre, this.build]) | |
| 67 : preRelease = pre { | |
|
nweiz
2012/05/15 23:12:19
Why not use "this." for pre/preRelease?
Bob Nystrom
2012/05/16 00:27:27
I wanted the named argument to be "pre" for brevit
| |
| 68 if (major < 0) throw new IllegalArgumentException( | |
| 69 'Major version must be non-negative.'); | |
| 70 if (minor < 0) throw new IllegalArgumentException( | |
| 71 'Minor version must be non-negative.'); | |
| 72 if (patch < 0) throw new IllegalArgumentException( | |
| 73 'Patch version must be non-negative.'); | |
| 74 } | |
| 75 | |
| 76 bool operator ==(Version other) { | |
|
nweiz
2012/05/15 23:12:19
Shouldn't this take an untyped "other" parameter?
Bob Nystrom
2012/05/16 00:27:27
It did at first, but then the first line was:
if
nweiz
2012/05/16 18:28:05
The first line should actually be:
if (other is
Bob Nystrom
2012/05/16 18:39:52
Done.
| |
| 77 // TODO(rnystrom): Remove this once new equality semantics are implemented. | |
| 78 if (other == null) return false; | |
| 79 return compareTo(other) == 0; | |
| 80 } | |
| 81 | |
| 82 bool operator <(Version other) => compareTo(other) < 0; | |
| 83 bool operator >(Version other) => compareTo(other) > 0; | |
| 84 bool operator <=(Version other) => compareTo(other) <= 0; | |
| 85 bool operator >=(Version other) => compareTo(other) >= 0; | |
| 86 | |
| 87 /** Tests if [other] matches this version exactly. */ | |
| 88 bool allows(Version other) => this == other; | |
| 89 | |
| 90 int compareTo(Version other) { | |
| 91 if (major != other.major) return major.compareTo(other.major); | |
| 92 if (minor != other.minor) return minor.compareTo(other.minor); | |
| 93 if (patch != other.patch) return patch.compareTo(other.patch); | |
| 94 | |
| 95 if (preRelease != other.preRelease) { | |
| 96 // Pre-releases always come before no pre-release string. | |
| 97 if (preRelease == null) return 1; | |
| 98 if (other.preRelease == null) return -1; | |
| 99 | |
| 100 return _compareStrings(preRelease, other.preRelease); | |
| 101 } | |
| 102 | |
| 103 if (build != other.build) { | |
| 104 // Builds always come after no build string. | |
| 105 if (build == null) return -1; | |
| 106 if (other.build == null) return 1; | |
| 107 | |
| 108 return _compareStrings(build, other.build); | |
| 109 } | |
| 110 | |
| 111 return 0; | |
| 112 } | |
| 113 | |
| 114 String toString() { | |
| 115 var buffer = new StringBuffer(); | |
| 116 buffer.add('$major.$minor.$patch'); | |
| 117 if (preRelease != null) buffer.add('-$preRelease'); | |
| 118 if (build != null) buffer.add('+$build'); | |
| 119 return buffer.toString(); | |
| 120 } | |
| 121 | |
| 122 /** | |
| 123 * Compares the string part of two versions. This is used for the pre-release | |
| 124 * and build version parts. This follows Rule 12. of the Semantic Versioning | |
| 125 * spec. | |
| 126 */ | |
| 127 int _compareStrings(String a, String b) { | |
| 128 var aParts = _splitParts(a); | |
| 129 var bParts = _splitParts(b); | |
| 130 | |
| 131 for (int i = 0; i < Math.max(aParts.length, bParts.length); i++) { | |
| 132 var aPart = (i < aParts.length) ? aParts[i] : null; | |
| 133 var bPart = (i < bParts.length) ? bParts[i] : null; | |
| 134 | |
| 135 if (aPart != bPart) { | |
| 136 // Missing parts come before present ones. | |
| 137 if (aPart == null) return -1; | |
| 138 if (bPart == null) return 1; | |
| 139 | |
| 140 if (aPart is int) { | |
| 141 if (bPart is int) { | |
| 142 // Compare two numbers. | |
| 143 return aPart.compareTo(bPart); | |
| 144 } else { | |
| 145 // Numbers come before strings. | |
| 146 return -1; | |
| 147 } | |
| 148 } else { | |
| 149 if (bPart is int) { | |
| 150 // Strings come after numbers. | |
| 151 return 1; | |
| 152 } else { | |
| 153 // Compare two strings. | |
| 154 return aPart.compareTo(bPart); | |
| 155 } | |
| 156 } | |
| 157 } | |
| 158 } | |
| 159 } | |
| 160 | |
| 161 /** | |
| 162 * Splits a string of dot-delimited identifiers into their component parts. | |
| 163 * Identifiers that are numeric are converted to numbers. | |
| 164 */ | |
| 165 List _splitParts(String text) { | |
| 166 return text.split('.').map((part) { | |
| 167 try { | |
| 168 return Math.parseInt(part); | |
| 169 } catch (BadNumberFormatException ex) { | |
| 170 // Not a number. | |
| 171 return part; | |
| 172 } | |
| 173 }); | |
| 174 } | |
| 175 } | |
| 176 | |
| 177 /** | |
| 178 * A [VersionConstraint] is a predicate that can determine whether a given | |
| 179 * version is valid or not. For example, a ">= 2.0.0" constraint allows any | |
| 180 * version that is "2.0.0" or greater. Version objects themselves implement | |
| 181 * this to match a specific version. | |
| 182 */ | |
| 183 interface VersionConstraint { | |
| 184 bool allows(Version version); | |
| 185 } | |
| 186 | |
| 187 /** | |
| 188 * Constrains versions to a fall within a given range. If there is a minimum, | |
| 189 * then this only allows versions that are at that minimum or greater. If there | |
| 190 * is a maximum, then only versions less than that are allowed. In other words, | |
| 191 * this allows `>= min, < max`. | |
| 192 */ | |
| 193 class VersionRange implements VersionConstraint { | |
|
nweiz
2012/05/15 23:12:19
Won't users potentially want to specify "> min" or
Bob Nystrom
2012/05/16 00:27:27
Yeah, I thought about that. I know we'll want thes
| |
| 194 final Version min; | |
| 195 final Version max; | |
| 196 | |
| 197 VersionRange([this.min, this.max]) { | |
| 198 if (min != null && max != null && min > max) { | |
| 199 throw new IllegalArgumentException( | |
| 200 'Maximum version ("$max") must be less than minimum ("$min").'); | |
| 201 } | |
| 202 } | |
| 203 | |
| 204 /** Tests if [other] matches falls within this version range. */ | |
| 205 bool allows(Version other) { | |
| 206 if (min != null && other < min) return false; | |
| 207 if (max != null && other >= max) return false; | |
| 208 return true; | |
| 209 } | |
| 210 } | |
| 211 | |
| 212 /** Thrown by [Version.parse()] if the argument isn't a valid version string. */ | |
| 213 class FormatException implements Exception { | |
| 214 final String message; | |
| 215 | |
| 216 FormatException(this.message); | |
| 217 | |
| 218 String toString() => message; | |
| 219 } | |
| OLD | NEW |