libzypp  17.35.11
Table.cc
Go to the documentation of this file.
1 /*---------------------------------------------------------------------\
2 | ____ _ __ __ ___ |
3 | |__ / \ / / . \ . \ |
4 | / / \ V /| _/ _/ |
5 | / /__ | | | | | | |
6 | /_____||_| |_| |_| |
7 | |
8 ----------------------------------------------------------------------*/
9 
10 #include <iostream>
11 #include <cstring>
12 #include <cstdlib>
13 
14 #include <zypp/base/LogTools.h>
15 #include <zypp/base/String.h>
16 #include <zypp/base/DtorReset.h>
17 #include <zypp-core/base/Gettext.h>
18 
19 #include <zypp-tui/Application>
20 #include <zypp-tui/utils/colors.h>
21 #include <zypp-tui/utils/console.h>
22 #include <zypp-tui/utils/text.h>
23 
24 #include "Table.h"
25 
26 // libzypp logger settings
27 #undef ZYPP_BASE_LOGGER_LOGGROUP
28 #define ZYPP_BASE_LOGGER_LOGGROUP "zypper"
29 
30 namespace ztui {
31 
32 const char * asYesNo( bool val_r ) { return val_r ? _("Yes") : _("No"); }
33 
35 
36 static const char * lines[][3] = {
37  { "|", "-", "+"},
38  // utf 8
39  { "\xE2\x94\x82", "\xE2\x94\x80", "\xE2\x94\xBC" },
40  { "\xE2\x94\x83", "\xE2\x94\x81", "\xE2\x95\x8B" },
41  { "\xE2\x95\x91", "\xE2\x95\x90", "\xE2\x95\xAC" },
42  { "\xE2\x94\x86", "\xE2\x94\x84", "\xE2\x94\xBC" },
43  { "\xE2\x94\x87", "\xE2\x94\x85", "\xE2\x94\x8B" },
44  { "\xE2\x94\x82", "\xE2\x94\x81", "\xE2\x94\xBF" },
45  { "\xE2\x94\x82", "\xE2\x95\x90", "\xE2\x95\xAA" },
46  { "\xE2\x94\x83", "\xE2\x94\x80", "\xE2\x95\x82" },
47  { "\xE2\x95\x91", "\xE2\x94\x80", "\xE2\x95\xAB" },
48  { ":", "-", "+" },
49 };
50 
51 
52 namespace {
54  inline int wccmp( const wchar_t & l, const wchar_t & r )
55  { return l == r ? 0 : l < r ? -1 : 1; }
56 
58  inline int wccasecmp( const wchar_t & l, const wchar_t & r )
59  { return ::wcsncasecmp( &l, &r, 1 ); }
60 
62  inline bool isZero( const wchar_t & ch )
63  { return ch == L'0'; }
64 
66  inline bool isDigit( const wchar_t & ch )
67  { return ::iswdigit( ch ); }
68 
70  inline bool bothDigits( const wchar_t & l, const wchar_t & r )
71  { return isDigit( l ) && isDigit( r ); }
72 
74  inline bool bothNotDigits( const wchar_t & l, const wchar_t & r )
75  { return not ( isDigit( l ) || isDigit( r ) ); }
76 
78  inline bool bothAtEnd( const mbs::MbsIteratorNoSGR & lit, const mbs::MbsIteratorNoSGR & rit )
79  { return lit.atEnd() && rit.atEnd(); }
80 
82  inline bool skipTrailingZeros( mbs::MbsIteratorNoSGR & it )
83  {
84  if ( isZero( *it ) ) {
85  do { ++it; } while ( isZero( *it ) );
86  return it.atEnd();
87  }
88  return false;
89  }
90 
92  inline int wcnumcmpValue( mbs::MbsIteratorNoSGR & lit, mbs::MbsIteratorNoSGR & rit )
93  {
94  // PRE: no leading Zeros
95  // POST: if 0(equal) is returned, all digis were skipped
96  int diff = 0;
97  for ( ;; ++lit, ++rit ) {
98  if ( isDigit( *lit ) ) {
99  if ( isDigit( *rit ) ) {
100  if ( not diff && *lit != *rit )
101  diff = *lit < *rit ? -1 : 1;
102  }
103  else
104  return 1; // DIG !DIG
105  }
106  else {
107  if ( isDigit( *rit ) )
108  return -1; // !DIG DIG
109  else
110  return diff; // !DIG !DIG
111  }
112  }
113  }
114 } // namespace
115 
116 int TableRow::Less::defaultStrComp( bool ci_r, const std::string & lhs, const std::string & rhs )
117 {
118  auto wcharcmp = &wccmp; // always start with case sensitive compare
119  int nbias = 0; // remember the 1st difference (in case num compare equal)
120  int cbias = 0; // remember the 1st difference (in case ci compare equal)
121  int cmp = 0;
122  mbs::MbsIteratorNoSGR lit { lhs };
123  mbs::MbsIteratorNoSGR rit { rhs };
124  while ( true ) {
125 
126  // Endgame: tricky: trailing Zeros are ignored, but count as nbias if there is none.
127  if ( lit.atEnd() ) {
128  if ( skipTrailingZeros( rit ) && not nbias ) return -1;
129  return rit.atEnd() ? (nbias ? nbias : cbias) : -1;
130  }
131  if ( rit.atEnd() ) {
132  if ( skipTrailingZeros( lit ) && not nbias ) return 1;
133  return lit.atEnd() ? (nbias ? nbias : cbias) : 1;
134  }
135 
136  // num <> num?
137  if ( bothDigits( *lit, *rit ) ) {
138  if ( isZero( *lit ) || isZero( *rit ) ) {
139  int lead = 0; // the more leasing zeros a number has, the less: 001 01 1
140  while ( isZero( *lit ) ) { ++lit; --lead; }
141  while ( isZero( *rit ) ) { ++rit; ++lead; }
142  if ( not nbias && lead )
143  nbias = bothAtEnd( lit, rit ) ? -lead : lead; // the less trailing zeros, the less: a a0 a00
144  }
145  if ( (cmp = wcnumcmpValue( lit, rit )) )
146  return cmp;
147  continue; // already skipped all digits
148  }
149  else {
150  if ( (cmp = wcharcmp( *lit, *rit )) ) {
151  if ( not cbias ) cbias = cmp; // remember the 1st difference (by wccmp)
152  if ( ci_r ) {
153  if ( (cmp = wccasecmp( *lit, *rit )) )
154  return cmp;
155  wcharcmp = &wccasecmp;
156  ci_r = false;
157  }
158  else
159  return cmp;
160  }
161  }
162  ++lit; ++rit;
163  }
164 }
165 
166 TableRow & TableRow::add( std::string s )
167 {
168  if ( _translateColumns )
169  _translatedColumns.push_back( _(s.c_str()) );
170  _columns.push_back( std::move(s) );
171  return *this;
172 }
173 
174 TableRow & TableRow::addDetail( std::string s )
175 {
176  _details.push_back( std::move(s) );
177  return *this;
178 }
179 
180 // 1st implementation: no width calculation, just tabs
181 std::ostream & TableRow::dumbDumpTo( std::ostream & stream ) const
182 {
183  bool seen_first = false;
184  for ( container::const_iterator i = _columns.begin(); i != _columns.end(); ++i )
185  {
186  if ( seen_first )
187  stream << '\t';
188  seen_first = true;
189 
190  stream << *i;
191  }
192  return stream << std::endl;
193 }
194 
195 std::ostream & TableRow::dumpDetails( std::ostream & stream, const Table & parent ) const
196 {
197  mbs::MbsWriteWrapped mww( stream, 4, parent._screen_width );
198  for ( const std::string & text : _details )
199  {
200  mww.writePar( text );
201  }
202  mww.gotoParBegin();
203  return stream;
204 }
205 
206 std::ostream & TableRow::dumpTo( std::ostream & stream, const Table & parent ) const
207 {
208  const char * vline = parent._style == none ? "" : lines[parent._style][0];
209 
210  unsigned ssize = 0; // string size in columns
211  bool seen_first = false;
212 
213  stream.setf( std::ios::left, std::ios::adjustfield );
214  stream << std::string( parent._margin, ' ' );
215  // current position at currently printed line
216  int curpos = parent._margin;
217  // On a table with 2 edition columns highlight the editions
218  // except for the common prefix.
219  std::string::size_type editionSep( std::string::npos );
220 
221  container::const_iterator i = _columns.begin (), e = _columns.end ();
222  const unsigned lastCol = _columns.size() - 1;
223  for ( unsigned c = 0; i != e ; ++i, ++c )
224  {
225  const std::string & s( *i );
226 
227  if ( seen_first )
228  {
229  bool do_wrap = parent._do_wrap // user requested wrapping
230  && parent._width > parent._screen_width // table is wider than screen
231  && ( curpos + (int)parent._max_width[c] + (parent._style == none ? 2 : 3) > parent._screen_width // the next table column would exceed the screen size
232  || parent._force_break_after == (int)(c - 1) ); // or the user wishes to first break after the previous column
233 
234  if ( do_wrap )
235  {
236  // start printing the next table columns to new line,
237  // indent by 2 console columns
238  stream << std::endl << std::string( parent._margin + 2, ' ' );
239  curpos = parent._margin + 2; // indent == 2
240  }
241  else
242  // vertical line, padded with spaces
243  stream << ' ' << vline << ' ';
244  stream.width( 0 );
245  }
246  else
247  seen_first = true;
248 
249  // stream.width (widths[c]); // that does not work with multibyte chars
250  ssize = mbs_width( s );
251  if ( ssize > parent._max_width[c] )
252  {
253  unsigned cutby = parent._max_width[c] - 2;
254  std::string cutstr = mbs_substr_by_width( s, 0, cutby );
255  stream << ( _ctxt << cutstr ) << std::string(cutby - mbs_width( cutstr ), ' ') << "->";
256  }
257  else
258  {
259  if ( !parent._inHeader && parent.header().hasStyle( c, table::CStyle::Edition ) && Application::instance().config().do_colors )
260  {
261  const std::set<unsigned> & editionColumns { parent.header().editionColumns() };
262  // Edition column
263  if ( editionColumns.size() == 2 )
264  {
265  // 2 Edition columns - highlight difference
266  if ( editionSep == std::string::npos )
267  {
268  editionSep = zypp::str::commonPrefix( _columns[*editionColumns.begin()],
269  _columns[*(++editionColumns.begin())] );
270  }
271 
272  if ( editionSep == 0 )
273  {
274  stream << ( ColorContext::CHANGE << s );
275  }
276  else if ( editionSep == s.size() )
277  {
278  stream << ( _ctxt << s );
279  }
280  else
281  {
282  stream << ( _ctxt << s.substr( 0, editionSep ) ) << ( ColorContext::CHANGE << s.substr( editionSep ) );
283  }
284  }
285  else
286  {
287  // highlight edition-release separator
288  editionSep = s.find( '-' );
289  if ( editionSep != std::string::npos )
290  {
291  stream << ( _ctxt << s.substr( 0, editionSep ) << ( ColorContext::HIGHLIGHT << "-" ) << s.substr( editionSep+1 ) );
292  }
293  else // no release part
294  {
295  stream << ( _ctxt << s );
296  }
297  }
298  }
299  else // no special style
300  {
301  stream << ( _ctxt << s );
302  }
303  stream.width( c == lastCol ? 0 : parent._max_width[c] - ssize );
304  }
305  stream << "";
306  curpos += parent._max_width[c] + (parent._style == none ? 2 : 3);
307  }
308  stream << std::endl;
309 
310  if ( !_details.empty() )
311  {
312  dumpDetails( stream, parent );
313  }
314  return stream;
315 }
316 
317 // ----------------------( Table )---------------------------------------------
318 
320  : _has_header( false )
321  , _max_col( 0 )
322  , _max_width( 1, 0 )
323  , _width( 0 )
324  , _style( defaultStyle )
325  , _screen_width( get_screen_width() )
326  , _margin( 0 )
327  , _force_break_after( -1 )
328  , _do_wrap( false )
329  , _inHeader( false )
330 {}
331 
333 {
334  _rows.push_back( std::move(tr) );
335  return *this;
336 }
337 
339 {
340  _header = std::move(tr);
342  return *this;
343 }
344 
345 void Table::allowAbbrev( unsigned column)
346 {
347  if ( column >= _abbrev_col.size() )
348  {
349  _abbrev_col.reserve( column + 1 );
350  _abbrev_col.insert( _abbrev_col.end(), column - _abbrev_col.size() + 1, false );
351  }
352  _abbrev_col[column] = true;
353 }
354 
355 void Table::updateColWidths( const TableRow & tr ) const
356 {
357  // how much columns spearators add to the width of the table
358  int sepwidth = _style == none ? 2 : 3;
359  // initialize the width to -sepwidth (the first column does not have a line
360  // on the left)
361  _width = -sepwidth;
362 
363  // ensure that _max_width[col] exists
364  const auto &columns = tr.columns();
365  if ( _max_width.size() < columns.size() )
366  {
367  _max_width.resize( columns.size(), 0 );
368  _max_col = _max_width.size()-1;
369  }
370 
371  unsigned c = 0;
372  for ( const auto & col : columns )
373  {
374  unsigned &max = _max_width[c++];
375  unsigned cur = mbs_width( col );
376 
377  if ( max < cur )
378  max = cur;
379 
380  _width += max + sepwidth;
381  }
382  _width += _margin * 2;
383 }
384 
385 void Table::dumpRule( std::ostream &stream ) const
386 {
387  const char * hline = _style != none ? lines[_style][1] : " ";
388  const char * cross = _style != none ? lines[_style][2] : " ";
389 
390  bool seen_first = false;
391 
392  stream.width( 0 );
393  stream << std::string(_margin, ' ' );
394  for ( unsigned c = 0; c <= _max_col; ++c )
395  {
396  if ( seen_first )
397  stream << hline << cross << hline;
398  seen_first = true;
399  // FIXME: could use fill character if hline were a (wide) character
400  for ( unsigned i = 0; i < _max_width[c]; ++i )
401  stream << hline;
402  }
403  stream << std::endl;
404 }
405 
406 std::ostream & Table::dumpTo( std::ostream & stream ) const
407 {
408  // compute column sizes
409  if ( _has_header )
411  for ( const auto & row : _rows )
412  updateColWidths( row );
413 
414  // reset column widths for columns that can be abbreviated
416  unsigned c = 0;
417  for ( std::vector<bool>::const_iterator it = _abbrev_col.begin(); it != _abbrev_col.end() && c <= _max_col; ++it, ++c )
418  {
419  if ( *it && _width > _screen_width &&
420  // don't resize the column to less than 3, or if the resulting table
421  // would still exceed the screen width (bnc #534795)
422  _max_width[c] > 3 &&
423  _width - _screen_width < ((int) _max_width[c]) - 3 )
424  {
426  break;
427  }
428  }
429 
430  if ( _has_header )
431  {
432  zypp::DtorReset inHeader( _inHeader, false );
433  _inHeader = true;
434  _header.dumpTo( stream, *this );
435  dumpRule (stream);
436  }
437 
438  for ( const auto & row : _rows )
439  row.dumpTo( stream, *this );
440 
441  return stream;
442 }
443 
444 void Table::wrap( int force_break_after )
445 {
446  if ( force_break_after >= 0 )
447  _force_break_after = force_break_after;
448  _do_wrap = true;
449 }
450 
452 {
453  if ( st < TLS_End )
454  _style = st;
455 }
456 
457 void Table::margin( unsigned margin )
458 {
459  if ( margin < (unsigned)(_screen_width/2) )
460  _margin = margin;
461  else
462  ERR << "margin of " << margin << " is greater than half of the screen" << std::endl;
463 }
464 
465 // Local Variables:
466 // c-basic-offset: 2
467 // End:
468 }
TableRow & add(std::string s)
Definition: Table.cc:166
container _rows
Definition: Table.h:456
int _width
table width (columns)
Definition: Table.h:463
static int defaultStrComp(bool ci_r, const std::string &lhs, const std::string &rhs)
Natural(&#39;sort -V&#39; like) [case insensitive] compare ignoring ANSI SGR chars.
Definition: Table.cc:116
std::ostream & dumbDumpTo(std::ostream &stream) const
tab separated output
Definition: Table.cc:181
#define _(MSG)
Definition: Gettext.h:39
void margin(unsigned margin)
Definition: Table.cc:457
Table & add(TableRow tr)
Definition: Table.cc:332
sentinel
Definition: Table.h:93
bool hasStyle(unsigned c, CStyle s) const
Definition: Table.h:288
std::string mbs_substr_by_width(boost::string_ref text_r, std::string::size_type colpos_r, std::string::size_type collen_r)
Returns a substring of a multi-byte character string text_r starting at screen column cpos_r and bein...
Definition: text.cc:16
std::string::size_type commonPrefix(const C_Str &lhs, const C_Str &rhs)
Return size of the common prefix of lhs and rhs.
Definition: String.h:1063
void allowAbbrev(unsigned column)
Definition: Table.cc:345
std::vector< bool > _abbrev_col
whether to abbreviate the respective column if needed
Definition: Table.h:469
TableLineStyle
table drawing style
Definition: Table.h:80
TableHeader _header
Definition: Table.h:455
Write MBString optionally wrapped and indented.
Definition: text.h:260
void gotoParBegin()
Open a new paragraph if not atParBegin.
Definition: text.h:321
int _screen_width
amount of space we have to print this table
Definition: Table.h:467
bool _do_wrap
Whether to wrap the table if it exceeds _screen_width.
Definition: Table.h:476
#define ERR
Definition: Logger.h:100
TableLineStyle _style
table line drawing style
Definition: Table.h:465
static const char * lines[][3]
Definition: Table.cc:36
const char * asYesNo(bool val_r)
Definition: Table.cc:32
Assign a vaiable a certain value when going out of scope.
Definition: dtorreset.h:49
size_t mbs_width(boost::string_ref text_r)
Returns the column width of a multi-byte character string text_r.
Definition: text.h:641
std::ostream & dumpTo(std::ostream &stream) const
Definition: Table.cc:406
const container & columns() const
Definition: Table.h:229
int _force_break_after
if _do_wrap is set, first break the table at this column; If negative, wrap as needed.
Definition: Table.h:474
unsigned _margin
left/right margin in number of spaces
Definition: Table.h:471
bool _translateColumns
Definition: Table.h:242
container _translatedColumns
Definition: Table.h:245
std::vector< unsigned > _max_width
maximum width of respective columns
Definition: Table.h:461
Table & setHeader(TableHeader tr)
Definition: Table.cc:338
std::ostream & dumpDetails(std::ostream &stream, const Table &parent) const
Definition: Table.cc:195
ColorContext _ctxt
Definition: Table.h:247
void updateColWidths(const TableRow &tr) const
Definition: Table.cc:355
bool atEnd() const
Definition: text.h:121
container _details
Definition: Table.h:246
std::ostream & dumpTo(std::ostream &stream, const Table &parent) const
output with parent table attributes
Definition: Table.cc:206
Editions with v-r setparator highlighted.
void lineStyle(TableLineStyle st)
Definition: Table.cc:451
const TableHeader & header() const
Definition: Table.h:441
bool _inHeader
Definition: Table.h:480
| - +
Definition: Table.h:81
unsigned _max_col
maximum column index seen in this table
Definition: Table.h:459
TableRow & addDetail(std::string s)
Definition: Table.cc:174
MbsIterator skipping ANSI SGR
Definition: text.h:225
void dumpRule(std::ostream &stream) const
Definition: Table.cc:385
Miscellaneous console utilities.
void writePar(boost::string_ref text_r)
Write text_r; starting a new paragraph and ending it after the text was written.
Definition: text.h:392
void wrap(int force_break_after=-1)
Definition: Table.cc:444
static TableLineStyle defaultStyle
Definition: Table.h:402
std::set< unsigned > editionColumns() const
Definition: Table.h:298
unsigned get_screen_width()
Reads COLUMNS environment variable or gets the screen width from readline, in that order...
Definition: console.cc:48
container _columns
Definition: Table.h:244
SolvableIdType size_type
Definition: PoolMember.h:126
bool empty() const
Definition: Table.h:204
bool _has_header
Definition: Table.h:454