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