blob: 5d7a87298c625ff73ffd481ec9103f8d70f6bcaf [file] [log] [blame]
library pub.utils;
import 'dart:async';
import "dart:convert";
import 'dart:io';
@MirrorsUsed(targets: const ['pub.io', 'test_pub'])
import 'dart:mirrors';
import "package:crypto/crypto.dart";
import 'package:path/path.dart' as path;
import "package:stack_trace/stack_trace.dart";
import 'exceptions.dart';
import 'log.dart' as log;
export '../../asset/dart/utils.dart';
class Pair<E, F> {
E first;
F last;
Pair(this.first, this.last);
String toString() => '($first, $last)';
bool operator ==(other) {
if (other is! Pair) return false;
return other.first == first && other.last == last;
}
int get hashCode => first.hashCode ^ last.hashCode;
}
class FutureGroup<T> {
int _pending = 0;
Completer<List<T>> _completer = new Completer<List<T>>();
final List<Future<T>> futures = <Future<T>>[];
bool completed = false;
final List<T> _values = <T>[];
Future<T> add(Future<T> task) {
if (completed) {
throw new StateError("The FutureGroup has already completed.");
}
_pending++;
futures.add(task.then((value) {
if (completed) return;
_pending--;
_values.add(value);
if (_pending <= 0) {
completed = true;
_completer.complete(_values);
}
}).catchError((e, stackTrace) {
if (completed) return;
completed = true;
_completer.completeError(e, stackTrace);
}));
return task;
}
Future<List> get future => _completer.future;
}
Future newFuture(callback()) => new Future.value().then((_) => callback());
Future captureErrors(Future callback(), {bool captureStackChains: false}) {
var completer = new Completer();
var wrappedCallback = () {
new Future.sync(
callback).then(completer.complete).catchError((e, stackTrace) {
if (stackTrace != null) {
stackTrace = new Chain.forTrace(stackTrace);
} else {
stackTrace = new Chain([]);
}
completer.completeError(e, stackTrace);
});
};
if (captureStackChains) {
Chain.capture(wrappedCallback, onError: completer.completeError);
} else {
runZoned(wrappedCallback, onError: (e, stackTrace) {
if (stackTrace == null) {
stackTrace = new Chain.current();
} else {
stackTrace = new Chain([new Trace.from(stackTrace)]);
}
completer.completeError(e, stackTrace);
});
}
return completer.future;
}
Future waitAndPrintErrors(Iterable<Future> futures) {
return Future.wait(futures.map((future) {
return future.catchError((error, stackTrace) {
log.exception(error, stackTrace);
throw error;
});
})).catchError((error, stackTrace) {
throw new SilentException(error, stackTrace);
});
}
StreamTransformer onDoneTransformer(void onDone()) {
return new StreamTransformer.fromHandlers(handleDone: (sink) {
onDone();
sink.close();
});
}
String padRight(String source, int length) {
final result = new StringBuffer();
result.write(source);
while (result.length < length) {
result.write(' ');
}
return result.toString();
}
String namedSequence(String name, Iterable iter, [String plural]) {
if (iter.length == 1) return "$name ${iter.single}";
if (plural == null) plural = "${name}s";
return "$plural ${toSentence(iter)}";
}
String toSentence(Iterable iter) {
if (iter.length == 1) return iter.first.toString();
return iter.take(iter.length - 1).join(", ") + " and ${iter.last}";
}
String pluralize(String name, int number, {String plural}) {
if (number == 1) return name;
if (plural != null) return plural;
return '${name}s';
}
String quoteRegExp(String string) {
for (var metacharacter in r"\^$.*+?()[]{}|".split("")) {
string = string.replaceAll(metacharacter, "\\$metacharacter");
}
return string;
}
Uri baseUrlForAddress(InternetAddress address, int port) {
if (address.isLoopback) {
return new Uri(scheme: "http", host: "localhost", port: port);
}
if (address.type == InternetAddressType.IP_V6) {
return new Uri(scheme: "http", host: "[${address.address}]", port: port);
}
return new Uri(scheme: "http", host: address.address, port: port);
}
bool isLoopback(String host) {
if (host == 'localhost') return true;
if (host.startsWith("[") && host.endsWith("]")) {
host = host.substring(1, host.length - 1);
}
try {
return new InternetAddress(host).isLoopback;
} on ArgumentError catch (_) {
return false;
}
}
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;
}
Set setMinus(Iterable minuend, Iterable subtrahend) {
var minuendSet = new Set.from(minuend);
minuendSet.removeAll(subtrahend);
return minuendSet;
}
bool overlaps(Set set1, Set set2) {
var smaller = set1.length > set2.length ? set1 : set2;
var larger = smaller == set1 ? set2 : set1;
return smaller.any(larger.contains);
}
List ordered(Iterable<Comparable> iter) {
var list = iter.toList();
list.sort();
return list;
}
minBy(Iterable iter, Comparable f(element)) {
var min = null;
var minComparable = null;
for (var element in iter) {
var comparable = f(element);
if (minComparable == null || comparable.compareTo(minComparable) < 0) {
min = element;
minComparable = comparable;
}
}
return min;
}
Iterable<Pair> pairs(Iterable iter) {
var previous = iter.first;
return iter.skip(1).map((element) {
var oldPrevious = previous;
previous = element;
return new Pair(oldPrevious, element);
});
}
Map mapMap(Map map, {key(key, value), value(key, value)}) {
if (key == null) key = (key, _) => key;
if (value == null) value = (_, value) => value;
var result = {};
map.forEach((mapKey, mapValue) {
result[key(mapKey, mapValue)] = value(mapKey, mapValue);
});
return result;
}
Future<Map> mapFromIterableAsync(Iterable iter, {key(element), value(element)})
{
if (key == null) key = (element) => element;
if (value == null) value = (element) => element;
var map = new Map();
return Future.wait(iter.map((element) {
return Future.wait(
[
new Future.sync(() => key(element)),
new Future.sync(() => value(element))]).then((results) {
map[results[0]] = results[1];
});
})).then((_) => map);
}
Map<dynamic, Set> transitiveClosure(Map<dynamic, Iterable> graph) {
var result = {};
graph.forEach((vertex, edges) {
result[vertex] = new Set.from(edges)..add(vertex);
});
for (var vertex1 in graph.keys) {
for (var vertex2 in graph.keys) {
for (var vertex3 in graph.keys) {
if (result[vertex2].contains(vertex1) &&
result[vertex1].contains(vertex3)) {
result[vertex2].add(vertex3);
}
}
}
}
return result;
}
Set<String> createFileFilter(Iterable<String> files) {
return files.expand((file) {
var result = ["/$file"];
if (Platform.operatingSystem == 'windows') result.add("\\$file");
return result;
}).toSet();
}
Set<String> createDirectoryFilter(Iterable<String> dirs) {
return dirs.expand((dir) {
var result = ["/$dir/"];
if (Platform.operatingSystem == 'windows') {
result
..add("/$dir\\")
..add("\\$dir/")
..add("\\$dir\\");
}
return result;
}).toSet();
}
maxAll(Iterable iter, [int compare(element1, element2)]) {
if (compare == null) compare = Comparable.compare;
return iter.reduce(
(max, element) => compare(element, max) > 0 ? element : max);
}
minAll(Iterable iter, [int compare(element1, element2)]) {
if (compare == null) compare = Comparable.compare;
return iter.reduce(
(max, element) => compare(element, max) < 0 ? element : max);
}
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();
}
bool endsWithPattern(String str, Pattern matcher) {
for (var match in matcher.allMatches(str)) {
if (match.end == str.length) return true;
}
return false;
}
String sha1(String source) {
var sha = new SHA1();
sha.add(source.codeUnits);
return CryptoUtils.bytesToHex(sha.close());
}
void chainToCompleter(Future future, Completer completer) {
future.then(completer.complete, onError: completer.completeError);
}
Future<Stream> validateStream(Stream stream) {
var completer = new Completer<Stream>();
var controller = new StreamController(sync: true);
StreamSubscription subscription;
subscription = stream.listen((value) {
if (!completer.isCompleted) completer.complete(controller.stream);
controller.add(value);
}, onError: (error, [stackTrace]) {
if (completer.isCompleted) {
controller.addError(error, stackTrace);
return;
}
completer.completeError(error, stackTrace);
subscription.cancel();
}, onDone: () {
if (!completer.isCompleted) completer.complete(controller.stream);
controller.close();
});
return completer.future;
}
Future streamFirst(Stream stream) {
var completer = new Completer();
var subscription;
subscription = stream.listen((value) {
subscription.cancel();
completer.complete(value);
}, onError: (e, [stackTrace]) {
completer.completeError(e, stackTrace);
}, onDone: () {
completer.completeError(new StateError("No elements"), new Chain.current());
}, cancelOnError: true);
return completer.future;
}
Pair<Stream, StreamSubscription> streamWithSubscription(Stream stream) {
var controller = stream.isBroadcast ?
new StreamController.broadcast(sync: true) :
new StreamController(sync: true);
var subscription = stream.listen(
controller.add,
onError: controller.addError,
onDone: controller.close);
return new Pair<Stream, StreamSubscription>(controller.stream, subscription);
}
Pair<Stream, Stream> tee(Stream stream) {
var controller1 = new StreamController(sync: true);
var controller2 = new StreamController(sync: true);
stream.listen((value) {
controller1.add(value);
controller2.add(value);
}, onError: (error, [stackTrace]) {
controller1.addError(error, stackTrace);
controller2.addError(error, stackTrace);
}, onDone: () {
controller1.close();
controller2.close();
});
return new Pair<Stream, Stream>(controller1.stream, controller2.stream);
}
Stream mergeStreams(Stream stream1, Stream stream2) {
var doneCount = 0;
var controller = new StreamController(sync: true);
for (var stream in [stream1, stream2]) {
stream.listen(controller.add, onError: controller.addError, onDone: () {
doneCount++;
if (doneCount == 2) controller.close();
});
}
return controller.stream;
}
final _trailingCR = new RegExp(r"\r$");
List<String> splitLines(String text) =>
text.split("\n").map((line) => line.replaceFirst(_trailingCR, "")).toList();
Stream<String> streamToLines(Stream<String> stream) {
var buffer = new StringBuffer();
return stream.transform(
new StreamTransformer.fromHandlers(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();
}));
}
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));
}
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)];
}
Uri addQueryParameters(Uri url, Map<String, String> parameters) {
var queryMap = queryToMap(url.query);
queryMap.addAll(parameters);
return url.resolve("?${mapToQuery(queryMap)}");
}
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;
}
String mapToQuery(Map<String, String> map) {
var pairs = <List<String>>[];
map.forEach((key, value) {
key = Uri.encodeQueryComponent(key);
value =
(value == null || value.isEmpty) ? null : Uri.encodeQueryComponent(value);
pairs.add([key, value]);
});
return pairs.map((pair) {
if (pair[1] == null) return pair[0];
return "${pair[0]}=${pair[1]}";
}).join("&");
}
Set unionAll(Iterable<Set> sets) =>
sets.fold(new Set(), (union, set) => union.union(set));
bool urisEqual(Uri uri1, Uri uri2) =>
canonicalizeUri(uri1) == canonicalizeUri(uri2);
Uri canonicalizeUri(Uri uri) {
return uri;
}
String nicePath(String inputPath) {
var relative = path.relative(inputPath);
var split = path.split(relative);
if (split.length > 1 && split[0] == '..' && split[1] == '..') {
return path.absolute(inputPath);
}
return relative;
}
String niceDuration(Duration duration) {
var result = duration.inMinutes > 0 ? "${duration.inMinutes}:" : "";
var s = duration.inSeconds % 59;
var ms = (duration.inMilliseconds % 1000) ~/ 100;
return result + "$s.${ms}s";
}
String urlDecode(String encoded) =>
Uri.decodeComponent(encoded.replaceAll("+", " "));
Future awaitObject(object) {
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 libraryPath(String libraryName) {
var lib = currentMirrorSystem().findLibrary(new Symbol(libraryName));
return path.fromUri(lib.uri);
}
bool get canUseSpecialChars =>
!runningAsTest &&
Platform.operatingSystem != 'windows' &&
stdioType(stdout) == StdioType.TERMINAL;
String getSpecial(String special, [String onWindows = '']) =>
canUseSpecialChars ? special : onWindows;
String prefixLines(String text, {String prefix: '| ', String firstPrefix}) {
var lines = text.split('\n');
if (firstPrefix == null) {
return lines.map((line) => '$prefix$line').join('\n');
}
var firstLine = "$firstPrefix${lines.first}";
lines = lines.skip(1).map((line) => '$prefix$line').toList();
lines.insert(0, firstLine);
return lines.join('\n');
}
bool runningAsTest = Platform.environment.containsKey('_PUB_TESTING');
bool get isAprilFools {
if (runningAsTest) return false;
var date = new DateTime.now();
return date.month == 4 && date.day == 1;
}
Future resetStack(fn()) {
var completer = new Completer();
newFuture(fn).then((val) {
scheduleMicrotask(() => completer.complete(val));
}).catchError((err, stackTrace) {
scheduleMicrotask(() => completer.completeError(err, stackTrace));
});
return completer.future;
}
final _unquotableYamlString = new RegExp(r"^[a-zA-Z_-][a-zA-Z_0-9-]*$");
String yamlToString(data) {
var buffer = new StringBuffer();
_stringify(bool isMapValue, String indent, data) {
if (data is Map && !data.isEmpty) {
if (isMapValue) {
buffer.writeln();
indent += ' ';
}
var keys = data.keys.toList();
keys.sort((a, b) => a.toString().compareTo(b.toString()));
var first = true;
for (var key in keys) {
if (!first) buffer.writeln();
first = false;
var keyString = key;
if (key is! String || !_unquotableYamlString.hasMatch(key)) {
keyString = JSON.encode(key);
}
buffer.write('$indent$keyString:');
_stringify(true, indent, data[key]);
}
return;
}
var string = data;
if (data is! String || !_unquotableYamlString.hasMatch(data)) {
string = JSON.encode(data);
}
if (isMapValue) {
buffer.write(' $string');
} else {
buffer.write('$indent$string');
}
}
_stringify(false, '', data);
return buffer.toString();
}
void fail(String message, [innerError, StackTrace innerTrace]) {
if (innerError != null) {
throw new WrappedException(message, innerError, innerTrace);
} else {
throw new ApplicationException(message);
}
}
void dataError(String message) => throw new DataException(message);