Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(470)

Side by Side Diff: utils/pub/version.dart

Issue 12310029: Allow whitespace in version constraints. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Don't allow mixing "any" with other constraints. Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | utils/tests/pub/version_test.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 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 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. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 /// Handles version numbers, following the [Semantic Versioning][semver] spec. 5 /// Handles version numbers, following the [Semantic Versioning][semver] spec.
6 /// 6 ///
7 /// [semver]: http://semver.org/ 7 /// [semver]: http://semver.org/
8 library version; 8 library version;
9 9
10 import 'dart:math'; 10 import 'dart:math';
11 11
12 import 'utils.dart'; 12 import 'utils.dart';
13 13
14
15 /// Regex that matches a version number at the beginning of a string.
16 final _START_VERSION = new RegExp(
17 r'^' // Start at beginning.
18 r'(\d+).(\d+).(\d+)' // Version number.
19 r'(-([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?' // Pre-release.
20 r'(\+([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?'); // Build.
21
22 /// Like [_START_VERSION] but matches the entire string.
23 final _COMPLETE_VERSION = new RegExp("${_START_VERSION.pattern}\$");
24
25 /// Parses a comparison operator ("<", ">", "<=", or ">=") at the beginning of
26 /// a string.
27 final _START_COMPARISON = new RegExp(r"^[<>]=?");
28
14 /// A parsed semantic version number. 29 /// A parsed semantic version number.
15 class Version implements Comparable<Version>, VersionConstraint { 30 class Version implements Comparable<Version>, VersionConstraint {
16 /// No released version: i.e. "0.0.0". 31 /// No released version: i.e. "0.0.0".
17 static Version get none => new Version(0, 0, 0); 32 static Version get none => new Version(0, 0, 0);
18
19 static final _PARSE_REGEX = new RegExp(
20 r'^' // Start at beginning.
21 r'(\d+).(\d+).(\d+)' // Version number.
22 r'(-([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?' // Pre-release.
23 r'(\+([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?' // Build.
24 r'$'); // Consume entire string.
25
26 /// The major version number: "1" in "1.2.3". 33 /// The major version number: "1" in "1.2.3".
27 final int major; 34 final int major;
28 35
29 /// The minor version number: "2" in "1.2.3". 36 /// The minor version number: "2" in "1.2.3".
30 final int minor; 37 final int minor;
31 38
32 /// The patch version number: "3" in "1.2.3". 39 /// The patch version number: "3" in "1.2.3".
33 final int patch; 40 final int patch;
34 41
35 /// The pre-release identifier: "foo" in "1.2.3-foo". May be `null`. 42 /// The pre-release identifier: "foo" in "1.2.3-foo". May be `null`.
36 final String preRelease; 43 final String preRelease;
37 44
38 /// The build identifier: "foo" in "1.2.3+foo". May be `null`. 45 /// The build identifier: "foo" in "1.2.3+foo". May be `null`.
39 final String build; 46 final String build;
40 47
41 /// Creates a new [Version] object. 48 /// Creates a new [Version] object.
42 Version(this.major, this.minor, this.patch, {String pre, this.build}) 49 Version(this.major, this.minor, this.patch, {String pre, this.build})
43 : preRelease = pre { 50 : preRelease = pre {
44 if (major < 0) throw new ArgumentError( 51 if (major < 0) throw new ArgumentError(
45 'Major version must be non-negative.'); 52 'Major version must be non-negative.');
46 if (minor < 0) throw new ArgumentError( 53 if (minor < 0) throw new ArgumentError(
47 'Minor version must be non-negative.'); 54 'Minor version must be non-negative.');
48 if (patch < 0) throw new ArgumentError( 55 if (patch < 0) throw new ArgumentError(
49 'Patch version must be non-negative.'); 56 'Patch version must be non-negative.');
50 } 57 }
51 58
52 /// Creates a new [Version] by parsing [text]. 59 /// Creates a new [Version] by parsing [text].
53 factory Version.parse(String text) { 60 factory Version.parse(String text) {
54 final match = _PARSE_REGEX.firstMatch(text); 61 final match = _COMPLETE_VERSION.firstMatch(text);
55 if (match == null) { 62 if (match == null) {
56 throw new FormatException('Could not parse "$text".'); 63 throw new FormatException('Could not parse "$text".');
57 } 64 }
58 65
59 try { 66 try {
60 int major = int.parse(match[1]); 67 int major = int.parse(match[1]);
61 int minor = int.parse(match[2]); 68 int minor = int.parse(match[2]);
62 int patch = int.parse(match[3]); 69 int patch = int.parse(match[3]);
63 70
64 String preRelease = match[5]; 71 String preRelease = match[5];
(...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after
139 return _compareStrings(build, other.build); 146 return _compareStrings(build, other.build);
140 } 147 }
141 148
142 return 0; 149 return 0;
143 } 150 }
144 151
145 int get hashCode => toString().hashCode; 152 int get hashCode => toString().hashCode;
146 153
147 String toString() { 154 String toString() {
148 var buffer = new StringBuffer(); 155 var buffer = new StringBuffer();
149 buffer.add('$major.$minor.$patch'); 156 buffer.write('$major.$minor.$patch');
150 if (preRelease != null) buffer.add('-$preRelease'); 157 if (preRelease != null) buffer.write('-$preRelease');
151 if (build != null) buffer.add('+$build'); 158 if (build != null) buffer.write('+$build');
152 return buffer.toString(); 159 return buffer.toString();
153 } 160 }
154 161
155 /// Compares the string part of two versions. This is used for the pre-release 162 /// Compares the string part of two versions. This is used for the pre-release
156 /// and build version parts. This follows Rule 12. of the Semantic Versioning 163 /// and build version parts. This follows Rule 12. of the Semantic Versioning
157 /// spec. 164 /// spec.
158 int _compareStrings(String a, String b) { 165 int _compareStrings(String a, String b) {
159 var aParts = _splitParts(a); 166 var aParts = _splitParts(a);
160 var bParts = _splitParts(b); 167 var bParts = _splitParts(b);
161 168
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after
207 /// version is valid or not. For example, a ">= 2.0.0" constraint allows any 214 /// version is valid or not. For example, a ">= 2.0.0" constraint allows any
208 /// version that is "2.0.0" or greater. Version objects themselves implement 215 /// version that is "2.0.0" or greater. Version objects themselves implement
209 /// this to match a specific version. 216 /// this to match a specific version.
210 abstract class VersionConstraint { 217 abstract class VersionConstraint {
211 /// A [VersionConstraint] that allows all versions. 218 /// A [VersionConstraint] that allows all versions.
212 static VersionConstraint any = new VersionRange(); 219 static VersionConstraint any = new VersionRange();
213 220
214 /// A [VersionConstraint] that allows no versions: i.e. the empty set. 221 /// A [VersionConstraint] that allows no versions: i.e. the empty set.
215 static VersionConstraint empty = const _EmptyVersion(); 222 static VersionConstraint empty = const _EmptyVersion();
216 223
217 /// Parses a version constraint. This string is a space-separated series of 224 /// Parses a version constraint. This string is either "any" or a series of
218 /// version parts. Each part can be one of: 225 /// version parts. Each part can be one of:
219 /// 226 ///
220 /// * A version string like `1.2.3`. In other words, anything that can be 227 /// * A version string like `1.2.3`. In other words, anything that can be
221 /// parsed by [Version.parse()]. 228 /// parsed by [Version.parse()].
222 /// * A comparison operator (`<`, `>`, `<=`, or `>=`) followed by a version 229 /// * A comparison operator (`<`, `>`, `<=`, or `>=`) followed by a version
223 /// string. There cannot be a space between the operator and the version. 230 /// string.
231 ///
232 /// Whitespace is ignored.
224 /// 233 ///
225 /// Examples: 234 /// Examples:
226 /// 235 ///
236 /// any
227 /// 1.2.3-alpha 237 /// 1.2.3-alpha
228 /// <=5.1.4 238 /// <=5.1.4
229 /// >2.0.4 <=2.4.6 239 /// >2.0.4 <= 2.4.6
230 factory VersionConstraint.parse(String text) { 240 factory VersionConstraint.parse(String text) {
231 if (text.trim() == '') { 241 // Handle the "any" constraint.
242 if (text.trim() == "any") return new VersionRange();
243
244 var originalText = text;
245 var constraints = <VersionConstraint>[];
246
247 void skipWhitespace() {
248 text = text.trim();
249 }
250
251 // Try to parse and consume a version number.
252 Version matchVersion() {
253 var version = _START_VERSION.firstMatch(text);
254 if (version == null) return null;
255
256 text = text.substring(version.end);
257 return new Version.parse(version[0]);
258 }
259
260 // Try to parse and consume a comparison operator followed by a version.
261 VersionConstraint matchComparison() {
262 var comparison = _START_COMPARISON.firstMatch(text);
263 if (comparison == null) return null;
264
265 var op = comparison[0];
266 text = text.substring(comparison.end);
267 skipWhitespace();
268
269 var version = matchVersion();
270 if (version == null) {
271 throw new FormatException('Expected version number after "$op" in '
272 '"$originalText", got "$text".');
273 }
274
275 switch (op) {
276 case '<=':
277 return new VersionRange(max: version, includeMax: true);
278 case '<':
279 return new VersionRange(max: version, includeMax: false);
280 case '>=':
281 return new VersionRange(min: version, includeMin: true);
282 case '>':
283 return new VersionRange(min: version, includeMin: false);
284 }
285 }
286
287 while (true) {
288 skipWhitespace();
289 if (text.isEmpty) break;
290
291 var version = matchVersion();
292 if (version != null) {
293 constraints.add(version);
294 continue;
295 }
296
297 var comparison = matchComparison();
298 if (comparison != null) {
299 constraints.add(comparison);
300 continue;
301 }
302
303 // If we got here, we couldn't parse the remaining string.
304 throw new FormatException('Could not parse version "$originalText". '
305 'Unknown text at "$text".');
306 }
307
308 if (constraints.isEmpty) {
232 throw new FormatException('Cannot parse an empty string.'); 309 throw new FormatException('Cannot parse an empty string.');
233 } 310 }
234 311
235 // Split it into space-separated parts.
236 var constraints = <VersionConstraint>[];
237 for (var part in text.split(' ')) {
238 constraints.add(_parseSingleConstraint(part));
239 }
240
241 return new VersionConstraint.intersection(constraints); 312 return new VersionConstraint.intersection(constraints);
242 } 313 }
243 314
244 /// Creates a new version constraint that is the intersection of 315 /// Creates a new version constraint that is the intersection of
245 /// [constraints]. It will only allow versions that all of those constraints 316 /// [constraints]. It will only allow versions that all of those constraints
246 /// allow. If constraints is empty, then it returns a VersionConstraint that 317 /// allow. If constraints is empty, then it returns a VersionConstraint that
247 /// allows all versions. 318 /// allows all versions.
248 factory VersionConstraint.intersection( 319 factory VersionConstraint.intersection(
249 Iterable<VersionConstraint> constraints) { 320 Iterable<VersionConstraint> constraints) {
250 var constraint = new VersionRange(); 321 var constraint = new VersionRange();
251 for (var other in constraints) { 322 for (var other in constraints) {
252 constraint = constraint.intersect(other); 323 constraint = constraint.intersect(other);
253 } 324 }
254 return constraint; 325 return constraint;
255 } 326 }
256 327
257 /// Returns `true` if this constraint allows no versions. 328 /// Returns `true` if this constraint allows no versions.
258 bool get isEmpty; 329 bool get isEmpty;
259 330
260 /// Returns `true` if this constraint allows all versions. 331 /// Returns `true` if this constraint allows all versions.
261 bool get isAny; 332 bool get isAny;
262 333
263 /// Returns `true` if this constraint allows [version]. 334 /// Returns `true` if this constraint allows [version].
264 bool allows(Version version); 335 bool allows(Version version);
265 336
266 /// Creates a new [VersionConstraint] that only allows [Version]s allowed by 337 /// Creates a new [VersionConstraint] that only allows [Version]s allowed by
267 /// both this and [other]. 338 /// both this and [other].
268 VersionConstraint intersect(VersionConstraint other); 339 VersionConstraint intersect(VersionConstraint other);
269
270 static VersionConstraint _parseSingleConstraint(String text) {
271 if (text == 'any') {
272 return new VersionRange();
273 }
274
275 // TODO(rnystrom): Consider other syntaxes for version constraints. This
276 // one is whitespace sensitive (you can't do "< 1.2.3") and "<" is
277 // unfortunately meaningful in YAML, requiring it to be quoted in a
278 // pubspec.
279 // See if it's a comparison operator followed by a version, like ">1.2.3".
280 var match = new RegExp(r"^([<>]=?)?(.*)$").firstMatch(text);
281 if (match != null) {
282 var comparison = match[1];
283 var version = new Version.parse(match[2]);
284 switch (match[1]) {
285 case '<=': return new VersionRange(max: version, includeMax: true);
286 case '<': return new VersionRange(max: version, includeMax: false);
287 case '>=': return new VersionRange(min: version, includeMin: true);
288 case '>': return new VersionRange(min: version, includeMin: false);
289 }
290 }
291
292 // Otherwise, it must be an explicit version.
293 return new Version.parse(text);
294 }
295 } 340 }
296 341
297 /// Constrains versions to a fall within a given range. If there is a minimum, 342 /// Constrains versions to a fall within a given range. If there is a minimum,
298 /// then this only allows versions that are at that minimum or greater. If there 343 /// then this only allows versions that are at that minimum or greater. If there
299 /// is a maximum, then only versions less than that are allowed. In other words, 344 /// is a maximum, then only versions less than that are allowed. In other words,
300 /// this allows `>= min, < max`. 345 /// this allows `>= min, < max`.
301 class VersionRange implements VersionConstraint { 346 class VersionRange implements VersionConstraint {
302 final Version min; 347 final Version min;
303 final Version max; 348 final Version max;
304 final bool includeMin; 349 final bool includeMin;
(...skipping 90 matching lines...) Expand 10 before | Expand all | Expand 10 after
395 } 440 }
396 441
397 throw new ArgumentError( 442 throw new ArgumentError(
398 'Unknown VersionConstraint type $other.'); 443 'Unknown VersionConstraint type $other.');
399 } 444 }
400 445
401 String toString() { 446 String toString() {
402 var buffer = new StringBuffer(); 447 var buffer = new StringBuffer();
403 448
404 if (min != null) { 449 if (min != null) {
405 buffer.add(includeMin ? '>=' : '>'); 450 buffer.write(includeMin ? '>=' : '>');
406 buffer.add(min); 451 buffer.write(min);
407 } 452 }
408 453
409 if (max != null) { 454 if (max != null) {
410 if (min != null) buffer.add(' '); 455 if (min != null) buffer.write(' ');
411 buffer.add(includeMax ? '<=' : '<'); 456 buffer.write(includeMax ? '<=' : '<');
412 buffer.add(max); 457 buffer.write(max);
413 } 458 }
414 459
415 if (min == null && max == null) buffer.add('any'); 460 if (min == null && max == null) buffer.write('any');
416 return buffer.toString(); 461 return buffer.toString();
417 } 462 }
418 } 463 }
419 464
420 class _EmptyVersion implements VersionConstraint { 465 class _EmptyVersion implements VersionConstraint {
421 const _EmptyVersion(); 466 const _EmptyVersion();
422 467
423 bool get isEmpty => true; 468 bool get isEmpty => true;
424 bool get isAny => false; 469 bool get isAny => false;
425 bool allows(Version other) => false; 470 bool allows(Version other) => false;
426 VersionConstraint intersect(VersionConstraint other) => this; 471 VersionConstraint intersect(VersionConstraint other) => this;
427 String toString() => '<empty>'; 472 String toString() => '<empty>';
428 } 473 }
OLDNEW
« no previous file with comments | « no previous file | utils/tests/pub/version_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698