LCOV - code coverage report
Current view: top level - include/utils - VariadicTable.h (source / functions) Hit Total Coverage
Test: idaholab/moose framework: 8601ad Lines: 88 89 98.9 %
Date: 2025-07-18 13:27:08 Functions: 142 142 100.0 %
Legend: Lines: hit not hit

          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             : };

Generated by: LCOV version 1.14