blob: d9d80173e3c275db927b939603561ad9f06314c5 [file] [log] [blame]
// Copyright (c) 2017, 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.
import 'dart:async';
import 'dart:convert';
import 'package:path/path.dart' as p;
import 'package:term_glyph/term_glyph.dart' as glyph;
import 'package:test/test.dart';
import 'descriptor.dart';
import 'sandbox.dart';
/// A UTF-8 codec that allows malformed byte sequences.
final utf8 = const Utf8Codec(allowMalformed: true);
/// Prepends a vertical bar to [text].
String addBar(String text) => prefixLines(text, '${glyph.verticalLine} ',
first: '${glyph.downEnd} ', last: '${glyph.upEnd} ', single: '| ');
/// Indents [text], and adds a bullet at the beginning.
String addBullet(String text) =>
prefixLines(text, ' ', first: '${glyph.bullet} ');
/// Converts [strings] to a bulleted list.
String bullet(Iterable<String> strings) => strings.map(addBullet).join('\n');
/// Returns a human-readable description of a directory with the given [name]
/// and [contents].
String describeDirectory(String name, List<Descriptor> contents) {
if (contents.isEmpty) return name;
var buffer = StringBuffer();
buffer.writeln(name);
for (var entry in contents.take(contents.length - 1)) {
var entryString = prefixLines(entry.describe(), '${glyph.verticalLine} ',
first: '${glyph.teeRight}${glyph.horizontalLine}'
'${glyph.horizontalLine} ');
buffer.writeln(entryString);
}
var lastEntryString = prefixLines(contents.last.describe(), ' ',
first: '${glyph.bottomLeftCorner}${glyph.horizontalLine}'
'${glyph.horizontalLine} ');
buffer.write(lastEntryString);
return buffer.toString();
}
/// Prepends each line in [text] with [prefix].
///
/// If [first] or [last] is passed, the first and last lines, respectively, are
/// prefixed with those instead. If [single] is passed, it's used if there's
/// only a single line; otherwise, [first], [last], or [prefix] is used, in that
/// order of precedence.
String prefixLines(String text, String prefix,
{String? first, String? last, String? single}) {
single ??= first ?? last ?? prefix;
first ??= prefix;
last ??= prefix;
var lines = text.split('\n');
if (lines.length == 1) return '$single$text';
var buffer = StringBuffer('$first${lines.first}\n');
for (var line in lines.skip(1).take(lines.length - 2)) {
buffer.writeln('$prefix$line');
}
buffer.write('$last${lines.last}');
return buffer.toString();
}
/// Returns a representation of [path] that's easy for humans to read.
///
/// This may not be a valid path relative to [p.current].
String prettyPath(String path) {
if (sandboxExists && p.isWithin(sandbox, path)) {
return p.relative(path, from: sandbox);
} else if (p.isWithin(p.current, path)) {
return p.relative(path);
} else {
return path;
}
}
/// Returns whether [pattern] matches all of [string].
bool matchesAll(Pattern pattern, String string) =>
pattern.matchAsPrefix(string)?.end == string.length;
/// Like [Future.wait] with `eagerError: true`, but reports errors after the
/// first using [registerException] rather than silently ignoring them.
Future<List<T>> waitAndReportErrors<T>(Iterable<Future<T>> futures) {
var errored = false;
return Future.wait(futures.map((future) {
// Avoid async/await so that we synchronously add error handlers for the
// futures to keep them from top-leveling.
return future.catchError((Object error, StackTrace stackTrace) {
if (!errored) {
errored = true;
throw error; // ignore: only_throw_errors
} else {
registerException(error, stackTrace);
}
});
}));
}