utils library
Generic utility functions. Stuff that should possibly be in core.
Functions
Future awaitObject(object) #
Takes a simple data structure (composed of Maps, Iterables, scalar objects, and Futures) and recursively resolves all the Futures contained within. Completes with the fully resolved structure.
Future awaitObject(object) {
// Unroll nested futures.
if (object is Future) return object.then(awaitObject);
if (object is Iterable) {
return Future.wait(object.map(awaitObject).toList());
}
if (object is! Map) return new Future.value(object);
var pairs = <Future<Pair>>[];
object.forEach((key, value) {
pairs.add(awaitObject(value)
.then((resolved) => new Pair(key, resolved)));
});
return Future.wait(pairs).then((resolvedPairs) {
var map = {};
for (var pair in resolvedPairs) {
map[pair.first] = pair.last;
}
return map;
});
}
String urlDecode(String encoded) #
Decodes a URL-encoded string. Unlike decodeUriComponent, this includes
replacing + with .
String urlDecode(String encoded) =>
Uri.decodeComponent(encoded.replaceAll("+", " "));
void mapAddAll(Map destination, Map source) #
Add all key/value pairs from source to destination, overwriting any pre-existing values.
void mapAddAll(Map destination, Map source) => source.forEach((key, value) => destination[key] = value);
Uri canonicalizeUri(Uri uri) #
Return uri with redundant port information removed.
Uri canonicalizeUri(Uri uri) {
if (uri == null) return null;
var sansPort = new Uri(
scheme: uri.scheme, userInfo: uri.userInfo, host: uri.host,
path: uri.path, query: uri.query, fragment: uri.fragment);
if (uri.scheme == 'http' && uri.port == 80) return sansPort;
if (uri.scheme == 'https' && uri.port == 443) return sansPort;
return uri;
}
bool urisEqual(Uri uri1, Uri uri2) #
Whether uri1 and uri2 are equal. This consider HTTP URIs to default to port 80, and HTTPs URIs to default to port 443.
bool urisEqual(Uri uri1, Uri uri2) => canonicalizeUri(uri1) == canonicalizeUri(uri2);
String mapToQuery(Map<String, String> map) #
Convert a Map from parameter names to values to a URL query string.
String mapToQuery(Map<String, String> map) {
var pairs = <List<String>>[];
map.forEach((key, value) {
key = Uri.encodeComponent(key);
value = (value == null || value.isEmpty) ? null : Uri.encodeComponent(value);
pairs.add([key, value]);
});
return pairs.map((pair) {
if (pair[1] == null) return pair[0];
return "${pair[0]}=${pair[1]}";
}).join("&");
}
Map<String, String> queryToMap(String queryList) #
Convert a URL query string (or application/x-www-form-urlencoded body)
into a Map from parameter names to values.
Map<String, String> queryToMap(String queryList) {
var map = {};
for (var pair in queryList.split("&")) {
var split = split1(pair, "=");
if (split.isEmpty) continue;
var key = urlDecode(split[0]);
var value = split.length > 1 ? urlDecode(split[1]) : "";
map[key] = value;
}
return map;
}
Uri addQueryParameters(Uri url, Map<String, String> parameters) #
Adds additional query parameters to url, overwriting the original parameters if a name conflict occurs.
Uri addQueryParameters(Uri url, Map<String, String> parameters) {
var queryMap = queryToMap(url.query);
mapAddAll(queryMap, parameters);
return url.resolve("?${mapToQuery(queryMap)}");
}
List<String> split1(String toSplit, String pattern) #
Like String.split, but only splits on the first occurrence of the pattern. This will always return an array of two elements or fewer.
List<String> split1(String toSplit, String pattern) {
if (toSplit.isEmpty) return <String>[];
var index = toSplit.indexOf(pattern);
if (index == -1) return [toSplit];
return [toSplit.substring(0, index),
toSplit.substring(index + pattern.length)];
}
Future<Iterable> futureWhere(Iterable iter, test(value)) #
Like Iterable.where, but allows test to return Futures and uses the results of those Futures as the test.
Future<Iterable> futureWhere(Iterable iter, test(value)) {
return Future.wait(iter.map((e) {
var result = test(e);
if (result is! Future) result = new Future.value(result);
return result.then((result) => new Pair(e, result));
}))
.then((pairs) => pairs.where((pair) => pair.last))
.then((pairs) => pairs.map((pair) => pair.first));
}
Stream<String> streamToLines(Stream<String> stream) #
Converts a stream of arbitrarily chunked strings into a line-by-line stream. The lines don't include line termination characters. A single trailing newline is ignored.
Stream<String> streamToLines(Stream<String> stream) {
var buffer = new StringBuffer();
return stream.transform(new StreamTransformer(
handleData: (chunk, sink) {
var lines = splitLines(chunk);
var leftover = lines.removeLast();
for (var line in lines) {
if (!buffer.isEmpty) {
buffer.write(line);
line = buffer.toString();
buffer = new StringBuffer();
}
sink.add(line);
}
buffer.write(leftover);
},
handleDone: (sink) {
if (!buffer.isEmpty) sink.add(buffer.toString());
sink.close();
}));
}
List<String> splitLines(String text) #
Splits text on its line breaks in a Windows-line-break-friendly way.
List<String> splitLines(String text) =>
text.split("\n").map((line) => line.replaceFirst(_trailingCR, "")).toList();
Pair<Stream, Stream> tee(Stream stream) #
Creates two single-subscription Streams that each emit all values and errors from stream. This is useful if stream is single-subscription but multiple subscribers are necessary.
Pair<Stream, Stream> tee(Stream stream) {
var controller1 = new StreamController();
var controller2 = new StreamController();
stream.listen((value) {
controller1.add(value);
controller2.add(value);
}, onError: (error) {
controller1.addError(error);
controller2.addError(error);
}, onDone: () {
controller1.close();
controller2.close();
});
return new Pair<Stream, Stream>(controller1.stream, controller2.stream);
}
Pair<Stream, StreamSubscription> streamWithSubscription(Stream stream) #
Returns a wrapped version of stream along with a StreamSubscription that can be used to control the wrapped stream.
Pair<Stream, StreamSubscription> streamWithSubscription(Stream stream) {
var controller = new StreamController();
var controllerStream = stream.isBroadcast ?
controller.stream.asBroadcastStream() :
controller.stream;
var subscription = stream.listen(controller.add,
onError: controller.addError,
onDone: controller.close);
return new Pair<Stream, StreamSubscription>(controllerStream, subscription);
}
Future streamFirst(Stream stream) #
Returns a Future that will complete to the first element of stream. Unlike Stream.first, this is safe to use with single-subscription streams.
Future streamFirst(Stream stream) {
var completer = new Completer();
var subscription;
subscription = stream.listen((value) {
subscription.cancel();
completer.complete(value);
}, onError: (e) {
completer.completeError(e);
}, onDone: () {
completer.completeError(new StateError("No elements"));
}, cancelOnError: true);
return completer.future;
}
void chainToCompleter(Future future, Completer completer) #
Configures future so that its result (success or exception) is passed on to completer.
void chainToCompleter(Future future, Completer completer) {
future.then((value) => completer.complete(value),
onError: (e) => completer.completeError(e));
}
Future sleep(int milliseconds) #
Returns a Future that completes in milliseconds.
Future sleep(int milliseconds) {
var completer = new Completer();
new Timer(new Duration(milliseconds: milliseconds), completer.complete);
return completer.future;
}
String sha1(String source) #
Returns the hex-encoded sha1 hash of source.
String sha1(String source) {
var sha = new SHA1();
sha.add(source.codeUnits);
return CryptoUtils.bytesToHex(sha.close());
}
bool endsWithPattern(String str, Pattern matcher) #
Returns whether or not str ends with matcher.
bool endsWithPattern(String str, Pattern matcher) {
for (var match in matcher.allMatches(str)) {
if (match.end == str.length) return true;
}
return false;
}
String replace(String source, Pattern matcher, String fn(Match)) #
Replace each instance of matcher in source with the return value of fn.
String replace(String source, Pattern matcher, String fn(Match)) {
var buffer = new StringBuffer();
var start = 0;
for (var match in matcher.allMatches(source)) {
buffer.write(source.substring(start, match.start));
start = match.end;
buffer.write(fn(match));
}
buffer.write(source.substring(start));
return buffer.toString();
}
Set setMinus(Iterable minuend, Iterable subtrahend) #
Returns a set containing all elements in minuend that are not in subtrahend.
Set setMinus(Iterable minuend, Iterable subtrahend) {
var minuendSet = new Set.from(minuend);
minuendSet.removeAll(subtrahend);
return minuendSet;
}
dynamic only(Iterable iter) #
Asserts that iter contains only one element, and returns it.
only(Iterable iter) {
var iterator = iter.iterator;
var currentIsValid = iterator.moveNext();
assert(currentIsValid);
var obj = iterator.current;
assert(!iterator.moveNext());
return obj;
}
List flatten(Iterable nested) #
Flattens nested lists inside an iterable into a single list containing only non-list elements.
List flatten(Iterable nested) {
var result = [];
helper(list) {
for (var element in list) {
if (element is List) {
helper(element);
} else {
result.add(element);
}
}
}
helper(nested);
return result;
}