| OLD | NEW |
| 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 /// Generic utility functions. Stuff that should possibly be in core. | 5 /// Generic utility functions. Stuff that should possibly be in core. |
| 6 import 'dart:async'; | 6 import 'dart:async'; |
| 7 import "dart:convert"; | 7 import "dart:convert"; |
| 8 import 'dart:io'; | 8 import 'dart:io'; |
| 9 import 'dart:math' as math; | 9 import 'dart:math' as math; |
| 10 | 10 |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 48 String toString() => '($first, $last)'; | 48 String toString() => '($first, $last)'; |
| 49 | 49 |
| 50 bool operator==(other) { | 50 bool operator==(other) { |
| 51 if (other is! Pair) return false; | 51 if (other is! Pair) return false; |
| 52 return other.first == first && other.last == last; | 52 return other.first == first && other.last == last; |
| 53 } | 53 } |
| 54 | 54 |
| 55 int get hashCode => first.hashCode ^ last.hashCode; | 55 int get hashCode => first.hashCode ^ last.hashCode; |
| 56 } | 56 } |
| 57 | 57 |
| 58 /// A completer that waits until all added [Future]s complete. | |
| 59 // TODO(rnystrom): Copied from web_components. Remove from here when it gets | |
| 60 // added to dart:core. (See #6626.) | |
| 61 class FutureGroup<T> { | |
| 62 int _pending = 0; | |
| 63 Completer<List<T>> _completer = new Completer<List<T>>(); | |
| 64 final List<Future<T>> futures = <Future<T>>[]; | |
| 65 bool completed = false; | |
| 66 | |
| 67 final List<T> _values = <T>[]; | |
| 68 | |
| 69 /// Wait for [task] to complete. | |
| 70 Future<T> add(Future<T> task) { | |
| 71 if (completed) { | |
| 72 throw new StateError("The FutureGroup has already completed."); | |
| 73 } | |
| 74 | |
| 75 _pending++; | |
| 76 futures.add(task.then((value) { | |
| 77 if (completed) return; | |
| 78 | |
| 79 _pending--; | |
| 80 _values.add(value); | |
| 81 | |
| 82 if (_pending <= 0) { | |
| 83 completed = true; | |
| 84 _completer.complete(_values); | |
| 85 } | |
| 86 }).catchError((e, stackTrace) { | |
| 87 if (completed) return; | |
| 88 | |
| 89 completed = true; | |
| 90 _completer.completeError(e, stackTrace); | |
| 91 })); | |
| 92 | |
| 93 return task; | |
| 94 } | |
| 95 | |
| 96 Future<List> get future => _completer.future; | |
| 97 } | |
| 98 | |
| 99 /// Like [new Future], but avoids around issue 11911 by using [new Future.value] | 58 /// Like [new Future], but avoids around issue 11911 by using [new Future.value] |
| 100 /// under the covers. | 59 /// under the covers. |
| 101 Future newFuture(callback()) => new Future.value().then((_) => callback()); | 60 Future newFuture(callback()) => new Future.value().then((_) => callback()); |
| 102 | 61 |
| 103 /// Runs [callback] in an error zone and pipes any unhandled error to the | 62 /// Runs [callback] in an error zone and pipes any unhandled error to the |
| 104 /// returned [Future]. | 63 /// returned [Future]. |
| 105 /// | 64 /// |
| 106 /// If the returned [Future] produces an error, its stack trace will always be a | 65 /// If the returned [Future] produces an error, its stack trace will always be a |
| 107 /// [Chain]. By default, this chain will contain only the local stack trace, but | 66 /// [Chain]. By default, this chain will contain only the local stack trace, but |
| 108 /// if [captureStackChains] is passed, it will contain the full stack chain for | 67 /// if [captureStackChains] is passed, it will contain the full stack chain for |
| (...skipping 29 matching lines...) Expand all Loading... |
| 138 }); | 97 }); |
| 139 } | 98 } |
| 140 | 99 |
| 141 return completer.future; | 100 return completer.future; |
| 142 } | 101 } |
| 143 | 102 |
| 144 /// Like [Future.wait], but prints all errors from the futures as they occur and | 103 /// Like [Future.wait], but prints all errors from the futures as they occur and |
| 145 /// only returns once all Futures have completed, successfully or not. | 104 /// only returns once all Futures have completed, successfully or not. |
| 146 /// | 105 /// |
| 147 /// This will wrap the first error thrown in a [SilentException] and rethrow it. | 106 /// This will wrap the first error thrown in a [SilentException] and rethrow it. |
| 148 Future waitAndPrintErrors(Iterable<Future> futures) { | 107 Future<List/*<T>*/> waitAndPrintErrors/*<T>*/(Iterable<Future/*<T>*/> futures) { |
| 149 return Future.wait(futures.map((future) { | 108 return Future.wait(futures.map((future) { |
| 150 return future.catchError((error, stackTrace) { | 109 return future.catchError((error, stackTrace) { |
| 151 log.exception(error, stackTrace); | 110 log.exception(error, stackTrace); |
| 152 throw error; | 111 throw error; |
| 153 }); | 112 }); |
| 154 })).catchError((error, stackTrace) { | 113 })).catchError((error, stackTrace) { |
| 155 throw new SilentException(error, stackTrace); | 114 throw new SilentException(error, stackTrace); |
| 156 }); | 115 }); |
| 157 } | 116 } |
| 158 | 117 |
| 159 /// Returns a [StreamTransformer] that will call [onDone] when the stream | 118 /// Returns a [StreamTransformer] that will call [onDone] when the stream |
| 160 /// completes. | 119 /// completes. |
| 161 /// | 120 /// |
| 162 /// The stream will be passed through unchanged. | 121 /// The stream will be passed through unchanged. |
| 163 StreamTransformer onDoneTransformer(void onDone()) { | 122 StreamTransformer/*<T, T>*/ onDoneTransformer/*<T>*/(void onDone()) { |
| 164 return new StreamTransformer.fromHandlers(handleDone: (sink) { | 123 return new StreamTransformer/*<T, T>*/.fromHandlers(handleDone: (sink) { |
| 165 onDone(); | 124 onDone(); |
| 166 sink.close(); | 125 sink.close(); |
| 167 }); | 126 }); |
| 168 } | 127 } |
| 169 | 128 |
| 170 // TODO(rnystrom): Move into String? | 129 // TODO(rnystrom): Move into String? |
| 171 /// Pads [source] to [length] by adding spaces at the end. | 130 /// Pads [source] to [length] by adding spaces at the end. |
| 172 String padRight(String source, int length) { | 131 String padRight(String source, int length) { |
| 173 final result = new StringBuffer(); | 132 final result = new StringBuffer(); |
| 174 result.write(source); | 133 result.write(source); |
| (...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 267 | 226 |
| 268 try { | 227 try { |
| 269 return new InternetAddress(host).isLoopback; | 228 return new InternetAddress(host).isLoopback; |
| 270 } on ArgumentError catch (_) { | 229 } on ArgumentError catch (_) { |
| 271 // The host isn't an IP address and isn't "localhost', so it's almost | 230 // The host isn't an IP address and isn't "localhost', so it's almost |
| 272 // certainly not a loopback host. | 231 // certainly not a loopback host. |
| 273 return false; | 232 return false; |
| 274 } | 233 } |
| 275 } | 234 } |
| 276 | 235 |
| 277 /// Flattens nested lists inside an iterable into a single list containing only | |
| 278 /// non-list elements. | |
| 279 List flatten(Iterable nested) { | |
| 280 var result = []; | |
| 281 helper(list) { | |
| 282 for (var element in list) { | |
| 283 if (element is List) { | |
| 284 helper(element); | |
| 285 } else { | |
| 286 result.add(element); | |
| 287 } | |
| 288 } | |
| 289 } | |
| 290 helper(nested); | |
| 291 return result; | |
| 292 } | |
| 293 | |
| 294 /// Randomly chooses a single element in [elements]. | 236 /// Randomly chooses a single element in [elements]. |
| 295 /*=T*/ choose/*<T>*/(List/*<T>*/ elements) => | 237 /*=T*/ choose/*<T>*/(List/*<T>*/ elements) => |
| 296 elements[random.nextInt(elements.length)]; | 238 elements[random.nextInt(elements.length)]; |
| 297 | 239 |
| 298 /// Returns a set containing all elements in [minuend] that are not in | 240 /// Returns a set containing all elements in [minuend] that are not in |
| 299 /// [subtrahend]. | 241 /// [subtrahend]. |
| 300 Set setMinus(Iterable minuend, Iterable subtrahend) { | 242 Set setMinus(Iterable minuend, Iterable subtrahend) { |
| 301 var minuendSet = new Set.from(minuend); | 243 var minuendSet = new Set.from(minuend); |
| 302 minuendSet.removeAll(subtrahend); | 244 minuendSet.removeAll(subtrahend); |
| 303 return minuendSet; | 245 return minuendSet; |
| 304 } | 246 } |
| 305 | 247 |
| 306 /// Returns whether there's any overlap between [set1] and [set2]. | 248 /// Returns whether there's any overlap between [set1] and [set2]. |
| 307 bool overlaps(Set set1, Set set2) { | 249 bool overlaps(Set set1, Set set2) { |
| 308 // Iterate through the smaller set. | 250 // Iterate through the smaller set. |
| 309 var smaller = set1.length > set2.length ? set1 : set2; | 251 var smaller = set1.length > set2.length ? set1 : set2; |
| 310 var larger = smaller == set1 ? set2 : set1; | 252 var larger = smaller == set1 ? set2 : set1; |
| 311 return smaller.any(larger.contains); | 253 return smaller.any(larger.contains); |
| 312 } | 254 } |
| 313 | 255 |
| 314 /// Returns a list containing the sorted elements of [iter]. | 256 /// Returns a list containing the sorted elements of [iter]. |
| 315 List ordered(Iterable<Comparable> iter) { | 257 List/*<T>*/ ordered/*<T extends Comparable<T>>*/(Iterable/*<T>*/ iter) { |
| 316 var list = iter.toList(); | 258 var list = iter.toList(); |
| 317 list.sort(); | 259 list.sort(); |
| 318 return list; | 260 return list; |
| 319 } | 261 } |
| 320 | 262 |
| 321 /// Returns the element of [iter] for which [f] returns the minimum value. | 263 /// Returns the element of [iter] for which [f] returns the minimum value. |
| 322 minBy(Iterable iter, Comparable f(element)) { | 264 minBy(Iterable iter, Comparable f(element)) { |
| 323 var min = null; | 265 var min = null; |
| 324 var minComparable = null; | 266 var minComparable = null; |
| 325 for (var element in iter) { | 267 for (var element in iter) { |
| (...skipping 13 matching lines...) Expand all Loading... |
| 339 /// (3, 4)]`. | 281 /// (3, 4)]`. |
| 340 Iterable<Pair> pairs(Iterable iter) { | 282 Iterable<Pair> pairs(Iterable iter) { |
| 341 var previous = iter.first; | 283 var previous = iter.first; |
| 342 return iter.skip(1).map((element) { | 284 return iter.skip(1).map((element) { |
| 343 var oldPrevious = previous; | 285 var oldPrevious = previous; |
| 344 previous = element; | 286 previous = element; |
| 345 return new Pair(oldPrevious, element); | 287 return new Pair(oldPrevious, element); |
| 346 }); | 288 }); |
| 347 } | 289 } |
| 348 | 290 |
| 349 /// Creates a new map from [map] with new keys and values. | |
| 350 /// | |
| 351 /// The return values of [key] are used as the keys and the return values of | |
| 352 /// [value] are used as the values for the new map. | |
| 353 /// | |
| 354 /// [key] defaults to returning the original key and [value] defaults to | |
| 355 /// returning the original value. | |
| 356 Map mapMap(Map map, {key(key, value), value(key, value)}) { | |
| 357 if (key == null) key = (key, _) => key; | |
| 358 if (value == null) value = (_, value) => value; | |
| 359 | |
| 360 var result = {}; | |
| 361 map.forEach((mapKey, mapValue) { | |
| 362 result[key(mapKey, mapValue)] = value(mapKey, mapValue); | |
| 363 }); | |
| 364 return result; | |
| 365 } | |
| 366 | |
| 367 /// Like [Map.fromIterable], but [key] and [value] may return [Future]s. | |
| 368 Future<Map> mapFromIterableAsync(Iterable iter, {key(element), | |
| 369 value(element)}) { | |
| 370 if (key == null) key = (element) => element; | |
| 371 if (value == null) value = (element) => element; | |
| 372 | |
| 373 var map = new Map(); | |
| 374 return Future.wait(iter.map((element) { | |
| 375 return Future.wait([ | |
| 376 new Future.sync(() => key(element)), | |
| 377 new Future.sync(() => value(element)) | |
| 378 ]).then((results) { | |
| 379 map[results[0]] = results[1]; | |
| 380 }); | |
| 381 })).then((_) => map); | |
| 382 } | |
| 383 | |
| 384 /// Returns a new map with all entries in both [map1] and [map2]. | |
| 385 /// | |
| 386 /// If there are overlapping keys, [map2]'s value wins. | |
| 387 Map mergeMaps(Map map1, Map map2) { | |
| 388 var result = {}; | |
| 389 result.addAll(map1); | |
| 390 result.addAll(map2); | |
| 391 return result; | |
| 392 } | |
| 393 | |
| 394 /// Returns the transitive closure of [graph]. | |
| 395 /// | |
| 396 /// This assumes [graph] represents a graph with a vertex for each key and an | |
| 397 /// edge betweek each key and the values for that key. | |
| 398 Map<dynamic, Set> transitiveClosure(Map<dynamic, Iterable> graph) { | |
| 399 // This uses the Floyd-Warshall algorithm | |
| 400 // (https://en.wikipedia.org/wiki/Floyd%E2%80%93Warshall_algorithm). | |
| 401 var result = {}; | |
| 402 graph.forEach((vertex, edges) { | |
| 403 result[vertex] = new Set.from(edges)..add(vertex); | |
| 404 }); | |
| 405 | |
| 406 for (var vertex1 in graph.keys) { | |
| 407 for (var vertex2 in graph.keys) { | |
| 408 for (var vertex3 in graph.keys) { | |
| 409 if (result[vertex2].contains(vertex1) && | |
| 410 result[vertex1].contains(vertex3)) { | |
| 411 result[vertex2].add(vertex3); | |
| 412 } | |
| 413 } | |
| 414 } | |
| 415 } | |
| 416 | |
| 417 return result; | |
| 418 } | |
| 419 | |
| 420 /// Given a list of filenames, returns a set of patterns that can be used to | 291 /// Given a list of filenames, returns a set of patterns that can be used to |
| 421 /// filter for those filenames. | 292 /// filter for those filenames. |
| 422 /// | 293 /// |
| 423 /// For a given path, that path ends with some string in the returned set if | 294 /// For a given path, that path ends with some string in the returned set if |
| 424 /// and only if that path's basename is in [files]. | 295 /// and only if that path's basename is in [files]. |
| 425 Set<String> createFileFilter(Iterable<String> files) { | 296 Set<String> createFileFilter(Iterable<String> files) { |
| 426 return files.expand((file) { | 297 return files.expand/*<String>*/((file) { |
| 427 var result = ["/$file"]; | 298 var result = ["/$file"]; |
| 428 if (Platform.operatingSystem == 'windows') result.add("\\$file"); | 299 if (Platform.operatingSystem == 'windows') result.add("\\$file"); |
| 429 return result; | 300 return result; |
| 430 }).toSet(); | 301 }).toSet(); |
| 431 } | 302 } |
| 432 | 303 |
| 433 /// Given a blacklist of directory names, returns a set of patterns that can | 304 /// Given a blacklist of directory names, returns a set of patterns that can |
| 434 /// be used to filter for those directory names. | 305 /// be used to filter for those directory names. |
| 435 /// | 306 /// |
| 436 /// For a given path, that path contains some string in the returned set if | 307 /// For a given path, that path contains some string in the returned set if |
| 437 /// and only if one of that path's components is in [dirs]. | 308 /// and only if one of that path's components is in [dirs]. |
| 438 Set<String> createDirectoryFilter(Iterable<String> dirs) { | 309 Set<String> createDirectoryFilter(Iterable<String> dirs) { |
| 439 return dirs.expand((dir) { | 310 return dirs.expand/*<String>*/((dir) { |
| 440 var result = ["/$dir/"]; | 311 var result = ["/$dir/"]; |
| 441 if (Platform.operatingSystem == 'windows') { | 312 if (Platform.operatingSystem == 'windows') { |
| 442 result..add("/$dir\\")..add("\\$dir/")..add("\\$dir\\"); | 313 result..add("/$dir\\")..add("\\$dir/")..add("\\$dir\\"); |
| 443 } | 314 } |
| 444 return result; | 315 return result; |
| 445 }).toSet(); | 316 }).toSet(); |
| 446 } | 317 } |
| 447 | 318 |
| 448 /// Returns the maximum value in [iter] by [compare]. | 319 /// Returns the maximum value in [iter] by [compare]. |
| 449 /// | 320 /// |
| (...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 526 /// | 397 /// |
| 527 /// For example, reading asynchronously from a non-existent file will return a | 398 /// For example, reading asynchronously from a non-existent file will return a |
| 528 /// stream that fails on the first chunk. In order to handle that more | 399 /// stream that fails on the first chunk. In order to handle that more |
| 529 /// gracefully, you may want to check that the stream looks like it's working | 400 /// gracefully, you may want to check that the stream looks like it's working |
| 530 /// before you pipe the stream to something else. | 401 /// before you pipe the stream to something else. |
| 531 /// | 402 /// |
| 532 /// This lets you do that. It returns a [Future] that completes to a [Stream] | 403 /// This lets you do that. It returns a [Future] that completes to a [Stream] |
| 533 /// emitting the same values and errors as [stream], but only if at least one | 404 /// emitting the same values and errors as [stream], but only if at least one |
| 534 /// value can be read successfully. If an error occurs before any values are | 405 /// value can be read successfully. If an error occurs before any values are |
| 535 /// emitted, the returned Future completes to that error. | 406 /// emitted, the returned Future completes to that error. |
| 536 Future<Stream> validateStream(Stream stream) { | 407 Future<Stream/*<T>*/> validateStream/*<T>*/(Stream/*<T>*/ stream) { |
| 537 var completer = new Completer<Stream>(); | 408 var completer = new Completer<Stream>(); |
| 538 var controller = new StreamController(sync: true); | 409 var controller = new StreamController(sync: true); |
| 539 | 410 |
| 540 StreamSubscription subscription; | 411 StreamSubscription subscription; |
| 541 subscription = stream.listen((value) { | 412 subscription = stream.listen((value) { |
| 542 // We got a value, so the stream is valid. | 413 // We got a value, so the stream is valid. |
| 543 if (!completer.isCompleted) completer.complete(controller.stream); | 414 if (!completer.isCompleted) completer.complete(controller.stream); |
| 544 controller.add(value); | 415 controller.add(value); |
| 545 }, onError: (error, [stackTrace]) { | 416 }, onError: (error, [stackTrace]) { |
| 546 // If the error came after values, it's OK. | 417 // If the error came after values, it's OK. |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 587 Pair<Stream, StreamSubscription> streamWithSubscription(Stream stream) { | 458 Pair<Stream, StreamSubscription> streamWithSubscription(Stream stream) { |
| 588 var controller = | 459 var controller = |
| 589 stream.isBroadcast ? new StreamController.broadcast(sync: true) | 460 stream.isBroadcast ? new StreamController.broadcast(sync: true) |
| 590 : new StreamController(sync: true); | 461 : new StreamController(sync: true); |
| 591 var subscription = stream.listen(controller.add, | 462 var subscription = stream.listen(controller.add, |
| 592 onError: controller.addError, | 463 onError: controller.addError, |
| 593 onDone: controller.close); | 464 onDone: controller.close); |
| 594 return new Pair<Stream, StreamSubscription>(controller.stream, subscription); | 465 return new Pair<Stream, StreamSubscription>(controller.stream, subscription); |
| 595 } | 466 } |
| 596 | 467 |
| 597 // TODO(nweiz): remove this when issue 7787 is fixed. | |
| 598 /// Creates two single-subscription [Stream]s that each emit all values and | |
| 599 /// errors from [stream]. | |
| 600 /// | |
| 601 /// This is useful if [stream] is single-subscription but multiple subscribers | |
| 602 /// are necessary. | |
| 603 Pair<Stream, Stream> tee(Stream stream) { | |
| 604 var controller1 = new StreamController(sync: true); | |
| 605 var controller2 = new StreamController(sync: true); | |
| 606 stream.listen((value) { | |
| 607 controller1.add(value); | |
| 608 controller2.add(value); | |
| 609 }, onError: (error, [stackTrace]) { | |
| 610 controller1.addError(error, stackTrace); | |
| 611 controller2.addError(error, stackTrace); | |
| 612 }, onDone: () { | |
| 613 controller1.close(); | |
| 614 controller2.close(); | |
| 615 }); | |
| 616 return new Pair<Stream, Stream>(controller1.stream, controller2.stream); | |
| 617 } | |
| 618 | |
| 619 /// Merges [stream1] and [stream2] into a single stream that emits events from | |
| 620 /// both sources. | |
| 621 Stream mergeStreams(Stream stream1, Stream stream2) { | |
| 622 var doneCount = 0; | |
| 623 var controller = new StreamController(sync: true); | |
| 624 | |
| 625 for (var stream in [stream1, stream2]) { | |
| 626 stream.listen( | |
| 627 controller.add, | |
| 628 onError: controller.addError, | |
| 629 onDone: () { | |
| 630 doneCount++; | |
| 631 if (doneCount == 2) controller.close(); | |
| 632 }); | |
| 633 } | |
| 634 | |
| 635 return controller.stream; | |
| 636 } | |
| 637 | |
| 638 /// Returns a [Stream] that will emit the same values as the stream returned by | |
| 639 /// [callback]. | |
| 640 /// | |
| 641 /// [callback] will only be called when the returned [Stream] gets a subscriber. | |
| 642 Stream callbackStream(Stream callback()) { | |
| 643 var subscription; | |
| 644 var controller; | |
| 645 controller = new StreamController(onListen: () { | |
| 646 subscription = callback().listen(controller.add, | |
| 647 onError: controller.addError, | |
| 648 onDone: controller.close); | |
| 649 }, | |
| 650 onCancel: () => subscription.cancel(), | |
| 651 onPause: () => subscription.pause(), | |
| 652 onResume: () => subscription.resume(), | |
| 653 sync: true); | |
| 654 return controller.stream; | |
| 655 } | |
| 656 | |
| 657 /// A regular expression matching a trailing CR character. | 468 /// A regular expression matching a trailing CR character. |
| 658 final _trailingCR = new RegExp(r"\r$"); | 469 final _trailingCR = new RegExp(r"\r$"); |
| 659 | 470 |
| 660 // TODO(nweiz): Use `text.split(new RegExp("\r\n?|\n\r?"))` when issue 9360 is | 471 // TODO(nweiz): Use `text.split(new RegExp("\r\n?|\n\r?"))` when issue 9360 is |
| 661 // fixed. | 472 // fixed. |
| 662 /// Splits [text] on its line breaks in a Windows-line-break-friendly way. | 473 /// Splits [text] on its line breaks in a Windows-line-break-friendly way. |
| 663 List<String> splitLines(String text) => | 474 List<String> splitLines(String text) => |
| 664 text.split("\n").map((line) => line.replaceFirst(_trailingCR, "")).toList(); | 475 text.split("\n").map((line) => line.replaceFirst(_trailingCR, "")).toList(); |
| 665 | 476 |
| 666 /// Converts a stream of arbitrarily chunked strings into a line-by-line stream. | 477 /// Converts a stream of arbitrarily chunked strings into a line-by-line stream. |
| (...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 721 /// parameters if a name conflict occurs. | 532 /// parameters if a name conflict occurs. |
| 722 Uri addQueryParameters(Uri url, Map<String, String> parameters) { | 533 Uri addQueryParameters(Uri url, Map<String, String> parameters) { |
| 723 var queryMap = queryToMap(url.query); | 534 var queryMap = queryToMap(url.query); |
| 724 queryMap.addAll(parameters); | 535 queryMap.addAll(parameters); |
| 725 return url.resolve("?${mapToQuery(queryMap)}"); | 536 return url.resolve("?${mapToQuery(queryMap)}"); |
| 726 } | 537 } |
| 727 | 538 |
| 728 /// Convert a URL query string (or `application/x-www-form-urlencoded` body) | 539 /// Convert a URL query string (or `application/x-www-form-urlencoded` body) |
| 729 /// into a [Map] from parameter names to values. | 540 /// into a [Map] from parameter names to values. |
| 730 Map<String, String> queryToMap(String queryList) { | 541 Map<String, String> queryToMap(String queryList) { |
| 731 var map = {}; | 542 var map = <String, String>{}; |
| 732 for (var pair in queryList.split("&")) { | 543 for (var pair in queryList.split("&")) { |
| 733 var split = split1(pair, "="); | 544 var split = split1(pair, "="); |
| 734 if (split.isEmpty) continue; | 545 if (split.isEmpty) continue; |
| 735 var key = urlDecode(split[0]); | 546 var key = urlDecode(split[0]); |
| 736 var value = split.length > 1 ? urlDecode(split[1]) : ""; | 547 var value = split.length > 1 ? urlDecode(split[1]) : ""; |
| 737 map[key] = value; | 548 map[key] = value; |
| 738 } | 549 } |
| 739 return map; | 550 return map; |
| 740 } | 551 } |
| 741 | 552 |
| 742 /// Convert a [Map] from parameter names to values to a URL query string. | 553 /// Convert a [Map] from parameter names to values to a URL query string. |
| 743 String mapToQuery(Map<String, String> map) { | 554 String mapToQuery(Map<String, String> map) { |
| 744 var pairs = <List<String>>[]; | 555 var pairs = <List<String>>[]; |
| 745 map.forEach((key, value) { | 556 map.forEach((key, value) { |
| 746 key = Uri.encodeQueryComponent(key); | 557 key = Uri.encodeQueryComponent(key); |
| 747 value = (value == null || value.isEmpty) | 558 value = (value == null || value.isEmpty) |
| 748 ? null : Uri.encodeQueryComponent(value); | 559 ? null : Uri.encodeQueryComponent(value); |
| 749 pairs.add([key, value]); | 560 pairs.add([key, value]); |
| 750 }); | 561 }); |
| 751 return pairs.map((pair) { | 562 return pairs.map((pair) { |
| 752 if (pair[1] == null) return pair[0]; | 563 if (pair[1] == null) return pair[0]; |
| 753 return "${pair[0]}=${pair[1]}"; | 564 return "${pair[0]}=${pair[1]}"; |
| 754 }).join("&"); | 565 }).join("&"); |
| 755 } | 566 } |
| 756 | 567 |
| 757 /// Returns the union of all elements in each set in [sets]. | 568 /// Returns the union of all elements in each set in [sets]. |
| 758 Set unionAll(Iterable<Set> sets) => | 569 Set/*<T>*/ unionAll/*<T>*/(Iterable<Set/*<T>*/> sets) => |
| 759 sets.fold(new Set(), (union, set) => union.union(set)); | 570 sets.fold(new Set(), (union, set) => union.union(set)); |
| 760 | 571 |
| 761 // TODO(nweiz): remove this when issue 9068 has been fixed. | 572 // TODO(nweiz): remove this when issue 9068 has been fixed. |
| 762 /// Whether [uri1] and [uri2] are equal. | 573 /// Whether [uri1] and [uri2] are equal. |
| 763 /// | 574 /// |
| 764 /// This consider HTTP URIs to default to port 80, and HTTPs URIs to default to | 575 /// This consider HTTP URIs to default to port 80, and HTTPs URIs to default to |
| 765 /// port 443. | 576 /// port 443. |
| 766 bool urisEqual(Uri uri1, Uri uri2) => | 577 bool urisEqual(Uri uri1, Uri uri2) => |
| 767 canonicalizeUri(uri1) == canonicalizeUri(uri2); | 578 canonicalizeUri(uri1) == canonicalizeUri(uri2); |
| 768 | 579 |
| (...skipping 19 matching lines...) Expand all Loading... |
| 788 /// Returns a human-friendly representation of [duration]. | 599 /// Returns a human-friendly representation of [duration]. |
| 789 String niceDuration(Duration duration) { | 600 String niceDuration(Duration duration) { |
| 790 var hasMinutes = duration.inMinutes > 0; | 601 var hasMinutes = duration.inMinutes > 0; |
| 791 var result = hasMinutes ? "${duration.inMinutes}:" : ""; | 602 var result = hasMinutes ? "${duration.inMinutes}:" : ""; |
| 792 | 603 |
| 793 var s = duration.inSeconds % 60; | 604 var s = duration.inSeconds % 60; |
| 794 var ms = duration.inMilliseconds % 1000; | 605 var ms = duration.inMilliseconds % 1000; |
| 795 | 606 |
| 796 // If we're using verbose logging, be more verbose but more accurate when | 607 // If we're using verbose logging, be more verbose but more accurate when |
| 797 // reporting timing information. | 608 // reporting timing information. |
| 798 if (log.verbosity.isLevelVisible(log.Level.FINE)) { | 609 var msString = log.verbosity.isLevelVisible(log.Level.FINE) |
| 799 ms = padLeft(ms.toString(), 3, '0'); | 610 ? padLeft(ms.toString(), 3, '0') |
| 800 } else { | 611 : (ms ~/ 100).toString(); |
| 801 ms ~/= 100; | |
| 802 } | |
| 803 | 612 |
| 804 return "$result${hasMinutes ? padLeft(s.toString(), 2, '0') : s}.${ms}s"; | 613 return "$result${hasMinutes ? padLeft(s.toString(), 2, '0') : s}" |
| 614 ".${msString}s"; |
| 805 } | 615 } |
| 806 | 616 |
| 807 /// Decodes a URL-encoded string. | 617 /// Decodes a URL-encoded string. |
| 808 /// | 618 /// |
| 809 /// Unlike [Uri.decodeComponent], this includes replacing `+` with ` `. | 619 /// Unlike [Uri.decodeComponent], this includes replacing `+` with ` `. |
| 810 String urlDecode(String encoded) => | 620 String urlDecode(String encoded) => |
| 811 Uri.decodeComponent(encoded.replaceAll("+", " ")); | 621 Uri.decodeComponent(encoded.replaceAll("+", " ")); |
| 812 | 622 |
| 813 /// Takes a simple data structure (composed of [Map]s, [Iterable]s, scalar | 623 /// Takes a simple data structure (composed of [Map]s, [Iterable]s, scalar |
| 814 /// objects, and [Future]s) and recursively resolves all the [Future]s contained | 624 /// objects, and [Future]s) and recursively resolves all the [Future]s contained |
| 815 /// within. | 625 /// within. |
| 816 /// | 626 /// |
| 817 /// Completes with the fully resolved structure. | 627 /// Completes with the fully resolved structure. |
| 818 Future awaitObject(object) { | 628 Future/*<T>*/ awaitObject/*<T>*/(/*=T*/ object) async { |
| 819 // Unroll nested futures. | 629 // Unroll nested futures. |
| 820 if (object is Future) return object.then(awaitObject); | 630 if (object is Future) return await awaitObject(await object); |
| 631 |
| 821 if (object is Iterable) { | 632 if (object is Iterable) { |
| 822 return Future.wait(object.map(awaitObject).toList()); | 633 // TODO(nweiz): Remove the unnecessary as check when sdk#26965 is fixed. |
| 634 return await Future.wait((object as Iterable).map(awaitObject)) |
| 635 as List/*=T*/; |
| 823 } | 636 } |
| 824 if (object is! Map) return new Future.value(object); | |
| 825 | 637 |
| 826 var pairs = <Future<Pair>>[]; | 638 if (object is Map) { |
| 827 object.forEach((key, value) { | 639 // TODO(nweiz): Remove the unnecessary as check when sdk#26965 is fixed. |
| 828 pairs.add(awaitObject(value) | 640 var oldMap = object as Map; |
| 829 .then((resolved) => new Pair(key, resolved))); | 641 var newMap = {}; |
| 830 }); | 642 await Future.wait(oldMap.keys.map((key) async { |
| 831 return Future.wait(pairs).then((resolvedPairs) { | 643 newMap[key] = await awaitObject(await oldMap[key]); |
| 832 var map = {}; | 644 })); |
| 833 for (var pair in resolvedPairs) { | 645 return newMap as Map/*=T*/; |
| 834 map[pair.first] = pair.last; | 646 } |
| 835 } | 647 |
| 836 return map; | 648 return object; |
| 837 }); | |
| 838 } | 649 } |
| 839 | 650 |
| 840 /// Whether "special" strings such as Unicode characters or color escapes are | 651 /// Whether "special" strings such as Unicode characters or color escapes are |
| 841 /// safe to use. | 652 /// safe to use. |
| 842 /// | 653 /// |
| 843 /// On Windows or when not printing to a terminal, only printable ASCII | 654 /// On Windows or when not printing to a terminal, only printable ASCII |
| 844 /// characters should be used. | 655 /// characters should be used. |
| 845 bool get canUseSpecialChars => !runningFromTest && !runningAsTest && | 656 bool get canUseSpecialChars => !runningFromTest && !runningAsTest && |
| 846 Platform.operatingSystem != 'windows' && | 657 Platform.operatingSystem != 'windows' && |
| 847 stdioType(stdout) == StdioType.TERMINAL; | 658 stdioType(stdout) == StdioType.TERMINAL; |
| (...skipping 124 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 972 } else { | 783 } else { |
| 973 throw new ApplicationException(message); | 784 throw new ApplicationException(message); |
| 974 } | 785 } |
| 975 } | 786 } |
| 976 | 787 |
| 977 /// Throw a [DataException] with [message] to indicate that the command has | 788 /// Throw a [DataException] with [message] to indicate that the command has |
| 978 /// failed because of invalid input data. | 789 /// failed because of invalid input data. |
| 979 /// | 790 /// |
| 980 /// This will report the error and cause pub to exit with [exit_codes.DATA]. | 791 /// This will report the error and cause pub to exit with [exit_codes.DATA]. |
| 981 void dataError(String message) => throw new DataException(message); | 792 void dataError(String message) => throw new DataException(message); |
| OLD | NEW |