]> git.ipfire.org Git - thirdparty/snapper.git/commitdiff
- use Table class from barrel 874/head
authorArvin Schnell <aschnell@suse.de>
Mon, 26 Feb 2024 09:59:10 +0000 (10:59 +0100)
committerArvin Schnell <aschnell@suse.de>
Mon, 26 Feb 2024 10:00:43 +0000 (11:00 +0100)
client/GlobalOptions.cc
client/GlobalOptions.h
client/cmd-get-config.cc
client/cmd-list-configs.cc
client/cmd-list.cc
client/utils/Table.cc
client/utils/Table.h
client/utils/TableFormatter.cc
client/utils/TableFormatter.h
testsuite/table-formatter.cc
testsuite/table.cc

index 0c1d544b91a3e137bd591b8284a5f5da24b468bb..9c8d5e00bee5af8c2f328d1d41d9b68fe1ee75b5 100644 (file)
@@ -117,31 +117,31 @@ namespace snapper
     }
 
 
-    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();
     }
 
 
index 3fb6e1f1610a9741ac08137ba9ff9f5c4b056dc2..ba24d40f9695d4c95d35d560e7bf4ebdc7d4ddee 100644 (file)
@@ -53,7 +53,7 @@ namespace snapper
        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; }
@@ -68,7 +68,7 @@ namespace snapper
 
        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;
@@ -83,7 +83,7 @@ namespace snapper
        bool _no_dbus;
        bool _version;
        bool _help;
-       TableStyle _table_style;
+       Style _table_style;
        bool _abbreviate;
        OutputFormat _output_format;
        string _separator;
index 98e02601140fa1d10cc40aaed9734b018a69fc86..bd4ee222747fc45b5558087d62a8cf9de96d6a94 100644 (file)
@@ -61,16 +61,16 @@ namespace snapper
        };
 
 
-       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"));
@@ -96,7 +96,7 @@ namespace snapper
 
 
        void
-       output_table(const vector<Column>& columns, TableStyle style, ProxySnapper* snapper)
+       output_table(const vector<Column>& columns, Style style, ProxySnapper* snapper)
        {
            TableFormatter formatter(style);
 
index 73cc60614080277c943dc5a8b06256c1ad4eb176..b4d6ecaf8f2f1f855f8e9fd5bdd0b6a12ddbb408 100644 (file)
@@ -60,16 +60,16 @@ namespace snapper
        };
 
 
-       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"));
@@ -95,7 +95,7 @@ namespace snapper
 
 
        void
-       output_table(const vector<Column>& columns, TableStyle style, ProxySnappers* snappers)
+       output_table(const vector<Column>& columns, Style style, ProxySnappers* snappers)
        {
            TableFormatter formatter(style);
 
index 75d66f3ac600d86ceeda10fb3eac1bd298055eb1..5725e0ad98931cec0cde84122913342d05e3ef61 100644 (file)
@@ -333,64 +333,64 @@ namespace snapper
        }
 
 
-       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"));
@@ -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)
index 659dcc0b05c2d01a00a0ded69cb835c6b297d7f3..4056e3f8cb6b900260cf41a508b66ea2e735e98c 100644 (file)
-#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);
 }
index 307d2f89b6525823982bafcf0305684083e5e6ee..308ce3bd991fd959977a362b5db06b003b00edaf 100644 (file)
-/*-----------------------------------------------------------*- 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
index a89032ff942c451d05526ae148486102465d2fdb..b2141ff15d8297dc213f90aeee69252922a2d8cb 100644 (file)
@@ -20,9 +20,6 @@
  */
 
 
-#include <cstring>
-#include <langinfo.h>
-
 #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<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);
index 251bde89b81d8b574661938f75a7a44ecb04c20b..cd02d9b0aba866ddf424e548f35c33d98b458e69 100644 (file)
@@ -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<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;
 
     };
index d6028e0944647ad6d9b0bea3777ae38f9b450786..436ce5ef9d322b3d2aaa32ae53e2f9dcdd019aaa 100644 (file)
@@ -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" },
index 557ee99b3c64308aa0ca64d2b4c75c9c5fadfb92..706615e2e25f55c8ca96b534d37eabb0a5e49cf2 100644 (file)
 
 
 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)(""),
@@ -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<string> 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<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);