1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
// *****************************************************************************
/*!
  \file      src/Base/Print.hpp
  \copyright 2012-2015 J. Bakosi,
             2016-2018 Los Alamos National Security, LLC.,
             2019-2021 Triad National Security, LLC.,
             2022-2024 J. Bakosi
             All rights reserved. See the LICENSE file for details.
  \brief     General purpose pretty printer functionality
  \details   This file contains general purpose printer functions. Using the
    functions defined here provides formatting, and a consistent look with
    simple client-side code.
*/
// *****************************************************************************
#pragma once

#include <iostream>
#include <cmath>

#include "Timer.hpp"
#include "Exception.hpp"
#include "PrintUtil.hpp"

namespace tk {

//! Pretty printer base. Contains general purpose printer functions. Using the
//! functions defined here provides formatting, and a consistent look with
//! simple client-side code.
class Print {

  public:
    //! Constructor
    explicit Print() : m_stream( std::cout ) {}

    //! Operator << for printing any type to the verbose stream.
    //! \param[in] os Reference to pretty printer object
    //! \param[in] t Reference to an arbitrary object of type T. T must define
    //! operator<< for std::ostream-compatible streams.
    //! \return The internal stream buffer of the stream
    template< typename T >
    friend const Print& operator<<( const Print& os, const T& t ) {
      os.m_stream << t << std::flush;
      return os;
    }

    //! Formatted print of section title
    //! \param[in] t Section title to be printed
    void section( const std::string& t ) const {
      std::string underline( t.size(), '-' );
      m_stream << '\n' << t.c_str() << '\n' << underline.c_str() << std::endl;
    }

    //! Formatted print of item: name : value
    //! \param[in] name Item name to be printed
    //! \param[in] value Item value to be printed
    template< typename T >
    void item( const std::string& name, const T& value ) const {
      m_stream << name.c_str() << ": ";
      if constexpr( std::is_same_v< T, std::string > ) {
        m_stream << value.c_str();
      } else {
        m_stream << value;
      }
      m_stream << std::endl;
    }

    //! Formatted print of item: h:m:s.
    //! \param[in] name Item name to be printed
    //! \param[in] watch Watch (in hours, minutes, seconds) to be printed as
    //!   item value
    void item( const std::string& name, const tk::Timer::Watch& watch ) const {
      m_stream << name.c_str() << ": "
               << watch.hrs.count() << ':'
               << watch.min.count() << ':'
               << watch.sec.count() << std::endl;
    }

    //! Formatted print of a performance statistic (an item of a list)
    //! \param[in] name Performance statistic name to be printed
    //! \param[in] value Performance statistic value
    void perfitem( const std::string& name, tk::real value ) const {
      m_stream << name.c_str() << " : " << value << std::endl;
    }

    //! Formatted print of elapsed times
    //! \param[in] t Title of section containing a list of elapsed times
    //! \param[in] clock std::vector of strings (clock names) and associated
    //!   timers which could be in various formats as long as there is a
    //!   corresponding item() overload that can apply operator << for outputing
    //!   their value to an output stream. Examples of allowed ClockFormats are:
    //!   tk::Timer::Watch, which is a struct containing a timestamp in h:m:s
    //!   format, and the return value of Timer::dsec(), which is a tk::real.
    template< class ClockFormat >
    void time( const std::string& t,
               const std::vector<
                 std::pair< std::string, ClockFormat > >& clock ) const
    {
      section( t );
      for (const auto& c : clock) item( c.first, c.second );
      m_stream << std::endl;
    }

    //! Echo formatted print of a diagnostics message within a progress section
    //! \param[in] labels Label parts of diagnostics message
    //! \param[in] values Value parts of diagnostics message
    //! \note The number of labels and values must equal.
    void diag( const std::vector< std::string >& labels,
               const std::vector< std::string >& values ) const
    {
      Assert( labels.size() == values.size(), "Size mismatch" );
      if (!labels.empty()) {
        m_stream << labels[0] << ": " << values[0];
        for (std::size_t i=1; i<labels.size(); ++i) {
          m_stream << ", " << labels[i] << ": " << values[i];
        }
        m_stream << std::flush;
      }
    }

    //! Start formatted print of a diagnostics message
    //! Start formatted print of a diagnostics message
    //! \param[in] msg First part of message to print as a diagnostics message
    void diagstart( const std::string& msg ) const {
      m_stream << msg.c_str() << ' ' << std::flush;
    }

    //! Finish formatted print of a diagnostics message
    //! \param[in] msg Last part of message to print as a diagnostics message
    void diagend( const std::string& msg ) const {
      m_stream << msg.c_str() << std::endl;
    }

    //! Echo formatted print of a progress message
    //! \param[in] prefix Strings to output prefixing the progress report
    //! \param[in] done Array of integers indicating how many have been done
    //! \param[in] max Array of integers indicating how many to be done
    //! \param[in] progress_size Size of previous progress report (to overwrite)
    //! \details All input arrays are the same size. The prefix strings
    //!   are optional, i.e., they can be empty strings. The function generates
    //!   an output to the stream configured in the following fashion:
    //!   pre1[done1/max1], pre2[done2/max2], ..., e.g., r:[1/3], b[2/8].
    //!   Whenever this function is called, a number of backspaces are put into
    //!   the stream so that the new progress report string overwrites the old
    //!   one. In order to backtrack the correct amount, the length of the old
    //!   progress report is stored (by whatever object holds us) and passed in
    //!   by reference in progress_size, which is overwritten here once it has
    //!   been used for backtracking. Therefore, for restarting a new series of
    //!   progress reports, this variable must be zeroed. Also, it is best to
    //!   not to interleave multiple tasks, because even if a different
    //!   progress_size is kept for each, there is no regard as to which line we
    //!   output to in the stream. In other words, multiple task outputs will
    //!   be intermingled, leading to confusing output.
    template< std::size_t N >
    void progress( const std::array< std::string, N >& prefix,
                   const std::array< int, N >& done,
                   const std::array< int, N >& max,
                   std::size_t& progress_size ) const
    {
      // lambda to determine the number of digits in an integer
      auto numdig = []( int i ) -> std::size_t {
        return i > 0 ?
          static_cast< std::size_t >( std::log10(static_cast<double>(i)) ) + 1
          : 1; };
      // Backspace so that new progress can overwrite old one
      m_stream << std::string( progress_size, '\b' ).c_str();
      std::stringstream ss;
      auto ip = prefix.cbegin();
      auto id = done.cbegin();
      auto im = max.cbegin();
      progress_size = 0;
      while (ip != prefix.cend()) {
        // Compute new length of progress string
        progress_size += 4 + ip->size() + numdig(*id) + numdig(*im);
        // Construct and output new progress string to stream
        ss << *ip << ":[" << *id << '/' << *im << ']';
        ++ip; ++id; ++im;
        // if next subprogress is not the last one, put in a comma
        if (ip != prefix.cend()) {
          ss << ", ";
          progress_size += 2;
        } else {
          ss << ' ';
          ++progress_size;
        }
      }
      m_stream << ss.str().c_str() << std::flush;
    }

    //! Print version information
    //! \param[in] executable Name of executable to output version for
    //! \param[in] git_commit Git commit sha1 to output
    void version( const std::string& executable,
                  const std::string& git_commit ) const {
      m_stream << "\nXyst::" << executable.c_str()
               << ", revision " << git_commit.c_str() << '\n' << std::endl;
    }

    //! Print mandatory arguments information
    //! \param[in] args Mandaatory-arguments infor to output
    void mandatory( const std::string& args ) const {
      m_stream << "\n>>> ERROR: " << args.c_str() << std::endl;
    }

    //! Print example usage information
    //! \param[in] example Example command line to output
    //! \param[in] msg Message to output after example
    void usage( const std::string& example, const std::string& msg ) const {
      m_stream << "\nUsage: " << example.c_str() << '\n'
               << msg.c_str() << ". See also -h." << '\n' << std::endl;
    }

    //! Print lower and upper bounds for a keyword if defined
    template< typename Info >
    void bounds( const Info& info ) const {
      if (info.lower) {
        m_stream << splitLines( *info.lower, "  ", "Lower bound: " ).c_str();
      }
      if (info.upper) {
        m_stream << splitLines( *info.upper, "  ", "Upper bound: " ).c_str();
      }
      m_stream << std::flush;
    }

    //! Print unit tests header (with legend)
    //! \param[in] t Section title
    //! \param[in] group String attempting to match unit test groups
    void unithead( const std::string& t, const std::string& group ) const {
      section( t );
      m_stream << "Groups: " + (group.empty() ? "all" : group) +
                  " (use -g str to match groups)\n" +
                  "Legend: [done/failed] group/test: result\n" << std::endl;
    }

    //! Print one-liner info for test
    //! \details Columns:
    //!   [done/failed]
    //!   - done: number of tests completed so far
    //!   - failed: number of failed tests so far
    //!   name of the test group
    //!   name of the test
    //!   result (with additional info if failed)
    //!   Assumed fields for status:
    //!   - status[0]: test group name
    //!   - status[1]: test name
    //!   - status[2]: result (tut::test_result::result_type as string)
    //!   - status[3]: exception message for failed test
    //!   - status[4]: exception type id for failed test
    void test( std::size_t ncomplete,
               std::size_t nfail,
               const std::vector< std::string >& status )
    {
      if (status[2] != "8") {             // if not dummy
        std::stringstream ss;
        ss << " [" << ncomplete << '/' << nfail << "] " << status[0] << ':'
           << status[1];
        auto s = ss.str() + ' ' + std::string(80-ss.str().size(),'.') + "  ";
        m_stream << s << result( status[2], status[3], status[4] ) << std::endl;
      }
    }

    //! Print Inciter header. Text ASCII Art Generator used for executable
    //! names: http://patorjk.com/software/taag.
    void headerInciter() const {
       m_stream << R"(
____  ___                __    __   .___              .__  __                
\   \/  /___.__. _______/  |_  \ \  |   | ____   ____ |__|/  |_  ___________ 
 \     /<   |  |/  ___/\   __\  \ \ |   |/    \_/ ___\|  \   __\/ __ \_  __ \
 /     \ \___  |\___ \  |  |    / / |   |   |  \  \___|  ||  | \  ___/|  | \/
/___/\  \/ ____/____  > |__|   /_/  |___|___|  /\___  >__||__|  \___  >__|   
      \_/\/         \/                       \/     \/              \/)"
      << std::endl;
    }

    //! Print UnitTest header. Text ASCII Art Generator used for executable
    //! names: http://patorjk.com/software/taag.
    void headerUnitTest() const {
       m_stream << R"(
____  ___                __    __    ____ ___      .__  __ ___________              __   
\   \/  /___.__. _______/  |_  \ \  |    |   \____ |__|/  |\__    ___/___   _______/  |_ 
 \     /<   |  |/  ___/\   __\  \ \ |    |   /    \|  \   __\|    |_/ __ \ /  ___/\   __\
 /     \ \___  |\___ \  |  |    / / |    |  /   |  \  ||  |  |    |\  ___/ \___ \  |  |  
/___/\  \/ ____/____  > |__|   /_/  |______/|___|  /__||__|  |____| \___  >____  > |__|  
      \_/\/         \/                           \/                     \/     \/)"
      << std::endl;
    }

    //! Print MeshConv header. Text ASCII Art Generator used for executable
    //! names: http://patorjk.com/software/taag.
    void headerMeshConv() const {
      m_stream << R"(
____  ___                __    __      _____                .__    _________                     
\   \/  /___.__. _______/  |_  \ \    /     \   ____   _____|  |__ \_   ___ \  ____   _______  __
 \     /<   |  |/  ___/\   __\  \ \  /  \ /  \_/ __ \ /  ___/  |  \/    \  \/ /  _ \ /    \  \/ /
 /     \ \___  |\___ \  |  |    / / /    Y    \  ___/ \___ \|   Y  \     \___(  <_> )   |  \   / 
/___/\  \/ ____/____  > |__|   /_/  \____|__  /\___  >____  >___|  /\______  /\____/|___|  /\_/  
      \_/\/         \/                      \/     \/     \/     \/        \/            \/)"
      << std::endl;
    }

  private:
    std::ostream& m_stream;     //!< Output stream

    //! Return human-readable test result based on result code
    //! \param[in] code Result code
    //! \param[in] msg Message to append
    //! \param[in] ex Expection message to attach to exceptions cases
    std::string result( const std::string& code,
                        const std::string& msg,
                        const std::string& ex ) const
    {
      if (code == "0") return "Pass";
      else if (code == "1") return "Fail: " + msg;
      else if (code == "2") return "Except: " + msg + ex;
      else if (code == "3") return "Warning: " + msg;
      else if (code == "4") return "Terminate: " + msg;
      else if (code == "5") return "Ex_ctor: " + msg + ex;
      else if (code == "6") return "Rethrown: " + msg + ex;
      else if (code == "7") return "Skipped: " + msg;
      else if (code == "8") return "Dummy";
      else Throw( "No such unit test result code found" );
    }
};

} // tk::