Line data Source code
1 : //* This file is part of the MOOSE framework
2 : //* https://mooseframework.inl.gov
3 : //*
4 : //* All rights reserved, see COPYRIGHT for full restrictions
5 : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
6 : //*
7 : //* Licensed under LGPL 2.1, please see LICENSE for details
8 : //* https://www.gnu.org/licenses/lgpl-2.1.html
9 :
10 : #pragma once
11 :
12 : /**
13 : * Note! This class comes from https://github.com/friedmud/variadic_table
14 : *
15 : *
16 : * DO NOT MODIFY THIS CLASS HERE... MODIFY IT THERE AND COPY IT HERE
17 : *
18 : */
19 :
20 : #include <iostream>
21 : #include <iomanip>
22 : #include <ios>
23 : #include <vector>
24 : #include <tuple>
25 : #include <type_traits>
26 : #include <cassert>
27 : #include <cmath>
28 :
29 : /**
30 : * Used to specify the column format
31 : */
32 : enum class VariadicTableColumnFormat
33 : {
34 : AUTO,
35 : SCIENTIFIC,
36 : FIXED,
37 : PERCENT
38 : };
39 :
40 : /**
41 : * A class for "pretty printing" a table of data.
42 : *
43 : * Requries C++11 (and nothing more)
44 : *
45 : * It's templated on the types that will be in each column
46 : * (all values in a column must have the same type)
47 : *
48 : * For instance, to use it with data that looks like: "Fred", 193.4, 35, "Sam"
49 : * with header names: "Name", "Weight", "Age", "Brother"
50 : *
51 : * You would invoke the table like so:
52 : * VariadicTable<std::string, double, int, std::string> vt("Name", "Weight", "Age", "Brother");
53 : *
54 : * Then add the data to the table:
55 : * vt.addRow("Fred", 193.4, 35, "Sam");
56 : *
57 : * And finally print it:
58 : * vt.print();
59 : */
60 : template <class... Ts>
61 : class VariadicTable
62 : {
63 : public:
64 : /// The type stored for each row
65 : typedef std::tuple<Ts...> DataTuple;
66 :
67 : /**
68 : * Construct the table with headers
69 : *
70 : * @param headers The names of the columns
71 : * @param static_column_size The size of columns that can't be found automatically
72 : */
73 61173 : VariadicTable(std::vector<std::string> headers,
74 : unsigned int static_column_size = 0,
75 : unsigned int cell_padding = 1)
76 61173 : : _headers(headers),
77 61173 : _num_columns(std::tuple_size<DataTuple>::value),
78 61173 : _static_column_size(static_column_size),
79 61173 : _cell_padding(cell_padding)
80 : {
81 : assert(headers.size() == _num_columns);
82 61173 : }
83 :
84 : /**
85 : * Add a row of data
86 : *
87 : * Easiest to use like:
88 : * table.addRow({data1, data2, data3});
89 : *
90 : * @param data A Tuple of data to add
91 : */
92 674643 : void addRow(Ts... entries) { _data.emplace_back(std::make_tuple(entries...)); }
93 :
94 : /**
95 : * Pretty print the table of data
96 : */
97 : template <typename StreamType>
98 58179 : void print(StreamType & stream)
99 : {
100 58179 : size_columns();
101 :
102 : // Start computing the total width
103 : // First - we will have _num_columns + 1 "|" characters
104 58179 : unsigned int total_width = _num_columns + 1;
105 :
106 : // Now add in the size of each colum
107 611333 : for (auto & col_size : _column_sizes)
108 553154 : total_width += col_size + (2 * _cell_padding);
109 :
110 : // Print out the top line
111 58480 : stream << std::string(total_width, '-') << "\n";
112 :
113 : // Print out the headers
114 58179 : stream << "|";
115 611333 : for (unsigned int i = 0; i < _num_columns; i++)
116 : {
117 : // Must find the center of the column
118 553154 : auto half = _column_sizes[i] / 2;
119 553154 : half -= _headers[i].size() / 2;
120 :
121 1107211 : stream << std::string(_cell_padding, ' ') << std::setw(_column_sizes[i]) << std::left
122 2765770 : << std::string(half, ' ') + _headers[i] << std::string(_cell_padding, ' ') << "|";
123 : }
124 :
125 58179 : stream << "\n";
126 :
127 : // Print out the line below the header
128 58480 : stream << std::string(total_width, '-') << "\n";
129 :
130 : // Now print the rows of the table
131 732822 : for (auto & row : _data)
132 : {
133 674643 : stream << "|";
134 674643 : print_each(row, stream);
135 674643 : stream << "\n";
136 : }
137 :
138 : // Print out the line below the header
139 58480 : stream << std::string(total_width, '-') << std::endl;
140 58179 : }
141 :
142 : /**
143 : * Set how to format numbers for each column
144 : *
145 : * Note: this is ignored for std::string columns
146 : *
147 : * @column_format The format for each column: MUST be the same length as the number of columns.
148 : */
149 55424 : void setColumnFormat(const std::vector<VariadicTableColumnFormat> & column_format)
150 : {
151 : assert(column_format.size() == std::tuple_size<DataTuple>::value);
152 :
153 55424 : _column_format = column_format;
154 55424 : }
155 :
156 : /**
157 : * Set how many digits of precision to show for floating point numbers
158 : *
159 : * Note: this is ignored for std::string columns
160 : *
161 : * @column_format The precision for each column: MUST be the same length as the number of columns.
162 : */
163 55424 : void setColumnPrecision(const std::vector<int> & precision)
164 : {
165 : assert(precision.size() == std::tuple_size<DataTuple>::value);
166 55424 : _precision = precision;
167 55424 : }
168 :
169 : protected:
170 : // Attempts to figure out the correct justification for the data
171 : template <typename T>
172 6695879 : static auto justify(int /*firstchoice*/)
173 : {
174 : if constexpr (std::is_arithmetic<typename std::remove_reference<T>::type>::value)
175 : // If it's a floating point value
176 6004239 : return std::right;
177 : else
178 : // Otherwise
179 691640 : return std::left;
180 : }
181 :
182 : /**
183 : * These three functions print out each item in a Tuple into the table
184 : *
185 : * Original Idea From From https://stackoverflow.com/a/26908596
186 : *
187 : * BTW: This would all be a lot easier with generic lambdas
188 : * there would only need to be one of this sequence and then
189 : * you could pass in a generic lambda. Unfortunately, that's C++14
190 : */
191 :
192 : /**
193 : * This gets called on each item
194 : */
195 : template <std::size_t I, typename TupleType, typename StreamType>
196 6695879 : void print_each(TupleType && t, StreamType & stream, std::integral_constant<size_t, I>)
197 : {
198 6695879 : auto & val = std::get<I>(t);
199 :
200 6695879 : auto original_precision = stream.precision();
201 6695879 : auto original_flags = stream.flags();
202 :
203 : // Set the precision
204 6695879 : if (!_precision.empty())
205 : {
206 : assert(_precision.size() ==
207 : std::tuple_size<typename std::remove_reference<TupleType>::type>::value);
208 :
209 6676132 : stream << std::setprecision(_precision[I]);
210 : }
211 :
212 : // Set the format
213 6695879 : if (!_column_format.empty())
214 : {
215 : assert(_column_format.size() ==
216 : std::tuple_size<typename std::remove_reference<TupleType>::type>::value);
217 :
218 6676132 : if (_column_format[I] == VariadicTableColumnFormat::SCIENTIFIC)
219 0 : stream << std::scientific;
220 :
221 6676132 : if (_column_format[I] == VariadicTableColumnFormat::FIXED)
222 2665802 : stream << std::fixed;
223 :
224 6676132 : if (_column_format[I] == VariadicTableColumnFormat::PERCENT)
225 1332901 : stream << std::fixed << std::setprecision(2);
226 : }
227 :
228 6701858 : stream << std::string(_cell_padding, ' ') << std::setw(_column_sizes[I])
229 20087637 : << justify<decltype(val)>(0) << val << std::string(_cell_padding, ' ') << "|";
230 :
231 : // Restore the format
232 6695879 : if (!_column_format.empty())
233 6676132 : stream.flags(original_flags);
234 :
235 : // Restore the precision
236 6695879 : if (!_precision.empty())
237 6676132 : stream.precision(original_precision);
238 :
239 : // Recursive call to print the next item (if there are any left)
240 : if constexpr (I + 1 != std::tuple_size<typename std::remove_reference<TupleType>::type>::value)
241 6021236 : print_each(std::forward<TupleType>(t), stream, std::integral_constant<size_t, I + 1>());
242 6695879 : }
243 :
244 : /**
245 : * This is what gets called first
246 : */
247 : template <typename TupleType, typename StreamType>
248 674643 : void print_each(TupleType && t, StreamType & stream)
249 : {
250 674643 : print_each(std::forward<TupleType>(t), stream, std::integral_constant<size_t, 0>());
251 674643 : }
252 :
253 : /**
254 : * Try to find the size the column will take up
255 : *
256 : * If the datatype has a size() member... let's call it
257 : */
258 : template <class T>
259 691640 : size_t sizeOfData(const T & data, decltype(((T *)nullptr)->size()) * /*dummy*/ = nullptr)
260 : {
261 691640 : return data.size();
262 : }
263 :
264 : /**
265 : * Try to find the size the column will take up
266 : *
267 : * If the datatype is an integer - let's get it's length
268 : */
269 : template <class T>
270 2005536 : size_t sizeOfData(const T & data,
271 : typename std::enable_if<std::is_integral<T>::value>::type * /*dummy*/ = nullptr)
272 : {
273 2005536 : if (data == 0)
274 976226 : return 1;
275 :
276 1029310 : return std::log10(data) + 1;
277 : }
278 :
279 : /**
280 : * If it doesn't... let's just use a statically set size
281 : */
282 3998703 : size_t sizeOfData(...) { return _static_column_size; }
283 :
284 : /**
285 : * These three functions iterate over the Tuple, find the printed size of each element and set it
286 : * in a vector
287 : */
288 :
289 : /**
290 : * End the recursion
291 : */
292 : template <typename TupleType>
293 674643 : void size_each(TupleType &&,
294 : std::vector<unsigned int> & /*sizes*/,
295 : std::integral_constant<
296 : size_t,
297 : std::tuple_size<typename std::remove_reference<TupleType>::type>::value>)
298 : {
299 674643 : }
300 :
301 : /**
302 : * Recursively called for each element
303 : */
304 : template <std::size_t I,
305 : typename TupleType,
306 : typename = typename std::enable_if<
307 : I != std::tuple_size<typename std::remove_reference<TupleType>::type>::value>::type>
308 : void
309 6695879 : size_each(TupleType && t, std::vector<unsigned int> & sizes, std::integral_constant<size_t, I>)
310 : {
311 6695879 : sizes[I] = sizeOfData(std::get<I>(t));
312 :
313 : // Override for Percent
314 6695879 : if (!_column_format.empty())
315 6676132 : if (_column_format[I] == VariadicTableColumnFormat::PERCENT)
316 1332901 : sizes[I] = 6; // 100.00
317 :
318 : // Continue the recursion
319 6695879 : size_each(std::forward<TupleType>(t), sizes, std::integral_constant<size_t, I + 1>());
320 6695879 : }
321 :
322 : /**
323 : * The function that is actually called that starts the recursion
324 : */
325 : template <typename TupleType>
326 674643 : void size_each(TupleType && t, std::vector<unsigned int> & sizes)
327 : {
328 674643 : size_each(std::forward<TupleType>(t), sizes, std::integral_constant<size_t, 0>());
329 674643 : }
330 :
331 : /**
332 : * Finds the size each column should be and set it in _column_sizes
333 : */
334 58179 : void size_columns()
335 : {
336 58179 : _column_sizes.resize(_num_columns);
337 :
338 : // Temporary for querying each row
339 58179 : std::vector<unsigned int> column_sizes(_num_columns);
340 :
341 : // Start with the size of the headers
342 611333 : for (unsigned int i = 0; i < _num_columns; i++)
343 553154 : _column_sizes[i] = _headers[i].size();
344 :
345 : // Grab the size of each entry of each row and see if it's bigger
346 732822 : for (auto & row : _data)
347 : {
348 674643 : size_each(row, column_sizes);
349 :
350 7370522 : for (unsigned int i = 0; i < _num_columns; i++)
351 6695879 : _column_sizes[i] = std::max(_column_sizes[i], column_sizes[i]);
352 : }
353 58179 : }
354 :
355 : /// The column headers
356 : std::vector<std::string> _headers;
357 :
358 : /// Number of columns in the table
359 : unsigned int _num_columns;
360 :
361 : /// Size of columns that we can't get the size of
362 : unsigned int _static_column_size;
363 :
364 : /// Size of the cell padding
365 : unsigned int _cell_padding;
366 :
367 : /// The actual data
368 : std::vector<DataTuple> _data;
369 :
370 : /// Holds the printable width of each column
371 : std::vector<unsigned int> _column_sizes;
372 :
373 : /// Column Format
374 : std::vector<VariadicTableColumnFormat> _column_format;
375 :
376 : /// Precision For each column
377 : std::vector<int> _precision;
378 : };
|