libzypp 17.37.17
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>
18
19#include <zypp-tui/Application>
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
30namespace ztui {
31
32const char * asYesNo( bool val_r ) { return val_r ? _("Yes") : _("No"); }
33
34TableLineStyle Table::defaultStyle = Ascii;
35
36static 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
52namespace {
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
116int 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
166TableRow & 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
175{
176 _details.push_back( std::move(s) );
177 return *this;
178}
179
180// 1st implementation: no width calculation, just tabs
181std::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
195std::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
206std::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 )
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);
341 _has_header = !_header.empty();
342 return *this;
343}
344
345void 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
355void 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
385void 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
406std::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
444void 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
457void 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}
static Application & instance()
std::set< unsigned > editionColumns() const
Definition Table.h:298
bool hasStyle(unsigned c, CStyle s) const
Definition Table.h:288
container _columns
Definition Table.h:244
TableRow & add(std::string s)
Definition Table.cc:166
ColorContext _ctxt
Definition Table.h:247
container _translatedColumns
Definition Table.h:245
std::ostream & dumpDetails(std::ostream &stream, const Table &parent) const
Definition Table.cc:195
TableRow & addDetail(std::string s)
Definition Table.cc:174
container _details
Definition Table.h:246
bool _translateColumns
Definition Table.h:242
std::ostream & dumpTo(std::ostream &stream, const Table &parent) const
output with parent table attributes
Definition Table.cc:206
std::ostream & dumbDumpTo(std::ostream &stream) const
tab separated output
Definition Table.cc:181
TableRow()
Binary predicate for sorting.
Definition Table.h:174
const container & columns() const
Definition Table.h:229
void dumpRule(std::ostream &stream) const
Definition Table.cc:385
Table & setHeader(TableHeader tr)
Definition Table.cc:338
void updateColWidths(const TableRow &tr) const
Definition Table.cc:355
bool _has_header
Definition Table.h:454
std::ostream & dumpTo(std::ostream &stream) const
Definition Table.cc:406
int _width
table width (columns)
Definition Table.h:463
friend class TableRow
Definition Table.h:482
void lineStyle(TableLineStyle st)
Definition Table.cc:451
void wrap(int force_break_after=-1)
Definition Table.cc:444
const TableHeader & header() const
Definition Table.h:441
std::vector< bool > _abbrev_col
whether to abbreviate the respective column if needed
Definition Table.h:469
std::vector< unsigned > _max_width
maximum width of respective columns
Definition Table.h:461
TableLineStyle _style
table line drawing style
Definition Table.h:465
Table & add(TableRow tr)
Definition Table.cc:332
void margin(unsigned margin)
Definition Table.cc:457
TableHeader _header
Definition Table.h:455
bool _inHeader
Definition Table.h:480
int _screen_width
amount of space we have to print this table
Definition Table.h:467
unsigned _max_col
maximum column index seen in this table
Definition Table.h:459
int _force_break_after
Definition Table.h:474
unsigned _margin
left/right margin in number of spaces
Definition Table.h:471
bool _do_wrap
Whether to wrap the table if it exceeds _screen_width.
Definition Table.h:476
void allowAbbrev(unsigned column)
Definition Table.cc:345
static TableLineStyle defaultStyle
Definition Table.h:402
container _rows
Definition Table.h:456
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.
Definition Table.h:160
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
const char * asYesNo(bool val_r)
Definition Table.cc:32
TableLineStyle
table drawing style
Definition Table.h:80
@ none
Definition Table.h:92
@ Ascii
| - +
Definition Table.h:81
@ TLS_End
sentinel
Definition Table.h:93
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:36
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:1133
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:116
MbsIterator skipping ANSI SGR
Definition text.h:226
bool atEnd() const
Definition text.h:121
Write MBString optionally wrapped and indented.
Definition text.h:261
void gotoParBegin()
Open a new paragraph if not atParBegin.
Definition text.h:321
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
#define _(MSG)
Definition Gettext.h:39
#define ERR
Definition Logger.h:102