blob: 39455d8e6b09aec98959efd3236ef74dda4c2d89 [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<int>(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({required this.value, required 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));
}
}
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));
}
}
}