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