diff --git a/xo-indentlog/.github/workflows/cmake-single-platform.yml b/xo-indentlog/.github/workflows/cmake-single-platform.yml new file mode 100644 index 00000000..a80d88cb --- /dev/null +++ b/xo-indentlog/.github/workflows/cmake-single-platform.yml @@ -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}}) diff --git a/xo-indentlog/.gitignore b/xo-indentlog/.gitignore new file mode 100644 index 00000000..d809ed06 --- /dev/null +++ b/xo-indentlog/.gitignore @@ -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* diff --git a/xo-indentlog/BUILD.md b/xo-indentlog/BUILD.md new file mode 100644 index 00000000..4223d6b5 --- /dev/null +++ b/xo-indentlog/BUILD.md @@ -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 diff --git a/xo-indentlog/CMakeLists.txt b/xo-indentlog/CMakeLists.txt new file mode 100644 index 00000000..2a7ff773 --- /dev/null +++ b/xo-indentlog/CMakeLists.txt @@ -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 diff --git a/xo-indentlog/FAQ b/xo-indentlog/FAQ new file mode 100644 index 00000000..27d04f3b --- /dev/null +++ b/xo-indentlog/FAQ @@ -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 diff --git a/xo-indentlog/FILES b/xo-indentlog/FILES new file mode 100644 index 00000000..7838f779 --- /dev/null +++ b/xo-indentlog/FILES @@ -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 + ... diff --git a/xo-indentlog/LICENSE b/xo-indentlog/LICENSE new file mode 100644 index 00000000..ed66b81e --- /dev/null +++ b/xo-indentlog/LICENSE @@ -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. diff --git a/xo-indentlog/MARKDOWN b/xo-indentlog/MARKDOWN new file mode 100644 index 00000000..990d8275 --- /dev/null +++ b/xo-indentlog/MARKDOWN @@ -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 diff --git a/xo-indentlog/README.md b/xo-indentlog/README.md new file mode 100644 index 00000000..a79ff1a6 --- /dev/null +++ b/xo-indentlog/README.md @@ -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) diff --git a/xo-indentlog/TODO b/xo-indentlog/TODO new file mode 100644 index 00000000..bc404053 --- /dev/null +++ b/xo-indentlog/TODO @@ -0,0 +1,5 @@ + + +sphinx_markdown_builder + +https://stackoverflow.com/questions/13396856/markdown-output-for-sphinx-based-documentation diff --git a/xo-indentlog/cmake/indentlogConfig.cmake.in b/xo-indentlog/cmake/indentlogConfig.cmake.in new file mode 100644 index 00000000..cc57615e --- /dev/null +++ b/xo-indentlog/cmake/indentlogConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/indentlogTargets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/xo-indentlog/cmake/xo-bootstrap-macros.cmake b/xo-indentlog/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..aba31169 --- /dev/null +++ b/xo-indentlog/cmake/xo-bootstrap-macros.cmake @@ -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() diff --git a/xo-indentlog/example/CMakeLists.txt b/xo-indentlog/example/CMakeLists.txt new file mode 100644 index 00000000..0f629f7a --- /dev/null +++ b/xo-indentlog/example/CMakeLists.txt @@ -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() diff --git a/xo-indentlog/example/ex1/CMakeLists.txt b/xo-indentlog/example/ex1/CMakeLists.txt new file mode 100644 index 00000000..69f49e17 --- /dev/null +++ b/xo-indentlog/example/ex1/CMakeLists.txt @@ -0,0 +1,4 @@ +if (XO_ENABLE_EXAMPLES) + add_executable(indentlog_ex1 ex1.cpp) + xo_include_options2(indentlog_ex1) +endif() diff --git a/xo-indentlog/example/ex1/ex1.cpp b/xo-indentlog/example/ex1/ex1.cpp new file mode 100644 index 00000000..b791aee0 --- /dev/null +++ b/xo-indentlog/example/ex1/ex1.cpp @@ -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 */ diff --git a/xo-indentlog/example/ex2/CMakeLists.txt b/xo-indentlog/example/ex2/CMakeLists.txt new file mode 100644 index 00000000..4b7f82de --- /dev/null +++ b/xo-indentlog/example/ex2/CMakeLists.txt @@ -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() diff --git a/xo-indentlog/example/ex2/ex2.cpp b/xo-indentlog/example/ex2/ex2.cpp new file mode 100644 index 00000000..4ccfaae2 --- /dev/null +++ b/xo-indentlog/example/ex2/ex2.cpp @@ -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); +} diff --git a/xo-indentlog/example/ex3/CMakeLists.txt b/xo-indentlog/example/ex3/CMakeLists.txt new file mode 100644 index 00000000..a15b03be --- /dev/null +++ b/xo-indentlog/example/ex3/CMakeLists.txt @@ -0,0 +1,4 @@ +if (XO_ENABLE_EXAMPLES) + add_executable(indentlog_ex3 ex3.cpp) + xo_include_options2(indentlog_ex3) +endif() diff --git a/xo-indentlog/example/ex3/ex3.cpp b/xo-indentlog/example/ex3/ex3.cpp new file mode 100644 index 00000000..0fc0fcc2 --- /dev/null +++ b/xo-indentlog/example/ex3/ex3.cpp @@ -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 */ diff --git a/xo-indentlog/example/ex4/CMakeLists.txt b/xo-indentlog/example/ex4/CMakeLists.txt new file mode 100644 index 00000000..9daba558 --- /dev/null +++ b/xo-indentlog/example/ex4/CMakeLists.txt @@ -0,0 +1,4 @@ +if (XO_ENABLE_EXAMPLES) + add_executable(indentlog_ex4 ex4.cpp) + xo_include_options2(indentlog_ex4) +endif() diff --git a/xo-indentlog/example/ex4/ex4.cpp b/xo-indentlog/example/ex4/ex4.cpp new file mode 100644 index 00000000..01a141c8 --- /dev/null +++ b/xo-indentlog/example/ex4/ex4.cpp @@ -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 */ diff --git a/xo-indentlog/example/hello/CMakeLists.txt b/xo-indentlog/example/hello/CMakeLists.txt new file mode 100644 index 00000000..14806f7e --- /dev/null +++ b/xo-indentlog/example/hello/CMakeLists.txt @@ -0,0 +1,4 @@ +if (XO_ENABLE_EXAMPLES) + add_executable(hello hello.cpp) + xo_include_options2(hello) +endif() diff --git a/xo-indentlog/example/hello/hello.cpp b/xo-indentlog/example/hello/hello.cpp new file mode 100644 index 00000000..5147511e --- /dev/null +++ b/xo-indentlog/example/hello/hello.cpp @@ -0,0 +1,5 @@ +#include + +int main(int argc, char ** argv) { + std::cout << "Hello, world!" << std::endl; +} diff --git a/xo-indentlog/img/ex1.png b/xo-indentlog/img/ex1.png new file mode 100755 index 00000000..89e37ca4 Binary files /dev/null and b/xo-indentlog/img/ex1.png differ diff --git a/xo-indentlog/img/ex2.png b/xo-indentlog/img/ex2.png new file mode 100755 index 00000000..8e554636 Binary files /dev/null and b/xo-indentlog/img/ex2.png differ diff --git a/xo-indentlog/img/ex3.png b/xo-indentlog/img/ex3.png new file mode 100755 index 00000000..6355c003 Binary files /dev/null and b/xo-indentlog/img/ex3.png differ diff --git a/xo-indentlog/img/ex4.png b/xo-indentlog/img/ex4.png new file mode 100755 index 00000000..38035dc6 Binary files /dev/null and b/xo-indentlog/img/ex4.png differ diff --git a/xo-indentlog/img/lcov1.png b/xo-indentlog/img/lcov1.png new file mode 100755 index 00000000..4fb14106 Binary files /dev/null and b/xo-indentlog/img/lcov1.png differ diff --git a/xo-indentlog/include/xo/indentlog/log_config.hpp b/xo-indentlog/include/xo/indentlog/log_config.hpp new file mode 100644 index 00000000..98a16304 --- /dev/null +++ b/xo-indentlog/include/xo/indentlog/log_config.hpp @@ -0,0 +1,104 @@ +/* @file log_config.hpp */ + +#pragma once + +#include "log_level.hpp" +#include "print/function.hpp" +#include "print/color.hpp" +#include + +namespace xo { + /* Tag here b/c we want header-only library */ + template + 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 + log_level + log_config_impl::min_log_level = log_level::default_level; + + template + bool + log_config_impl::time_enabled = 1; + + template + bool + log_config_impl::time_local_flag = true; + + template + bool + log_config_impl::time_usec_flag = true; + + template + std::uint32_t + log_config_impl::indent_width = 2; + + template + std::uint32_t + log_config_impl::max_indent_width = 32; + + template + bool + log_config_impl::nesting_level_enabled = true; + + template + color_spec_type + log_config_impl::nesting_level_color = color_spec_type::xterm(195); + + template + function_style + log_config_impl::style = function_style::streamlined; + + template + color_spec_type + log_config_impl::function_entry_color = color_spec_type::ansi(34); + + template + color_spec_type + log_config_impl::function_exit_color = color_spec_type::ansi(32); + + template + bool + log_config_impl::location_enabled = true; + + template + std::uint32_t + log_config_impl::location_tab = 80; + + template + color_spec_type + log_config_impl::code_location_color = color_spec_type::red(); + + using log_config = log_config_impl; +} /*namespace xo*/ + +/* end log_config.hpp */ diff --git a/xo-indentlog/include/xo/indentlog/log_level.hpp b/xo-indentlog/include/xo/indentlog/log_level.hpp new file mode 100644 index 00000000..dbe49b29 --- /dev/null +++ b/xo-indentlog/include/xo/indentlog/log_level.hpp @@ -0,0 +1,77 @@ +/* @file log_level.hpp */ + +#pragma once + +#include +#include + +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(x) > static_cast(y)); + } + + inline bool + operator>=(log_level x, log_level y) { + return (static_cast(x) >= static_cast(y)); + } + + inline bool + operator<(log_level x, log_level y) { + return (static_cast(x) < static_cast(y)); + } + + inline bool + operator<=(log_level x, log_level y) { + return (static_cast(x) <= static_cast(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 */ diff --git a/xo-indentlog/include/xo/indentlog/log_state.hpp b/xo-indentlog/include/xo/indentlog/log_state.hpp new file mode 100644 index 00000000..29ad0dda --- /dev/null +++ b/xo-indentlog/include/xo/indentlog/log_state.hpp @@ -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 +#include +#include // for std::unique_ptr + +namespace xo { + enum EntryExit { + EE_Entry, + EE_Exit + }; + + // track per-thread state associated with nesting logger + // + template + class state_impl { + public: + using log_streambuf_type = log_streambuf>; + 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 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 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 + state_impl::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 + void + state_impl::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 + void + state_impl::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 + void + state_impl::preamble(function_style style, + std::string_view name1, + std::string_view name2) + { + this->entryexit_aux(style, name1, name2, EE_Entry); + } /*preamble*/ + + template + void + state_impl::postamble(function_style style, + std::string_view name1, + std::string_view name2) + { + this->entryexit_aux(style, name1, name2, EE_Exit); + } /*postamble*/ + + template + void + state_impl::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 */ diff --git a/xo-indentlog/include/xo/indentlog/log_streambuf.hpp b/xo-indentlog/include/xo/indentlog/log_streambuf.hpp new file mode 100644 index 00000000..1a761655 --- /dev/null +++ b/xo-indentlog/include/xo/indentlog/log_streambuf.hpp @@ -0,0 +1,123 @@ +/* @file log_streambuf.hpp */ + +#pragma once + +#include +#include +#include // e.g. for std::memcpy() +#include +#include + +namespace xo { + /* recycling buffer for logging. + * write to self-extending storage array; + */ + template + 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(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 buf_v_; + }; /*log_streambuf*/ + +} /*namespace xo*/ + +/* end log_streambuf.hpp */ diff --git a/xo-indentlog/include/xo/indentlog/machdep/machdep.hpp b/xo-indentlog/include/xo/indentlog/machdep/machdep.hpp new file mode 100644 index 00000000..df03099c --- /dev/null +++ b/xo-indentlog/include/xo/indentlog/machdep/machdep.hpp @@ -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 , for example */ +#define __rdtsc() fake_rdtsc() +#define _mm_getcsr(a) fake_mm_getcsr() +#define _mm_setcsr(a) fake_mm_setcsr() + +#endif + +/* end machdep.hpp */ diff --git a/xo-indentlog/include/xo/indentlog/print/array.hpp b/xo-indentlog/include/xo/indentlog/print/array.hpp new file mode 100644 index 00000000..03b265e9 --- /dev/null +++ b/xo-indentlog/include/xo/indentlog/print/array.hpp @@ -0,0 +1,25 @@ +/* @file array.hpp */ + +#pragma once + +#include +#include + +namespace std { + template + inline std::ostream & + operator<<(std::ostream & os, + std::array 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 */ diff --git a/xo-indentlog/include/xo/indentlog/print/code_location.hpp b/xo-indentlog/include/xo/indentlog/print/code_location.hpp new file mode 100644 index 00000000..cd6c9308 --- /dev/null +++ b/xo-indentlog/include/xo/indentlog/print/code_location.hpp @@ -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 + 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; + + inline std::ostream & + operator<<(std::ostream & os, + code_location const & x) + { + x.print_code_location(os); + return os; + } +} /*namespace xo*/ + +/* end code_location.hpp */ diff --git a/xo-indentlog/include/xo/indentlog/print/color.hpp b/xo-indentlog/include/xo/indentlog/print/color.hpp new file mode 100644 index 00000000..bcd3e634 --- /dev/null +++ b/xo-indentlog/include/xo/indentlog/print/color.hpp @@ -0,0 +1,202 @@ +/* color.hpp */ + +#pragma once + +#include +//#include // for std::move +#include + +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 << ""; + 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(x) & static_cast(y); + } + inline std::uint8_t operator| (coloring_control_flags x, coloring_control_flags y) { + return static_cast(x) | static_cast(y); + } + + /* stream-insertable color control */ + template + class color_impl { + public: + color_impl(coloring_control_flags flags, color_spec_type spec, Contents && contents) + : flags_{flags}, spec_{spec}, contents_{std::forward(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 + color_impl with_color(color_spec_type const & spec, Contents && contents) { + return color_impl(coloring_control_flags::all, spec, std::forward(contents)); + } /*with_color*/ + + inline color_impl + color_on(color_spec_type const & spec) { + return color_impl(coloring_control_flags::color_on, spec, 0); + } /*color_on*/ + + inline color_impl + color_off(color_spec_type const & spec) { + /* any spec other than color_spec_type::none() works here */ + return color_impl(coloring_control_flags::color_off, spec, 0); + } /*color_off*/ + + template + inline std::ostream & + operator<<(std::ostream & os, color_impl const & x) { + x.print(os); + return os; + } /*operator<<*/ + +} /*namespace xo*/ + +/* end color.hpp */ diff --git a/xo-indentlog/include/xo/indentlog/print/concat.hpp b/xo-indentlog/include/xo/indentlog/print/concat.hpp new file mode 100644 index 00000000..223900ec --- /dev/null +++ b/xo-indentlog/include/xo/indentlog/print/concat.hpp @@ -0,0 +1,42 @@ +/* @file concat.hpp */ + +#pragma once + +#include +#include // for std::move() + +namespace xo { + template + struct concat_impl { + public: + concat_impl(T1 && x1, T2 && x2) + : x1_{std::forward(x1)}, x2_{std::forward(x2)} {} + + T1 const & x1() const { return x1_; } + T2 const & x2() const { return x2_; } + + private: + T1 x1_; + T2 x2_; + }; /*concat_impl*/ + + template + T1 concat(T1 && x1) { + return x1; + } /*concat*/ + + template + concat_impl concat(T1 && x1, T2 && x2) { + return concat_impl(std::move(x1), std::move(x2)); + } /*concat*/ + + template + inline std::ostream & + operator<<(std::ostream & os, concat_impl const & x) { + os << x.x1() << x.x2(); + return os; + } /*operator<<*/ + +} /*namespace xo*/ + +/* end concat.hpp */ diff --git a/xo-indentlog/include/xo/indentlog/print/filename.hpp b/xo-indentlog/include/xo/indentlog/print/filename.hpp new file mode 100644 index 00000000..ead3b65e --- /dev/null +++ b/xo-indentlog/include/xo/indentlog/print/filename.hpp @@ -0,0 +1,70 @@ +/* @file filename.hpp */ + +#pragma once + +#include +#include + +namespace xo { + /* Example: + * os << basename("/path/to/basename.cpp") + * prints + * basename.cpp + * on os + */ + + /* Tag to drive header-only expression */ + template + 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; + + inline std::ostream & + operator<<(std::ostream & os, + basename const & bn) + { + basename::print_basename(os, bn.path()); + return os; + } +} /*xo*/ + +/* end filename.hpp */ diff --git a/xo-indentlog/include/xo/indentlog/print/fixed.hpp b/xo-indentlog/include/xo/indentlog/print/fixed.hpp new file mode 100644 index 00000000..84b7e225 --- /dev/null +++ b/xo-indentlog/include/xo/indentlog/print/fixed.hpp @@ -0,0 +1,46 @@ +/* @file fixed.hpp */ + +#pragma once + +#include +#include + +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 */ diff --git a/xo-indentlog/include/xo/indentlog/print/function.hpp b/xo-indentlog/include/xo/indentlog/print/function.hpp new file mode 100644 index 00000000..9a6e56ee --- /dev/null +++ b/xo-indentlog/include/xo/indentlog/print/function.hpp @@ -0,0 +1,318 @@ +/* @file function.hpp */ + +#include "color.hpp" + +#include +#include + +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 + 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 xo::sometemplateclass::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 xo::sometemplateclass::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::notify(const T&) [with T = std::pair; 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::somemethod(xo::enum1, std::vector) + * ^ + * 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>) -> quux + * foo::bar>() -> 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; + + 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 */ diff --git a/xo-indentlog/include/xo/indentlog/print/hex.hpp b/xo-indentlog/include/xo/indentlog/print/hex.hpp new file mode 100644 index 00000000..8a6517d5 --- /dev/null +++ b/xo-indentlog/include/xo/indentlog/print/hex.hpp @@ -0,0 +1,145 @@ +/** @file hex.hpp **/ + +#pragma once + +#include +#include + +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 + 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(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(lo)}, + hi_{reinterpret_cast(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 + 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 + Stream & + operator<< (Stream & os, hex_view const & ins) { + ins.print(os); + return os; + } + +} /*namespace xo*/ + +/* end hex.hpp */ diff --git a/xo-indentlog/include/xo/indentlog/print/pad.hpp b/xo-indentlog/include/xo/indentlog/print/pad.hpp new file mode 100644 index 00000000..e85d9c25 --- /dev/null +++ b/xo-indentlog/include/xo/indentlog/print/pad.hpp @@ -0,0 +1,52 @@ +/* @file pad.hpp */ + +#pragma once + +#include +#include + +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 +#include + +namespace std { + template + inline std::ostream & + operator<<(std::ostream & os, + std::pair const & x) + { + os << "[" + << x.first + << " " + << x.second + << "]"; + + return os; + } /*operator<<*/ +} /*namespace std*/ + +/* end pair.hpp */ diff --git a/xo-indentlog/include/xo/indentlog/print/printer.hpp b/xo-indentlog/include/xo/indentlog/print/printer.hpp new file mode 100644 index 00000000..f8f74582 --- /dev/null +++ b/xo-indentlog/include/xo/indentlog/print/printer.hpp @@ -0,0 +1,28 @@ +/* @file printer.hpp */ + +#pragma once + +#include + +namespace xo { + namespace print { + /* print an event to a logfile + * intended to be usable as EventSink argument + * to RealizationSimSource + */ + template + 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 */ diff --git a/xo-indentlog/include/xo/indentlog/print/quoted.hpp b/xo-indentlog/include/xo/indentlog/print/quoted.hpp new file mode 100644 index 00000000..16e66267 --- /dev/null +++ b/xo-indentlog/include/xo/indentlog/print/quoted.hpp @@ -0,0 +1,153 @@ +/* file quoted.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include "tostr.hpp" +#include +#include +#include +#include + +namespace xo { + namespace print { + /* use this to avoid template conversion hassles + * since literal strings get treated as arrays + */ + template + char const * ccs(T x) { return x; } + + /* Printing cases: + * 1. T&&: + * move into quot_impl. T must be moveable! + * 2. T&: + * copy reference into quot_impl. + * similarly for T const &, copy reference into quot_impl + */ + + template + 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 + std::ostream & + operator<<(std::ostream & os, quot_impl const & x) { + x.print(os); + return os; + } /*operator*/ + + /* writing out std::forward 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 + auto quot(T && x) { + return quot_impl(false /*unq_flag*/, std::forward(x)); + } + + inline auto qcstr(char const * x) { + return quot(x); + } /*qcstr*/ + + template + auto unq(T && x) { + return quot_impl(true /*unq_flag*/, std::forward(x)); + } + } /*namespace print*/ +} /*namespace xo*/ + +/* end quoted.hpp */ diff --git a/xo-indentlog/include/xo/indentlog/print/quoted_char.hpp b/xo-indentlog/include/xo/indentlog/print/quoted_char.hpp new file mode 100644 index 00000000..010eefa3 --- /dev/null +++ b/xo-indentlog/include/xo/indentlog/print/quoted_char.hpp @@ -0,0 +1,43 @@ +/* @file quoted_char.hpp */ + +#pragma once + +#include + +namespace xo { + template + 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 + inline std::ostream & + operator<<(std::ostream & os, quoted_char const & x) { + x.print(os); + return os; + } /*operator<<*/ + +} /*namespace xo*/ + + +/* end quoted_char.hpp */ diff --git a/xo-indentlog/include/xo/indentlog/print/tag.hpp b/xo-indentlog/include/xo/indentlog/print/tag.hpp new file mode 100644 index 00000000..0973506d --- /dev/null +++ b/xo-indentlog/include/xo/indentlog/print/tag.hpp @@ -0,0 +1,115 @@ +/* @file tag.hpp */ + +#pragma once + +#include "tag_config.hpp" +#include "concat.hpp" +#include "quoted.hpp" +#include "color.hpp" +#include + +// 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 + struct tag_impl { + tag_impl(Name const & n, Value const & v) + : name_{n}, value_{v} {} + tag_impl(Name && n, Value && v) + : name_{std::forward(n)}, value_{std::forward(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 + tag_impl + make_tag(Name && n, Value && v) + { + return tag_impl(n, v); + } /*make_tag*/ + + template + tag_impl + make_tag(char const * n, Value && v) { + return tag_impl(n, v); + } /*make_tag*/ + + template + tag_impl + xtag(Name && n, Value && v) + { + return tag_impl(n, v); + } /*xtag*/ + + template + tag_impl + xtag(char const * n, Value && v) { + return tag_impl(n, v); + } /*xtag*/ + + inline + tag_impl + xtag_pre(char const * n) { + return tag_impl(n, ""); + } /*xtag_pre*/ + + // ----- tag ----- + + template + tag_impl + tag(Name && n, Value && v) + { + return tag_impl(n, v); + } /*tag*/ + + template + tag_impl + tag(char const * n, Value && v) + { + return tag_impl(n, v); + } /*tag*/ + + // ----- operator<< on tag_impl ----- + + template + inline std::ostream & + operator<<(std::ostream &s, + tag_impl 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 */ diff --git a/xo-indentlog/include/xo/indentlog/print/tag_config.hpp b/xo-indentlog/include/xo/indentlog/print/tag_config.hpp new file mode 100644 index 00000000..f86b2c8f --- /dev/null +++ b/xo-indentlog/include/xo/indentlog/print/tag_config.hpp @@ -0,0 +1,28 @@ +/* @file tag_config.hpp */ + +#pragma once + +#include "color.hpp" +#include + +namespace xo { + /* Tag here b/c we want header-only library */ + template + 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 + color_spec_type + tag_config_impl::tag_color = color_spec_type::xterm(245); + + using tag_config = tag_config_impl; +} /*namespace xo*/ + +/* end tag_config.hpp */ diff --git a/xo-indentlog/include/xo/indentlog/print/time.hpp b/xo-indentlog/include/xo/indentlog/print/time.hpp new file mode 100644 index 00000000..28a1a687 --- /dev/null +++ b/xo-indentlog/include/xo/indentlog/print/time.hpp @@ -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 */ diff --git a/xo-indentlog/include/xo/indentlog/print/tostr.hpp b/xo-indentlog/include/xo/indentlog/print/tostr.hpp new file mode 100644 index 00000000..e395cfe3 --- /dev/null +++ b/xo-indentlog/include/xo/indentlog/print/tostr.hpp @@ -0,0 +1,105 @@ +/* file tostr.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include +#include +#include + +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 + Stream & tos(Stream & s) { + return s; + } + + // Use: + // tos(s,a,b,c) + // is the same as + // s << a << b << c; + // + template + Stream & tos(Stream & s, T && x) { + s << x; + return s; + } /*tos*/ + + template + Stream &tos(Stream &s, T &&x, Tn &&...rest) { + s << x; + return tos(s, rest...); + } /*tos*/ + + // like tos(..), but append newline + // + template + 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 std::string tostr(Tn &&...args) { + std::stringstream ss; + tos(ss, args...); + //ss << std::ends; + return ss.str(); + } /*tostr*/ +} /*namespace xo*/ + +/* end tostr.hpp */ diff --git a/xo-indentlog/include/xo/indentlog/print/vector.hpp b/xo-indentlog/include/xo/indentlog/print/vector.hpp new file mode 100644 index 00000000..be5f4497 --- /dev/null +++ b/xo-indentlog/include/xo/indentlog/print/vector.hpp @@ -0,0 +1,28 @@ +/* file vector.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include +#include + +namespace std { + template + inline std::ostream & + operator<<(std::ostream & os, + std::vector const & v) + { + os << "["; + for(size_t i=0, z=v.size(); i 0) + os << " "; + os << v[i]; + } + os << "]"; + return os; + } /*operator<<*/ +} /*namespace std*/ + +/* end vector.hpp */ diff --git a/xo-indentlog/include/xo/indentlog/scope.hpp b/xo-indentlog/include/xo/indentlog/scope.hpp new file mode 100644 index 00000000..5a1cbff0 --- /dev/null +++ b/xo-indentlog/include/xo/indentlog/scope.hpp @@ -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 +#include +#include // for std::unique_ptr + +namespace xo { + + template + 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 > + class basic_scope { + public: + using state_impl_type = state_impl; + + public: + //basic_scope(std::string_view name1, bool enabled_flag); + template + 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 + 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(rest)...); + + this->flush2sbuf(logstate); + } + + return true; + } /*log*/ + + template + bool operator()(Tn&&... args) { return this->log(std::forward(args)...); } + + /* call once to end scope before dtor */ + template + 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 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_ = ""; + /* name of this scope (part 2) */ + std::string_view name2_ = "::"; + /* captured value of __FILE__ */ + std::string_view file_ = ""; + /* captured value of __LINE__ */ + std::uint32_t line_ = 0; + /* set once per scope .finalized=true <-> logging disabled */ + bool finalized_ = false; + }; /*basic_scope*/ + + template + template + basic_scope::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(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 + basic_scope::~basic_scope() { + if(!this->finalized_) + this->end_scope(); + } /*dtor*/ + + template + thread_local std::unique_ptr> + basic_scope::s_threadlocal_state; + + template + std::uint32_t + basic_scope::nesting_level() const { + return require_thread_local_state()->nesting_level(); + } /*nesting_level*/ + + template + typename basic_scope::state_impl_type * + basic_scope::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 basic_scope::state_impl_type * + basic_scope::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 + std::ostream & + basic_scope::logstate2stream(state_impl_type * logstate) + { + return logstate->ss(); + } /*logstate2stream*/ + + template + void + basic_scope::flush2sbuf(state_impl_type * logstate) + { + logstate->flush2sbuf(this->dest_sbuf_); + } /*flush2sbuf*/ + + template + template + void + basic_scope::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(args)...); + + logstate->flush2sbuf(std::clog.rdbuf()); + } + } /*end_scope*/ + + + using scope = basic_scope; + +} /*namespace xo*/ + +/* end scope.hpp */ diff --git a/xo-indentlog/include/xo/indentlog/timeutil/timeutil.hpp b/xo-indentlog/include/xo/indentlog/timeutil/timeutil.hpp new file mode 100644 index 00000000..1a2a0240 --- /dev/null +++ b/xo-indentlog/include/xo/indentlog/timeutil/timeutil.hpp @@ -0,0 +1,297 @@ +/* @file time.hpp */ + +#pragma once + +#include +#include +#ifdef NOT_YET +# include +#endif +#include +#include +#include + +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; + using utc_micros = std::chrono::time_point; + + + 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_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(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( + std::chrono::system_clock::from_time_t(midnight_time_t))); + + nanos t0_tdy = t0 - t0_midnight; + + return std::pair(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 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(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( + std::chrono::system_clock::from_time_t(midnight_time_t))); + + nanos t0_tdy = t0 - t0_midnight; + + return std::pair(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 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(t0))); + //time_t t0_time_t = (std::chrono::system_clock::to_time_t(std::chrono::time_point_cast(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( + std::chrono::system_clock::from_time_t(midnight_time_t))); + + uint32_t usec = + (std::chrono::duration_cast( + 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(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(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 */ diff --git a/xo-indentlog/utest/CMakeLists.txt b/xo-indentlog/utest/CMakeLists.txt new file mode 100644 index 00000000..ea4bb819 --- /dev/null +++ b/xo-indentlog/utest/CMakeLists.txt @@ -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 diff --git a/xo-indentlog/utest/array.test.cpp b/xo-indentlog/utest/array.test.cpp new file mode 100644 index 00000000..e916802d --- /dev/null +++ b/xo-indentlog/utest/array.test.cpp @@ -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 +#include + +using namespace xo; + +namespace ut { + TEST_CASE("array", "[array]") { + tag_config::tag_color = color_spec_type::none(); + + { + std::array x = {}; + std::stringstream ss; + ss << x; + + REQUIRE(ss.str() == "[]"); + } + + { + std::array x = {1}; + std::stringstream ss; + ss << x; + + REQUIRE(ss.str() == "[1]"); + } + + { + std::array x = {1, 2}; + std::stringstream ss; + ss << x; + + REQUIRE(ss.str() == "[1 2]"); + } + } +} /*namespace ut*/ + +/* end array.test.cpp */ diff --git a/xo-indentlog/utest/code_location.test.cpp b/xo-indentlog/utest/code_location.test.cpp new file mode 100644 index 00000000..7be0eb6a --- /dev/null +++ b/xo-indentlog/utest/code_location.test.cpp @@ -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 +#include + +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 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 */ diff --git a/xo-indentlog/utest/filename.test.cpp b/xo-indentlog/utest/filename.test.cpp new file mode 100644 index 00000000..a47a5ab0 --- /dev/null +++ b/xo-indentlog/utest/filename.test.cpp @@ -0,0 +1,43 @@ +/* @file filename.test.cpp */ + +#include "xo/indentlog/print/filename.hpp" +#include "xo/indentlog/print/tag.hpp" +#include +#include + +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 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 */ diff --git a/xo-indentlog/utest/fixed.test.cpp b/xo-indentlog/utest/fixed.test.cpp new file mode 100644 index 00000000..d42ffc63 --- /dev/null +++ b/xo-indentlog/utest/fixed.test.cpp @@ -0,0 +1,88 @@ +/* @file fixed.test.cpp */ + +#include "xo/indentlog/print/fixed.hpp" +#include "xo/indentlog/print/tag.hpp" +#include +#include + +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 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 */ diff --git a/xo-indentlog/utest/function.test.cpp b/xo-indentlog/utest/function.test.cpp new file mode 100644 index 00000000..84b49515 --- /dev/null +++ b/xo-indentlog/utest/function.test.cpp @@ -0,0 +1,64 @@ +/* @file function.test.cpp */ + +#include "xo/indentlog/print/function.hpp" +#include "xo/indentlog/print/tag.hpp" +#include +#include + +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 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::notify_ev(const T&) [with T = std::pair > >, long unsigned int>; EvTimeFn = xo::reactor::EventTimeFn > >, long unsigned int> >]", "FifoQueue::notify_ev"), + function_tcase(function_style::streamlined, color_spec_type::none(), "token_type xo::tok::tokenizer::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 */ diff --git a/xo-indentlog/utest/indentlog_utest_main.cpp b/xo-indentlog/utest/indentlog_utest_main.cpp new file mode 100644 index 00000000..6337b638 --- /dev/null +++ b/xo-indentlog/utest/indentlog_utest_main.cpp @@ -0,0 +1,6 @@ +/* @file indentlog_utest_main.cpp */ + +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" + +/* end indentlog_utest_main.cpp */ diff --git a/xo-indentlog/utest/quoted.test.cpp b/xo-indentlog/utest/quoted.test.cpp new file mode 100644 index 00000000..0b00ba9a --- /dev/null +++ b/xo-indentlog/utest/quoted.test.cpp @@ -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 +#include + +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 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("", true, ""), + quot_tcase("", false, ""), + }); + + 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 */ diff --git a/xo-indentlog/utest/tag.test.cpp b/xo-indentlog/utest/tag.test.cpp new file mode 100644 index 00000000..72bba0f2 --- /dev/null +++ b/xo-indentlog/utest/tag.test.cpp @@ -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 +#include + +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 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 */ diff --git a/xo-indentlog/utest/timeutil.test.cpp b/xo-indentlog/utest/timeutil.test.cpp new file mode 100644 index 00000000..c0c17bf2 --- /dev/null +++ b/xo-indentlog/utest/timeutil.test.cpp @@ -0,0 +1,175 @@ +/* @file timeutil.test.cpp */ + +#include "xo/indentlog/timeutil/timeutil.hpp" +#include "xo/indentlog/print/tag.hpp" +#include +#include + +using namespace xo; +using namespace xo::time; +using namespace std::chrono; + +namespace ut { + template + inline utc_micros to_micros(FromTime tm) { + return std::chrono::time_point_cast(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 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 */ diff --git a/xo-indentlog/utest/vector.test.cpp b/xo-indentlog/utest/vector.test.cpp new file mode 100644 index 00000000..eaa1b4ea --- /dev/null +++ b/xo-indentlog/utest/vector.test.cpp @@ -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 +#include + +using namespace xo; + +namespace ut { + struct vector_tcase { + vector_tcase() = default; + vector_tcase(std::vector const & x, std::string s) + : x_{x}, s_{std::move(s)} {} + + /* vector to print */ + std::vector x_; + /* expected result */ + std::string s_; + }; /*vector_tcase*/ + + std::vector 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 */