Add 'xo-indentlog/' from commit 'd43c4af0b4'
git-subtree-dir: xo-indentlog git-subtree-mainline:1c3f033933git-subtree-split:d43c4af0b4
This commit is contained in:
commit
341fcfd1c7
64 changed files with 4682 additions and 0 deletions
98
xo-indentlog/.github/workflows/cmake-single-platform.yml
vendored
Normal file
98
xo-indentlog/.github/workflows/cmake-single-platform.yml
vendored
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
# This starter workflow is for a CMake project running on a single platform. There is a different starter workflow if you need cross-platform coverage.
|
||||
# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-multi-platform.yml
|
||||
name: CMake on a single platform
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
env:
|
||||
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
|
||||
BUILD_TYPE: Release
|
||||
|
||||
jobs:
|
||||
build:
|
||||
# The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac.
|
||||
# You can convert this to a matrix build if you need cross-platform coverage.
|
||||
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: checkout source
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install dependencies
|
||||
# install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]]
|
||||
run: |
|
||||
echo "::group::install catch2"
|
||||
sudo apt-get install -y catch2
|
||||
echo "::endgroup"
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
- name: Clone xo-cmake
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: Rconybea/xo-cmake
|
||||
path: repo/xo-cmake
|
||||
|
||||
- name: build xo-cmake
|
||||
run: |
|
||||
XONAME=xo-cmake
|
||||
XOSRC=repo/${XONAME}
|
||||
BUILDDIR=${{github.workspace}}/build_${XONAME}
|
||||
PREFIX=${{github.workspace}}/local
|
||||
|
||||
echo "::group::configure ${XONAME}"
|
||||
cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC}
|
||||
echo "::endgroup"
|
||||
|
||||
echo "::group::compile ${XONAME}"
|
||||
cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}}
|
||||
echo "::endgroup"
|
||||
|
||||
echo "::group::local install ${XONAME}"
|
||||
cmake --install ${BUILDDIR}
|
||||
echo "::endgroup"
|
||||
|
||||
echo "::group::local dir tree"
|
||||
tree ${PREFIX}
|
||||
echo "::endgroup"
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
- name: build self (indentlog)
|
||||
run: |
|
||||
XONAME=xo-indentlog
|
||||
BUILDDIR=${{github.workspace}}/build_${XONAME}
|
||||
PREFIX=${{github.workspace}}/local
|
||||
|
||||
echo "::group::repo dir tree"
|
||||
tree -L 2 repo
|
||||
echo "::endgroup"
|
||||
|
||||
echo "::group::configure ${XONAME}"
|
||||
cmake -B ${BUILDDIR} -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${PREFIX} -DCMAKE_INSTALL_PREFIX=${PREFIX} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
|
||||
echo "::endgroup"
|
||||
|
||||
echo "::group::compile ${XONAME}"
|
||||
cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}}
|
||||
echo "::endgroup"
|
||||
|
||||
echo "::group::run unit tests ${XONAME}"
|
||||
cmake --build ${BUILDDIR} -- test
|
||||
echo "::endgroup"
|
||||
|
||||
echo "::group::local install ${XONAME}"
|
||||
cmake --install ${BUILDDIR}
|
||||
echo "::endgroup"
|
||||
|
||||
echo "::group::local dir tree"
|
||||
tree -L 3 ${PREFIX}
|
||||
echo "::endgroup"
|
||||
|
||||
# Execute tests defined by the CMake configuration.
|
||||
# See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
|
||||
(cd ${BUILDDIR} && ctest -C ${{env.BUILD_TYPE}})
|
||||
8
xo-indentlog/.gitignore
vendored
Normal file
8
xo-indentlog/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# emacs projectile config
|
||||
.projectile
|
||||
# symlink to ${my_build_directory}/compile_commands.json to make LSP work
|
||||
compile_commands.json
|
||||
# lsp keeps state here
|
||||
.cache
|
||||
# typical build dirs
|
||||
.build*
|
||||
47
xo-indentlog/BUILD.md
Normal file
47
xo-indentlog/BUILD.md
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
# indentlog build details
|
||||
|
||||
## mac osx
|
||||
|
||||
Note: ~ expansion doesn't work in a pure build environment.
|
||||
|
||||
## Test Coverage
|
||||
|
||||
### enable coverage build
|
||||
```
|
||||
$ cd indentlog
|
||||
$ mkdir ccov
|
||||
$ cd ccov
|
||||
$ cmake -DCODE_COVERAGE=ON .. # prepares coverage build
|
||||
```
|
||||
|
||||
### build + generate test coverage
|
||||
```
|
||||
$ make ccov # builds + runs unit tests
|
||||
$ make ccov-all # generates .html report
|
||||
```
|
||||
|
||||
### view coverage report
|
||||
```
|
||||
$ firefox
|
||||
[navigate to coverage report; path something like file://home/roland/proj/indentlog/ccov/ccov/all-merged/index.html]
|
||||
```
|
||||
|
||||

|
||||
|
||||
## 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
|
||||
48
xo-indentlog/CMakeLists.txt
Normal file
48
xo-indentlog/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
# indentlog/CMakeLists.txt
|
||||
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
project(indentlog VERSION 1.0)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
include(cmake/xo-bootstrap-macros.cmake)
|
||||
|
||||
xo_cxx_toplevel_options3()
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# c++ settings
|
||||
|
||||
# one-time project-specific c++ flags. usually empty
|
||||
#set(PROJECT_CXX_FLAGS "-Wstringop-overread")
|
||||
#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2")
|
||||
add_definitions(${PROJECT_CXX_FLAGS})
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
add_subdirectory(example)
|
||||
add_subdirectory(utest)
|
||||
|
||||
# header-only library.
|
||||
# see [[https://stackoverflow.com/questions/47718485/install-and-export-interface-only-library-cmake]]
|
||||
#
|
||||
set(SELF_LIB indentlog)
|
||||
xo_add_headeronly_library(${SELF_LIB})
|
||||
xo_install_library4(${SELF_LIB} ${PROJECT_NAME}Targets)
|
||||
xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets)
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# docs targets depend on all the other library/utest targets
|
||||
#
|
||||
#add_subdirectory(docs)
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
if (XO_ENABLE_EXAMPLES)
|
||||
install(TARGETS hello DESTINATION bin/indentlog/example)
|
||||
install(TARGETS indentlog_ex1 DESTINATION bin/indentlog/example)
|
||||
install(TARGETS indentlog_ex2 DESTINATION bin/indentlog/example)
|
||||
install(TARGETS indentlog_ex3 DESTINATION bin/indentlog/example)
|
||||
install(TARGETS indentlog_ex4 DESTINATION bin/indentlog/example)
|
||||
endif()
|
||||
|
||||
# end CMakeLists.txt
|
||||
40
xo-indentlog/FAQ
Normal file
40
xo-indentlog/FAQ
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
Q1. how to get a nix development environment that works
|
||||
|
||||
1.
|
||||
1. nix stdenv = gcc12Stdenv (see mkderivation.nix)
|
||||
2. baseInputs has gcc (but probably doesn't need it)
|
||||
3. devInputs has llvmPackages_16.clang-unwrapped
|
||||
|
||||
This leads to env with
|
||||
CC=gcc
|
||||
CXX=g++
|
||||
NIX_CC=/nix/store/$hash-gcc-wrapper-12.3.0
|
||||
|
||||
2.
|
||||
|
||||
1. nix stdenv = clang16Stdenv (see mkderivation.nix)
|
||||
2. baseInputs has gcc
|
||||
3. devInputs has llvmPackages_16.clang-unwrapped
|
||||
|
||||
This leads to env with:
|
||||
CC=clang
|
||||
CXX=clang++
|
||||
NIX_CC=/nix/store/$hash-clang-wrapper-16.0.1
|
||||
|
||||
To build, need to tell cmake to use gcc:
|
||||
cmake -DCMAKE_CXX_COMPILER=$(which g++) -DCMAKE_C_COMPILER=$(which gcc) path/to/src
|
||||
|
||||
Q2. how to add a dependency to github workflow
|
||||
|
||||
comments.
|
||||
1. workflow configured in ./.github/cmake-single-platform.yml
|
||||
2. workflow runs on ubuntu vm. see
|
||||
runs-on: ubuntu-latest
|
||||
in cmake-single-platform.yml
|
||||
3. find a desired dependency
|
||||
$ apt-cache search ${keyword}
|
||||
e.g.
|
||||
$ apt-cache search catch2
|
||||
4. add/edit install step to ./.github/cmake-single-platform.yml
|
||||
- name: Install catch2
|
||||
run: sudo apt-get install -y catch2
|
||||
29
xo-indentlog/FILES
Normal file
29
xo-indentlog/FILES
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
directory layout
|
||||
|
||||
+- README.md markdown README, for github
|
||||
+- img image files, used in docs
|
||||
| +- ex1.png
|
||||
| +- ex2.png
|
||||
| +- ex3.png
|
||||
| \- ex4.png
|
||||
+- LICENSE software license
|
||||
+- CMakeLists.txt toplevel cmake config
|
||||
+- cmake
|
||||
| \- nestlog.cmake cmake support files
|
||||
+- compile_commands.json symlink to record of compiler commands; for LSP support
|
||||
+- include to install, copy contents of this directory to permanent location
|
||||
| \- indentlog
|
||||
| +- scope.hpp logger api -- appl code will #include this
|
||||
| +- log_config.hpp logger api -- control logger format, verbosity, colors etc.
|
||||
| +- log_level.hpp encode logger verbosity
|
||||
| +- log_state.hpp per-thread state tracking (e.g. recognize nesting)
|
||||
| +- log_streambuf.hpp custom streambuf
|
||||
| \- print
|
||||
| +- tag.hpp stream inserters
|
||||
| ...
|
||||
\- example
|
||||
+- CMakeLists.txt cmake config
|
||||
+- ex1
|
||||
| +- CMakeLists.txt ex1 cmake config
|
||||
| \- ex1.cpp example .cpp exercising indentlog
|
||||
...
|
||||
14
xo-indentlog/LICENSE
Normal file
14
xo-indentlog/LICENSE
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
The MIT License (MIT)
|
||||
Copyright © 2023 Roland Conybeare
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”),
|
||||
to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
31
xo-indentlog/MARKDOWN
Normal file
31
xo-indentlog/MARKDOWN
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# heading level 1
|
||||
## heading level 2
|
||||
###### heading level 6
|
||||
|
||||
blank line new paragraph
|
||||
two spaces at eod force line break
|
||||
|
||||
**bold** bold text
|
||||
__bold__ also bold text (but don't embed within a word)
|
||||
|
||||
*italics*
|
||||
|
||||
***bolditalic***
|
||||
|
||||
> text to blockquote
|
||||
>
|
||||
> + more paragraphs
|
||||
|
||||
> text to blockquote
|
||||
>
|
||||
>> with nested blockquote
|
||||
|
||||
- bullets also can prefix with + or *
|
||||
|
||||
1. numbered lists
|
||||
|
||||
indent 4 spaces (or 1 tab) for code blocks
|
||||
|
||||
`inline code`
|
||||
|
||||
--- on a line by itself -> horizontal rule
|
||||
261
xo-indentlog/README.md
Normal file
261
xo-indentlog/README.md
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
# indentlog -- logging with automatic call-graph indenting
|
||||
|
||||
Indentlog is a lightweight header-only library for console logging.
|
||||
|
||||
## Features
|
||||
|
||||
- header-only; nothing to link
|
||||
- easy-to-read format uses indenting to show call structure.
|
||||
indentation has user-controlled upper limit to preserve readability with
|
||||
deeply nested call graphs
|
||||
- colorized output using vt100 color codes (ansi or xterm)
|
||||
- automatically captures + displays timestamp, function name and code location.
|
||||
supports several function-name formats to reflect tradeoff readability for precision
|
||||
- application code may issue logging code that contains embedded newlines and/or color escapes;
|
||||
logger preserves indentation.
|
||||
- logger is 'truthy' -> only pay for formatting when entry points is enabled.
|
||||
- also provides family of convenience stream-inserters
|
||||
|
||||
## Getting Started
|
||||
|
||||
### build + install `xo-cmake` dependency (cmake macros)
|
||||
|
||||
see [github/Rconybea/xo-cmake](https://github.com/Rconybea/xo-cmake)
|
||||
|
||||
Installs a few cmake ingredients, along with a build assistant for XO projects such as this one.
|
||||
|
||||
### copy repository locally
|
||||
|
||||
Using `xo-build` (provided by `xo-cmake`):
|
||||
```
|
||||
$ xo-build --clone xo-indentlog
|
||||
```
|
||||
|
||||
or equivalently:
|
||||
```
|
||||
$ cd ~/proj
|
||||
$ git clone git@github.com:Rconybea/indentlog.git xo-indentlog
|
||||
```
|
||||
|
||||
### build & install
|
||||
|
||||
Using `xo-build`:
|
||||
```
|
||||
$ xo-build --configure --build --install xo-indentlog
|
||||
```
|
||||
|
||||
or equivalently:
|
||||
```
|
||||
$ mkdir xo-indentlog/.build
|
||||
$ PREFIX=/usr/local # for example
|
||||
$ cmake -DCMAKE_INSTALL_PREFIX=${PREFIX} -S xo-indentlog -B xo-indentlog/.build
|
||||
$ cmake --build xo-indentlog/.build
|
||||
$ cmake --install xo-indentlog/.build
|
||||
```
|
||||
|
||||
For some more detail see [BUILD.md](BUILD.md)
|
||||
|
||||
### LSP support
|
||||
|
||||
lsp will look for `compile_commands.json` in the root of the source tree; cmake creates it in build directory
|
||||
|
||||
```
|
||||
$ cd xo-indentlog
|
||||
$ ln -s build/compile_commands.json
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### 1
|
||||
|
||||
```
|
||||
#include "indentlog/scope.hpp"
|
||||
|
||||
using namespace xo;
|
||||
|
||||
void inner(int x) {
|
||||
scope log(XO_ENTER0(always), ":x ", x);
|
||||
}
|
||||
|
||||
void outer(int y) {
|
||||
scope log(XO_ENTER0(always), ":y ", y);
|
||||
|
||||
inner(2*y);
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char ** argv) {
|
||||
outer(123);
|
||||
}
|
||||
```
|
||||
|
||||
output:
|
||||

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

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

|
||||
|
||||
### 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:
|
||||
|
||||

|
||||
5
xo-indentlog/TODO
Normal file
5
xo-indentlog/TODO
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
|
||||
|
||||
sphinx_markdown_builder
|
||||
|
||||
https://stackoverflow.com/questions/13396856/markdown-output-for-sphinx-based-documentation
|
||||
4
xo-indentlog/cmake/indentlogConfig.cmake.in
Normal file
4
xo-indentlog/cmake/indentlogConfig.cmake.in
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
@PACKAGE_INIT@
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/indentlogTargets.cmake")
|
||||
check_required_components("@PROJECT_NAME@")
|
||||
35
xo-indentlog/cmake/xo-bootstrap-macros.cmake
Normal file
35
xo-indentlog/cmake/xo-bootstrap-macros.cmake
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# ----------------------------------------------------------------
|
||||
# for example:
|
||||
# $ PREFIX=/usr/local # for example
|
||||
# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build
|
||||
#
|
||||
# will get
|
||||
# CMAKE_MODULE_PATH
|
||||
# from xo-cmake-config --cmake-module-path
|
||||
#
|
||||
# and expect .cmake macros in
|
||||
# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED)
|
||||
|
||||
if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND")
|
||||
message(FATAL "could not find xo-cmake-config executable")
|
||||
endif()
|
||||
|
||||
message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}")
|
||||
|
||||
if (NOT XO_SUBMODULE_BUILD)
|
||||
if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix))
|
||||
# default to typical install location for xo-project-macros
|
||||
execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH)
|
||||
message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# needs to have been installed somewhere on CMAKE_MODULE_PATH,
|
||||
# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX)
|
||||
#
|
||||
include(xo_macros/xo_cxx)
|
||||
|
||||
xo_cxx_bootstrap_message()
|
||||
25
xo-indentlog/example/CMakeLists.txt
Normal file
25
xo-indentlog/example/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
set(PROJECT_CXX_FLAGS "--std=c++20")
|
||||
|
||||
add_definitions(${PROJECT_CXX_FLAGS})
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "")
|
||||
|
||||
#include(cmake/FindSphinx.cmake)
|
||||
|
||||
add_subdirectory(hello)
|
||||
add_subdirectory(ex1)
|
||||
add_subdirectory(ex2)
|
||||
add_subdirectory(ex3)
|
||||
add_subdirectory(ex4)
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# make standard directories for std:: includes explicit
|
||||
# so that
|
||||
# (1) they appear in compile_commands.json.
|
||||
# (2) clangd (run from emacs lsp-mode) can find them
|
||||
#
|
||||
if(CMAKE_EXPORT_COMPILE_COMMANDS)
|
||||
set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES
|
||||
${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES})
|
||||
endif()
|
||||
4
xo-indentlog/example/ex1/CMakeLists.txt
Normal file
4
xo-indentlog/example/ex1/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
if (XO_ENABLE_EXAMPLES)
|
||||
add_executable(indentlog_ex1 ex1.cpp)
|
||||
xo_include_options2(indentlog_ex1)
|
||||
endif()
|
||||
22
xo-indentlog/example/ex1/ex1.cpp
Normal file
22
xo-indentlog/example/ex1/ex1.cpp
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
/* ex1.cpp */
|
||||
|
||||
#include "xo/indentlog/scope.hpp"
|
||||
|
||||
using namespace xo;
|
||||
|
||||
void inner(int x) {
|
||||
scope log(XO_ENTER0(always), ":x ", x);
|
||||
}
|
||||
|
||||
void outer(int y) {
|
||||
scope log(XO_ENTER0(always), ":y ", y);
|
||||
|
||||
inner(2*y);
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char ** argv) {
|
||||
outer(123);
|
||||
}
|
||||
|
||||
/* end ex1.cpp */
|
||||
6
xo-indentlog/example/ex2/CMakeLists.txt
Normal file
6
xo-indentlog/example/ex2/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# NOTE: need target names to be globally unique within the xo umbrella
|
||||
|
||||
if (XO_ENABLE_EXAMPLES)
|
||||
add_executable(indentlog_ex2 ex2.cpp)
|
||||
xo_include_options2(indentlog_ex2)
|
||||
endif()
|
||||
36
xo-indentlog/example/ex2/ex2.cpp
Normal file
36
xo-indentlog/example/ex2/ex2.cpp
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/* examples ex2/ex2.cpp */
|
||||
|
||||
#include "xo/indentlog/scope.hpp"
|
||||
|
||||
using namespace xo;
|
||||
|
||||
int
|
||||
fib(int n) {
|
||||
scope log(XO_ENTER0(info), ":n ", n);
|
||||
|
||||
int retval = 1;
|
||||
|
||||
if (n >= 2) {
|
||||
retval = fib(n - 1) + fib(n - 2);
|
||||
log && log(":n ", n);
|
||||
}
|
||||
|
||||
log.end_scope("<- :retval ", retval);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char ** argv) {
|
||||
log_config::min_log_level = xo::log_level::info;
|
||||
log_config::indent_width = 4;
|
||||
|
||||
int n = 4;
|
||||
|
||||
scope log(XO_ENTER0(info), ":n ", 4);
|
||||
|
||||
int fn = fib(n);
|
||||
|
||||
log && log(":n ", n);
|
||||
log && log("<- :fib(n) ", fn);
|
||||
}
|
||||
4
xo-indentlog/example/ex3/CMakeLists.txt
Normal file
4
xo-indentlog/example/ex3/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
if (XO_ENABLE_EXAMPLES)
|
||||
add_executable(indentlog_ex3 ex3.cpp)
|
||||
xo_include_options2(indentlog_ex3)
|
||||
endif()
|
||||
48
xo-indentlog/example/ex3/ex3.cpp
Normal file
48
xo-indentlog/example/ex3/ex3.cpp
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/* examples ex3/ex3.cpp */
|
||||
|
||||
#include "xo/indentlog/scope.hpp"
|
||||
|
||||
using namespace xo;
|
||||
|
||||
int
|
||||
fib(int n) {
|
||||
scope log(XO_ENTER0(info), tag("n", n));
|
||||
|
||||
int retval = 1;
|
||||
|
||||
if (n >= 2) {
|
||||
retval = fib(n - 1) + fib(n - 2);
|
||||
}
|
||||
|
||||
log.end_scope(tag("n", n), " <-", xtag("retval", retval));
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char ** argv) {
|
||||
//std::cerr << "0 1 2 3 4 5 6 7 8 9 10" << std::endl;
|
||||
//std::cerr << "01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" << std::endl;
|
||||
|
||||
log_config::min_log_level = log_level::info;
|
||||
log_config::time_enabled = true;
|
||||
log_config::time_local_flag = true;
|
||||
log_config::style = function_style::streamlined;
|
||||
log_config::indent_width = 4;
|
||||
log_config::max_indent_width = 30;
|
||||
log_config::location_tab = 80;
|
||||
log_config::function_entry_color = color_spec_type::xterm(69);
|
||||
log_config::function_exit_color = color_spec_type::xterm(70);
|
||||
log_config::code_location_color = color_spec_type::xterm(166);
|
||||
|
||||
int n = 3;
|
||||
|
||||
scope log(XO_ENTER0(info), ":n ", 4);
|
||||
|
||||
int fn = fib(n);
|
||||
|
||||
log && log(tag("n", n));
|
||||
log && log("<-", xtag("fib(n)", fn));
|
||||
}
|
||||
|
||||
/* ex3/ex3.cpp */
|
||||
4
xo-indentlog/example/ex4/CMakeLists.txt
Normal file
4
xo-indentlog/example/ex4/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
if (XO_ENABLE_EXAMPLES)
|
||||
add_executable(indentlog_ex4 ex4.cpp)
|
||||
xo_include_options2(indentlog_ex4)
|
||||
endif()
|
||||
52
xo-indentlog/example/ex4/ex4.cpp
Normal file
52
xo-indentlog/example/ex4/ex4.cpp
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
/* @file ex4.cpp */
|
||||
|
||||
#include "xo/indentlog/scope.hpp"
|
||||
|
||||
using namespace xo;
|
||||
|
||||
class Quadratic {
|
||||
public:
|
||||
Quadratic(double a, double b, double c) : a_{a}, b_{b}, c_{c} {}
|
||||
|
||||
double operator() (double x) const {
|
||||
scope log(XO_ENTER0(info), tag("a", a_), xtag("b", b_), xtag("c", c_), xtag("x", x));
|
||||
|
||||
double retval = (a_ * x * x) + (b_ * x) + c_;
|
||||
|
||||
log.end_scope("<-", xtag("retval", retval));
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
private:
|
||||
double a_ = 0.0;;
|
||||
double b_ = 0.0;
|
||||
double c_ = 0.0;
|
||||
};
|
||||
|
||||
int
|
||||
main(int argc, char ** argv) {
|
||||
log_config::style = function_style::streamlined;
|
||||
log_config::min_log_level = log_level::info;
|
||||
|
||||
scope log(XO_ENTER0(info));
|
||||
|
||||
Quadratic quadratic(2.0, -5.0, 7.0);
|
||||
|
||||
double x = 3.0;
|
||||
double r = 0.0;
|
||||
|
||||
log_config::style = function_style::pretty;
|
||||
|
||||
r = quadratic(x);
|
||||
|
||||
log_config::style = function_style::streamlined;
|
||||
|
||||
r = quadratic(x);
|
||||
|
||||
log_config::style = function_style::simple;
|
||||
|
||||
r = quadratic(x);
|
||||
}
|
||||
|
||||
/* end ex4.cpp */
|
||||
4
xo-indentlog/example/hello/CMakeLists.txt
Normal file
4
xo-indentlog/example/hello/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
if (XO_ENABLE_EXAMPLES)
|
||||
add_executable(hello hello.cpp)
|
||||
xo_include_options2(hello)
|
||||
endif()
|
||||
5
xo-indentlog/example/hello/hello.cpp
Normal file
5
xo-indentlog/example/hello/hello.cpp
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
#include <iostream>
|
||||
|
||||
int main(int argc, char ** argv) {
|
||||
std::cout << "Hello, world!" << std::endl;
|
||||
}
|
||||
BIN
xo-indentlog/img/ex1.png
Executable file
BIN
xo-indentlog/img/ex1.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
BIN
xo-indentlog/img/ex2.png
Executable file
BIN
xo-indentlog/img/ex2.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
BIN
xo-indentlog/img/ex3.png
Executable file
BIN
xo-indentlog/img/ex3.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
BIN
xo-indentlog/img/ex4.png
Executable file
BIN
xo-indentlog/img/ex4.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
BIN
xo-indentlog/img/lcov1.png
Executable file
BIN
xo-indentlog/img/lcov1.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 59 KiB |
104
xo-indentlog/include/xo/indentlog/log_config.hpp
Normal file
104
xo-indentlog/include/xo/indentlog/log_config.hpp
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
/* @file log_config.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "log_level.hpp"
|
||||
#include "print/function.hpp"
|
||||
#include "print/color.hpp"
|
||||
#include <cstdint>
|
||||
|
||||
namespace xo {
|
||||
/* Tag here b/c we want header-only library */
|
||||
template <typename Tag>
|
||||
struct log_config_impl {
|
||||
/* display log messages with severity >= .log_level */
|
||||
static log_level min_log_level;
|
||||
/* true to log local time */
|
||||
static bool time_enabled;
|
||||
/* true to log time-of-day in local coords; false for UTC coords */
|
||||
static bool time_local_flag;
|
||||
/* true to log time-of-day with microsecond precision; false for millisecond precision */
|
||||
static bool time_usec_flag;
|
||||
/* spaces per nesting level. 0 -> no indenting */
|
||||
static std::uint32_t indent_width;
|
||||
/* max #of spaces to introduce when indenting */
|
||||
static std::uint32_t max_indent_width;
|
||||
/* if true enable explicit nesting level display [nnn] */
|
||||
static bool nesting_level_enabled;
|
||||
/* color to use for explicit nesting level */
|
||||
static color_spec_type nesting_level_color;
|
||||
/* display style for function names. function_style:: literal|simple|pretty|streamlined */
|
||||
static function_style style;
|
||||
/* color to use for function name, on entry/exit (xo::scope creation/destruction)
|
||||
* (ansi color codes, see Select Graphics Rendition subset)
|
||||
*/
|
||||
static color_spec_type function_entry_color;
|
||||
static color_spec_type function_exit_color;
|
||||
/* if true, append [file:line] to output */
|
||||
static bool location_enabled;
|
||||
/* when .location_enabled, write [file:line] starting this many chars from left margin */
|
||||
static std::uint32_t location_tab;
|
||||
/* color to use for code location */
|
||||
static color_spec_type code_location_color;
|
||||
}; /*log_config_impl*/
|
||||
|
||||
template <typename Tag>
|
||||
log_level
|
||||
log_config_impl<Tag>::min_log_level = log_level::default_level;
|
||||
|
||||
template <typename Tag>
|
||||
bool
|
||||
log_config_impl<Tag>::time_enabled = 1;
|
||||
|
||||
template <typename Tag>
|
||||
bool
|
||||
log_config_impl<Tag>::time_local_flag = true;
|
||||
|
||||
template <typename Tag>
|
||||
bool
|
||||
log_config_impl<Tag>::time_usec_flag = true;
|
||||
|
||||
template <typename Tag>
|
||||
std::uint32_t
|
||||
log_config_impl<Tag>::indent_width = 2;
|
||||
|
||||
template <typename Tag>
|
||||
std::uint32_t
|
||||
log_config_impl<Tag>::max_indent_width = 32;
|
||||
|
||||
template <typename Tag>
|
||||
bool
|
||||
log_config_impl<Tag>::nesting_level_enabled = true;
|
||||
|
||||
template <typename Tag>
|
||||
color_spec_type
|
||||
log_config_impl<Tag>::nesting_level_color = color_spec_type::xterm(195);
|
||||
|
||||
template <typename Tag>
|
||||
function_style
|
||||
log_config_impl<Tag>::style = function_style::streamlined;
|
||||
|
||||
template <typename Tag>
|
||||
color_spec_type
|
||||
log_config_impl<Tag>::function_entry_color = color_spec_type::ansi(34);
|
||||
|
||||
template <typename Tag>
|
||||
color_spec_type
|
||||
log_config_impl<Tag>::function_exit_color = color_spec_type::ansi(32);
|
||||
|
||||
template <typename Tag>
|
||||
bool
|
||||
log_config_impl<Tag>::location_enabled = true;
|
||||
|
||||
template <typename Tag>
|
||||
std::uint32_t
|
||||
log_config_impl<Tag>::location_tab = 80;
|
||||
|
||||
template <typename Tag>
|
||||
color_spec_type
|
||||
log_config_impl<Tag>::code_location_color = color_spec_type::red();
|
||||
|
||||
using log_config = log_config_impl<class log_config_tag>;
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end log_config.hpp */
|
||||
77
xo-indentlog/include/xo/indentlog/log_level.hpp
Normal file
77
xo-indentlog/include/xo/indentlog/log_level.hpp
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
/* @file log_level.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
|
||||
namespace xo {
|
||||
enum class log_level : std::uint8_t {
|
||||
/* control log message severity
|
||||
* silent > always > severe > error > warning > info > chatty > never
|
||||
*
|
||||
* never:
|
||||
* used internally e.g. by XO_ENTER1()
|
||||
* a log message with this severity will never be printed
|
||||
*
|
||||
* always:
|
||||
* use with XO_ENTER1():
|
||||
* scope log(XO_ENTER1(always, mydebug_flag));
|
||||
* to log message whenever mydebug_flag is true (for any .min_log_level except silent)
|
||||
*
|
||||
* silent:
|
||||
* use in log_config to suppress all log messages
|
||||
*/
|
||||
never,
|
||||
verbose,
|
||||
chatty,
|
||||
info,
|
||||
warning,
|
||||
error,
|
||||
severe,
|
||||
always,
|
||||
silent,
|
||||
|
||||
default_level = error
|
||||
}; /*log_level*/
|
||||
|
||||
inline bool
|
||||
operator>(log_level x, log_level y) {
|
||||
return (static_cast<std::uint32_t>(x) > static_cast<std::uint32_t>(y));
|
||||
}
|
||||
|
||||
inline bool
|
||||
operator>=(log_level x, log_level y) {
|
||||
return (static_cast<std::uint32_t>(x) >= static_cast<std::uint32_t>(y));
|
||||
}
|
||||
|
||||
inline bool
|
||||
operator<(log_level x, log_level y) {
|
||||
return (static_cast<std::uint32_t>(x) < static_cast<std::uint32_t>(y));
|
||||
}
|
||||
|
||||
inline bool
|
||||
operator<=(log_level x, log_level y) {
|
||||
return (static_cast<std::uint32_t>(x) <= static_cast<std::uint32_t>(y));
|
||||
}
|
||||
|
||||
inline std::ostream &
|
||||
operator<<(std::ostream & os,
|
||||
log_level x) {
|
||||
switch(x) {
|
||||
case log_level::never: os << "never"; break;
|
||||
case log_level::verbose: os << "verbose"; break;
|
||||
case log_level::chatty: os << "chatty"; break;
|
||||
case log_level::info: os << "info"; break;
|
||||
case log_level::warning: os << "warning"; break;
|
||||
case log_level::error: os << "error"; break;
|
||||
case log_level::severe: os << "severe"; break;
|
||||
case log_level::always: os << "always"; break;
|
||||
case log_level::silent: os << "silent"; break;
|
||||
//default: os << "???"; break;
|
||||
}
|
||||
return os;
|
||||
} /* operator<<*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end log_level.hpp */
|
||||
403
xo-indentlog/include/xo/indentlog/log_state.hpp
Normal file
403
xo-indentlog/include/xo/indentlog/log_state.hpp
Normal file
|
|
@ -0,0 +1,403 @@
|
|||
/* @file log_state.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "log_config.hpp"
|
||||
#include "log_streambuf.hpp"
|
||||
#include "print/pad.hpp"
|
||||
#include "print/filename.hpp"
|
||||
#include "print/code_location.hpp"
|
||||
#include "print/time.hpp"
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <memory> // for std::unique_ptr
|
||||
|
||||
namespace xo {
|
||||
enum EntryExit {
|
||||
EE_Entry,
|
||||
EE_Exit
|
||||
};
|
||||
|
||||
// track per-thread state associated with nesting logger
|
||||
//
|
||||
template <typename CharT, typename Traits>
|
||||
class state_impl {
|
||||
public:
|
||||
using log_streambuf_type = log_streambuf<char, std::char_traits<char>>;
|
||||
using utc_nanos = xo::time::utc_nanos;
|
||||
|
||||
public:
|
||||
state_impl();
|
||||
|
||||
std::uint32_t nesting_level() const { return nesting_level_; }
|
||||
|
||||
void incr_nesting() { ++nesting_level_; }
|
||||
void decr_nesting() { --nesting_level_; }
|
||||
|
||||
std::ostream & ss() { return ss_; }
|
||||
|
||||
void check_print_time(utc_nanos now_tm) {
|
||||
using xo::time::timeutil;
|
||||
using xo::time::utc_nanos;
|
||||
using xo::time::hms_msec;
|
||||
using xo::time::hms_usec;
|
||||
|
||||
if (log_config::time_enabled) {
|
||||
if (log_config::time_local_flag) {
|
||||
if (log_config::time_usec_flag)
|
||||
this->ss_ << hms_usec::local(now_tm) << " ";
|
||||
else
|
||||
this->ss_ << hms_msec::local(now_tm) << " ";
|
||||
} else {
|
||||
if (log_config::time_usec_flag)
|
||||
this->ss_ << hms_usec::utc(now_tm) << " ";
|
||||
else
|
||||
this->ss_ << hms_msec::utc(now_tm) << " ";
|
||||
}
|
||||
}
|
||||
} /*check_print_time*/
|
||||
|
||||
/* space budget for time-of-day */
|
||||
std::size_t calc_time_indent() const {
|
||||
if (log_config::time_enabled) {
|
||||
if (log_config::time_usec_flag) {
|
||||
/*strlen("14:38:19.123456 ")*/
|
||||
return 16;
|
||||
} else {
|
||||
/*strlen("14:38:19.974 ")*/
|
||||
return 13;
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
} /*calc_time_indent*/
|
||||
|
||||
void time_indent() {
|
||||
if (log_config::time_enabled)
|
||||
this->ss_ << pad(this->calc_time_indent(), ' ');
|
||||
} /*time_indent*/
|
||||
|
||||
/* call on entry to new scope */
|
||||
void preamble(function_style style, std::string_view name1, std::string_view name2);
|
||||
/* call before each new log entry */
|
||||
void indent(char pad_char);
|
||||
/* call on exit from scope */
|
||||
void postamble(function_style style, std::string_view name1, std::string_view name2);
|
||||
|
||||
/* write collected output to *p_sbuf */
|
||||
void flush2sbuf(std::streambuf * p_sbuf);
|
||||
|
||||
/* discard output, reset write pointer to beginning of buffer */
|
||||
void reset_stream() {
|
||||
p_sbuf_phase1_->reset_stream();
|
||||
p_sbuf_phase2_->reset_stream();
|
||||
}
|
||||
|
||||
void set_location(std::string_view file, std::uint32_t line) {
|
||||
this->location_flag_ = true;
|
||||
this->file_ = std::move(file);
|
||||
this->line_ = line;
|
||||
} /*set_location*/
|
||||
|
||||
private:
|
||||
/* common implementation for .preamble(), .postamble() */
|
||||
void entryexit_aux(function_style style,
|
||||
std::string_view name1,
|
||||
std::string_view name2,
|
||||
EntryExit entryexit);
|
||||
|
||||
private:
|
||||
/* current nesting level for this thread */
|
||||
std::uint32_t nesting_level_ = 0;
|
||||
|
||||
/* buffer space for logging
|
||||
* (before pretty-printing for scope::log() calls that span multiple lines)
|
||||
* reused across tos() and scope::log() calls
|
||||
*/
|
||||
std::unique_ptr<log_streambuf_type> p_sbuf_phase1_;
|
||||
|
||||
/* #of characters found in .p_sbuf_phase1 since last \n.
|
||||
* this value is established+updated in .flush2sbuf().
|
||||
* (in particular ignored by stream .ss())
|
||||
*/
|
||||
std::size_t lpos_ = 0;
|
||||
|
||||
/* whenever .set_location() is called:
|
||||
* - capture (file, line)
|
||||
* - print them near right margin with next output line
|
||||
* - ..and reset .location_flag
|
||||
*/
|
||||
bool location_flag_ = false;
|
||||
std::string_view file_;
|
||||
std::uint32_t line_ = 0;
|
||||
|
||||
/* buffer space for handling scope::log() calls that span multiple lines;
|
||||
* inserts extra characters in effort to indent gracefully
|
||||
*/
|
||||
std::unique_ptr<log_streambuf_type> p_sbuf_phase2_;
|
||||
|
||||
/* output stream -- always attached to .p_sbuf_phase1
|
||||
* stream inserters for application datatypes will target this stream
|
||||
*/
|
||||
std::ostream ss_;
|
||||
}; /*state_impl*/
|
||||
|
||||
constexpr uint32_t c_default_buf_size = 1024;
|
||||
|
||||
template <typename CharT, typename Traits>
|
||||
state_impl<CharT, Traits>::state_impl()
|
||||
: p_sbuf_phase1_(new log_streambuf_type(c_default_buf_size)),
|
||||
p_sbuf_phase2_(new log_streambuf_type(c_default_buf_size)),
|
||||
ss_(p_sbuf_phase1_.get())
|
||||
{
|
||||
assert(p_sbuf_phase1_.get() == ss_.rdbuf());
|
||||
} /*ctor*/
|
||||
|
||||
template <typename CharT, typename Traits>
|
||||
void
|
||||
state_impl<CharT, Traits>::indent(char pad_char)
|
||||
{
|
||||
//log_streambuf * sbuf = this->p_sbuf_phase1_.get();
|
||||
|
||||
#ifdef NOT_IN_USE
|
||||
{
|
||||
char buf[80];
|
||||
::snprintf(buf, sizeof(buf), "[%02d] ", this->nesting_level_);
|
||||
|
||||
this->ss_ << buf;
|
||||
//this->p_sbuf_->sputn(buf, strlen(buf));
|
||||
}
|
||||
#endif
|
||||
|
||||
/* indent to nesting level.
|
||||
*
|
||||
* note: see also flush2sbuf(), need special indent handling for continuation lines
|
||||
* (when application sends explicit newlines to this logger)
|
||||
*/
|
||||
this->ss_ << pad(std::min(this->nesting_level_ * log_config::indent_width,
|
||||
log_config::max_indent_width),
|
||||
pad_char);
|
||||
} /*indent*/
|
||||
|
||||
template <typename CharT, typename Traits>
|
||||
void
|
||||
state_impl<CharT, Traits>::entryexit_aux(function_style style,
|
||||
std::string_view name1,
|
||||
std::string_view name2,
|
||||
EntryExit entryexit)
|
||||
{
|
||||
log_streambuf_type * sbuf = this->p_sbuf_phase1_.get();
|
||||
|
||||
sbuf->reset_stream();
|
||||
|
||||
this->check_print_time(xo::time::timeutil::now());
|
||||
this->indent(' ');
|
||||
|
||||
char ee_label = '\0';
|
||||
color_spec_type fn_color;
|
||||
|
||||
/* mnemonic for scope entry/exit */
|
||||
switch(entryexit) {
|
||||
case EE_Entry:
|
||||
ee_label = '+';
|
||||
fn_color = log_config::function_entry_color;
|
||||
break;
|
||||
case EE_Exit:
|
||||
ee_label = '-';
|
||||
fn_color = log_config::function_exit_color;
|
||||
break;
|
||||
}
|
||||
|
||||
this->ss_ << ee_label;
|
||||
|
||||
if (log_config::nesting_level_enabled) {
|
||||
/* e.g.
|
||||
* (^[[38;5;195m7^[[0m)
|
||||
* <-----a---->b<-c->
|
||||
*
|
||||
* a = color on
|
||||
* b = level - displayed in color
|
||||
* c = color off
|
||||
*/
|
||||
this->ss_
|
||||
<< "("
|
||||
<< with_color(log_config::nesting_level_color,
|
||||
this->nesting_level_)
|
||||
<< ")";
|
||||
}
|
||||
|
||||
if (log_config::indent_width > 0)
|
||||
this->ss_ << ' ';
|
||||
|
||||
/* scope name - note no trailing newline; expect .preamble()/.postamble() caller to supply */
|
||||
this->ss_ << function_name(style, fn_color, name1) << name2;
|
||||
} /*entryexit_aux*/
|
||||
|
||||
template <typename CharT, typename Traits>
|
||||
void
|
||||
state_impl<CharT, Traits>::preamble(function_style style,
|
||||
std::string_view name1,
|
||||
std::string_view name2)
|
||||
{
|
||||
this->entryexit_aux(style, name1, name2, EE_Entry);
|
||||
} /*preamble*/
|
||||
|
||||
template <typename CharT, typename Traits>
|
||||
void
|
||||
state_impl<CharT, Traits>::postamble(function_style style,
|
||||
std::string_view name1,
|
||||
std::string_view name2)
|
||||
{
|
||||
this->entryexit_aux(style, name1, name2, EE_Exit);
|
||||
} /*postamble*/
|
||||
|
||||
template <typename CharT, typename Traits>
|
||||
void
|
||||
state_impl<CharT, Traits>::flush2sbuf(std::streambuf * p_sbuf)
|
||||
{
|
||||
log_streambuf_type * sbuf1 = this->p_sbuf_phase1_.get();
|
||||
log_streambuf_type * sbuf2 = this->p_sbuf_phase2_.get();
|
||||
|
||||
/* generally expecting sbuf to contain one line of output.
|
||||
* if it contains multiple newlines, need to indent
|
||||
* after each one.
|
||||
*
|
||||
* will scan output in *sbuf1, post-process to *sbuf2,
|
||||
* then write *sbuf2 to output stream
|
||||
*
|
||||
* note: we inherit .lpos from prec call to .flush2sbuf(),
|
||||
* in the unlikely event that it's non-zero
|
||||
*/
|
||||
char const * s = sbuf1->lo();
|
||||
char const * e = s + sbuf1->pos();
|
||||
|
||||
char const * p = s;
|
||||
|
||||
/* point to first space following a non-space character.
|
||||
* will indent to just after this space
|
||||
*/
|
||||
char const * space_after_nonspace = nullptr;
|
||||
|
||||
/* true on VT100 color escape (\033); in which case false on terminating char (m)
|
||||
* don't advance lpos during escape
|
||||
*/
|
||||
bool in_color_escape = false;
|
||||
|
||||
while(true) {
|
||||
bool have_nonspace = false;
|
||||
|
||||
/* invariant: s<=p<=e */
|
||||
|
||||
/* for indenting, looking for first 'space following non-space, on first line', if any */
|
||||
|
||||
std::size_t lpos_on_newline = 0;
|
||||
|
||||
while(p < e) {
|
||||
if(space_after_nonspace) {
|
||||
;
|
||||
} else {
|
||||
if(*p != ' ')
|
||||
have_nonspace = true;
|
||||
|
||||
if(have_nonspace && (*p == ' ')) {
|
||||
space_after_nonspace = p;
|
||||
}
|
||||
}
|
||||
|
||||
if (in_color_escape && (*p != '\n')) {
|
||||
/* in color escape -> don't advance .lpos */
|
||||
if (*p == 'm')
|
||||
in_color_escape = false;
|
||||
++p;
|
||||
} else if (*p == '\033') {
|
||||
/* begin color escape sequence */
|
||||
in_color_escape = true;
|
||||
++p;
|
||||
} else if (*p == '\n') {
|
||||
/* reset .pos on newline; also drop any (incomplete + ill-formed) color escape */
|
||||
|
||||
in_color_escape = false;
|
||||
|
||||
lpos_on_newline = this->lpos_;
|
||||
this->lpos_ = 0;
|
||||
|
||||
++p;
|
||||
break;
|
||||
} else {
|
||||
/* increment .lpos on non-newline */
|
||||
++(this->lpos_);
|
||||
++p;
|
||||
}
|
||||
}
|
||||
|
||||
/* p=e or *p=\n */
|
||||
|
||||
/* charseq [s,p) does not contain any newlines, print it */
|
||||
if (lpos_on_newline > 0) {
|
||||
/* charseq [s,p) does not contain any newlines, print it */
|
||||
sbuf2->sputn(s, p - s - 1);
|
||||
|
||||
if (this->location_flag_) {
|
||||
/* 'tab' to position lpos for [file:line] */
|
||||
sbuf2->sputc(' ');
|
||||
for (std::uint32_t i = lpos_on_newline + 1; i < log_config::location_tab; ++i)
|
||||
sbuf2->sputc(' ');
|
||||
|
||||
std::stringstream ss;
|
||||
ss << code_location(this->file_, this->line_,
|
||||
log_config::code_location_color);
|
||||
|
||||
std::string ss_str = ss.str(); /*hoping for copy elision here*/
|
||||
sbuf2->sputn(ss_str.c_str(), ss_str.size());
|
||||
|
||||
this->location_flag_ = false;
|
||||
this->file_ = "";
|
||||
this->line_ = 0;
|
||||
}
|
||||
|
||||
sbuf2->sputc('\n');
|
||||
} else {
|
||||
/* control here if .flush2sbuf() called without trailing newline in .p_sbuf_phase1 */
|
||||
sbuf2->sputn(s, p - s);
|
||||
}
|
||||
|
||||
if (p == e)
|
||||
break;
|
||||
|
||||
// {
|
||||
// char buf[80];
|
||||
// snprintf(buf, sizeof(buf), "*** indent=[%d] next=[%c]", this->nesting_level_, *(p+1));
|
||||
//
|
||||
// std::clog.rdbuf()->sputn(buf, strlen(buf));
|
||||
//}
|
||||
|
||||
/* control here only for continuation lines (application logging code embedding its own newlines)
|
||||
* - minimum indent = nesting level;
|
||||
* - however if space_after_nonspace defined, also indent for that
|
||||
*/
|
||||
std::uint32_t n_indent = 0;
|
||||
|
||||
n_indent += this->calc_time_indent();
|
||||
|
||||
n_indent += std::min(this->nesting_level_ * log_config::indent_width,
|
||||
log_config::max_indent_width);
|
||||
|
||||
/* this is just to indent for per-line entry/exit label */
|
||||
if(space_after_nonspace)
|
||||
n_indent += (space_after_nonspace - s);
|
||||
|
||||
for(std::uint32_t i = 0; i < n_indent; ++i)
|
||||
sbuf2->sputc(' ');
|
||||
|
||||
s = p;
|
||||
}
|
||||
|
||||
/* now write entire contents of *sbuf2 to clog */
|
||||
p_sbuf->sputn(sbuf2->lo(), sbuf2->pos());
|
||||
|
||||
/* reset streams for next message */
|
||||
this->reset_stream();
|
||||
} /*flush2sbuf*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end log_state.hpp */
|
||||
123
xo-indentlog/include/xo/indentlog/log_streambuf.hpp
Normal file
123
xo-indentlog/include/xo/indentlog/log_streambuf.hpp
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
/* @file log_streambuf.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <cstring> // e.g. for std::memcpy()
|
||||
#include <cstdint>
|
||||
#include <cassert>
|
||||
|
||||
namespace xo {
|
||||
/* recycling buffer for logging.
|
||||
* write to self-extending storage array;
|
||||
*/
|
||||
template <typename CharT, typename Traits>
|
||||
class log_streambuf : public std::streambuf {
|
||||
public:
|
||||
log_streambuf(std::uint32_t buf_z) {
|
||||
this->buf_v_.resize(buf_z);
|
||||
this->reset_stream();
|
||||
} /*ctor*/
|
||||
|
||||
std::streamsize capacity() const { return this->buf_v_.size(); }
|
||||
char const * lo() const { return this->pbase(); }
|
||||
char const * hi() const { return this->lo() + this->capacity(); }
|
||||
std::uint32_t pos() const { return this->pptr() - this->pbase(); }
|
||||
|
||||
void reset_stream() {
|
||||
char * p_lo = &(this->buf_v_[0]);
|
||||
char * p_hi = p_lo + this->capacity();
|
||||
|
||||
/* tells parent our buffer extent */
|
||||
this->setp(p_lo, p_hi);
|
||||
} /*reset_stream*/
|
||||
|
||||
protected:
|
||||
virtual std::streamsize
|
||||
xsputn(char const * s, std::streamsize n) override {
|
||||
/* s must be an address in [this->lo() .. this->lo() + capacity()] */
|
||||
|
||||
assert(this->hi() >= this->pptr());
|
||||
|
||||
#ifdef NOT_USING_DEBUG
|
||||
std::cout << "xsputn: pbase=" << (void *)(this->pbase())
|
||||
<< ", pptr=" << (void*)(this->pptr())
|
||||
<< "(+" << (this->pptr() - this->lo()) << ")"
|
||||
<< ", n=" << n << " -> (+" << (this->pptr() + n - this->lo()) << ")"
|
||||
<< ", buf_v.size=" << this->buf_v_.size()
|
||||
<< std::endl;
|
||||
#endif
|
||||
//std::cout << "xsputn: s=" << quot(string_view(s, n)) << ", n=" << n << std::endl;
|
||||
|
||||
if (this->pptr() + n > this->hi()) {
|
||||
n = this->hi() - this->pptr();
|
||||
std::memcpy(this->pptr(), s, n);
|
||||
} else {
|
||||
std::memcpy(this->pptr(), s, n);
|
||||
}
|
||||
this->pbump(n);
|
||||
|
||||
return n;
|
||||
} /*xsputn*/
|
||||
|
||||
virtual int_type
|
||||
overflow(int_type new_ch) override
|
||||
{
|
||||
char * old_pptr = this->pptr();
|
||||
std::streamsize old_n = old_pptr - this->pbase();
|
||||
|
||||
assert(old_n <= static_cast<std::streamsize>(this->buf_v_.size()));
|
||||
|
||||
std::size_t new_z = 2 * this->buf_v_.size();
|
||||
|
||||
this->buf_v_.resize(new_z);
|
||||
this->buf_v_[old_n] = new_ch;
|
||||
|
||||
/* 'buffered range' will now be .buf_v[old_n .. new_z] */
|
||||
char * p_base = &(this->buf_v_[0]);
|
||||
//char * p_lo = &(this->buf_v_[old_n+1]);
|
||||
char * p_hi = p_base + this->buf_v_.capacity();
|
||||
|
||||
this->setp(p_base, p_hi);
|
||||
this->pbump(old_n + 1); /*see 'this->buf_v_[old_n] - new_ch' above*/
|
||||
|
||||
return new_ch;
|
||||
} /*overflow*/
|
||||
|
||||
/* off. offset, relative to starting point dir.
|
||||
* dir.
|
||||
* which. in|out|both
|
||||
*/
|
||||
virtual pos_type seekoff(off_type off,
|
||||
std::ios_base::seekdir dir,
|
||||
std::ios_base::openmode which) override {
|
||||
//std::cout << "seekoff: off=" << off << ", dir=" << dir << ", which=" << which << std::endl;
|
||||
|
||||
// Only output stream is supported
|
||||
if (which != std::ios_base::out)
|
||||
throw std::runtime_error("log_streambuf: only output mode supported");
|
||||
|
||||
if (dir == std::ios_base::cur) {
|
||||
this->pbump(off);
|
||||
} else if (dir == std::ios_base::end) {
|
||||
/* .setp(): using for side effect: sets .pptr to .pbase */
|
||||
this->setp(this->pbase(), this->epptr());
|
||||
this->pbump(off);
|
||||
} else if (dir == std::ios_base::beg) {
|
||||
/* .setp(): using for side effect: sets .pptr to .pbase */
|
||||
this->setp(this->pbase(), this->epptr());
|
||||
this->pbump(this->capacity() + off);
|
||||
}
|
||||
|
||||
return this->pptr() - this->pbase();
|
||||
} /*seekoff*/
|
||||
|
||||
private:
|
||||
/* buffered output stored here */
|
||||
std::vector<char> buf_v_;
|
||||
}; /*log_streambuf*/
|
||||
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end log_streambuf.hpp */
|
||||
29
xo-indentlog/include/xo/indentlog/machdep/machdep.hpp
Normal file
29
xo-indentlog/include/xo/indentlog/machdep/machdep.hpp
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
/* @file machdep.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
/** Carveout for LSP (language server process):
|
||||
LSP uses clang, but with the same compiler flags as primary build.
|
||||
This triggers a handful of false alarms, in which clang complains about
|
||||
gcc builtins.
|
||||
|
||||
Replace these with something innocuous. Ok since LSP stops
|
||||
once parsing completes and does not generate code
|
||||
**/
|
||||
#if __clang__ && __GNUG__
|
||||
|
||||
extern "C" {
|
||||
/* never defined! must not ever generate code that relies on these */
|
||||
unsigned int fake_rdtsc();
|
||||
unsigned int fake_mm_getcsr();
|
||||
unsigned int fake_mm_setcsr();
|
||||
}
|
||||
|
||||
/* __rdtsc: clang encounters this from <immintrin.h>, for example */
|
||||
#define __rdtsc() fake_rdtsc()
|
||||
#define _mm_getcsr(a) fake_mm_getcsr()
|
||||
#define _mm_setcsr(a) fake_mm_setcsr()
|
||||
|
||||
#endif
|
||||
|
||||
/* end machdep.hpp */
|
||||
25
xo-indentlog/include/xo/indentlog/print/array.hpp
Normal file
25
xo-indentlog/include/xo/indentlog/print/array.hpp
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
/* @file array.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <array>
|
||||
|
||||
namespace std {
|
||||
template<typename T, size_t N>
|
||||
inline std::ostream &
|
||||
operator<<(std::ostream & os,
|
||||
std::array<T, N> const & v)
|
||||
{
|
||||
os << "[";
|
||||
for(size_t i = 0; i < N; ++i) {
|
||||
if(i > 0)
|
||||
os << " ";
|
||||
os << v[i];
|
||||
}
|
||||
os << "]";
|
||||
return os;
|
||||
} /*operator<<*/
|
||||
} /*namespace std*/
|
||||
|
||||
/* end array.hpp */
|
||||
53
xo-indentlog/include/xo/indentlog/print/code_location.hpp
Normal file
53
xo-indentlog/include/xo/indentlog/print/code_location.hpp
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
/* @file code_location.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "filename.hpp"
|
||||
#include "color.hpp"
|
||||
|
||||
namespace xo {
|
||||
/* Example:
|
||||
* os << code_location("/path/to/foo.cpp", 123)
|
||||
* writes
|
||||
* foo.cpp:123
|
||||
* on stream os
|
||||
*/
|
||||
|
||||
/* Tag to drive header-only expression */
|
||||
template <typename Tag>
|
||||
class code_location_impl {
|
||||
public:
|
||||
code_location_impl(std::string_view file,
|
||||
std::uint32_t line,
|
||||
color_spec_type colorspec)
|
||||
: file_{file}, line_{line}, color_spec_{colorspec} {}
|
||||
|
||||
void print_code_location(std::ostream & os) const {
|
||||
os << "["
|
||||
<< with_color(color_spec_, basename(file_))
|
||||
<< ":"
|
||||
<< line_
|
||||
<< "]";
|
||||
} /*print_code_location*/
|
||||
|
||||
private:
|
||||
/* __FILE__ */
|
||||
std::string_view file_;
|
||||
/* __LINE__ */
|
||||
std::uint32_t line_ = 0;
|
||||
/* color encoding for [file:line] */
|
||||
color_spec_type color_spec_;
|
||||
}; /*code_location_impl*/
|
||||
|
||||
using code_location = code_location_impl<class code_location_impl_tag>;
|
||||
|
||||
inline std::ostream &
|
||||
operator<<(std::ostream & os,
|
||||
code_location const & x)
|
||||
{
|
||||
x.print_code_location(os);
|
||||
return os;
|
||||
}
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end code_location.hpp */
|
||||
202
xo-indentlog/include/xo/indentlog/print/color.hpp
Normal file
202
xo-indentlog/include/xo/indentlog/print/color.hpp
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
/* color.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ostream>
|
||||
//#include <utility> // for std::move
|
||||
#include <cstdint>
|
||||
|
||||
namespace xo {
|
||||
enum class color_encoding : std::uint8_t {
|
||||
none,
|
||||
ansi,
|
||||
xterm,
|
||||
rgb
|
||||
};
|
||||
|
||||
inline std::ostream &
|
||||
operator<< (std::ostream & os, color_encoding x) {
|
||||
switch(x) {
|
||||
case color_encoding::none: os << "none"; break;
|
||||
case color_encoding::ansi: os << "ansi"; break;
|
||||
case color_encoding::xterm: os << "xterm"; break;
|
||||
case color_encoding::rgb: os << "rgb"; break;
|
||||
default: os << "???"; break;
|
||||
}
|
||||
return os;
|
||||
} /*operator<<*/
|
||||
|
||||
/* specify a color (consistent with ANSI escape sequences - the Select Graphics Rendition subset
|
||||
* see [[https://stackoverflow.com/questions/4842424/list-of-ansi-color-escape-sequences]]
|
||||
*
|
||||
* this provides three ways to specify foreground color:
|
||||
*
|
||||
* | enum | escape | example | description | foreground codes |
|
||||
* +-------+-----------+---------------------+---------------+------------------+
|
||||
* | ansi | \033[31 | \033[31;34m | 4-bit colors | 30..37, 90..97 |
|
||||
* | xterm | \033[38;5 | \033[38;5;143m | 8-bit colors | 0..255 |
|
||||
* | rgb | \033[38;2 | \033[38;2;10;20;30m | 24-bit colors | 3x 0..255 |
|
||||
*
|
||||
*/
|
||||
class color_spec_type {
|
||||
public:
|
||||
color_spec_type() = default;
|
||||
color_spec_type(color_encoding encoding, std::uint32_t code)
|
||||
: encoding_{encoding}, code_{code} {}
|
||||
|
||||
static color_spec_type none() { return color_spec_type(); }
|
||||
static color_spec_type ansi(std::uint32_t code) { return color_spec_type(color_encoding::ansi, code); }
|
||||
static color_spec_type xterm(std::uint32_t code) { return color_spec_type(color_encoding::xterm, code); }
|
||||
static color_spec_type rgb(std::uint8_t red, std::uint8_t green, std::uint8_t blue) {
|
||||
return color_spec_type(color_encoding::rgb, (red << 16 | green << 8 | blue));
|
||||
}
|
||||
|
||||
/* 4-bit foreground colors */
|
||||
static color_spec_type black () { return ansi(30); }
|
||||
static color_spec_type red () { return ansi(31); }
|
||||
static color_spec_type green () { return ansi(32); }
|
||||
static color_spec_type yellow () { return ansi(33); }
|
||||
static color_spec_type blue () { return ansi(34); }
|
||||
static color_spec_type magenta () { return ansi(35); }
|
||||
static color_spec_type cyan () { return ansi(36); }
|
||||
static color_spec_type white () { return ansi(37); }
|
||||
static color_spec_type bright_black () { return ansi(90); }
|
||||
static color_spec_type bright_red () { return ansi(91); }
|
||||
static color_spec_type bright_green () { return ansi(92); }
|
||||
static color_spec_type bright_yellow () { return ansi(99); }
|
||||
static color_spec_type bright_blue () { return ansi(94); }
|
||||
static color_spec_type bright_magenta () { return ansi(95); }
|
||||
static color_spec_type bright_cyan () { return ansi(96); }
|
||||
static color_spec_type bright_white () { return ansi(97); }
|
||||
|
||||
color_encoding encoding() const { return encoding_; }
|
||||
std::uint32_t code() const { return code_; }
|
||||
|
||||
void print_fg_color_on (std::ostream & os) const {
|
||||
switch (encoding_) {
|
||||
case color_encoding::none:
|
||||
break;
|
||||
case color_encoding::ansi:
|
||||
os << "\033[31;" << code_ << "m";
|
||||
break;
|
||||
case color_encoding::xterm:
|
||||
os << "\033[38;5;" << code_ << "m";
|
||||
break;
|
||||
case color_encoding::rgb:
|
||||
os << "\033[38;2;"
|
||||
<< (0xff & (code_ >> 16)) << ";"
|
||||
<< (0xff & (code_ >> 8)) << ";"
|
||||
<< (0xff & (code_ >> 0)) << "m";
|
||||
}
|
||||
} /*print_fg_color_on*/
|
||||
|
||||
/* escape to reverse effect of .print_on() */
|
||||
void print_fg_color_off (std::ostream & os) const {
|
||||
switch (encoding_) {
|
||||
case color_encoding::none:
|
||||
break;
|
||||
case color_encoding::ansi:
|
||||
case color_encoding::xterm:
|
||||
case color_encoding::rgb:
|
||||
os << "\033[0m";
|
||||
break;
|
||||
}
|
||||
} /*print_fg_color_off*/
|
||||
|
||||
private:
|
||||
/* none | ansi | xterm | rgb */
|
||||
color_encoding encoding_ = color_encoding::none;
|
||||
/* ansi : 30..37, 90..97
|
||||
* xterm : 0..255
|
||||
* see [[https://i.stack.imgur.com/KTSQa.png]]
|
||||
* 0..7 standard colors (muted: grey, red, green, yellow, blue, pink, cyan, white)
|
||||
* 8..15 high-intensity colors (grey, red, green, yellow, blue, pink, cyan, white)
|
||||
* 16..51 chooses hue
|
||||
* 16..51 + (0..5)x36 increases whiteness
|
||||
* rgb : r={hi 8 bits}, g={mid 8 bits}, b={lo 8 bits}
|
||||
*/
|
||||
std::uint32_t code_ = 0;
|
||||
}; /*color_spec_type*/
|
||||
|
||||
inline std::ostream &
|
||||
operator<< (std::ostream & os, color_spec_type const & x) {
|
||||
os << "<color_spec_type :encoding " << x.encoding() << " :code " << x.code() << ">";
|
||||
return os;
|
||||
} /*operator<<*/
|
||||
|
||||
enum class coloring_control_flags : std::uint8_t {
|
||||
none = 0x0,
|
||||
color_on = 0x01,
|
||||
contents = 0x02,
|
||||
color_off = 0x04,
|
||||
all = 0x07
|
||||
};
|
||||
|
||||
inline std::uint8_t operator& (coloring_control_flags x, coloring_control_flags y) {
|
||||
return static_cast<std::uint8_t>(x) & static_cast<std::uint8_t>(y);
|
||||
}
|
||||
inline std::uint8_t operator| (coloring_control_flags x, coloring_control_flags y) {
|
||||
return static_cast<std::uint8_t>(x) | static_cast<std::uint8_t>(y);
|
||||
}
|
||||
|
||||
/* stream-insertable color control */
|
||||
template <typename Contents>
|
||||
class color_impl {
|
||||
public:
|
||||
color_impl(coloring_control_flags flags, color_spec_type spec, Contents && contents)
|
||||
: flags_{flags}, spec_{spec}, contents_{std::forward<Contents>(contents)} {}
|
||||
|
||||
color_spec_type const & spec() const { return spec_; }
|
||||
std::uint32_t color() const { return spec_.code(); }
|
||||
Contents const & contents() const { return contents_; }
|
||||
|
||||
void print(std::ostream & os) const {
|
||||
if (flags_ & coloring_control_flags::color_on)
|
||||
spec_.print_fg_color_on(os);
|
||||
|
||||
if (flags_ & coloring_control_flags::contents)
|
||||
os << contents_;
|
||||
|
||||
if (flags_ & coloring_control_flags::color_off)
|
||||
spec_.print_fg_color_off(os);
|
||||
} /*print*/
|
||||
|
||||
private:
|
||||
/* controls independently what to print
|
||||
* \033[38;5;117m hello, world! \033[0m
|
||||
* <------------> <-----------> <----->
|
||||
* color_on contents color_off
|
||||
*/
|
||||
coloring_control_flags flags_ = coloring_control_flags::none;
|
||||
|
||||
color_spec_type spec_;
|
||||
|
||||
Contents contents_;
|
||||
}; /*color_impl*/
|
||||
|
||||
template <typename Contents>
|
||||
color_impl<Contents> with_color(color_spec_type const & spec, Contents && contents) {
|
||||
return color_impl<Contents>(coloring_control_flags::all, spec, std::forward<Contents>(contents));
|
||||
} /*with_color*/
|
||||
|
||||
inline color_impl<int>
|
||||
color_on(color_spec_type const & spec) {
|
||||
return color_impl<int>(coloring_control_flags::color_on, spec, 0);
|
||||
} /*color_on*/
|
||||
|
||||
inline color_impl<int>
|
||||
color_off(color_spec_type const & spec) {
|
||||
/* any spec other than color_spec_type::none() works here */
|
||||
return color_impl<int>(coloring_control_flags::color_off, spec, 0);
|
||||
} /*color_off*/
|
||||
|
||||
template <typename Contents>
|
||||
inline std::ostream &
|
||||
operator<<(std::ostream & os, color_impl<Contents> const & x) {
|
||||
x.print(os);
|
||||
return os;
|
||||
} /*operator<<*/
|
||||
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end color.hpp */
|
||||
42
xo-indentlog/include/xo/indentlog/print/concat.hpp
Normal file
42
xo-indentlog/include/xo/indentlog/print/concat.hpp
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/* @file concat.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ostream>
|
||||
#include <utility> // for std::move()
|
||||
|
||||
namespace xo {
|
||||
template <typename T1, typename T2>
|
||||
struct concat_impl {
|
||||
public:
|
||||
concat_impl(T1 && x1, T2 && x2)
|
||||
: x1_{std::forward<T1>(x1)}, x2_{std::forward<T2>(x2)} {}
|
||||
|
||||
T1 const & x1() const { return x1_; }
|
||||
T2 const & x2() const { return x2_; }
|
||||
|
||||
private:
|
||||
T1 x1_;
|
||||
T2 x2_;
|
||||
}; /*concat_impl*/
|
||||
|
||||
template <typename T1>
|
||||
T1 concat(T1 && x1) {
|
||||
return x1;
|
||||
} /*concat*/
|
||||
|
||||
template <typename T1, typename T2>
|
||||
concat_impl<T1,T2> concat(T1 && x1, T2 && x2) {
|
||||
return concat_impl<T1,T2>(std::move(x1), std::move(x2));
|
||||
} /*concat*/
|
||||
|
||||
template <typename T1, typename T2>
|
||||
inline std::ostream &
|
||||
operator<<(std::ostream & os, concat_impl<T1, T2> const & x) {
|
||||
os << x.x1() << x.x2();
|
||||
return os;
|
||||
} /*operator<<*/
|
||||
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end concat.hpp */
|
||||
70
xo-indentlog/include/xo/indentlog/print/filename.hpp
Normal file
70
xo-indentlog/include/xo/indentlog/print/filename.hpp
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
/* @file filename.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
|
||||
namespace xo {
|
||||
/* Example:
|
||||
* os << basename("/path/to/basename.cpp")
|
||||
* prints
|
||||
* basename.cpp
|
||||
* on os
|
||||
*/
|
||||
|
||||
/* Tag to drive header-only expression */
|
||||
template <typename Tag>
|
||||
class basename_impl {
|
||||
public:
|
||||
basename_impl(std::string_view path)
|
||||
: path_{path} {}
|
||||
|
||||
std::string_view const & path() const { return path_; }
|
||||
|
||||
/* /home/roland/proj/nestlog/include/nestlog/filename.hpp
|
||||
* <-basename->
|
||||
*/
|
||||
static void print_basename(std::ostream & os, std::string_view const & s) {
|
||||
std::size_t p = exclude_dirname(s);
|
||||
|
||||
os << s.substr(p);
|
||||
} /*print_basename*/
|
||||
|
||||
private:
|
||||
static std::size_t exclude_dirname(std::string_view const & s) {
|
||||
std::size_t z = s.size();
|
||||
|
||||
if (z == 0)
|
||||
return 0;
|
||||
|
||||
if (s[z-1] == '/') {
|
||||
/* ignore trailing '/' */
|
||||
return exclude_dirname(s.substr(0, z-1));
|
||||
}
|
||||
|
||||
std::size_t p = s.find_last_of('/');
|
||||
|
||||
if (p == std::string_view::npos)
|
||||
return 0;
|
||||
else
|
||||
return p + 1;
|
||||
} /*exclude_dirname*/
|
||||
|
||||
private:
|
||||
/* some unix pathname, e.g. [/home/roland/proj/nestlog/include/nestlog/filename.hpp] */
|
||||
std::string_view path_;
|
||||
}; /*basename_impl*/
|
||||
|
||||
using basename = basename_impl<class basename_impl_tag>;
|
||||
|
||||
inline std::ostream &
|
||||
operator<<(std::ostream & os,
|
||||
basename const & bn)
|
||||
{
|
||||
basename::print_basename(os, bn.path());
|
||||
return os;
|
||||
}
|
||||
} /*xo*/
|
||||
|
||||
/* end filename.hpp */
|
||||
46
xo-indentlog/include/xo/indentlog/print/fixed.hpp
Normal file
46
xo-indentlog/include/xo/indentlog/print/fixed.hpp
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/* @file fixed.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
|
||||
namespace xo {
|
||||
/* use:
|
||||
* ostream os = ...;
|
||||
*
|
||||
* os << fixed(3.1415926, 3)
|
||||
*
|
||||
* writes
|
||||
* 3.142
|
||||
*
|
||||
* on os, restoring stream's formatting+precision state
|
||||
*/
|
||||
class fixed {
|
||||
public:
|
||||
fixed(double x, std::uint16_t prec) : x_{x}, prec_{prec} {}
|
||||
|
||||
/* print this value */
|
||||
double x_;
|
||||
/* precision */
|
||||
std::uint16_t prec_ = 0;
|
||||
}; /*fixed*/
|
||||
|
||||
inline std::ostream &
|
||||
operator<<(std::ostream & s, fixed const & fx)
|
||||
{
|
||||
std::ios::fmtflags orig_flags = s.flags();
|
||||
std::streamsize orig_p = s.precision();
|
||||
|
||||
s.flags(std::ios::fixed);
|
||||
s.precision(fx.prec_);
|
||||
s << fx.x_;
|
||||
|
||||
s.flags(orig_flags);
|
||||
s.precision(orig_p);
|
||||
|
||||
return s;
|
||||
} /*operator<<*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end fixed.hpp */
|
||||
318
xo-indentlog/include/xo/indentlog/print/function.hpp
Normal file
318
xo-indentlog/include/xo/indentlog/print/function.hpp
Normal file
|
|
@ -0,0 +1,318 @@
|
|||
/* @file function.hpp */
|
||||
|
||||
#include "color.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
|
||||
namespace xo {
|
||||
enum class function_style : std::uint8_t {
|
||||
/** literal: print supplied text, no alterations **/
|
||||
literal,
|
||||
/** pretty: print name, surrounded by []
|
||||
* [double Quadratic::operator()(double) const]
|
||||
**/
|
||||
pretty,
|
||||
/** streamlined: remove extraneous detail,
|
||||
* try to print something like class::method
|
||||
* Quadratic::operator()
|
||||
**/
|
||||
streamlined,
|
||||
/** simple: remove everything except function/method name
|
||||
* operator()
|
||||
**/
|
||||
simple
|
||||
};
|
||||
|
||||
inline std::ostream &
|
||||
operator<< (std::ostream & os, function_style x) {
|
||||
switch(x) {
|
||||
case function_style::literal: os << "literal"; break;
|
||||
case function_style::pretty: os << "pretty"; break;
|
||||
case function_style::streamlined: os << "streamlined"; break;
|
||||
case function_style::simple: os << "simple"; break;
|
||||
default: os << "???"; break;
|
||||
}
|
||||
return os;
|
||||
} /*operator<<*/
|
||||
|
||||
/* Tag to drive header-only expression */
|
||||
template <typename Tag>
|
||||
class function_name_impl {
|
||||
public:
|
||||
/* color: ANSI escape color (lookup Select Graphic Rendition subset)
|
||||
* 0 = none
|
||||
* 31 = red
|
||||
*/
|
||||
function_name_impl(function_style style,
|
||||
color_spec_type const & spec,
|
||||
std::string_view pretty)
|
||||
: style_{style}, color_spec_{spec}, pretty_{pretty} {}
|
||||
|
||||
function_style style() const { return style_; }
|
||||
color_spec_type const & colorspec() const { return color_spec_; }
|
||||
std::string_view const & pretty() const { return pretty_; }
|
||||
|
||||
/* e.g.
|
||||
* <----------------------------------------------------- s ---------------------------------------------------------->
|
||||
* <----------------------------------------- s2 --------------------------------------->
|
||||
* <------------------------------------- s3 ------------------------------------->
|
||||
* <--------------------- s4 ----------------->
|
||||
* <----- s5 ----->
|
||||
* std::vector<std::pair<int, xo::bar> xo::sometemplateclass<T,U>::fib(int, char**) const [with T = int; with U = char]
|
||||
* ^ ^
|
||||
* p q
|
||||
*
|
||||
* fib <- .print_aux()
|
||||
*/
|
||||
static void print_simple(std::ostream & os, std::string_view const & s) {
|
||||
std::string_view s2 = exclude_template_footnote_suffix(s);
|
||||
std::string_view s3 = exclude_const_suffix(s2) /*no const suffix*/;
|
||||
std::size_t q = exclude_return_type(s3);
|
||||
std::string_view s4 = s3.substr(q); /* no return type */
|
||||
std::size_t r = find_toplevel_sep(s4, true /*last_flag*/);
|
||||
std::string_view s5 = s4.substr(r);
|
||||
|
||||
print_aux(os, s5);
|
||||
} /*print_simple*/
|
||||
|
||||
/* e.g.
|
||||
* <------------------------------------------------------------- s -------------------------------------------------->
|
||||
* <----------------------------------------------- s2 --------------------------------->
|
||||
* <----------------------------------- s3 --------------------------------------->
|
||||
* <--------------------- s4 ----------------->
|
||||
* <----------------- s5 ----------------->
|
||||
* std::vector<std::pair<int, xo::bar> xo::sometemplateclass<T,U>::fib(int, char**) const [with T = int; with U = char]
|
||||
* ^ ^ ^
|
||||
* q r p
|
||||
*
|
||||
* sometemplateclass ::fib <- .print_aux()
|
||||
*
|
||||
*/
|
||||
static void print_streamlined(std::ostream & os, std::string_view const & s) {
|
||||
std::string_view s2 = exclude_template_footnote_suffix(s);
|
||||
std::string_view s3 = exclude_const_suffix(s2) /*no const suffix */;
|
||||
std::size_t q = exclude_return_type(s3);
|
||||
std::string_view s4 = s3.substr(q); /*no return type*/
|
||||
std::size_t r = find_toplevel_sep(s4, false /*!last_flag*/);
|
||||
std::string_view s5 = s4.substr(r); /*no namespace qualifier (unless function)*/
|
||||
|
||||
//std::cerr << "print_streamlined: s=[" << s << "]" << std::endl;
|
||||
//std::cerr << "print_streamlined: s2=[" << s2 << "] (excluded [with ..] suffix)" << std::endl;
|
||||
//std::cerr << "print_streamlined: s3=[" << s3 << "] (excluded const suffix)" << std::endl;
|
||||
//std::cerr << "print_streamlined: s4=[" << s4 << "], q=" << q << " (excluded return type)" << std::endl;
|
||||
//std::cerr << "print_streamlined: s5=[" << s5 << "], r=" << r << " (excluded ns qualifier)" << std::endl;
|
||||
|
||||
print_aux(os, s5);
|
||||
} /*print_streamlined*/
|
||||
|
||||
private:
|
||||
static std::size_t exclude_return_type(std::string_view const & s) {
|
||||
/* strategy:
|
||||
* - scan right-to-left
|
||||
* - ignore anything between matching <>, () pairs (i.e. anything nested)
|
||||
* - stop at rightmost toplevel space --> return suffix following that space
|
||||
*/
|
||||
std::size_t nesting_level = 0;
|
||||
|
||||
std::size_t z = s.size();
|
||||
for (std::size_t rp = 0; rp < z; ++rp) {
|
||||
std::size_t p = z-1-rp;
|
||||
char ch = s[p];
|
||||
|
||||
if (ch == '<' || ch == '(')
|
||||
++nesting_level;
|
||||
|
||||
if (nesting_level == 0) {
|
||||
if (ch == ' ')
|
||||
return p + 1;
|
||||
}
|
||||
|
||||
if (ch == '>' || ch == ')')
|
||||
--nesting_level;
|
||||
}
|
||||
|
||||
return 0;
|
||||
} /*exclude_return_type*/
|
||||
|
||||
/* e.g.
|
||||
* void xo::foo::Foo<T, S>::notify(const T&) [with T = std::pair<int, char>; S = xo::foo::Bar]
|
||||
*/
|
||||
static std::string_view exclude_template_footnote_suffix(std::string_view const & s) {
|
||||
/* strategy:
|
||||
* - left-to-right
|
||||
* - exclude ' [with '... to end of string
|
||||
*/
|
||||
#if __clang__
|
||||
/* clang footnote like [CharT = char] instead of [with CharT = char] */
|
||||
std::size_t p = s.find(" [");
|
||||
#else
|
||||
# if (__GNUC__ > 13) || ((__GNUC__ == 13) && (__GNUC_MINOR__ >= 3))
|
||||
/* gcc footnote like [CharT = char] instead of [with CharT = char] starting w/ gcc 13.3 (approximately ?)*/
|
||||
std::size_t p = s.find(" [");
|
||||
# else
|
||||
std::size_t p = s.find(" [with ");
|
||||
# endif
|
||||
#endif
|
||||
|
||||
return s.substr(0, p);
|
||||
} /*exclude_template_footnote_suffix*/
|
||||
|
||||
static std::string_view exclude_const_suffix(std::string_view const & s) {
|
||||
constexpr std::uint32_t c_suffix_z = 6 /*strlen(" const")*/;
|
||||
|
||||
if ((s.size() > c_suffix_z)
|
||||
&& (s.substr(s.size() - c_suffix_z) == " const"))
|
||||
{
|
||||
return s.substr(0, s.size() - c_suffix_z);
|
||||
}
|
||||
|
||||
return s;
|
||||
} /*exclude_const_suffix*/
|
||||
|
||||
/* e.g.
|
||||
* xo::ns::someclass<xo::foo, xo::bar>::somemethod(xo::enum1, std::vector<xo::blah>)
|
||||
* ^
|
||||
* return this pos
|
||||
* (pos just after 2nd-last non-nested separator)
|
||||
*
|
||||
* last_flag: return pos after last ::
|
||||
* !last_flag: return pos after 2nd-last ::
|
||||
*/
|
||||
static std::size_t find_toplevel_sep(std::string_view const & s, bool last_flag) {
|
||||
/* strategy:
|
||||
* - scan left-to-right
|
||||
* - ignore anything between matching <>, () pairs (i.e. anything nested)
|
||||
* - count :: pairs
|
||||
* - remember 2nd-last :: pair; reports pos just after it
|
||||
*
|
||||
* note:
|
||||
* - if no :: pairs, or only one such pair, return 0
|
||||
*/
|
||||
std::size_t nesting_level = 0;
|
||||
|
||||
std::size_t pos_after_last_sep = 0;
|
||||
std::size_t pos_after_2ndlast_sep = 0;
|
||||
|
||||
for (std::size_t p = 0; p < s.size(); ++p) {
|
||||
char ch = s[p];
|
||||
|
||||
if (ch == '<' || ch == '(')
|
||||
++nesting_level;
|
||||
|
||||
if (nesting_level == 0) {
|
||||
if ((ch == ':')
|
||||
&& (p+1 < s.size())
|
||||
&& s[p+1] == ':')
|
||||
{
|
||||
pos_after_2ndlast_sep = pos_after_last_sep;
|
||||
pos_after_last_sep = p+2;
|
||||
++p; /* skipping 1st : in separator */
|
||||
}
|
||||
}
|
||||
|
||||
if (ch == '>' || ch == ')')
|
||||
--nesting_level;
|
||||
}
|
||||
|
||||
std::size_t retval = (last_flag ? pos_after_last_sep : pos_after_2ndlast_sep);
|
||||
|
||||
return retval;
|
||||
} /*find_toplevel_sep*/
|
||||
|
||||
/* fib(int, char **) --> fib
|
||||
* quux(std::vector<std::size_t, std::allocator<std::size_t>>) -> quux
|
||||
* foo::bar<std::vector<char>>() -> foo::bar
|
||||
*/
|
||||
static void print_aux(std::ostream & os, std::string_view const & s) {
|
||||
//std::cerr << "print_aux: s=[" << s << "]" << std::endl;
|
||||
|
||||
/* strategy:
|
||||
* - print left-to-right, omit anything between matching <> or () pairs.
|
||||
* - don't keep track of which is which, so would also match < with ) etc;
|
||||
* this acceptable since pretty functions won't visit this corner case
|
||||
*/
|
||||
std::size_t nesting_level = 0;
|
||||
|
||||
/* index of next match within string 'operator()'.
|
||||
* if we would print 'operator', and it's followed by trailing paren pair,
|
||||
* then don't exclude the trailing ()
|
||||
*/
|
||||
std::int32_t match_operator_ix = 0;
|
||||
constexpr char const * c_target_str = "operator(";
|
||||
|
||||
for (char ch : s) {
|
||||
//std::cerr << "print_aux: ch=[" << ch << "]" << ", nesting_level=" << nesting_level << ", match_operator_ix=" << match_operator_ix << std::endl;
|
||||
|
||||
/* looking for match on 'operator(' at nesting level 0 */
|
||||
if ((nesting_level == 0) && (ch == c_target_str[match_operator_ix]) && (match_operator_ix < 9))
|
||||
++match_operator_ix;
|
||||
else
|
||||
match_operator_ix = 0;
|
||||
|
||||
/* don't increment nesting level if immediately after 'operator' */
|
||||
if (ch == '<') {
|
||||
++nesting_level;
|
||||
} else if (ch == '(') {
|
||||
if ((nesting_level == 0) && (match_operator_ix == 9)) {
|
||||
/* special case:
|
||||
* 012345678
|
||||
* operator(
|
||||
* at toplevel; don't count the '(' here toward nesting level
|
||||
*/
|
||||
;
|
||||
} else {
|
||||
++nesting_level;
|
||||
}
|
||||
}
|
||||
|
||||
if (nesting_level == 0)
|
||||
os << ch;
|
||||
|
||||
if (nesting_level > 0 && ((ch == '>') || (ch == ')')))
|
||||
--nesting_level;
|
||||
}
|
||||
} /*print_aux*/
|
||||
|
||||
private:
|
||||
/* FS_Simple | FS_Pretty (= FS_Literal) | FS_Streamlined */
|
||||
function_style style_;
|
||||
/* terminal color (controls vt100 escape) */
|
||||
color_spec_type color_spec_;
|
||||
/* e.g. __PRETTY_FUNCTION__ */
|
||||
std::string_view pretty_;
|
||||
}; /*function_name_impl*/
|
||||
|
||||
using function_name = function_name_impl<class function_name_impl_tag>;
|
||||
|
||||
inline std::ostream &
|
||||
operator<<(std::ostream & os,
|
||||
function_name const & fn)
|
||||
{
|
||||
/* set text color */
|
||||
|
||||
switch(fn.style()) {
|
||||
case function_style::literal:
|
||||
os << with_color(fn.colorspec(), fn.pretty());
|
||||
break;
|
||||
case function_style::pretty:
|
||||
os << "[" << with_color(fn.colorspec(), fn.pretty()) << "]";
|
||||
break;
|
||||
case function_style::streamlined:
|
||||
/* omit namespace qualifiers and template arguments */
|
||||
os << color_on(fn.colorspec());
|
||||
function_name::print_streamlined(os, fn.pretty());
|
||||
os << color_off(fn.colorspec());
|
||||
break;
|
||||
case function_style::simple:
|
||||
os << color_on(fn.colorspec());
|
||||
function_name::print_simple(os, fn.pretty());
|
||||
os << color_off(fn.colorspec());
|
||||
break;
|
||||
}
|
||||
|
||||
return os;
|
||||
} /*operator<<*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end function.hpp */
|
||||
145
xo-indentlog/include/xo/indentlog/print/hex.hpp
Normal file
145
xo-indentlog/include/xo/indentlog/print/hex.hpp
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
/** @file hex.hpp **/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
|
||||
namespace xo {
|
||||
/**
|
||||
@class hex indentlog/print/hex.hpp
|
||||
|
||||
@brief Container for a (1-byte) value to be printed in hexadecimal
|
||||
|
||||
Example:
|
||||
@code
|
||||
#include "indentlog/print/hex.hpp"
|
||||
|
||||
std::ostream os = ...;
|
||||
os << hex(16 + 63); // output: 1f
|
||||
os << hex(16 + 63, true); // output: 1f(O)
|
||||
@endcode
|
||||
**/
|
||||
struct hex {
|
||||
/** @brief constructor; create stream-inserter instance */
|
||||
explicit hex(std::uint8_t x, bool w_char = false) : x_{x}, with_char_{w_char} {}
|
||||
|
||||
/**
|
||||
@brief print hexadecimal byte-value on to stream.
|
||||
@param os print on this stream.
|
||||
|
||||
@tparam Stream typename for character stream.
|
||||
**/
|
||||
template <typename Stream>
|
||||
void print(Stream & os) const {
|
||||
std::uint8_t lo = x_ & 0xf;
|
||||
std::uint8_t hi = x_ >> 4;
|
||||
|
||||
char lo_ch = (lo < 10) ? '0' + lo : 'a' + lo - 10;
|
||||
char hi_ch = (hi < 10) ? '0' + hi : 'a' + hi - 10;
|
||||
|
||||
os << hi_ch << lo_ch;
|
||||
|
||||
if (with_char_) {
|
||||
os << "(";
|
||||
if (std::isprint(x_))
|
||||
os << static_cast<char>(x_);
|
||||
else
|
||||
os << "?";
|
||||
os << ")";
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
/** @brief value to print (in hexadecimal) **/
|
||||
std::uint8_t x_;
|
||||
/** @brief if true, follow with ascii character encoding **/
|
||||
bool with_char_;
|
||||
};
|
||||
|
||||
/**
|
||||
@brief stream inserter for an 8-bit quantity to be printed in hexadecimal.
|
||||
|
||||
@param os print on this stream
|
||||
@param ins package for value to insert
|
||||
**/
|
||||
inline std::iostream &
|
||||
operator<< (std::iostream & os, hex const & ins) {
|
||||
ins.print(os);
|
||||
return os;
|
||||
}
|
||||
|
||||
/**
|
||||
@class hex_view indentlog/print/hex.hpp
|
||||
|
||||
@brief Container for a range (unowned) of 1-byte values to be printed in hexadecimal
|
||||
|
||||
Print a range of bytes on an arbitrary character stream.
|
||||
Does not use @c iomanips, so will not alter stream formatting flags if used with @c iostream.
|
||||
|
||||
Example:
|
||||
@code
|
||||
#include "indentlog/print/hex.hpp"
|
||||
|
||||
std::ostream os = ...;
|
||||
os << hex_view("hello", false); // output: [68 65 6c 6c 6f]
|
||||
os << hex_view("hello", true); // output: [68(h) 65(e) 6c(l) 6c(l) 6f(o)]
|
||||
@endcode
|
||||
**/
|
||||
struct hex_view {
|
||||
/** @brief constructor; create stream-inserter instance for a range of bytes **/
|
||||
hex_view(std::uint8_t const * lo, std::uint8_t const * hi, bool as_text)
|
||||
: lo_{lo}, hi_{hi}, as_text_{as_text} {}
|
||||
/** @brief constructor; create stream-inserter instance for a range of chars **/
|
||||
hex_view(char const * lo, char const * hi, bool as_text)
|
||||
: lo_{reinterpret_cast<std::uint8_t const *>(lo)},
|
||||
hi_{reinterpret_cast<std::uint8_t const *>(hi)},
|
||||
as_text_{as_text} {}
|
||||
|
||||
/**
|
||||
@brief print hexadecimal byte range on stream.
|
||||
@param os print on this stream
|
||||
|
||||
@tparam Stream typename for character stream.
|
||||
**/
|
||||
template <typename Stream>
|
||||
void print(Stream & os) const {
|
||||
os << "[";
|
||||
std::size_t i = 0;
|
||||
for (std::uint8_t const * p = lo_; p < hi_; ++p) {
|
||||
if (i > 0)
|
||||
os << " ";
|
||||
xo::hex(*p, as_text_).print(os);
|
||||
//os << xo::hex(*p, as_text_);
|
||||
++i;
|
||||
}
|
||||
os << "]";
|
||||
}
|
||||
|
||||
private:
|
||||
/** @brief print byte range starting at this address **/
|
||||
std::uint8_t const * lo_;
|
||||
/** @brief print byte range up to (but not including) this address **/
|
||||
std::uint8_t const * hi_;
|
||||
/** @brief if true also print ascii encoding (for printable codes),
|
||||
* \c ? otherwise. @see hex::with_char
|
||||
**/
|
||||
bool as_text_;
|
||||
};
|
||||
|
||||
/**
|
||||
@brief stream inserter for a range of 1-byte values to be printed in hexadecimal
|
||||
|
||||
@param os print on this stream.
|
||||
@param ins (container for) values to insert.
|
||||
**/
|
||||
template <typename Stream>
|
||||
Stream &
|
||||
operator<< (Stream & os, hex_view const & ins) {
|
||||
ins.print(os);
|
||||
return os;
|
||||
}
|
||||
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end hex.hpp */
|
||||
52
xo-indentlog/include/xo/indentlog/print/pad.hpp
Normal file
52
xo-indentlog/include/xo/indentlog/print/pad.hpp
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
/* @file pad.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
|
||||
namespace xo {
|
||||
/* use:
|
||||
* ostream os = ...;
|
||||
*
|
||||
* 1.
|
||||
* os << ":" << pad(8) << ":"
|
||||
*
|
||||
* writes
|
||||
* : :
|
||||
*
|
||||
* 2.
|
||||
* os << pad(16, '-')
|
||||
*
|
||||
* writes
|
||||
* ----------------
|
||||
*
|
||||
* on os
|
||||
*/
|
||||
class pad_impl {
|
||||
public:
|
||||
pad_impl(std::uint32_t n, char pad_char) : n_pad_{n}, pad_char_{pad_char} {}
|
||||
|
||||
std::uint32_t n_pad() const { return n_pad_; }
|
||||
char pad_char() const { return pad_char_; }
|
||||
|
||||
private:
|
||||
std::uint32_t n_pad_ = 0;
|
||||
char pad_char_ = '\0';
|
||||
}; /*pad_impl*/
|
||||
|
||||
inline pad_impl
|
||||
pad(std::uint32_t n, char pad_char = ' ') { return pad_impl(n, pad_char); }
|
||||
|
||||
inline std::ostream &
|
||||
operator<<(std::ostream & s,
|
||||
pad_impl const & pad)
|
||||
{
|
||||
for(std::uint32_t i=0; i<pad.n_pad(); ++i)
|
||||
s << pad.pad_char();
|
||||
return s;
|
||||
} /*operator<<*/
|
||||
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end pad.hpp */
|
||||
24
xo-indentlog/include/xo/indentlog/print/pair.hpp
Normal file
24
xo-indentlog/include/xo/indentlog/print/pair.hpp
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
/* @file pair.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <utility>
|
||||
|
||||
namespace std {
|
||||
template <typename T, typename U>
|
||||
inline std::ostream &
|
||||
operator<<(std::ostream & os,
|
||||
std::pair<T,U> const & x)
|
||||
{
|
||||
os << "["
|
||||
<< x.first
|
||||
<< " "
|
||||
<< x.second
|
||||
<< "]";
|
||||
|
||||
return os;
|
||||
} /*operator<<*/
|
||||
} /*namespace std*/
|
||||
|
||||
/* end pair.hpp */
|
||||
28
xo-indentlog/include/xo/indentlog/print/printer.hpp
Normal file
28
xo-indentlog/include/xo/indentlog/print/printer.hpp
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/* @file printer.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace xo {
|
||||
namespace print {
|
||||
/* print an event to a logfile
|
||||
* intended to be usable as EventSink argument
|
||||
* to RealizationSimSource<T, EventSink>
|
||||
*/
|
||||
template<typename T, typename Stream>
|
||||
class printer {
|
||||
public:
|
||||
printer(Stream && os) : os_{std::move(os)} {}
|
||||
|
||||
void operator()(T const & x) {
|
||||
this->os_ << x;
|
||||
}
|
||||
|
||||
private:
|
||||
Stream os_;
|
||||
}; /*printer*/
|
||||
} /*namespace print*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end printer.hpp */
|
||||
153
xo-indentlog/include/xo/indentlog/print/quoted.hpp
Normal file
153
xo-indentlog/include/xo/indentlog/print/quoted.hpp
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
/* file quoted.hpp
|
||||
*
|
||||
* author: Roland Conybeare, Sep 2022
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "tostr.hpp"
|
||||
#include <sstream>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
|
||||
namespace xo {
|
||||
namespace print {
|
||||
/* use this to avoid template conversion hassles
|
||||
* since literal strings get treated as arrays
|
||||
*/
|
||||
template<typename T>
|
||||
char const * ccs(T x) { return x; }
|
||||
|
||||
/* Printing cases:
|
||||
* 1. T&&:
|
||||
* move into quot_impl<T>. T must be moveable!
|
||||
* 2. T&:
|
||||
* copy reference into quot_impl<T&>.
|
||||
* similarly for T const &, copy reference into quot_impl<T const &>
|
||||
*/
|
||||
|
||||
template<typename T>
|
||||
class quot_impl {
|
||||
public:
|
||||
quot_impl(bool unq_flag, T x) : unq_flag_{unq_flag}, value_{std::move(x)} {}
|
||||
|
||||
bool unq_flag() const { return unq_flag_; }
|
||||
T const & value() const { return value_; }
|
||||
|
||||
static void print_with_escapes(const std::string & xs,
|
||||
std::ostream & os)
|
||||
{
|
||||
/* printed value contains a space
|
||||
* and/or a must-be-escaped character.
|
||||
* in any case, need quotes
|
||||
*/
|
||||
|
||||
os << "\"";
|
||||
|
||||
/* print contents of ss, with escapes:
|
||||
* \ => \\
|
||||
* " => \"
|
||||
* newline => \n
|
||||
* cr => \r
|
||||
*/
|
||||
for (char ch : xs) {
|
||||
switch (ch) {
|
||||
case '"':
|
||||
/* " => \" */
|
||||
os << "\\\"";
|
||||
break;
|
||||
case '\n':
|
||||
/* newline -> \n */
|
||||
/* somehow attempt to escape the newline triggers collapse */
|
||||
os << "\\n";
|
||||
break;
|
||||
case '\r':
|
||||
/* cr -> \r */
|
||||
os << "\\r";
|
||||
break;
|
||||
case '\\':
|
||||
/* \ => \\ (mind c++ requires we escape \) */
|
||||
os << "\\\\";
|
||||
break;
|
||||
default:
|
||||
os << ch;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
os << "\"";
|
||||
}
|
||||
|
||||
void print(std::ostream & os) const {
|
||||
std::string xs = xo::tostr(value_);
|
||||
|
||||
if (xs.empty()) {
|
||||
/* always print empty string as "" */
|
||||
os << "\"\"";
|
||||
} else if ((xs.at(0) == '<') && (xs.at(xs.size() - 1) == '>')) {
|
||||
/* assume string represents output of a well-formed object printer,
|
||||
* and already self-escapes
|
||||
*/
|
||||
os << xs;
|
||||
} else if (xs.find_first_of(" \"\n\r\\") == std::string::npos) {
|
||||
/* no escapes needed, just print xs */
|
||||
if (unq_flag_)
|
||||
os << xs;
|
||||
else
|
||||
os << "\"" << xs << "\"";
|
||||
} else {
|
||||
print_with_escapes(xs, os);
|
||||
}
|
||||
} /*print*/
|
||||
|
||||
private:
|
||||
/* .unq_flag: if true, omit surrounding " chars
|
||||
* whenever printed value satisfies both:
|
||||
* - no escaped chars
|
||||
* - no spaces
|
||||
*/
|
||||
bool unq_flag_ = false;
|
||||
/* .value: value to be printed */
|
||||
T value_;
|
||||
}; /*quot_impl*/
|
||||
|
||||
template<typename T>
|
||||
std::ostream &
|
||||
operator<<(std::ostream & os, quot_impl<T> const & x) {
|
||||
x.print(os);
|
||||
return os;
|
||||
} /*operator*/
|
||||
|
||||
/* writing out std::forward<T> behavior for completeness' sake:
|
||||
*
|
||||
* 1. call quoted(x) with rvalue std::string x, then:
|
||||
* - T will be deduced to [std::string]
|
||||
* (in particular: _not_ std::string &, std::string const &, std::string &&)
|
||||
* - rvalue std::string passed to quot_impl ctor
|
||||
*
|
||||
* 2a. call quoted(x) with std::string & x, then:
|
||||
* - T deduced to [std::string &]
|
||||
* - std::string & passed to quot_impl ctor
|
||||
*
|
||||
* 2b. call quoted(x) with std::string const & x, then:
|
||||
* - T deduced to [std::string const &]
|
||||
* - std::string const & passed to quot_impl ctor
|
||||
*/
|
||||
template<typename T>
|
||||
auto quot(T && x) {
|
||||
return quot_impl(false /*unq_flag*/, std::forward<T>(x));
|
||||
}
|
||||
|
||||
inline auto qcstr(char const * x) {
|
||||
return quot(x);
|
||||
} /*qcstr*/
|
||||
|
||||
template<typename T>
|
||||
auto unq(T && x) {
|
||||
return quot_impl(true /*unq_flag*/, std::forward<T>(x));
|
||||
}
|
||||
} /*namespace print*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end quoted.hpp */
|
||||
43
xo-indentlog/include/xo/indentlog/print/quoted_char.hpp
Normal file
43
xo-indentlog/include/xo/indentlog/print/quoted_char.hpp
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
/* @file quoted_char.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace xo {
|
||||
template<typename CharT>
|
||||
class quoted_char {
|
||||
public:
|
||||
quoted_char(CharT ch) : ch_{ch} {}
|
||||
|
||||
void print(std::ostream & os) const {
|
||||
switch(ch_) {
|
||||
case '\033':
|
||||
os << "\\033";
|
||||
break;
|
||||
case '\n':
|
||||
os << "\\n";
|
||||
break;
|
||||
case '\r':
|
||||
os << "\\r";
|
||||
break;
|
||||
default:
|
||||
os << ch_;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
CharT ch_;
|
||||
}; /*quoted_char*/
|
||||
|
||||
template<typename CharT>
|
||||
inline std::ostream &
|
||||
operator<<(std::ostream & os, quoted_char<CharT> const & x) {
|
||||
x.print(os);
|
||||
return os;
|
||||
} /*operator<<*/
|
||||
|
||||
} /*namespace xo*/
|
||||
|
||||
|
||||
/* end quoted_char.hpp */
|
||||
115
xo-indentlog/include/xo/indentlog/print/tag.hpp
Normal file
115
xo-indentlog/include/xo/indentlog/print/tag.hpp
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
/* @file tag.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "tag_config.hpp"
|
||||
#include "concat.hpp"
|
||||
#include "quoted.hpp"
|
||||
#include "color.hpp"
|
||||
#include <iostream>
|
||||
|
||||
// STRINGIFY(xyz) -> "xyz"
|
||||
#ifndef STRINGIFY
|
||||
# define STRINGIFY(x) #x
|
||||
#endif
|
||||
|
||||
// TAG(xyz) -> tag("xyz", xyz)
|
||||
#define TAG(x) xo::make_tag(STRINGIFY(x), x)
|
||||
#define TAG2(x, y) xo::make_tag(x, y)
|
||||
|
||||
#define XTAG(x) xo::xtag(STRINGIFY(x), x)
|
||||
//#define XTAG2(x, y) xo::xtag(x, y)
|
||||
|
||||
namespace xo {
|
||||
// associate a name with a value
|
||||
//
|
||||
// will print like
|
||||
// :name value
|
||||
//
|
||||
// NOTE: will search for operator<< overloads in the logutil
|
||||
// namespace
|
||||
//*/
|
||||
template <bool PrefixSpace, typename Name, typename Value>
|
||||
struct tag_impl {
|
||||
tag_impl(Name const & n, Value const & v)
|
||||
: name_{n}, value_{v} {}
|
||||
tag_impl(Name && n, Value && v)
|
||||
: name_{std::forward<Name>(n)}, value_{std::forward<Value>(v)} {}
|
||||
|
||||
Name const & name() const { return name_; }
|
||||
Value const & value() const { return value_; }
|
||||
|
||||
private:
|
||||
Name name_;
|
||||
Value value_;
|
||||
}; /*tag_impl*/
|
||||
|
||||
/* deduce tag template-type from arguments */
|
||||
template<typename Name, typename Value>
|
||||
tag_impl<false, Name, Value>
|
||||
make_tag(Name && n, Value && v)
|
||||
{
|
||||
return tag_impl<false, Name, Value>(n, v);
|
||||
} /*make_tag*/
|
||||
|
||||
template<typename Value>
|
||||
tag_impl<false, char const *, Value>
|
||||
make_tag(char const * n, Value && v) {
|
||||
return tag_impl<false, char const *, Value>(n, v);
|
||||
} /*make_tag*/
|
||||
|
||||
template<typename Name, typename Value>
|
||||
tag_impl<true, Name, Value>
|
||||
xtag(Name && n, Value && v)
|
||||
{
|
||||
return tag_impl<true, Name, Value>(n, v);
|
||||
} /*xtag*/
|
||||
|
||||
template<typename Value>
|
||||
tag_impl<true, char const *, Value>
|
||||
xtag(char const * n, Value && v) {
|
||||
return tag_impl<true, char const *, Value>(n, v);
|
||||
} /*xtag*/
|
||||
|
||||
inline
|
||||
tag_impl<true, char const *, char const *>
|
||||
xtag_pre(char const * n) {
|
||||
return tag_impl<true, char const *, char const *>(n, "");
|
||||
} /*xtag_pre*/
|
||||
|
||||
// ----- tag -----
|
||||
|
||||
template<typename Name, typename Value>
|
||||
tag_impl<false, Name, Value>
|
||||
tag(Name && n, Value && v)
|
||||
{
|
||||
return tag_impl<false, Name, Value>(n, v);
|
||||
} /*tag*/
|
||||
|
||||
template<typename Value>
|
||||
tag_impl<false, char const *, Value>
|
||||
tag(char const * n, Value && v)
|
||||
{
|
||||
return tag_impl<false, char const *, Value>(n, v);
|
||||
} /*tag*/
|
||||
|
||||
// ----- operator<< on tag_impl -----
|
||||
|
||||
template <bool PrefixSpace, typename Name, typename Value>
|
||||
inline std::ostream &
|
||||
operator<<(std::ostream &s,
|
||||
tag_impl<PrefixSpace, Name, Value> const & tag)
|
||||
{
|
||||
using xo::print::unq;
|
||||
|
||||
if(PrefixSpace)
|
||||
s << " ";
|
||||
|
||||
s << with_color(tag_config::tag_color, concat((char const *)":", tag.name()))
|
||||
<< " " << unq(tag.value());
|
||||
|
||||
return s;
|
||||
} /*operator<<*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end tag.hpp */
|
||||
28
xo-indentlog/include/xo/indentlog/print/tag_config.hpp
Normal file
28
xo-indentlog/include/xo/indentlog/print/tag_config.hpp
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/* @file tag_config.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "color.hpp"
|
||||
#include <cstdint>
|
||||
|
||||
namespace xo {
|
||||
/* Tag here b/c we want header-only library */
|
||||
template <typename Tag>
|
||||
struct tag_config_impl {
|
||||
/* color to use for tags
|
||||
* os << tag("foo", foovalue)
|
||||
* to produces output like
|
||||
* :foo foovalue
|
||||
* with :foo using .tag_color
|
||||
*/
|
||||
static color_spec_type tag_color;
|
||||
}; /*tag_config_impl*/
|
||||
|
||||
template <typename Tag>
|
||||
color_spec_type
|
||||
tag_config_impl<Tag>::tag_color = color_spec_type::xterm(245);
|
||||
|
||||
using tag_config = tag_config_impl<class tag_config_tag>;
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end tag_config.hpp */
|
||||
90
xo-indentlog/include/xo/indentlog/print/time.hpp
Normal file
90
xo-indentlog/include/xo/indentlog/print/time.hpp
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
/* @file time.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "xo/indentlog/timeutil/timeutil.hpp"
|
||||
|
||||
namespace xo {
|
||||
namespace time {
|
||||
// ----- iso8601 -----
|
||||
|
||||
/* stream inserter that displays time in ISO 8601 format:
|
||||
* 2012-04-23T18:25:43.511Z
|
||||
*/
|
||||
struct iso8601 {
|
||||
iso8601(utc_nanos t0) : t0_{t0} {}
|
||||
|
||||
utc_nanos t0_;
|
||||
}; /*iso8601*/
|
||||
|
||||
inline std::ostream &
|
||||
operator<<(std::ostream & os,
|
||||
iso8601 x)
|
||||
{
|
||||
timeutil::print_iso8601(x.t0_, os);
|
||||
return os;
|
||||
} /*operator<<*/
|
||||
|
||||
// ----- hms_msec -----
|
||||
|
||||
/* stream inserter that display time like:
|
||||
* hh:mm:ss.nnn
|
||||
*/
|
||||
struct hms_msec {
|
||||
hms_msec(nanos dt) : dt_{dt} {}
|
||||
|
||||
static hms_msec utc(utc_nanos t0) { return hms_msec(timeutil::utc_split_vs_midnight(t0).second); }
|
||||
static hms_msec local(utc_nanos t0) { return hms_msec(timeutil::local_split_vs_midnight(t0).second); }
|
||||
|
||||
nanos dt_;
|
||||
}; /*hms_msec*/
|
||||
|
||||
inline std::ostream &
|
||||
operator<<(std::ostream & os, hms_msec x)
|
||||
{
|
||||
timeutil::print_hms_msec(x.dt_, os);
|
||||
return os;
|
||||
} /*operator<<*/
|
||||
|
||||
// ----- hms_usec -----
|
||||
|
||||
/* stream inserter that display time like:
|
||||
* hh:mm:ss.nnnnnn
|
||||
*/
|
||||
struct hms_usec {
|
||||
hms_usec(nanos dt) : dt_{dt} {}
|
||||
|
||||
static hms_usec utc(utc_nanos t0) { return hms_usec(timeutil::utc_split_vs_midnight(t0).second); }
|
||||
static hms_usec local(utc_nanos t0) { return hms_usec(timeutil::local_split_vs_midnight(t0).second); }
|
||||
|
||||
nanos dt_;
|
||||
}; /*hms_msec*/
|
||||
|
||||
inline std::ostream &
|
||||
operator<<(std::ostream & os, hms_usec x)
|
||||
{
|
||||
timeutil::print_hms_usec(x.dt_, os);
|
||||
return os;
|
||||
} /*operator<<*/
|
||||
} /*namespace time*/
|
||||
} /*namespace xo*/
|
||||
|
||||
namespace std {
|
||||
namespace chrono {
|
||||
inline std::ostream & operator<<(std::ostream & os,
|
||||
xo::time::utc_nanos t0)
|
||||
{
|
||||
xo::time::timeutil::print_utc_ymd_hms_usec(t0, os);
|
||||
return os;
|
||||
} /*operator<<*/
|
||||
|
||||
inline std::ostream & operator<<(std::ostream & os,
|
||||
xo::time::nanos dt)
|
||||
{
|
||||
xo::time::timeutil::print_hms_usec(dt, os);
|
||||
return os;
|
||||
} /*operator<<*/
|
||||
} /*namespace chrono*/
|
||||
} /*namespace std*/
|
||||
|
||||
/* end time.hpp */
|
||||
105
xo-indentlog/include/xo/indentlog/print/tostr.hpp
Normal file
105
xo-indentlog/include/xo/indentlog/print/tostr.hpp
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
/* file tostr.hpp
|
||||
*
|
||||
* author: Roland Conybeare, Sep 2022
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace xo {
|
||||
/*
|
||||
* write x to stream s
|
||||
* note: here x is a universal reference, since
|
||||
* (a) it's a template type
|
||||
* (b) requires deduction to establish x's type
|
||||
* this means:
|
||||
* x will be an r-value reference or an l-value reference
|
||||
* depending on calling context
|
||||
*
|
||||
* see:
|
||||
* https://eli.thegreenplace.net/2014/variadic-templates-in-c/
|
||||
* http://bajamircea.github.io/coding/cpp/2016/04/07/move-forward.html
|
||||
* https://en.cppreference.com/w/cpp/language/value_category
|
||||
*
|
||||
* has identity == has address
|
||||
*
|
||||
* /- has identity -----------------\
|
||||
* | |
|
||||
* | lvalue |
|
||||
* | glvalue /------------------------------\
|
||||
* | | | |
|
||||
* | | xvalue | |
|
||||
* | | rvalue | |
|
||||
* | | glvalue | |
|
||||
* | | | |
|
||||
* \--------------------------------/ |
|
||||
* | rvalue |
|
||||
* | prvalue |
|
||||
* | |
|
||||
* \- can be moved ---------------/
|
||||
*
|
||||
* 1. has identity, but cannot be moved -> it's an lvalue; otherwise it's an rvalue
|
||||
* e.g: local variable name
|
||||
*
|
||||
* 2. can be moved, but no identity -> it's a prvalue (pure right-value);
|
||||
* otherwise it's a glvalue (generalized left-value)
|
||||
* e.g: non-reference function return value, or literal constant
|
||||
*
|
||||
* 3. has identity and can be moved -> it's an xvalue (strange value)
|
||||
* e.g: std::move(a)
|
||||
*
|
||||
* reminder:
|
||||
* - std::move() does not move: it converts lvalue to rvalue, so compiler can select
|
||||
* desired overload
|
||||
* - std::forward() does not forward: it recovers original value category
|
||||
* (when starting with a universal reference), so compiler can select
|
||||
* desired ctor
|
||||
*/
|
||||
|
||||
/* no-op terminal case */
|
||||
template<class Stream>
|
||||
Stream & tos(Stream & s) {
|
||||
return s;
|
||||
}
|
||||
|
||||
// Use:
|
||||
// tos(s,a,b,c)
|
||||
// is the same as
|
||||
// s << a << b << c;
|
||||
//
|
||||
template<class Stream, typename T>
|
||||
Stream & tos(Stream & s, T && x) {
|
||||
s << x;
|
||||
return s;
|
||||
} /*tos*/
|
||||
|
||||
template <class Stream, typename T, typename... Tn>
|
||||
Stream &tos(Stream &s, T &&x, Tn &&...rest) {
|
||||
s << x;
|
||||
return tos(s, rest...);
|
||||
} /*tos*/
|
||||
|
||||
// like tos(..), but append newline
|
||||
//
|
||||
template <class Stream, typename... Tn>
|
||||
Stream &tosn(Stream &s, Tn &&...args) {
|
||||
tos(s, args...);
|
||||
s << std::endl;
|
||||
return s;
|
||||
} /*tosn*/
|
||||
|
||||
// tostr(args..) writes arguments to temporary stingstream,
|
||||
// returns its contents
|
||||
//
|
||||
template <typename... Tn> std::string tostr(Tn &&...args) {
|
||||
std::stringstream ss;
|
||||
tos(ss, args...);
|
||||
//ss << std::ends;
|
||||
return ss.str();
|
||||
} /*tostr*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end tostr.hpp */
|
||||
28
xo-indentlog/include/xo/indentlog/print/vector.hpp
Normal file
28
xo-indentlog/include/xo/indentlog/print/vector.hpp
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/* file vector.hpp
|
||||
*
|
||||
* author: Roland Conybeare, Sep 2022
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
namespace std {
|
||||
template<typename T>
|
||||
inline std::ostream &
|
||||
operator<<(std::ostream & os,
|
||||
std::vector<T> const & v)
|
||||
{
|
||||
os << "[";
|
||||
for(size_t i=0, z=v.size(); i<z; ++i) {
|
||||
if(i > 0)
|
||||
os << " ";
|
||||
os << v[i];
|
||||
}
|
||||
os << "]";
|
||||
return os;
|
||||
} /*operator<<*/
|
||||
} /*namespace std*/
|
||||
|
||||
/* end vector.hpp */
|
||||
297
xo-indentlog/include/xo/indentlog/scope.hpp
Normal file
297
xo-indentlog/include/xo/indentlog/scope.hpp
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
/* @file scope.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "log_state.hpp"
|
||||
#include "print/filename.hpp"
|
||||
#include "print/tostr.hpp"
|
||||
#include "print/tag.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <cstdint>
|
||||
#include <memory> // for std::unique_ptr
|
||||
|
||||
namespace xo {
|
||||
|
||||
template <typename ChartT, typename Traits>
|
||||
class state_impl;
|
||||
|
||||
# define XO_ENTER0(lvl) xo::scope_setup(xo::log_level::lvl, xo::log_config::style, __PRETTY_FUNCTION__, __FILE__, __LINE__)
|
||||
# define XO_ENTER1(lvl, debug_flag) XO_ENTER2(lvl, debug_flag, __PRETTY_FUNCTION__)
|
||||
# define XO_ENTER2(lvl, debug_flag, name1) xo::scope_setup((debug_flag ? xo::log_level::lvl : xo::log_level::never), xo::log_config::style, name1, __FILE__, __LINE__)
|
||||
|
||||
# define XO_DEBUG(debug_flag) XO_ENTER1(always, debug_flag)
|
||||
# define XO_DEBUG2(debug_flag, name1) XO_ENTER2(always, debug_flag, name1)
|
||||
|
||||
# define XO_LITERAL(lvl, name1, name2) xo::scope_setup(lvl, function_style::literal, name1, name2, __FILE__, __LINE__)
|
||||
|
||||
//# define XO_SSETUP0() xo::scope_setup(__FUNCTION__)
|
||||
//# define XO_SSETUP0(lvl) xo::scope_setup(xo::log_level::lvl, xo::log_config::style, __PRETTY_FUNCTION__, __FILE__, __LINE__)
|
||||
|
||||
/* throw exception if condition not met*/
|
||||
# define XO_EXPECT(f,msg) if(!(f)) { throw std::runtime_error(msg); }
|
||||
/* establish scope using current function name */
|
||||
# define XO_SCOPE(name, lvl) xo::scope name(xo::scope_setup(xo::log_level::lvl, xo::log_config::style, __PRETTY_FUNCTION__, __FILE__, __LINE__))
|
||||
/* like XO_SCOPE(name), but also set enabled flag */
|
||||
//# define XO_SCOPE2(name, debug_flag) xo::scope name(xo::scope_setup(xo::log_config::style, __PRETTY_FUNCTION__, __FILE__, __LINE__, debug_flag))
|
||||
# define XO_SCOPE_DISABLED(name) xo::scope name(xo::scope_setup(xo::log_level::never, xo::log_config::style, __PRETTY_FUNCTION__, __FILE__, __LINE__))
|
||||
# define XO_STUB() { XO_SCOPE(logr); logr.log("STUB"); }
|
||||
|
||||
/* convenience class for basic_scope<..> construction (see below).
|
||||
* use to disambiguate setup from other arguments
|
||||
*/
|
||||
struct scope_setup {
|
||||
scope_setup(log_level level, function_style style, std::string_view name1, std::string_view name2,
|
||||
std::string_view file, std::uint32_t line)
|
||||
: log_level_{level}, style_{style}, name1_{name1}, name2_{name2}, file_{file}, line_{line} {}
|
||||
scope_setup(log_level level, function_style style,
|
||||
std::string_view name1, std::string_view file, std::uint32_t line)
|
||||
: scope_setup(level, style, name1, "" /*name2*/, file, line) {}
|
||||
|
||||
bool is_enabled() const { return (this->log_level_ >= log_config::min_log_level); }
|
||||
|
||||
//static scope_setup literal(std::string_view name1, bool enabled_flag = true) { return scope_setup(FS_Literal, name1, enabled_flag); }
|
||||
//static scope_setup literal(std::string_view name1, std::string_view name2, bool enabled_flag = true) { return scope_setup(FS_Literal, name1, name2, enabled_flag); }
|
||||
|
||||
/* threshold level for logging -- write messages with severity >= this level */
|
||||
log_level log_level_ = log_level::error;
|
||||
/* FS_Pretty | FS_Streamlined | FS_Simple */
|
||||
function_style style_ = function_style::pretty;
|
||||
std::string_view name1_ = "<.name1>";
|
||||
std::string_view name2_ = "<.name2>";
|
||||
/* __FILE__ */
|
||||
std::string_view file_ = "<.file>";
|
||||
/* __LINE__ */
|
||||
std::uint32_t line_ = 0;
|
||||
}; /*scope_setup*/
|
||||
|
||||
/* nesting logger
|
||||
*
|
||||
* Use:
|
||||
* using xo::scope;
|
||||
*
|
||||
* void myfunc() {
|
||||
* XO_SCOPE(log); //or scope x("myfunc")
|
||||
* log(a,b,c);
|
||||
* anotherfunc();
|
||||
* log(d,e,f);
|
||||
* }
|
||||
*
|
||||
* void anotherfunc() {
|
||||
* XO_SCOPE(x); // or scope x("anotherfunc")
|
||||
* x.log(y);
|
||||
* }
|
||||
*
|
||||
* or:
|
||||
* void myfunc() {
|
||||
* bool log_flag = true;
|
||||
* XO_SCOPE2(log, log_flag); // create local variable 'log'
|
||||
* log && log(a,b,c); // log iff enabled
|
||||
* log.end_scope(); // optional protection against compiler destroying 'log' early
|
||||
* }
|
||||
*
|
||||
* output like:
|
||||
* +myfunc:
|
||||
* a,b,c
|
||||
* +anotherfunc:
|
||||
* y
|
||||
* -anotherfunc:
|
||||
* d,e,f
|
||||
* -myfunc:
|
||||
*/
|
||||
template <typename CharT, typename Traits = std::char_traits<CharT>>
|
||||
class basic_scope {
|
||||
public:
|
||||
using state_impl_type = state_impl<CharT, Traits>;
|
||||
|
||||
public:
|
||||
//basic_scope(std::string_view name1, bool enabled_flag);
|
||||
template <typename... Tn>
|
||||
basic_scope(scope_setup setup, Tn&&... rest);
|
||||
~basic_scope();
|
||||
|
||||
bool enabled() const { return !finalized_; }
|
||||
|
||||
operator bool() const { return this->enabled(); }
|
||||
|
||||
/* report current nesting level */
|
||||
std::uint32_t nesting_level() const;
|
||||
|
||||
void set_dest_sbuf(std::streambuf * x) { this->dest_sbuf_ = x; }
|
||||
|
||||
template<typename... Tn>
|
||||
bool log(Tn&&... rest) {
|
||||
if(this->finalized_) {
|
||||
throw std::runtime_error("basic_scope: attempt to use finalized scope");
|
||||
} else {
|
||||
state_impl_type * logstate = require_indent_thread_local_state();
|
||||
|
||||
/* indent for timestamp (not printed on this line) */
|
||||
logstate->time_indent();
|
||||
|
||||
/* log to per-thread stream to prevent data races */
|
||||
tosn(logstate2stream(logstate), std::forward<Tn>(rest)...);
|
||||
|
||||
this->flush2sbuf(logstate);
|
||||
}
|
||||
|
||||
return true;
|
||||
} /*log*/
|
||||
|
||||
template<typename... Tn>
|
||||
bool operator()(Tn&&... args) { return this->log(std::forward<Tn>(args)...); }
|
||||
|
||||
/* call once to end scope before dtor */
|
||||
template<typename... Tn>
|
||||
void end_scope(Tn&&... args);
|
||||
|
||||
private:
|
||||
/* establish stream for logging; use thread-local storage for threadsafetỵ
|
||||
* stream, if recycled (ịẹ after 1st call to scopẹlog() from a particular thread),
|
||||
* will be in 'reset-to-beginning of buffer' statẹ
|
||||
*/
|
||||
static state_impl_type * require_indent_thread_local_state();
|
||||
|
||||
/* establish logging state; use thread-local storage for threadsafety */
|
||||
static state_impl_type * require_thread_local_state();
|
||||
|
||||
/* retrieve permanently-associated ostream for logging-state */
|
||||
static std::ostream & logstate2stream(state_impl_type * logstate);
|
||||
|
||||
/* write collected output to std::clog, or chosen streambuf */
|
||||
void flush2sbuf(state_impl_type * logstate);
|
||||
|
||||
private:
|
||||
/* keep logging state separately for each thread */
|
||||
static thread_local std::unique_ptr<state_impl_type> s_threadlocal_state;
|
||||
|
||||
/* send indented output to this streambuf (e.g. std::clog.rdbuf()) */
|
||||
std::streambuf * dest_sbuf_ = std::clog.rdbuf();
|
||||
/* style for displaying .name1 */
|
||||
function_style style_ = function_style::pretty;
|
||||
/* name of this scope (part 1) */
|
||||
std::string_view name1_ = "<name1>";
|
||||
/* name of this scope (part 2) */
|
||||
std::string_view name2_ = "::<name2>";
|
||||
/* captured value of __FILE__ */
|
||||
std::string_view file_ = "<file>";
|
||||
/* captured value of __LINE__ */
|
||||
std::uint32_t line_ = 0;
|
||||
/* set once per scope .finalized=true <-> logging disabled */
|
||||
bool finalized_ = false;
|
||||
}; /*basic_scope*/
|
||||
|
||||
template <typename CharT, typename Traits>
|
||||
template <typename... Tn>
|
||||
basic_scope<CharT, Traits>::basic_scope(scope_setup setup, Tn&&... args)
|
||||
|
||||
: style_{setup.style_},
|
||||
name1_{std::move(setup.name1_)},
|
||||
name2_{std::move(setup.name2_)},
|
||||
file_{std::move(setup.file_)},
|
||||
line_{setup.line_},
|
||||
finalized_{!(setup.is_enabled())}
|
||||
{
|
||||
if(setup.is_enabled()) {
|
||||
state_impl_type * logstate = basic_scope::require_thread_local_state();
|
||||
std::ostream & os = logstate2stream(logstate);
|
||||
|
||||
logstate->preamble(this->style_, this->name1_, this->name2_);
|
||||
|
||||
tosn(os, " ", std::forward<Tn>(args)...);
|
||||
|
||||
if (log_config::location_enabled) {
|
||||
/* prints on next call to flush2sbuf */
|
||||
logstate->set_location(this->file_, this->line_);
|
||||
//tosn(os, " [", basename(this->file_), ":", this->line_, "]");
|
||||
}
|
||||
|
||||
logstate->flush2sbuf(std::clog.rdbuf());
|
||||
|
||||
///* next call to scope::log() can reset to beginning of buffer space */
|
||||
//logstate->ss().seekp(0);
|
||||
|
||||
logstate->incr_nesting();
|
||||
}
|
||||
} /*ctor*/
|
||||
|
||||
template <typename CharT, typename Traits>
|
||||
basic_scope<CharT, Traits>::~basic_scope() {
|
||||
if(!this->finalized_)
|
||||
this->end_scope();
|
||||
} /*dtor*/
|
||||
|
||||
template <typename CharT, typename Traits>
|
||||
thread_local std::unique_ptr<state_impl<CharT, Traits>>
|
||||
basic_scope<CharT, Traits>::s_threadlocal_state;
|
||||
|
||||
template <typename CharT, typename Traits>
|
||||
std::uint32_t
|
||||
basic_scope<CharT, Traits>::nesting_level() const {
|
||||
return require_thread_local_state()->nesting_level();
|
||||
} /*nesting_level*/
|
||||
|
||||
template <typename CharT, typename Traits>
|
||||
typename basic_scope<CharT, Traits>::state_impl_type *
|
||||
basic_scope<CharT, Traits>::require_indent_thread_local_state()
|
||||
{
|
||||
state_impl_type * local_state = require_thread_local_state();
|
||||
|
||||
local_state->reset_stream();
|
||||
local_state->indent(' ' /*pad_char*/);
|
||||
|
||||
return local_state;
|
||||
} /*require_thread_local_stream*/
|
||||
|
||||
template <typename CharT, typename Traits>
|
||||
typename basic_scope<CharT, Traits>::state_impl_type *
|
||||
basic_scope<CharT, Traits>::require_thread_local_state()
|
||||
{
|
||||
if(!s_threadlocal_state) {
|
||||
s_threadlocal_state.reset(new state_impl_type());
|
||||
}
|
||||
|
||||
return s_threadlocal_state.get();
|
||||
} /*require_thread_local_state*/
|
||||
|
||||
template <typename CharT, typename Traits>
|
||||
std::ostream &
|
||||
basic_scope<CharT, Traits>::logstate2stream(state_impl_type * logstate)
|
||||
{
|
||||
return logstate->ss();
|
||||
} /*logstate2stream*/
|
||||
|
||||
template <typename CharT, typename Traits>
|
||||
void
|
||||
basic_scope<CharT, Traits>::flush2sbuf(state_impl_type * logstate)
|
||||
{
|
||||
logstate->flush2sbuf(this->dest_sbuf_);
|
||||
} /*flush2sbuf*/
|
||||
|
||||
template <typename CharT, typename Traits>
|
||||
template <typename... Tn>
|
||||
void
|
||||
basic_scope<CharT, Traits>::end_scope(Tn&&... args)
|
||||
{
|
||||
if(!this->finalized_) {
|
||||
this->finalized_ = true;
|
||||
|
||||
state_impl_type * logstate
|
||||
= basic_scope::require_thread_local_state();
|
||||
|
||||
logstate->decr_nesting();
|
||||
|
||||
logstate->postamble(this->style_, this->name1_, this->name2_);
|
||||
|
||||
tosn(logstate2stream(logstate), " ", std::forward<Tn>(args)...);
|
||||
|
||||
logstate->flush2sbuf(std::clog.rdbuf());
|
||||
}
|
||||
} /*end_scope*/
|
||||
|
||||
|
||||
using scope = basic_scope<char>;
|
||||
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end scope.hpp */
|
||||
297
xo-indentlog/include/xo/indentlog/timeutil/timeutil.hpp
Normal file
297
xo-indentlog/include/xo/indentlog/timeutil/timeutil.hpp
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
/* @file time.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#ifdef NOT_YET
|
||||
# include <format>
|
||||
#endif
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <time.h>
|
||||
|
||||
namespace xo {
|
||||
namespace time {
|
||||
using nanos = std::chrono::nanoseconds;
|
||||
using microseconds = std::chrono::microseconds;
|
||||
using milliseconds = std::chrono::milliseconds;
|
||||
using seconds = std::chrono::seconds;
|
||||
using hours = std::chrono::hours;
|
||||
using days = std::chrono::days;
|
||||
|
||||
using utc_nanos = std::chrono::time_point<std::chrono::system_clock,
|
||||
std::chrono::nanoseconds>;
|
||||
using utc_micros = std::chrono::time_point<std::chrono::system_clock,
|
||||
std::chrono::microseconds>;
|
||||
|
||||
|
||||
struct timeutil {
|
||||
static utc_nanos now() {
|
||||
return utc_nanos(std::chrono::system_clock::now());
|
||||
}
|
||||
|
||||
static utc_nanos epoch() {
|
||||
return utc_nanos(std::chrono::system_clock::from_time_t(0));
|
||||
} /*epoch*/
|
||||
|
||||
static utc_nanos ymd_hms(uint32_t ymd, uint32_t hms) {
|
||||
/* e.g. ymd=20220610 -> n_yr=2022, n_mon=06, n_dy=10 */
|
||||
|
||||
uint32_t n_yr = ymd / 10000;
|
||||
uint32_t n_mon = (ymd % 10000) / 100;
|
||||
uint32_t n_dy = ymd % 100;
|
||||
|
||||
uint32_t n_hr = hms / 10000;
|
||||
uint32_t n_min = (hms % 10000) / 100;
|
||||
uint32_t n_sec = hms % 100;
|
||||
|
||||
struct tm t;
|
||||
|
||||
t.tm_year = n_yr - 1900; /* 0 means 1900 */
|
||||
t.tm_mon = n_mon - 1; /* 0 means january */
|
||||
t.tm_mday = n_dy;
|
||||
|
||||
t.tm_hour = n_hr; /* 24 hour clock */
|
||||
t.tm_min = n_min;
|
||||
t.tm_sec = n_sec;
|
||||
|
||||
/* time since epoch */
|
||||
time_t epoch_time = timegm(&t);
|
||||
|
||||
return std::chrono::system_clock::from_time_t(epoch_time);
|
||||
} /*ymd_hms*/
|
||||
|
||||
/* midnight UTC on date ymd.
|
||||
* e.g. ymd_midnight(20220707) -> midnight UTC on 7jul22
|
||||
*/
|
||||
static utc_nanos ymd_midnight(uint32_t ymd) {
|
||||
return ymd_hms(ymd, 0);
|
||||
} /*ymd_midnight*/
|
||||
|
||||
static utc_nanos ymd_hms_usec(uint32_t ymd, uint32_t hms, uint32_t usec) {
|
||||
utc_nanos s = ymd_hms(ymd, hms);
|
||||
|
||||
return s + microseconds(usec);
|
||||
} /*ymd_hms_usec*/
|
||||
|
||||
/* .first: UTC midnight on same calendar day as t0
|
||||
* .second: elapsed time from .first to t0 (i.e. UTC time-of-day for t0)
|
||||
*/
|
||||
static std::pair<utc_nanos, nanos> utc_split_vs_midnight(utc_nanos t0) {
|
||||
//using xo::timeutil::microseconds;
|
||||
//using xo::timeutil::utc_nanos;
|
||||
|
||||
/* use yyyymmdd.hh:mm:ss.nnnnnn */
|
||||
|
||||
time_t t0_time_t = (std::chrono::system_clock::to_time_t
|
||||
(std::chrono::time_point_cast<xo::time::microseconds>(t0)));
|
||||
|
||||
/* convert to std::tm,
|
||||
* only provides 1-second precision
|
||||
*/
|
||||
std::tm t0_tm;
|
||||
::gmtime_r(&t0_time_t, &t0_tm);
|
||||
|
||||
/* midnight on the same calendar day as t0_tm */
|
||||
std::tm midnight_tm = t0_tm;
|
||||
{
|
||||
midnight_tm.tm_hour = 0;
|
||||
midnight_tm.tm_min = 0;
|
||||
midnight_tm.tm_sec = 0;
|
||||
}
|
||||
|
||||
/* convert to UTC epoch seconds */
|
||||
time_t midnight_time_t = ::timegm(&midnight_tm);
|
||||
|
||||
utc_nanos t0_midnight =
|
||||
(std::chrono::time_point_cast<xo::time::microseconds>(
|
||||
std::chrono::system_clock::from_time_t(midnight_time_t)));
|
||||
|
||||
nanos t0_tdy = t0 - t0_midnight;
|
||||
|
||||
return std::pair<utc_nanos, nanos>(t0_midnight, t0_tdy);
|
||||
} /*utc_split_vs_midnight*/
|
||||
|
||||
/* .first: LOCAL midnight on same calendar day as t0 (but in UTC coords)
|
||||
* .second: elapsed time from .first to t0 (i.e. LOCAL time-of-day for t0)
|
||||
*/
|
||||
static std::pair<utc_nanos, nanos> local_split_vs_midnight(utc_nanos t0) {
|
||||
using xo::time::microseconds;
|
||||
using xo::time::utc_nanos;
|
||||
|
||||
/* use yyyymmdd.hh:mm:ss.nnnnnn */
|
||||
|
||||
time_t t0_time_t = (std::chrono::system_clock::to_time_t
|
||||
(std::chrono::time_point_cast<xo::time::microseconds>(t0)));
|
||||
|
||||
/* convert to std::tm,
|
||||
* only provides 1-second precision
|
||||
*/
|
||||
std::tm t0_tm;
|
||||
::localtime_r(&t0_time_t, &t0_tm);
|
||||
|
||||
/* midnight on the same calendar day as t0_tm */
|
||||
std::tm midnight_tm = t0_tm;
|
||||
{
|
||||
midnight_tm.tm_hour = 0;
|
||||
midnight_tm.tm_min = 0;
|
||||
midnight_tm.tm_sec = 0;
|
||||
}
|
||||
|
||||
/* convert local midnight to UTC epoch seconds */
|
||||
time_t midnight_time_t = ::timelocal(&midnight_tm);
|
||||
|
||||
utc_nanos t0_midnight =
|
||||
(std::chrono::time_point_cast<xo::time::microseconds>(
|
||||
std::chrono::system_clock::from_time_t(midnight_time_t)));
|
||||
|
||||
nanos t0_tdy = t0 - t0_midnight;
|
||||
|
||||
return std::pair<utc_nanos, nanos>(t0_midnight, t0_tdy);
|
||||
} /*local_split_vs_midnight*/
|
||||
|
||||
/* split utc_nanos into
|
||||
* std::tm
|
||||
* .tm_year
|
||||
* .tm_mon (1-12)
|
||||
* .tm_mday (1-31)
|
||||
* .tm_hour (0-23)
|
||||
* .tm_min (0-59)
|
||||
* .tm_sec (0-59)
|
||||
* .tm_wday (0=sunday .. 6=saturday)
|
||||
* .tm_yday (0=1jan .. 365)
|
||||
* .tm_isdst (daylight savings time flag)
|
||||
* usec (0-999999)
|
||||
*/
|
||||
static std::pair<std::tm, uint32_t> utc_split_tm(utc_nanos t0) {
|
||||
using xo::time::microseconds;
|
||||
using xo::time::utc_nanos;
|
||||
|
||||
/* use yyyymmdd.hh:mm:ss.nnnnnn */
|
||||
|
||||
time_t t0_time_t
|
||||
= (std::chrono::system_clock::to_time_t
|
||||
(std::chrono::time_point_cast<microseconds>(t0)));
|
||||
//time_t t0_time_t = (std::chrono::system_clock::to_time_t(std::chrono::time_point_cast<xo::time::microseconds>(t0)));
|
||||
|
||||
/* convert to std::tm, in UTC coords,
|
||||
* only provides 1-second precision
|
||||
*/
|
||||
std::tm t0_tm;
|
||||
::gmtime_r(&t0_time_t, &t0_tm);
|
||||
|
||||
/* midnight on the same calendar day as t0_tm */
|
||||
std::tm midnight_tm = t0_tm;
|
||||
|
||||
midnight_tm.tm_isdst = 0;
|
||||
midnight_tm.tm_hour = 0;
|
||||
midnight_tm.tm_min = 0;
|
||||
midnight_tm.tm_sec = 0;
|
||||
|
||||
/* convert back to epoch seconds */
|
||||
time_t midnight_time_t = ::timegm(&midnight_tm);
|
||||
|
||||
utc_nanos t0_midnight =
|
||||
(std::chrono::time_point_cast<xo::time::microseconds>(
|
||||
std::chrono::system_clock::from_time_t(midnight_time_t)));
|
||||
|
||||
uint32_t usec =
|
||||
(std::chrono::duration_cast<microseconds>(
|
||||
std::chrono::hh_mm_ss(t0 - t0_midnight).subseconds()))
|
||||
.count();
|
||||
|
||||
return std::make_pair(t0_tm, usec);
|
||||
} /*utc_split_tm*/
|
||||
|
||||
static void print_hms_msec(nanos dt, std::ostream & os) {
|
||||
/* use hhmmss.nnn */
|
||||
using std::int32_t;
|
||||
|
||||
auto hms = std::chrono::hh_mm_ss(dt);
|
||||
int32_t h = hms.hours().count();
|
||||
int32_t m = hms.minutes().count();
|
||||
int32_t s = hms.seconds().count();
|
||||
int32_t msec = std::chrono::duration_cast<milliseconds>(hms.subseconds()).count();
|
||||
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "%02d:%02d:%02d.%03d", h, m, s, msec);
|
||||
|
||||
os << buf;
|
||||
} /*print_hms_msec*/
|
||||
|
||||
static void print_utc_hms_msec(utc_nanos t0, std::ostream & os) {
|
||||
print_hms_msec(utc_split_vs_midnight(t0).second, os);
|
||||
} /*print_utc_hms_usec*/
|
||||
|
||||
static void print_hms_usec(nanos dt, std::ostream & os) {
|
||||
/* use hhmmss.uuuuuu */
|
||||
using std::int32_t;
|
||||
|
||||
auto hms = std::chrono::hh_mm_ss(dt);
|
||||
int32_t h = hms.hours().count();
|
||||
int32_t m = hms.minutes().count();
|
||||
int32_t s = hms.seconds().count();
|
||||
int32_t usec = std::chrono::duration_cast<microseconds>(hms.subseconds()).count();
|
||||
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "%02d:%02d:%02d.%06d", h, m, s, usec);
|
||||
|
||||
os << buf;
|
||||
} /*print_hms_usec*/
|
||||
|
||||
/* print t0 like:
|
||||
* yyyymmdd:hh:mm:ss.uuuuuu
|
||||
* e.g.
|
||||
* 19700101:00:00:00.000000 // epoch
|
||||
* 20230921:16:29:35.123456 // 21sep2023 4:29:35 pm + 12345 us
|
||||
*/
|
||||
static void print_utc_ymd_hms_usec(utc_nanos t0, std::ostream & os) {
|
||||
using xo::time::microseconds;
|
||||
using xo::time::utc_nanos;
|
||||
|
||||
/* use yyyymmdd.hh:mm:ss.nnnnnn */
|
||||
|
||||
//std::tm t0_tm;
|
||||
//uint32_t t0_usec;
|
||||
|
||||
/* (structured binding ftw!) */
|
||||
auto [t0_tm, t0_usec] = utc_split_tm(t0);
|
||||
|
||||
/* no std::format in clang11 afaict */
|
||||
char usec_buf[15];
|
||||
snprintf(usec_buf, sizeof(usec_buf), "%06d", t0_usec);
|
||||
|
||||
|
||||
/* control string | example
|
||||
* ----------------------------+--------------------------
|
||||
* %c - locale-specific string | Fri Jun 10 16:29:05 2022
|
||||
* %Y - year | 2022
|
||||
* %m - month | 06
|
||||
* %d - day of month | 10
|
||||
* %H - hour | 16
|
||||
* %M - minute | 29
|
||||
* %S - second | 05
|
||||
* %Z - timezone | UTC
|
||||
*/
|
||||
os << std::put_time(&t0_tm, "%Y%m%d:%H:%M:%S.") << usec_buf;
|
||||
} /*print_utc_ymd_hms_usec*/
|
||||
|
||||
/* print datetime in format compatible with ISO 8601.
|
||||
* copying the format javascript uses, e.g:
|
||||
* 2012-04-23T18:25:43.511Z
|
||||
*/
|
||||
static void print_iso8601(utc_nanos t0, std::ostream & os) {
|
||||
auto [t0_tm, t0_usec] = utc_split_tm(t0);
|
||||
|
||||
char msec_buf[8];
|
||||
snprintf(msec_buf, sizeof(msec_buf), "%03d", t0_usec / 1000);
|
||||
|
||||
os << std::put_time(&t0_tm, "%Y-%m-%dT%H:%M:%S.") << msec_buf << "Z";
|
||||
} /*print_iso8601*/
|
||||
}; /*timeutil*/
|
||||
|
||||
} /*namespace time*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end timeutil.hpp */
|
||||
17
xo-indentlog/utest/CMakeLists.txt
Normal file
17
xo-indentlog/utest/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# indentlog unit test
|
||||
|
||||
set(SELF_EXECUTABLE_NAME utest.indentlog)
|
||||
set(SELF_SOURCE_FILES
|
||||
fixed.test.cpp quoted.test.cpp vector.test.cpp array.test.cpp timeutil.test.cpp tag.test.cpp
|
||||
filename.test.cpp code_location.test.cpp function.test.cpp
|
||||
indentlog_utest_main.cpp)
|
||||
|
||||
xo_add_utest_executable(${SELF_EXECUTABLE_NAME} ${SELF_SOURCE_FILES})
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# 3rd party dependency: catch2
|
||||
|
||||
xo_self_dependency(${SELF_EXECUTABLE_NAME} indentlog)
|
||||
xo_external_target_dependency(${SELF_EXECUTABLE_NAME} Catch2 Catch2::Catch2)
|
||||
|
||||
# end CMakeLists.txt
|
||||
40
xo-indentlog/utest/array.test.cpp
Normal file
40
xo-indentlog/utest/array.test.cpp
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/* @file array.test.cpp */
|
||||
|
||||
#include "xo/indentlog/print/array.hpp" /* overload operator<< for std::array */
|
||||
#include "xo/indentlog/print/tag.hpp"
|
||||
#include <catch2/catch.hpp>
|
||||
#include <sstream>
|
||||
|
||||
using namespace xo;
|
||||
|
||||
namespace ut {
|
||||
TEST_CASE("array", "[array]") {
|
||||
tag_config::tag_color = color_spec_type::none();
|
||||
|
||||
{
|
||||
std::array<int, 0> x = {};
|
||||
std::stringstream ss;
|
||||
ss << x;
|
||||
|
||||
REQUIRE(ss.str() == "[]");
|
||||
}
|
||||
|
||||
{
|
||||
std::array<int, 1> x = {1};
|
||||
std::stringstream ss;
|
||||
ss << x;
|
||||
|
||||
REQUIRE(ss.str() == "[1]");
|
||||
}
|
||||
|
||||
{
|
||||
std::array<int, 2> x = {1, 2};
|
||||
std::stringstream ss;
|
||||
ss << x;
|
||||
|
||||
REQUIRE(ss.str() == "[1 2]");
|
||||
}
|
||||
}
|
||||
} /*namespace ut*/
|
||||
|
||||
/* end array.test.cpp */
|
||||
47
xo-indentlog/utest/code_location.test.cpp
Normal file
47
xo-indentlog/utest/code_location.test.cpp
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
/* @file code_location.test.cpp */
|
||||
|
||||
#include "xo/indentlog/print/code_location.hpp"
|
||||
#include "xo/indentlog/print/color.hpp"
|
||||
#include "xo/indentlog/print/tag.hpp"
|
||||
#include <vector>
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
using namespace xo;
|
||||
|
||||
namespace ut {
|
||||
struct code_location_tcase {
|
||||
code_location_tcase() = default;
|
||||
code_location_tcase(std::string_view file, std::uint32_t line, color_spec_type color, std::string_view output)
|
||||
: file_{file}, line_{line}, color_{color}, output_{output} {}
|
||||
|
||||
/* target time value to test */
|
||||
std::string_view file_;
|
||||
std::uint32_t line_;
|
||||
color_spec_type color_;
|
||||
std::string_view output_;
|
||||
}; /*code_location_tcase*/
|
||||
|
||||
std::vector<code_location_tcase> s_code_location_tcase_v(
|
||||
{
|
||||
code_location_tcase("/foo/bar", 123, color_spec_type::none(), "[bar:123]"),
|
||||
code_location_tcase("/foo/bar", 123, color_spec_type::blue(), "[\033[31;34mbar\033[0m:123]"),
|
||||
code_location_tcase("/foo/bar", 123, color_spec_type::xterm(196), "[\033[38;5;196mbar\033[0m:123]"),
|
||||
code_location_tcase("/foo/bar", 123, color_spec_type::rgb(255, 127, 63), "[\033[38;2;255;127;63mbar\033[0m:123]"),
|
||||
});
|
||||
|
||||
TEST_CASE("code_location", "[code_location]") {
|
||||
for (std::uint32_t i_tc = 0, z_tc = s_code_location_tcase_v.size(); i_tc < z_tc; ++i_tc) {
|
||||
code_location_tcase const & tc = s_code_location_tcase_v[i_tc];
|
||||
|
||||
INFO(tostr(xtag("i_tc", i_tc), xtag("file", tc.file_), xtag("line", tc.line_), xtag("color", tc.color_)));
|
||||
INFO(xtag("tc.output", tc.output_));
|
||||
|
||||
std::stringstream ss;
|
||||
ss << code_location(tc.file_, tc.line_, tc.color_);
|
||||
|
||||
REQUIRE(ss.str() == tc.output_);
|
||||
}
|
||||
} /*TEST_CASE(code_location)*/
|
||||
} /*namespace ut*/
|
||||
|
||||
/* end code_location.test.cpp */
|
||||
43
xo-indentlog/utest/filename.test.cpp
Normal file
43
xo-indentlog/utest/filename.test.cpp
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
/* @file filename.test.cpp */
|
||||
|
||||
#include "xo/indentlog/print/filename.hpp"
|
||||
#include "xo/indentlog/print/tag.hpp"
|
||||
#include <vector>
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
using namespace xo;
|
||||
|
||||
namespace ut {
|
||||
struct filename_tcase {
|
||||
filename_tcase() = default;
|
||||
filename_tcase(std::string_view path, std::string_view basename)
|
||||
: path_{path}, basename_{basename} {}
|
||||
|
||||
/* target time value to test */
|
||||
std::string_view path_;
|
||||
std::string_view basename_;
|
||||
}; /*filename_tcase*/
|
||||
|
||||
std::vector<filename_tcase> s_filename_tcase_v(
|
||||
{
|
||||
filename_tcase("foo", "foo"),
|
||||
filename_tcase("/foo", "foo"),
|
||||
filename_tcase("/foo/bar", "bar"),
|
||||
});
|
||||
|
||||
TEST_CASE("filename", "[filename]") {
|
||||
for (std::uint32_t i_tc = 0, z_tc = s_filename_tcase_v.size(); i_tc < z_tc; ++i_tc) {
|
||||
filename_tcase const & tc = s_filename_tcase_v[i_tc];
|
||||
|
||||
INFO(tostr(xtag("i_tc", i_tc), xtag("path", tc.path_)));
|
||||
INFO(xtag("tc.basename", tc.basename_));
|
||||
|
||||
std::stringstream ss;
|
||||
ss << basename(tc.path_);
|
||||
|
||||
REQUIRE(ss.str() == tc.basename_);
|
||||
}
|
||||
} /*TEST_CASE(filename)*/
|
||||
} /*namespace ut*/
|
||||
|
||||
/* end filename.test.cpp */
|
||||
88
xo-indentlog/utest/fixed.test.cpp
Normal file
88
xo-indentlog/utest/fixed.test.cpp
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
/* @file fixed.test.cpp */
|
||||
|
||||
#include "xo/indentlog/print/fixed.hpp"
|
||||
#include "xo/indentlog/print/tag.hpp"
|
||||
#include <catch2/catch.hpp>
|
||||
#include <sstream>
|
||||
|
||||
using namespace xo;
|
||||
|
||||
namespace ut {
|
||||
struct fixed_tcase {
|
||||
fixed_tcase() = default;
|
||||
fixed_tcase(double x, std::uint32_t prec, std::string s)
|
||||
: x_{x}, prec_{prec}, s_{std::move(s)} {}
|
||||
|
||||
/* floating-point value to format */
|
||||
double x_ = 0.0;
|
||||
/* precision */
|
||||
std::uint32_t prec_ = 0;
|
||||
/* expected result */
|
||||
std::string s_;
|
||||
}; /*fixed_tcase*/
|
||||
|
||||
std::vector<fixed_tcase> s_fixed_tcase_v(
|
||||
{fixed_tcase(0.0, 0, "0"),
|
||||
fixed_tcase(0.0, 1, "0.0"),
|
||||
fixed_tcase(0.0, 2, "0.00"),
|
||||
|
||||
//fixed_tcase(0.5, 0, "1"), // failing --> 0
|
||||
fixed_tcase(0.5, 1, "0.5"),
|
||||
|
||||
fixed_tcase(0.049, 0, "0"),
|
||||
fixed_tcase(0.049, 1, "0.0"),
|
||||
fixed_tcase(0.049, 2, "0.05"),
|
||||
|
||||
fixed_tcase(0.05, 0, "0"),
|
||||
fixed_tcase(0.05, 1, "0.1"),
|
||||
fixed_tcase(0.05, 2, "0.05"),
|
||||
|
||||
fixed_tcase(-0.05, 0, "-0"),
|
||||
fixed_tcase(-0.05, 1, "-0.1"),
|
||||
fixed_tcase(-0.05, 2, "-0.05"),
|
||||
|
||||
fixed_tcase(1e-6, 0, "0"),
|
||||
fixed_tcase(1e-6, 1, "0.0"),
|
||||
fixed_tcase(1e-6, 2, "0.00"),
|
||||
fixed_tcase(1e-6, 3, "0.000"),
|
||||
fixed_tcase(1e-6, 4, "0.0000"),
|
||||
fixed_tcase(1e-6, 5, "0.00000"),
|
||||
fixed_tcase(1e-6, 6, "0.000001"),
|
||||
|
||||
fixed_tcase(-1e-6, 0, "-0"),
|
||||
fixed_tcase(-1e-6, 1, "-0.0"),
|
||||
fixed_tcase(-1e-6, 2, "-0.00"),
|
||||
fixed_tcase(-1e-6, 3, "-0.000"),
|
||||
fixed_tcase(-1e-6, 4, "-0.0000"),
|
||||
fixed_tcase(-1e-6, 5, "-0.00000"),
|
||||
fixed_tcase(-1e-6, 6, "-0.000001"),
|
||||
|
||||
fixed_tcase(666.66, 1, "666.7"),
|
||||
fixed_tcase(666.66, 2, "666.66"),
|
||||
|
||||
fixed_tcase(-666.66, 1, "-666.7"),
|
||||
fixed_tcase(-666.66, 2, "-666.66"),
|
||||
|
||||
});
|
||||
|
||||
TEST_CASE("fixed", "[fixed]") {
|
||||
tag_config::tag_color = color_spec_type::none();
|
||||
|
||||
for (std::uint32_t i_tc = 0, z_tc = s_fixed_tcase_v.size(); i_tc < z_tc; ++i_tc) {
|
||||
fixed_tcase const & tc = s_fixed_tcase_v[i_tc];
|
||||
|
||||
INFO(tostr(xtag("i_tc", i_tc), xtag("x", tc.x_), xtag("prec", tc.prec_)));
|
||||
|
||||
std::stringstream ss;
|
||||
ss << fixed(tc.x_, tc.prec_);
|
||||
|
||||
INFO(xtag("ss.str", ss.str()));
|
||||
|
||||
REQUIRE(ss.str() == tc.s_);
|
||||
}
|
||||
|
||||
REQUIRE(s_fixed_tcase_v.size() > 1);
|
||||
}
|
||||
} /*namespace ut*/
|
||||
|
||||
/* end fixed.test.cpp */
|
||||
64
xo-indentlog/utest/function.test.cpp
Normal file
64
xo-indentlog/utest/function.test.cpp
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
/* @file function.test.cpp */
|
||||
|
||||
#include "xo/indentlog/print/function.hpp"
|
||||
#include "xo/indentlog/print/tag.hpp"
|
||||
#include <catch2/catch.hpp>
|
||||
#include <sstream>
|
||||
|
||||
using namespace xo;
|
||||
|
||||
namespace ut {
|
||||
struct function_tcase {
|
||||
function_tcase() = default;
|
||||
function_tcase(function_style style, color_spec_type spec, std::string_view pretty, std::string_view output)
|
||||
: style_{style}, spec_{spec}, pretty_{pretty}, output_{output} {}
|
||||
|
||||
/* function style: literal|pretty|streamlined|simple*/
|
||||
function_style style_;
|
||||
/* color spec for output */
|
||||
color_spec_type spec_;
|
||||
/* function signature (as per __PRETTY_FUNCTION__) */
|
||||
std::string_view pretty_;
|
||||
/* output text */
|
||||
std::string_view output_;
|
||||
}; /*function_tcase*/
|
||||
|
||||
std::vector<function_tcase> s_function_tcase_v(
|
||||
{
|
||||
function_tcase(function_style::literal, color_spec_type::none(), "anything goes here", "anything goes here"),
|
||||
|
||||
function_tcase(function_style::pretty, color_spec_type::none(), "void foo() const", "[void foo() const]"),
|
||||
function_tcase(function_style::streamlined, color_spec_type::none(), "void foo() const", "foo"),
|
||||
function_tcase(function_style::simple, color_spec_type::none(), "void foo() const", "foo"),
|
||||
|
||||
function_tcase(function_style::pretty, color_spec_type::none(), "void xo::class::foo() const", "[void xo::class::foo() const]"),
|
||||
function_tcase(function_style::streamlined, color_spec_type::none(), "void xo::class::foo() const", "class::foo"),
|
||||
function_tcase(function_style::simple, color_spec_type::none(), "void xo::class::foo() const", "foo"),
|
||||
|
||||
function_tcase(function_style::pretty, color_spec_type::blue(), "void xo::class::foo() const", "[\033[31;34mvoid xo::class::foo() const\033[0m]"),
|
||||
|
||||
function_tcase(function_style::streamlined, color_spec_type::none(), "void xo::reactor::FifoQueue<T, EvTimeFn>::notify_ev(const T&) [with T = std::pair<std::chrono::time_point<std::chrono::_V2::system_clock, std::chrono::duration<long int, std::ratio<1, 1000000000> > >, long unsigned int>; EvTimeFn = xo::reactor::EventTimeFn<std::pair<std::chrono::time_point<std::chrono::_V2::system_clock, std::chrono::duration<long int, std::ratio<1, 1000000000> > >, long unsigned int> >]", "FifoQueue::notify_ev"),
|
||||
function_tcase(function_style::streamlined, color_spec_type::none(), "token_type xo::tok::tokenizer<char>::assemble_token(const span_type &) const [CharT = char]", "tokenizer::assemble_token"),
|
||||
});
|
||||
|
||||
TEST_CASE("function", "[function]") {
|
||||
tag_config::tag_color = color_spec_type::none();
|
||||
|
||||
for (std::uint32_t i_tc = 0, z_tc = s_function_tcase_v.size(); i_tc < z_tc; ++i_tc) {
|
||||
function_tcase const & tc = s_function_tcase_v[i_tc];
|
||||
|
||||
INFO(tostr(xtag("i_tc", i_tc), xtag("style", tc.style_), xtag("spec", tc.spec_), xtag("pretty", tc.pretty_)));
|
||||
|
||||
std::stringstream ss;
|
||||
ss << function_name(tc.style_, tc.spec_, tc.pretty_);
|
||||
|
||||
INFO(xtag("ss.str", ss.str()));
|
||||
|
||||
REQUIRE(ss.str() == tc.output_);
|
||||
}
|
||||
|
||||
REQUIRE(s_function_tcase_v.size() > 1);
|
||||
}
|
||||
} /*namespace ut*/
|
||||
|
||||
/* end function.test.cpp */
|
||||
6
xo-indentlog/utest/indentlog_utest_main.cpp
Normal file
6
xo-indentlog/utest/indentlog_utest_main.cpp
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
/* @file indentlog_utest_main.cpp */
|
||||
|
||||
#define CATCH_CONFIG_MAIN
|
||||
#include "catch2/catch.hpp"
|
||||
|
||||
/* end indentlog_utest_main.cpp */
|
||||
356
xo-indentlog/utest/quoted.test.cpp
Normal file
356
xo-indentlog/utest/quoted.test.cpp
Normal file
|
|
@ -0,0 +1,356 @@
|
|||
/* @file fixed.test.cpp */
|
||||
|
||||
#include "xo/indentlog/scope.hpp"
|
||||
#include "xo/indentlog/print/quoted.hpp"
|
||||
//#include "xo/indentlog/print/tag.hpp"
|
||||
#include "xo/indentlog/print/hex.hpp"
|
||||
#include <catch2/catch.hpp>
|
||||
#include <sstream>
|
||||
|
||||
namespace ut {
|
||||
using namespace xo;
|
||||
using namespace xo::print;
|
||||
|
||||
struct quot_tcase {
|
||||
quot_tcase() = default;
|
||||
quot_tcase(std::string x, bool unq_flag, std::string s)
|
||||
: x_{std::move(x)}, unq_flag_{unq_flag}, s_{std::move(s)} {}
|
||||
|
||||
/* string to be printed-in-machine-readable-form */
|
||||
std::string x_;
|
||||
/* if true: omit surrounding " chars when unambiguous
|
||||
* (printed string does not contain spaces or escaped chars)
|
||||
* if false: always require surrounding " chars
|
||||
*/
|
||||
bool unq_flag_ = true;
|
||||
/* expected result */
|
||||
std::string s_;
|
||||
}; /*quot_tcase*/
|
||||
|
||||
/* NOTE: spelled out tests here in aftermath
|
||||
* of hard-to-diagnose regression in gcc 13.2;
|
||||
* turned out to have something to originate in confusion
|
||||
* between xo::print::quoted and std::quoted.
|
||||
*
|
||||
* Problem does not occur in gcc 12.3 and earlier,
|
||||
* perhaps some alias for std::quoted appears somewhere in global
|
||||
* namespace??
|
||||
*
|
||||
* Resolved by renaming xo::print::quoted -> xo::print::quot
|
||||
*/
|
||||
|
||||
TEST_CASE("sstream.1char", "[sstream]") {
|
||||
constexpr bool c_debug_flag = false;
|
||||
|
||||
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.sstream.1char"));
|
||||
|
||||
/* testing unexpected sstream behavior */
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
||||
log && log("empty stream");
|
||||
|
||||
ss << '\\';
|
||||
|
||||
std::string str = ss.str();
|
||||
|
||||
log && log("after: lone escaped backslash");
|
||||
log && log(hex_view(str.data(), str.data() + str.size(), true));
|
||||
|
||||
//REQUIRE(ss.view() == std::string_view("\\")); // n/avail on osx
|
||||
REQUIRE(str == std::string("\\"));
|
||||
|
||||
ss << 'n';
|
||||
|
||||
std::string str2 = ss.str();
|
||||
|
||||
log && log("after: lone 'n' char");
|
||||
log && log(hex_view(str2.data(), str2.data() + str2.size(), true));
|
||||
|
||||
// REQUIRE(ss.view() == std::string_view("\\n")); // n/avail on osx
|
||||
REQUIRE(str2 == std::string("\\n"));
|
||||
|
||||
log && log("ss.str()=[", str2, "]");
|
||||
}
|
||||
} /*TEST_CASE(sstream.1char)*/
|
||||
|
||||
TEST_CASE("sstream.2bslash", "[sstream]") {
|
||||
constexpr bool c_debug_flag = false;
|
||||
|
||||
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.sstream.2bslash"));
|
||||
|
||||
/* testing unexpected sstream behavior */
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
||||
log && log("empty stream");
|
||||
|
||||
ss << "\\\\";
|
||||
|
||||
std::string str = ss.str();
|
||||
|
||||
log && log("after: 2x escaped backslash");
|
||||
log && log(hex_view(str.data(), str.data() + str.size(), true));
|
||||
|
||||
REQUIRE(str == std::string("\\\\"));
|
||||
|
||||
log && log("ss.str()=[", ss.str(), "]");
|
||||
}
|
||||
} /*TEST_CASE(sstream.2bslash)*/
|
||||
|
||||
TEST_CASE("sstream.2char", "[sstream]") {
|
||||
constexpr bool c_debug_flag = false;
|
||||
|
||||
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.sstream.2char"));
|
||||
|
||||
/* testing unexpected sstream behavior */
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
||||
log && log("empty stream");
|
||||
|
||||
ss << "\\n";
|
||||
|
||||
std::string str = ss.str();
|
||||
|
||||
log && log("after: '\\n' escaped backslash + n");
|
||||
log && log(hex_view(str.data(), str.data() + str.size(), true));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("sstream.3char", "[sstream]") {
|
||||
constexpr bool c_debug_flag = false;
|
||||
|
||||
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.sstream.3char"));
|
||||
|
||||
/* testing unexpected sstream behavior */
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
||||
log && log("empty stream");
|
||||
|
||||
/* this is what quot("\\n") should wind up executing.. */
|
||||
ss << "\\\\";
|
||||
ss << 'n';
|
||||
|
||||
std::string str = ss.str();
|
||||
|
||||
log && log("after: '\\\\n' 2x escaped backslash + n");
|
||||
log && log(hex_view(str.data(), str.data() + str.size(), true));
|
||||
|
||||
REQUIRE(str == std::string("\\\\n"));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("sstream.quot.1bslash", "[quot]") {
|
||||
constexpr bool c_debug_flag = false;
|
||||
|
||||
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.sstream.quot.1bslash"));
|
||||
|
||||
log && log("quot(\"\\\")=[", quot("\\"), "]");
|
||||
|
||||
std::stringstream ss2;
|
||||
ss2 << quot("\\");
|
||||
|
||||
std::string str = ss2.str();
|
||||
|
||||
REQUIRE(str == std::string("\"\\\\\"")); /* ["\\"] */
|
||||
}
|
||||
|
||||
TEST_CASE("sstream.quot.newline", "[quot]") {
|
||||
constexpr bool c_debug_flag = false;
|
||||
|
||||
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.sstream.quot.newline"));
|
||||
|
||||
log && log("quot(\"\\n\")=[", quot("\n"), "]");
|
||||
|
||||
std::stringstream ss2;
|
||||
ss2 << quot("\n");
|
||||
|
||||
std::string str = ss2.str();
|
||||
|
||||
REQUIRE(str == std::string("\"\\n\"")); /* ["\n"] */
|
||||
}
|
||||
|
||||
TEST_CASE("sstream.quot.2bslash", "[quot]") {
|
||||
constexpr bool c_debug_flag = false;
|
||||
|
||||
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.quot.2bslash"));
|
||||
|
||||
log && log("quot(\"\\\\\")=[", quot("\\\\"), "]");
|
||||
|
||||
std::stringstream ss2;
|
||||
ss2 << quot("\\\\"); /* quoting string with two backslashes need to give ["\\\\"] */
|
||||
|
||||
std::string str = ss2.str();
|
||||
|
||||
REQUIRE(str == std::string("\"\\\\\\\\\"")); /* rhs is ["\\\\"] */
|
||||
}
|
||||
|
||||
TEST_CASE("sstream.quot.2charnewline", "[quot]") {
|
||||
constexpr bool c_debug_flag = false;
|
||||
|
||||
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.sstream.quot.2charnewline"));
|
||||
|
||||
log && log("quot(\"x\\n\")=[", quot("x\n"), "]");
|
||||
|
||||
std::stringstream ss2;
|
||||
ss2 << quot("x\n");
|
||||
|
||||
std::string str = ss2.str();
|
||||
|
||||
REQUIRE(str == std::string("\"x\\n\"")); /* ["\n"] */
|
||||
}
|
||||
|
||||
TEST_CASE("sstream.quot.2char", "[quot]") {
|
||||
constexpr bool c_debug_flag = false;
|
||||
|
||||
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.quot.2char"));
|
||||
|
||||
log && log("quot(\"\\n\")=[", quot("\\n"), "]");
|
||||
|
||||
std::stringstream ss2;
|
||||
ss2 << quot("\\n");
|
||||
|
||||
std::string str = ss2.str();
|
||||
|
||||
//std::cerr << quoted_debug::s_log_last_quoted.view() << std::endl;
|
||||
|
||||
//log && log("debug_log=[", quoted_debug::s_log_last_quoted.view() , "]");
|
||||
|
||||
REQUIRE(str == std::string("\"\\\\n\""));
|
||||
}
|
||||
|
||||
TEST_CASE("sstream.quot.foonewline", "[quot]") {
|
||||
constexpr bool c_debug_flag = false;
|
||||
|
||||
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.sstream.quot.2charnewline"));
|
||||
|
||||
std::stringstream ss2;
|
||||
ss2 << quot("foo\n");
|
||||
|
||||
std::string str = ss2.str();
|
||||
|
||||
REQUIRE(str == std::string("\"foo\\n\"")); /* ["\n"] */
|
||||
}
|
||||
|
||||
TEST_CASE("sstream.rest", "[quot]") {
|
||||
constexpr bool c_debug_flag = false;
|
||||
|
||||
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.sstream"));
|
||||
|
||||
/* testing unexpected sstream behavior */
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
||||
log && log("empty stream");
|
||||
|
||||
ss << quot("\n");
|
||||
|
||||
std::string str = ss.str();
|
||||
|
||||
log && log("after: quot('\\n')");
|
||||
log && log(hex_view(str.data(), str.data() + str.size(), true));
|
||||
}
|
||||
|
||||
/* testing unexpected sstream behavior */
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
||||
log && log("empty stream");
|
||||
|
||||
ss << quot("foo\n");
|
||||
|
||||
std::string str = ss.str();
|
||||
|
||||
log && log("after: quot(\"foo\n\")");
|
||||
log && log(hex_view(str.data(), str.data() + str.size(), true));
|
||||
log && log("> ss.str ----------------");
|
||||
log && log(ss.str());
|
||||
log && log("< ss.str ----------------");
|
||||
}
|
||||
|
||||
/* testing unexpected sstream behavior */
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
||||
log && log("empty stream");
|
||||
|
||||
ss << unq("\n");
|
||||
|
||||
std::string str = ss.str();
|
||||
|
||||
log && log("after: unq('\\n')");
|
||||
log && log(hex_view(str.data(), str.data() + str.size(), true));
|
||||
}
|
||||
|
||||
} /*TEST_CASE(sstream)*/
|
||||
|
||||
std::vector<quot_tcase> s_quot_tcase_v(
|
||||
{
|
||||
quot_tcase("", true, "\"\""),
|
||||
quot_tcase("", false, "\"\""),
|
||||
|
||||
quot_tcase("foo", true, "foo"),
|
||||
quot_tcase("foo", false, "\"foo\""),
|
||||
|
||||
quot_tcase("foo\n", true, "\"foo\\n\""),
|
||||
quot_tcase("foo\n", false, "\"foo\\n\""),
|
||||
|
||||
quot_tcase("two words", true, "\"two words\""),
|
||||
quot_tcase("two words", false, "\"two words\""),
|
||||
|
||||
quot_tcase("1st\n2nd", true, "\"1st\\n2nd\""),
|
||||
quot_tcase("1st\n2nd", false, "\"1st\\n2nd\""),
|
||||
|
||||
quot_tcase("misakte\rfix", true, "\"misakte\\rfix\""),
|
||||
quot_tcase("misakte\rfix", false, "\"misakte\\rfix\""),
|
||||
|
||||
quot_tcase("\"oh!\", she said", true, "\"\\\"oh!\\\", she said\""),
|
||||
quot_tcase("\"oh!\", she said", false, "\"\\\"oh!\\\", she said\""),
|
||||
|
||||
// special carveout for strings bracketed by <..>; assume already well-formed
|
||||
quot_tcase("<object printer output>", true, "<object printer output>"),
|
||||
quot_tcase("<object printer output>", false, "<object printer output>"),
|
||||
});
|
||||
|
||||
TEST_CASE("quot", "[quot]") {
|
||||
for (std::uint32_t i_tc = 0, z_tc = s_quot_tcase_v.size(); i_tc < z_tc; ++i_tc) {
|
||||
quot_tcase const & tc = s_quot_tcase_v[i_tc];
|
||||
|
||||
/* NOTE: don't use tag()/xtag() here,
|
||||
* since implementation relies on the inserter we are testing
|
||||
*/
|
||||
|
||||
INFO(tostr("i_tc=", i_tc, " unq_flag=", tc.unq_flag_));
|
||||
INFO("tc.x_ ----------------");
|
||||
INFO(tostr("[", tc.x_, "]"));
|
||||
INFO("tc.x_ ----------------");
|
||||
|
||||
std::stringstream ss;
|
||||
if (tc.unq_flag_)
|
||||
ss << unq(tc.x_);
|
||||
else
|
||||
ss << quot(tc.x_);
|
||||
|
||||
std::string str = ss.str();
|
||||
|
||||
INFO("tc.s ----------------");
|
||||
INFO(tostr("[", tc.s_, "]"));
|
||||
INFO("tc.s ----------------");
|
||||
INFO("ss.str ----------------");
|
||||
INFO(tostr("[", hex_view(str.data(), str.data() + str.size(), true), "]"));
|
||||
INFO(tostr("[", ss.str(), "]"));
|
||||
INFO("ss.str ----------------");
|
||||
|
||||
REQUIRE(ss.str() == tc.s_);
|
||||
|
||||
if (ss.str() != tc.s_)
|
||||
break;
|
||||
}
|
||||
|
||||
REQUIRE(s_quot_tcase_v.size() > 1);
|
||||
}
|
||||
} /*namespace ut*/
|
||||
|
||||
/* end quoted.test.cpp */
|
||||
69
xo-indentlog/utest/tag.test.cpp
Normal file
69
xo-indentlog/utest/tag.test.cpp
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
/* @file tag.test.cpp */
|
||||
|
||||
#include "xo/indentlog/print/tag.hpp"
|
||||
#include "xo/indentlog/print/vector.hpp"
|
||||
#include "xo/indentlog/print/concat.hpp"
|
||||
#include <catch2/catch.hpp>
|
||||
#include <sstream>
|
||||
|
||||
using namespace xo;
|
||||
|
||||
namespace ut {
|
||||
using xo::print::ccs;
|
||||
|
||||
TEST_CASE("tag", "[tag]") {
|
||||
tag_config::tag_color = color_spec_type::none();
|
||||
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << tag("foo", ccs("hello,world!"));
|
||||
|
||||
REQUIRE(ss.str() == ":foo hello,world!");
|
||||
}
|
||||
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << tag("foo", ccs("hello, world!"));
|
||||
|
||||
REQUIRE(ss.str() == ":foo \"hello, world!\"");
|
||||
}
|
||||
|
||||
{
|
||||
std::stringstream ss;
|
||||
std::vector<int> v = {1, 2, 3};
|
||||
ss << tag("foo", v);
|
||||
|
||||
REQUIRE(ss.str() == ":foo \"[1 2 3]\"");
|
||||
}
|
||||
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << tag("foo", concat("farenheit", 451));
|
||||
|
||||
REQUIRE(ss.str() == ":foo farenheit451");
|
||||
}
|
||||
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << tag("foo", ccs("hello")) << xtag("bar", ccs("there"));
|
||||
|
||||
REQUIRE(ss.str() == ":foo hello :bar there");
|
||||
}
|
||||
|
||||
tag_config::tag_color = color_spec_type::blue();
|
||||
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << tag("foo", ccs("hello,world!"));
|
||||
|
||||
/* color on color off
|
||||
* <---------> <----->
|
||||
*
|
||||
* see [indentlog/print/color.hpp] for escape sequences
|
||||
*/
|
||||
REQUIRE(ss.str() == "\033[31;34m:foo\033[0m hello,world!");
|
||||
}
|
||||
} /*TEST_CASE(tag)*/
|
||||
} /*namespace ut*/
|
||||
|
||||
/* end tag.test.cpp */
|
||||
175
xo-indentlog/utest/timeutil.test.cpp
Normal file
175
xo-indentlog/utest/timeutil.test.cpp
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
/* @file timeutil.test.cpp */
|
||||
|
||||
#include "xo/indentlog/timeutil/timeutil.hpp"
|
||||
#include "xo/indentlog/print/tag.hpp"
|
||||
#include <catch2/catch.hpp>
|
||||
#include <sstream>
|
||||
|
||||
using namespace xo;
|
||||
using namespace xo::time;
|
||||
using namespace std::chrono;
|
||||
|
||||
namespace ut {
|
||||
template <typename FromTime>
|
||||
inline utc_micros to_micros(FromTime tm) {
|
||||
return std::chrono::time_point_cast<xo::time::microseconds>(tm);
|
||||
} /*to_micros*/
|
||||
|
||||
TEST_CASE("epoch", "[timeutil]") {
|
||||
//tag_config::tag_color = color_spec_type::none();
|
||||
using xo::time::microseconds;
|
||||
|
||||
|
||||
utc_nanos t0 = timeutil::epoch();
|
||||
|
||||
REQUIRE(std::chrono::system_clock::to_time_t(to_micros(t0)) == std::time_t(0));
|
||||
} /*TEST_CASE(epoch)*/
|
||||
|
||||
TEST_CASE("ymd_hms", "[timeutil]") {
|
||||
{
|
||||
utc_nanos t0 = timeutil::ymd_hms(19700101 /*ymd*/, 0 /*hms*/);
|
||||
REQUIRE(std::chrono::system_clock::to_time_t(to_micros(t0)) == std::time_t(0));
|
||||
}
|
||||
|
||||
{
|
||||
utc_nanos t0 = timeutil::ymd_hms(19700101 /*ymd*/, 1 /*hms*/);
|
||||
REQUIRE(std::chrono::system_clock::to_time_t(to_micros(t0)) == std::time_t(1));
|
||||
}
|
||||
|
||||
{
|
||||
utc_nanos t0 = timeutil::ymd_hms(19700101 /*ymd*/, 100 /*hms*/);
|
||||
REQUIRE(std::chrono::system_clock::to_time_t(to_micros(t0)) == std::time_t(60));
|
||||
}
|
||||
|
||||
{
|
||||
utc_nanos t0 = timeutil::ymd_hms(19700101 /*ymd*/, 10000 /*hms*/);
|
||||
REQUIRE(std::chrono::system_clock::to_time_t(to_micros(t0)) == std::time_t(3600));
|
||||
}
|
||||
|
||||
{
|
||||
utc_nanos t0 = timeutil::ymd_hms(19700101 /*ymd*/, 235959 /*hms*/);
|
||||
REQUIRE(std::chrono::system_clock::to_time_t(to_micros(t0)) == std::time_t(86399));
|
||||
}
|
||||
|
||||
{
|
||||
utc_nanos t0 = timeutil::ymd_hms(19700102 /*ymd*/, 235959 /*hms*/);
|
||||
REQUIRE(std::chrono::system_clock::to_time_t(to_micros(t0)) == std::time_t(86400 + 86399));
|
||||
}
|
||||
|
||||
{
|
||||
utc_nanos t0 = timeutil::ymd_hms(19700131 /*ymd*/, 235959 /*hms*/);
|
||||
REQUIRE(std::chrono::system_clock::to_time_t(to_micros(t0)) == std::time_t(30 * 86400 + 86399));
|
||||
}
|
||||
|
||||
{
|
||||
utc_nanos t0 = timeutil::ymd_hms(19700201 /*ymd*/, 235959 /*hms*/);
|
||||
REQUIRE(std::chrono::system_clock::to_time_t(to_micros(t0)) == std::time_t(31 * 86400 + 86399));
|
||||
}
|
||||
} /*TEST_CASE(ymd_hms)*/
|
||||
|
||||
TEST_CASE("ymd_midnight", "[timeutil]") {
|
||||
{
|
||||
utc_nanos t0 = timeutil::ymd_midnight(19700101 /*ymd*/);
|
||||
REQUIRE(std::chrono::system_clock::to_time_t(to_micros(t0)) == std::time_t(0));
|
||||
}
|
||||
|
||||
{
|
||||
utc_nanos t0 = timeutil::ymd_midnight(19700102 /*ymd*/);
|
||||
REQUIRE(std::chrono::system_clock::to_time_t(to_micros(t0)) == std::time_t(86400));
|
||||
}
|
||||
|
||||
{
|
||||
utc_nanos t0 = timeutil::ymd_midnight(19700131 /*ymd*/);
|
||||
REQUIRE(std::chrono::system_clock::to_time_t(to_micros(t0)) == std::time_t(30 * 86400));
|
||||
}
|
||||
|
||||
{
|
||||
utc_nanos t0 = timeutil::ymd_midnight(19700201 /*ymd*/);
|
||||
REQUIRE(system_clock::to_time_t(to_micros(t0)) == std::time_t(31 * 86400));
|
||||
}
|
||||
} /*TEST_CASE(ymd_midnight)*/
|
||||
|
||||
struct timeutil_tcase {
|
||||
timeutil_tcase() = default;
|
||||
timeutil_tcase(uint32_t ymd, uint32_t hms, uint32_t usec,
|
||||
std::time_t epoch_sec, std::time_t midnight_sec, std::uint32_t fractional_sec, std::uint32_t fractional_usec,
|
||||
std::string const & utc_ymd_hms_usec_str,
|
||||
std::string const & iso8601_str)
|
||||
: ymd_{ymd}, hms_{hms}, usec_{usec},
|
||||
epoch_sec_{epoch_sec},
|
||||
midnight_sec_{midnight_sec},
|
||||
fractional_sec_{fractional_sec},
|
||||
fractional_usec_{fractional_usec},
|
||||
utc_ymd_hms_usec_str_{utc_ymd_hms_usec_str},
|
||||
iso8601_str_{iso8601_str} {}
|
||||
|
||||
/* target time value to test */
|
||||
std::uint32_t ymd_ = 19700101;
|
||||
std::uint32_t hms_ = 0;
|
||||
std::uint32_t usec_ = 0;
|
||||
|
||||
std::time_t epoch_sec_ = 0;
|
||||
std::time_t midnight_sec_ = 0;
|
||||
std::uint32_t fractional_sec_ = 0;
|
||||
std::uint32_t fractional_usec_ = 0;
|
||||
|
||||
std::string utc_ymd_hms_usec_str_;
|
||||
std::string iso8601_str_;
|
||||
}; /*timeutil_tcase*/
|
||||
|
||||
std::vector<timeutil_tcase> s_timeutil_tcase_v(
|
||||
/* -------- inputs ------- ------------------------------------------------ outputs ---------------------------------------
|
||||
* fractional_usec
|
||||
* fractional_sec |
|
||||
* ymd hms usec epoch_sec midnight_sec v v utc_ymd_hms_usec_str iso8601_str
|
||||
*/
|
||||
{
|
||||
timeutil_tcase(19700101, 0, 0, 0, 0, 0, 0, "19700101:00:00:00.000000", "1970-01-01T00:00:00.000Z"),
|
||||
timeutil_tcase(19700101, 0, 1, 0, 0, 0, 1, "19700101:00:00:00.000001", "1970-01-01T00:00:00.000Z"),
|
||||
timeutil_tcase(19700101, 0, 123456, 0, 0, 0, 123456, "19700101:00:00:00.123456", "1970-01-01T00:00:00.123Z"),
|
||||
timeutil_tcase(19700101, 0, 500000, 0, 0, 0, 500000, "19700101:00:00:00.500000", "1970-01-01T00:00:00.500Z"),
|
||||
timeutil_tcase(19700101, 0, 987654, 0, 0, 0, 987654, "19700101:00:00:00.987654", "1970-01-01T00:00:00.987Z"),
|
||||
|
||||
timeutil_tcase(19700101, 0, 999999, 0, 0, 0, 999999, "19700101:00:00:00.999999", "1970-01-01T00:00:00.999Z"),
|
||||
|
||||
timeutil_tcase(19700101, 1, 999999, 1, 0, 1, 999999, "19700101:00:00:01.999999", "1970-01-01T00:00:01.999Z"),
|
||||
|
||||
timeutil_tcase(19700101, 100, 999999, 60, 0, 60, 999999, "19700101:00:01:00.999999", "1970-01-01T00:01:00.999Z"),
|
||||
timeutil_tcase(19700101, 10000, 999999, 3600, 0, 3600, 999999, "19700101:01:00:00.999999", "1970-01-01T01:00:00.999Z"),
|
||||
timeutil_tcase(19700101, 235959, 999999, 24*3600-1, 0, 86399, 999999, "19700101:23:59:59.999999", "1970-01-01T23:59:59.999Z"),
|
||||
|
||||
timeutil_tcase(19700102, 100, 999999, 86400+60, 86400, 60, 999999, "19700102:00:01:00.999999", "1970-01-02T00:01:00.999Z"),
|
||||
|
||||
});
|
||||
|
||||
TEST_CASE("ymd_hms_usec", "[timeutil]") {
|
||||
for (std::uint32_t i_tc = 0, z_tc = s_timeutil_tcase_v.size(); i_tc < z_tc; ++i_tc) {
|
||||
timeutil_tcase const & tc = s_timeutil_tcase_v[i_tc];
|
||||
|
||||
INFO(tostr(xtag("i_tc", i_tc), xtag("ymd", tc.ymd_)));
|
||||
INFO(xtag("tc.epoch_sec", tc.epoch_sec_));
|
||||
INFO(xtag("tc.utc_ymd_hms_usec_str", tc.utc_ymd_hms_usec_str_));
|
||||
|
||||
utc_nanos const t0 = timeutil::ymd_hms_usec(tc.ymd_, tc.hms_, tc.usec_);
|
||||
REQUIRE(system_clock::to_time_t(to_micros(t0)) == std::time_t(tc.epoch_sec_));
|
||||
|
||||
auto x = timeutil::utc_split_vs_midnight(t0);
|
||||
REQUIRE(system_clock::to_time_t(to_micros(x.first)) == tc.midnight_sec_);
|
||||
REQUIRE(x.second == seconds(tc.fractional_sec_) + microseconds(tc.fractional_usec_));
|
||||
|
||||
{
|
||||
std::stringstream ss;
|
||||
timeutil::print_utc_ymd_hms_usec(t0, ss);
|
||||
REQUIRE(ss.str() == tc.utc_ymd_hms_usec_str_);
|
||||
}
|
||||
|
||||
{
|
||||
std::stringstream ss;
|
||||
timeutil::print_iso8601(t0, ss);
|
||||
REQUIRE(ss.str() == tc.iso8601_str_);
|
||||
}
|
||||
}
|
||||
} /*TEST_CASE(ymd_hms_usec)*/
|
||||
} /*namespace ut*/
|
||||
|
||||
/* end timeutil.test.cpp */
|
||||
50
xo-indentlog/utest/vector.test.cpp
Normal file
50
xo-indentlog/utest/vector.test.cpp
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
/* @file vector.test.cpp */
|
||||
|
||||
#include "xo/indentlog/print/vector.hpp" /* overload operator<< for std::vector */
|
||||
#include "xo/indentlog/print/tag.hpp"
|
||||
#include <catch2/catch.hpp>
|
||||
#include <sstream>
|
||||
|
||||
using namespace xo;
|
||||
|
||||
namespace ut {
|
||||
struct vector_tcase {
|
||||
vector_tcase() = default;
|
||||
vector_tcase(std::vector<int> const & x, std::string s)
|
||||
: x_{x}, s_{std::move(s)} {}
|
||||
|
||||
/* vector to print */
|
||||
std::vector<int> x_;
|
||||
/* expected result */
|
||||
std::string s_;
|
||||
}; /*vector_tcase*/
|
||||
|
||||
std::vector<vector_tcase> s_vector_tcase_v(
|
||||
{vector_tcase({}, "[]"),
|
||||
vector_tcase({1}, "[1]"),
|
||||
vector_tcase({1, 2}, "[1 2]"),
|
||||
vector_tcase({10, 20, 30}, "[10 20 30]"),
|
||||
|
||||
});
|
||||
|
||||
TEST_CASE("vector", "[vector]") {
|
||||
tag_config::tag_color = color_spec_type::none();
|
||||
|
||||
for (std::uint32_t i_tc = 0, z_tc = s_vector_tcase_v.size(); i_tc < z_tc; ++i_tc) {
|
||||
vector_tcase const & tc = s_vector_tcase_v[i_tc];
|
||||
|
||||
INFO(tostr(xtag("i_tc", i_tc), xtag("x", tc.x_)));
|
||||
|
||||
std::stringstream ss;
|
||||
ss << tc.x_;
|
||||
|
||||
INFO(xtag("ss.str", ss.str()));
|
||||
|
||||
REQUIRE(ss.str() == tc.s_);
|
||||
}
|
||||
|
||||
REQUIRE(s_vector_tcase_v.size() > 1);
|
||||
}
|
||||
} /*namespace ut*/
|
||||
|
||||
/* end vector.test.cpp */
|
||||
Loading…
Add table
Add a link
Reference in a new issue