include/nestlog -> include/indentlog
This commit is contained in:
parent
8268eef8b2
commit
61441f2513
21 changed files with 0 additions and 0 deletions
25
include/indentlog/array.hpp
Normal file
25
include/indentlog/array.hpp
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
/* @file array.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <array>
|
||||
|
||||
namespace std {
|
||||
template<typename T, size_t N>
|
||||
inline std::ostream &
|
||||
operator<<(std::ostream & os,
|
||||
std::array<T, N> const & v)
|
||||
{
|
||||
os << "[";
|
||||
for(size_t i = 0; i < N; ++i) {
|
||||
if(i > 0)
|
||||
os << " ";
|
||||
os << v[i];
|
||||
}
|
||||
os << "]";
|
||||
return os;
|
||||
} /*operator<<*/
|
||||
} /*namespace std*/
|
||||
|
||||
/* end array.hpp */
|
||||
56
include/indentlog/code_location.hpp
Normal file
56
include/indentlog/code_location.hpp
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
/* @file code_location.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "filename.hpp"
|
||||
#include "color.hpp"
|
||||
|
||||
namespace xo {
|
||||
/* Example:
|
||||
* os << code_location("/path/to/foo.cpp", 123)
|
||||
* writes
|
||||
* foo.cpp:123
|
||||
* on stream os
|
||||
*/
|
||||
|
||||
/* Tag to drive header-only expression */
|
||||
template <typename Tag>
|
||||
class code_location_impl {
|
||||
public:
|
||||
code_location_impl(std::string_view file,
|
||||
std::uint32_t line,
|
||||
color_encoding encoding = CE_Ansi,
|
||||
std::uint32_t color = 31 /*red*/)
|
||||
: file_{file}, line_{line}, encoding_{encoding}, color_{color} {}
|
||||
|
||||
void print_code_location(std::ostream & os) const {
|
||||
os << "["
|
||||
<< with_color(encoding_, color_, basename(file_))
|
||||
<< ":"
|
||||
<< line_
|
||||
<< "]";
|
||||
} /*print_code_location*/
|
||||
|
||||
private:
|
||||
/* __FILE__ */
|
||||
std::string_view file_;
|
||||
/* __LINE__ */
|
||||
std::uint32_t line_ = 0;
|
||||
/* color encoding for file,line */
|
||||
color_encoding encoding_ = CE_Ansi;
|
||||
/* color for file,line */
|
||||
std::uint32_t color_ = 0;
|
||||
}; /*code_location_impl*/
|
||||
|
||||
using code_location = code_location_impl<class code_location_impl_tag>;
|
||||
|
||||
inline std::ostream &
|
||||
operator<<(std::ostream & os,
|
||||
code_location const & x)
|
||||
{
|
||||
x.print_code_location(os);
|
||||
return os;
|
||||
}
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end code_location.hpp */
|
||||
133
include/indentlog/color.hpp
Normal file
133
include/indentlog/color.hpp
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
/* color.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ostream>
|
||||
//#include <utility> // for std::move
|
||||
#include <cstdint>
|
||||
|
||||
namespace xo {
|
||||
enum color_encoding {
|
||||
CE_None,
|
||||
CE_Ansi,
|
||||
CE_Xterm,
|
||||
};
|
||||
|
||||
enum color_flags {
|
||||
CF_None = 0x0,
|
||||
CF_ColorOn = 0x01,
|
||||
CF_Contents = 0x02,
|
||||
CF_ColorOff = 0x04,
|
||||
CF_All = 0x07
|
||||
};
|
||||
|
||||
template <typename Contents>
|
||||
class color_impl {
|
||||
public:
|
||||
color_impl(color_flags flags, color_encoding encoding, std::uint32_t color, Contents && contents)
|
||||
: flags_{flags}, encoding_{encoding}, color_{color}, contents_{std::forward<Contents>(contents)} {}
|
||||
|
||||
std::uint32_t color() const { return color_; }
|
||||
Contents const & contents() const { return contents_; }
|
||||
|
||||
void print(std::ostream & os) const {
|
||||
if ((flags_ & CF_ColorOn) && (color_ > 0)) {
|
||||
switch(encoding_) {
|
||||
case CE_None:
|
||||
break;
|
||||
case CE_Ansi:
|
||||
os << "\033[" << color_ << "m";
|
||||
break;
|
||||
case CE_Xterm:
|
||||
os << "\033[38;5;" << color_ << "m";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (flags_ & CF_Contents)
|
||||
os << contents_;
|
||||
|
||||
if ((flags_ & CF_ColorOff) && (color_ > 0)) {
|
||||
switch(encoding_) {
|
||||
case CE_None:
|
||||
break;
|
||||
case CE_Ansi:
|
||||
case CE_Xterm:
|
||||
os << "\033[0m";
|
||||
break;
|
||||
}
|
||||
}
|
||||
} /*print*/
|
||||
|
||||
private:
|
||||
color_flags flags_ = CF_None;
|
||||
|
||||
color_encoding encoding_ = CE_Ansi;
|
||||
/* .encoding = CE_Ansi:
|
||||
* 0 = no color
|
||||
* 30 = black
|
||||
* 31 = red
|
||||
* 32 = green
|
||||
* 33 = yellow
|
||||
* 34 = blue
|
||||
* 35 = magenta
|
||||
* 36 = cyan
|
||||
*
|
||||
* .encoding = CE_Xterm:
|
||||
* see [[https://i.stack.imgur.com/KTSQa.png]]
|
||||
* 0..7 standard colors (muted: grey, red, green, yellow, blue, pink, cyan, white)
|
||||
* 8..15 high-intensity colors (grey, red, green, yellow, blue, pink, cyan, white)
|
||||
* 16..51 chooses hue
|
||||
* 16..51 + (0..5)x36 increases whiteness
|
||||
*/
|
||||
std::uint32_t color_ = 0;
|
||||
|
||||
Contents contents_;
|
||||
}; /*color_impl*/
|
||||
|
||||
template <typename Contents>
|
||||
color_impl<Contents> with_ansi_color(std::uint32_t color, Contents && contents) {
|
||||
return color_impl<Contents>(CF_All, CE_Ansi, color, std::forward(contents));
|
||||
} /*with_ansi_color*/
|
||||
|
||||
template <typename Contents>
|
||||
color_impl<Contents> with_xterm_color(std::uint32_t color, Contents && contents) {
|
||||
return color_impl<Contents>(CF_All, CE_Xterm, color, std::forward(contents));
|
||||
} /*with_ansi_color*/
|
||||
|
||||
template <typename Contents>
|
||||
color_impl<Contents> with_color(color_encoding encoding, std::uint32_t color, Contents && contents) {
|
||||
return color_impl<Contents>(CF_All, encoding, color, std::forward<Contents>(contents));
|
||||
} /*with_color*/
|
||||
|
||||
inline color_impl<int>
|
||||
color_on_ansi(std::uint32_t color) {
|
||||
return color_impl<int>(CF_ColorOn, CE_Ansi, color, 0);
|
||||
} /*color_on_ansi*/
|
||||
|
||||
inline color_impl<int>
|
||||
color_on_xterm(std::uint32_t color) {
|
||||
return color_impl<int>(CF_ColorOn, CE_Xterm, color, 0);
|
||||
} /*color_on_xterm*/
|
||||
|
||||
inline color_impl<int>
|
||||
color_on(color_encoding encoding, std::uint32_t color) {
|
||||
return color_impl<int>(CF_ColorOn, encoding, color, 0);
|
||||
} /*color_on*/
|
||||
|
||||
inline color_impl<int>
|
||||
color_off() {
|
||||
/* any non-zero value works here for color */
|
||||
return color_impl<int>(CF_ColorOff, CE_Ansi, 1 /*color*/, 0);
|
||||
} /*color_off*/
|
||||
|
||||
template <typename Contents>
|
||||
inline std::ostream &
|
||||
operator<<(std::ostream & os, color_impl<Contents> const & x) {
|
||||
x.print(os);
|
||||
return os;
|
||||
} /*operator<<*/
|
||||
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end color.hpp */
|
||||
37
include/indentlog/concat.hpp
Normal file
37
include/indentlog/concat.hpp
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/* @file concat.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ostream>
|
||||
#include <utility> // for std::move()
|
||||
|
||||
namespace xo {
|
||||
template <typename T1, typename T2>
|
||||
struct concat_impl {
|
||||
public:
|
||||
concat_impl(T1 && x1, T2 && x2)
|
||||
: x1_{std::forward<T1>(x1)}, x2_{std::forward<T2>(x2)} {}
|
||||
|
||||
T1 const & x1() const { return x1_; }
|
||||
T2 const & x2() const { return x2_; }
|
||||
|
||||
private:
|
||||
T1 x1_;
|
||||
T2 x2_;
|
||||
}; /*concat_impl*/
|
||||
|
||||
template <typename T1, typename T2>
|
||||
concat_impl<T1,T2> concat(T1 && x1, T2 && x2) {
|
||||
return concat_impl<T1,T2>(std::move(x1), std::move(x2));
|
||||
} /*concat*/
|
||||
|
||||
template <typename T1, typename T2>
|
||||
inline std::ostream &
|
||||
operator<<(std::ostream & os, concat_impl<T1, T2> const & x) {
|
||||
os << x.x1() << x.x2();
|
||||
return os;
|
||||
} /*operator<<*/
|
||||
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end concat.hpp */
|
||||
70
include/indentlog/filename.hpp
Normal file
70
include/indentlog/filename.hpp
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
/* @file filename.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
|
||||
namespace xo {
|
||||
/* Example:
|
||||
* os << basename("/path/to/basename.cpp")
|
||||
* prints
|
||||
* basename.cpp
|
||||
* on os
|
||||
*/
|
||||
|
||||
/* Tag to drive header-only expression */
|
||||
template <typename Tag>
|
||||
class basename_impl {
|
||||
public:
|
||||
basename_impl(std::string_view path)
|
||||
: path_{path} {}
|
||||
|
||||
std::string_view const & path() const { return path_; }
|
||||
|
||||
/* /home/roland/proj/nestlog/include/nestlog/filename.hpp
|
||||
* <-basename->
|
||||
*/
|
||||
static void print_basename(std::ostream & os, std::string_view const & s) {
|
||||
std::size_t p = exclude_dirname(s);
|
||||
|
||||
os << s.substr(p);
|
||||
} /*print_basename*/
|
||||
|
||||
private:
|
||||
static std::size_t exclude_dirname(std::string_view const & s) {
|
||||
std::size_t z = s.size();
|
||||
|
||||
if (z == 0)
|
||||
return 0;
|
||||
|
||||
if (s[z-1] == '/') {
|
||||
/* ignore trailing '/' */
|
||||
return exclude_dirname(s.substr(0, z-1));
|
||||
}
|
||||
|
||||
std::size_t p = s.find_last_of('/');
|
||||
|
||||
if (p == std::string_view::npos)
|
||||
return 0;
|
||||
else
|
||||
return p + 1;
|
||||
} /*exclude_dirname*/
|
||||
|
||||
private:
|
||||
/* some unix pathname, e.g. [/home/roland/proj/nestlog/include/nestlog/filename.hpp] */
|
||||
std::string_view path_;
|
||||
}; /*basename_impl*/
|
||||
|
||||
using basename = basename_impl<class basename_impl_tag>;
|
||||
|
||||
inline std::ostream &
|
||||
operator<<(std::ostream & os,
|
||||
basename const & bn)
|
||||
{
|
||||
basename::print_basename(os, bn.path());
|
||||
return os;
|
||||
}
|
||||
} /*xo*/
|
||||
|
||||
/* end filename.hpp */
|
||||
45
include/indentlog/fixed.hpp
Normal file
45
include/indentlog/fixed.hpp
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/* @file fixed.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace xo {
|
||||
/* use:
|
||||
* ostream os = ...;
|
||||
*
|
||||
* os << fixed(3.1415926, 3)
|
||||
*
|
||||
* writes
|
||||
* 3.142
|
||||
*
|
||||
* on os, restoring stream's formatting+precision state
|
||||
*/
|
||||
class fixed {
|
||||
public:
|
||||
fixed(double x, uint16_t prec) : x_{x}, prec_{prec} {}
|
||||
|
||||
/* print this value */
|
||||
double x_;
|
||||
/* precision */
|
||||
uint16_t prec_ = 0;
|
||||
}; /*fixed*/
|
||||
|
||||
inline std::ostream &
|
||||
operator<<(std::ostream & s, fixed const & fx)
|
||||
{
|
||||
std::ios::fmtflags orig_flags = s.flags();
|
||||
std::streamsize orig_p = s.precision();
|
||||
|
||||
s.flags(std::ios::fixed);
|
||||
s.precision(fx.prec_);
|
||||
s << fx.x_;
|
||||
|
||||
s.flags(orig_flags);
|
||||
s.precision(orig_p);
|
||||
|
||||
return s;
|
||||
} /*operator<<*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end fixed.hpp */
|
||||
211
include/indentlog/function.hpp
Normal file
211
include/indentlog/function.hpp
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
/* @file function.hpp */
|
||||
|
||||
#include "color.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
|
||||
namespace xo {
|
||||
enum function_style {
|
||||
/* literal: print given name, no alterations */
|
||||
FS_Literal,
|
||||
/* pretty: print name, surrounded by [] */
|
||||
FS_Pretty,
|
||||
/* streamlined: remove extraneous detail, try to print something like class::method */
|
||||
FS_Streamlined,
|
||||
/* simple: remove everything except function/method name */
|
||||
FS_Simple
|
||||
};
|
||||
|
||||
/* Tag to drive header-only expression */
|
||||
template <typename Tag>
|
||||
class function_name_impl {
|
||||
public:
|
||||
/* color: ANSI escape color (lookup Select Graphic Rendition subset)
|
||||
* 0 = none
|
||||
* 31 = red
|
||||
*/
|
||||
function_name_impl(function_style style,
|
||||
color_encoding encoding,
|
||||
std::uint32_t color,
|
||||
std::string_view pretty)
|
||||
: style_{style}, encoding_{encoding}, color_{color}, pretty_{pretty} {}
|
||||
|
||||
function_style style() const { return style_; }
|
||||
color_encoding encoding() const { return encoding_; }
|
||||
std::uint32_t color() const { return color_; }
|
||||
std::string_view const & pretty() const { return pretty_; }
|
||||
|
||||
/* e.g.
|
||||
* std::vector<std::pair<int, xo::bar> xo::sometemplateclass<T,U>::fib(int, char**)
|
||||
* ^ ^
|
||||
* p q
|
||||
*/
|
||||
static void print_simple(std::ostream & os, std::string_view const & s) {
|
||||
std::size_t p = exclude_return_type(s);
|
||||
std::string_view s2 = s.substr(p);
|
||||
std::size_t q = find_toplevel_sep(s2, true /*last_flag*/);
|
||||
|
||||
print_aux(os, s2.substr(q));
|
||||
} /*print_simple*/
|
||||
|
||||
/* e.g.
|
||||
* std::vector<std::pair<int, xo::bar> xo::sometemplateclass<T,U>::fib(int, char**)
|
||||
* ^ ^
|
||||
* p q
|
||||
*/
|
||||
static void print_streamlined(std::ostream & os, std::string_view const & s) {
|
||||
std::size_t p = exclude_return_type(s);
|
||||
std::string_view s2 = s.substr(p);
|
||||
std::size_t q = find_toplevel_sep(s2, false /*!last_flag*/);
|
||||
|
||||
print_aux(os, s2.substr(q));
|
||||
} /*print_streamlined*/
|
||||
|
||||
private:
|
||||
static std::size_t exclude_return_type(std::string_view const & s) {
|
||||
/* strategy:
|
||||
* - scan right-to-left
|
||||
* - ignore anything between matching <>, () pairs (i.e. anything nested)
|
||||
* - stop at rightmost toplevel space --> return suffix following that space
|
||||
*/
|
||||
std::size_t nesting_level = 0;
|
||||
|
||||
std::size_t z = s.size();
|
||||
for (std::size_t rp = 0; rp < z; ++rp) {
|
||||
std::size_t p = z-1-rp;
|
||||
char ch = s[p];
|
||||
|
||||
if (ch == '<' || ch == '(')
|
||||
++nesting_level;
|
||||
|
||||
if (nesting_level == 0) {
|
||||
if (ch == ' ')
|
||||
return p + 1;
|
||||
}
|
||||
|
||||
if (ch == '>' || ch == ')')
|
||||
--nesting_level;
|
||||
}
|
||||
|
||||
return 0;
|
||||
} /*exclude_return_type*/
|
||||
|
||||
/* e.g.
|
||||
* xo::ns::someclass<xo::foo, xo::bar>::somemethod(xo::enum1, std::vector<xo::blah>)
|
||||
* ^
|
||||
* return this pos
|
||||
*
|
||||
* last_flag: return pos after last ::
|
||||
* !last_flag: return pos after 2nd-last ::
|
||||
*/
|
||||
static std::size_t find_toplevel_sep(std::string_view const & s, bool last_flag) {
|
||||
/* strategy:
|
||||
* - scan left-to-right
|
||||
* - ignore anything between matching <>, () pairs (i.e. anything nested)
|
||||
* - count :: pairs
|
||||
* - remember 2nd-last :: pair; reports pos just after it
|
||||
*
|
||||
* note:
|
||||
* - if no :: pairs, or only one such pair, return 0
|
||||
*/
|
||||
std::size_t nesting_level = 0;
|
||||
|
||||
std::size_t pos_after_last_sep = 0;
|
||||
std::size_t pos_after_2ndlast_sep = 0;
|
||||
|
||||
for (std::size_t p = 0; p < s.size(); ++p) {
|
||||
char ch = s[p];
|
||||
|
||||
if (ch == '<' || ch == '(')
|
||||
++nesting_level;
|
||||
|
||||
if (nesting_level == 0) {
|
||||
if ((ch == ':')
|
||||
&& (p+1 < s.size())
|
||||
&& s[p+1] == ':')
|
||||
{
|
||||
pos_after_2ndlast_sep = pos_after_last_sep;
|
||||
pos_after_last_sep = p+2;
|
||||
++p; /* skipping 1st : in separator */
|
||||
}
|
||||
}
|
||||
|
||||
if (ch == '>' || ch == ')')
|
||||
--nesting_level;
|
||||
}
|
||||
|
||||
return last_flag ? pos_after_last_sep : pos_after_2ndlast_sep;
|
||||
} /*find_toplevel_sep*/
|
||||
|
||||
/* fib(int, char **) --> fib
|
||||
* quux(std::vector<std::size_t, std::allocator<std::size_t>>) -> quux
|
||||
* foo::bar<std::vector<char>>() -> foo::bar
|
||||
*/
|
||||
static void print_aux(std::ostream & os, std::string_view const & s) {
|
||||
//std::cerr << "print_aux: s=" << s << std::endl;
|
||||
|
||||
/* strategy:
|
||||
* - print left-to-right, omit anything between matching <> or () pairs.
|
||||
* - don't keep track of which is which, so would also match < with ) etc;
|
||||
* this acceptable since pretty functions won't visit this corner case
|
||||
*/
|
||||
std::size_t nesting_level = 0;
|
||||
|
||||
for (char ch : s) {
|
||||
if (ch == '<' || ch == '(')
|
||||
++nesting_level;
|
||||
|
||||
if (nesting_level == 0)
|
||||
os << ch;
|
||||
|
||||
if (ch == '>' || ch == ')')
|
||||
--nesting_level;
|
||||
}
|
||||
|
||||
} /*print_aux*/
|
||||
|
||||
private:
|
||||
/* FS_Simple | FS_Pretty (= FS_Literal) | FS_Streamlined */
|
||||
function_style style_;
|
||||
/* CE_Ansi | CE_Xterm */
|
||||
color_encoding encoding_;
|
||||
/* color, if non-zero */
|
||||
std::uint32_t color_;
|
||||
/* e.g. __PRETTY_FUNCTION__ */
|
||||
std::string_view pretty_;
|
||||
}; /*function_name_impl*/
|
||||
|
||||
using function_name = function_name_impl<class function_name_impl_tag>;
|
||||
|
||||
inline std::ostream &
|
||||
operator<<(std::ostream & os,
|
||||
function_name const & fn)
|
||||
{
|
||||
/* set text color */
|
||||
|
||||
switch(fn.style()) {
|
||||
case FS_Literal:
|
||||
os << with_color(fn.encoding(), fn.color(), fn.pretty());
|
||||
break;
|
||||
case FS_Pretty:
|
||||
os << "[" << with_color(fn.encoding(), fn.color(), fn.pretty()) << "]";
|
||||
break;
|
||||
case FS_Simple:
|
||||
os << color_on(fn.encoding(), fn.color());
|
||||
function_name::print_simple(os, fn.pretty());
|
||||
os << color_off();
|
||||
break;
|
||||
case FS_Streamlined:
|
||||
/* omit namespace qualifiers and template arguments */
|
||||
os << color_on(fn.encoding(), fn.color());
|
||||
function_name::print_streamlined(os, fn.pretty());
|
||||
os << color_off();
|
||||
break;
|
||||
}
|
||||
|
||||
return os;
|
||||
} /*operator<<*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end function.hpp */
|
||||
110
include/indentlog/log_config.hpp
Normal file
110
include/indentlog/log_config.hpp
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
/* @file log_config.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "log_level.hpp"
|
||||
#include "function.hpp"
|
||||
#include "color.hpp"
|
||||
#include <cstdint>
|
||||
|
||||
namespace xo {
|
||||
/* Tag here b/c we want header-only library */
|
||||
template <typename Tag>
|
||||
struct log_config_impl {
|
||||
/* display log messages with severity >= .log_level */
|
||||
static log_level min_log_level;
|
||||
/* true to log local time */
|
||||
static bool time_enabled;
|
||||
/* true to log time-of-day in local coords; false for UTC coords */
|
||||
static bool time_local_flag;
|
||||
/* true to log time-of-day with microsecond precision; false for millisecond precision */
|
||||
static bool time_usec_flag;
|
||||
/* spaces per nesting level. 0 -> no indenting */
|
||||
static std::uint32_t indent_width;
|
||||
/* max #of spaces to introduce when indenting */
|
||||
static std::uint32_t max_indent_width;
|
||||
/* if true enable explicit nesting level display [nnn] */
|
||||
static bool nesting_level_enabled;
|
||||
/* color to use for explicit nesting level */
|
||||
static std::uint32_t nesting_level_color;
|
||||
/* display style for function names. FS_Simple|FS_Pretty|FS_Streamlined */
|
||||
static function_style style;
|
||||
/* color encoding */
|
||||
static color_encoding encoding;
|
||||
/* color to use for function name, on entry/exit (xo::scope creation/destruction)
|
||||
* (ansi color codes, see Select Graphics Rendition subset)
|
||||
*/
|
||||
static std::uint32_t function_entry_color;
|
||||
static std::uint32_t function_exit_color;
|
||||
/* if true, append [file:line] to output */
|
||||
static bool location_enabled;
|
||||
/* when .location_enabled, write [file:line] starting this many chars from left margin */
|
||||
static std::uint32_t location_tab;
|
||||
/* color to use for code location */
|
||||
static std::uint32_t code_location_color;
|
||||
}; /*log_config_impl*/
|
||||
|
||||
template <typename Tag>
|
||||
log_level
|
||||
log_config_impl<Tag>::min_log_level = log_level::default_level;
|
||||
|
||||
template <typename Tag>
|
||||
bool
|
||||
log_config_impl<Tag>::time_enabled = 1;
|
||||
|
||||
template <typename Tag>
|
||||
bool
|
||||
log_config_impl<Tag>::time_local_flag = true;
|
||||
|
||||
template <typename Tag>
|
||||
bool
|
||||
log_config_impl<Tag>::time_usec_flag = true;
|
||||
|
||||
template <typename Tag>
|
||||
std::uint32_t
|
||||
log_config_impl<Tag>::indent_width = 2;
|
||||
|
||||
template <typename Tag>
|
||||
std::uint32_t
|
||||
log_config_impl<Tag>::max_indent_width = 32;
|
||||
|
||||
template <typename Tag>
|
||||
bool
|
||||
log_config_impl<Tag>::nesting_level_enabled = true;
|
||||
|
||||
template <typename Tag>
|
||||
std::uint32_t
|
||||
log_config_impl<Tag>::nesting_level_color = 195;
|
||||
|
||||
template <typename Tag>
|
||||
function_style
|
||||
log_config_impl<Tag>::style = FS_Streamlined;
|
||||
|
||||
template <typename Tag>
|
||||
color_encoding
|
||||
log_config_impl<Tag>::encoding = CE_Ansi;
|
||||
|
||||
template <typename Tag>
|
||||
std::uint32_t
|
||||
log_config_impl<Tag>::function_entry_color = 34;
|
||||
|
||||
template <typename Tag>
|
||||
std::uint32_t
|
||||
log_config_impl<Tag>::function_exit_color = 32;
|
||||
|
||||
template <typename Tag>
|
||||
bool
|
||||
log_config_impl<Tag>::location_enabled = true;
|
||||
|
||||
template <typename Tag>
|
||||
std::uint32_t
|
||||
log_config_impl<Tag>::location_tab = 80;
|
||||
|
||||
template <typename Tag>
|
||||
std::uint32_t
|
||||
log_config_impl<Tag>::code_location_color = 31;
|
||||
|
||||
using log_config = log_config_impl<class log_config_tag>;
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end log_config.hpp */
|
||||
57
include/indentlog/log_level.hpp
Normal file
57
include/indentlog/log_level.hpp
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
/* @file log_level.hpp */
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xo {
|
||||
enum class log_level : std::uint32_t {
|
||||
/* control log message severity
|
||||
* silent > always > severe > error > warning > info > chatty > never
|
||||
*
|
||||
* never:
|
||||
* used internally e.g. by XO_ENTER1()
|
||||
* a log message with this severity will never be printed
|
||||
*
|
||||
* always:
|
||||
* use with XO_ENTER1():
|
||||
* scope log(XO_ENTER1(always, mydebug_flag));
|
||||
* to log message whenever mydebug_flag is true (for any .min_log_level except silent)
|
||||
*
|
||||
* silent:
|
||||
* use in log_config to suppress all log messages
|
||||
*/
|
||||
never,
|
||||
verbose,
|
||||
chatty,
|
||||
info,
|
||||
warning,
|
||||
error,
|
||||
severe,
|
||||
always,
|
||||
silent,
|
||||
|
||||
default_level = error
|
||||
}; /*log_level*/
|
||||
|
||||
inline bool
|
||||
operator>(log_level x, log_level y) {
|
||||
return (static_cast<std::uint32_t>(x) > static_cast<std::uint32_t>(y));
|
||||
}
|
||||
|
||||
inline bool
|
||||
operator>=(log_level x, log_level y) {
|
||||
return (static_cast<std::uint32_t>(x) >= static_cast<std::uint32_t>(y));
|
||||
}
|
||||
|
||||
inline bool
|
||||
operator<(log_level x, log_level y) {
|
||||
return (static_cast<std::uint32_t>(x) < static_cast<std::uint32_t>(y));
|
||||
}
|
||||
|
||||
inline bool
|
||||
operator<=(log_level x, log_level y) {
|
||||
return (static_cast<std::uint32_t>(x) <= static_cast<std::uint32_t>(y));
|
||||
}
|
||||
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end log_level.hpp */
|
||||
406
include/indentlog/log_state.hpp
Normal file
406
include/indentlog/log_state.hpp
Normal file
|
|
@ -0,0 +1,406 @@
|
|||
/* @file log_state.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "log_config.hpp"
|
||||
#include "log_streambuf.hpp"
|
||||
#include "pad.hpp"
|
||||
#include "filename.hpp"
|
||||
#include "code_location.hpp"
|
||||
#include "time.hpp"
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <memory> // for std::unique_ptr
|
||||
|
||||
namespace xo {
|
||||
enum EntryExit {
|
||||
EE_Entry,
|
||||
EE_Exit
|
||||
};
|
||||
|
||||
// track per-thread state associated with nesting logger
|
||||
//
|
||||
template <typename CharT, typename Traits>
|
||||
class state_impl {
|
||||
public:
|
||||
using log_streambuf_type = log_streambuf<char, std::char_traits<char>>;
|
||||
using utc_nanos = xo::time::utc_nanos;
|
||||
|
||||
public:
|
||||
state_impl();
|
||||
|
||||
std::uint32_t nesting_level() const { return nesting_level_; }
|
||||
|
||||
void incr_nesting() { ++nesting_level_; }
|
||||
void decr_nesting() { --nesting_level_; }
|
||||
|
||||
std::ostream & ss() { return ss_; }
|
||||
|
||||
void check_print_time(utc_nanos now_tm) {
|
||||
using xo::time::time;
|
||||
using xo::time::utc_nanos;
|
||||
using xo::time::hms_msec;
|
||||
using xo::time::hms_usec;
|
||||
|
||||
if (log_config::time_enabled) {
|
||||
if (log_config::time_local_flag) {
|
||||
if (log_config::time_usec_flag)
|
||||
this->ss_ << hms_usec::local(now_tm) << " ";
|
||||
else
|
||||
this->ss_ << hms_msec::local(now_tm) << " ";
|
||||
} else {
|
||||
if (log_config::time_usec_flag)
|
||||
this->ss_ << hms_usec::utc(now_tm) << " ";
|
||||
else
|
||||
this->ss_ << hms_msec::utc(now_tm) << " ";
|
||||
}
|
||||
}
|
||||
} /*check_print_time*/
|
||||
|
||||
/* space budget for time-of-day */
|
||||
std::size_t calc_time_indent() const {
|
||||
if (log_config::time_enabled) {
|
||||
if (log_config::time_usec_flag) {
|
||||
/*strlen("14:38:19.123456 ")*/
|
||||
return 16;
|
||||
} else {
|
||||
/*strlen("14:38:19.974 ")*/
|
||||
return 13;
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
} /*calc_time_indent*/
|
||||
|
||||
void time_indent() {
|
||||
if (log_config::time_enabled)
|
||||
this->ss_ << pad(this->calc_time_indent(), ' ');
|
||||
} /*time_indent*/
|
||||
|
||||
/* call on entry to new scope */
|
||||
void preamble(function_style style, std::string_view name1, std::string_view name2);
|
||||
/* call before each new log entry */
|
||||
void indent(char pad_char);
|
||||
/* call on exit from scope */
|
||||
void postamble(function_style style, std::string_view name1, std::string_view name2);
|
||||
|
||||
/* write collected output to *p_sbuf */
|
||||
void flush2sbuf(std::streambuf * p_sbuf);
|
||||
|
||||
/* discard output, reset write pointer to beginning of buffer */
|
||||
void reset_stream() {
|
||||
p_sbuf_phase1_->reset_stream();
|
||||
p_sbuf_phase2_->reset_stream();
|
||||
}
|
||||
|
||||
void set_location(std::string_view file, std::uint32_t line) {
|
||||
this->location_flag_ = true;
|
||||
this->file_ = std::move(file);
|
||||
this->line_ = line;
|
||||
} /*set_location*/
|
||||
|
||||
private:
|
||||
/* common implementation for .preamble(), .postamble() */
|
||||
void entryexit_aux(function_style style,
|
||||
std::string_view name1,
|
||||
std::string_view name2,
|
||||
EntryExit entryexit);
|
||||
|
||||
private:
|
||||
/* current nesting level for this thread */
|
||||
std::uint32_t nesting_level_ = 0;
|
||||
|
||||
/* buffer space for logging
|
||||
* (before pretty-printing for scope::log() calls that span multiple lines)
|
||||
* reused across tos() and scope::log() calls
|
||||
*/
|
||||
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
|
||||
* - ..and reset .location_flag
|
||||
*/
|
||||
bool location_flag_ = false;
|
||||
std::string_view file_;
|
||||
std::uint32_t line_ = 0;
|
||||
|
||||
/* buffer space for handling scope::log() calls that span multiple lines;
|
||||
* inserts extra characters in effort to indent gracefully
|
||||
*/
|
||||
std::unique_ptr<log_streambuf_type> p_sbuf_phase2_;
|
||||
|
||||
/* output stream -- always attached to .p_sbuf_phase1
|
||||
* stream inserters for application datatypes will target this stream
|
||||
*/
|
||||
std::ostream ss_;
|
||||
}; /*state_impl*/
|
||||
|
||||
constexpr uint32_t c_default_buf_size = 1024;
|
||||
|
||||
template <typename CharT, typename Traits>
|
||||
state_impl<CharT, Traits>::state_impl()
|
||||
: p_sbuf_phase1_(new log_streambuf_type(c_default_buf_size)),
|
||||
p_sbuf_phase2_(new log_streambuf_type(c_default_buf_size)),
|
||||
ss_(p_sbuf_phase1_.get())
|
||||
{
|
||||
assert(p_sbuf_phase1_.get() == ss_.rdbuf());
|
||||
} /*ctor*/
|
||||
|
||||
template <typename CharT, typename Traits>
|
||||
void
|
||||
state_impl<CharT, Traits>::indent(char pad_char)
|
||||
{
|
||||
//log_streambuf * sbuf = this->p_sbuf_phase1_.get();
|
||||
|
||||
#ifdef NOT_IN_USE
|
||||
{
|
||||
char buf[80];
|
||||
::snprintf(buf, sizeof(buf), "[%02d] ", this->nesting_level_);
|
||||
|
||||
this->ss_ << buf;
|
||||
//this->p_sbuf_->sputn(buf, strlen(buf));
|
||||
}
|
||||
#endif
|
||||
|
||||
/* indent to nesting level.
|
||||
*
|
||||
* note: see also flush2sbuf(), need special indent handling for continuation lines
|
||||
* (when application sends explicit newlines to this logger)
|
||||
*/
|
||||
this->ss_ << pad(std::min(this->nesting_level_ * log_config::indent_width,
|
||||
log_config::max_indent_width),
|
||||
pad_char);
|
||||
} /*indent*/
|
||||
|
||||
template <typename CharT, typename Traits>
|
||||
void
|
||||
state_impl<CharT, Traits>::entryexit_aux(function_style style,
|
||||
std::string_view name1,
|
||||
std::string_view name2,
|
||||
EntryExit entryexit)
|
||||
{
|
||||
log_streambuf_type * sbuf = this->p_sbuf_phase1_.get();
|
||||
|
||||
sbuf->reset_stream();
|
||||
|
||||
this->check_print_time(xo::time::time::now());
|
||||
this->indent(' ');
|
||||
|
||||
char ee_label = '\0';
|
||||
std::uint32_t fn_color = 0;
|
||||
|
||||
color_encoding encoding = log_config::encoding;
|
||||
|
||||
/* mnemonic for scope entry/exit */
|
||||
switch(entryexit) {
|
||||
case EE_Entry:
|
||||
ee_label = '+';
|
||||
fn_color = log_config::function_entry_color;
|
||||
break;
|
||||
case EE_Exit:
|
||||
ee_label = '-';
|
||||
fn_color = log_config::function_exit_color;
|
||||
break;
|
||||
}
|
||||
|
||||
this->ss_ << ee_label;
|
||||
|
||||
if (log_config::nesting_level_enabled) {
|
||||
/* e.g.
|
||||
* (^[[38;5;195m7^[[0m)
|
||||
* <-----a---->b<-c->
|
||||
*
|
||||
* a = color on
|
||||
* b = level - displayed in color
|
||||
* c = color off
|
||||
*/
|
||||
this->ss_
|
||||
<< "("
|
||||
<< with_color(log_config::encoding,
|
||||
log_config::nesting_level_color,
|
||||
this->nesting_level_)
|
||||
<< ")";
|
||||
}
|
||||
|
||||
if (log_config::indent_width > 0)
|
||||
this->ss_ << ' ';
|
||||
|
||||
/* scope name - note no trailing newline; expect .preamble()/.postamble() caller to supply */
|
||||
this->ss_ << function_name(style, encoding, fn_color, name1) << name2;
|
||||
} /*entryexit_aux*/
|
||||
|
||||
template <typename CharT, typename Traits>
|
||||
void
|
||||
state_impl<CharT, Traits>::preamble(function_style style,
|
||||
std::string_view name1,
|
||||
std::string_view name2)
|
||||
{
|
||||
this->entryexit_aux(style, name1, name2, EE_Entry);
|
||||
} /*preamble*/
|
||||
|
||||
template <typename CharT, typename Traits>
|
||||
void
|
||||
state_impl<CharT, Traits>::postamble(function_style style,
|
||||
std::string_view name1,
|
||||
std::string_view name2)
|
||||
{
|
||||
this->entryexit_aux(style, name1, name2, EE_Exit);
|
||||
} /*postamble*/
|
||||
|
||||
template <typename CharT, typename Traits>
|
||||
void
|
||||
state_impl<CharT, Traits>::flush2sbuf(std::streambuf * p_sbuf)
|
||||
{
|
||||
log_streambuf_type * sbuf1 = this->p_sbuf_phase1_.get();
|
||||
log_streambuf_type * sbuf2 = this->p_sbuf_phase2_.get();
|
||||
|
||||
/* generally expecting sbuf to contain one line of output.
|
||||
* if it contains multiple newlines, need to indent
|
||||
* after each one.
|
||||
*
|
||||
* will scan output in *sbuf1, post-process to *sbuf2,
|
||||
* then write *sbuf2 to output stream
|
||||
*
|
||||
* note: we inherit .lpos from prec call to .flush2sbuf(),
|
||||
* in the unlikely event that it's non-zero
|
||||
*/
|
||||
char const * s = sbuf1->lo();
|
||||
char const * e = s + sbuf1->pos();
|
||||
|
||||
char const * p = s;
|
||||
|
||||
/* point to first space following a non-space character.
|
||||
* will indent to just after this space
|
||||
*/
|
||||
char const * space_after_nonspace = nullptr;
|
||||
|
||||
/* true on VT100 color escape (\033); in which case false on terminating char (m)
|
||||
* don't advance lpos during escape
|
||||
*/
|
||||
bool in_color_escape = false;
|
||||
|
||||
while(true) {
|
||||
bool have_nonspace = false;
|
||||
|
||||
/* invariant: s<=p<=e */
|
||||
|
||||
/* for indenting, looking for first 'space following non-space, on first line', if any */
|
||||
|
||||
std::size_t lpos_on_newline = 0;
|
||||
|
||||
while(p < e) {
|
||||
if(space_after_nonspace) {
|
||||
;
|
||||
} else {
|
||||
if(*p != ' ')
|
||||
have_nonspace = true;
|
||||
|
||||
if(have_nonspace && (*p == ' ')) {
|
||||
space_after_nonspace = p;
|
||||
}
|
||||
}
|
||||
|
||||
if (in_color_escape && (*p != '\n')) {
|
||||
/* in color escape -> don't advance .lpos */
|
||||
if (*p == 'm')
|
||||
in_color_escape = false;
|
||||
++p;
|
||||
} else if (*p == '\033') {
|
||||
/* begin color escape sequence */
|
||||
in_color_escape = true;
|
||||
++p;
|
||||
} else if (*p == '\n') {
|
||||
/* reset .pos on newline; also drop any (incomplete + ill-formed) color escape */
|
||||
|
||||
in_color_escape = false;
|
||||
|
||||
lpos_on_newline = this->lpos_;
|
||||
this->lpos_ = 0;
|
||||
|
||||
++p;
|
||||
break;
|
||||
} else {
|
||||
/* increment .lpos on non-newline */
|
||||
++(this->lpos_);
|
||||
++p;
|
||||
}
|
||||
}
|
||||
|
||||
/* p=e or *p=\n */
|
||||
|
||||
/* charseq [s,p) does not contain any newlines, print it */
|
||||
if (lpos_on_newline > 0) {
|
||||
/* charseq [s,p) does not contain any newlines, print it */
|
||||
sbuf2->sputn(s, p - s - 1);
|
||||
|
||||
if (this->location_flag_) {
|
||||
/* 'tab' to position lpos for [file:line] */
|
||||
sbuf2->sputc(' ');
|
||||
for (std::uint32_t i = lpos_on_newline + 1; i < log_config::location_tab; ++i)
|
||||
sbuf2->sputc(' ');
|
||||
|
||||
std::stringstream ss;
|
||||
ss << code_location(this->file_, this->line_,
|
||||
log_config::encoding, log_config::code_location_color);
|
||||
|
||||
std::string ss_str = std::move(ss.str()); /*c++20*/
|
||||
sbuf2->sputn(ss_str.c_str(), ss_str.size());
|
||||
|
||||
this->location_flag_ = false;
|
||||
this->file_ = "";
|
||||
this->line_ = 0;
|
||||
}
|
||||
|
||||
sbuf2->sputc('\n');
|
||||
} else {
|
||||
/* control here if .flush2sbuf() called without trailing newline in .p_sbuf_phase1 */
|
||||
sbuf2->sputn(s, p - s);
|
||||
}
|
||||
|
||||
if (p == e)
|
||||
break;
|
||||
|
||||
// {
|
||||
// char buf[80];
|
||||
// snprintf(buf, sizeof(buf), "*** indent=[%d] next=[%c]", this->nesting_level_, *(p+1));
|
||||
//
|
||||
// std::clog.rdbuf()->sputn(buf, strlen(buf));
|
||||
//}
|
||||
|
||||
/* control here only for continuation lines (application logging code embedding its own newlines)
|
||||
* - minimum indent = nesting level;
|
||||
* - however if space_after_nonspace defined, also indent for that
|
||||
*/
|
||||
std::uint32_t n_indent = 0;
|
||||
|
||||
n_indent += this->calc_time_indent();
|
||||
|
||||
n_indent += std::min(this->nesting_level_ * log_config::indent_width,
|
||||
log_config::max_indent_width);
|
||||
|
||||
/* this is just to indent for per-line entry/exit label */
|
||||
if(space_after_nonspace)
|
||||
n_indent += (space_after_nonspace - s);
|
||||
|
||||
for(std::uint32_t i = 0; i < n_indent; ++i)
|
||||
sbuf2->sputc(' ');
|
||||
|
||||
s = p;
|
||||
}
|
||||
|
||||
/* now write entire contents of *sbuf2 to clog */
|
||||
p_sbuf->sputn(sbuf2->lo(), sbuf2->pos());
|
||||
|
||||
/* reset streams for next message */
|
||||
this->reset_stream();
|
||||
} /*flush2sbuf*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end log_state.hpp */
|
||||
123
include/indentlog/log_streambuf.hpp
Normal file
123
include/indentlog/log_streambuf.hpp
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
/* @file log_streambuf.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <cstring> // e.g. for std::memcpy()
|
||||
#include <cstdint>
|
||||
#include <cassert>
|
||||
|
||||
namespace xo {
|
||||
/* recycling buffer for logging.
|
||||
* write to self-extending storage array;
|
||||
*/
|
||||
template <typename CharT, typename Traits>
|
||||
class log_streambuf : public std::streambuf {
|
||||
public:
|
||||
log_streambuf(std::uint32_t buf_z) {
|
||||
this->buf_v_.resize(buf_z);
|
||||
this->reset_stream();
|
||||
} /*ctor*/
|
||||
|
||||
std::streamsize capacity() const { return this->buf_v_.size(); }
|
||||
char const * lo() const { return this->pbase(); }
|
||||
char const * hi() const { return this->lo() + this->capacity(); }
|
||||
std::uint32_t pos() const { return this->pptr() - this->pbase(); }
|
||||
|
||||
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);
|
||||
} /*reset_stream*/
|
||||
|
||||
protected:
|
||||
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());
|
||||
|
||||
#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=" << quoted(string_view(s, n)) << ", n=" << n << std::endl;
|
||||
|
||||
if (this->pptr() + n > this->hi()) {
|
||||
n = this->hi() - this->pptr();
|
||||
std::memcpy(this->pptr(), s, n);
|
||||
} else {
|
||||
std::memcpy(this->pptr(), s, n);
|
||||
}
|
||||
this->pbump(n);
|
||||
|
||||
return n;
|
||||
} /*xsputn*/
|
||||
|
||||
virtual int_type
|
||||
overflow(int_type new_ch) override
|
||||
{
|
||||
char * old_pptr = this->pptr();
|
||||
std::streamsize old_n = old_pptr - this->pbase();
|
||||
|
||||
assert(old_n <= static_cast<std::streamsize>(this->buf_v_.size()));
|
||||
|
||||
std::size_t new_z = 2 * this->buf_v_.size();
|
||||
|
||||
this->buf_v_.resize(new_z);
|
||||
this->buf_v_[old_n] = new_ch;
|
||||
|
||||
/* '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();
|
||||
|
||||
this->setp(p_base, p_hi);
|
||||
this->pbump(old_n + 1);
|
||||
|
||||
return new_ch;
|
||||
} /*overflow*/
|
||||
|
||||
/* off. offset, relative to starting point dir.
|
||||
* dir.
|
||||
* which. in|out|both
|
||||
*/
|
||||
virtual pos_type seekoff(off_type off,
|
||||
std::ios_base::seekdir dir,
|
||||
std::ios_base::openmode which) override {
|
||||
//std::cout << "seekoff: off=" << off << ", dir=" << dir << ", which=" << which << std::endl;
|
||||
|
||||
// Only output stream is supported
|
||||
if (which != std::ios_base::out)
|
||||
throw std::runtime_error("log_streambuf: only output mode supported");
|
||||
|
||||
if (dir == std::ios_base::cur) {
|
||||
this->pbump(off);
|
||||
} else if (dir == std::ios_base::end) {
|
||||
/* .setp(): using for side effect: sets .pptr to .pbase */
|
||||
this->setp(this->pbase(), this->epptr());
|
||||
this->pbump(off);
|
||||
} else if (dir == std::ios_base::beg) {
|
||||
/* .setp(): using for side effect: sets .pptr to .pbase */
|
||||
this->setp(this->pbase(), this->epptr());
|
||||
this->pbump(this->capacity() + off);
|
||||
}
|
||||
|
||||
return this->pptr() - this->pbase();
|
||||
} /*seekoff*/
|
||||
|
||||
private:
|
||||
/* buffered output stored here */
|
||||
std::vector<char> buf_v_;
|
||||
}; /*log_streambuf*/
|
||||
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end log_streambuf.hpp */
|
||||
52
include/indentlog/pad.hpp
Normal file
52
include/indentlog/pad.hpp
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
/* @file pad.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
|
||||
namespace xo {
|
||||
/* use:
|
||||
* ostream os = ...;
|
||||
*
|
||||
* 1.
|
||||
* os << ":" << pad(8) << ":"
|
||||
*
|
||||
* writes
|
||||
* : :
|
||||
*
|
||||
* 2.
|
||||
* os << pad(16, '-')
|
||||
*
|
||||
* writes
|
||||
* ----------------
|
||||
*
|
||||
* on os
|
||||
*/
|
||||
class pad_impl {
|
||||
public:
|
||||
pad_impl(std::uint32_t n, char pad_char) : n_pad_{n}, pad_char_{pad_char} {}
|
||||
|
||||
std::uint32_t n_pad() const { return n_pad_; }
|
||||
char pad_char() const { return pad_char_; }
|
||||
|
||||
private:
|
||||
std::uint32_t n_pad_ = 0;
|
||||
char pad_char_ = '\0';
|
||||
}; /*pad_impl*/
|
||||
|
||||
inline pad_impl
|
||||
pad(std::uint32_t n, char pad_char = ' ') { return pad_impl(n, pad_char); }
|
||||
|
||||
inline std::ostream &
|
||||
operator<<(std::ostream & s,
|
||||
pad_impl const & pad)
|
||||
{
|
||||
for(std::uint32_t i=0; i<pad.n_pad(); ++i)
|
||||
s << pad.pad_char();
|
||||
return s;
|
||||
} /*operator<<*/
|
||||
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end pad.hpp */
|
||||
28
include/indentlog/printer.hpp
Normal file
28
include/indentlog/printer.hpp
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/* @file printer.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace xo {
|
||||
namespace print {
|
||||
/* print an event to a logfile
|
||||
* intended to be usable as EventSink argument
|
||||
* to RealizationSimSource<T, EventSink>
|
||||
*/
|
||||
template<typename T, typename Stream>
|
||||
class printer {
|
||||
public:
|
||||
printer(Stream && os) : os_{std::move(os)} {}
|
||||
|
||||
void operator()(T const & x) {
|
||||
this->os_ << x;
|
||||
}
|
||||
|
||||
private:
|
||||
Stream os_;
|
||||
}; /*printer*/
|
||||
} /*namespace print*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end printer.hpp */
|
||||
147
include/indentlog/quoted.hpp
Normal file
147
include/indentlog/quoted.hpp
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
/* file quoted.hpp
|
||||
*
|
||||
* author: Roland Conybeare, Sep 2022
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "nestlog/tostr.hpp"
|
||||
#include <sstream>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
|
||||
namespace xo {
|
||||
namespace print {
|
||||
/* use this to avoid template conversion hassles
|
||||
* since literal strings get treated as arrays
|
||||
*/
|
||||
template<typename T>
|
||||
char const * ccs(T x) { return x; }
|
||||
|
||||
/* Printing cases:
|
||||
* 1. T&&:
|
||||
* move into quoted_impl<T>. T must be moveable!
|
||||
* 2. T&:
|
||||
* copy reference into quoted_impl<T&>.
|
||||
* similarly for T const &, copy reference into quoted_impl<T const &>
|
||||
*/
|
||||
|
||||
template<typename T>
|
||||
class quoted_impl {
|
||||
public:
|
||||
quoted_impl(bool unq_flag, T const & x) : unq_flag_{unq_flag}, value_{x} {}
|
||||
quoted_impl(bool unq_flag, T && x) : unq_flag_{unq_flag}, value_{std::move(x)} {}
|
||||
|
||||
bool unq_flag() const { return unq_flag_; }
|
||||
T const & value() const { return value_; }
|
||||
|
||||
void print(std::ostream & os) const {
|
||||
std::string xs = xo::tostr(value_);
|
||||
|
||||
if (xs.empty()) {
|
||||
/* print empty string as "" */
|
||||
os << "\"\"";
|
||||
} else if ((xs.at(0) == '<') && (xs.at(xs.size() - 1) == '>')) {
|
||||
/* assume string represents output of a well-formed object printer,
|
||||
* and already self-escapes
|
||||
*/
|
||||
os << xs;
|
||||
} else if (xs.find_first_of(" \"\n\r\\") == std::string::npos) {
|
||||
/* no escapes needed, just print xs */
|
||||
if (unq_flag_)
|
||||
os << xs;
|
||||
else
|
||||
os << "\"" << xs << "\"";
|
||||
} else {
|
||||
/* printed value contains a space
|
||||
* and/or a must-be-escaped character.
|
||||
* in any case, need quotes
|
||||
*/
|
||||
|
||||
os << "\"";
|
||||
|
||||
/* print contents of ss, with escapes:
|
||||
* \ => \\
|
||||
* " => \"
|
||||
* newline => \n
|
||||
* cr => \r
|
||||
*/
|
||||
for (char ch : xs) {
|
||||
switch (ch) {
|
||||
case '"':
|
||||
/* " => \" */
|
||||
os << "\\\"";
|
||||
break;
|
||||
case '\n':
|
||||
/* newline -> \n */
|
||||
os << "\\\n";
|
||||
break;
|
||||
case '\r':
|
||||
/* cr -> \r */
|
||||
os << "\\\r";
|
||||
break;
|
||||
case '\\':
|
||||
/* \ => \\ (mind c++ requires we escape \) */
|
||||
os << "\\\\";
|
||||
break;
|
||||
default:
|
||||
os << ch;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
os << "\"";
|
||||
}
|
||||
} /*print*/
|
||||
|
||||
private:
|
||||
/* .unq_flag: if true, omit surrounding " chars
|
||||
* if printed value satisfies both:
|
||||
* - no escaped chars
|
||||
* - no spaces
|
||||
*/
|
||||
bool unq_flag_ = false;
|
||||
/* .value: value to be printed */
|
||||
T value_;
|
||||
}; /*quoted_impl*/
|
||||
|
||||
template<typename T>
|
||||
std::ostream &
|
||||
operator<<(std::ostream & os, quoted_impl<T> const & x) {
|
||||
x.print(os);
|
||||
return os;
|
||||
} /*operator*/
|
||||
|
||||
/* writing out std::forward<T> behavior for completeness' sake:
|
||||
*
|
||||
* 1. call quoted(x) with rvalue std::string x, then:
|
||||
* - T will be deduced to [std::string]
|
||||
* (in particular: _not_ std::string &, std::string const &, std::string &&)
|
||||
* - rvalue std::string passed to quoted_impl ctor
|
||||
*
|
||||
* 2a. call quoted(x) with std::string & x, then:
|
||||
* - T deduced to [std::string &]
|
||||
* - std::string & passed to quoted_impl ctor
|
||||
*
|
||||
* 2b. call quoted(x) with std::string const & x, then:
|
||||
* - T deduced to [std::string const &]
|
||||
* - std::string const & passed to quoted_impl ctor
|
||||
*/
|
||||
template<typename T>
|
||||
auto quoted(T && x) {
|
||||
return quoted_impl(false /*unq_flag*/, std::forward<T>(x));
|
||||
}
|
||||
|
||||
inline auto qcstr(char const * x) {
|
||||
return quoted(x);
|
||||
} /*qcstr*/
|
||||
|
||||
template<typename T>
|
||||
auto unq(T && x) {
|
||||
return quoted_impl(true /*unq_flag*/, std::forward<T>(x));
|
||||
}
|
||||
} /*namespace print*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end quoted.hpp */
|
||||
43
include/indentlog/quoted_char.hpp
Normal file
43
include/indentlog/quoted_char.hpp
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
/* @file quoted_char.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace xo {
|
||||
template<typename CharT>
|
||||
class quoted_char {
|
||||
public:
|
||||
quoted_char(CharT ch) : ch_{ch} {}
|
||||
|
||||
void print(std::ostream & os) const {
|
||||
switch(ch_) {
|
||||
case '\033':
|
||||
os << "\\033";
|
||||
break;
|
||||
case '\n':
|
||||
os << "\\n";
|
||||
break;
|
||||
case '\r':
|
||||
os << "\\r";
|
||||
break;
|
||||
default:
|
||||
os << ch_;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
CharT ch_;
|
||||
}; /*quoted_char*/
|
||||
|
||||
template<typename CharT>
|
||||
inline std::ostream &
|
||||
operator<<(std::ostream & os, quoted_char<CharT> const & x) {
|
||||
x.print(os);
|
||||
return os;
|
||||
} /*operator<<*/
|
||||
|
||||
} /*namespace xo*/
|
||||
|
||||
|
||||
/* end quoted_char.hpp */
|
||||
297
include/indentlog/scope.hpp
Normal file
297
include/indentlog/scope.hpp
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
/* @file scope.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "log_state.hpp"
|
||||
#include "filename.hpp"
|
||||
#include "tostr.hpp"
|
||||
#include "tag.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <cstdint>
|
||||
#include <memory> // for std::unique_ptr
|
||||
|
||||
namespace xo {
|
||||
|
||||
template <typename ChartT, typename Traits>
|
||||
class state_impl;
|
||||
|
||||
# define XO_ENTER0(lvl) xo::scope_setup(xo::log_level::lvl, xo::log_config::style, __PRETTY_FUNCTION__, __FILE__, __LINE__)
|
||||
# define XO_ENTER1(lvl, debug_flag) XO_ENTER2(lvl, debug_flag, __PRETTY_FUNCTION__)
|
||||
# define XO_ENTER2(lvl, debug_flag, name1) xo::scope_setup((debug_flag ? xo::log_level::lvl : xo::log_level::never), xo::log_config::style, name1, __FILE__, __LINE__)
|
||||
|
||||
# define XO_DEBUG(debug_flag) XO_ENTER1(always, debug_flag)
|
||||
# define XO_DEBUG2(debug_flag, name1) XO_ENTER2(always, debug_flag, name1)
|
||||
|
||||
# define XO_LITERAL(lvl, name1, name2) xo::scope_setup(xo::log_level::lvl, FS_Literal, name1, name2, __FILE__, __LINE__)
|
||||
|
||||
//# define XO_SSETUP0() xo::scope_setup(__FUNCTION__)
|
||||
//# define XO_SSETUP0(lvl) xo::scope_setup(xo::log_level::lvl, xo::log_config::style, __PRETTY_FUNCTION__, __FILE__, __LINE__)
|
||||
|
||||
/* 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__))
|
||||
/* 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
|
||||
*/
|
||||
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)
|
||||
: log_level_{level}, style_{style}, name1_{name1}, name2_{name2}, file_{file}, line_{line} {}
|
||||
scope_setup(log_level level, function_style style,
|
||||
std::string_view name1, std::string_view file, std::uint32_t line)
|
||||
: scope_setup(level, style, name1, "" /*name2*/, file, line) {}
|
||||
|
||||
bool is_enabled() const { return (this->log_level_ >= log_config::min_log_level); }
|
||||
|
||||
//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 */
|
||||
log_level log_level_ = log_level::error;
|
||||
/* FS_Pretty | FS_Streamlined | FS_Simple */
|
||||
function_style style_ = FS_Pretty;
|
||||
std::string_view name1_ = "<.name1>";
|
||||
std::string_view name2_ = "<.name2>";
|
||||
/* __FILE__ */
|
||||
std::string_view file_ = "<.file>";
|
||||
/* __LINE__ */
|
||||
std::uint32_t line_ = 0;
|
||||
}; /*scope_setup*/
|
||||
|
||||
/* nesting logger
|
||||
*
|
||||
* Use:
|
||||
* using xo::scope;
|
||||
*
|
||||
* void myfunc() {
|
||||
* XO_SCOPE(log); //or scope x("myfunc")
|
||||
* log(a,b,c);
|
||||
* anotherfunc();
|
||||
* log(d,e,f);
|
||||
* }
|
||||
*
|
||||
* void anotherfunc() {
|
||||
* XO_SCOPE(x); // or scope x("anotherfunc")
|
||||
* x.log(y);
|
||||
* }
|
||||
*
|
||||
* or:
|
||||
* void myfunc() {
|
||||
* bool log_flag = true;
|
||||
* XO_SCOPE2(log, log_flag); // create local variable 'log'
|
||||
* log && log(a,b,c); // log iff enabled
|
||||
* log.end_scope(); // optional protection against compiler destroying 'log' early
|
||||
* }
|
||||
*
|
||||
* output like:
|
||||
* +myfunc:
|
||||
* a,b,c
|
||||
* +anotherfunc:
|
||||
* y
|
||||
* -anotherfunc:
|
||||
* d,e,f
|
||||
* -myfunc:
|
||||
*/
|
||||
template <typename CharT, typename Traits = std::char_traits<CharT>>
|
||||
class basic_scope {
|
||||
public:
|
||||
using state_impl_type = state_impl<CharT, Traits>;
|
||||
|
||||
public:
|
||||
//basic_scope(std::string_view name1, bool enabled_flag);
|
||||
template <typename... Tn>
|
||||
basic_scope(scope_setup setup, Tn&&... rest);
|
||||
~basic_scope();
|
||||
|
||||
bool enabled() const { return !finalized_; }
|
||||
|
||||
operator bool() const { return this->enabled(); }
|
||||
|
||||
/* report current nesting level */
|
||||
std::uint32_t nesting_level() const;
|
||||
|
||||
void set_dest_sbuf(std::streambuf * x) { this->dest_sbuf_ = x; }
|
||||
|
||||
template<typename... Tn>
|
||||
bool log(Tn&&... rest) {
|
||||
if(this->finalized_) {
|
||||
throw std::runtime_error("basic_scope: attempt to use finalized scope");
|
||||
} else {
|
||||
state_impl_type * logstate = require_indent_thread_local_state();
|
||||
|
||||
/* 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)...);
|
||||
|
||||
this->flush2sbuf(logstate);
|
||||
}
|
||||
|
||||
return true;
|
||||
} /*log*/
|
||||
|
||||
template<typename... Tn>
|
||||
bool operator()(Tn&&... args) { return this->log(std::forward<Tn>(args)...); }
|
||||
|
||||
/* call once to end scope before dtor */
|
||||
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ẹ
|
||||
*/
|
||||
static state_impl_type * require_indent_thread_local_state();
|
||||
|
||||
/* establish logging state; use thread-local storage for threadsafety */
|
||||
static state_impl_type * require_thread_local_state();
|
||||
|
||||
/* retrieve permanently-associated ostream for logging-state */
|
||||
static std::ostream & logstate2stream(state_impl_type * logstate);
|
||||
|
||||
/* write collected output to std::clog, or chosen streambuf */
|
||||
void flush2sbuf(state_impl_type * logstate);
|
||||
|
||||
private:
|
||||
/* keep logging state separately for each thread */
|
||||
static thread_local std::unique_ptr<state_impl_type> s_threadlocal_state;
|
||||
|
||||
/* send indented output to this streambuf (e.g. std::clog.rdbuf()) */
|
||||
std::streambuf * dest_sbuf_ = std::clog.rdbuf();
|
||||
/* style for displaying .name1 */
|
||||
function_style style_ = FS_Pretty;
|
||||
/* name of this scope (part 1) */
|
||||
std::string_view name1_ = "<name1>";
|
||||
/* name of this scope (part 2) */
|
||||
std::string_view name2_ = "::<name2>";
|
||||
/* captured value of __FILE__ */
|
||||
std::string_view file_ = "<file>";
|
||||
/* captured value of __LINE__ */
|
||||
std::uint32_t line_ = 0;
|
||||
/* set once per scope .finalized=true <-> logging disabled */
|
||||
bool finalized_ = false;
|
||||
}; /*basic_scope*/
|
||||
|
||||
template <typename CharT, typename Traits>
|
||||
template <typename... Tn>
|
||||
basic_scope<CharT, Traits>::basic_scope(scope_setup setup, Tn&&... args)
|
||||
|
||||
: style_{setup.style_},
|
||||
name1_{std::move(setup.name1_)},
|
||||
name2_{std::move(setup.name2_)},
|
||||
file_{std::move(setup.file_)},
|
||||
line_{setup.line_},
|
||||
finalized_{!(setup.is_enabled())}
|
||||
{
|
||||
if(setup.is_enabled()) {
|
||||
state_impl_type * logstate = basic_scope::require_thread_local_state();
|
||||
std::ostream & os = logstate2stream(logstate);
|
||||
|
||||
logstate->preamble(this->style_, this->name1_, this->name2_);
|
||||
|
||||
tosn(os, " ", std::forward<Tn>(args)...);
|
||||
|
||||
if (log_config::location_enabled) {
|
||||
/* prints on next call to flush2sbuf */
|
||||
logstate->set_location(this->file_, this->line_);
|
||||
//tosn(os, " [", basename(this->file_), ":", this->line_, "]");
|
||||
}
|
||||
|
||||
logstate->flush2sbuf(std::clog.rdbuf());
|
||||
|
||||
///* next call to scope::log() can reset to beginning of buffer space */
|
||||
//logstate->ss().seekp(0);
|
||||
|
||||
logstate->incr_nesting();
|
||||
}
|
||||
} /*ctor*/
|
||||
|
||||
template <typename CharT, typename Traits>
|
||||
basic_scope<CharT, Traits>::~basic_scope() {
|
||||
if(!this->finalized_)
|
||||
this->end_scope();
|
||||
} /*dtor*/
|
||||
|
||||
template <typename CharT, typename Traits>
|
||||
thread_local std::unique_ptr<state_impl<CharT, Traits>>
|
||||
basic_scope<CharT, Traits>::s_threadlocal_state;
|
||||
|
||||
template <typename CharT, typename Traits>
|
||||
std::uint32_t
|
||||
basic_scope<CharT, Traits>::nesting_level() const {
|
||||
return require_thread_local_state()->nesting_level();
|
||||
} /*nesting_level*/
|
||||
|
||||
template <typename CharT, typename Traits>
|
||||
basic_scope<CharT, Traits>::state_impl_type *
|
||||
basic_scope<CharT, Traits>::require_indent_thread_local_state()
|
||||
{
|
||||
state_impl_type * local_state = require_thread_local_state();
|
||||
|
||||
local_state->reset_stream();
|
||||
local_state->indent(' ' /*pad_char*/);
|
||||
|
||||
return local_state;
|
||||
} /*require_thread_local_stream*/
|
||||
|
||||
template <typename CharT, typename Traits>
|
||||
basic_scope<CharT, Traits>::state_impl_type *
|
||||
basic_scope<CharT, Traits>::require_thread_local_state()
|
||||
{
|
||||
if(!s_threadlocal_state) {
|
||||
s_threadlocal_state.reset(new state_impl_type());
|
||||
}
|
||||
|
||||
return s_threadlocal_state.get();
|
||||
} /*require_thread_local_state*/
|
||||
|
||||
template <typename CharT, typename Traits>
|
||||
std::ostream &
|
||||
basic_scope<CharT, Traits>::logstate2stream(state_impl_type * logstate)
|
||||
{
|
||||
return logstate->ss();
|
||||
} /*logstate2stream*/
|
||||
|
||||
template <typename CharT, typename Traits>
|
||||
void
|
||||
basic_scope<CharT, Traits>::flush2sbuf(state_impl_type * logstate)
|
||||
{
|
||||
logstate->flush2sbuf(this->dest_sbuf_);
|
||||
} /*flush2sbuf*/
|
||||
|
||||
template <typename CharT, typename Traits>
|
||||
template <typename... Tn>
|
||||
void
|
||||
basic_scope<CharT, Traits>::end_scope(Tn&&... args)
|
||||
{
|
||||
if(!this->finalized_) {
|
||||
this->finalized_ = true;
|
||||
|
||||
state_impl_type * logstate
|
||||
= basic_scope::require_thread_local_state();
|
||||
|
||||
logstate->decr_nesting();
|
||||
|
||||
logstate->postamble(this->style_, this->name1_, this->name2_);
|
||||
|
||||
tosn(logstate2stream(logstate), " ", std::forward<Tn>(args)...);
|
||||
|
||||
logstate->flush2sbuf(std::clog.rdbuf());
|
||||
}
|
||||
} /*end_scope*/
|
||||
|
||||
|
||||
using scope = basic_scope<char>;
|
||||
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end scope.hpp */
|
||||
106
include/indentlog/tag.hpp
Normal file
106
include/indentlog/tag.hpp
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
/* @file tag.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "tag_config.hpp"
|
||||
#include "concat.hpp"
|
||||
#include "quoted.hpp"
|
||||
#include "color.hpp"
|
||||
#include <iostream>
|
||||
|
||||
// STRINGIFY(xyz) -> "xyz"
|
||||
#define STRINGIFY(x) #x
|
||||
|
||||
// TAG(xyz) -> tag("xyz", xyz)
|
||||
#define TAG(x) xo::make_tag(STRINGIFY(x), x)
|
||||
#define TAG2(x, y) xo::make_tag(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>
|
||||
struct tag_impl {
|
||||
tag_impl(Name const & n, Value const & v)
|
||||
: name_{n}, value_{v} {}
|
||||
tag_impl(Name && n, Value && v)
|
||||
: name_{std::move(n)}, value_{std::move(v)} {}
|
||||
|
||||
Name const & name() const { return name_; }
|
||||
Value const & value() const { return value_; }
|
||||
|
||||
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*/
|
||||
|
||||
template<typename Name, typename Value>
|
||||
tag_impl<true, Name, Value>
|
||||
xtag(Name && n, Value && v)
|
||||
{
|
||||
return tag_impl<true, Name, Value>(n, v);
|
||||
} /*xtag*/
|
||||
|
||||
template<typename Value>
|
||||
tag_impl<true, char const *, Value>
|
||||
xtag(char const * n, Value && v) {
|
||||
return tag_impl<true, char const *, 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*/
|
||||
|
||||
template<typename Name, typename Value>
|
||||
tag_impl<false, Name, Value>
|
||||
tag(Name && n, Value && v)
|
||||
{
|
||||
return tag_impl<false, Name, Value>(n, v);
|
||||
} /*tag*/
|
||||
|
||||
template<typename Value>
|
||||
tag_impl<false, char const *, Value>
|
||||
tag(char const * n, Value && v)
|
||||
{
|
||||
return tag_impl<false, char const *, Value>(n, v);
|
||||
} /*tag*/
|
||||
|
||||
template <bool PrefixSpace, typename Name, typename Value>
|
||||
inline std::ostream &
|
||||
operator<<(std::ostream &s,
|
||||
tag_impl<PrefixSpace, Name, Value> const & tag)
|
||||
{
|
||||
using xo::print::unq;
|
||||
|
||||
if(PrefixSpace)
|
||||
s << " ";
|
||||
|
||||
s << with_color(tag_config::encoding, tag_config::tag_color, concat((char const *)":", tag.name()))
|
||||
<< " " << unq(tag.value());
|
||||
|
||||
return s;
|
||||
} /*operator<<*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end tag.hpp */
|
||||
34
include/indentlog/tag_config.hpp
Normal file
34
include/indentlog/tag_config.hpp
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/* @file tag_config.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "color.hpp"
|
||||
#include <cstdint>
|
||||
|
||||
namespace xo {
|
||||
/* Tag here b/c we want header-only library */
|
||||
template <typename Tag>
|
||||
struct tag_config_impl {
|
||||
/* color encoding */
|
||||
static color_encoding encoding;
|
||||
/* color to use for tags
|
||||
* os << tag("foo", foovalue)
|
||||
* to produces output like
|
||||
* :foo foovalue
|
||||
* with :foo using .tag_color
|
||||
*/
|
||||
static std::uint32_t tag_color;
|
||||
}; /*tag_config_impl*/
|
||||
|
||||
template <typename Tag>
|
||||
color_encoding
|
||||
tag_config_impl<Tag>::encoding = CE_Xterm;
|
||||
|
||||
template <typename Tag>
|
||||
std::uint32_t
|
||||
tag_config_impl<Tag>::tag_color = 245;
|
||||
|
||||
using tag_config = tag_config_impl<class tag_config_tag>;
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end tag_config.hpp */
|
||||
362
include/indentlog/time.hpp
Normal file
362
include/indentlog/time.hpp
Normal file
|
|
@ -0,0 +1,362 @@
|
|||
/* @file time.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#ifdef NOT_YET
|
||||
# include <format>
|
||||
#endif
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <time.h>
|
||||
|
||||
namespace xo {
|
||||
namespace time {
|
||||
|
||||
using utc_nanos = std::chrono::time_point<std::chrono::system_clock,
|
||||
std::chrono::nanoseconds>;
|
||||
|
||||
using nanos = std::chrono::nanoseconds;
|
||||
using microseconds = std::chrono::microseconds;
|
||||
using milliseconds = std::chrono::milliseconds;
|
||||
using seconds = std::chrono::seconds;
|
||||
using hours = std::chrono::hours;
|
||||
using days = std::chrono::days;
|
||||
|
||||
struct time {
|
||||
static utc_nanos now() {
|
||||
return utc_nanos(std::chrono::system_clock::now());
|
||||
}
|
||||
|
||||
static utc_nanos epoch() {
|
||||
return utc_nanos(std::chrono::system_clock::from_time_t(0));
|
||||
} /*epoch*/
|
||||
|
||||
static utc_nanos ymd_hms(uint32_t ymd, uint32_t hms) {
|
||||
/* e.g. ymd=20220610 -> n_yr=2022, n_mon=06, n_dy=10 */
|
||||
|
||||
uint32_t n_yr = ymd / 10000;
|
||||
uint32_t n_mon = (ymd % 10000) / 100;
|
||||
uint32_t n_dy = ymd % 100;
|
||||
|
||||
uint32_t n_hr = hms / 10000;
|
||||
uint32_t n_min = (hms % 10000) / 100;
|
||||
uint32_t n_sec = hms % 100;
|
||||
|
||||
struct tm t;
|
||||
|
||||
t.tm_year = n_yr - 1900; /* 0 means 1900 */
|
||||
t.tm_mon = n_mon - 1; /* 0 means january */
|
||||
t.tm_mday = n_dy;
|
||||
|
||||
t.tm_hour = n_hr; /* 24 hour clock */
|
||||
t.tm_min = n_min;
|
||||
t.tm_sec = n_sec;
|
||||
|
||||
/* time since epoch */
|
||||
time_t epoch_time = timegm(&t);
|
||||
|
||||
return std::chrono::system_clock::from_time_t(epoch_time);
|
||||
} /*ymd_hms*/
|
||||
|
||||
/* midnight UTC on date ymd.
|
||||
* e.g. ymd_midnight(20220707) -> midnight UTC on 7jul22
|
||||
*/
|
||||
static utc_nanos ymd_midnight(uint32_t ymd) {
|
||||
return ymd_hms(ymd, 0);
|
||||
} /*ymd_midnight*/
|
||||
|
||||
static utc_nanos ymd_hms_usec(uint32_t ymd, uint32_t hms, uint32_t usec) {
|
||||
utc_nanos s = ymd_hms(ymd, hms);
|
||||
|
||||
return s + microseconds(usec);
|
||||
} /*ymd_hms_usec*/
|
||||
|
||||
/* .first: UTC midnight on same calendar day as t0
|
||||
* .second: elapsed time from .first to t0 (i.e. UTC time-of-day for t0)
|
||||
*/
|
||||
static std::pair<utc_nanos, nanos> utc_split_vs_midnight(utc_nanos t0) {
|
||||
using xo::time::microseconds;
|
||||
using xo::time::utc_nanos;
|
||||
|
||||
/* use yyyymmdd.hh:mm:ss.nnnnnn */
|
||||
|
||||
time_t t0_time_t = (std::chrono::system_clock::to_time_t
|
||||
(std::chrono::time_point_cast<xo::time::microseconds>(t0)));
|
||||
|
||||
/* convert to std::tm,
|
||||
* only provides 1-second precision
|
||||
*/
|
||||
std::tm t0_tm;
|
||||
::gmtime_r(&t0_time_t, &t0_tm);
|
||||
|
||||
/* midnight on the same calendar day as t0_tm */
|
||||
std::tm midnight_tm = t0_tm;
|
||||
{
|
||||
midnight_tm.tm_hour = 0;
|
||||
midnight_tm.tm_min = 0;
|
||||
midnight_tm.tm_sec = 0;
|
||||
}
|
||||
|
||||
/* convert to UTC epoch seconds */
|
||||
time_t midnight_time_t = ::timegm(&midnight_tm);
|
||||
|
||||
utc_nanos t0_midnight =
|
||||
(std::chrono::time_point_cast<xo::time::microseconds>(
|
||||
std::chrono::system_clock::from_time_t(midnight_time_t)));
|
||||
|
||||
nanos t0_tdy = t0 - t0_midnight;
|
||||
|
||||
return std::pair<utc_nanos, nanos>(t0_midnight, t0_tdy);
|
||||
} /*utc_split_vs_midnight*/
|
||||
|
||||
/* .first: LOCAL midnight on same calendar day as t0 (but in UTC coords)
|
||||
* .second: elapsed time from .first to t0 (i.e. LOCAL time-of-day for t0)
|
||||
*/
|
||||
static std::pair<utc_nanos, nanos> local_split_vs_midnight(utc_nanos t0) {
|
||||
using xo::time::microseconds;
|
||||
using xo::time::utc_nanos;
|
||||
|
||||
/* use yyyymmdd.hh:mm:ss.nnnnnn */
|
||||
|
||||
time_t t0_time_t = (std::chrono::system_clock::to_time_t
|
||||
(std::chrono::time_point_cast<xo::time::microseconds>(t0)));
|
||||
|
||||
/* convert to std::tm,
|
||||
* only provides 1-second precision
|
||||
*/
|
||||
std::tm t0_tm;
|
||||
::localtime_r(&t0_time_t, &t0_tm);
|
||||
|
||||
/* midnight on the same calendar day as t0_tm */
|
||||
std::tm midnight_tm = t0_tm;
|
||||
{
|
||||
midnight_tm.tm_hour = 0;
|
||||
midnight_tm.tm_min = 0;
|
||||
midnight_tm.tm_sec = 0;
|
||||
}
|
||||
|
||||
/* convert local midnight to UTC epoch seconds */
|
||||
time_t midnight_time_t = ::timelocal(&midnight_tm);
|
||||
|
||||
utc_nanos t0_midnight =
|
||||
(std::chrono::time_point_cast<xo::time::microseconds>(
|
||||
std::chrono::system_clock::from_time_t(midnight_time_t)));
|
||||
|
||||
nanos t0_tdy = t0 - t0_midnight;
|
||||
|
||||
return std::pair<utc_nanos, nanos>(t0_midnight, t0_tdy);
|
||||
} /*local_split_vs_midnight*/
|
||||
|
||||
/* split utc_nanos into
|
||||
* std::tm
|
||||
* .tm_year
|
||||
* .tm_mon (1-12)
|
||||
* .tm_mday (1-31)
|
||||
* .tm_hour (0-23)
|
||||
* .tm_min (0-59)
|
||||
* .tm_sec (0-59)
|
||||
* .tm_wday (0=sunday .. 6=saturday)
|
||||
* .tm_yday (0=1jan .. 365)
|
||||
* .tm_isdst (daylight savings time flag)
|
||||
* usec (0-999999)
|
||||
*/
|
||||
static std::pair<std::tm, uint32_t> split_tm(utc_nanos t0) {
|
||||
using xo::time::microseconds;
|
||||
using xo::time::utc_nanos;
|
||||
|
||||
/* use yyyymmdd.hh:mm:ss.nnnnnn */
|
||||
|
||||
time_t t0_time_t = (std::chrono::system_clock::to_time_t
|
||||
(std::chrono::time_point_cast<xo::time::microseconds>(t0)));
|
||||
|
||||
/* convert to std::tm, un UTC coords,
|
||||
* only provides 1-second precision
|
||||
*/
|
||||
std::tm t0_tm;
|
||||
::gmtime_r(&t0_time_t, &t0_tm);
|
||||
|
||||
/* midnight on the same calendar day as t0_tm */
|
||||
std::tm midnight_tm = t0_tm;
|
||||
|
||||
midnight_tm.tm_isdst = 0;
|
||||
midnight_tm.tm_hour = 0;
|
||||
midnight_tm.tm_min = 0;
|
||||
midnight_tm.tm_sec = 0;
|
||||
|
||||
/* convert back to epoch seconds */
|
||||
time_t midnight_time_t = ::mktime(&midnight_tm);
|
||||
|
||||
utc_nanos t0_midnight =
|
||||
(std::chrono::time_point_cast<xo::time::microseconds>(
|
||||
std::chrono::system_clock::from_time_t(midnight_time_t)));
|
||||
|
||||
uint32_t usec =
|
||||
(std::chrono::duration_cast<microseconds>(
|
||||
std::chrono::hh_mm_ss(t0 - t0_midnight).subseconds()))
|
||||
.count();
|
||||
|
||||
return std::make_pair(t0_tm, usec);
|
||||
} /*split_tm*/
|
||||
|
||||
static void print_hms_msec(nanos dt, std::ostream & os) {
|
||||
/* use hhmmss.nnn */
|
||||
using std::int32_t;
|
||||
|
||||
auto hms = std::chrono::hh_mm_ss(dt);
|
||||
int32_t h = hms.hours().count();
|
||||
int32_t m = hms.minutes().count();
|
||||
int32_t s = hms.seconds().count();
|
||||
int32_t msec = std::chrono::duration_cast<milliseconds>(hms.subseconds()).count();
|
||||
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "%02d:%02d:%02d.%03d", h, m, s, msec);
|
||||
|
||||
os << buf;
|
||||
} /*print_hms_msec*/
|
||||
|
||||
static void print_utc_hms_msec(utc_nanos t0, std::ostream & os) {
|
||||
print_hms_msec(utc_split_vs_midnight(t0).second, os);
|
||||
} /*print_utc_hms_usec*/
|
||||
|
||||
static void print_hms_usec(nanos dt, std::ostream & os) {
|
||||
/* use hhmmss.uuuuuu */
|
||||
using std::int32_t;
|
||||
|
||||
auto hms = std::chrono::hh_mm_ss(dt);
|
||||
int32_t h = hms.hours().count();
|
||||
int32_t m = hms.minutes().count();
|
||||
int32_t s = hms.seconds().count();
|
||||
int32_t usec = std::chrono::duration_cast<microseconds>(hms.subseconds()).count();
|
||||
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "%02d:%02d:%02d.%06d", h, m, s, usec);
|
||||
|
||||
os << buf;
|
||||
} /*print_hms_usec*/
|
||||
|
||||
static void print_utc_ymd_hms_usec(utc_nanos t0, std::ostream & os) {
|
||||
using xo::time::microseconds;
|
||||
using xo::time::utc_nanos;
|
||||
|
||||
/* use yyyymmdd.hh:mm:ss.nnnnnn */
|
||||
|
||||
//std::tm t0_tm;
|
||||
//uint32_t t0_usec;
|
||||
|
||||
/* (structured binding ftw!) */
|
||||
auto [t0_tm, t0_usec] = split_tm(t0);
|
||||
|
||||
/* no std::format in clang11 afaict */
|
||||
char usec_buf[7];
|
||||
snprintf(usec_buf, sizeof(usec_buf), "%06d", t0_usec);
|
||||
|
||||
|
||||
/* control string | example
|
||||
* ----------------------------+--------------------------
|
||||
* %c - locale-specific string | Fri Jun 10 16:29:05 2022
|
||||
* %Y - year | 2022
|
||||
* %m - month | 06
|
||||
* %d - day of month | 10
|
||||
* %H - hour | 16
|
||||
* %M - minute | 29
|
||||
* %S - second | 05
|
||||
* %Z - timezone | UTC
|
||||
*/
|
||||
os << std::put_time(&t0_tm, "%Y%m%d:%H:%M:%S.") << usec_buf;
|
||||
} /*print_utc_ymd_hms_usec*/
|
||||
|
||||
/* print datetime in format compatible with ISO 8601.
|
||||
* copying the format javascript uses, e.g:
|
||||
* 2012-04-23T18:25:43.511Z
|
||||
*/
|
||||
static void print_iso8601(utc_nanos t0, std::ostream & os) {
|
||||
auto [t0_tm, t0_usec] = split_tm(t0);
|
||||
|
||||
char msec_buf[8];
|
||||
snprintf(msec_buf, sizeof(msec_buf), "%03d", t0_usec / 1000);
|
||||
|
||||
os << std::put_time(&t0_tm, "%Y-%m-%dT%H:%M:%S.") << msec_buf << "Z";
|
||||
} /*print_iso8601*/
|
||||
}; /*time*/
|
||||
|
||||
/* stream inserter that displays time in ISO 8601 format:
|
||||
* 2012-04-23T18:25:43.511Z
|
||||
*/
|
||||
struct iso8601 {
|
||||
iso8601(utc_nanos t0) : t0_{t0} {}
|
||||
|
||||
utc_nanos t0_;
|
||||
}; /*iso8601*/
|
||||
|
||||
inline std::ostream &
|
||||
operator<<(std::ostream & os,
|
||||
iso8601 x)
|
||||
{
|
||||
time::print_iso8601(x.t0_, os);
|
||||
return os;
|
||||
} /*operator<<*/
|
||||
|
||||
/* stream inserter that display time like:
|
||||
* hh:mm:ss.nnn
|
||||
*/
|
||||
struct hms_msec {
|
||||
hms_msec(nanos dt) : dt_{dt} {}
|
||||
|
||||
static hms_msec utc(utc_nanos t0) { return hms_msec(time::utc_split_vs_midnight(t0).second); }
|
||||
static hms_msec local(utc_nanos t0) { return hms_msec(time::local_split_vs_midnight(t0).second); }
|
||||
|
||||
nanos dt_;
|
||||
}; /*hms_msec*/
|
||||
|
||||
inline std::ostream &
|
||||
operator<<(std::ostream & os, hms_msec x)
|
||||
{
|
||||
time::print_hms_msec(x.dt_, os);
|
||||
return os;
|
||||
} /*operator<<*/
|
||||
|
||||
|
||||
/* stream inserter that display time like:
|
||||
* hh:mm:ss.nnnnnn
|
||||
*/
|
||||
struct hms_usec {
|
||||
hms_usec(nanos dt) : dt_{dt} {}
|
||||
|
||||
static hms_usec utc(utc_nanos t0) { return hms_usec(time::utc_split_vs_midnight(t0).second); }
|
||||
static hms_usec local(utc_nanos t0) { return hms_usec(time::local_split_vs_midnight(t0).second); }
|
||||
|
||||
nanos dt_;
|
||||
}; /*hms_msec*/
|
||||
|
||||
inline std::ostream &
|
||||
operator<<(std::ostream & os, hms_usec x)
|
||||
{
|
||||
time::print_hms_usec(x.dt_, os);
|
||||
return os;
|
||||
} /*operator<<*/
|
||||
|
||||
|
||||
} /*namespace time*/
|
||||
} /*namespace xo*/
|
||||
|
||||
namespace std {
|
||||
namespace chrono {
|
||||
inline std::ostream & operator<<(std::ostream & os,
|
||||
xo::time::utc_nanos t0)
|
||||
{
|
||||
xo::time::time::print_utc_ymd_hms_usec(t0, os);
|
||||
return os;
|
||||
} /*operator<<*/
|
||||
|
||||
inline std::ostream & operator<<(std::ostream & os,
|
||||
xo::time::nanos dt)
|
||||
{
|
||||
xo::time::time::print_hms_usec(dt, os);
|
||||
return os;
|
||||
} /*operator<<*/
|
||||
} /*namespace chrono*/
|
||||
} /*namespace std*/
|
||||
|
||||
/* end time.hpp */
|
||||
105
include/indentlog/tostr.hpp
Normal file
105
include/indentlog/tostr.hpp
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
/* file tostr.hpp
|
||||
*
|
||||
* author: Roland Conybeare, Sep 2022
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace xo {
|
||||
/*
|
||||
* write x to stream s
|
||||
* note: here x is a universal reference, since
|
||||
* (a) it's a template type
|
||||
* (b) requires deduction to establish x's type
|
||||
* this means:
|
||||
* x will be an r-value reference or an l-value reference
|
||||
* depending on calling context
|
||||
*
|
||||
* see:
|
||||
* https://eli.thegreenplace.net/2014/variadic-templates-in-c/
|
||||
* http://bajamircea.github.io/coding/cpp/2016/04/07/move-forward.html
|
||||
* https://en.cppreference.com/w/cpp/language/value_category
|
||||
*
|
||||
* has identity == has address
|
||||
*
|
||||
* /- has identity -----------------\
|
||||
* | |
|
||||
* | lvalue |
|
||||
* | glvalue /------------------------------\
|
||||
* | | | |
|
||||
* | | xvalue | |
|
||||
* | | rvalue | |
|
||||
* | | glvalue | |
|
||||
* | | | |
|
||||
* \--------------------------------/ |
|
||||
* | rvalue |
|
||||
* | prvalue |
|
||||
* | |
|
||||
* \- can be moved ---------------/
|
||||
*
|
||||
* 1. has identity, but cannot be moved -> it's an lvalue; otherwise it's an rvalue
|
||||
* e.g: local variable name
|
||||
*
|
||||
* 2. can be moved, but no identity -> it's a prvalue (pure right-value);
|
||||
* otherwise it's a glvalue (generalized left-value)
|
||||
* e.g: non-reference function return value, or literal constant
|
||||
*
|
||||
* 3. has identity and can be moved -> it's an xvalue (strange value)
|
||||
* e.g: std::move(a)
|
||||
*
|
||||
* reminder:
|
||||
* - std::move() does not move: it converts lvalue to rvalue, so compiler can select
|
||||
* desired overload
|
||||
* - std::forward() does not forward: it recovers original value category
|
||||
* (when starting with a universal reference), so compiler can select
|
||||
* desired ctor
|
||||
*/
|
||||
|
||||
/* no-op terminal case */
|
||||
template<class Stream>
|
||||
Stream & tos(Stream & s) {
|
||||
return s;
|
||||
}
|
||||
|
||||
// Use:
|
||||
// tos(s,a,b,c)
|
||||
// is the same as
|
||||
// s << a << b << c;
|
||||
//
|
||||
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) {
|
||||
s << x;
|
||||
return tos(s, rest...);
|
||||
} /*tos*/
|
||||
|
||||
// like tos(..), but append newline
|
||||
//
|
||||
template <class Stream, typename... Tn>
|
||||
Stream &tosn(Stream &s, Tn &&...args) {
|
||||
tos(s, args...);
|
||||
s << std::endl;
|
||||
return s;
|
||||
} /*tosn*/
|
||||
|
||||
// tostr(args..) writes arguments to temporary stingstream,
|
||||
// returns its contents
|
||||
//
|
||||
template <typename... Tn> std::string tostr(Tn &&...args) {
|
||||
std::stringstream ss;
|
||||
tos(ss, args...);
|
||||
//ss << std::ends;
|
||||
return ss.str();
|
||||
} /*tostr*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end tostr.hpp */
|
||||
28
include/indentlog/vector.hpp
Normal file
28
include/indentlog/vector.hpp
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/* file vector.hpp
|
||||
*
|
||||
* author: Roland Conybeare, Sep 2022
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
namespace std {
|
||||
template<typename T>
|
||||
inline std::ostream &
|
||||
operator<<(std::ostream & os,
|
||||
std::vector<T> const & v)
|
||||
{
|
||||
os << "[";
|
||||
for(size_t i=0, z=v.size(); i<z; ++i) {
|
||||
if(i > 0)
|
||||
os << " ";
|
||||
os << v[i];
|
||||
}
|
||||
os << "]";
|
||||
return os;
|
||||
} /*operator<<*/
|
||||
} /*namespace std*/
|
||||
|
||||
/* end vector.hpp */
|
||||
Loading…
Add table
Add a link
Reference in a new issue