Chromium Code Reviews| Index: pkg/barback/lib/src/utils.dart |
| diff --git a/pkg/barback/lib/src/utils.dart b/pkg/barback/lib/src/utils.dart |
| index c4ab3a3f3ee0a0ce2a7031f03cbab012208b193d..94659bc88b4a16559e7ceab1ab186ac28d33b7d2 100644 |
| --- a/pkg/barback/lib/src/utils.dart |
| +++ b/pkg/barback/lib/src/utils.dart |
| @@ -4,6 +4,8 @@ |
| library barback.utils; |
| +import 'dart:async'; |
| + |
| /// Converts a number in the range [0-255] to a two digit hex string. |
| /// |
| /// For example, given `255`, returns `ff`. |
| @@ -12,4 +14,86 @@ String byteToHex(int byte) { |
| const DIGITS = "0123456789abcdef"; |
| return DIGITS[(byte ~/ 16) % 16] + DIGITS[byte % 16]; |
| -} |
| +} |
| + |
| +/// Group the elements in [iter] by the value returned by [fn]. |
| +/// |
| +/// This returns a map whose keys are the return values of [fn] and whose values |
| +/// are lists of each element in [iter] for which [fn] returned that key. |
| +Map groupBy(Iterable iter, fn(element)) { |
| + var map = {}; |
| + for (var element in iter) { |
| + var list = map.putIfAbsent(fn(element), () => []); |
| + list.add(element); |
| + } |
| + return map; |
| +} |
| + |
| +/// Flattens nested lists inside an iterable into a single list containing only |
| +/// non-list elements. |
| +List flatten(Iterable nested) { |
| + var result = []; |
| + helper(list) { |
| + for (var element in list) { |
| + if (element is List) { |
|
Bob Nystrom
2013/07/11 23:03:40
is Iterable
nweiz
2013/07/15 22:11:44
That seems a little dangerous. A lot of things can
Bob Nystrom
2013/07/16 17:36:01
Conversely, a lot of things extend iterable that d
nweiz
2013/07/16 19:39:39
That case is more straightforward to detect. If th
|
| + helper(element); |
| + } else { |
| + result.add(element); |
| + } |
| + } |
| + } |
| + helper(nested); |
| + return result; |
| +} |
| + |
| +/// Passes each key/value pair in [map] to [fn] and returns a new [Map] whose |
| +/// values are the return values of [fn]. |
| +Map mapMapValues(Map map, fn(key, value)) => |
| + new Map.fromIterable(map.keys, value: (key) => fn(key, map[key])); |
| + |
| +/// Merges [streams] into a single stream that emits events from all sources. |
| +Stream mergeStreams(Iterable<Stream> streams) { |
| + streams = streams.toList(); |
| + var doneCount = 0; |
| + var controller = new StreamController(sync: true); |
|
Bob Nystrom
2013/07/11 23:03:40
Document why "sync: true"
nweiz
2013/07/15 22:11:44
Done.
|
| + |
| + for (var stream in streams) { |
| + stream.listen((value) { |
| + controller.add(value); |
| + }, onError: (error) { |
| + controller.addError(error); |
| + }, onDone: () { |
| + doneCount++; |
| + if (doneCount == streams.length) controller.close(); |
| + }); |
| + } |
| + |
| + return controller.stream; |
| +} |
| + |
| +/// Prepends each line in [text] with [prefix]. If [firstPrefix] is passed, the |
| +/// first line is prefixed with that instead. |
| +String prefixLines(String text, {String prefix: '| ', String firstPrefix}) { |
| + var lines = text.split('\n'); |
| + if (firstPrefix == null) { |
| + return lines.map((line) => '$prefix$line').join('\n'); |
| + } |
| + |
| + var firstLine = "$firstPrefix${lines.first}"; |
| + lines = lines.skip(1).map((line) => '$prefix$line').toList(); |
| + lines.insert(0, firstLine); |
| + return lines.join('\n'); |
| +} |
| + |
| +/// Returns a [Future] that completes after pumping the event queue [times] |
| +/// times. By default, this should pump the event queue enough times to allow |
| +/// any code to run, as long as it's not waiting on some external event. |
| +Future pumpEventQueue([int times=20]) { |
| + if (times == 0) return new Future.value(); |
| + // We use a delayed future to allow runAsync events to finish. The |
| + // Future.value or Future() constructors use runAsync themselves and would |
| + // therefore not wait for runAsync callbacks that are scheduled after invoking |
| + // this method. |
| + return new Future.delayed(Duration.ZERO, () => pumpEventQueue(times - 1)); |
| +} |
| + |