| OLD | NEW |
| (Empty) |
| 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 | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 library utils; | |
| 6 | |
| 7 import 'dart:async'; | |
| 8 | |
| 9 import 'package:stack_trace/stack_trace.dart'; | |
| 10 | |
| 11 /// A pair of values. | |
| 12 class Pair<E, F> { | |
| 13 E first; | |
| 14 F last; | |
| 15 | |
| 16 Pair(this.first, this.last); | |
| 17 | |
| 18 String toString() => '($first, $last)'; | |
| 19 | |
| 20 bool operator ==(other) { | |
| 21 if (other is! Pair) return false; | |
| 22 return other.first == first && other.last == last; | |
| 23 } | |
| 24 | |
| 25 int get hashCode => first.hashCode ^ last.hashCode; | |
| 26 } | |
| 27 | |
| 28 /// A class that represents a value or an error. | |
| 29 class Fallible<E> { | |
| 30 /// Whether [this] has a [value], as opposed to an [error]. | |
| 31 final bool hasValue; | |
| 32 | |
| 33 /// Whether [this] has an [error], as opposed to a [value]. | |
| 34 bool get hasError => !hasValue; | |
| 35 | |
| 36 /// The value. | |
| 37 /// | |
| 38 /// This will be `null` if [this] has an [error]. | |
| 39 final E _value; | |
| 40 | |
| 41 /// The value. | |
| 42 /// | |
| 43 /// This will throw a [StateError] if [this] has an [error]. | |
| 44 E get value { | |
| 45 if (hasValue) return _value; | |
| 46 throw new StateError("Fallible has no value.\n" | |
| 47 "$_error$_stackTraceSuffix"); | |
| 48 } | |
| 49 | |
| 50 /// The error. | |
| 51 /// | |
| 52 /// This will be `null` if [this] has a [value]. | |
| 53 final _error; | |
| 54 | |
| 55 /// The error. | |
| 56 /// | |
| 57 /// This will throw a [StateError] if [this] has a [value]. | |
| 58 get error { | |
| 59 if (hasError) return _error; | |
| 60 throw new StateError("Fallible has no error."); | |
| 61 } | |
| 62 | |
| 63 /// The stack trace for [_error]. | |
| 64 /// | |
| 65 /// This will be `null` if [this] has a [value], or if no stack trace was | |
| 66 /// provided. | |
| 67 final StackTrace _stackTrace; | |
| 68 | |
| 69 /// The stack trace for [error]. | |
| 70 /// | |
| 71 /// This will throw a [StateError] if [this] has a [value]. | |
| 72 StackTrace get stackTrace { | |
| 73 if (hasError) return _stackTrace; | |
| 74 throw new StateError("Fallible has no error."); | |
| 75 } | |
| 76 | |
| 77 Fallible.withValue(this._value) | |
| 78 : _error = null, | |
| 79 _stackTrace = null, | |
| 80 hasValue = true; | |
| 81 | |
| 82 Fallible.withError(this._error, [this._stackTrace]) | |
| 83 : _value = null, | |
| 84 hasValue = false; | |
| 85 | |
| 86 /// Returns a completed Future with the same value or error as [this]. | |
| 87 Future toFuture() { | |
| 88 if (hasValue) return new Future.value(value); | |
| 89 return new Future.error(error, stackTrace); | |
| 90 } | |
| 91 | |
| 92 String toString() { | |
| 93 if (hasValue) return "Fallible value: $value"; | |
| 94 return "Fallible error: $error$_stackTraceSuffix"; | |
| 95 } | |
| 96 | |
| 97 String get _stackTraceSuffix { | |
| 98 if (stackTrace == null) return ""; | |
| 99 return "\nStack trace:\n${new Chain.forTrace(_stackTrace).terse}"; | |
| 100 } | |
| 101 } | |
| 102 | |
| 103 /// Configures [future] so that its result (success or exception) is passed on | |
| 104 /// to [completer]. | |
| 105 void chainToCompleter(Future future, Completer completer) { | |
| 106 future.then(completer.complete, onError: completer.completeError); | |
| 107 } | |
| 108 | |
| 109 /// Like [Future.sync], but wraps the Future in [Chain.track] as well. | |
| 110 Future syncFuture(callback()) => Chain.track(new Future.sync(callback)); | |
| 111 | |
| 112 /// Prepends each line in [text] with [prefix]. If [firstPrefix] is passed, the | |
| 113 /// first line is prefixed with that instead. | |
| 114 String prefixLines(String text, {String prefix: '| ', String firstPrefix}) { | |
| 115 var lines = text.split('\n'); | |
| 116 if (firstPrefix == null) { | |
| 117 return lines.map((line) => '$prefix$line').join('\n'); | |
| 118 } | |
| 119 | |
| 120 var firstLine = "$firstPrefix${lines.first}"; | |
| 121 lines = lines.skip(1).map((line) => '$prefix$line').toList(); | |
| 122 lines.insert(0, firstLine); | |
| 123 return lines.join('\n'); | |
| 124 } | |
| 125 | |
| 126 /// Returns a [Future] that completes after pumping the event queue [times] | |
| 127 /// times. By default, this should pump the event queue enough times to allow | |
| 128 /// any code to run, as long as it's not waiting on some external event. | |
| 129 Future pumpEventQueue([int times = 20]) { | |
| 130 if (times == 0) return new Future.value(); | |
| 131 // We use a delayed future to allow microtask events to finish. The | |
| 132 // Future.value or Future() constructors use scheduleMicrotask themselves and | |
| 133 // would therefore not wait for microtask callbacks that are scheduled after | |
| 134 // invoking this method. | |
| 135 return new Future.delayed(Duration.ZERO, () => pumpEventQueue(times - 1)); | |
| 136 } | |
| 137 | |
| 138 /// Returns whether [iterable1] has the same elements in the same order as | |
| 139 /// [iterable2]. The elements are compared using `==`. | |
| 140 bool orderedIterableEquals(Iterable iterable1, Iterable iterable2) { | |
| 141 var iter1 = iterable1.iterator; | |
| 142 var iter2 = iterable2.iterator; | |
| 143 | |
| 144 while (true) { | |
| 145 var hasNext1 = iter1.moveNext(); | |
| 146 var hasNext2 = iter2.moveNext(); | |
| 147 if (hasNext1 != hasNext2) return false; | |
| 148 if (!hasNext1) return true; | |
| 149 if (iter1.current != iter2.current) return false; | |
| 150 } | |
| 151 } | |
| 152 | |
| 153 /// Returns a buffered stream that will emit the same values as the stream | |
| 154 /// returned by [future] once [future] completes. | |
| 155 /// | |
| 156 /// If [future] completes to an error, the return value will emit that error and | |
| 157 /// then close. | |
| 158 /// | |
| 159 /// If [broadcast] is true, a broadcast stream is returned. This assumes that | |
| 160 /// the stream returned by [future] will be a broadcast stream as well. | |
| 161 /// [broadcast] defaults to false. | |
| 162 Stream futureStream(Future<Stream> future, {bool broadcast: false}) { | |
| 163 var subscription; | |
| 164 var controller; | |
| 165 | |
| 166 future = future.catchError((e, stackTrace) { | |
| 167 // Since [controller] is synchronous, it's likely that emitting an error | |
| 168 // will cause it to be cancelled before we call close. | |
| 169 if (controller != null) controller.addError(e, stackTrace); | |
| 170 if (controller != null) controller.close(); | |
| 171 controller = null; | |
| 172 }); | |
| 173 | |
| 174 onListen() { | |
| 175 future.then((stream) { | |
| 176 if (controller == null) return; | |
| 177 subscription = stream.listen( | |
| 178 controller.add, | |
| 179 onError: controller.addError, | |
| 180 onDone: controller.close); | |
| 181 }); | |
| 182 } | |
| 183 | |
| 184 onCancel() { | |
| 185 if (subscription != null) subscription.cancel(); | |
| 186 subscription = null; | |
| 187 controller = null; | |
| 188 } | |
| 189 | |
| 190 if (broadcast) { | |
| 191 controller = new StreamController.broadcast( | |
| 192 sync: true, onListen: onListen, onCancel: onCancel); | |
| 193 } else { | |
| 194 controller = new StreamController( | |
| 195 sync: true, onListen: onListen, onCancel: onCancel); | |
| 196 } | |
| 197 return controller.stream; | |
| 198 } | |
| 199 | |
| 200 /// Returns the first element of a [StreamIterator]. | |
| 201 /// | |
| 202 /// If the [StreamIterator] has no elements, the result is a state error. | |
| 203 Future<String> streamIteratorFirst(StreamIterator<String> streamIterator) { | |
| 204 return streamIterator.moveNext().then((hasNext) { | |
| 205 if (hasNext) { | |
| 206 return streamIterator.current; | |
| 207 } else { | |
| 208 throw new StateError("No elements"); | |
| 209 } | |
| 210 }); | |
| 211 } | |
| 212 | |
| 213 /// Collects all remaining lines from a [StreamIterator] of lines. | |
| 214 /// | |
| 215 /// Returns the concatenation of the collected lines joined by newlines. | |
| 216 Future<String> concatRest(StreamIterator<String> streamIterator) { | |
| 217 var completer = new Completer<String>(); | |
| 218 var buffer = new StringBuffer(); | |
| 219 void collectAll() { | |
| 220 streamIterator.moveNext().then((hasNext) { | |
| 221 if (hasNext) { | |
| 222 if (!buffer.isEmpty) buffer.write('\n'); | |
| 223 buffer.write(streamIterator.current); | |
| 224 collectAll(); | |
| 225 } else { | |
| 226 completer.complete(buffer.toString()); | |
| 227 } | |
| 228 }, onError: completer.completeError); | |
| 229 } | |
| 230 collectAll(); | |
| 231 return completer.future; | |
| 232 } | |
| 233 | |
| 234 /// A function that can be called to cancel a [Stream] and send a done message. | |
| 235 typedef void StreamCanceller(); | |
| 236 | |
| 237 // TODO(nweiz): use a StreamSubscription when issue 9026 is fixed. | |
| 238 /// Returns a wrapped version of [stream] along with a function that will cancel | |
| 239 /// the wrapped stream. Unlike [StreamSubscription], this canceller will send a | |
| 240 /// "done" message to the wrapped stream. | |
| 241 Pair<Stream, StreamCanceller> streamWithCanceller(Stream stream) { | |
| 242 var controller = | |
| 243 stream.isBroadcast ? new StreamController.broadcast(sync: true) | |
| 244 : new StreamController(sync: true); | |
| 245 var controllerStream = controller.stream; | |
| 246 var subscription = stream.listen((value) { | |
| 247 if (!controller.isClosed) controller.add(value); | |
| 248 }, onError: (error, [stackTrace]) { | |
| 249 if (!controller.isClosed) controller.addError(error, stackTrace); | |
| 250 }, onDone: controller.close); | |
| 251 return new Pair<Stream, StreamCanceller>(controllerStream, controller.close); | |
| 252 } | |
| 253 | |
| 254 // TODO(nweiz): remove this when issue 7787 is fixed. | |
| 255 /// Creates two single-subscription [Stream]s that each emit all values and | |
| 256 /// errors from [stream]. This is useful if [stream] is single-subscription but | |
| 257 /// multiple subscribers are necessary. | |
| 258 Pair<Stream, Stream> tee(Stream stream) { | |
| 259 var controller1 = new StreamController(sync: true); | |
| 260 var controller2 = new StreamController(sync: true); | |
| 261 stream.listen((value) { | |
| 262 controller1.add(value); | |
| 263 controller2.add(value); | |
| 264 }, onError: (error, [stackTrace]) { | |
| 265 controller1.addError(error, stackTrace); | |
| 266 controller2.addError(error, stackTrace); | |
| 267 }, onDone: () { | |
| 268 controller1.close(); | |
| 269 controller2.close(); | |
| 270 }); | |
| 271 return new Pair<Stream, Stream>(controller1.stream, controller2.stream); | |
| 272 } | |
| 273 | |
| 274 /// Takes a simple data structure (composed of [Map]s, [Iterable]s, scalar | |
| 275 /// objects, and [Future]s) and recursively resolves all the [Future]s contained | |
| 276 /// within. Completes with the fully resolved structure. | |
| 277 Future awaitObject(object) { | |
| 278 // Unroll nested futures. | |
| 279 if (object is Future) return object.then(awaitObject); | |
| 280 if (object is Iterable) { | |
| 281 return Future.wait(object.map(awaitObject).toList()); | |
| 282 } | |
| 283 if (object is! Map) return new Future.value(object); | |
| 284 | |
| 285 var pairs = <Future<Pair>>[]; | |
| 286 object.forEach((key, value) { | |
| 287 pairs.add(awaitObject(value) | |
| 288 .then((resolved) => new Pair(key, resolved))); | |
| 289 }); | |
| 290 return Future.wait(pairs).then((resolvedPairs) { | |
| 291 var map = {}; | |
| 292 for (var pair in resolvedPairs) { | |
| 293 map[pair.first] = pair.last; | |
| 294 } | |
| 295 return map; | |
| 296 }); | |
| 297 } | |
| 298 | |
| 299 /// Returns whether [pattern] matches all of [string]. | |
| 300 bool fullMatch(String string, Pattern pattern) { | |
| 301 var matches = pattern.allMatches(string); | |
| 302 if (matches.isEmpty) return false; | |
| 303 return matches.first.start == 0 && matches.first.end == string.length; | |
| 304 } | |
| 305 | |
| 306 /// Returns a string representation of [trace] that has the core and test frames | |
| 307 /// folded together. | |
| 308 String terseTraceString(StackTrace trace) { | |
| 309 return new Chain.forTrace(trace).terse.toString().trim(); | |
| 310 } | |
| OLD | NEW |