Imported Upstream version 17.14.0
[platform/upstream/libzypp.git] / zypp / base / DrunkenBishop.cc
1 /*---------------------------------------------------------------------\
2 |                          ____ _   __ __ ___                          |
3 |                         |__  / \ / / . \ . \                         |
4 |                           / / \ V /|  _/  _/                         |
5 |                          / /__ | | | | | |                           |
6 |                         /_____||_| |_| |_|                           |
7 |                                                                      |
8 \---------------------------------------------------------------------*/
9 /** \file       zypp/base/DrunkenBishop.cc
10  */
11 #include <iostream>
12 //#include "zypp/base/LogTools.h"
13 #include "zypp/base/Flags.h"
14 #include "zypp/base/String.h"
15 #include "zypp/base/NonCopyable.h"
16 #include "zypp/base/DrunkenBishop.h"
17
18 using std::endl;
19
20 ///////////////////////////////////////////////////////////////////
21 namespace zypp
22 {
23   ///////////////////////////////////////////////////////////////////
24   namespace base
25   {
26     ///////////////////////////////////////////////////////////////////
27     namespace
28     {
29       /** Direction the drunken Bishop wants to move. */
30       enum class Direction : std::uint8_t       // actually 2 bits
31       {
32         NW = 0x0,
33         NE = 0x1,
34         SW = 0x2,
35         SE = 0x3,
36       };
37
38       /** Convert a hex digit (case insensitive) into it's (4bit) integral value.
39        * \throws std::invalid_argument if char
40        */
41       inline std::uint8_t hexDigit( char ch_r )
42       {
43         switch ( ch_r )
44         {
45           case 'F': case 'f': return 15;
46           case 'E': case 'e': return 14;
47           case 'D': case 'd': return 13;
48           case 'C': case 'c': return 12;
49           case 'B': case 'b': return 11;
50           case 'A': case 'a': return 10;
51           case '9': return 9;
52           case '8': return 8;
53           case '7': return 7;
54           case '6': return 6;
55           case '5': return 5;
56           case '4': return 4;
57           case '3': return 3;
58           case '2': return 2;
59           case '1': return 1;
60           case '0': return 0;
61         }
62         throw std::invalid_argument( str::Str() << "Not a hex digit '" << ch_r << "'" );
63       }
64     } // namespace
65     ///////////////////////////////////////////////////////////////////
66
67     ///////////////////////////////////////////////////////////////////
68     /// \class DrunkenBishop::Impl
69     /// \brief DrunkenBishop implementation.
70     ///////////////////////////////////////////////////////////////////
71     class DrunkenBishop::Impl : private base::NonCopyable
72     {
73     public:
74       /** Default is an empty board. */
75       Impl()
76       : _h( 0U )
77       , _w( 0u )
78       , _s( 0U )
79       , _e( 0U )
80       , _renderSSH( true )
81       {}
82
83       /** Build up a new board.
84        * \throws std::invalid_argument
85        */
86       void compute( const std::string & data_r, const std::string & title_r, unsigned height_r = Auto, unsigned width_r = Auto )
87       {
88         // store rendering details
89         _renderSSH = ( data_r.size() <= 32 );   // up to the ssh fingerprint size
90         _fp = str::toUpper( data_r.size() <= 8 ? data_r : data_r.substr( data_r.size()-8 ) );
91         _tt = title_r;
92
93         // init the board
94         _h = odd(height_r);
95         _w = odd(width_r);
96
97         if ( _h == Auto )
98         {
99           if ( _renderSSH )
100           { _w = 17; _h = 9; }
101           else
102           { _w = 19; _h = 11; }
103         }
104         else if ( _w == Auto )
105         {
106           _w = (2*_h)-1;
107         }
108
109         _board = std::vector<std::uint8_t>( _w*_h, 0 );
110         _s = _w*_h/2;   // start
111         _e = _s;        // current/end
112         ++_board[_e];
113
114         // go
115         for ( const char * ch = data_r.c_str(); *ch; /*NOOP*/ )
116         {
117           std::uint8_t next4 = bite( ch );
118           // next4:     0x94
119           // bits:  10 01 01 00
120           // step:   4  3  2  1
121           static const std::uint8_t stepMask(0x3);
122           move( Direction(  next4     & stepMask ) );
123           move( Direction( (next4>>2) & stepMask ) );
124           move( Direction( (next4>>4) & stepMask ) );
125           move( Direction( (next4>>6) ) );
126         }
127       }
128
129       /** Render board to a stream. */
130       std::ostream & dumpOn( std::ostream & str, const std::string & prefix_r, Options options_r ) const
131       {
132         if ( _board.empty() )
133         {
134           // "++\n"
135           // "++"
136           return str << prefix_r << "++" << endl << prefix_r << "++";
137         }
138
139         static const char * colorReset = "\033[0m";
140         static const char * colorBg = "\033[48;5;242m";
141         bool useColor = options_r.testFlag( USE_COLOR );
142
143         renderTitleOn( str << prefix_r , _tt );
144
145         for ( unsigned p = 0; p < _board.size(); ++p )
146         {
147           if ( ( p % _w ) == 0 )
148           {
149             if ( p )
150               str << ( useColor ? colorReset: "" ) << '|';
151             str << endl << prefix_r << '|' << ( useColor ? colorBg : "" );
152           }
153           renderOn( str, useColor, p );
154         }
155         str << ( useColor ? colorReset: "" ) << '|';
156
157         renderTitleOn( str << endl << prefix_r, _fp );
158         return str;
159       }
160
161     private:
162       /** Increment even width/height values. */
163       static unsigned odd( unsigned val_r )
164       { return( val_r == Auto ? val_r : val_r|1U ); }
165
166       /** Get next 4 moves (8 bit) from next 2 hex digits (1st digit != '\0' asserted, 0-pad if necessary).
167        * \throws std::invalid_argument if char is not a hex digit or 1st char is \c \0
168        */
169       static std::uint8_t bite( const char *& ch_r )
170       {
171         std::uint8_t ret = hexDigit( *ch_r ) << 4;
172         if ( *(++ch_r) )
173           ret |= hexDigit( *(ch_r++) );
174         return ret;
175       }
176
177     private:
178       /** Move Bishop from \ref _e into \a direction_r and update the \ref _board. */
179       void move( Direction direction_r )
180       {
181         switch ( direction_r )
182         {
183           case Direction::NW:
184             if ( atTL() )
185               /*no move*/;
186             else if ( atT() )
187               _e -= 1;
188             else if ( atL() )
189               _e -= _w;
190             else
191               _e -= _w+1;
192             break;
193
194           case Direction::NE:
195             if ( atTR() )
196               /*no move*/;
197             else if ( atT() )
198               _e += 1;
199             else if ( atR() )
200               _e -= _w;
201             else
202               _e -= _w-1;
203             break;
204
205           case Direction::SW:
206             if ( atBL() )
207               /*no move*/;
208             else if ( atB() )
209               _e -= 1;
210             else if ( atL() )
211               _e += _w;
212             else
213               _e += _w-1;
214             break;
215
216           case Direction::SE:
217             if ( atBR() )
218               /*no move*/;
219             else if ( atB() )
220               _e += 1;
221             else if ( atR() )
222               _e += _w;
223             else
224               _e += _w+1;
225             break;
226
227           default:
228             throw std::invalid_argument( str::Str() << "Bad Direction " << unsigned(direction_r) );
229         }
230         // update the board
231         ++_board[_e];
232       }
233
234       /** Whether \ref _e is in the top left corner. */
235       bool atTL() const
236       { return( _e == 0 ); }
237
238       /** Whether \ref _e is in the top right corner. */
239       bool atTR() const
240       { return( _e == _w-1 ); }
241
242       /** Whether \ref _e is in the bottom left corner. */
243       bool atBL() const
244       { return( _e == _board.size()-_w ); }
245
246       /** Whether \ref _e is in the bottom right corner. */
247       bool atBR() const
248       { return( _e == _board.size()-1 ); }
249
250       /** Whether \ref _e is in the top row. */
251       bool atT() const
252       { return( _e < _w ); }
253
254       /** Whether \ref _e is in the bottom row. */
255       bool atB() const
256       { return( _e >= _board.size()-_w ); }
257
258       /** Whether \ref _e is in the left column. */
259       bool atL() const
260       { return( ( _e % _w ) == 0 ); }
261
262       /** Whether \ref _e is in the right column. */
263       bool atR() const
264       { return( ( _e % _w ) == (_w-1) ); }
265
266     private:
267       /** ANSI color heatmap. */
268       const char * color( std::uint8_t idx_r ) const
269       {
270         static const std::vector<const char *> colors = {
271           "",                   // no coin
272           "\033[38;5;21m",      // blue (cold)
273           "\033[38;5;39m",
274           "\033[38;5;50m",
275           "\033[38;5;48m",
276           "\033[38;5;46m",      // green
277           "\033[38;5;118m",
278           "\033[38;5;190m",
279           "\033[38;5;226m",     // yellow
280           "\033[38;5;220m",
281           "\033[38;5;214m",     // orange
282           "\033[38;5;208m",
283           "\033[38;5;202m",
284           "\033[38;5;196m",     // red
285           "\033[38;5;203m",
286           "\033[38;5;210m",
287           "\033[38;5;217m",     // pink
288           "\033[38;5;224m",
289           "\033[38;5;231m",     // white (hot)
290         };
291 #if 0
292         // cycle through heat map to test all colors
293         if ( ! idx_r )
294           return "";
295         static unsigned i = 0;
296         if ( ++i == colors.size() )
297           i = 1;
298         return colors[i];
299 #endif
300         return ( idx_r < colors.size() ? colors[idx_r] : *colors.rbegin() );
301       }
302
303       /** Render non empty title strings */
304       std::ostream & renderTitleOn( std::ostream & str, const std::string & title_r ) const
305       {
306         std::string buffer( _w+2, '-' );
307         *buffer.begin() = *buffer.rbegin() = '+';
308
309         if ( !title_r.empty() && _w >= 2 )      // extra 2 for "[]"
310         {
311           std::string::size_type tlen = std::min( title_r.size(), std::string::size_type(_w-2) );
312           std::string::size_type tpos = (_w-tlen)/2;    // not (_w-2-tlen) because buffer is size _w+2
313           buffer[tpos++] = '[';
314           for ( std::string::size_type p = 0; p < tlen; ++p, ++tpos )
315             buffer[tpos] = title_r[p];
316           buffer[tpos] = ']';
317         }
318         return str << buffer;
319       }
320
321       /** Render board numbers to printable chars. */
322       std::ostream & renderOn( std::ostream & str, bool useColor_r, unsigned pos_r ) const
323       {
324         static const std::string sshSet( " .o+=*BOX@%&#/^" );
325         static const std::string gpgSet( " .^:li?(fxXZ#MW&8%@" );
326         const std::string & charSet( _renderSSH ? sshSet : gpgSet );
327
328         if ( useColor_r )
329           str << color( _board[pos_r] );
330
331         if ( pos_r == _e )
332           return str << 'E';
333
334         if ( pos_r == _s )
335           return str << 'S';
336
337         return str << ( _board[pos_r] < charSet.size() ? charSet[_board[pos_r]] : *charSet.rbegin() );
338       }
339
340     private:
341       /** Request default width/height values. */
342       static constexpr const unsigned Auto = unsigned(-1);
343
344     private:
345       std::vector<std::uint8_t> _board; ///< the board
346       unsigned _h;                      ///< board height
347       unsigned _w;                      ///< board with
348       unsigned _s;                      ///< start position
349       unsigned _e;                      ///< end position
350
351     private:
352       bool _renderSSH;                  ///< whether to render the ssh (or gpg) char set
353       std::string _fp;                  ///< fingerprint to render as bottom title
354       std::string _tt;                  ///< text to render as top title
355
356     public:
357       /** Offer default Impl. */
358       static shared_ptr<Impl> nullimpl()
359       {
360         static shared_ptr<Impl> _nullimpl( new Impl );
361         return _nullimpl;
362       }
363     };
364
365     ///////////////////////////////////////////////////////////////////
366     //  CLASS NAME : DrunkenBishop
367     ///////////////////////////////////////////////////////////////////
368
369     DrunkenBishop::DrunkenBishop()
370     : _pimpl( Impl::nullimpl() )
371     { /*nothing to compute*/ }
372
373     DrunkenBishop::DrunkenBishop( const std::string & data_r, const std::string & title_r )
374     : _pimpl( new Impl )
375     { _pimpl->compute( data_r, title_r ); }
376
377     DrunkenBishop::DrunkenBishop( const std::string & data_r, const std::string & title_r, unsigned height_r )
378     : _pimpl( new Impl )
379     { _pimpl->compute( data_r, title_r, height_r ); }
380
381     DrunkenBishop::DrunkenBishop( const std::string & data_r, const std::string & title_r, unsigned height_r, unsigned width_r )
382     : _pimpl( new Impl )
383     { _pimpl->compute( data_r, title_r, height_r, width_r ); }
384
385     DrunkenBishop::~DrunkenBishop()
386     {}
387
388     std::ostream & DrunkenBishop::dumpOn( std::ostream & str, const std::string & prefix_r, Options options_r ) const
389     { return _pimpl->dumpOn( str, prefix_r, options_r ); }
390
391     std::string DrunkenBishop::asString( const std::string & prefix_r, Options options_r ) const
392     {
393       std::ostringstream str;
394       dumpOn( str, prefix_r, options_r );
395       return str.str();
396     }
397
398     std::vector<std::string> DrunkenBishop::asLines( const std::string & prefix_r, Options options_r ) const
399     {
400       std::vector<std::string> ret;
401       str::split( asString(  prefix_r, options_r ), std::back_inserter(ret), "\n" );
402       return ret;
403     }
404
405   } // namespace base
406   ///////////////////////////////////////////////////////////////////
407 } // namespace zypp
408 ///////////////////////////////////////////////////////////////////