xo-indentlog: add general-purposee pretty-printing [WIP]

This commit is contained in:
Roland Conybeare 2025-07-13 21:17:13 -05:00
commit 28172fc3d6
24 changed files with 1326 additions and 121 deletions

View file

@ -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

View file

@ -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)

View file

@ -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;

View file

@ -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();

View file

@ -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*/

View file

@ -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 */

View file

@ -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 */

View file

@ -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)

View 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*/

View 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
}
}

View 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 */

View 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*/

View 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 */

View 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 */

View 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);
}
};
}
}

View file

@ -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*/

View file

@ -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<<*/

View file

@ -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*/

View file

@ -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) {

View file

@ -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)

View file

@ -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})

View 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 */

View 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
utest/toppstr.test.cpp Normal file
View 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");
}
}