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 |