| OLD | NEW |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, 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 library barback.utils; | 5 library barback.utils; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:typed_data'; | 8 import 'dart:typed_data'; |
| 9 | 9 |
| 10 import 'package:async/async.dart'; | 10 import 'package:async/async.dart'; |
| (...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 123 // TODO(nweiz): remove "as" when issue 11080 is fixed. | 123 // TODO(nweiz): remove "as" when issue 11080 is fixed. |
| 124 return new Uint8List.view((input as TypedData).buffer); | 124 return new Uint8List.view((input as TypedData).buffer); |
| 125 } | 125 } |
| 126 return new Uint8List.fromList(input); | 126 return new Uint8List.fromList(input); |
| 127 } | 127 } |
| 128 | 128 |
| 129 /// Group the elements in [iter] by the value returned by [fn]. | 129 /// Group the elements in [iter] by the value returned by [fn]. |
| 130 /// | 130 /// |
| 131 /// This returns a map whose keys are the return values of [fn] and whose values | 131 /// This returns a map whose keys are the return values of [fn] and whose values |
| 132 /// are lists of each element in [iter] for which [fn] returned that key. | 132 /// are lists of each element in [iter] for which [fn] returned that key. |
| 133 Map<Object/*=T*/, List/*<S>*/> groupBy/*<S, T>*/(Iterable/*<S>*/ iter, | 133 Map<Object, List<S>> groupBy<S, T>(Iterable<S> iter, T fn(S element)) { |
| 134 /*=T*/ fn(/*=S*/ element)) { | 134 var map = <T, List<S>>{}; |
| 135 var map = /*<T, List<S>>*/{}; | |
| 136 for (var element in iter) { | 135 for (var element in iter) { |
| 137 var list = map.putIfAbsent(fn(element), () => []); | 136 var list = map.putIfAbsent(fn(element), () => []); |
| 138 list.add(element); | 137 list.add(element); |
| 139 } | 138 } |
| 140 return map; | 139 return map; |
| 141 } | 140 } |
| 142 | 141 |
| 143 /// Flattens nested lists inside an iterable into a single list containing only | 142 /// Flattens nested lists inside an iterable into a single list containing only |
| 144 /// non-list elements. | 143 /// non-list elements. |
| 145 List flatten(Iterable nested) { | 144 List flatten(Iterable nested) { |
| 146 var result = []; | 145 var result = []; |
| 147 helper(list) { | 146 helper(list) { |
| 148 for (var element in list) { | 147 for (var element in list) { |
| 149 if (element is List) { | 148 if (element is List) { |
| 150 helper(element); | 149 helper(element); |
| 151 } else { | 150 } else { |
| 152 result.add(element); | 151 result.add(element); |
| 153 } | 152 } |
| 154 } | 153 } |
| 155 } | 154 } |
| 155 |
| 156 helper(nested); | 156 helper(nested); |
| 157 return result; | 157 return result; |
| 158 } | 158 } |
| 159 | 159 |
| 160 /// Returns the union of all elements in each set in [sets]. | 160 /// Returns the union of all elements in each set in [sets]. |
| 161 Set/*<T>*/ unionAll/*<T>*/(Iterable<Set/*<T>*/> sets) => | 161 Set<T> unionAll<T>(Iterable<Set<T>> sets) => |
| 162 sets.fold(new Set(), (union, set) => union.union(set)); | 162 sets.fold(new Set(), (union, set) => union.union(set)); |
| 163 | 163 |
| 164 /// Creates a new map from [map] with new keys and values. | 164 /// Creates a new map from [map] with new keys and values. |
| 165 /// | 165 /// |
| 166 /// The return values of [keyFn] are used as the keys and the return values of | 166 /// The return values of [keyFn] are used as the keys and the return values of |
| 167 /// [valueFn] are used as the values for the new map. | 167 /// [valueFn] are used as the values for the new map. |
| 168 Map/*<K2, V2>*/ mapMap/*<K1, V1, K2, V2>*/(Map/*<K1, V1>*/ map, | 168 Map<K2, V2> mapMap<K1, V1, K2, V2>(Map<K1, V1> map, K2 keyFn(K1 key, V1 value), |
| 169 /*=K2*/ keyFn(/*=K1*/ key, /*=V1*/ value), | 169 V2 valueFn(K1 key, V1 value)) => |
| 170 /*=V2*/ valueFn(/*=K1*/ key, /*=V1*/ value)) => | 170 new Map.fromIterable(map.keys, |
| 171 new Map.fromIterable(map.keys, | 171 key: (key) => keyFn(key as K1, map[key]), |
| 172 key: (key) => keyFn(key as dynamic/*=K1*/, map[key]), | 172 value: (key) => valueFn(key as K1, map[key])); |
| 173 value: (key) => valueFn(key as dynamic/*=K1*/, map[key])); | |
| 174 | 173 |
| 175 /// Creates a new map from [map] with the same keys. | 174 /// Creates a new map from [map] with the same keys. |
| 176 /// | 175 /// |
| 177 /// The return values of [fn] are used as the values for the new map. | 176 /// The return values of [fn] are used as the values for the new map. |
| 178 Map/*<K, V2>*/ mapMapValues/*<K, V1, V2>*/(Map/*<K, V1>*/ map, | 177 Map<K, V2> mapMapValues<K, V1, V2>(Map<K, V1> map, V2 fn(K key, V1 value)) => |
| 179 /*=V2*/ fn(/*=K*/ key, /*=V1*/ value)) => | 178 // TODO(nweiz): Don't explicitly type [key] when sdk#25490 is fixed. |
| 180 // TODO(nweiz): Don't explicitly type [key] when sdk#25490 is fixed. | 179 mapMap(map, (K key, _) => key, fn); |
| 181 mapMap(map, (/*=K*/ key, _) => key, fn); | |
| 182 | 180 |
| 183 /// Creates a new map from [map] with the same keys. | 181 /// Creates a new map from [map] with the same keys. |
| 184 /// | 182 /// |
| 185 /// The return values of [fn] are used as the keys for the new map. | 183 /// The return values of [fn] are used as the keys for the new map. |
| 186 Map/*<K2, V>*/ mapMapKeys/*<K1, V, K2>*/(Map/*<K1, V>*/ map, | 184 Map<K2, V> mapMapKeys<K1, V, K2>(Map<K1, V> map, K2 fn(K1 key, V value)) => |
| 187 /*=K2*/ fn(/*=K1*/ key, /*=V*/ value)) => | 185 // TODO(nweiz): Don't explicitly type [value] when sdk#25490 is fixed. |
| 188 // TODO(nweiz): Don't explicitly type [value] when sdk#25490 is fixed. | 186 mapMap(map, fn, (_, V value) => value); |
| 189 mapMap(map, fn, (_, /*=V*/ value) => value); | |
| 190 | 187 |
| 191 /// Returns whether [set1] has exactly the same elements as [set2]. | 188 /// Returns whether [set1] has exactly the same elements as [set2]. |
| 192 bool setEquals(Set set1, Set set2) => | 189 bool setEquals(Set set1, Set set2) => |
| 193 set1.length == set2.length && set1.containsAll(set2); | 190 set1.length == set2.length && set1.containsAll(set2); |
| 194 | 191 |
| 195 /// Merges [streams] into a single stream that emits events from all sources. | 192 /// Merges [streams] into a single stream that emits events from all sources. |
| 196 /// | 193 /// |
| 197 /// If [broadcast] is true, this will return a broadcast stream; otherwise, it | 194 /// If [broadcast] is true, this will return a broadcast stream; otherwise, it |
| 198 /// will return a buffered stream. | 195 /// will return a buffered stream. |
| 199 Stream/*<T>*/ mergeStreams/*<T>*/(Iterable<Stream/*<T>*/> streams, | 196 Stream<T> mergeStreams<T>(Iterable<Stream<T>> streams, |
| 200 {bool broadcast: false}) { | 197 {bool broadcast: false}) { |
| 201 streams = streams.toList(); | 198 streams = streams.toList(); |
| 202 var doneCount = 0; | 199 var doneCount = 0; |
| 203 // Use a sync stream to preserve the synchrony behavior of the input streams. | 200 // Use a sync stream to preserve the synchrony behavior of the input streams. |
| 204 // If the inputs are sync, then this will be sync as well; if the inputs are | 201 // If the inputs are sync, then this will be sync as well; if the inputs are |
| 205 // async, then the events we receive will also be async, and forwarding them | 202 // async, then the events we receive will also be async, and forwarding them |
| 206 // sync won't change that. | 203 // sync won't change that. |
| 207 var controller = broadcast | 204 var controller = broadcast |
| 208 ? new StreamController/*<T>*/.broadcast(sync: true) | 205 ? new StreamController<T>.broadcast(sync: true) |
| 209 : new StreamController/*<T>*/(sync: true); | 206 : new StreamController<T>(sync: true); |
| 210 | 207 |
| 211 for (var stream in streams) { | 208 for (var stream in streams) { |
| 212 stream.listen( | 209 stream.listen(controller.add, onError: controller.addError, onDone: () { |
| 213 controller.add, | |
| 214 onError: controller.addError, | |
| 215 onDone: () { | |
| 216 doneCount++; | 210 doneCount++; |
| 217 if (doneCount == streams.length) controller.close(); | 211 if (doneCount == streams.length) controller.close(); |
| 218 }); | 212 }); |
| 219 } | 213 } |
| 220 | 214 |
| 221 return controller.stream; | 215 return controller.stream; |
| 222 } | 216 } |
| 223 | 217 |
| 224 /// Prepends each line in [text] with [prefix]. If [firstPrefix] is passed, the | 218 /// Prepends each line in [text] with [prefix]. If [firstPrefix] is passed, the |
| 225 /// first line is prefixed with that instead. | 219 /// first line is prefixed with that instead. |
| 226 String prefixLines(String text, {String prefix: '| ', String firstPrefix}) { | 220 String prefixLines(String text, {String prefix: '| ', String firstPrefix}) { |
| 227 var lines = text.split('\n'); | 221 var lines = text.split('\n'); |
| 228 if (firstPrefix == null) { | 222 if (firstPrefix == null) { |
| 229 return lines.map((line) => '$prefix$line').join('\n'); | 223 return lines.map((line) => '$prefix$line').join('\n'); |
| 230 } | 224 } |
| 231 | 225 |
| 232 var firstLine = "$firstPrefix${lines.first}"; | 226 var firstLine = "$firstPrefix${lines.first}"; |
| 233 lines = lines.skip(1).map((line) => '$prefix$line').toList(); | 227 lines = lines.skip(1).map((line) => '$prefix$line').toList(); |
| 234 lines.insert(0, firstLine); | 228 lines.insert(0, firstLine); |
| 235 return lines.join('\n'); | 229 return lines.join('\n'); |
| 236 } | 230 } |
| 237 | 231 |
| 238 /// Returns a [Future] that completes after pumping the event queue [times] | 232 /// Returns a [Future] that completes after pumping the event queue [times] |
| 239 /// times. By default, this should pump the event queue enough times to allow | 233 /// times. By default, this should pump the event queue enough times to allow |
| 240 /// any code to run, as long as it's not waiting on some external event. | 234 /// any code to run, as long as it's not waiting on some external event. |
| 241 Future pumpEventQueue([int times=20]) { | 235 Future pumpEventQueue([int times = 20]) { |
| 242 if (times == 0) return new Future.value(); | 236 if (times == 0) return new Future.value(); |
| 243 // We use a delayed future to allow microtask events to finish. The | 237 // We use a delayed future to allow microtask events to finish. The |
| 244 // Future.value or Future() constructors use scheduleMicrotask themselves and | 238 // Future.value or Future() constructors use scheduleMicrotask themselves and |
| 245 // would therefore not wait for microtask callbacks that are scheduled after | 239 // would therefore not wait for microtask callbacks that are scheduled after |
| 246 // invoking this method. | 240 // invoking this method. |
| 247 return new Future.delayed(Duration.ZERO, () => pumpEventQueue(times - 1)); | 241 return new Future.delayed(Duration.ZERO, () => pumpEventQueue(times - 1)); |
| 248 } | 242 } |
| 249 | 243 |
| 250 /// Like [new Future], but avoids dartbug.com/11911 by using async/await under | 244 /// Like [new Future], but avoids dartbug.com/11911 by using async/await under |
| 251 /// the covers. | 245 /// the covers. |
| 252 Future/*<T>*/ newFuture/*<T>*/(/*=T*/ callback()) async => await callback(); | 246 Future<T> newFuture<T>(T callback()) async => await callback(); |
| 253 | 247 |
| 254 /// Returns a buffered stream that will emit the same values as the stream | 248 /// Returns a buffered stream that will emit the same values as the stream |
| 255 /// returned by [future] once [future] completes. | 249 /// returned by [future] once [future] completes. |
| 256 /// | 250 /// |
| 257 /// If [future] completes to an error, the return value will emit that error and | 251 /// If [future] completes to an error, the return value will emit that error and |
| 258 /// then close. | 252 /// then close. |
| 259 /// | 253 /// |
| 260 /// If [broadcast] is true, a broadcast stream is returned. This assumes that | 254 /// If [broadcast] is true, a broadcast stream is returned. This assumes that |
| 261 /// the stream returned by [future] will be a broadcast stream as well. | 255 /// the stream returned by [future] will be a broadcast stream as well. |
| 262 /// [broadcast] defaults to false. | 256 /// [broadcast] defaults to false. |
| 263 Stream/*<T>*/ futureStream/*<T>*/(Future<Stream/*<T>*/> future, | 257 Stream<T> futureStream<T>(Future<Stream<T>> future, {bool broadcast: false}) { |
| 264 {bool broadcast: false}) { | 258 StreamSubscription<T> subscription; |
| 265 StreamSubscription/*<T>*/ subscription; | 259 StreamController<T> controller; |
| 266 StreamController/*<T>*/ controller; | |
| 267 | 260 |
| 268 future = DelegatingFuture.typed(future.catchError((e, stackTrace) { | 261 future = DelegatingFuture.typed(future.catchError((e, stackTrace) { |
| 269 // Since [controller] is synchronous, it's likely that emitting an error | 262 // Since [controller] is synchronous, it's likely that emitting an error |
| 270 // will cause it to be cancelled before we call close. | 263 // will cause it to be cancelled before we call close. |
| 271 if (controller != null) controller.addError(e, stackTrace); | 264 if (controller != null) controller.addError(e, stackTrace); |
| 272 if (controller != null) controller.close(); | 265 if (controller != null) controller.close(); |
| 273 controller = null; | 266 controller = null; |
| 274 })); | 267 })); |
| 275 | 268 |
| 276 onListen() { | 269 onListen() { |
| 277 future.then((stream) { | 270 future.then((stream) { |
| 278 if (controller == null) return; | 271 if (controller == null) return; |
| 279 subscription = stream.listen( | 272 subscription = stream.listen(controller.add, |
| 280 controller.add, | 273 onError: controller.addError, onDone: controller.close); |
| 281 onError: controller.addError, | |
| 282 onDone: controller.close); | |
| 283 }); | 274 }); |
| 284 } | 275 } |
| 285 | 276 |
| 286 onCancel() { | 277 onCancel() { |
| 287 if (subscription != null) subscription.cancel(); | 278 if (subscription != null) subscription.cancel(); |
| 288 subscription = null; | 279 subscription = null; |
| 289 controller = null; | 280 controller = null; |
| 290 } | 281 } |
| 291 | 282 |
| 292 if (broadcast) { | 283 if (broadcast) { |
| 293 controller = new StreamController/*<T>*/.broadcast( | 284 controller = new StreamController<T>.broadcast( |
| 294 sync: true, onListen: onListen, onCancel: onCancel); | 285 sync: true, onListen: onListen, onCancel: onCancel); |
| 295 } else { | 286 } else { |
| 296 controller = new StreamController/*<T>*/( | 287 controller = new StreamController<T>( |
| 297 sync: true, onListen: onListen, onCancel: onCancel); | 288 sync: true, onListen: onListen, onCancel: onCancel); |
| 298 } | 289 } |
| 299 return controller.stream; | 290 return controller.stream; |
| 300 } | 291 } |
| 301 | 292 |
| 302 /// Returns a [Stream] that will emit the same values as the stream returned by | 293 /// Returns a [Stream] that will emit the same values as the stream returned by |
| 303 /// [callback]. | 294 /// [callback]. |
| 304 /// | 295 /// |
| 305 /// [callback] will only be called when the returned [Stream] gets a subscriber. | 296 /// [callback] will only be called when the returned [Stream] gets a subscriber. |
| 306 Stream/*<T>*/ callbackStream/*<T>*/(Stream/*<T>*/ callback()) { | 297 Stream<T> callbackStream<T>(Stream<T> callback()) { |
| 307 StreamSubscription/*<T>*/ subscription; | 298 StreamSubscription<T> subscription; |
| 308 StreamController/*<T>*/ controller; | 299 StreamController<T> controller; |
| 309 controller = new StreamController/*<T>*/(onListen: () { | 300 controller = new StreamController<T>( |
| 310 subscription = callback().listen(controller.add, | 301 onListen: () { |
| 311 onError: controller.addError, | 302 subscription = callback().listen(controller.add, |
| 312 onDone: controller.close); | 303 onError: controller.addError, onDone: controller.close); |
| 313 }, | 304 }, |
| 314 onCancel: () => subscription.cancel(), | 305 onCancel: () => subscription.cancel(), |
| 315 onPause: () => subscription.pause(), | 306 onPause: () => subscription.pause(), |
| 316 onResume: () => subscription.resume(), | 307 onResume: () => subscription.resume(), |
| 317 sync: true); | 308 sync: true); |
| 318 return controller.stream; | 309 return controller.stream; |
| 319 } | 310 } |
| 320 | 311 |
| 321 /// Creates a single-subscription stream from a broadcast stream. | 312 /// Creates a single-subscription stream from a broadcast stream. |
| 322 /// | 313 /// |
| 323 /// The returned stream will enqueue events from [broadcast] until a listener is | 314 /// The returned stream will enqueue events from [broadcast] until a listener is |
| 324 /// attached, then pipe events to that listener. | 315 /// attached, then pipe events to that listener. |
| 325 Stream/*<T>*/ broadcastToSingleSubscription/*<T>*/(Stream/*<T>*/ broadcast) { | 316 Stream<T> broadcastToSingleSubscription<T>(Stream<T> broadcast) { |
| 326 if (!broadcast.isBroadcast) return broadcast; | 317 if (!broadcast.isBroadcast) return broadcast; |
| 327 | 318 |
| 328 // TODO(nweiz): Implement this using a transformer when issues 18588 and 18586 | 319 // TODO(nweiz): Implement this using a transformer when issues 18588 and 18586 |
| 329 // are fixed. | 320 // are fixed. |
| 330 var subscription; | 321 var subscription; |
| 331 var controller = new StreamController/*<T>*/( | 322 var controller = |
| 332 onCancel: () => subscription.cancel()); | 323 new StreamController<T>(onCancel: () => subscription.cancel()); |
| 333 subscription = broadcast.listen(controller.add, | 324 subscription = broadcast.listen(controller.add, |
| 334 onError: controller.addError, | 325 onError: controller.addError, onDone: controller.close); |
| 335 onDone: controller.close); | |
| 336 return controller.stream; | 326 return controller.stream; |
| 337 } | 327 } |
| 338 | 328 |
| 339 /// A regular expression to match the exception prefix that some exceptions' | 329 /// A regular expression to match the exception prefix that some exceptions' |
| 340 /// [Object.toString] values contain. | 330 /// [Object.toString] values contain. |
| 341 final _exceptionPrefix = new RegExp(r'^([A-Z][a-zA-Z]*)?(Exception|Error): '); | 331 final _exceptionPrefix = new RegExp(r'^([A-Z][a-zA-Z]*)?(Exception|Error): '); |
| 342 | 332 |
| 343 /// Get a string description of an exception. | 333 /// Get a string description of an exception. |
| 344 /// | 334 /// |
| 345 /// Many exceptions include the exception class name at the beginning of their | 335 /// Many exceptions include the exception class name at the beginning of their |
| 346 /// [toString], so we remove that if it exists. | 336 /// [toString], so we remove that if it exists. |
| 347 String getErrorMessage(error) => | 337 String getErrorMessage(error) => |
| 348 error.toString().replaceFirst(_exceptionPrefix, ''); | 338 error.toString().replaceFirst(_exceptionPrefix, ''); |
| 349 | 339 |
| 350 /// Returns a human-friendly representation of [duration]. | 340 /// Returns a human-friendly representation of [duration]. |
| 351 String niceDuration(Duration duration) { | 341 String niceDuration(Duration duration) { |
| 352 var result = duration.inMinutes > 0 ? "${duration.inMinutes}:" : ""; | 342 var result = duration.inMinutes > 0 ? "${duration.inMinutes}:" : ""; |
| 353 | 343 |
| 354 var s = duration.inSeconds % 59; | 344 var s = duration.inSeconds % 59; |
| 355 var ms = (duration.inMilliseconds % 1000) ~/ 100; | 345 var ms = (duration.inMilliseconds % 1000) ~/ 100; |
| 356 return result + "$s.${ms}s"; | 346 return result + "$s.${ms}s"; |
| 357 } | 347 } |
| OLD | NEW |