blob: 4258e9fa909fabd1898ee6d7ebd66f5a9aa5401a [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "garnet/bin/zxdb/console/format_table.h"
#include "garnet/bin/zxdb/console/output_buffer.h"
#include "garnet/bin/zxdb/console/string_util.h"
#include "garnet/public/lib/fxl/logging.h"
namespace zxdb {
namespace {
template <typename CellType>
bool SpansRemainingCols(const std::vector<ColSpec>& spec,
const std::vector<CellType>& row, size_t column) {
// The last item in a row will span all remaining columns if there are more
// in the column spec.
return column == row.size() - 1 && column < spec.size() - 1;
}
// Character width for both cell types.
int CellWidth(const std::string& str) {
return static_cast<int>(UnicodeCharWidth(str));
}
int CellWidth(const OutputBuffer& buf) {
return static_cast<int>(buf.UnicodeCharWidth());
}
// Appends the given cell type to the OutputBuffer. The Syntax is ignored
// in the OutputBuffer->OutputBuffer variant because it will contain its own
// syntax.
void AppendCell(const std::string& str, Syntax syntax, OutputBuffer* out) {
out->Append(syntax, str);
}
void AppendCell(const OutputBuffer& buf, Syntax, OutputBuffer* out) {
out->Append(buf);
}
// Appends the given string to the output, padding with spaces to the width
// as necessary.
template <typename CellType>
void AppendPadded(const CellType& cell, int width, Align align, Syntax syntax,
bool is_last_col, OutputBuffer* out) {
int pad = std::max(0, width - CellWidth(cell));
if (pad > 0 && align == Align::kRight)
out->Append(std::string(pad, ' '));
AppendCell(cell, syntax, out);
// Padding on the right. Don't add for the last col.
if (pad > 0 && !is_last_col && align == Align::kLeft)
out->Append(std::string(pad, ' '));
// Separator after columns for all but the last.
if (!is_last_col)
out->Append(std::string(1, ' '));
}
// Backend for FormatColumns variants. The requirements are that CellWidth()
// and AppendCell() are define for CellType.
template <typename CellType>
void FormatTableT(const std::vector<ColSpec>& spec,
const std::vector<std::vector<CellType>>& rows,
OutputBuffer* out) {
std::vector<int> max; // Max width of each column.
// Max widths of headings.
bool has_head = false;
for (const auto& col : spec) {
max.push_back(UnicodeCharWidth(col.head));
has_head |= !col.head.empty();
}
// Max widths of contents.
for (const auto& row : rows) {
FXL_DCHECK(row.size() <= max.size()) << "Column spec size is too small.";
for (size_t i = 0; i < row.size(); i++) {
// Only count the ones that don't overflow (either because they span the
// remaining columns, or because they're beyond max_width).
if (!SpansRemainingCols(spec, row, i)) {
int cell_size = CellWidth(row[i]);
if (spec[i].max_width == 0 || cell_size <= spec[i].max_width)
max[i] = std::max(max[i], cell_size);
}
}
}
// Print heading.
if (has_head) {
for (size_t i = 0; i < max.size(); i++) {
const ColSpec& col = spec[i];
if (col.pad_left)
out->Append(Syntax::kNormal, std::string(col.pad_left, ' '));
AppendPadded(col.head, max[i], col.align, Syntax::kHeading,
i == max.size() - 1, out);
}
out->Append("\n");
}
// Print rows.
for (const auto& row : rows) {
std::string text;
for (size_t i = 0; i < row.size(); i++) {
const ColSpec& col = spec[i];
if (SpansRemainingCols(spec, row, i)) {
AppendCell(row[i], col.syntax, out);
} else {
if (col.pad_left)
out->Append(Syntax::kNormal, std::string(col.pad_left, ' '));
AppendPadded(row[i], max[i], col.align, col.syntax, i == max.size() - 1,
out);
}
}
out->Append("\n");
}
}
} // namespace
void FormatTable(const std::vector<ColSpec>& spec,
const std::vector<std::vector<std::string>>& rows,
OutputBuffer* out) {
FormatTableT<std::string>(spec, rows, out);
}
void FormatTable(const std::vector<ColSpec>& spec,
const std::vector<std::vector<OutputBuffer>>& rows,
OutputBuffer* out) {
FormatTableT<OutputBuffer>(spec, rows, out);
}
} // namespace zxdb