| // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| library barback.utils; |
| |
| import 'dart:async'; |
| |
| /// A pair of values. |
| class Pair<E, F> { |
| E first; |
| F last; |
| |
| Pair(this.first, this.last); |
| |
| String toString() => '($first, $last)'; |
| |
| bool operator==(other) { |
| if (other is! Pair) return false; |
| return other.first == first && other.last == last; |
| } |
| |
| int get hashCode => first.hashCode ^ last.hashCode; |
| } |
| |
| /// Converts a number in the range [0-255] to a two digit hex string. |
| /// |
| /// For example, given `255`, returns `ff`. |
| String byteToHex(int byte) { |
| assert(byte >= 0 && byte <= 255); |
| |
| 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) { |
| helper(element); |
| } else { |
| result.add(element); |
| } |
| } |
| } |
| helper(nested); |
| return result; |
| } |
| |
| /// Returns the union of all elements in each set in [sets]. |
| Set unionAll(Iterable<Set> sets) => |
| sets.fold(new Set(), (union, set) => union.union(set)); |
| |
| /// 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])); |
| |
| /// Returns whether [set1] has exactly the same elements as [set2]. |
| bool setEquals(Set set1, Set set2) => |
| set1.length == set2.length && set1.containsAll(set2); |
| |
| /// Merges [streams] into a single stream that emits events from all sources. |
| Stream mergeStreams(Iterable<Stream> streams) { |
| streams = streams.toList(); |
| var doneCount = 0; |
| // Use a sync stream to preserve the synchrony behavior of the input streams. |
| // If the inputs are sync, then this will be sync as well; if the inputs are |
| // async, then the events we receive will also be async, and forwarding them |
| // sync won't change that. |
| var controller = new StreamController(sync: true); |
| |
| 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)); |
| } |
| |
| /// Like [new Future], but avoids issue 11911 by using [new Future.value] under |
| /// the covers. |
| Future newFuture(callback()) => new Future.value().then((_) => callback()); |