blob: 690f92fb7622220ac6b0447da9b8435e4b0db6ae [file] [log] [blame]
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library descriptor.file;
import 'dart:async';
import 'dart:io';
import '../../../../../pkg/pathos/lib/path.dart' as path;
import '../../descriptor.dart';
import '../../scheduled_test.dart';
import '../utils.dart';
/// A path builder to ensure that [load] uses POSIX paths.
final path.Builder _path = new path.Builder(style: path.Style.posix);
/// A descriptor describing a directory containing multiple files.
class DirectoryDescriptor extends Descriptor {
/// The entries contained within this directory. This is intentionally
/// mutable.
final List<Descriptor> contents;
DirectoryDescriptor(String name, Iterable<Descriptor> contents)
: super(name),
contents = contents.toList();
Future create([String parent]) => schedule(() {
if (parent == null) parent = defaultRoot;
var fullPath = path.join(parent, name);
return new Directory(fullPath).create(recursive: true).then((_) {
return Future.wait(
contents.map((entry) => entry.create(fullPath)).toList());
});
}, 'creating directory:\n${describe()}');
Future validate([String parent]) => schedule(() => validateNow(parent),
'validating directory:\n${describe()}');
Future validateNow([String parent]) {
if (parent == null) parent = defaultRoot;
var fullPath = path.join(parent, name);
if (!new Directory(fullPath).existsSync()) {
throw "Directory not found: '$fullPath'.";
}
return Future.wait(contents.map((entry) {
return new Future.of(() => entry.validateNow(fullPath))
.then((_) => null)
.catchError((e) => e.error);
})).then((results) {
var errors = results.where((e) => e != null);
if (errors.isEmpty) return;
throw _DirectoryValidationError.merge(errors);
});
}
Stream<List<int>> load(String pathToLoad) {
return futureStream(new Future.immediate(null).then((_) {
if (_path.isAbsolute(pathToLoad)) {
throw "Can't load absolute path '$pathToLoad'.";
}
var split = _path.split(_path.normalize(pathToLoad));
if (split.isEmpty || split.first == '.' || split.first == '..') {
throw "Can't load '$pathToLoad' from within '$name'.";
}
var matchingEntries = contents.where((entry) =>
entry.name == split.first).toList();
if (matchingEntries.length == 0) {
throw "Couldn't find an entry named '${split.first}' within '$name'.";
} else if (matchingEntries.length > 1) {
throw "Found multiple entries named '${split.first}' within '$name'.";
} else {
var remainingPath = split.sublist(1);
if (remainingPath.isEmpty) {
return matchingEntries.first.read();
} else {
return matchingEntries.first.load(_path.joinAll(remainingPath));
}
}
}));
}
Stream<List<int>> read() => errorStream("Can't read the contents of '$name': "
"is a directory.");
String describe() {
if (contents.isEmpty) return name;
var buffer = new StringBuffer();
buffer.writeln(name);
for (var entry in contents.take(contents.length - 1)) {
var entryString = prefixLines(entry.describe(), prefix: '| ')
.replaceFirst('| ', '|-- ');
buffer.writeln(entryString);
}
var lastEntryString = prefixLines(contents.last.describe(), prefix: ' ')
.replaceFirst(' ', "'-- ");
buffer.write(lastEntryString);
return buffer.toString();
}
}
/// A class for formatting errors thrown by [DirectoryDescriptor].
class _DirectoryValidationError {
final Collection<String> errors;
/// Flatten nested [_DirectoryValidationError]s in [errors] to create a single
/// list of errors.
static _DirectoryValidationError merge(Iterable errors) {
return new _DirectoryValidationError(errors.expand((error) {
if (error is _DirectoryValidationError) return error.errors;
return [error];
}));
}
_DirectoryValidationError(Iterable errors)
: errors = errors.map((e) => e.toString()).toList();
String toString() {
if (errors.length == 1) return errors.single;
return errors.map((e) => prefixLines(e, prefix: ' ', firstPrefix: '* '))
.join('\n');
}
}