| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012, 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 import 'dart:convert'; | |
| 9 import 'dart:typed_data'; | |
| 10 | |
| 11 import 'package:stack_trace/stack_trace.dart'; | |
| 12 | |
| 13 import 'byte_stream.dart'; | |
| 14 | |
| 15 /// Converts a URL query string (or `application/x-www-form-urlencoded` body) | |
| 16 /// into a [Map] from parameter names to values. | |
| 17 /// | |
| 18 /// queryToMap("foo=bar&baz=bang&qux"); | |
| 19 /// //=> {"foo": "bar", "baz": "bang", "qux": ""} | |
| 20 Map<String, String> queryToMap(String queryList, {Encoding encoding}) { | |
| 21 var map = {}; | |
| 22 for (var pair in queryList.split("&")) { | |
| 23 var split = split1(pair, "="); | |
| 24 if (split.isEmpty) continue; | |
| 25 var key = Uri.decodeQueryComponent(split[0], encoding: encoding); | |
| 26 var value = Uri.decodeQueryComponent(split.length > 1 ? split[1] : "", | |
| 27 encoding: encoding); | |
| 28 map[key] = value; | |
| 29 } | |
| 30 return map; | |
| 31 } | |
| 32 | |
| 33 /// Converts a [Map] from parameter names to values to a URL query string. | |
| 34 /// | |
| 35 /// mapToQuery({"foo": "bar", "baz": "bang"}); | |
| 36 /// //=> "foo=bar&baz=bang" | |
| 37 String mapToQuery(Map<String, String> map, {Encoding encoding}) { | |
| 38 var pairs = <List<String>>[]; | |
| 39 map.forEach((key, value) => | |
| 40 pairs.add([Uri.encodeQueryComponent(key, encoding: encoding), | |
| 41 Uri.encodeQueryComponent(value, encoding: encoding)])); | |
| 42 return pairs.map((pair) => "${pair[0]}=${pair[1]}").join("&"); | |
| 43 } | |
| 44 | |
| 45 /// Like [String.split], but only splits on the first occurrence of the pattern. | |
| 46 /// This will always return an array of two elements or fewer. | |
| 47 /// | |
| 48 /// split1("foo,bar,baz", ","); //=> ["foo", "bar,baz"] | |
| 49 /// split1("foo", ","); //=> ["foo"] | |
| 50 /// split1("", ","); //=> [] | |
| 51 List<String> split1(String toSplit, String pattern) { | |
| 52 if (toSplit.isEmpty) return <String>[]; | |
| 53 | |
| 54 var index = toSplit.indexOf(pattern); | |
| 55 if (index == -1) return [toSplit]; | |
| 56 return [ | |
| 57 toSplit.substring(0, index), | |
| 58 toSplit.substring(index + pattern.length) | |
| 59 ]; | |
| 60 } | |
| 61 | |
| 62 /// Returns the [Encoding] that corresponds to [charset]. Returns [fallback] if | |
| 63 /// [charset] is null or if no [Encoding] was found that corresponds to | |
| 64 /// [charset]. | |
| 65 Encoding encodingForCharset(String charset, [Encoding fallback = LATIN1]) { | |
| 66 if (charset == null) return fallback; | |
| 67 var encoding = Encoding.getByName(charset); | |
| 68 return encoding == null ? fallback : encoding; | |
| 69 } | |
| 70 | |
| 71 | |
| 72 /// Returns the [Encoding] that corresponds to [charset]. Throws a | |
| 73 /// [FormatException] if no [Encoding] was found that corresponds to [charset]. | |
| 74 /// [charset] may not be null. | |
| 75 Encoding requiredEncodingForCharset(String charset) { | |
| 76 var encoding = Encoding.getByName(charset); | |
| 77 if (encoding != null) return encoding; | |
| 78 throw new FormatException('Unsupported encoding "$charset".'); | |
| 79 } | |
| 80 | |
| 81 /// A regular expression that matches strings that are composed entirely of | |
| 82 /// ASCII-compatible characters. | |
| 83 final RegExp _ASCII_ONLY = new RegExp(r"^[\x00-\x7F]+$"); | |
| 84 | |
| 85 /// Returns whether [string] is composed entirely of ASCII-compatible | |
| 86 /// characters. | |
| 87 bool isPlainAscii(String string) => _ASCII_ONLY.hasMatch(string); | |
| 88 | |
| 89 /// Converts [input] into a [Uint8List]. | |
| 90 /// | |
| 91 /// If [input] is a [TypedData], this just returns a view on [input]. | |
| 92 Uint8List toUint8List(List<int> input) { | |
| 93 if (input is Uint8List) return input; | |
| 94 if (input is TypedData) { | |
| 95 // TODO(nweiz): remove "as" when issue 11080 is fixed. | |
| 96 return new Uint8List.view((input as TypedData).buffer); | |
| 97 } | |
| 98 return new Uint8List.fromList(input); | |
| 99 } | |
| 100 | |
| 101 /// If [stream] is already a [ByteStream], returns it. Otherwise, wraps it in a | |
| 102 /// [ByteStream]. | |
| 103 ByteStream toByteStream(Stream<List<int>> stream) { | |
| 104 if (stream is ByteStream) return stream; | |
| 105 return new ByteStream(stream); | |
| 106 } | |
| 107 | |
| 108 /// Calls [onDone] once [stream] (a single-subscription [Stream]) is finished. | |
| 109 /// The return value, also a single-subscription [Stream] should be used in | |
| 110 /// place of [stream] after calling this method. | |
| 111 Stream onDone(Stream stream, void onDone()) { | |
| 112 var pair = tee(stream); | |
| 113 pair.first.listen((_) {}, onError: (_) {}, onDone: onDone); | |
| 114 return pair.last; | |
| 115 } | |
| 116 | |
| 117 // TODO(nweiz): remove this when issue 7786 is fixed. | |
| 118 /// Pipes all data and errors from [stream] into [sink]. When [stream] is done, | |
| 119 /// [sink] is closed and the returned [Future] is completed. | |
| 120 Future store(Stream stream, EventSink sink) { | |
| 121 var completer = new Completer(); | |
| 122 stream.listen(sink.add, | |
| 123 onError: sink.addError, | |
| 124 onDone: () { | |
| 125 sink.close(); | |
| 126 completer.complete(); | |
| 127 }); | |
| 128 return completer.future; | |
| 129 } | |
| 130 | |
| 131 /// Pipes all data and errors from [stream] into [sink]. Completes [Future] once | |
| 132 /// [stream] is done. Unlike [store], [sink] remains open after [stream] is | |
| 133 /// done. | |
| 134 Future writeStreamToSink(Stream stream, EventSink sink) { | |
| 135 var completer = new Completer(); | |
| 136 stream.listen(sink.add, | |
| 137 onError: sink.addError, | |
| 138 onDone: () => completer.complete()); | |
| 139 return completer.future; | |
| 140 } | |
| 141 | |
| 142 /// Returns a [Future] that asynchronously completes to `null`. | |
| 143 Future get async => new Future.value(); | |
| 144 | |
| 145 /// Returns a closed [Stream] with no elements. | |
| 146 Stream get emptyStream => streamFromIterable([]); | |
| 147 | |
| 148 /// Creates a single-subscription stream that emits the items in [iter] and then | |
| 149 /// ends. | |
| 150 Stream streamFromIterable(Iterable iter) { | |
| 151 var controller = new StreamController(sync: true); | |
| 152 iter.forEach(controller.add); | |
| 153 controller.close(); | |
| 154 return controller.stream; | |
| 155 } | |
| 156 | |
| 157 // TODO(nweiz): remove this when issue 7787 is fixed. | |
| 158 /// Creates two single-subscription [Stream]s that each emit all values and | |
| 159 /// errors from [stream]. This is useful if [stream] is single-subscription but | |
| 160 /// multiple subscribers are necessary. | |
| 161 Pair<Stream, Stream> tee(Stream stream) { | |
| 162 var controller1 = new StreamController(sync: true); | |
| 163 var controller2 = new StreamController(sync: true); | |
| 164 stream.listen((value) { | |
| 165 controller1.add(value); | |
| 166 controller2.add(value); | |
| 167 }, onError: (error, [StackTrace stackTrace]) { | |
| 168 controller1.addError(error, stackTrace); | |
| 169 controller2.addError(error, stackTrace); | |
| 170 }, onDone: () { | |
| 171 controller1.close(); | |
| 172 controller2.close(); | |
| 173 }); | |
| 174 return new Pair<Stream, Stream>(controller1.stream, controller2.stream); | |
| 175 } | |
| 176 | |
| 177 /// A pair of values. | |
| 178 class Pair<E, F> { | |
| 179 E first; | |
| 180 F last; | |
| 181 | |
| 182 Pair(this.first, this.last); | |
| 183 | |
| 184 String toString() => '($first, $last)'; | |
| 185 | |
| 186 bool operator==(other) { | |
| 187 if (other is! Pair) return false; | |
| 188 return other.first == first && other.last == last; | |
| 189 } | |
| 190 | |
| 191 int get hashCode => first.hashCode ^ last.hashCode; | |
| 192 } | |
| 193 | |
| 194 /// Configures [future] so that its result (success or exception) is passed on | |
| 195 /// to [completer]. | |
| 196 void chainToCompleter(Future future, Completer completer) { | |
| 197 future.then(completer.complete, onError: completer.completeError); | |
| 198 } | |
| 199 | |
| 200 /// Like [Future.sync], but wraps the Future in [Chain.track] as well. | |
| 201 Future syncFuture(callback()) => Chain.track(new Future.sync(callback)); | |
| OLD | NEW |