blob: a0e54041832ecfbfdc81b01d390117a6197b4700 [file] [log] [blame]
// Copyright (c) 2020, 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 vm_snapshot_analysis.ascii_table;
import 'dart:math' as math;
/// A row in the [AsciiTable].
abstract class Row {
String render(List<int> widths, List<AlignmentDirection> alignments);
/// Compute the total width of the row given [widths] of individual
/// columns.
///
/// Note: there is a border on the left and right of each column
/// plus whitespace around it.
static int totalWidth(List<int> widths) =>
widths.fold(0, (sum, width) => sum + width + 3) + 1;
}
enum Separator {
/// Line separator looks like this: `+-------+------+`
Line,
/// Wave separator looks like this: `~~~~~~~~~~~~~~~~`.
Wave,
}
/// A separator row in the [AsciiTable].
class SeparatorRow extends Row {
final Separator filler;
SeparatorRow(this.filler);
@override
String render(List<int> widths, List<AlignmentDirection> alignments) {
final sb = StringBuffer();
switch (filler) {
case Separator.Line:
sb.write('+');
for (var i = 0; i < widths.length; i++) {
sb.write('-' * (widths[i] + 2));
sb.write('+');
}
break;
case Separator.Wave:
sb.write('~' * Row.totalWidth(widths));
break;
}
return sb.toString();
}
}
/// A separator row in the [AsciiTable].
class TextSeparatorRow extends Row {
final Text text;
TextSeparatorRow(String text)
: text = Text(value: text, direction: AlignmentDirection.Center);
@override
String render(List<int> widths, List<AlignmentDirection> alignments) {
return text.render(Row.totalWidth(widths));
}
}
class NormalRow extends Row {
final List<dynamic> columns;
NormalRow(this.columns);
@override
String render(List<int> widths, List<AlignmentDirection> alignments) {
final sb = StringBuffer();
sb.write('|');
for (var i = 0; i < widths.length; i++) {
sb.write(' ');
final text = columns[i] is Text
? columns[i]
: Text(value: columns[i], direction: alignments[i]);
sb.write(text.render(widths[i]));
sb.write(' |');
}
return sb.toString();
}
}
enum AlignmentDirection { Left, Right, Center }
/// A chunk of text aligned in the given direction within a cell.
class Text {
final String value;
final AlignmentDirection direction;
Text({this.value, this.direction});
Text.left(String value)
: this(value: value, direction: AlignmentDirection.Left);
Text.right(String value)
: this(value: value, direction: AlignmentDirection.Right);
Text.center(String value)
: this(value: value, direction: AlignmentDirection.Center);
String render(int width) {
if (value.length > width) {
// Narrowed column.
return value.substring(0, width - 2) + '..';
}
switch (direction) {
case AlignmentDirection.Left:
return value.padRight(width);
case AlignmentDirection.Right:
return value.padLeft(width);
case AlignmentDirection.Center:
final diff = width - value.length;
return ' ' * (diff ~/ 2) + value + (' ' * (diff - diff ~/ 2));
}
return null; // Make analyzer happy.
}
int get length => value.length;
}
class AsciiTable {
static const int unlimitedWidth = 0;
final int maxWidth;
final List<Row> rows = <Row>[];
AsciiTable({List<dynamic> header, this.maxWidth = unlimitedWidth}) {
if (header != null) {
addSeparator();
addRow(header);
addSeparator();
}
}
void addRow(List<dynamic> columns) => rows.add(NormalRow(columns));
void addSeparator([Separator filler = Separator.Line]) =>
rows.add(SeparatorRow(filler));
void addTextSeparator(String text) => rows.add(TextSeparatorRow(text));
void render() {
// We assume that the first row gives us alignment directions that
// subsequent rows would follow.
List<AlignmentDirection> alignments = rows
.whereType<NormalRow>()
.first
.columns
.map((v) => v is Text ? v.direction : AlignmentDirection.Left)
.toList();
List<int> widths =
List<int>.filled(rows.whereType<NormalRow>().first.columns.length, 0);
// Compute max width for each column in the table.
for (var row in rows.whereType<NormalRow>()) {
assert(row.columns.length == widths.length);
for (var i = 0; i < widths.length; i++) {
widths[i] = math.max(row.columns[i].length, widths[i]);
}
}
if (maxWidth > 0) {
for (var i = 0; i < widths.length; i++) {
widths[i] = math.min(widths[i], maxWidth);
}
}
for (var row in rows) {
print(row.render(widths, alignments));
}
}
}