| // 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 'package:path/path.dart' as p; |
| import 'package:term_glyph/term_glyph.dart' as glyph; |
| import 'package:test/test.dart'; |
| |
| import 'sandbox.dart'; |
| |
| /// 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"); |
| |
| /// 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}) { |
| first ??= prefix; |
| last ??= prefix; |
| single ??= first ?? last ?? prefix; |
| |
| var lines = text.split('\n'); |
| if (lines.length == 1) return "$single$text"; |
| |
| var buffer = new 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 waitAndReportErrors(Iterable<Future> 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((error, stackTrace) { |
| if (!errored) { |
| errored = true; |
| throw error; |
| } else { |
| registerException(error, stackTrace); |
| } |
| }); |
| })); |
| } |