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

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: 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') | utils/tests/pub/version_test.dart » ('J')
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"[<>]=?");
nweiz 2013/02/21 01:31:11 This should start with "^".
Bob Nystrom 2013/02/21 20:00:50 Oops. Done.
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 142 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 a series of version parts.
218 /// version parts. Each part can be one of: 225 /// 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 ///
227 /// 1.2.3-alpha 236 /// 1.2.3-alpha
228 /// <=5.1.4 237 /// <=5.1.4
229 /// >2.0.4 <=2.4.6 238 /// >2.0.4 <= 2.4.6
239 /// any
230 factory VersionConstraint.parse(String text) { 240 factory VersionConstraint.parse(String text) {
231 if (text.trim() == '') { 241 var originalText = text;
242 var constraints = <VersionConstraint>[];
243
244 void skipWhitespace() {
245 text = text.trim();
246 }
247
248 // Try to parse and consume "any".
249 VersionRange matchAny() {
250 // TODO(rnystrom): This doesn't require whitespace or a punctuator to
251 // separate "any". So this is valid: "anyany>1.0.0any". Is that OK?
nweiz 2013/02/21 01:31:11 It's weird that we allow "any" to be alongside any
Bob Nystrom 2013/02/21 20:00:50 You're exactly right. Thinking of "any" as a const
252 if (!text.startsWith("any")) return null;
253 text = text.substring("any".length);
254 return new VersionRange();
255 }
256
257 // Try to parse and consume a version number.
258 Version matchVersion() {
259 var version = _START_VERSION.firstMatch(text);
260 if (version == null) return null;
261
262 text = text.substring(version.end);
263 return new Version.parse(version[0]);
264 }
265
266 // Try to parse and consume a comparison operator followed by a version.
267 VersionConstraint matchComparison() {
268 var comparison = _START_COMPARISON.firstMatch(text);
269 if (comparison == null) return null;
270
271 var op = comparison[0];
272 text = text.substring(comparison.end);
273 skipWhitespace();
274
275 var version = matchVersion();
276 if (version == null) {
277 throw new FormatException('Expected version number after "$op" in '
278 '"$originalText", got "$text".');
nweiz 2013/02/21 01:31:11 I still wish these exceptions gave more context, a
Bob Nystrom 2013/02/21 20:00:50 There's not much Version can do here. What we can
279 }
280
281 switch (op) {
282 case '<=':
283 return new VersionRange(max: version, includeMax: true);
284 case '<':
285 return new VersionRange(max: version, includeMax: false);
286 case '>=':
287 return new VersionRange(min: version, includeMin: true);
288 case '>':
289 return new VersionRange(min: version, includeMin: false);
290 }
291 }
292
293 while (true) {
294 skipWhitespace();
295 if (text.isEmpty) break;
296
297 var any = matchAny();
298 if (any != null) {
299 constraints.add(any);
300 continue;
301 }
302
303 var version = matchVersion();
304 if (version != null) {
305 constraints.add(version);
306 continue;
307 }
308
309 var comparison = matchComparison();
310 if (comparison != null) {
311 constraints.add(comparison);
312 continue;
313 }
314
315 // If we got here, we couldn't parse the remaining string.
316 throw new FormatException('Could not parse version "$originalText". '
317 'Unknown text at "$text".');
318 }
319
320 if (constraints.isEmpty) {
232 throw new FormatException('Cannot parse an empty string.'); 321 throw new FormatException('Cannot parse an empty string.');
233 } 322 }
234 323
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); 324 return new VersionConstraint.intersection(constraints);
242 } 325 }
243 326
244 /// Creates a new version constraint that is the intersection of 327 /// Creates a new version constraint that is the intersection of
245 /// [constraints]. It will only allow versions that all of those constraints 328 /// [constraints]. It will only allow versions that all of those constraints
246 /// allow. If constraints is empty, then it returns a VersionConstraint that 329 /// allow. If constraints is empty, then it returns a VersionConstraint that
247 /// allows all versions. 330 /// allows all versions.
248 factory VersionConstraint.intersection( 331 factory VersionConstraint.intersection(
249 Iterable<VersionConstraint> constraints) { 332 Iterable<VersionConstraint> constraints) {
250 var constraint = new VersionRange(); 333 var constraint = new VersionRange();
251 for (var other in constraints) { 334 for (var other in constraints) {
252 constraint = constraint.intersect(other); 335 constraint = constraint.intersect(other);
253 } 336 }
254 return constraint; 337 return constraint;
255 } 338 }
256 339
257 /// Returns `true` if this constraint allows no versions. 340 /// Returns `true` if this constraint allows no versions.
258 bool get isEmpty; 341 bool get isEmpty;
259 342
260 /// Returns `true` if this constraint allows all versions. 343 /// Returns `true` if this constraint allows all versions.
261 bool get isAny; 344 bool get isAny;
262 345
263 /// Returns `true` if this constraint allows [version]. 346 /// Returns `true` if this constraint allows [version].
264 bool allows(Version version); 347 bool allows(Version version);
265 348
266 /// Creates a new [VersionConstraint] that only allows [Version]s allowed by 349 /// Creates a new [VersionConstraint] that only allows [Version]s allowed by
267 /// both this and [other]. 350 /// both this and [other].
268 VersionConstraint intersect(VersionConstraint other); 351 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 } 352 }
296 353
297 /// Constrains versions to a fall within a given range. If there is a minimum, 354 /// 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 355 /// 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, 356 /// is a maximum, then only versions less than that are allowed. In other words,
300 /// this allows `>= min, < max`. 357 /// this allows `>= min, < max`.
301 class VersionRange implements VersionConstraint { 358 class VersionRange implements VersionConstraint {
302 final Version min; 359 final Version min;
303 final Version max; 360 final Version max;
304 final bool includeMin; 361 final bool includeMin;
(...skipping 114 matching lines...) Expand 10 before | Expand all | Expand 10 after
419 476
420 class _EmptyVersion implements VersionConstraint { 477 class _EmptyVersion implements VersionConstraint {
421 const _EmptyVersion(); 478 const _EmptyVersion();
422 479
423 bool get isEmpty => true; 480 bool get isEmpty => true;
424 bool get isAny => false; 481 bool get isAny => false;
425 bool allows(Version other) => false; 482 bool allows(Version other) => false;
426 VersionConstraint intersect(VersionConstraint other) => this; 483 VersionConstraint intersect(VersionConstraint other) => this;
427 String toString() => '<empty>'; 484 String toString() => '<empty>';
428 } 485 }
OLDNEW
« no previous file with comments | « no previous file | utils/tests/pub/version_test.dart » ('j') | utils/tests/pub/version_test.dart » ('J')

Powered by Google App Engine
This is Rietveld 408576698