mirror of
https://github.com/Zygo/bees.git
synced 2025-05-17 05:15:45 +02:00
table: add a simple text table renderer
This should help clean up some of the uglier status outputs. Supports: * multi-line table cells * character fills * sparse tables * insert, delete by row and column * vertical separators and not much else. Signed-off-by: Zygo Blaxell <bees@furryterror.org>
This commit is contained in:
parent
e22653e2c6
commit
a59a02174f
106
include/crucible/table.h
Normal file
106
include/crucible/table.h
Normal file
@ -0,0 +1,106 @@
|
||||
#ifndef CRUCIBLE_TABLE_H
|
||||
#define CRUCIBLE_TABLE_H
|
||||
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace crucible {
|
||||
namespace Table {
|
||||
using namespace std;
|
||||
|
||||
using Content = function<string(size_t width, size_t height)>;
|
||||
const size_t endpos = numeric_limits<size_t>::max();
|
||||
|
||||
Content Fill(const char c);
|
||||
Content Text(const string& s);
|
||||
|
||||
template <class T>
|
||||
Content Number(const T& num)
|
||||
{
|
||||
ostringstream oss;
|
||||
oss << num;
|
||||
return Text(oss.str());
|
||||
}
|
||||
|
||||
class Cell {
|
||||
Content m_content;
|
||||
public:
|
||||
Cell(const Content &fn = [](size_t, size_t) { return string(); } );
|
||||
Cell& operator=(const Content &fn);
|
||||
string text(size_t width, size_t height) const;
|
||||
};
|
||||
|
||||
class Dimension {
|
||||
size_t m_next_pos = 0;
|
||||
vector<size_t> m_elements;
|
||||
friend class Table;
|
||||
size_t at(size_t) const;
|
||||
public:
|
||||
size_t size() const;
|
||||
size_t insert(size_t pos);
|
||||
void erase(size_t pos);
|
||||
};
|
||||
|
||||
class Table {
|
||||
Dimension m_rows, m_cols;
|
||||
map<pair<size_t, size_t>, Cell> m_cells;
|
||||
string m_left = "|";
|
||||
string m_mid = "|";
|
||||
string m_right = "|";
|
||||
public:
|
||||
Dimension &rows();
|
||||
const Dimension& rows() const;
|
||||
Dimension &cols();
|
||||
const Dimension& cols() const;
|
||||
Cell& at(size_t row, size_t col);
|
||||
const Cell& at(size_t row, size_t col) const;
|
||||
template <class T> void insert_row(size_t pos, const T& container);
|
||||
template <class T> void insert_col(size_t pos, const T& container);
|
||||
void left(const string &s);
|
||||
void mid(const string &s);
|
||||
void right(const string &s);
|
||||
const string& left() const;
|
||||
const string& mid() const;
|
||||
const string& right() const;
|
||||
};
|
||||
|
||||
ostream& operator<<(ostream &os, const Table &table);
|
||||
|
||||
template <class T>
|
||||
void
|
||||
Table::insert_row(size_t pos, const T& container)
|
||||
{
|
||||
const auto new_pos = m_rows.insert(pos);
|
||||
size_t col = 0;
|
||||
for (const auto &i : container) {
|
||||
if (col >= cols().size()) {
|
||||
cols().insert(col);
|
||||
}
|
||||
at(new_pos, col++) = i;
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void
|
||||
Table::insert_col(size_t pos, const T& container)
|
||||
{
|
||||
const auto new_pos = m_cols.insert(pos);
|
||||
size_t row = 0;
|
||||
for (const auto &i : container) {
|
||||
if (row >= rows().size()) {
|
||||
rows().insert(row);
|
||||
}
|
||||
at(row++, new_pos) = i;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif // CRUCIBLE_TABLE_H
|
@ -17,6 +17,7 @@ CRUCIBLE_OBJS = \
|
||||
path.o \
|
||||
process.o \
|
||||
string.o \
|
||||
table.o \
|
||||
task.o \
|
||||
time.o \
|
||||
uname.o \
|
||||
|
254
lib/table.cc
Normal file
254
lib/table.cc
Normal file
@ -0,0 +1,254 @@
|
||||
#include "crucible/table.h"
|
||||
|
||||
#include "crucible/string.h"
|
||||
|
||||
namespace crucible {
|
||||
namespace Table {
|
||||
using namespace std;
|
||||
|
||||
Content
|
||||
Fill(const char c)
|
||||
{
|
||||
return [=](size_t width, size_t height) -> string {
|
||||
string rv;
|
||||
while (height--) {
|
||||
rv += string(width, c);
|
||||
if (height) {
|
||||
rv += "\n";
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
};
|
||||
}
|
||||
|
||||
Content
|
||||
Text(const string &s)
|
||||
{
|
||||
return [=](size_t width, size_t height) -> string {
|
||||
const auto lines = split("\n", s);
|
||||
string rv;
|
||||
size_t line_count = 0;
|
||||
for (const auto &i : lines) {
|
||||
if (line_count++) {
|
||||
rv += "\n";
|
||||
}
|
||||
if (i.length() < width) {
|
||||
rv += string(width - i.length(), ' ');
|
||||
}
|
||||
rv += i;
|
||||
}
|
||||
while (line_count < height) {
|
||||
if (line_count++) {
|
||||
rv += "\n";
|
||||
}
|
||||
rv += string(width, ' ');
|
||||
}
|
||||
return rv;
|
||||
};
|
||||
}
|
||||
|
||||
Content
|
||||
Number(const string &s)
|
||||
{
|
||||
return [=](size_t width, size_t height) -> string {
|
||||
const auto lines = split("\n", s);
|
||||
string rv;
|
||||
size_t line_count = 0;
|
||||
for (const auto &i : lines) {
|
||||
if (line_count++) {
|
||||
rv += "\n";
|
||||
}
|
||||
if (i.length() < width) {
|
||||
rv += string(width - i.length(), ' ');
|
||||
}
|
||||
rv += i;
|
||||
}
|
||||
while (line_count < height) {
|
||||
if (line_count++) {
|
||||
rv += "\n";
|
||||
}
|
||||
rv += string(width, ' ');
|
||||
}
|
||||
return rv;
|
||||
};
|
||||
}
|
||||
|
||||
Cell::Cell(const Content &fn) :
|
||||
m_content(fn)
|
||||
{
|
||||
}
|
||||
|
||||
Cell&
|
||||
Cell::operator=(const Content &fn)
|
||||
{
|
||||
m_content = fn;
|
||||
return *this;
|
||||
}
|
||||
|
||||
string
|
||||
Cell::text(size_t width, size_t height) const
|
||||
{
|
||||
return m_content(width, height);
|
||||
}
|
||||
|
||||
size_t
|
||||
Dimension::size() const
|
||||
{
|
||||
return m_elements.size();
|
||||
}
|
||||
|
||||
size_t
|
||||
Dimension::insert(size_t pos)
|
||||
{
|
||||
++m_next_pos;
|
||||
const auto insert_pos = min(m_elements.size(), pos);
|
||||
const auto it = m_elements.begin() + insert_pos;
|
||||
m_elements.insert(it, m_next_pos);
|
||||
return insert_pos;
|
||||
}
|
||||
|
||||
void
|
||||
Dimension::erase(size_t pos)
|
||||
{
|
||||
const auto it = m_elements.begin() + min(m_elements.size(), pos);
|
||||
m_elements.erase(it);
|
||||
}
|
||||
|
||||
size_t
|
||||
Dimension::at(size_t pos) const
|
||||
{
|
||||
return m_elements.at(pos);
|
||||
}
|
||||
|
||||
Dimension&
|
||||
Table::rows()
|
||||
{
|
||||
return m_rows;
|
||||
};
|
||||
|
||||
const Dimension&
|
||||
Table::rows() const
|
||||
{
|
||||
return m_rows;
|
||||
};
|
||||
|
||||
Dimension&
|
||||
Table::cols()
|
||||
{
|
||||
return m_cols;
|
||||
};
|
||||
|
||||
const Dimension&
|
||||
Table::cols() const
|
||||
{
|
||||
return m_cols;
|
||||
};
|
||||
|
||||
const Cell&
|
||||
Table::at(size_t row, size_t col) const
|
||||
{
|
||||
const auto row_idx = m_rows.at(row);
|
||||
const auto col_idx = m_cols.at(col);
|
||||
const auto found = m_cells.find(make_pair(row_idx, col_idx));
|
||||
if (found == m_cells.end()) {
|
||||
static const Cell s_empty(Fill('.'));
|
||||
return s_empty;
|
||||
}
|
||||
return found->second;
|
||||
};
|
||||
|
||||
Cell&
|
||||
Table::at(size_t row, size_t col)
|
||||
{
|
||||
const auto row_idx = m_rows.at(row);
|
||||
const auto col_idx = m_cols.at(col);
|
||||
return m_cells[make_pair(row_idx, col_idx)];
|
||||
};
|
||||
|
||||
static
|
||||
pair<size_t, size_t>
|
||||
text_size(const string &s)
|
||||
{
|
||||
const auto s_split = split("\n", s);
|
||||
size_t width = 0;
|
||||
for (const auto &i : s_split) {
|
||||
width = max(width, i.length());
|
||||
}
|
||||
return make_pair(width, s_split.size());
|
||||
}
|
||||
|
||||
ostream& operator<<(ostream &os, const Table &table)
|
||||
{
|
||||
const auto rows = table.rows().size();
|
||||
const auto cols = table.cols().size();
|
||||
vector<size_t> row_heights(rows, 1);
|
||||
vector<size_t> col_widths(cols, 1);
|
||||
// Get the size of all fixed- and minimum-sized content cells
|
||||
for (size_t row = 0; row < table.rows().size(); ++row) {
|
||||
vector<string> col_text;
|
||||
for (size_t col = 0; col < table.cols().size(); ++col) {
|
||||
col_text.push_back(table.at(row, col).text(0, 0));
|
||||
const auto tsize = text_size(*col_text.rbegin());
|
||||
row_heights[row] = max(row_heights[row], tsize.second);
|
||||
col_widths[col] = max(col_widths[col], tsize.first);
|
||||
}
|
||||
}
|
||||
// Render the table
|
||||
for (size_t row = 0; row < table.rows().size(); ++row) {
|
||||
vector<string> lines(row_heights[row], "");
|
||||
for (size_t col = 0; col < table.cols().size(); ++col) {
|
||||
const auto& table_cell = table.at(row, col);
|
||||
const auto table_text = table_cell.text(col_widths[col], row_heights[row]);
|
||||
auto col_lines = split("\n", table_text);
|
||||
col_lines.resize(row_heights[row], "");
|
||||
for (size_t line = 0; line < row_heights[row]; ++line) {
|
||||
if (col > 0) {
|
||||
lines[line] += table.mid();
|
||||
}
|
||||
lines[line] += col_lines[line];
|
||||
}
|
||||
}
|
||||
for (const auto &line : lines) {
|
||||
os << table.left() << line << table.right() << "\n";
|
||||
}
|
||||
}
|
||||
return os;
|
||||
}
|
||||
|
||||
void
|
||||
Table::left(const string &s)
|
||||
{
|
||||
m_left = s;
|
||||
}
|
||||
|
||||
void
|
||||
Table::mid(const string &s)
|
||||
{
|
||||
m_mid = s;
|
||||
}
|
||||
|
||||
void
|
||||
Table::right(const string &s)
|
||||
{
|
||||
m_right = s;
|
||||
}
|
||||
|
||||
const string&
|
||||
Table::left() const
|
||||
{
|
||||
return m_left;
|
||||
}
|
||||
|
||||
const string&
|
||||
Table::mid() const
|
||||
{
|
||||
return m_mid;
|
||||
}
|
||||
|
||||
const string&
|
||||
Table::right() const
|
||||
{
|
||||
return m_right;
|
||||
}
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ PROGRAMS = \
|
||||
process \
|
||||
progress \
|
||||
seeker \
|
||||
table \
|
||||
task \
|
||||
|
||||
all: test
|
||||
|
63
test/table.cc
Normal file
63
test/table.cc
Normal file
@ -0,0 +1,63 @@
|
||||
#include "tests.h"
|
||||
|
||||
#include "crucible/table.h"
|
||||
|
||||
using namespace crucible;
|
||||
using namespace std;
|
||||
|
||||
void
|
||||
print_table(const Table::Table& t)
|
||||
{
|
||||
cerr << "BEGIN TABLE\n";
|
||||
cerr << t;
|
||||
cerr << "END TABLE\n";
|
||||
cerr << endl;
|
||||
}
|
||||
|
||||
void
|
||||
test_table()
|
||||
{
|
||||
Table::Table t;
|
||||
t.insert_row(Table::endpos, vector<Table::Content> {
|
||||
Table::Text("Hello, World!"),
|
||||
Table::Text("2"),
|
||||
Table::Text("3"),
|
||||
Table::Text("4"),
|
||||
});
|
||||
print_table(t);
|
||||
t.insert_row(Table::endpos, vector<Table::Content> {
|
||||
Table::Text("Greeting"),
|
||||
Table::Text("two"),
|
||||
Table::Text("three"),
|
||||
Table::Text("four"),
|
||||
});
|
||||
print_table(t);
|
||||
t.insert_row(Table::endpos, vector<Table::Content> {
|
||||
Table::Fill('-'),
|
||||
Table::Text("ii"),
|
||||
Table::Text("iii"),
|
||||
Table::Text("iv"),
|
||||
});
|
||||
print_table(t);
|
||||
t.mid(" | ");
|
||||
t.left("| ");
|
||||
t.right(" |");
|
||||
print_table(t);
|
||||
t.insert_col(1, vector<Table::Content> {
|
||||
Table::Text("1"),
|
||||
Table::Text("one"),
|
||||
Table::Text("i"),
|
||||
Table::Text("I"),
|
||||
});
|
||||
print_table(t);
|
||||
t.at(2, 1) = Table::Text("Two\nLines");
|
||||
print_table(t);
|
||||
}
|
||||
|
||||
int
|
||||
main(int, char**)
|
||||
{
|
||||
RUN_A_TEST(test_table());
|
||||
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user