blob: f3542bdc9bdfadc6852fdac6322afdcbe4fb61db [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: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.
const utf8 = 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;
final buffer = StringBuffer();
buffer.writeln(name);
for (var entry in contents.take(contents.length - 1)) {
final entryString = prefixLines(
entry.describe(),
'${glyph.verticalLine} ',
first: '${glyph.teeRight}${glyph.horizontalLine}'
'${glyph.horizontalLine} ',
);
buffer.writeln(entryString);
}
final 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;
final lines = text.split('\n');
if (lines.length == 1) return '$single$text';
final 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.
future.catchError(
// ignore: body_might_complete_normally_catch_error
(Object error, StackTrace stackTrace) {
if (!errored) {
errored = true;
throw error; // ignore: only_throw_errors
} else {
registerException(error, stackTrace);
}
},
),
),
);
}