xo-indentlog: add general-purposee pretty-printing [WIP]
This commit is contained in:
parent
78d0230e5a
commit
56d00a5913
24 changed files with 1326 additions and 121 deletions
|
|
@ -1,4 +1,4 @@
|
|||
# indentlog/CMakeLists.txt
|
||||
# xo-indentlog/CMakeLists.txt
|
||||
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
|
|
@ -28,12 +28,8 @@ add_subdirectory(utest)
|
|||
set(SELF_LIB indentlog)
|
||||
xo_add_headeronly_library(${SELF_LIB})
|
||||
xo_install_library4(${SELF_LIB} ${PROJECT_NAME}Targets)
|
||||
xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets)
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# docs targets depend on all the other library/utest targets
|
||||
#
|
||||
#add_subdirectory(docs)
|
||||
xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets)
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
|
|
@ -45,4 +41,10 @@ if (XO_ENABLE_EXAMPLES)
|
|||
install(TARGETS indentlog_ex4 DESTINATION bin/indentlog/example)
|
||||
endif()
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# docs targets depends on other library/utest/exec targets,
|
||||
# must come after them
|
||||
#
|
||||
add_subdirectory(docs)
|
||||
|
||||
# end CMakeLists.txt
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
set(PROJECT_CXX_FLAGS "--std=c++20")
|
||||
|
||||
add_definitions(${PROJECT_CXX_FLAGS})
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "")
|
||||
#set(CMAKE_CXX_STANDARD 20)
|
||||
#set(CMAKE_CXX_STANDARD_REQUIRED True)
|
||||
#set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "")
|
||||
|
||||
#include(cmake/FindSphinx.cmake)
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,10 @@ namespace xo {
|
|||
static bool time_local_flag;
|
||||
/* true to log time-of-day with microsecond precision; false for millisecond precision */
|
||||
static bool time_usec_flag;
|
||||
/* true to enable pretty-printing (see basic_scope::log()) */
|
||||
static bool pretty_print_enabled;
|
||||
/* when pretty-printing enabled, use newlines in effort to avoid writing beyond this margin */
|
||||
static std::uint32_t right_margin;
|
||||
/* spaces per nesting level. 0 -> no indenting */
|
||||
static std::uint32_t indent_width;
|
||||
/* max #of spaces to introduce when indenting */
|
||||
|
|
@ -58,6 +62,14 @@ namespace xo {
|
|||
bool
|
||||
log_config_impl<Tag>::time_usec_flag = true;
|
||||
|
||||
template <typename Tag>
|
||||
bool
|
||||
log_config_impl<Tag>::pretty_print_enabled = true;
|
||||
|
||||
template <typename Tag>
|
||||
std::uint32_t
|
||||
log_config_impl<Tag>::right_margin = 130;
|
||||
|
||||
template <typename Tag>
|
||||
std::uint32_t
|
||||
log_config_impl<Tag>::indent_width = 2;
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ namespace xo {
|
|||
void decr_nesting() { --nesting_level_; }
|
||||
|
||||
std::ostream & ss() { return ss_; }
|
||||
log_streambuf_type & sbuf() { return *p_sbuf_phase1_.get(); }
|
||||
|
||||
void check_print_time(utc_nanos now_tm) {
|
||||
using xo::time::timeutil;
|
||||
|
|
@ -116,12 +117,6 @@ namespace xo {
|
|||
*/
|
||||
std::unique_ptr<log_streambuf_type> p_sbuf_phase1_;
|
||||
|
||||
/* #of characters found in .p_sbuf_phase1 since last \n.
|
||||
* this value is established+updated in .flush2sbuf().
|
||||
* (in particular ignored by stream .ss())
|
||||
*/
|
||||
std::size_t lpos_ = 0;
|
||||
|
||||
/* whenever .set_location() is called:
|
||||
* - capture (file, line)
|
||||
* - print them near right margin with next output line
|
||||
|
|
@ -256,9 +251,14 @@ namespace xo {
|
|||
state_impl<CharT, Traits>::flush2sbuf(std::streambuf * p_sbuf)
|
||||
{
|
||||
log_streambuf_type * sbuf1 = this->p_sbuf_phase1_.get();
|
||||
|
||||
// instead of post-processing, rely on newline-aware log_streambuf
|
||||
// to indent in advance
|
||||
|
||||
#ifdef OBSOLETE
|
||||
log_streambuf_type * sbuf2 = this->p_sbuf_phase2_.get();
|
||||
|
||||
/* generally expecting sbuf to contain one line of output.
|
||||
/* often sbuf contains one line of output.
|
||||
* if it contains multiple newlines, need to indent
|
||||
* after each one.
|
||||
*
|
||||
|
|
@ -269,8 +269,8 @@ namespace xo {
|
|||
* in the unlikely event that it's non-zero
|
||||
*/
|
||||
char const * s = sbuf1->lo();
|
||||
char const * e = s + sbuf1->pos();
|
||||
|
||||
char const * e = s + sbuf1->pos();
|
||||
char const * p = s;
|
||||
|
||||
/* point to first space following a non-space character.
|
||||
|
|
@ -290,8 +290,14 @@ namespace xo {
|
|||
|
||||
/* for indenting, looking for first 'space following non-space, on first line', if any */
|
||||
|
||||
std::size_t lpos_on_newline = 0;
|
||||
#ifdef OBSOLETE
|
||||
// ..multiline input should have already been indented by custom log_streambuf.
|
||||
// may need to extend to recognize terminal control sequences like below
|
||||
|
||||
std::size_t lpos_on_newline = 0;
|
||||
#endif
|
||||
|
||||
#ifdef OBSOLETE
|
||||
while(p < e) {
|
||||
if(space_after_nonspace) {
|
||||
;
|
||||
|
|
@ -318,8 +324,10 @@ namespace xo {
|
|||
|
||||
in_color_escape = false;
|
||||
|
||||
#ifdef OBSOLETE
|
||||
lpos_on_newline = this->lpos_;
|
||||
this->lpos_ = 0;
|
||||
#endif
|
||||
|
||||
++p;
|
||||
break;
|
||||
|
|
@ -329,6 +337,7 @@ namespace xo {
|
|||
++p;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* p=e or *p=\n */
|
||||
|
||||
|
|
@ -382,9 +391,11 @@ namespace xo {
|
|||
n_indent += std::min(this->nesting_level_ * log_config::indent_width,
|
||||
log_config::max_indent_width);
|
||||
|
||||
#ifdef OBSOLETE // nice try, broken for multiline input + written before log_streambuf calculated lpos
|
||||
/* this is just to indent for per-line entry/exit label */
|
||||
if(space_after_nonspace)
|
||||
n_indent += (space_after_nonspace - s);
|
||||
#endif
|
||||
|
||||
for(std::uint32_t i = 0; i < n_indent; ++i)
|
||||
sbuf2->sputc(' ');
|
||||
|
|
@ -394,6 +405,8 @@ namespace xo {
|
|||
|
||||
/* now write entire contents of *sbuf2 to clog */
|
||||
p_sbuf->sputn(sbuf2->lo(), sbuf2->pos());
|
||||
#endif
|
||||
p_sbuf->sputn(sbuf1->lo(), sbuf1->pos());
|
||||
|
||||
/* reset streams for next message */
|
||||
this->reset_stream();
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "print/quoted_char.hpp"
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <cstring> // e.g. for std::memcpy()
|
||||
|
|
@ -9,13 +10,14 @@
|
|||
#include <cassert>
|
||||
|
||||
namespace xo {
|
||||
/* recycling buffer for logging.
|
||||
* write to self-extending storage array;
|
||||
*/
|
||||
template <typename CharT, typename Traits>
|
||||
/** recycling buffer for logging and pretty-printing
|
||||
* - write to self-extending storage array
|
||||
* - track position relative to start of line
|
||||
**/
|
||||
template <typename CharT, typename Traits = std::char_traits<CharT>>
|
||||
class log_streambuf : public std::streambuf {
|
||||
public:
|
||||
log_streambuf(std::uint32_t buf_z) {
|
||||
log_streambuf(std::uint32_t buf_z, bool debug_flag = false) : debug_flag_{debug_flag} {
|
||||
this->buf_v_.resize(buf_z);
|
||||
this->reset_stream();
|
||||
} /*ctor*/
|
||||
|
|
@ -25,40 +27,93 @@ namespace xo {
|
|||
char const * hi() const { return this->lo() + this->capacity(); }
|
||||
std::uint32_t pos() const { return this->pptr() - this->pbase(); }
|
||||
|
||||
/** number of characters since start of line (last \n or \r) **/
|
||||
std::uint32_t lpos() const { return pos() - solpos_; }
|
||||
|
||||
bool debug_flag() const { return debug_flag_; }
|
||||
|
||||
void reset_stream() {
|
||||
char * p_lo = &(this->buf_v_[0]);
|
||||
char * p_hi = p_lo + this->capacity();
|
||||
|
||||
/* tells parent our buffer extent */
|
||||
this->setp(p_lo, p_hi);
|
||||
|
||||
this->solpos_ = 0;
|
||||
} /*reset_stream*/
|
||||
|
||||
void rewind_to(std::uint32_t p) {
|
||||
/* .setp(): using for side effect: sets .pptr to .pbase */
|
||||
this->setp(this->pbase(), this->epptr());
|
||||
this->pbump(p);
|
||||
}
|
||||
|
||||
protected:
|
||||
/** expand buffer storage (by 2x), preserve current contents **/
|
||||
void
|
||||
expand_to(std::size_t new_z) {
|
||||
char * old_pptr = pptr();
|
||||
std::streamsize old_n = old_pptr - pbase();
|
||||
|
||||
assert(old_n <= static_cast<std::streamsize>(buf_v_.size()));
|
||||
assert(new_z > buf_v_.capacity());
|
||||
|
||||
this->buf_v_.resize(new_z);
|
||||
|
||||
char * p_base = &(this->buf_v_[0]);
|
||||
char * p_hi = p_base + this->buf_v_.capacity();
|
||||
|
||||
this->setp(p_base, p_hi);
|
||||
this->pbump(old_n);
|
||||
} /*expand*/
|
||||
|
||||
virtual std::streamsize
|
||||
xsputn(char const * s, std::streamsize n) override {
|
||||
/* s must be an address in [this->lo() .. this->lo() + capacity()] */
|
||||
|
||||
assert(this->hi() >= this->pptr());
|
||||
assert(hi() >= pptr());
|
||||
|
||||
#ifdef NOT_USING_DEBUG
|
||||
std::cout << "xsputn: pbase=" << (void *)(this->pbase())
|
||||
<< ", pptr=" << (void*)(this->pptr())
|
||||
<< "(+" << (this->pptr() - this->lo()) << ")"
|
||||
<< ", n=" << n << " -> (+" << (this->pptr() + n - this->lo()) << ")"
|
||||
<< ", buf_v.size=" << this->buf_v_.size()
|
||||
<< std::endl;
|
||||
#endif
|
||||
//std::cout << "xsputn: s=" << quot(string_view(s, n)) << ", n=" << n << std::endl;
|
||||
if (pptr() + n > hi()) {
|
||||
this->expand_to(std::max(2 * this->buf_v_.capacity(), std::size_t(this->pos() + n + 1)));
|
||||
}
|
||||
|
||||
if (debug_flag_) {
|
||||
std::cout << "xsputn: pbase=" << (void *)(this->pbase())
|
||||
<< ", pptr=" << (void*)(this->pptr())
|
||||
<< "(+" << (this->pptr() - this->lo()) << ")"
|
||||
<< ", n=" << n << " -> (+" << (this->pptr() + n - this->lo()) << ")"
|
||||
<< ", buf_v.size=" << this->buf_v_.size()
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
std::streamsize ncopied = 0;
|
||||
|
||||
if (this->pptr() + n > this->hi()) {
|
||||
n = this->hi() - this->pptr();
|
||||
std::memcpy(this->pptr(), s, n);
|
||||
ncopied = this->hi() - this->pptr();
|
||||
} else {
|
||||
std::memcpy(this->pptr(), s, n);
|
||||
ncopied = n;
|
||||
}
|
||||
this->pbump(n);
|
||||
|
||||
return n;
|
||||
if (debug_flag_) {
|
||||
std::cout << "xsputn: copying ncopied=" << ncopied << " (/n=" << n << ") bytes into range [lo,hi)"
|
||||
<< ", lo=" << (void*)this->pptr()
|
||||
<< ", hi=" << (void*)(this->pptr() + n)
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
std::memcpy(this->pptr(), s, ncopied);
|
||||
|
||||
/* scan range [pptr, pptr+n] backwards, to account for newline (if any) */
|
||||
for (char const * p_lo = this->pptr(), * p_hi = p_lo + n - 1, * p = p_hi; p >= p_lo; --p) {
|
||||
if (*p == '\n' || *p == '\r') {
|
||||
this->solpos_ = (p+1 - this->pbase());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this->pbump(ncopied);
|
||||
|
||||
return ncopied;
|
||||
} /*xsputn*/
|
||||
|
||||
virtual int_type
|
||||
|
|
@ -69,25 +124,31 @@ namespace xo {
|
|||
|
||||
assert(old_n <= static_cast<std::streamsize>(this->buf_v_.size()));
|
||||
|
||||
std::size_t new_z = 2 * this->buf_v_.size();
|
||||
if (debug_flag_) {
|
||||
std::cout << "overflow: new_ch=" << quoted_char(new_ch) << std::endl;
|
||||
}
|
||||
|
||||
this->expand_to(2 * buf_v_.size());
|
||||
|
||||
this->buf_v_.resize(new_z);
|
||||
this->buf_v_[old_n] = new_ch;
|
||||
this->pbump(1);
|
||||
|
||||
/* 'buffered range' will now be .buf_v[old_n .. new_z] */
|
||||
char * p_base = &(this->buf_v_[0]);
|
||||
//char * p_lo = &(this->buf_v_[old_n+1]);
|
||||
char * p_hi = p_base + this->buf_v_.capacity();
|
||||
if ((new_ch == static_cast<int_type>('\n')) || (new_ch == static_cast<int_type>('\r')))
|
||||
this->solpos_ = this->pos();
|
||||
|
||||
this->setp(p_base, p_hi);
|
||||
this->pbump(old_n + 1); /*see 'this->buf_v_[old_n] - new_ch' above*/
|
||||
|
||||
return new_ch;
|
||||
if (new_ch == Traits::eof()) {
|
||||
/* reminder: returning eof sets badbit on ostream */
|
||||
return Traits::not_eof(new_ch);
|
||||
} else {
|
||||
return new_ch;
|
||||
}
|
||||
} /*overflow*/
|
||||
|
||||
/* off. offset, relative to starting point dir.
|
||||
* dir.
|
||||
* which. in|out|both
|
||||
*
|
||||
* Note that off=0,dir=cur,which=out reads offset
|
||||
*/
|
||||
virtual pos_type seekoff(off_type off,
|
||||
std::ios_base::seekdir dir,
|
||||
|
|
@ -114,8 +175,14 @@ namespace xo {
|
|||
} /*seekoff*/
|
||||
|
||||
private:
|
||||
/* buffered output stored here */
|
||||
/** position (relative to pbase) one character after last \n or \r.
|
||||
* Use to drive @ref lpos
|
||||
**/
|
||||
std::size_t solpos_ = 0;
|
||||
/** buffered output stored here **/
|
||||
std::vector<char> buf_v_;
|
||||
/** true to debug log_streambuf itself **/
|
||||
bool debug_flag_ = false;
|
||||
}; /*log_streambuf*/
|
||||
|
||||
} /*namespace xo*/
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "ppdetail_atomic.hpp"
|
||||
#include <ostream>
|
||||
#include <utility> // for std::move()
|
||||
|
||||
|
|
@ -37,6 +38,25 @@ namespace xo {
|
|||
return os;
|
||||
} /*operator<<*/
|
||||
|
||||
#ifndef ppdetail_atomic
|
||||
namespace print {
|
||||
/* concat expected to be used on short string-like things.
|
||||
* i.e. don't want structure visible to pretty-printer.
|
||||
* could be using it like concat("boeing", 777)
|
||||
*/
|
||||
template <typename T1, typename T2>
|
||||
struct ppdetail<concat_impl<T1,T2>> {
|
||||
using target_type = concat_impl<T1,T2>;
|
||||
|
||||
static bool print_upto(ppstate * pps, const target_type & x) {
|
||||
return ppdetail_atomic<target_type>::print_upto(pps, x);
|
||||
}
|
||||
static void print_pretty(ppstate * pps, const target_type & x) {
|
||||
ppdetail_atomic<target_type>::print_pretty(pps, x);
|
||||
}
|
||||
};
|
||||
}
|
||||
#endif
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end concat.hpp */
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "ppdetail_atomic.hpp"
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
|
||||
|
|
@ -140,6 +141,12 @@ namespace xo {
|
|||
return os;
|
||||
}
|
||||
|
||||
#ifndef ppdetail_atomic
|
||||
namespace print {
|
||||
PPDETAIL_ATOMIC(hex_view);
|
||||
}
|
||||
#endif
|
||||
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end hex.hpp */
|
||||
|
|
|
|||
|
|
@ -38,6 +38,9 @@ namespace xo {
|
|||
inline pad_impl
|
||||
pad(std::uint32_t n, char pad_char = ' ') { return pad_impl(n, pad_char); }
|
||||
|
||||
inline pad_impl
|
||||
spaces(std::uint32_t n) { return pad_impl(n, ' '); }
|
||||
|
||||
inline std::ostream &
|
||||
operator<<(std::ostream & s,
|
||||
pad_impl const & pad)
|
||||
|
|
|
|||
34
xo-indentlog/include/xo/indentlog/print/ppconfig.hpp
Normal file
34
xo-indentlog/include/xo/indentlog/print/ppconfig.hpp
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/* @file ppconfig.hpp
|
||||
*
|
||||
* author: Roland Conybeare, Jul 2025
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xo {
|
||||
namespace print {
|
||||
|
||||
/** @class ppconfig
|
||||
* @brief hold pretty-printer control parameters
|
||||
*
|
||||
* Need one read-only instance of this to invoke pretty printer
|
||||
**/
|
||||
struct ppconfig {
|
||||
/** @defgroup ppconfig-instance-vars ppconfig instance variables **/
|
||||
///@{
|
||||
|
||||
/** max line length.
|
||||
* Pretty-printer will introduce newlines if needed
|
||||
* to stay to the left of this margin
|
||||
**/
|
||||
std::uint32_t right_margin_ = 135;
|
||||
|
||||
/** amount of additional indent per nesting level **/
|
||||
std::uint32_t indent_width_ = 2;
|
||||
|
||||
///@}
|
||||
};
|
||||
} /*namespace print*/
|
||||
} /*namespace xo*/
|
||||
119
xo-indentlog/include/xo/indentlog/print/ppdetail_atomic.hpp
Normal file
119
xo-indentlog/include/xo/indentlog/print/ppdetail_atomic.hpp
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
/* ppdetail_atomic.hpp
|
||||
*
|
||||
* author: Roland Conybeare, Jul 2025
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
|
||||
namespace xo {
|
||||
namespace print {
|
||||
struct ppstate; // see pretty.hpp
|
||||
|
||||
// Defining this means ppdetail_atomic is not used.
|
||||
// In that case where not explicitly specialized ppdetail will revert to ordinary printing for a type,
|
||||
// instead of giving compile-time error about missing template specialization of ppdetail
|
||||
|
||||
#define ppdetail_atomic ppdetail
|
||||
|
||||
/** @class ppdetail
|
||||
* @brief template for opt-in to pretty-printer
|
||||
*
|
||||
* Provide a specialization that covers each type you want participating in pretty-printing.
|
||||
* Default treats T as atomic.
|
||||
*
|
||||
* Expectations for specializers:
|
||||
* - don't print final newline:
|
||||
* - parent container will do that
|
||||
* - want option to put closing ),],>,} on the same line
|
||||
**/
|
||||
template <typename T>
|
||||
struct ppdetail;
|
||||
|
||||
template <typename T>
|
||||
struct ppdetail_atomic;
|
||||
|
||||
template <typename T>
|
||||
struct ppdetail_atomic {
|
||||
/** 1. print @p x to private stream @ref scratch_ss_
|
||||
* 2. return true iff N = number of characters printed satisfies N <= @p budget.
|
||||
* content actually printed to *sbuf may be used as-is in this case
|
||||
* 3. return false otherwise. Will trigger non-degenerate pretty-printing.
|
||||
* 4. in any case consume some of @ref scratch_sbuf_
|
||||
**/
|
||||
static bool print_upto(ppstate * pps, const T & x);
|
||||
/** print @p x to private stream @ref scratch_ss_ **/
|
||||
static void print_pretty(ppstate * pps, const T & x);
|
||||
};
|
||||
|
||||
#ifndef ppdetail_atomic
|
||||
template <int N>
|
||||
struct ppdetail<const char[N]> {
|
||||
using target_type = const char[N];
|
||||
|
||||
static bool print_upto(ppstate * pps, const target_type & x) {
|
||||
return ppdetail_atomic<target_type>::print_upto(pps, x);
|
||||
}
|
||||
static void print_pretty(ppstate * pps, const target_type & x) {
|
||||
ppdetail_atomic<target_type>::print_pretty(pps, x);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ppdetail<const char *> {
|
||||
static bool print_upto(ppstate * pps, const char * x) {
|
||||
return ppdetail_atomic<const char *>::print_upto(pps, x);
|
||||
}
|
||||
static void print_pretty(ppstate * pps, const char * x) {
|
||||
ppdetail_atomic<const char *>::print_pretty(pps, x);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
#define PPDETAIL_ATOMIC_BODY(target_type) \
|
||||
struct ppdetail<target_type> { \
|
||||
static bool print_upto(ppstate * pps, const target_type & x) { \
|
||||
return ppdetail_atomic<target_type>::print_upto(pps, x); \
|
||||
} \
|
||||
\
|
||||
static void print_pretty(ppstate * pps, const target_type & x) { \
|
||||
ppdetail_atomic<target_type>::print_pretty(pps, x); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define PPDETAIL_ATOMIC_BODY_CONST(target_type) \
|
||||
struct ppdetail<const target_type> { \
|
||||
static bool print_upto(ppstate * pps, const target_type & x) { \
|
||||
return ppdetail_atomic<target_type>::print_upto(pps, x); \
|
||||
} \
|
||||
\
|
||||
static void print_pretty(ppstate * pps, const target_type & x) { \
|
||||
ppdetail_atomic<target_type>::print_pretty(pps, x); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define PPDETAIL_ATOMIC(target_type) \
|
||||
template<> \
|
||||
PPDETAIL_ATOMIC_BODY(target_type)
|
||||
|
||||
#define PPDETAIL_ATOMIC_CONST(target_type) \
|
||||
template<> \
|
||||
PPDETAIL_ATOMIC_BODY_CONST(target_type)
|
||||
|
||||
PPDETAIL_ATOMIC(bool);
|
||||
PPDETAIL_ATOMIC(char);
|
||||
// PPDETAIL_ATOMIC_CONST(char);
|
||||
PPDETAIL_ATOMIC(std::int64_t);
|
||||
PPDETAIL_ATOMIC(std::uint64_t);
|
||||
PPDETAIL_ATOMIC(std::int32_t);
|
||||
PPDETAIL_ATOMIC(std::uint32_t);
|
||||
PPDETAIL_ATOMIC(std::string);
|
||||
|
||||
using voidptr = void*;
|
||||
PPDETAIL_ATOMIC(voidptr);
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
190
xo-indentlog/include/xo/indentlog/print/ppstr.hpp
Normal file
190
xo-indentlog/include/xo/indentlog/print/ppstr.hpp
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
/* file ppstr.hpp
|
||||
*
|
||||
* author: Roland Conybeare, Jul 2025
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "xo/indentlog/print/pretty.hpp"
|
||||
#include "xo/indentlog/print/pretty_vector.hpp"
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace xo {
|
||||
|
||||
/** @class ppconcat
|
||||
* @brief Container for a tuple of values to be pretty-printed in order
|
||||
* @tparam Args... parameter pack of argument types
|
||||
*
|
||||
* We don't use the tuple directly, so that we don't inadvertently
|
||||
* usurp pretty-printing behavior for all tuples.
|
||||
*
|
||||
**/
|
||||
template <typename... Args>
|
||||
struct ppconcat {
|
||||
/** tuple to be pretty-printed. See toppstr(), toppstr2() **/
|
||||
std::tuple<Args...> contents_;
|
||||
};
|
||||
|
||||
namespace print {
|
||||
namespace detail {
|
||||
inline bool
|
||||
ppconcat_printupto_aux(print::ppstate *) {
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool
|
||||
ppconcat_printupto_aux(print::ppstate * pps, T && x) {
|
||||
if (!pps->print_upto(x))
|
||||
return false;
|
||||
if (!pps->has_margin())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T, typename... Tn>
|
||||
bool
|
||||
ppconcat_printupto_aux(print::ppstate * pps, T && x, Tn && ...rest) {
|
||||
if (!pps->print_upto(x))
|
||||
return false;
|
||||
if (!pps->has_margin())
|
||||
return false;
|
||||
return ppconcat_printupto_aux(pps, rest...);
|
||||
}
|
||||
|
||||
inline void
|
||||
ppconcat_print_pretty_rest(print::ppstate *, std::uint32_t) {}
|
||||
|
||||
template <typename T>
|
||||
void
|
||||
ppconcat_print_pretty_rest(print::ppstate * pps, std::uint32_t ci1, T && x) {
|
||||
pps->newline_indent(ci1);
|
||||
pps->pretty(x);
|
||||
}
|
||||
|
||||
template <typename T, typename... Tn>
|
||||
void
|
||||
ppconcat_print_pretty_rest(print::ppstate * pps, std::uint32_t ci1, T && x, Tn && ...rest) {
|
||||
pps->newline_indent(ci1);
|
||||
pps->pretty(x);
|
||||
|
||||
ppconcat_print_pretty_rest(pps, ci1, rest...);
|
||||
}
|
||||
|
||||
inline void
|
||||
ppconcat_print_pretty_aux(print::ppstate *, std::uint32_t /*ci1*/) {}
|
||||
|
||||
template <typename T, typename... Tn>
|
||||
void
|
||||
ppconcat_print_pretty_aux(print::ppstate * pps, std::uint32_t ci1, T && x, Tn && ...rest) {
|
||||
// first item doesn't get extra indent. Want pretty output like:
|
||||
//
|
||||
// pretty(first)
|
||||
// pretty(second)
|
||||
// pretty(third)
|
||||
// ..
|
||||
//
|
||||
pps->pretty(x);
|
||||
ppconcat_print_pretty_rest(pps, ci1, rest...);
|
||||
}
|
||||
}
|
||||
|
||||
/** implement pretty-printing for template @c ppconcat r**/
|
||||
template <typename... Args>
|
||||
struct ppdetail<ppconcat<Args...>> {
|
||||
/** try to print @p target on one line.
|
||||
* return false if budget (space until right margin) exhausted
|
||||
* or if an embedded newline appears
|
||||
*
|
||||
* @return true on success, otherwise false.
|
||||
**/
|
||||
static bool print_upto(ppstate * pps, ppconcat<Args...> target) {
|
||||
std::uint32_t saved = pps->pos();
|
||||
|
||||
if (!pps->has_margin())
|
||||
return false;
|
||||
|
||||
if (std::apply(
|
||||
[pps](auto &&... args) {
|
||||
return detail::ppconcat_printupto_aux(pps, std::forward<decltype(args)>(args)...);
|
||||
},
|
||||
std::forward<std::tuple<Args...>>(target.contents_)
|
||||
) == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return pps->scan_no_newline(saved);
|
||||
}
|
||||
|
||||
/** pretty-print @p target using multiple lines
|
||||
**/
|
||||
static void print_pretty(ppstate * pps, ppconcat<Args...> target) {
|
||||
std::uint32_t ci0 = pps->lpos();
|
||||
std::uint32_t ci1 = ci0 + pps->indent_width();
|
||||
|
||||
std::apply(
|
||||
[pps, ci1](auto &&... args) {
|
||||
detail::ppconcat_print_pretty_aux(pps, ci1,
|
||||
std::forward<decltype(args)>(args)...);
|
||||
},
|
||||
std::forward<std::tuple<Args...>>(target.contents_)
|
||||
);
|
||||
}
|
||||
};
|
||||
} /*namespace print*/
|
||||
|
||||
/** @return string comprised of pretty-printed sequence of values in @p args
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* toppstr("hello") -> "hello"
|
||||
* toppstr("string1", .., "string99")
|
||||
* -> "string1
|
||||
* string2
|
||||
* ...
|
||||
* string99"
|
||||
* @endcode
|
||||
**/
|
||||
template <typename... Tn>
|
||||
std::string toppstr(Tn&&... args) {
|
||||
print::ppconfig ppc;
|
||||
|
||||
std::stringstream ss;
|
||||
print::ppstate_standalone pps(&ss, 0, &ppc);
|
||||
|
||||
// - std::decay_t<T> remove reference and cv-qualifiers
|
||||
// - brace initialization of ppconcat and its contained tuple
|
||||
pps.pretty(ppconcat<std::decay_t<Tn>...>{{std::forward<Tn>(args)...}});
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
/** like @ref toppstr, but use pretty-printing configuration @p ppc
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* ppconfig ppc{.right_margin_=20, .indent_width_=4};
|
||||
* toppstr2("string1", "string2", "string3") ->
|
||||
* "string1
|
||||
* string2
|
||||
* string3"
|
||||
* @endcode
|
||||
**/
|
||||
template <typename... Tn>
|
||||
std::string toppstr2(const print::ppconfig& ppc, Tn&&... args) {
|
||||
std::stringstream ss;
|
||||
print::ppstate_standalone pps(&ss, 0, &ppc);
|
||||
|
||||
// - std::decay_t<T> remove reference and cv-qualifiers
|
||||
// - brace initialization of ppconcat and its contained tuple
|
||||
pps.pretty(ppconcat<std::decay_t<Tn>...>{{std::forward<Tn>(args)...}});
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end ppstr.hpp */
|
||||
275
xo-indentlog/include/xo/indentlog/print/pretty.hpp
Normal file
275
xo-indentlog/include/xo/indentlog/print/pretty.hpp
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
/* file pretty.hpp
|
||||
*
|
||||
* author: Roland Conybeare, Jul 2025
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "xo/indentlog/print/ppconfig.hpp"
|
||||
#include "xo/indentlog/log_streambuf.hpp"
|
||||
#include "ppdetail_atomic.hpp"
|
||||
#include "pad.hpp"
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include <cstdint>
|
||||
|
||||
namespace xo {
|
||||
namespace print {
|
||||
|
||||
/** @class ppstate
|
||||
* @brief hold pretty-printer state
|
||||
*
|
||||
* Use:
|
||||
*
|
||||
* ppconfig ppc;
|
||||
* ppstate pps(&cout, 0, &ppc);
|
||||
*
|
||||
* pps.pretty("first");
|
||||
* pps.pretty("second");
|
||||
**/
|
||||
struct ppstate {
|
||||
using streambuf_type = log_streambuf<char, std::char_traits<char>>;
|
||||
|
||||
explicit ppstate(std::uint32_t ci, const ppconfig * config, std::ostream * scratch_ss, streambuf_type * scratch_sbuf)
|
||||
: scratch_sbuf_{scratch_sbuf}, scratch_ss_{scratch_ss},
|
||||
current_indent_{ci}, config_{config}
|
||||
{}
|
||||
|
||||
uint32_t indent_width() const { return config_->indent_width_; }
|
||||
|
||||
std::uint32_t pos() const { return scratch_sbuf_->pos(); }
|
||||
std::uint32_t lpos() const { return scratch_sbuf_->lpos(); }
|
||||
|
||||
/** space available from current position until @c ppconfig.right_margin_
|
||||
* 0 if current position is already beyond right margin
|
||||
**/
|
||||
std::uint32_t avail_margin() const {
|
||||
std::uint32_t p = lpos();
|
||||
std::uint32_t m = config_->right_margin_;
|
||||
|
||||
return (p < m) ? (m - p) : 0;
|
||||
}
|
||||
|
||||
bool has_margin() const { return avail_margin() > 0; }
|
||||
|
||||
/** true if at least @p budget chars of space available
|
||||
* before reaching right margin @c ppconfig.right_margin_
|
||||
**/
|
||||
bool has_budget(std::uint32_t budget) {
|
||||
return avail_margin() >= budget;
|
||||
}
|
||||
|
||||
/** true if no newlines in range [@p lo, pos),
|
||||
* where pos is current position
|
||||
**/
|
||||
bool scan_no_newline(std::uint32_t start) const {
|
||||
char const * p = scratch_sbuf_->lo() + start;
|
||||
char const * e = scratch_sbuf_->lo() + pos();
|
||||
|
||||
for (; p < e; ++p) {
|
||||
if (*p == '\n')
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void indent(std::uint32_t tab) {
|
||||
(*scratch_ss_) << spaces(tab);
|
||||
}
|
||||
|
||||
void newline_indent(std::uint32_t tab) {
|
||||
(*scratch_ss_) << "\n";
|
||||
this->indent(tab);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void write(T && x) {
|
||||
(*scratch_ss_) << x;
|
||||
}
|
||||
|
||||
uint32_t incr_nesting_level() { return ++nesting_level_; }
|
||||
uint32_t decr_nesting_level() { return --nesting_level_; }
|
||||
|
||||
virtual void commit() {}
|
||||
|
||||
template <typename T>
|
||||
bool print_upto(T && x);
|
||||
|
||||
template <typename T>
|
||||
ppstate& pretty(T && x);
|
||||
|
||||
/** like pretty(x), but follow with trailing \n **/
|
||||
template <typename T>
|
||||
ppstate& prettyn(T && x);
|
||||
|
||||
private:
|
||||
/** scratch space for pretty-printed output.
|
||||
* Shared across nesting pretty calls.
|
||||
* Caller responsible for saving/restoring state.
|
||||
**/
|
||||
streambuf_type * scratch_sbuf_ = nullptr;
|
||||
|
||||
/** ostream pointing to @ref scratch_sbuf_ **/
|
||||
std::ostream * scratch_ss_ = nullptr;
|
||||
|
||||
/** level of reentrant pretty calls.
|
||||
* Collect formatted text in @ref scratch_sbuf_
|
||||
* until end of toplevel @ref pretty call
|
||||
**/
|
||||
std::uint32_t nesting_level_ = 0;
|
||||
|
||||
/** current desired indent from left margin **/
|
||||
std::uint32_t current_indent_ = 0;
|
||||
|
||||
/** readonly pretty-printer config **/
|
||||
const ppconfig * config_ = nullptr;
|
||||
};
|
||||
|
||||
/** @class ppstate_standalone
|
||||
* @brief like ppstate, but also holds streambuf
|
||||
*/
|
||||
struct ppstate_standalone : public ppstate {
|
||||
explicit ppstate_standalone(std::ostream * os, std::uint32_t ci, const ppconfig * config)
|
||||
: ppstate(ci, config, &scratch_ss_, &scratch_sbuf_), output_{os}, scratch_sbuf_{1024*1024}
|
||||
{}
|
||||
|
||||
virtual void commit() override {
|
||||
output_->write(scratch_sbuf_.lo(), scratch_sbuf_.pos());
|
||||
reset_scratch_sbuf();
|
||||
}
|
||||
|
||||
void reset_scratch_sbuf() {
|
||||
/* reset scratch_sbuf for next time */
|
||||
scratch_sbuf_.pubseekoff(0, std::ios_base::beg, std::ios_base::out);
|
||||
}
|
||||
|
||||
/** send final output to this stream **/
|
||||
std::ostream * output_ = nullptr;
|
||||
|
||||
/** streambuf (with newline tracking) **/
|
||||
streambuf_type scratch_sbuf_;
|
||||
|
||||
/** always attached to @ref scratch_sbuf_ **/
|
||||
std::ostream scratch_ss_{&scratch_sbuf_};
|
||||
};
|
||||
|
||||
struct ppcommitter {
|
||||
ppcommitter(ppstate * pps) : pps_{pps} {
|
||||
pps_->incr_nesting_level();
|
||||
}
|
||||
~ppcommitter() {
|
||||
check_commit();
|
||||
}
|
||||
|
||||
void check_commit() {
|
||||
if (pps_) {
|
||||
ppstate * pps = pps_;
|
||||
pps_ = nullptr;
|
||||
if (pps->decr_nesting_level() == 0)
|
||||
pps->commit();
|
||||
}
|
||||
}
|
||||
|
||||
ppstate * pps_ = nullptr;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
bool
|
||||
ppdetail_atomic<T>::print_upto(ppstate * pps, const T & x)
|
||||
{
|
||||
/* position before we write */
|
||||
if (!pps->has_margin())
|
||||
return false;
|
||||
|
||||
std::uint32_t start = pps->pos();
|
||||
|
||||
/* if x is non-atomic may optimize by checking budget more often */
|
||||
pps->write(x);
|
||||
|
||||
if (!pps->has_margin())
|
||||
return false;
|
||||
|
||||
/* scan characters [lo, hi) in line buffer.
|
||||
* Newline -> caller should use print_pretty()
|
||||
*/
|
||||
return (pps->scan_no_newline(start));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void
|
||||
ppdetail_atomic<T>::print_pretty(ppstate * pps, const T & x)
|
||||
{
|
||||
/* In default implementation we don't know where to introduce newlines.
|
||||
* Still need to calculate lpos though
|
||||
*/
|
||||
pps->write(x);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool
|
||||
ppstate::print_upto(T && value) {
|
||||
return ppdetail<std::decay_t<T>>::print_upto(this, value);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
ppstate &
|
||||
ppstate::pretty(T && value) {
|
||||
/* just because value fits doesn't mean we should print it.
|
||||
* Decision depends on ancestor context
|
||||
*/
|
||||
|
||||
std::uint32_t saved = pos();
|
||||
|
||||
ppcommitter ppc(this);
|
||||
|
||||
/* print_upto() modifies @ref scratch_sbuf_ (and @ref scratch_ss_) */
|
||||
bool fits = ppdetail<std::decay_t<T>>::print_upto(this, static_cast<const T &>(value));
|
||||
|
||||
if (fits) {
|
||||
/* fits on 1 line -> pretty-printing maybe not required */
|
||||
return *this;
|
||||
}
|
||||
|
||||
/* here: didn't fit -> split over multiple lines */
|
||||
|
||||
this->scratch_sbuf_->rewind_to(saved);
|
||||
ppdetail<std::decay_t<T>>::print_pretty(this, value);
|
||||
ppc.check_commit();
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
ppstate &
|
||||
ppstate::prettyn(T && value) {
|
||||
/* just because value fits doesn't mean we should print it.
|
||||
* Decision depends on ancestor context
|
||||
*/
|
||||
|
||||
std::uint32_t saved = pos();
|
||||
|
||||
ppcommitter ppc(this);
|
||||
|
||||
/* print_upto() modifies @ref scratch_sbuf_ (and @ref scratch_ss_) */
|
||||
bool fits = ppdetail<std::decay_t<T>>::print_upto(this, value);
|
||||
|
||||
if (fits) {
|
||||
this->newline_indent(0);
|
||||
/* fits on 1 line -> pretty-printing maybe not required */
|
||||
return *this;
|
||||
}
|
||||
|
||||
/* here: didn't fit -> split over multiple lines */
|
||||
|
||||
this->scratch_sbuf_->rewind_to(saved);
|
||||
ppdetail<std::remove_reference_t<T>>::print_pretty(this, value);
|
||||
this->newline_indent(0);
|
||||
ppc.check_commit();
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
} /*namespace print*/
|
||||
} /*namespace xo*/
|
||||
14
xo-indentlog/include/xo/indentlog/print/pretty_concat.hpp
Normal file
14
xo-indentlog/include/xo/indentlog/print/pretty_concat.hpp
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
/* @file pretty_concat.hpp
|
||||
*
|
||||
* author: Roland Conybeare, Jul 2025
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "concat.hpp"
|
||||
#include "pretty.hpp"
|
||||
|
||||
namespace xo {
|
||||
}
|
||||
|
||||
/* end pretty_concat.hpp */
|
||||
56
xo-indentlog/include/xo/indentlog/print/pretty_tag.hpp
Normal file
56
xo-indentlog/include/xo/indentlog/print/pretty_tag.hpp
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
/* file pretty_tag.hpp
|
||||
*
|
||||
* author: Roland Conybeare, Jul 2025
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "pretty.hpp"
|
||||
#include "tag.hpp"
|
||||
|
||||
namespace xo {
|
||||
namespace print {
|
||||
template <bool PrefixSpace, tagstyle TagStyle, typename Name, typename Value>
|
||||
struct ppdetail<tag_impl<PrefixSpace, TagStyle, Name, Value>> {
|
||||
static bool print_upto(ppstate * pps, const tag_impl<PrefixSpace, TagStyle, Name, Value> & tag) {
|
||||
std::uint32_t saved = pps->pos();
|
||||
if (!pps->has_margin())
|
||||
return false;
|
||||
|
||||
if (PrefixSpace)
|
||||
pps->write(" ");
|
||||
|
||||
/* skil colorizing here, since doesn't consume visible space */
|
||||
if (!pps->print_upto(concat((char const *)":", concat(tag.name(), (char const *)" "))))
|
||||
return false;
|
||||
|
||||
if (!pps->print_upto(quot_impl(TagStyle == tagstyle::autoescape, tag.value())))
|
||||
return false;
|
||||
|
||||
return pps->scan_no_newline(saved);
|
||||
}
|
||||
|
||||
static void print_pretty(ppstate * pps, const tag_impl<PrefixSpace, TagStyle, Name, Value> & tag) {
|
||||
/* pretty-print like
|
||||
* :somename
|
||||
* pretty(value)
|
||||
*/
|
||||
std::uint32_t ci0 = pps->lpos();
|
||||
std::uint32_t ci1 = ci0 + pps->indent_width();
|
||||
|
||||
if (PrefixSpace)
|
||||
pps->write(" ");
|
||||
pps->write(with_color(tag_config::tag_color,
|
||||
concat((char const *)":", tag.name())));
|
||||
|
||||
pps->newline_indent(ci1);
|
||||
pps->pretty(tag.value());
|
||||
|
||||
pps->newline_indent(ci0);
|
||||
}
|
||||
};
|
||||
|
||||
} /*namespace print*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end pretty_tag.hpp */
|
||||
67
xo-indentlog/include/xo/indentlog/print/pretty_vector.hpp
Normal file
67
xo-indentlog/include/xo/indentlog/print/pretty_vector.hpp
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
/* file pretty_vector.hpp
|
||||
*
|
||||
* author: Roland Conybeare, Jul 2025
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "pretty.hpp"
|
||||
#include "pad.hpp"
|
||||
#include <vector>
|
||||
|
||||
namespace xo {
|
||||
namespace print {
|
||||
template <typename T>
|
||||
struct ppdetail<std::vector<T>> {
|
||||
static bool print_upto(ppstate * pps, const std::vector<T> & x) {
|
||||
std::uint32_t saved = pps->pos();
|
||||
if (!pps->has_margin())
|
||||
return false;
|
||||
|
||||
pps->write("[");
|
||||
for (size_t i = 0, z = x.size(); i < z; ++i) {
|
||||
if (i > 0)
|
||||
pps->write(", ");
|
||||
|
||||
if (!pps->print_upto(x[i]))
|
||||
return false;
|
||||
if (!pps->has_margin())
|
||||
return false;
|
||||
}
|
||||
pps->write("]");
|
||||
if (!pps->has_margin())
|
||||
return false;
|
||||
|
||||
return pps->scan_no_newline(saved);
|
||||
}
|
||||
|
||||
static void print_pretty(ppstate * pps, const std::vector<T> & x) {
|
||||
std::uint32_t ci0 = pps->lpos();
|
||||
|
||||
pps->write('[');
|
||||
|
||||
std::uint32_t ci1 = ci0 + pps->indent_width();
|
||||
for (size_t i = 0, z = x.size(); i < z; ++i) {
|
||||
pps->newline_indent(ci1);
|
||||
pps->pretty(x[i]);
|
||||
|
||||
if (i+1 < z)
|
||||
pps->write(',');
|
||||
}
|
||||
|
||||
pps->newline_indent(ci0);
|
||||
pps->write(']');
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct ppdetail<const std::vector<T>> {
|
||||
static bool print_upto(ppstate * pps, const std::vector<T> & x) {
|
||||
return ppdetail<std::vector<T>>::print_upto(pps, x);
|
||||
}
|
||||
static void print_pretty(ppstate * pps, const std::vector<T> & x) {
|
||||
ppdetail<std::vector<T>>::print_pretty(pps, x);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "ppdetail_atomic.hpp"
|
||||
#include "tostr.hpp"
|
||||
#include <sstream>
|
||||
#include <string_view>
|
||||
|
|
@ -147,6 +148,11 @@ namespace xo {
|
|||
auto unq(T && x) {
|
||||
return quot_impl(true /*unq_flag*/, std::forward<T>(x));
|
||||
}
|
||||
|
||||
#ifndef ppdetail_atomic
|
||||
template <typename T>
|
||||
PPDETAIL_ATOMIC_BODY(quot_impl<T>);
|
||||
#endif
|
||||
} /*namespace print*/
|
||||
} /*namespace xo*/
|
||||
|
||||
|
|
|
|||
|
|
@ -18,18 +18,45 @@
|
|||
#define TAG2(x, y) xo::make_tag(x, y)
|
||||
|
||||
#define XTAG(x) xo::xtag(STRINGIFY(x), x)
|
||||
//#define XTAG2(x, y) xo::xtag(x, y)
|
||||
|
||||
namespace xo {
|
||||
// associate a name with a value
|
||||
//
|
||||
// will print like
|
||||
// :name value
|
||||
//
|
||||
// NOTE: will search for operator<< overloads in the logutil
|
||||
// namespace
|
||||
//*/
|
||||
template <bool PrefixSpace, typename Name, typename Value>
|
||||
enum class tagstyle {
|
||||
/** print with automatic escapes for embedded special characters
|
||||
* (any of ' ','"','\','\n','\r','\t').
|
||||
* print inside double-quotes if an escape is required.
|
||||
**/
|
||||
autoescape,
|
||||
/** print literally. caller responsible for machine-readability **/
|
||||
raw,
|
||||
};
|
||||
|
||||
/** K,V pair for printing.
|
||||
*
|
||||
* @tparam PrefixSpace if true print one space before :K
|
||||
* @tparam TagStyle controls printing format
|
||||
* @tparam Name typename for key K.
|
||||
* @tparam Value typename for value V.
|
||||
*
|
||||
* 1. Uses escapes to preserve machine-readability of V.
|
||||
* 2. Optionally colors K for readability.
|
||||
* Can disable with
|
||||
* @code
|
||||
* xo::tag_config::tag_color = xo::color_spec_type::none()
|
||||
* @endcode
|
||||
*
|
||||
* Printing styles
|
||||
* @pre
|
||||
* :name value // unadorned
|
||||
* :name "value needing\nescapes" // with escapes
|
||||
* :name <foo :nested 1 :param 2> // autoescape disabled
|
||||
* @endpre
|
||||
*
|
||||
* NOTE: will search for operator<< overloads in the xo
|
||||
* namespace
|
||||
*
|
||||
*
|
||||
**/
|
||||
template <bool PrefixSpace, tagstyle TagStyle, typename Name, typename Value>
|
||||
struct tag_impl {
|
||||
tag_impl(Name const & n, Value const & v)
|
||||
: name_{n}, value_{v} {}
|
||||
|
|
@ -42,71 +69,58 @@ namespace xo {
|
|||
private:
|
||||
Name name_;
|
||||
Value value_;
|
||||
}; /*tag_impl*/
|
||||
};
|
||||
|
||||
/* deduce tag template-type from arguments */
|
||||
template<typename Name, typename Value>
|
||||
tag_impl<false, Name, Value>
|
||||
make_tag(Name && n, Value && v)
|
||||
{
|
||||
return tag_impl<false, Name, Value>(n, v);
|
||||
} /*make_tag*/
|
||||
|
||||
template<typename Value>
|
||||
tag_impl<false, char const *, Value>
|
||||
make_tag(char const * n, Value && v) {
|
||||
return tag_impl<false, char const *, Value>(n, v);
|
||||
} /*make_tag*/
|
||||
// ----- xtag -----
|
||||
|
||||
template<typename Name, typename Value>
|
||||
tag_impl<true, Name, Value>
|
||||
auto //tag_impl<true, tagstyle::autoescape, Name, std::decay_t<Value>>
|
||||
xtag(Name && n, Value && v)
|
||||
{
|
||||
return tag_impl<true, Name, Value>(n, v);
|
||||
return tag_impl<true, tagstyle::autoescape, Name, std::decay_t<Value>>(n, v);
|
||||
} /*xtag*/
|
||||
|
||||
template<typename Value>
|
||||
tag_impl<true, char const *, Value>
|
||||
auto //tag_impl<true, tagstyle::autoescape, char const *, std::decay_t<Value>>
|
||||
xtag(char const * n, Value && v) {
|
||||
return tag_impl<true, char const *, Value>(n, v);
|
||||
return tag_impl<true, tagstyle::autoescape, char const *, std::decay_t<Value>>(n, v);
|
||||
} /*xtag*/
|
||||
|
||||
inline
|
||||
tag_impl<true, char const *, char const *>
|
||||
xtag_pre(char const * n) {
|
||||
return tag_impl<true, char const *, char const *>(n, "");
|
||||
} /*xtag_pre*/
|
||||
|
||||
// ----- tag -----
|
||||
|
||||
template<typename Name, typename Value>
|
||||
tag_impl<false, Name, Value>
|
||||
tag_impl<false, tagstyle::autoescape, Name, Value>
|
||||
tag(Name && n, Value && v)
|
||||
{
|
||||
return tag_impl<false, Name, Value>(n, v);
|
||||
} /*tag*/
|
||||
return tag_impl<false, tagstyle::autoescape, Name, Value>(n, v);
|
||||
}
|
||||
|
||||
template<typename Value>
|
||||
tag_impl<false, char const *, Value>
|
||||
tag_impl<false, tagstyle::autoescape, char const *, Value>
|
||||
tag(char const * n, Value && v)
|
||||
{
|
||||
return tag_impl<false, char const *, Value>(n, v);
|
||||
} /*tag*/
|
||||
return tag_impl<false, tagstyle::autoescape, char const *, Value>(n, v);
|
||||
}
|
||||
|
||||
// ----- operator<< on tag_impl -----
|
||||
|
||||
template <bool PrefixSpace, typename Name, typename Value>
|
||||
template <bool PrefixSpace, tagstyle TagStyle, typename Name, typename Value>
|
||||
inline std::ostream &
|
||||
operator<<(std::ostream &s,
|
||||
tag_impl<PrefixSpace, Name, Value> const & tag)
|
||||
tag_impl<PrefixSpace, TagStyle, Name, Value> const & tag)
|
||||
{
|
||||
using xo::print::unq;
|
||||
|
||||
if(PrefixSpace)
|
||||
if (PrefixSpace)
|
||||
s << " ";
|
||||
|
||||
s << with_color(tag_config::tag_color, concat((char const *)":", tag.name()))
|
||||
<< " " << unq(tag.value());
|
||||
<< " ";
|
||||
|
||||
if (TagStyle == tagstyle::autoescape)
|
||||
s << unq(tag.value());
|
||||
else
|
||||
s << tag.value();
|
||||
|
||||
return s;
|
||||
} /*operator<<*/
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ namespace xo {
|
|||
*/
|
||||
|
||||
/* no-op terminal case */
|
||||
template<class Stream>
|
||||
template <class Stream>
|
||||
Stream & tos(Stream & s) {
|
||||
return s;
|
||||
}
|
||||
|
|
@ -70,14 +70,14 @@ namespace xo {
|
|||
// is the same as
|
||||
// s << a << b << c;
|
||||
//
|
||||
template<class Stream, typename T>
|
||||
template <class Stream, typename T>
|
||||
Stream & tos(Stream & s, T && x) {
|
||||
s << x;
|
||||
return s;
|
||||
} /*tos*/
|
||||
|
||||
template <class Stream, typename T, typename... Tn>
|
||||
Stream &tos(Stream &s, T &&x, Tn &&...rest) {
|
||||
Stream &tos(Stream & s, T && x, Tn && ...rest) {
|
||||
s << x;
|
||||
return tos(s, rest...);
|
||||
} /*tos*/
|
||||
|
|
|
|||
|
|
@ -7,12 +7,13 @@
|
|||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
namespace std {
|
||||
template<typename T>
|
||||
inline std::ostream &
|
||||
operator<<(std::ostream & os,
|
||||
std::vector<T> const & v)
|
||||
const std::vector<T> & v)
|
||||
{
|
||||
os << "[";
|
||||
for(size_t i=0, z=v.size(); i<z; ++i) {
|
||||
|
|
|
|||
|
|
@ -3,9 +3,12 @@
|
|||
#pragma once
|
||||
|
||||
#include "log_state.hpp"
|
||||
#include "print/ppconfig.hpp"
|
||||
#include "print/filename.hpp"
|
||||
#include "print/ppstr.hpp"
|
||||
#include "print/tostr.hpp"
|
||||
#include "print/tag.hpp"
|
||||
#include "print/pretty_concat.hpp"
|
||||
#include "print/pretty_tag.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <cstdint>
|
||||
|
|
@ -31,15 +34,18 @@ namespace xo {
|
|||
/* throw exception if condition not met*/
|
||||
# define XO_EXPECT(f,msg) if(!(f)) { throw std::runtime_error(msg); }
|
||||
/* establish scope using current function name */
|
||||
# define XO_SCOPE(name, lvl) xo::scope name(xo::scope_setup(xo::log_level::lvl, xo::log_config::style, __PRETTY_FUNCTION__, __FILE__, __LINE__))
|
||||
# define XO_SCOPE(varname, lvl) xo::scope varname(xo::scope_setup(xo::log_level::lvl, xo::log_config::style, __PRETTY_FUNCTION__, __FILE__, __LINE__))
|
||||
/* like XO_SCOPE(name), but also set enabled flag */
|
||||
//# define XO_SCOPE2(name, debug_flag) xo::scope name(xo::scope_setup(xo::log_config::style, __PRETTY_FUNCTION__, __FILE__, __LINE__, debug_flag))
|
||||
# define XO_SCOPE_DISABLED(name) xo::scope name(xo::scope_setup(xo::log_level::never, xo::log_config::style, __PRETTY_FUNCTION__, __FILE__, __LINE__))
|
||||
# define XO_STUB() { XO_SCOPE(logr); logr.log("STUB"); }
|
||||
|
||||
/* convenience class for basic_scope<..> construction (see below).
|
||||
* use to disambiguate setup from other arguments
|
||||
*/
|
||||
/** @class scope_setup
|
||||
* @brief Collect code-location information.
|
||||
*
|
||||
* Typically used with logging macros like @ref XO_SCOPE
|
||||
* Application code isn't expected to interact with this class directly
|
||||
**/
|
||||
struct scope_setup {
|
||||
scope_setup(log_level level, function_style style, std::string_view name1, std::string_view name2,
|
||||
std::string_view file, std::uint32_t line)
|
||||
|
|
@ -53,21 +59,33 @@ namespace xo {
|
|||
//static scope_setup literal(std::string_view name1, bool enabled_flag = true) { return scope_setup(FS_Literal, name1, enabled_flag); }
|
||||
//static scope_setup literal(std::string_view name1, std::string_view name2, bool enabled_flag = true) { return scope_setup(FS_Literal, name1, name2, enabled_flag); }
|
||||
|
||||
/* threshold level for logging -- write messages with severity >= this level */
|
||||
/** @defgroup scope_setup-instance-vars scope_setup instance variables **/
|
||||
///@{
|
||||
|
||||
/** minimum severity level for logging -- write messages with severity >= this level **/
|
||||
log_level log_level_ = log_level::error;
|
||||
/* FS_Pretty | FS_Streamlined | FS_Simple */
|
||||
/** @c FS_Pretty | @c FS_Streamlined | @c FS_Simple **/
|
||||
function_style style_ = function_style::pretty;
|
||||
/** name extracted from left-hand side of symbol split (e.g. foo in method foo::bar) **/
|
||||
std::string_view name1_ = "<.name1>";
|
||||
/** name extracted from right-hand side of symbol split (e.g. bar in method foo::bar) **/
|
||||
std::string_view name2_ = "<.name2>";
|
||||
/* __FILE__ */
|
||||
/** captured value of `__FILE__` **/
|
||||
std::string_view file_ = "<.file>";
|
||||
/* __LINE__ */
|
||||
/** captured value of `__LINE__` **/
|
||||
std::uint32_t line_ = 0;
|
||||
|
||||
///@}
|
||||
}; /*scope_setup*/
|
||||
|
||||
/* nesting logger
|
||||
/** @class basic_scope
|
||||
* @brief Track nesting level across participating function calls use to drive indentation.
|
||||
*
|
||||
* Use:
|
||||
* @tparam CharT character representation type. Usually @c char
|
||||
* @tparam Traits character traits, usually @c std::char_traits<ChartT>
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* using xo::scope;
|
||||
*
|
||||
* void myfunc() {
|
||||
|
|
@ -78,7 +96,7 @@ namespace xo {
|
|||
* }
|
||||
*
|
||||
* void anotherfunc() {
|
||||
* XO_SCOPE(x); // or scope x("anotherfunc")
|
||||
* XO_SCOPE(x); // or scope x("anotherfunc").
|
||||
* x.log(y);
|
||||
* }
|
||||
*
|
||||
|
|
@ -98,14 +116,15 @@ namespace xo {
|
|||
* -anotherfunc:
|
||||
* d,e,f
|
||||
* -myfunc:
|
||||
*/
|
||||
* @endcode
|
||||
**/
|
||||
template <typename CharT, typename Traits = std::char_traits<CharT>>
|
||||
class basic_scope {
|
||||
public:
|
||||
using state_impl_type = state_impl<CharT, Traits>;
|
||||
using log_streambuf_type = log_streambuf<CharT, Traits>;
|
||||
|
||||
public:
|
||||
//basic_scope(std::string_view name1, bool enabled_flag);
|
||||
template <typename... Tn>
|
||||
basic_scope(scope_setup setup, Tn&&... rest);
|
||||
~basic_scope();
|
||||
|
|
@ -119,6 +138,7 @@ namespace xo {
|
|||
|
||||
void set_dest_sbuf(std::streambuf * x) { this->dest_sbuf_ = x; }
|
||||
|
||||
/** Log arguments in pack @p rest **/
|
||||
template<typename... Tn>
|
||||
bool log(Tn&&... rest) {
|
||||
if(this->finalized_) {
|
||||
|
|
@ -129,8 +149,23 @@ namespace xo {
|
|||
/* indent for timestamp (not printed on this line) */
|
||||
logstate->time_indent();
|
||||
|
||||
/* log to per-thread stream to prevent data races */
|
||||
tosn(logstate2stream(logstate), std::forward<Tn>(rest)...);
|
||||
if (log_config::pretty_print_enabled) {
|
||||
print::ppconfig ppc = {
|
||||
.right_margin_ = log_config::right_margin,
|
||||
.indent_width_ = log_config::indent_width
|
||||
};
|
||||
std::ostream& ss = logstate2stream(logstate);
|
||||
log_streambuf_type & sbuf = logstate2streambuf(logstate);
|
||||
|
||||
/* use 0 for indent because flush2sbuf responsible for basic_scope toplevel indent */
|
||||
print::ppstate pps(0 /*ci*/, &ppc, &ss, &sbuf);
|
||||
|
||||
pps.prettyn(ppconcat<std::decay_t<Tn>...>{{std::forward<Tn>(rest)...}});
|
||||
|
||||
} else {
|
||||
/* log to per-thread stream to prevent data races */
|
||||
tosn(logstate2stream(logstate), std::forward<Tn>(rest)...);
|
||||
}
|
||||
|
||||
this->flush2sbuf(logstate);
|
||||
}
|
||||
|
|
@ -138,18 +173,21 @@ namespace xo {
|
|||
return true;
|
||||
} /*log*/
|
||||
|
||||
/** Log argument in pack @p args **/
|
||||
template<typename... Tn>
|
||||
bool operator()(Tn&&... args) { return this->log(std::forward<Tn>(args)...); }
|
||||
|
||||
/* call once to end scope before dtor */
|
||||
/** Optionally, call once to end scope before dtor.
|
||||
* Logs arguments in pack @p args
|
||||
**/
|
||||
template<typename... Tn>
|
||||
void end_scope(Tn&&... args);
|
||||
|
||||
private:
|
||||
/* establish stream for logging; use thread-local storage for threadsafetỵ
|
||||
* stream, if recycled (ịẹ after 1st call to scopẹlog() from a particular thread),
|
||||
* will be in 'reset-to-beginning of buffer' statẹ
|
||||
*/
|
||||
/** establish stream for logging; use thread-local storage for threadsafetỵ
|
||||
* stream, if recycled (ịẹ after 1st call to scopẹlog() from a particular thread),
|
||||
* will be in 'reset-to-beginning of buffer' statẹ
|
||||
**/
|
||||
static state_impl_type * require_indent_thread_local_state();
|
||||
|
||||
/* establish logging state; use thread-local storage for threadsafety */
|
||||
|
|
@ -158,6 +196,9 @@ namespace xo {
|
|||
/* retrieve permanently-associated ostream for logging-state */
|
||||
static std::ostream & logstate2stream(state_impl_type * logstate);
|
||||
|
||||
/* retreive permanently-associated streambuf for logging-state */
|
||||
static log_streambuf_type & logstate2streambuf(state_impl_type * logstate);
|
||||
|
||||
/* write collected output to std::clog, or chosen streambuf */
|
||||
void flush2sbuf(state_impl_type * logstate);
|
||||
|
||||
|
|
@ -261,6 +302,13 @@ namespace xo {
|
|||
return logstate->ss();
|
||||
} /*logstate2stream*/
|
||||
|
||||
template <typename CharT, typename Traits>
|
||||
log_streambuf<CharT, Traits> &
|
||||
basic_scope<CharT, Traits>::logstate2streambuf(state_impl_type * logstate)
|
||||
{
|
||||
return logstate->sbuf();
|
||||
}
|
||||
|
||||
template <typename CharT, typename Traits>
|
||||
void
|
||||
basic_scope<CharT, Traits>::flush2sbuf(state_impl_type * logstate)
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
set(SELF_EXECUTABLE_NAME utest.indentlog)
|
||||
set(SELF_SOURCE_FILES
|
||||
fixed.test.cpp quoted.test.cpp vector.test.cpp array.test.cpp timeutil.test.cpp tag.test.cpp
|
||||
filename.test.cpp code_location.test.cpp function.test.cpp
|
||||
indentlog_utest_main.cpp)
|
||||
filename.test.cpp code_location.test.cpp function.test.cpp pretty_vector.test.cpp
|
||||
indentlog_utest_main.cpp log_streambuf.test.cpp toppstr.test.cpp)
|
||||
|
||||
xo_add_utest_executable(${SELF_EXECUTABLE_NAME} ${SELF_SOURCE_FILES})
|
||||
|
||||
|
|
|
|||
107
xo-indentlog/utest/log_streambuf.test.cpp
Normal file
107
xo-indentlog/utest/log_streambuf.test.cpp
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
/* @file log_streambuf.test.cpp */
|
||||
|
||||
#include "xo/indentlog/log_streambuf.hpp"
|
||||
#include "xo/indentlog/print/tag.hpp"
|
||||
#include "xo/indentlog/print/quoted.hpp"
|
||||
#include <catch2/catch.hpp>
|
||||
//#include <sstream>
|
||||
|
||||
namespace ut {
|
||||
using xo::log_streambuf;
|
||||
|
||||
TEST_CASE("log_streamhuf", "[log_streambuf]") {
|
||||
std::size_t z = 16;
|
||||
log_streambuf<char, std::char_traits<char>> sbuf(z);
|
||||
std::ostream ss(&sbuf);
|
||||
|
||||
REQUIRE(sbuf.capacity() == z);
|
||||
REQUIRE(sbuf.pos() == 0);
|
||||
REQUIRE(sbuf.lpos() == 0);
|
||||
|
||||
ss << '\n';
|
||||
|
||||
REQUIRE(sbuf.capacity() == z);
|
||||
REQUIRE(sbuf.pos() == 1);
|
||||
REQUIRE(sbuf.lpos() == 1);
|
||||
}
|
||||
|
||||
// write test cases with some random strings.
|
||||
// for each string a list of tuples {ch,pos,lpos}
|
||||
|
||||
struct recd {
|
||||
char const * text_ = nullptr;
|
||||
std::size_t expect_pos_ = 0;
|
||||
std::size_t expect_lpos_ = 0;
|
||||
};
|
||||
|
||||
struct test_case {
|
||||
std::size_t buf_capacity_;
|
||||
std::vector<recd> steps_;
|
||||
};
|
||||
|
||||
std::vector<test_case> s_testcase_v = {
|
||||
{64, {{"\n", 1, 0},
|
||||
{"abcd", 5, 4},
|
||||
{"abcd\nefg\nhij", 17, 3},
|
||||
{"klmnopqrstuvwxyz", 33, 19},
|
||||
}},
|
||||
{32, {{"\n", 1, 0},
|
||||
{"abcd", 5, 4},
|
||||
{"abcd\nefg\nhij", 17, 3},
|
||||
{"klmnopqrstuvwxyz", 33, 19},
|
||||
}},
|
||||
{16, {{"\n", 1, 0},
|
||||
{"abcd", 5, 4},
|
||||
{"abcd\nefg\nhij", 17, 3},
|
||||
{"klmnopqrstuvwxyz", 33, 19},
|
||||
}},
|
||||
{8, {{"\n", 1, 0},
|
||||
{"abcd", 5, 4},
|
||||
{"abcd\nefg\nhij", 17, 3},
|
||||
{"klmnopqrstuvwxyz", 33, 19},
|
||||
}},
|
||||
{4, {{"\n", 1, 0},
|
||||
{"abcd", 5, 4},
|
||||
{"abcd\nefg\nhij", 17, 3},
|
||||
{"klmnopqrstuvwxyz", 33, 19},
|
||||
}},
|
||||
};
|
||||
|
||||
TEST_CASE("log_streambuf2", "[log_streambuf]") {
|
||||
using xo::xtag;
|
||||
using xo::print::quot;
|
||||
using xo::print::unq;
|
||||
|
||||
for (std::size_t i = 0; i < s_testcase_v.size(); ++i) {
|
||||
INFO(tostr(xtag("i", i)));
|
||||
|
||||
const test_case & tc = s_testcase_v[i];
|
||||
|
||||
std::size_t z = tc.buf_capacity_;
|
||||
log_streambuf<char> sbuf(z, true /*debug_flag*/);
|
||||
std::ostream ss(&sbuf);
|
||||
|
||||
REQUIRE(sbuf.capacity() == z);
|
||||
REQUIRE(sbuf.pos() == 0);
|
||||
REQUIRE(sbuf.lpos() == 0);
|
||||
REQUIRE(sbuf.lo() + sbuf.capacity() == sbuf.hi());
|
||||
|
||||
std::size_t j = 0;
|
||||
for (const recd & r : tc.steps_) {
|
||||
INFO(tostr(xtag("j", j), xtag("text", unq(r.text_))));
|
||||
|
||||
ss << r.text_;
|
||||
|
||||
REQUIRE(ss.good());
|
||||
REQUIRE(sbuf.capacity() >= z);
|
||||
REQUIRE(sbuf.lo() + sbuf.capacity() == sbuf.hi());
|
||||
REQUIRE(sbuf.pos() == r.expect_pos_);
|
||||
REQUIRE(sbuf.lpos() == r.expect_lpos_);
|
||||
|
||||
++j;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* end log_streambuf.test.cpp */
|
||||
97
xo-indentlog/utest/pretty_vector.test.cpp
Normal file
97
xo-indentlog/utest/pretty_vector.test.cpp
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
/* @file pretty_vector.test.cpp */
|
||||
|
||||
#include "xo/indentlog/print/pretty.hpp"
|
||||
#include "xo/indentlog/print/pretty_vector.hpp"
|
||||
#include <catch2/catch.hpp>
|
||||
#include <sstream>
|
||||
|
||||
namespace ut {
|
||||
using xo::print::ppconfig;
|
||||
using xo::print::ppstate_standalone;
|
||||
|
||||
TEST_CASE("print-upto", "[pretty]") {
|
||||
std::stringstream ss;
|
||||
ppconfig cfg;
|
||||
ppstate_standalone pps(&ss, 0, &cfg);
|
||||
|
||||
REQUIRE(pps.pos() == 0);
|
||||
REQUIRE(pps.lpos() == 0);
|
||||
REQUIRE(pps.avail_margin() == cfg.right_margin_);
|
||||
REQUIRE(pps.has_margin());
|
||||
REQUIRE(pps.has_budget(cfg.right_margin_));
|
||||
REQUIRE(pps.scan_no_newline(0));
|
||||
}
|
||||
|
||||
TEST_CASE("pretty", "[pretty]") {
|
||||
ppconfig ppc;
|
||||
ppc.right_margin_ = 40;
|
||||
ppc.indent_width_ = 2;
|
||||
|
||||
std::stringstream ss;
|
||||
ppstate_standalone pps(&ss, 0, &ppc);
|
||||
|
||||
pps.pretty("hello");
|
||||
|
||||
REQUIRE(ss.str() == "hello");
|
||||
}
|
||||
|
||||
TEST_CASE("prettyvec", "[pretty]") {
|
||||
ppconfig ppc;
|
||||
ppc.right_margin_ = 20;
|
||||
ppc.indent_width_ = 2;
|
||||
|
||||
std::stringstream ss;
|
||||
ppstate_standalone pps(&ss, 0, &ppc);
|
||||
|
||||
std::vector<int64_t> test = {1, 2, 3, 4, 5, 6};
|
||||
|
||||
pps.pretty(test);
|
||||
|
||||
REQUIRE(ss.str() == "[1, 2, 3, 4, 5, 6]");
|
||||
}
|
||||
|
||||
TEST_CASE("prettyvec2", "[pretty]") {
|
||||
ppconfig ppc;
|
||||
ppc.right_margin_ = 10;
|
||||
ppc.indent_width_ = 2;
|
||||
|
||||
std::stringstream ss;
|
||||
ppstate_standalone pps(&ss, 0, &ppc);
|
||||
|
||||
std::vector<int64_t> test = {1, 2, 3, 4, 5, 6};
|
||||
|
||||
pps.pretty(test);
|
||||
|
||||
REQUIRE(ss.str() == "[\n 1,\n 2,\n 3,\n 4,\n 5,\n 6\n]");
|
||||
}
|
||||
|
||||
TEST_CASE("prettyvec3", "[pretty]") {
|
||||
ppconfig ppc;
|
||||
ppc.right_margin_ = 20;
|
||||
ppc.indent_width_ = 2;
|
||||
|
||||
std::stringstream ss;
|
||||
ppstate_standalone pps(&ss, 0, &ppc);
|
||||
|
||||
std::vector<std::vector<int64_t>> test = {{1, 2, 3, 4}, {4, 5, 6, 7}};
|
||||
|
||||
pps.pretty(test);
|
||||
|
||||
REQUIRE(ss.str() == "[\n [1, 2, 3, 4],\n [4, 5, 6, 7]\n]");
|
||||
}
|
||||
|
||||
TEST_CASE("prettyvec4", "[pretty]") {
|
||||
ppconfig ppc;
|
||||
ppc.right_margin_ = 10;
|
||||
ppc.indent_width_ = 2;
|
||||
|
||||
std::stringstream ss;
|
||||
ppstate_standalone pps(&ss, 0, &ppc);
|
||||
|
||||
std::vector<std::vector<int64_t>> test = {{1, 2, 3, 4}, {4, 5, 6, 7}};
|
||||
|
||||
pps.pretty(test);
|
||||
|
||||
REQUIRE(ss.str() == "[\n [\n 1,\n 2,\n 3,\n 4\n ],\n [\n 4,\n 5,\n 6,\n 7\n ]\n]");
|
||||
}
|
||||
}
|
||||
55
xo-indentlog/utest/toppstr.test.cpp
Normal file
55
xo-indentlog/utest/toppstr.test.cpp
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
/* @file toppstr.cpp */
|
||||
|
||||
#include "xo/indentlog/print/ppstr.hpp"
|
||||
#include "xo/indentlog/print/tag.hpp"
|
||||
#include <catch2/catch.hpp>
|
||||
#include <sstream>
|
||||
|
||||
namespace ut {
|
||||
using xo::toppstr;
|
||||
using xo::toppstr2;
|
||||
using xo::print::ppconfig;
|
||||
using xo::print::ppstate;
|
||||
|
||||
TEST_CASE("toppstr_1", "[toppstr]") {
|
||||
std::string s = toppstr();
|
||||
REQUIRE(s.empty());
|
||||
}
|
||||
|
||||
TEST_CASE("toppstr_2", "[toppstr]") {
|
||||
std::string s = toppstr("hello");
|
||||
REQUIRE(s == "hello");
|
||||
}
|
||||
|
||||
TEST_CASE("toppstr_3", "[toppstr]") {
|
||||
std::string s = toppstr("the", " quick", " brown", " fox", " jumps", " over", " the", " lazy", " dog");
|
||||
REQUIRE(s == "the quick brown fox jumps over the lazy dog");
|
||||
}
|
||||
|
||||
TEST_CASE("toppstr2_0", "[toppstr2]") {
|
||||
ppconfig ppc;
|
||||
ppc.right_margin_ = 40;
|
||||
ppc.indent_width_ = 0;
|
||||
|
||||
std::string s = toppstr2(ppc, "the", " quick", " brown", " fox", " jumps", " over", " the", " lazy", " dog");
|
||||
REQUIRE(s == "the\n quick\n brown\n fox\n jumps\n over\n the\n lazy\n dog");
|
||||
}
|
||||
|
||||
TEST_CASE("toppstr2_1", "[toppstr2]") {
|
||||
ppconfig ppc;
|
||||
ppc.right_margin_ = 40;
|
||||
ppc.indent_width_ = 2;
|
||||
|
||||
std::string s = toppstr2(ppc, "the", " quick", " brown", " fox", " jumps", " over", " the", " lazy", " dog");
|
||||
REQUIRE(s == "the\n quick\n brown\n fox\n jumps\n over\n the\n lazy\n dog");
|
||||
}
|
||||
|
||||
TEST_CASE("toppstr2_2", "[toppstr2]") {
|
||||
ppconfig ppc;
|
||||
ppc.right_margin_ = 40;
|
||||
ppc.indent_width_ = 4;
|
||||
|
||||
std::string s = toppstr2(ppc, "the", " quick", " brown", " fox", " jumps", " over", " the", " lazy", " dog");
|
||||
REQUIRE(s == "the\n quick\n brown\n fox\n jumps\n over\n the\n lazy\n dog");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue