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 |