blob: 9b0a789bd32a293a6d6f3acb1583cb8493062e69 [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.
import 'description.dart';
import 'interfaces.dart';
import 'util.dart';
/// Returns a pretty-printed representation of [object].
///
/// If [maxLineLength] is passed, this will attempt to ensure that each line is
/// no longer than [maxLineLength] characters long. This isn't guaranteed, since
/// individual objects may have string representations that are too long, but
/// most lines will be less than [maxLineLength] long.
///
/// If [maxItems] is passed, [Iterable]s and [Map]s will only print their first
/// [maxItems] members or key/value pairs, respectively.
String prettyPrint(object, {int maxLineLength, int maxItems}) {
String _prettyPrint(object, int indent, Set seen, bool top) {
// If the object is a matcher, use its description.
if (object is Matcher) {
var description = new StringDescription();
object.describe(description);
return "<$description>";
}
// Avoid looping infinitely on recursively-nested data structures.
if (seen.contains(object)) return "(recursive)";
seen = seen.union(new Set.from([object]));
String pp(child) => _prettyPrint(child, indent + 2, seen, false);
if (object is Iterable) {
// Print the type name for non-List iterables.
var type = object is List ? "" : _typeName(object) + ":";
// Truncate the list of strings if it's longer than [maxItems].
var strings = object.map(pp).toList();
if (maxItems != null && strings.length > maxItems) {
strings.replaceRange(maxItems - 1, strings.length, ['...']);
}
// If the printed string is short and doesn't contain a newline, print it
// as a single line.
var singleLine = "$type[${strings.join(', ')}]";
if ((maxLineLength == null ||
singleLine.length + indent <= maxLineLength) &&
!singleLine.contains("\n")) {
return singleLine;
}
// Otherwise, print each member on its own line.
return "$type[\n" + strings.map((string) {
return _indent(indent + 2) + string;
}).join(",\n") + "\n" + _indent(indent) + "]";
} else if (object is Map) {
// Convert the contents of the map to string representations.
var strings = object.keys.map((key) {
return '${pp(key)}: ${pp(object[key])}';
}).toList();
// Truncate the list of strings if it's longer than [maxItems].
if (maxItems != null && strings.length > maxItems) {
strings.replaceRange(maxItems - 1, strings.length, ['...']);
}
// If the printed string is short and doesn't contain a newline, print it
// as a single line.
var singleLine = "{${strings.join(", ")}}";
if ((maxLineLength == null ||
singleLine.length + indent <= maxLineLength) &&
!singleLine.contains("\n")) {
return singleLine;
}
// Otherwise, print each key/value pair on its own line.
return "{\n" + strings.map((string) {
return _indent(indent + 2) + string;
}).join(",\n") + "\n" + _indent(indent) + "}";
} else if (object is String) {
// Escape strings and print each line on its own line.
var lines = object.split("\n");
return "'" +
lines.map(_escapeString).join("\\n'\n${_indent(indent + 2)}'") +
"'";
} else {
var value = object.toString().replaceAll("\n", _indent(indent) + "\n");
var defaultToString = value.startsWith("Instance of ");
// If this is the top-level call to [prettyPrint], wrap the value on angle
// brackets to set it apart visually.
if (top) value = "<$value>";
// Print the type of objects with custom [toString] methods. Primitive
// objects and objects that don't implement a custom [toString] don't need
// to have their types printed.
if (object is num ||
object is bool ||
object is Function ||
object == null ||
defaultToString) {
return value;
} else {
return "${_typeName(object)}:$value";
}
}
}
return _prettyPrint(object, 0, new Set(), true);
}
String _indent(int length) => new List.filled(length, ' ').join('');
/// Returns the name of the type of [x], or "Unknown" if the type name can't be
/// determined.
String _typeName(x) {
// dart2js blows up on some objects (e.g. window.navigator).
// So we play safe here.
try {
if (x == null) return "null";
var type = x.runtimeType.toString();
// TODO(nweiz): if the object's type is private, find a public superclass to
// display once there's a portable API to do that.
return type.startsWith("_") ? "?" : type;
} catch (e) {
return "?";
}
}
/// Returns [source] with any control characters replaced by their escape
/// sequences.
///
/// This doesn't add quotes to the string, but it does escape single quote
/// characters so that single quotes can be applied externally.
String _escapeString(String source) => escape(source).replaceAll("'", r"\'");