]> git.armaanb.net Git - gen-shell.git/blob - src/libshared/src/Table.cpp
added install instructions
[gen-shell.git] / src / libshared / src / Table.cpp
1 ////////////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright 2006 - 2017, Paul Beckingham, Federico Hernandez.
4 //
5 // Permission is hereby granted, free of charge, to any person obtaining a copy
6 // of this software and associated documentation files (the "Software"), to deal
7 // in the Software without restriction, including without limitation the rights
8 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 // copies of the Software, and to permit persons to whom the Software is
10 // furnished to do so, subject to the following conditions:
11 //
12 // The above copyright notice and this permission notice shall be included
13 // in all copies or substantial portions of the Software.
14 //
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 // SOFTWARE.
22 //
23 // http://www.opensource.org/licenses/mit-license.php
24 //
25 ////////////////////////////////////////////////////////////////////////////////
26
27 #include <cmake.h>
28 #include <Table.h>
29 #include <shared.h>
30 #include <format.h>
31 #include <utf8.h>
32 #include <unistd.h>
33
34 ////////////////////////////////////////////////////////////////////////////////
35 void Table::add (const std::string& col, bool alignLeft, bool wrap)
36 {
37   _columns.push_back (col);
38   _align.push_back (alignLeft);
39   _wrap.push_back (wrap);
40 }
41
42 ////////////////////////////////////////////////////////////////////////////////
43 int Table::addRow ()
44 {
45   _data.push_back (std::vector <std::string> (_columns.size (), ""));
46   _color.push_back (std::vector <Color> (_columns.size (), Color::nocolor));
47   _oddness.push_back (_data.size () % 2 ? true : false);
48   return _data.size () - 1;
49 }
50
51 ////////////////////////////////////////////////////////////////////////////////
52 int Table::addRowOdd ()
53 {
54   _data.push_back (std::vector <std::string> (_columns.size (), ""));
55   _color.push_back (std::vector <Color> (_columns.size (), Color::nocolor));
56   _oddness.push_back (true);
57   return _data.size () - 1;
58 }
59
60 ////////////////////////////////////////////////////////////////////////////////
61 int Table::addRowEven ()
62 {
63   _data.push_back (std::vector <std::string> (_columns.size (), ""));
64   _color.push_back (std::vector <Color> (_columns.size (), Color::nocolor));
65   _oddness.push_back (false);
66   return _data.size () - 1;
67 }
68
69 ////////////////////////////////////////////////////////////////////////////////
70 void Table::set (int row, int col, const std::string& value, const Color color)
71 {
72   _data[row][col] = value;
73
74   if (color.nontrivial ())
75     _color[row][col] = color;
76 }
77
78 ////////////////////////////////////////////////////////////////////////////////
79 void Table::set (int row, int col, int value, const Color color)
80 {
81   std::string string_value = format (value);
82   _data[row][col] = string_value;
83
84   if (color.nontrivial ())
85     _color[row][col] = color;
86 }
87
88 ////////////////////////////////////////////////////////////////////////////////
89 void Table::set (int row, int col, const Color color)
90 {
91   if (color.nontrivial ())
92     _color[row][col] = color;
93 }
94
95 ////////////////////////////////////////////////////////////////////////////////
96 std::string Table::render ()
97 {
98   // Piped output disables color, unless overridden.
99   if (! _forceColor &&
100       ! isatty (STDOUT_FILENO))
101   {
102     _header     = Color ("");
103     _odd        = Color ("");
104     _even       = Color ("");
105     _intra_odd  = Color ("");
106     _intra_even = Color ("");
107     _extra_odd  = Color ("");
108     _extra_even = Color ("");
109
110     for (auto& row : _color)
111       for (auto& col : row)
112         col = Color ("");
113
114     _underline_headers = true;
115   }
116
117   // Determine minimal, ideal column widths.
118   std::vector <int> minimal;
119   std::vector <int> ideal;
120   for (unsigned int col = 0; col < _columns.size (); ++col)
121   {
122     // Headers factor in to width calculations.
123     unsigned int global_min = utf8_text_width (_columns[col]);
124     unsigned int global_ideal = global_min;
125
126     for (unsigned int row = 0; row < _data.size (); ++row)
127     {
128       // Determine minimum and ideal width for this column.
129       unsigned int min = 0;
130       unsigned int ideal = 0;
131       measureCell (_data[row][col], min, ideal);
132
133       if (min   > global_min)   global_min = min;
134       if (ideal > global_ideal) global_ideal = ideal;
135     }
136
137     minimal.push_back (global_min);
138     ideal.push_back (global_ideal);
139   }
140
141   // Sum the minimal widths.
142   int sum_minimal = 0;
143   for (const auto& c : minimal)
144     sum_minimal += c;
145
146   // Sum the ideal widths.
147   int sum_ideal = 0;
148   for (const auto& c : ideal)
149     sum_ideal += c;
150
151   // Calculate final column widths.
152   int overage = _width
153               - _left_margin
154               - (2 * _extra_padding)
155               - ((_columns.size () - 1) * _intra_padding);
156
157   std::vector <int> widths;
158   if (sum_ideal <= overage)
159     widths = ideal;
160   else if (sum_minimal > overage || overage < 0)
161     widths = minimal;
162   else if (overage > 0)
163   {
164     widths = minimal;
165     overage -= sum_minimal;
166
167     // Spread 'overage' among columns where width[i] < ideal[i]
168     while (overage)
169     {
170       for (unsigned int i = 0; i < _columns.size () && overage; ++i)
171       {
172         if (widths[i] < ideal[i])
173         {
174           ++widths[i];
175           --overage;
176         }
177       }
178     }
179   }
180
181   // Compose column headers.
182   unsigned int max_lines = 0;
183   std::vector <std::vector <std::string>> headers;
184   for (unsigned int c = 0; c < _columns.size (); ++c)
185   {
186     headers.push_back ({});
187     renderCell (headers[c], _columns[c], widths[c], _align[c], _wrap[c], _header);
188
189     if (headers[c].size () > max_lines)
190       max_lines = headers[c].size ();
191   }
192
193   // Output string.
194   std::string out;
195   _lines = 0;
196
197   // Render column headers.
198   std::string left_margin = std::string (_left_margin, ' ');
199   std::string extra       = std::string (_extra_padding, ' ');
200   std::string intra       = std::string (_intra_padding, ' ');
201
202   std::string extra_odd   = _extra_odd.colorize  (extra);
203   std::string extra_even  = _extra_even.colorize (extra);
204   std::string intra_odd   = _intra_odd.colorize  (intra);
205   std::string intra_even  = _intra_even.colorize (intra);
206
207   for (unsigned int i = 0; i < max_lines; ++i)
208   {
209     out += left_margin + extra;
210
211     for (unsigned int c = 0; c < _columns.size (); ++c)
212     {
213       if (c)
214         out += intra;
215
216       if (headers[c].size () < max_lines - i)
217         out += _header.colorize (std::string (widths[c], ' '));
218       else
219         out += headers[c][i];
220     }
221
222     out += extra;
223
224     // Trim right.
225     out.erase (out.find_last_not_of (" ") + 1);
226     out += '\n';
227
228     // Stop if the line limit is exceeded.
229     if (++_lines >= _truncate_lines && _truncate_lines != 0)
230       return out;
231   }
232
233   // Underline headers with ------ if necessary.
234   if (_underline_headers)
235   {
236     out += left_margin + extra;
237     for (unsigned int c = 0; c < _columns.size (); ++c)
238     {
239       if (c)
240         out += intra;
241
242       out += _header.colorize (std::string (widths[c], '-'));
243     }
244
245     out += '\n';
246   }
247
248   // Compose, render columns, in sequence.
249   _rows = 0;
250   std::vector <std::vector <std::string>> cells;
251   for (unsigned int row = 0; row < _data.size (); ++row)
252   {
253     max_lines = 0;
254
255     // Alternate rows based on |s % 2|
256     auto oddness = _oddness[row];
257     Color row_color = oddness ? _odd : _even;
258
259     // TODO row_color.blend (provided color);
260     // TODO Problem: colors for columns are specified, not rows,
261     //      therefore there are only cell colors, not intra colors.
262
263     Color cell_color;
264     for (unsigned int col = 0; col < _columns.size (); ++col)
265     {
266       cell_color = row_color;
267       cell_color.blend (_color[row][col]);
268
269       cells.push_back (std::vector <std::string> ());
270       renderCell (cells[col], _data[row][col], widths[col], _align[col], _wrap[col], cell_color);
271
272       if (cells[col].size () > max_lines)
273         max_lines = cells[col].size ();
274
275       if (_obfuscate)
276         for (auto& value : cells[col])
277           value = obfuscateText (value);
278     }
279
280     for (unsigned int i = 0; i < max_lines; ++i)
281     {
282       out += left_margin + (oddness ? extra_odd : extra_even);
283
284       for (unsigned int col = 0; col < _columns.size (); ++col)
285       {
286         if (col)
287         {
288           if (row_color.nontrivial ())
289             out += row_color.colorize (intra);
290           else
291             out += (oddness ? intra_odd : intra_even);
292         }
293
294         if (i < cells[col].size ())
295           out += cells[col][i];
296         else
297         {
298           cell_color = row_color;
299           cell_color.blend (_color[row][col]);
300           out += cell_color.colorize (std::string (widths[col], ' '));
301         }
302       }
303
304       out += (oddness ? extra_odd : extra_even);
305
306       // Trim right.
307       out.erase (out.find_last_not_of (" ") + 1);
308       out += '\n';
309
310       // Stop if the line limit is exceeded.
311       if (++_lines >= _truncate_lines && _truncate_lines != 0)
312         return out;
313     }
314
315     cells.clear ();
316
317     // Stop if the row limit is exceeded.
318     if (++_rows >= _truncate_rows && _truncate_rows != 0)
319       return out;
320   }
321
322   return out;
323 }
324
325 ////////////////////////////////////////////////////////////////////////////////
326 void Table::measureCell (
327   const std::string& data,
328   unsigned int& minimum,
329   unsigned int& maximum) const
330 {
331   std::string stripped = Color::strip (data);
332   maximum = longestLine (stripped);
333   minimum = longestWord (stripped);
334 }
335
336 ////////////////////////////////////////////////////////////////////////////////
337 void Table::renderCell (
338   std::vector <std::string>& lines,
339   const std::string& value,
340   int width,
341   bool alignLeft,
342   bool wrap,
343   const Color& color) const
344 {
345   if (wrap)
346   {
347     std::vector <std::string> raw;
348     wrapText (raw, value, width, false);
349
350     for (const auto& line : raw)
351       if (alignLeft)
352         lines.push_back (
353           color.colorize (
354             leftJustify (line, width)));
355       else
356         lines.push_back (
357           color.colorize (
358             rightJustify (line, width)));
359   }
360   else
361   {
362     if (alignLeft)
363       lines.push_back (
364         color.colorize (
365           leftJustify (value, width)));
366     else
367       lines.push_back (
368         color.colorize (
369           rightJustify (value, width)));
370   }
371 }
372
373 ////////////////////////////////////////////////////////////////////////////////
374