}
- TableStyle
+ Style
GlobalOptions::table_style_value(const ParsedOpts& opts) const
{
ParsedOpts::const_iterator it = opts.find("table-style");
if (it == opts.end())
- return TableFormatter::default_style();
+ return TableFormatter::auto_style();
try
{
unsigned long value = stoul(it->second);
- if (value >= Table::numStyles)
+ if (value >= Table::num_styles)
throw exception();
- return (TableStyle)(value);
+ return (Style)(value);
}
catch (const exception&)
{
string error = sformat(_("Invalid table style '%s'."), it->second.c_str()) + '\n' +
- sformat(_("Use an integer number from %d to %d."), 0, Table::numStyles - 1);
+ sformat(_("Use an integer number from %d to %d."), 0, Table::num_styles - 1);
SN_THROW(OptionsException(error));
}
- return TableFormatter::default_style();
+ return TableFormatter::auto_style();
}
bool no_dbus() const { return _no_dbus; }
bool version() const { return _version; }
bool help() const { return _help; }
- TableStyle table_style() const { return _table_style; }
+ Style table_style() const { return _table_style; }
bool abbreviate() const { return _abbreviate; }
OutputFormat output_format() const { return _output_format; }
string separator() const { return _separator; }
void check_options(const ParsedOpts& parsed_opts) const;
- TableStyle table_style_value(const ParsedOpts& parsed_opts) const;
+ Style table_style_value(const ParsedOpts& parsed_opts) const;
OutputFormat output_format_value(const ParsedOpts& parsed_opts) const;
string separator_value(const ParsedOpts& parsed_opts) const;
string config_value(const ParsedOpts& parsed_opts) const;
bool _no_dbus;
bool _version;
bool _help;
- TableStyle _table_style;
+ Style _table_style;
bool _abbreviate;
OutputFormat _output_format;
string _separator;
};
- pair<string, TableAlign>
+ 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"));
void
- output_table(const vector<Column>& columns, TableStyle style, ProxySnapper* snapper)
+ output_table(const vector<Column>& columns, Style style, ProxySnapper* snapper)
{
TableFormatter formatter(style);
};
- pair<string, TableAlign>
+ 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"));
void
- output_table(const vector<Column>& columns, TableStyle style, ProxySnappers* snappers)
+ output_table(const vector<Column>& columns, Style style, ProxySnappers* snappers)
{
TableFormatter formatter(style);
}
- pair<string, TableAlign>
+ 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"));
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)
-#include <iostream>
+/*
+ * 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 <langinfo.h>
#include <cstring>
-#include <cstdlib>
-#include <iomanip>
-#include <boost/io/ios_state.hpp>
-#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<string>& 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<string>& 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<bool>& 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<bool>& lasts) const
{
- _max_col = c;
- _max_width.resize (_max_col + 1);
- _max_width[c] = 0;
+ s << string(global_indent, ' ');
+
+ const vector<string>& 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<Table::Row>& subrows = row.get_subrows();
+ for (size_t i = 0; i < subrows.size(); ++i)
+ {
+ vector<bool> 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<bool>::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<Cell> 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<Cell>& 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);
}
-/*-----------------------------------------------------------*- 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 <string>
-#include <iosfwd>
-#include <list>
#include <vector>
+#include <iostream>
-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<string> 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<TableAlign> _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<TableRow> 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<bool>& 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<unsigned> _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<bool> _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<Cell> init);
+ explicit Table(const vector<Cell>& init);
+
+ class Row
+ {
+ public:
+
+ explicit Row(const Table& table)
+ : table(table) {}
+
+ explicit Row(const Table& table, std::initializer_list<string> 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<string>& get_columns() const { return columns; }
+
+ void add_subrow(const Row& row) { subrows.push_back(row); }
+
+ const vector<Row>& get_subrows() const { return subrows; }
+
+ private:
+
+ // backref, used when accessing columns by id
+ const Table& table;
+
+ vector<string> columns;
+
+ vector<Row> subrows;
+
+ };
+
+ class Header : public Row
+ {
+ public:
+
+ explicit Header(const Table& table)
+ : Row(table) {}
+
+ explicit Header(const Table& table, std::initializer_list<string> init)
+ : Row(table, init) {}
+
+ };
+
+ void add(const Row& row) { rows.push_back(row); }
+
+ void add(std::initializer_list<string> init) { rows.emplace_back(Row(*this, init)); }
+
+ const vector<Row> 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<Row> rows;
+
+ bool show_header = true;
+ bool show_grid = true;
+ Style style = Style::ASCII;
+ size_t global_indent = 0;
+ size_t screen_width = -1;
+
+ vector<Align> aligns;
+ vector<Id> ids;
+ vector<size_t> min_widths;
+ vector<Visibility> visibilities;
+ vector<bool> 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<bool> hidden;
+ vector<size_t> widths;
+ };
+
+ void output(std::ostream& s, const Table::Row& row, const OutputInfo& output_info,
+ const vector<bool>& 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
*/
-#include <cstring>
-#include <langinfo.h>
-
#include "client/utils/TableFormatter.h"
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<string, TableAlign>& 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<string>& row : table_formatter._rows)
{
- TableRow table_row;
+ Table::Row table_row(table);
for (const string& value : row)
table_row.add(value);
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<pair<string, TableAlign>>& header() { return _header; }
- vector<bool>& abbrev() { return _abbrev; }
+ vector<Cell>& header() { return _header; }
+ vector<Id>& abbrev() { return _abbrev; }
vector<vector<string>>& rows() { return _rows; }
friend ostream& operator<<(ostream& stream, const TableFormatter& table_formatter);
private:
- const TableStyle style;
+ const Style style;
- vector<pair<string, TableAlign>> _header;
- vector<bool> _abbrev;
+ vector<Cell> _header;
+ vector<Id> _abbrev;
vector<vector<string>> _rows;
};
{
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" },
using namespace std;
+using namespace snapper;
void
check(const Table& table, const vector<string>& output)
{
ostringstream tmp;
- tmp << setw(42) << table;
+ tmp << table;
string lhs = tmp.str();
string rhs = accumulate(output.begin(), output.end(), (string)(""),
{
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<string> output = {
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<string> 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);