Add 'xo-indentlog/' from commit 'd43c4af0b4'

git-subtree-dir: xo-indentlog
git-subtree-mainline: 1c3f033933
git-subtree-split: d43c4af0b4
This commit is contained in:
Roland Conybeare 2025-05-10 17:00:33 -05:00
commit 341fcfd1c7
64 changed files with 4682 additions and 0 deletions

View file

@ -0,0 +1,98 @@
# This starter workflow is for a CMake project running on a single platform. There is a different starter workflow if you need cross-platform coverage.
# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-multi-platform.yml
name: CMake on a single platform
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
env:
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
BUILD_TYPE: Release
jobs:
build:
# The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac.
# You can convert this to a matrix build if you need cross-platform coverage.
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
runs-on: ubuntu-latest
steps:
- name: checkout source
uses: actions/checkout@v3
- name: Install dependencies
# install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]]
run: |
echo "::group::install catch2"
sudo apt-get install -y catch2
echo "::endgroup"
# ----------------------------------------------------------------
- name: Clone xo-cmake
uses: actions/checkout@v3
with:
repository: Rconybea/xo-cmake
path: repo/xo-cmake
- name: build xo-cmake
run: |
XONAME=xo-cmake
XOSRC=repo/${XONAME}
BUILDDIR=${{github.workspace}}/build_${XONAME}
PREFIX=${{github.workspace}}/local
echo "::group::configure ${XONAME}"
cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC}
echo "::endgroup"
echo "::group::compile ${XONAME}"
cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}}
echo "::endgroup"
echo "::group::local install ${XONAME}"
cmake --install ${BUILDDIR}
echo "::endgroup"
echo "::group::local dir tree"
tree ${PREFIX}
echo "::endgroup"
# ----------------------------------------------------------------
- name: build self (indentlog)
run: |
XONAME=xo-indentlog
BUILDDIR=${{github.workspace}}/build_${XONAME}
PREFIX=${{github.workspace}}/local
echo "::group::repo dir tree"
tree -L 2 repo
echo "::endgroup"
echo "::group::configure ${XONAME}"
cmake -B ${BUILDDIR} -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${PREFIX} -DCMAKE_INSTALL_PREFIX=${PREFIX} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
echo "::endgroup"
echo "::group::compile ${XONAME}"
cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}}
echo "::endgroup"
echo "::group::run unit tests ${XONAME}"
cmake --build ${BUILDDIR} -- test
echo "::endgroup"
echo "::group::local install ${XONAME}"
cmake --install ${BUILDDIR}
echo "::endgroup"
echo "::group::local dir tree"
tree -L 3 ${PREFIX}
echo "::endgroup"
# Execute tests defined by the CMake configuration.
# See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
(cd ${BUILDDIR} && ctest -C ${{env.BUILD_TYPE}})

8
xo-indentlog/.gitignore vendored Normal file
View file

@ -0,0 +1,8 @@
# emacs projectile config
.projectile
# symlink to ${my_build_directory}/compile_commands.json to make LSP work
compile_commands.json
# lsp keeps state here
.cache
# typical build dirs
.build*

47
xo-indentlog/BUILD.md Normal file
View file

@ -0,0 +1,47 @@
# indentlog build details
## mac osx
Note: ~ expansion doesn't work in a pure build environment.
## Test Coverage
### enable coverage build
```
$ cd indentlog
$ mkdir ccov
$ cd ccov
$ cmake -DCODE_COVERAGE=ON .. # prepares coverage build
```
### build + generate test coverage
```
$ make ccov # builds + runs unit tests
$ make ccov-all # generates .html report
```
### view coverage report
```
$ firefox
[navigate to coverage report; path something like file://home/roland/proj/indentlog/ccov/ccov/all-merged/index.html]
```
![lcov_output](img/lcov1.png)
## Implementation Notes
- coverage builds creates `.gcno` files alongside `.o` object files
- running coverage-enabled executables creates/appends to `.gcda` files
- e.g. see `ccov/utest/CMakeFiles/utest.indentlog.dir`
- coverage feature enabled globally in top-level `CMakeLists.txt` by:
```
include(cmake/code-coverage.cmake)
add_code_coverage()
add_code_coverage_all_targets()
```
- looks like these need to appear before executable targets are introduced
- also need to opt-in individual executables, e.g. in `utest/CMakeLists.txt`:
```
target_code_coverage(${SELF_EXECUTABLE_NAME} AUTO ALL)
```
- here `AUTO` opts in to the `ccov` target; `ALL` opts in to the `ccov-all` target

View file

@ -0,0 +1,48 @@
# indentlog/CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(indentlog VERSION 1.0)
include(GNUInstallDirs)
include(cmake/xo-bootstrap-macros.cmake)
xo_cxx_toplevel_options3()
# ----------------------------------------------------------------
# c++ settings
# one-time project-specific c++ flags. usually empty
#set(PROJECT_CXX_FLAGS "-Wstringop-overread")
#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2")
add_definitions(${PROJECT_CXX_FLAGS})
# ----------------------------------------------------------------
add_subdirectory(example)
add_subdirectory(utest)
# header-only library.
# see [[https://stackoverflow.com/questions/47718485/install-and-export-interface-only-library-cmake]]
#
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)
# ----------------------------------------------------------------
if (XO_ENABLE_EXAMPLES)
install(TARGETS hello DESTINATION bin/indentlog/example)
install(TARGETS indentlog_ex1 DESTINATION bin/indentlog/example)
install(TARGETS indentlog_ex2 DESTINATION bin/indentlog/example)
install(TARGETS indentlog_ex3 DESTINATION bin/indentlog/example)
install(TARGETS indentlog_ex4 DESTINATION bin/indentlog/example)
endif()
# end CMakeLists.txt

40
xo-indentlog/FAQ Normal file
View file

@ -0,0 +1,40 @@
Q1. how to get a nix development environment that works
1.
1. nix stdenv = gcc12Stdenv (see mkderivation.nix)
2. baseInputs has gcc (but probably doesn't need it)
3. devInputs has llvmPackages_16.clang-unwrapped
This leads to env with
CC=gcc
CXX=g++
NIX_CC=/nix/store/$hash-gcc-wrapper-12.3.0
2.
1. nix stdenv = clang16Stdenv (see mkderivation.nix)
2. baseInputs has gcc
3. devInputs has llvmPackages_16.clang-unwrapped
This leads to env with:
CC=clang
CXX=clang++
NIX_CC=/nix/store/$hash-clang-wrapper-16.0.1
To build, need to tell cmake to use gcc:
cmake -DCMAKE_CXX_COMPILER=$(which g++) -DCMAKE_C_COMPILER=$(which gcc) path/to/src
Q2. how to add a dependency to github workflow
comments.
1. workflow configured in ./.github/cmake-single-platform.yml
2. workflow runs on ubuntu vm. see
runs-on: ubuntu-latest
in cmake-single-platform.yml
3. find a desired dependency
$ apt-cache search ${keyword}
e.g.
$ apt-cache search catch2
4. add/edit install step to ./.github/cmake-single-platform.yml
- name: Install catch2
run: sudo apt-get install -y catch2

29
xo-indentlog/FILES Normal file
View file

@ -0,0 +1,29 @@
directory layout
+- README.md markdown README, for github
+- img image files, used in docs
| +- ex1.png
| +- ex2.png
| +- ex3.png
| \- ex4.png
+- LICENSE software license
+- CMakeLists.txt toplevel cmake config
+- cmake
| \- nestlog.cmake cmake support files
+- compile_commands.json symlink to record of compiler commands; for LSP support
+- include to install, copy contents of this directory to permanent location
| \- indentlog
| +- scope.hpp logger api -- appl code will #include this
| +- log_config.hpp logger api -- control logger format, verbosity, colors etc.
| +- log_level.hpp encode logger verbosity
| +- log_state.hpp per-thread state tracking (e.g. recognize nesting)
| +- log_streambuf.hpp custom streambuf
| \- print
| +- tag.hpp stream inserters
| ...
\- example
+- CMakeLists.txt cmake config
+- ex1
| +- CMakeLists.txt ex1 cmake config
| \- ex1.cpp example .cpp exercising indentlog
...

14
xo-indentlog/LICENSE Normal file
View file

@ -0,0 +1,14 @@
The MIT License (MIT)
Copyright © 2023 Roland Conybeare
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”),
to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

31
xo-indentlog/MARKDOWN Normal file
View file

@ -0,0 +1,31 @@
# heading level 1
## heading level 2
###### heading level 6
blank line new paragraph
two spaces at eod force line break
**bold** bold text
__bold__ also bold text (but don't embed within a word)
*italics*
***bolditalic***
> text to blockquote
>
> + more paragraphs
> text to blockquote
>
>> with nested blockquote
- bullets also can prefix with + or *
1. numbered lists
indent 4 spaces (or 1 tab) for code blocks
`inline code`
--- on a line by itself -> horizontal rule

261
xo-indentlog/README.md Normal file
View file

@ -0,0 +1,261 @@
# indentlog -- logging with automatic call-graph indenting
Indentlog is a lightweight header-only library for console logging.
## Features
- header-only; nothing to link
- easy-to-read format uses indenting to show call structure.
indentation has user-controlled upper limit to preserve readability with
deeply nested call graphs
- colorized output using vt100 color codes (ansi or xterm)
- automatically captures + displays timestamp, function name and code location.
supports several function-name formats to reflect tradeoff readability for precision
- application code may issue logging code that contains embedded newlines and/or color escapes;
logger preserves indentation.
- logger is 'truthy' -> only pay for formatting when entry points is enabled.
- also provides family of convenience stream-inserters
## Getting Started
### build + install `xo-cmake` dependency (cmake macros)
see [github/Rconybea/xo-cmake](https://github.com/Rconybea/xo-cmake)
Installs a few cmake ingredients, along with a build assistant for XO projects such as this one.
### copy repository locally
Using `xo-build` (provided by `xo-cmake`):
```
$ xo-build --clone xo-indentlog
```
or equivalently:
```
$ cd ~/proj
$ git clone git@github.com:Rconybea/indentlog.git xo-indentlog
```
### build & install
Using `xo-build`:
```
$ xo-build --configure --build --install xo-indentlog
```
or equivalently:
```
$ mkdir xo-indentlog/.build
$ PREFIX=/usr/local # for example
$ cmake -DCMAKE_INSTALL_PREFIX=${PREFIX} -S xo-indentlog -B xo-indentlog/.build
$ cmake --build xo-indentlog/.build
$ cmake --install xo-indentlog/.build
```
For some more detail see [BUILD.md](BUILD.md)
### LSP support
lsp will look for `compile_commands.json` in the root of the source tree; cmake creates it in build directory
```
$ cd xo-indentlog
$ ln -s build/compile_commands.json
```
## Examples
### 1
```
#include "indentlog/scope.hpp"
using namespace xo;
void inner(int x) {
scope log(XO_ENTER0(always), ":x ", x);
}
void outer(int y) {
scope log(XO_ENTER0(always), ":y ", y);
inner(2*y);
}
int
main(int argc, char ** argv) {
outer(123);
}
```
output:
![ex1 output](img/ex1.png)
- indentlog types are provided in the `xo` namespace.
macros are prefixed with `XO_`
- indentation reflects call structure. We don't see anything for `main()`,
since we didn't put any logging there
### 2 slightly more elaborate example
```
/* examples ex2/ex2.cpp */
#include "indentlog/scope.hpp"
using namespace xo;
int
fib(int n) {
scope log(XO_ENTER0(info), ":n ", n);
int retval = 1;
if (n >= 2) {
retval = fib(n - 1) + fib(n - 2);
log && log(":n ", n);
}
log.end_scope("<- :retval ", retval);
return retval;
}
int
main(int argc, char ** argv) {
log_config::min_log_level = xo::log_level::info;
log_config::indent_width = 4;
int n = 4;
scope log(XO_ENTER0(info), ":n ", 4);
int fn = fib(n);
log && log(":n ", n);
log && log("<- :fib(n) ", fn);
}
```
output:
![ex2 output](img/ex2.png)
- global configuration settings live in the `xo::log_config` class. see [log_config.hpp](include/indentlog/log_config.hpp)
- the recommended form `log && log(...)` tests whether logging at this site is enabled /before/ evaluating/formatting the log message;
when logging is disabled, this saves the cost of computing and formatting that message.
### 3 example exposing runtime configuration options
```
/* examples ex3/ex3.cpp */
#include "indentlog/scope.hpp"
using namespace xo;
int
fib(int n) {
scope log(XO_ENTER0(info), tag("n", n));
int retval = 1;
if (n >= 2) {
retval = fib(n - 1) + fib(n - 2);
}
log.end_scope(tag("n", n), " <-", xtag("retval", retval));
return retval;
}
int
main(int argc, char ** argv) {
log_config::min_log_level = log_level::info;
log_config::time_enabled = true;
log_config::time_local_flag = true;
log_config::style = FS_Streamlined;
log_config::indent_width = 4;
log_config::max_indent_width = 30;
log_config::location_tab = 80;
log_config::encoding = CE_Xterm;
log_config::function_entry_color = 69;
log_config::function_exit_color = 70;
log_config::code_location_color = 166;
int n = 3;
scope log(XO_ENTER0(info), ":n ", 4);
int fn = fib(n);
log && log(tag("n", n));
log && log("<-", xtag("fib(n)", fn));
}
/* ex3/ex3.cpp */
```
output:
![ex3 output](img/ex3.png)
### 4 example: function signatures
```
/* @file ex4.cpp */
#include "indentlog/scope.hpp"
using namespace xo;
class Quadratic {
public:
Quadratic(double a, double b, double c) : a_{a}, b_{b}, c_{c} {}
double operator() (double x) const {
scope log(XO_ENTER0(info), tag("a", a_), xtag("b", b_), xtag("c", c_), xtag("x", x));
double retval = (a_ * x * x) + (b_ * x) + c_;
log.end_scope("<-", xtag("retval", retval));
return retval;
}
private:
double a_ = 0.0;;
double b_ = 0.0;
double c_ = 0.0;
};
int
main(int argc, char ** argv) {
//log_config::style = FS_Pretty;
log_config::style = FS_Streamlined;
log_config::min_log_level = log_level::info;
scope log(XO_ENTER0(info));
Quadratic quadratic(2.0, -5.0, 7.0);
double x = 3.0;
double r = 0.0;
log_config::style = FS_Pretty;
r = quadratic(x);
log_config::style = FS_Streamlined;
r = quadratic(x);
log_config::style = FS_Simple;
r = quadratic(x);
}
/* end ex4.cpp */
```
output:
![ex4 output](img/ex4.png)

5
xo-indentlog/TODO Normal file
View file

@ -0,0 +1,5 @@
sphinx_markdown_builder
https://stackoverflow.com/questions/13396856/markdown-output-for-sphinx-based-documentation

View file

@ -0,0 +1,4 @@
@PACKAGE_INIT@
include("${CMAKE_CURRENT_LIST_DIR}/indentlogTargets.cmake")
check_required_components("@PROJECT_NAME@")

View file

@ -0,0 +1,35 @@
# ----------------------------------------------------------------
# for example:
# $ PREFIX=/usr/local # for example
# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build
#
# will get
# CMAKE_MODULE_PATH
# from xo-cmake-config --cmake-module-path
#
# and expect .cmake macros in
# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake
# ----------------------------------------------------------------
find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED)
if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND")
message(FATAL "could not find xo-cmake-config executable")
endif()
message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}")
if (NOT XO_SUBMODULE_BUILD)
if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix))
# default to typical install location for xo-project-macros
execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH)
message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}")
endif()
endif()
# needs to have been installed somewhere on CMAKE_MODULE_PATH,
# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX)
#
include(xo_macros/xo_cxx)
xo_cxx_bootstrap_message()

View file

@ -0,0 +1,25 @@
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 "")
#include(cmake/FindSphinx.cmake)
add_subdirectory(hello)
add_subdirectory(ex1)
add_subdirectory(ex2)
add_subdirectory(ex3)
add_subdirectory(ex4)
# ----------------------------------------------------------------
# make standard directories for std:: includes explicit
# so that
# (1) they appear in compile_commands.json.
# (2) clangd (run from emacs lsp-mode) can find them
#
if(CMAKE_EXPORT_COMPILE_COMMANDS)
set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES
${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES})
endif()

View file

@ -0,0 +1,4 @@
if (XO_ENABLE_EXAMPLES)
add_executable(indentlog_ex1 ex1.cpp)
xo_include_options2(indentlog_ex1)
endif()

View file

@ -0,0 +1,22 @@
/* ex1.cpp */
#include "xo/indentlog/scope.hpp"
using namespace xo;
void inner(int x) {
scope log(XO_ENTER0(always), ":x ", x);
}
void outer(int y) {
scope log(XO_ENTER0(always), ":y ", y);
inner(2*y);
}
int
main(int argc, char ** argv) {
outer(123);
}
/* end ex1.cpp */

View file

@ -0,0 +1,6 @@
# NOTE: need target names to be globally unique within the xo umbrella
if (XO_ENABLE_EXAMPLES)
add_executable(indentlog_ex2 ex2.cpp)
xo_include_options2(indentlog_ex2)
endif()

View file

@ -0,0 +1,36 @@
/* examples ex2/ex2.cpp */
#include "xo/indentlog/scope.hpp"
using namespace xo;
int
fib(int n) {
scope log(XO_ENTER0(info), ":n ", n);
int retval = 1;
if (n >= 2) {
retval = fib(n - 1) + fib(n - 2);
log && log(":n ", n);
}
log.end_scope("<- :retval ", retval);
return retval;
}
int
main(int argc, char ** argv) {
log_config::min_log_level = xo::log_level::info;
log_config::indent_width = 4;
int n = 4;
scope log(XO_ENTER0(info), ":n ", 4);
int fn = fib(n);
log && log(":n ", n);
log && log("<- :fib(n) ", fn);
}

View file

@ -0,0 +1,4 @@
if (XO_ENABLE_EXAMPLES)
add_executable(indentlog_ex3 ex3.cpp)
xo_include_options2(indentlog_ex3)
endif()

View file

@ -0,0 +1,48 @@
/* examples ex3/ex3.cpp */
#include "xo/indentlog/scope.hpp"
using namespace xo;
int
fib(int n) {
scope log(XO_ENTER0(info), tag("n", n));
int retval = 1;
if (n >= 2) {
retval = fib(n - 1) + fib(n - 2);
}
log.end_scope(tag("n", n), " <-", xtag("retval", retval));
return retval;
}
int
main(int argc, char ** argv) {
//std::cerr << "0 1 2 3 4 5 6 7 8 9 10" << std::endl;
//std::cerr << "01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" << std::endl;
log_config::min_log_level = log_level::info;
log_config::time_enabled = true;
log_config::time_local_flag = true;
log_config::style = function_style::streamlined;
log_config::indent_width = 4;
log_config::max_indent_width = 30;
log_config::location_tab = 80;
log_config::function_entry_color = color_spec_type::xterm(69);
log_config::function_exit_color = color_spec_type::xterm(70);
log_config::code_location_color = color_spec_type::xterm(166);
int n = 3;
scope log(XO_ENTER0(info), ":n ", 4);
int fn = fib(n);
log && log(tag("n", n));
log && log("<-", xtag("fib(n)", fn));
}
/* ex3/ex3.cpp */

View file

@ -0,0 +1,4 @@
if (XO_ENABLE_EXAMPLES)
add_executable(indentlog_ex4 ex4.cpp)
xo_include_options2(indentlog_ex4)
endif()

View file

@ -0,0 +1,52 @@
/* @file ex4.cpp */
#include "xo/indentlog/scope.hpp"
using namespace xo;
class Quadratic {
public:
Quadratic(double a, double b, double c) : a_{a}, b_{b}, c_{c} {}
double operator() (double x) const {
scope log(XO_ENTER0(info), tag("a", a_), xtag("b", b_), xtag("c", c_), xtag("x", x));
double retval = (a_ * x * x) + (b_ * x) + c_;
log.end_scope("<-", xtag("retval", retval));
return retval;
}
private:
double a_ = 0.0;;
double b_ = 0.0;
double c_ = 0.0;
};
int
main(int argc, char ** argv) {
log_config::style = function_style::streamlined;
log_config::min_log_level = log_level::info;
scope log(XO_ENTER0(info));
Quadratic quadratic(2.0, -5.0, 7.0);
double x = 3.0;
double r = 0.0;
log_config::style = function_style::pretty;
r = quadratic(x);
log_config::style = function_style::streamlined;
r = quadratic(x);
log_config::style = function_style::simple;
r = quadratic(x);
}
/* end ex4.cpp */

View file

@ -0,0 +1,4 @@
if (XO_ENABLE_EXAMPLES)
add_executable(hello hello.cpp)
xo_include_options2(hello)
endif()

View file

@ -0,0 +1,5 @@
#include <iostream>
int main(int argc, char ** argv) {
std::cout << "Hello, world!" << std::endl;
}

BIN
xo-indentlog/img/ex1.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
xo-indentlog/img/ex2.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

BIN
xo-indentlog/img/ex3.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
xo-indentlog/img/ex4.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
xo-indentlog/img/lcov1.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

View file

@ -0,0 +1,104 @@
/* @file log_config.hpp */
#pragma once
#include "log_level.hpp"
#include "print/function.hpp"
#include "print/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 color_spec_type nesting_level_color;
/* display style for function names. function_style:: literal|simple|pretty|streamlined */
static function_style style;
/* color to use for function name, on entry/exit (xo::scope creation/destruction)
* (ansi color codes, see Select Graphics Rendition subset)
*/
static color_spec_type function_entry_color;
static color_spec_type 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 color_spec_type 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>
color_spec_type
log_config_impl<Tag>::nesting_level_color = color_spec_type::xterm(195);
template <typename Tag>
function_style
log_config_impl<Tag>::style = function_style::streamlined;
template <typename Tag>
color_spec_type
log_config_impl<Tag>::function_entry_color = color_spec_type::ansi(34);
template <typename Tag>
color_spec_type
log_config_impl<Tag>::function_exit_color = color_spec_type::ansi(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>
color_spec_type
log_config_impl<Tag>::code_location_color = color_spec_type::red();
using log_config = log_config_impl<class log_config_tag>;
} /*namespace xo*/
/* end log_config.hpp */

View file

@ -0,0 +1,77 @@
/* @file log_level.hpp */
#pragma once
#include <iostream>
#include <cstdint>
namespace xo {
enum class log_level : std::uint8_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));
}
inline std::ostream &
operator<<(std::ostream & os,
log_level x) {
switch(x) {
case log_level::never: os << "never"; break;
case log_level::verbose: os << "verbose"; break;
case log_level::chatty: os << "chatty"; break;
case log_level::info: os << "info"; break;
case log_level::warning: os << "warning"; break;
case log_level::error: os << "error"; break;
case log_level::severe: os << "severe"; break;
case log_level::always: os << "always"; break;
case log_level::silent: os << "silent"; break;
//default: os << "???"; break;
}
return os;
} /* operator<<*/
} /*namespace xo*/
/* end log_level.hpp */

View file

@ -0,0 +1,403 @@
/* @file log_state.hpp */
#pragma once
#include "log_config.hpp"
#include "log_streambuf.hpp"
#include "print/pad.hpp"
#include "print/filename.hpp"
#include "print/code_location.hpp"
#include "print/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::timeutil;
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::timeutil::now());
this->indent(' ');
char ee_label = '\0';
color_spec_type fn_color;
/* 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::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, 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::code_location_color);
std::string ss_str = ss.str(); /*hoping for copy elision here*/
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 */

View 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=" << quot(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); /*see 'this->buf_v_[old_n] - new_ch' above*/
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 */

View file

@ -0,0 +1,29 @@
/* @file machdep.hpp */
#pragma once
/** Carveout for LSP (language server process):
LSP uses clang, but with the same compiler flags as primary build.
This triggers a handful of false alarms, in which clang complains about
gcc builtins.
Replace these with something innocuous. Ok since LSP stops
once parsing completes and does not generate code
**/
#if __clang__ && __GNUG__
extern "C" {
/* never defined! must not ever generate code that relies on these */
unsigned int fake_rdtsc();
unsigned int fake_mm_getcsr();
unsigned int fake_mm_setcsr();
}
/* __rdtsc: clang encounters this from <immintrin.h>, for example */
#define __rdtsc() fake_rdtsc()
#define _mm_getcsr(a) fake_mm_getcsr()
#define _mm_setcsr(a) fake_mm_setcsr()
#endif
/* end machdep.hpp */

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

View file

@ -0,0 +1,53 @@
/* @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_spec_type colorspec)
: file_{file}, line_{line}, color_spec_{colorspec} {}
void print_code_location(std::ostream & os) const {
os << "["
<< with_color(color_spec_, basename(file_))
<< ":"
<< line_
<< "]";
} /*print_code_location*/
private:
/* __FILE__ */
std::string_view file_;
/* __LINE__ */
std::uint32_t line_ = 0;
/* color encoding for [file:line] */
color_spec_type color_spec_;
}; /*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 */

View file

@ -0,0 +1,202 @@
/* color.hpp */
#pragma once
#include <ostream>
//#include <utility> // for std::move
#include <cstdint>
namespace xo {
enum class color_encoding : std::uint8_t {
none,
ansi,
xterm,
rgb
};
inline std::ostream &
operator<< (std::ostream & os, color_encoding x) {
switch(x) {
case color_encoding::none: os << "none"; break;
case color_encoding::ansi: os << "ansi"; break;
case color_encoding::xterm: os << "xterm"; break;
case color_encoding::rgb: os << "rgb"; break;
default: os << "???"; break;
}
return os;
} /*operator<<*/
/* specify a color (consistent with ANSI escape sequences - the Select Graphics Rendition subset
* see [[https://stackoverflow.com/questions/4842424/list-of-ansi-color-escape-sequences]]
*
* this provides three ways to specify foreground color:
*
* | enum | escape | example | description | foreground codes |
* +-------+-----------+---------------------+---------------+------------------+
* | ansi | \033[31 | \033[31;34m | 4-bit colors | 30..37, 90..97 |
* | xterm | \033[38;5 | \033[38;5;143m | 8-bit colors | 0..255 |
* | rgb | \033[38;2 | \033[38;2;10;20;30m | 24-bit colors | 3x 0..255 |
*
*/
class color_spec_type {
public:
color_spec_type() = default;
color_spec_type(color_encoding encoding, std::uint32_t code)
: encoding_{encoding}, code_{code} {}
static color_spec_type none() { return color_spec_type(); }
static color_spec_type ansi(std::uint32_t code) { return color_spec_type(color_encoding::ansi, code); }
static color_spec_type xterm(std::uint32_t code) { return color_spec_type(color_encoding::xterm, code); }
static color_spec_type rgb(std::uint8_t red, std::uint8_t green, std::uint8_t blue) {
return color_spec_type(color_encoding::rgb, (red << 16 | green << 8 | blue));
}
/* 4-bit foreground colors */
static color_spec_type black () { return ansi(30); }
static color_spec_type red () { return ansi(31); }
static color_spec_type green () { return ansi(32); }
static color_spec_type yellow () { return ansi(33); }
static color_spec_type blue () { return ansi(34); }
static color_spec_type magenta () { return ansi(35); }
static color_spec_type cyan () { return ansi(36); }
static color_spec_type white () { return ansi(37); }
static color_spec_type bright_black () { return ansi(90); }
static color_spec_type bright_red () { return ansi(91); }
static color_spec_type bright_green () { return ansi(92); }
static color_spec_type bright_yellow () { return ansi(99); }
static color_spec_type bright_blue () { return ansi(94); }
static color_spec_type bright_magenta () { return ansi(95); }
static color_spec_type bright_cyan () { return ansi(96); }
static color_spec_type bright_white () { return ansi(97); }
color_encoding encoding() const { return encoding_; }
std::uint32_t code() const { return code_; }
void print_fg_color_on (std::ostream & os) const {
switch (encoding_) {
case color_encoding::none:
break;
case color_encoding::ansi:
os << "\033[31;" << code_ << "m";
break;
case color_encoding::xterm:
os << "\033[38;5;" << code_ << "m";
break;
case color_encoding::rgb:
os << "\033[38;2;"
<< (0xff & (code_ >> 16)) << ";"
<< (0xff & (code_ >> 8)) << ";"
<< (0xff & (code_ >> 0)) << "m";
}
} /*print_fg_color_on*/
/* escape to reverse effect of .print_on() */
void print_fg_color_off (std::ostream & os) const {
switch (encoding_) {
case color_encoding::none:
break;
case color_encoding::ansi:
case color_encoding::xterm:
case color_encoding::rgb:
os << "\033[0m";
break;
}
} /*print_fg_color_off*/
private:
/* none | ansi | xterm | rgb */
color_encoding encoding_ = color_encoding::none;
/* ansi : 30..37, 90..97
* xterm : 0..255
* 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
* rgb : r={hi 8 bits}, g={mid 8 bits}, b={lo 8 bits}
*/
std::uint32_t code_ = 0;
}; /*color_spec_type*/
inline std::ostream &
operator<< (std::ostream & os, color_spec_type const & x) {
os << "<color_spec_type :encoding " << x.encoding() << " :code " << x.code() << ">";
return os;
} /*operator<<*/
enum class coloring_control_flags : std::uint8_t {
none = 0x0,
color_on = 0x01,
contents = 0x02,
color_off = 0x04,
all = 0x07
};
inline std::uint8_t operator& (coloring_control_flags x, coloring_control_flags y) {
return static_cast<std::uint8_t>(x) & static_cast<std::uint8_t>(y);
}
inline std::uint8_t operator| (coloring_control_flags x, coloring_control_flags y) {
return static_cast<std::uint8_t>(x) | static_cast<std::uint8_t>(y);
}
/* stream-insertable color control */
template <typename Contents>
class color_impl {
public:
color_impl(coloring_control_flags flags, color_spec_type spec, Contents && contents)
: flags_{flags}, spec_{spec}, contents_{std::forward<Contents>(contents)} {}
color_spec_type const & spec() const { return spec_; }
std::uint32_t color() const { return spec_.code(); }
Contents const & contents() const { return contents_; }
void print(std::ostream & os) const {
if (flags_ & coloring_control_flags::color_on)
spec_.print_fg_color_on(os);
if (flags_ & coloring_control_flags::contents)
os << contents_;
if (flags_ & coloring_control_flags::color_off)
spec_.print_fg_color_off(os);
} /*print*/
private:
/* controls independently what to print
* \033[38;5;117m hello, world! \033[0m
* <------------> <-----------> <----->
* color_on contents color_off
*/
coloring_control_flags flags_ = coloring_control_flags::none;
color_spec_type spec_;
Contents contents_;
}; /*color_impl*/
template <typename Contents>
color_impl<Contents> with_color(color_spec_type const & spec, Contents && contents) {
return color_impl<Contents>(coloring_control_flags::all, spec, std::forward<Contents>(contents));
} /*with_color*/
inline color_impl<int>
color_on(color_spec_type const & spec) {
return color_impl<int>(coloring_control_flags::color_on, spec, 0);
} /*color_on*/
inline color_impl<int>
color_off(color_spec_type const & spec) {
/* any spec other than color_spec_type::none() works here */
return color_impl<int>(coloring_control_flags::color_off, spec, 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 */

View file

@ -0,0 +1,42 @@
/* @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>
T1 concat(T1 && x1) {
return x1;
} /*concat*/
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 */

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

View file

@ -0,0 +1,46 @@
/* @file fixed.hpp */
#pragma once
#include <iostream>
#include <cstdint>
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, std::uint16_t prec) : x_{x}, prec_{prec} {}
/* print this value */
double x_;
/* precision */
std::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 */

View file

@ -0,0 +1,318 @@
/* @file function.hpp */
#include "color.hpp"
#include <iostream>
#include <cstdint>
namespace xo {
enum class function_style : std::uint8_t {
/** literal: print supplied text, no alterations **/
literal,
/** pretty: print name, surrounded by []
* [double Quadratic::operator()(double) const]
**/
pretty,
/** streamlined: remove extraneous detail,
* try to print something like class::method
* Quadratic::operator()
**/
streamlined,
/** simple: remove everything except function/method name
* operator()
**/
simple
};
inline std::ostream &
operator<< (std::ostream & os, function_style x) {
switch(x) {
case function_style::literal: os << "literal"; break;
case function_style::pretty: os << "pretty"; break;
case function_style::streamlined: os << "streamlined"; break;
case function_style::simple: os << "simple"; break;
default: os << "???"; break;
}
return os;
} /*operator<<*/
/* 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_spec_type const & spec,
std::string_view pretty)
: style_{style}, color_spec_{spec}, pretty_{pretty} {}
function_style style() const { return style_; }
color_spec_type const & colorspec() const { return color_spec_; }
std::string_view const & pretty() const { return pretty_; }
/* e.g.
* <----------------------------------------------------- s ---------------------------------------------------------->
* <----------------------------------------- s2 --------------------------------------->
* <------------------------------------- s3 ------------------------------------->
* <--------------------- s4 ----------------->
* <----- s5 ----->
* std::vector<std::pair<int, xo::bar> xo::sometemplateclass<T,U>::fib(int, char**) const [with T = int; with U = char]
* ^ ^
* p q
*
* fib <- .print_aux()
*/
static void print_simple(std::ostream & os, std::string_view const & s) {
std::string_view s2 = exclude_template_footnote_suffix(s);
std::string_view s3 = exclude_const_suffix(s2) /*no const suffix*/;
std::size_t q = exclude_return_type(s3);
std::string_view s4 = s3.substr(q); /* no return type */
std::size_t r = find_toplevel_sep(s4, true /*last_flag*/);
std::string_view s5 = s4.substr(r);
print_aux(os, s5);
} /*print_simple*/
/* e.g.
* <------------------------------------------------------------- s -------------------------------------------------->
* <----------------------------------------------- s2 --------------------------------->
* <----------------------------------- s3 --------------------------------------->
* <--------------------- s4 ----------------->
* <----------------- s5 ----------------->
* std::vector<std::pair<int, xo::bar> xo::sometemplateclass<T,U>::fib(int, char**) const [with T = int; with U = char]
* ^ ^ ^
* q r p
*
* sometemplateclass ::fib <- .print_aux()
*
*/
static void print_streamlined(std::ostream & os, std::string_view const & s) {
std::string_view s2 = exclude_template_footnote_suffix(s);
std::string_view s3 = exclude_const_suffix(s2) /*no const suffix */;
std::size_t q = exclude_return_type(s3);
std::string_view s4 = s3.substr(q); /*no return type*/
std::size_t r = find_toplevel_sep(s4, false /*!last_flag*/);
std::string_view s5 = s4.substr(r); /*no namespace qualifier (unless function)*/
//std::cerr << "print_streamlined: s=[" << s << "]" << std::endl;
//std::cerr << "print_streamlined: s2=[" << s2 << "] (excluded [with ..] suffix)" << std::endl;
//std::cerr << "print_streamlined: s3=[" << s3 << "] (excluded const suffix)" << std::endl;
//std::cerr << "print_streamlined: s4=[" << s4 << "], q=" << q << " (excluded return type)" << std::endl;
//std::cerr << "print_streamlined: s5=[" << s5 << "], r=" << r << " (excluded ns qualifier)" << std::endl;
print_aux(os, s5);
} /*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.
* void xo::foo::Foo<T, S>::notify(const T&) [with T = std::pair<int, char>; S = xo::foo::Bar]
*/
static std::string_view exclude_template_footnote_suffix(std::string_view const & s) {
/* strategy:
* - left-to-right
* - exclude ' [with '... to end of string
*/
#if __clang__
/* clang footnote like [CharT = char] instead of [with CharT = char] */
std::size_t p = s.find(" [");
#else
# if (__GNUC__ > 13) || ((__GNUC__ == 13) && (__GNUC_MINOR__ >= 3))
/* gcc footnote like [CharT = char] instead of [with CharT = char] starting w/ gcc 13.3 (approximately ?)*/
std::size_t p = s.find(" [");
# else
std::size_t p = s.find(" [with ");
# endif
#endif
return s.substr(0, p);
} /*exclude_template_footnote_suffix*/
static std::string_view exclude_const_suffix(std::string_view const & s) {
constexpr std::uint32_t c_suffix_z = 6 /*strlen(" const")*/;
if ((s.size() > c_suffix_z)
&& (s.substr(s.size() - c_suffix_z) == " const"))
{
return s.substr(0, s.size() - c_suffix_z);
}
return s;
} /*exclude_const_suffix*/
/* e.g.
* xo::ns::someclass<xo::foo, xo::bar>::somemethod(xo::enum1, std::vector<xo::blah>)
* ^
* return this pos
* (pos just after 2nd-last non-nested separator)
*
* 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;
}
std::size_t retval = (last_flag ? pos_after_last_sep : pos_after_2ndlast_sep);
return retval;
} /*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;
/* index of next match within string 'operator()'.
* if we would print 'operator', and it's followed by trailing paren pair,
* then don't exclude the trailing ()
*/
std::int32_t match_operator_ix = 0;
constexpr char const * c_target_str = "operator(";
for (char ch : s) {
//std::cerr << "print_aux: ch=[" << ch << "]" << ", nesting_level=" << nesting_level << ", match_operator_ix=" << match_operator_ix << std::endl;
/* looking for match on 'operator(' at nesting level 0 */
if ((nesting_level == 0) && (ch == c_target_str[match_operator_ix]) && (match_operator_ix < 9))
++match_operator_ix;
else
match_operator_ix = 0;
/* don't increment nesting level if immediately after 'operator' */
if (ch == '<') {
++nesting_level;
} else if (ch == '(') {
if ((nesting_level == 0) && (match_operator_ix == 9)) {
/* special case:
* 012345678
* operator(
* at toplevel; don't count the '(' here toward nesting level
*/
;
} else {
++nesting_level;
}
}
if (nesting_level == 0)
os << ch;
if (nesting_level > 0 && ((ch == '>') || (ch == ')')))
--nesting_level;
}
} /*print_aux*/
private:
/* FS_Simple | FS_Pretty (= FS_Literal) | FS_Streamlined */
function_style style_;
/* terminal color (controls vt100 escape) */
color_spec_type color_spec_;
/* 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 function_style::literal:
os << with_color(fn.colorspec(), fn.pretty());
break;
case function_style::pretty:
os << "[" << with_color(fn.colorspec(), fn.pretty()) << "]";
break;
case function_style::streamlined:
/* omit namespace qualifiers and template arguments */
os << color_on(fn.colorspec());
function_name::print_streamlined(os, fn.pretty());
os << color_off(fn.colorspec());
break;
case function_style::simple:
os << color_on(fn.colorspec());
function_name::print_simple(os, fn.pretty());
os << color_off(fn.colorspec());
break;
}
return os;
} /*operator<<*/
} /*namespace xo*/
/* end function.hpp */

View file

@ -0,0 +1,145 @@
/** @file hex.hpp **/
#pragma once
#include <iostream>
#include <cstdint>
namespace xo {
/**
@class hex indentlog/print/hex.hpp
@brief Container for a (1-byte) value to be printed in hexadecimal
Example:
@code
#include "indentlog/print/hex.hpp"
std::ostream os = ...;
os << hex(16 + 63); // output: 1f
os << hex(16 + 63, true); // output: 1f(O)
@endcode
**/
struct hex {
/** @brief constructor; create stream-inserter instance */
explicit hex(std::uint8_t x, bool w_char = false) : x_{x}, with_char_{w_char} {}
/**
@brief print hexadecimal byte-value on to stream.
@param os print on this stream.
@tparam Stream typename for character stream.
**/
template <typename Stream>
void print(Stream & os) const {
std::uint8_t lo = x_ & 0xf;
std::uint8_t hi = x_ >> 4;
char lo_ch = (lo < 10) ? '0' + lo : 'a' + lo - 10;
char hi_ch = (hi < 10) ? '0' + hi : 'a' + hi - 10;
os << hi_ch << lo_ch;
if (with_char_) {
os << "(";
if (std::isprint(x_))
os << static_cast<char>(x_);
else
os << "?";
os << ")";
}
}
private:
/** @brief value to print (in hexadecimal) **/
std::uint8_t x_;
/** @brief if true, follow with ascii character encoding **/
bool with_char_;
};
/**
@brief stream inserter for an 8-bit quantity to be printed in hexadecimal.
@param os print on this stream
@param ins package for value to insert
**/
inline std::iostream &
operator<< (std::iostream & os, hex const & ins) {
ins.print(os);
return os;
}
/**
@class hex_view indentlog/print/hex.hpp
@brief Container for a range (unowned) of 1-byte values to be printed in hexadecimal
Print a range of bytes on an arbitrary character stream.
Does not use @c iomanips, so will not alter stream formatting flags if used with @c iostream.
Example:
@code
#include "indentlog/print/hex.hpp"
std::ostream os = ...;
os << hex_view("hello", false); // output: [68 65 6c 6c 6f]
os << hex_view("hello", true); // output: [68(h) 65(e) 6c(l) 6c(l) 6f(o)]
@endcode
**/
struct hex_view {
/** @brief constructor; create stream-inserter instance for a range of bytes **/
hex_view(std::uint8_t const * lo, std::uint8_t const * hi, bool as_text)
: lo_{lo}, hi_{hi}, as_text_{as_text} {}
/** @brief constructor; create stream-inserter instance for a range of chars **/
hex_view(char const * lo, char const * hi, bool as_text)
: lo_{reinterpret_cast<std::uint8_t const *>(lo)},
hi_{reinterpret_cast<std::uint8_t const *>(hi)},
as_text_{as_text} {}
/**
@brief print hexadecimal byte range on stream.
@param os print on this stream
@tparam Stream typename for character stream.
**/
template <typename Stream>
void print(Stream & os) const {
os << "[";
std::size_t i = 0;
for (std::uint8_t const * p = lo_; p < hi_; ++p) {
if (i > 0)
os << " ";
xo::hex(*p, as_text_).print(os);
//os << xo::hex(*p, as_text_);
++i;
}
os << "]";
}
private:
/** @brief print byte range starting at this address **/
std::uint8_t const * lo_;
/** @brief print byte range up to (but not including) this address **/
std::uint8_t const * hi_;
/** @brief if true also print ascii encoding (for printable codes),
* \c ? otherwise. @see hex::with_char
**/
bool as_text_;
};
/**
@brief stream inserter for a range of 1-byte values to be printed in hexadecimal
@param os print on this stream.
@param ins (container for) values to insert.
**/
template <typename Stream>
Stream &
operator<< (Stream & os, hex_view const & ins) {
ins.print(os);
return os;
}
} /*namespace xo*/
/* end hex.hpp */

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

View file

@ -0,0 +1,24 @@
/* @file pair.hpp */
#pragma once
#include <iostream>
#include <utility>
namespace std {
template <typename T, typename U>
inline std::ostream &
operator<<(std::ostream & os,
std::pair<T,U> const & x)
{
os << "["
<< x.first
<< " "
<< x.second
<< "]";
return os;
} /*operator<<*/
} /*namespace std*/
/* end pair.hpp */

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

View file

@ -0,0 +1,153 @@
/* file quoted.hpp
*
* author: Roland Conybeare, Sep 2022
*/
#pragma once
#include "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 quot_impl<T>. T must be moveable!
* 2. T&:
* copy reference into quot_impl<T&>.
* similarly for T const &, copy reference into quot_impl<T const &>
*/
template<typename T>
class quot_impl {
public:
quot_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_; }
static void print_with_escapes(const std::string & xs,
std::ostream & os)
{
/* 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 */
/* somehow attempt to escape the newline triggers collapse */
os << "\\n";
break;
case '\r':
/* cr -> \r */
os << "\\r";
break;
case '\\':
/* \ => \\ (mind c++ requires we escape \) */
os << "\\\\";
break;
default:
os << ch;
break;
}
}
os << "\"";
}
void print(std::ostream & os) const {
std::string xs = xo::tostr(value_);
if (xs.empty()) {
/* always 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 {
print_with_escapes(xs, os);
}
} /*print*/
private:
/* .unq_flag: if true, omit surrounding " chars
* whenever printed value satisfies both:
* - no escaped chars
* - no spaces
*/
bool unq_flag_ = false;
/* .value: value to be printed */
T value_;
}; /*quot_impl*/
template<typename T>
std::ostream &
operator<<(std::ostream & os, quot_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 quot_impl ctor
*
* 2a. call quoted(x) with std::string & x, then:
* - T deduced to [std::string &]
* - std::string & passed to quot_impl ctor
*
* 2b. call quoted(x) with std::string const & x, then:
* - T deduced to [std::string const &]
* - std::string const & passed to quot_impl ctor
*/
template<typename T>
auto quot(T && x) {
return quot_impl(false /*unq_flag*/, std::forward<T>(x));
}
inline auto qcstr(char const * x) {
return quot(x);
} /*qcstr*/
template<typename T>
auto unq(T && x) {
return quot_impl(true /*unq_flag*/, std::forward<T>(x));
}
} /*namespace print*/
} /*namespace xo*/
/* end quoted.hpp */

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

View file

@ -0,0 +1,115 @@
/* @file tag.hpp */
#pragma once
#include "tag_config.hpp"
#include "concat.hpp"
#include "quoted.hpp"
#include "color.hpp"
#include <iostream>
// STRINGIFY(xyz) -> "xyz"
#ifndef STRINGIFY
# define STRINGIFY(x) #x
#endif
// TAG(xyz) -> tag("xyz", xyz)
#define TAG(x) xo::make_tag(STRINGIFY(x), x)
#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>
struct tag_impl {
tag_impl(Name const & n, Value const & v)
: name_{n}, value_{v} {}
tag_impl(Name && n, Value && v)
: name_{std::forward<Name>(n)}, value_{std::forward<Value>(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*/
// ----- tag -----
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*/
// ----- operator<< on tag_impl -----
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::tag_color, concat((char const *)":", tag.name()))
<< " " << unq(tag.value());
return s;
} /*operator<<*/
} /*namespace xo*/
/* end tag.hpp */

View file

@ -0,0 +1,28 @@
/* @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 to use for tags
* os << tag("foo", foovalue)
* to produces output like
* :foo foovalue
* with :foo using .tag_color
*/
static color_spec_type tag_color;
}; /*tag_config_impl*/
template <typename Tag>
color_spec_type
tag_config_impl<Tag>::tag_color = color_spec_type::xterm(245);
using tag_config = tag_config_impl<class tag_config_tag>;
} /*namespace xo*/
/* end tag_config.hpp */

View file

@ -0,0 +1,90 @@
/* @file time.hpp */
#pragma once
#include "xo/indentlog/timeutil/timeutil.hpp"
namespace xo {
namespace time {
// ----- iso8601 -----
/* 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)
{
timeutil::print_iso8601(x.t0_, os);
return os;
} /*operator<<*/
// ----- hms_msec -----
/* 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(timeutil::utc_split_vs_midnight(t0).second); }
static hms_msec local(utc_nanos t0) { return hms_msec(timeutil::local_split_vs_midnight(t0).second); }
nanos dt_;
}; /*hms_msec*/
inline std::ostream &
operator<<(std::ostream & os, hms_msec x)
{
timeutil::print_hms_msec(x.dt_, os);
return os;
} /*operator<<*/
// ----- hms_usec -----
/* 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(timeutil::utc_split_vs_midnight(t0).second); }
static hms_usec local(utc_nanos t0) { return hms_usec(timeutil::local_split_vs_midnight(t0).second); }
nanos dt_;
}; /*hms_msec*/
inline std::ostream &
operator<<(std::ostream & os, hms_usec x)
{
timeutil::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::timeutil::print_utc_ymd_hms_usec(t0, os);
return os;
} /*operator<<*/
inline std::ostream & operator<<(std::ostream & os,
xo::time::nanos dt)
{
xo::time::timeutil::print_hms_usec(dt, os);
return os;
} /*operator<<*/
} /*namespace chrono*/
} /*namespace std*/
/* end time.hpp */

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

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

View file

@ -0,0 +1,297 @@
/* @file scope.hpp */
#pragma once
#include "log_state.hpp"
#include "print/filename.hpp"
#include "print/tostr.hpp"
#include "print/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(lvl, function_style::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_ = function_style::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_ = function_style::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>
typename 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>
typename 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 */

View file

@ -0,0 +1,297 @@
/* @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 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;
using utc_nanos = std::chrono::time_point<std::chrono::system_clock,
std::chrono::nanoseconds>;
using utc_micros = std::chrono::time_point<std::chrono::system_clock,
std::chrono::microseconds>;
struct timeutil {
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::timeutil::microseconds;
//using xo::timeutil::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> utc_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<microseconds>(t0)));
//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, in 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 = ::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)));
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);
} /*utc_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*/
/* print t0 like:
* yyyymmdd:hh:mm:ss.uuuuuu
* e.g.
* 19700101:00:00:00.000000 // epoch
* 20230921:16:29:35.123456 // 21sep2023 4:29:35 pm + 12345 us
*/
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] = utc_split_tm(t0);
/* no std::format in clang11 afaict */
char usec_buf[15];
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] = utc_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*/
}; /*timeutil*/
} /*namespace time*/
} /*namespace xo*/
/* end timeutil.hpp */

View file

@ -0,0 +1,17 @@
# indentlog unit test
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)
xo_add_utest_executable(${SELF_EXECUTABLE_NAME} ${SELF_SOURCE_FILES})
# ----------------------------------------------------------------
# 3rd party dependency: catch2
xo_self_dependency(${SELF_EXECUTABLE_NAME} indentlog)
xo_external_target_dependency(${SELF_EXECUTABLE_NAME} Catch2 Catch2::Catch2)
# end CMakeLists.txt

View file

@ -0,0 +1,40 @@
/* @file array.test.cpp */
#include "xo/indentlog/print/array.hpp" /* overload operator<< for std::array */
#include "xo/indentlog/print/tag.hpp"
#include <catch2/catch.hpp>
#include <sstream>
using namespace xo;
namespace ut {
TEST_CASE("array", "[array]") {
tag_config::tag_color = color_spec_type::none();
{
std::array<int, 0> x = {};
std::stringstream ss;
ss << x;
REQUIRE(ss.str() == "[]");
}
{
std::array<int, 1> x = {1};
std::stringstream ss;
ss << x;
REQUIRE(ss.str() == "[1]");
}
{
std::array<int, 2> x = {1, 2};
std::stringstream ss;
ss << x;
REQUIRE(ss.str() == "[1 2]");
}
}
} /*namespace ut*/
/* end array.test.cpp */

View file

@ -0,0 +1,47 @@
/* @file code_location.test.cpp */
#include "xo/indentlog/print/code_location.hpp"
#include "xo/indentlog/print/color.hpp"
#include "xo/indentlog/print/tag.hpp"
#include <vector>
#include <catch2/catch.hpp>
using namespace xo;
namespace ut {
struct code_location_tcase {
code_location_tcase() = default;
code_location_tcase(std::string_view file, std::uint32_t line, color_spec_type color, std::string_view output)
: file_{file}, line_{line}, color_{color}, output_{output} {}
/* target time value to test */
std::string_view file_;
std::uint32_t line_;
color_spec_type color_;
std::string_view output_;
}; /*code_location_tcase*/
std::vector<code_location_tcase> s_code_location_tcase_v(
{
code_location_tcase("/foo/bar", 123, color_spec_type::none(), "[bar:123]"),
code_location_tcase("/foo/bar", 123, color_spec_type::blue(), "[\033[31;34mbar\033[0m:123]"),
code_location_tcase("/foo/bar", 123, color_spec_type::xterm(196), "[\033[38;5;196mbar\033[0m:123]"),
code_location_tcase("/foo/bar", 123, color_spec_type::rgb(255, 127, 63), "[\033[38;2;255;127;63mbar\033[0m:123]"),
});
TEST_CASE("code_location", "[code_location]") {
for (std::uint32_t i_tc = 0, z_tc = s_code_location_tcase_v.size(); i_tc < z_tc; ++i_tc) {
code_location_tcase const & tc = s_code_location_tcase_v[i_tc];
INFO(tostr(xtag("i_tc", i_tc), xtag("file", tc.file_), xtag("line", tc.line_), xtag("color", tc.color_)));
INFO(xtag("tc.output", tc.output_));
std::stringstream ss;
ss << code_location(tc.file_, tc.line_, tc.color_);
REQUIRE(ss.str() == tc.output_);
}
} /*TEST_CASE(code_location)*/
} /*namespace ut*/
/* end code_location.test.cpp */

View file

@ -0,0 +1,43 @@
/* @file filename.test.cpp */
#include "xo/indentlog/print/filename.hpp"
#include "xo/indentlog/print/tag.hpp"
#include <vector>
#include <catch2/catch.hpp>
using namespace xo;
namespace ut {
struct filename_tcase {
filename_tcase() = default;
filename_tcase(std::string_view path, std::string_view basename)
: path_{path}, basename_{basename} {}
/* target time value to test */
std::string_view path_;
std::string_view basename_;
}; /*filename_tcase*/
std::vector<filename_tcase> s_filename_tcase_v(
{
filename_tcase("foo", "foo"),
filename_tcase("/foo", "foo"),
filename_tcase("/foo/bar", "bar"),
});
TEST_CASE("filename", "[filename]") {
for (std::uint32_t i_tc = 0, z_tc = s_filename_tcase_v.size(); i_tc < z_tc; ++i_tc) {
filename_tcase const & tc = s_filename_tcase_v[i_tc];
INFO(tostr(xtag("i_tc", i_tc), xtag("path", tc.path_)));
INFO(xtag("tc.basename", tc.basename_));
std::stringstream ss;
ss << basename(tc.path_);
REQUIRE(ss.str() == tc.basename_);
}
} /*TEST_CASE(filename)*/
} /*namespace ut*/
/* end filename.test.cpp */

View file

@ -0,0 +1,88 @@
/* @file fixed.test.cpp */
#include "xo/indentlog/print/fixed.hpp"
#include "xo/indentlog/print/tag.hpp"
#include <catch2/catch.hpp>
#include <sstream>
using namespace xo;
namespace ut {
struct fixed_tcase {
fixed_tcase() = default;
fixed_tcase(double x, std::uint32_t prec, std::string s)
: x_{x}, prec_{prec}, s_{std::move(s)} {}
/* floating-point value to format */
double x_ = 0.0;
/* precision */
std::uint32_t prec_ = 0;
/* expected result */
std::string s_;
}; /*fixed_tcase*/
std::vector<fixed_tcase> s_fixed_tcase_v(
{fixed_tcase(0.0, 0, "0"),
fixed_tcase(0.0, 1, "0.0"),
fixed_tcase(0.0, 2, "0.00"),
//fixed_tcase(0.5, 0, "1"), // failing --> 0
fixed_tcase(0.5, 1, "0.5"),
fixed_tcase(0.049, 0, "0"),
fixed_tcase(0.049, 1, "0.0"),
fixed_tcase(0.049, 2, "0.05"),
fixed_tcase(0.05, 0, "0"),
fixed_tcase(0.05, 1, "0.1"),
fixed_tcase(0.05, 2, "0.05"),
fixed_tcase(-0.05, 0, "-0"),
fixed_tcase(-0.05, 1, "-0.1"),
fixed_tcase(-0.05, 2, "-0.05"),
fixed_tcase(1e-6, 0, "0"),
fixed_tcase(1e-6, 1, "0.0"),
fixed_tcase(1e-6, 2, "0.00"),
fixed_tcase(1e-6, 3, "0.000"),
fixed_tcase(1e-6, 4, "0.0000"),
fixed_tcase(1e-6, 5, "0.00000"),
fixed_tcase(1e-6, 6, "0.000001"),
fixed_tcase(-1e-6, 0, "-0"),
fixed_tcase(-1e-6, 1, "-0.0"),
fixed_tcase(-1e-6, 2, "-0.00"),
fixed_tcase(-1e-6, 3, "-0.000"),
fixed_tcase(-1e-6, 4, "-0.0000"),
fixed_tcase(-1e-6, 5, "-0.00000"),
fixed_tcase(-1e-6, 6, "-0.000001"),
fixed_tcase(666.66, 1, "666.7"),
fixed_tcase(666.66, 2, "666.66"),
fixed_tcase(-666.66, 1, "-666.7"),
fixed_tcase(-666.66, 2, "-666.66"),
});
TEST_CASE("fixed", "[fixed]") {
tag_config::tag_color = color_spec_type::none();
for (std::uint32_t i_tc = 0, z_tc = s_fixed_tcase_v.size(); i_tc < z_tc; ++i_tc) {
fixed_tcase const & tc = s_fixed_tcase_v[i_tc];
INFO(tostr(xtag("i_tc", i_tc), xtag("x", tc.x_), xtag("prec", tc.prec_)));
std::stringstream ss;
ss << fixed(tc.x_, tc.prec_);
INFO(xtag("ss.str", ss.str()));
REQUIRE(ss.str() == tc.s_);
}
REQUIRE(s_fixed_tcase_v.size() > 1);
}
} /*namespace ut*/
/* end fixed.test.cpp */

View file

@ -0,0 +1,64 @@
/* @file function.test.cpp */
#include "xo/indentlog/print/function.hpp"
#include "xo/indentlog/print/tag.hpp"
#include <catch2/catch.hpp>
#include <sstream>
using namespace xo;
namespace ut {
struct function_tcase {
function_tcase() = default;
function_tcase(function_style style, color_spec_type spec, std::string_view pretty, std::string_view output)
: style_{style}, spec_{spec}, pretty_{pretty}, output_{output} {}
/* function style: literal|pretty|streamlined|simple*/
function_style style_;
/* color spec for output */
color_spec_type spec_;
/* function signature (as per __PRETTY_FUNCTION__) */
std::string_view pretty_;
/* output text */
std::string_view output_;
}; /*function_tcase*/
std::vector<function_tcase> s_function_tcase_v(
{
function_tcase(function_style::literal, color_spec_type::none(), "anything goes here", "anything goes here"),
function_tcase(function_style::pretty, color_spec_type::none(), "void foo() const", "[void foo() const]"),
function_tcase(function_style::streamlined, color_spec_type::none(), "void foo() const", "foo"),
function_tcase(function_style::simple, color_spec_type::none(), "void foo() const", "foo"),
function_tcase(function_style::pretty, color_spec_type::none(), "void xo::class::foo() const", "[void xo::class::foo() const]"),
function_tcase(function_style::streamlined, color_spec_type::none(), "void xo::class::foo() const", "class::foo"),
function_tcase(function_style::simple, color_spec_type::none(), "void xo::class::foo() const", "foo"),
function_tcase(function_style::pretty, color_spec_type::blue(), "void xo::class::foo() const", "[\033[31;34mvoid xo::class::foo() const\033[0m]"),
function_tcase(function_style::streamlined, color_spec_type::none(), "void xo::reactor::FifoQueue<T, EvTimeFn>::notify_ev(const T&) [with T = std::pair<std::chrono::time_point<std::chrono::_V2::system_clock, std::chrono::duration<long int, std::ratio<1, 1000000000> > >, long unsigned int>; EvTimeFn = xo::reactor::EventTimeFn<std::pair<std::chrono::time_point<std::chrono::_V2::system_clock, std::chrono::duration<long int, std::ratio<1, 1000000000> > >, long unsigned int> >]", "FifoQueue::notify_ev"),
function_tcase(function_style::streamlined, color_spec_type::none(), "token_type xo::tok::tokenizer<char>::assemble_token(const span_type &) const [CharT = char]", "tokenizer::assemble_token"),
});
TEST_CASE("function", "[function]") {
tag_config::tag_color = color_spec_type::none();
for (std::uint32_t i_tc = 0, z_tc = s_function_tcase_v.size(); i_tc < z_tc; ++i_tc) {
function_tcase const & tc = s_function_tcase_v[i_tc];
INFO(tostr(xtag("i_tc", i_tc), xtag("style", tc.style_), xtag("spec", tc.spec_), xtag("pretty", tc.pretty_)));
std::stringstream ss;
ss << function_name(tc.style_, tc.spec_, tc.pretty_);
INFO(xtag("ss.str", ss.str()));
REQUIRE(ss.str() == tc.output_);
}
REQUIRE(s_function_tcase_v.size() > 1);
}
} /*namespace ut*/
/* end function.test.cpp */

View file

@ -0,0 +1,6 @@
/* @file indentlog_utest_main.cpp */
#define CATCH_CONFIG_MAIN
#include "catch2/catch.hpp"
/* end indentlog_utest_main.cpp */

View file

@ -0,0 +1,356 @@
/* @file fixed.test.cpp */
#include "xo/indentlog/scope.hpp"
#include "xo/indentlog/print/quoted.hpp"
//#include "xo/indentlog/print/tag.hpp"
#include "xo/indentlog/print/hex.hpp"
#include <catch2/catch.hpp>
#include <sstream>
namespace ut {
using namespace xo;
using namespace xo::print;
struct quot_tcase {
quot_tcase() = default;
quot_tcase(std::string x, bool unq_flag, std::string s)
: x_{std::move(x)}, unq_flag_{unq_flag}, s_{std::move(s)} {}
/* string to be printed-in-machine-readable-form */
std::string x_;
/* if true: omit surrounding " chars when unambiguous
* (printed string does not contain spaces or escaped chars)
* if false: always require surrounding " chars
*/
bool unq_flag_ = true;
/* expected result */
std::string s_;
}; /*quot_tcase*/
/* NOTE: spelled out tests here in aftermath
* of hard-to-diagnose regression in gcc 13.2;
* turned out to have something to originate in confusion
* between xo::print::quoted and std::quoted.
*
* Problem does not occur in gcc 12.3 and earlier,
* perhaps some alias for std::quoted appears somewhere in global
* namespace??
*
* Resolved by renaming xo::print::quoted -> xo::print::quot
*/
TEST_CASE("sstream.1char", "[sstream]") {
constexpr bool c_debug_flag = false;
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.sstream.1char"));
/* testing unexpected sstream behavior */
{
std::stringstream ss;
log && log("empty stream");
ss << '\\';
std::string str = ss.str();
log && log("after: lone escaped backslash");
log && log(hex_view(str.data(), str.data() + str.size(), true));
//REQUIRE(ss.view() == std::string_view("\\")); // n/avail on osx
REQUIRE(str == std::string("\\"));
ss << 'n';
std::string str2 = ss.str();
log && log("after: lone 'n' char");
log && log(hex_view(str2.data(), str2.data() + str2.size(), true));
// REQUIRE(ss.view() == std::string_view("\\n")); // n/avail on osx
REQUIRE(str2 == std::string("\\n"));
log && log("ss.str()=[", str2, "]");
}
} /*TEST_CASE(sstream.1char)*/
TEST_CASE("sstream.2bslash", "[sstream]") {
constexpr bool c_debug_flag = false;
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.sstream.2bslash"));
/* testing unexpected sstream behavior */
{
std::stringstream ss;
log && log("empty stream");
ss << "\\\\";
std::string str = ss.str();
log && log("after: 2x escaped backslash");
log && log(hex_view(str.data(), str.data() + str.size(), true));
REQUIRE(str == std::string("\\\\"));
log && log("ss.str()=[", ss.str(), "]");
}
} /*TEST_CASE(sstream.2bslash)*/
TEST_CASE("sstream.2char", "[sstream]") {
constexpr bool c_debug_flag = false;
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.sstream.2char"));
/* testing unexpected sstream behavior */
{
std::stringstream ss;
log && log("empty stream");
ss << "\\n";
std::string str = ss.str();
log && log("after: '\\n' escaped backslash + n");
log && log(hex_view(str.data(), str.data() + str.size(), true));
}
}
TEST_CASE("sstream.3char", "[sstream]") {
constexpr bool c_debug_flag = false;
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.sstream.3char"));
/* testing unexpected sstream behavior */
{
std::stringstream ss;
log && log("empty stream");
/* this is what quot("\\n") should wind up executing.. */
ss << "\\\\";
ss << 'n';
std::string str = ss.str();
log && log("after: '\\\\n' 2x escaped backslash + n");
log && log(hex_view(str.data(), str.data() + str.size(), true));
REQUIRE(str == std::string("\\\\n"));
}
}
TEST_CASE("sstream.quot.1bslash", "[quot]") {
constexpr bool c_debug_flag = false;
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.sstream.quot.1bslash"));
log && log("quot(\"\\\")=[", quot("\\"), "]");
std::stringstream ss2;
ss2 << quot("\\");
std::string str = ss2.str();
REQUIRE(str == std::string("\"\\\\\"")); /* ["\\"] */
}
TEST_CASE("sstream.quot.newline", "[quot]") {
constexpr bool c_debug_flag = false;
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.sstream.quot.newline"));
log && log("quot(\"\\n\")=[", quot("\n"), "]");
std::stringstream ss2;
ss2 << quot("\n");
std::string str = ss2.str();
REQUIRE(str == std::string("\"\\n\"")); /* ["\n"] */
}
TEST_CASE("sstream.quot.2bslash", "[quot]") {
constexpr bool c_debug_flag = false;
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.quot.2bslash"));
log && log("quot(\"\\\\\")=[", quot("\\\\"), "]");
std::stringstream ss2;
ss2 << quot("\\\\"); /* quoting string with two backslashes need to give ["\\\\"] */
std::string str = ss2.str();
REQUIRE(str == std::string("\"\\\\\\\\\"")); /* rhs is ["\\\\"] */
}
TEST_CASE("sstream.quot.2charnewline", "[quot]") {
constexpr bool c_debug_flag = false;
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.sstream.quot.2charnewline"));
log && log("quot(\"x\\n\")=[", quot("x\n"), "]");
std::stringstream ss2;
ss2 << quot("x\n");
std::string str = ss2.str();
REQUIRE(str == std::string("\"x\\n\"")); /* ["\n"] */
}
TEST_CASE("sstream.quot.2char", "[quot]") {
constexpr bool c_debug_flag = false;
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.quot.2char"));
log && log("quot(\"\\n\")=[", quot("\\n"), "]");
std::stringstream ss2;
ss2 << quot("\\n");
std::string str = ss2.str();
//std::cerr << quoted_debug::s_log_last_quoted.view() << std::endl;
//log && log("debug_log=[", quoted_debug::s_log_last_quoted.view() , "]");
REQUIRE(str == std::string("\"\\\\n\""));
}
TEST_CASE("sstream.quot.foonewline", "[quot]") {
constexpr bool c_debug_flag = false;
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.sstream.quot.2charnewline"));
std::stringstream ss2;
ss2 << quot("foo\n");
std::string str = ss2.str();
REQUIRE(str == std::string("\"foo\\n\"")); /* ["\n"] */
}
TEST_CASE("sstream.rest", "[quot]") {
constexpr bool c_debug_flag = false;
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.sstream"));
/* testing unexpected sstream behavior */
{
std::stringstream ss;
log && log("empty stream");
ss << quot("\n");
std::string str = ss.str();
log && log("after: quot('\\n')");
log && log(hex_view(str.data(), str.data() + str.size(), true));
}
/* testing unexpected sstream behavior */
{
std::stringstream ss;
log && log("empty stream");
ss << quot("foo\n");
std::string str = ss.str();
log && log("after: quot(\"foo\n\")");
log && log(hex_view(str.data(), str.data() + str.size(), true));
log && log("> ss.str ----------------");
log && log(ss.str());
log && log("< ss.str ----------------");
}
/* testing unexpected sstream behavior */
{
std::stringstream ss;
log && log("empty stream");
ss << unq("\n");
std::string str = ss.str();
log && log("after: unq('\\n')");
log && log(hex_view(str.data(), str.data() + str.size(), true));
}
} /*TEST_CASE(sstream)*/
std::vector<quot_tcase> s_quot_tcase_v(
{
quot_tcase("", true, "\"\""),
quot_tcase("", false, "\"\""),
quot_tcase("foo", true, "foo"),
quot_tcase("foo", false, "\"foo\""),
quot_tcase("foo\n", true, "\"foo\\n\""),
quot_tcase("foo\n", false, "\"foo\\n\""),
quot_tcase("two words", true, "\"two words\""),
quot_tcase("two words", false, "\"two words\""),
quot_tcase("1st\n2nd", true, "\"1st\\n2nd\""),
quot_tcase("1st\n2nd", false, "\"1st\\n2nd\""),
quot_tcase("misakte\rfix", true, "\"misakte\\rfix\""),
quot_tcase("misakte\rfix", false, "\"misakte\\rfix\""),
quot_tcase("\"oh!\", she said", true, "\"\\\"oh!\\\", she said\""),
quot_tcase("\"oh!\", she said", false, "\"\\\"oh!\\\", she said\""),
// special carveout for strings bracketed by <..>; assume already well-formed
quot_tcase("<object printer output>", true, "<object printer output>"),
quot_tcase("<object printer output>", false, "<object printer output>"),
});
TEST_CASE("quot", "[quot]") {
for (std::uint32_t i_tc = 0, z_tc = s_quot_tcase_v.size(); i_tc < z_tc; ++i_tc) {
quot_tcase const & tc = s_quot_tcase_v[i_tc];
/* NOTE: don't use tag()/xtag() here,
* since implementation relies on the inserter we are testing
*/
INFO(tostr("i_tc=", i_tc, " unq_flag=", tc.unq_flag_));
INFO("tc.x_ ----------------");
INFO(tostr("[", tc.x_, "]"));
INFO("tc.x_ ----------------");
std::stringstream ss;
if (tc.unq_flag_)
ss << unq(tc.x_);
else
ss << quot(tc.x_);
std::string str = ss.str();
INFO("tc.s ----------------");
INFO(tostr("[", tc.s_, "]"));
INFO("tc.s ----------------");
INFO("ss.str ----------------");
INFO(tostr("[", hex_view(str.data(), str.data() + str.size(), true), "]"));
INFO(tostr("[", ss.str(), "]"));
INFO("ss.str ----------------");
REQUIRE(ss.str() == tc.s_);
if (ss.str() != tc.s_)
break;
}
REQUIRE(s_quot_tcase_v.size() > 1);
}
} /*namespace ut*/
/* end quoted.test.cpp */

View file

@ -0,0 +1,69 @@
/* @file tag.test.cpp */
#include "xo/indentlog/print/tag.hpp"
#include "xo/indentlog/print/vector.hpp"
#include "xo/indentlog/print/concat.hpp"
#include <catch2/catch.hpp>
#include <sstream>
using namespace xo;
namespace ut {
using xo::print::ccs;
TEST_CASE("tag", "[tag]") {
tag_config::tag_color = color_spec_type::none();
{
std::stringstream ss;
ss << tag("foo", ccs("hello,world!"));
REQUIRE(ss.str() == ":foo hello,world!");
}
{
std::stringstream ss;
ss << tag("foo", ccs("hello, world!"));
REQUIRE(ss.str() == ":foo \"hello, world!\"");
}
{
std::stringstream ss;
std::vector<int> v = {1, 2, 3};
ss << tag("foo", v);
REQUIRE(ss.str() == ":foo \"[1 2 3]\"");
}
{
std::stringstream ss;
ss << tag("foo", concat("farenheit", 451));
REQUIRE(ss.str() == ":foo farenheit451");
}
{
std::stringstream ss;
ss << tag("foo", ccs("hello")) << xtag("bar", ccs("there"));
REQUIRE(ss.str() == ":foo hello :bar there");
}
tag_config::tag_color = color_spec_type::blue();
{
std::stringstream ss;
ss << tag("foo", ccs("hello,world!"));
/* color on color off
* <---------> <----->
*
* see [indentlog/print/color.hpp] for escape sequences
*/
REQUIRE(ss.str() == "\033[31;34m:foo\033[0m hello,world!");
}
} /*TEST_CASE(tag)*/
} /*namespace ut*/
/* end tag.test.cpp */

View file

@ -0,0 +1,175 @@
/* @file timeutil.test.cpp */
#include "xo/indentlog/timeutil/timeutil.hpp"
#include "xo/indentlog/print/tag.hpp"
#include <catch2/catch.hpp>
#include <sstream>
using namespace xo;
using namespace xo::time;
using namespace std::chrono;
namespace ut {
template <typename FromTime>
inline utc_micros to_micros(FromTime tm) {
return std::chrono::time_point_cast<xo::time::microseconds>(tm);
} /*to_micros*/
TEST_CASE("epoch", "[timeutil]") {
//tag_config::tag_color = color_spec_type::none();
using xo::time::microseconds;
utc_nanos t0 = timeutil::epoch();
REQUIRE(std::chrono::system_clock::to_time_t(to_micros(t0)) == std::time_t(0));
} /*TEST_CASE(epoch)*/
TEST_CASE("ymd_hms", "[timeutil]") {
{
utc_nanos t0 = timeutil::ymd_hms(19700101 /*ymd*/, 0 /*hms*/);
REQUIRE(std::chrono::system_clock::to_time_t(to_micros(t0)) == std::time_t(0));
}
{
utc_nanos t0 = timeutil::ymd_hms(19700101 /*ymd*/, 1 /*hms*/);
REQUIRE(std::chrono::system_clock::to_time_t(to_micros(t0)) == std::time_t(1));
}
{
utc_nanos t0 = timeutil::ymd_hms(19700101 /*ymd*/, 100 /*hms*/);
REQUIRE(std::chrono::system_clock::to_time_t(to_micros(t0)) == std::time_t(60));
}
{
utc_nanos t0 = timeutil::ymd_hms(19700101 /*ymd*/, 10000 /*hms*/);
REQUIRE(std::chrono::system_clock::to_time_t(to_micros(t0)) == std::time_t(3600));
}
{
utc_nanos t0 = timeutil::ymd_hms(19700101 /*ymd*/, 235959 /*hms*/);
REQUIRE(std::chrono::system_clock::to_time_t(to_micros(t0)) == std::time_t(86399));
}
{
utc_nanos t0 = timeutil::ymd_hms(19700102 /*ymd*/, 235959 /*hms*/);
REQUIRE(std::chrono::system_clock::to_time_t(to_micros(t0)) == std::time_t(86400 + 86399));
}
{
utc_nanos t0 = timeutil::ymd_hms(19700131 /*ymd*/, 235959 /*hms*/);
REQUIRE(std::chrono::system_clock::to_time_t(to_micros(t0)) == std::time_t(30 * 86400 + 86399));
}
{
utc_nanos t0 = timeutil::ymd_hms(19700201 /*ymd*/, 235959 /*hms*/);
REQUIRE(std::chrono::system_clock::to_time_t(to_micros(t0)) == std::time_t(31 * 86400 + 86399));
}
} /*TEST_CASE(ymd_hms)*/
TEST_CASE("ymd_midnight", "[timeutil]") {
{
utc_nanos t0 = timeutil::ymd_midnight(19700101 /*ymd*/);
REQUIRE(std::chrono::system_clock::to_time_t(to_micros(t0)) == std::time_t(0));
}
{
utc_nanos t0 = timeutil::ymd_midnight(19700102 /*ymd*/);
REQUIRE(std::chrono::system_clock::to_time_t(to_micros(t0)) == std::time_t(86400));
}
{
utc_nanos t0 = timeutil::ymd_midnight(19700131 /*ymd*/);
REQUIRE(std::chrono::system_clock::to_time_t(to_micros(t0)) == std::time_t(30 * 86400));
}
{
utc_nanos t0 = timeutil::ymd_midnight(19700201 /*ymd*/);
REQUIRE(system_clock::to_time_t(to_micros(t0)) == std::time_t(31 * 86400));
}
} /*TEST_CASE(ymd_midnight)*/
struct timeutil_tcase {
timeutil_tcase() = default;
timeutil_tcase(uint32_t ymd, uint32_t hms, uint32_t usec,
std::time_t epoch_sec, std::time_t midnight_sec, std::uint32_t fractional_sec, std::uint32_t fractional_usec,
std::string const & utc_ymd_hms_usec_str,
std::string const & iso8601_str)
: ymd_{ymd}, hms_{hms}, usec_{usec},
epoch_sec_{epoch_sec},
midnight_sec_{midnight_sec},
fractional_sec_{fractional_sec},
fractional_usec_{fractional_usec},
utc_ymd_hms_usec_str_{utc_ymd_hms_usec_str},
iso8601_str_{iso8601_str} {}
/* target time value to test */
std::uint32_t ymd_ = 19700101;
std::uint32_t hms_ = 0;
std::uint32_t usec_ = 0;
std::time_t epoch_sec_ = 0;
std::time_t midnight_sec_ = 0;
std::uint32_t fractional_sec_ = 0;
std::uint32_t fractional_usec_ = 0;
std::string utc_ymd_hms_usec_str_;
std::string iso8601_str_;
}; /*timeutil_tcase*/
std::vector<timeutil_tcase> s_timeutil_tcase_v(
/* -------- inputs ------- ------------------------------------------------ outputs ---------------------------------------
* fractional_usec
* fractional_sec |
* ymd hms usec epoch_sec midnight_sec v v utc_ymd_hms_usec_str iso8601_str
*/
{
timeutil_tcase(19700101, 0, 0, 0, 0, 0, 0, "19700101:00:00:00.000000", "1970-01-01T00:00:00.000Z"),
timeutil_tcase(19700101, 0, 1, 0, 0, 0, 1, "19700101:00:00:00.000001", "1970-01-01T00:00:00.000Z"),
timeutil_tcase(19700101, 0, 123456, 0, 0, 0, 123456, "19700101:00:00:00.123456", "1970-01-01T00:00:00.123Z"),
timeutil_tcase(19700101, 0, 500000, 0, 0, 0, 500000, "19700101:00:00:00.500000", "1970-01-01T00:00:00.500Z"),
timeutil_tcase(19700101, 0, 987654, 0, 0, 0, 987654, "19700101:00:00:00.987654", "1970-01-01T00:00:00.987Z"),
timeutil_tcase(19700101, 0, 999999, 0, 0, 0, 999999, "19700101:00:00:00.999999", "1970-01-01T00:00:00.999Z"),
timeutil_tcase(19700101, 1, 999999, 1, 0, 1, 999999, "19700101:00:00:01.999999", "1970-01-01T00:00:01.999Z"),
timeutil_tcase(19700101, 100, 999999, 60, 0, 60, 999999, "19700101:00:01:00.999999", "1970-01-01T00:01:00.999Z"),
timeutil_tcase(19700101, 10000, 999999, 3600, 0, 3600, 999999, "19700101:01:00:00.999999", "1970-01-01T01:00:00.999Z"),
timeutil_tcase(19700101, 235959, 999999, 24*3600-1, 0, 86399, 999999, "19700101:23:59:59.999999", "1970-01-01T23:59:59.999Z"),
timeutil_tcase(19700102, 100, 999999, 86400+60, 86400, 60, 999999, "19700102:00:01:00.999999", "1970-01-02T00:01:00.999Z"),
});
TEST_CASE("ymd_hms_usec", "[timeutil]") {
for (std::uint32_t i_tc = 0, z_tc = s_timeutil_tcase_v.size(); i_tc < z_tc; ++i_tc) {
timeutil_tcase const & tc = s_timeutil_tcase_v[i_tc];
INFO(tostr(xtag("i_tc", i_tc), xtag("ymd", tc.ymd_)));
INFO(xtag("tc.epoch_sec", tc.epoch_sec_));
INFO(xtag("tc.utc_ymd_hms_usec_str", tc.utc_ymd_hms_usec_str_));
utc_nanos const t0 = timeutil::ymd_hms_usec(tc.ymd_, tc.hms_, tc.usec_);
REQUIRE(system_clock::to_time_t(to_micros(t0)) == std::time_t(tc.epoch_sec_));
auto x = timeutil::utc_split_vs_midnight(t0);
REQUIRE(system_clock::to_time_t(to_micros(x.first)) == tc.midnight_sec_);
REQUIRE(x.second == seconds(tc.fractional_sec_) + microseconds(tc.fractional_usec_));
{
std::stringstream ss;
timeutil::print_utc_ymd_hms_usec(t0, ss);
REQUIRE(ss.str() == tc.utc_ymd_hms_usec_str_);
}
{
std::stringstream ss;
timeutil::print_iso8601(t0, ss);
REQUIRE(ss.str() == tc.iso8601_str_);
}
}
} /*TEST_CASE(ymd_hms_usec)*/
} /*namespace ut*/
/* end timeutil.test.cpp */

View file

@ -0,0 +1,50 @@
/* @file vector.test.cpp */
#include "xo/indentlog/print/vector.hpp" /* overload operator<< for std::vector */
#include "xo/indentlog/print/tag.hpp"
#include <catch2/catch.hpp>
#include <sstream>
using namespace xo;
namespace ut {
struct vector_tcase {
vector_tcase() = default;
vector_tcase(std::vector<int> const & x, std::string s)
: x_{x}, s_{std::move(s)} {}
/* vector to print */
std::vector<int> x_;
/* expected result */
std::string s_;
}; /*vector_tcase*/
std::vector<vector_tcase> s_vector_tcase_v(
{vector_tcase({}, "[]"),
vector_tcase({1}, "[1]"),
vector_tcase({1, 2}, "[1 2]"),
vector_tcase({10, 20, 30}, "[10 20 30]"),
});
TEST_CASE("vector", "[vector]") {
tag_config::tag_color = color_spec_type::none();
for (std::uint32_t i_tc = 0, z_tc = s_vector_tcase_v.size(); i_tc < z_tc; ++i_tc) {
vector_tcase const & tc = s_vector_tcase_v[i_tc];
INFO(tostr(xtag("i_tc", i_tc), xtag("x", tc.x_)));
std::stringstream ss;
ss << tc.x_;
INFO(xtag("ss.str", ss.str()));
REQUIRE(ss.str() == tc.s_);
}
REQUIRE(s_vector_tcase_v.size() > 1);
}
} /*namespace ut*/
/* end vector.test.cpp */