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