From a6f169ae377ba101e6f71afed98f597c207fdae5 Mon Sep 17 00:00:00 2001 From: Arvin Schnell Date: Mon, 26 Feb 2024 10:59:10 +0100 Subject: [PATCH] - use Table class from barrel --- client/cmd-get-config.cc | 8 +- client/cmd-list-configs.cc | 8 +- client/cmd-list.cc | 43 +-- client/utils/Table.cc | 584 ++++++++++++++++++++------------- client/utils/Table.h | 349 +++++++++++--------- client/utils/TableFormatter.cc | 24 +- client/utils/TableFormatter.h | 14 +- testsuite/table-formatter.cc | 12 +- testsuite/table.cc | 59 ++-- 9 files changed, 619 insertions(+), 482 deletions(-) diff --git a/client/cmd-get-config.cc b/client/cmd-get-config.cc index 98e026011..bd4ee2227 100644 --- a/client/cmd-get-config.cc +++ b/client/cmd-get-config.cc @@ -61,16 +61,16 @@ namespace snapper }; - pair + Cell header_for(Column column) { switch (column) { case Column::KEY: - return make_pair(_("Key"), TableAlign::LEFT); + return Cell(_("Key")); case Column::VALUE: - return make_pair(_("Value"), TableAlign::LEFT); + return Cell(_("Value")); } SN_THROW(Exception("invalid column value")); @@ -96,7 +96,7 @@ namespace snapper void - output_table(const vector& columns, TableStyle style, ProxySnapper* snapper) + output_table(const vector& columns, Style style, ProxySnapper* snapper) { TableFormatter formatter(style); diff --git a/client/cmd-list-configs.cc b/client/cmd-list-configs.cc index 73cc60614..b4d6ecaf8 100644 --- a/client/cmd-list-configs.cc +++ b/client/cmd-list-configs.cc @@ -60,16 +60,16 @@ namespace snapper }; - pair + Cell header_for(Column column) { switch (column) { case Column::CONFIG: - return make_pair(_("Config"), TableAlign::LEFT); + return Cell(_("Config")); case Column::SUBVOLUME: - return make_pair(_("Subvolume"), TableAlign::LEFT); + return Cell(_("Subvolume")); } SN_THROW(Exception("invalid column value")); @@ -95,7 +95,7 @@ namespace snapper void - output_table(const vector& columns, TableStyle style, ProxySnappers* snappers) + output_table(const vector& columns, Style style, ProxySnappers* snappers) { TableFormatter formatter(style); diff --git a/client/cmd-list.cc b/client/cmd-list.cc index 75d66f3ac..5725e0ad9 100644 --- a/client/cmd-list.cc +++ b/client/cmd-list.cc @@ -333,64 +333,64 @@ namespace snapper } - pair + Cell header_for(ListMode list_mode, Column column) { switch (column) { case Column::CONFIG: - return make_pair(_("Config"), TableAlign::LEFT); + return Cell(_("Config")); case Column::SUBVOLUME: - return make_pair(_("Subvolume"), TableAlign::LEFT); + return Cell(_("Subvolume")); case Column::NUMBER: if (list_mode != ListMode::PRE_POST) - return make_pair(_("#"), TableAlign::RIGHT); + return Cell(_("#"), Id::NUMBER, Align::RIGHT); else - return make_pair(_("Pre #"), TableAlign::RIGHT); + return Cell(_("Pre #"), Id::NUMBER, Align::RIGHT); case Column::DEFAULT: - return make_pair(_("Default"), TableAlign::LEFT); + return Cell(_("Default")); case Column::ACTIVE: - return make_pair(_("Active"), TableAlign::LEFT); + return Cell(_("Active")); case Column::TYPE: - return make_pair(_("Type"), TableAlign::LEFT); + return Cell(_("Type"), Id::TYPE); case Column::DATE: if (list_mode != ListMode::PRE_POST) - return make_pair(_("Date"), TableAlign::LEFT); + return Cell(_("Date")); else - return make_pair(_("Pre Date"), TableAlign::LEFT); + return Cell(_("Pre Date")); case Column::USER: - return make_pair(_("User"), TableAlign::LEFT); + return Cell(_("User")); case Column::USED_SPACE: - return make_pair(_("Used Space"), TableAlign::RIGHT);; + return Cell(_("Used Space"), Align::RIGHT);; case Column::CLEANUP: - return make_pair(_("Cleanup"), TableAlign::LEFT); + return Cell(_("Cleanup")); case Column::DESCRIPTION: - return make_pair(_("Description"), TableAlign::LEFT); + return Cell(_("Description"), Id::DESCRIPTION); case Column::USERDATA: - return make_pair(_("Userdata"), TableAlign::LEFT); + return Cell(_("Userdata"), Id::USERDATA); case Column::PRE_NUMBER: - return make_pair(_("Pre #"), TableAlign::RIGHT); + return Cell(_("Pre #"), Id::PRE_NUMBER, Align::RIGHT); case Column::POST_NUMBER: - return make_pair(_("Post #"), TableAlign::RIGHT); + return Cell(_("Post #"), Id::POST_NUMBER, Align::RIGHT); case Column::POST_DATE: - return make_pair(_("Post Date"), TableAlign::LEFT); + return Cell(_("Post Date")); case Column::READ_ONLY: - return make_pair(_("Read-Only"), TableAlign::LEFT); + return Cell(_("Read-Only")); } SN_THROW(Exception("invalid column value")); @@ -614,8 +614,9 @@ namespace snapper continue; formatter.header().push_back(header_for(list_mode, column)); - formatter.abbrev().push_back(global_options.abbreviate() && - column == Column::DESCRIPTION); + + if (global_options.abbreviate()) + formatter.abbrev().push_back(Id::DESCRIPTION); } for (const ProxySnapshot& snapshot : output_helper.snapshots) diff --git a/client/utils/Table.cc b/client/utils/Table.cc index 659dcc0b0..4056e3f8c 100644 --- a/client/utils/Table.cc +++ b/client/utils/Table.cc @@ -1,274 +1,404 @@ -#include +/* + * Copyright (c) [2021-2024] SUSE LLC + * + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, contact SUSE LLC. + * + * To contact SUSE LLC about this file by physical or electronic mail, you may + * find current contact information at www.suse.com. + */ + + +#include #include -#include -#include -#include -#include "console.h" +#include "Table.h" #include "text.h" +#include "console.h" -#include "Table.h" -using namespace std; - - -static -const char * lines[][3] = { - { "|", "-", "+"}, ///< Ascii - // utf 8 - { "\xE2\x94\x82", "\xE2\x94\x80", "\xE2\x94\xBC"}, ///< light - { "\xE2\x94\x83", "\xE2\x94\x81", "\xE2\x95\x8B"}, ///< heavy - { "\xE2\x95\x91", "\xE2\x95\x90", "\xE2\x95\xAC"}, ///< double - { "\xE2\x94\x86", "\xE2\x94\x84", "\xE2\x94\xBC"}, ///< light 3 - { "\xE2\x94\x87", "\xE2\x94\x85", "\xE2\x94\x8B"}, ///< heavy 3 - { "\xE2\x94\x82", "\xE2\x94\x81", "\xE2\x94\xBF"}, ///< v light, h heavy - { "\xE2\x94\x82", "\xE2\x95\x90", "\xE2\x95\xAA"}, ///< v light, h double - { "\xE2\x94\x83", "\xE2\x94\x80", "\xE2\x95\x82"}, ///< v heavy, h light - { "\xE2\x95\x91", "\xE2\x94\x80", "\xE2\x95\xAB"}, ///< v double, h light - { ":", "-", "+" }, ///< colon separated values -}; - -void TableRow::add (const string& s) { - _columns.push_back (s); -} +namespace snapper +{ -unsigned int TableRow::cols( void ) const { - return _columns.size(); -} + using namespace std; -// 1st implementation: no width calculation, just tabs -void TableRow::dumbDumpTo (ostream &stream) const { - bool seen_first = false; - for (container::const_iterator i = _columns.begin (); i != _columns.end (); ++i) { - if (seen_first) - stream << '\t'; - seen_first = true; - - stream << *i; - } - stream << endl; -} -void TableRow::dumpTo (ostream &stream, const Table & parent) const -{ - const char * vline = parent._style != none ? lines[parent._style][0] : ""; - - bool seen_first = false; - container::const_iterator - i = _columns.begin (), - e = _columns.end (); - - stream << string(parent._margin, ' '); - // current position at currently printed line - int curpos = parent._margin; - // whether to break the line now in order to wrap it to screen width - bool do_wrap = false; - for (unsigned c = 0; i != e ; ++i, ++c) - { - if (seen_first) + Table::OutputInfo::OutputInfo(const Table& table) { - do_wrap = - // user requested wrapping - parent._do_wrap && - // table is wider than screen - parent._width > parent._screen_width && ( - // the next table column would exceed the screen size - curpos + (int) parent._max_width[c] + (parent._style != none ? 2 : 3) > - parent._screen_width || - // or the user wishes to first break after the previous column - parent._force_break_after == (int) (c - 1)); - - if (do_wrap) - { - // start printing the next table columns to new line, - // indent by 2 console columns - stream << endl << string(parent._margin + 2, ' '); - curpos = parent._margin + 2; // indent == 2 - } - else - // vertical line, padded with spaces - stream << ' ' << vline << ' '; + // calculate hidden, default to false + + hidden.resize(table.header.get_columns().size()); + + for (size_t i = 0; i < table.visibilities.size(); ++i) + { + if (table.visibilities[i] == Visibility::AUTO || table.visibilities[i] == Visibility::OFF) + hidden[i] = true; + } + + for (const Table::Row& row : table.rows) + calculate_hidden(table, row); + + // calculate widths + + widths = table.min_widths; + + if (table.show_header) + calculate_widths(table, table.header, 0); + + for (const Table::Row& row : table.rows) + calculate_widths(table, row, 0); + + calculate_abbriviated_widths(table); } - else - seen_first = true; - // stream.width (widths[c]); // that does not work with multibyte chars - const string & s = *i; - unsigned int ssize = mbs_width(s); - if (ssize > parent._max_width[c]) + + void + Table::OutputInfo::calculate_hidden(const Table& table, const Table::Row& row) { - unsigned cutby = parent._max_width[c] - 2; - string cutstr = mbs_substr_by_width(s, 0, cutby); - stream << cutstr << string(cutby - mbs_width(cutstr), ' ') << "->"; + const vector& columns = row.get_columns(); + + for (size_t i = 0; i < min(columns.size(), table.visibilities.size()); ++i) + { + if (table.visibilities[i] == Visibility::AUTO && !columns[i].empty()) + hidden[i] = false; + } + + for (const Table::Row& subrow : row.get_subrows()) + calculate_hidden(table, subrow); } - else + + + void + Table::OutputInfo::calculate_widths(const Table& table, const Table::Row& row, unsigned indent) { - if (parent._header.align(c) == TableAlign::LEFT) - stream << s << setw(parent._max_width[c] - ssize) << ""; - else - stream << setw(parent._max_width[c] - ssize) << "" << s; + const vector& columns = row.get_columns(); + + if (columns.size() > widths.size()) + widths.resize(columns.size()); + + for (size_t i = 0; i < columns.size(); ++i) + { + if (hidden[i]) + continue; + + size_t width = mbs_width(columns[i]); + + if (i == table.tree_index) + width += 2 * indent; + + widths[i] = max(widths[i], width); + } + + for (const Table::Row& subrow : row.get_subrows()) + calculate_widths(table, subrow, indent + 1); } - curpos += parent._max_width[c] + (parent._style != none ? 2 : 3); - } - stream << endl; -} -void -TableHeader::add(const string& s, TableAlign align) -{ - TableRow::add(s); - _aligns.push_back(align); -} + /** + * Including global_indent and grid. Excluding screen_width. + */ + size_t + Table::OutputInfo::calculate_total_width(const Table& table) const + { + size_t total_width = table.global_indent; + bool first = true; -// ----------------------( Table )--------------------------------------------- + for (size_t i = 0; i < widths.size(); ++i) + { + if (hidden[i]) + continue; -Table::Table() - : _has_header (false) - , _max_col (0) - , _max_width(1, 0) - , _width(0) - , _style (Ascii) - , _screen_width(snapper::get_screen_width()) - , _margin(0) - , _force_break_after(-1) - , _do_wrap(false) -{} + if (first) + first = false; + else + total_width += 2 + (table.show_grid ? 1 : 0); -void Table::add (const TableRow& tr) { - _rows.push_back (tr); - updateColWidths (tr); -} + total_width += widths[i]; + } -void Table::setHeader (const TableHeader& tr) { - _has_header = true; - _header = tr; - updateColWidths (tr); -} + return total_width; + } -void -Table::set_abbrev(const vector& abbrev) -{ - _abbrev_col = abbrev; -} + /** + * So far only one column can be abbreviated. + */ + void + Table::OutputInfo::calculate_abbriviated_widths(const Table& table) + { + size_t total_width = calculate_total_width(table); + + if (total_width <= table.screen_width) + return; + + size_t too_much = total_width - table.screen_width; + for (size_t i = 0; i < table.abbreviates.size(); ++i) + { + if (table.abbreviates[i]) + { + widths[i] = max(widths[i] - too_much, (size_t) 5); + break; + } + } + } -void Table::updateColWidths (const TableRow& tr) { - // how much columns the separators add to the width of the table - int sepwidth = _style == none ? 2 : 3; - // initialize the width to -sepwidth (the first column does not have a line - // on the left) - _width = -sepwidth; - TableRow::container::const_iterator - i = tr._columns.begin (), - e = tr._columns.end (); - for (unsigned c = 0; i != e; ++i, ++c) { - // ensure that _max_width[c] exists - if (_max_col < c) + void + Table::output(std::ostream& s, const Table::Row& row, const OutputInfo& output_info, const vector& lasts) const { - _max_col = c; - _max_width.resize (_max_col + 1); - _max_width[c] = 0; + s << string(global_indent, ' '); + + const vector& columns = row.get_columns(); + + for (size_t i = 0; i < output_info.widths.size(); ++i) + { + if (output_info.hidden[i]) + continue; + + string column = i < columns.size() ? columns[i] : ""; + + bool first = i == 0; + bool last = i == output_info.widths.size() - 1; + + size_t extra = (i == tree_index) ? 2 * lasts.size() : 0; + + if (last && column.empty()) + break; + + if (!first) + s << " "; + + if (i == tree_index) + { + for (size_t tl = 0; tl < lasts.size(); ++tl) + { + if (tl == lasts.size() - 1) + s << (lasts[tl] ? glyph(4) : glyph(3)); + else + s << (lasts[tl] ? glyph(6) : glyph(5)); + } + } + + size_t width = mbs_width(column); + + if (aligns[i] == Align::RIGHT) + { + if (width < output_info.widths[i] - extra) + s << string(output_info.widths[i] - width - extra, ' '); + } + + if (width > output_info.widths[i] - extra) + { + const char* ellipsis = glyph(7); + s << mbs_substr_by_width(column, 0, output_info.widths[i] - extra - mbs_width(ellipsis)) + << ellipsis; + } + else + s << column; + + if (last) + break; + + if (aligns[i] == Align::LEFT) + { + if (width < output_info.widths[i] - extra) + s << string(output_info.widths[i] - width - extra, ' '); + } + + s << " "; + + if (show_grid) + s << glyph(0); + } + + s << '\n'; + + const vector& subrows = row.get_subrows(); + for (size_t i = 0; i < subrows.size(); ++i) + { + vector sub_lasts = lasts; + sub_lasts.push_back(i == subrows.size() - 1); + output(s, subrows[i], output_info, sub_lasts); + } } - unsigned &max = _max_width[c]; - unsigned cur = mbs_width (*i); - if (max < cur) - max = cur; + /** + * Output grid line under header. + */ + void + Table::output(std::ostream& s, const OutputInfo& output_info) const + { + s << string(global_indent, ' '); + + for (size_t i = 0; i < output_info.widths.size(); ++i) + { + if (output_info.hidden[i]) + continue; - _width += max + sepwidth; - } - _width += _margin * 2; -} + for (size_t j = 0; j < output_info.widths[i]; ++j) + s << glyph(1); -void Table::dumpRule (ostream &stream) const { - const char * hline = _style != none ? lines[_style][1] : " "; - const char * cross = _style != none ? lines[_style][2] : " "; + if (i == output_info.widths.size() - 1) + break; - bool seen_first = false; + s << glyph(1) << glyph(2) << glyph(1); + } - stream << string(_margin, ' '); - for (unsigned c = 0; c <= _max_col; ++c) { - if (seen_first) { - stream << hline << cross << hline; + s << '\n'; } - seen_first = true; - // FIXME: could use fill character if hline were a (wide) character - for (unsigned i = 0; i < _max_width[c]; ++i) { - stream << hline; + + + size_t + Table::id_to_index(Id id) const + { + for (size_t i = 0; i < ids.size(); ++i) + if (ids[i] == id) + return i; + + throw runtime_error("id not found"); } - } - stream << endl; -} -void Table::dumpTo (ostream &stream) const { - - boost::io::ios_flags_saver ifs(stream); - stream.width(0); - - // reset column widths for columns that can be abbreviated - //! \todo allow abbrev of multiple columns? - unsigned c = 0; - for (vector::const_iterator it = _abbrev_col.begin(); - it != _abbrev_col.end() && c <= _max_col; ++it, ++c) { - if (*it && - _width > _screen_width && - // don't resize the column to less than 3, or if the resulting table - // would still exceed the screen width (bnc #534795) - _max_width[c] > 3 && - _width - _screen_width < ((int) _max_width[c]) - 3) { - _max_width[c] -= _width - _screen_width; - break; + + string& + Table::Row::operator[](Id id) + { + size_t i = table.id_to_index(id); + + if (columns.size() < i + 1) + columns.resize(i + 1); + + return columns[i]; } - } - - if (_has_header) { - _header.dumpTo (stream, *this); - dumpRule (stream); - } - - container::const_iterator - b = _rows.begin (), - e = _rows.end (), - i; - for (i = b; i != e; ++i) { - i->dumpTo (stream, *this); - } -} -void Table::wrap(int force_break_after) -{ - if (force_break_after >= 0) - _force_break_after = force_break_after; - _do_wrap = true; -} + Style + Table::auto_style() + { + return strcmp(nl_langinfo(CODESET), "UTF-8") == 0 ? Style::LIGHT : Style::ASCII; + } -void -Table::set_style(TableStyle st) -{ - if (st < _End) - _style = st; -} + Table::Table() + : header(*this) + { + screen_width = get_screen_width(); + } -void Table::margin(unsigned margin) { - if (margin < (unsigned) (_screen_width/2)) - _margin = margin; - // else - // ERR << "margin of " << margin << " is greater than half of the screen" << endl; -} -void Table::sort (unsigned by_column) { - if (by_column > _max_col) { - // ERR << "by_column >= _max_col (" << by_column << ">=" << _max_col << ")" << endl; - // return; - } + Table::Table(std::initializer_list init) + : Table() + { + for (const Cell& cell : init) + { + header.add(cell.name); + ids.push_back(cell.id); + aligns.push_back(cell.align); + } + } + + + Table::Table(const vector& init) + : Table() + { + for (const Cell& cell : init) + { + header.add(cell.name); + ids.push_back(cell.id); + aligns.push_back(cell.align); + } + } + + + void + Table::set_min_width(Id id, size_t min_width) + { + size_t i = id_to_index(id); + + if (min_widths.size() < i + 1) + min_widths.resize(i + 1); + + min_widths[i] = min_width; + } + + + void + Table::set_visibility(Id id, Visibility visibility) + { + size_t i = id_to_index(id); + + if (visibilities.size() < i + 1) + visibilities.resize(i + 1); + + visibilities[i] = visibility; + } + + + void + Table::set_abbreviate(Id id, bool abbreviate) + { + size_t i = id_to_index(id); + + if (abbreviates.size() < i + 1) + abbreviates.resize(i + 1); + + abbreviates[i] = abbreviate; + } + + + void + Table::set_tree_id(Id id) + { + tree_index = id_to_index(id); + } + + + std::ostream& + operator<<(std::ostream& s, const Table& table) + { + // calculate hidden and widths + + Table::OutputInfo output_info(table); + + // output header and rows + + if (table.show_header) + table.output(s, table.header, output_info, {}); + + if (table.show_header && table.show_grid) + table.output(s, output_info); + + for (const Table::Row& row : table.rows) + table.output(s, row, output_info, {}); + + return s; + } + + + const char* + Table::glyph(unsigned int i) const + { + const char* glyphs[][8] = { + { "|", "-", "+", "+-", "+-", "| ", " ", "..." }, // ASCII + { "│", "─", "┼", "├─", "└─", "│ ", " ", "…" }, // LIGHT + { "┃", "━", "╋", "├─", "└─", "│ ", " ", "…" }, // HEAVY + { "║", "═", "╬", "├─", "└─", "│ ", " ", "…" }, // DOUBLE + }; + + return glyphs[(unsigned int)(style)][i]; + } - TableRow::Less comp (by_column); - _rows.sort (comp); } diff --git a/client/utils/Table.h b/client/utils/Table.h index 307d2f89b..308ce3bd9 100644 --- a/client/utils/Table.h +++ b/client/utils/Table.h @@ -1,169 +1,202 @@ -/*-----------------------------------------------------------*- c++ -*-\ -| ____ _ __ __ ___ | -| |__ / \ / / . \ . \ | -| / / \ V /| _/ _/ | -| / /__ | | | | | | | -| /_____||_| |_| |_| | -| | -\---------------------------------------------------------------------*/ - -#ifndef ZYPPER_TABULATOR_H -#define ZYPPER_TABULATOR_H +/* + * Copyright (c) [2021-2024] SUSE LLC + * + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, contact SUSE LLC. + * + * To contact SUSE LLC about this file by physical or electronic mail, you may + * find current contact information at www.suse.com. + */ + + +#ifndef SNAPPER_TABLE_H +#define SNAPPER_TABLE_H + #include -#include -#include #include +#include -using std::string; -using std::ostream; -using std::list; -using std::vector; - -//! table style -enum TableStyle { - Ascii = 0, ///< | - + - Light, - Heavy, - Double, - Light3, - Heavy3, - LightHeavy, - LightDouble, - HeavyLight, - DoubleLight, - Colon, - none, - _End, ///< sentinel -}; - -enum class TableAlign { - LEFT, RIGHT -}; - -class Table; - -class TableRow { -public: - //! Constructor. Reserve place for c columns. - TableRow (unsigned c = 0) { - _columns.reserve (c); - } - - void add (const string& s); - - // return number of columns - unsigned int cols( void ) const; - - //! tab separated output - void dumbDumpTo (ostream &stream) const; - //! output with \a parent table attributes - void dumpTo (ostream & stream, const Table & parent) const; - - typedef vector container; - - // BinaryPredicate - struct Less { - unsigned _by_column; - Less (unsigned by_column): _by_column (by_column) {} - - bool operator ()(const TableRow& a, const TableRow& b) const { - return a._columns[_by_column] < b._columns[_by_column]; - } - }; - -private: - container _columns; - friend class Table; -}; - -class TableHeader : public TableRow { -public: - //! Constructor. Reserve place for c columns. - TableHeader (unsigned c = 0): TableRow (c) {} - - void add(const string& s, TableAlign align = TableAlign::LEFT); - - TableAlign align(int c) const { return _aligns[c]; } - -private: - vector _aligns; -}; - -inline -TableRow& operator << (TableRow& tr, const string& s) { - tr.add (s); - return tr; -} +/* + * Try to keep in sync between snapper and barrel even if some features are not needed + * here or there. + */ -class Table { -public: - typedef list container; - - static const unsigned int numStyles = _End; - - void add (const TableRow& tr); - void setHeader (const TableHeader& tr); - void dumpTo (ostream& stream) const; - bool empty () const { return _rows.empty(); } - void sort (unsigned by_column); // columns start with 0... - - void set_style(TableStyle st); - void wrap(int force_break_after = -1); - void set_abbrev(const vector& abbrev); - void margin(unsigned margin); - void set_screen_width(int screen_width) { _screen_width = screen_width; } - - Table (); - -private: - void dumpRule (ostream &stream) const; - void updateColWidths (const TableRow& tr); - - bool _has_header; - TableHeader _header; - container _rows; - - //! maximum column index seen in this table - unsigned _max_col; - //! maximum width of respective columns - mutable vector _max_width; - //! table width (columns) - int _width; - //! table style - TableStyle _style; - //! amount of space we have to print this table - int _screen_width; - //! whether to abbreviate the respective column if needed - vector _abbrev_col; - //! left/right margin in number of spaces - unsigned _margin; - //! if _do_wrap is set, first break the table at this column; - //! If negative, wrap as needed. - int _force_break_after; - //! Whether to wrap the table if it exceeds _screen_width - bool _do_wrap; - - friend class TableRow; -}; - -inline -Table& operator << (Table& table, const TableRow& tr) { - table.add (tr); - return table; -} -inline -Table& operator << (Table& table, const TableHeader& tr) { - table.setHeader (tr); - return table; -} +namespace snapper +{ + + using namespace std; + + + enum class Id + { + NONE, NAME, TYPE, NUMBER, PRE_NUMBER, POST_NUMBER, DESCRIPTION, USERDATA + }; + + + enum class Align + { + LEFT, RIGHT + }; + + + enum class Visibility + { + ON, AUTO, OFF + }; + + + enum class Style + { + ASCII, LIGHT, HEAVY, DOUBLE + }; + + + struct Cell + { + Cell(const char* name) : name(name) {} + Cell(const char* name, Id id) : name(name), id(id) {} + Cell(const char* name, Align align) : name(name), align(align) {} + Cell(const char* name, Id id, Align align) : name(name), id(id), align(align) {} + + const char* name; + const Id id = Id::NONE; + const Align align = Align::LEFT; + }; + + + class Table + { + + public: + + static const unsigned int num_styles = 4; + + static Style auto_style(); + + explicit Table(std::initializer_list init); + explicit Table(const vector& init); + + class Row + { + public: + + explicit Row(const Table& table) + : table(table) {} + + explicit Row(const Table& table, std::initializer_list init) + : table(table), columns(init) {} + + const Table& get_table() const { return table; } + + void add(const string& s) { columns.push_back(s); } + + string& operator[](Id id); + + const vector& get_columns() const { return columns; } + + void add_subrow(const Row& row) { subrows.push_back(row); } + + const vector& get_subrows() const { return subrows; } + + private: + + // backref, used when accessing columns by id + const Table& table; + + vector columns; + + vector subrows; + + }; + + class Header : public Row + { + public: + + explicit Header(const Table& table) + : Row(table) {} + + explicit Header(const Table& table, std::initializer_list init) + : Row(table, init) {} + + }; + + void add(const Row& row) { rows.push_back(row); } + + void add(std::initializer_list init) { rows.emplace_back(Row(*this, init)); } + + const vector get_rows() const { return rows; } + + void set_show_header(bool show_header) { Table::show_header = show_header; } + void set_show_grid(bool show_grid) { Table::show_grid = show_grid; } + void set_style(Style style) { Table::style = style; } + void set_global_indent(size_t global_indent) { Table::global_indent = global_indent; } + void set_screen_width(size_t screen_width) { Table::screen_width = screen_width; } + void set_min_width(Id id, size_t min_width); + void set_visibility(Id id, Visibility visibility); + void set_abbreviate(Id id, bool abbreviate); + void set_tree_id(Id id); + + friend std::ostream& operator<<(std::ostream& s, const Table& Table); + + private: + + Table(); + + Header header; + vector rows; + + bool show_header = true; + bool show_grid = true; + Style style = Style::ASCII; + size_t global_indent = 0; + size_t screen_width = -1; + + vector aligns; + vector ids; + vector min_widths; + vector visibilities; + vector abbreviates; + size_t tree_index = 0; + + size_t id_to_index(Id id) const; + + struct OutputInfo + { + OutputInfo(const Table& table); + + void calculate_hidden(const Table& table, const Table::Row& row); + void calculate_widths(const Table& table, const Table::Row& row, unsigned indent); + size_t calculate_total_width(const Table& table) const; + void calculate_abbriviated_widths(const Table& table); + + vector hidden; + vector widths; + }; + + void output(std::ostream& s, const Table::Row& row, const OutputInfo& output_info, + const vector& lasts) const; + + void output(std::ostream& s, const OutputInfo& output_info) const; + + const char* glyph(unsigned int i) const; + + }; -inline -ostream& operator << (ostream& stream, const Table& table) { - table.dumpTo (stream); - return stream; } #endif diff --git a/client/utils/TableFormatter.cc b/client/utils/TableFormatter.cc index a89032ff9..b2141ff15 100644 --- a/client/utils/TableFormatter.cc +++ b/client/utils/TableFormatter.cc @@ -20,9 +20,6 @@ */ -#include -#include - #include "client/utils/TableFormatter.h" @@ -32,30 +29,19 @@ namespace snapper using namespace std; - TableStyle - TableFormatter::default_style() - { - return strcmp(nl_langinfo(CODESET), "UTF-8") == 0 ? TableStyle::Light : TableStyle::Ascii; - } - - ostream& operator<<(ostream& stream, const TableFormatter& table_formatter) { - Table table; - table.set_style(table_formatter.style); + Table table(table_formatter._header); - TableHeader table_header; - - for (const pair& column : table_formatter._header) - table_header.add(column.first, column.second); + table.set_style(table_formatter.style); - table.setHeader(table_header); - table.set_abbrev(table_formatter._abbrev); + for (Id id : table_formatter._abbrev) + table.set_abbreviate(id, true); for (const vector& row : table_formatter._rows) { - TableRow table_row; + Table::Row table_row(table); for (const string& value : row) table_row.add(value); diff --git a/client/utils/TableFormatter.h b/client/utils/TableFormatter.h index 251bde89b..cd02d9b0a 100644 --- a/client/utils/TableFormatter.h +++ b/client/utils/TableFormatter.h @@ -41,26 +41,26 @@ namespace snapper public: - static TableStyle default_style(); + static Style auto_style() { return Table::auto_style(); } - TableFormatter(TableStyle style) : style(style) {} + TableFormatter(Style style) : style(style) {} TableFormatter(const TableFormatter&) = delete; TableFormatter& operator=(const TableFormatter&) = delete; - vector>& header() { return _header; } - vector& abbrev() { return _abbrev; } + vector& header() { return _header; } + vector& abbrev() { return _abbrev; } vector>& rows() { return _rows; } friend ostream& operator<<(ostream& stream, const TableFormatter& table_formatter); private: - const TableStyle style; + const Style style; - vector> _header; - vector _abbrev; + vector _header; + vector _abbrev; vector> _rows; }; diff --git a/testsuite/table-formatter.cc b/testsuite/table-formatter.cc index d6028e094..436ce5ef9 100644 --- a/testsuite/table-formatter.cc +++ b/testsuite/table-formatter.cc @@ -24,14 +24,12 @@ BOOST_AUTO_TEST_CASE(test1) { locale::global(locale("en_GB.UTF-8")); - TableFormatter formatter(Ascii); + TableFormatter formatter(Style::ASCII); - formatter.header() = { - { "Number", TableAlign::RIGHT }, - { "Name EN", TableAlign::LEFT }, - { "Name DE", TableAlign::LEFT }, - { "Square", TableAlign::RIGHT } - }; + formatter.header().push_back(Cell("Number", Id::NUMBER, Align::RIGHT)); + formatter.header().push_back(Cell("Name EN")); + formatter.header().push_back(Cell("Name DE")); + formatter.header().push_back(Cell("Square", Align::RIGHT)); formatter.rows() = { { "0", "zero", "Null", "0" }, diff --git a/testsuite/table.cc b/testsuite/table.cc index 557ee99b3..706615e2e 100644 --- a/testsuite/table.cc +++ b/testsuite/table.cc @@ -12,13 +12,14 @@ using namespace std; +using namespace snapper; void check(const Table& table, const vector& output) { ostringstream tmp; - tmp << setw(42) << table; + tmp << table; string lhs = tmp.str(); string rhs = accumulate(output.begin(), output.end(), (string)(""), @@ -32,32 +33,23 @@ BOOST_AUTO_TEST_CASE(test1) { locale::global(locale("en_GB.UTF-8")); - Table table; + Table table({ + Cell("Number", Id::NUMBER, Align::RIGHT), Cell("Name EN"), Cell("Name DE"), + Cell("Square", Align::RIGHT) + }); - TableHeader header; - header.add("Number", TableAlign::RIGHT); - header.add("Name EN"); - header.add("Name DE"); - header.add("Square", TableAlign::RIGHT); - table.setHeader(header); + table.set_style(Style::ASCII); - TableRow row1; - row1.add("0"); - row1.add("zero"); - row1.add("Null"); - row1.add("0"); + Table::Row row1(table, { "0", "zero", "Null", "0" }); table.add(row1); - TableRow row2; - row2 << "1" << "one" << "Eins" << "1"; + Table::Row row2(table, { "1", "one", "Eins", "1" }); table.add(row2); - TableRow row3; - row3 << "5" << "five" << "Fünf" << "25"; + Table::Row row3(table, { "5", "five", "Fünf", "25" }); table.add(row3); - TableRow row4; - row4 << "12" << "twelve" << "Zwölf" << "144"; + Table::Row row4(table, { "12", "twelve", "Zwölf", "144" }); table.add(row4); vector output = { @@ -75,30 +67,27 @@ BOOST_AUTO_TEST_CASE(test1) BOOST_AUTO_TEST_CASE(test2) { - locale::global(locale("en_GB.UTF-8")); + Table table({ Cell("Number", Align::RIGHT), Cell("Description", Id::DESCRIPTION) }); - Table table; + table.set_style(Style::LIGHT); table.set_screen_width(25); - table.set_abbrev({ false, true }); - - TableHeader header; - header.add("Number", TableAlign::RIGHT); - header.add("Description"); - table.setHeader(header); + table.set_abbreviate(Id::DESCRIPTION, true); - TableRow row1; - row1 << "1" << "boot"; + Table::Row row1(table, { "1", "boot" }); table.add(row1); - TableRow row2; - row2 << "2" << "before the system update"; + Table::Row row2(table, { "2", "before the system update" }); table.add(row2); + Table::Row row3(table, { "3", "läuft schön rund" }); + table.add(row3); + vector output = { - "Number | Description ", - "-------+-----------------", - " 1 | boot ", - " 2 | before the sys->" + "Number │ Description", + "───────┼─────────────────", + " 1 │ boot", + " 2 │ before the syst…", + " 3 │ läuft schön rund" }; check(table, output);