From cb9c6b27c82efc6a9457bac9e9e8bd95f790ecc5 Mon Sep 17 00:00:00 2001 From: Andreas Tille Date: Wed, 19 Sep 2018 20:38:22 +0100 Subject: [PATCH 1/1] Import spdlog_1.1.0.orig.tar.gz [dgit import orig spdlog_1.1.0.orig.tar.gz] --- .clang-format | 108 + .gitignore | 68 + .travis.yml | 113 + CMakeLists.txt | 139 + INSTALL | 13 + LICENSE | 22 + README.md | 282 + appveyor.yml | 32 + bench/CMakeLists.txt | 43 + bench/Makefile | 27 + bench/async_bench.cpp | 138 + bench/bench.cpp | 148 + bench/latency.cpp | 151 + bench/logs/.gitignore | 4 + bench/mem | 19 + bench/spdlog-async.cpp | 77 + bench/utils.h | 34 + cmake/Config.cmake.in | 24 + cmake/sanitizers.cmake | 21 + cmake/spdlog.pc.in | 6 + example/CMakeLists.txt | 43 + example/Makefile | 21 + example/Makefile.clang | 26 + example/Makefile.mingw | 25 + example/example.cpp | 205 + example/example.sln | 106 + example/example.vcxproj | 168 + example/jni/Android.mk | 15 + example/jni/Application.mk | 2 + example/jni/example.cpp | 157 + example/logs/.gitignore | 1 + example/multisink.cpp | 46 + example/utils.h | 34 + format.sh | 9 + include/spdlog/async.h | 87 + include/spdlog/async_logger.h | 71 + include/spdlog/common.h | 177 + include/spdlog/details/async_logger_impl.h | 100 + include/spdlog/details/circular_q.h | 72 + include/spdlog/details/console_globals.h | 61 + include/spdlog/details/file_helper.h | 153 + include/spdlog/details/fmt_helper.h | 130 + include/spdlog/details/log_msg.h | 48 + include/spdlog/details/logger_impl.h | 345 + include/spdlog/details/mpmc_blocking_q.h | 121 + include/spdlog/details/null_mutex.h | 45 + include/spdlog/details/os.h | 432 + include/spdlog/details/pattern_formatter.h | 772 ++ include/spdlog/details/periodic_worker.h | 71 + include/spdlog/details/registry.h | 214 + include/spdlog/details/thread_pool.h | 225 + include/spdlog/fmt/bundled/LICENSE.rst | 23 + include/spdlog/fmt/bundled/colors.h | 257 + include/spdlog/fmt/bundled/core.h | 1578 ++++ include/spdlog/fmt/bundled/format-inl.h | 578 ++ include/spdlog/fmt/bundled/format.h | 4716 ++++++++++ include/spdlog/fmt/bundled/locale.h | 27 + include/spdlog/fmt/bundled/ostream.h | 173 + include/spdlog/fmt/bundled/posix.h | 484 + include/spdlog/fmt/bundled/printf.h | 807 ++ include/spdlog/fmt/bundled/ranges.h | 344 + include/spdlog/fmt/bundled/time.h | 199 + include/spdlog/fmt/fmt.h | 25 + include/spdlog/fmt/ostr.h | 18 + include/spdlog/formatter.h | 20 + include/spdlog/logger.h | 162 + include/spdlog/sinks/android_sink.h | 117 + include/spdlog/sinks/ansicolor_sink.h | 156 + include/spdlog/sinks/base_sink.h | 63 + include/spdlog/sinks/basic_file_sink.h | 66 + include/spdlog/sinks/daily_file_sink.h | 132 + include/spdlog/sinks/dist_sink.h | 68 + include/spdlog/sinks/msvc_sink.h | 50 + include/spdlog/sinks/null_sink.h | 28 + include/spdlog/sinks/ostream_sink.h | 51 + include/spdlog/sinks/rotating_file_sink.h | 144 + include/spdlog/sinks/sink.h | 57 + include/spdlog/sinks/stdout_color_sinks.h | 53 + include/spdlog/sinks/stdout_sinks.h | 100 + include/spdlog/sinks/syslog_sink.h | 91 + include/spdlog/sinks/wincolor_sink.h | 139 + include/spdlog/spdlog.h | 161 + include/spdlog/tweakme.h | 132 + include/spdlog/version.h | 12 + tests/CMakeLists.txt | 29 + tests/Makefile | 22 + tests/catch.hpp | 9427 ++++++++++++++++++++ tests/catch.license | 23 + tests/errors.cpp | 119 + tests/file_helper.cpp | 110 + tests/file_log.cpp | 171 + tests/includes.h | 22 + tests/main.cpp | 2 + tests/registry.cpp | 77 + tests/test_async.cpp | 195 + tests/test_macros.cpp | 40 + tests/test_misc.cpp | 93 + tests/test_mpmc_q.cpp | 103 + tests/test_pattern_formatter.cpp | 126 + tests/test_sink.h | 59 + tests/tests.sln | 100 + tests/tests.vcxproj | 148 + tests/tests.vcxproj.filters | 60 + tests/tests.vcxproj.user | 4 + tests/utils.cpp | 52 + tests/utils.h | 16 + 106 files changed, 27480 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 CMakeLists.txt create mode 100644 INSTALL create mode 100644 LICENSE create mode 100644 README.md create mode 100644 appveyor.yml create mode 100644 bench/CMakeLists.txt create mode 100644 bench/Makefile create mode 100644 bench/async_bench.cpp create mode 100644 bench/bench.cpp create mode 100644 bench/latency.cpp create mode 100644 bench/logs/.gitignore create mode 100755 bench/mem create mode 100644 bench/spdlog-async.cpp create mode 100644 bench/utils.h create mode 100644 cmake/Config.cmake.in create mode 100644 cmake/sanitizers.cmake create mode 100644 cmake/spdlog.pc.in create mode 100644 example/CMakeLists.txt create mode 100644 example/Makefile create mode 100644 example/Makefile.clang create mode 100644 example/Makefile.mingw create mode 100644 example/example.cpp create mode 100644 example/example.sln create mode 100644 example/example.vcxproj create mode 100644 example/jni/Android.mk create mode 100644 example/jni/Application.mk create mode 100644 example/jni/example.cpp create mode 100644 example/logs/.gitignore create mode 100644 example/multisink.cpp create mode 100644 example/utils.h create mode 100755 format.sh create mode 100644 include/spdlog/async.h create mode 100644 include/spdlog/async_logger.h create mode 100644 include/spdlog/common.h create mode 100644 include/spdlog/details/async_logger_impl.h create mode 100644 include/spdlog/details/circular_q.h create mode 100644 include/spdlog/details/console_globals.h create mode 100644 include/spdlog/details/file_helper.h create mode 100644 include/spdlog/details/fmt_helper.h create mode 100644 include/spdlog/details/log_msg.h create mode 100644 include/spdlog/details/logger_impl.h create mode 100644 include/spdlog/details/mpmc_blocking_q.h create mode 100644 include/spdlog/details/null_mutex.h create mode 100644 include/spdlog/details/os.h create mode 100644 include/spdlog/details/pattern_formatter.h create mode 100644 include/spdlog/details/periodic_worker.h create mode 100644 include/spdlog/details/registry.h create mode 100644 include/spdlog/details/thread_pool.h create mode 100644 include/spdlog/fmt/bundled/LICENSE.rst create mode 100644 include/spdlog/fmt/bundled/colors.h create mode 100644 include/spdlog/fmt/bundled/core.h create mode 100644 include/spdlog/fmt/bundled/format-inl.h create mode 100644 include/spdlog/fmt/bundled/format.h create mode 100644 include/spdlog/fmt/bundled/locale.h create mode 100644 include/spdlog/fmt/bundled/ostream.h create mode 100644 include/spdlog/fmt/bundled/posix.h create mode 100644 include/spdlog/fmt/bundled/printf.h create mode 100644 include/spdlog/fmt/bundled/ranges.h create mode 100644 include/spdlog/fmt/bundled/time.h create mode 100644 include/spdlog/fmt/fmt.h create mode 100644 include/spdlog/fmt/ostr.h create mode 100644 include/spdlog/formatter.h create mode 100644 include/spdlog/logger.h create mode 100644 include/spdlog/sinks/android_sink.h create mode 100644 include/spdlog/sinks/ansicolor_sink.h create mode 100644 include/spdlog/sinks/base_sink.h create mode 100644 include/spdlog/sinks/basic_file_sink.h create mode 100644 include/spdlog/sinks/daily_file_sink.h create mode 100644 include/spdlog/sinks/dist_sink.h create mode 100644 include/spdlog/sinks/msvc_sink.h create mode 100644 include/spdlog/sinks/null_sink.h create mode 100644 include/spdlog/sinks/ostream_sink.h create mode 100644 include/spdlog/sinks/rotating_file_sink.h create mode 100644 include/spdlog/sinks/sink.h create mode 100644 include/spdlog/sinks/stdout_color_sinks.h create mode 100644 include/spdlog/sinks/stdout_sinks.h create mode 100644 include/spdlog/sinks/syslog_sink.h create mode 100644 include/spdlog/sinks/wincolor_sink.h create mode 100644 include/spdlog/spdlog.h create mode 100644 include/spdlog/tweakme.h create mode 100644 include/spdlog/version.h create mode 100644 tests/CMakeLists.txt create mode 100644 tests/Makefile create mode 100644 tests/catch.hpp create mode 100644 tests/catch.license create mode 100644 tests/errors.cpp create mode 100644 tests/file_helper.cpp create mode 100644 tests/file_log.cpp create mode 100644 tests/includes.h create mode 100644 tests/main.cpp create mode 100644 tests/registry.cpp create mode 100644 tests/test_async.cpp create mode 100644 tests/test_macros.cpp create mode 100644 tests/test_misc.cpp create mode 100644 tests/test_mpmc_q.cpp create mode 100644 tests/test_pattern_formatter.cpp create mode 100644 tests/test_sink.h create mode 100644 tests/tests.sln create mode 100644 tests/tests.vcxproj create mode 100644 tests/tests.vcxproj.filters create mode 100644 tests/tests.vcxproj.user create mode 100644 tests/utils.cpp create mode 100644 tests/utils.h diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..89e5b90 --- /dev/null +++ b/.clang-format @@ -0,0 +1,108 @@ +--- +Language: Cpp +# BasedOnStyle: LLVM +AccessModifierOffset: -4 +AlignAfterOpenBracket: DontAlign +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Right +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: true +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: true +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: true + AfterControlStatement: true + AfterEnum: true + AfterFunction: true + AfterNamespace: false + AfterObjCDeclaration: true + AfterStruct: true + AfterUnion: true + BeforeCatch: true + BeforeElse: true + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: true +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 140 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: false +IndentWidth: 4 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +TabWidth: 8 +UseTab: Never +... + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b1a4191 --- /dev/null +++ b/.gitignore @@ -0,0 +1,68 @@ +# Auto generated files +build/* +*.slo +*.lo +*.o +*.obj +*.suo +*.tlog +*.ilk +*.log +*.pdb +*.idb +*.iobj +*.ipdb +*.opensdf +*.sdf + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# Codelite +.codelite + +# .orig files +*.orig + +# example files +example/* +!example/example.cpp +!example/bench.cpp +!example/utils.h +!example/Makefile* +!example/example.sln +!example/example.vcxproj +!example/CMakeLists.txt +!example/multisink.cpp +!example/jni + +# generated files +generated + +# Cmake +CMakeCache.txt +CMakeFiles +CMakeScripts +Makefile +cmake_install.cmake +install_manifest.txt +/tests/tests.VC.VC.opendb +/tests/tests.VC.db +/tests/tests +/tests/logs/* + +# idea +.idea/ \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..b0dbcba --- /dev/null +++ b/.travis.yml @@ -0,0 +1,113 @@ +# Adapted from various sources, including: +# - Louis Dionne's Hana: https://github.com/ldionne/hana +# - Paul Fultz II's FIT: https://github.com/pfultz2/Fit +# - Eric Niebler's range-v3: https://github.com/ericniebler/range-v3 +sudo: required +language: cpp + +addons: &gcc48 + apt: + packages: + - g++-4.8 + - valgrind + sources: + - ubuntu-toolchain-r-test + +addons: &gcc7 + apt: + packages: + - g++-7 + - valgrind + sources: + - ubuntu-toolchain-r-test + +addons: &clang35 + apt: + packages: + - clang-3.5 + - valgrind + sources: + - ubuntu-toolchain-r-test + - llvm-toolchain-precise-3.5 + +addons: &clang6 + apt: + packages: + - clang-6.0 + - valgrind + sources: + - ubuntu-toolchain-r-test + - llvm-toolchain-trusty-6.0 + +matrix: + include: + # Test gcc-4.8: C++11, Build=Debug/Release + - env: GCC_VERSION=4.8 BUILD_TYPE=Debug CPP=11 + os: linux + addons: *gcc48 + + - env: GCC_VERSION=4.8 BUILD_TYPE=Release CPP=11 + os: linux + addons: *gcc48 + + - env: GCC_VERSION=7 BUILD_TYPE=Release CPP=11 + os: linux + addons: *gcc7 + + # Test clang-3.5: C++11, Build=Debug/Release + - env: CLANG_VERSION=3.5 BUILD_TYPE=Debug CPP=11 + os: linux + addons: *clang35 + + - env: CLANG_VERSION=3.5 BUILD_TYPE=Release CPP=11 + os: linux + addons: *clang35 + + # Test clang-6.0: C++11, Build=Debug, ASAN=On + - env: CLANG_VERSION=6.0 BUILD_TYPE=Debug CPP=11 ASAN=On TSAN=Off + os: linux + addons: *clang6 + + - env: CLANG_VERSION=6.0 BUILD_TYPE=Release CPP=11 ASAN=On TSAN=Off + os: linux + addons: *clang6 + + # Test clang-6.0: C++11, Build=Debug, TSAN=On + - env: CLANG_VERSION=6.0 BUILD_TYPE=Debug CPP=11 ASAN=Off TSAN=On + os: linux + addons: *clang6 + + - env: CLANG_VERSION=6.0 BUILD_TYPE=Release CPP=11 ASAN=Off TSAN=On + os: linux + addons: *clang6 + + +before_script: + - if [ -n "$GCC_VERSION" ]; then export CXX="g++-${GCC_VERSION}" CC="gcc-${GCC_VERSION}"; fi + - if [ -n "$CLANG_VERSION" ]; then export CXX="clang++-${CLANG_VERSION}" CC="clang-${CLANG_VERSION}"; fi + - which $CXX + - which $CC + - which valgrind + - $CXX --version + - cmake --version + - valgrind --version + +script: + - cd ${TRAVIS_BUILD_DIR} + - mkdir -p build && cd build + - | + cmake .. \ + --warn-uninitialized \ + -DCMAKE_BUILD_TYPE=$BUILD_TYPE \ + -DCMAKE_CXX_STANDARD=$CPP \ + -DSPDLOG_BUILD_EXAMPLES=ON \ + -DSPDLOG_BUILD_BENCH=OFF \ + -DSPDLOG_SANITIZE_ADDRESS=$ASAN \ + -DSPDLOG_SANITIZE_THREAD=$TSAN + - make VERBOSE=1 -j2 + - ctest -j2 --output-on-failure + + + +notifications: + email: false diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..fee5655 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,139 @@ +# +# Copyright(c) 2015 Ruslan Baratov. +# Distributed under the MIT License (http://opensource.org/licenses/MIT) +# + +cmake_minimum_required(VERSION 3.1) +project(spdlog VERSION 1.1.0 LANGUAGES CXX) +include(CTest) +include(CMakeDependentOption) +include(GNUInstallDirs) + +#--------------------------------------------------------------------------------------- +# set default build to release +#--------------------------------------------------------------------------------------- +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose Release or Debug" FORCE) +endif() + +message(STATUS "Build type: " ${CMAKE_BUILD_TYPE}) + +#--------------------------------------------------------------------------------------- +# compiler config +#--------------------------------------------------------------------------------------- +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") + add_compile_options("-Wall") + add_compile_options("-Wextra") + add_compile_options("-pedantic") +endif() + +#--------------------------------------------------------------------------------------- +# address sanitizers check +#--------------------------------------------------------------------------------------- +include(cmake/sanitizers.cmake) + +#--------------------------------------------------------------------------------------- +# spdlog target +#--------------------------------------------------------------------------------------- +add_library(spdlog INTERFACE) +add_library(spdlog::spdlog ALIAS spdlog) + +option(SPDLOG_BUILD_EXAMPLES "Build examples" ON) +option(SPDLOG_BUILD_BENCH "Build benchmarks" ON) + +cmake_dependent_option(SPDLOG_BUILD_TESTING + "Build spdlog tests" ON + "BUILD_TESTING" OFF +) + +target_include_directories( + spdlog + INTERFACE + "$" + "$" +) + +set(HEADER_BASE "${CMAKE_CURRENT_SOURCE_DIR}/include") + +if(SPDLOG_BUILD_EXAMPLES) + add_subdirectory(example) +endif() + +if(SPDLOG_BUILD_TESTING) + add_subdirectory(tests) +endif() + +if(SPDLOG_BUILD_BENCH) + add_subdirectory(bench) +endif() + +#--------------------------------------------------------------------------------------- +# Install/export targets and files +#--------------------------------------------------------------------------------------- +# set files and directories +set(config_install_dir "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}") +set(include_install_dir "${CMAKE_INSTALL_INCLUDEDIR}") +set(pkgconfig_install_dir "${CMAKE_INSTALL_LIBDIR}/pkgconfig") +set(version_config "${CMAKE_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake") +set(project_config "${PROJECT_NAME}Config.cmake") +set(pkg_config "${CMAKE_BINARY_DIR}/${PROJECT_NAME}.pc") +set(targets_export_name "${PROJECT_NAME}Targets") +set(namespace "${PROJECT_NAME}::") + +# generate package version file +include(CMakePackageConfigHelpers) +write_basic_package_version_file( + "${version_config}" COMPATIBILITY SameMajorVersion +) + +# configure pkg config file +configure_file("cmake/spdlog.pc.in" "${pkg_config}" @ONLY) + +# install targets +install( + TARGETS spdlog + EXPORT "${targets_export_name}" +) + +# install headers +install( + DIRECTORY "${HEADER_BASE}/${PROJECT_NAME}" + DESTINATION "${include_install_dir}" +) + +# install project version file +install( + FILES "${version_config}" + DESTINATION "${config_install_dir}" +) + +# install pkg config file +install( + FILES "${pkg_config}" + DESTINATION "${pkgconfig_install_dir}" +) + +# install project config file +install( + EXPORT "${targets_export_name}" + NAMESPACE "${namespace}" + DESTINATION "${config_install_dir}" + FILE ${project_config} +) + +# export build directory config file +export( + EXPORT ${targets_export_name} + NAMESPACE "${namespace}" + FILE ${project_config} +) + +# register project in CMake user registry +export(PACKAGE ${PROJECT_NAME}) + +file(GLOB_RECURSE spdlog_include_SRCS "${HEADER_BASE}/*.h") +add_custom_target(spdlog_headers_for_ide SOURCES ${spdlog_include_SRCS}) diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..664509d --- /dev/null +++ b/INSTALL @@ -0,0 +1,13 @@ +spdlog is header only library. +Just copy the files to your build tree and use a C++11 compiler + +Tested on: +gcc 4.8.1 and above +clang 3.5 +Visual Studio 2013 + +gcc 4.8 flags: --std==c++11 -pthread -O3 -flto -Wl,--no-as-needed +gcc 4.9 flags: --std=c++11 -pthread -O3 -flto + + +see the makefile in the example folder diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4b43e06 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2016 Gabi Melman. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..ca797e1 --- /dev/null +++ b/README.md @@ -0,0 +1,282 @@ +# spdlog + +Very fast, header only, C++ logging library. [![Build Status](https://travis-ci.org/gabime/spdlog.svg?branch=master)](https://travis-ci.org/gabime/spdlog)  [![Build status](https://ci.appveyor.com/api/projects/status/d2jnxclg20vd0o50?svg=true)](https://ci.appveyor.com/project/gabime/spdlog) + + +## Install +#### Just copy the headers: + +* Copy the source [folder](https://github.com/gabime/spdlog/tree/v1.x/include/spdlog) to your build tree and use a C++11 compiler. + +#### Or use your favorite package manager: + +* Ubuntu: `apt-get install libspdlog-dev` +* Homebrew: `brew install spdlog` +* FreeBSD: `cd /usr/ports/devel/spdlog/ && make install clean` +* Fedora: `yum install spdlog` +* Gentoo: `emerge dev-libs/spdlog` +* Arch Linux: `yaourt -S spdlog-git` +* vcpkg: `vcpkg install spdlog` + + +## Platforms + * Linux, FreeBSD, Solaris, AIX + * Windows (vc 2013+, cygwin) + * Mac OSX (clang 3.5+) + * Android + +## Features +* Very fast - performance is the primary goal (see [benchmarks](#benchmarks) below). +* Headers only, just copy and use. +* Feature rich using the excellent [fmt](https://github.com/fmtlib/fmt) library. +* Fast asynchronous mode (optional) +* [Custom](https://github.com/gabime/spdlog/wiki/3.-Custom-formatting) formatting. +* Conditional Logging +* Multi/Single threaded loggers. +* Various log targets: + * Rotating log files. + * Daily log files. + * Console logging (colors supported). + * syslog. + * Windows debugger (```OutputDebugString(..)```) + * Easily extendable with custom log targets (just implement a single function in the [sink](include/spdlog/sinks/sink.h) interface). +* Severity based filtering - threshold levels can be modified in runtime as well as in compile time. + + + +## Benchmarks + +Below are some [benchmarks](https://github.com/gabime/spdlog/blob/v1.x/bench/bench.cpp) done in Ubuntu 64 bit, Intel i7-4770 CPU @ 3.40GHz + +#### Synchronous mode +``` +******************************************************************************* +Single thread, 1,000,000 iterations +******************************************************************************* +basic_st... Elapsed: 0.226664 4,411,806/sec +rotating_st... Elapsed: 0.214339 4,665,499/sec +daily_st... Elapsed: 0.211292 4,732,797/sec +null_st... Elapsed: 0.102815 9,726,227/sec + +******************************************************************************* +10 threads sharing same logger, 1,000,000 iterations +******************************************************************************* +basic_mt... Elapsed: 0.882268 1,133,441/sec +rotating_mt... Elapsed: 0.875515 1,142,184/sec +daily_mt... Elapsed: 0.879573 1,136,915/sec +null_mt... Elapsed: 0.220114 4,543,105/sec +``` +#### Asynchronous mode +``` +******************************************************************************* +10 threads sharing same logger, 1,000,000 iterations +******************************************************************************* +async... Elapsed: 0.429088 2,330,524/sec +async... Elapsed: 0.411501 2,430,126/sec +async... Elapsed: 0.428979 2,331,116/sec + +``` + +## Usage samples + +```c++ +#include "spdlog/spdlog.h" +#include "spdlog/sinks/stdout_color_sinks.h" +void stdout_example() +{ + // create color multi threaded logger + auto console = spdlog::stdout_color_mt("console"); + console->info("Welcome to spdlog!"); + console->error("Some error message with arg: {}", 1); + + auto err_logger = spdlog::stderr_color_mt("stderr"); + err_logger->error("Some error message"); + + // Formatting examples + console->warn("Easy padding in numbers like {:08d}", 12); + console->critical("Support for int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42); + console->info("Support for floats {:03.2f}", 1.23456); + console->info("Positional args are {1} {0}..", "too", "supported"); + console->info("{:<30}", "left aligned"); + + spdlog::get("console")->info("loggers can be retrieved from a global registry using the spdlog::get(logger_name)"); + + // Runtime log levels + spdlog::set_level(spdlog::level::info); // Set global log level to info + console->debug("This message should not be displayed!"); + console->set_level(spdlog::level::trace); // Set specific logger's log level + console->debug("This message should be displayed.."); + + // Customize msg format for all loggers + spdlog::set_pattern("[%H:%M:%S %z] [%n] [%^---%L---%$] [thread %t] %v"); + console->info("This an info message with custom format"); + + // Compile time log levels + // define SPDLOG_DEBUG_ON or SPDLOG_TRACE_ON + SPDLOG_TRACE(console, "Enabled only #ifdef SPDLOG_TRACE_ON..{} ,{}", 1, 3.23); + SPDLOG_DEBUG(console, "Enabled only #ifdef SPDLOG_DEBUG_ON.. {} ,{}", 1, 3.23); +} +``` +--- +#### Basic file logger +```c++ +#include "spdlog/sinks/basic_file_sink.h" +void basic_logfile_example() +{ + try + { + auto my_logger = spdlog::basic_logger_mt("basic_logger", "logs/basic-log.txt"); + } + catch (const spdlog::spdlog_ex &ex) + { + std::cout << "Log init failed: " << ex.what() << std::endl; + return 1; + } +} +``` +--- +#### Rotating files +```c++ +#include "spdlog/sinks/rotating_file_sink.h" +void rotating_example() +{ + // Create a file rotating logger with 5mb size max and 3 rotated files + auto rotating_logger = spdlog::rotating_logger_mt("some_logger_name", "logs/rotating.txt", 1048576 * 5, 3); +} +``` + +--- +#### Daily files +```c++ + +#include "spdlog/sinks/daily_file_sink.h" +void daily_example() +{ + // Create a daily logger - a new file is created every day on 2:30am + auto daily_logger = spdlog::daily_logger_mt("daily_logger", "logs/daily.txt", 2, 30); +} + +``` + +--- +#### Periodic flush +```c++ +// periodically flush all *registered* loggers every 3 seconds: +// warning: only use if all your loggers are thread safe! +spdlog::flush_every(std::chrono::seconds(3)); + +``` + + +--- +#### Logger with multi sinks - each with different format and log level +```c++ + +// create logger with 2 targets with different log levels and formats. +// the console will show only warnings or errors, while the file will log all. +void multi_sink_example() +{ + auto console_sink = std::make_shared(); + console_sink->set_level(spdlog::level::warn); + console_sink->set_pattern("[multi_sink_example] [%^%l%$] %v"); + + auto file_sink = std::make_shared("logs/multisink.txt", true); + file_sink->set_level(spdlog::level::trace); + + spdlog::logger logger("multi_sink", {console_sink, file_sink}); + logger.set_level(spdlog::level::debug); + logger.warn("this should appear in both console and file"); + logger.info("this message should not appear in the console, only in the file"); +} +``` + +--- +#### Asynchronous logging +```c++ +#include "spdlog/async.h" +#include "spdlog/sinks/basic_file_sink.h" +void async_example() +{ + // default thread pool settings can be modified *before* creating the async logger: + // spdlog::init_thread_pool(8192, 1); // queue with 8k items and 1 backing thread. + auto async_file = spdlog::basic_logger_mt("async_file_logger", "logs/async_log.txt"); + // alternatively: + // auto async_file = spdlog::create_async("async_file_logger", "logs/async_log.txt"); +} + +``` + +--- +#### Asynchronous logger with multi sinks +```c++ +#include "spdlog/sinks/stdout_color_sinks.h" +#include "spdlog/sinks/rotating_file_sink.h" + +void multi_sink_example2() +{ + spdlog::init_thread_pool(8192, 1); + auto stdout_sink = std::make_shared(); + auto rotating_sink = std::make_shared("mylog.txt", 1024*1024*10, 3); + std::vector sinks {stdout_sink, rotating_sink}; + auto logger = std::make_shared("loggername", sinks.begin(), sinks.end(), spdlog::thread_pool(), spdlog::async_overflow_policy::block); + spdlog::register_logger(logger); +} +``` + +--- +#### User defined types +```c++ +// user defined types logging by implementing operator<< +#include "spdlog/fmt/ostr.h" // must be included +struct my_type +{ + int i; + template + friend OStream &operator<<(OStream &os, const my_type &c) + { + return os << "[my_type i=" << c.i << "]"; + } +}; + +void user_defined_example() +{ + spdlog::get("console")->info("user defined type: {}", my_type{14}); +} + +``` +--- +#### Custom error handler +```c++ +void err_handler_example() +{ + // can be set globally or per logger(logger->set_error_handler(..)) + spdlog::set_error_handler([](const std::string &msg) { spdlog::get("console")->error("*** LOGGER ERROR ***: {}", msg); }); + spdlog::get("console")->info("some invalid message to trigger an error {}{}{}{}", 3); +} + +``` +--- +#### syslog +```c++ +#include "spdlog/sinks/syslog_sink.h" +void syslog_example() +{ + std::string ident = "spdlog-example"; + auto syslog_logger = spdlog::syslog_logger_mt("syslog", ident, LOG_PID); + syslog_logger->warn("This is warning that will end up in syslog."); +} +``` +--- +#### Android example +```c++ +#incude "spdlog/sinks/android_sink.h" +void android_example() +{ + std::string tag = "spdlog-android"; + auto android_logger = spdlog::android_logger("android", tag); + android_logger->critical("Use \"adb shell logcat\" to view this message."); +} +``` + +## Documentation +Documentation can be found in the [wiki](https://github.com/gabime/spdlog/wiki/1.-QuickStart) pages. diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..2a176c1 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,32 @@ +version: 1.0.{build} +image: Visual Studio 2015 +environment: + matrix: + - GENERATOR: '"MinGW Makefiles"' + BUILD_TYPE: Debug + - GENERATOR: '"MinGW Makefiles"' + BUILD_TYPE: Release + - GENERATOR: '"Visual Studio 14 2015"' + BUILD_TYPE: Debug + - GENERATOR: '"Visual Studio 14 2015"' + BUILD_TYPE: Release + - GENERATOR: '"Visual Studio 14 2015 Win64"' + BUILD_TYPE: Debug + - GENERATOR: '"Visual Studio 14 2015 Win64"' + BUILD_TYPE: Release +build_script: +- cmd: >- + set + + mkdir build + + cd build + + set PATH=%PATH:C:\Program Files\Git\usr\bin;=% + + set PATH=C:\mingw-w64\i686-5.3.0-posix-dwarf-rt_v4-rev0\mingw32\bin;%PATH% + + cmake .. -G %GENERATOR% -DCMAKE_BUILD_TYPE=%BUILD_TYPE% + + cmake --build . --config %BUILD_TYPE% +test: off diff --git a/bench/CMakeLists.txt b/bench/CMakeLists.txt new file mode 100644 index 0000000..5e492c0 --- /dev/null +++ b/bench/CMakeLists.txt @@ -0,0 +1,43 @@ +# *************************************************************************/ +# * Copyright (c) 2015 Ruslan Baratov. */ +# * */ +# * 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. */ +# *************************************************************************/ + +cmake_minimum_required(VERSION 3.1) +project(SpdlogBench CXX) + +if(NOT TARGET spdlog) + # Stand-alone build + find_package(spdlog CONFIG REQUIRED) +endif() + +find_package(Threads REQUIRED) + +add_executable(bench bench.cpp) +target_link_libraries(bench spdlog::spdlog Threads::Threads) + +add_executable(async_bench async_bench.cpp) +target_link_libraries(async_bench spdlog::spdlog Threads::Threads) + +add_executable(latency latency.cpp) +target_link_libraries(latency spdlog::spdlog Threads::Threads) + +file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/logs") diff --git a/bench/Makefile b/bench/Makefile new file mode 100644 index 0000000..ae23d3a --- /dev/null +++ b/bench/Makefile @@ -0,0 +1,27 @@ +CXX ?= g++ +CXXFLAGS = -march=native -Wall -Wextra -pedantic -std=c++11 -pthread -I../include -fmax-errors=1 +CXX_RELEASE_FLAGS = -Ofast -flto -Wl,--no-as-needed + + +binaries=bench latency async_bench + +all: $(binaries) + +bench: bench.cpp + $(CXX) bench.cpp -o bench $(CXXFLAGS) $(CXX_RELEASE_FLAGS) + + +async_bench: async_bench.cpp + $(CXX) async_bench.cpp -o async_bench $(CXXFLAGS) $(CXX_RELEASE_FLAGS) + + +latency: latency.cpp + $(CXX) latency.cpp -o latency $(CXXFLAGS) $(CXX_RELEASE_FLAGS) + + +.PHONY: clean + +clean: + rm -f *.o logs/* $(binaries) + +rebuild: clean all diff --git a/bench/async_bench.cpp b/bench/async_bench.cpp new file mode 100644 index 0000000..802a07c --- /dev/null +++ b/bench/async_bench.cpp @@ -0,0 +1,138 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +// +// bench.cpp : spdlog benchmarks +// +#include "spdlog/async.h" +#include "spdlog/sinks/basic_file_sink.h" +#include "spdlog/sinks/stdout_color_sinks.h" +#include "spdlog/spdlog.h" +#include "utils.h" +#include +#include +#include +#include +#include + +using namespace std; +using namespace std::chrono; +using namespace spdlog; +using namespace spdlog::sinks; +using namespace utils; + +void bench_mt(int howmany, std::shared_ptr log, int thread_count); + +int count_lines(const char *filename) +{ + int counter = 0; + auto *infile = fopen(filename, "r"); + int ch; + while (EOF != (ch = getc(infile))) + { + if ('\n' == ch) + counter++; + } + + return counter; +} +int main(int argc, char *argv[]) +{ + + int howmany = 1000000; + int queue_size = howmany + 2; + int threads = 10; + int iters = 3; + + try + { + auto console = spdlog::stdout_color_mt("console"); + if (argc == 1) + { + console->set_pattern("%v"); + console->info("Usage: {} ", argv[0]); + return 0; + } + + if (argc > 1) + howmany = atoi(argv[1]); + if (argc > 2) + threads = atoi(argv[2]); + if (argc > 3) + queue_size = atoi(argv[3]); + + if (argc > 4) + iters = atoi(argv[4]); + + console->info("-------------------------------------------------"); + console->info("Messages: {:14n}", howmany); + console->info("Threads : {:14n}", threads); + console->info("Queue : {:14n}", queue_size); + console->info("Iters : {:>14n}", iters); + console->info("-------------------------------------------------"); + + const char *filename = "logs/basic_async.log"; + + for (int i = 0; i < iters; i++) + { + auto tp = std::make_shared(queue_size, 1); + auto file_sink = std::make_shared(filename, true); + auto logger = std::make_shared("async_logger", std::move(file_sink), std::move(tp), async_overflow_policy::block); + bench_mt(howmany, std::move(logger), threads); + auto count = count_lines(filename); + + if (count != howmany) + { + console->error("Test failed. {} has {:n} lines instead of {:n}", filename, count, howmany); + exit(1); + } + else + { + console->info("Line count OK ({:n})\n", count); + } + } + } + catch (std::exception &ex) + { + std::cerr << "Error: " << ex.what() << std::endl; + perror("Last error"); + return 1; + } + return 0; +} + +void thread_fun(std::shared_ptr logger, int howmany) +{ + for (int i = 0; i < howmany; i++) + { + logger->info("Hello logger: msg number {}", i); + } +} + +void bench_mt(int howmany, std::shared_ptr logger, int thread_count) +{ + using std::chrono::high_resolution_clock; + vector threads; + auto start = high_resolution_clock::now(); + + int msgs_per_thread = howmany / thread_count; + int msgs_per_thread_mod = howmany % thread_count; + for (int t = 0; t < thread_count; ++t) + { + if (t == 0 && msgs_per_thread_mod) + threads.push_back(std::thread(thread_fun, logger, msgs_per_thread + msgs_per_thread_mod)); + else + threads.push_back(std::thread(thread_fun, logger, msgs_per_thread)); + } + + for (auto &t : threads) + { + t.join(); + }; + + auto delta = high_resolution_clock::now() - start; + auto delta_d = duration_cast>(delta).count(); + spdlog::get("console")->info("Elapsed: {} secs\t {:n}/sec", delta_d, int(howmany / delta_d)); +} diff --git a/bench/bench.cpp b/bench/bench.cpp new file mode 100644 index 0000000..f5a8060 --- /dev/null +++ b/bench/bench.cpp @@ -0,0 +1,148 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +// +// bench.cpp : spdlog benchmarks +// +#include "spdlog/async.h" +#include "spdlog/sinks/basic_file_sink.h" +#include "spdlog/sinks/daily_file_sink.h" +#include "spdlog/sinks/null_sink.h" +#include "spdlog/sinks/rotating_file_sink.h" +#include "spdlog/spdlog.h" +#include "utils.h" +#include +#include // EXIT_FAILURE +#include +#include +#include +#include + +using namespace std; +using namespace std::chrono; +using namespace spdlog; +using namespace spdlog::sinks; +using namespace utils; + +void bench(int howmany, std::shared_ptr log); +void bench_mt(int howmany, std::shared_ptr log, int thread_count); + +int main(int argc, char *argv[]) +{ + + int howmany = 1000000; + int queue_size = howmany + 2; + int threads = 10; + int file_size = 30 * 1024 * 1024; + int rotating_files = 5; + + try + { + + if (argc > 1) + howmany = atoi(argv[1]); + if (argc > 2) + threads = atoi(argv[2]); + if (argc > 3) + queue_size = atoi(argv[3]); + + cout << "******************************************************************" + "*************\n"; + cout << "Single thread, " << format(howmany) << " iterations" << endl; + cout << "******************************************************************" + "*************\n"; + + auto basic_st = spdlog::basic_logger_mt("basic_st", "logs/basic_st.log", true); + bench(howmany, basic_st); + + auto rotating_st = spdlog::rotating_logger_st("rotating_st", "logs/rotating_st.log", file_size, rotating_files); + bench(howmany, rotating_st); + + auto daily_st = spdlog::daily_logger_st("daily_st", "logs/daily_st.log"); + bench(howmany, daily_st); + + bench(howmany, spdlog::create("null_st")); + + cout << "\n****************************************************************" + "***************\n"; + cout << threads << " threads sharing same logger, " << format(howmany) << " iterations" << endl; + cout << "******************************************************************" + "*************\n"; + + auto basic_mt = spdlog::basic_logger_mt("basic_mt", "logs/basic_mt.log", true); + bench_mt(howmany, basic_mt, threads); + + auto rotating_mt = spdlog::rotating_logger_mt("rotating_mt", "logs/rotating_mt.log", file_size, rotating_files); + bench_mt(howmany, rotating_mt, threads); + + auto daily_mt = spdlog::daily_logger_mt("daily_mt", "logs/daily_mt.log"); + bench_mt(howmany, daily_mt, threads); + bench_mt(howmany, spdlog::create("null_mt"), threads); + + cout << "\n****************************************************************" + "***************\n"; + cout << "async logging.. " << threads << " threads sharing same logger, " << format(howmany) << " iterations " << endl; + cout << "******************************************************************" + "*************\n"; + + for (int i = 0; i < 3; ++i) + { + spdlog::init_thread_pool(queue_size, 1); + auto as = spdlog::basic_logger_mt("async", "logs/basic_async.log", true); + bench_mt(howmany, as, threads); + spdlog::drop("async"); + } + } + catch (std::exception &ex) + { + std::cerr << "Error: " << ex.what() << std::endl; + perror("Last error"); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} + +void bench(int howmany, std::shared_ptr log) +{ + using std::chrono::high_resolution_clock; + cout << log->name() << "...\t\t" << flush; + auto start = high_resolution_clock::now(); + for (auto i = 0; i < howmany; ++i) + { + log->info("Hello logger: msg number {}", i); + } + + auto delta = high_resolution_clock::now() - start; + auto delta_d = duration_cast>(delta).count(); + + cout << "Elapsed: " << delta_d << "\t" << format(int(howmany / delta_d)) << "/sec" << endl; + spdlog::drop(log->name()); +} + +void bench_mt(int howmany, std::shared_ptr log, int thread_count) +{ + using std::chrono::high_resolution_clock; + cout << log->name() << "...\t\t" << flush; + vector threads; + auto start = high_resolution_clock::now(); + for (int t = 0; t < thread_count; ++t) + { + threads.push_back(std::thread([&]() { + for (int j = 0; j < howmany / thread_count; j++) + { + log->info("Hello logger: msg number {}", j); + } + })); + } + + for (auto &t : threads) + { + t.join(); + }; + + auto delta = high_resolution_clock::now() - start; + auto delta_d = duration_cast>(delta).count(); + cout << "Elapsed: " << delta_d << "\t" << format(int(howmany / delta_d)) << "/sec" << endl; +} diff --git a/bench/latency.cpp b/bench/latency.cpp new file mode 100644 index 0000000..374eafb --- /dev/null +++ b/bench/latency.cpp @@ -0,0 +1,151 @@ +// +// Copyright(c) 2018 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +// +// latency.cpp : spdlog latency benchmarks +// +#include "spdlog/async.h" +#include "spdlog/sinks/basic_file_sink.h" +#include "spdlog/sinks/daily_file_sink.h" +#include "spdlog/sinks/null_sink.h" +#include "spdlog/sinks/rotating_file_sink.h" +#include "spdlog/spdlog.h" +#include "utils.h" +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace std::chrono; +using namespace spdlog; +using namespace spdlog::sinks; +using namespace utils; + +void bench(int howmany, std::shared_ptr log); +void bench_mt(int howmany, std::shared_ptr log, int thread_count); + +int main(int, char *[]) +{ + std::srand(static_cast(std::time(nullptr))); // use current time as seed for random generator + int howmany = 1000000; + int queue_size = howmany + 2; + int threads = 10; + int file_size = 30 * 1024 * 1024; + int rotating_files = 5; + + try + { + + cout << "******************************************************************" + "*************\n"; + cout << "Single thread\n"; + cout << "******************************************************************" + "*************\n"; + + auto basic_st = spdlog::basic_logger_mt("basic_st", "logs/basic_st.log", true); + bench(howmany, basic_st); + + auto rotating_st = spdlog::rotating_logger_st("rotating_st", "logs/rotating_st.log", file_size, rotating_files); + bench(howmany, rotating_st); + + auto daily_st = spdlog::daily_logger_st("daily_st", "logs/daily_st.log"); + bench(howmany, daily_st); + + bench(howmany, spdlog::create("null_st")); + + cout << "\n****************************************************************" + "***************\n"; + cout << threads << " threads sharing same logger\n"; + cout << "******************************************************************" + "*************\n"; + + auto basic_mt = spdlog::basic_logger_mt("basic_mt", "logs/basic_mt.log", true); + bench_mt(howmany, basic_mt, threads); + + auto rotating_mt = spdlog::rotating_logger_mt("rotating_mt", "logs/rotating_mt.log", file_size, rotating_files); + bench_mt(howmany, rotating_mt, threads); + + auto daily_mt = spdlog::daily_logger_mt("daily_mt", "logs/daily_mt.log"); + bench_mt(howmany, daily_mt, threads); + bench(howmany, spdlog::create("null_mt")); + + cout << "\n****************************************************************" + "***************\n"; + cout << "async logging.. " << threads << " threads sharing same logger\n"; + cout << "******************************************************************" + "*************\n"; + + for (int i = 0; i < 3; ++i) + { + spdlog::init_thread_pool(queue_size, 1); + auto as = spdlog::basic_logger_mt("async", "logs/basic_async.log", true); + bench_mt(howmany, as, threads); + spdlog::drop("async"); + } + } + catch (std::exception &ex) + { + std::cerr << "Error: " << ex.what() << std::endl; + perror("Last error"); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} + +void bench(int howmany, std::shared_ptr log) +{ + using namespace std::chrono; + using chrono::high_resolution_clock; + using chrono::milliseconds; + using chrono::nanoseconds; + + cout << log->name() << "...\t\t" << flush; + nanoseconds total_nanos = nanoseconds::zero(); + for (auto i = 0; i < howmany; ++i) + { + auto start = high_resolution_clock::now(); + log->info("Hello logger: msg number {}", i); + auto delta_nanos = chrono::duration_cast(high_resolution_clock::now() - start); + total_nanos += delta_nanos; + } + + auto avg = total_nanos.count() / howmany; + cout << format(avg) << " ns/call" << endl; +} + +void bench_mt(int howmany, std::shared_ptr log, int thread_count) +{ + using namespace std::chrono; + using chrono::high_resolution_clock; + using chrono::milliseconds; + using chrono::nanoseconds; + + cout << log->name() << "...\t\t" << flush; + vector threads; + std::atomic total_nanos{0}; + for (int t = 0; t < thread_count; ++t) + { + threads.push_back(std::thread([&]() { + for (int j = 0; j < howmany / thread_count; j++) + { + auto start = high_resolution_clock::now(); + log->info("Hello logger: msg number {}", j); + auto delta_nanos = chrono::duration_cast(high_resolution_clock::now() - start); + total_nanos += delta_nanos.count(); + } + })); + } + + for (auto &t : threads) + { + t.join(); + }; + + auto avg = total_nanos / howmany; + cout << format(avg) << " ns/call" << endl; +} diff --git a/bench/logs/.gitignore b/bench/logs/.gitignore new file mode 100644 index 0000000..4063701 --- /dev/null +++ b/bench/logs/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore diff --git a/bench/mem b/bench/mem new file mode 100755 index 0000000..c49744e --- /dev/null +++ b/bench/mem @@ -0,0 +1,19 @@ +#!/bin/sh + +if [ $# -lt 1 ]; then + echo "usage: $0 " +fi + +PROG=$1 + +if [ ! -x "$PROG" ]; then + echo $PROG not found or not executable. + exit 1 +fi + +$* & +PID=$! + +while `kill -0 $PID 2>/dev/null`; do + ps -eo size,pid,user,pcpu,command --sort -size | awk '{ line=1 ; hr=$1/1024 ; printf("%13.2f Mb ",hr); } { for ( x=4 ; x<=NF ; x++ ) { printf("%s ",$x) } print "" }' | grep -v grep | grep -v $0 | grep $PROG +done diff --git a/bench/spdlog-async.cpp b/bench/spdlog-async.cpp new file mode 100644 index 0000000..fc4a61f --- /dev/null +++ b/bench/spdlog-async.cpp @@ -0,0 +1,77 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#include +#include +#include +#include +#include +#include + +#include "spdlog/async.h" +#include "spdlog/sinks/basic_file_sink.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + using namespace std::chrono; + using clock = steady_clock; + + int thread_count = 10; + if (argc > 1) + thread_count = std::atoi(argv[1]); + + int howmany = 1000000; + spdlog::init_thread_pool(howmany, 1); + + auto logger = spdlog::create_async_logger("file_logger", "logs/spdlog-bench-async.log", false); + logger->set_pattern("[%Y-%m-%d %T.%F]: %L %t %v"); + + std::cout << "To stop, press " << std::endl; + std::atomic run{true}; + std::thread stoper(std::thread([&run]() { + std::cin.get(); + run = false; + })); + + while (run) + { + std::atomic msg_counter{0}; + std::vector threads; + + auto start = clock::now(); + for (int t = 0; t < thread_count; ++t) + { + threads.push_back(std::thread([&]() { + while (true) + { + int counter = ++msg_counter; + if (counter > howmany) + break; + logger->info("spdlog message #{}: This is some text for your pleasure", counter); + } + })); + } + + for (auto &t : threads) + { + t.join(); + } + + duration delta = clock::now() - start; + float deltaf = delta.count(); + auto rate = howmany / deltaf; + + std::cout << "Total: " << howmany << std::endl; + std::cout << "Threads: " << thread_count << std::endl; + std::cout << "Delta = " << std::fixed << deltaf << " seconds" << std::endl; + std::cout << "Rate = " << std::fixed << rate << "/sec" << std::endl; + } // while + + stoper.join(); + + return 0; +} diff --git a/bench/utils.h b/bench/utils.h new file mode 100644 index 0000000..9161012 --- /dev/null +++ b/bench/utils.h @@ -0,0 +1,34 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include +#include +#include + +namespace utils { + +template +inline std::string format(const T &value) +{ + static std::locale loc(""); + std::stringstream ss; + ss.imbue(loc); + ss << value; + return ss.str(); +} + +template<> +inline std::string format(const double &value) +{ + static std::locale loc(""); + std::stringstream ss; + ss.imbue(loc); + ss << std::fixed << std::setprecision(1) << value; + return ss.str(); +} + +} // namespace utils diff --git a/cmake/Config.cmake.in b/cmake/Config.cmake.in new file mode 100644 index 0000000..ba0b36f --- /dev/null +++ b/cmake/Config.cmake.in @@ -0,0 +1,24 @@ +# *************************************************************************/ +# * Copyright (c) 2015 Ruslan Baratov. */ +# * */ +# * 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. */ +# *************************************************************************/ + +include("${CMAKE_CURRENT_LIST_DIR}/@targets_export_name@.cmake") diff --git a/cmake/sanitizers.cmake b/cmake/sanitizers.cmake new file mode 100644 index 0000000..3b09083 --- /dev/null +++ b/cmake/sanitizers.cmake @@ -0,0 +1,21 @@ +if(SPDLOG_SANITIZE_THREAD AND SPDLOG_SANITIZE_ADDRESS) + message(FATAL_ERROR "AddressSanitizer is not compatible with ThreadSanitizer.") +endif() + +if(SPDLOG_SANITIZE_ADDRESS) + message(STATUS "AddressSanitizer enabled") + set(SANITIZER_FLAGS "-fsanitize=address,undefined") + add_compile_options("-fno-sanitize=signed-integer-overflow") +endif() + +if(SPDLOG_SANITIZE_THREAD) + message(STATUS "ThreadSanitizer enabled") + set(SANITIZER_FLAGS "-fsanitize=thread") +endif() + +if(SPDLOG_SANITIZE_THREAD OR SPDLOG_SANITIZE_ADDRESS) + add_compile_options(${SANITIZER_FLAGS}) + add_compile_options("-fno-sanitize-recover=all") + add_compile_options("-fno-omit-frame-pointer") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${SANITIZER_FLAGS} -fuse-ld=gold") +endif() diff --git a/cmake/spdlog.pc.in b/cmake/spdlog.pc.in new file mode 100644 index 0000000..262248a --- /dev/null +++ b/cmake/spdlog.pc.in @@ -0,0 +1,6 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +includedir=${prefix}/include + +Name: @PROJECT_NAME@ +Description: Super fast C++ logging library. +Version: @PROJECT_VERSION@ diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt new file mode 100644 index 0000000..695053c --- /dev/null +++ b/example/CMakeLists.txt @@ -0,0 +1,43 @@ +# *************************************************************************/ +# * Copyright (c) 2015 Ruslan Baratov. */ +# * */ +# * 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. */ +# *************************************************************************/ + +cmake_minimum_required(VERSION 3.1) +project(SpdlogExamples CXX) + +if(NOT TARGET spdlog) + # Stand-alone build + find_package(spdlog CONFIG REQUIRED) +endif() + +find_package(Threads REQUIRED) + +add_executable(example example.cpp) +target_link_libraries(example spdlog::spdlog Threads::Threads) + +add_executable(multisink multisink.cpp) +target_link_libraries(multisink spdlog::spdlog Threads::Threads) + +file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/logs") + +enable_testing() +add_test(NAME example COMMAND example) diff --git a/example/Makefile b/example/Makefile new file mode 100644 index 0000000..01bdf50 --- /dev/null +++ b/example/Makefile @@ -0,0 +1,21 @@ +CXX ?= g++ +CXX_FLAGS = -Wall -Wextra -pedantic -std=c++11 -pthread -I../include -fmax-errors=1 +CXX_RELEASE_FLAGS = -O3 -march=native +CXX_DEBUG_FLAGS= -g + +all: example +debug: example-debug + +example: example.cpp + $(CXX) example.cpp -o example $(CXX_FLAGS) $(CXX_RELEASE_FLAGS) $(CXXFLAGS) + + +example-debug: example.cpp + $(CXX) example.cpp -o example-debug $(CXX_FLAGS) $(CXX_DEBUG_FLAGS) $(CXXFLAGS) + +clean: + rm -f *.o logs/*.txt example example-debug + + +rebuild: clean all +rebuild-debug: clean debug diff --git a/example/Makefile.clang b/example/Makefile.clang new file mode 100644 index 0000000..475855d --- /dev/null +++ b/example/Makefile.clang @@ -0,0 +1,26 @@ +CXX = clang++ +CXXFLAGS = -march=native -Wall -Wextra -Wshadow -pedantic -std=c++11 -pthread -I../include +CXX_RELEASE_FLAGS = -O2 +CXX_DEBUG_FLAGS= -g + + +all: example +debug: example-debug + +example: example.cpp + $(CXX) example.cpp -o example-clang $(CXXFLAGS) $(CXX_RELEASE_FLAGS) + + + +example-debug: example.cpp + $(CXX) example.cpp -o example-clang-debug $(CXXFLAGS) $(CXX_DEBUG_FLAGS) + + +clean: + rm -f *.o logs/*.txt example-clang example-clang-debug + + +rebuild: clean all +rebuild-debug: clean debug + + diff --git a/example/Makefile.mingw b/example/Makefile.mingw new file mode 100644 index 0000000..247a818 --- /dev/null +++ b/example/Makefile.mingw @@ -0,0 +1,25 @@ +CXX ?= g++ +CXXFLAGS = -D_WIN32_WINNT=0x600 -march=native -Wall -Wextra -pedantic -std=gnu++0x -pthread -Wl,--no-as-needed -I../include +CXX_RELEASE_FLAGS = -O3 +CXX_DEBUG_FLAGS= -g + + +all: example +debug: example-debug + +example: example.cpp + $(CXX) example.cpp -o example $(CXXFLAGS) $(CXX_RELEASE_FLAGS) + + +example-debug: example.cpp + $(CXX) example.cpp -o example-debug $(CXXFLAGS) $(CXX_DEBUG_FLAGS) + + +clean: + rm -f *.o logs/*.txt example example-debug + + +rebuild: clean all +rebuild-debug: clean debug + + diff --git a/example/example.cpp b/example/example.cpp new file mode 100644 index 0000000..4222c82 --- /dev/null +++ b/example/example.cpp @@ -0,0 +1,205 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// +// +// spdlog usage example +// +// + +#include + +void stdout_example(); +void basic_example(); +void rotating_example(); +void daily_example(); +void async_example(); +void multi_sink_example(); +void user_defined_example(); +void err_handler_example(); +void syslog_example(); + +#include "spdlog/spdlog.h" + +int main(int, char *[]) +{ + + try + { + // console logging example + stdout_example(); + + // various file loggers + basic_example(); + rotating_example(); + daily_example(); + + // async logging using a backing thread pool + async_example(); + + // a logger can have multiple targets with different formats + multi_sink_example(); + + // user defined types logging by implementing operator<< + user_defined_example(); + + // custom error handler + err_handler_example(); + + // flush all *registered* loggers using a worker thread every 3 seconds. + // note: registered loggers *must* be thread safe for this to work correctly! + spdlog::flush_every(std::chrono::seconds(3)); + + // apply some function on all registered loggers + spdlog::apply_all([&](std::shared_ptr l) { l->info("End of example."); }); + + // release any threads created by spdlog, and drop all loggers in the registry. + spdlog::shutdown(); + } + // Exceptions will only be thrown upon failed logger or sink construction (not during logging) + catch (const spdlog::spdlog_ex &ex) + { + std::cout << "Log init failed: " << ex.what() << std::endl; + return 1; + } +} + +#include "spdlog/sinks/stdout_color_sinks.h" +// or #include "spdlog/sinks/stdout_sinks.h" if no colors needed +void stdout_example() +{ + // create color multi threaded logger + auto console = spdlog::stdout_color_mt("console"); + console->info("Welcome to spdlog version {}.{}.{} !", SPDLOG_VER_MAJOR, SPDLOG_VER_MINOR, SPDLOG_VER_PATCH); + console->error("Some error message with arg: {}", 1); + + auto err_logger = spdlog::stderr_color_mt("stderr"); + err_logger->error("Some error message"); + + // Formatting examples + console->warn("Easy padding in numbers like {:08d}", 12); + console->critical("Support for int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42); + console->info("Support for floats {:03.2f}", 1.23456); + console->info("Positional args are {1} {0}..", "too", "supported"); + console->info("{:<30}", "left aligned"); + + spdlog::get("console")->info("loggers can be retrieved from a global registry using the spdlog::get(logger_name)"); + + // Runtime log levels + spdlog::set_level(spdlog::level::info); // Set global log level to info + console->debug("This message should not be displayed!"); + console->set_level(spdlog::level::trace); // Set specific logger's log level + console->debug("This message should be displayed.."); + + // Customize msg format for all loggers + spdlog::set_pattern("[%H:%M:%S %z] [%n] [%^---%L---%$] [thread %t] %v"); + console->info("This an info message with custom format"); + + // Compile time log levels + // define SPDLOG_DEBUG_ON or SPDLOG_TRACE_ON + SPDLOG_TRACE(console, "Enabled only #ifdef SPDLOG_TRACE_ON..{} ,{}", 1, 3.23); + SPDLOG_DEBUG(console, "Enabled only #ifdef SPDLOG_DEBUG_ON.. {} ,{}", 1, 3.23); +} + +#include "spdlog/sinks/basic_file_sink.h" +void basic_example() +{ + // Create basic file logger (not rotated) + auto my_logger = spdlog::basic_logger_mt("basic_logger", "logs/basic-log.txt"); +} + +#include "spdlog/sinks/rotating_file_sink.h" +void rotating_example() +{ + // Create a file rotating logger with 5mb size max and 3 rotated files + auto rotating_logger = spdlog::rotating_logger_mt("some_logger_name", "logs/rotating.txt", 1048576 * 5, 3); +} + +#include "spdlog/sinks/daily_file_sink.h" +void daily_example() +{ + // Create a daily logger - a new file is created every day on 2:30am + auto daily_logger = spdlog::daily_logger_mt("daily_logger", "logs/daily.txt", 2, 30); +} + +#include "spdlog/async.h" +void async_example() +{ + // default thread pool settings can be modified *before* creating the async logger: + // spdlog::init_thread_pool(32768, 1); // queue with max 32k items 1 backing thread. + auto async_file = spdlog::basic_logger_mt("async_file_logger", "logs/async_log.txt"); + // alternatively: + // auto async_file = spdlog::create_async("async_file_logger", "logs/async_log.txt"); + + for (int i = 1; i < 101; ++i) + { + async_file->info("Async message #{}", i); + } +} + +// create logger with 2 targets with different log levels and formats +// the console will show only warnings or errors, while the file will log all + +void multi_sink_example() +{ + auto console_sink = std::make_shared(); + console_sink->set_level(spdlog::level::warn); + console_sink->set_pattern("[multi_sink_example] [%^%l%$] %v"); + + auto file_sink = std::make_shared("logs/multisink.txt", true); + file_sink->set_level(spdlog::level::trace); + + spdlog::logger logger("multi_sink", {console_sink, file_sink}); + logger.set_level(spdlog::level::debug); + logger.warn("this should appear in both console and file"); + logger.info("this message should not appear in the console, only in the file"); +} +// user defined types logging by implementing operator<< +#include "spdlog/fmt/ostr.h" // must be included +struct my_type +{ + int i; + template + friend OStream &operator<<(OStream &os, const my_type &c) + { + return os << "[my_type i=" << c.i << "]"; + } +}; + +void user_defined_example() +{ + spdlog::get("console")->info("user defined type: {}", my_type{14}); +} + +// +// custom error handler +// +void err_handler_example() +{ + // can be set globally or per logger(logger->set_error_handler(..)) + spdlog::set_error_handler([](const std::string &msg) { spdlog::get("console")->error("*** ERROR HANDLER EXAMPLE ***: {}", msg); }); + spdlog::get("console")->info("some invalid message to trigger an error {}{}{}{}", 3); +} + +// syslog example (linux/osx/freebsd) +#ifndef _WIN32 +#include "spdlog/sinks/syslog_sink.h" +void syslog_example() +{ + std::string ident = "spdlog-example"; + auto syslog_logger = spdlog::syslog_logger_mt("syslog", ident, LOG_PID); + syslog_logger->warn("This is warning that will end up in syslog."); +} +#endif + +// Android example +#if defined(__ANDROID__) +#include "spdlog/sinks/android_sink.h" +void android_example() +{ + std::string tag = "spdlog-android"; + auto android_logger = spdlog::android_logger_mt("android", tag); + android_logger->critical("Use \"adb shell logcat\" to view this message."); +} + +#endif diff --git a/example/example.sln b/example/example.sln new file mode 100644 index 0000000..b29a9e4 --- /dev/null +++ b/example/example.sln @@ -0,0 +1,106 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27703.2018 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "example", "example.vcxproj", "{9E5AB93A-0CCE-4BAC-9FCB-0FC9CB5EB8D2}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "spdlog", "spdlog", "{7FC6AB76-AD88-4135-888C-0568E81475AF}" + ProjectSection(SolutionItems) = preProject + ..\include\spdlog\async.h = ..\include\spdlog\async.h + ..\include\spdlog\async_logger.h = ..\include\spdlog\async_logger.h + ..\include\spdlog\common.h = ..\include\spdlog\common.h + ..\include\spdlog\formatter.h = ..\include\spdlog\formatter.h + ..\include\spdlog\logger.h = ..\include\spdlog\logger.h + ..\include\spdlog\spdlog.h = ..\include\spdlog\spdlog.h + ..\include\spdlog\tweakme.h = ..\include\spdlog\tweakme.h + ..\include\spdlog\version.h = ..\include\spdlog\version.h + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "details", "details", "{08E93803-E650-42D9-BBB4-3C16979F850E}" + ProjectSection(SolutionItems) = preProject + ..\include\spdlog\details\async_logger_impl.h = ..\include\spdlog\details\async_logger_impl.h + ..\include\spdlog\details\circular_q.h = ..\include\spdlog\details\circular_q.h + ..\include\spdlog\details\console_globals.h = ..\include\spdlog\details\console_globals.h + ..\include\spdlog\details\file_helper.h = ..\include\spdlog\details\file_helper.h + ..\include\spdlog\details\fmt_helper.h = ..\include\spdlog\details\fmt_helper.h + ..\include\spdlog\details\log_msg.h = ..\include\spdlog\details\log_msg.h + ..\include\spdlog\details\logger_impl.h = ..\include\spdlog\details\logger_impl.h + ..\include\spdlog\details\mpmc_blocking_q.h = ..\include\spdlog\details\mpmc_blocking_q.h + ..\include\spdlog\details\null_mutex.h = ..\include\spdlog\details\null_mutex.h + ..\include\spdlog\details\os.h = ..\include\spdlog\details\os.h + ..\include\spdlog\details\pattern_formatter.h = ..\include\spdlog\details\pattern_formatter.h + ..\include\spdlog\details\periodic_worker.h = ..\include\spdlog\details\periodic_worker.h + ..\include\spdlog\details\registry.h = ..\include\spdlog\details\registry.h + ..\include\spdlog\details\spdlog_impl.h = ..\include\spdlog\details\spdlog_impl.h + ..\include\spdlog\details\thread_pool.h = ..\include\spdlog\details\thread_pool.h + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "fmt", "fmt", "{82378DE1-8463-4F91-91A0-C2C40E2AEA2A}" + ProjectSection(SolutionItems) = preProject + ..\include\spdlog\fmt\fmt.h = ..\include\spdlog\fmt\fmt.h + ..\include\spdlog\fmt\ostr.h = ..\include\spdlog\fmt\ostr.h + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "bundled", "bundled", "{D9CA4494-80D1-48D1-A897-D3564F7B27FF}" + ProjectSection(SolutionItems) = preProject + ..\include\spdlog\fmt\bundled\format.cc = ..\include\spdlog\fmt\bundled\format.cc + ..\include\spdlog\fmt\bundled\format.h = ..\include\spdlog\fmt\bundled\format.h + ..\include\spdlog\fmt\bundled\LICENSE.rst = ..\include\spdlog\fmt\bundled\LICENSE.rst + ..\include\spdlog\fmt\bundled\ostream.cc = ..\include\spdlog\fmt\bundled\ostream.cc + ..\include\spdlog\fmt\bundled\ostream.h = ..\include\spdlog\fmt\bundled\ostream.h + ..\include\spdlog\fmt\bundled\posix.cc = ..\include\spdlog\fmt\bundled\posix.cc + ..\include\spdlog\fmt\bundled\posix.h = ..\include\spdlog\fmt\bundled\posix.h + ..\include\spdlog\fmt\bundled\printf.cc = ..\include\spdlog\fmt\bundled\printf.cc + ..\include\spdlog\fmt\bundled\printf.h = ..\include\spdlog\fmt\bundled\printf.h + ..\include\spdlog\fmt\bundled\time.h = ..\include\spdlog\fmt\bundled\time.h + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sinks", "sinks", "{27D16BB9-2B81-4F61-80EC-0C7A777248E4}" + ProjectSection(SolutionItems) = preProject + ..\include\spdlog\sinks\android_sink.h = ..\include\spdlog\sinks\android_sink.h + ..\include\spdlog\sinks\ansicolor_sink.h = ..\include\spdlog\sinks\ansicolor_sink.h + ..\include\spdlog\sinks\base_sink.h = ..\include\spdlog\sinks\base_sink.h + ..\include\spdlog\sinks\dist_sink.h = ..\include\spdlog\sinks\dist_sink.h + ..\include\spdlog\sinks\file_sinks.h = ..\include\spdlog\sinks\file_sinks.h + ..\include\spdlog\sinks\msvc_sink.h = ..\include\spdlog\sinks\msvc_sink.h + ..\include\spdlog\sinks\null_sink.h = ..\include\spdlog\sinks\null_sink.h + ..\include\spdlog\sinks\ostream_sink.h = ..\include\spdlog\sinks\ostream_sink.h + ..\include\spdlog\sinks\sink.h = ..\include\spdlog\sinks\sink.h + ..\include\spdlog\sinks\stdout_color_sinks.h = ..\include\spdlog\sinks\stdout_color_sinks.h + ..\include\spdlog\sinks\stdout_sinks.h = ..\include\spdlog\sinks\stdout_sinks.h + ..\include\spdlog\sinks\syslog_sink.h = ..\include\spdlog\sinks\syslog_sink.h + ..\include\spdlog\sinks\wincolor_sink.h = ..\include\spdlog\sinks\wincolor_sink.h + ..\include\spdlog\sinks\windebug_sink.h = ..\include\spdlog\sinks\windebug_sink.h + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9E5AB93A-0CCE-4BAC-9FCB-0FC9CB5EB8D2}.Debug|Win32.ActiveCfg = Debug|Win32 + {9E5AB93A-0CCE-4BAC-9FCB-0FC9CB5EB8D2}.Debug|Win32.Build.0 = Debug|Win32 + {9E5AB93A-0CCE-4BAC-9FCB-0FC9CB5EB8D2}.Debug|x64.ActiveCfg = Debug|x64 + {9E5AB93A-0CCE-4BAC-9FCB-0FC9CB5EB8D2}.Debug|x64.Build.0 = Debug|x64 + {9E5AB93A-0CCE-4BAC-9FCB-0FC9CB5EB8D2}.Release|Win32.ActiveCfg = Release|Win32 + {9E5AB93A-0CCE-4BAC-9FCB-0FC9CB5EB8D2}.Release|Win32.Build.0 = Release|Win32 + {9E5AB93A-0CCE-4BAC-9FCB-0FC9CB5EB8D2}.Release|x64.ActiveCfg = Release|x64 + {9E5AB93A-0CCE-4BAC-9FCB-0FC9CB5EB8D2}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {08E93803-E650-42D9-BBB4-3C16979F850E} = {7FC6AB76-AD88-4135-888C-0568E81475AF} + {82378DE1-8463-4F91-91A0-C2C40E2AEA2A} = {7FC6AB76-AD88-4135-888C-0568E81475AF} + {D9CA4494-80D1-48D1-A897-D3564F7B27FF} = {82378DE1-8463-4F91-91A0-C2C40E2AEA2A} + {27D16BB9-2B81-4F61-80EC-0C7A777248E4} = {7FC6AB76-AD88-4135-888C-0568E81475AF} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {1BF53532-C5DC-4236-B195-9E17CBE40A48} + EndGlobalSection +EndGlobal diff --git a/example/example.vcxproj b/example/example.vcxproj new file mode 100644 index 0000000..0cedd62 --- /dev/null +++ b/example/example.vcxproj @@ -0,0 +1,168 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + + + + {9E5AB93A-0CCE-4BAC-9FCB-0FC9CB5EB8D2} + Win32Proj + . + 10.0.16299.0 + + + + Application + true + v141 + Unicode + + + Application + true + v141 + Unicode + + + Application + false + v141 + true + Unicode + + + Application + false + v141 + true + Unicode + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + ..\include;%(AdditionalIncludeDirectories) + + + + + Console + true + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + ..\include;%(AdditionalIncludeDirectories) + + + + + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + ..\include;%(AdditionalIncludeDirectories) + + + + + Console + true + true + true + %(AdditionalLibraryDirectories) + %(AdditionalDependencies) + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + ..\include;%(AdditionalIncludeDirectories) + + + + + + + Console + true + true + true + %(AdditionalLibraryDirectories) + %(AdditionalDependencies) + + + + + + \ No newline at end of file diff --git a/example/jni/Android.mk b/example/jni/Android.mk new file mode 100644 index 0000000..7accbad --- /dev/null +++ b/example/jni/Android.mk @@ -0,0 +1,15 @@ +# Setup a project +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE := example +LOCAL_SRC_FILES := example.cpp +LOCAL_CPPFLAGS += -Wall -Wshadow -Wextra -pedantic -std=c++11 -fPIE -pie +LOCAL_LDFLAGS += -fPIE -pie + +# Add exception support and set path for spdlog's headers +LOCAL_CPPFLAGS += -fexceptions -I../include +# Use android's log library +LOCAL_LDFLAGS += -llog + +include $(BUILD_EXECUTABLE) diff --git a/example/jni/Application.mk b/example/jni/Application.mk new file mode 100644 index 0000000..dccd2a5 --- /dev/null +++ b/example/jni/Application.mk @@ -0,0 +1,2 @@ +# Exceptions are used in spdlog. Link to an exception-ready C++ runtime. +APP_STL = gnustl_static diff --git a/example/jni/example.cpp b/example/jni/example.cpp new file mode 100644 index 0000000..48c4b19 --- /dev/null +++ b/example/jni/example.cpp @@ -0,0 +1,157 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// +// +// spdlog usage example +// +// + +#define SPDLOG_TRACE_ON +#define SPDLOG_DEBUG_ON + +#include "spdlog/spdlog.h" + +#include +#include + +void async_example(); +void syslog_example(); +void android_example(); +void user_defined_example(); +void err_handler_example(); + +namespace spd = spdlog; +int main(int, char *[]) +{ + try + { + // Console logger with color + auto console = spd::stdout_color_mt("console"); + console->info("Welcome to spdlog!"); + console->error("Some error message with arg{}..", 1); + + // Formatting examples + console->warn("Easy padding in numbers like {:08d}", 12); + console->critical("Support for int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42); + console->info("Support for floats {:03.2f}", 1.23456); + console->info("Positional args are {1} {0}..", "too", "supported"); + console->info("{:<30}", "left aligned"); + + spd::get("console")->info("loggers can be retrieved from a global registry using the spdlog::get(logger_name) function"); + + // Create basic file logger (not rotated) + auto my_logger = spd::basic_logger_mt("basic_logger", "logs/basic-log.txt"); + my_logger->info("Some log message"); + + // Create a file rotating logger with 5mb size max and 3 rotated files + auto rotating_logger = spd::rotating_logger_mt("some_logger_name", "logs/rotating.txt", 1048576 * 5, 3); + for (int i = 0; i < 10; ++i) + rotating_logger->info("{} * {} equals {:>10}", i, i, i * i); + + // Create a daily logger - a new file is created every day on 2:30am + auto daily_logger = spd::daily_logger_mt("daily_logger", "logs/daily.txt", 2, 30); + // trigger flush if the log severity is error or higher + daily_logger->flush_on(spd::level::err); + daily_logger->info(123.44); + + // Customize msg format for all messages + spd::set_pattern("*** [%H:%M:%S %z] [thread %t] %v ***"); + rotating_logger->info("This is another message with custom format"); + + // Runtime log levels + spd::set_level(spd::level::info); // Set global log level to info + console->debug("This message should not be displayed!"); + console->set_level(spd::level::debug); // Set specific logger's log level + console->debug("This message should be displayed.."); + + // Compile time log levels + // define SPDLOG_DEBUG_ON or SPDLOG_TRACE_ON + SPDLOG_TRACE(console, "Enabled only #ifdef SPDLOG_TRACE_ON..{} ,{}", 1, 3.23); + SPDLOG_DEBUG(console, "Enabled only #ifdef SPDLOG_DEBUG_ON.. {} ,{}", 1, 3.23); + + // Asynchronous logging is very fast.. + // Just call spdlog::set_async_mode(q_size) and all created loggers from now on will be asynchronous.. + async_example(); + + // syslog example. linux/osx only + syslog_example(); + + // android example. compile with NDK + android_example(); + + // Log user-defined types example + user_defined_example(); + + // Change default log error handler + err_handler_example(); + + // Apply a function on all registered loggers + spd::apply_all([&](std::shared_ptr l) { l->info("End of example."); }); + + // Release and close all loggers + spdlog::drop_all(); + } + // Exceptions will only be thrown upon failed logger or sink construction (not during logging) + catch (const spd::spdlog_ex &ex) + { + std::cout << "Log init failed: " << ex.what() << std::endl; + return 1; + } +} + +void async_example() +{ + size_t q_size = 4096; // queue size must be power of 2 + spdlog::set_async_mode(q_size); + auto async_file = spd::daily_logger_st("async_file_logger", "logs/async_log.txt"); + for (int i = 0; i < 100; ++i) + async_file->info("Async message #{}", i); +} + +// syslog example (linux/osx/freebsd) +void syslog_example() +{ +#ifdef SPDLOG_ENABLE_SYSLOG + std::string ident = "spdlog-example"; + auto syslog_logger = spd::syslog_logger("syslog", ident, LOG_PID); + syslog_logger->warn("This is warning that will end up in syslog."); +#endif +} + +// Android example +void android_example() +{ +#if defined(__ANDROID__) + std::string tag = "spdlog-android"; + auto android_logger = spd::android_logger("android", tag); + android_logger->critical("Use \"adb shell logcat\" to view this message."); +#endif +} + +// user defined types logging by implementing operator<< +struct my_type +{ + int i; + template + friend OStream &operator<<(OStream &os, const my_type &c) + { + return os << "[my_type i=" << c.i << "]"; + } +}; + +#include "spdlog/fmt/ostr.h" // must be included +void user_defined_example() +{ + spd::get("console")->info("user defined type: {}", my_type{14}); +} + +// +// custom error handler +// +void err_handler_example() +{ + // can be set globaly or per logger(logger->set_error_handler(..)) + spdlog::set_error_handler([](const std::string &msg) { std::cerr << "my err handler: " << msg << std::endl; }); + spd::get("console")->info("some invalid message to trigger an error {}{}{}{}", 3); +} diff --git a/example/logs/.gitignore b/example/logs/.gitignore new file mode 100644 index 0000000..2032513 --- /dev/null +++ b/example/logs/.gitignore @@ -0,0 +1 @@ +*.txt diff --git a/example/multisink.cpp b/example/multisink.cpp new file mode 100644 index 0000000..7dc267d --- /dev/null +++ b/example/multisink.cpp @@ -0,0 +1,46 @@ +#include "spdlog/sinks/basic_file_sink.h" +#include "spdlog/sinks/stdout_sinks.h" +#include "spdlog/spdlog.h" +#include +#include + +int main(int, char *[]) +{ + bool enable_debug = true; + try + { + // This other example use a single logger with multiple sinks. + // This means that the same log_msg is forwarded to multiple sinks; + // Each sink can have it's own log level and a message will be logged. + std::vector sinks; + sinks.push_back(std::make_shared()); + sinks.push_back(std::make_shared("./log_regular_file.txt")); + sinks.push_back(std::make_shared("./log_debug_file.txt")); + + spdlog::logger console_multisink("multisink", sinks.begin(), sinks.end()); + console_multisink.set_level(spdlog::level::warn); + + sinks[0]->set_level(spdlog::level::trace); // console. Allow everything. Default value + sinks[1]->set_level(spdlog::level::trace); // regular file. Allow everything. Default value + sinks[2]->set_level(spdlog::level::off); // regular file. Ignore everything. + + console_multisink.warn("warn: will print only on console and regular file"); + + if (enable_debug) + { + console_multisink.set_level(spdlog::level::debug); // level of the logger + sinks[1]->set_level(spdlog::level::debug); // regular file + sinks[2]->set_level(spdlog::level::debug); // debug file + } + console_multisink.debug("Debug: you should see this on console and both files"); + + // Release and close all loggers + spdlog::drop_all(); + } + // Exceptions will only be thrown upon failed logger or sink construction (not during logging) + catch (const spdlog::spdlog_ex &ex) + { + std::cout << "Log init failed: " << ex.what() << std::endl; + return 1; + } +} diff --git a/example/utils.h b/example/utils.h new file mode 100644 index 0000000..9161012 --- /dev/null +++ b/example/utils.h @@ -0,0 +1,34 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include +#include +#include + +namespace utils { + +template +inline std::string format(const T &value) +{ + static std::locale loc(""); + std::stringstream ss; + ss.imbue(loc); + ss << value; + return ss.str(); +} + +template<> +inline std::string format(const double &value) +{ + static std::locale loc(""); + std::stringstream ss; + ss.imbue(loc); + ss << std::fixed << std::setprecision(1) << value; + return ss.str(); +} + +} // namespace utils diff --git a/format.sh b/format.sh new file mode 100755 index 0000000..7bb076d --- /dev/null +++ b/format.sh @@ -0,0 +1,9 @@ +#!/bin/bash +echo -n "Running dos2unix " +find . -name "*\.h" -o -name "*\.cpp"|xargs -I {} sh -c "dos2unix '{}' 2>/dev/null; echo -n '.'" +echo +echo -n "Running clang-format " +find . -name "*\.h" -o -name "*\.cpp"|xargs -I {} sh -c "clang-format -i {}; echo -n '.'" +echo + + diff --git a/include/spdlog/async.h b/include/spdlog/async.h new file mode 100644 index 0000000..9c3e955 --- /dev/null +++ b/include/spdlog/async.h @@ -0,0 +1,87 @@ + +// +// Copyright(c) 2018 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +// +// Async logging using global thread pool +// All loggers created here share same global thread pool. +// Each log message is pushed to a queue along withe a shared pointer to the +// logger. +// If a logger deleted while having pending messages in the queue, it's actual +// destruction will defer +// until all its messages are processed by the thread pool. +// This is because each message in the queue holds a shared_ptr to the +// originating logger. + +#include "spdlog/async_logger.h" +#include "spdlog/details/registry.h" +#include "spdlog/details/thread_pool.h" + +#include +#include + +namespace spdlog { + +namespace details { +static const size_t default_async_q_size = 8192; +} + +// async logger factory - creates async loggers backed with thread pool. +// if a global thread pool doesn't already exist, create it with default queue +// size of 8192 items and single thread. +template +struct async_factory_impl +{ + template + static std::shared_ptr create(const std::string &logger_name, SinkArgs &&... args) + { + auto ®istry_inst = details::registry::instance(); + + // create global thread pool if not already exists.. + std::lock_guard tp_lock(registry_inst.tp_mutex()); + auto tp = registry_inst.get_tp(); + if (tp == nullptr) + { + tp = std::make_shared(details::default_async_q_size, 1); + registry_inst.set_tp(tp); + } + + auto sink = std::make_shared(std::forward(args)...); + auto new_logger = std::make_shared(logger_name, std::move(sink), std::move(tp), OverflowPolicy); + registry_inst.register_and_init(new_logger); + return new_logger; + } +}; + +using async_factory = async_factory_impl; +using async_factory_nonblock = async_factory_impl; + +template +inline std::shared_ptr create_async(const std::string &logger_name, SinkArgs &&... sink_args) +{ + return async_factory::create(logger_name, std::forward(sink_args)...); +} + +template +inline std::shared_ptr create_async_nb(const std::string &logger_name, SinkArgs &&... sink_args) +{ + return async_factory_nonblock::create(logger_name, std::forward(sink_args)...); +} + +// set global thread pool. +inline void init_thread_pool(size_t q_size, size_t thread_count) +{ + auto tp = std::make_shared(q_size, thread_count); + details::registry::instance().set_tp(std::move(tp)); +} + +// get the global thread pool. +inline std::shared_ptr thread_pool() +{ + return details::registry::instance().get_tp(); +} +} // namespace spdlog diff --git a/include/spdlog/async_logger.h b/include/spdlog/async_logger.h new file mode 100644 index 0000000..3250f4a --- /dev/null +++ b/include/spdlog/async_logger.h @@ -0,0 +1,71 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +// Very fast asynchronous logger (millions of logs per second on an average +// desktop) +// Uses pre allocated lockfree queue for maximum throughput even under large +// number of threads. +// Creates a single back thread to pop messages from the queue and log them. +// +// Upon each log write the logger: +// 1. Checks if its log level is enough to log the message +// 2. Push a new copy of the message to a queue (or block the caller until +// space is available in the queue) +// 3. will throw spdlog_ex upon log exceptions +// Upon destruction, logs all remaining messages in the queue before +// destructing.. + +#include "spdlog/common.h" +#include "spdlog/logger.h" + +#include +#include +#include + +namespace spdlog { + +// Async overflow policy - block by default. +enum class async_overflow_policy +{ + block, // Block until message can be enqueued + overrun_oldest // Discard oldest message in the queue if full when trying to + // add new item. +}; + +namespace details { +class thread_pool; +} + +class async_logger SPDLOG_FINAL : public std::enable_shared_from_this, public logger +{ + friend class details::thread_pool; + +public: + template + async_logger(std::string logger_name, const It &begin, const It &end, std::weak_ptr tp, + async_overflow_policy overflow_policy = async_overflow_policy::block); + + async_logger(std::string logger_name, sinks_init_list sinks_list, std::weak_ptr tp, + async_overflow_policy overflow_policy = async_overflow_policy::block); + + async_logger(std::string logger_name, sink_ptr single_sink, std::weak_ptr tp, + async_overflow_policy overflow_policy = async_overflow_policy::block); + +protected: + void sink_it_(details::log_msg &msg) override; + void flush_() override; + + void backend_log_(details::log_msg &incoming_log_msg); + void backend_flush_(); + +private: + std::weak_ptr thread_pool_; + async_overflow_policy overflow_policy_; +}; +} // namespace spdlog + +#include "details/async_logger_impl.h" diff --git a/include/spdlog/common.h b/include/spdlog/common.h new file mode 100644 index 0000000..1542fa9 --- /dev/null +++ b/include/spdlog/common.h @@ -0,0 +1,177 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "spdlog/tweakme.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(SPDLOG_WCHAR_FILENAMES) || defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) +#include +#include +#endif + +#include "spdlog/details/null_mutex.h" + +// visual studio upto 2013 does not support noexcept nor constexpr +#if defined(_MSC_VER) && (_MSC_VER < 1900) +#define SPDLOG_NOEXCEPT throw() +#define SPDLOG_CONSTEXPR +#else +#define SPDLOG_NOEXCEPT noexcept +#define SPDLOG_CONSTEXPR constexpr +#endif + +// final keyword support. On by default. See tweakme.h +#if defined(SPDLOG_NO_FINAL) +#define SPDLOG_FINAL +#else +#define SPDLOG_FINAL final +#endif + +#if defined(__GNUC__) || defined(__clang__) +#define SPDLOG_DEPRECATED __attribute__((deprecated)) +#elif defined(_MSC_VER) +#define SPDLOG_DEPRECATED __declspec(deprecated) +#else +#define SPDLOG_DEPRECATED +#endif + +#include "spdlog/fmt/fmt.h" + +namespace spdlog { + +class formatter; + +namespace sinks { +class sink; +} + +using log_clock = std::chrono::system_clock; +using sink_ptr = std::shared_ptr; +using sinks_init_list = std::initializer_list; +using log_err_handler = std::function; + +#if defined(SPDLOG_NO_ATOMIC_LEVELS) +using level_t = details::null_atomic_int; +#else +using level_t = std::atomic; +#endif + +// Log level enum +namespace level { +enum level_enum +{ + trace = 0, + debug = 1, + info = 2, + warn = 3, + err = 4, + critical = 5, + off = 6 +}; + +#if !defined(SPDLOG_LEVEL_NAMES) +#define SPDLOG_LEVEL_NAMES \ + { \ + "trace", "debug", "info", "warning", "error", "critical", "off" \ + } +#endif +static const char *level_names[] SPDLOG_LEVEL_NAMES; + +static const char *short_level_names[]{"T", "D", "I", "W", "E", "C", "O"}; + +inline const char *to_c_str(spdlog::level::level_enum l) +{ + return level_names[l]; +} + +inline const char *to_short_c_str(spdlog::level::level_enum l) +{ + return short_level_names[l]; +} + +inline spdlog::level::level_enum from_str(const std::string &name) +{ + static std::unordered_map name_to_level = // map string->level + {{level_names[0], level::trace}, // trace + {level_names[1], level::debug}, // debug + {level_names[2], level::info}, // info + {level_names[3], level::warn}, // warn + {level_names[4], level::err}, // err + {level_names[5], level::critical}, // critical + {level_names[6], level::off}}; // off + + auto lvl_it = name_to_level.find(name); + return lvl_it != name_to_level.end() ? lvl_it->second : level::off; +} + +using level_hasher = std::hash; +} // namespace level + +// +// Pattern time - specific time getting to use for pattern_formatter. +// local time by default +// +enum class pattern_time_type +{ + local, // log localtime + utc // log utc +}; + +// +// Log exception +// +class spdlog_ex : public std::exception +{ +public: + explicit spdlog_ex(const std::string &msg) + : msg_(msg) + { + } + + spdlog_ex(const std::string &msg, int last_errno) + { + fmt::memory_buffer outbuf; + fmt::format_system_error(outbuf, last_errno, msg); + msg_ = fmt::to_string(outbuf); + } + + const char *what() const SPDLOG_NOEXCEPT override + { + return msg_.c_str(); + } + +private: + std::string msg_; +}; + +// +// wchar support for windows file names (SPDLOG_WCHAR_FILENAMES must be defined) +// +#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) +using filename_t = std::wstring; +#else +using filename_t = std::string; +#endif + +#define SPDLOG_CATCH_AND_HANDLE \ + catch (const std::exception &ex) \ + { \ + err_handler_(ex.what()); \ + } \ + catch (...) \ + { \ + err_handler_("Unknown exeption in logger"); \ + } +} // namespace spdlog diff --git a/include/spdlog/details/async_logger_impl.h b/include/spdlog/details/async_logger_impl.h new file mode 100644 index 0000000..47659dd --- /dev/null +++ b/include/spdlog/details/async_logger_impl.h @@ -0,0 +1,100 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +// async logger implementation +// uses a thread pool to perform the actual logging + +#include "spdlog/details/thread_pool.h" + +#include +#include +#include + +template +inline spdlog::async_logger::async_logger( + std::string logger_name, const It &begin, const It &end, std::weak_ptr tp, async_overflow_policy overflow_policy) + : logger(std::move(logger_name), begin, end) + , thread_pool_(tp) + , overflow_policy_(overflow_policy) +{ +} + +inline spdlog::async_logger::async_logger( + std::string logger_name, sinks_init_list sinks_list, std::weak_ptr tp, async_overflow_policy overflow_policy) + : async_logger(std::move(logger_name), sinks_list.begin(), sinks_list.end(), tp, overflow_policy) +{ +} + +inline spdlog::async_logger::async_logger( + std::string logger_name, sink_ptr single_sink, std::weak_ptr tp, async_overflow_policy overflow_policy) + : async_logger(std::move(logger_name), {single_sink}, tp, overflow_policy) +{ +} + +// send the log message to the thread pool +inline void spdlog::async_logger::sink_it_(details::log_msg &msg) +{ +#if defined(SPDLOG_ENABLE_MESSAGE_COUNTER) + incr_msg_counter_(msg); +#endif + if (auto pool_ptr = thread_pool_.lock()) + { + pool_ptr->post_log(shared_from_this(), std::move(msg), overflow_policy_); + } + else + { + throw spdlog_ex("async log: thread pool doesn't exist anymore"); + } +} + +// send flush request to the thread pool +inline void spdlog::async_logger::flush_() +{ + if (auto pool_ptr = thread_pool_.lock()) + { + pool_ptr->post_flush(shared_from_this(), overflow_policy_); + } + else + { + throw spdlog_ex("async flush: thread pool doesn't exist anymore"); + } +} + +// +// backend functions - called from the thread pool to do the actual job +// +inline void spdlog::async_logger::backend_log_(details::log_msg &incoming_log_msg) +{ + try + { + for (auto &s : sinks_) + { + if (s->should_log(incoming_log_msg.level)) + { + s->log(incoming_log_msg); + } + } + } + SPDLOG_CATCH_AND_HANDLE + + if (should_flush_(incoming_log_msg)) + { + backend_flush_(); + } +} + +inline void spdlog::async_logger::backend_flush_() +{ + try + { + for (auto &sink : sinks_) + { + sink->flush(); + } + } + SPDLOG_CATCH_AND_HANDLE +} diff --git a/include/spdlog/details/circular_q.h b/include/spdlog/details/circular_q.h new file mode 100644 index 0000000..b01325b --- /dev/null +++ b/include/spdlog/details/circular_q.h @@ -0,0 +1,72 @@ +// +// Copyright(c) 2018 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +// cirucal q view of std::vector. +#pragma once + +#include + +namespace spdlog { +namespace details { +template +class circular_q +{ +public: + using item_type = T; + + explicit circular_q(size_t max_items) + : max_items_(max_items + 1) // one item is reserved as marker for full q + , v_(max_items_) + { + } + + // push back, overrun (oldest) item if no room left + void push_back(T &&item) + { + v_[tail_] = std::move(item); + tail_ = (tail_ + 1) % max_items_; + + if (tail_ == head_) // overrun last item if full + { + head_ = (head_ + 1) % max_items_; + ++overrun_counter_; + } + } + + // Pop item from front. + // If there are no elements in the container, the behavior is undefined. + void pop_front(T &popped_item) + { + popped_item = std::move(v_[head_]); + head_ = (head_ + 1) % max_items_; + } + + bool empty() + { + return tail_ == head_; + } + + bool full() + { + // head is ahead of the tail by 1 + return ((tail_ + 1) % max_items_) == head_; + } + + size_t overrun_counter() const + { + return overrun_counter_; + } + +private: + size_t max_items_; + typename std::vector::size_type head_ = 0; + typename std::vector::size_type tail_ = 0; + + std::vector v_; + + size_t overrun_counter_ = 0; +}; +} // namespace details +} // namespace spdlog diff --git a/include/spdlog/details/console_globals.h b/include/spdlog/details/console_globals.h new file mode 100644 index 0000000..4ac7f74 --- /dev/null +++ b/include/spdlog/details/console_globals.h @@ -0,0 +1,61 @@ +#pragma once +// +// Copyright(c) 2018 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#include "spdlog/details/null_mutex.h" +#include +#include + +namespace spdlog { +namespace details { +struct console_stdout +{ + static std::FILE *stream() + { + return stdout; + } +#ifdef _WIN32 + static HANDLE handle() + { + return ::GetStdHandle(STD_OUTPUT_HANDLE); + } +#endif +}; + +struct console_stderr +{ + static std::FILE *stream() + { + return stderr; + } +#ifdef _WIN32 + static HANDLE handle() + { + return ::GetStdHandle(STD_ERROR_HANDLE); + } +#endif +}; + +struct console_mutex +{ + using mutex_t = std::mutex; + static mutex_t &mutex() + { + static mutex_t s_mutex; + return s_mutex; + } +}; + +struct console_nullmutex +{ + using mutex_t = null_mutex; + static mutex_t &mutex() + { + static mutex_t s_mutex; + return s_mutex; + } +}; +} // namespace details +} // namespace spdlog diff --git a/include/spdlog/details/file_helper.h b/include/spdlog/details/file_helper.h new file mode 100644 index 0000000..65b3560 --- /dev/null +++ b/include/spdlog/details/file_helper.h @@ -0,0 +1,153 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +// Helper class for file sink +// When failing to open a file, retry several times(5) with small delay between +// the tries(10 ms) +// Throw spdlog_ex exception on errors + +#include "../details/log_msg.h" +#include "../details/os.h" + +#include +#include +#include +#include +#include +#include + +namespace spdlog { +namespace details { + +class file_helper +{ + +public: + const int open_tries = 5; + const int open_interval = 10; + + explicit file_helper() = default; + + file_helper(const file_helper &) = delete; + file_helper &operator=(const file_helper &) = delete; + + ~file_helper() + { + close(); + } + + void open(const filename_t &fname, bool truncate = false) + { + close(); + auto *mode = truncate ? SPDLOG_FILENAME_T("wb") : SPDLOG_FILENAME_T("ab"); + _filename = fname; + for (int tries = 0; tries < open_tries; ++tries) + { + if (!os::fopen_s(&fd_, fname, mode)) + { + return; + } + + details::os::sleep_for_millis(open_interval); + } + + throw spdlog_ex("Failed opening file " + os::filename_to_str(_filename) + " for writing", errno); + } + + void reopen(bool truncate) + { + if (_filename.empty()) + { + throw spdlog_ex("Failed re opening file - was not opened before"); + } + open(_filename, truncate); + } + + void flush() + { + std::fflush(fd_); + } + + void close() + { + if (fd_ != nullptr) + { + std::fclose(fd_); + fd_ = nullptr; + } + } + + void write(const fmt::memory_buffer &buf) + { + size_t msg_size = buf.size(); + auto data = buf.data(); + if (std::fwrite(data, 1, msg_size, fd_) != msg_size) + { + throw spdlog_ex("Failed writing to file " + os::filename_to_str(_filename), errno); + } + } + + size_t size() const + { + if (fd_ == nullptr) + { + throw spdlog_ex("Cannot use size() on closed file " + os::filename_to_str(_filename)); + } + return os::filesize(fd_); + } + + const filename_t &filename() const + { + return _filename; + } + + static bool file_exists(const filename_t &fname) + { + return os::file_exists(fname); + } + + // + // return file path and its extension: + // + // "mylog.txt" => ("mylog", ".txt") + // "mylog" => ("mylog", "") + // "mylog." => ("mylog.", "") + // "/dir1/dir2/mylog.txt" => ("/dir1/dir2/mylog", ".txt") + // + // the starting dot in filenames is ignored (hidden files): + // + // ".mylog" => (".mylog". "") + // "my_folder/.mylog" => ("my_folder/.mylog", "") + // "my_folder/.mylog.txt" => ("my_folder/.mylog", ".txt") + static std::tuple split_by_extenstion(const spdlog::filename_t &fname) + { + auto ext_index = fname.rfind('.'); + + // no valid extension found - return whole path and empty string as + // extension + if (ext_index == filename_t::npos || ext_index == 0 || ext_index == fname.size() - 1) + { + return std::make_tuple(fname, spdlog::filename_t()); + } + + // treat casese like "/etc/rc.d/somelogfile or "/abc/.hiddenfile" + auto folder_index = fname.rfind(details::os::folder_sep); + if (folder_index != filename_t::npos && folder_index >= ext_index - 1) + { + return std::make_tuple(fname, spdlog::filename_t()); + } + + // finally - return a valid base and extension tuple + return std::make_tuple(fname.substr(0, ext_index), fname.substr(ext_index)); + } + +private: + FILE *fd_{nullptr}; + filename_t _filename; +}; +} // namespace details +} // namespace spdlog diff --git a/include/spdlog/details/fmt_helper.h b/include/spdlog/details/fmt_helper.h new file mode 100644 index 0000000..eabf93b --- /dev/null +++ b/include/spdlog/details/fmt_helper.h @@ -0,0 +1,130 @@ +// +// Created by gabi on 6/15/18. +// + +#pragma once + +#include "chrono" +#include "spdlog/fmt/fmt.h" + +// Some fmt helpers to efficiently format and pad ints and strings +namespace spdlog { +namespace details { +namespace fmt_helper { + +template +inline void append_str(const std::string &str, fmt::basic_memory_buffer &dest) +{ + auto *str_ptr = str.data(); + dest.append(str_ptr, str_ptr + str.size()); +} + +template +inline void append_c_str(const char *c_str, fmt::basic_memory_buffer &dest) +{ + char ch; + while ((ch = *c_str) != '\0') + { + dest.push_back(ch); + ++c_str; + } +} + +template +inline void append_buf(const fmt::basic_memory_buffer &buf, fmt::basic_memory_buffer &dest) +{ + auto *buf_ptr = buf.data(); + dest.append(buf_ptr, buf_ptr + buf.size()); +} + +template +inline void append_int(T n, fmt::basic_memory_buffer &dest) +{ + fmt::format_int i(n); + dest.append(i.data(), i.data() + i.size()); +} + +template +inline void pad2(int n, fmt::basic_memory_buffer &dest) +{ + if (n > 99) + { + append_int(n, dest); + return; + } + if (n > 9) // 10-99 + { + dest.push_back('0' + static_cast(n / 10)); + dest.push_back('0' + static_cast(n % 10)); + return; + } + if (n >= 0) // 0-9 + { + dest.push_back('0'); + dest.push_back('0' + static_cast(n)); + return; + } + // negatives (unlikely, but just in case, let fmt deal with it) + fmt::format_to(dest, "{:02}", n); +} + +template +inline void pad3(int n, fmt::basic_memory_buffer &dest) +{ + if (n > 999) + { + append_int(n, dest); + return; + } + + if (n > 99) // 100-999 + { + append_int(n / 100, dest); + pad2(n % 100, dest); + return; + } + if (n > 9) // 10-99 + { + dest.push_back('0'); + dest.push_back('0' + static_cast(n / 10)); + dest.push_back('0' + static_cast(n % 10)); + return; + } + if (n >= 0) + { + dest.push_back('0'); + dest.push_back('0'); + dest.push_back('0' + static_cast(n)); + return; + } + // negatives (unlikely, but just in case let fmt deal with it) + fmt::format_to(dest, "{:03}", n); +} + +template +inline void pad6(size_t n, fmt::basic_memory_buffer &dest) +{ + if (n > 99999) + { + append_int(n, dest); + return; + } + pad3(static_cast(n / 1000), dest); + pad3(static_cast(n % 1000), dest); +} + +// return fraction of a second of the given time_point. +// e.g. +// fraction(tp) -> will return the millis part of the second +template +inline ToDuration time_fraction(const log_clock::time_point &tp) +{ + using namespace std::chrono; + auto duration = tp.time_since_epoch(); + auto secs = duration_cast(duration); + return duration_cast(duration) - duration_cast(secs); +} + +} // namespace fmt_helper +} // namespace details +} // namespace spdlog diff --git a/include/spdlog/details/log_msg.h b/include/spdlog/details/log_msg.h new file mode 100644 index 0000000..3272dd5 --- /dev/null +++ b/include/spdlog/details/log_msg.h @@ -0,0 +1,48 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "spdlog/common.h" +#include "spdlog/details/os.h" + +#include +#include + +namespace spdlog { +namespace details { +struct log_msg +{ + log_msg() = default; + + log_msg(const std::string *loggers_name, level::level_enum lvl) + : logger_name(loggers_name) + , level(lvl) +#ifndef SPDLOG_NO_DATETIME + , time(os::now()) +#endif + +#ifndef SPDLOG_NO_THREAD_ID + , thread_id(os::thread_id()) +#endif + { + } + + log_msg(const log_msg &other) = delete; + log_msg(log_msg &&other) = delete; + log_msg &operator=(log_msg &&other) = delete; + + const std::string *logger_name{nullptr}; + level::level_enum level; + log_clock::time_point time; + size_t thread_id; + fmt::memory_buffer raw; + size_t msg_id{0}; + // info about wrapping the formatted text with color + mutable size_t color_range_start{0}; + mutable size_t color_range_end{0}; +}; +} // namespace details +} // namespace spdlog diff --git a/include/spdlog/details/logger_impl.h b/include/spdlog/details/logger_impl.h new file mode 100644 index 0000000..0a7c5b6 --- /dev/null +++ b/include/spdlog/details/logger_impl.h @@ -0,0 +1,345 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include +#include + +// create logger with given name, sinks and the default pattern formatter +// all other ctors will call this one +template +inline spdlog::logger::logger(std::string logger_name, const It &begin, const It &end) + : name_(std::move(logger_name)) + , sinks_(begin, end) + , level_(level::info) + , flush_level_(level::off) + , last_err_time_(0) + , msg_counter_(1) // message counter will start from 1. 0-message id will be + // reserved for controll messages +{ + err_handler_ = [this](const std::string &msg) { this->default_err_handler_(msg); }; +} + +// ctor with sinks as init list +inline spdlog::logger::logger(std::string logger_name, sinks_init_list sinks_list) + : logger(std::move(logger_name), sinks_list.begin(), sinks_list.end()) +{ +} + +// ctor with single sink +inline spdlog::logger::logger(std::string logger_name, spdlog::sink_ptr single_sink) + : logger(std::move(logger_name), {std::move(single_sink)}) +{ +} + +inline spdlog::logger::~logger() = default; + +inline void spdlog::logger::set_formatter(std::unique_ptr f) +{ + for (auto &sink : sinks_) + { + sink->set_formatter(f->clone()); + } +} + +inline void spdlog::logger::set_pattern(std::string pattern, pattern_time_type time_type) +{ + set_formatter(std::unique_ptr(new pattern_formatter(std::move(pattern), time_type))); +} + +template +inline void spdlog::logger::log(level::level_enum lvl, const char *fmt, const Args &... args) +{ + if (!should_log(lvl)) + { + return; + } + + try + { + details::log_msg log_msg(&name_, lvl); + fmt::format_to(log_msg.raw, fmt, args...); + sink_it_(log_msg); + } + SPDLOG_CATCH_AND_HANDLE +} + +template +inline void spdlog::logger::log(level::level_enum lvl, const char *msg) +{ + if (!should_log(lvl)) + { + return; + } + try + { + details::log_msg log_msg(&name_, lvl); + fmt::format_to(log_msg.raw, "{}", msg); + sink_it_(log_msg); + } + SPDLOG_CATCH_AND_HANDLE +} + +template +inline void spdlog::logger::log(level::level_enum lvl, const T &msg) +{ + if (!should_log(lvl)) + { + return; + } + try + { + details::log_msg log_msg(&name_, lvl); + fmt::format_to(log_msg.raw, "{}", msg); + sink_it_(log_msg); + } + SPDLOG_CATCH_AND_HANDLE +} + +template +inline void spdlog::logger::trace(const char *fmt, const Args &... args) +{ + log(level::trace, fmt, args...); +} + +template +inline void spdlog::logger::debug(const char *fmt, const Args &... args) +{ + log(level::debug, fmt, args...); +} + +template +inline void spdlog::logger::info(const char *fmt, const Args &... args) +{ + log(level::info, fmt, args...); +} + +template +inline void spdlog::logger::warn(const char *fmt, const Args &... args) +{ + log(level::warn, fmt, args...); +} + +template +inline void spdlog::logger::error(const char *fmt, const Args &... args) +{ + log(level::err, fmt, args...); +} + +template +inline void spdlog::logger::critical(const char *fmt, const Args &... args) +{ + log(level::critical, fmt, args...); +} + +template +inline void spdlog::logger::trace(const T &msg) +{ + log(level::trace, msg); +} + +template +inline void spdlog::logger::debug(const T &msg) +{ + log(level::debug, msg); +} + +template +inline void spdlog::logger::info(const T &msg) +{ + log(level::info, msg); +} + +template +inline void spdlog::logger::warn(const T &msg) +{ + log(level::warn, msg); +} + +template +inline void spdlog::logger::error(const T &msg) +{ + log(level::err, msg); +} + +template +inline void spdlog::logger::critical(const T &msg) +{ + log(level::critical, msg); +} + +#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT +template +inline void spdlog::logger::log(level::level_enum lvl, const wchar_t *fmt, const Args &... args) +{ + if (!should_log(lvl)) + { + return; + } + + decltype(wstring_converter_)::byte_string utf8_string; + + try + { + { + std::lock_guard lock(wstring_converter_mutex_); + utf8_string = wstring_converter_.to_bytes(fmt); + } + log(lvl, utf8_string.c_str(), args...); + } + SPDLOG_CATCH_AND_HANDLE +} + +template +inline void spdlog::logger::trace(const wchar_t *fmt, const Args &... args) +{ + log(level::trace, fmt, args...); +} + +template +inline void spdlog::logger::debug(const wchar_t *fmt, const Args &... args) +{ + log(level::debug, fmt, args...); +} + +template +inline void spdlog::logger::info(const wchar_t *fmt, const Args &... args) +{ + log(level::info, fmt, args...); +} + +template +inline void spdlog::logger::warn(const wchar_t *fmt, const Args &... args) +{ + log(level::warn, fmt, args...); +} + +template +inline void spdlog::logger::error(const wchar_t *fmt, const Args &... args) +{ + log(level::err, fmt, args...); +} + +template +inline void spdlog::logger::critical(const wchar_t *fmt, const Args &... args) +{ + log(level::critical, fmt, args...); +} + +#endif // SPDLOG_WCHAR_TO_UTF8_SUPPORT + +// +// name and level +// +inline const std::string &spdlog::logger::name() const +{ + return name_; +} + +inline void spdlog::logger::set_level(spdlog::level::level_enum log_level) +{ + level_.store(log_level); +} + +inline void spdlog::logger::set_error_handler(spdlog::log_err_handler err_handler) +{ + err_handler_ = std::move(err_handler); +} + +inline spdlog::log_err_handler spdlog::logger::error_handler() +{ + return err_handler_; +} + +inline void spdlog::logger::flush() +{ + try + { + flush_(); + } + SPDLOG_CATCH_AND_HANDLE +} + +inline void spdlog::logger::flush_on(level::level_enum log_level) +{ + flush_level_.store(log_level); +} + +inline bool spdlog::logger::should_flush_(const details::log_msg &msg) +{ + auto flush_level = flush_level_.load(std::memory_order_relaxed); + return (msg.level >= flush_level) && (msg.level != level::off); +} + +inline spdlog::level::level_enum spdlog::logger::level() const +{ + return static_cast(level_.load(std::memory_order_relaxed)); +} + +inline bool spdlog::logger::should_log(spdlog::level::level_enum msg_level) const +{ + return msg_level >= level_.load(std::memory_order_relaxed); +} + +// +// protected virtual called at end of each user log call (if enabled) by the +// line_logger +// +inline void spdlog::logger::sink_it_(details::log_msg &msg) +{ +#if defined(SPDLOG_ENABLE_MESSAGE_COUNTER) + incr_msg_counter_(msg); +#endif + for (auto &sink : sinks_) + { + if (sink->should_log(msg.level)) + { + sink->log(msg); + } + } + + if (should_flush_(msg)) + { + flush(); + } +} + +inline void spdlog::logger::flush_() +{ + for (auto &sink : sinks_) + { + sink->flush(); + } +} + +inline void spdlog::logger::default_err_handler_(const std::string &msg) +{ + auto now = time(nullptr); + if (now - last_err_time_ < 60) + { + return; + } + last_err_time_ = now; + auto tm_time = details::os::localtime(now); + char date_buf[100]; + std::strftime(date_buf, sizeof(date_buf), "%Y-%m-%d %H:%M:%S", &tm_time); + fmt::print(stderr, "[*** LOG ERROR ***] [{}] [{}] {}\n", date_buf, name(), msg); +} + +inline void spdlog::logger::incr_msg_counter_(details::log_msg &msg) +{ + msg.msg_id = msg_counter_.fetch_add(1, std::memory_order_relaxed); +} + +inline const std::vector &spdlog::logger::sinks() const +{ + return sinks_; +} + +inline std::vector &spdlog::logger::sinks() +{ + return sinks_; +} diff --git a/include/spdlog/details/mpmc_blocking_q.h b/include/spdlog/details/mpmc_blocking_q.h new file mode 100644 index 0000000..ca789fc --- /dev/null +++ b/include/spdlog/details/mpmc_blocking_q.h @@ -0,0 +1,121 @@ +#pragma once + +// +// Copyright(c) 2018 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +// multi producer-multi consumer blocking queue. +// enqueue(..) - will block until room found to put the new message. +// enqueue_nowait(..) - will return immediately with false if no room left in +// the queue. +// dequeue_for(..) - will block until the queue is not empty or timeout have +// passed. + +#include "spdlog/details/circular_q.h" + +#include +#include + +namespace spdlog { +namespace details { + +template +class mpmc_blocking_queue +{ +public: + using item_type = T; + explicit mpmc_blocking_queue(size_t max_items) + : q_(max_items) + { + } + +#ifndef __MINGW32__ + // try to enqueue and block if no room left + void enqueue(T &&item) + { + { + std::unique_lock lock(queue_mutex_); + pop_cv_.wait(lock, [this] { return !this->q_.full(); }); + q_.push_back(std::move(item)); + } + push_cv_.notify_one(); + } + + // enqueue immediately. overrun oldest message in the queue if no room left. + void enqueue_nowait(T &&item) + { + { + std::unique_lock lock(queue_mutex_); + q_.push_back(std::move(item)); + } + push_cv_.notify_one(); + } + + // try to dequeue item. if no item found. wait upto timeout and try again + // Return true, if succeeded dequeue item, false otherwise + bool dequeue_for(T &popped_item, std::chrono::milliseconds wait_duration) + { + { + std::unique_lock lock(queue_mutex_); + if (!push_cv_.wait_for(lock, wait_duration, [this] { return !this->q_.empty(); })) + { + return false; + } + q_.pop_front(popped_item); + } + pop_cv_.notify_one(); + return true; + } + +#else + // apparently mingw deadlocks if the mutex is released before cv.notify_one(), + // so release the mutex at the very end each function. + + // try to enqueue and block if no room left + void enqueue(T &&item) + { + std::unique_lock lock(queue_mutex_); + pop_cv_.wait(lock, [this] { return !this->q_.full(); }); + q_.push_back(std::move(item)); + push_cv_.notify_one(); + } + + // enqueue immediately. overrun oldest message in the queue if no room left. + void enqueue_nowait(T &&item) + { + std::unique_lock lock(queue_mutex_); + q_.push_back(std::move(item)); + push_cv_.notify_one(); + } + + // try to dequeue item. if no item found. wait upto timeout and try again + // Return true, if succeeded dequeue item, false otherwise + bool dequeue_for(T &popped_item, std::chrono::milliseconds wait_duration) + { + std::unique_lock lock(queue_mutex_); + if (!push_cv_.wait_for(lock, wait_duration, [this] { return !this->q_.empty(); })) + { + return false; + } + q_.pop_front(popped_item); + pop_cv_.notify_one(); + return true; + } + +#endif + + size_t overrun_counter() + { + std::unique_lock lock(queue_mutex_); + return q_.overrun_counter(); + } + +private: + std::mutex queue_mutex_; + std::condition_variable push_cv_; + std::condition_variable pop_cv_; + spdlog::details::circular_q q_; +}; +} // namespace details +} // namespace spdlog diff --git a/include/spdlog/details/null_mutex.h b/include/spdlog/details/null_mutex.h new file mode 100644 index 0000000..3f495bd --- /dev/null +++ b/include/spdlog/details/null_mutex.h @@ -0,0 +1,45 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include +// null, no cost dummy "mutex" and dummy "atomic" int + +namespace spdlog { +namespace details { +struct null_mutex +{ + void lock() {} + void unlock() {} + bool try_lock() + { + return true; + } +}; + +struct null_atomic_int +{ + int value; + null_atomic_int() = default; + + explicit null_atomic_int(int val) + : value(val) + { + } + + int load(std::memory_order) const + { + return value; + } + + void store(int val) + { + value = val; + } +}; + +} // namespace details +} // namespace spdlog diff --git a/include/spdlog/details/os.h b/include/spdlog/details/os.h new file mode 100644 index 0000000..d7f7430 --- /dev/null +++ b/include/spdlog/details/os.h @@ -0,0 +1,432 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// +#pragma once + +#include "../common.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 + +#ifndef NOMINMAX +#define NOMINMAX // prevent windows redefining min/max +#endif + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include // _get_osfhandle and _isatty support +#include // _get_pid support +#include + +#ifdef __MINGW32__ +#include +#endif + +#else // unix + +#include +#include + +#ifdef __linux__ +#include //Use gettid() syscall under linux to get thread id + +#elif __FreeBSD__ +#include //Use thr_self() syscall under FreeBSD to get thread id +#endif + +#endif // unix + +#ifndef __has_feature // Clang - feature checking macros. +#define __has_feature(x) 0 // Compatibility with non-clang compilers. +#endif + +namespace spdlog { +namespace details { +namespace os { + +inline spdlog::log_clock::time_point now() +{ + +#if defined __linux__ && defined SPDLOG_CLOCK_COARSE + timespec ts; + ::clock_gettime(CLOCK_REALTIME_COARSE, &ts); + return std::chrono::time_point( + std::chrono::duration_cast(std::chrono::seconds(ts.tv_sec) + std::chrono::nanoseconds(ts.tv_nsec))); + +#else + return log_clock::now(); +#endif +} +inline std::tm localtime(const std::time_t &time_tt) +{ + +#ifdef _WIN32 + std::tm tm; + localtime_s(&tm, &time_tt); +#else + std::tm tm; + localtime_r(&time_tt, &tm); +#endif + return tm; +} + +inline std::tm localtime() +{ + std::time_t now_t = time(nullptr); + return localtime(now_t); +} + +inline std::tm gmtime(const std::time_t &time_tt) +{ + +#ifdef _WIN32 + std::tm tm; + gmtime_s(&tm, &time_tt); +#else + std::tm tm; + gmtime_r(&time_tt, &tm); +#endif + return tm; +} + +inline std::tm gmtime() +{ + std::time_t now_t = time(nullptr); + return gmtime(now_t); +} +inline bool operator==(const std::tm &tm1, const std::tm &tm2) +{ + return (tm1.tm_sec == tm2.tm_sec && tm1.tm_min == tm2.tm_min && tm1.tm_hour == tm2.tm_hour && tm1.tm_mday == tm2.tm_mday && + tm1.tm_mon == tm2.tm_mon && tm1.tm_year == tm2.tm_year && tm1.tm_isdst == tm2.tm_isdst); +} + +inline bool operator!=(const std::tm &tm1, const std::tm &tm2) +{ + return !(tm1 == tm2); +} + +// eol definition +#if !defined(SPDLOG_EOL) +#ifdef _WIN32 +#define SPDLOG_EOL "\r\n" +#else +#define SPDLOG_EOL "\n" +#endif +#endif + +SPDLOG_CONSTEXPR static const char *default_eol = SPDLOG_EOL; + +// folder separator +#ifdef _WIN32 +SPDLOG_CONSTEXPR static const char folder_sep = '\\'; +#else +SPDLOG_CONSTEXPR static const char folder_sep = '/'; +#endif + +inline void prevent_child_fd(FILE *f) +{ + +#ifdef _WIN32 +#if !defined(__cplusplus_winrt) + auto file_handle = (HANDLE)_get_osfhandle(_fileno(f)); + if (!::SetHandleInformation(file_handle, HANDLE_FLAG_INHERIT, 0)) + throw spdlog_ex("SetHandleInformation failed", errno); +#endif +#else + auto fd = fileno(f); + if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) + { + throw spdlog_ex("fcntl with FD_CLOEXEC failed", errno); + } +#endif +} + +// fopen_s on non windows for writing +inline bool fopen_s(FILE **fp, const filename_t &filename, const filename_t &mode) +{ +#ifdef _WIN32 +#ifdef SPDLOG_WCHAR_FILENAMES + *fp = _wfsopen((filename.c_str()), mode.c_str(), _SH_DENYWR); +#else + *fp = _fsopen((filename.c_str()), mode.c_str(), _SH_DENYWR); +#endif +#else // unix + *fp = fopen((filename.c_str()), mode.c_str()); +#endif + +#ifdef SPDLOG_PREVENT_CHILD_FD + if (*fp != nullptr) + { + prevent_child_fd(*fp); + } +#endif + return *fp == nullptr; +} + +inline int remove(const filename_t &filename) +{ +#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) + return _wremove(filename.c_str()); +#else + return std::remove(filename.c_str()); +#endif +} + +inline int rename(const filename_t &filename1, const filename_t &filename2) +{ +#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) + return _wrename(filename1.c_str(), filename2.c_str()); +#else + return std::rename(filename1.c_str(), filename2.c_str()); +#endif +} + +// Return if file exists +inline bool file_exists(const filename_t &filename) +{ +#ifdef _WIN32 +#ifdef SPDLOG_WCHAR_FILENAMES + auto attribs = GetFileAttributesW(filename.c_str()); +#else + auto attribs = GetFileAttributesA(filename.c_str()); +#endif + return (attribs != INVALID_FILE_ATTRIBUTES && !(attribs & FILE_ATTRIBUTE_DIRECTORY)); +#else // common linux/unix all have the stat system call + struct stat buffer; + return (stat(filename.c_str(), &buffer) == 0); +#endif +} + +// Return file size according to open FILE* object +inline size_t filesize(FILE *f) +{ + if (f == nullptr) + { + throw spdlog_ex("Failed getting file size. fd is null"); + } +#if defined(_WIN32) && !defined(__CYGWIN__) + int fd = _fileno(f); +#if _WIN64 // 64 bits + struct _stat64 st; + if (_fstat64(fd, &st) == 0) + { + return st.st_size; + } + +#else // windows 32 bits + long ret = _filelength(fd); + if (ret >= 0) + { + return static_cast(ret); + } +#endif + +#else // unix + int fd = fileno(f); +// 64 bits(but not in osx or cygwin, where fstat64 is deprecated) +#if !defined(__FreeBSD__) && !defined(__APPLE__) && (defined(__x86_64__) || defined(__ppc64__)) && !defined(__CYGWIN__) + struct stat64 st; + if (fstat64(fd, &st) == 0) + { + return static_cast(st.st_size); + } +#else // unix 32 bits or cygwin + struct stat st; + + if (fstat(fd, &st) == 0) + { + return static_cast(st.st_size); + } +#endif +#endif + throw spdlog_ex("Failed getting file size from fd", errno); +} + +// Return utc offset in minutes or throw spdlog_ex on failure +inline int utc_minutes_offset(const std::tm &tm = details::os::localtime()) +{ + +#ifdef _WIN32 +#if _WIN32_WINNT < _WIN32_WINNT_WS08 + TIME_ZONE_INFORMATION tzinfo; + auto rv = GetTimeZoneInformation(&tzinfo); +#else + DYNAMIC_TIME_ZONE_INFORMATION tzinfo; + auto rv = GetDynamicTimeZoneInformation(&tzinfo); +#endif + if (rv == TIME_ZONE_ID_INVALID) + throw spdlog::spdlog_ex("Failed getting timezone info. ", errno); + + int offset = -tzinfo.Bias; + if (tm.tm_isdst) + { + offset -= tzinfo.DaylightBias; + } + else + { + offset -= tzinfo.StandardBias; + } + return offset; +#else + +#if defined(sun) || defined(__sun) || defined(_AIX) + // 'tm_gmtoff' field is BSD extension and it's missing on SunOS/Solaris + struct helper + { + static long int calculate_gmt_offset(const std::tm &localtm = details::os::localtime(), const std::tm &gmtm = details::os::gmtime()) + { + int local_year = localtm.tm_year + (1900 - 1); + int gmt_year = gmtm.tm_year + (1900 - 1); + + long int days = ( + // difference in day of year + localtm.tm_yday - + gmtm.tm_yday + + // + intervening leap days + + ((local_year >> 2) - (gmt_year >> 2)) - (local_year / 100 - gmt_year / 100) + + ((local_year / 100 >> 2) - (gmt_year / 100 >> 2)) + + // + difference in years * 365 */ + + (long int)(local_year - gmt_year) * 365); + + long int hours = (24 * days) + (localtm.tm_hour - gmtm.tm_hour); + long int mins = (60 * hours) + (localtm.tm_min - gmtm.tm_min); + long int secs = (60 * mins) + (localtm.tm_sec - gmtm.tm_sec); + + return secs; + } + }; + + auto offset_seconds = helper::calculate_gmt_offset(tm); +#else + auto offset_seconds = tm.tm_gmtoff; +#endif + + return static_cast(offset_seconds / 60); +#endif +} + +// Return current thread id as size_t +// It exists because the std::this_thread::get_id() is much slower(especially +// under VS 2013) +inline size_t _thread_id() +{ +#ifdef _WIN32 + return static_cast(::GetCurrentThreadId()); +#elif __linux__ +#if defined(__ANDROID__) && defined(__ANDROID_API__) && (__ANDROID_API__ < 21) +#define SYS_gettid __NR_gettid +#endif + return static_cast(syscall(SYS_gettid)); +#elif __FreeBSD__ + long tid; + thr_self(&tid); + return static_cast(tid); +#elif __APPLE__ + uint64_t tid; + pthread_threadid_np(nullptr, &tid); + return static_cast(tid); +#else // Default to standard C++11 (other Unix) + return static_cast(std::hash()(std::this_thread::get_id())); +#endif +} + +// Return current thread id as size_t (from thread local storage) +inline size_t thread_id() +{ +#if defined(SPDLOG_DISABLE_TID_CACHING) || (defined(_MSC_VER) && (_MSC_VER < 1900)) || defined(__cplusplus_winrt) || \ + (defined(__clang__) && !__has_feature(cxx_thread_local)) + return _thread_id(); +#else // cache thread id in tls + static thread_local const size_t tid = _thread_id(); + return tid; +#endif +} + +// This is avoid msvc issue in sleep_for that happens if the clock changes. +// See https://github.com/gabime/spdlog/issues/609 +inline void sleep_for_millis(int milliseconds) +{ +#if defined(_WIN32) + ::Sleep(milliseconds); +#else + std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds)); +#endif +} + +// wchar support for windows file names (SPDLOG_WCHAR_FILENAMES must be defined) +#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) +#define SPDLOG_FILENAME_T(s) L##s +inline std::string filename_to_str(const filename_t &filename) +{ + std::wstring_convert, wchar_t> c; + return c.to_bytes(filename); +} +#else +#define SPDLOG_FILENAME_T(s) s +inline std::string filename_to_str(const filename_t &filename) +{ + return filename; +} +#endif + +inline int pid() +{ + +#ifdef _WIN32 + return static_cast(::GetCurrentProcessId()); +#else + return static_cast(::getpid()); +#endif +} + +// Determine if the terminal supports colors +// Source: https://github.com/agauniyal/rang/ +inline bool is_color_terminal() +{ +#ifdef _WIN32 + return true; +#else + static constexpr const char *Terms[] = { + "ansi", "color", "console", "cygwin", "gnome", "konsole", "kterm", "linux", "msys", "putty", "rxvt", "screen", "vt100", "xterm"}; + + const char *env_p = std::getenv("TERM"); + if (env_p == nullptr) + { + return false; + } + + static const bool result = + std::any_of(std::begin(Terms), std::end(Terms), [&](const char *term) { return std::strstr(env_p, term) != nullptr; }); + return result; +#endif +} + +// Detrmine if the terminal attached +// Source: https://github.com/agauniyal/rang/ +inline bool in_terminal(FILE *file) +{ + +#ifdef _WIN32 + return _isatty(_fileno(file)) != 0; +#else + return isatty(fileno(file)) != 0; +#endif +} +} // namespace os +} // namespace details +} // namespace spdlog diff --git a/include/spdlog/details/pattern_formatter.h b/include/spdlog/details/pattern_formatter.h new file mode 100644 index 0000000..dd352ae --- /dev/null +++ b/include/spdlog/details/pattern_formatter.h @@ -0,0 +1,772 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "spdlog/details/fmt_helper.h" +#include "spdlog/details/log_msg.h" +#include "spdlog/details/os.h" +#include "spdlog/fmt/fmt.h" +#include "spdlog/formatter.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace spdlog { +namespace details { + +class flag_formatter +{ +public: + virtual ~flag_formatter() = default; + virtual void format(const details::log_msg &msg, const std::tm &tm_time, fmt::memory_buffer &dest) = 0; +}; + +/////////////////////////////////////////////////////////////////////// +// name & level pattern appenders +/////////////////////////////////////////////////////////////////////// +class name_formatter : public flag_formatter +{ + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override + { + fmt_helper::append_str(*msg.logger_name, dest); + } +}; + +// log level appender +class level_formatter : public flag_formatter +{ + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override + { + fmt_helper::append_c_str(level::to_c_str(msg.level), dest); + } +}; + +// short log level appender +class short_level_formatter : public flag_formatter +{ + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override + { + fmt_helper::append_c_str(level::to_short_c_str(msg.level), dest); + } +}; + +/////////////////////////////////////////////////////////////////////// +// Date time pattern appenders +/////////////////////////////////////////////////////////////////////// + +static const char *ampm(const tm &t) +{ + return t.tm_hour >= 12 ? "PM" : "AM"; +} + +static int to12h(const tm &t) +{ + return t.tm_hour > 12 ? t.tm_hour - 12 : t.tm_hour; +} + +// Abbreviated weekday name +static const char *days[]{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; +class a_formatter : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::append_c_str(days[tm_time.tm_wday], dest); + } +}; + +// Full weekday name +static const char *full_days[]{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; +class A_formatter : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::append_c_str(full_days[tm_time.tm_wday], dest); + } +}; + +// Abbreviated month +static const char *months[]{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"}; +class b_formatter : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::append_c_str(months[tm_time.tm_mon], dest); + } +}; + +// Full month name +static const char *full_months[]{ + "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}; +class B_formatter : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::append_c_str(full_months[tm_time.tm_mon], dest); + } +}; + +// Date and time representation (Thu Aug 23 15:35:46 2014) +class c_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + // fmt::format_to(dest, "{} {} {} ", days[tm_time.tm_wday], + // months[tm_time.tm_mon], tm_time.tm_mday); + // date + fmt_helper::append_str(days[tm_time.tm_wday], dest); + dest.push_back(' '); + fmt_helper::append_str(months[tm_time.tm_mon], dest); + dest.push_back(' '); + fmt_helper::append_int(tm_time.tm_mday, dest); + dest.push_back(' '); + // time + + fmt_helper::pad2(tm_time.tm_hour, dest); + dest.push_back(':'); + fmt_helper::pad2(tm_time.tm_min, dest); + dest.push_back(':'); + fmt_helper::pad2(tm_time.tm_sec, dest); + dest.push_back(' '); + fmt_helper::append_int(tm_time.tm_year + 1900, dest); + } +}; + +// year - 2 digit +class C_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::pad2(tm_time.tm_year % 100, dest); + } +}; + +// Short MM/DD/YY date, equivalent to %m/%d/%y 08/23/01 +class D_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::pad2(tm_time.tm_mon + 1, dest); + dest.push_back('/'); + fmt_helper::pad2(tm_time.tm_mday, dest); + dest.push_back('/'); + fmt_helper::pad2(tm_time.tm_year % 100, dest); + } +}; + +// year - 4 digit +class Y_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::append_int(tm_time.tm_year + 1900, dest); + } +}; + +// month 1-12 +class m_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::pad2(tm_time.tm_mon + 1, dest); + } +}; + +// day of month 1-31 +class d_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::pad2(tm_time.tm_mday, dest); + } +}; + +// hours in 24 format 0-23 +class H_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::pad2(tm_time.tm_hour, dest); + } +}; + +// hours in 12 format 1-12 +class I_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::pad2(to12h(tm_time), dest); + } +}; + +// minutes 0-59 +class M_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::pad2(tm_time.tm_min, dest); + } +}; + +// seconds 0-59 +class S_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::pad2(tm_time.tm_sec, dest); + } +}; + +// milliseconds +class e_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override + { + auto millis = fmt_helper::time_fraction(msg.time); + fmt_helper::pad3(static_cast(millis.count()), dest); + } +}; + +// microseconds +class f_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override + { + auto micros = fmt_helper::time_fraction(msg.time); + fmt_helper::pad6(static_cast(micros.count()), dest); + } +}; + +// nanoseconds +class F_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override + { + auto ns = fmt_helper::time_fraction(msg.time); + fmt::format_to(dest, "{:09}", ns.count()); + } +}; + +// seconds since epoch +class E_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override + { + auto duration = msg.time.time_since_epoch(); + auto seconds = std::chrono::duration_cast(duration).count(); + fmt_helper::append_int(seconds, dest); + } +}; + +// AM/PM +class p_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::append_c_str(ampm(tm_time), dest); + } +}; + +// 12 hour clock 02:55:02 pm +class r_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::pad2(to12h(tm_time), dest); + dest.push_back(':'); + fmt_helper::pad2(tm_time.tm_min, dest); + dest.push_back(':'); + fmt_helper::pad2(tm_time.tm_sec, dest); + dest.push_back(' '); + fmt_helper::append_c_str(ampm(tm_time), dest); + } +}; + +// 24-hour HH:MM time, equivalent to %H:%M +class R_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::pad2(tm_time.tm_hour, dest); + dest.push_back(':'); + fmt_helper::pad2(tm_time.tm_min, dest); + } +}; + +// ISO 8601 time format (HH:MM:SS), equivalent to %H:%M:%S +class T_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + // fmt::format_to(dest, "{:02}:{:02}:{:02}", tm_time.tm_hour, + // tm_time.tm_min, tm_time.tm_sec); + fmt_helper::pad2(tm_time.tm_hour, dest); + dest.push_back(':'); + fmt_helper::pad2(tm_time.tm_min, dest); + dest.push_back(':'); + fmt_helper::pad2(tm_time.tm_sec, dest); + } +}; + +// ISO 8601 offset from UTC in timezone (+-HH:MM) +class z_formatter SPDLOG_FINAL : public flag_formatter +{ +public: + const std::chrono::seconds cache_refresh = std::chrono::seconds(5); + + z_formatter() = default; + z_formatter(const z_formatter &) = delete; + z_formatter &operator=(const z_formatter &) = delete; + + void format(const details::log_msg &msg, const std::tm &tm_time, fmt::memory_buffer &dest) override + { +#ifdef _WIN32 + int total_minutes = get_cached_offset(msg, tm_time); +#else + // No need to chache under gcc, + // it is very fast (already stored in tm.tm_gmtoff) + (void)(msg); + int total_minutes = os::utc_minutes_offset(tm_time); +#endif + bool is_negative = total_minutes < 0; + if (is_negative) + { + total_minutes = -total_minutes; + dest.push_back('-'); + } + else + { + dest.push_back('+'); + } + + fmt_helper::pad2(total_minutes / 60, dest); // hours + dest.push_back(':'); + fmt_helper::pad2(total_minutes % 60, dest); // minutes + } + +private: + log_clock::time_point last_update_{std::chrono::seconds(0)}; +#ifdef _WIN32 + int offset_minutes_{0}; + + int get_cached_offset(const log_msg &msg, const std::tm &tm_time) + { + if (msg.time - last_update_ >= cache_refresh) + { + offset_minutes_ = os::utc_minutes_offset(tm_time); + last_update_ = msg.time; + } + return offset_minutes_; + } +#endif +}; + +// Thread id +class t_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override + { + fmt_helper::pad6(msg.thread_id, dest); + } +}; + +// Current pid +class pid_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &, fmt::memory_buffer &dest) override + { + fmt_helper::append_int(details::os::pid(), dest); + } +}; + +// message counter formatter +class i_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override + { + fmt_helper::pad6(msg.msg_id, dest); + } +}; + +class v_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override + { + fmt_helper::append_buf(msg.raw, dest); + } +}; + +class ch_formatter SPDLOG_FINAL : public flag_formatter +{ +public: + explicit ch_formatter(char ch) + : ch_(ch) + { + } + void format(const details::log_msg &, const std::tm &, fmt::memory_buffer &dest) override + { + dest.push_back(ch_); + } + +private: + char ch_; +}; + +// aggregate user chars to display as is +class aggregate_formatter SPDLOG_FINAL : public flag_formatter +{ +public: + aggregate_formatter() = default; + + void add_ch(char ch) + { + str_ += ch; + } + void format(const details::log_msg &, const std::tm &, fmt::memory_buffer &dest) override + { + fmt_helper::append_str(str_, dest); + } + +private: + std::string str_; +}; + +// mark the color range. expect it to be in the form of "%^colored text%$" +class color_start_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override + { + msg.color_range_start = dest.size(); + } +}; +class color_stop_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override + { + msg.color_range_end = dest.size(); + } +}; + +// Full info formatter +// pattern: [%Y-%m-%d %H:%M:%S.%e] [%n] [%l] %v +class full_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &msg, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + using namespace std::chrono; +#ifndef SPDLOG_NO_DATETIME + + // cache the date/time part for the next second. + auto duration = msg.time.time_since_epoch(); + auto secs = duration_cast(duration); + + if (cache_timestamp_ != secs || cached_datetime_.size() == 0) + { + cached_datetime_.resize(0); + cached_datetime_.push_back('['); + fmt_helper::append_int(tm_time.tm_year + 1900, cached_datetime_); + cached_datetime_.push_back('-'); + + fmt_helper::pad2(tm_time.tm_mon + 1, cached_datetime_); + cached_datetime_.push_back('-'); + + fmt_helper::pad2(tm_time.tm_mday, cached_datetime_); + cached_datetime_.push_back(' '); + + fmt_helper::pad2(tm_time.tm_hour, cached_datetime_); + cached_datetime_.push_back(':'); + + fmt_helper::pad2(tm_time.tm_min, cached_datetime_); + cached_datetime_.push_back(':'); + + fmt_helper::pad2(tm_time.tm_sec, cached_datetime_); + cached_datetime_.push_back('.'); + + cache_timestamp_ = secs; + } + fmt_helper::append_buf(cached_datetime_, dest); + + auto millis = fmt_helper::time_fraction(msg.time); + fmt_helper::pad3(static_cast(millis.count()), dest); + dest.push_back(']'); + dest.push_back(' '); + +#else // no datetime needed + (void)tm_time; +#endif + +#ifndef SPDLOG_NO_NAME + dest.push_back('['); + fmt_helper::append_str(*msg.logger_name, dest); + dest.push_back(']'); + dest.push_back(' '); +#endif + + dest.push_back('['); + // wrap the level name with color + msg.color_range_start = dest.size(); + fmt_helper::append_c_str(level::to_c_str(msg.level), dest); + msg.color_range_end = dest.size(); + dest.push_back(']'); + dest.push_back(' '); + fmt_helper::append_buf(msg.raw, dest); + } + +private: + std::chrono::seconds cache_timestamp_{0}; + fmt::basic_memory_buffer cached_datetime_; +}; + +} // namespace details + +class pattern_formatter SPDLOG_FINAL : public formatter +{ +public: + explicit pattern_formatter( + std::string pattern, pattern_time_type time_type = pattern_time_type::local, std::string eol = spdlog::details::os::default_eol) + : pattern_(std::move(pattern)) + , eol_(std::move(eol)) + , pattern_time_type_(time_type) + , last_log_secs_(0) + { + std::memset(&cached_tm_, 0, sizeof(cached_tm_)); + compile_pattern_(pattern_); + } + + pattern_formatter(const pattern_formatter &other) = delete; + pattern_formatter &operator=(const pattern_formatter &other) = delete; + + std::unique_ptr clone() const override + { + return std::unique_ptr(new pattern_formatter(pattern_, pattern_time_type_, eol_)); + } + + void format(const details::log_msg &msg, fmt::memory_buffer &dest) override + { +#ifndef SPDLOG_NO_DATETIME + auto secs = std::chrono::duration_cast(msg.time.time_since_epoch()); + if (secs != last_log_secs_) + { + cached_tm_ = get_time_(msg); + last_log_secs_ = secs; + } +#endif + for (auto &f : formatters_) + { + f->format(msg, cached_tm_, dest); + } + // write eol + details::fmt_helper::append_str(eol_, dest); + } + +private: + std::string pattern_; + std::string eol_; + pattern_time_type pattern_time_type_; + std::tm cached_tm_; + std::chrono::seconds last_log_secs_; + + std::vector> formatters_; + + std::tm get_time_(const details::log_msg &msg) + { + if (pattern_time_type_ == pattern_time_type::local) + { + return details::os::localtime(log_clock::to_time_t(msg.time)); + } + return details::os::gmtime(log_clock::to_time_t(msg.time)); + } + + void handle_flag_(char flag) + { + using flag_formatter_ptr = std::unique_ptr; + switch (flag) + { + // logger name + case 'n': + formatters_.push_back(flag_formatter_ptr(new details::name_formatter())); + break; + + case 'l': + formatters_.push_back(flag_formatter_ptr(new details::level_formatter())); + break; + + case 'L': + formatters_.push_back(flag_formatter_ptr(new details::short_level_formatter())); + break; + + case ('t'): + formatters_.push_back(flag_formatter_ptr(new details::t_formatter())); + break; + + case ('v'): + formatters_.push_back(flag_formatter_ptr(new details::v_formatter())); + break; + + case ('a'): + formatters_.push_back(flag_formatter_ptr(new details::a_formatter())); + break; + + case ('A'): + formatters_.push_back(flag_formatter_ptr(new details::A_formatter())); + break; + + case ('b'): + case ('h'): + formatters_.push_back(flag_formatter_ptr(new details::b_formatter())); + break; + + case ('B'): + formatters_.push_back(flag_formatter_ptr(new details::B_formatter())); + break; + case ('c'): + formatters_.push_back(flag_formatter_ptr(new details::c_formatter())); + break; + + case ('C'): + formatters_.push_back(flag_formatter_ptr(new details::C_formatter())); + break; + + case ('Y'): + formatters_.push_back(flag_formatter_ptr(new details::Y_formatter())); + break; + + case ('D'): + case ('x'): + formatters_.push_back(flag_formatter_ptr(new details::D_formatter())); + break; + + case ('m'): + formatters_.push_back(flag_formatter_ptr(new details::m_formatter())); + break; + + case ('d'): + formatters_.push_back(flag_formatter_ptr(new details::d_formatter())); + break; + + case ('H'): + formatters_.push_back(flag_formatter_ptr(new details::H_formatter())); + break; + + case ('I'): + formatters_.push_back(flag_formatter_ptr(new details::I_formatter())); + break; + + case ('M'): + formatters_.push_back(flag_formatter_ptr(new details::M_formatter())); + break; + + case ('S'): + formatters_.push_back(flag_formatter_ptr(new details::S_formatter())); + break; + + case ('e'): + formatters_.push_back(flag_formatter_ptr(new details::e_formatter())); + break; + + case ('f'): + formatters_.push_back(flag_formatter_ptr(new details::f_formatter())); + break; + case ('F'): + formatters_.push_back(flag_formatter_ptr(new details::F_formatter())); + break; + + case ('E'): + formatters_.push_back(flag_formatter_ptr(new details::E_formatter())); + break; + + case ('p'): + formatters_.push_back(flag_formatter_ptr(new details::p_formatter())); + break; + + case ('r'): + formatters_.push_back(flag_formatter_ptr(new details::r_formatter())); + break; + + case ('R'): + formatters_.push_back(flag_formatter_ptr(new details::R_formatter())); + break; + + case ('T'): + case ('X'): + formatters_.push_back(flag_formatter_ptr(new details::T_formatter())); + break; + + case ('z'): + formatters_.push_back(flag_formatter_ptr(new details::z_formatter())); + break; + + case ('+'): + formatters_.push_back(flag_formatter_ptr(new details::full_formatter())); + break; + + case ('P'): + formatters_.push_back(flag_formatter_ptr(new details::pid_formatter())); + break; + + case ('i'): + formatters_.push_back(flag_formatter_ptr(new details::i_formatter())); + break; + + case ('^'): + formatters_.push_back(flag_formatter_ptr(new details::color_start_formatter())); + break; + + case ('$'): + formatters_.push_back(flag_formatter_ptr(new details::color_stop_formatter())); + break; + + default: // Unknown flag appears as is + formatters_.push_back(flag_formatter_ptr(new details::ch_formatter('%'))); + formatters_.push_back(flag_formatter_ptr(new details::ch_formatter(flag))); + break; + } + } + + void compile_pattern_(const std::string &pattern) + { + auto end = pattern.end(); + std::unique_ptr user_chars; + formatters_.clear(); + for (auto it = pattern.begin(); it != end; ++it) + { + if (*it == '%') + { + if (user_chars) // append user chars found so far + { + formatters_.push_back(std::move(user_chars)); + } + if (++it != end) + { + handle_flag_(*it); + } + else + { + break; + } + } + else // chars not following the % sign should be displayed as is + { + if (!user_chars) + { + user_chars = std::unique_ptr(new details::aggregate_formatter()); + } + user_chars->add_ch(*it); + } + } + if (user_chars) // append raw chars found so far + { + formatters_.push_back(std::move(user_chars)); + } + } +}; +} // namespace spdlog diff --git a/include/spdlog/details/periodic_worker.h b/include/spdlog/details/periodic_worker.h new file mode 100644 index 0000000..57e5fa7 --- /dev/null +++ b/include/spdlog/details/periodic_worker.h @@ -0,0 +1,71 @@ + +// +// Copyright(c) 2018 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +// periodic worker thread - periodically executes the given callback function. +// +// RAII over the owned thread: +// creates the thread on construction. +// stops and joins the thread on destruction. + +#include +#include +#include +#include +#include +namespace spdlog { +namespace details { + +class periodic_worker +{ +public: + periodic_worker(const std::function &callback_fun, std::chrono::seconds interval) + { + active_ = (interval > std::chrono::seconds::zero()); + if (!active_) + { + return; + } + + worker_thread_ = std::thread([this, callback_fun, interval]() { + for (;;) + { + std::unique_lock lock(this->mutex_); + if (this->cv_.wait_for(lock, interval, [this] { return !this->active_; })) + { + return; // active_ == false, so exit this thread + } + callback_fun(); + } + }); + } + + periodic_worker(const periodic_worker &) = delete; + periodic_worker &operator=(const periodic_worker &) = delete; + + // stop the worker thread and join it + ~periodic_worker() + { + if (worker_thread_.joinable()) + { + { + std::lock_guard lock(mutex_); + active_ = false; + } + cv_.notify_one(); + worker_thread_.join(); + } + } + +private: + bool active_; + std::thread worker_thread_; + std::mutex mutex_; + std::condition_variable cv_; +}; +} // namespace details +} // namespace spdlog diff --git a/include/spdlog/details/registry.h b/include/spdlog/details/registry.h new file mode 100644 index 0000000..3988552 --- /dev/null +++ b/include/spdlog/details/registry.h @@ -0,0 +1,214 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +// Loggers registy of unique name->logger pointer +// An attempt to create a logger with an already existing name will be ignored +// If user requests a non existing logger, nullptr will be returned +// This class is thread safe + +#include "spdlog/common.h" +#include "spdlog/details/periodic_worker.h" +#include "spdlog/logger.h" + +#include +#include +#include +#include +#include + +namespace spdlog { +namespace details { +class thread_pool; + +class registry +{ +public: + registry(const registry &) = delete; + registry &operator=(const registry &) = delete; + + void register_logger(std::shared_ptr new_logger) + { + std::lock_guard lock(logger_map_mutex_); + auto logger_name = new_logger->name(); + throw_if_exists_(logger_name); + loggers_[logger_name] = std::move(new_logger); + } + + void register_and_init(std::shared_ptr new_logger) + { + std::lock_guard lock(logger_map_mutex_); + auto logger_name = new_logger->name(); + throw_if_exists_(logger_name); + + // set the global formatter pattern + new_logger->set_formatter(formatter_->clone()); + + if (err_handler_) + { + new_logger->set_error_handler(err_handler_); + } + + new_logger->set_level(level_); + new_logger->flush_on(flush_level_); + + // add to registry + loggers_[logger_name] = std::move(new_logger); + } + + std::shared_ptr get(const std::string &logger_name) + { + std::lock_guard lock(logger_map_mutex_); + auto found = loggers_.find(logger_name); + return found == loggers_.end() ? nullptr : found->second; + } + + void set_tp(std::shared_ptr tp) + { + std::lock_guard lock(tp_mutex_); + tp_ = std::move(tp); + } + + std::shared_ptr get_tp() + { + std::lock_guard lock(tp_mutex_); + return tp_; + } + + // Set global formatter. Each sink in each logger will get a clone of this object + void set_formatter(std::unique_ptr formatter) + { + std::lock_guard lock(logger_map_mutex_); + formatter_ = std::move(formatter); + for (auto &l : loggers_) + { + l.second->set_formatter(formatter_->clone()); + } + } + + void set_level(level::level_enum log_level) + { + std::lock_guard lock(logger_map_mutex_); + for (auto &l : loggers_) + { + l.second->set_level(log_level); + } + level_ = log_level; + } + + void flush_on(level::level_enum log_level) + { + std::lock_guard lock(logger_map_mutex_); + for (auto &l : loggers_) + { + l.second->flush_on(log_level); + } + flush_level_ = log_level; + } + + void flush_every(std::chrono::seconds interval) + { + std::lock_guard lock(flusher_mutex_); + std::function clbk = std::bind(®istry::flush_all, this); + periodic_flusher_.reset(new periodic_worker(clbk, interval)); + } + + void set_error_handler(log_err_handler handler) + { + std::lock_guard lock(logger_map_mutex_); + for (auto &l : loggers_) + { + l.second->set_error_handler(handler); + } + err_handler_ = handler; + } + + void apply_all(const std::function)> &fun) + { + std::lock_guard lock(logger_map_mutex_); + for (auto &l : loggers_) + { + fun(l.second); + } + } + + void flush_all() + { + std::lock_guard lock(logger_map_mutex_); + for (auto &l : loggers_) + { + l.second->flush(); + } + } + + void drop(const std::string &logger_name) + { + std::lock_guard lock(logger_map_mutex_); + loggers_.erase(logger_name); + } + + void drop_all() + { + std::lock_guard lock(logger_map_mutex_); + loggers_.clear(); + } + + // clean all reasources and threads started by the registry + void shutdown() + { + { + std::lock_guard lock(flusher_mutex_); + periodic_flusher_.reset(); + } + + drop_all(); + + { + std::lock_guard lock(tp_mutex_); + tp_.reset(); + } + } + + std::recursive_mutex &tp_mutex() + { + return tp_mutex_; + } + + static registry &instance() + { + static registry s_instance; + return s_instance; + } + +private: + registry() + : formatter_(new pattern_formatter("%+")) + { + } + + ~registry() = default; + + void throw_if_exists_(const std::string &logger_name) + { + if (loggers_.find(logger_name) != loggers_.end()) + { + throw spdlog_ex("logger with name '" + logger_name + "' already exists"); + } + } + + std::mutex logger_map_mutex_, flusher_mutex_; + std::recursive_mutex tp_mutex_; + std::unordered_map> loggers_; + std::unique_ptr formatter_; + level::level_enum level_ = level::info; + level::level_enum flush_level_ = level::off; + log_err_handler err_handler_; + std::shared_ptr tp_; + std::unique_ptr periodic_flusher_; +}; + +} // namespace details +} // namespace spdlog diff --git a/include/spdlog/details/thread_pool.h b/include/spdlog/details/thread_pool.h new file mode 100644 index 0000000..282d67e --- /dev/null +++ b/include/spdlog/details/thread_pool.h @@ -0,0 +1,225 @@ +#pragma once + +#include "spdlog/details/log_msg.h" +#include "spdlog/details/mpmc_blocking_q.h" +#include "spdlog/details/os.h" + +#include +#include +#include +#include + +namespace spdlog { +namespace details { + +using async_logger_ptr = std::shared_ptr; + +enum class async_msg_type +{ + log, + flush, + terminate +}; + +// Async msg to move to/from the queue +// Movable only. should never be copied +struct async_msg +{ + async_msg_type msg_type; + level::level_enum level; + log_clock::time_point time; + size_t thread_id; + fmt::basic_memory_buffer raw; + + size_t msg_id; + async_logger_ptr worker_ptr; + + async_msg() = default; + ~async_msg() = default; + + // should only be moved in or out of the queue.. + async_msg(const async_msg &) = delete; + +// support for vs2013 move +#if defined(_MSC_VER) && _MSC_VER <= 1800 + async_msg(async_msg &&other) SPDLOG_NOEXCEPT : msg_type(other.msg_type), + level(other.level), + time(other.time), + thread_id(other.thread_id), + raw(move(other.raw)), + msg_id(other.msg_id), + worker_ptr(std::move(other.worker_ptr)) + { + } + + async_msg &operator=(async_msg &&other) SPDLOG_NOEXCEPT + { + msg_type = other.msg_type; + level = other.level; + time = other.time; + thread_id = other.thread_id; + raw = std::move(other.raw); + msg_id = other.msg_id; + worker_ptr = std::move(other.worker_ptr); + return *this; + } +#else // (_MSC_VER) && _MSC_VER <= 1800 + async_msg(async_msg &&other) = default; + async_msg &operator=(async_msg &&other) = default; +#endif + + // construct from log_msg with given type + async_msg(async_logger_ptr &&worker, async_msg_type the_type, details::log_msg &&m) + : msg_type(the_type) + , level(m.level) + , time(m.time) + , thread_id(m.thread_id) + , msg_id(m.msg_id) + , worker_ptr(std::forward(worker)) + { + fmt_helper::append_buf(m.raw, raw); + } + + async_msg(async_logger_ptr &&worker, async_msg_type the_type) + : async_msg(std::forward(worker), the_type, details::log_msg()) + { + } + + explicit async_msg(async_msg_type the_type) + : async_msg(nullptr, the_type, details::log_msg()) + { + } + + // copy into log_msg + void to_log_msg(log_msg &msg) + { + msg.logger_name = &worker_ptr->name(); + msg.level = level; + msg.time = time; + msg.thread_id = thread_id; + fmt_helper::append_buf(raw, msg.raw); + msg.msg_id = msg_id; + msg.color_range_start = 0; + msg.color_range_end = 0; + } +}; + +class thread_pool +{ +public: + using item_type = async_msg; + using q_type = details::mpmc_blocking_queue; + + thread_pool(size_t q_max_items, size_t threads_n) + : q_(q_max_items) + { + // std::cout << "thread_pool() q_size_bytes: " << q_size_bytes << + // "\tthreads_n: " << threads_n << std::endl; + if (threads_n == 0 || threads_n > 1000) + { + throw spdlog_ex("spdlog::thread_pool(): invalid threads_n param (valid " + "range is 1-1000)"); + } + for (size_t i = 0; i < threads_n; i++) + { + threads_.emplace_back(&thread_pool::worker_loop_, this); + } + } + + // message all threads to terminate gracefully join them + ~thread_pool() + { + try + { + for (size_t i = 0; i < threads_.size(); i++) + { + post_async_msg_(async_msg(async_msg_type::terminate), async_overflow_policy::block); + } + + for (auto &t : threads_) + { + t.join(); + } + } + catch (...) + { + } + } + + void post_log(async_logger_ptr &&worker_ptr, details::log_msg &&msg, async_overflow_policy overflow_policy) + { + async_msg async_m(std::forward(worker_ptr), async_msg_type::log, std::forward(msg)); + post_async_msg_(std::move(async_m), overflow_policy); + } + + void post_flush(async_logger_ptr &&worker_ptr, async_overflow_policy overflow_policy) + { + post_async_msg_(async_msg(std::move(worker_ptr), async_msg_type::flush), overflow_policy); + } + + size_t overrun_counter() + { + return q_.overrun_counter(); + } + +private: + q_type q_; + + std::vector threads_; + + void post_async_msg_(async_msg &&new_msg, async_overflow_policy overflow_policy) + { + if (overflow_policy == async_overflow_policy::block) + { + q_.enqueue(std::move(new_msg)); + } + else + { + q_.enqueue_nowait(std::move(new_msg)); + } + } + + void worker_loop_() + { + while (process_next_msg_()) {}; + } + + // process next message in the queue + // return true if this thread should still be active (while no terminate msg + // was received) + bool process_next_msg_() + { + async_msg incoming_async_msg; + bool dequeued = q_.dequeue_for(incoming_async_msg, std::chrono::seconds(10)); + if (!dequeued) + { + return true; + } + + switch (incoming_async_msg.msg_type) + { + case async_msg_type::flush: + { + incoming_async_msg.worker_ptr->backend_flush_(); + return true; + } + + case async_msg_type::terminate: + { + return false; + } + + default: + { + log_msg msg; + incoming_async_msg.to_log_msg(msg); + incoming_async_msg.worker_ptr->backend_log_(msg); + return true; + } + } + return true; // should not be reached + } +}; + +} // namespace details +} // namespace spdlog diff --git a/include/spdlog/fmt/bundled/LICENSE.rst b/include/spdlog/fmt/bundled/LICENSE.rst new file mode 100644 index 0000000..eb6be65 --- /dev/null +++ b/include/spdlog/fmt/bundled/LICENSE.rst @@ -0,0 +1,23 @@ +Copyright (c) 2012 - 2016, Victor Zverovich + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/include/spdlog/fmt/bundled/colors.h b/include/spdlog/fmt/bundled/colors.h new file mode 100644 index 0000000..003a667 --- /dev/null +++ b/include/spdlog/fmt/bundled/colors.h @@ -0,0 +1,257 @@ +// Formatting library for C++ - the core API +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. +// +// Copyright (c) 2018 - present, Remotion (Igor Schulz) +// All Rights Reserved +// {fmt} support for rgb color output. + +#ifndef FMT_COLORS_H_ +#define FMT_COLORS_H_ + +#include "format.h" + +FMT_BEGIN_NAMESPACE + +// rgb is a struct for red, green and blue colors. +// We use rgb as name because some editors will show it as color direct in the +// editor. +struct rgb +{ + FMT_CONSTEXPR_DECL rgb() + : r(0) + , g(0) + , b(0) + { + } + FMT_CONSTEXPR_DECL rgb(uint8_t r_, uint8_t g_, uint8_t b_) + : r(r_) + , g(g_) + , b(b_) + { + } + FMT_CONSTEXPR_DECL rgb(uint32_t hex) + : r((hex >> 16) & 0xFF) + , g((hex >> 8) & 0xFF) + , b((hex)&0xFF) + { + } + uint8_t r; + uint8_t g; + uint8_t b; +}; + +namespace internal { + +FMT_CONSTEXPR inline void to_esc(uint8_t c, char out[], int offset) +{ + out[offset + 0] = static_cast('0' + c / 100); + out[offset + 1] = static_cast('0' + c / 10 % 10); + out[offset + 2] = static_cast('0' + c % 10); +} + +} // namespace internal + +FMT_FUNC void vprint_rgb(rgb fd, string_view format, format_args args) +{ + char escape_fd[] = "\x1b[38;2;000;000;000m"; + static FMT_CONSTEXPR_DECL const char RESET_COLOR[] = "\x1b[0m"; + internal::to_esc(fd.r, escape_fd, 7); + internal::to_esc(fd.g, escape_fd, 11); + internal::to_esc(fd.b, escape_fd, 15); + + std::fputs(escape_fd, stdout); + vprint(format, args); + std::fputs(RESET_COLOR, stdout); +} + +FMT_FUNC void vprint_rgb(rgb fd, rgb bg, string_view format, format_args args) +{ + char escape_fd[] = "\x1b[38;2;000;000;000m"; // foreground color + char escape_bg[] = "\x1b[48;2;000;000;000m"; // background color + static FMT_CONSTEXPR_DECL const char RESET_COLOR[] = "\x1b[0m"; + internal::to_esc(fd.r, escape_fd, 7); + internal::to_esc(fd.g, escape_fd, 11); + internal::to_esc(fd.b, escape_fd, 15); + + internal::to_esc(bg.r, escape_bg, 7); + internal::to_esc(bg.g, escape_bg, 11); + internal::to_esc(bg.b, escape_bg, 15); + + std::fputs(escape_fd, stdout); + std::fputs(escape_bg, stdout); + vprint(format, args); + std::fputs(RESET_COLOR, stdout); +} + +template +inline void print_rgb(rgb fd, string_view format_str, const Args &... args) +{ + vprint_rgb(fd, format_str, make_format_args(args...)); +} + +// rgb foreground color +template +inline void print(rgb fd, string_view format_str, const Args &... args) +{ + vprint_rgb(fd, format_str, make_format_args(args...)); +} + +// rgb foreground color and background color +template +inline void print(rgb fd, rgb bg, string_view format_str, const Args &... args) +{ + vprint_rgb(fd, bg, format_str, make_format_args(args...)); +} + +enum class color : uint32_t +{ + alice_blue = 0xF0F8FF, // rgb(240,248,255) + antique_white = 0xFAEBD7, // rgb(250,235,215) + aqua = 0x00FFFF, // rgb(0,255,255) + aquamarine = 0x7FFFD4, // rgb(127,255,212) + azure = 0xF0FFFF, // rgb(240,255,255) + beige = 0xF5F5DC, // rgb(245,245,220) + bisque = 0xFFE4C4, // rgb(255,228,196) + black = 0x000000, // rgb(0,0,0) + blanched_almond = 0xFFEBCD, // rgb(255,235,205) + blue = 0x0000FF, // rgb(0,0,255) + blue_violet = 0x8A2BE2, // rgb(138,43,226) + brown = 0xA52A2A, // rgb(165,42,42) + burly_wood = 0xDEB887, // rgb(222,184,135) + cadet_blue = 0x5F9EA0, // rgb(95,158,160) + chartreuse = 0x7FFF00, // rgb(127,255,0) + chocolate = 0xD2691E, // rgb(210,105,30) + coral = 0xFF7F50, // rgb(255,127,80) + cornflower_blue = 0x6495ED, // rgb(100,149,237) + cornsilk = 0xFFF8DC, // rgb(255,248,220) + crimson = 0xDC143C, // rgb(220,20,60) + cyan = 0x00FFFF, // rgb(0,255,255) + dark_blue = 0x00008B, // rgb(0,0,139) + dark_cyan = 0x008B8B, // rgb(0,139,139) + dark_golden_rod = 0xB8860B, // rgb(184,134,11) + dark_gray = 0xA9A9A9, // rgb(169,169,169) + dark_green = 0x006400, // rgb(0,100,0) + dark_khaki = 0xBDB76B, // rgb(189,183,107) + dark_magenta = 0x8B008B, // rgb(139,0,139) + dark_olive_green = 0x556B2F, // rgb(85,107,47) + dark_orange = 0xFF8C00, // rgb(255,140,0) + dark_orchid = 0x9932CC, // rgb(153,50,204) + dark_red = 0x8B0000, // rgb(139,0,0) + dark_salmon = 0xE9967A, // rgb(233,150,122) + dark_sea_green = 0x8FBC8F, // rgb(143,188,143) + dark_slate_blue = 0x483D8B, // rgb(72,61,139) + dark_slate_gray = 0x2F4F4F, // rgb(47,79,79) + dark_turquoise = 0x00CED1, // rgb(0,206,209) + dark_violet = 0x9400D3, // rgb(148,0,211) + deep_pink = 0xFF1493, // rgb(255,20,147) + deep_sky_blue = 0x00BFFF, // rgb(0,191,255) + dim_gray = 0x696969, // rgb(105,105,105) + dodger_blue = 0x1E90FF, // rgb(30,144,255) + fire_brick = 0xB22222, // rgb(178,34,34) + floral_white = 0xFFFAF0, // rgb(255,250,240) + forest_green = 0x228B22, // rgb(34,139,34) + fuchsia = 0xFF00FF, // rgb(255,0,255) + gainsboro = 0xDCDCDC, // rgb(220,220,220) + ghost_white = 0xF8F8FF, // rgb(248,248,255) + gold = 0xFFD700, // rgb(255,215,0) + golden_rod = 0xDAA520, // rgb(218,165,32) + gray = 0x808080, // rgb(128,128,128) + green = 0x008000, // rgb(0,128,0) + green_yellow = 0xADFF2F, // rgb(173,255,47) + honey_dew = 0xF0FFF0, // rgb(240,255,240) + hot_pink = 0xFF69B4, // rgb(255,105,180) + indian_red = 0xCD5C5C, // rgb(205,92,92) + indigo = 0x4B0082, // rgb(75,0,130) + ivory = 0xFFFFF0, // rgb(255,255,240) + khaki = 0xF0E68C, // rgb(240,230,140) + lavender = 0xE6E6FA, // rgb(230,230,250) + lavender_blush = 0xFFF0F5, // rgb(255,240,245) + lawn_green = 0x7CFC00, // rgb(124,252,0) + lemon_chiffon = 0xFFFACD, // rgb(255,250,205) + light_blue = 0xADD8E6, // rgb(173,216,230) + light_coral = 0xF08080, // rgb(240,128,128) + light_cyan = 0xE0FFFF, // rgb(224,255,255) + light_golden_rod_yellow = 0xFAFAD2, // rgb(250,250,210) + light_gray = 0xD3D3D3, // rgb(211,211,211) + light_green = 0x90EE90, // rgb(144,238,144) + light_pink = 0xFFB6C1, // rgb(255,182,193) + light_salmon = 0xFFA07A, // rgb(255,160,122) + light_sea_green = 0x20B2AA, // rgb(32,178,170) + light_sky_blue = 0x87CEFA, // rgb(135,206,250) + light_slate_gray = 0x778899, // rgb(119,136,153) + light_steel_blue = 0xB0C4DE, // rgb(176,196,222) + light_yellow = 0xFFFFE0, // rgb(255,255,224) + lime = 0x00FF00, // rgb(0,255,0) + lime_green = 0x32CD32, // rgb(50,205,50) + linen = 0xFAF0E6, // rgb(250,240,230) + magenta = 0xFF00FF, // rgb(255,0,255) + maroon = 0x800000, // rgb(128,0,0) + medium_aqua_marine = 0x66CDAA, // rgb(102,205,170) + medium_blue = 0x0000CD, // rgb(0,0,205) + medium_orchid = 0xBA55D3, // rgb(186,85,211) + medium_purple = 0x9370DB, // rgb(147,112,219) + medium_sea_green = 0x3CB371, // rgb(60,179,113) + medium_slate_blue = 0x7B68EE, // rgb(123,104,238) + medium_spring_green = 0x00FA9A, // rgb(0,250,154) + medium_turquoise = 0x48D1CC, // rgb(72,209,204) + medium_violet_red = 0xC71585, // rgb(199,21,133) + midnight_blue = 0x191970, // rgb(25,25,112) + mint_cream = 0xF5FFFA, // rgb(245,255,250) + misty_rose = 0xFFE4E1, // rgb(255,228,225) + moccasin = 0xFFE4B5, // rgb(255,228,181) + navajo_white = 0xFFDEAD, // rgb(255,222,173) + navy = 0x000080, // rgb(0,0,128) + old_lace = 0xFDF5E6, // rgb(253,245,230) + olive = 0x808000, // rgb(128,128,0) + olive_drab = 0x6B8E23, // rgb(107,142,35) + orange = 0xFFA500, // rgb(255,165,0) + orange_red = 0xFF4500, // rgb(255,69,0) + orchid = 0xDA70D6, // rgb(218,112,214) + pale_golden_rod = 0xEEE8AA, // rgb(238,232,170) + pale_green = 0x98FB98, // rgb(152,251,152) + pale_turquoise = 0xAFEEEE, // rgb(175,238,238) + pale_violet_red = 0xDB7093, // rgb(219,112,147) + papaya_whip = 0xFFEFD5, // rgb(255,239,213) + peach_puff = 0xFFDAB9, // rgb(255,218,185) + peru = 0xCD853F, // rgb(205,133,63) + pink = 0xFFC0CB, // rgb(255,192,203) + plum = 0xDDA0DD, // rgb(221,160,221) + powder_blue = 0xB0E0E6, // rgb(176,224,230) + purple = 0x800080, // rgb(128,0,128) + rebecca_purple = 0x663399, // rgb(102,51,153) + red = 0xFF0000, // rgb(255,0,0) + rosy_brown = 0xBC8F8F, // rgb(188,143,143) + royal_blue = 0x4169E1, // rgb(65,105,225) + saddle_brown = 0x8B4513, // rgb(139,69,19) + salmon = 0xFA8072, // rgb(250,128,114) + sandy_brown = 0xF4A460, // rgb(244,164,96) + sea_green = 0x2E8B57, // rgb(46,139,87) + sea_shell = 0xFFF5EE, // rgb(255,245,238) + sienna = 0xA0522D, // rgb(160,82,45) + silver = 0xC0C0C0, // rgb(192,192,192) + sky_blue = 0x87CEEB, // rgb(135,206,235) + slate_blue = 0x6A5ACD, // rgb(106,90,205) + slate_gray = 0x708090, // rgb(112,128,144) + snow = 0xFFFAFA, // rgb(255,250,250) + spring_green = 0x00FF7F, // rgb(0,255,127) + steel_blue = 0x4682B4, // rgb(70,130,180) + tan = 0xD2B48C, // rgb(210,180,140) + teal = 0x008080, // rgb(0,128,128) + thistle = 0xD8BFD8, // rgb(216,191,216) + tomato = 0xFF6347, // rgb(255,99,71) + turquoise = 0x40E0D0, // rgb(64,224,208) + violet = 0xEE82EE, // rgb(238,130,238) + wheat = 0xF5DEB3, // rgb(245,222,179) + white = 0xFFFFFF, // rgb(255,255,255) + white_smoke = 0xF5F5F5, // rgb(245,245,245) + yellow = 0xFFFF00, // rgb(255,255,0) + yellow_green = 0x9ACD32, // rgb(154,205,50) +}; // enum class colors + +FMT_END_NAMESPACE + +#endif // FMT_COLORS_H_ diff --git a/include/spdlog/fmt/bundled/core.h b/include/spdlog/fmt/bundled/core.h new file mode 100644 index 0000000..19273a6 --- /dev/null +++ b/include/spdlog/fmt/bundled/core.h @@ -0,0 +1,1578 @@ +// Formatting library for C++ - the core API +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_CORE_H_ +#define FMT_CORE_H_ + +#include +#include +#include +#include +#include +#include + +// The fmt library version in the form major * 10000 + minor * 100 + patch. +#define FMT_VERSION 50100 + +#ifdef __has_feature +#define FMT_HAS_FEATURE(x) __has_feature(x) +#else +#define FMT_HAS_FEATURE(x) 0 +#endif + +#ifdef __has_include +#define FMT_HAS_INCLUDE(x) __has_include(x) +#else +#define FMT_HAS_INCLUDE(x) 0 +#endif + +#ifdef __has_cpp_attribute +#define FMT_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x) +#else +#define FMT_HAS_CPP_ATTRIBUTE(x) 0 +#endif + +#if defined(__GNUC__) && !defined(__clang__) +#define FMT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) +#else +#define FMT_GCC_VERSION 0 +#endif + +#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) +#define FMT_HAS_GXX_CXX11 FMT_GCC_VERSION +#else +#define FMT_HAS_GXX_CXX11 0 +#endif + +#ifdef _MSC_VER +#define FMT_MSC_VER _MSC_VER +#else +#define FMT_MSC_VER 0 +#endif + +// Check if relaxed c++14 constexpr is supported. +// GCC doesn't allow throw in constexpr until version 6 (bug 67371). +#ifndef FMT_USE_CONSTEXPR +#define FMT_USE_CONSTEXPR \ + (FMT_HAS_FEATURE(cxx_relaxed_constexpr) || FMT_MSC_VER >= 1910 || (FMT_GCC_VERSION >= 600 && __cplusplus >= 201402L)) +#endif +#if FMT_USE_CONSTEXPR +#define FMT_CONSTEXPR constexpr +#define FMT_CONSTEXPR_DECL constexpr +#else +#define FMT_CONSTEXPR inline +#define FMT_CONSTEXPR_DECL +#endif + +#ifndef FMT_OVERRIDE +#if FMT_HAS_FEATURE(cxx_override) || (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900 +#define FMT_OVERRIDE override +#else +#define FMT_OVERRIDE +#endif +#endif + +#if FMT_HAS_FEATURE(cxx_explicit_conversions) || FMT_MSC_VER >= 1800 +#define FMT_EXPLICIT explicit +#else +#define FMT_EXPLICIT +#endif + +#ifndef FMT_NULL +#if FMT_HAS_FEATURE(cxx_nullptr) || (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1600 +#define FMT_NULL nullptr +#define FMT_USE_NULLPTR 1 +#else +#define FMT_NULL NULL +#endif +#endif + +#ifndef FMT_USE_NULLPTR +#define FMT_USE_NULLPTR 0 +#endif + +#if FMT_HAS_CPP_ATTRIBUTE(noreturn) +#define FMT_NORETURN [[noreturn]] +#else +#define FMT_NORETURN +#endif + +// Check if exceptions are disabled. +#if defined(__GNUC__) && !defined(__EXCEPTIONS) +#define FMT_EXCEPTIONS 0 +#elif FMT_MSC_VER && !_HAS_EXCEPTIONS +#define FMT_EXCEPTIONS 0 +#endif +#ifndef FMT_EXCEPTIONS +#define FMT_EXCEPTIONS 1 +#endif + +// Define FMT_USE_NOEXCEPT to make fmt use noexcept (C++11 feature). +#ifndef FMT_USE_NOEXCEPT +#define FMT_USE_NOEXCEPT 0 +#endif + +#if FMT_USE_NOEXCEPT || FMT_HAS_FEATURE(cxx_noexcept) || (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900 +#define FMT_DETECTED_NOEXCEPT noexcept +#else +#define FMT_DETECTED_NOEXCEPT throw() +#endif + +#ifndef FMT_NOEXCEPT +#if FMT_EXCEPTIONS +#define FMT_NOEXCEPT FMT_DETECTED_NOEXCEPT +#else +#define FMT_NOEXCEPT +#endif +#endif + +// This is needed because GCC still uses throw() in its headers when exceptions +// are disabled. +#if FMT_GCC_VERSION +#define FMT_DTOR_NOEXCEPT FMT_DETECTED_NOEXCEPT +#else +#define FMT_DTOR_NOEXCEPT FMT_NOEXCEPT +#endif + +#ifndef FMT_BEGIN_NAMESPACE +#if FMT_HAS_FEATURE(cxx_inline_namespaces) || FMT_GCC_VERSION >= 404 || FMT_MSC_VER >= 1900 +#define FMT_INLINE_NAMESPACE inline namespace +#define FMT_END_NAMESPACE \ + } \ + } +#else +#define FMT_INLINE_NAMESPACE namespace +#define FMT_END_NAMESPACE \ + } \ + using namespace v5; \ + } +#endif +#define FMT_BEGIN_NAMESPACE \ + namespace fmt { \ + FMT_INLINE_NAMESPACE v5 \ + { +#endif + +#if !defined(FMT_HEADER_ONLY) && defined(_WIN32) +#ifdef FMT_EXPORT +#define FMT_API __declspec(dllexport) +#elif defined(FMT_SHARED) +#define FMT_API __declspec(dllimport) +#endif +#endif +#ifndef FMT_API +#define FMT_API +#endif + +#ifndef FMT_ASSERT +#define FMT_ASSERT(condition, message) assert((condition) && message) +#endif + +#define FMT_DELETED = delete + +// A macro to disallow the copy construction and assignment. +#define FMT_DISALLOW_COPY_AND_ASSIGN(Type) \ + Type(const Type &) FMT_DELETED; \ + void operator=(const Type &) FMT_DELETED + +// libc++ supports string_view in pre-c++17. +#if (FMT_HAS_INCLUDE() && (__cplusplus > 201402L || defined(_LIBCPP_VERSION))) || \ + (defined(_MSVC_LANG) && _MSVC_LANG > 201402L && _MSC_VER >= 1910) +#include +#define FMT_USE_STD_STRING_VIEW +#elif (FMT_HAS_INCLUDE() && __cplusplus >= 201402L) +#include +#define FMT_USE_EXPERIMENTAL_STRING_VIEW +#endif + +// std::result_of is defined in in gcc 4.4. +#if FMT_GCC_VERSION && FMT_GCC_VERSION <= 404 +#include +#endif + +FMT_BEGIN_NAMESPACE + +namespace internal { + +// An implementation of declval for pre-C++11 compilers such as gcc 4. +template +typename std::add_rvalue_reference::type declval() FMT_NOEXCEPT; + +// Casts nonnegative integer to unsigned. +template +FMT_CONSTEXPR typename std::make_unsigned::type to_unsigned(Int value) +{ + FMT_ASSERT(value >= 0, "negative value"); + return static_cast::type>(value); +} + +} // namespace internal + +/** + An implementation of ``std::basic_string_view`` for pre-C++17. It provides a + subset of the API. ``fmt::basic_string_view`` is used for format strings even + if ``std::string_view`` is available to prevent issues when a library is + compiled with a different ``-std`` option than the client code (which is not + recommended). + */ +template +class basic_string_view +{ +private: + const Char *data_; + size_t size_; + +public: + typedef Char char_type; + typedef const Char *iterator; + +// Standard basic_string_view type. +#if defined(FMT_USE_STD_STRING_VIEW) + typedef std::basic_string_view type; +#elif defined(FMT_USE_EXPERIMENTAL_STRING_VIEW) + typedef std::experimental::basic_string_view type; +#else + struct type + { + const char *data() const + { + return FMT_NULL; + } + size_t size() const + { + return 0; + } + }; +#endif + + FMT_CONSTEXPR basic_string_view() FMT_NOEXCEPT : data_(FMT_NULL), size_(0) {} + + /** Constructs a string reference object from a C string and a size. */ + FMT_CONSTEXPR basic_string_view(const Char *s, size_t count) FMT_NOEXCEPT : data_(s), size_(count) {} + + /** + \rst + Constructs a string reference object from a C string computing + the size with ``std::char_traits::length``. + \endrst + */ + basic_string_view(const Char *s) + : data_(s) + , size_(std::char_traits::length(s)) + { + } + + /** Constructs a string reference from a ``std::basic_string`` object. */ + template + FMT_CONSTEXPR basic_string_view(const std::basic_string &s) FMT_NOEXCEPT : data_(s.c_str()), size_(s.size()) + { + } + + FMT_CONSTEXPR basic_string_view(type s) FMT_NOEXCEPT : data_(s.data()), size_(s.size()) {} + + /** Returns a pointer to the string data. */ + const Char *data() const + { + return data_; + } + + /** Returns the string size. */ + FMT_CONSTEXPR size_t size() const + { + return size_; + } + + FMT_CONSTEXPR iterator begin() const + { + return data_; + } + FMT_CONSTEXPR iterator end() const + { + return data_ + size_; + } + + FMT_CONSTEXPR void remove_prefix(size_t n) + { + data_ += n; + size_ -= n; + } + + // Lexicographically compare this string reference to other. + int compare(basic_string_view other) const + { + size_t str_size = size_ < other.size_ ? size_ : other.size_; + int result = std::char_traits::compare(data_, other.data_, str_size); + if (result == 0) + result = size_ == other.size_ ? 0 : (size_ < other.size_ ? -1 : 1); + return result; + } + + friend bool operator==(basic_string_view lhs, basic_string_view rhs) + { + return lhs.compare(rhs) == 0; + } + friend bool operator!=(basic_string_view lhs, basic_string_view rhs) + { + return lhs.compare(rhs) != 0; + } + friend bool operator<(basic_string_view lhs, basic_string_view rhs) + { + return lhs.compare(rhs) < 0; + } + friend bool operator<=(basic_string_view lhs, basic_string_view rhs) + { + return lhs.compare(rhs) <= 0; + } + friend bool operator>(basic_string_view lhs, basic_string_view rhs) + { + return lhs.compare(rhs) > 0; + } + friend bool operator>=(basic_string_view lhs, basic_string_view rhs) + { + return lhs.compare(rhs) >= 0; + } +}; + +typedef basic_string_view string_view; +typedef basic_string_view wstring_view; + +template +class basic_format_arg; + +template +class basic_format_args; + +// A formatter for objects of type T. +template +struct formatter; + +namespace internal { + +/** A contiguous memory buffer with an optional growing ability. */ +template +class basic_buffer +{ +private: + FMT_DISALLOW_COPY_AND_ASSIGN(basic_buffer); + + T *ptr_; + std::size_t size_; + std::size_t capacity_; + +protected: + basic_buffer(T *p = FMT_NULL, std::size_t sz = 0, std::size_t cap = 0) FMT_NOEXCEPT : ptr_(p), size_(sz), capacity_(cap) {} + + /** Sets the buffer data and capacity. */ + void set(T *buf_data, std::size_t buf_capacity) FMT_NOEXCEPT + { + ptr_ = buf_data; + capacity_ = buf_capacity; + } + + /** Increases the buffer capacity to hold at least *capacity* elements. */ + virtual void grow(std::size_t capacity) = 0; + +public: + typedef T value_type; + typedef const T &const_reference; + + virtual ~basic_buffer() {} + + T *begin() FMT_NOEXCEPT + { + return ptr_; + } + T *end() FMT_NOEXCEPT + { + return ptr_ + size_; + } + + /** Returns the size of this buffer. */ + std::size_t size() const FMT_NOEXCEPT + { + return size_; + } + + /** Returns the capacity of this buffer. */ + std::size_t capacity() const FMT_NOEXCEPT + { + return capacity_; + } + + /** Returns a pointer to the buffer data. */ + T *data() FMT_NOEXCEPT + { + return ptr_; + } + + /** Returns a pointer to the buffer data. */ + const T *data() const FMT_NOEXCEPT + { + return ptr_; + } + + /** + Resizes the buffer. If T is a POD type new elements may not be initialized. + */ + void resize(std::size_t new_size) + { + reserve(new_size); + size_ = new_size; + } + + /** Reserves space to store at least *capacity* elements. */ + void reserve(std::size_t new_capacity) + { + if (new_capacity > capacity_) + grow(new_capacity); + } + + void push_back(const T &value) + { + reserve(size_ + 1); + ptr_[size_++] = value; + } + + /** Appends data to the end of the buffer. */ + template + void append(const U *begin, const U *end); + + T &operator[](std::size_t index) + { + return ptr_[index]; + } + const T &operator[](std::size_t index) const + { + return ptr_[index]; + } +}; + +typedef basic_buffer buffer; +typedef basic_buffer wbuffer; + +// A container-backed buffer. +template +class container_buffer : public basic_buffer +{ +private: + Container &container_; + +protected: + void grow(std::size_t capacity) FMT_OVERRIDE + { + container_.resize(capacity); + this->set(&container_[0], capacity); + } + +public: + explicit container_buffer(Container &c) + : basic_buffer(&c[0], c.size(), c.size()) + , container_(c) + { + } +}; + +struct error_handler +{ + FMT_CONSTEXPR error_handler() {} + FMT_CONSTEXPR error_handler(const error_handler &) {} + + // This function is intentionally not constexpr to give a compile-time error. + FMT_API void on_error(const char *message); +}; + +// Formatting of wide characters and strings into a narrow output is disallowed: +// fmt::format("{}", L"test"); // error +// To fix this, use a wide format string: +// fmt::format(L"{}", L"test"); +template +inline void require_wchar() +{ + static_assert(std::is_same::value, "formatting of wide characters into a narrow output is disallowed"); +} + +template +struct named_arg_base; + +template +struct named_arg; + +template +struct is_named_arg : std::false_type +{ +}; + +template +struct is_named_arg> : std::true_type +{ +}; + +enum type +{ + none_type, + named_arg_type, + // Integer types should go first, + int_type, + uint_type, + long_long_type, + ulong_long_type, + bool_type, + char_type, + last_integer_type = char_type, + // followed by floating-point types. + double_type, + long_double_type, + last_numeric_type = long_double_type, + cstring_type, + string_type, + pointer_type, + custom_type +}; + +FMT_CONSTEXPR bool is_integral(type t) +{ + FMT_ASSERT(t != internal::named_arg_type, "invalid argument type"); + return t > internal::none_type && t <= internal::last_integer_type; +} + +FMT_CONSTEXPR bool is_arithmetic(type t) +{ + FMT_ASSERT(t != internal::named_arg_type, "invalid argument type"); + return t > internal::none_type && t <= internal::last_numeric_type; +} + +template +struct convert_to_int +{ + enum + { + value = !std::is_arithmetic::value && std::is_convertible::value + }; +}; + +template +struct string_value +{ + const Char *value; + std::size_t size; +}; + +template +struct custom_value +{ + const void *value; + void (*format)(const void *arg, Context &ctx); +}; + +// A formatting argument value. +template +class value +{ +public: + typedef typename Context::char_type char_type; + + union + { + int int_value; + unsigned uint_value; + long long long_long_value; + unsigned long long ulong_long_value; + double double_value; + long double long_double_value; + const void *pointer; + string_value string; + string_value sstring; + string_value ustring; + custom_value custom; + }; + + FMT_CONSTEXPR value(int val = 0) + : int_value(val) + { + } + value(unsigned val) + { + uint_value = val; + } + value(long long val) + { + long_long_value = val; + } + value(unsigned long long val) + { + ulong_long_value = val; + } + value(double val) + { + double_value = val; + } + value(long double val) + { + long_double_value = val; + } + value(const char_type *val) + { + string.value = val; + } + value(const signed char *val) + { + static_assert(std::is_same::value, "incompatible string types"); + sstring.value = val; + } + value(const unsigned char *val) + { + static_assert(std::is_same::value, "incompatible string types"); + ustring.value = val; + } + value(basic_string_view val) + { + string.value = val.data(); + string.size = val.size(); + } + value(const void *val) + { + pointer = val; + } + + template + explicit value(const T &val) + { + custom.value = &val; + custom.format = &format_custom_arg; + } + + const named_arg_base &as_named_arg() + { + return *static_cast *>(pointer); + } + +private: + // Formats an argument of a custom type, such as a user-defined class. + template + static void format_custom_arg(const void *arg, Context &ctx) + { + // Get the formatter type through the context to allow different contexts + // have different extension points, e.g. `formatter` for `format` and + // `printf_formatter` for `printf`. + typename Context::template formatter_type::type f; + auto &&parse_ctx = ctx.parse_context(); + parse_ctx.advance_to(f.parse(parse_ctx)); + ctx.advance_to(f.format(*static_cast(arg), ctx)); + } +}; + +template +struct typed_value : value +{ + static const type type_tag = TYPE; + + template + FMT_CONSTEXPR typed_value(const T &val) + : value(val) + { + } +}; + +template +FMT_CONSTEXPR basic_format_arg make_arg(const T &value); + +#define FMT_MAKE_VALUE(TAG, ArgType, ValueType) \ + template \ + FMT_CONSTEXPR typed_value make_value(ArgType val) \ + { \ + return static_cast(val); \ + } + +#define FMT_MAKE_VALUE_SAME(TAG, Type) \ + template \ + FMT_CONSTEXPR typed_value make_value(Type val) \ + { \ + return val; \ + } + +FMT_MAKE_VALUE(bool_type, bool, int) +FMT_MAKE_VALUE(int_type, short, int) +FMT_MAKE_VALUE(uint_type, unsigned short, unsigned) +FMT_MAKE_VALUE_SAME(int_type, int) +FMT_MAKE_VALUE_SAME(uint_type, unsigned) + +// To minimize the number of types we need to deal with, long is translated +// either to int or to long long depending on its size. +typedef std::conditional::type long_type; +FMT_MAKE_VALUE((sizeof(long) == sizeof(int) ? int_type : long_long_type), long, long_type) +typedef std::conditional::type ulong_type; +FMT_MAKE_VALUE((sizeof(unsigned long) == sizeof(unsigned) ? uint_type : ulong_long_type), unsigned long, ulong_type) + +FMT_MAKE_VALUE_SAME(long_long_type, long long) +FMT_MAKE_VALUE_SAME(ulong_long_type, unsigned long long) +FMT_MAKE_VALUE(int_type, signed char, int) +FMT_MAKE_VALUE(uint_type, unsigned char, unsigned) +FMT_MAKE_VALUE(char_type, char, int) + +#if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED) +template +inline typed_value make_value(wchar_t val) +{ + require_wchar(); + return static_cast(val); +} +#endif + +FMT_MAKE_VALUE(double_type, float, double) +FMT_MAKE_VALUE_SAME(double_type, double) +FMT_MAKE_VALUE_SAME(long_double_type, long double) + +// Formatting of wide strings into a narrow buffer and multibyte strings +// into a wide buffer is disallowed (https://github.com/fmtlib/fmt/pull/606). +FMT_MAKE_VALUE(cstring_type, typename C::char_type *, const typename C::char_type *) +FMT_MAKE_VALUE(cstring_type, const typename C::char_type *, const typename C::char_type *) + +FMT_MAKE_VALUE(cstring_type, signed char *, const signed char *) +FMT_MAKE_VALUE_SAME(cstring_type, const signed char *) +FMT_MAKE_VALUE(cstring_type, unsigned char *, const unsigned char *) +FMT_MAKE_VALUE_SAME(cstring_type, const unsigned char *) +FMT_MAKE_VALUE_SAME(string_type, basic_string_view) +FMT_MAKE_VALUE(string_type, typename basic_string_view::type, basic_string_view) +FMT_MAKE_VALUE(string_type, const std::basic_string &, basic_string_view) +FMT_MAKE_VALUE(pointer_type, void *, const void *) +FMT_MAKE_VALUE_SAME(pointer_type, const void *) + +#if FMT_USE_NULLPTR +FMT_MAKE_VALUE(pointer_type, std::nullptr_t, const void *) +#endif + +// Formatting of arbitrary pointers is disallowed. If you want to output a +// pointer cast it to "void *" or "const void *". In particular, this forbids +// formatting of "[const] volatile char *" which is printed as bool by +// iostreams. +template +typename std::enable_if::value>::type make_value(const T *) +{ + static_assert(!sizeof(T), "formatting of non-void pointers is disallowed"); +} + +template +inline typename std::enable_if::value && convert_to_int::value, typed_value>::type +make_value(const T &val) +{ + return static_cast(val); +} + +template +inline typename std::enable_if::value && !std::is_convertible>::value, + // Implicit conversion to std::string is not handled here because it's + // unsafe: https://github.com/fmtlib/fmt/issues/729 + typed_value>::type +make_value(const T &val) +{ + return val; +} + +template +typed_value make_value(const named_arg &val) +{ + basic_format_arg arg = make_arg(val.value); + std::memcpy(val.data, &arg, sizeof(arg)); + return static_cast(&val); +} + +// Maximum number of arguments with packed types. +enum +{ + max_packed_args = 15 +}; + +template +class arg_map; + +template +struct result_of; + +template +struct result_of +{ + // A workaround for gcc 4.4 that doesn't allow F to be a reference. + typedef typename std::result_of::type(Args...)>::type type; +}; +} // namespace internal + +// A formatting argument. It is a trivially copyable/constructible type to +// allow storage in basic_memory_buffer. +template +class basic_format_arg +{ +private: + internal::value value_; + internal::type type_; + + template + friend FMT_CONSTEXPR basic_format_arg internal::make_arg(const T &value); + + template + friend FMT_CONSTEXPR typename internal::result_of::type visit(Visitor &&vis, basic_format_arg arg); + + friend class basic_format_args; + friend class internal::arg_map; + + typedef typename Context::char_type char_type; + +public: + class handle + { + public: + explicit handle(internal::custom_value custom) + : custom_(custom) + { + } + + void format(Context &ctx) const + { + custom_.format(custom_.value, ctx); + } + + private: + internal::custom_value custom_; + }; + + FMT_CONSTEXPR basic_format_arg() + : type_(internal::none_type) + { + } + + FMT_EXPLICIT operator bool() const FMT_NOEXCEPT + { + return type_ != internal::none_type; + } + + internal::type type() const + { + return type_; + } + + bool is_integral() const + { + return internal::is_integral(type_); + } + bool is_arithmetic() const + { + return internal::is_arithmetic(type_); + } +}; + +// Parsing context consisting of a format string range being parsed and an +// argument counter for automatic indexing. +template +class basic_parse_context : private ErrorHandler +{ +private: + basic_string_view format_str_; + int next_arg_id_; + +public: + typedef Char char_type; + typedef typename basic_string_view::iterator iterator; + + explicit FMT_CONSTEXPR basic_parse_context(basic_string_view format_str, ErrorHandler eh = ErrorHandler()) + : ErrorHandler(eh) + , format_str_(format_str) + , next_arg_id_(0) + { + } + + // Returns an iterator to the beginning of the format string range being + // parsed. + FMT_CONSTEXPR iterator begin() const FMT_NOEXCEPT + { + return format_str_.begin(); + } + + // Returns an iterator past the end of the format string range being parsed. + FMT_CONSTEXPR iterator end() const FMT_NOEXCEPT + { + return format_str_.end(); + } + + // Advances the begin iterator to ``it``. + FMT_CONSTEXPR void advance_to(iterator it) + { + format_str_.remove_prefix(internal::to_unsigned(it - begin())); + } + + // Returns the next argument index. + FMT_CONSTEXPR unsigned next_arg_id(); + + FMT_CONSTEXPR bool check_arg_id(unsigned) + { + if (next_arg_id_ > 0) + { + on_error("cannot switch from automatic to manual argument indexing"); + return false; + } + next_arg_id_ = -1; + return true; + } + void check_arg_id(basic_string_view) {} + + FMT_CONSTEXPR void on_error(const char *message) + { + ErrorHandler::on_error(message); + } + + FMT_CONSTEXPR ErrorHandler error_handler() const + { + return *this; + } +}; + +typedef basic_parse_context parse_context; +typedef basic_parse_context wparse_context; + +namespace internal { +// A map from argument names to their values for named arguments. +template +class arg_map +{ +private: + FMT_DISALLOW_COPY_AND_ASSIGN(arg_map); + + typedef typename Context::char_type char_type; + + struct entry + { + basic_string_view name; + basic_format_arg arg; + }; + + entry *map_; + unsigned size_; + + void push_back(value val) + { + const internal::named_arg_base &named = val.as_named_arg(); + map_[size_] = entry{named.name, named.template deserialize()}; + ++size_; + } + +public: + arg_map() + : map_(FMT_NULL) + , size_(0) + { + } + void init(const basic_format_args &args); + ~arg_map() + { + delete[] map_; + } + + basic_format_arg find(basic_string_view name) const + { + // The list is unsorted, so just return the first matching name. + for (entry *it = map_, *end = map_ + size_; it != end; ++it) + { + if (it->name == name) + return it->arg; + } + return basic_format_arg(); + } +}; + +template +class context_base +{ +public: + typedef OutputIt iterator; + +private: + basic_parse_context parse_context_; + iterator out_; + basic_format_args args_; + +protected: + typedef Char char_type; + typedef basic_format_arg format_arg; + + context_base(OutputIt out, basic_string_view format_str, basic_format_args ctx_args) + : parse_context_(format_str) + , out_(out) + , args_(ctx_args) + { + } + + // Returns the argument with specified index. + format_arg do_get_arg(unsigned arg_id) + { + format_arg arg = args_.get(arg_id); + if (!arg) + parse_context_.on_error("argument index out of range"); + return arg; + } + + // Checks if manual indexing is used and returns the argument with + // specified index. + format_arg get_arg(unsigned arg_id) + { + return this->parse_context().check_arg_id(arg_id) ? this->do_get_arg(arg_id) : format_arg(); + } + +public: + basic_parse_context &parse_context() + { + return parse_context_; + } + + internal::error_handler error_handler() + { + return parse_context_.error_handler(); + } + + void on_error(const char *message) + { + parse_context_.on_error(message); + } + + // Returns an iterator to the beginning of the output range. + iterator out() + { + return out_; + } + iterator begin() + { + return out_; + } // deprecated + + // Advances the begin iterator to ``it``. + void advance_to(iterator it) + { + out_ = it; + } + + basic_format_args args() const + { + return args_; + } +}; + +// Extracts a reference to the container from back_insert_iterator. +template +inline Container &get_container(std::back_insert_iterator it) +{ + typedef std::back_insert_iterator bi_iterator; + struct accessor : bi_iterator + { + accessor(bi_iterator iter) + : bi_iterator(iter) + { + } + using bi_iterator::container; + }; + return *accessor(it).container; +} +} // namespace internal + +// Formatting context. +template +class basic_format_context : public internal::context_base, Char> +{ +public: + /** The character type for the output. */ + typedef Char char_type; + + // using formatter_type = formatter; + template + struct formatter_type + { + typedef formatter type; + }; + +private: + internal::arg_map map_; + + FMT_DISALLOW_COPY_AND_ASSIGN(basic_format_context); + + typedef internal::context_base base; + typedef typename base::format_arg format_arg; + using base::get_arg; + +public: + using typename base::iterator; + + /** + Constructs a ``basic_format_context`` object. References to the arguments are + stored in the object so make sure they have appropriate lifetimes. + */ + basic_format_context(OutputIt out, basic_string_view format_str, basic_format_args ctx_args) + : base(out, format_str, ctx_args) + { + } + + format_arg next_arg() + { + return this->do_get_arg(this->parse_context().next_arg_id()); + } + format_arg get_arg(unsigned arg_id) + { + return this->do_get_arg(arg_id); + } + + // Checks if manual indexing is used and returns the argument with the + // specified name. + format_arg get_arg(basic_string_view name); +}; + +template +struct buffer_context +{ + typedef basic_format_context>, Char> type; +}; +typedef buffer_context::type format_context; +typedef buffer_context::type wformat_context; + +namespace internal { +template +struct get_type +{ + typedef decltype(make_value(declval::type &>())) value_type; + static const type value = value_type::type_tag; +}; + +template +FMT_CONSTEXPR unsigned long long get_types() +{ + return 0; +} + +template +FMT_CONSTEXPR unsigned long long get_types() +{ + return get_type::value | (get_types() << 4); +} + +template +FMT_CONSTEXPR basic_format_arg make_arg(const T &value) +{ + basic_format_arg arg; + arg.type_ = get_type::value; + arg.value_ = make_value(value); + return arg; +} + +template +inline typename std::enable_if>::type make_arg(const T &value) +{ + return make_value(value); +} + +template +inline typename std::enable_if>::type make_arg(const T &value) +{ + return make_arg(value); +} +} // namespace internal + +/** + \rst + An array of references to arguments. It can be implicitly converted into + `~fmt::basic_format_args` for passing into type-erased formatting functions + such as `~fmt::vformat`. + \endrst + */ +template +class format_arg_store +{ +private: + static const size_t NUM_ARGS = sizeof...(Args); + + // Packed is a macro on MinGW so use IS_PACKED instead. + static const bool IS_PACKED = NUM_ARGS < internal::max_packed_args; + + typedef typename std::conditional, basic_format_arg>::type value_type; + + // If the arguments are not packed, add one more element to mark the end. + static const size_t DATA_SIZE = NUM_ARGS + (IS_PACKED && NUM_ARGS != 0 ? 0 : 1); + value_type data_[DATA_SIZE]; + + friend class basic_format_args; + + static FMT_CONSTEXPR long long get_types() + { + return IS_PACKED ? static_cast(internal::get_types()) : -static_cast(NUM_ARGS); + } + +public: +#if FMT_USE_CONSTEXPR + static constexpr long long TYPES = get_types(); +#else + static const long long TYPES; +#endif + +#if (FMT_GCC_VERSION && FMT_GCC_VERSION <= 405) || (FMT_MSC_VER && FMT_MSC_VER <= 1800) + // Workaround array initialization issues in gcc <= 4.5 and MSVC <= 2013. + format_arg_store(const Args &... args) + { + value_type init[DATA_SIZE] = {internal::make_arg(args)...}; + std::memcpy(data_, init, sizeof(init)); + } +#else + format_arg_store(const Args &... args) + : data_{internal::make_arg(args)...} + { + } +#endif +}; + +#if !FMT_USE_CONSTEXPR +template +const long long format_arg_store::TYPES = get_types(); +#endif + +/** + \rst + Constructs an `~fmt::format_arg_store` object that contains references to + arguments and can be implicitly converted to `~fmt::format_args`. `Context` + can + be omitted in which case it defaults to `~fmt::context`. + \endrst + */ +template +inline format_arg_store make_format_args(const Args &... args) +{ + return format_arg_store(args...); +} + +template +inline format_arg_store make_format_args(const Args &... args) +{ + return format_arg_store(args...); +} + +/** Formatting arguments. */ +template +class basic_format_args +{ +public: + typedef unsigned size_type; + typedef basic_format_arg format_arg; + +private: + // To reduce compiled code size per formatting function call, types of first + // max_packed_args arguments are passed in the types_ field. + unsigned long long types_; + union + { + // If the number of arguments is less than max_packed_args, the argument + // values are stored in values_, otherwise they are stored in args_. + // This is done to reduce compiled code size as storing larger objects + // may require more code (at least on x86-64) even if the same amount of + // data is actually copied to stack. It saves ~10% on the bloat test. + const internal::value *values_; + const format_arg *args_; + }; + + typename internal::type type(unsigned index) const + { + unsigned shift = index * 4; + unsigned long long mask = 0xf; + return static_cast((types_ & (mask << shift)) >> shift); + } + + friend class internal::arg_map; + + void set_data(const internal::value *values) + { + values_ = values; + } + void set_data(const format_arg *args) + { + args_ = args; + } + + format_arg do_get(size_type index) const + { + long long signed_types = static_cast(types_); + if (signed_types < 0) + { + unsigned long long num_args = static_cast(-signed_types); + return index < num_args ? args_[index] : format_arg(); + } + format_arg arg; + if (index > internal::max_packed_args) + return arg; + arg.type_ = type(index); + if (arg.type_ == internal::none_type) + return arg; + internal::value &val = arg.value_; + val = values_[index]; + return arg; + } + +public: + basic_format_args() + : types_(0) + { + } + + /** + \rst + Constructs a `basic_format_args` object from `~fmt::format_arg_store`. + \endrst + */ + template + basic_format_args(const format_arg_store &store) + : types_(static_cast(store.TYPES)) + { + set_data(store.data_); + } + + /** Returns the argument at specified index. */ + format_arg get(size_type index) const + { + format_arg arg = do_get(index); + return arg.type_ == internal::named_arg_type ? arg.value_.as_named_arg().template deserialize() : arg; + } + + unsigned max_size() const + { + long long signed_types = static_cast(types_); + return static_cast(signed_types < 0 ? -signed_types : static_cast(internal::max_packed_args)); + } +}; + +/** An alias to ``basic_format_args``. */ +// It is a separate type rather than a typedef to make symbols readable. +struct format_args : basic_format_args +{ + template + format_args(Args &&... arg) + : basic_format_args(std::forward(arg)...) + { + } +}; +struct wformat_args : basic_format_args +{ + template + wformat_args(Args &&... arg) + : basic_format_args(std::forward(arg)...) + { + } +}; + +namespace internal { +template +struct named_arg_base +{ + basic_string_view name; + + // Serialized value. + mutable char data[sizeof(basic_format_arg)]; + + named_arg_base(basic_string_view nm) + : name(nm) + { + } + + template + basic_format_arg deserialize() const + { + basic_format_arg arg; + std::memcpy(&arg, data, sizeof(basic_format_arg)); + return arg; + } +}; + +template +struct named_arg : named_arg_base +{ + const T &value; + + named_arg(basic_string_view name, const T &val) + : named_arg_base(name) + , value(val) + { + } +}; +} // namespace internal + +/** + \rst + Returns a named argument to be used in a formatting function. + + **Example**:: + + fmt::print("Elapsed time: {s:.2f} seconds", fmt::arg("s", 1.23)); + \endrst + */ +template +inline internal::named_arg arg(string_view name, const T &arg) +{ + return internal::named_arg(name, arg); +} + +template +inline internal::named_arg arg(wstring_view name, const T &arg) +{ + return internal::named_arg(name, arg); +} + +// This function template is deleted intentionally to disable nested named +// arguments as in ``format("{}", arg("a", arg("b", 42)))``. +template +void arg(S, internal::named_arg) FMT_DELETED; + +#ifndef FMT_EXTENDED_COLORS +// color and (v)print_colored are deprecated. +enum color +{ + black, + red, + green, + yellow, + blue, + magenta, + cyan, + white +}; +FMT_API void vprint_colored(color c, string_view format, format_args args); +FMT_API void vprint_colored(color c, wstring_view format, wformat_args args); +template +inline void print_colored(color c, string_view format_str, const Args &... args) +{ + vprint_colored(c, format_str, make_format_args(args...)); +} +template +inline void print_colored(color c, wstring_view format_str, const Args &... args) +{ + vprint_colored(c, format_str, make_format_args(args...)); +} +#endif + +format_context::iterator vformat_to(internal::buffer &buf, string_view format_str, format_args args); +wformat_context::iterator vformat_to(internal::wbuffer &buf, wstring_view format_str, wformat_args args); + +template +struct is_contiguous : std::false_type +{ +}; + +template +struct is_contiguous> : std::true_type +{ +}; + +template +struct is_contiguous> : std::true_type +{ +}; + +/** Formats a string and writes the output to ``out``. */ +template +typename std::enable_if::value, std::back_insert_iterator>::type vformat_to( + std::back_insert_iterator out, string_view format_str, format_args args) +{ + auto &container = internal::get_container(out); + internal::container_buffer buf(container); + vformat_to(buf, format_str, args); + return std::back_inserter(container); +} + +template +typename std::enable_if::value, std::back_insert_iterator>::type vformat_to( + std::back_insert_iterator out, wstring_view format_str, wformat_args args) +{ + auto &container = internal::get_container(out); + internal::container_buffer buf(container); + vformat_to(buf, format_str, args); + return std::back_inserter(container); +} + +std::string vformat(string_view format_str, format_args args); +std::wstring vformat(wstring_view format_str, wformat_args args); + +/** + \rst + Formats arguments and returns the result as a string. + + **Example**:: + + #include + std::string message = fmt::format("The answer is {}", 42); + \endrst +*/ +template +inline std::string format(string_view format_str, const Args &... args) +{ + // This should be just + // return vformat(format_str, make_format_args(args...)); + // but gcc has trouble optimizing the latter, so break it down. + format_arg_store as{args...}; + return vformat(format_str, as); +} +template +inline std::wstring format(wstring_view format_str, const Args &... args) +{ + format_arg_store as{args...}; + return vformat(format_str, as); +} + +FMT_API void vprint(std::FILE *f, string_view format_str, format_args args); +FMT_API void vprint(std::FILE *f, wstring_view format_str, wformat_args args); + +/** + \rst + Prints formatted data to the file *f*. + + **Example**:: + + fmt::print(stderr, "Don't {}!", "panic"); + \endrst + */ +template +inline void print(std::FILE *f, string_view format_str, const Args &... args) +{ + format_arg_store as(args...); + vprint(f, format_str, as); +} +/** + Prints formatted data to the file *f* which should be in wide-oriented mode + set + via ``fwide(f, 1)`` or ``_setmode(_fileno(f), _O_U8TEXT)`` on Windows. + */ +template +inline void print(std::FILE *f, wstring_view format_str, const Args &... args) +{ + format_arg_store as(args...); + vprint(f, format_str, as); +} + +FMT_API void vprint(string_view format_str, format_args args); +FMT_API void vprint(wstring_view format_str, wformat_args args); + +/** + \rst + Prints formatted data to ``stdout``. + + **Example**:: + + fmt::print("Elapsed time: {0:.2f} seconds", 1.23); + \endrst + */ +template +inline void print(string_view format_str, const Args &... args) +{ + format_arg_store as{args...}; + vprint(format_str, as); +} + +template +inline void print(wstring_view format_str, const Args &... args) +{ + format_arg_store as(args...); + vprint(format_str, as); +} +FMT_END_NAMESPACE + +#endif // FMT_CORE_H_ diff --git a/include/spdlog/fmt/bundled/format-inl.h b/include/spdlog/fmt/bundled/format-inl.h new file mode 100644 index 0000000..1aba8c6 --- /dev/null +++ b/include/spdlog/fmt/bundled/format-inl.h @@ -0,0 +1,578 @@ +// Formatting library for C++ +// +// Copyright (c) 2012 - 2016, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_FORMAT_INL_H_ +#define FMT_FORMAT_INL_H_ + +#include "format.h" + +#include + +#include +#include +#include +#include +#include +#include // for std::ptrdiff_t +#include + +#if defined(_WIN32) && defined(__MINGW32__) +#include +#endif + +#if FMT_USE_WINDOWS_H +#if !defined(FMT_HEADER_ONLY) && !defined(WIN32_LEAN_AND_MEAN) +#define WIN32_LEAN_AND_MEAN +#endif +#if defined(NOMINMAX) || defined(FMT_WIN_MINMAX) +#include +#else +#define NOMINMAX +#include +#undef NOMINMAX +#endif +#endif + +#if FMT_EXCEPTIONS +#define FMT_TRY try +#define FMT_CATCH(x) catch (x) +#else +#define FMT_TRY if (true) +#define FMT_CATCH(x) if (false) +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4127) // conditional expression is constant +#pragma warning(disable : 4702) // unreachable code +// Disable deprecation warning for strerror. The latter is not called but +// MSVC fails to detect it. +#pragma warning(disable : 4996) +#endif + +// Dummy implementations of strerror_r and strerror_s called if corresponding +// system functions are not available. +inline fmt::internal::null<> strerror_r(int, char *, ...) +{ + return fmt::internal::null<>(); +} +inline fmt::internal::null<> strerror_s(char *, std::size_t, ...) +{ + return fmt::internal::null<>(); +} + +FMT_BEGIN_NAMESPACE + +namespace { + +#ifndef _MSC_VER +#define FMT_SNPRINTF snprintf +#else // _MSC_VER +inline int fmt_snprintf(char *buffer, size_t size, const char *format, ...) +{ + va_list args; + va_start(args, format); + int result = vsnprintf_s(buffer, size, _TRUNCATE, format, args); + va_end(args); + return result; +} +#define FMT_SNPRINTF fmt_snprintf +#endif // _MSC_VER + +#if defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT) +#define FMT_SWPRINTF snwprintf +#else +#define FMT_SWPRINTF swprintf +#endif // defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT) + +typedef void (*FormatFunc)(internal::buffer &, int, string_view); + +// Portable thread-safe version of strerror. +// Sets buffer to point to a string describing the error code. +// This can be either a pointer to a string stored in buffer, +// or a pointer to some static immutable string. +// Returns one of the following values: +// 0 - success +// ERANGE - buffer is not large enough to store the error message +// other - failure +// Buffer should be at least of size 1. +int safe_strerror(int error_code, char *&buffer, std::size_t buffer_size) FMT_NOEXCEPT +{ + FMT_ASSERT(buffer != FMT_NULL && buffer_size != 0, "invalid buffer"); + + class dispatcher + { + private: + int error_code_; + char *&buffer_; + std::size_t buffer_size_; + + // A noop assignment operator to avoid bogus warnings. + void operator=(const dispatcher &) {} + + // Handle the result of XSI-compliant version of strerror_r. + int handle(int result) + { + // glibc versions before 2.13 return result in errno. + return result == -1 ? errno : result; + } + + // Handle the result of GNU-specific version of strerror_r. + int handle(char *message) + { + // If the buffer is full then the message is probably truncated. + if (message == buffer_ && strlen(buffer_) == buffer_size_ - 1) + return ERANGE; + buffer_ = message; + return 0; + } + + // Handle the case when strerror_r is not available. + int handle(internal::null<>) + { + return fallback(strerror_s(buffer_, buffer_size_, error_code_)); + } + + // Fallback to strerror_s when strerror_r is not available. + int fallback(int result) + { + // If the buffer is full then the message is probably truncated. + return result == 0 && strlen(buffer_) == buffer_size_ - 1 ? ERANGE : result; + } + + // Fallback to strerror if strerror_r and strerror_s are not available. + int fallback(internal::null<>) + { + errno = 0; + buffer_ = strerror(error_code_); + return errno; + } + + public: + dispatcher(int err_code, char *&buf, std::size_t buf_size) + : error_code_(err_code) + , buffer_(buf) + , buffer_size_(buf_size) + { + } + + int run() + { + return handle(strerror_r(error_code_, buffer_, buffer_size_)); + } + }; + return dispatcher(error_code, buffer, buffer_size).run(); +} + +void format_error_code(internal::buffer &out, int error_code, string_view message) FMT_NOEXCEPT +{ + // Report error code making sure that the output fits into + // inline_buffer_size to avoid dynamic memory allocation and potential + // bad_alloc. + out.resize(0); + static const char SEP[] = ": "; + static const char ERROR_STR[] = "error "; + // Subtract 2 to account for terminating null characters in SEP and ERROR_STR. + std::size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2; + typedef internal::int_traits::main_type main_type; + main_type abs_value = static_cast(error_code); + if (internal::is_negative(error_code)) + { + abs_value = 0 - abs_value; + ++error_code_size; + } + error_code_size += internal::count_digits(abs_value); + writer w(out); + if (message.size() <= inline_buffer_size - error_code_size) + { + w.write(message); + w.write(SEP); + } + w.write(ERROR_STR); + w.write(error_code); + assert(out.size() <= inline_buffer_size); +} + +void report_error(FormatFunc func, int error_code, string_view message) FMT_NOEXCEPT +{ + memory_buffer full_message; + func(full_message, error_code, message); + // Use Writer::data instead of Writer::c_str to avoid potential memory + // allocation. + std::fwrite(full_message.data(), full_message.size(), 1, stderr); + std::fputc('\n', stderr); +} +} // namespace + +class locale +{ +private: + std::locale locale_; + +public: + explicit locale(std::locale loc = std::locale()) + : locale_(loc) + { + } + std::locale get() + { + return locale_; + } +}; + +template +FMT_FUNC Char internal::thousands_sep(locale_provider *lp) +{ + std::locale loc = lp ? lp->locale().get() : std::locale(); + return std::use_facet>(loc).thousands_sep(); +} + +FMT_FUNC void system_error::init(int err_code, string_view format_str, format_args args) +{ + error_code_ = err_code; + memory_buffer buffer; + format_system_error(buffer, err_code, vformat(format_str, args)); + std::runtime_error &base = *this; + base = std::runtime_error(to_string(buffer)); +} + +namespace internal { +template +int char_traits::format_float(char *buffer, std::size_t size, const char *format, int precision, T value) +{ + return precision < 0 ? FMT_SNPRINTF(buffer, size, format, value) : FMT_SNPRINTF(buffer, size, format, precision, value); +} + +template +int char_traits::format_float(wchar_t *buffer, std::size_t size, const wchar_t *format, int precision, T value) +{ + return precision < 0 ? FMT_SWPRINTF(buffer, size, format, value) : FMT_SWPRINTF(buffer, size, format, precision, value); +} + +template +const char basic_data::DIGITS[] = "0001020304050607080910111213141516171819" + "2021222324252627282930313233343536373839" + "4041424344454647484950515253545556575859" + "6061626364656667686970717273747576777879" + "8081828384858687888990919293949596979899"; + +#define FMT_POWERS_OF_10(factor) \ + factor * 10, factor * 100, factor * 1000, factor * 10000, factor * 100000, factor * 1000000, factor * 10000000, factor * 100000000, \ + factor * 1000000000 + +template +const uint32_t basic_data::POWERS_OF_10_32[] = {0, FMT_POWERS_OF_10(1)}; + +template +const uint64_t basic_data::POWERS_OF_10_64[] = {0, FMT_POWERS_OF_10(1), FMT_POWERS_OF_10(1000000000ull), 10000000000000000000ull}; + +// Normalized 64-bit significands of pow(10, k), for k = -348, -340, ..., 340. +// These are generated by support/compute-powers.py. +template +const uint64_t basic_data::POW10_SIGNIFICANDS[] = {0xfa8fd5a0081c0288, 0xbaaee17fa23ebf76, 0x8b16fb203055ac76, 0xcf42894a5dce35ea, + 0x9a6bb0aa55653b2d, 0xe61acf033d1a45df, 0xab70fe17c79ac6ca, 0xff77b1fcbebcdc4f, 0xbe5691ef416bd60c, 0x8dd01fad907ffc3c, + 0xd3515c2831559a83, 0x9d71ac8fada6c9b5, 0xea9c227723ee8bcb, 0xaecc49914078536d, 0x823c12795db6ce57, 0xc21094364dfb5637, + 0x9096ea6f3848984f, 0xd77485cb25823ac7, 0xa086cfcd97bf97f4, 0xef340a98172aace5, 0xb23867fb2a35b28e, 0x84c8d4dfd2c63f3b, + 0xc5dd44271ad3cdba, 0x936b9fcebb25c996, 0xdbac6c247d62a584, 0xa3ab66580d5fdaf6, 0xf3e2f893dec3f126, 0xb5b5ada8aaff80b8, + 0x87625f056c7c4a8b, 0xc9bcff6034c13053, 0x964e858c91ba2655, 0xdff9772470297ebd, 0xa6dfbd9fb8e5b88f, 0xf8a95fcf88747d94, + 0xb94470938fa89bcf, 0x8a08f0f8bf0f156b, 0xcdb02555653131b6, 0x993fe2c6d07b7fac, 0xe45c10c42a2b3b06, 0xaa242499697392d3, + 0xfd87b5f28300ca0e, 0xbce5086492111aeb, 0x8cbccc096f5088cc, 0xd1b71758e219652c, 0x9c40000000000000, 0xe8d4a51000000000, + 0xad78ebc5ac620000, 0x813f3978f8940984, 0xc097ce7bc90715b3, 0x8f7e32ce7bea5c70, 0xd5d238a4abe98068, 0x9f4f2726179a2245, + 0xed63a231d4c4fb27, 0xb0de65388cc8ada8, 0x83c7088e1aab65db, 0xc45d1df942711d9a, 0x924d692ca61be758, 0xda01ee641a708dea, + 0xa26da3999aef774a, 0xf209787bb47d6b85, 0xb454e4a179dd1877, 0x865b86925b9bc5c2, 0xc83553c5c8965d3d, 0x952ab45cfa97a0b3, + 0xde469fbd99a05fe3, 0xa59bc234db398c25, 0xf6c69a72a3989f5c, 0xb7dcbf5354e9bece, 0x88fcf317f22241e2, 0xcc20ce9bd35c78a5, + 0x98165af37b2153df, 0xe2a0b5dc971f303a, 0xa8d9d1535ce3b396, 0xfb9b7cd9a4a7443c, 0xbb764c4ca7a44410, 0x8bab8eefb6409c1a, + 0xd01fef10a657842c, 0x9b10a4e5e9913129, 0xe7109bfba19c0c9d, 0xac2820d9623bf429, 0x80444b5e7aa7cf85, 0xbf21e44003acdd2d, + 0x8e679c2f5e44ff8f, 0xd433179d9c8cb841, 0x9e19db92b4e31ba9, 0xeb96bf6ebadf77d9, 0xaf87023b9bf0ee6b}; + +// Binary exponents of pow(10, k), for k = -348, -340, ..., 340, corresponding +// to significands above. +template +const int16_t basic_data::POW10_EXPONENTS[] = {-1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980, -954, -927, -901, + -874, -847, -821, -794, -768, -741, -715, -688, -661, -635, -608, -582, -555, -529, -502, -475, -449, -422, -396, -369, -343, -316, + -289, -263, -236, -210, -183, -157, -130, -103, -77, -50, -24, 3, 30, 56, 83, 109, 136, 162, 189, 216, 242, 269, 295, 322, 348, 375, + 402, 428, 455, 481, 508, 534, 561, 588, 614, 641, 667, 694, 720, 747, 774, 800, 827, 853, 880, 907, 933, 960, 986, 1013, 1039, 1066}; + +template +const char basic_data::RESET_COLOR[] = "\x1b[0m"; +template +const wchar_t basic_data::WRESET_COLOR[] = L"\x1b[0m"; + +FMT_FUNC fp operator*(fp x, fp y) +{ + // Multiply 32-bit parts of significands. + uint64_t mask = (1ULL << 32) - 1; + uint64_t a = x.f >> 32, b = x.f & mask; + uint64_t c = y.f >> 32, d = y.f & mask; + uint64_t ac = a * c, bc = b * c, ad = a * d, bd = b * d; + // Compute mid 64-bit of result and round. + uint64_t mid = (bd >> 32) + (ad & mask) + (bc & mask) + (1U << 31); + return fp(ac + (ad >> 32) + (bc >> 32) + (mid >> 32), x.e + y.e + 64); +} + +FMT_FUNC fp get_cached_power(int min_exponent, int &pow10_exponent) +{ + const double one_over_log2_10 = 0.30102999566398114; // 1 / log2(10) + int index = static_cast(std::ceil((min_exponent + fp::significand_size - 1) * one_over_log2_10)); + // Decimal exponent of the first (smallest) cached power of 10. + const int first_dec_exp = -348; + // Difference between two consecutive decimal exponents in cached powers of + // 10. + const int dec_exp_step = 8; + index = (index - first_dec_exp - 1) / dec_exp_step + 1; + pow10_exponent = first_dec_exp + index * dec_exp_step; + return fp(data::POW10_SIGNIFICANDS[index], data::POW10_EXPONENTS[index]); +} +} // namespace internal + +#if FMT_USE_WINDOWS_H + +FMT_FUNC internal::utf8_to_utf16::utf8_to_utf16(string_view s) +{ + static const char ERROR_MSG[] = "cannot convert string from UTF-8 to UTF-16"; + if (s.size() > INT_MAX) + FMT_THROW(windows_error(ERROR_INVALID_PARAMETER, ERROR_MSG)); + int s_size = static_cast(s.size()); + if (s_size == 0) + { + // MultiByteToWideChar does not support zero length, handle separately. + buffer_.resize(1); + buffer_[0] = 0; + return; + } + + int length = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, FMT_NULL, 0); + if (length == 0) + FMT_THROW(windows_error(GetLastError(), ERROR_MSG)); + buffer_.resize(length + 1); + length = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, &buffer_[0], length); + if (length == 0) + FMT_THROW(windows_error(GetLastError(), ERROR_MSG)); + buffer_[length] = 0; +} + +FMT_FUNC internal::utf16_to_utf8::utf16_to_utf8(wstring_view s) +{ + if (int error_code = convert(s)) + { + FMT_THROW(windows_error(error_code, "cannot convert string from UTF-16 to UTF-8")); + } +} + +FMT_FUNC int internal::utf16_to_utf8::convert(wstring_view s) +{ + if (s.size() > INT_MAX) + return ERROR_INVALID_PARAMETER; + int s_size = static_cast(s.size()); + if (s_size == 0) + { + // WideCharToMultiByte does not support zero length, handle separately. + buffer_.resize(1); + buffer_[0] = 0; + return 0; + } + + int length = WideCharToMultiByte(CP_UTF8, 0, s.data(), s_size, FMT_NULL, 0, FMT_NULL, FMT_NULL); + if (length == 0) + return GetLastError(); + buffer_.resize(length + 1); + length = WideCharToMultiByte(CP_UTF8, 0, s.data(), s_size, &buffer_[0], length, FMT_NULL, FMT_NULL); + if (length == 0) + return GetLastError(); + buffer_[length] = 0; + return 0; +} + +FMT_FUNC void windows_error::init(int err_code, string_view format_str, format_args args) +{ + error_code_ = err_code; + memory_buffer buffer; + internal::format_windows_error(buffer, err_code, vformat(format_str, args)); + std::runtime_error &base = *this; + base = std::runtime_error(to_string(buffer)); +} + +FMT_FUNC void internal::format_windows_error(internal::buffer &out, int error_code, string_view message) FMT_NOEXCEPT +{ + FMT_TRY + { + wmemory_buffer buf; + buf.resize(inline_buffer_size); + for (;;) + { + wchar_t *system_message = &buf[0]; + int result = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, FMT_NULL, error_code, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), system_message, static_cast(buf.size()), FMT_NULL); + if (result != 0) + { + utf16_to_utf8 utf8_message; + if (utf8_message.convert(system_message) == ERROR_SUCCESS) + { + writer w(out); + w.write(message); + w.write(": "); + w.write(utf8_message); + return; + } + break; + } + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) + break; // Can't get error message, report error code instead. + buf.resize(buf.size() * 2); + } + } + FMT_CATCH(...) {} + format_error_code(out, error_code, message); +} + +#endif // FMT_USE_WINDOWS_H + +FMT_FUNC void format_system_error(internal::buffer &out, int error_code, string_view message) FMT_NOEXCEPT +{ + FMT_TRY + { + memory_buffer buf; + buf.resize(inline_buffer_size); + for (;;) + { + char *system_message = &buf[0]; + int result = safe_strerror(error_code, system_message, buf.size()); + if (result == 0) + { + writer w(out); + w.write(message); + w.write(": "); + w.write(system_message); + return; + } + if (result != ERANGE) + break; // Can't get error message, report error code instead. + buf.resize(buf.size() * 2); + } + } + FMT_CATCH(...) {} + format_error_code(out, error_code, message); +} + +template +void basic_fixed_buffer::grow(std::size_t) +{ + FMT_THROW(std::runtime_error("buffer overflow")); +} + +FMT_FUNC void internal::error_handler::on_error(const char *message) +{ + FMT_THROW(format_error(message)); +} + +FMT_FUNC void report_system_error(int error_code, fmt::string_view message) FMT_NOEXCEPT +{ + report_error(format_system_error, error_code, message); +} + +#if FMT_USE_WINDOWS_H +FMT_FUNC void report_windows_error(int error_code, fmt::string_view message) FMT_NOEXCEPT +{ + report_error(internal::format_windows_error, error_code, message); +} +#endif + +FMT_FUNC void vprint(std::FILE *f, string_view format_str, format_args args) +{ + memory_buffer buffer; + vformat_to(buffer, format_str, args); + std::fwrite(buffer.data(), 1, buffer.size(), f); +} + +FMT_FUNC void vprint(std::FILE *f, wstring_view format_str, wformat_args args) +{ + wmemory_buffer buffer; + vformat_to(buffer, format_str, args); + std::fwrite(buffer.data(), sizeof(wchar_t), buffer.size(), f); +} + +FMT_FUNC void vprint(string_view format_str, format_args args) +{ + vprint(stdout, format_str, args); +} + +FMT_FUNC void vprint(wstring_view format_str, wformat_args args) +{ + vprint(stdout, format_str, args); +} + +#ifndef FMT_EXTENDED_COLORS +FMT_FUNC void vprint_colored(color c, string_view format, format_args args) +{ + char escape[] = "\x1b[30m"; + escape[3] = static_cast('0' + c); + std::fputs(escape, stdout); + vprint(format, args); + std::fputs(internal::data::RESET_COLOR, stdout); +} + +FMT_FUNC void vprint_colored(color c, wstring_view format, wformat_args args) +{ + wchar_t escape[] = L"\x1b[30m"; + escape[3] = static_cast('0' + c); + std::fputws(escape, stdout); + vprint(format, args); + std::fputws(internal::data::WRESET_COLOR, stdout); +} +#else +namespace internal { +FMT_CONSTEXPR void to_esc(uint8_t c, char out[], int offset) +{ + out[offset + 0] = static_cast('0' + c / 100); + out[offset + 1] = static_cast('0' + c / 10 % 10); + out[offset + 2] = static_cast('0' + c % 10); +} +} // namespace internal + +FMT_FUNC void vprint_rgb(rgb fd, string_view format, format_args args) +{ + char escape_fd[] = "\x1b[38;2;000;000;000m"; + internal::to_esc(fd.r, escape_fd, 7); + internal::to_esc(fd.g, escape_fd, 11); + internal::to_esc(fd.b, escape_fd, 15); + + std::fputs(escape_fd, stdout); + vprint(format, args); + std::fputs(internal::data::RESET_COLOR, stdout); +} + +FMT_FUNC void vprint_rgb(rgb fd, rgb bg, string_view format, format_args args) +{ + char escape_fd[] = "\x1b[38;2;000;000;000m"; // foreground color + char escape_bg[] = "\x1b[48;2;000;000;000m"; // background color + internal::to_esc(fd.r, escape_fd, 7); + internal::to_esc(fd.g, escape_fd, 11); + internal::to_esc(fd.b, escape_fd, 15); + + internal::to_esc(bg.r, escape_bg, 7); + internal::to_esc(bg.g, escape_bg, 11); + internal::to_esc(bg.b, escape_bg, 15); + + std::fputs(escape_fd, stdout); + std::fputs(escape_bg, stdout); + vprint(format, args); + std::fputs(internal::data::RESET_COLOR, stdout); +} +#endif + +FMT_FUNC locale locale_provider::locale() +{ + return fmt::locale(); +} + +FMT_END_NAMESPACE + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif // FMT_FORMAT_INL_H_ diff --git a/include/spdlog/fmt/bundled/format.h b/include/spdlog/fmt/bundled/format.h new file mode 100644 index 0000000..60001f3 --- /dev/null +++ b/include/spdlog/fmt/bundled/format.h @@ -0,0 +1,4716 @@ +/* + Formatting library for C++ + + Copyright (c) 2012 - present, Victor Zverovich + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef FMT_FORMAT_H_ +#define FMT_FORMAT_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __clang__ +#define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) +#else +#define FMT_CLANG_VERSION 0 +#endif + +#ifdef __INTEL_COMPILER +#define FMT_ICC_VERSION __INTEL_COMPILER +#elif defined(__ICL) +#define FMT_ICC_VERSION __ICL +#else +#define FMT_ICC_VERSION 0 +#endif + +#include "core.h" + +#if FMT_GCC_VERSION >= 406 || FMT_CLANG_VERSION +#pragma GCC diagnostic push + +// Disable the warning about declaration shadowing because it affects too +// many valid cases. +#pragma GCC diagnostic ignored "-Wshadow" + +// Disable the warning about implicit conversions that may change the sign of +// an integer; silencing it otherwise would require many explicit casts. +#pragma GCC diagnostic ignored "-Wsign-conversion" +#endif + +#ifdef _SECURE_SCL +#define FMT_SECURE_SCL _SECURE_SCL +#else +#define FMT_SECURE_SCL 0 +#endif + +#if FMT_SECURE_SCL +#include +#endif + +#ifdef __has_builtin +#define FMT_HAS_BUILTIN(x) __has_builtin(x) +#else +#define FMT_HAS_BUILTIN(x) 0 +#endif + +#ifdef __GNUC_LIBSTD__ +#define FMT_GNUC_LIBSTD_VERSION (__GNUC_LIBSTD__ * 100 + __GNUC_LIBSTD_MINOR__) +#endif + +#ifndef FMT_THROW +#if FMT_EXCEPTIONS +#if FMT_MSC_VER +FMT_BEGIN_NAMESPACE +namespace internal { +template +inline void do_throw(const Exception &x) +{ + // Silence unreachable code warnings in MSVC because these are nearly + // impossible to fix in a generic code. + volatile bool b = true; + if (b) + throw x; +} +} // namespace internal +FMT_END_NAMESPACE +#define FMT_THROW(x) fmt::internal::do_throw(x) +#else +#define FMT_THROW(x) throw x +#endif +#else +#define FMT_THROW(x) \ + do \ + { \ + static_cast(sizeof(x)); \ + assert(false); \ + } while (false); +#endif +#endif + +#ifndef FMT_USE_USER_DEFINED_LITERALS +// For Intel's compiler both it and the system gcc/msc must support UDLs. +#if (FMT_HAS_FEATURE(cxx_user_literals) || FMT_GCC_VERSION >= 407 || FMT_MSC_VER >= 1900) && (!FMT_ICC_VERSION || FMT_ICC_VERSION >= 1500) +#define FMT_USE_USER_DEFINED_LITERALS 1 +#else +#define FMT_USE_USER_DEFINED_LITERALS 0 +#endif +#endif + +#if FMT_USE_USER_DEFINED_LITERALS && !defined(FMT_ICC_VERSION) && \ + ((FMT_GCC_VERSION >= 600 && __cplusplus >= 201402L) || (defined(FMT_CLANG_VERSION) && FMT_CLANG_VERSION >= 304)) +#define FMT_UDL_TEMPLATE 1 +#else +#define FMT_UDL_TEMPLATE 0 +#endif + +#ifndef FMT_USE_EXTERN_TEMPLATES +#ifndef FMT_HEADER_ONLY +#define FMT_USE_EXTERN_TEMPLATES ((FMT_CLANG_VERSION >= 209 && __cplusplus >= 201103L) || (FMT_GCC_VERSION >= 303 && FMT_HAS_GXX_CXX11)) +#else +#define FMT_USE_EXTERN_TEMPLATES 0 +#endif +#endif + +#if FMT_HAS_GXX_CXX11 || FMT_HAS_FEATURE(cxx_trailing_return) || FMT_MSC_VER >= 1600 +#define FMT_USE_TRAILING_RETURN 1 +#else +#define FMT_USE_TRAILING_RETURN 0 +#endif + +#if FMT_HAS_GXX_CXX11 || FMT_HAS_FEATURE(cxx_rvalue_references) || FMT_MSC_VER >= 1600 +#define FMT_USE_RVALUE_REFERENCES 1 +#else +#define FMT_USE_RVALUE_REFERENCES 0 +#endif + +#ifndef FMT_USE_GRISU +#define FMT_USE_GRISU 0 +#endif + +// __builtin_clz is broken in clang with Microsoft CodeGen: +// https://github.com/fmtlib/fmt/issues/519 +#ifndef _MSC_VER +#if FMT_GCC_VERSION >= 400 || FMT_HAS_BUILTIN(__builtin_clz) +#define FMT_BUILTIN_CLZ(n) __builtin_clz(n) +#endif + +#if FMT_GCC_VERSION >= 400 || FMT_HAS_BUILTIN(__builtin_clzll) +#define FMT_BUILTIN_CLZLL(n) __builtin_clzll(n) +#endif +#endif + +// A workaround for gcc 4.4 that doesn't support union members with ctors. +#if (FMT_GCC_VERSION && FMT_GCC_VERSION <= 404) || (FMT_MSC_VER && FMT_MSC_VER <= 1800) +#define FMT_UNION struct +#else +#define FMT_UNION union +#endif + +// Some compilers masquerade as both MSVC and GCC-likes or otherwise support +// __builtin_clz and __builtin_clzll, so only define FMT_BUILTIN_CLZ using the +// MSVC intrinsics if the clz and clzll builtins are not available. +#if FMT_MSC_VER && !defined(FMT_BUILTIN_CLZLL) && !defined(_MANAGED) +#include // _BitScanReverse, _BitScanReverse64 + +FMT_BEGIN_NAMESPACE +namespace internal { +// Avoid Clang with Microsoft CodeGen's -Wunknown-pragmas warning. +#ifndef __clang__ +#pragma intrinsic(_BitScanReverse) +#endif +inline uint32_t clz(uint32_t x) +{ + unsigned long r = 0; + _BitScanReverse(&r, x); + + assert(x != 0); +// Static analysis complains about using uninitialized data +// "r", but the only way that can happen is if "x" is 0, +// which the callers guarantee to not happen. +#pragma warning(suppress : 6102) + return 31 - r; +} +#define FMT_BUILTIN_CLZ(n) fmt::internal::clz(n) + +#if defined(_WIN64) && !defined(__clang__) +#pragma intrinsic(_BitScanReverse64) +#endif + +inline uint32_t clzll(uint64_t x) +{ + unsigned long r = 0; +#ifdef _WIN64 + _BitScanReverse64(&r, x); +#else + // Scan the high 32 bits. + if (_BitScanReverse(&r, static_cast(x >> 32))) + return 63 - (r + 32); + + // Scan the low 32 bits. + _BitScanReverse(&r, static_cast(x)); +#endif + + assert(x != 0); +// Static analysis complains about using uninitialized data +// "r", but the only way that can happen is if "x" is 0, +// which the callers guarantee to not happen. +#pragma warning(suppress : 6102) + return 63 - r; +} +#define FMT_BUILTIN_CLZLL(n) fmt::internal::clzll(n) +} // namespace internal +FMT_END_NAMESPACE +#endif + +FMT_BEGIN_NAMESPACE +namespace internal { + +// An equivalent of `*reinterpret_cast(&source)` that doesn't produce +// undefined behavior (e.g. due to type aliasing). +// Example: uint64_t d = bit_cast(2.718); +template +inline Dest bit_cast(const Source &source) +{ + static_assert(sizeof(Dest) == sizeof(Source), "size mismatch"); + Dest dest; + std::memcpy(&dest, &source, sizeof(dest)); + return dest; +} + +// An implementation of begin and end for pre-C++11 compilers such as gcc 4. +template +FMT_CONSTEXPR auto begin(const C &c) -> decltype(c.begin()) +{ + return c.begin(); +} +template +FMT_CONSTEXPR T *begin(T (&array)[N]) FMT_NOEXCEPT +{ + return array; +} +template +FMT_CONSTEXPR auto end(const C &c) -> decltype(c.end()) +{ + return c.end(); +} +template +FMT_CONSTEXPR T *end(T (&array)[N]) FMT_NOEXCEPT +{ + return array + N; +} + +// For std::result_of in gcc 4.4. +template +struct function +{ + template + struct result + { + typedef Result type; + }; +}; + +struct dummy_int +{ + int data[2]; + operator int() const + { + return 0; + } +}; +typedef std::numeric_limits fputil; + +// Dummy implementations of system functions such as signbit and ecvt called +// if the latter are not available. +inline dummy_int signbit(...) +{ + return dummy_int(); +} +inline dummy_int _ecvt_s(...) +{ + return dummy_int(); +} +inline dummy_int isinf(...) +{ + return dummy_int(); +} +inline dummy_int _finite(...) +{ + return dummy_int(); +} +inline dummy_int isnan(...) +{ + return dummy_int(); +} +inline dummy_int _isnan(...) +{ + return dummy_int(); +} + +// A handmade floating-point number f * pow(2, e). +class fp +{ +private: + typedef uint64_t significand_type; + + // All sizes are in bits. + static FMT_CONSTEXPR_DECL const int char_size = std::numeric_limits::digits; + // Subtract 1 to account for an implicit most significant bit in the + // normalized form. + static FMT_CONSTEXPR_DECL const int double_significand_size = std::numeric_limits::digits - 1; + static FMT_CONSTEXPR_DECL const uint64_t implicit_bit = 1ull << double_significand_size; + +public: + significand_type f; + int e; + + static FMT_CONSTEXPR_DECL const int significand_size = sizeof(significand_type) * char_size; + + fp(uint64_t f, int e) + : f(f) + , e(e) + { + } + + // Constructs fp from an IEEE754 double. It is a template to prevent compile + // errors on platforms where double is not IEEE754. + template + explicit fp(Double d) + { + // Assume double is in the format [sign][exponent][significand]. + typedef std::numeric_limits limits; + const int double_size = sizeof(Double) * char_size; + const int exponent_size = double_size - double_significand_size - 1; // -1 for sign + const uint64_t significand_mask = implicit_bit - 1; + const uint64_t exponent_mask = (~0ull >> 1) & ~significand_mask; + const int exponent_bias = (1 << exponent_size) - limits::max_exponent - 1; + auto u = bit_cast(d); + auto biased_e = (u & exponent_mask) >> double_significand_size; + f = u & significand_mask; + if (biased_e != 0) + f += implicit_bit; + else + biased_e = 1; // Subnormals use biased exponent 1 (min exponent). + e = static_cast(biased_e - exponent_bias - double_significand_size); + } + + // Normalizes the value converted from double and multiplied by (1 << SHIFT). + template + void normalize() + { + // Handle subnormals. + auto shifted_implicit_bit = implicit_bit << SHIFT; + while ((f & shifted_implicit_bit) == 0) + { + f <<= 1; + --e; + } + // Subtract 1 to account for hidden bit. + auto offset = significand_size - double_significand_size - SHIFT - 1; + f <<= offset; + e -= offset; + } +}; + +// Returns an fp number representing x - y. Result may not be normalized. +inline fp operator-(fp x, fp y) +{ + FMT_ASSERT(x.f >= y.f && x.e == y.e, "invalid operands"); + return fp(x.f - y.f, x.e); +} + +// Computes an fp number r with r.f = x.f * y.f / pow(2, 64) rounded to nearest +// with half-up tie breaking, r.e = x.e + y.e + 64. Result may not be +// normalized. +fp operator*(fp x, fp y); + +// Returns cached power (of 10) c_k = c_k.f * pow(2, c_k.e) such that its +// (binary) exponent satisfies min_exponent <= c_k.e <= min_exponent + 3. +fp get_cached_power(int min_exponent, int &pow10_exponent); + +template +typename Allocator::value_type *allocate(Allocator &alloc, std::size_t n) +{ +#if __cplusplus >= 201103L || FMT_MSC_VER >= 1700 + return std::allocator_traits::allocate(alloc, n); +#else + return alloc.allocate(n); +#endif +} + +// A helper function to suppress bogus "conditional expression is constant" +// warnings. +template +inline T const_check(T value) +{ + return value; +} +} // namespace internal +FMT_END_NAMESPACE + +namespace std { +// Standard permits specialization of std::numeric_limits. This specialization +// is used to resolve ambiguity between isinf and std::isinf in glibc: +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=48891 +// and the same for isnan and signbit. +template<> +class numeric_limits : public std::numeric_limits +{ +public: + // Portable version of isinf. + template + static bool isinfinity(T x) + { + using namespace fmt::internal; + // The resolution "priority" is: + // isinf macro > std::isinf > ::isinf > fmt::internal::isinf + if (const_check(sizeof(isinf(x)) != sizeof(dummy_int))) + return isinf(x) != 0; + return !_finite(static_cast(x)); + } + + // Portable version of isnan. + template + static bool isnotanumber(T x) + { + using namespace fmt::internal; + if (const_check(sizeof(isnan(x)) != sizeof(fmt::internal::dummy_int))) + return isnan(x) != 0; + return _isnan(static_cast(x)) != 0; + } + + // Portable version of signbit. + static bool isnegative(double x) + { + using namespace fmt::internal; + if (const_check(sizeof(signbit(x)) != sizeof(fmt::internal::dummy_int))) + return signbit(x) != 0; + if (x < 0) + return true; + if (!isnotanumber(x)) + return false; + int dec = 0, sign = 0; + char buffer[2]; // The buffer size must be >= 2 or _ecvt_s will fail. + _ecvt_s(buffer, sizeof(buffer), x, 0, &dec, &sign); + return sign != 0; + } +}; +} // namespace std + +FMT_BEGIN_NAMESPACE +template +class basic_writer; + +template +class output_range +{ +private: + OutputIt it_; + + // Unused yet. + typedef void sentinel; + sentinel end() const; + +public: + typedef OutputIt iterator; + typedef T value_type; + + explicit output_range(OutputIt it) + : it_(it) + { + } + OutputIt begin() const + { + return it_; + } +}; + +// A range where begin() returns back_insert_iterator. +template +class back_insert_range : public output_range> +{ + typedef output_range> base; + +public: + typedef typename Container::value_type value_type; + + back_insert_range(Container &c) + : base(std::back_inserter(c)) + { + } + back_insert_range(typename base::iterator it) + : base(it) + { + } +}; + +typedef basic_writer> writer; +typedef basic_writer> wwriter; + +/** A formatting error such as invalid format string. */ +class format_error : public std::runtime_error +{ +public: + explicit format_error(const char *message) + : std::runtime_error(message) + { + } + + explicit format_error(const std::string &message) + : std::runtime_error(message) + { + } +}; + +namespace internal { + +#if FMT_SECURE_SCL +template +struct checked +{ + typedef stdext::checked_array_iterator type; +}; + +// Make a checked iterator to avoid warnings on MSVC. +template +inline stdext::checked_array_iterator make_checked(T *p, std::size_t size) +{ + return {p, size}; +} +#else +template +struct checked +{ + typedef T *type; +}; +template +inline T *make_checked(T *p, std::size_t) +{ + return p; +} +#endif + +template +template +void basic_buffer::append(const U *begin, const U *end) +{ + std::size_t new_size = size_ + internal::to_unsigned(end - begin); + reserve(new_size); + std::uninitialized_copy(begin, end, internal::make_checked(ptr_, capacity_) + size_); + size_ = new_size; +} +} // namespace internal + +// A wrapper around std::locale used to reduce compile times since +// is very heavy. +class locale; + +class locale_provider +{ +public: + virtual ~locale_provider() {} + virtual fmt::locale locale(); +}; + +// The number of characters to store in the basic_memory_buffer object itself +// to avoid dynamic memory allocation. +enum +{ + inline_buffer_size = 500 +}; + +/** + \rst + A dynamically growing memory buffer for trivially copyable/constructible types + with the first ``SIZE`` elements stored in the object itself. + + You can use one of the following typedefs for common character types: + + +----------------+------------------------------+ + | Type | Definition | + +================+==============================+ + | memory_buffer | basic_memory_buffer | + +----------------+------------------------------+ + | wmemory_buffer | basic_memory_buffer | + +----------------+------------------------------+ + + **Example**:: + + fmt::memory_buffer out; + format_to(out, "The answer is {}.", 42); + + This will write the following output to the ``out`` object: + + .. code-block:: none + + The answer is 42. + + The output can be converted to an ``std::string`` with ``to_string(out)``. + \endrst + */ +template> +class basic_memory_buffer : private Allocator, public internal::basic_buffer +{ +private: + T store_[SIZE]; + + // Deallocate memory allocated by the buffer. + void deallocate() + { + T *data = this->data(); + if (data != store_) + Allocator::deallocate(data, this->capacity()); + } + +protected: + void grow(std::size_t size) FMT_OVERRIDE; + +public: + explicit basic_memory_buffer(const Allocator &alloc = Allocator()) + : Allocator(alloc) + { + this->set(store_, SIZE); + } + ~basic_memory_buffer() + { + deallocate(); + } + +private: + // Move data from other to this buffer. + void move(basic_memory_buffer &other) + { + Allocator &this_alloc = *this, &other_alloc = other; + this_alloc = std::move(other_alloc); + T *data = other.data(); + std::size_t size = other.size(), capacity = other.capacity(); + if (data == other.store_) + { + this->set(store_, capacity); + std::uninitialized_copy(other.store_, other.store_ + size, internal::make_checked(store_, capacity)); + } + else + { + this->set(data, capacity); + // Set pointer to the inline array so that delete is not called + // when deallocating. + other.set(other.store_, 0); + } + this->resize(size); + } + +public: + /** + \rst + Constructs a :class:`fmt::basic_memory_buffer` object moving the content + of the other object to it. + \endrst + */ + basic_memory_buffer(basic_memory_buffer &&other) + { + move(other); + } + + /** + \rst + Moves the content of the other ``basic_memory_buffer`` object to this one. + \endrst + */ + basic_memory_buffer &operator=(basic_memory_buffer &&other) + { + assert(this != &other); + deallocate(); + move(other); + return *this; + } + + // Returns a copy of the allocator associated with this buffer. + Allocator get_allocator() const + { + return *this; + } +}; + +template +void basic_memory_buffer::grow(std::size_t size) +{ + std::size_t old_capacity = this->capacity(); + std::size_t new_capacity = old_capacity + old_capacity / 2; + if (size > new_capacity) + new_capacity = size; + T *old_data = this->data(); + T *new_data = internal::allocate(*this, new_capacity); + // The following code doesn't throw, so the raw pointer above doesn't leak. + std::uninitialized_copy(old_data, old_data + this->size(), internal::make_checked(new_data, new_capacity)); + this->set(new_data, new_capacity); + // deallocate must not throw according to the standard, but even if it does, + // the buffer already uses the new storage and will deallocate it in + // destructor. + if (old_data != store_) + Allocator::deallocate(old_data, old_capacity); +} + +typedef basic_memory_buffer memory_buffer; +typedef basic_memory_buffer wmemory_buffer; + +/** + \rst + A fixed-size memory buffer. For a dynamically growing buffer use + :class:`fmt::basic_memory_buffer`. + + Trying to increase the buffer size past the initial capacity will throw + ``std::runtime_error``. + \endrst + */ +template +class basic_fixed_buffer : public internal::basic_buffer +{ +public: + /** + \rst + Constructs a :class:`fmt::basic_fixed_buffer` object for *array* of the + given size. + \endrst + */ + basic_fixed_buffer(Char *array, std::size_t size) + { + this->set(array, size); + } + + /** + \rst + Constructs a :class:`fmt::basic_fixed_buffer` object for *array* of the + size known at compile time. + \endrst + */ + template + explicit basic_fixed_buffer(Char (&array)[SIZE]) + { + this->set(array, SIZE); + } + +protected: + FMT_API void grow(std::size_t size) FMT_OVERRIDE; +}; + +namespace internal { + +template +struct char_traits; + +template<> +struct char_traits +{ + // Formats a floating-point number. + template + FMT_API static int format_float(char *buffer, std::size_t size, const char *format, int precision, T value); +}; + +template<> +struct char_traits +{ + template + FMT_API static int format_float(wchar_t *buffer, std::size_t size, const wchar_t *format, int precision, T value); +}; + +#if FMT_USE_EXTERN_TEMPLATES +extern template int char_traits::format_float( + char *buffer, std::size_t size, const char *format, int precision, double value); +extern template int char_traits::format_float( + char *buffer, std::size_t size, const char *format, int precision, long double value); + +extern template int char_traits::format_float( + wchar_t *buffer, std::size_t size, const wchar_t *format, int precision, double value); +extern template int char_traits::format_float( + wchar_t *buffer, std::size_t size, const wchar_t *format, int precision, long double value); +#endif + +template +inline typename std::enable_if::value, typename checked::type>::type reserve( + std::back_insert_iterator &it, std::size_t n) +{ + Container &c = internal::get_container(it); + std::size_t size = c.size(); + c.resize(size + n); + return make_checked(&c[size], n); +} + +template +inline Iterator &reserve(Iterator &it, std::size_t) +{ + return it; +} + +template +class null_terminating_iterator; + +template +FMT_CONSTEXPR_DECL const Char *pointer_from(null_terminating_iterator it); + +// An iterator that produces a null terminator on *end. This simplifies parsing +// and allows comparing the performance of processing a null-terminated string +// vs string_view. +template +class null_terminating_iterator +{ +public: + typedef std::ptrdiff_t difference_type; + typedef Char value_type; + typedef const Char *pointer; + typedef const Char &reference; + typedef std::random_access_iterator_tag iterator_category; + + null_terminating_iterator() + : ptr_(0) + , end_(0) + { + } + + FMT_CONSTEXPR null_terminating_iterator(const Char *ptr, const Char *end) + : ptr_(ptr) + , end_(end) + { + } + + template + FMT_CONSTEXPR explicit null_terminating_iterator(const Range &r) + : ptr_(r.begin()) + , end_(r.end()) + { + } + + null_terminating_iterator &operator=(const Char *ptr) + { + assert(ptr <= end_); + ptr_ = ptr; + return *this; + } + + FMT_CONSTEXPR Char operator*() const + { + return ptr_ != end_ ? *ptr_ : 0; + } + + FMT_CONSTEXPR null_terminating_iterator operator++() + { + ++ptr_; + return *this; + } + + FMT_CONSTEXPR null_terminating_iterator operator++(int) + { + null_terminating_iterator result(*this); + ++ptr_; + return result; + } + + FMT_CONSTEXPR null_terminating_iterator operator--() + { + --ptr_; + return *this; + } + + FMT_CONSTEXPR null_terminating_iterator operator+(difference_type n) + { + return null_terminating_iterator(ptr_ + n, end_); + } + + FMT_CONSTEXPR null_terminating_iterator operator-(difference_type n) + { + return null_terminating_iterator(ptr_ - n, end_); + } + + FMT_CONSTEXPR null_terminating_iterator operator+=(difference_type n) + { + ptr_ += n; + return *this; + } + + FMT_CONSTEXPR difference_type operator-(null_terminating_iterator other) const + { + return ptr_ - other.ptr_; + } + + FMT_CONSTEXPR bool operator!=(null_terminating_iterator other) const + { + return ptr_ != other.ptr_; + } + + bool operator>=(null_terminating_iterator other) const + { + return ptr_ >= other.ptr_; + } + + // This should be a friend specialization pointer_from but the latter + // doesn't compile by gcc 5.1 due to a compiler bug. + template + friend FMT_CONSTEXPR_DECL const CharT *pointer_from(null_terminating_iterator it); + +private: + const Char *ptr_; + const Char *end_; +}; + +template +FMT_CONSTEXPR const T *pointer_from(const T *p) +{ + return p; +} + +template +FMT_CONSTEXPR const Char *pointer_from(null_terminating_iterator it) +{ + return it.ptr_; +} + +// An output iterator that counts the number of objects written to it and +// discards them. +template +class counting_iterator +{ +private: + std::size_t count_; + mutable T blackhole_; + +public: + typedef std::output_iterator_tag iterator_category; + typedef T value_type; + typedef std::ptrdiff_t difference_type; + typedef T *pointer; + typedef T &reference; + typedef counting_iterator _Unchecked_type; // Mark iterator as checked. + + counting_iterator() + : count_(0) + { + } + + std::size_t count() const + { + return count_; + } + + counting_iterator &operator++() + { + ++count_; + return *this; + } + + counting_iterator operator++(int) + { + auto it = *this; + ++*this; + return it; + } + + T &operator*() const + { + return blackhole_; + } +}; + +// An output iterator that truncates the output and counts the number of objects +// written to it. +template +class truncating_iterator +{ +private: + typedef std::iterator_traits traits; + + OutputIt out_; + std::size_t limit_; + std::size_t count_; + mutable typename traits::value_type blackhole_; + +public: + typedef std::output_iterator_tag iterator_category; + typedef typename traits::value_type value_type; + typedef typename traits::difference_type difference_type; + typedef typename traits::pointer pointer; + typedef typename traits::reference reference; + typedef truncating_iterator _Unchecked_type; // Mark iterator as checked. + + truncating_iterator(OutputIt out, std::size_t limit) + : out_(out) + , limit_(limit) + , count_(0) + { + } + + OutputIt base() const + { + return out_; + } + std::size_t count() const + { + return count_; + } + + truncating_iterator &operator++() + { + if (count_++ < limit_) + ++out_; + return *this; + } + + truncating_iterator operator++(int) + { + auto it = *this; + ++*this; + return it; + } + + reference operator*() const + { + return count_ < limit_ ? *out_ : blackhole_; + } +}; + +// Returns true if value is negative, false otherwise. +// Same as (value < 0) but doesn't produce warnings if T is an unsigned type. +template +FMT_CONSTEXPR typename std::enable_if::is_signed, bool>::type is_negative(T value) +{ + return value < 0; +} +template +FMT_CONSTEXPR typename std::enable_if::is_signed, bool>::type is_negative(T) +{ + return false; +} + +template +struct int_traits +{ + // Smallest of uint32_t and uint64_t that is large enough to represent + // all values of T. + typedef typename std::conditional::digits <= 32, uint32_t, uint64_t>::type main_type; +}; + +// Static data is placed in this class template to allow header-only +// configuration. +template +struct FMT_API basic_data +{ + static const uint32_t POWERS_OF_10_32[]; + static const uint64_t POWERS_OF_10_64[]; + static const uint64_t POW10_SIGNIFICANDS[]; + static const int16_t POW10_EXPONENTS[]; + static const char DIGITS[]; + static const char RESET_COLOR[]; + static const wchar_t WRESET_COLOR[]; +}; + +#if FMT_USE_EXTERN_TEMPLATES +extern template struct basic_data; +#endif + +typedef basic_data<> data; + +#ifdef FMT_BUILTIN_CLZLL +// Returns the number of decimal digits in n. Leading zeros are not counted +// except for n == 0 in which case count_digits returns 1. +inline unsigned count_digits(uint64_t n) +{ + // Based on http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog10 + // and the benchmark https://github.com/localvoid/cxx-benchmark-count-digits. + int t = (64 - FMT_BUILTIN_CLZLL(n | 1)) * 1233 >> 12; + return to_unsigned(t) - (n < data::POWERS_OF_10_64[t]) + 1; +} +#else +// Fallback version of count_digits used when __builtin_clz is not available. +inline unsigned count_digits(uint64_t n) +{ + unsigned count = 1; + for (;;) + { + // Integer division is slow so do it for a group of four digits instead + // of for every digit. The idea comes from the talk by Alexandrescu + // "Three Optimization Tips for C++". See speed-test for a comparison. + if (n < 10) + return count; + if (n < 100) + return count + 1; + if (n < 1000) + return count + 2; + if (n < 10000) + return count + 3; + n /= 10000u; + count += 4; + } +} +#endif + +#if FMT_HAS_CPP_ATTRIBUTE(always_inline) +#define FMT_ALWAYS_INLINE __attribute__((always_inline)) +#else +#define FMT_ALWAYS_INLINE +#endif + +template +inline char *lg(uint32_t n, Handler h) FMT_ALWAYS_INLINE; + +// Computes g = floor(log10(n)) and calls h.on(n); +template +inline char *lg(uint32_t n, Handler h) +{ + return n < 100 ? n < 10 ? h.template on<0>(n) : h.template on<1>(n) + : n < 1000000 ? n < 10000 ? n < 1000 ? h.template on<2>(n) : h.template on<3>(n) + : n < 100000 ? h.template on<4>(n) : h.template on<5>(n) + : n < 100000000 ? n < 10000000 ? h.template on<6>(n) : h.template on<7>(n) + : n < 1000000000 ? h.template on<8>(n) : h.template on<9>(n); +} + +// An lg handler that formats a decimal number. +// Usage: lg(n, decimal_formatter(buffer)); +class decimal_formatter +{ +private: + char *buffer_; + + void write_pair(unsigned N, uint32_t index) + { + std::memcpy(buffer_ + N, data::DIGITS + index * 2, 2); + } + +public: + explicit decimal_formatter(char *buf) + : buffer_(buf) + { + } + + template + char *on(uint32_t u) + { + if (N == 0) + { + *buffer_ = static_cast(u) + '0'; + } + else if (N == 1) + { + write_pair(0, u); + } + else + { + // The idea of using 4.32 fixed-point numbers is based on + // https://github.com/jeaiii/itoa + unsigned n = N - 1; + unsigned a = n / 5 * n * 53 / 16; + uint64_t t = ((1ULL << (32 + a)) / data::POWERS_OF_10_32[n] + 1 - n / 9); + t = ((t * u) >> a) + n / 5 * 4; + write_pair(0, t >> 32); + for (unsigned i = 2; i < N; i += 2) + { + t = 100ULL * static_cast(t); + write_pair(i, t >> 32); + } + if (N % 2 == 0) + { + buffer_[N] = static_cast((10ULL * static_cast(t)) >> 32) + '0'; + } + } + return buffer_ += N + 1; + } +}; + +// An lg handler that formats a decimal number with a terminating null. +class decimal_formatter_null : public decimal_formatter +{ +public: + explicit decimal_formatter_null(char *buf) + : decimal_formatter(buf) + { + } + + template + char *on(uint32_t u) + { + char *buf = decimal_formatter::on(u); + *buf = '\0'; + return buf; + } +}; + +#ifdef FMT_BUILTIN_CLZ +// Optional version of count_digits for better performance on 32-bit platforms. +inline unsigned count_digits(uint32_t n) +{ + int t = (32 - FMT_BUILTIN_CLZ(n | 1)) * 1233 >> 12; + return to_unsigned(t) - (n < data::POWERS_OF_10_32[t]) + 1; +} +#endif + +// A functor that doesn't add a thousands separator. +struct no_thousands_sep +{ + typedef char char_type; + + template + void operator()(Char *) + { + } +}; + +// A functor that adds a thousands separator. +template +class add_thousands_sep +{ +private: + basic_string_view sep_; + + // Index of a decimal digit with the least significant digit having index 0. + unsigned digit_index_; + +public: + typedef Char char_type; + + explicit add_thousands_sep(basic_string_view sep) + : sep_(sep) + , digit_index_(0) + { + } + + void operator()(Char *&buffer) + { + if (++digit_index_ % 3 != 0) + return; + buffer -= sep_.size(); + std::uninitialized_copy(sep_.data(), sep_.data() + sep_.size(), internal::make_checked(buffer, sep_.size())); + } +}; + +template +FMT_API Char thousands_sep(locale_provider *lp); + +// Formats a decimal unsigned integer value writing into buffer. +// thousands_sep is a functor that is called after writing each char to +// add a thousands separator if necessary. +template +inline Char *format_decimal(Char *buffer, UInt value, unsigned num_digits, ThousandsSep thousands_sep) +{ + buffer += num_digits; + Char *end = buffer; + while (value >= 100) + { + // Integer division is slow so do it for a group of two digits instead + // of for every digit. The idea comes from the talk by Alexandrescu + // "Three Optimization Tips for C++". See speed-test for a comparison. + unsigned index = static_cast((value % 100) * 2); + value /= 100; + *--buffer = data::DIGITS[index + 1]; + thousands_sep(buffer); + *--buffer = data::DIGITS[index]; + thousands_sep(buffer); + } + if (value < 10) + { + *--buffer = static_cast('0' + value); + return end; + } + unsigned index = static_cast(value * 2); + *--buffer = data::DIGITS[index + 1]; + thousands_sep(buffer); + *--buffer = data::DIGITS[index]; + return end; +} + +template +inline Iterator format_decimal(Iterator out, UInt value, unsigned num_digits, ThousandsSep sep) +{ + typedef typename ThousandsSep::char_type char_type; + // Buffer should be large enough to hold all digits (digits10 + 1) and null. + char_type buffer[std::numeric_limits::digits10 + 2]; + format_decimal(buffer, value, num_digits, sep); + return std::copy_n(buffer, num_digits, out); +} + +template +inline It format_decimal(It out, UInt value, unsigned num_digits) +{ + return format_decimal(out, value, num_digits, no_thousands_sep()); +} + +template +inline Char *format_uint(Char *buffer, UInt value, unsigned num_digits, bool upper = false) +{ + buffer += num_digits; + Char *end = buffer; + do + { + const char *digits = upper ? "0123456789ABCDEF" : "0123456789abcdef"; + unsigned digit = (value & ((1 << BASE_BITS) - 1)); + *--buffer = BASE_BITS < 4 ? static_cast('0' + digit) : digits[digit]; + } while ((value >>= BASE_BITS) != 0); + return end; +} + +template +inline It format_uint(It out, UInt value, unsigned num_digits, bool upper = false) +{ + // Buffer should be large enough to hold all digits (digits / BASE_BITS + 1) + // and null. + char buffer[std::numeric_limits::digits / BASE_BITS + 2]; + format_uint(buffer, value, num_digits, upper); + return std::copy_n(buffer, num_digits, out); +} + +#ifndef _WIN32 +#define FMT_USE_WINDOWS_H 0 +#elif !defined(FMT_USE_WINDOWS_H) +#define FMT_USE_WINDOWS_H 1 +#endif + +// Define FMT_USE_WINDOWS_H to 0 to disable use of windows.h. +// All the functionality that relies on it will be disabled too. +#if FMT_USE_WINDOWS_H +// A converter from UTF-8 to UTF-16. +// It is only provided for Windows since other systems support UTF-8 natively. +class utf8_to_utf16 +{ +private: + wmemory_buffer buffer_; + +public: + FMT_API explicit utf8_to_utf16(string_view s); + operator wstring_view() const + { + return wstring_view(&buffer_[0], size()); + } + size_t size() const + { + return buffer_.size() - 1; + } + const wchar_t *c_str() const + { + return &buffer_[0]; + } + std::wstring str() const + { + return std::wstring(&buffer_[0], size()); + } +}; + +// A converter from UTF-16 to UTF-8. +// It is only provided for Windows since other systems support UTF-8 natively. +class utf16_to_utf8 +{ +private: + memory_buffer buffer_; + +public: + utf16_to_utf8() {} + FMT_API explicit utf16_to_utf8(wstring_view s); + operator string_view() const + { + return string_view(&buffer_[0], size()); + } + size_t size() const + { + return buffer_.size() - 1; + } + const char *c_str() const + { + return &buffer_[0]; + } + std::string str() const + { + return std::string(&buffer_[0], size()); + } + + // Performs conversion returning a system error code instead of + // throwing exception on conversion error. This method may still throw + // in case of memory allocation error. + FMT_API int convert(wstring_view s); +}; + +FMT_API void format_windows_error(fmt::internal::buffer &out, int error_code, fmt::string_view message) FMT_NOEXCEPT; +#endif + +template +struct null +{ +}; +} // namespace internal + +struct monostate +{ +}; + +/** + \rst + Visits an argument dispatching to the appropriate visit method based on + the argument type. For example, if the argument type is ``double`` then + ``vis(value)`` will be called with the value of type ``double``. + \endrst + */ +template +FMT_CONSTEXPR typename internal::result_of::type visit(Visitor &&vis, basic_format_arg arg) +{ + typedef typename Context::char_type char_type; + switch (arg.type_) + { + case internal::none_type: + break; + case internal::named_arg_type: + FMT_ASSERT(false, "invalid argument type"); + break; + case internal::int_type: + return vis(arg.value_.int_value); + case internal::uint_type: + return vis(arg.value_.uint_value); + case internal::long_long_type: + return vis(arg.value_.long_long_value); + case internal::ulong_long_type: + return vis(arg.value_.ulong_long_value); + case internal::bool_type: + return vis(arg.value_.int_value != 0); + case internal::char_type: + return vis(static_cast(arg.value_.int_value)); + case internal::double_type: + return vis(arg.value_.double_value); + case internal::long_double_type: + return vis(arg.value_.long_double_value); + case internal::cstring_type: + return vis(arg.value_.string.value); + case internal::string_type: + return vis(basic_string_view(arg.value_.string.value, arg.value_.string.size)); + case internal::pointer_type: + return vis(arg.value_.pointer); + case internal::custom_type: + return vis(typename basic_format_arg::handle(arg.value_.custom)); + } + return vis(monostate()); +} + +enum alignment +{ + ALIGN_DEFAULT, + ALIGN_LEFT, + ALIGN_RIGHT, + ALIGN_CENTER, + ALIGN_NUMERIC +}; + +// Flags. +enum +{ + SIGN_FLAG = 1, + PLUS_FLAG = 2, + MINUS_FLAG = 4, + HASH_FLAG = 8 +}; + +enum format_spec_tag +{ + fill_tag, + align_tag, + width_tag, + type_tag +}; + +// Format specifier. +template +class format_spec +{ +private: + T value_; + +public: + typedef T value_type; + + explicit format_spec(T value) + : value_(value) + { + } + + T value() const + { + return value_; + } +}; + +// template +// typedef format_spec fill_spec; +template +class fill_spec : public format_spec +{ +public: + explicit fill_spec(Char value) + : format_spec(value) + { + } +}; + +typedef format_spec width_spec; +typedef format_spec type_spec; + +// An empty format specifier. +struct empty_spec +{ +}; + +// An alignment specifier. +struct align_spec : empty_spec +{ + unsigned width_; + // Fill is always wchar_t and cast to char if necessary to avoid having + // two specialization of AlignSpec and its subclasses. + wchar_t fill_; + alignment align_; + + FMT_CONSTEXPR align_spec(unsigned width, wchar_t fill, alignment align = ALIGN_DEFAULT) + : width_(width) + , fill_(fill) + , align_(align) + { + } + + FMT_CONSTEXPR unsigned width() const + { + return width_; + } + FMT_CONSTEXPR wchar_t fill() const + { + return fill_; + } + FMT_CONSTEXPR alignment align() const + { + return align_; + } + + int precision() const + { + return -1; + } +}; + +// Format specifiers. +template +class basic_format_specs : public align_spec +{ +public: + unsigned flags_; + int precision_; + Char type_; + + FMT_CONSTEXPR basic_format_specs(unsigned width = 0, char type = 0, wchar_t fill = ' ') + : align_spec(width, fill) + , flags_(0) + , precision_(-1) + , type_(type) + { + } + + FMT_CONSTEXPR bool flag(unsigned f) const + { + return (flags_ & f) != 0; + } + FMT_CONSTEXPR int precision() const + { + return precision_; + } + FMT_CONSTEXPR Char type() const + { + return type_; + } +}; + +typedef basic_format_specs format_specs; + +template +FMT_CONSTEXPR unsigned basic_parse_context::next_arg_id() +{ + if (next_arg_id_ >= 0) + return internal::to_unsigned(next_arg_id_++); + on_error("cannot switch from manual to automatic argument indexing"); + return 0; +} + +struct format_string +{ +}; + +namespace internal { + +template +FMT_CONSTEXPR void handle_int_type_spec(Char spec, Handler &&handler) +{ + switch (spec) + { + case 0: + case 'd': + handler.on_dec(); + break; + case 'x': + case 'X': + handler.on_hex(); + break; + case 'b': + case 'B': + handler.on_bin(); + break; + case 'o': + handler.on_oct(); + break; + case 'n': + handler.on_num(); + break; + default: + handler.on_error(); + } +} + +template +FMT_CONSTEXPR void handle_float_type_spec(Char spec, Handler &&handler) +{ + switch (spec) + { + case 0: + case 'g': + case 'G': + handler.on_general(); + break; + case 'e': + case 'E': + handler.on_exp(); + break; + case 'f': + case 'F': + handler.on_fixed(); + break; + case 'a': + case 'A': + handler.on_hex(); + break; + default: + handler.on_error(); + break; + } +} + +template +FMT_CONSTEXPR void handle_char_specs(const basic_format_specs &specs, Handler &&handler) +{ + if (specs.type() && specs.type() != 'c') + { + handler.on_int(); + return; + } + if (specs.align() == ALIGN_NUMERIC || specs.flag(~0u) != 0) + handler.on_error("invalid format specifier for char"); + handler.on_char(); +} + +template +FMT_CONSTEXPR void handle_cstring_type_spec(Char spec, Handler &&handler) +{ + if (spec == 0 || spec == 's') + handler.on_string(); + else if (spec == 'p') + handler.on_pointer(); + else + handler.on_error("invalid type specifier"); +} + +template +FMT_CONSTEXPR void check_string_type_spec(Char spec, ErrorHandler &&eh) +{ + if (spec != 0 && spec != 's') + eh.on_error("invalid type specifier"); +} + +template +FMT_CONSTEXPR void check_pointer_type_spec(Char spec, ErrorHandler &&eh) +{ + if (spec != 0 && spec != 'p') + eh.on_error("invalid type specifier"); +} + +template +class int_type_checker : private ErrorHandler +{ +public: + FMT_CONSTEXPR explicit int_type_checker(ErrorHandler eh) + : ErrorHandler(eh) + { + } + + FMT_CONSTEXPR void on_dec() {} + FMT_CONSTEXPR void on_hex() {} + FMT_CONSTEXPR void on_bin() {} + FMT_CONSTEXPR void on_oct() {} + FMT_CONSTEXPR void on_num() {} + + FMT_CONSTEXPR void on_error() + { + ErrorHandler::on_error("invalid type specifier"); + } +}; + +template +class float_type_checker : private ErrorHandler +{ +public: + FMT_CONSTEXPR explicit float_type_checker(ErrorHandler eh) + : ErrorHandler(eh) + { + } + + FMT_CONSTEXPR void on_general() {} + FMT_CONSTEXPR void on_exp() {} + FMT_CONSTEXPR void on_fixed() {} + FMT_CONSTEXPR void on_hex() {} + + FMT_CONSTEXPR void on_error() + { + ErrorHandler::on_error("invalid type specifier"); + } +}; + +template +class char_specs_checker : public ErrorHandler +{ +private: + CharType type_; + +public: + FMT_CONSTEXPR char_specs_checker(CharType type, ErrorHandler eh) + : ErrorHandler(eh) + , type_(type) + { + } + + FMT_CONSTEXPR void on_int() + { + handle_int_type_spec(type_, int_type_checker(*this)); + } + FMT_CONSTEXPR void on_char() {} +}; + +template +class cstring_type_checker : public ErrorHandler +{ +public: + FMT_CONSTEXPR explicit cstring_type_checker(ErrorHandler eh) + : ErrorHandler(eh) + { + } + + FMT_CONSTEXPR void on_string() {} + FMT_CONSTEXPR void on_pointer() {} +}; + +template +void arg_map::init(const basic_format_args &args) +{ + if (map_) + return; + map_ = new entry[args.max_size()]; + bool use_values = args.type(max_packed_args - 1) == internal::none_type; + if (use_values) + { + for (unsigned i = 0; /*nothing*/; ++i) + { + internal::type arg_type = args.type(i); + switch (arg_type) + { + case internal::none_type: + return; + case internal::named_arg_type: + push_back(args.values_[i]); + break; + default: + break; // Do nothing. + } + } + return; + } + for (unsigned i = 0;; ++i) + { + switch (args.args_[i].type_) + { + case internal::none_type: + return; + case internal::named_arg_type: + push_back(args.args_[i].value_); + break; + default: + break; // Do nothing. + } + } +} + +template +class arg_formatter_base +{ +public: + typedef typename Range::value_type char_type; + typedef decltype(internal::declval().begin()) iterator; + typedef basic_format_specs format_specs; + +private: + typedef basic_writer writer_type; + writer_type writer_; + format_specs &specs_; + + FMT_DISALLOW_COPY_AND_ASSIGN(arg_formatter_base); + + struct char_writer + { + char_type value; + template + void operator()(It &&it) const + { + *it++ = value; + } + }; + + void write_char(char_type value) + { + writer_.write_padded(1, specs_, char_writer{value}); + } + + void write_pointer(const void *p) + { + format_specs specs = specs_; + specs.flags_ = HASH_FLAG; + specs.type_ = 'x'; + writer_.write_int(reinterpret_cast(p), specs); + } + +protected: + writer_type &writer() + { + return writer_; + } + format_specs &spec() + { + return specs_; + } + iterator out() + { + return writer_.out(); + } + + void write(bool value) + { + writer_.write_str(string_view(value ? "true" : "false"), specs_); + } + + void write(const char_type *value) + { + if (!value) + FMT_THROW(format_error("string pointer is null")); + auto length = std::char_traits::length(value); + writer_.write_str(basic_string_view(value, length), specs_); + } + +public: + arg_formatter_base(Range r, format_specs &s) + : writer_(r) + , specs_(s) + { + } + + iterator operator()(monostate) + { + FMT_ASSERT(false, "invalid argument type"); + return out(); + } + + template + typename std::enable_if::value, iterator>::type operator()(T value) + { + // MSVC2013 fails to compile separate overloads for bool and char_type so + // use std::is_same instead. + if (std::is_same::value) + { + if (specs_.type_) + return (*this)(value ? 1 : 0); + write(value != 0); + } + else if (std::is_same::value) + { + internal::handle_char_specs(specs_, char_spec_handler(*this, static_cast(value))); + } + else + { + writer_.write_int(value, specs_); + } + return out(); + } + + template + typename std::enable_if::value, iterator>::type operator()(T value) + { + writer_.write_double(value, specs_); + return out(); + } + + struct char_spec_handler : internal::error_handler + { + arg_formatter_base &formatter; + char_type value; + + char_spec_handler(arg_formatter_base &f, char_type val) + : formatter(f) + , value(val) + { + } + + void on_int() + { + formatter.writer_.write_int(value, formatter.specs_); + } + void on_char() + { + formatter.write_char(value); + } + }; + + struct cstring_spec_handler : internal::error_handler + { + arg_formatter_base &formatter; + const char_type *value; + + cstring_spec_handler(arg_formatter_base &f, const char_type *val) + : formatter(f) + , value(val) + { + } + + void on_string() + { + formatter.write(value); + } + void on_pointer() + { + formatter.write_pointer(value); + } + }; + + iterator operator()(const char_type *value) + { + internal::handle_cstring_type_spec(specs_.type_, cstring_spec_handler(*this, value)); + return out(); + } + + iterator operator()(basic_string_view value) + { + internal::check_string_type_spec(specs_.type_, internal::error_handler()); + writer_.write_str(value, specs_); + return out(); + } + + iterator operator()(const void *value) + { + check_pointer_type_spec(specs_.type_, internal::error_handler()); + write_pointer(value); + return out(); + } +}; + +template +struct is_format_string : std::integral_constant::value> +{ +}; + +template +FMT_CONSTEXPR bool is_name_start(Char c) +{ + return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || '_' == c; +} + +// Parses the input as an unsigned integer. This function assumes that the +// first character is a digit and presence of a non-digit character at the end. +// it: an iterator pointing to the beginning of the input range. +template +FMT_CONSTEXPR unsigned parse_nonnegative_int(Iterator &it, ErrorHandler &&eh) +{ + assert('0' <= *it && *it <= '9'); + unsigned value = 0; + // Convert to unsigned to prevent a warning. + unsigned max_int = (std::numeric_limits::max)(); + unsigned big = max_int / 10; + do + { + // Check for overflow. + if (value > big) + { + value = max_int + 1; + break; + } + value = value * 10 + unsigned(*it - '0'); + // Workaround for MSVC "setup_exception stack overflow" error: + auto next = it; + ++next; + it = next; + } while ('0' <= *it && *it <= '9'); + if (value > max_int) + eh.on_error("number is too big"); + return value; +} + +template +class custom_formatter : public function +{ +private: + Context &ctx_; + +public: + explicit custom_formatter(Context &ctx) + : ctx_(ctx) + { + } + + bool operator()(typename basic_format_arg::handle h) const + { + h.format(ctx_); + return true; + } + + template + bool operator()(T) const + { + return false; + } +}; + +template +struct is_integer +{ + enum + { + value = + std::is_integral::value && !std::is_same::value && !std::is_same::value && !std::is_same::value + }; +}; + +template +class width_checker : public function +{ +public: + explicit FMT_CONSTEXPR width_checker(ErrorHandler &eh) + : handler_(eh) + { + } + + template + FMT_CONSTEXPR typename std::enable_if::value, unsigned long long>::type operator()(T value) + { + if (is_negative(value)) + handler_.on_error("negative width"); + return static_cast(value); + } + + template + FMT_CONSTEXPR typename std::enable_if::value, unsigned long long>::type operator()(T) + { + handler_.on_error("width is not integer"); + return 0; + } + +private: + ErrorHandler &handler_; +}; + +template +class precision_checker : public function +{ +public: + explicit FMT_CONSTEXPR precision_checker(ErrorHandler &eh) + : handler_(eh) + { + } + + template + FMT_CONSTEXPR typename std::enable_if::value, unsigned long long>::type operator()(T value) + { + if (is_negative(value)) + handler_.on_error("negative precision"); + return static_cast(value); + } + + template + FMT_CONSTEXPR typename std::enable_if::value, unsigned long long>::type operator()(T) + { + handler_.on_error("precision is not integer"); + return 0; + } + +private: + ErrorHandler &handler_; +}; + +// A format specifier handler that sets fields in basic_format_specs. +template +class specs_setter +{ +public: + explicit FMT_CONSTEXPR specs_setter(basic_format_specs &specs) + : specs_(specs) + { + } + + FMT_CONSTEXPR specs_setter(const specs_setter &other) + : specs_(other.specs_) + { + } + + FMT_CONSTEXPR void on_align(alignment align) + { + specs_.align_ = align; + } + FMT_CONSTEXPR void on_fill(Char fill) + { + specs_.fill_ = fill; + } + FMT_CONSTEXPR void on_plus() + { + specs_.flags_ |= SIGN_FLAG | PLUS_FLAG; + } + FMT_CONSTEXPR void on_minus() + { + specs_.flags_ |= MINUS_FLAG; + } + FMT_CONSTEXPR void on_space() + { + specs_.flags_ |= SIGN_FLAG; + } + FMT_CONSTEXPR void on_hash() + { + specs_.flags_ |= HASH_FLAG; + } + + FMT_CONSTEXPR void on_zero() + { + specs_.align_ = ALIGN_NUMERIC; + specs_.fill_ = '0'; + } + + FMT_CONSTEXPR void on_width(unsigned width) + { + specs_.width_ = width; + } + FMT_CONSTEXPR void on_precision(unsigned precision) + { + specs_.precision_ = static_cast(precision); + } + FMT_CONSTEXPR void end_precision() {} + + FMT_CONSTEXPR void on_type(Char type) + { + specs_.type_ = type; + } + +protected: + basic_format_specs &specs_; +}; + +// A format specifier handler that checks if specifiers are consistent with the +// argument type. +template +class specs_checker : public Handler +{ +public: + FMT_CONSTEXPR specs_checker(const Handler &handler, internal::type arg_type) + : Handler(handler) + , arg_type_(arg_type) + { + } + + FMT_CONSTEXPR specs_checker(const specs_checker &other) + : Handler(other) + , arg_type_(other.arg_type_) + { + } + + FMT_CONSTEXPR void on_align(alignment align) + { + if (align == ALIGN_NUMERIC) + require_numeric_argument(); + Handler::on_align(align); + } + + FMT_CONSTEXPR void on_plus() + { + check_sign(); + Handler::on_plus(); + } + + FMT_CONSTEXPR void on_minus() + { + check_sign(); + Handler::on_minus(); + } + + FMT_CONSTEXPR void on_space() + { + check_sign(); + Handler::on_space(); + } + + FMT_CONSTEXPR void on_hash() + { + require_numeric_argument(); + Handler::on_hash(); + } + + FMT_CONSTEXPR void on_zero() + { + require_numeric_argument(); + Handler::on_zero(); + } + + FMT_CONSTEXPR void end_precision() + { + if (is_integral(arg_type_) || arg_type_ == pointer_type) + this->on_error("precision not allowed for this argument type"); + } + +private: + FMT_CONSTEXPR void require_numeric_argument() + { + if (!is_arithmetic(arg_type_)) + this->on_error("format specifier requires numeric argument"); + } + + FMT_CONSTEXPR void check_sign() + { + require_numeric_argument(); + if (is_integral(arg_type_) && arg_type_ != int_type && arg_type_ != long_long_type && arg_type_ != internal::char_type) + { + this->on_error("format specifier requires signed argument"); + } + } + + internal::type arg_type_; +}; + +template class Handler, typename T, typename Context, typename ErrorHandler> +FMT_CONSTEXPR void set_dynamic_spec(T &value, basic_format_arg arg, ErrorHandler eh) +{ + unsigned long long big_value = visit(Handler(eh), arg); + if (big_value > (std::numeric_limits::max)()) + eh.on_error("number is too big"); + value = static_cast(big_value); +} + +struct auto_id +{ +}; + +// The standard format specifier handler with checking. +template +class specs_handler : public specs_setter +{ +public: + typedef typename Context::char_type char_type; + + FMT_CONSTEXPR specs_handler(basic_format_specs &specs, Context &ctx) + : specs_setter(specs) + , context_(ctx) + { + } + + template + FMT_CONSTEXPR void on_dynamic_width(Id arg_id) + { + set_dynamic_spec(this->specs_.width_, get_arg(arg_id), context_.error_handler()); + } + + template + FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) + { + set_dynamic_spec(this->specs_.precision_, get_arg(arg_id), context_.error_handler()); + } + + void on_error(const char *message) + { + context_.on_error(message); + } + +private: + FMT_CONSTEXPR basic_format_arg get_arg(auto_id) + { + return context_.next_arg(); + } + + template + FMT_CONSTEXPR basic_format_arg get_arg(Id arg_id) + { + context_.parse_context().check_arg_id(arg_id); + return context_.get_arg(arg_id); + } + + Context &context_; +}; + +// An argument reference. +template +struct arg_ref +{ + enum Kind + { + NONE, + INDEX, + NAME + }; + + FMT_CONSTEXPR arg_ref() + : kind(NONE) + , index(0) + { + } + FMT_CONSTEXPR explicit arg_ref(unsigned index) + : kind(INDEX) + , index(index) + { + } + explicit arg_ref(basic_string_view name) + : kind(NAME) + , name(name) + { + } + + FMT_CONSTEXPR arg_ref &operator=(unsigned idx) + { + kind = INDEX; + index = idx; + return *this; + } + + Kind kind; + FMT_UNION + { + unsigned index; + basic_string_view name; + }; +}; + +// Format specifiers with width and precision resolved at formatting rather +// than parsing time to allow re-using the same parsed specifiers with +// differents sets of arguments (precompilation of format strings). +template +struct dynamic_format_specs : basic_format_specs +{ + arg_ref width_ref; + arg_ref precision_ref; +}; + +// Format spec handler that saves references to arguments representing dynamic +// width and precision to be resolved at formatting time. +template +class dynamic_specs_handler : public specs_setter +{ +public: + typedef typename ParseContext::char_type char_type; + + FMT_CONSTEXPR dynamic_specs_handler(dynamic_format_specs &specs, ParseContext &ctx) + : specs_setter(specs) + , specs_(specs) + , context_(ctx) + { + } + + FMT_CONSTEXPR dynamic_specs_handler(const dynamic_specs_handler &other) + : specs_setter(other) + , specs_(other.specs_) + , context_(other.context_) + { + } + + template + FMT_CONSTEXPR void on_dynamic_width(Id arg_id) + { + specs_.width_ref = make_arg_ref(arg_id); + } + + template + FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) + { + specs_.precision_ref = make_arg_ref(arg_id); + } + + FMT_CONSTEXPR void on_error(const char *message) + { + context_.on_error(message); + } + +private: + typedef arg_ref arg_ref_type; + + template + FMT_CONSTEXPR arg_ref_type make_arg_ref(Id arg_id) + { + context_.check_arg_id(arg_id); + return arg_ref_type(arg_id); + } + + FMT_CONSTEXPR arg_ref_type make_arg_ref(auto_id) + { + return arg_ref_type(context_.next_arg_id()); + } + + dynamic_format_specs &specs_; + ParseContext &context_; +}; + +template +FMT_CONSTEXPR Iterator parse_arg_id(Iterator it, IDHandler &&handler) +{ + typedef typename std::iterator_traits::value_type char_type; + char_type c = *it; + if (c == '}' || c == ':') + { + handler(); + return it; + } + if (c >= '0' && c <= '9') + { + unsigned index = parse_nonnegative_int(it, handler); + if (*it != '}' && *it != ':') + { + handler.on_error("invalid format string"); + return it; + } + handler(index); + return it; + } + if (!is_name_start(c)) + { + handler.on_error("invalid format string"); + return it; + } + auto start = it; + do + { + c = *++it; + } while (is_name_start(c) || ('0' <= c && c <= '9')); + handler(basic_string_view(pointer_from(start), to_unsigned(it - start))); + return it; +} + +// Adapts SpecHandler to IDHandler API for dynamic width. +template +struct width_adapter +{ + explicit FMT_CONSTEXPR width_adapter(SpecHandler &h) + : handler(h) + { + } + + FMT_CONSTEXPR void operator()() + { + handler.on_dynamic_width(auto_id()); + } + FMT_CONSTEXPR void operator()(unsigned id) + { + handler.on_dynamic_width(id); + } + FMT_CONSTEXPR void operator()(basic_string_view id) + { + handler.on_dynamic_width(id); + } + + FMT_CONSTEXPR void on_error(const char *message) + { + handler.on_error(message); + } + + SpecHandler &handler; +}; + +// Adapts SpecHandler to IDHandler API for dynamic precision. +template +struct precision_adapter +{ + explicit FMT_CONSTEXPR precision_adapter(SpecHandler &h) + : handler(h) + { + } + + FMT_CONSTEXPR void operator()() + { + handler.on_dynamic_precision(auto_id()); + } + FMT_CONSTEXPR void operator()(unsigned id) + { + handler.on_dynamic_precision(id); + } + FMT_CONSTEXPR void operator()(basic_string_view id) + { + handler.on_dynamic_precision(id); + } + + FMT_CONSTEXPR void on_error(const char *message) + { + handler.on_error(message); + } + + SpecHandler &handler; +}; + +// Parses standard format specifiers and sends notifications about parsed +// components to handler. +// it: an iterator pointing to the beginning of a null-terminated range of +// characters, possibly emulated via null_terminating_iterator, representing +// format specifiers. +template +FMT_CONSTEXPR Iterator parse_format_specs(Iterator it, SpecHandler &&handler) +{ + typedef typename std::iterator_traits::value_type char_type; + char_type c = *it; + if (c == '}' || !c) + return it; + + // Parse fill and alignment. + alignment align = ALIGN_DEFAULT; + int i = 1; + do + { + auto p = it + i; + switch (*p) + { + case '<': + align = ALIGN_LEFT; + break; + case '>': + align = ALIGN_RIGHT; + break; + case '=': + align = ALIGN_NUMERIC; + break; + case '^': + align = ALIGN_CENTER; + break; + } + if (align != ALIGN_DEFAULT) + { + if (p != it) + { + if (c == '{') + { + handler.on_error("invalid fill character '{'"); + return it; + } + it += 2; + handler.on_fill(c); + } + else + ++it; + handler.on_align(align); + break; + } + } while (--i >= 0); + + // Parse sign. + switch (*it) + { + case '+': + handler.on_plus(); + ++it; + break; + case '-': + handler.on_minus(); + ++it; + break; + case ' ': + handler.on_space(); + ++it; + break; + } + + if (*it == '#') + { + handler.on_hash(); + ++it; + } + + // Parse zero flag. + if (*it == '0') + { + handler.on_zero(); + ++it; + } + + // Parse width. + if ('0' <= *it && *it <= '9') + { + handler.on_width(parse_nonnegative_int(it, handler)); + } + else if (*it == '{') + { + it = parse_arg_id(it + 1, width_adapter(handler)); + if (*it++ != '}') + { + handler.on_error("invalid format string"); + return it; + } + } + + // Parse precision. + if (*it == '.') + { + ++it; + if ('0' <= *it && *it <= '9') + { + handler.on_precision(parse_nonnegative_int(it, handler)); + } + else if (*it == '{') + { + it = parse_arg_id(it + 1, precision_adapter(handler)); + if (*it++ != '}') + { + handler.on_error("invalid format string"); + return it; + } + } + else + { + handler.on_error("missing precision specifier"); + return it; + } + handler.end_precision(); + } + + // Parse type. + if (*it != '}' && *it) + handler.on_type(*it++); + return it; +} + +template +struct id_adapter +{ + FMT_CONSTEXPR explicit id_adapter(Handler &h) + : handler(h) + { + } + + FMT_CONSTEXPR void operator()() + { + handler.on_arg_id(); + } + FMT_CONSTEXPR void operator()(unsigned id) + { + handler.on_arg_id(id); + } + FMT_CONSTEXPR void operator()(basic_string_view id) + { + handler.on_arg_id(id); + } + + FMT_CONSTEXPR void on_error(const char *message) + { + handler.on_error(message); + } + + Handler &handler; +}; + +template +FMT_CONSTEXPR void parse_format_string(Iterator it, Handler &&handler) +{ + typedef typename std::iterator_traits::value_type char_type; + auto start = it; + while (*it) + { + char_type ch = *it++; + if (ch != '{' && ch != '}') + continue; + if (*it == ch) + { + handler.on_text(start, it); + start = ++it; + continue; + } + if (ch == '}') + { + handler.on_error("unmatched '}' in format string"); + return; + } + handler.on_text(start, it - 1); + + it = parse_arg_id(it, id_adapter(handler)); + if (*it == '}') + { + handler.on_replacement_field(it); + } + else if (*it == ':') + { + ++it; + it = handler.on_format_specs(it); + if (*it != '}') + { + handler.on_error("unknown format specifier"); + return; + } + } + else + { + handler.on_error("missing '}' in format string"); + return; + } + + start = ++it; + } + handler.on_text(start, it); +} + +template +FMT_CONSTEXPR const typename ParseContext::char_type *parse_format_specs(ParseContext &ctx) +{ + // GCC 7.2 requires initializer. + formatter f{}; + return f.parse(ctx); +} + +template +class format_string_checker +{ +public: + explicit FMT_CONSTEXPR format_string_checker(basic_string_view format_str, ErrorHandler eh) + : arg_id_(-1) + , context_(format_str, eh) + , parse_funcs_{&parse_format_specs...} + { + } + + FMT_CONSTEXPR void on_text(const Char *, const Char *) {} + + FMT_CONSTEXPR void on_arg_id() + { + arg_id_ = context_.next_arg_id(); + check_arg_id(); + } + FMT_CONSTEXPR void on_arg_id(unsigned id) + { + arg_id_ = id; + context_.check_arg_id(id); + check_arg_id(); + } + FMT_CONSTEXPR void on_arg_id(basic_string_view) {} + + FMT_CONSTEXPR void on_replacement_field(const Char *) {} + + FMT_CONSTEXPR const Char *on_format_specs(const Char *s) + { + context_.advance_to(s); + return to_unsigned(arg_id_) < NUM_ARGS ? parse_funcs_[arg_id_](context_) : s; + } + + FMT_CONSTEXPR void on_error(const char *message) + { + context_.on_error(message); + } + +private: + typedef basic_parse_context parse_context_type; + enum + { + NUM_ARGS = sizeof...(Args) + }; + + FMT_CONSTEXPR void check_arg_id() + { + if (internal::to_unsigned(arg_id_) >= NUM_ARGS) + context_.on_error("argument index out of range"); + } + + // Format specifier parsing function. + typedef const Char *(*parse_func)(parse_context_type &); + + int arg_id_; + parse_context_type context_; + parse_func parse_funcs_[NUM_ARGS > 0 ? NUM_ARGS : 1]; +}; + +template +FMT_CONSTEXPR bool check_format_string(basic_string_view s, ErrorHandler eh = ErrorHandler()) +{ + format_string_checker checker(s, eh); + parse_format_string(s.begin(), checker); + return true; +} + +template +void check_format_string(String format_str) +{ + FMT_CONSTEXPR_DECL bool invalid_format = + internal::check_format_string(string_view(format_str.data(), format_str.size())); + (void)invalid_format; +} + +// Specifies whether to format T using the standard formatter. +// It is not possible to use get_type in formatter specialization directly +// because of a bug in MSVC. +template +struct format_type : std::integral_constant::value != custom_type> +{ +}; + +// Specifies whether to format enums. +template +struct format_enum : std::integral_constant::value> +{ +}; + +template class Handler, typename Spec, typename Context> +void handle_dynamic_spec(Spec &value, arg_ref ref, Context &ctx) +{ + typedef typename Context::char_type char_type; + switch (ref.kind) + { + case arg_ref::NONE: + break; + case arg_ref::INDEX: + internal::set_dynamic_spec(value, ctx.get_arg(ref.index), ctx.error_handler()); + break; + case arg_ref::NAME: + internal::set_dynamic_spec(value, ctx.get_arg(ref.name), ctx.error_handler()); + break; + } +} +} // namespace internal + +/** The default argument formatter. */ +template +class arg_formatter : public internal::function::iterator>, + public internal::arg_formatter_base +{ +private: + typedef typename Range::value_type char_type; + typedef internal::arg_formatter_base base; + typedef basic_format_context context_type; + + context_type &ctx_; + +public: + typedef Range range; + typedef typename base::iterator iterator; + typedef typename base::format_specs format_specs; + + /** + \rst + Constructs an argument formatter object. + *ctx* is a reference to the formatting context, + *spec* contains format specifier information for standard argument types. + \endrst + */ + arg_formatter(context_type &ctx, format_specs &spec) + : base(Range(ctx.out()), spec) + , ctx_(ctx) + { + } + + using base::operator(); + + /** Formats an argument of a user-defined type. */ + iterator operator()(typename basic_format_arg::handle handle) + { + handle.format(ctx_); + return this->out(); + } +}; + +/** + An error returned by an operating system or a language runtime, + for example a file opening error. +*/ +class system_error : public std::runtime_error +{ +private: + FMT_API void init(int err_code, string_view format_str, format_args args); + +protected: + int error_code_; + + system_error() + : std::runtime_error("") + { + } + +public: + /** + \rst + Constructs a :class:`fmt::system_error` object with a description + formatted with `fmt::format_system_error`. *message* and additional + arguments passed into the constructor are formatted similarly to + `fmt::format`. + + **Example**:: + + // This throws a system_error with the description + // cannot open file 'madeup': No such file or directory + // or similar (system message may vary). + const char *filename = "madeup"; + std::FILE *file = std::fopen(filename, "r"); + if (!file) + throw fmt::system_error(errno, "cannot open file '{}'", filename); + \endrst + */ + template + system_error(int error_code, string_view message, const Args &... args) + : std::runtime_error("") + { + init(error_code, message, make_format_args(args...)); + } + + int error_code() const + { + return error_code_; + } +}; + +/** + \rst + Formats an error returned by an operating system or a language runtime, + for example a file opening error, and writes it to *out* in the following + form: + + .. parsed-literal:: + **: ** + + where ** is the passed message and ** is + the system message corresponding to the error code. + *error_code* is a system error code as given by ``errno``. + If *error_code* is not a valid error code such as -1, the system message + may look like "Unknown error -1" and is platform-dependent. + \endrst + */ +FMT_API void format_system_error(internal::buffer &out, int error_code, fmt::string_view message) FMT_NOEXCEPT; + +/** + This template provides operations for formatting and writing data into a + character range. + */ +template +class basic_writer +{ +public: + typedef typename Range::value_type char_type; + typedef decltype(internal::declval().begin()) iterator; + typedef basic_format_specs format_specs; + +private: + // Output iterator. + iterator out_; + + std::unique_ptr locale_; + + FMT_DISALLOW_COPY_AND_ASSIGN(basic_writer); + + iterator out() const + { + return out_; + } + + // Attempts to reserve space for n extra characters in the output range. + // Returns a pointer to the reserved range or a reference to out_. + auto reserve(std::size_t n) -> decltype(internal::reserve(out_, n)) + { + return internal::reserve(out_, n); + } + + // Writes a value in the format + // + // where is written by f(it). + template + void write_padded(std::size_t size, const align_spec &spec, F f); + + template + struct padded_int_writer + { + string_view prefix; + char_type fill; + std::size_t padding; + F f; + + template + void operator()(It &&it) const + { + if (prefix.size() != 0) + it = std::copy_n(prefix.data(), prefix.size(), it); + it = std::fill_n(it, padding, fill); + f(it); + } + }; + + // Writes an integer in the format + // + // where are written by f(it). + template + void write_int(unsigned num_digits, string_view prefix, const Spec &spec, F f) + { + std::size_t size = prefix.size() + num_digits; + char_type fill = static_cast(spec.fill()); + std::size_t padding = 0; + if (spec.align() == ALIGN_NUMERIC) + { + if (spec.width() > size) + { + padding = spec.width() - size; + size = spec.width(); + } + } + else if (spec.precision() > static_cast(num_digits)) + { + size = prefix.size() + static_cast(spec.precision()); + padding = static_cast(spec.precision()) - num_digits; + fill = '0'; + } + align_spec as = spec; + if (spec.align() == ALIGN_DEFAULT) + as.align_ = ALIGN_RIGHT; + write_padded(size, as, padded_int_writer{prefix, fill, padding, f}); + } + + // Writes a decimal integer. + template + void write_decimal(Int value) + { + typedef typename internal::int_traits::main_type main_type; + main_type abs_value = static_cast(value); + bool is_negative = internal::is_negative(value); + if (is_negative) + abs_value = 0 - abs_value; + unsigned num_digits = internal::count_digits(abs_value); + auto &&it = reserve((is_negative ? 1 : 0) + num_digits); + if (is_negative) + *it++ = '-'; + internal::format_decimal(it, abs_value, num_digits); + } + + // The handle_int_type_spec handler that writes an integer. + template + struct int_writer + { + typedef typename internal::int_traits::main_type unsigned_type; + + basic_writer &writer; + const Spec &spec; + unsigned_type abs_value; + char prefix[4]; + unsigned prefix_size; + + string_view get_prefix() const + { + return string_view(prefix, prefix_size); + } + + // Counts the number of digits in abs_value. BITS = log2(radix). + template + unsigned count_digits() const + { + unsigned_type n = abs_value; + unsigned num_digits = 0; + do + { + ++num_digits; + } while ((n >>= BITS) != 0); + return num_digits; + } + + int_writer(basic_writer &w, Int value, const Spec &s) + : writer(w) + , spec(s) + , abs_value(static_cast(value)) + , prefix_size(0) + { + if (internal::is_negative(value)) + { + prefix[0] = '-'; + ++prefix_size; + abs_value = 0 - abs_value; + } + else if (spec.flag(SIGN_FLAG)) + { + prefix[0] = spec.flag(PLUS_FLAG) ? '+' : ' '; + ++prefix_size; + } + } + + struct dec_writer + { + unsigned_type abs_value; + unsigned num_digits; + + template + void operator()(It &&it) const + { + it = internal::format_decimal(it, abs_value, num_digits); + } + }; + + void on_dec() + { + unsigned num_digits = internal::count_digits(abs_value); + writer.write_int(num_digits, get_prefix(), spec, dec_writer{abs_value, num_digits}); + } + + struct hex_writer + { + int_writer &self; + unsigned num_digits; + + template + void operator()(It &&it) const + { + it = internal::format_uint<4>(it, self.abs_value, num_digits, self.spec.type() != 'x'); + } + }; + + void on_hex() + { + if (spec.flag(HASH_FLAG)) + { + prefix[prefix_size++] = '0'; + prefix[prefix_size++] = static_cast(spec.type()); + } + unsigned num_digits = count_digits<4>(); + writer.write_int(num_digits, get_prefix(), spec, hex_writer{*this, num_digits}); + } + + template + struct bin_writer + { + unsigned_type abs_value; + unsigned num_digits; + + template + void operator()(It &&it) const + { + it = internal::format_uint(it, abs_value, num_digits); + } + }; + + void on_bin() + { + if (spec.flag(HASH_FLAG)) + { + prefix[prefix_size++] = '0'; + prefix[prefix_size++] = static_cast(spec.type()); + } + unsigned num_digits = count_digits<1>(); + writer.write_int(num_digits, get_prefix(), spec, bin_writer<1>{abs_value, num_digits}); + } + + void on_oct() + { + unsigned num_digits = count_digits<3>(); + if (spec.flag(HASH_FLAG) && spec.precision() <= static_cast(num_digits)) + { + // Octal prefix '0' is counted as a digit, so only add it if precision + // is not greater than the number of digits. + prefix[prefix_size++] = '0'; + } + writer.write_int(num_digits, get_prefix(), spec, bin_writer<3>{abs_value, num_digits}); + } + + enum + { + SEP_SIZE = 1 + }; + + struct num_writer + { + unsigned_type abs_value; + unsigned size; + char_type sep; + + template + void operator()(It &&it) const + { + basic_string_view s(&sep, SEP_SIZE); + it = format_decimal(it, abs_value, size, internal::add_thousands_sep(s)); + } + }; + + void on_num() + { + unsigned num_digits = internal::count_digits(abs_value); + char_type sep = internal::thousands_sep(writer.locale_.get()); + unsigned size = num_digits + SEP_SIZE * ((num_digits - 1) / 3); + writer.write_int(size, get_prefix(), spec, num_writer{abs_value, size, sep}); + } + + void on_error() + { + FMT_THROW(format_error("invalid type specifier")); + } + }; + + // Writes a formatted integer. + template + void write_int(T value, const Spec &spec) + { + internal::handle_int_type_spec(spec.type(), int_writer(*this, value, spec)); + } + + enum + { + INF_SIZE = 3 + }; // This is an enum to workaround a bug in MSVC. + + struct inf_or_nan_writer + { + char sign; + const char *str; + + template + void operator()(It &&it) const + { + if (sign) + *it++ = sign; + it = std::copy_n(str, static_cast(INF_SIZE), it); + } + }; + + struct double_writer + { + size_t n; + char sign; + basic_memory_buffer &buffer; + + template + void operator()(It &&it) + { + if (sign) + { + *it++ = sign; + --n; + } + it = std::copy_n(buffer.begin(), n, it); + } + }; + + // Formats a floating-point number (double or long double). + template + void write_double(T value, const format_specs &spec); + template + void write_double_sprintf(T value, const format_specs &spec, internal::basic_buffer &buffer); + + template + struct str_writer + { + const Char *s; + std::size_t size; + + template + void operator()(It &&it) const + { + it = std::copy_n(s, size, it); + } + }; + + // Writes a formatted string. + template + void write_str(const Char *s, std::size_t size, const align_spec &spec) + { + write_padded(size, spec, str_writer{s, size}); + } + + template + void write_str(basic_string_view str, const format_specs &spec); + + // Appends floating-point length specifier to the format string. + // The second argument is only used for overload resolution. + void append_float_length(char_type *&format_ptr, long double) + { + *format_ptr++ = 'L'; + } + + template + void append_float_length(char_type *&, T) + { + } + + template + friend class internal::arg_formatter_base; + +public: + /** Constructs a ``basic_writer`` object. */ + explicit basic_writer(Range out) + : out_(out.begin()) + { + } + + void write(int value) + { + write_decimal(value); + } + void write(long value) + { + write_decimal(value); + } + void write(long long value) + { + write_decimal(value); + } + + void write(unsigned value) + { + write_decimal(value); + } + void write(unsigned long value) + { + write_decimal(value); + } + void write(unsigned long long value) + { + write_decimal(value); + } + + /** + \rst + Formats *value* and writes it to the buffer. + \endrst + */ + template + typename std::enable_if::value, void>::type write(T value, FormatSpec spec, FormatSpecs... specs) + { + format_specs s(spec, specs...); + s.align_ = ALIGN_RIGHT; + write_int(value, s); + } + + void write(double value) + { + write_double(value, format_specs()); + } + + /** + \rst + Formats *value* using the general format for floating-point numbers + (``'g'``) and writes it to the buffer. + \endrst + */ + void write(long double value) + { + write_double(value, format_specs()); + } + + /** Writes a character to the buffer. */ + void write(char value) + { + *reserve(1) = value; + } + + void write(wchar_t value) + { + internal::require_wchar(); + *reserve(1) = value; + } + + /** + \rst + Writes *value* to the buffer. + \endrst + */ + void write(string_view value) + { + auto &&it = reserve(value.size()); + it = std::copy(value.begin(), value.end(), it); + } + + void write(wstring_view value) + { + internal::require_wchar(); + auto &&it = reserve(value.size()); + it = std::uninitialized_copy(value.begin(), value.end(), it); + } + + template + void write(basic_string_view str, FormatSpecs... specs) + { + write_str(str, format_specs(specs...)); + } + + template + typename std::enable_if::value>::type write(const T *p) + { + format_specs specs; + specs.flags_ = HASH_FLAG; + specs.type_ = 'x'; + write_int(reinterpret_cast(p), specs); + } +}; + +template +template +void basic_writer::write_padded(std::size_t size, const align_spec &spec, F f) +{ + unsigned width = spec.width(); + if (width <= size) + return f(reserve(size)); + auto &&it = reserve(width); + char_type fill = static_cast(spec.fill()); + std::size_t padding = width - size; + if (spec.align() == ALIGN_RIGHT) + { + it = std::fill_n(it, padding, fill); + f(it); + } + else if (spec.align() == ALIGN_CENTER) + { + std::size_t left_padding = padding / 2; + it = std::fill_n(it, left_padding, fill); + f(it); + it = std::fill_n(it, padding - left_padding, fill); + } + else + { + f(it); + it = std::fill_n(it, padding, fill); + } +} + +template +template +void basic_writer::write_str(basic_string_view s, const format_specs &spec) +{ + const Char *data = s.data(); + std::size_t size = s.size(); + std::size_t precision = static_cast(spec.precision_); + if (spec.precision_ >= 0 && precision < size) + size = precision; + write_str(data, size, spec); +} + +template +struct float_spec_handler +{ + Char type; + bool upper; + + explicit float_spec_handler(Char t) + : type(t) + , upper(false) + { + } + + void on_general() + { + if (type == 'G') + upper = true; + else + type = 'g'; + } + + void on_exp() + { + if (type == 'E') + upper = true; + } + + void on_fixed() + { + if (type == 'F') + { + upper = true; +#if FMT_MSC_VER + // MSVC's printf doesn't support 'F'. + type = 'f'; +#endif + } + } + + void on_hex() + { + if (type == 'A') + upper = true; + } + + void on_error() + { + FMT_THROW(format_error("invalid type specifier")); + } +}; + +template +template +void basic_writer::write_double(T value, const format_specs &spec) +{ + // Check type. + float_spec_handler handler(spec.type()); + internal::handle_float_type_spec(spec.type(), handler); + + char sign = 0; + // Use isnegative instead of value < 0 because the latter is always + // false for NaN. + if (internal::fputil::isnegative(static_cast(value))) + { + sign = '-'; + value = -value; + } + else if (spec.flag(SIGN_FLAG)) + { + sign = spec.flag(PLUS_FLAG) ? '+' : ' '; + } + + struct write_inf_or_nan_t + { + basic_writer &writer; + format_specs spec; + char sign; + void operator()(const char *str) const + { + writer.write_padded(INF_SIZE + (sign ? 1 : 0), spec, inf_or_nan_writer{sign, str}); + } + } write_inf_or_nan = {*this, spec, sign}; + + // Format NaN and ininity ourselves because sprintf's output is not consistent + // across platforms. + if (internal::fputil::isnotanumber(value)) + return write_inf_or_nan(handler.upper ? "NAN" : "nan"); + if (internal::fputil::isinfinity(value)) + return write_inf_or_nan(handler.upper ? "INF" : "inf"); + + basic_memory_buffer buffer; + if (FMT_USE_GRISU && sizeof(T) <= sizeof(double) && std::numeric_limits::is_iec559) + { + internal::fp fp_value(static_cast(value)); + fp_value.normalize(); + // Find a cached power of 10 close to 1 / fp_value. + int dec_exp = 0; + const int min_exp = -60; + auto dec_pow = internal::get_cached_power(min_exp - (fp_value.e + internal::fp::significand_size), dec_exp); + internal::fp product = fp_value * dec_pow; + // Generate output. + internal::fp one(1ull << -product.e, product.e); + uint64_t hi = product.f >> -one.e; + uint64_t f = product.f & (one.f - 1); + typedef back_insert_range> range; + basic_writer w{range(buffer)}; + w.write(hi); + size_t digits = buffer.size(); + w.write('.'); + const unsigned max_digits = 18; + while (digits++ < max_digits) + { + f *= 10; + w.write(static_cast('0' + (f >> -one.e))); + f &= one.f - 1; + } + w.write('e'); + w.write(-dec_exp); + } + else + { + format_specs normalized_spec(spec); + normalized_spec.type_ = handler.type; + write_double_sprintf(value, normalized_spec, buffer); + } + size_t n = buffer.size(); + align_spec as = spec; + if (spec.align() == ALIGN_NUMERIC) + { + if (sign) + { + auto &&it = reserve(1); + *it++ = sign; + sign = 0; + if (as.width_) + --as.width_; + } + as.align_ = ALIGN_RIGHT; + } + else + { + if (spec.align() == ALIGN_DEFAULT) + as.align_ = ALIGN_RIGHT; + if (sign) + ++n; + } + write_padded(n, as, double_writer{n, sign, buffer}); +} + +template +template +void basic_writer::write_double_sprintf(T value, const format_specs &spec, internal::basic_buffer &buffer) +{ + // Buffer capacity must be non-zero, otherwise MSVC's vsnprintf_s will fail. + FMT_ASSERT(buffer.capacity() != 0, "empty buffer"); + + // Build format string. + enum + { + MAX_FORMAT_SIZE = 10 + }; // longest format: %#-*.*Lg + char_type format[MAX_FORMAT_SIZE]; + char_type *format_ptr = format; + *format_ptr++ = '%'; + if (spec.flag(HASH_FLAG)) + *format_ptr++ = '#'; + if (spec.precision() >= 0) + { + *format_ptr++ = '.'; + *format_ptr++ = '*'; + } + + append_float_length(format_ptr, value); + *format_ptr++ = spec.type(); + *format_ptr = '\0'; + + // Format using snprintf. + char_type *start = FMT_NULL; + for (;;) + { + std::size_t buffer_size = buffer.capacity(); + start = &buffer[0]; + int result = internal::char_traits::format_float(start, buffer_size, format, spec.precision(), value); + if (result >= 0) + { + unsigned n = internal::to_unsigned(result); + if (n < buffer.capacity()) + { + buffer.resize(n); + break; // The buffer is large enough - continue with formatting. + } + buffer.reserve(n + 1); + } + else + { + // If result is negative we ask to increase the capacity by at least 1, + // but as std::vector, the buffer grows exponentially. + buffer.reserve(buffer.capacity() + 1); + } + } +} + +// Reports a system error without throwing an exception. +// Can be used to report errors from destructors. +FMT_API void report_system_error(int error_code, string_view message) FMT_NOEXCEPT; + +#if FMT_USE_WINDOWS_H + +/** A Windows error. */ +class windows_error : public system_error +{ +private: + FMT_API void init(int error_code, string_view format_str, format_args args); + +public: + /** + \rst + Constructs a :class:`fmt::windows_error` object with the description + of the form + + .. parsed-literal:: + **: ** + + where ** is the formatted message and ** is the + system message corresponding to the error code. + *error_code* is a Windows error code as given by ``GetLastError``. + If *error_code* is not a valid error code such as -1, the system message + will look like "error -1". + + **Example**:: + + // This throws a windows_error with the description + // cannot open file 'madeup': The system cannot find the file specified. + // or similar (system message may vary). + const char *filename = "madeup"; + LPOFSTRUCT of = LPOFSTRUCT(); + HFILE file = OpenFile(filename, &of, OF_READ); + if (file == HFILE_ERROR) { + throw fmt::windows_error(GetLastError(), + "cannot open file '{}'", filename); + } + \endrst + */ + template + windows_error(int error_code, string_view message, const Args &... args) + { + init(error_code, message, make_format_args(args...)); + } +}; + +// Reports a Windows error without throwing an exception. +// Can be used to report errors from destructors. +FMT_API void report_windows_error(int error_code, string_view message) FMT_NOEXCEPT; + +#endif + +/** Fast integer formatter. */ +class format_int +{ +private: + // Buffer should be large enough to hold all digits (digits10 + 1), + // a sign and a null character. + enum + { + BUFFER_SIZE = std::numeric_limits::digits10 + 3 + }; + mutable char buffer_[BUFFER_SIZE]; + char *str_; + + // Formats value in reverse and returns a pointer to the beginning. + char *format_decimal(unsigned long long value) + { + char *ptr = buffer_ + BUFFER_SIZE - 1; + while (value >= 100) + { + // Integer division is slow so do it for a group of two digits instead + // of for every digit. The idea comes from the talk by Alexandrescu + // "Three Optimization Tips for C++". See speed-test for a comparison. + unsigned index = static_cast((value % 100) * 2); + value /= 100; + *--ptr = internal::data::DIGITS[index + 1]; + *--ptr = internal::data::DIGITS[index]; + } + if (value < 10) + { + *--ptr = static_cast('0' + value); + return ptr; + } + unsigned index = static_cast(value * 2); + *--ptr = internal::data::DIGITS[index + 1]; + *--ptr = internal::data::DIGITS[index]; + return ptr; + } + + void format_signed(long long value) + { + unsigned long long abs_value = static_cast(value); + bool negative = value < 0; + if (negative) + abs_value = 0 - abs_value; + str_ = format_decimal(abs_value); + if (negative) + *--str_ = '-'; + } + +public: + explicit format_int(int value) + { + format_signed(value); + } + explicit format_int(long value) + { + format_signed(value); + } + explicit format_int(long long value) + { + format_signed(value); + } + explicit format_int(unsigned value) + : str_(format_decimal(value)) + { + } + explicit format_int(unsigned long value) + : str_(format_decimal(value)) + { + } + explicit format_int(unsigned long long value) + : str_(format_decimal(value)) + { + } + + /** Returns the number of characters written to the output buffer. */ + std::size_t size() const + { + return internal::to_unsigned(buffer_ - str_ + BUFFER_SIZE - 1); + } + + /** + Returns a pointer to the output buffer content. No terminating null + character is appended. + */ + const char *data() const + { + return str_; + } + + /** + Returns a pointer to the output buffer content with terminating null + character appended. + */ + const char *c_str() const + { + buffer_[BUFFER_SIZE - 1] = '\0'; + return str_; + } + + /** + \rst + Returns the content of the output buffer as an ``std::string``. + \endrst + */ + std::string str() const + { + return std::string(str_, size()); + } +}; + +// Formats a decimal integer value writing into buffer and returns +// a pointer to the end of the formatted string. This function doesn't +// write a terminating null character. +template +inline void format_decimal(char *&buffer, T value) +{ + typedef typename internal::int_traits::main_type main_type; + main_type abs_value = static_cast(value); + if (internal::is_negative(value)) + { + *buffer++ = '-'; + abs_value = 0 - abs_value; + } + if (abs_value < 100) + { + if (abs_value < 10) + { + *buffer++ = static_cast('0' + abs_value); + return; + } + unsigned index = static_cast(abs_value * 2); + *buffer++ = internal::data::DIGITS[index]; + *buffer++ = internal::data::DIGITS[index + 1]; + return; + } + unsigned num_digits = internal::count_digits(abs_value); + internal::format_decimal(buffer, abs_value, num_digits); + buffer += num_digits; +} + +// Formatter of objects of type T. +template +struct formatter::type, T>::value>::type> +{ + + // Parses format specifiers stopping either at the end of the range or at the + // terminating '}'. + template + FMT_CONSTEXPR typename ParseContext::iterator parse(ParseContext &ctx) + { + auto it = internal::null_terminating_iterator(ctx); + typedef internal::dynamic_specs_handler handler_type; + auto type = internal::get_type::type, T>::value; + internal::specs_checker handler(handler_type(specs_, ctx), type); + it = parse_format_specs(it, handler); + auto type_spec = specs_.type(); + auto eh = ctx.error_handler(); + switch (type) + { + case internal::none_type: + case internal::named_arg_type: + FMT_ASSERT(false, "invalid argument type"); + break; + case internal::int_type: + case internal::uint_type: + case internal::long_long_type: + case internal::ulong_long_type: + case internal::bool_type: + handle_int_type_spec(type_spec, internal::int_type_checker(eh)); + break; + case internal::char_type: + handle_char_specs(specs_, internal::char_specs_checker(type_spec, eh)); + break; + case internal::double_type: + case internal::long_double_type: + handle_float_type_spec(type_spec, internal::float_type_checker(eh)); + break; + case internal::cstring_type: + internal::handle_cstring_type_spec(type_spec, internal::cstring_type_checker(eh)); + break; + case internal::string_type: + internal::check_string_type_spec(type_spec, eh); + break; + case internal::pointer_type: + internal::check_pointer_type_spec(type_spec, eh); + break; + case internal::custom_type: + // Custom format specifiers should be checked in parse functions of + // formatter specializations. + break; + } + return pointer_from(it); + } + + template + auto format(const T &val, FormatContext &ctx) -> decltype(ctx.out()) + { + internal::handle_dynamic_spec(specs_.width_, specs_.width_ref, ctx); + internal::handle_dynamic_spec(specs_.precision_, specs_.precision_ref, ctx); + typedef output_range range_type; + return visit(arg_formatter(ctx, specs_), internal::make_arg(val)); + } + +private: + internal::dynamic_format_specs specs_; +}; + +template +struct formatter::value>::type> : public formatter +{ + template + auto parse(ParseContext &ctx) -> decltype(ctx.begin()) + { + return ctx.begin(); + } +}; + +// A formatter for types known only at run time such as variant alternatives. +// +// Usage: +// typedef std::variant variant; +// template <> +// struct formatter: dynamic_formatter<> { +// void format(buffer &buf, const variant &v, context &ctx) { +// visit([&](const auto &val) { format(buf, val, ctx); }, v); +// } +// }; +template +class dynamic_formatter +{ +private: + struct null_handler : internal::error_handler + { + void on_align(alignment) {} + void on_plus() {} + void on_minus() {} + void on_space() {} + void on_hash() {} + }; + +public: + template + auto parse(ParseContext &ctx) -> decltype(ctx.begin()) + { + auto it = internal::null_terminating_iterator(ctx); + // Checks are deferred to formatting time when the argument type is known. + internal::dynamic_specs_handler handler(specs_, ctx); + it = parse_format_specs(it, handler); + return pointer_from(it); + } + + template + auto format(const T &val, FormatContext &ctx) -> decltype(ctx.out()) + { + handle_specs(ctx); + internal::specs_checker checker(null_handler(), internal::get_type::value); + checker.on_align(specs_.align()); + if (specs_.flags_ == 0) + { + // Do nothing. + } + else if (specs_.flag(SIGN_FLAG)) + { + if (specs_.flag(PLUS_FLAG)) + checker.on_plus(); + else + checker.on_space(); + } + else if (specs_.flag(MINUS_FLAG)) + { + checker.on_minus(); + } + else if (specs_.flag(HASH_FLAG)) + { + checker.on_hash(); + } + if (specs_.precision_ != -1) + checker.end_precision(); + typedef output_range range; + visit(arg_formatter(ctx, specs_), internal::make_arg(val)); + return ctx.out(); + } + +private: + template + void handle_specs(Context &ctx) + { + internal::handle_dynamic_spec(specs_.width_, specs_.width_ref, ctx); + internal::handle_dynamic_spec(specs_.precision_, specs_.precision_ref, ctx); + } + + internal::dynamic_format_specs specs_; +}; + +template +typename basic_format_context::format_arg basic_format_context::get_arg(basic_string_view name) +{ + map_.init(this->args()); + format_arg arg = map_.find(name); + if (arg.type() == internal::none_type) + this->on_error("argument not found"); + return arg; +} + +template +struct format_handler : internal::error_handler +{ + typedef internal::null_terminating_iterator iterator; + typedef typename ArgFormatter::range range; + + format_handler(range r, basic_string_view str, basic_format_args format_args) + : context(r.begin(), str, format_args) + { + } + + void on_text(iterator begin, iterator end) + { + auto size = internal::to_unsigned(end - begin); + auto out = context.out(); + auto &&it = internal::reserve(out, size); + it = std::copy_n(begin, size, it); + context.advance_to(out); + } + + void on_arg_id() + { + arg = context.next_arg(); + } + void on_arg_id(unsigned id) + { + context.parse_context().check_arg_id(id); + arg = context.get_arg(id); + } + void on_arg_id(basic_string_view id) + { + arg = context.get_arg(id); + } + + void on_replacement_field(iterator it) + { + context.parse_context().advance_to(pointer_from(it)); + if (visit(internal::custom_formatter(context), arg)) + return; + basic_format_specs specs; + context.advance_to(visit(ArgFormatter(context, specs), arg)); + } + + iterator on_format_specs(iterator it) + { + auto &parse_ctx = context.parse_context(); + parse_ctx.advance_to(pointer_from(it)); + if (visit(internal::custom_formatter(context), arg)) + return iterator(parse_ctx); + basic_format_specs specs; + using internal::specs_handler; + internal::specs_checker> handler(specs_handler(specs, context), arg.type()); + it = parse_format_specs(it, handler); + if (*it != '}') + on_error("missing '}' in format string"); + parse_ctx.advance_to(pointer_from(it)); + context.advance_to(visit(ArgFormatter(context, specs), arg)); + return it; + } + + Context context; + basic_format_arg arg; +}; + +/** Formats arguments and writes the output to the range. */ +template +typename Context::iterator vformat_to(typename ArgFormatter::range out, basic_string_view format_str, basic_format_args args) +{ + typedef internal::null_terminating_iterator iterator; + format_handler h(out, format_str, args); + parse_format_string(iterator(format_str.begin(), format_str.end()), h); + return h.context.out(); +} + +// Casts ``p`` to ``const void*`` for pointer formatting. +// Example: +// auto s = format("{}", ptr(p)); +template +inline const void *ptr(const T *p) +{ + return p; +} + +template +struct arg_join +{ + It begin; + It end; + basic_string_view sep; + + arg_join(It begin, It end, basic_string_view sep) + : begin(begin) + , end(end) + , sep(sep) + { + } +}; + +template +struct formatter, Char> : formatter::value_type, Char> +{ + template + auto format(const arg_join &value, FormatContext &ctx) -> decltype(ctx.out()) + { + typedef formatter::value_type, Char> base; + auto it = value.begin; + auto out = ctx.out(); + if (it != value.end) + { + out = base::format(*it++, ctx); + while (it != value.end) + { + out = std::copy(value.sep.begin(), value.sep.end(), out); + ctx.advance_to(out); + out = base::format(*it++, ctx); + } + } + return out; + } +}; + +template +arg_join join(It begin, It end, string_view sep) +{ + return arg_join(begin, end, sep); +} + +template +arg_join join(It begin, It end, wstring_view sep) +{ + return arg_join(begin, end, sep); +} + +// The following causes ICE in gcc 4.4. +#if FMT_USE_TRAILING_RETURN && (!FMT_GCC_VERSION || FMT_GCC_VERSION >= 405) +template +auto join(const Range &range, string_view sep) -> arg_join +{ + return join(internal::begin(range), internal::end(range), sep); +} + +template +auto join(const Range &range, wstring_view sep) -> arg_join +{ + return join(internal::begin(range), internal::end(range), sep); +} +#endif + +/** + \rst + Converts *value* to ``std::string`` using the default format for type *T*. + + **Example**:: + + #include + + std::string answer = fmt::to_string(42); + \endrst + */ +template +std::string to_string(const T &value) +{ + std::string str; + internal::container_buffer buf(str); + writer(buf).write(value); + return str; +} + +/** + Converts *value* to ``std::wstring`` using the default format for type *T*. + */ +template +std::wstring to_wstring(const T &value) +{ + std::wstring str; + internal::container_buffer buf(str); + wwriter(buf).write(value); + return str; +} + +template +std::basic_string to_string(const basic_memory_buffer &buf) +{ + return std::basic_string(buf.data(), buf.size()); +} + +inline format_context::iterator vformat_to(internal::buffer &buf, string_view format_str, format_args args) +{ + typedef back_insert_range range; + return vformat_to>(buf, format_str, args); +} + +inline wformat_context::iterator vformat_to(internal::wbuffer &buf, wstring_view format_str, wformat_args args) +{ + typedef back_insert_range range; + return vformat_to>(buf, format_str, args); +} + +template +inline format_context::iterator format_to(basic_memory_buffer &buf, string_view format_str, const Args &... args) +{ + return vformat_to(buf, format_str, make_format_args(args...)); +} + +template +inline wformat_context::iterator format_to(basic_memory_buffer &buf, wstring_view format_str, const Args &... args) +{ + return vformat_to(buf, format_str, make_format_args(args...)); +} + +template +// using format_context_t = basic_format_context; +struct format_context_t +{ + typedef basic_format_context type; +}; + +template +// using format_args_t = basic_format_args>; +struct format_args_t +{ + typedef basic_format_args::type> type; +}; + +template +inline OutputIt vformat_to(OutputIt out, string_view format_str, typename format_args_t::type args) +{ + typedef output_range range; + return vformat_to>(range(out), format_str, args); +} +template +inline OutputIt vformat_to(OutputIt out, wstring_view format_str, typename format_args_t::type args) +{ + typedef output_range range; + return vformat_to>(range(out), format_str, args); +} + +/** + \rst + Formats arguments, writes the result to the output iterator ``out`` and returns + the iterator past the end of the output range. + + **Example**:: + + std::vector out; + fmt::format_to(std::back_inserter(out), "{}", 42); + \endrst + */ +template +inline OutputIt format_to(OutputIt out, string_view format_str, const Args &... args) +{ + return vformat_to(out, format_str, make_format_args::type>(args...)); +} + +template +inline typename std::enable_if::value, std::back_insert_iterator>::type format_to( + std::back_insert_iterator out, string_view format_str, const Args &... args) +{ + return vformat_to(out, format_str, make_format_args(args...)); +} + +template +inline typename std::enable_if::value, std::back_insert_iterator>::type format_to( + std::back_insert_iterator out, wstring_view format_str, const Args &... args) +{ + return vformat_to(out, format_str, make_format_args(args...)); +} + +template +struct format_to_n_result +{ + /** Iterator past the end of the output range. */ + OutputIt out; + /** Total (not truncated) output size. */ + std::size_t size; +}; + +template +using format_to_n_context = typename fmt::format_context_t>::type; + +template +using format_to_n_args = fmt::basic_format_args>; + +template +inline format_arg_store, Args...> make_format_to_n_args(const Args &... args) +{ + return format_arg_store, Args...>(args...); +} + +template +inline format_to_n_result vformat_to_n(OutputIt out, std::size_t n, string_view format_str, format_to_n_args args) +{ + typedef internal::truncating_iterator It; + auto it = vformat_to(It(out, n), format_str, args); + return {it.base(), it.count()}; +} + +/** + \rst + Formats arguments, writes up to ``n`` characters of the result to the output + iterator ``out`` and returns the total output size and the iterator past the + end + of the output range. + \endrst + */ +template +inline format_to_n_result format_to_n(OutputIt out, std::size_t n, string_view format_str, const Args &... args) +{ + return vformat_to_n(out, n, format_str, make_format_to_n_args(args...)); +} +template +inline format_to_n_result format_to_n(OutputIt out, std::size_t n, wstring_view format_str, const Args &... args) +{ + typedef internal::truncating_iterator It; + auto it = vformat_to(It(out, n), format_str, make_format_args::type>(args...)); + return {it.base(), it.count()}; +} + +inline std::string vformat(string_view format_str, format_args args) +{ + memory_buffer buffer; + vformat_to(buffer, format_str, args); + return fmt::to_string(buffer); +} + +inline std::wstring vformat(wstring_view format_str, wformat_args args) +{ + wmemory_buffer buffer; + vformat_to(buffer, format_str, args); + return to_string(buffer); +} + +template +inline typename std::enable_if::value, std::string>::type format(String format_str, const Args &... args) +{ + internal::check_format_string(format_str); + return vformat(format_str.data(), make_format_args(args...)); +} + +template +inline typename std::enable_if::value>::type print(String format_str, const Args &... args) +{ + internal::check_format_string(format_str); + return vprint(format_str.data(), make_format_args(args...)); +} + +/** + Returns the number of characters in the output of + ``format(format_str, args...)``. + */ +template +inline std::size_t formatted_size(string_view format_str, const Args &... args) +{ + auto it = format_to(internal::counting_iterator(), format_str, args...); + return it.count(); +} + +// Experimental color support. +#ifdef FMT_EXTENDED_COLORS +enum class color : uint32_t +{ + alice_blue = 0xF0F8FF, // rgb(240,248,255) + antique_white = 0xFAEBD7, // rgb(250,235,215) + aqua = 0x00FFFF, // rgb(0,255,255) + aquamarine = 0x7FFFD4, // rgb(127,255,212) + azure = 0xF0FFFF, // rgb(240,255,255) + beige = 0xF5F5DC, // rgb(245,245,220) + bisque = 0xFFE4C4, // rgb(255,228,196) + black = 0x000000, // rgb(0,0,0) + blanched_almond = 0xFFEBCD, // rgb(255,235,205) + blue = 0x0000FF, // rgb(0,0,255) + blue_violet = 0x8A2BE2, // rgb(138,43,226) + brown = 0xA52A2A, // rgb(165,42,42) + burly_wood = 0xDEB887, // rgb(222,184,135) + cadet_blue = 0x5F9EA0, // rgb(95,158,160) + chartreuse = 0x7FFF00, // rgb(127,255,0) + chocolate = 0xD2691E, // rgb(210,105,30) + coral = 0xFF7F50, // rgb(255,127,80) + cornflower_blue = 0x6495ED, // rgb(100,149,237) + cornsilk = 0xFFF8DC, // rgb(255,248,220) + crimson = 0xDC143C, // rgb(220,20,60) + cyan = 0x00FFFF, // rgb(0,255,255) + dark_blue = 0x00008B, // rgb(0,0,139) + dark_cyan = 0x008B8B, // rgb(0,139,139) + dark_golden_rod = 0xB8860B, // rgb(184,134,11) + dark_gray = 0xA9A9A9, // rgb(169,169,169) + dark_green = 0x006400, // rgb(0,100,0) + dark_khaki = 0xBDB76B, // rgb(189,183,107) + dark_magenta = 0x8B008B, // rgb(139,0,139) + dark_olive_green = 0x556B2F, // rgb(85,107,47) + dark_orange = 0xFF8C00, // rgb(255,140,0) + dark_orchid = 0x9932CC, // rgb(153,50,204) + dark_red = 0x8B0000, // rgb(139,0,0) + dark_salmon = 0xE9967A, // rgb(233,150,122) + dark_sea_green = 0x8FBC8F, // rgb(143,188,143) + dark_slate_blue = 0x483D8B, // rgb(72,61,139) + dark_slate_gray = 0x2F4F4F, // rgb(47,79,79) + dark_turquoise = 0x00CED1, // rgb(0,206,209) + dark_violet = 0x9400D3, // rgb(148,0,211) + deep_pink = 0xFF1493, // rgb(255,20,147) + deep_sky_blue = 0x00BFFF, // rgb(0,191,255) + dim_gray = 0x696969, // rgb(105,105,105) + dodger_blue = 0x1E90FF, // rgb(30,144,255) + fire_brick = 0xB22222, // rgb(178,34,34) + floral_white = 0xFFFAF0, // rgb(255,250,240) + forest_green = 0x228B22, // rgb(34,139,34) + fuchsia = 0xFF00FF, // rgb(255,0,255) + gainsboro = 0xDCDCDC, // rgb(220,220,220) + ghost_white = 0xF8F8FF, // rgb(248,248,255) + gold = 0xFFD700, // rgb(255,215,0) + golden_rod = 0xDAA520, // rgb(218,165,32) + gray = 0x808080, // rgb(128,128,128) + green = 0x008000, // rgb(0,128,0) + green_yellow = 0xADFF2F, // rgb(173,255,47) + honey_dew = 0xF0FFF0, // rgb(240,255,240) + hot_pink = 0xFF69B4, // rgb(255,105,180) + indian_red = 0xCD5C5C, // rgb(205,92,92) + indigo = 0x4B0082, // rgb(75,0,130) + ivory = 0xFFFFF0, // rgb(255,255,240) + khaki = 0xF0E68C, // rgb(240,230,140) + lavender = 0xE6E6FA, // rgb(230,230,250) + lavender_blush = 0xFFF0F5, // rgb(255,240,245) + lawn_green = 0x7CFC00, // rgb(124,252,0) + lemon_chiffon = 0xFFFACD, // rgb(255,250,205) + light_blue = 0xADD8E6, // rgb(173,216,230) + light_coral = 0xF08080, // rgb(240,128,128) + light_cyan = 0xE0FFFF, // rgb(224,255,255) + light_golden_rod_yellow = 0xFAFAD2, // rgb(250,250,210) + light_gray = 0xD3D3D3, // rgb(211,211,211) + light_green = 0x90EE90, // rgb(144,238,144) + light_pink = 0xFFB6C1, // rgb(255,182,193) + light_salmon = 0xFFA07A, // rgb(255,160,122) + light_sea_green = 0x20B2AA, // rgb(32,178,170) + light_sky_blue = 0x87CEFA, // rgb(135,206,250) + light_slate_gray = 0x778899, // rgb(119,136,153) + light_steel_blue = 0xB0C4DE, // rgb(176,196,222) + light_yellow = 0xFFFFE0, // rgb(255,255,224) + lime = 0x00FF00, // rgb(0,255,0) + lime_green = 0x32CD32, // rgb(50,205,50) + linen = 0xFAF0E6, // rgb(250,240,230) + magenta = 0xFF00FF, // rgb(255,0,255) + maroon = 0x800000, // rgb(128,0,0) + medium_aquamarine = 0x66CDAA, // rgb(102,205,170) + medium_blue = 0x0000CD, // rgb(0,0,205) + medium_orchid = 0xBA55D3, // rgb(186,85,211) + medium_purple = 0x9370DB, // rgb(147,112,219) + medium_sea_green = 0x3CB371, // rgb(60,179,113) + medium_slate_blue = 0x7B68EE, // rgb(123,104,238) + medium_spring_green = 0x00FA9A, // rgb(0,250,154) + medium_turquoise = 0x48D1CC, // rgb(72,209,204) + medium_violet_red = 0xC71585, // rgb(199,21,133) + midnight_blue = 0x191970, // rgb(25,25,112) + mint_cream = 0xF5FFFA, // rgb(245,255,250) + misty_rose = 0xFFE4E1, // rgb(255,228,225) + moccasin = 0xFFE4B5, // rgb(255,228,181) + navajo_white = 0xFFDEAD, // rgb(255,222,173) + navy = 0x000080, // rgb(0,0,128) + old_lace = 0xFDF5E6, // rgb(253,245,230) + olive = 0x808000, // rgb(128,128,0) + olive_drab = 0x6B8E23, // rgb(107,142,35) + orange = 0xFFA500, // rgb(255,165,0) + orange_red = 0xFF4500, // rgb(255,69,0) + orchid = 0xDA70D6, // rgb(218,112,214) + pale_golden_rod = 0xEEE8AA, // rgb(238,232,170) + pale_green = 0x98FB98, // rgb(152,251,152) + pale_turquoise = 0xAFEEEE, // rgb(175,238,238) + pale_violet_red = 0xDB7093, // rgb(219,112,147) + papaya_whip = 0xFFEFD5, // rgb(255,239,213) + peach_puff = 0xFFDAB9, // rgb(255,218,185) + peru = 0xCD853F, // rgb(205,133,63) + pink = 0xFFC0CB, // rgb(255,192,203) + plum = 0xDDA0DD, // rgb(221,160,221) + powder_blue = 0xB0E0E6, // rgb(176,224,230) + purple = 0x800080, // rgb(128,0,128) + rebecca_purple = 0x663399, // rgb(102,51,153) + red = 0xFF0000, // rgb(255,0,0) + rosy_brown = 0xBC8F8F, // rgb(188,143,143) + royal_blue = 0x4169E1, // rgb(65,105,225) + saddle_brown = 0x8B4513, // rgb(139,69,19) + salmon = 0xFA8072, // rgb(250,128,114) + sandy_brown = 0xF4A460, // rgb(244,164,96) + sea_green = 0x2E8B57, // rgb(46,139,87) + sea_shell = 0xFFF5EE, // rgb(255,245,238) + sienna = 0xA0522D, // rgb(160,82,45) + silver = 0xC0C0C0, // rgb(192,192,192) + sky_blue = 0x87CEEB, // rgb(135,206,235) + slate_blue = 0x6A5ACD, // rgb(106,90,205) + slate_gray = 0x708090, // rgb(112,128,144) + snow = 0xFFFAFA, // rgb(255,250,250) + spring_green = 0x00FF7F, // rgb(0,255,127) + steel_blue = 0x4682B4, // rgb(70,130,180) + tan = 0xD2B48C, // rgb(210,180,140) + teal = 0x008080, // rgb(0,128,128) + thistle = 0xD8BFD8, // rgb(216,191,216) + tomato = 0xFF6347, // rgb(255,99,71) + turquoise = 0x40E0D0, // rgb(64,224,208) + violet = 0xEE82EE, // rgb(238,130,238) + wheat = 0xF5DEB3, // rgb(245,222,179) + white = 0xFFFFFF, // rgb(255,255,255) + white_smoke = 0xF5F5F5, // rgb(245,245,245) + yellow = 0xFFFF00, // rgb(255,255,0) + yellow_green = 0x9ACD32, // rgb(154,205,50) +}; // enum class color + +// rgb is a struct for red, green and blue colors. +// We use rgb as name because some editors will show it as color direct in the +// editor. +struct rgb +{ + FMT_CONSTEXPR_DECL rgb() + : r(0) + , g(0) + , b(0) + { + } + FMT_CONSTEXPR_DECL rgb(uint8_t r_, uint8_t g_, uint8_t b_) + : r(r_) + , g(g_) + , b(b_) + { + } + FMT_CONSTEXPR_DECL rgb(uint32_t hex) + : r((hex >> 16) & 0xFF) + , g((hex >> 8) & 0xFF) + , b((hex)&0xFF) + { + } + FMT_CONSTEXPR_DECL rgb(color hex) + : r((uint32_t(hex) >> 16) & 0xFF) + , g((uint32_t(hex) >> 8) & 0xFF) + , b(uint32_t(hex) & 0xFF) + { + } + uint8_t r; + uint8_t g; + uint8_t b; +}; + +void vprint_rgb(rgb fd, string_view format, format_args args); +void vprint_rgb(rgb fd, rgb bg, string_view format, format_args args); + +/** + Formats a string and prints it to stdout using ANSI escape sequences to + specify foreground color 'fd'. + Example: + fmt::print(fmt::color::red, "Elapsed time: {0:.2f} seconds", 1.23); + */ +template +inline void print(rgb fd, string_view format_str, const Args &... args) +{ + vprint_rgb(fd, format_str, make_format_args(args...)); +} + +/** + Formats a string and prints it to stdout using ANSI escape sequences to + specify foreground color 'fd' and background color 'bg'. + Example: + fmt::print(fmt::color::red, fmt::color::black, "Elapsed time: {0:.2f} + seconds", 1.23); + */ +template +inline void print(rgb fd, rgb bg, string_view format_str, const Args &... args) +{ + vprint_rgb(fd, bg, format_str, make_format_args(args...)); +} +#endif // FMT_EXTENDED_COLORS + +#if FMT_USE_USER_DEFINED_LITERALS +namespace internal { + +#if FMT_UDL_TEMPLATE +template +class udl_formatter +{ +public: + template + std::basic_string operator()(const Args &... args) const + { + FMT_CONSTEXPR_DECL Char s[] = {CHARS..., '\0'}; + FMT_CONSTEXPR_DECL bool invalid_format = + check_format_string(basic_string_view(s, sizeof...(CHARS))); + (void)invalid_format; + return format(s, args...); + } +}; +#else +template +struct udl_formatter +{ + const Char *str; + + template + auto operator()(Args &&... args) const -> decltype(format(str, std::forward(args)...)) + { + return format(str, std::forward(args)...); + } +}; +#endif // FMT_UDL_TEMPLATE + +template +struct udl_arg +{ + const Char *str; + + template + named_arg operator=(T &&value) const + { + return {str, std::forward(value)}; + } +}; + +} // namespace internal + +inline namespace literals { + +#if FMT_UDL_TEMPLATE +template +FMT_CONSTEXPR internal::udl_formatter operator""_format() +{ + return {}; +} +#else +/** + \rst + User-defined literal equivalent of :func:`fmt::format`. + + **Example**:: + + using namespace fmt::literals; + std::string message = "The answer is {}"_format(42); + \endrst + */ +inline internal::udl_formatter operator"" _format(const char *s, std::size_t) +{ + return {s}; +} +inline internal::udl_formatter operator"" _format(const wchar_t *s, std::size_t) +{ + return {s}; +} +#endif // FMT_UDL_TEMPLATE + +/** + \rst + User-defined literal equivalent of :func:`fmt::arg`. + + **Example**:: + + using namespace fmt::literals; + fmt::print("Elapsed time: {s:.2f} seconds", "s"_a=1.23); + \endrst + */ +inline internal::udl_arg operator"" _a(const char *s, std::size_t) +{ + return {s}; +} +inline internal::udl_arg operator"" _a(const wchar_t *s, std::size_t) +{ + return {s}; +} +} // namespace literals +#endif // FMT_USE_USER_DEFINED_LITERALS +FMT_END_NAMESPACE + +#define FMT_STRING(s) \ + [] { \ + struct S : fmt::format_string \ + { \ + static FMT_CONSTEXPR decltype(s) data() \ + { \ + return s; \ + } \ + static FMT_CONSTEXPR size_t size() \ + { \ + return sizeof(s); \ + } \ + }; \ + return S{}; \ + }() + +#ifndef FMT_NO_FMT_STRING_ALIAS +/** + \rst + Constructs a compile-time format string. + + **Example**:: + + #include + // A compile-time error because 'd' is an invalid specifier for strings. + std::string s = format(fmt("{:d}"), "foo"); + \endrst + */ +#define fmt(s) FMT_STRING(s) +#endif + +#ifdef FMT_HEADER_ONLY +#define FMT_FUNC inline +#include "format-inl.h" +#else +#define FMT_FUNC +#endif + +// Restore warnings. +#if FMT_GCC_VERSION >= 406 || FMT_CLANG_VERSION +#pragma GCC diagnostic pop +#endif + +#endif // FMT_FORMAT_H_ diff --git a/include/spdlog/fmt/bundled/locale.h b/include/spdlog/fmt/bundled/locale.h new file mode 100644 index 0000000..0fa78c8 --- /dev/null +++ b/include/spdlog/fmt/bundled/locale.h @@ -0,0 +1,27 @@ +// Formatting library for C++ - locale support +// +// Copyright (c) 2012 - 2016, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#include "format.h" +#include + +namespace fmt { +class locale +{ +private: + std::locale locale_; + +public: + explicit locale(std::locale loc = std::locale()) + : locale_(loc) + { + } + std::locale get() + { + return locale_; + } +}; +} // namespace fmt diff --git a/include/spdlog/fmt/bundled/ostream.h b/include/spdlog/fmt/bundled/ostream.h new file mode 100644 index 0000000..439ded6 --- /dev/null +++ b/include/spdlog/fmt/bundled/ostream.h @@ -0,0 +1,173 @@ +// Formatting library for C++ - std::ostream support +// +// Copyright (c) 2012 - 2016, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_OSTREAM_H_ +#define FMT_OSTREAM_H_ + +#include "format.h" +#include + +FMT_BEGIN_NAMESPACE +namespace internal { + +template +class formatbuf : public std::basic_streambuf +{ +private: + typedef typename std::basic_streambuf::int_type int_type; + typedef typename std::basic_streambuf::traits_type traits_type; + + basic_buffer &buffer_; + +public: + formatbuf(basic_buffer &buffer) + : buffer_(buffer) + { + } + +protected: + // The put-area is actually always empty. This makes the implementation + // simpler and has the advantage that the streambuf and the buffer are always + // in sync and sputc never writes into uninitialized memory. The obvious + // disadvantage is that each call to sputc always results in a (virtual) call + // to overflow. There is no disadvantage here for sputn since this always + // results in a call to xsputn. + + int_type overflow(int_type ch = traits_type::eof()) FMT_OVERRIDE + { + if (!traits_type::eq_int_type(ch, traits_type::eof())) + buffer_.push_back(static_cast(ch)); + return ch; + } + + std::streamsize xsputn(const Char *s, std::streamsize count) FMT_OVERRIDE + { + buffer_.append(s, s + count); + return count; + } +}; + +template +struct test_stream : std::basic_ostream +{ +private: + struct null; + // Hide all operator<< from std::basic_ostream. + void operator<<(null); +}; + +// Checks if T has a user-defined operator<< (e.g. not a member of +// std::ostream). +template +class is_streamable +{ +private: + template + static decltype(internal::declval &>() << internal::declval(), std::true_type()) test(int); + + template + static std::false_type test(...); + + typedef decltype(test(0)) result; + +public: + // std::string operator<< is not considered user-defined because we handle + // strings + // specially. + static const bool value = result::value && !std::is_same::value; +}; + +// Disable conversion to int if T has an overloaded operator<< which is a free +// function (not a member of std::ostream). +template +class convert_to_int +{ +public: + static const bool value = convert_to_int::value && !is_streamable::value; +}; + +// Write the content of buf to os. +template +void write(std::basic_ostream &os, basic_buffer &buf) +{ + const Char *data = buf.data(); + typedef std::make_unsigned::type UnsignedStreamSize; + UnsignedStreamSize size = buf.size(); + UnsignedStreamSize max_size = internal::to_unsigned((std::numeric_limits::max)()); + do + { + UnsignedStreamSize n = size <= max_size ? size : max_size; + os.write(data, static_cast(n)); + data += n; + size -= n; + } while (size != 0); +} + +template +void format_value(basic_buffer &buffer, const T &value) +{ + internal::formatbuf format_buf(buffer); + std::basic_ostream output(&format_buf); + output.exceptions(std::ios_base::failbit | std::ios_base::badbit); + output << value; + buffer.resize(buffer.size()); +} + +// Disable builtin formatting of enums and use operator<< instead. +template +struct format_enum::value>::type> : std::false_type +{ +}; +} // namespace internal + +// Formats an object of type T that has an overloaded ostream operator<<. +template +struct formatter::value>::type> : formatter, Char> +{ + + template + auto format(const T &value, Context &ctx) -> decltype(ctx.out()) + { + basic_memory_buffer buffer; + internal::format_value(buffer, value); + basic_string_view str(buffer.data(), buffer.size()); + formatter, Char>::format(str, ctx); + return ctx.out(); + } +}; + +template +inline void vprint( + std::basic_ostream &os, basic_string_view format_str, basic_format_args::type> args) +{ + basic_memory_buffer buffer; + vformat_to(buffer, format_str, args); + internal::write(os, buffer); +} +/** + \rst + Prints formatted data to the stream *os*. + + **Example**:: + + fmt::print(cerr, "Don't {}!", "panic"); + \endrst + */ +template +inline void print(std::ostream &os, string_view format_str, const Args &... args) +{ + vprint(os, format_str, make_format_args(args...)); +} + +template +inline void print(std::wostream &os, wstring_view format_str, const Args &... args) +{ + vprint(os, format_str, make_format_args(args...)); +} +FMT_END_NAMESPACE + +#endif // FMT_OSTREAM_H_ diff --git a/include/spdlog/fmt/bundled/posix.h b/include/spdlog/fmt/bundled/posix.h new file mode 100644 index 0000000..20c7589 --- /dev/null +++ b/include/spdlog/fmt/bundled/posix.h @@ -0,0 +1,484 @@ +// A C++ interface to POSIX functions. +// +// Copyright (c) 2012 - 2016, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_POSIX_H_ +#define FMT_POSIX_H_ + +#if defined(__MINGW32__) || defined(__CYGWIN__) +// Workaround MinGW bug https://sourceforge.net/p/mingw/bugs/2024/. +#undef __STRICT_ANSI__ +#endif + +#include +#include // for O_RDONLY +#include // for locale_t +#include +#include // for strtod_l + +#include + +#if defined __APPLE__ || defined(__FreeBSD__) +#include // for LC_NUMERIC_MASK on OS X +#endif + +#include "format.h" + +#ifndef FMT_POSIX +#if defined(_WIN32) && !defined(__MINGW32__) +// Fix warnings about deprecated symbols. +#define FMT_POSIX(call) _##call +#else +#define FMT_POSIX(call) call +#endif +#endif + +// Calls to system functions are wrapped in FMT_SYSTEM for testability. +#ifdef FMT_SYSTEM +#define FMT_POSIX_CALL(call) FMT_SYSTEM(call) +#else +#define FMT_SYSTEM(call) call +#ifdef _WIN32 +// Fix warnings about deprecated symbols. +#define FMT_POSIX_CALL(call) ::_##call +#else +#define FMT_POSIX_CALL(call) ::call +#endif +#endif + +// Retries the expression while it evaluates to error_result and errno +// equals to EINTR. +#ifndef _WIN32 +#define FMT_RETRY_VAL(result, expression, error_result) \ + do \ + { \ + result = (expression); \ + } while (result == error_result && errno == EINTR) +#else +#define FMT_RETRY_VAL(result, expression, error_result) result = (expression) +#endif + +#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1) + +FMT_BEGIN_NAMESPACE + +/** + \rst + A reference to a null-terminated string. It can be constructed from a C + string or ``std::string``. + + You can use one of the following typedefs for common character types: + + +---------------+-----------------------------+ + | Type | Definition | + +===============+=============================+ + | cstring_view | basic_cstring_view | + +---------------+-----------------------------+ + | wcstring_view | basic_cstring_view | + +---------------+-----------------------------+ + + This class is most useful as a parameter type to allow passing + different types of strings to a function, for example:: + + template + std::string format(cstring_view format_str, const Args & ... args); + + format("{}", 42); + format(std::string("{}"), 42); + \endrst + */ +template +class basic_cstring_view +{ +private: + const Char *data_; + +public: + /** Constructs a string reference object from a C string. */ + basic_cstring_view(const Char *s) + : data_(s) + { + } + + /** + \rst + Constructs a string reference from an ``std::string`` object. + \endrst + */ + basic_cstring_view(const std::basic_string &s) + : data_(s.c_str()) + { + } + + /** Returns the pointer to a C string. */ + const Char *c_str() const + { + return data_; + } +}; + +typedef basic_cstring_view cstring_view; +typedef basic_cstring_view wcstring_view; + +// An error code. +class error_code +{ +private: + int value_; + +public: + explicit error_code(int value = 0) FMT_NOEXCEPT : value_(value) {} + + int get() const FMT_NOEXCEPT + { + return value_; + } +}; + +// A buffered file. +class buffered_file +{ +private: + FILE *file_; + + friend class file; + + explicit buffered_file(FILE *f) + : file_(f) + { + } + +public: + // Constructs a buffered_file object which doesn't represent any file. + buffered_file() FMT_NOEXCEPT : file_(FMT_NULL) {} + + // Destroys the object closing the file it represents if any. + FMT_API ~buffered_file() FMT_DTOR_NOEXCEPT; + +#if !FMT_USE_RVALUE_REFERENCES + // Emulate a move constructor and a move assignment operator if rvalue + // references are not supported. + +private: + // A proxy object to emulate a move constructor. + // It is private to make it impossible call operator Proxy directly. + struct Proxy + { + FILE *file; + }; + +public: + // A "move constructor" for moving from a temporary. + buffered_file(Proxy p) FMT_NOEXCEPT : file_(p.file) {} + + // A "move constructor" for moving from an lvalue. + buffered_file(buffered_file &f) FMT_NOEXCEPT : file_(f.file_) + { + f.file_ = FMT_NULL; + } + + // A "move assignment operator" for moving from a temporary. + buffered_file &operator=(Proxy p) + { + close(); + file_ = p.file; + return *this; + } + + // A "move assignment operator" for moving from an lvalue. + buffered_file &operator=(buffered_file &other) + { + close(); + file_ = other.file_; + other.file_ = FMT_NULL; + return *this; + } + + // Returns a proxy object for moving from a temporary: + // buffered_file file = buffered_file(...); + operator Proxy() FMT_NOEXCEPT + { + Proxy p = {file_}; + file_ = FMT_NULL; + return p; + } + +#else +private: + FMT_DISALLOW_COPY_AND_ASSIGN(buffered_file); + +public: + buffered_file(buffered_file &&other) FMT_NOEXCEPT : file_(other.file_) + { + other.file_ = FMT_NULL; + } + + buffered_file &operator=(buffered_file &&other) + { + close(); + file_ = other.file_; + other.file_ = FMT_NULL; + return *this; + } +#endif + + // Opens a file. + FMT_API buffered_file(cstring_view filename, cstring_view mode); + + // Closes the file. + FMT_API void close(); + + // Returns the pointer to a FILE object representing this file. + FILE *get() const FMT_NOEXCEPT + { + return file_; + } + + // We place parentheses around fileno to workaround a bug in some versions + // of MinGW that define fileno as a macro. + FMT_API int(fileno)() const; + + void vprint(string_view format_str, format_args args) + { + fmt::vprint(file_, format_str, args); + } + + template + inline void print(string_view format_str, const Args &... args) + { + vprint(format_str, make_format_args(args...)); + } +}; + +// A file. Closed file is represented by a file object with descriptor -1. +// Methods that are not declared with FMT_NOEXCEPT may throw +// fmt::system_error in case of failure. Note that some errors such as +// closing the file multiple times will cause a crash on Windows rather +// than an exception. You can get standard behavior by overriding the +// invalid parameter handler with _set_invalid_parameter_handler. +class file +{ +private: + int fd_; // File descriptor. + + // Constructs a file object with a given descriptor. + explicit file(int fd) + : fd_(fd) + { + } + +public: + // Possible values for the oflag argument to the constructor. + enum + { + RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only. + WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only. + RDWR = FMT_POSIX(O_RDWR) // Open for reading and writing. + }; + + // Constructs a file object which doesn't represent any file. + file() FMT_NOEXCEPT : fd_(-1) {} + + // Opens a file and constructs a file object representing this file. + FMT_API file(cstring_view path, int oflag); + +#if !FMT_USE_RVALUE_REFERENCES + // Emulate a move constructor and a move assignment operator if rvalue + // references are not supported. + +private: + // A proxy object to emulate a move constructor. + // It is private to make it impossible call operator Proxy directly. + struct Proxy + { + int fd; + }; + +public: + // A "move constructor" for moving from a temporary. + file(Proxy p) FMT_NOEXCEPT : fd_(p.fd) {} + + // A "move constructor" for moving from an lvalue. + file(file &other) FMT_NOEXCEPT : fd_(other.fd_) + { + other.fd_ = -1; + } + + // A "move assignment operator" for moving from a temporary. + file &operator=(Proxy p) + { + close(); + fd_ = p.fd; + return *this; + } + + // A "move assignment operator" for moving from an lvalue. + file &operator=(file &other) + { + close(); + fd_ = other.fd_; + other.fd_ = -1; + return *this; + } + + // Returns a proxy object for moving from a temporary: + // file f = file(...); + operator Proxy() FMT_NOEXCEPT + { + Proxy p = {fd_}; + fd_ = -1; + return p; + } + +#else +private: + FMT_DISALLOW_COPY_AND_ASSIGN(file); + +public: + file(file &&other) FMT_NOEXCEPT : fd_(other.fd_) + { + other.fd_ = -1; + } + + file &operator=(file &&other) + { + close(); + fd_ = other.fd_; + other.fd_ = -1; + return *this; + } +#endif + + // Destroys the object closing the file it represents if any. + FMT_API ~file() FMT_DTOR_NOEXCEPT; + + // Returns the file descriptor. + int descriptor() const FMT_NOEXCEPT + { + return fd_; + } + + // Closes the file. + FMT_API void close(); + + // Returns the file size. The size has signed type for consistency with + // stat::st_size. + FMT_API long long size() const; + + // Attempts to read count bytes from the file into the specified buffer. + FMT_API std::size_t read(void *buffer, std::size_t count); + + // Attempts to write count bytes from the specified buffer to the file. + FMT_API std::size_t write(const void *buffer, std::size_t count); + + // Duplicates a file descriptor with the dup function and returns + // the duplicate as a file object. + FMT_API static file dup(int fd); + + // Makes fd be the copy of this file descriptor, closing fd first if + // necessary. + FMT_API void dup2(int fd); + + // Makes fd be the copy of this file descriptor, closing fd first if + // necessary. + FMT_API void dup2(int fd, error_code &ec) FMT_NOEXCEPT; + + // Creates a pipe setting up read_end and write_end file objects for reading + // and writing respectively. + FMT_API static void pipe(file &read_end, file &write_end); + + // Creates a buffered_file object associated with this file and detaches + // this file object from the file. + FMT_API buffered_file fdopen(const char *mode); +}; + +// Returns the memory page size. +long getpagesize(); + +#if (defined(LC_NUMERIC_MASK) || defined(_MSC_VER)) && !defined(__ANDROID__) && !defined(__CYGWIN__) && !defined(__OpenBSD__) +#define FMT_LOCALE +#endif + +#ifdef FMT_LOCALE +// A "C" numeric locale. +class Locale +{ +private: +#ifdef _MSC_VER + typedef _locale_t locale_t; + + enum + { + LC_NUMERIC_MASK = LC_NUMERIC + }; + + static locale_t newlocale(int category_mask, const char *locale, locale_t) + { + return _create_locale(category_mask, locale); + } + + static void freelocale(locale_t locale) + { + _free_locale(locale); + } + + static double strtod_l(const char *nptr, char **endptr, _locale_t locale) + { + return _strtod_l(nptr, endptr, locale); + } +#endif + + locale_t locale_; + + FMT_DISALLOW_COPY_AND_ASSIGN(Locale); + +public: + typedef locale_t Type; + + Locale() + : locale_(newlocale(LC_NUMERIC_MASK, "C", FMT_NULL)) + { + if (!locale_) + FMT_THROW(system_error(errno, "cannot create locale")); + } + ~Locale() + { + freelocale(locale_); + } + + Type get() const + { + return locale_; + } + + // Converts string to floating-point number and advances str past the end + // of the parsed input. + double strtod(const char *&str) const + { + char *end = FMT_NULL; + double result = strtod_l(str, &end, locale_); + str = end; + return result; + } +}; +#endif // FMT_LOCALE +FMT_END_NAMESPACE + +#if !FMT_USE_RVALUE_REFERENCES +namespace std { +// For compatibility with C++98. +inline fmt::buffered_file &move(fmt::buffered_file &f) +{ + return f; +} +inline fmt::file &move(fmt::file &f) +{ + return f; +} +} // namespace std +#endif + +#endif // FMT_POSIX_H_ diff --git a/include/spdlog/fmt/bundled/printf.h b/include/spdlog/fmt/bundled/printf.h new file mode 100644 index 0000000..1782c2e --- /dev/null +++ b/include/spdlog/fmt/bundled/printf.h @@ -0,0 +1,807 @@ +// Formatting library for C++ +// +// Copyright (c) 2012 - 2016, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_PRINTF_H_ +#define FMT_PRINTF_H_ + +#include // std::fill_n +#include // std::numeric_limits + +#include "ostream.h" + +FMT_BEGIN_NAMESPACE +namespace internal { + +// Checks if a value fits in int - used to avoid warnings about comparing +// signed and unsigned integers. +template +struct int_checker +{ + template + static bool fits_in_int(T value) + { + unsigned max = std::numeric_limits::max(); + return value <= max; + } + static bool fits_in_int(bool) + { + return true; + } +}; + +template<> +struct int_checker +{ + template + static bool fits_in_int(T value) + { + return value >= std::numeric_limits::min() && value <= std::numeric_limits::max(); + } + static bool fits_in_int(int) + { + return true; + } +}; + +class printf_precision_handler : public function +{ +public: + template + typename std::enable_if::value, int>::type operator()(T value) + { + if (!int_checker::is_signed>::fits_in_int(value)) + FMT_THROW(format_error("number is too big")); + return static_cast(value); + } + + template + typename std::enable_if::value, int>::type operator()(T) + { + FMT_THROW(format_error("precision is not integer")); + return 0; + } +}; + +// An argument visitor that returns true iff arg is a zero integer. +class is_zero_int : public function +{ +public: + template + typename std::enable_if::value, bool>::type operator()(T value) + { + return value == 0; + } + + template + typename std::enable_if::value, bool>::type operator()(T) + { + return false; + } +}; + +template +struct make_unsigned_or_bool : std::make_unsigned +{ +}; + +template<> +struct make_unsigned_or_bool +{ + typedef bool type; +}; + +template +class arg_converter : public function +{ +private: + typedef typename Context::char_type Char; + + basic_format_arg &arg_; + typename Context::char_type type_; + +public: + arg_converter(basic_format_arg &arg, Char type) + : arg_(arg) + , type_(type) + { + } + + void operator()(bool value) + { + if (type_ != 's') + operator()(value); + } + + template + typename std::enable_if::value>::type operator()(U value) + { + bool is_signed = type_ == 'd' || type_ == 'i'; + typedef typename std::conditional::value, U, T>::type TargetType; + if (const_check(sizeof(TargetType) <= sizeof(int))) + { + // Extra casts are used to silence warnings. + if (is_signed) + { + arg_ = internal::make_arg(static_cast(static_cast(value))); + } + else + { + typedef typename make_unsigned_or_bool::type Unsigned; + arg_ = internal::make_arg(static_cast(static_cast(value))); + } + } + else + { + if (is_signed) + { + // glibc's printf doesn't sign extend arguments of smaller types: + // std::printf("%lld", -42); // prints "4294967254" + // but we don't have to do the same because it's a UB. + arg_ = internal::make_arg(static_cast(value)); + } + else + { + arg_ = internal::make_arg(static_cast::type>(value)); + } + } + } + + template + typename std::enable_if::value>::type operator()(U) + { + // No coversion needed for non-integral types. + } +}; + +// Converts an integer argument to T for printf, if T is an integral type. +// If T is void, the argument is converted to corresponding signed or unsigned +// type depending on the type specifier: 'd' and 'i' - signed, other - +// unsigned). +template +void convert_arg(basic_format_arg &arg, Char type) +{ + visit(arg_converter(arg, type), arg); +} + +// Converts an integer argument to char for printf. +template +class char_converter : public function +{ +private: + basic_format_arg &arg_; + + FMT_DISALLOW_COPY_AND_ASSIGN(char_converter); + +public: + explicit char_converter(basic_format_arg &arg) + : arg_(arg) + { + } + + template + typename std::enable_if::value>::type operator()(T value) + { + typedef typename Context::char_type Char; + arg_ = internal::make_arg(static_cast(value)); + } + + template + typename std::enable_if::value>::type operator()(T) + { + // No coversion needed for non-integral types. + } +}; + +// Checks if an argument is a valid printf width specifier and sets +// left alignment if it is negative. +template +class printf_width_handler : public function +{ +private: + typedef basic_format_specs format_specs; + + format_specs &spec_; + + FMT_DISALLOW_COPY_AND_ASSIGN(printf_width_handler); + +public: + explicit printf_width_handler(format_specs &spec) + : spec_(spec) + { + } + + template + typename std::enable_if::value, unsigned>::type operator()(T value) + { + typedef typename internal::int_traits::main_type UnsignedType; + UnsignedType width = static_cast(value); + if (internal::is_negative(value)) + { + spec_.align_ = ALIGN_LEFT; + width = 0 - width; + } + unsigned int_max = std::numeric_limits::max(); + if (width > int_max) + FMT_THROW(format_error("number is too big")); + return static_cast(width); + } + + template + typename std::enable_if::value, unsigned>::type operator()(T) + { + FMT_THROW(format_error("width is not integer")); + return 0; + } +}; +} // namespace internal + +template +class printf_arg_formatter; + +template>>> +class basic_printf_context; + +/** + \rst + The ``printf`` argument formatter. + \endrst + */ +template +class printf_arg_formatter : public internal::function::iterator>, + public internal::arg_formatter_base +{ +private: + typedef typename Range::value_type char_type; + typedef decltype(internal::declval().begin()) iterator; + typedef internal::arg_formatter_base base; + typedef basic_printf_context context_type; + + context_type &context_; + + void write_null_pointer(char) + { + this->spec().type_ = 0; + this->write("(nil)"); + } + + void write_null_pointer(wchar_t) + { + this->spec().type_ = 0; + this->write(L"(nil)"); + } + +public: + typedef typename base::format_specs format_specs; + + /** + \rst + Constructs an argument formatter object. + *buffer* is a reference to the output buffer and *spec* contains format + specifier information for standard argument types. + \endrst + */ + printf_arg_formatter(internal::basic_buffer &buffer, format_specs &spec, context_type &ctx) + : base(back_insert_range>(buffer), spec) + , context_(ctx) + { + } + + template + typename std::enable_if::value, iterator>::type operator()(T value) + { + // MSVC2013 fails to compile separate overloads for bool and char_type so + // use std::is_same instead. + if (std::is_same::value) + { + format_specs &fmt_spec = this->spec(); + if (fmt_spec.type_ != 's') + return base::operator()(value ? 1 : 0); + fmt_spec.type_ = 0; + this->write(value != 0); + } + else if (std::is_same::value) + { + format_specs &fmt_spec = this->spec(); + if (fmt_spec.type_ && fmt_spec.type_ != 'c') + return (*this)(static_cast(value)); + fmt_spec.flags_ = 0; + fmt_spec.align_ = ALIGN_RIGHT; + return base::operator()(value); + } + else + { + return base::operator()(value); + } + return this->out(); + } + + template + typename std::enable_if::value, iterator>::type operator()(T value) + { + return base::operator()(value); + } + + /** Formats a null-terminated C string. */ + iterator operator()(const char *value) + { + if (value) + base::operator()(value); + else if (this->spec().type_ == 'p') + write_null_pointer(char_type()); + else + this->write("(null)"); + return this->out(); + } + + /** Formats a null-terminated wide C string. */ + iterator operator()(const wchar_t *value) + { + if (value) + base::operator()(value); + else if (this->spec().type_ == 'p') + write_null_pointer(char_type()); + else + this->write(L"(null)"); + return this->out(); + } + + iterator operator()(basic_string_view value) + { + return base::operator()(value); + } + + iterator operator()(monostate value) + { + return base::operator()(value); + } + + /** Formats a pointer. */ + iterator operator()(const void *value) + { + if (value) + return base::operator()(value); + this->spec().type_ = 0; + write_null_pointer(char_type()); + return this->out(); + } + + /** Formats an argument of a custom (user-defined) type. */ + iterator operator()(typename basic_format_arg::handle handle) + { + handle.format(context_); + return this->out(); + } +}; + +template +struct printf_formatter +{ + template + auto parse(ParseContext &ctx) -> decltype(ctx.begin()) + { + return ctx.begin(); + } + + template + auto format(const T &value, FormatContext &ctx) -> decltype(ctx.out()) + { + internal::format_value(internal::get_container(ctx.out()), value); + return ctx.out(); + } +}; + +/** This template formats data and writes the output to a writer. */ +template +class basic_printf_context : private internal::context_base, Char> +{ +public: + /** The character type for the output. */ + typedef Char char_type; + + template + struct formatter_type + { + typedef printf_formatter type; + }; + +private: + typedef internal::context_base base; + typedef typename base::format_arg format_arg; + typedef basic_format_specs format_specs; + typedef internal::null_terminating_iterator iterator; + + void parse_flags(format_specs &spec, iterator &it); + + // Returns the argument with specified index or, if arg_index is equal + // to the maximum unsigned value, the next argument. + format_arg get_arg(iterator it, unsigned arg_index = (std::numeric_limits::max)()); + + // Parses argument index, flags and width and returns the argument index. + unsigned parse_header(iterator &it, format_specs &spec); + +public: + /** + \rst + Constructs a ``printf_context`` object. References to the arguments and + the writer are stored in the context object so make sure they have + appropriate lifetimes. + \endrst + */ + basic_printf_context(OutputIt out, basic_string_view format_str, basic_format_args args) + : base(out, format_str, args) + { + } + + using base::advance_to; + using base::out; + using base::parse_context; + + /** Formats stored arguments and writes the output to the range. */ + void format(); +}; + +template +void basic_printf_context::parse_flags(format_specs &spec, iterator &it) +{ + for (;;) + { + switch (*it++) + { + case '-': + spec.align_ = ALIGN_LEFT; + break; + case '+': + spec.flags_ |= SIGN_FLAG | PLUS_FLAG; + break; + case '0': + spec.fill_ = '0'; + break; + case ' ': + spec.flags_ |= SIGN_FLAG; + break; + case '#': + spec.flags_ |= HASH_FLAG; + break; + default: + --it; + return; + } + } +} + +template +typename basic_printf_context::format_arg basic_printf_context::get_arg( + iterator it, unsigned arg_index) +{ + (void)it; + if (arg_index == std::numeric_limits::max()) + return this->do_get_arg(this->parse_context().next_arg_id()); + return base::get_arg(arg_index - 1); +} + +template +unsigned basic_printf_context::parse_header(iterator &it, format_specs &spec) +{ + unsigned arg_index = std::numeric_limits::max(); + char_type c = *it; + if (c >= '0' && c <= '9') + { + // Parse an argument index (if followed by '$') or a width possibly + // preceded with '0' flag(s). + internal::error_handler eh; + unsigned value = parse_nonnegative_int(it, eh); + if (*it == '$') + { // value is an argument index + ++it; + arg_index = value; + } + else + { + if (c == '0') + spec.fill_ = '0'; + if (value != 0) + { + // Nonzero value means that we parsed width and don't need to + // parse it or flags again, so return now. + spec.width_ = value; + return arg_index; + } + } + } + parse_flags(spec, it); + // Parse width. + if (*it >= '0' && *it <= '9') + { + internal::error_handler eh; + spec.width_ = parse_nonnegative_int(it, eh); + } + else if (*it == '*') + { + ++it; + spec.width_ = visit(internal::printf_width_handler(spec), get_arg(it)); + } + return arg_index; +} + +template +void basic_printf_context::format() +{ + auto &buffer = internal::get_container(this->out()); + auto start = iterator(this->parse_context()); + auto it = start; + using internal::pointer_from; + while (*it) + { + char_type c = *it++; + if (c != '%') + continue; + if (*it == c) + { + buffer.append(pointer_from(start), pointer_from(it)); + start = ++it; + continue; + } + buffer.append(pointer_from(start), pointer_from(it) - 1); + + format_specs spec; + spec.align_ = ALIGN_RIGHT; + + // Parse argument index, flags and width. + unsigned arg_index = parse_header(it, spec); + + // Parse precision. + if (*it == '.') + { + ++it; + if ('0' <= *it && *it <= '9') + { + internal::error_handler eh; + spec.precision_ = static_cast(parse_nonnegative_int(it, eh)); + } + else if (*it == '*') + { + ++it; + spec.precision_ = visit(internal::printf_precision_handler(), get_arg(it)); + } + else + { + spec.precision_ = 0; + } + } + + format_arg arg = get_arg(it, arg_index); + if (spec.flag(HASH_FLAG) && visit(internal::is_zero_int(), arg)) + spec.flags_ &= ~internal::to_unsigned(HASH_FLAG); + if (spec.fill_ == '0') + { + if (arg.is_arithmetic()) + spec.align_ = ALIGN_NUMERIC; + else + spec.fill_ = ' '; // Ignore '0' flag for non-numeric types. + } + + // Parse length and convert the argument to the required type. + using internal::convert_arg; + switch (*it++) + { + case 'h': + if (*it == 'h') + convert_arg(arg, *++it); + else + convert_arg(arg, *it); + break; + case 'l': + if (*it == 'l') + convert_arg(arg, *++it); + else + convert_arg(arg, *it); + break; + case 'j': + convert_arg(arg, *it); + break; + case 'z': + convert_arg(arg, *it); + break; + case 't': + convert_arg(arg, *it); + break; + case 'L': + // printf produces garbage when 'L' is omitted for long double, no + // need to do the same. + break; + default: + --it; + convert_arg(arg, *it); + } + + // Parse type. + if (!*it) + FMT_THROW(format_error("invalid format string")); + spec.type_ = static_cast(*it++); + if (arg.is_integral()) + { + // Normalize type. + switch (spec.type_) + { + case 'i': + case 'u': + spec.type_ = 'd'; + break; + case 'c': + // TODO: handle wchar_t better? + visit(internal::char_converter(arg), arg); + break; + } + } + + start = it; + + // Format argument. + visit(AF(buffer, spec, *this), arg); + } + buffer.append(pointer_from(start), pointer_from(it)); +} + +template +void printf(internal::basic_buffer &buf, basic_string_view format, basic_format_args args) +{ + Context(std::back_inserter(buf), format, args).format(); +} + +template +struct printf_context +{ + typedef basic_printf_context, typename Buffer::value_type> type; +}; + +template +inline format_arg_store::type, Args...> make_printf_args(const Args &... args) +{ + return format_arg_store::type, Args...>(args...); +} +typedef basic_format_args::type> printf_args; +typedef basic_format_args::type> wprintf_args; + +inline std::string vsprintf(string_view format, printf_args args) +{ + memory_buffer buffer; + printf(buffer, format, args); + return to_string(buffer); +} + +/** + \rst + Formats arguments and returns the result as a string. + + **Example**:: + + std::string message = fmt::sprintf("The answer is %d", 42); + \endrst +*/ +template +inline std::string sprintf(string_view format_str, const Args &... args) +{ + return vsprintf(format_str, make_format_args::type>(args...)); +} + +inline std::wstring vsprintf(wstring_view format, wprintf_args args) +{ + wmemory_buffer buffer; + printf(buffer, format, args); + return to_string(buffer); +} + +template +inline std::wstring sprintf(wstring_view format_str, const Args &... args) +{ + return vsprintf(format_str, make_format_args::type>(args...)); +} + +template +inline int vfprintf( + std::FILE *f, basic_string_view format, basic_format_args>::type> args) +{ + basic_memory_buffer buffer; + printf(buffer, format, args); + std::size_t size = buffer.size(); + return std::fwrite(buffer.data(), sizeof(Char), size, f) < size ? -1 : static_cast(size); +} + +/** + \rst + Prints formatted data to the file *f*. + + **Example**:: + + fmt::fprintf(stderr, "Don't %s!", "panic"); + \endrst + */ +template +inline int fprintf(std::FILE *f, string_view format_str, const Args &... args) +{ + auto vargs = make_format_args::type>(args...); + return vfprintf(f, format_str, vargs); +} + +template +inline int fprintf(std::FILE *f, wstring_view format_str, const Args &... args) +{ + return vfprintf(f, format_str, make_format_args::type>(args...)); +} + +inline int vprintf(string_view format, printf_args args) +{ + return vfprintf(stdout, format, args); +} + +inline int vprintf(wstring_view format, wprintf_args args) +{ + return vfprintf(stdout, format, args); +} + +/** + \rst + Prints formatted data to ``stdout``. + + **Example**:: + + fmt::printf("Elapsed time: %.2f seconds", 1.23); + \endrst + */ +template +inline int printf(string_view format_str, const Args &... args) +{ + return vprintf(format_str, make_format_args::type>(args...)); +} + +template +inline int printf(wstring_view format_str, const Args &... args) +{ + return vprintf(format_str, make_format_args::type>(args...)); +} + +inline int vfprintf(std::ostream &os, string_view format_str, printf_args args) +{ + memory_buffer buffer; + printf(buffer, format_str, args); + internal::write(os, buffer); + return static_cast(buffer.size()); +} + +inline int vfprintf(std::wostream &os, wstring_view format_str, wprintf_args args) +{ + wmemory_buffer buffer; + printf(buffer, format_str, args); + internal::write(os, buffer); + return static_cast(buffer.size()); +} + +/** + \rst + Prints formatted data to the stream *os*. + + **Example**:: + + fmt::fprintf(cerr, "Don't %s!", "panic"); + \endrst + */ +template +inline int fprintf(std::ostream &os, string_view format_str, const Args &... args) +{ + auto vargs = make_format_args::type>(args...); + return vfprintf(os, format_str, vargs); +} + +template +inline int fprintf(std::wostream &os, wstring_view format_str, const Args &... args) +{ + auto vargs = make_format_args::type>(args...); + return vfprintf(os, format_str, vargs); +} +FMT_END_NAMESPACE + +#endif // FMT_PRINTF_H_ diff --git a/include/spdlog/fmt/bundled/ranges.h b/include/spdlog/fmt/bundled/ranges.h new file mode 100644 index 0000000..ba2a979 --- /dev/null +++ b/include/spdlog/fmt/bundled/ranges.h @@ -0,0 +1,344 @@ +// Formatting library for C++ - the core API +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. +// +// Copyright (c) 2018 - present, Remotion (Igor Schulz) +// All Rights Reserved +// {fmt} support for ranges, containers and types tuple interface. + +#ifndef FMT_RANGES_H_ +#define FMT_RANGES_H_ + +#include "format.h" +#include + +// output only up to N items from the range. +#ifndef FMT_RANGE_OUTPUT_LENGTH_LIMIT +#define FMT_RANGE_OUTPUT_LENGTH_LIMIT 256 +#endif + +FMT_BEGIN_NAMESPACE + +template +struct formatting_base +{ + template + FMT_CONSTEXPR auto parse(ParseContext &ctx) -> decltype(ctx.begin()) + { + return ctx.begin(); + } +}; + +template +struct formatting_range : formatting_base +{ + static FMT_CONSTEXPR_DECL const std::size_t range_length_limit = FMT_RANGE_OUTPUT_LENGTH_LIMIT; // output only up to N items from the + // range. + Char prefix; + Char delimiter; + Char postfix; + formatting_range() + : prefix('{') + , delimiter(',') + , postfix('}') + { + } + static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true; + static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false; +}; + +template +struct formatting_tuple : formatting_base +{ + Char prefix; + Char delimiter; + Char postfix; + formatting_tuple() + : prefix('(') + , delimiter(',') + , postfix(')') + { + } + static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true; + static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false; +}; + +namespace internal { + +template +void copy(const RangeT &range, OutputIterator out) +{ + for (auto it = range.begin(), end = range.end(); it != end; ++it) + *out++ = *it; +} + +template +void copy(const char *str, OutputIterator out) +{ + const char *p_curr = str; + while (*p_curr) + { + *out++ = *p_curr++; + } +} + +template +void copy(char ch, OutputIterator out) +{ + *out++ = ch; +} + +/// Return true value if T has std::string interface, like std::string_view. +template +class is_like_std_string +{ + template + static auto check(U *p) -> decltype(p->find('a'), p->length(), p->data(), int()); + template + static void check(...); + +public: + static FMT_CONSTEXPR_DECL const bool value = !std::is_void(FMT_NULL))>::value; +}; + +template +struct conditional_helper +{ +}; + +template +struct is_range_ : std::false_type +{ +}; + +#if !FMT_MSC_VER || FMT_MSC_VER > 1800 +template +struct is_range_().begin()), decltype(internal::declval().end())>, void>::type> + : std::true_type +{ +}; +#endif + +/// tuple_size and tuple_element check. +template +class is_tuple_like_ +{ + template + static auto check(U *p) -> decltype(std::tuple_size::value, internal::declval::type>(), int()); + template + static void check(...); + +public: + static FMT_CONSTEXPR_DECL const bool value = !std::is_void(FMT_NULL))>::value; +}; + +// Check for integer_sequence +#if defined(__cpp_lib_integer_sequence) || FMT_MSC_VER >= 1900 +template +using integer_sequence = std::integer_sequence; +template +using index_sequence = std::index_sequence; +template +using make_index_sequence = std::make_index_sequence; +#else +template +struct integer_sequence +{ + typedef T value_type; + + static FMT_CONSTEXPR std::size_t size() + { + return sizeof...(N); + } +}; + +template +using index_sequence = integer_sequence; + +template +struct make_integer_sequence : make_integer_sequence +{ +}; +template +struct make_integer_sequence : integer_sequence +{ +}; + +template +using make_index_sequence = make_integer_sequence; +#endif + +template +void for_each(index_sequence, Tuple &&tup, F &&f) FMT_NOEXCEPT +{ + using std::get; + // using free function get(T) now. + const int _[] = {0, ((void)f(get(tup)), 0)...}; + (void)_; // blocks warnings +} + +template +FMT_CONSTEXPR make_index_sequence::value> get_indexes(T const &) +{ + return {}; +} + +template +void for_each(Tuple &&tup, F &&f) +{ + const auto indexes = get_indexes(tup); + for_each(indexes, std::forward(tup), std::forward(f)); +} + +template +FMT_CONSTEXPR const char *format_str_quoted( + bool add_space, const Arg &, typename std::enable_if::type>::value>::type * = nullptr) +{ + return add_space ? " {}" : "{}"; +} + +template +FMT_CONSTEXPR const char *format_str_quoted( + bool add_space, const Arg &, typename std::enable_if::type>::value>::type * = nullptr) +{ + return add_space ? " \"{}\"" : "\"{}\""; +} + +FMT_CONSTEXPR const char *format_str_quoted(bool add_space, const char *) +{ + return add_space ? " \"{}\"" : "\"{}\""; +} +FMT_CONSTEXPR const wchar_t *format_str_quoted(bool add_space, const wchar_t *) +{ + return add_space ? L" \"{}\"" : L"\"{}\""; +} + +FMT_CONSTEXPR const char *format_str_quoted(bool add_space, const char) +{ + return add_space ? " '{}'" : "'{}'"; +} +FMT_CONSTEXPR const wchar_t *format_str_quoted(bool add_space, const wchar_t) +{ + return add_space ? L" '{}'" : L"'{}'"; +} + +} // namespace internal + +template +struct is_tuple_like +{ + static FMT_CONSTEXPR_DECL const bool value = internal::is_tuple_like_::value && !internal::is_range_::value; +}; + +template +struct formatter::value>::type> +{ +private: + // C++11 generic lambda for format() + template + struct format_each + { + template + void operator()(const T &v) + { + if (i > 0) + { + if (formatting.add_prepostfix_space) + { + *out++ = ' '; + } + internal::copy(formatting.delimiter, out); + } + format_to(out, internal::format_str_quoted((formatting.add_delimiter_spaces && i > 0), v), v); + ++i; + } + + formatting_tuple &formatting; + std::size_t &i; + typename std::add_lvalue_reference().out())>::type out; + }; + +public: + formatting_tuple formatting; + + template + FMT_CONSTEXPR auto parse(ParseContext &ctx) -> decltype(ctx.begin()) + { + return formatting.parse(ctx); + } + + template + auto format(const TupleT &values, FormatContext &ctx) -> decltype(ctx.out()) + { + auto out = ctx.out(); + std::size_t i = 0; + internal::copy(formatting.prefix, out); + + internal::for_each(values, format_each{formatting, i, out}); + if (formatting.add_prepostfix_space) + { + *out++ = ' '; + } + internal::copy(formatting.postfix, out); + + return ctx.out(); + } +}; + +template +struct is_range +{ + static FMT_CONSTEXPR_DECL const bool value = internal::is_range_::value && !internal::is_like_std_string::value; +}; + +template +struct formatter::value>::type> +{ + + formatting_range formatting; + + template + FMT_CONSTEXPR auto parse(ParseContext &ctx) -> decltype(ctx.begin()) + { + return formatting.parse(ctx); + } + + template + typename FormatContext::iterator format(const RangeT &values, FormatContext &ctx) + { + auto out = ctx.out(); + internal::copy(formatting.prefix, out); + std::size_t i = 0; + for (auto it = values.begin(), end = values.end(); it != end; ++it) + { + if (i > 0) + { + if (formatting.add_prepostfix_space) + { + *out++ = ' '; + } + internal::copy(formatting.delimiter, out); + } + format_to(out, internal::format_str_quoted((formatting.add_delimiter_spaces && i > 0), *it), *it); + if (++i > formatting.range_length_limit) + { + format_to(out, " ... "); + break; + } + } + if (formatting.add_prepostfix_space) + { + *out++ = ' '; + } + internal::copy(formatting.postfix, out); + return ctx.out(); + } +}; + +FMT_END_NAMESPACE + +#endif // FMT_RANGES_H_ diff --git a/include/spdlog/fmt/bundled/time.h b/include/spdlog/fmt/bundled/time.h new file mode 100644 index 0000000..3e1443b --- /dev/null +++ b/include/spdlog/fmt/bundled/time.h @@ -0,0 +1,199 @@ +// Formatting library for C++ - time formatting +// +// Copyright (c) 2012 - 2016, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_TIME_H_ +#define FMT_TIME_H_ + +#include "format.h" +#include + +FMT_BEGIN_NAMESPACE + +namespace internal { +inline null<> localtime_r(...) +{ + return null<>(); +} +inline null<> localtime_s(...) +{ + return null<>(); +} +inline null<> gmtime_r(...) +{ + return null<>(); +} +inline null<> gmtime_s(...) +{ + return null<>(); +} +} // namespace internal + +// Thread-safe replacement for std::localtime +inline std::tm localtime(std::time_t time) +{ + struct dispatcher + { + std::time_t time_; + std::tm tm_; + + dispatcher(std::time_t t) + : time_(t) + { + } + + bool run() + { + using namespace fmt::internal; + return handle(localtime_r(&time_, &tm_)); + } + + bool handle(std::tm *tm) + { + return tm != FMT_NULL; + } + + bool handle(internal::null<>) + { + using namespace fmt::internal; + return fallback(localtime_s(&tm_, &time_)); + } + + bool fallback(int res) + { + return res == 0; + } + + bool fallback(internal::null<>) + { + using namespace fmt::internal; + std::tm *tm = std::localtime(&time_); + if (tm) + tm_ = *tm; + return tm != FMT_NULL; + } + }; + dispatcher lt(time); + if (lt.run()) + return lt.tm_; + // Too big time values may be unsupported. + FMT_THROW(format_error("time_t value out of range")); +} + +// Thread-safe replacement for std::gmtime +inline std::tm gmtime(std::time_t time) +{ + struct dispatcher + { + std::time_t time_; + std::tm tm_; + + dispatcher(std::time_t t) + : time_(t) + { + } + + bool run() + { + using namespace fmt::internal; + return handle(gmtime_r(&time_, &tm_)); + } + + bool handle(std::tm *tm) + { + return tm != FMT_NULL; + } + + bool handle(internal::null<>) + { + using namespace fmt::internal; + return fallback(gmtime_s(&tm_, &time_)); + } + + bool fallback(int res) + { + return res == 0; + } + + bool fallback(internal::null<>) + { + std::tm *tm = std::gmtime(&time_); + if (tm) + tm_ = *tm; + return tm != FMT_NULL; + } + }; + dispatcher gt(time); + if (gt.run()) + return gt.tm_; + // Too big time values may be unsupported. + FMT_THROW(format_error("time_t value out of range")); +} + +namespace internal { +inline std::size_t strftime(char *str, std::size_t count, const char *format, const std::tm *time) +{ + return std::strftime(str, count, format, time); +} + +inline std::size_t strftime(wchar_t *str, std::size_t count, const wchar_t *format, const std::tm *time) +{ + return std::wcsftime(str, count, format, time); +} +} // namespace internal + +template +struct formatter +{ + template + auto parse(ParseContext &ctx) -> decltype(ctx.begin()) + { + auto it = internal::null_terminating_iterator(ctx); + if (*it == ':') + ++it; + auto end = it; + while (*end && *end != '}') + ++end; + tm_format.reserve(end - it + 1); + using internal::pointer_from; + tm_format.append(pointer_from(it), pointer_from(end)); + tm_format.push_back('\0'); + return pointer_from(end); + } + + template + auto format(const std::tm &tm, FormatContext &ctx) -> decltype(ctx.out()) + { + internal::basic_buffer &buf = internal::get_container(ctx.out()); + std::size_t start = buf.size(); + for (;;) + { + std::size_t size = buf.capacity() - start; + std::size_t count = internal::strftime(&buf[start], size, &tm_format[0], &tm); + if (count != 0) + { + buf.resize(start + count); + break; + } + if (size >= tm_format.size() * 256) + { + // If the buffer is 256 times larger than the format string, assume + // that `strftime` gives an empty result. There doesn't seem to be a + // better way to distinguish the two cases: + // https://github.com/fmtlib/fmt/issues/367 + break; + } + const std::size_t MIN_GROWTH = 10; + buf.reserve(buf.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH)); + } + return ctx.out(); + } + + basic_memory_buffer tm_format; +}; +FMT_END_NAMESPACE + +#endif // FMT_TIME_H_ diff --git a/include/spdlog/fmt/fmt.h b/include/spdlog/fmt/fmt.h new file mode 100644 index 0000000..616af0c --- /dev/null +++ b/include/spdlog/fmt/fmt.h @@ -0,0 +1,25 @@ +// +// Copyright(c) 2016-2018 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +// +// Include a bundled header-only copy of fmtlib or an external one. +// By default spdlog include its own copy. +// + +#if !defined(SPDLOG_FMT_EXTERNAL) +#ifndef FMT_HEADER_ONLY +#define FMT_HEADER_ONLY +#endif +#ifndef FMT_USE_WINDOWS_H +#define FMT_USE_WINDOWS_H 0 +#endif +#include "bundled/core.h" +#include "bundled/format.h" +#else // external fmtlib +#include +#include +#endif diff --git a/include/spdlog/fmt/ostr.h b/include/spdlog/fmt/ostr.h new file mode 100644 index 0000000..9902898 --- /dev/null +++ b/include/spdlog/fmt/ostr.h @@ -0,0 +1,18 @@ +// +// Copyright(c) 2016 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once +// +// include bundled or external copy of fmtlib's ostream support +// +#if !defined(SPDLOG_FMT_EXTERNAL) +#ifndef FMT_HEADER_ONLY +#define FMT_HEADER_ONLY +#endif +#include "bundled/ostream.h" +#include "fmt.h" +#else +#include +#endif diff --git a/include/spdlog/formatter.h b/include/spdlog/formatter.h new file mode 100644 index 0000000..a7ef6b8 --- /dev/null +++ b/include/spdlog/formatter.h @@ -0,0 +1,20 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "fmt/fmt.h" +#include "spdlog/details/log_msg.h" + +namespace spdlog { + +class formatter +{ +public: + virtual ~formatter() = default; + virtual void format(const details::log_msg &msg, fmt::memory_buffer &dest) = 0; + virtual std::unique_ptr clone() const = 0; +}; +} // namespace spdlog diff --git a/include/spdlog/logger.h b/include/spdlog/logger.h new file mode 100644 index 0000000..91eec4b --- /dev/null +++ b/include/spdlog/logger.h @@ -0,0 +1,162 @@ +// +// Copyright(c) 2015-2108 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +// Thread safe logger (except for set_pattern(..), set_formatter(..) and +// set_error_handler()) +// Has name, log level, vector of std::shared sink pointers and formatter +// Upon each log write the logger: +// 1. Checks if its log level is enough to log the message and if yes: +// 2. Call the underlying sinks to do the job. +// 3. Each sink use its own private copy of a formatter to format the message +// and send to its destination. +// +// The use of private formatter per sink provides the opportunity to cache some +// formatted data, +// and support customize format per each sink. + +#include "spdlog/common.h" +#include "spdlog/formatter.h" +#include "spdlog/sinks/sink.h" + +#include +#include +#include + +namespace spdlog { + +class logger +{ +public: + logger(std::string name, sink_ptr single_sink); + logger(std::string name, sinks_init_list sinks); + + template + logger(std::string name, const It &begin, const It &end); + + virtual ~logger(); + + logger(const logger &) = delete; + logger &operator=(const logger &) = delete; + + template + void log(level::level_enum lvl, const char *fmt, const Args &... args); + + template + void log(level::level_enum lvl, const char *msg); + + template + void trace(const char *fmt, const Args &... args); + + template + void debug(const char *fmt, const Args &... args); + + template + void info(const char *fmt, const Args &... args); + + template + void warn(const char *fmt, const Args &... args); + + template + void error(const char *fmt, const Args &... args); + + template + void critical(const char *fmt, const Args &... args); + +#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT + template + void log(level::level_enum lvl, const wchar_t *fmt, const Args &... args); + + template + void trace(const wchar_t *fmt, const Args &... args); + + template + void debug(const wchar_t *fmt, const Args &... args); + + template + void info(const wchar_t *fmt, const Args &... args); + + template + void warn(const wchar_t *fmt, const Args &... args); + + template + void error(const wchar_t *fmt, const Args &... args); + + template + void critical(const wchar_t *fmt, const Args &... args); +#endif // SPDLOG_WCHAR_TO_UTF8_SUPPORT + + template + void log(level::level_enum lvl, const T &); + + template + void trace(const T &msg); + + template + void debug(const T &msg); + + template + void info(const T &msg); + + template + void warn(const T &msg); + + template + void error(const T &msg); + + template + void critical(const T &msg); + + bool should_log(level::level_enum msg_level) const; + void set_level(level::level_enum log_level); + level::level_enum level() const; + const std::string &name() const; + + // set formatting for the sinks in this logger. + // each sink will get a seperate instance of the formatter object. + void set_formatter(std::unique_ptr formatter); + void set_pattern(std::string pattern, pattern_time_type time_type = pattern_time_type::local); + + void flush(); + void flush_on(level::level_enum log_level); + + const std::vector &sinks() const; + + std::vector &sinks(); + + void set_error_handler(log_err_handler err_handler); + log_err_handler error_handler(); + +protected: + virtual void sink_it_(details::log_msg &msg); + virtual void flush_(); + + bool should_flush_(const details::log_msg &msg); + + // default error handler: print the error to stderr with the max rate of 1 + // message/minute + void default_err_handler_(const std::string &msg); + + // increment the message count (only if + // defined(SPDLOG_ENABLE_MESSAGE_COUNTER)) + void incr_msg_counter_(details::log_msg &msg); + + const std::string name_; + std::vector sinks_; + spdlog::level_t level_; + spdlog::level_t flush_level_; + log_err_handler err_handler_; + std::atomic last_err_time_; + std::atomic msg_counter_; + +#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT + std::wstring_convert> wstring_converter_; + std::mutex wstring_converter_mutex_; +#endif +}; +} // namespace spdlog + +#include "details/logger_impl.h" diff --git a/include/spdlog/sinks/android_sink.h b/include/spdlog/sinks/android_sink.h new file mode 100644 index 0000000..fcb9ccb --- /dev/null +++ b/include/spdlog/sinks/android_sink.h @@ -0,0 +1,117 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "spdlog/details/fmt_helper.h" +#include "spdlog/details/null_mutex.h" +#include "spdlog/details/os.h" +#include "spdlog/sinks/base_sink.h" + +#include +#include +#include +#include +#include + +#if !defined(SPDLOG_ANDROID_RETRIES) +#define SPDLOG_ANDROID_RETRIES 2 +#endif + +namespace spdlog { +namespace sinks { + +/* + * Android sink (logging using __android_log_write) + */ +template +class android_sink SPDLOG_FINAL : public base_sink +{ +public: + explicit android_sink(std::string tag = "spdlog", bool use_raw_msg = false) + : tag_(std::move(tag)) + , use_raw_msg_(use_raw_msg) + { + } + +protected: + void sink_it_(const details::log_msg &msg) override + { + const android_LogPriority priority = convert_to_android_(msg.level); + fmt::memory_buffer formatted; + if (use_raw_msg_) + { + details::fmt_helper::append_buf(msg.raw, formatted); + } + else + { + sink::formatter_->format(msg, formatted); + } + formatted.push_back('\0'); + const char *msg_output = formatted.data(); + + // See system/core/liblog/logger_write.c for explanation of return value + int ret = __android_log_write(priority, tag_.c_str(), msg_output); + int retry_count = 0; + while ((ret == -11 /*EAGAIN*/) && (retry_count < SPDLOG_ANDROID_RETRIES)) + { + details::os::sleep_for_millis(5); + ret = __android_log_write(priority, tag_.c_str(), msg_output); + retry_count++; + } + + if (ret < 0) + { + throw spdlog_ex("__android_log_write() failed", ret); + } + } + + void flush_() override {} + +private: + static android_LogPriority convert_to_android_(spdlog::level::level_enum level) + { + switch (level) + { + case spdlog::level::trace: + return ANDROID_LOG_VERBOSE; + case spdlog::level::debug: + return ANDROID_LOG_DEBUG; + case spdlog::level::info: + return ANDROID_LOG_INFO; + case spdlog::level::warn: + return ANDROID_LOG_WARN; + case spdlog::level::err: + return ANDROID_LOG_ERROR; + case spdlog::level::critical: + return ANDROID_LOG_FATAL; + default: + return ANDROID_LOG_DEFAULT; + } + } + + std::string tag_; + bool use_raw_msg_; +}; + +using android_sink_mt = android_sink; +using android_sink_st = android_sink; +} // namespace sinks + +// Create and register android syslog logger + +template +inline std::shared_ptr android_logger_mt(const std::string &logger_name, const std::string &tag = "spdlog") +{ + return Factory::template create(logger_name, tag); +} + +template +inline std::shared_ptr android_logger_st(const std::string &logger_name, const std::string &tag = "spdlog") +{ + return Factory::template create(logger_name, tag); +} + +} // namespace spdlog diff --git a/include/spdlog/sinks/ansicolor_sink.h b/include/spdlog/sinks/ansicolor_sink.h new file mode 100644 index 0000000..3f6c63b --- /dev/null +++ b/include/spdlog/sinks/ansicolor_sink.h @@ -0,0 +1,156 @@ +// +// Copyright(c) 2017 spdlog authors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "spdlog/details/console_globals.h" +#include "spdlog/details/null_mutex.h" +#include "spdlog/details/os.h" + +#include +#include +#include +#include + +namespace spdlog { +namespace sinks { + +/** + * This sink prefixes the output with an ANSI escape sequence color code + * depending on the severity + * of the message. + * If no color terminal detected, omit the escape codes. + */ +template +class ansicolor_sink SPDLOG_FINAL : public sink +{ +public: + using mutex_t = typename ConsoleMutex::mutex_t; + ansicolor_sink() + : target_file_(TargetStream::stream()) + , mutex_(ConsoleMutex::mutex()) + + { + should_do_colors_ = details::os::in_terminal(target_file_) && details::os::is_color_terminal(); + colors_[level::trace] = white; + colors_[level::debug] = cyan; + colors_[level::info] = green; + colors_[level::warn] = yellow + bold; + colors_[level::err] = red + bold; + colors_[level::critical] = bold + on_red; + colors_[level::off] = reset; + } + + ~ansicolor_sink() override = default; + + ansicolor_sink(const ansicolor_sink &other) = delete; + ansicolor_sink &operator=(const ansicolor_sink &other) = delete; + + void set_color(level::level_enum color_level, const std::string &color) + { + std::lock_guard lock(mutex_); + colors_[color_level] = color; + } + + /// Formatting codes + const std::string reset = "\033[m"; + const std::string bold = "\033[1m"; + const std::string dark = "\033[2m"; + const std::string underline = "\033[4m"; + const std::string blink = "\033[5m"; + const std::string reverse = "\033[7m"; + const std::string concealed = "\033[8m"; + const std::string clear_line = "\033[K"; + + // Foreground colors + const std::string black = "\033[30m"; + const std::string red = "\033[31m"; + const std::string green = "\033[32m"; + const std::string yellow = "\033[33m"; + const std::string blue = "\033[34m"; + const std::string magenta = "\033[35m"; + const std::string cyan = "\033[36m"; + const std::string white = "\033[37m"; + + /// Background colors + const std::string on_black = "\033[40m"; + const std::string on_red = "\033[41m"; + const std::string on_green = "\033[42m"; + const std::string on_yellow = "\033[43m"; + const std::string on_blue = "\033[44m"; + const std::string on_magenta = "\033[45m"; + const std::string on_cyan = "\033[46m"; + const std::string on_white = "\033[47m"; + + void log(const details::log_msg &msg) override + { + // Wrap the originally formatted message in color codes. + // If color is not supported in the terminal, log as is instead. + std::lock_guard lock(mutex_); + + fmt::memory_buffer formatted; + formatter_->format(msg, formatted); + if (should_do_colors_ && msg.color_range_end > msg.color_range_start) + { + // before color range + print_range_(formatted, 0, msg.color_range_start); + // in color range + print_ccode_(colors_[msg.level]); + print_range_(formatted, msg.color_range_start, msg.color_range_end); + print_ccode_(reset); + // after color range + print_range_(formatted, msg.color_range_end, formatted.size()); + } + else // no color + { + print_range_(formatted, 0, formatted.size()); + } + fflush(target_file_); + } + + void flush() override + { + std::lock_guard lock(mutex_); + fflush(target_file_); + } + + void set_pattern(const std::string &pattern) SPDLOG_FINAL + { + std::lock_guard lock(mutex_); + formatter_ = std::unique_ptr(new pattern_formatter(pattern)); + } + + void set_formatter(std::unique_ptr sink_formatter) override + { + std::lock_guard lock(mutex_); + formatter_ = std::move(sink_formatter); + } + +private: + void print_ccode_(const std::string &color_code) + { + fwrite(color_code.data(), sizeof(char), color_code.size(), target_file_); + } + void print_range_(const fmt::memory_buffer &formatted, size_t start, size_t end) + { + fwrite(formatted.data() + start, sizeof(char), end - start, target_file_); + } + + FILE *target_file_; + mutex_t &mutex_; + + bool should_do_colors_; + std::unordered_map colors_; +}; + +using ansicolor_stdout_sink_mt = ansicolor_sink; +using ansicolor_stdout_sink_st = ansicolor_sink; + +using ansicolor_stderr_sink_mt = ansicolor_sink; +using ansicolor_stderr_sink_st = ansicolor_sink; + +} // namespace sinks + +} // namespace spdlog diff --git a/include/spdlog/sinks/base_sink.h b/include/spdlog/sinks/base_sink.h new file mode 100644 index 0000000..43863ea --- /dev/null +++ b/include/spdlog/sinks/base_sink.h @@ -0,0 +1,63 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once +// +// base sink templated over a mutex (either dummy or real) +// concrete implementation should override the sink_it_() and flush_() methods. +// locking is taken care of in this class - no locking needed by the +// implementers.. +// + +#include "spdlog/common.h" +#include "spdlog/details/log_msg.h" +#include "spdlog/formatter.h" +#include "spdlog/sinks/sink.h" + +namespace spdlog { +namespace sinks { +template +class base_sink : public sink +{ +public: + base_sink() + : sink() + { + } + + base_sink(const base_sink &) = delete; + base_sink &operator=(const base_sink &) = delete; + + void log(const details::log_msg &msg) SPDLOG_FINAL + { + std::lock_guard lock(mutex_); + sink_it_(msg); + } + + void flush() SPDLOG_FINAL override + { + std::lock_guard lock(mutex_); + flush_(); + } + + void set_pattern(const std::string &pattern) SPDLOG_FINAL override + { + std::lock_guard lock(mutex_); + formatter_ = std::unique_ptr(new pattern_formatter(pattern)); + } + + void set_formatter(std::unique_ptr sink_formatter) SPDLOG_FINAL override + { + std::lock_guard lock(mutex_); + formatter_ = std::move(sink_formatter); + } + +protected: + virtual void sink_it_(const details::log_msg &msg) = 0; + virtual void flush_() = 0; + Mutex mutex_; +}; +} // namespace sinks +} // namespace spdlog diff --git a/include/spdlog/sinks/basic_file_sink.h b/include/spdlog/sinks/basic_file_sink.h new file mode 100644 index 0000000..5db38e8 --- /dev/null +++ b/include/spdlog/sinks/basic_file_sink.h @@ -0,0 +1,66 @@ +// +// Copyright(c) 2015-2018 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once +#include "spdlog/details/file_helper.h" +#include "spdlog/details/null_mutex.h" +#include "spdlog/sinks/base_sink.h" +#include "spdlog/spdlog.h" + +#include +#include + +namespace spdlog { +namespace sinks { +/* + * Trivial file sink with single file as target + */ +template +class basic_file_sink SPDLOG_FINAL : public base_sink +{ +public: + explicit basic_file_sink(const filename_t &filename, bool truncate = false) + { + file_helper_.open(filename, truncate); + } + +protected: + void sink_it_(const details::log_msg &msg) override + { + fmt::memory_buffer formatted; + sink::formatter_->format(msg, formatted); + file_helper_.write(formatted); + } + + void flush_() override + { + file_helper_.flush(); + } + +private: + details::file_helper file_helper_; +}; + +using basic_file_sink_mt = basic_file_sink; +using basic_file_sink_st = basic_file_sink; + +} // namespace sinks + +// +// factory functions +// +template +inline std::shared_ptr basic_logger_mt(const std::string &logger_name, const filename_t &filename, bool truncate = false) +{ + return Factory::template create(logger_name, filename, truncate); +} + +template +inline std::shared_ptr basic_logger_st(const std::string &logger_name, const filename_t &filename, bool truncate = false) +{ + return Factory::template create(logger_name, filename, truncate); +} + +} // namespace spdlog diff --git a/include/spdlog/sinks/daily_file_sink.h b/include/spdlog/sinks/daily_file_sink.h new file mode 100644 index 0000000..940d55d --- /dev/null +++ b/include/spdlog/sinks/daily_file_sink.h @@ -0,0 +1,132 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once +#include "spdlog/details/file_helper.h" +#include "spdlog/details/null_mutex.h" +#include "spdlog/fmt/fmt.h" +#include "spdlog/sinks/base_sink.h" +#include "spdlog/spdlog.h" + +#include +#include +#include +#include +#include + +namespace spdlog { +namespace sinks { + +/* + * Generator of daily log file names in format basename.YYYY-MM-DD.ext + */ +struct daily_filename_calculator +{ + // Create filename for the form basename.YYYY-MM-DD + static filename_t calc_filename(const filename_t &filename, const tm &now_tm) + { + filename_t basename, ext; + std::tie(basename, ext) = details::file_helper::split_by_extenstion(filename); + std::conditional::value, fmt::memory_buffer, fmt::wmemory_buffer>::type w; + fmt::format_to( + w, SPDLOG_FILENAME_T("{}_{:04d}-{:02d}-{:02d}{}"), basename, now_tm.tm_year + 1900, now_tm.tm_mon + 1, now_tm.tm_mday, ext); + return fmt::to_string(w); + } +}; + +/* + * Rotating file sink based on date. rotates at midnight + */ +template +class daily_file_sink SPDLOG_FINAL : public base_sink +{ +public: + // create daily file sink which rotates on given time + daily_file_sink(filename_t base_filename, int rotation_hour, int rotation_minute, bool truncate = false) + : base_filename_(std::move(base_filename)) + , rotation_h_(rotation_hour) + , rotation_m_(rotation_minute) + , truncate_(truncate) + { + if (rotation_hour < 0 || rotation_hour > 23 || rotation_minute < 0 || rotation_minute > 59) + { + throw spdlog_ex("daily_file_sink: Invalid rotation time in ctor"); + } + auto now = log_clock::now(); + file_helper_.open(FileNameCalc::calc_filename(base_filename_, now_tm(now)), truncate_); + rotation_tp_ = next_rotation_tp_(); + } + +protected: + void sink_it_(const details::log_msg &msg) override + { + + if (msg.time >= rotation_tp_) + { + file_helper_.open(FileNameCalc::calc_filename(base_filename_, now_tm(msg.time)), truncate_); + rotation_tp_ = next_rotation_tp_(); + } + fmt::memory_buffer formatted; + sink::formatter_->format(msg, formatted); + file_helper_.write(formatted); + } + + void flush_() override + { + file_helper_.flush(); + } + +private: + tm now_tm(log_clock::time_point tp) + { + time_t tnow = log_clock::to_time_t(tp); + return spdlog::details::os::localtime(tnow); + } + + log_clock::time_point next_rotation_tp_() + { + auto now = log_clock::now(); + tm date = now_tm(now); + date.tm_hour = rotation_h_; + date.tm_min = rotation_m_; + date.tm_sec = 0; + auto rotation_time = log_clock::from_time_t(std::mktime(&date)); + if (rotation_time > now) + { + return rotation_time; + } + return {rotation_time + std::chrono::hours(24)}; + } + + filename_t base_filename_; + int rotation_h_; + int rotation_m_; + log_clock::time_point rotation_tp_; + details::file_helper file_helper_; + bool truncate_; +}; + +using daily_file_sink_mt = daily_file_sink; +using daily_file_sink_st = daily_file_sink; + +} // namespace sinks + +// +// factory functions +// +template +inline std::shared_ptr daily_logger_mt( + const std::string &logger_name, const filename_t &filename, int hour = 0, int minute = 0, bool truncate = false) +{ + return Factory::template create(logger_name, filename, hour, minute, truncate); +} + +template +inline std::shared_ptr daily_logger_st( + const std::string &logger_name, const filename_t &filename, int hour = 0, int minute = 0, bool truncate = false) +{ + return Factory::template create(logger_name, filename, hour, minute, truncate); +} +} // namespace spdlog diff --git a/include/spdlog/sinks/dist_sink.h b/include/spdlog/sinks/dist_sink.h new file mode 100644 index 0000000..8d2f6f9 --- /dev/null +++ b/include/spdlog/sinks/dist_sink.h @@ -0,0 +1,68 @@ +// +// Copyright (c) 2015 David Schury, Gabi Melman +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "base_sink.h" +#include "spdlog/details/log_msg.h" +#include "spdlog/details/null_mutex.h" + +#include +#include +#include +#include + +// Distribution sink (mux). Stores a vector of sinks which get called when log +// is called + +namespace spdlog { +namespace sinks { + +template +class dist_sink : public base_sink +{ +public: + dist_sink() = default; + dist_sink(const dist_sink &) = delete; + dist_sink &operator=(const dist_sink &) = delete; + + void add_sink(std::shared_ptr sink) + { + std::lock_guard lock(base_sink::mutex_); + sinks_.push_back(sink); + } + + void remove_sink(std::shared_ptr sink) + { + std::lock_guard lock(base_sink::mutex_); + sinks_.erase(std::remove(sinks_.begin(), sinks_.end(), sink), sinks_.end()); + } + +protected: + void sink_it_(const details::log_msg &msg) override + { + + for (auto &sink : sinks_) + { + if (sink->should_log(msg.level)) + { + sink->log(msg); + } + } + } + + void flush_() override + { + for (auto &sink : sinks_) + sink->flush(); + } + std::vector> sinks_; +}; + +using dist_sink_mt = dist_sink; +using dist_sink_st = dist_sink; + +} // namespace sinks +} // namespace spdlog diff --git a/include/spdlog/sinks/msvc_sink.h b/include/spdlog/sinks/msvc_sink.h new file mode 100644 index 0000000..a028496 --- /dev/null +++ b/include/spdlog/sinks/msvc_sink.h @@ -0,0 +1,50 @@ +// +// Copyright(c) 2016 Alexander Dalshov. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#if defined(_WIN32) + +#include "spdlog/details/null_mutex.h" +#include "spdlog/sinks/base_sink.h" + +#include + +#include +#include + +namespace spdlog { +namespace sinks { +/* + * MSVC sink (logging using OutputDebugStringA) + */ +template +class msvc_sink : public base_sink +{ +public: + explicit msvc_sink() {} + +protected: + void sink_it_(const details::log_msg &msg) override + { + + fmt::memory_buffer formatted; + sink::formatter_->format(msg, formatted); + OutputDebugStringA(fmt::to_string(formatted).c_str()); + } + + void flush_() override {} +}; + +using msvc_sink_mt = msvc_sink; +using msvc_sink_st = msvc_sink; + +using windebug_sink_mt = msvc_sink_mt; +using windebug_sink_st = msvc_sink_st; + +} // namespace sinks +} // namespace spdlog + +#endif diff --git a/include/spdlog/sinks/null_sink.h b/include/spdlog/sinks/null_sink.h new file mode 100644 index 0000000..73b8c2b --- /dev/null +++ b/include/spdlog/sinks/null_sink.h @@ -0,0 +1,28 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "spdlog/details/null_mutex.h" +#include "spdlog/sinks/base_sink.h" + +#include + +namespace spdlog { +namespace sinks { + +template +class null_sink : public base_sink +{ +protected: + void sink_it_(const details::log_msg &) override {} + void flush_() override {} +}; + +using null_sink_mt = null_sink; +using null_sink_st = null_sink; + +} // namespace sinks +} // namespace spdlog diff --git a/include/spdlog/sinks/ostream_sink.h b/include/spdlog/sinks/ostream_sink.h new file mode 100644 index 0000000..5f31993 --- /dev/null +++ b/include/spdlog/sinks/ostream_sink.h @@ -0,0 +1,51 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "spdlog/details/null_mutex.h" +#include "spdlog/sinks/base_sink.h" + +#include +#include + +namespace spdlog { +namespace sinks { +template +class ostream_sink SPDLOG_FINAL : public base_sink +{ +public: + explicit ostream_sink(std::ostream &os, bool force_flush = false) + : ostream_(os) + , force_flush_(force_flush) + { + } + ostream_sink(const ostream_sink &) = delete; + ostream_sink &operator=(const ostream_sink &) = delete; + +protected: + void sink_it_(const details::log_msg &msg) override + { + fmt::memory_buffer formatted; + sink::formatter_->format(msg, formatted); + ostream_.write(formatted.data(), formatted.size()); + if (force_flush_) + ostream_.flush(); + } + + void flush_() override + { + ostream_.flush(); + } + + std::ostream &ostream_; + bool force_flush_; +}; + +using ostream_sink_mt = ostream_sink; +using ostream_sink_st = ostream_sink; + +} // namespace sinks +} // namespace spdlog diff --git a/include/spdlog/sinks/rotating_file_sink.h b/include/spdlog/sinks/rotating_file_sink.h new file mode 100644 index 0000000..7ed2385 --- /dev/null +++ b/include/spdlog/sinks/rotating_file_sink.h @@ -0,0 +1,144 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once +#include "spdlog/details/file_helper.h" +#include "spdlog/details/null_mutex.h" +#include "spdlog/fmt/fmt.h" +#include "spdlog/sinks/base_sink.h" +#include "spdlog/spdlog.h" + +#include +#include +#include +#include +#include +#include + +namespace spdlog { +namespace sinks { + +// +// Rotating file sink based on size +// +template +class rotating_file_sink SPDLOG_FINAL : public base_sink +{ +public: + rotating_file_sink(filename_t base_filename, std::size_t max_size, std::size_t max_files) + : base_filename_(std::move(base_filename)) + , max_size_(max_size) + , max_files_(max_files) + { + file_helper_.open(calc_filename(base_filename_, 0)); + current_size_ = file_helper_.size(); // expensive. called only once + } + + // calc filename according to index and file extension if exists. + // e.g. calc_filename("logs/mylog.txt, 3) => "logs/mylog.3.txt". + static filename_t calc_filename(const filename_t &filename, std::size_t index) + { + typename std::conditional::value, fmt::memory_buffer, fmt::wmemory_buffer>::type w; + if (index != 0u) + { + filename_t basename, ext; + std::tie(basename, ext) = details::file_helper::split_by_extenstion(filename); + fmt::format_to(w, SPDLOG_FILENAME_T("{}.{}{}"), basename, index, ext); + } + else + { + fmt::format_to(w, SPDLOG_FILENAME_T("{}"), filename); + } + return fmt::to_string(w); + } + +protected: + void sink_it_(const details::log_msg &msg) override + { + fmt::memory_buffer formatted; + sink::formatter_->format(msg, formatted); + current_size_ += formatted.size(); + if (current_size_ > max_size_) + { + rotate_(); + current_size_ = formatted.size(); + } + file_helper_.write(formatted); + } + + void flush_() override + { + file_helper_.flush(); + } + +private: + // Rotate files: + // log.txt -> log.1.txt + // log.1.txt -> log.2.txt + // log.2.txt -> log.3.txt + // log.3.txt -> delete + void rotate_() + { + using details::os::filename_to_str; + file_helper_.close(); + for (auto i = max_files_; i > 0; --i) + { + filename_t src = calc_filename(base_filename_, i - 1); + filename_t target = calc_filename(base_filename_, i); + + if (details::file_helper::file_exists(target)) + { + if (details::os::remove(target) != 0) + { + throw spdlog_ex("rotating_file_sink: failed removing " + filename_to_str(target), errno); + } + } + if (details::file_helper::file_exists(src) && details::os::rename(src, target) != 0) + { + // if failed try again after small delay. + // this is a workaround to a windows issue, where very high rotation + // rates sometimes fail (because of antivirus?). + details::os::sleep_for_millis(20); + details::os::remove(target); + if (details::os::rename(src, target) != 0) + { + throw spdlog_ex( + "rotating_file_sink: failed renaming " + filename_to_str(src) + " to " + filename_to_str(target), errno); + } + } + } + file_helper_.reopen(true); + } + + filename_t base_filename_; + std::size_t max_size_; + std::size_t max_files_; + std::size_t current_size_; + details::file_helper file_helper_; +}; + +using rotating_file_sink_mt = rotating_file_sink; +using rotating_file_sink_st = rotating_file_sink; + +} // namespace sinks + +// +// factory functions +// + +template +inline std::shared_ptr rotating_logger_mt( + const std::string &logger_name, const filename_t &filename, size_t max_file_size, size_t max_files) +{ + return Factory::template create(logger_name, filename, max_file_size, max_files); +} + +template +inline std::shared_ptr rotating_logger_st( + const std::string &logger_name, const filename_t &filename, size_t max_file_size, size_t max_files) +{ + return Factory::template create(logger_name, filename, max_file_size, max_files); +} +} // namespace spdlog diff --git a/include/spdlog/sinks/sink.h b/include/spdlog/sinks/sink.h new file mode 100644 index 0000000..cb8aecb --- /dev/null +++ b/include/spdlog/sinks/sink.h @@ -0,0 +1,57 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "spdlog/details/log_msg.h" +#include "spdlog/details/pattern_formatter.h" +#include "spdlog/formatter.h" + +namespace spdlog { +namespace sinks { +class sink +{ +public: + sink() + : level_(level::trace) + , formatter_(new pattern_formatter("%+")) + { + } + + explicit sink(std::unique_ptr formatter) + : level_(level::trace) + , formatter_(std::move(formatter)){}; + + virtual ~sink() = default; + virtual void log(const details::log_msg &msg) = 0; + virtual void flush() = 0; + virtual void set_pattern(const std::string &pattern) = 0; + virtual void set_formatter(std::unique_ptr sink_formatter) = 0; + + bool should_log(level::level_enum msg_level) const + { + return msg_level >= level_.load(std::memory_order_relaxed); + } + + void set_level(level::level_enum log_level) + { + level_.store(log_level); + } + + level::level_enum level() const + { + return static_cast(level_.load(std::memory_order_relaxed)); + } + +protected: + // sink log level - default is all + level_t level_; + + // sink formatter - default is full format + std::unique_ptr formatter_; +}; + +} // namespace sinks +} // namespace spdlog diff --git a/include/spdlog/sinks/stdout_color_sinks.h b/include/spdlog/sinks/stdout_color_sinks.h new file mode 100644 index 0000000..761e32a --- /dev/null +++ b/include/spdlog/sinks/stdout_color_sinks.h @@ -0,0 +1,53 @@ +// +// Copyright(c) 2018 spdlog +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "spdlog/spdlog.h" +#ifdef _WIN32 +#include "spdlog/sinks/wincolor_sink.h" +#else +#include "spdlog/sinks/ansicolor_sink.h" +#endif + +namespace spdlog { +namespace sinks { +#ifdef _WIN32 +using stdout_color_sink_mt = wincolor_stdout_sink_mt; +using stdout_color_sink_st = wincolor_stdout_sink_st; +using stderr_color_sink_mt = wincolor_stderr_sink_mt; +using stderr_color_sink_st = wincolor_stderr_sink_st; +#else +using stdout_color_sink_mt = ansicolor_stdout_sink_mt; +using stdout_color_sink_st = ansicolor_stdout_sink_st; +using stderr_color_sink_mt = ansicolor_stderr_sink_mt; +using stderr_color_sink_st = ansicolor_stderr_sink_st; +#endif +} // namespace sinks + +template +inline std::shared_ptr stdout_color_mt(const std::string &logger_name) +{ + return Factory::template create(logger_name); +} + +template +inline std::shared_ptr stdout_color_st(const std::string &logger_name) +{ + return Factory::template create(logger_name); +} + +template +inline std::shared_ptr stderr_color_mt(const std::string &logger_name) +{ + return Factory::template create(logger_name); +} + +template +inline std::shared_ptr stderr_color_st(const std::string &logger_name) +{ + return Factory::template create(logger_name); +} +} // namespace spdlog diff --git a/include/spdlog/sinks/stdout_sinks.h b/include/spdlog/sinks/stdout_sinks.h new file mode 100644 index 0000000..e12739c --- /dev/null +++ b/include/spdlog/sinks/stdout_sinks.h @@ -0,0 +1,100 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "spdlog/details/console_globals.h" +#include "spdlog/details/null_mutex.h" +#include "spdlog/spdlog.h" + +#include +#include +#include +#include + +namespace spdlog { + +namespace sinks { + +template +class stdout_sink SPDLOG_FINAL : public sink +{ +public: + using mutex_t = typename ConsoleMutex::mutex_t; + stdout_sink() + : mutex_(ConsoleMutex::mutex()) + , file_(TargetStream::stream()) + { + } + ~stdout_sink() override = default; + + stdout_sink(const stdout_sink &other) = delete; + stdout_sink &operator=(const stdout_sink &other) = delete; + + void log(const details::log_msg &msg) override + { + std::lock_guard lock(mutex_); + fmt::memory_buffer formatted; + formatter_->format(msg, formatted); + fwrite(formatted.data(), sizeof(char), formatted.size(), file_); + fflush(TargetStream::stream()); + } + + void flush() override + { + std::lock_guard lock(mutex_); + fflush(file_); + } + + void set_pattern(const std::string &pattern) override + { + std::lock_guard lock(mutex_); + formatter_ = std::unique_ptr(new pattern_formatter(pattern)); + } + + void set_formatter(std::unique_ptr sink_formatter) override + { + std::lock_guard lock(mutex_); + formatter_ = std::move(sink_formatter); + } + +private: + mutex_t &mutex_; + FILE *file_; +}; + +using stdout_sink_mt = stdout_sink; +using stdout_sink_st = stdout_sink; + +using stderr_sink_mt = stdout_sink; +using stderr_sink_st = stdout_sink; + +} // namespace sinks + +// factory methods +template +inline std::shared_ptr stdout_logger_mt(const std::string &logger_name) +{ + return Factory::template create(logger_name); +} + +template +inline std::shared_ptr stdout_logger_st(const std::string &logger_name) +{ + return Factory::template create(logger_name); +} + +template +inline std::shared_ptr stderr_logger_mt(const std::string &logger_name) +{ + return Factory::template create(logger_name); +} + +template +inline std::shared_ptr stderr_logger_st(const std::string &logger_name) +{ + return Factory::template create(logger_name); +} +} // namespace spdlog diff --git a/include/spdlog/sinks/syslog_sink.h b/include/spdlog/sinks/syslog_sink.h new file mode 100644 index 0000000..151e7a1 --- /dev/null +++ b/include/spdlog/sinks/syslog_sink.h @@ -0,0 +1,91 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "spdlog/sinks/base_sink.h" +#include "spdlog/spdlog.h" + +#include +#include +#include + +namespace spdlog { +namespace sinks { +/** + * Sink that write to syslog using the `syscall()` library call. + * + * Locking is not needed, as `syslog()` itself is thread-safe. + */ +template +class syslog_sink : public base_sink +{ +public: + // + explicit syslog_sink(std::string ident = "", int syslog_option = 0, int syslog_facility = LOG_USER) + : ident_(std::move(ident)) + { + priorities_[static_cast(level::trace)] = LOG_DEBUG; + priorities_[static_cast(level::debug)] = LOG_DEBUG; + priorities_[static_cast(level::info)] = LOG_INFO; + priorities_[static_cast(level::warn)] = LOG_WARNING; + priorities_[static_cast(level::err)] = LOG_ERR; + priorities_[static_cast(level::critical)] = LOG_CRIT; + priorities_[static_cast(level::off)] = LOG_INFO; + + // set ident to be program name if empty + ::openlog(ident_.empty() ? nullptr : ident_.c_str(), syslog_option, syslog_facility); + } + + ~syslog_sink() override + { + ::closelog(); + } + + syslog_sink(const syslog_sink &) = delete; + syslog_sink &operator=(const syslog_sink &) = delete; + +protected: + void sink_it_(const details::log_msg &msg) override + { + ::syslog(syslog_prio_from_level(msg), "%s", fmt::to_string(msg.raw).c_str()); + } + + void flush_() override {} + +private: + std::array priorities_; + // must store the ident because the man says openlog might use the pointer as + // is and not a string copy + const std::string ident_; + + // + // Simply maps spdlog's log level to syslog priority level. + // + int syslog_prio_from_level(const details::log_msg &msg) const + { + return priorities_[static_cast(msg.level)]; + } +}; + +using syslog_sink_mt = syslog_sink; +using syslog_sink_st = syslog_sink; +} // namespace sinks + +// Create and register a syslog logger +template +inline std::shared_ptr syslog_logger_mt( + const std::string &logger_name, const std::string &syslog_ident = "", int syslog_option = 0, int syslog_facility = (1 << 3)) +{ + return Factory::template create(logger_name, syslog_ident, syslog_option, syslog_facility); +} + +template +inline std::shared_ptr syslog_logger_st( + const std::string &logger_name, const std::string &syslog_ident = "", int syslog_option = 0, int syslog_facility = (1 << 3)) +{ + return Factory::template create(logger_name, syslog_ident, syslog_option, syslog_facility); +} +} // namespace spdlog diff --git a/include/spdlog/sinks/wincolor_sink.h b/include/spdlog/sinks/wincolor_sink.h new file mode 100644 index 0000000..8c63e7f --- /dev/null +++ b/include/spdlog/sinks/wincolor_sink.h @@ -0,0 +1,139 @@ +// +// Copyright(c) 2016 spdlog +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "spdlog/common.h" +#include "spdlog/details/console_globals.h" +#include "spdlog/details/null_mutex.h" +#include "spdlog/sinks/sink.h" + +#include +#include +#include +#include +#include + +namespace spdlog { +namespace sinks { +/* + * Windows color console sink. Uses WriteConsoleA to write to the console with + * colors + */ +template +class wincolor_sink : public sink +{ +public: + const WORD BOLD = FOREGROUND_INTENSITY; + const WORD RED = FOREGROUND_RED; + const WORD GREEN = FOREGROUND_GREEN; + const WORD CYAN = FOREGROUND_GREEN | FOREGROUND_BLUE; + const WORD WHITE = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; + const WORD YELLOW = FOREGROUND_RED | FOREGROUND_GREEN; + + wincolor_sink() + : out_handle_(OutHandle::handle()) + , mutex_(ConsoleMutex::mutex()) + { + colors_[level::trace] = WHITE; + colors_[level::debug] = CYAN; + colors_[level::info] = GREEN; + colors_[level::warn] = YELLOW | BOLD; + colors_[level::err] = RED | BOLD; // red bold + colors_[level::critical] = BACKGROUND_RED | WHITE | BOLD; // white bold on red background + colors_[level::off] = 0; + } + + ~wincolor_sink() override + { + this->flush(); + } + + wincolor_sink(const wincolor_sink &other) = delete; + wincolor_sink &operator=(const wincolor_sink &other) = delete; + + // change the color for the given level + void set_color(level::level_enum level, WORD color) + { + std::lock_guard lock(mutex_); + colors_[level] = color; + } + + void log(const details::log_msg &msg) SPDLOG_FINAL override + { + std::lock_guard lock(mutex_); + fmt::memory_buffer formatted; + formatter_->format(msg, formatted); + if (msg.color_range_end > msg.color_range_start) + { + // before color range + print_range_(formatted, 0, msg.color_range_start); + + // in color range + auto orig_attribs = set_console_attribs(colors_[msg.level]); + print_range_(formatted, msg.color_range_start, msg.color_range_end); + ::SetConsoleTextAttribute(out_handle_, + orig_attribs); // reset to orig colors + // after color range + print_range_(formatted, msg.color_range_end, formatted.size()); + } + else // print without colors if color range is invalid + { + print_range_(formatted, 0, formatted.size()); + } + } + + void flush() SPDLOG_FINAL override + { + // windows console always flushed? + } + + void set_pattern(const std::string &pattern) override SPDLOG_FINAL + { + std::lock_guard lock(mutex_); + formatter_ = std::unique_ptr(new pattern_formatter(pattern)); + } + + void set_formatter(std::unique_ptr sink_formatter) override SPDLOG_FINAL + { + std::lock_guard lock(mutex_); + formatter_ = std::move(sink_formatter); + } + +private: + using mutex_t = typename ConsoleMutex::mutex_t; + // set color and return the orig console attributes (for resetting later) + WORD set_console_attribs(WORD attribs) + { + CONSOLE_SCREEN_BUFFER_INFO orig_buffer_info; + ::GetConsoleScreenBufferInfo(out_handle_, &orig_buffer_info); + WORD back_color = orig_buffer_info.wAttributes; + // retrieve the current background color + back_color &= static_cast(~(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY)); + // keep the background color unchanged + ::SetConsoleTextAttribute(out_handle_, attribs | back_color); + return orig_buffer_info.wAttributes; // return orig attribs + } + + // print a range of formatted message to console + void print_range_(const fmt::memory_buffer &formatted, size_t start, size_t end) + { + auto size = static_cast(end - start); + ::WriteConsoleA(out_handle_, formatted.data() + start, size, nullptr, nullptr); + } + + HANDLE out_handle_; + mutex_t &mutex_; + std::unordered_map colors_; +}; + +using wincolor_stdout_sink_mt = wincolor_sink; +using wincolor_stdout_sink_st = wincolor_sink; + +using wincolor_stderr_sink_mt = wincolor_sink; +using wincolor_stderr_sink_st = wincolor_sink; + +} // namespace sinks +} // namespace spdlog diff --git a/include/spdlog/spdlog.h b/include/spdlog/spdlog.h new file mode 100644 index 0000000..40640ab --- /dev/null +++ b/include/spdlog/spdlog.h @@ -0,0 +1,161 @@ +// +// Copyright(c) 2015-2018 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// +// spdlog main header file. +// see example.cpp for usage example + +#pragma once + +#include "spdlog/common.h" +#include "spdlog/details/registry.h" +#include "spdlog/logger.h" +#include "spdlog/version.h" + +#include +#include +#include +#include + +namespace spdlog { + +// Default logger factory- creates synchronous loggers +struct synchronous_factory +{ + template + + static std::shared_ptr create(std::string logger_name, SinkArgs &&... args) + { + auto sink = std::make_shared(std::forward(args)...); + auto new_logger = std::make_shared(std::move(logger_name), std::move(sink)); + details::registry::instance().register_and_init(new_logger); + return new_logger; + } +}; + +using default_factory = synchronous_factory; + +// Create and register a logger with a templated sink type +// The logger's level, formatter and flush level will be set according the +// global settings. +// Example: +// spdlog::create("logger_name", "dailylog_filename", 11, 59); +template +inline std::shared_ptr create(std::string logger_name, SinkArgs &&... sink_args) +{ + return default_factory::create(std::move(logger_name), std::forward(sink_args)...); +} + +// Return an existing logger or nullptr if a logger with such name doesn't +// exist. +// example: spdlog::get("my_logger")->info("hello {}", "world"); +inline std::shared_ptr get(const std::string &name) +{ + return details::registry::instance().get(name); +} + +// Set global formatter. Each sink in each logger will get a clone of this object +inline void set_formatter(std::unique_ptr formatter) +{ + details::registry::instance().set_formatter(std::move(formatter)); +} + +// Set global format string. +// example: spdlog::set_pattern("%Y-%m-%d %H:%M:%S.%e %l : %v"); +inline void set_pattern(std::string pattern, pattern_time_type time_type = pattern_time_type::local) +{ + set_formatter(std::unique_ptr(new pattern_formatter(std::move(pattern), time_type))); +} + +// Set global logging level +inline void set_level(level::level_enum log_level) +{ + details::registry::instance().set_level(log_level); +} + +// Set global flush level +inline void flush_on(level::level_enum log_level) +{ + details::registry::instance().flush_on(log_level); +} + +// Start/Restart a periodic flusher thread +// Warning: Use only if all your loggers are thread safe! +inline void flush_every(std::chrono::seconds interval) +{ + details::registry::instance().flush_every(interval); +} + +// Set global error handler +inline void set_error_handler(log_err_handler handler) +{ + details::registry::instance().set_error_handler(std::move(handler)); +} + +// Register the given logger with the given name +inline void register_logger(std::shared_ptr logger) +{ + details::registry::instance().register_logger(std::move(logger)); +} + +// Apply a user defined function on all registered loggers +// Example: +// spdlog::apply_all([&](std::shared_ptr l) {l->flush();}); +inline void apply_all(const std::function)> &fun) +{ + details::registry::instance().apply_all(fun); +} + +// Drop the reference to the given logger +inline void drop(const std::string &name) +{ + details::registry::instance().drop(name); +} + +// Drop all references from the registry +inline void drop_all() +{ + details::registry::instance().drop_all(); +} + +// stop any running threads started by spdlog and clean registry loggers +inline void shutdown() +{ + details::registry::instance().shutdown(); +} + +/////////////////////////////////////////////////////////////////////////////// +// +// Trace & Debug can be switched on/off at compile time for zero cost debug +// statements. +// Uncomment SPDLOG_DEBUG_ON/SPDLOG_TRACE_ON in tweakme.h to enable. +// SPDLOG_TRACE(..) will also print current file and line. +// +// Example: +// spdlog::set_level(spdlog::level::trace); +// SPDLOG_TRACE(my_logger, "some trace message"); +// SPDLOG_TRACE(my_logger, "another trace message {} {}", 1, 2); +// SPDLOG_DEBUG(my_logger, "some debug message {} {}", 3, 4); +/////////////////////////////////////////////////////////////////////////////// + +#ifdef SPDLOG_TRACE_ON +#define SPDLOG_STR_H(x) #x +#define SPDLOG_STR_HELPER(x) SPDLOG_STR_H(x) +#ifdef _MSC_VER +#define SPDLOG_TRACE(logger, ...) logger->trace("[ " __FILE__ "(" SPDLOG_STR_HELPER(__LINE__) ") ] " __VA_ARGS__) +#else +#define SPDLOG_TRACE(logger, ...) \ + logger->trace("[ " __FILE__ ":" SPDLOG_STR_HELPER(__LINE__) " ]" \ + " " __VA_ARGS__) +#endif +#else +#define SPDLOG_TRACE(logger, ...) (void)0 +#endif + +#ifdef SPDLOG_DEBUG_ON +#define SPDLOG_DEBUG(logger, ...) logger->debug(__VA_ARGS__) +#else +#define SPDLOG_DEBUG(logger, ...) (void)0 +#endif + +} // namespace spdlog diff --git a/include/spdlog/tweakme.h b/include/spdlog/tweakme.h new file mode 100644 index 0000000..5325645 --- /dev/null +++ b/include/spdlog/tweakme.h @@ -0,0 +1,132 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +/////////////////////////////////////////////////////////////////////////////// +// +// Edit this file to squeeze more performance, and to customize supported +// features +// +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Under Linux, the much faster CLOCK_REALTIME_COARSE clock can be used. +// This clock is less accurate - can be off by dozens of millis - depending on +// the kernel HZ. +// Uncomment to use it instead of the regular clock. +// +// #define SPDLOG_CLOCK_COARSE +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment if date/time logging is not needed and never appear in the log +// pattern. +// This will prevent spdlog from querying the clock on each log call. +// +// WARNING: If the log pattern contains any date/time while this flag is on, the +// result is undefined. +// You must set new pattern(spdlog::set_pattern(..") without any +// date/time in it +// +// #define SPDLOG_NO_DATETIME +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment if thread id logging is not needed (i.e. no %t in the log pattern). +// This will prevent spdlog from querying the thread id on each log call. +// +// WARNING: If the log pattern contains thread id (i.e, %t) while this flag is +// on, the result is undefined. +// +// #define SPDLOG_NO_THREAD_ID +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to prevent spdlog from caching thread ids in thread local storage. +// By default spdlog saves thread ids in tls to gain a few micros for each call. +// +// WARNING: if your program forks, UNCOMMENT this flag to prevent undefined +// thread ids in the children logs. +// +// #define SPDLOG_DISABLE_TID_CACHING +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment if logger name logging is not needed. +// This will prevent spdlog from copying the logger name on each log call. +// +// #define SPDLOG_NO_NAME +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to enable the SPDLOG_DEBUG/SPDLOG_TRACE macros. +// +// #define SPDLOG_DEBUG_ON +// #define SPDLOG_TRACE_ON +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to avoid spdlog's usage of atomic log levels +// Use only if your code never modifies a logger's log levels concurrently by +// different threads. +// +// #define SPDLOG_NO_ATOMIC_LEVELS +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to enable usage of wchar_t for file names on Windows. +// +// #define SPDLOG_WCHAR_FILENAMES +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to override default eol ("\n" or "\r\n" under Linux/Windows) +// +// #define SPDLOG_EOL ";-)\n" +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to use your own copy of the fmt library instead of spdlog's copy. +// In this case spdlog will try to include so set your -I flag +// accordingly. +// +// #define SPDLOG_FMT_EXTERNAL +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to enable wchar_t support (convert to utf8) +// +// #define SPDLOG_WCHAR_TO_UTF8_SUPPORT +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to prevent child processes from inheriting log file descriptors +// +// #define SPDLOG_PREVENT_CHILD_FD +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment if your compiler doesn't support the "final" keyword. +// The final keyword allows more optimizations in release +// mode with recent compilers. See GCC's documentation for -Wsuggest-final-types +// for instance. +// +// #define SPDLOG_NO_FINAL +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to enable message counting feature. +// Use the %i in the logger pattern to display log message sequence id. +// +// #define SPDLOG_ENABLE_MESSAGE_COUNTER +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to customize level names (e.g. "MT TRACE") +// +// #define SPDLOG_LEVEL_NAMES { "MY TRACE", "MY DEBUG", "MY INFO", "MY WARNING", +// "MY ERROR", "MY CRITICAL", "OFF" } +/////////////////////////////////////////////////////////////////////////////// diff --git a/include/spdlog/version.h b/include/spdlog/version.h new file mode 100644 index 0000000..f14078e --- /dev/null +++ b/include/spdlog/version.h @@ -0,0 +1,12 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#define SPDLOG_VER_MAJOR 1 +#define SPDLOG_VER_MINOR 1 +#define SPDLOG_VER_PATCH 0 + +#define SPDLOG_VERSION (SPDLOG_VER_MAJOR * 10000 + SPDLOG_VER_MINOR * 100 + SPDLOG_VER_PATCH) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..2fb0b4b --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,29 @@ +project(spdlog-utests CXX) + +find_package(Threads REQUIRED) + +set(SPDLOG_UTESTS_SOURCES + errors.cpp + file_helper.cpp + file_log.cpp + test_misc.cpp + test_pattern_formatter.cpp + test_async.cpp + includes.h + registry.cpp + test_macros.cpp + utils.cpp + utils.h + main.cpp + test_mpmc_q.cpp + test_sink.h +) + +add_executable(${PROJECT_NAME} ${SPDLOG_UTESTS_SOURCES}) +target_link_libraries(${PROJECT_NAME} PRIVATE Threads::Threads) +target_link_libraries(${PROJECT_NAME} PRIVATE spdlog::spdlog) + +file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/logs") + +enable_testing() +add_test(NAME ${PROJECT_NAME} COMMAND ${PROJECT_NAME}) diff --git a/tests/Makefile b/tests/Makefile new file mode 100644 index 0000000..00c7c58 --- /dev/null +++ b/tests/Makefile @@ -0,0 +1,22 @@ +CXX ?= g++ +CXXFLAGS = -Wall -pedantic -std=c++11 -pthread -O3 -I../include -fmax-errors=1 +LDPFALGS = -pthread + +CPP_FILES := $(wildcard *.cpp) +OBJ_FILES := $(addprefix ./,$(notdir $(CPP_FILES:.cpp=.o))) + + +tests: $(OBJ_FILES) + $(CXX) $(CXXFLAGS) $(LDPFALGS) -o $@ $^ + mkdir -p logs + +%.o: %.cpp + $(CXX) $(CXXFLAGS) -c -o $@ $< + +clean: + rm -f tests *.o logs/*.txt + +rebuild: clean tests + + + diff --git a/tests/catch.hpp b/tests/catch.hpp new file mode 100644 index 0000000..5585d1e --- /dev/null +++ b/tests/catch.hpp @@ -0,0 +1,9427 @@ +/* + * CATCH v1.1 build 1 (master branch) + * Generated: 2015-03-27 18:00:16.346230 + * ---------------------------------------------------------- + * This file has been merged from multiple headers. Please don't edit it directly + * Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved. + * + * Distributed under the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED + +#define TWOBLUECUBES_CATCH_HPP_INCLUDED + +// #included from: internal/catch_suppress_warnings.h + +#define TWOBLUECUBES_CATCH_SUPPRESS_WARNINGS_H_INCLUDED + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(push) +# pragma warning(disable: 161 1682) +# else // __ICC +# pragma clang diagnostic ignored "-Wglobal-constructors" +# pragma clang diagnostic ignored "-Wvariadic-macros" +# pragma clang diagnostic ignored "-Wc99-extensions" +# pragma clang diagnostic ignored "-Wunused-variable" +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wpadded" +# pragma clang diagnostic ignored "-Wc++98-compat" +# pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +# endif +#elif defined __GNUC__ +# pragma GCC diagnostic ignored "-Wvariadic-macros" +# pragma GCC diagnostic ignored "-Wunused-variable" +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wpadded" +#endif + +#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) +# define CATCH_IMPL +#endif + +#ifdef CATCH_IMPL +# ifndef CLARA_CONFIG_MAIN +# define CLARA_CONFIG_MAIN_NOT_DEFINED +# define CLARA_CONFIG_MAIN +# endif +#endif + +// #included from: internal/catch_notimplemented_exception.h +#define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_H_INCLUDED + +// #included from: catch_common.h +#define TWOBLUECUBES_CATCH_COMMON_H_INCLUDED + +#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line +#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) +#define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) + +#define INTERNAL_CATCH_STRINGIFY2( expr ) #expr +#define INTERNAL_CATCH_STRINGIFY( expr ) INTERNAL_CATCH_STRINGIFY2( expr ) + +#include +#include +#include + +// #included from: catch_compiler_capabilities.h +#define TWOBLUECUBES_CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED + +// Much of the following code is based on Boost (1.53) + +#ifdef __clang__ + +# if __has_feature(cxx_nullptr) +# define CATCH_CONFIG_CPP11_NULLPTR +# endif + +# if __has_feature(cxx_noexcept) +# define CATCH_CONFIG_CPP11_NOEXCEPT +# endif + +#endif // __clang__ + +//////////////////////////////////////////////////////////////////////////////// +// Borland +#ifdef __BORLANDC__ + +#if (__BORLANDC__ > 0x582 ) +//#define CATCH_CONFIG_SFINAE // Not confirmed +#endif + +#endif // __BORLANDC__ + +//////////////////////////////////////////////////////////////////////////////// +// EDG +#ifdef __EDG_VERSION__ + +#if (__EDG_VERSION__ > 238 ) +//#define CATCH_CONFIG_SFINAE // Not confirmed +#endif + +#endif // __EDG_VERSION__ + +//////////////////////////////////////////////////////////////////////////////// +// Digital Mars +#ifdef __DMC__ + +#if (__DMC__ > 0x840 ) +//#define CATCH_CONFIG_SFINAE // Not confirmed +#endif + +#endif // __DMC__ + +//////////////////////////////////////////////////////////////////////////////// +// GCC +#ifdef __GNUC__ + +#if __GNUC__ < 3 + +#if (__GNUC_MINOR__ >= 96 ) +//#define CATCH_CONFIG_SFINAE +#endif + +#elif __GNUC__ >= 3 + +// #define CATCH_CONFIG_SFINAE // Taking this out completely for now + +#endif // __GNUC__ < 3 + +#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) ) + +#define CATCH_CONFIG_CPP11_NULLPTR +#endif + +#endif // __GNUC__ + +//////////////////////////////////////////////////////////////////////////////// +// Visual C++ +#ifdef _MSC_VER + +#if (_MSC_VER >= 1600) +#define CATCH_CONFIG_CPP11_NULLPTR +#endif + +#if (_MSC_VER >= 1310 ) // (VC++ 7.0+) +//#define CATCH_CONFIG_SFINAE // Not confirmed +#endif + +#endif // _MSC_VER + +// Use variadic macros if the compiler supports them +#if ( defined _MSC_VER && _MSC_VER > 1400 && !defined __EDGE__) || \ + ( defined __WAVE__ && __WAVE_HAS_VARIADICS ) || \ + ( defined __GNUC__ && __GNUC__ >= 3 ) || \ + ( !defined __cplusplus && __STDC_VERSION__ >= 199901L || __cplusplus >= 201103L ) + +#ifndef CATCH_CONFIG_NO_VARIADIC_MACROS +#define CATCH_CONFIG_VARIADIC_MACROS +#endif + +#endif + +//////////////////////////////////////////////////////////////////////////////// +// C++ language feature support + +// detect language version: +#if (__cplusplus == 201103L) +# define CATCH_CPP11 +# define CATCH_CPP11_OR_GREATER +#elif (__cplusplus >= 201103L) +# define CATCH_CPP11_OR_GREATER +#endif + +// noexcept support: +#if defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_NOEXCEPT) +# define CATCH_NOEXCEPT noexcept +# define CATCH_NOEXCEPT_IS(x) noexcept(x) +#else +# define CATCH_NOEXCEPT throw() +# define CATCH_NOEXCEPT_IS(x) +#endif + +namespace Catch { + + class NonCopyable { +#ifdef CATCH_CPP11_OR_GREATER + NonCopyable( NonCopyable const& ) = delete; + NonCopyable( NonCopyable && ) = delete; + NonCopyable& operator = ( NonCopyable const& ) = delete; + NonCopyable& operator = ( NonCopyable && ) = delete; +#else + NonCopyable( NonCopyable const& info ); + NonCopyable& operator = ( NonCopyable const& ); +#endif + + protected: + NonCopyable() {} + virtual ~NonCopyable(); + }; + + class SafeBool { + public: + typedef void (SafeBool::*type)() const; + + static type makeSafe( bool value ) { + return value ? &SafeBool::trueValue : 0; + } + private: + void trueValue() const {} + }; + + template + inline void deleteAll( ContainerT& container ) { + typename ContainerT::const_iterator it = container.begin(); + typename ContainerT::const_iterator itEnd = container.end(); + for(; it != itEnd; ++it ) + delete *it; + } + template + inline void deleteAllValues( AssociativeContainerT& container ) { + typename AssociativeContainerT::const_iterator it = container.begin(); + typename AssociativeContainerT::const_iterator itEnd = container.end(); + for(; it != itEnd; ++it ) + delete it->second; + } + + bool startsWith( std::string const& s, std::string const& prefix ); + bool endsWith( std::string const& s, std::string const& suffix ); + bool contains( std::string const& s, std::string const& infix ); + void toLowerInPlace( std::string& s ); + std::string toLower( std::string const& s ); + std::string trim( std::string const& str ); + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ); + + struct pluralise { + pluralise( std::size_t count, std::string const& label ); + + friend std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ); + + std::size_t m_count; + std::string m_label; + }; + + struct SourceLineInfo { + + SourceLineInfo(); + SourceLineInfo( char const* _file, std::size_t _line ); + SourceLineInfo( SourceLineInfo const& other ); +# ifdef CATCH_CPP11_OR_GREATER + SourceLineInfo( SourceLineInfo && ) = default; + SourceLineInfo& operator = ( SourceLineInfo const& ) = default; + SourceLineInfo& operator = ( SourceLineInfo && ) = default; +# endif + bool empty() const; + bool operator == ( SourceLineInfo const& other ) const; + bool operator < ( SourceLineInfo const& other ) const; + + std::string file; + std::size_t line; + }; + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); + + // This is just here to avoid compiler warnings with macro constants and boolean literals + inline bool isTrue( bool value ){ return value; } + inline bool alwaysTrue() { return true; } + inline bool alwaysFalse() { return false; } + + void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ); + + // Use this in variadic streaming macros to allow + // >> +StreamEndStop + // as well as + // >> stuff +StreamEndStop + struct StreamEndStop { + std::string operator+() { + return std::string(); + } + }; + template + T const& operator + ( T const& value, StreamEndStop ) { + return value; + } +} + +#define CATCH_INTERNAL_LINEINFO ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) +#define CATCH_INTERNAL_ERROR( msg ) ::Catch::throwLogicError( msg, CATCH_INTERNAL_LINEINFO ); + +#include + +namespace Catch { + + class NotImplementedException : public std::exception + { + public: + NotImplementedException( SourceLineInfo const& lineInfo ); + NotImplementedException( NotImplementedException const& ) {} + + virtual ~NotImplementedException() CATCH_NOEXCEPT {} + + virtual const char* what() const CATCH_NOEXCEPT; + + private: + std::string m_what; + SourceLineInfo m_lineInfo; + }; + +} // end namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define CATCH_NOT_IMPLEMENTED throw Catch::NotImplementedException( CATCH_INTERNAL_LINEINFO ) + +// #included from: internal/catch_context.h +#define TWOBLUECUBES_CATCH_CONTEXT_H_INCLUDED + +// #included from: catch_interfaces_generators.h +#define TWOBLUECUBES_CATCH_INTERFACES_GENERATORS_H_INCLUDED + +#include + +namespace Catch { + + struct IGeneratorInfo { + virtual ~IGeneratorInfo(); + virtual bool moveNext() = 0; + virtual std::size_t getCurrentIndex() const = 0; + }; + + struct IGeneratorsForTest { + virtual ~IGeneratorsForTest(); + + virtual IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) = 0; + virtual bool moveNext() = 0; + }; + + IGeneratorsForTest* createGeneratorsForTest(); + +} // end namespace Catch + +// #included from: catch_ptr.hpp +#define TWOBLUECUBES_CATCH_PTR_HPP_INCLUDED + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch { + + // An intrusive reference counting smart pointer. + // T must implement addRef() and release() methods + // typically implementing the IShared interface + template + class Ptr { + public: + Ptr() : m_p( NULL ){} + Ptr( T* p ) : m_p( p ){ + if( m_p ) + m_p->addRef(); + } + Ptr( Ptr const& other ) : m_p( other.m_p ){ + if( m_p ) + m_p->addRef(); + } + ~Ptr(){ + if( m_p ) + m_p->release(); + } + void reset() { + if( m_p ) + m_p->release(); + m_p = NULL; + } + Ptr& operator = ( T* p ){ + Ptr temp( p ); + swap( temp ); + return *this; + } + Ptr& operator = ( Ptr const& other ){ + Ptr temp( other ); + swap( temp ); + return *this; + } + void swap( Ptr& other ) { std::swap( m_p, other.m_p ); } + T* get() { return m_p; } + const T* get() const{ return m_p; } + T& operator*() const { return *m_p; } + T* operator->() const { return m_p; } + bool operator !() const { return m_p == NULL; } + operator SafeBool::type() const { return SafeBool::makeSafe( m_p != NULL ); } + + private: + T* m_p; + }; + + struct IShared : NonCopyable { + virtual ~IShared(); + virtual void addRef() const = 0; + virtual void release() const = 0; + }; + + template + struct SharedImpl : T { + + SharedImpl() : m_rc( 0 ){} + + virtual void addRef() const { + ++m_rc; + } + virtual void release() const { + if( --m_rc == 0 ) + delete this; + } + + mutable unsigned int m_rc; + }; + +} // end namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#include +#include +#include + +namespace Catch { + + class TestCase; + class Stream; + struct IResultCapture; + struct IRunner; + struct IGeneratorsForTest; + struct IConfig; + + struct IContext + { + virtual ~IContext(); + + virtual IResultCapture* getResultCapture() = 0; + virtual IRunner* getRunner() = 0; + virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) = 0; + virtual bool advanceGeneratorsForCurrentTest() = 0; + virtual Ptr getConfig() const = 0; + }; + + struct IMutableContext : IContext + { + virtual ~IMutableContext(); + virtual void setResultCapture( IResultCapture* resultCapture ) = 0; + virtual void setRunner( IRunner* runner ) = 0; + virtual void setConfig( Ptr const& config ) = 0; + }; + + IContext& getCurrentContext(); + IMutableContext& getCurrentMutableContext(); + void cleanUpContext(); + Stream createStream( std::string const& streamName ); + +} + +// #included from: internal/catch_test_registry.hpp +#define TWOBLUECUBES_CATCH_TEST_REGISTRY_HPP_INCLUDED + +// #included from: catch_interfaces_testcase.h +#define TWOBLUECUBES_CATCH_INTERFACES_TESTCASE_H_INCLUDED + +#include + +namespace Catch { + + class TestSpec; + + struct ITestCase : IShared { + virtual void invoke () const = 0; + protected: + virtual ~ITestCase(); + }; + + class TestCase; + struct IConfig; + + struct ITestCaseRegistry { + virtual ~ITestCaseRegistry(); + virtual std::vector const& getAllTests() const = 0; + virtual void getFilteredTests( TestSpec const& testSpec, IConfig const& config, std::vector& matchingTestCases, bool negated = false ) const = 0; + + }; +} + +namespace Catch { + +template +class MethodTestCase : public SharedImpl { + +public: + MethodTestCase( void (C::*method)() ) : m_method( method ) {} + + virtual void invoke() const { + C obj; + (obj.*m_method)(); + } + +private: + virtual ~MethodTestCase() {} + + void (C::*m_method)(); +}; + +typedef void(*TestFunction)(); + +struct NameAndDesc { + NameAndDesc( const char* _name = "", const char* _description= "" ) + : name( _name ), description( _description ) + {} + + const char* name; + const char* description; +}; + +struct AutoReg { + + AutoReg( TestFunction function, + SourceLineInfo const& lineInfo, + NameAndDesc const& nameAndDesc ); + + template + AutoReg( void (C::*method)(), + char const* className, + NameAndDesc const& nameAndDesc, + SourceLineInfo const& lineInfo ) { + registerTestCase( new MethodTestCase( method ), + className, + nameAndDesc, + lineInfo ); + } + + void registerTestCase( ITestCase* testCase, + char const* className, + NameAndDesc const& nameAndDesc, + SourceLineInfo const& lineInfo ); + + ~AutoReg(); + +private: + AutoReg( AutoReg const& ); + void operator= ( AutoReg const& ); +}; + +} // end namespace Catch + +#ifdef CATCH_CONFIG_VARIADIC_MACROS + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TESTCASE( ... ) \ + static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )(); \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); }\ + static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )() + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); } + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... )\ + namespace{ \ + struct INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) : ClassName{ \ + void test(); \ + }; \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test, #ClassName, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); \ + } \ + void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test() + +#else + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TESTCASE( Name, Desc ) \ + static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )(); \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); }\ + static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )() + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, Name, Desc ) \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( Name, Desc ), CATCH_INTERNAL_LINEINFO ); } + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, TestName, Desc )\ + namespace{ \ + struct INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) : ClassName{ \ + void test(); \ + }; \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test, #ClassName, Catch::NameAndDesc( TestName, Desc ), CATCH_INTERNAL_LINEINFO ); \ + } \ + void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test() + +#endif + +// #included from: internal/catch_capture.hpp +#define TWOBLUECUBES_CATCH_CAPTURE_HPP_INCLUDED + +// #included from: catch_result_builder.h +#define TWOBLUECUBES_CATCH_RESULT_BUILDER_H_INCLUDED + +// #included from: catch_result_type.h +#define TWOBLUECUBES_CATCH_RESULT_TYPE_H_INCLUDED + +namespace Catch { + + // ResultWas::OfType enum + struct ResultWas { enum OfType { + Unknown = -1, + Ok = 0, + Info = 1, + Warning = 2, + + FailureBit = 0x10, + + ExpressionFailed = FailureBit | 1, + ExplicitFailure = FailureBit | 2, + + Exception = 0x100 | FailureBit, + + ThrewException = Exception | 1, + DidntThrowException = Exception | 2, + + FatalErrorCondition = 0x200 | FailureBit + + }; }; + + inline bool isOk( ResultWas::OfType resultType ) { + return ( resultType & ResultWas::FailureBit ) == 0; + } + inline bool isJustInfo( int flags ) { + return flags == ResultWas::Info; + } + + // ResultDisposition::Flags enum + struct ResultDisposition { enum Flags { + Normal = 0x00, + + ContinueOnFailure = 0x01, // Failures fail test, but execution continues + FalseTest = 0x02, // Prefix expression with ! + SuppressFail = 0x04 // Failures are reported but do not fail the test + }; }; + + inline ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) { + return static_cast( static_cast( lhs ) | static_cast( rhs ) ); + } + + inline bool shouldContinueOnFailure( int flags ) { return ( flags & ResultDisposition::ContinueOnFailure ) != 0; } + inline bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; } + inline bool shouldSuppressFailure( int flags ) { return ( flags & ResultDisposition::SuppressFail ) != 0; } + +} // end namespace Catch + +// #included from: catch_assertionresult.h +#define TWOBLUECUBES_CATCH_ASSERTIONRESULT_H_INCLUDED + +#include + +namespace Catch { + + struct AssertionInfo + { + AssertionInfo() {} + AssertionInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + std::string const& _capturedExpression, + ResultDisposition::Flags _resultDisposition ); + + std::string macroName; + SourceLineInfo lineInfo; + std::string capturedExpression; + ResultDisposition::Flags resultDisposition; + }; + + struct AssertionResultData + { + AssertionResultData() : resultType( ResultWas::Unknown ) {} + + std::string reconstructedExpression; + std::string message; + ResultWas::OfType resultType; + }; + + class AssertionResult { + public: + AssertionResult(); + AssertionResult( AssertionInfo const& info, AssertionResultData const& data ); + ~AssertionResult(); +# ifdef CATCH_CPP11_OR_GREATER + AssertionResult( AssertionResult const& ) = default; + AssertionResult( AssertionResult && ) = default; + AssertionResult& operator = ( AssertionResult const& ) = default; + AssertionResult& operator = ( AssertionResult && ) = default; +# endif + + bool isOk() const; + bool succeeded() const; + ResultWas::OfType getResultType() const; + bool hasExpression() const; + bool hasMessage() const; + std::string getExpression() const; + std::string getExpressionInMacro() const; + bool hasExpandedExpression() const; + std::string getExpandedExpression() const; + std::string getMessage() const; + SourceLineInfo getSourceInfo() const; + std::string getTestMacroName() const; + + protected: + AssertionInfo m_info; + AssertionResultData m_resultData; + }; + +} // end namespace Catch + +namespace Catch { + + struct TestFailureException{}; + + template class ExpressionLhs; + + struct STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison; + + struct CopyableStream { + CopyableStream() {} + CopyableStream( CopyableStream const& other ) { + oss << other.oss.str(); + } + CopyableStream& operator=( CopyableStream const& other ) { + oss.str(""); + oss << other.oss.str(); + return *this; + } + std::ostringstream oss; + }; + + class ResultBuilder { + public: + ResultBuilder( char const* macroName, + SourceLineInfo const& lineInfo, + char const* capturedExpression, + ResultDisposition::Flags resultDisposition ); + + template + ExpressionLhs operator->* ( T const& operand ); + ExpressionLhs operator->* ( bool value ); + + template + ResultBuilder& operator << ( T const& value ) { + m_stream.oss << value; + return *this; + } + + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( RhsT const& ); + + ResultBuilder& setResultType( ResultWas::OfType result ); + ResultBuilder& setResultType( bool result ); + ResultBuilder& setLhs( std::string const& lhs ); + ResultBuilder& setRhs( std::string const& rhs ); + ResultBuilder& setOp( std::string const& op ); + + void endExpression(); + + std::string reconstructExpression() const; + AssertionResult build() const; + + void useActiveException( ResultDisposition::Flags resultDisposition = ResultDisposition::Normal ); + void captureResult( ResultWas::OfType resultType ); + void captureExpression(); + void react(); + bool shouldDebugBreak() const; + bool allowThrows() const; + + private: + AssertionInfo m_assertionInfo; + AssertionResultData m_data; + struct ExprComponents { + ExprComponents() : testFalse( false ) {} + bool testFalse; + std::string lhs, rhs, op; + } m_exprComponents; + CopyableStream m_stream; + + bool m_shouldDebugBreak; + bool m_shouldThrow; + }; + +} // namespace Catch + +// Include after due to circular dependency: +// #included from: catch_expression_lhs.hpp +#define TWOBLUECUBES_CATCH_EXPRESSION_LHS_HPP_INCLUDED + +// #included from: catch_evaluate.hpp +#define TWOBLUECUBES_CATCH_EVALUATE_HPP_INCLUDED + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4389) // '==' : signed/unsigned mismatch +#endif + +#include + +namespace Catch { +namespace Internal { + + enum Operator { + IsEqualTo, + IsNotEqualTo, + IsLessThan, + IsGreaterThan, + IsLessThanOrEqualTo, + IsGreaterThanOrEqualTo + }; + + template struct OperatorTraits { static const char* getName(){ return "*error*"; } }; + template<> struct OperatorTraits { static const char* getName(){ return "=="; } }; + template<> struct OperatorTraits { static const char* getName(){ return "!="; } }; + template<> struct OperatorTraits { static const char* getName(){ return "<"; } }; + template<> struct OperatorTraits { static const char* getName(){ return ">"; } }; + template<> struct OperatorTraits { static const char* getName(){ return "<="; } }; + template<> struct OperatorTraits{ static const char* getName(){ return ">="; } }; + + template + inline T& opCast(T const& t) { return const_cast(t); } + +// nullptr_t support based on pull request #154 from Konstantin Baumann +#ifdef CATCH_CONFIG_CPP11_NULLPTR + inline std::nullptr_t opCast(std::nullptr_t) { return nullptr; } +#endif // CATCH_CONFIG_CPP11_NULLPTR + + // So the compare overloads can be operator agnostic we convey the operator as a template + // enum, which is used to specialise an Evaluator for doing the comparison. + template + class Evaluator{}; + + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs) { + return opCast( lhs ) == opCast( rhs ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return opCast( lhs ) != opCast( rhs ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return opCast( lhs ) < opCast( rhs ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return opCast( lhs ) > opCast( rhs ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return opCast( lhs ) >= opCast( rhs ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return opCast( lhs ) <= opCast( rhs ); + } + }; + + template + bool applyEvaluator( T1 const& lhs, T2 const& rhs ) { + return Evaluator::evaluate( lhs, rhs ); + } + + // This level of indirection allows us to specialise for integer types + // to avoid signed/ unsigned warnings + + // "base" overload + template + bool compare( T1 const& lhs, T2 const& rhs ) { + return Evaluator::evaluate( lhs, rhs ); + } + + // unsigned X to int + template bool compare( unsigned int lhs, int rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned long lhs, int rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned char lhs, int rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + + // unsigned X to long + template bool compare( unsigned int lhs, long rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned long lhs, long rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned char lhs, long rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + + // int to unsigned X + template bool compare( int lhs, unsigned int rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( int lhs, unsigned long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( int lhs, unsigned char rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + + // long to unsigned X + template bool compare( long lhs, unsigned int rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( long lhs, unsigned long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( long lhs, unsigned char rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + + // pointer to long (when comparing against NULL) + template bool compare( long lhs, T* rhs ) { + return Evaluator::evaluate( reinterpret_cast( lhs ), rhs ); + } + template bool compare( T* lhs, long rhs ) { + return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); + } + + // pointer to int (when comparing against NULL) + template bool compare( int lhs, T* rhs ) { + return Evaluator::evaluate( reinterpret_cast( lhs ), rhs ); + } + template bool compare( T* lhs, int rhs ) { + return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); + } + +#ifdef CATCH_CONFIG_CPP11_NULLPTR + // pointer to nullptr_t (when comparing against nullptr) + template bool compare( std::nullptr_t, T* rhs ) { + return Evaluator::evaluate( NULL, rhs ); + } + template bool compare( T* lhs, std::nullptr_t ) { + return Evaluator::evaluate( lhs, NULL ); + } +#endif // CATCH_CONFIG_CPP11_NULLPTR + +} // end of namespace Internal +} // end of namespace Catch + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +// #included from: catch_tostring.h +#define TWOBLUECUBES_CATCH_TOSTRING_H_INCLUDED + +// #included from: catch_sfinae.hpp +#define TWOBLUECUBES_CATCH_SFINAE_HPP_INCLUDED + +// Try to detect if the current compiler supports SFINAE + +namespace Catch { + + struct TrueType { + static const bool value = true; + typedef void Enable; + char sizer[1]; + }; + struct FalseType { + static const bool value = false; + typedef void Disable; + char sizer[2]; + }; + +#ifdef CATCH_CONFIG_SFINAE + + template struct NotABooleanExpression; + + template struct If : NotABooleanExpression {}; + template<> struct If : TrueType {}; + template<> struct If : FalseType {}; + + template struct SizedIf; + template<> struct SizedIf : TrueType {}; + template<> struct SizedIf : FalseType {}; + +#endif // CATCH_CONFIG_SFINAE + +} // end namespace Catch + +#include +#include +#include +#include +#include + +#ifdef __OBJC__ +// #included from: catch_objc_arc.hpp +#define TWOBLUECUBES_CATCH_OBJC_ARC_HPP_INCLUDED + +#import + +#ifdef __has_feature +#define CATCH_ARC_ENABLED __has_feature(objc_arc) +#else +#define CATCH_ARC_ENABLED 0 +#endif + +void arcSafeRelease( NSObject* obj ); +id performOptionalSelector( id obj, SEL sel ); + +#if !CATCH_ARC_ENABLED +inline void arcSafeRelease( NSObject* obj ) { + [obj release]; +} +inline id performOptionalSelector( id obj, SEL sel ) { + if( [obj respondsToSelector: sel] ) + return [obj performSelector: sel]; + return nil; +} +#define CATCH_UNSAFE_UNRETAINED +#define CATCH_ARC_STRONG +#else +inline void arcSafeRelease( NSObject* ){} +inline id performOptionalSelector( id obj, SEL sel ) { +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" +#endif + if( [obj respondsToSelector: sel] ) + return [obj performSelector: sel]; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + return nil; +} +#define CATCH_UNSAFE_UNRETAINED __unsafe_unretained +#define CATCH_ARC_STRONG __strong +#endif + +#endif + +#ifdef CATCH_CPP11_OR_GREATER +#include +#include +#endif + +namespace Catch { + +// Why we're here. +template +std::string toString( T const& value ); + +// Built in overloads + +std::string toString( std::string const& value ); +std::string toString( std::wstring const& value ); +std::string toString( const char* const value ); +std::string toString( char* const value ); +std::string toString( const wchar_t* const value ); +std::string toString( wchar_t* const value ); +std::string toString( int value ); +std::string toString( unsigned long value ); +std::string toString( unsigned int value ); +std::string toString( const double value ); +std::string toString( const float value ); +std::string toString( bool value ); +std::string toString( char value ); +std::string toString( signed char value ); +std::string toString( unsigned char value ); + +#ifdef CATCH_CONFIG_CPP11_NULLPTR +std::string toString( std::nullptr_t ); +#endif + +#ifdef __OBJC__ + std::string toString( NSString const * const& nsstring ); + std::string toString( NSString * CATCH_ARC_STRONG const& nsstring ); + std::string toString( NSObject* const& nsObject ); +#endif + +namespace Detail { + + extern std::string unprintableString; + +// SFINAE is currently disabled by default for all compilers. +// If the non SFINAE version of IsStreamInsertable is ambiguous for you +// and your compiler supports SFINAE, try #defining CATCH_CONFIG_SFINAE +#ifdef CATCH_CONFIG_SFINAE + + template + class IsStreamInsertableHelper { + template struct TrueIfSizeable : TrueType {}; + + template + static TrueIfSizeable dummy(T2*); + static FalseType dummy(...); + + public: + typedef SizedIf type; + }; + + template + struct IsStreamInsertable : IsStreamInsertableHelper::type {}; + +#else + + struct BorgType { + template BorgType( T const& ); + }; + + TrueType& testStreamable( std::ostream& ); + FalseType testStreamable( FalseType ); + + FalseType operator<<( std::ostream const&, BorgType const& ); + + template + struct IsStreamInsertable { + static std::ostream &s; + static T const&t; + enum { value = sizeof( testStreamable(s << t) ) == sizeof( TrueType ) }; + }; + +#endif + +#if defined(CATCH_CPP11_OR_GREATER) + template::value + > + struct EnumStringMaker + { + static std::string convert( T const& ) { return unprintableString; } + }; + + template + struct EnumStringMaker + { + static std::string convert( T const& v ) + { + return ::Catch::toString( + static_cast::type>(v) + ); + } + }; +#endif + template + struct StringMakerBase { +#if defined(CATCH_CPP11_OR_GREATER) + template + static std::string convert( T const& v ) + { + return EnumStringMaker::convert( v ); + } +#else + template + static std::string convert( T const& ) { return unprintableString; } +#endif + }; + + template<> + struct StringMakerBase { + template + static std::string convert( T const& _value ) { + std::ostringstream oss; + oss << _value; + return oss.str(); + } + }; + + std::string rawMemoryToString( const void *object, std::size_t size ); + + template + inline std::string rawMemoryToString( const T& object ) { + return rawMemoryToString( &object, sizeof(object) ); + } + +} // end namespace Detail + +template +struct StringMaker : + Detail::StringMakerBase::value> {}; + +template +struct StringMaker { + template + static std::string convert( U* p ) { + if( !p ) + return INTERNAL_CATCH_STRINGIFY( NULL ); + else + return Detail::rawMemoryToString( p ); + } +}; + +template +struct StringMaker { + static std::string convert( R C::* p ) { + if( !p ) + return INTERNAL_CATCH_STRINGIFY( NULL ); + else + return Detail::rawMemoryToString( p ); + } +}; + +namespace Detail { + template + std::string rangeToString( InputIterator first, InputIterator last ); +} + +//template +//struct StringMaker > { +// static std::string convert( std::vector const& v ) { +// return Detail::rangeToString( v.begin(), v.end() ); +// } +//}; + +template +std::string toString( std::vector const& v ) { + return Detail::rangeToString( v.begin(), v.end() ); +} + +#ifdef CATCH_CPP11_OR_GREATER + +// toString for tuples +namespace TupleDetail { + template< + typename Tuple, + std::size_t N = 0, + bool = (N < std::tuple_size::value) + > + struct ElementPrinter { + static void print( const Tuple& tuple, std::ostream& os ) + { + os << ( N ? ", " : " " ) + << Catch::toString(std::get(tuple)); + ElementPrinter::print(tuple,os); + } + }; + + template< + typename Tuple, + std::size_t N + > + struct ElementPrinter { + static void print( const Tuple&, std::ostream& ) {} + }; + +} + +template +struct StringMaker> { + + static std::string convert( const std::tuple& tuple ) + { + std::ostringstream os; + os << '{'; + TupleDetail::ElementPrinter>::print( tuple, os ); + os << " }"; + return os.str(); + } +}; +#endif + +namespace Detail { + template + std::string makeString( T const& value ) { + return StringMaker::convert( value ); + } +} // end namespace Detail + +/// \brief converts any type to a string +/// +/// The default template forwards on to ostringstream - except when an +/// ostringstream overload does not exist - in which case it attempts to detect +/// that and writes {?}. +/// Overload (not specialise) this template for custom typs that you don't want +/// to provide an ostream overload for. +template +std::string toString( T const& value ) { + return StringMaker::convert( value ); +} + + namespace Detail { + template + std::string rangeToString( InputIterator first, InputIterator last ) { + std::ostringstream oss; + oss << "{ "; + if( first != last ) { + oss << Catch::toString( *first ); + for( ++first ; first != last ; ++first ) + oss << ", " << Catch::toString( *first ); + } + oss << " }"; + return oss.str(); + } +} + +} // end namespace Catch + +namespace Catch { + +// Wraps the LHS of an expression and captures the operator and RHS (if any) - +// wrapping them all in a ResultBuilder object +template +class ExpressionLhs { + ExpressionLhs& operator = ( ExpressionLhs const& ); +# ifdef CATCH_CPP11_OR_GREATER + ExpressionLhs& operator = ( ExpressionLhs && ) = delete; +# endif + +public: + ExpressionLhs( ResultBuilder& rb, T lhs ) : m_rb( rb ), m_lhs( lhs ) {} +# ifdef CATCH_CPP11_OR_GREATER + ExpressionLhs( ExpressionLhs const& ) = default; + ExpressionLhs( ExpressionLhs && ) = default; +# endif + + template + ResultBuilder& operator == ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + ResultBuilder& operator != ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + ResultBuilder& operator < ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + ResultBuilder& operator > ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + ResultBuilder& operator <= ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + ResultBuilder& operator >= ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + ResultBuilder& operator == ( bool rhs ) { + return captureExpression( rhs ); + } + + ResultBuilder& operator != ( bool rhs ) { + return captureExpression( rhs ); + } + + void endExpression() { + bool value = m_lhs ? true : false; + m_rb + .setLhs( Catch::toString( value ) ) + .setResultType( value ) + .endExpression(); + } + + // Only simple binary expressions are allowed on the LHS. + // If more complex compositions are required then place the sub expression in parentheses + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator + ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator - ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator / ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator * ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( RhsT const& ); + +private: + template + ResultBuilder& captureExpression( RhsT const& rhs ) { + return m_rb + .setResultType( Internal::compare( m_lhs, rhs ) ) + .setLhs( Catch::toString( m_lhs ) ) + .setRhs( Catch::toString( rhs ) ) + .setOp( Internal::OperatorTraits::getName() ); + } + +private: + ResultBuilder& m_rb; + T m_lhs; +}; + +} // end namespace Catch + + +namespace Catch { + + template + inline ExpressionLhs ResultBuilder::operator->* ( T const& operand ) { + return ExpressionLhs( *this, operand ); + } + + inline ExpressionLhs ResultBuilder::operator->* ( bool value ) { + return ExpressionLhs( *this, value ); + } + +} // namespace Catch + +// #included from: catch_message.h +#define TWOBLUECUBES_CATCH_MESSAGE_H_INCLUDED + +#include + +namespace Catch { + + struct MessageInfo { + MessageInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ); + + std::string macroName; + SourceLineInfo lineInfo; + ResultWas::OfType type; + std::string message; + unsigned int sequence; + + bool operator == ( MessageInfo const& other ) const { + return sequence == other.sequence; + } + bool operator < ( MessageInfo const& other ) const { + return sequence < other.sequence; + } + private: + static unsigned int globalCount; + }; + + struct MessageBuilder { + MessageBuilder( std::string const& macroName, + SourceLineInfo const& lineInfo, + ResultWas::OfType type ) + : m_info( macroName, lineInfo, type ) + {} + + template + MessageBuilder& operator << ( T const& value ) { + m_stream << value; + return *this; + } + + MessageInfo m_info; + std::ostringstream m_stream; + }; + + class ScopedMessage { + public: + ScopedMessage( MessageBuilder const& builder ); + ScopedMessage( ScopedMessage const& other ); + ~ScopedMessage(); + + MessageInfo m_info; + }; + +} // end namespace Catch + +// #included from: catch_interfaces_capture.h +#define TWOBLUECUBES_CATCH_INTERFACES_CAPTURE_H_INCLUDED + +#include + +namespace Catch { + + class TestCase; + class AssertionResult; + struct AssertionInfo; + struct SectionInfo; + struct MessageInfo; + class ScopedMessageBuilder; + struct Counts; + + struct IResultCapture { + + virtual ~IResultCapture(); + + virtual void assertionEnded( AssertionResult const& result ) = 0; + virtual bool sectionStarted( SectionInfo const& sectionInfo, + Counts& assertions ) = 0; + virtual void sectionEnded( SectionInfo const& name, Counts const& assertions, double _durationInSeconds ) = 0; + virtual void pushScopedMessage( MessageInfo const& message ) = 0; + virtual void popScopedMessage( MessageInfo const& message ) = 0; + + virtual std::string getCurrentTestName() const = 0; + virtual const AssertionResult* getLastResult() const = 0; + + virtual void handleFatalErrorCondition( std::string const& message ) = 0; + }; + + IResultCapture& getResultCapture(); +} + +// #included from: catch_debugger.h +#define TWOBLUECUBES_CATCH_DEBUGGER_H_INCLUDED + +// #included from: catch_platform.h +#define TWOBLUECUBES_CATCH_PLATFORM_H_INCLUDED + +#if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) +#define CATCH_PLATFORM_MAC +#elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED) +#define CATCH_PLATFORM_IPHONE +#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) +#define CATCH_PLATFORM_WINDOWS +#endif + +#include + +namespace Catch{ + + bool isDebuggerActive(); + void writeToDebugConsole( std::string const& text ); +} + +#ifdef CATCH_PLATFORM_MAC + + // The following code snippet based on: + // http://cocoawithlove.com/2008/03/break-into-debugger.html + #ifdef DEBUG + #if defined(__ppc64__) || defined(__ppc__) + #define CATCH_BREAK_INTO_DEBUGGER() \ + if( Catch::isDebuggerActive() ) { \ + __asm__("li r0, 20\nsc\nnop\nli r0, 37\nli r4, 2\nsc\nnop\n" \ + : : : "memory","r0","r3","r4" ); \ + } + #else + #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) {__asm__("int $3\n" : : );} + #endif + #endif + +#elif defined(_MSC_VER) + #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { __debugbreak(); } +#elif defined(__MINGW32__) + extern "C" __declspec(dllimport) void __stdcall DebugBreak(); + #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { DebugBreak(); } +#endif + +#ifndef CATCH_BREAK_INTO_DEBUGGER +#define CATCH_BREAK_INTO_DEBUGGER() Catch::alwaysTrue(); +#endif + +// #included from: catch_interfaces_runner.h +#define TWOBLUECUBES_CATCH_INTERFACES_RUNNER_H_INCLUDED + +namespace Catch { + class TestCase; + + struct IRunner { + virtual ~IRunner(); + virtual bool aborting() const = 0; + }; +} + +/////////////////////////////////////////////////////////////////////////////// +// In the event of a failure works out if the debugger needs to be invoked +// and/or an exception thrown and takes appropriate action. +// This needs to be done as a macro so the debugger will stop in the user +// source code rather than in Catch library code +#define INTERNAL_CATCH_REACT( resultBuilder ) \ + if( resultBuilder.shouldDebugBreak() ) CATCH_BREAK_INTO_DEBUGGER(); \ + resultBuilder.react(); + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + try { \ + ( __catchResult->*expr ).endExpression(); \ + } \ + catch( ... ) { \ + __catchResult.useActiveException( Catch::ResultDisposition::Normal ); \ + } \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::isTrue( false && (expr) ) ) // expr here is never evaluated at runtime but it forces the compiler to give it a look + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_IF( expr, resultDisposition, macroName ) \ + INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ); \ + if( Catch::getResultCapture().getLastResult()->succeeded() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_ELSE( expr, resultDisposition, macroName ) \ + INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ); \ + if( !Catch::getResultCapture().getLastResult()->succeeded() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_NO_THROW( expr, resultDisposition, macroName ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + try { \ + expr; \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + } \ + catch( ... ) { \ + __catchResult.useActiveException( resultDisposition ); \ + } \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS( expr, resultDisposition, macroName ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + if( __catchResult.allowThrows() ) \ + try { \ + expr; \ + __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \ + } \ + catch( ... ) { \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + } \ + else \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS_AS( expr, exceptionType, resultDisposition, macroName ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + if( __catchResult.allowThrows() ) \ + try { \ + expr; \ + __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \ + } \ + catch( exceptionType ) { \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + } \ + catch( ... ) { \ + __catchResult.useActiveException( resultDisposition ); \ + } \ + else \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +/////////////////////////////////////////////////////////////////////////////// +#ifdef CATCH_CONFIG_VARIADIC_MACROS + #define INTERNAL_CATCH_MSG( messageType, resultDisposition, macroName, ... ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ + __catchResult << __VA_ARGS__ + ::Catch::StreamEndStop(); \ + __catchResult.captureResult( messageType ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) +#else + #define INTERNAL_CATCH_MSG( messageType, resultDisposition, macroName, log ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ + __catchResult << log + ::Catch::StreamEndStop(); \ + __catchResult.captureResult( messageType ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) +#endif + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_INFO( log, macroName ) \ + Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage ) = Catch::MessageBuilder( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log; + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CHECK_THAT( arg, matcher, resultDisposition, macroName ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #arg " " #matcher, resultDisposition ); \ + try { \ + std::string matcherAsString = ::Catch::Matchers::matcher.toString(); \ + __catchResult \ + .setLhs( Catch::toString( arg ) ) \ + .setRhs( matcherAsString == Catch::Detail::unprintableString ? #matcher : matcherAsString ) \ + .setOp( "matches" ) \ + .setResultType( ::Catch::Matchers::matcher.match( arg ) ); \ + __catchResult.captureExpression(); \ + } catch( ... ) { \ + __catchResult.useActiveException( resultDisposition | Catch::ResultDisposition::ContinueOnFailure ); \ + } \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +// #included from: internal/catch_section.h +#define TWOBLUECUBES_CATCH_SECTION_H_INCLUDED + +// #included from: catch_section_info.h +#define TWOBLUECUBES_CATCH_SECTION_INFO_H_INCLUDED + +namespace Catch { + + struct SectionInfo { + SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name, + std::string const& _description = std::string() ); + + std::string name; + std::string description; + SourceLineInfo lineInfo; + }; + +} // end namespace Catch + +// #included from: catch_totals.hpp +#define TWOBLUECUBES_CATCH_TOTALS_HPP_INCLUDED + +#include + +namespace Catch { + + struct Counts { + Counts() : passed( 0 ), failed( 0 ), failedButOk( 0 ) {} + + Counts operator - ( Counts const& other ) const { + Counts diff; + diff.passed = passed - other.passed; + diff.failed = failed - other.failed; + diff.failedButOk = failedButOk - other.failedButOk; + return diff; + } + Counts& operator += ( Counts const& other ) { + passed += other.passed; + failed += other.failed; + failedButOk += other.failedButOk; + return *this; + } + + std::size_t total() const { + return passed + failed + failedButOk; + } + bool allPassed() const { + return failed == 0 && failedButOk == 0; + } + bool allOk() const { + return failed == 0; + } + + std::size_t passed; + std::size_t failed; + std::size_t failedButOk; + }; + + struct Totals { + + Totals operator - ( Totals const& other ) const { + Totals diff; + diff.assertions = assertions - other.assertions; + diff.testCases = testCases - other.testCases; + return diff; + } + + Totals delta( Totals const& prevTotals ) const { + Totals diff = *this - prevTotals; + if( diff.assertions.failed > 0 ) + ++diff.testCases.failed; + else if( diff.assertions.failedButOk > 0 ) + ++diff.testCases.failedButOk; + else + ++diff.testCases.passed; + return diff; + } + + Totals& operator += ( Totals const& other ) { + assertions += other.assertions; + testCases += other.testCases; + return *this; + } + + Counts assertions; + Counts testCases; + }; +} + +// #included from: catch_timer.h +#define TWOBLUECUBES_CATCH_TIMER_H_INCLUDED + +#ifdef CATCH_PLATFORM_WINDOWS +typedef unsigned long long uint64_t; +#else +#include +#endif + +namespace Catch { + + class Timer { + public: + Timer() : m_ticks( 0 ) {} + void start(); + unsigned int getElapsedMicroseconds() const; + unsigned int getElapsedMilliseconds() const; + double getElapsedSeconds() const; + + private: + uint64_t m_ticks; + }; + +} // namespace Catch + +#include + +namespace Catch { + + class Section : NonCopyable { + public: + Section( SectionInfo const& info ); + ~Section(); + + // This indicates whether the section should be executed or not + operator bool() const; + + private: + SectionInfo m_info; + + std::string m_name; + Counts m_assertions; + bool m_sectionIncluded; + Timer m_timer; + }; + +} // end namespace Catch + +#ifdef CATCH_CONFIG_VARIADIC_MACROS + #define INTERNAL_CATCH_SECTION( ... ) \ + if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) ) +#else + #define INTERNAL_CATCH_SECTION( name, desc ) \ + if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, name, desc ) ) +#endif + +// #included from: internal/catch_generators.hpp +#define TWOBLUECUBES_CATCH_GENERATORS_HPP_INCLUDED + +#include +#include +#include +#include + +namespace Catch { + +template +struct IGenerator { + virtual ~IGenerator() {} + virtual T getValue( std::size_t index ) const = 0; + virtual std::size_t size () const = 0; +}; + +template +class BetweenGenerator : public IGenerator { +public: + BetweenGenerator( T from, T to ) : m_from( from ), m_to( to ){} + + virtual T getValue( std::size_t index ) const { + return m_from+static_cast( index ); + } + + virtual std::size_t size() const { + return static_cast( 1+m_to-m_from ); + } + +private: + + T m_from; + T m_to; +}; + +template +class ValuesGenerator : public IGenerator { +public: + ValuesGenerator(){} + + void add( T value ) { + m_values.push_back( value ); + } + + virtual T getValue( std::size_t index ) const { + return m_values[index]; + } + + virtual std::size_t size() const { + return m_values.size(); + } + +private: + std::vector m_values; +}; + +template +class CompositeGenerator { +public: + CompositeGenerator() : m_totalSize( 0 ) {} + + // *** Move semantics, similar to auto_ptr *** + CompositeGenerator( CompositeGenerator& other ) + : m_fileInfo( other.m_fileInfo ), + m_totalSize( 0 ) + { + move( other ); + } + + CompositeGenerator& setFileInfo( const char* fileInfo ) { + m_fileInfo = fileInfo; + return *this; + } + + ~CompositeGenerator() { + deleteAll( m_composed ); + } + + operator T () const { + size_t overallIndex = getCurrentContext().getGeneratorIndex( m_fileInfo, m_totalSize ); + + typename std::vector*>::const_iterator it = m_composed.begin(); + typename std::vector*>::const_iterator itEnd = m_composed.end(); + for( size_t index = 0; it != itEnd; ++it ) + { + const IGenerator* generator = *it; + if( overallIndex >= index && overallIndex < index + generator->size() ) + { + return generator->getValue( overallIndex-index ); + } + index += generator->size(); + } + CATCH_INTERNAL_ERROR( "Indexed past end of generated range" ); + return T(); // Suppress spurious "not all control paths return a value" warning in Visual Studio - if you know how to fix this please do so + } + + void add( const IGenerator* generator ) { + m_totalSize += generator->size(); + m_composed.push_back( generator ); + } + + CompositeGenerator& then( CompositeGenerator& other ) { + move( other ); + return *this; + } + + CompositeGenerator& then( T value ) { + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( value ); + add( valuesGen ); + return *this; + } + +private: + + void move( CompositeGenerator& other ) { + std::copy( other.m_composed.begin(), other.m_composed.end(), std::back_inserter( m_composed ) ); + m_totalSize += other.m_totalSize; + other.m_composed.clear(); + } + + std::vector*> m_composed; + std::string m_fileInfo; + size_t m_totalSize; +}; + +namespace Generators +{ + template + CompositeGenerator between( T from, T to ) { + CompositeGenerator generators; + generators.add( new BetweenGenerator( from, to ) ); + return generators; + } + + template + CompositeGenerator values( T val1, T val2 ) { + CompositeGenerator generators; + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( val1 ); + valuesGen->add( val2 ); + generators.add( valuesGen ); + return generators; + } + + template + CompositeGenerator values( T val1, T val2, T val3 ){ + CompositeGenerator generators; + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( val1 ); + valuesGen->add( val2 ); + valuesGen->add( val3 ); + generators.add( valuesGen ); + return generators; + } + + template + CompositeGenerator values( T val1, T val2, T val3, T val4 ) { + CompositeGenerator generators; + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( val1 ); + valuesGen->add( val2 ); + valuesGen->add( val3 ); + valuesGen->add( val4 ); + generators.add( valuesGen ); + return generators; + } + +} // end namespace Generators + +using namespace Generators; + +} // end namespace Catch + +#define INTERNAL_CATCH_LINESTR2( line ) #line +#define INTERNAL_CATCH_LINESTR( line ) INTERNAL_CATCH_LINESTR2( line ) + +#define INTERNAL_CATCH_GENERATE( expr ) expr.setFileInfo( __FILE__ "(" INTERNAL_CATCH_LINESTR( __LINE__ ) ")" ) + +// #included from: internal/catch_interfaces_exception.h +#define TWOBLUECUBES_CATCH_INTERFACES_EXCEPTION_H_INCLUDED + +#include +// #included from: catch_interfaces_registry_hub.h +#define TWOBLUECUBES_CATCH_INTERFACES_REGISTRY_HUB_H_INCLUDED + +#include + +namespace Catch { + + class TestCase; + struct ITestCaseRegistry; + struct IExceptionTranslatorRegistry; + struct IExceptionTranslator; + struct IReporterRegistry; + struct IReporterFactory; + + struct IRegistryHub { + virtual ~IRegistryHub(); + + virtual IReporterRegistry const& getReporterRegistry() const = 0; + virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0; + virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() = 0; + }; + + struct IMutableRegistryHub { + virtual ~IMutableRegistryHub(); + virtual void registerReporter( std::string const& name, IReporterFactory* factory ) = 0; + virtual void registerTest( TestCase const& testInfo ) = 0; + virtual void registerTranslator( const IExceptionTranslator* translator ) = 0; + }; + + IRegistryHub& getRegistryHub(); + IMutableRegistryHub& getMutableRegistryHub(); + void cleanUp(); + std::string translateActiveException(); + +} + + +namespace Catch { + + typedef std::string(*exceptionTranslateFunction)(); + + struct IExceptionTranslator { + virtual ~IExceptionTranslator(); + virtual std::string translate() const = 0; + }; + + struct IExceptionTranslatorRegistry { + virtual ~IExceptionTranslatorRegistry(); + + virtual std::string translateActiveException() const = 0; + }; + + class ExceptionTranslatorRegistrar { + template + class ExceptionTranslator : public IExceptionTranslator { + public: + + ExceptionTranslator( std::string(*translateFunction)( T& ) ) + : m_translateFunction( translateFunction ) + {} + + virtual std::string translate() const { + try { + throw; + } + catch( T& ex ) { + return m_translateFunction( ex ); + } + } + + protected: + std::string(*m_translateFunction)( T& ); + }; + + public: + template + ExceptionTranslatorRegistrar( std::string(*translateFunction)( T& ) ) { + getMutableRegistryHub().registerTranslator + ( new ExceptionTranslator( translateFunction ) ); + } + }; +} + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) \ + static std::string INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator )( signature ); \ + namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ) ); }\ + static std::string INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator )( signature ) + +// #included from: internal/catch_approx.hpp +#define TWOBLUECUBES_CATCH_APPROX_HPP_INCLUDED + +#include +#include + +namespace Catch { +namespace Detail { + + class Approx { + public: + explicit Approx ( double value ) + : m_epsilon( std::numeric_limits::epsilon()*100 ), + m_scale( 1.0 ), + m_value( value ) + {} + + Approx( Approx const& other ) + : m_epsilon( other.m_epsilon ), + m_scale( other.m_scale ), + m_value( other.m_value ) + {} + + static Approx custom() { + return Approx( 0 ); + } + + Approx operator()( double value ) { + Approx approx( value ); + approx.epsilon( m_epsilon ); + approx.scale( m_scale ); + return approx; + } + + friend bool operator == ( double lhs, Approx const& rhs ) { + // Thanks to Richard Harris for his help refining this formula + return fabs( lhs - rhs.m_value ) < rhs.m_epsilon * (rhs.m_scale + (std::max)( fabs(lhs), fabs(rhs.m_value) ) ); + } + + friend bool operator == ( Approx const& lhs, double rhs ) { + return operator==( rhs, lhs ); + } + + friend bool operator != ( double lhs, Approx const& rhs ) { + return !operator==( lhs, rhs ); + } + + friend bool operator != ( Approx const& lhs, double rhs ) { + return !operator==( rhs, lhs ); + } + + Approx& epsilon( double newEpsilon ) { + m_epsilon = newEpsilon; + return *this; + } + + Approx& scale( double newScale ) { + m_scale = newScale; + return *this; + } + + std::string toString() const { + std::ostringstream oss; + oss << "Approx( " << Catch::toString( m_value ) << " )"; + return oss.str(); + } + + private: + double m_epsilon; + double m_scale; + double m_value; + }; +} + +template<> +inline std::string toString( Detail::Approx const& value ) { + return value.toString(); +} + +} // end namespace Catch + +// #included from: internal/catch_matchers.hpp +#define TWOBLUECUBES_CATCH_MATCHERS_HPP_INCLUDED + +namespace Catch { +namespace Matchers { + namespace Impl { + + template + struct Matcher : SharedImpl + { + typedef ExpressionT ExpressionType; + + virtual ~Matcher() {} + virtual Ptr clone() const = 0; + virtual bool match( ExpressionT const& expr ) const = 0; + virtual std::string toString() const = 0; + }; + + template + struct MatcherImpl : Matcher { + + virtual Ptr > clone() const { + return Ptr >( new DerivedT( static_cast( *this ) ) ); + } + }; + + namespace Generic { + + template + class AllOf : public MatcherImpl, ExpressionT> { + public: + + AllOf() {} + AllOf( AllOf const& other ) : m_matchers( other.m_matchers ) {} + + AllOf& add( Matcher const& matcher ) { + m_matchers.push_back( matcher.clone() ); + return *this; + } + virtual bool match( ExpressionT const& expr ) const + { + for( std::size_t i = 0; i < m_matchers.size(); ++i ) + if( !m_matchers[i]->match( expr ) ) + return false; + return true; + } + virtual std::string toString() const { + std::ostringstream oss; + oss << "( "; + for( std::size_t i = 0; i < m_matchers.size(); ++i ) { + if( i != 0 ) + oss << " and "; + oss << m_matchers[i]->toString(); + } + oss << " )"; + return oss.str(); + } + + private: + std::vector > > m_matchers; + }; + + template + class AnyOf : public MatcherImpl, ExpressionT> { + public: + + AnyOf() {} + AnyOf( AnyOf const& other ) : m_matchers( other.m_matchers ) {} + + AnyOf& add( Matcher const& matcher ) { + m_matchers.push_back( matcher.clone() ); + return *this; + } + virtual bool match( ExpressionT const& expr ) const + { + for( std::size_t i = 0; i < m_matchers.size(); ++i ) + if( m_matchers[i]->match( expr ) ) + return true; + return false; + } + virtual std::string toString() const { + std::ostringstream oss; + oss << "( "; + for( std::size_t i = 0; i < m_matchers.size(); ++i ) { + if( i != 0 ) + oss << " or "; + oss << m_matchers[i]->toString(); + } + oss << " )"; + return oss.str(); + } + + private: + std::vector > > m_matchers; + }; + + } + + namespace StdString { + + inline std::string makeString( std::string const& str ) { return str; } + inline std::string makeString( const char* str ) { return str ? std::string( str ) : std::string(); } + + struct Equals : MatcherImpl { + Equals( std::string const& str ) : m_str( str ){} + Equals( Equals const& other ) : m_str( other.m_str ){} + + virtual ~Equals(); + + virtual bool match( std::string const& expr ) const { + return m_str == expr; + } + virtual std::string toString() const { + return "equals: \"" + m_str + "\""; + } + + std::string m_str; + }; + + struct Contains : MatcherImpl { + Contains( std::string const& substr ) : m_substr( substr ){} + Contains( Contains const& other ) : m_substr( other.m_substr ){} + + virtual ~Contains(); + + virtual bool match( std::string const& expr ) const { + return expr.find( m_substr ) != std::string::npos; + } + virtual std::string toString() const { + return "contains: \"" + m_substr + "\""; + } + + std::string m_substr; + }; + + struct StartsWith : MatcherImpl { + StartsWith( std::string const& substr ) : m_substr( substr ){} + StartsWith( StartsWith const& other ) : m_substr( other.m_substr ){} + + virtual ~StartsWith(); + + virtual bool match( std::string const& expr ) const { + return expr.find( m_substr ) == 0; + } + virtual std::string toString() const { + return "starts with: \"" + m_substr + "\""; + } + + std::string m_substr; + }; + + struct EndsWith : MatcherImpl { + EndsWith( std::string const& substr ) : m_substr( substr ){} + EndsWith( EndsWith const& other ) : m_substr( other.m_substr ){} + + virtual ~EndsWith(); + + virtual bool match( std::string const& expr ) const { + return expr.find( m_substr ) == expr.size() - m_substr.size(); + } + virtual std::string toString() const { + return "ends with: \"" + m_substr + "\""; + } + + std::string m_substr; + }; + } // namespace StdString + } // namespace Impl + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + template + inline Impl::Generic::AllOf AllOf( Impl::Matcher const& m1, + Impl::Matcher const& m2 ) { + return Impl::Generic::AllOf().add( m1 ).add( m2 ); + } + template + inline Impl::Generic::AllOf AllOf( Impl::Matcher const& m1, + Impl::Matcher const& m2, + Impl::Matcher const& m3 ) { + return Impl::Generic::AllOf().add( m1 ).add( m2 ).add( m3 ); + } + template + inline Impl::Generic::AnyOf AnyOf( Impl::Matcher const& m1, + Impl::Matcher const& m2 ) { + return Impl::Generic::AnyOf().add( m1 ).add( m2 ); + } + template + inline Impl::Generic::AnyOf AnyOf( Impl::Matcher const& m1, + Impl::Matcher const& m2, + Impl::Matcher const& m3 ) { + return Impl::Generic::AnyOf().add( m1 ).add( m2 ).add( m3 ); + } + + inline Impl::StdString::Equals Equals( std::string const& str ) { + return Impl::StdString::Equals( str ); + } + inline Impl::StdString::Equals Equals( const char* str ) { + return Impl::StdString::Equals( Impl::StdString::makeString( str ) ); + } + inline Impl::StdString::Contains Contains( std::string const& substr ) { + return Impl::StdString::Contains( substr ); + } + inline Impl::StdString::Contains Contains( const char* substr ) { + return Impl::StdString::Contains( Impl::StdString::makeString( substr ) ); + } + inline Impl::StdString::StartsWith StartsWith( std::string const& substr ) { + return Impl::StdString::StartsWith( substr ); + } + inline Impl::StdString::StartsWith StartsWith( const char* substr ) { + return Impl::StdString::StartsWith( Impl::StdString::makeString( substr ) ); + } + inline Impl::StdString::EndsWith EndsWith( std::string const& substr ) { + return Impl::StdString::EndsWith( substr ); + } + inline Impl::StdString::EndsWith EndsWith( const char* substr ) { + return Impl::StdString::EndsWith( Impl::StdString::makeString( substr ) ); + } + +} // namespace Matchers + +using namespace Matchers; + +} // namespace Catch + +// #included from: internal/catch_interfaces_tag_alias_registry.h +#define TWOBLUECUBES_CATCH_INTERFACES_TAG_ALIAS_REGISTRY_H_INCLUDED + +// #included from: catch_tag_alias.h +#define TWOBLUECUBES_CATCH_TAG_ALIAS_H_INCLUDED + +#include + +namespace Catch { + + struct TagAlias { + TagAlias( std::string _tag, SourceLineInfo _lineInfo ) : tag( _tag ), lineInfo( _lineInfo ) {} + + std::string tag; + SourceLineInfo lineInfo; + }; + + struct RegistrarForTagAliases { + RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + }; + +} // end namespace Catch + +#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } +// #included from: catch_option.hpp +#define TWOBLUECUBES_CATCH_OPTION_HPP_INCLUDED + +namespace Catch { + + // An optional type + template + class Option { + public: + Option() : nullableValue( NULL ) {} + Option( T const& _value ) + : nullableValue( new( storage ) T( _value ) ) + {} + Option( Option const& _other ) + : nullableValue( _other ? new( storage ) T( *_other ) : NULL ) + {} + + ~Option() { + reset(); + } + + Option& operator= ( Option const& _other ) { + if( &_other != this ) { + reset(); + if( _other ) + nullableValue = new( storage ) T( *_other ); + } + return *this; + } + Option& operator = ( T const& _value ) { + reset(); + nullableValue = new( storage ) T( _value ); + return *this; + } + + void reset() { + if( nullableValue ) + nullableValue->~T(); + nullableValue = NULL; + } + + T& operator*() { return *nullableValue; } + T const& operator*() const { return *nullableValue; } + T* operator->() { return nullableValue; } + const T* operator->() const { return nullableValue; } + + T valueOr( T const& defaultValue ) const { + return nullableValue ? *nullableValue : defaultValue; + } + + bool some() const { return nullableValue != NULL; } + bool none() const { return nullableValue == NULL; } + + bool operator !() const { return nullableValue == NULL; } + operator SafeBool::type() const { + return SafeBool::makeSafe( some() ); + } + + private: + T* nullableValue; + char storage[sizeof(T)]; + }; + +} // end namespace Catch + +namespace Catch { + + struct ITagAliasRegistry { + virtual ~ITagAliasRegistry(); + virtual Option find( std::string const& alias ) const = 0; + virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const = 0; + + static ITagAliasRegistry const& get(); + }; + +} // end namespace Catch + +// These files are included here so the single_include script doesn't put them +// in the conditionally compiled sections +// #included from: internal/catch_test_case_info.h +#define TWOBLUECUBES_CATCH_TEST_CASE_INFO_H_INCLUDED + +#include +#include + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch { + + struct ITestCase; + + struct TestCaseInfo { + enum SpecialProperties{ + None = 0, + IsHidden = 1 << 1, + ShouldFail = 1 << 2, + MayFail = 1 << 3, + Throws = 1 << 4 + }; + + TestCaseInfo( std::string const& _name, + std::string const& _className, + std::string const& _description, + std::set const& _tags, + SourceLineInfo const& _lineInfo ); + + TestCaseInfo( TestCaseInfo const& other ); + + bool isHidden() const; + bool throws() const; + bool okToFail() const; + bool expectedToFail() const; + + std::string name; + std::string className; + std::string description; + std::set tags; + std::set lcaseTags; + std::string tagsAsString; + SourceLineInfo lineInfo; + SpecialProperties properties; + }; + + class TestCase : public TestCaseInfo { + public: + + TestCase( ITestCase* testCase, TestCaseInfo const& info ); + TestCase( TestCase const& other ); + + TestCase withName( std::string const& _newName ) const; + + void invoke() const; + + TestCaseInfo const& getTestCaseInfo() const; + + void swap( TestCase& other ); + bool operator == ( TestCase const& other ) const; + bool operator < ( TestCase const& other ) const; + TestCase& operator = ( TestCase const& other ); + + private: + Ptr test; + }; + + TestCase makeTestCase( ITestCase* testCase, + std::string const& className, + std::string const& name, + std::string const& description, + SourceLineInfo const& lineInfo ); +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + +#ifdef __OBJC__ +// #included from: internal/catch_objc.hpp +#define TWOBLUECUBES_CATCH_OBJC_HPP_INCLUDED + +#import + +#include + +// NB. Any general catch headers included here must be included +// in catch.hpp first to make sure they are included by the single +// header for non obj-usage + +/////////////////////////////////////////////////////////////////////////////// +// This protocol is really only here for (self) documenting purposes, since +// all its methods are optional. +@protocol OcFixture + +@optional + +-(void) setUp; +-(void) tearDown; + +@end + +namespace Catch { + + class OcMethod : public SharedImpl { + + public: + OcMethod( Class cls, SEL sel ) : m_cls( cls ), m_sel( sel ) {} + + virtual void invoke() const { + id obj = [[m_cls alloc] init]; + + performOptionalSelector( obj, @selector(setUp) ); + performOptionalSelector( obj, m_sel ); + performOptionalSelector( obj, @selector(tearDown) ); + + arcSafeRelease( obj ); + } + private: + virtual ~OcMethod() {} + + Class m_cls; + SEL m_sel; + }; + + namespace Detail{ + + inline std::string getAnnotation( Class cls, + std::string const& annotationName, + std::string const& testCaseName ) { + NSString* selStr = [[NSString alloc] initWithFormat:@"Catch_%s_%s", annotationName.c_str(), testCaseName.c_str()]; + SEL sel = NSSelectorFromString( selStr ); + arcSafeRelease( selStr ); + id value = performOptionalSelector( cls, sel ); + if( value ) + return [(NSString*)value UTF8String]; + return ""; + } + } + + inline size_t registerTestMethods() { + size_t noTestMethods = 0; + int noClasses = objc_getClassList( NULL, 0 ); + + Class* classes = (CATCH_UNSAFE_UNRETAINED Class *)malloc( sizeof(Class) * noClasses); + objc_getClassList( classes, noClasses ); + + for( int c = 0; c < noClasses; c++ ) { + Class cls = classes[c]; + { + u_int count; + Method* methods = class_copyMethodList( cls, &count ); + for( u_int m = 0; m < count ; m++ ) { + SEL selector = method_getName(methods[m]); + std::string methodName = sel_getName(selector); + if( startsWith( methodName, "Catch_TestCase_" ) ) { + std::string testCaseName = methodName.substr( 15 ); + std::string name = Detail::getAnnotation( cls, "Name", testCaseName ); + std::string desc = Detail::getAnnotation( cls, "Description", testCaseName ); + const char* className = class_getName( cls ); + + getMutableRegistryHub().registerTest( makeTestCase( new OcMethod( cls, selector ), className, name.c_str(), desc.c_str(), SourceLineInfo() ) ); + noTestMethods++; + } + } + free(methods); + } + } + return noTestMethods; + } + + namespace Matchers { + namespace Impl { + namespace NSStringMatchers { + + template + struct StringHolder : MatcherImpl{ + StringHolder( NSString* substr ) : m_substr( [substr copy] ){} + StringHolder( StringHolder const& other ) : m_substr( [other.m_substr copy] ){} + StringHolder() { + arcSafeRelease( m_substr ); + } + + NSString* m_substr; + }; + + struct Equals : StringHolder { + Equals( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( ExpressionType const& str ) const { + return (str != nil || m_substr == nil ) && + [str isEqualToString:m_substr]; + } + + virtual std::string toString() const { + return "equals string: " + Catch::toString( m_substr ); + } + }; + + struct Contains : StringHolder { + Contains( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( ExpressionType const& str ) const { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location != NSNotFound; + } + + virtual std::string toString() const { + return "contains string: " + Catch::toString( m_substr ); + } + }; + + struct StartsWith : StringHolder { + StartsWith( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( ExpressionType const& str ) const { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location == 0; + } + + virtual std::string toString() const { + return "starts with: " + Catch::toString( m_substr ); + } + }; + struct EndsWith : StringHolder { + EndsWith( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( ExpressionType const& str ) const { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location == [str length] - [m_substr length]; + } + + virtual std::string toString() const { + return "ends with: " + Catch::toString( m_substr ); + } + }; + + } // namespace NSStringMatchers + } // namespace Impl + + inline Impl::NSStringMatchers::Equals + Equals( NSString* substr ){ return Impl::NSStringMatchers::Equals( substr ); } + + inline Impl::NSStringMatchers::Contains + Contains( NSString* substr ){ return Impl::NSStringMatchers::Contains( substr ); } + + inline Impl::NSStringMatchers::StartsWith + StartsWith( NSString* substr ){ return Impl::NSStringMatchers::StartsWith( substr ); } + + inline Impl::NSStringMatchers::EndsWith + EndsWith( NSString* substr ){ return Impl::NSStringMatchers::EndsWith( substr ); } + + } // namespace Matchers + + using namespace Matchers; + +} // namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define OC_TEST_CASE( name, desc )\ ++(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Name_test ) \ +{\ +return @ name; \ +}\ ++(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Description_test ) \ +{ \ +return @ desc; \ +} \ +-(void) INTERNAL_CATCH_UNIQUE_NAME( Catch_TestCase_test ) + +#endif + +#ifdef CATCH_IMPL +// #included from: internal/catch_impl.hpp +#define TWOBLUECUBES_CATCH_IMPL_HPP_INCLUDED + +// Collect all the implementation files together here +// These are the equivalent of what would usually be cpp files + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wweak-vtables" +#endif + +// #included from: ../catch_runner.hpp +#define TWOBLUECUBES_CATCH_RUNNER_HPP_INCLUDED + +// #included from: internal/catch_commandline.hpp +#define TWOBLUECUBES_CATCH_COMMANDLINE_HPP_INCLUDED + +// #included from: catch_config.hpp +#define TWOBLUECUBES_CATCH_CONFIG_HPP_INCLUDED + +// #included from: catch_test_spec_parser.hpp +#define TWOBLUECUBES_CATCH_TEST_SPEC_PARSER_HPP_INCLUDED + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +// #included from: catch_test_spec.hpp +#define TWOBLUECUBES_CATCH_TEST_SPEC_HPP_INCLUDED + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +#include +#include + +namespace Catch { + + class TestSpec { + struct Pattern : SharedImpl<> { + virtual ~Pattern(); + virtual bool matches( TestCaseInfo const& testCase ) const = 0; + }; + class NamePattern : public Pattern { + enum WildcardPosition { + NoWildcard = 0, + WildcardAtStart = 1, + WildcardAtEnd = 2, + WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd + }; + + public: + NamePattern( std::string const& name ) : m_name( toLower( name ) ), m_wildcard( NoWildcard ) { + if( startsWith( m_name, "*" ) ) { + m_name = m_name.substr( 1 ); + m_wildcard = WildcardAtStart; + } + if( endsWith( m_name, "*" ) ) { + m_name = m_name.substr( 0, m_name.size()-1 ); + m_wildcard = static_cast( m_wildcard | WildcardAtEnd ); + } + } + virtual ~NamePattern(); + virtual bool matches( TestCaseInfo const& testCase ) const { + switch( m_wildcard ) { + case NoWildcard: + return m_name == toLower( testCase.name ); + case WildcardAtStart: + return endsWith( toLower( testCase.name ), m_name ); + case WildcardAtEnd: + return startsWith( toLower( testCase.name ), m_name ); + case WildcardAtBothEnds: + return contains( toLower( testCase.name ), m_name ); + } + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunreachable-code" +#endif + throw std::logic_error( "Unknown enum" ); +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + } + private: + std::string m_name; + WildcardPosition m_wildcard; + }; + class TagPattern : public Pattern { + public: + TagPattern( std::string const& tag ) : m_tag( toLower( tag ) ) {} + virtual ~TagPattern(); + virtual bool matches( TestCaseInfo const& testCase ) const { + return testCase.lcaseTags.find( m_tag ) != testCase.lcaseTags.end(); + } + private: + std::string m_tag; + }; + class ExcludedPattern : public Pattern { + public: + ExcludedPattern( Ptr const& underlyingPattern ) : m_underlyingPattern( underlyingPattern ) {} + virtual ~ExcludedPattern(); + virtual bool matches( TestCaseInfo const& testCase ) const { return !m_underlyingPattern->matches( testCase ); } + private: + Ptr m_underlyingPattern; + }; + + struct Filter { + std::vector > m_patterns; + + bool matches( TestCaseInfo const& testCase ) const { + // All patterns in a filter must match for the filter to be a match + for( std::vector >::const_iterator it = m_patterns.begin(), itEnd = m_patterns.end(); it != itEnd; ++it ) + if( !(*it)->matches( testCase ) ) + return false; + return true; + } + }; + + public: + bool hasFilters() const { + return !m_filters.empty(); + } + bool matches( TestCaseInfo const& testCase ) const { + // A TestSpec matches if any filter matches + for( std::vector::const_iterator it = m_filters.begin(), itEnd = m_filters.end(); it != itEnd; ++it ) + if( it->matches( testCase ) ) + return true; + return false; + } + + private: + std::vector m_filters; + + friend class TestSpecParser; + }; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +namespace Catch { + + class TestSpecParser { + enum Mode{ None, Name, QuotedName, Tag }; + Mode m_mode; + bool m_exclusion; + std::size_t m_start, m_pos; + std::string m_arg; + TestSpec::Filter m_currentFilter; + TestSpec m_testSpec; + ITagAliasRegistry const* m_tagAliases; + + public: + TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {} + + TestSpecParser& parse( std::string const& arg ) { + m_mode = None; + m_exclusion = false; + m_start = std::string::npos; + m_arg = m_tagAliases->expandAliases( arg ); + for( m_pos = 0; m_pos < m_arg.size(); ++m_pos ) + visitChar( m_arg[m_pos] ); + if( m_mode == Name ) + addPattern(); + return *this; + } + TestSpec testSpec() { + addFilter(); + return m_testSpec; + } + private: + void visitChar( char c ) { + if( m_mode == None ) { + switch( c ) { + case ' ': return; + case '~': m_exclusion = true; return; + case '[': return startNewMode( Tag, ++m_pos ); + case '"': return startNewMode( QuotedName, ++m_pos ); + default: startNewMode( Name, m_pos ); break; + } + } + if( m_mode == Name ) { + if( c == ',' ) { + addPattern(); + addFilter(); + } + else if( c == '[' ) { + if( subString() == "exclude:" ) + m_exclusion = true; + else + addPattern(); + startNewMode( Tag, ++m_pos ); + } + } + else if( m_mode == QuotedName && c == '"' ) + addPattern(); + else if( m_mode == Tag && c == ']' ) + addPattern(); + } + void startNewMode( Mode mode, std::size_t start ) { + m_mode = mode; + m_start = start; + } + std::string subString() const { return m_arg.substr( m_start, m_pos - m_start ); } + template + void addPattern() { + std::string token = subString(); + if( startsWith( token, "exclude:" ) ) { + m_exclusion = true; + token = token.substr( 8 ); + } + if( !token.empty() ) { + Ptr pattern = new T( token ); + if( m_exclusion ) + pattern = new TestSpec::ExcludedPattern( pattern ); + m_currentFilter.m_patterns.push_back( pattern ); + } + m_exclusion = false; + m_mode = None; + } + void addFilter() { + if( !m_currentFilter.m_patterns.empty() ) { + m_testSpec.m_filters.push_back( m_currentFilter ); + m_currentFilter = TestSpec::Filter(); + } + } + }; + inline TestSpec parseTestSpec( std::string const& arg ) { + return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec(); + } + +} // namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// #included from: catch_interfaces_config.h +#define TWOBLUECUBES_CATCH_INTERFACES_CONFIG_H_INCLUDED + +#include +#include +#include + +namespace Catch { + + struct Verbosity { enum Level { + NoOutput = 0, + Quiet, + Normal + }; }; + + struct WarnAbout { enum What { + Nothing = 0x00, + NoAssertions = 0x01 + }; }; + + struct ShowDurations { enum OrNot { + DefaultForReporter, + Always, + Never + }; }; + struct RunTests { enum InWhatOrder { + InDeclarationOrder, + InLexicographicalOrder, + InRandomOrder + }; }; + + class TestSpec; + + struct IConfig : IShared { + + virtual ~IConfig(); + + virtual bool allowThrows() const = 0; + virtual std::ostream& stream() const = 0; + virtual std::string name() const = 0; + virtual bool includeSuccessfulResults() const = 0; + virtual bool shouldDebugBreak() const = 0; + virtual bool warnAboutMissingAssertions() const = 0; + virtual int abortAfter() const = 0; + virtual bool showInvisibles() const = 0; + virtual ShowDurations::OrNot showDurations() const = 0; + virtual TestSpec const& testSpec() const = 0; + virtual RunTests::InWhatOrder runOrder() const = 0; + virtual unsigned int rngSeed() const = 0; + virtual bool forceColour() const = 0; + }; +} + +// #included from: catch_stream.h +#define TWOBLUECUBES_CATCH_STREAM_H_INCLUDED + +#include + +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch { + + class Stream { + public: + Stream(); + Stream( std::streambuf* _streamBuf, bool _isOwned ); + void release(); + + std::streambuf* streamBuf; + + private: + bool isOwned; + }; + + std::ostream& cout(); + std::ostream& cerr(); +} + +#include +#include +#include +#include +#include + +#ifndef CATCH_CONFIG_CONSOLE_WIDTH +#define CATCH_CONFIG_CONSOLE_WIDTH 80 +#endif + +namespace Catch { + + struct ConfigData { + + ConfigData() + : listTests( false ), + listTags( false ), + listReporters( false ), + listTestNamesOnly( false ), + showSuccessfulTests( false ), + shouldDebugBreak( false ), + noThrow( false ), + showHelp( false ), + showInvisibles( false ), + forceColour( false ), + abortAfter( -1 ), + rngSeed( 0 ), + verbosity( Verbosity::Normal ), + warnings( WarnAbout::Nothing ), + showDurations( ShowDurations::DefaultForReporter ), + runOrder( RunTests::InDeclarationOrder ) + {} + + bool listTests; + bool listTags; + bool listReporters; + bool listTestNamesOnly; + + bool showSuccessfulTests; + bool shouldDebugBreak; + bool noThrow; + bool showHelp; + bool showInvisibles; + bool forceColour; + + int abortAfter; + unsigned int rngSeed; + + Verbosity::Level verbosity; + WarnAbout::What warnings; + ShowDurations::OrNot showDurations; + RunTests::InWhatOrder runOrder; + + std::string reporterName; + std::string outputFilename; + std::string name; + std::string processName; + + std::vector testsOrTags; + }; + + class Config : public SharedImpl { + private: + Config( Config const& other ); + Config& operator = ( Config const& other ); + virtual void dummy(); + public: + + Config() + : m_os( Catch::cout().rdbuf() ) + {} + + Config( ConfigData const& data ) + : m_data( data ), + m_os( Catch::cout().rdbuf() ) + { + if( !data.testsOrTags.empty() ) { + TestSpecParser parser( ITagAliasRegistry::get() ); + for( std::size_t i = 0; i < data.testsOrTags.size(); ++i ) + parser.parse( data.testsOrTags[i] ); + m_testSpec = parser.testSpec(); + } + } + + virtual ~Config() { + m_os.rdbuf( Catch::cout().rdbuf() ); + m_stream.release(); + } + + void setFilename( std::string const& filename ) { + m_data.outputFilename = filename; + } + + std::string const& getFilename() const { + return m_data.outputFilename ; + } + + bool listTests() const { return m_data.listTests; } + bool listTestNamesOnly() const { return m_data.listTestNamesOnly; } + bool listTags() const { return m_data.listTags; } + bool listReporters() const { return m_data.listReporters; } + + std::string getProcessName() const { return m_data.processName; } + + bool shouldDebugBreak() const { return m_data.shouldDebugBreak; } + + void setStreamBuf( std::streambuf* buf ) { + m_os.rdbuf( buf ? buf : Catch::cout().rdbuf() ); + } + + void useStream( std::string const& streamName ) { + Stream stream = createStream( streamName ); + setStreamBuf( stream.streamBuf ); + m_stream.release(); + m_stream = stream; + } + + std::string getReporterName() const { return m_data.reporterName; } + + int abortAfter() const { return m_data.abortAfter; } + + TestSpec const& testSpec() const { return m_testSpec; } + + bool showHelp() const { return m_data.showHelp; } + bool showInvisibles() const { return m_data.showInvisibles; } + + // IConfig interface + virtual bool allowThrows() const { return !m_data.noThrow; } + virtual std::ostream& stream() const { return m_os; } + virtual std::string name() const { return m_data.name.empty() ? m_data.processName : m_data.name; } + virtual bool includeSuccessfulResults() const { return m_data.showSuccessfulTests; } + virtual bool warnAboutMissingAssertions() const { return m_data.warnings & WarnAbout::NoAssertions; } + virtual ShowDurations::OrNot showDurations() const { return m_data.showDurations; } + virtual RunTests::InWhatOrder runOrder() const { return m_data.runOrder; } + virtual unsigned int rngSeed() const { return m_data.rngSeed; } + virtual bool forceColour() const { return m_data.forceColour; } + + private: + ConfigData m_data; + + Stream m_stream; + mutable std::ostream m_os; + TestSpec m_testSpec; + }; + +} // end namespace Catch + +// #included from: catch_clara.h +#define TWOBLUECUBES_CATCH_CLARA_H_INCLUDED + +// Use Catch's value for console width (store Clara's off to the side, if present) +#ifdef CLARA_CONFIG_CONSOLE_WIDTH +#define CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH CLARA_CONFIG_CONSOLE_WIDTH +#undef CLARA_CONFIG_CONSOLE_WIDTH +#endif +#define CLARA_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH + +// Declare Clara inside the Catch namespace +#define STITCH_CLARA_OPEN_NAMESPACE namespace Catch { +// #included from: ../external/clara.h + +// Only use header guard if we are not using an outer namespace +#if !defined(TWOBLUECUBES_CLARA_H_INCLUDED) || defined(STITCH_CLARA_OPEN_NAMESPACE) + +#ifndef STITCH_CLARA_OPEN_NAMESPACE +#define TWOBLUECUBES_CLARA_H_INCLUDED +#define STITCH_CLARA_OPEN_NAMESPACE +#define STITCH_CLARA_CLOSE_NAMESPACE +#else +#define STITCH_CLARA_CLOSE_NAMESPACE } +#endif + +#define STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE STITCH_CLARA_OPEN_NAMESPACE + +// ----------- #included from tbc_text_format.h ----------- + +// Only use header guard if we are not using an outer namespace +#if !defined(TBC_TEXT_FORMAT_H_INCLUDED) || defined(STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE) +#ifndef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE +#define TBC_TEXT_FORMAT_H_INCLUDED +#endif + +#include +#include +#include + +// Use optional outer namespace +#ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE +namespace STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE { +#endif + +namespace Tbc { + +#ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH + const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH; +#else + const unsigned int consoleWidth = 80; +#endif + + struct TextAttributes { + TextAttributes() + : initialIndent( std::string::npos ), + indent( 0 ), + width( consoleWidth-1 ), + tabChar( '\t' ) + {} + + TextAttributes& setInitialIndent( std::size_t _value ) { initialIndent = _value; return *this; } + TextAttributes& setIndent( std::size_t _value ) { indent = _value; return *this; } + TextAttributes& setWidth( std::size_t _value ) { width = _value; return *this; } + TextAttributes& setTabChar( char _value ) { tabChar = _value; return *this; } + + std::size_t initialIndent; // indent of first line, or npos + std::size_t indent; // indent of subsequent lines, or all if initialIndent is npos + std::size_t width; // maximum width of text, including indent. Longer text will wrap + char tabChar; // If this char is seen the indent is changed to current pos + }; + + class Text { + public: + Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() ) + : attr( _attr ) + { + std::string wrappableChars = " [({.,/|\\-"; + std::size_t indent = _attr.initialIndent != std::string::npos + ? _attr.initialIndent + : _attr.indent; + std::string remainder = _str; + + while( !remainder.empty() ) { + if( lines.size() >= 1000 ) { + lines.push_back( "... message truncated due to excessive size" ); + return; + } + std::size_t tabPos = std::string::npos; + std::size_t width = (std::min)( remainder.size(), _attr.width - indent ); + std::size_t pos = remainder.find_first_of( '\n' ); + if( pos <= width ) { + width = pos; + } + pos = remainder.find_last_of( _attr.tabChar, width ); + if( pos != std::string::npos ) { + tabPos = pos; + if( remainder[width] == '\n' ) + width--; + remainder = remainder.substr( 0, tabPos ) + remainder.substr( tabPos+1 ); + } + + if( width == remainder.size() ) { + spliceLine( indent, remainder, width ); + } + else if( remainder[width] == '\n' ) { + spliceLine( indent, remainder, width ); + if( width <= 1 || remainder.size() != 1 ) + remainder = remainder.substr( 1 ); + indent = _attr.indent; + } + else { + pos = remainder.find_last_of( wrappableChars, width ); + if( pos != std::string::npos && pos > 0 ) { + spliceLine( indent, remainder, pos ); + if( remainder[0] == ' ' ) + remainder = remainder.substr( 1 ); + } + else { + spliceLine( indent, remainder, width-1 ); + lines.back() += "-"; + } + if( lines.size() == 1 ) + indent = _attr.indent; + if( tabPos != std::string::npos ) + indent += tabPos; + } + } + } + + void spliceLine( std::size_t _indent, std::string& _remainder, std::size_t _pos ) { + lines.push_back( std::string( _indent, ' ' ) + _remainder.substr( 0, _pos ) ); + _remainder = _remainder.substr( _pos ); + } + + typedef std::vector::const_iterator const_iterator; + + const_iterator begin() const { return lines.begin(); } + const_iterator end() const { return lines.end(); } + std::string const& last() const { return lines.back(); } + std::size_t size() const { return lines.size(); } + std::string const& operator[]( std::size_t _index ) const { return lines[_index]; } + std::string toString() const { + std::ostringstream oss; + oss << *this; + return oss.str(); + } + + inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) { + for( Text::const_iterator it = _text.begin(), itEnd = _text.end(); + it != itEnd; ++it ) { + if( it != _text.begin() ) + _stream << "\n"; + _stream << *it; + } + return _stream; + } + + private: + std::string str; + TextAttributes attr; + std::vector lines; + }; + +} // end namespace Tbc + +#ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE +} // end outer namespace +#endif + +#endif // TBC_TEXT_FORMAT_H_INCLUDED + +// ----------- end of #include from tbc_text_format.h ----------- +// ........... back in /Users/philnash/Dev/OSS/Clara/srcs/clara.h + +#undef STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE + +#include +#include +#include +#include + +// Use optional outer namespace +#ifdef STITCH_CLARA_OPEN_NAMESPACE +STITCH_CLARA_OPEN_NAMESPACE +#endif + +namespace Clara { + + struct UnpositionalTag {}; + + extern UnpositionalTag _; + +#ifdef CLARA_CONFIG_MAIN + UnpositionalTag _; +#endif + + namespace Detail { + +#ifdef CLARA_CONSOLE_WIDTH + const unsigned int consoleWidth = CLARA_CONFIG_CONSOLE_WIDTH; +#else + const unsigned int consoleWidth = 80; +#endif + + using namespace Tbc; + + inline bool startsWith( std::string const& str, std::string const& prefix ) { + return str.size() >= prefix.size() && str.substr( 0, prefix.size() ) == prefix; + } + + template struct RemoveConstRef{ typedef T type; }; + template struct RemoveConstRef{ typedef T type; }; + template struct RemoveConstRef{ typedef T type; }; + template struct RemoveConstRef{ typedef T type; }; + + template struct IsBool { static const bool value = false; }; + template<> struct IsBool { static const bool value = true; }; + + template + void convertInto( std::string const& _source, T& _dest ) { + std::stringstream ss; + ss << _source; + ss >> _dest; + if( ss.fail() ) + throw std::runtime_error( "Unable to convert " + _source + " to destination type" ); + } + inline void convertInto( std::string const& _source, std::string& _dest ) { + _dest = _source; + } + inline void convertInto( std::string const& _source, bool& _dest ) { + std::string sourceLC = _source; + std::transform( sourceLC.begin(), sourceLC.end(), sourceLC.begin(), ::tolower ); + if( sourceLC == "y" || sourceLC == "1" || sourceLC == "true" || sourceLC == "yes" || sourceLC == "on" ) + _dest = true; + else if( sourceLC == "n" || sourceLC == "0" || sourceLC == "false" || sourceLC == "no" || sourceLC == "off" ) + _dest = false; + else + throw std::runtime_error( "Expected a boolean value but did not recognise:\n '" + _source + "'" ); + } + inline void convertInto( bool _source, bool& _dest ) { + _dest = _source; + } + template + inline void convertInto( bool, T& ) { + throw std::runtime_error( "Invalid conversion" ); + } + + template + struct IArgFunction { + virtual ~IArgFunction() {} +# ifdef CATCH_CPP11_OR_GREATER + IArgFunction() = default; + IArgFunction( IArgFunction const& ) = default; +# endif + virtual void set( ConfigT& config, std::string const& value ) const = 0; + virtual void setFlag( ConfigT& config ) const = 0; + virtual bool takesArg() const = 0; + virtual IArgFunction* clone() const = 0; + }; + + template + class BoundArgFunction { + public: + BoundArgFunction() : functionObj( NULL ) {} + BoundArgFunction( IArgFunction* _functionObj ) : functionObj( _functionObj ) {} + BoundArgFunction( BoundArgFunction const& other ) : functionObj( other.functionObj ? other.functionObj->clone() : NULL ) {} + BoundArgFunction& operator = ( BoundArgFunction const& other ) { + IArgFunction* newFunctionObj = other.functionObj ? other.functionObj->clone() : NULL; + delete functionObj; + functionObj = newFunctionObj; + return *this; + } + ~BoundArgFunction() { delete functionObj; } + + void set( ConfigT& config, std::string const& value ) const { + functionObj->set( config, value ); + } + void setFlag( ConfigT& config ) const { + functionObj->setFlag( config ); + } + bool takesArg() const { return functionObj->takesArg(); } + + bool isSet() const { + return functionObj != NULL; + } + private: + IArgFunction* functionObj; + }; + + template + struct NullBinder : IArgFunction{ + virtual void set( C&, std::string const& ) const {} + virtual void setFlag( C& ) const {} + virtual bool takesArg() const { return true; } + virtual IArgFunction* clone() const { return new NullBinder( *this ); } + }; + + template + struct BoundDataMember : IArgFunction{ + BoundDataMember( M C::* _member ) : member( _member ) {} + virtual void set( C& p, std::string const& stringValue ) const { + convertInto( stringValue, p.*member ); + } + virtual void setFlag( C& p ) const { + convertInto( true, p.*member ); + } + virtual bool takesArg() const { return !IsBool::value; } + virtual IArgFunction* clone() const { return new BoundDataMember( *this ); } + M C::* member; + }; + template + struct BoundUnaryMethod : IArgFunction{ + BoundUnaryMethod( void (C::*_member)( M ) ) : member( _member ) {} + virtual void set( C& p, std::string const& stringValue ) const { + typename RemoveConstRef::type value; + convertInto( stringValue, value ); + (p.*member)( value ); + } + virtual void setFlag( C& p ) const { + typename RemoveConstRef::type value; + convertInto( true, value ); + (p.*member)( value ); + } + virtual bool takesArg() const { return !IsBool::value; } + virtual IArgFunction* clone() const { return new BoundUnaryMethod( *this ); } + void (C::*member)( M ); + }; + template + struct BoundNullaryMethod : IArgFunction{ + BoundNullaryMethod( void (C::*_member)() ) : member( _member ) {} + virtual void set( C& p, std::string const& stringValue ) const { + bool value; + convertInto( stringValue, value ); + if( value ) + (p.*member)(); + } + virtual void setFlag( C& p ) const { + (p.*member)(); + } + virtual bool takesArg() const { return false; } + virtual IArgFunction* clone() const { return new BoundNullaryMethod( *this ); } + void (C::*member)(); + }; + + template + struct BoundUnaryFunction : IArgFunction{ + BoundUnaryFunction( void (*_function)( C& ) ) : function( _function ) {} + virtual void set( C& obj, std::string const& stringValue ) const { + bool value; + convertInto( stringValue, value ); + if( value ) + function( obj ); + } + virtual void setFlag( C& p ) const { + function( p ); + } + virtual bool takesArg() const { return false; } + virtual IArgFunction* clone() const { return new BoundUnaryFunction( *this ); } + void (*function)( C& ); + }; + + template + struct BoundBinaryFunction : IArgFunction{ + BoundBinaryFunction( void (*_function)( C&, T ) ) : function( _function ) {} + virtual void set( C& obj, std::string const& stringValue ) const { + typename RemoveConstRef::type value; + convertInto( stringValue, value ); + function( obj, value ); + } + virtual void setFlag( C& obj ) const { + typename RemoveConstRef::type value; + convertInto( true, value ); + function( obj, value ); + } + virtual bool takesArg() const { return !IsBool::value; } + virtual IArgFunction* clone() const { return new BoundBinaryFunction( *this ); } + void (*function)( C&, T ); + }; + + } // namespace Detail + + struct Parser { + Parser() : separators( " \t=:" ) {} + + struct Token { + enum Type { Positional, ShortOpt, LongOpt }; + Token( Type _type, std::string const& _data ) : type( _type ), data( _data ) {} + Type type; + std::string data; + }; + + void parseIntoTokens( int argc, char const * const * argv, std::vector& tokens ) const { + const std::string doubleDash = "--"; + for( int i = 1; i < argc && argv[i] != doubleDash; ++i ) + parseIntoTokens( argv[i] , tokens); + } + void parseIntoTokens( std::string arg, std::vector& tokens ) const { + while( !arg.empty() ) { + Parser::Token token( Parser::Token::Positional, arg ); + arg = ""; + if( token.data[0] == '-' ) { + if( token.data.size() > 1 && token.data[1] == '-' ) { + token = Parser::Token( Parser::Token::LongOpt, token.data.substr( 2 ) ); + } + else { + token = Parser::Token( Parser::Token::ShortOpt, token.data.substr( 1 ) ); + if( token.data.size() > 1 && separators.find( token.data[1] ) == std::string::npos ) { + arg = "-" + token.data.substr( 1 ); + token.data = token.data.substr( 0, 1 ); + } + } + } + if( token.type != Parser::Token::Positional ) { + std::size_t pos = token.data.find_first_of( separators ); + if( pos != std::string::npos ) { + arg = token.data.substr( pos+1 ); + token.data = token.data.substr( 0, pos ); + } + } + tokens.push_back( token ); + } + } + std::string separators; + }; + + template + struct CommonArgProperties { + CommonArgProperties() {} + CommonArgProperties( Detail::BoundArgFunction const& _boundField ) : boundField( _boundField ) {} + + Detail::BoundArgFunction boundField; + std::string description; + std::string detail; + std::string placeholder; // Only value if boundField takes an arg + + bool takesArg() const { + return !placeholder.empty(); + } + void validate() const { + if( !boundField.isSet() ) + throw std::logic_error( "option not bound" ); + } + }; + struct OptionArgProperties { + std::vector shortNames; + std::string longName; + + bool hasShortName( std::string const& shortName ) const { + return std::find( shortNames.begin(), shortNames.end(), shortName ) != shortNames.end(); + } + bool hasLongName( std::string const& _longName ) const { + return _longName == longName; + } + }; + struct PositionalArgProperties { + PositionalArgProperties() : position( -1 ) {} + int position; // -1 means non-positional (floating) + + bool isFixedPositional() const { + return position != -1; + } + }; + + template + class CommandLine { + + struct Arg : CommonArgProperties, OptionArgProperties, PositionalArgProperties { + Arg() {} + Arg( Detail::BoundArgFunction const& _boundField ) : CommonArgProperties( _boundField ) {} + + using CommonArgProperties::placeholder; // !TBD + + std::string dbgName() const { + if( !longName.empty() ) + return "--" + longName; + if( !shortNames.empty() ) + return "-" + shortNames[0]; + return "positional args"; + } + std::string commands() const { + std::ostringstream oss; + bool first = true; + std::vector::const_iterator it = shortNames.begin(), itEnd = shortNames.end(); + for(; it != itEnd; ++it ) { + if( first ) + first = false; + else + oss << ", "; + oss << "-" << *it; + } + if( !longName.empty() ) { + if( !first ) + oss << ", "; + oss << "--" << longName; + } + if( !placeholder.empty() ) + oss << " <" << placeholder << ">"; + return oss.str(); + } + }; + + // NOTE: std::auto_ptr is deprecated in c++11/c++0x +#if defined(__cplusplus) && __cplusplus > 199711L + typedef std::unique_ptr ArgAutoPtr; +#else + typedef std::auto_ptr ArgAutoPtr; +#endif + + friend void addOptName( Arg& arg, std::string const& optName ) + { + if( optName.empty() ) + return; + if( Detail::startsWith( optName, "--" ) ) { + if( !arg.longName.empty() ) + throw std::logic_error( "Only one long opt may be specified. '" + + arg.longName + + "' already specified, now attempting to add '" + + optName + "'" ); + arg.longName = optName.substr( 2 ); + } + else if( Detail::startsWith( optName, "-" ) ) + arg.shortNames.push_back( optName.substr( 1 ) ); + else + throw std::logic_error( "option must begin with - or --. Option was: '" + optName + "'" ); + } + friend void setPositionalArg( Arg& arg, int position ) + { + arg.position = position; + } + + class ArgBuilder { + public: + ArgBuilder( Arg* arg ) : m_arg( arg ) {} + + // Bind a non-boolean data member (requires placeholder string) + template + void bind( M C::* field, std::string const& placeholder ) { + m_arg->boundField = new Detail::BoundDataMember( field ); + m_arg->placeholder = placeholder; + } + // Bind a boolean data member (no placeholder required) + template + void bind( bool C::* field ) { + m_arg->boundField = new Detail::BoundDataMember( field ); + } + + // Bind a method taking a single, non-boolean argument (requires a placeholder string) + template + void bind( void (C::* unaryMethod)( M ), std::string const& placeholder ) { + m_arg->boundField = new Detail::BoundUnaryMethod( unaryMethod ); + m_arg->placeholder = placeholder; + } + + // Bind a method taking a single, boolean argument (no placeholder string required) + template + void bind( void (C::* unaryMethod)( bool ) ) { + m_arg->boundField = new Detail::BoundUnaryMethod( unaryMethod ); + } + + // Bind a method that takes no arguments (will be called if opt is present) + template + void bind( void (C::* nullaryMethod)() ) { + m_arg->boundField = new Detail::BoundNullaryMethod( nullaryMethod ); + } + + // Bind a free function taking a single argument - the object to operate on (no placeholder string required) + template + void bind( void (* unaryFunction)( C& ) ) { + m_arg->boundField = new Detail::BoundUnaryFunction( unaryFunction ); + } + + // Bind a free function taking a single argument - the object to operate on (requires a placeholder string) + template + void bind( void (* binaryFunction)( C&, T ), std::string const& placeholder ) { + m_arg->boundField = new Detail::BoundBinaryFunction( binaryFunction ); + m_arg->placeholder = placeholder; + } + + ArgBuilder& describe( std::string const& description ) { + m_arg->description = description; + return *this; + } + ArgBuilder& detail( std::string const& detail ) { + m_arg->detail = detail; + return *this; + } + + protected: + Arg* m_arg; + }; + + class OptBuilder : public ArgBuilder { + public: + OptBuilder( Arg* arg ) : ArgBuilder( arg ) {} + OptBuilder( OptBuilder& other ) : ArgBuilder( other ) {} + + OptBuilder& operator[]( std::string const& optName ) { + addOptName( *ArgBuilder::m_arg, optName ); + return *this; + } + }; + + public: + + CommandLine() + : m_boundProcessName( new Detail::NullBinder() ), + m_highestSpecifiedArgPosition( 0 ), + m_throwOnUnrecognisedTokens( false ) + {} + CommandLine( CommandLine const& other ) + : m_boundProcessName( other.m_boundProcessName ), + m_options ( other.m_options ), + m_positionalArgs( other.m_positionalArgs ), + m_highestSpecifiedArgPosition( other.m_highestSpecifiedArgPosition ), + m_throwOnUnrecognisedTokens( other.m_throwOnUnrecognisedTokens ) + { + if( other.m_floatingArg.get() ) + m_floatingArg.reset( new Arg( *other.m_floatingArg ) ); + } + + CommandLine& setThrowOnUnrecognisedTokens( bool shouldThrow = true ) { + m_throwOnUnrecognisedTokens = shouldThrow; + return *this; + } + + OptBuilder operator[]( std::string const& optName ) { + m_options.push_back( Arg() ); + addOptName( m_options.back(), optName ); + OptBuilder builder( &m_options.back() ); + return builder; + } + + ArgBuilder operator[]( int position ) { + m_positionalArgs.insert( std::make_pair( position, Arg() ) ); + if( position > m_highestSpecifiedArgPosition ) + m_highestSpecifiedArgPosition = position; + setPositionalArg( m_positionalArgs[position], position ); + ArgBuilder builder( &m_positionalArgs[position] ); + return builder; + } + + // Invoke this with the _ instance + ArgBuilder operator[]( UnpositionalTag ) { + if( m_floatingArg.get() ) + throw std::logic_error( "Only one unpositional argument can be added" ); + m_floatingArg.reset( new Arg() ); + ArgBuilder builder( m_floatingArg.get() ); + return builder; + } + + template + void bindProcessName( M C::* field ) { + m_boundProcessName = new Detail::BoundDataMember( field ); + } + template + void bindProcessName( void (C::*_unaryMethod)( M ) ) { + m_boundProcessName = new Detail::BoundUnaryMethod( _unaryMethod ); + } + + void optUsage( std::ostream& os, std::size_t indent = 0, std::size_t width = Detail::consoleWidth ) const { + typename std::vector::const_iterator itBegin = m_options.begin(), itEnd = m_options.end(), it; + std::size_t maxWidth = 0; + for( it = itBegin; it != itEnd; ++it ) + maxWidth = (std::max)( maxWidth, it->commands().size() ); + + for( it = itBegin; it != itEnd; ++it ) { + Detail::Text usage( it->commands(), Detail::TextAttributes() + .setWidth( maxWidth+indent ) + .setIndent( indent ) ); + Detail::Text desc( it->description, Detail::TextAttributes() + .setWidth( width - maxWidth - 3 ) ); + + for( std::size_t i = 0; i < (std::max)( usage.size(), desc.size() ); ++i ) { + std::string usageCol = i < usage.size() ? usage[i] : ""; + os << usageCol; + + if( i < desc.size() && !desc[i].empty() ) + os << std::string( indent + 2 + maxWidth - usageCol.size(), ' ' ) + << desc[i]; + os << "\n"; + } + } + } + std::string optUsage() const { + std::ostringstream oss; + optUsage( oss ); + return oss.str(); + } + + void argSynopsis( std::ostream& os ) const { + for( int i = 1; i <= m_highestSpecifiedArgPosition; ++i ) { + if( i > 1 ) + os << " "; + typename std::map::const_iterator it = m_positionalArgs.find( i ); + if( it != m_positionalArgs.end() ) + os << "<" << it->second.placeholder << ">"; + else if( m_floatingArg.get() ) + os << "<" << m_floatingArg->placeholder << ">"; + else + throw std::logic_error( "non consecutive positional arguments with no floating args" ); + } + // !TBD No indication of mandatory args + if( m_floatingArg.get() ) { + if( m_highestSpecifiedArgPosition > 1 ) + os << " "; + os << "[<" << m_floatingArg->placeholder << "> ...]"; + } + } + std::string argSynopsis() const { + std::ostringstream oss; + argSynopsis( oss ); + return oss.str(); + } + + void usage( std::ostream& os, std::string const& procName ) const { + validate(); + os << "usage:\n " << procName << " "; + argSynopsis( os ); + if( !m_options.empty() ) { + os << " [options]\n\nwhere options are: \n"; + optUsage( os, 2 ); + } + os << "\n"; + } + std::string usage( std::string const& procName ) const { + std::ostringstream oss; + usage( oss, procName ); + return oss.str(); + } + + ConfigT parse( int argc, char const * const * argv ) const { + ConfigT config; + parseInto( argc, argv, config ); + return config; + } + + std::vector parseInto( int argc, char const * const * argv, ConfigT& config ) const { + std::string processName = argv[0]; + std::size_t lastSlash = processName.find_last_of( "/\\" ); + if( lastSlash != std::string::npos ) + processName = processName.substr( lastSlash+1 ); + m_boundProcessName.set( config, processName ); + std::vector tokens; + Parser parser; + parser.parseIntoTokens( argc, argv, tokens ); + return populate( tokens, config ); + } + + std::vector populate( std::vector const& tokens, ConfigT& config ) const { + validate(); + std::vector unusedTokens = populateOptions( tokens, config ); + unusedTokens = populateFixedArgs( unusedTokens, config ); + unusedTokens = populateFloatingArgs( unusedTokens, config ); + return unusedTokens; + } + + std::vector populateOptions( std::vector const& tokens, ConfigT& config ) const { + std::vector unusedTokens; + std::vector errors; + for( std::size_t i = 0; i < tokens.size(); ++i ) { + Parser::Token const& token = tokens[i]; + typename std::vector::const_iterator it = m_options.begin(), itEnd = m_options.end(); + for(; it != itEnd; ++it ) { + Arg const& arg = *it; + + try { + if( ( token.type == Parser::Token::ShortOpt && arg.hasShortName( token.data ) ) || + ( token.type == Parser::Token::LongOpt && arg.hasLongName( token.data ) ) ) { + if( arg.takesArg() ) { + if( i == tokens.size()-1 || tokens[i+1].type != Parser::Token::Positional ) + errors.push_back( "Expected argument to option: " + token.data ); + else + arg.boundField.set( config, tokens[++i].data ); + } + else { + arg.boundField.setFlag( config ); + } + break; + } + } + catch( std::exception& ex ) { + errors.push_back( std::string( ex.what() ) + "\n- while parsing: (" + arg.commands() + ")" ); + } + } + if( it == itEnd ) { + if( token.type == Parser::Token::Positional || !m_throwOnUnrecognisedTokens ) + unusedTokens.push_back( token ); + else if( errors.empty() && m_throwOnUnrecognisedTokens ) + errors.push_back( "unrecognised option: " + token.data ); + } + } + if( !errors.empty() ) { + std::ostringstream oss; + for( std::vector::const_iterator it = errors.begin(), itEnd = errors.end(); + it != itEnd; + ++it ) { + if( it != errors.begin() ) + oss << "\n"; + oss << *it; + } + throw std::runtime_error( oss.str() ); + } + return unusedTokens; + } + std::vector populateFixedArgs( std::vector const& tokens, ConfigT& config ) const { + std::vector unusedTokens; + int position = 1; + for( std::size_t i = 0; i < tokens.size(); ++i ) { + Parser::Token const& token = tokens[i]; + typename std::map::const_iterator it = m_positionalArgs.find( position ); + if( it != m_positionalArgs.end() ) + it->second.boundField.set( config, token.data ); + else + unusedTokens.push_back( token ); + if( token.type == Parser::Token::Positional ) + position++; + } + return unusedTokens; + } + std::vector populateFloatingArgs( std::vector const& tokens, ConfigT& config ) const { + if( !m_floatingArg.get() ) + return tokens; + std::vector unusedTokens; + for( std::size_t i = 0; i < tokens.size(); ++i ) { + Parser::Token const& token = tokens[i]; + if( token.type == Parser::Token::Positional ) + m_floatingArg->boundField.set( config, token.data ); + else + unusedTokens.push_back( token ); + } + return unusedTokens; + } + + void validate() const + { + if( m_options.empty() && m_positionalArgs.empty() && !m_floatingArg.get() ) + throw std::logic_error( "No options or arguments specified" ); + + for( typename std::vector::const_iterator it = m_options.begin(), + itEnd = m_options.end(); + it != itEnd; ++it ) + it->validate(); + } + + private: + Detail::BoundArgFunction m_boundProcessName; + std::vector m_options; + std::map m_positionalArgs; + ArgAutoPtr m_floatingArg; + int m_highestSpecifiedArgPosition; + bool m_throwOnUnrecognisedTokens; + }; + +} // end namespace Clara + +STITCH_CLARA_CLOSE_NAMESPACE +#undef STITCH_CLARA_OPEN_NAMESPACE +#undef STITCH_CLARA_CLOSE_NAMESPACE + +#endif // TWOBLUECUBES_CLARA_H_INCLUDED +#undef STITCH_CLARA_OPEN_NAMESPACE + +// Restore Clara's value for console width, if present +#ifdef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#define CLARA_CONFIG_CONSOLE_WIDTH CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#undef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#endif + +#include + +namespace Catch { + + inline void abortAfterFirst( ConfigData& config ) { config.abortAfter = 1; } + inline void abortAfterX( ConfigData& config, int x ) { + if( x < 1 ) + throw std::runtime_error( "Value after -x or --abortAfter must be greater than zero" ); + config.abortAfter = x; + } + inline void addTestOrTags( ConfigData& config, std::string const& _testSpec ) { config.testsOrTags.push_back( _testSpec ); } + + inline void addWarning( ConfigData& config, std::string const& _warning ) { + if( _warning == "NoAssertions" ) + config.warnings = static_cast( config.warnings | WarnAbout::NoAssertions ); + else + throw std::runtime_error( "Unrecognised warning: '" + _warning + "'" ); + } + inline void setOrder( ConfigData& config, std::string const& order ) { + if( startsWith( "declared", order ) ) + config.runOrder = RunTests::InDeclarationOrder; + else if( startsWith( "lexical", order ) ) + config.runOrder = RunTests::InLexicographicalOrder; + else if( startsWith( "random", order ) ) + config.runOrder = RunTests::InRandomOrder; + else + throw std::runtime_error( "Unrecognised ordering: '" + order + "'" ); + } + inline void setRngSeed( ConfigData& config, std::string const& seed ) { + if( seed == "time" ) { + config.rngSeed = static_cast( std::time(0) ); + } + else { + std::stringstream ss; + ss << seed; + ss >> config.rngSeed; + if( ss.fail() ) + throw std::runtime_error( "Argment to --rng-seed should be the word 'time' or a number" ); + } + } + inline void setVerbosity( ConfigData& config, int level ) { + // !TBD: accept strings? + config.verbosity = static_cast( level ); + } + inline void setShowDurations( ConfigData& config, bool _showDurations ) { + config.showDurations = _showDurations + ? ShowDurations::Always + : ShowDurations::Never; + } + inline void loadTestNamesFromFile( ConfigData& config, std::string const& _filename ) { + std::ifstream f( _filename.c_str() ); + if( !f.is_open() ) + throw std::domain_error( "Unable to load input file: " + _filename ); + + std::string line; + while( std::getline( f, line ) ) { + line = trim(line); + if( !line.empty() && !startsWith( line, "#" ) ) + addTestOrTags( config, "\"" + line + "\"," ); + } + } + + inline Clara::CommandLine makeCommandLineParser() { + + using namespace Clara; + CommandLine cli; + + cli.bindProcessName( &ConfigData::processName ); + + cli["-?"]["-h"]["--help"] + .describe( "display usage information" ) + .bind( &ConfigData::showHelp ); + + cli["-l"]["--list-tests"] + .describe( "list all/matching test cases" ) + .bind( &ConfigData::listTests ); + + cli["-t"]["--list-tags"] + .describe( "list all/matching tags" ) + .bind( &ConfigData::listTags ); + + cli["-s"]["--success"] + .describe( "include successful tests in output" ) + .bind( &ConfigData::showSuccessfulTests ); + + cli["-b"]["--break"] + .describe( "break into debugger on failure" ) + .bind( &ConfigData::shouldDebugBreak ); + + cli["-e"]["--nothrow"] + .describe( "skip exception tests" ) + .bind( &ConfigData::noThrow ); + + cli["-i"]["--invisibles"] + .describe( "show invisibles (tabs, newlines)" ) + .bind( &ConfigData::showInvisibles ); + + cli["-o"]["--out"] + .describe( "output filename" ) + .bind( &ConfigData::outputFilename, "filename" ); + + cli["-r"]["--reporter"] +// .placeholder( "name[:filename]" ) + .describe( "reporter to use (defaults to console)" ) + .bind( &ConfigData::reporterName, "name" ); + + cli["-n"]["--name"] + .describe( "suite name" ) + .bind( &ConfigData::name, "name" ); + + cli["-a"]["--abort"] + .describe( "abort at first failure" ) + .bind( &abortAfterFirst ); + + cli["-x"]["--abortx"] + .describe( "abort after x failures" ) + .bind( &abortAfterX, "no. failures" ); + + cli["-w"]["--warn"] + .describe( "enable warnings" ) + .bind( &addWarning, "warning name" ); + +// - needs updating if reinstated +// cli.into( &setVerbosity ) +// .describe( "level of verbosity (0=no output)" ) +// .shortOpt( "v") +// .longOpt( "verbosity" ) +// .placeholder( "level" ); + + cli[_] + .describe( "which test or tests to use" ) + .bind( &addTestOrTags, "test name, pattern or tags" ); + + cli["-d"]["--durations"] + .describe( "show test durations" ) + .bind( &setShowDurations, "yes/no" ); + + cli["-f"]["--input-file"] + .describe( "load test names to run from a file" ) + .bind( &loadTestNamesFromFile, "filename" ); + + // Less common commands which don't have a short form + cli["--list-test-names-only"] + .describe( "list all/matching test cases names only" ) + .bind( &ConfigData::listTestNamesOnly ); + + cli["--list-reporters"] + .describe( "list all reporters" ) + .bind( &ConfigData::listReporters ); + + cli["--order"] + .describe( "test case order (defaults to decl)" ) + .bind( &setOrder, "decl|lex|rand" ); + + cli["--rng-seed"] + .describe( "set a specific seed for random numbers" ) + .bind( &setRngSeed, "'time'|number" ); + + cli["--force-colour"] + .describe( "force colourised output" ) + .bind( &ConfigData::forceColour ); + + return cli; + } + +} // end namespace Catch + +// #included from: internal/catch_list.hpp +#define TWOBLUECUBES_CATCH_LIST_HPP_INCLUDED + +// #included from: catch_text.h +#define TWOBLUECUBES_CATCH_TEXT_H_INCLUDED + +#define TBC_TEXT_FORMAT_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH + +#define CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE Catch +// #included from: ../external/tbc_text_format.h +// Only use header guard if we are not using an outer namespace +#ifndef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE +# ifdef TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED +# ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +# define TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +# endif +# else +# define TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED +# endif +#endif +#ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +#include +#include +#include + +// Use optional outer namespace +#ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE +namespace CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE { +#endif + +namespace Tbc { + +#ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH + const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH; +#else + const unsigned int consoleWidth = 80; +#endif + + struct TextAttributes { + TextAttributes() + : initialIndent( std::string::npos ), + indent( 0 ), + width( consoleWidth-1 ), + tabChar( '\t' ) + {} + + TextAttributes& setInitialIndent( std::size_t _value ) { initialIndent = _value; return *this; } + TextAttributes& setIndent( std::size_t _value ) { indent = _value; return *this; } + TextAttributes& setWidth( std::size_t _value ) { width = _value; return *this; } + TextAttributes& setTabChar( char _value ) { tabChar = _value; return *this; } + + std::size_t initialIndent; // indent of first line, or npos + std::size_t indent; // indent of subsequent lines, or all if initialIndent is npos + std::size_t width; // maximum width of text, including indent. Longer text will wrap + char tabChar; // If this char is seen the indent is changed to current pos + }; + + class Text { + public: + Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() ) + : attr( _attr ) + { + std::string wrappableChars = " [({.,/|\\-"; + std::size_t indent = _attr.initialIndent != std::string::npos + ? _attr.initialIndent + : _attr.indent; + std::string remainder = _str; + + while( !remainder.empty() ) { + if( lines.size() >= 1000 ) { + lines.push_back( "... message truncated due to excessive size" ); + return; + } + std::size_t tabPos = std::string::npos; + std::size_t width = (std::min)( remainder.size(), _attr.width - indent ); + std::size_t pos = remainder.find_first_of( '\n' ); + if( pos <= width ) { + width = pos; + } + pos = remainder.find_last_of( _attr.tabChar, width ); + if( pos != std::string::npos ) { + tabPos = pos; + if( remainder[width] == '\n' ) + width--; + remainder = remainder.substr( 0, tabPos ) + remainder.substr( tabPos+1 ); + } + + if( width == remainder.size() ) { + spliceLine( indent, remainder, width ); + } + else if( remainder[width] == '\n' ) { + spliceLine( indent, remainder, width ); + if( width <= 1 || remainder.size() != 1 ) + remainder = remainder.substr( 1 ); + indent = _attr.indent; + } + else { + pos = remainder.find_last_of( wrappableChars, width ); + if( pos != std::string::npos && pos > 0 ) { + spliceLine( indent, remainder, pos ); + if( remainder[0] == ' ' ) + remainder = remainder.substr( 1 ); + } + else { + spliceLine( indent, remainder, width-1 ); + lines.back() += "-"; + } + if( lines.size() == 1 ) + indent = _attr.indent; + if( tabPos != std::string::npos ) + indent += tabPos; + } + } + } + + void spliceLine( std::size_t _indent, std::string& _remainder, std::size_t _pos ) { + lines.push_back( std::string( _indent, ' ' ) + _remainder.substr( 0, _pos ) ); + _remainder = _remainder.substr( _pos ); + } + + typedef std::vector::const_iterator const_iterator; + + const_iterator begin() const { return lines.begin(); } + const_iterator end() const { return lines.end(); } + std::string const& last() const { return lines.back(); } + std::size_t size() const { return lines.size(); } + std::string const& operator[]( std::size_t _index ) const { return lines[_index]; } + std::string toString() const { + std::ostringstream oss; + oss << *this; + return oss.str(); + } + + inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) { + for( Text::const_iterator it = _text.begin(), itEnd = _text.end(); + it != itEnd; ++it ) { + if( it != _text.begin() ) + _stream << "\n"; + _stream << *it; + } + return _stream; + } + + private: + std::string str; + TextAttributes attr; + std::vector lines; + }; + +} // end namespace Tbc + +#ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE +} // end outer namespace +#endif + +#endif // TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +#undef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE + +namespace Catch { + using Tbc::Text; + using Tbc::TextAttributes; +} + +// #included from: catch_console_colour.hpp +#define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_HPP_INCLUDED + +namespace Catch { + + struct Colour { + enum Code { + None = 0, + + White, + Red, + Green, + Blue, + Cyan, + Yellow, + Grey, + + Bright = 0x10, + + BrightRed = Bright | Red, + BrightGreen = Bright | Green, + LightGrey = Bright | Grey, + BrightWhite = Bright | White, + + // By intention + FileName = LightGrey, + Warning = Yellow, + ResultError = BrightRed, + ResultSuccess = BrightGreen, + ResultExpectedFailure = Warning, + + Error = BrightRed, + Success = Green, + + OriginalExpression = Cyan, + ReconstructedExpression = Yellow, + + SecondaryText = LightGrey, + Headers = White + }; + + // Use constructed object for RAII guard + Colour( Code _colourCode ); + Colour( Colour const& other ); + ~Colour(); + + // Use static method for one-shot changes + static void use( Code _colourCode ); + + private: + bool m_moved; + }; + + inline std::ostream& operator << ( std::ostream& os, Colour const& ) { return os; } + +} // end namespace Catch + +// #included from: catch_interfaces_reporter.h +#define TWOBLUECUBES_CATCH_INTERFACES_REPORTER_H_INCLUDED + +#include +#include +#include +#include + +namespace Catch +{ + struct ReporterConfig { + explicit ReporterConfig( Ptr const& _fullConfig ) + : m_stream( &_fullConfig->stream() ), m_fullConfig( _fullConfig ) {} + + ReporterConfig( Ptr const& _fullConfig, std::ostream& _stream ) + : m_stream( &_stream ), m_fullConfig( _fullConfig ) {} + + std::ostream& stream() const { return *m_stream; } + Ptr fullConfig() const { return m_fullConfig; } + + private: + std::ostream* m_stream; + Ptr m_fullConfig; + }; + + struct ReporterPreferences { + ReporterPreferences() + : shouldRedirectStdOut( false ) + {} + + bool shouldRedirectStdOut; + }; + + template + struct LazyStat : Option { + LazyStat() : used( false ) {} + LazyStat& operator=( T const& _value ) { + Option::operator=( _value ); + used = false; + return *this; + } + void reset() { + Option::reset(); + used = false; + } + bool used; + }; + + struct TestRunInfo { + TestRunInfo( std::string const& _name ) : name( _name ) {} + std::string name; + }; + struct GroupInfo { + GroupInfo( std::string const& _name, + std::size_t _groupIndex, + std::size_t _groupsCount ) + : name( _name ), + groupIndex( _groupIndex ), + groupsCounts( _groupsCount ) + {} + + std::string name; + std::size_t groupIndex; + std::size_t groupsCounts; + }; + + struct AssertionStats { + AssertionStats( AssertionResult const& _assertionResult, + std::vector const& _infoMessages, + Totals const& _totals ) + : assertionResult( _assertionResult ), + infoMessages( _infoMessages ), + totals( _totals ) + { + if( assertionResult.hasMessage() ) { + // Copy message into messages list. + // !TBD This should have been done earlier, somewhere + MessageBuilder builder( assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType() ); + builder << assertionResult.getMessage(); + builder.m_info.message = builder.m_stream.str(); + + infoMessages.push_back( builder.m_info ); + } + } + virtual ~AssertionStats(); + +# ifdef CATCH_CPP11_OR_GREATER + AssertionStats( AssertionStats const& ) = default; + AssertionStats( AssertionStats && ) = default; + AssertionStats& operator = ( AssertionStats const& ) = default; + AssertionStats& operator = ( AssertionStats && ) = default; +# endif + + AssertionResult assertionResult; + std::vector infoMessages; + Totals totals; + }; + + struct SectionStats { + SectionStats( SectionInfo const& _sectionInfo, + Counts const& _assertions, + double _durationInSeconds, + bool _missingAssertions ) + : sectionInfo( _sectionInfo ), + assertions( _assertions ), + durationInSeconds( _durationInSeconds ), + missingAssertions( _missingAssertions ) + {} + virtual ~SectionStats(); +# ifdef CATCH_CPP11_OR_GREATER + SectionStats( SectionStats const& ) = default; + SectionStats( SectionStats && ) = default; + SectionStats& operator = ( SectionStats const& ) = default; + SectionStats& operator = ( SectionStats && ) = default; +# endif + + SectionInfo sectionInfo; + Counts assertions; + double durationInSeconds; + bool missingAssertions; + }; + + struct TestCaseStats { + TestCaseStats( TestCaseInfo const& _testInfo, + Totals const& _totals, + std::string const& _stdOut, + std::string const& _stdErr, + bool _aborting ) + : testInfo( _testInfo ), + totals( _totals ), + stdOut( _stdOut ), + stdErr( _stdErr ), + aborting( _aborting ) + {} + virtual ~TestCaseStats(); + +# ifdef CATCH_CPP11_OR_GREATER + TestCaseStats( TestCaseStats const& ) = default; + TestCaseStats( TestCaseStats && ) = default; + TestCaseStats& operator = ( TestCaseStats const& ) = default; + TestCaseStats& operator = ( TestCaseStats && ) = default; +# endif + + TestCaseInfo testInfo; + Totals totals; + std::string stdOut; + std::string stdErr; + bool aborting; + }; + + struct TestGroupStats { + TestGroupStats( GroupInfo const& _groupInfo, + Totals const& _totals, + bool _aborting ) + : groupInfo( _groupInfo ), + totals( _totals ), + aborting( _aborting ) + {} + TestGroupStats( GroupInfo const& _groupInfo ) + : groupInfo( _groupInfo ), + aborting( false ) + {} + virtual ~TestGroupStats(); + +# ifdef CATCH_CPP11_OR_GREATER + TestGroupStats( TestGroupStats const& ) = default; + TestGroupStats( TestGroupStats && ) = default; + TestGroupStats& operator = ( TestGroupStats const& ) = default; + TestGroupStats& operator = ( TestGroupStats && ) = default; +# endif + + GroupInfo groupInfo; + Totals totals; + bool aborting; + }; + + struct TestRunStats { + TestRunStats( TestRunInfo const& _runInfo, + Totals const& _totals, + bool _aborting ) + : runInfo( _runInfo ), + totals( _totals ), + aborting( _aborting ) + {} + virtual ~TestRunStats(); + +# ifndef CATCH_CPP11_OR_GREATER + TestRunStats( TestRunStats const& _other ) + : runInfo( _other.runInfo ), + totals( _other.totals ), + aborting( _other.aborting ) + {} +# else + TestRunStats( TestRunStats const& ) = default; + TestRunStats( TestRunStats && ) = default; + TestRunStats& operator = ( TestRunStats const& ) = default; + TestRunStats& operator = ( TestRunStats && ) = default; +# endif + + TestRunInfo runInfo; + Totals totals; + bool aborting; + }; + + struct IStreamingReporter : IShared { + virtual ~IStreamingReporter(); + + // Implementing class must also provide the following static method: + // static std::string getDescription(); + + virtual ReporterPreferences getPreferences() const = 0; + + virtual void noMatchingTestCases( std::string const& spec ) = 0; + + virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0; + virtual void testGroupStarting( GroupInfo const& groupInfo ) = 0; + + virtual void testCaseStarting( TestCaseInfo const& testInfo ) = 0; + virtual void sectionStarting( SectionInfo const& sectionInfo ) = 0; + + virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0; + + // The return value indicates if the messages buffer should be cleared: + virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0; + virtual void sectionEnded( SectionStats const& sectionStats ) = 0; + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0; + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0; + virtual void testRunEnded( TestRunStats const& testRunStats ) = 0; + + virtual void skipTest( TestCaseInfo const& testInfo ) = 0; + }; + + struct IReporterFactory { + virtual ~IReporterFactory(); + virtual IStreamingReporter* create( ReporterConfig const& config ) const = 0; + virtual std::string getDescription() const = 0; + }; + + struct IReporterRegistry { + typedef std::map FactoryMap; + + virtual ~IReporterRegistry(); + virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const = 0; + virtual FactoryMap const& getFactories() const = 0; + }; + +} + +#include +#include + +namespace Catch { + + inline std::size_t listTests( Config const& config ) { + + TestSpec testSpec = config.testSpec(); + if( config.testSpec().hasFilters() ) + Catch::cout() << "Matching test cases:\n"; + else { + Catch::cout() << "All available test cases:\n"; + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); + } + + std::size_t matchedTests = 0; + TextAttributes nameAttr, tagsAttr; + nameAttr.setInitialIndent( 2 ).setIndent( 4 ); + tagsAttr.setIndent( 6 ); + + std::vector matchedTestCases; + getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases ); + for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); + it != itEnd; + ++it ) { + matchedTests++; + TestCaseInfo const& testCaseInfo = it->getTestCaseInfo(); + Colour::Code colour = testCaseInfo.isHidden() + ? Colour::SecondaryText + : Colour::None; + Colour colourGuard( colour ); + + Catch::cout() << Text( testCaseInfo.name, nameAttr ) << std::endl; + if( !testCaseInfo.tags.empty() ) + Catch::cout() << Text( testCaseInfo.tagsAsString, tagsAttr ) << std::endl; + } + + if( !config.testSpec().hasFilters() ) + Catch::cout() << pluralise( matchedTests, "test case" ) << "\n" << std::endl; + else + Catch::cout() << pluralise( matchedTests, "matching test case" ) << "\n" << std::endl; + return matchedTests; + } + + inline std::size_t listTestsNamesOnly( Config const& config ) { + TestSpec testSpec = config.testSpec(); + if( !config.testSpec().hasFilters() ) + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); + std::size_t matchedTests = 0; + std::vector matchedTestCases; + getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases ); + for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); + it != itEnd; + ++it ) { + matchedTests++; + TestCaseInfo const& testCaseInfo = it->getTestCaseInfo(); + Catch::cout() << testCaseInfo.name << std::endl; + } + return matchedTests; + } + + struct TagInfo { + TagInfo() : count ( 0 ) {} + void add( std::string const& spelling ) { + ++count; + spellings.insert( spelling ); + } + std::string all() const { + std::string out; + for( std::set::const_iterator it = spellings.begin(), itEnd = spellings.end(); + it != itEnd; + ++it ) + out += "[" + *it + "]"; + return out; + } + std::set spellings; + std::size_t count; + }; + + inline std::size_t listTags( Config const& config ) { + TestSpec testSpec = config.testSpec(); + if( config.testSpec().hasFilters() ) + Catch::cout() << "Tags for matching test cases:\n"; + else { + Catch::cout() << "All available tags:\n"; + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); + } + + std::map tagCounts; + + std::vector matchedTestCases; + getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases ); + for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); + it != itEnd; + ++it ) { + for( std::set::const_iterator tagIt = it->getTestCaseInfo().tags.begin(), + tagItEnd = it->getTestCaseInfo().tags.end(); + tagIt != tagItEnd; + ++tagIt ) { + std::string tagName = *tagIt; + std::string lcaseTagName = toLower( tagName ); + std::map::iterator countIt = tagCounts.find( lcaseTagName ); + if( countIt == tagCounts.end() ) + countIt = tagCounts.insert( std::make_pair( lcaseTagName, TagInfo() ) ).first; + countIt->second.add( tagName ); + } + } + + for( std::map::const_iterator countIt = tagCounts.begin(), + countItEnd = tagCounts.end(); + countIt != countItEnd; + ++countIt ) { + std::ostringstream oss; + oss << " " << std::setw(2) << countIt->second.count << " "; + Text wrapper( countIt->second.all(), TextAttributes() + .setInitialIndent( 0 ) + .setIndent( oss.str().size() ) + .setWidth( CATCH_CONFIG_CONSOLE_WIDTH-10 ) ); + Catch::cout() << oss.str() << wrapper << "\n"; + } + Catch::cout() << pluralise( tagCounts.size(), "tag" ) << "\n" << std::endl; + return tagCounts.size(); + } + + inline std::size_t listReporters( Config const& /*config*/ ) { + Catch::cout() << "Available reporters:\n"; + IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories(); + IReporterRegistry::FactoryMap::const_iterator itBegin = factories.begin(), itEnd = factories.end(), it; + std::size_t maxNameLen = 0; + for(it = itBegin; it != itEnd; ++it ) + maxNameLen = (std::max)( maxNameLen, it->first.size() ); + + for(it = itBegin; it != itEnd; ++it ) { + Text wrapper( it->second->getDescription(), TextAttributes() + .setInitialIndent( 0 ) + .setIndent( 7+maxNameLen ) + .setWidth( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen-8 ) ); + Catch::cout() << " " + << it->first + << ":" + << std::string( maxNameLen - it->first.size() + 2, ' ' ) + << wrapper << "\n"; + } + Catch::cout() << std::endl; + return factories.size(); + } + + inline Option list( Config const& config ) { + Option listedCount; + if( config.listTests() ) + listedCount = listedCount.valueOr(0) + listTests( config ); + if( config.listTestNamesOnly() ) + listedCount = listedCount.valueOr(0) + listTestsNamesOnly( config ); + if( config.listTags() ) + listedCount = listedCount.valueOr(0) + listTags( config ); + if( config.listReporters() ) + listedCount = listedCount.valueOr(0) + listReporters( config ); + return listedCount; + } + +} // end namespace Catch + +// #included from: internal/catch_runner_impl.hpp +#define TWOBLUECUBES_CATCH_RUNNER_IMPL_HPP_INCLUDED + +// #included from: catch_test_case_tracker.hpp +#define TWOBLUECUBES_CATCH_TEST_CASE_TRACKER_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { +namespace SectionTracking { + + class TrackedSection { + + typedef std::map TrackedSections; + + public: + enum RunState { + NotStarted, + Executing, + ExecutingChildren, + Completed + }; + + TrackedSection( std::string const& name, TrackedSection* parent ) + : m_name( name ), m_runState( NotStarted ), m_parent( parent ) + {} + + RunState runState() const { return m_runState; } + + TrackedSection* findChild( std::string const& childName ) { + TrackedSections::iterator it = m_children.find( childName ); + return it != m_children.end() + ? &it->second + : NULL; + } + TrackedSection* acquireChild( std::string const& childName ) { + if( TrackedSection* child = findChild( childName ) ) + return child; + m_children.insert( std::make_pair( childName, TrackedSection( childName, this ) ) ); + return findChild( childName ); + } + void enter() { + if( m_runState == NotStarted ) + m_runState = Executing; + } + void leave() { + for( TrackedSections::const_iterator it = m_children.begin(), itEnd = m_children.end(); + it != itEnd; + ++it ) + if( it->second.runState() != Completed ) { + m_runState = ExecutingChildren; + return; + } + m_runState = Completed; + } + TrackedSection* getParent() { + return m_parent; + } + bool hasChildren() const { + return !m_children.empty(); + } + + private: + std::string m_name; + RunState m_runState; + TrackedSections m_children; + TrackedSection* m_parent; + + }; + + class TestCaseTracker { + public: + TestCaseTracker( std::string const& testCaseName ) + : m_testCase( testCaseName, NULL ), + m_currentSection( &m_testCase ), + m_completedASectionThisRun( false ) + {} + + bool enterSection( std::string const& name ) { + TrackedSection* child = m_currentSection->acquireChild( name ); + if( m_completedASectionThisRun || child->runState() == TrackedSection::Completed ) + return false; + + m_currentSection = child; + m_currentSection->enter(); + return true; + } + void leaveSection() { + m_currentSection->leave(); + m_currentSection = m_currentSection->getParent(); + assert( m_currentSection != NULL ); + m_completedASectionThisRun = true; + } + + bool currentSectionHasChildren() const { + return m_currentSection->hasChildren(); + } + bool isCompleted() const { + return m_testCase.runState() == TrackedSection::Completed; + } + + class Guard { + public: + Guard( TestCaseTracker& tracker ) : m_tracker( tracker ) { + m_tracker.enterTestCase(); + } + ~Guard() { + m_tracker.leaveTestCase(); + } + private: + Guard( Guard const& ); + void operator = ( Guard const& ); + TestCaseTracker& m_tracker; + }; + + private: + void enterTestCase() { + m_currentSection = &m_testCase; + m_completedASectionThisRun = false; + m_testCase.enter(); + } + void leaveTestCase() { + m_testCase.leave(); + } + + TrackedSection m_testCase; + TrackedSection* m_currentSection; + bool m_completedASectionThisRun; + }; + +} // namespace SectionTracking + +using SectionTracking::TestCaseTracker; + +} // namespace Catch + +// #included from: catch_fatal_condition.hpp +#define TWOBLUECUBES_CATCH_FATAL_CONDITION_H_INCLUDED + +namespace Catch { + + // Report the error condition then exit the process + inline void fatal( std::string const& message, int exitCode ) { + IContext& context = Catch::getCurrentContext(); + IResultCapture* resultCapture = context.getResultCapture(); + resultCapture->handleFatalErrorCondition( message ); + + if( Catch::alwaysTrue() ) // avoids "no return" warnings + exit( exitCode ); + } + +} // namespace Catch + +#if defined ( CATCH_PLATFORM_WINDOWS ) ///////////////////////////////////////// + +namespace Catch { + + struct FatalConditionHandler { + void reset() {} + }; + +} // namespace Catch + +#else // Not Windows - assumed to be POSIX compatible ////////////////////////// + +#include + +namespace Catch { + + struct SignalDefs { int id; const char* name; }; + extern SignalDefs signalDefs[]; + SignalDefs signalDefs[] = { + { SIGINT, "SIGINT - Terminal interrupt signal" }, + { SIGILL, "SIGILL - Illegal instruction signal" }, + { SIGFPE, "SIGFPE - Floating point error signal" }, + { SIGSEGV, "SIGSEGV - Segmentation violation signal" }, + { SIGTERM, "SIGTERM - Termination request signal" }, + { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" } + }; + + struct FatalConditionHandler { + + static void handleSignal( int sig ) { + for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) + if( sig == signalDefs[i].id ) + fatal( signalDefs[i].name, -sig ); + fatal( "", -sig ); + } + + FatalConditionHandler() : m_isSet( true ) { + for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) + signal( signalDefs[i].id, handleSignal ); + } + ~FatalConditionHandler() { + reset(); + } + void reset() { + if( m_isSet ) { + for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) + signal( signalDefs[i].id, SIG_DFL ); + m_isSet = false; + } + } + + bool m_isSet; + }; + +} // namespace Catch + +#endif // not Windows + +#include +#include + +namespace Catch { + + class StreamRedirect { + + public: + StreamRedirect( std::ostream& stream, std::string& targetString ) + : m_stream( stream ), + m_prevBuf( stream.rdbuf() ), + m_targetString( targetString ) + { + stream.rdbuf( m_oss.rdbuf() ); + } + + ~StreamRedirect() { + m_targetString += m_oss.str(); + m_stream.rdbuf( m_prevBuf ); + } + + private: + std::ostream& m_stream; + std::streambuf* m_prevBuf; + std::ostringstream m_oss; + std::string& m_targetString; + }; + + /////////////////////////////////////////////////////////////////////////// + + class RunContext : public IResultCapture, public IRunner { + + RunContext( RunContext const& ); + void operator =( RunContext const& ); + + public: + + explicit RunContext( Ptr const& config, Ptr const& reporter ) + : m_runInfo( config->name() ), + m_context( getCurrentMutableContext() ), + m_activeTestCase( NULL ), + m_config( config ), + m_reporter( reporter ), + m_prevRunner( m_context.getRunner() ), + m_prevResultCapture( m_context.getResultCapture() ), + m_prevConfig( m_context.getConfig() ) + { + m_context.setRunner( this ); + m_context.setConfig( m_config ); + m_context.setResultCapture( this ); + m_reporter->testRunStarting( m_runInfo ); + } + + virtual ~RunContext() { + m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, aborting() ) ); + m_context.setRunner( m_prevRunner ); + m_context.setConfig( NULL ); + m_context.setResultCapture( m_prevResultCapture ); + m_context.setConfig( m_prevConfig ); + } + + void testGroupStarting( std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount ) { + m_reporter->testGroupStarting( GroupInfo( testSpec, groupIndex, groupsCount ) ); + } + void testGroupEnded( std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount ) { + m_reporter->testGroupEnded( TestGroupStats( GroupInfo( testSpec, groupIndex, groupsCount ), totals, aborting() ) ); + } + + Totals runTest( TestCase const& testCase ) { + Totals prevTotals = m_totals; + + std::string redirectedCout; + std::string redirectedCerr; + + TestCaseInfo testInfo = testCase.getTestCaseInfo(); + + m_reporter->testCaseStarting( testInfo ); + + m_activeTestCase = &testCase; + m_testCaseTracker = TestCaseTracker( testInfo.name ); + + do { + do { + runCurrentTest( redirectedCout, redirectedCerr ); + } + while( !m_testCaseTracker->isCompleted() && !aborting() ); + } + while( getCurrentContext().advanceGeneratorsForCurrentTest() && !aborting() ); + + Totals deltaTotals = m_totals.delta( prevTotals ); + m_totals.testCases += deltaTotals.testCases; + m_reporter->testCaseEnded( TestCaseStats( testInfo, + deltaTotals, + redirectedCout, + redirectedCerr, + aborting() ) ); + + m_activeTestCase = NULL; + m_testCaseTracker.reset(); + + return deltaTotals; + } + + Ptr config() const { + return m_config; + } + + private: // IResultCapture + + virtual void assertionEnded( AssertionResult const& result ) { + if( result.getResultType() == ResultWas::Ok ) { + m_totals.assertions.passed++; + } + else if( !result.isOk() ) { + m_totals.assertions.failed++; + } + + if( m_reporter->assertionEnded( AssertionStats( result, m_messages, m_totals ) ) ) + m_messages.clear(); + + // Reset working state + m_lastAssertionInfo = AssertionInfo( "", m_lastAssertionInfo.lineInfo, "{Unknown expression after the reported line}" , m_lastAssertionInfo.resultDisposition ); + m_lastResult = result; + } + + virtual bool sectionStarted ( + SectionInfo const& sectionInfo, + Counts& assertions + ) + { + std::ostringstream oss; + oss << sectionInfo.name << "@" << sectionInfo.lineInfo; + + if( !m_testCaseTracker->enterSection( oss.str() ) ) + return false; + + m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo; + + m_reporter->sectionStarting( sectionInfo ); + + assertions = m_totals.assertions; + + return true; + } + bool testForMissingAssertions( Counts& assertions ) { + if( assertions.total() != 0 || + !m_config->warnAboutMissingAssertions() || + m_testCaseTracker->currentSectionHasChildren() ) + return false; + m_totals.assertions.failed++; + assertions.failed++; + return true; + } + + virtual void sectionEnded( SectionInfo const& info, Counts const& prevAssertions, double _durationInSeconds ) { + if( std::uncaught_exception() ) { + m_unfinishedSections.push_back( UnfinishedSections( info, prevAssertions, _durationInSeconds ) ); + return; + } + + Counts assertions = m_totals.assertions - prevAssertions; + bool missingAssertions = testForMissingAssertions( assertions ); + + m_testCaseTracker->leaveSection(); + + m_reporter->sectionEnded( SectionStats( info, assertions, _durationInSeconds, missingAssertions ) ); + m_messages.clear(); + } + + virtual void pushScopedMessage( MessageInfo const& message ) { + m_messages.push_back( message ); + } + + virtual void popScopedMessage( MessageInfo const& message ) { + m_messages.erase( std::remove( m_messages.begin(), m_messages.end(), message ), m_messages.end() ); + } + + virtual std::string getCurrentTestName() const { + return m_activeTestCase + ? m_activeTestCase->getTestCaseInfo().name + : ""; + } + + virtual const AssertionResult* getLastResult() const { + return &m_lastResult; + } + + virtual void handleFatalErrorCondition( std::string const& message ) { + ResultBuilder resultBuilder = makeUnexpectedResultBuilder(); + resultBuilder.setResultType( ResultWas::FatalErrorCondition ); + resultBuilder << message; + resultBuilder.captureExpression(); + + handleUnfinishedSections(); + + // Recreate section for test case (as we will lose the one that was in scope) + TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description ); + + Counts assertions; + assertions.failed = 1; + SectionStats testCaseSectionStats( testCaseSection, assertions, 0, false ); + m_reporter->sectionEnded( testCaseSectionStats ); + + TestCaseInfo testInfo = m_activeTestCase->getTestCaseInfo(); + + Totals deltaTotals; + deltaTotals.testCases.failed = 1; + m_reporter->testCaseEnded( TestCaseStats( testInfo, + deltaTotals, + "", + "", + false ) ); + m_totals.testCases.failed++; + testGroupEnded( "", m_totals, 1, 1 ); + m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, false ) ); + } + + public: + // !TBD We need to do this another way! + bool aborting() const { + return m_totals.assertions.failed == static_cast( m_config->abortAfter() ); + } + + private: + + void runCurrentTest( std::string& redirectedCout, std::string& redirectedCerr ) { + TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description ); + m_reporter->sectionStarting( testCaseSection ); + Counts prevAssertions = m_totals.assertions; + double duration = 0; + try { + m_lastAssertionInfo = AssertionInfo( "TEST_CASE", testCaseInfo.lineInfo, "", ResultDisposition::Normal ); + TestCaseTracker::Guard guard( *m_testCaseTracker ); + + Timer timer; + timer.start(); + if( m_reporter->getPreferences().shouldRedirectStdOut ) { + StreamRedirect coutRedir( Catch::cout(), redirectedCout ); + StreamRedirect cerrRedir( Catch::cerr(), redirectedCerr ); + invokeActiveTestCase(); + } + else { + invokeActiveTestCase(); + } + duration = timer.getElapsedSeconds(); + } + catch( TestFailureException& ) { + // This just means the test was aborted due to failure + } + catch(...) { + makeUnexpectedResultBuilder().useActiveException(); + } + handleUnfinishedSections(); + m_messages.clear(); + + Counts assertions = m_totals.assertions - prevAssertions; + bool missingAssertions = testForMissingAssertions( assertions ); + + if( testCaseInfo.okToFail() ) { + std::swap( assertions.failedButOk, assertions.failed ); + m_totals.assertions.failed -= assertions.failedButOk; + m_totals.assertions.failedButOk += assertions.failedButOk; + } + + SectionStats testCaseSectionStats( testCaseSection, assertions, duration, missingAssertions ); + m_reporter->sectionEnded( testCaseSectionStats ); + } + + void invokeActiveTestCase() { + FatalConditionHandler fatalConditionHandler; // Handle signals + m_activeTestCase->invoke(); + fatalConditionHandler.reset(); + } + + private: + + ResultBuilder makeUnexpectedResultBuilder() const { + return ResultBuilder( m_lastAssertionInfo.macroName.c_str(), + m_lastAssertionInfo.lineInfo, + m_lastAssertionInfo.capturedExpression.c_str(), + m_lastAssertionInfo.resultDisposition ); + } + + void handleUnfinishedSections() { + // If sections ended prematurely due to an exception we stored their + // infos here so we can tear them down outside the unwind process. + for( std::vector::const_reverse_iterator it = m_unfinishedSections.rbegin(), + itEnd = m_unfinishedSections.rend(); + it != itEnd; + ++it ) + sectionEnded( it->info, it->prevAssertions, it->durationInSeconds ); + m_unfinishedSections.clear(); + } + + struct UnfinishedSections { + UnfinishedSections( SectionInfo const& _info, Counts const& _prevAssertions, double _durationInSeconds ) + : info( _info ), prevAssertions( _prevAssertions ), durationInSeconds( _durationInSeconds ) + {} + + SectionInfo info; + Counts prevAssertions; + double durationInSeconds; + }; + + TestRunInfo m_runInfo; + IMutableContext& m_context; + TestCase const* m_activeTestCase; + Option m_testCaseTracker; + AssertionResult m_lastResult; + + Ptr m_config; + Totals m_totals; + Ptr m_reporter; + std::vector m_messages; + IRunner* m_prevRunner; + IResultCapture* m_prevResultCapture; + Ptr m_prevConfig; + AssertionInfo m_lastAssertionInfo; + std::vector m_unfinishedSections; + }; + + IResultCapture& getResultCapture() { + if( IResultCapture* capture = getCurrentContext().getResultCapture() ) + return *capture; + else + throw std::logic_error( "No result capture instance" ); + } + +} // end namespace Catch + +// #included from: internal/catch_version.h +#define TWOBLUECUBES_CATCH_VERSION_H_INCLUDED + +namespace Catch { + + // Versioning information + struct Version { + Version( unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _buildNumber, + char const* const _branchName ) + : majorVersion( _majorVersion ), + minorVersion( _minorVersion ), + buildNumber( _buildNumber ), + branchName( _branchName ) + {} + + unsigned int const majorVersion; + unsigned int const minorVersion; + unsigned int const buildNumber; + char const* const branchName; + + private: + void operator=( Version const& ); + }; + + extern Version libraryVersion; +} + +#include +#include +#include + +namespace Catch { + + class Runner { + + public: + Runner( Ptr const& config ) + : m_config( config ) + { + openStream(); + makeReporter(); + } + + Totals runTests() { + + RunContext context( m_config.get(), m_reporter ); + + Totals totals; + + context.testGroupStarting( "all tests", 1, 1 ); // deprecated? + + TestSpec testSpec = m_config->testSpec(); + if( !testSpec.hasFilters() ) + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "~[.]" ).testSpec(); // All not hidden tests + + std::vector testCases; + getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, *m_config, testCases ); + + int testsRunForGroup = 0; + for( std::vector::const_iterator it = testCases.begin(), itEnd = testCases.end(); + it != itEnd; + ++it ) { + testsRunForGroup++; + if( m_testsAlreadyRun.find( *it ) == m_testsAlreadyRun.end() ) { + + if( context.aborting() ) + break; + + totals += context.runTest( *it ); + m_testsAlreadyRun.insert( *it ); + } + } + std::vector skippedTestCases; + getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, *m_config, skippedTestCases, true ); + + for( std::vector::const_iterator it = skippedTestCases.begin(), itEnd = skippedTestCases.end(); + it != itEnd; + ++it ) + m_reporter->skipTest( *it ); + + context.testGroupEnded( "all tests", totals, 1, 1 ); + return totals; + } + + private: + void openStream() { + // Open output file, if specified + if( !m_config->getFilename().empty() ) { + m_ofs.open( m_config->getFilename().c_str() ); + if( m_ofs.fail() ) { + std::ostringstream oss; + oss << "Unable to open file: '" << m_config->getFilename() << "'"; + throw std::domain_error( oss.str() ); + } + m_config->setStreamBuf( m_ofs.rdbuf() ); + } + } + void makeReporter() { + std::string reporterName = m_config->getReporterName().empty() + ? "console" + : m_config->getReporterName(); + + m_reporter = getRegistryHub().getReporterRegistry().create( reporterName, m_config.get() ); + if( !m_reporter ) { + std::ostringstream oss; + oss << "No reporter registered with name: '" << reporterName << "'"; + throw std::domain_error( oss.str() ); + } + } + + private: + Ptr m_config; + std::ofstream m_ofs; + Ptr m_reporter; + std::set m_testsAlreadyRun; + }; + + class Session : NonCopyable { + static bool alreadyInstantiated; + + public: + + struct OnUnusedOptions { enum DoWhat { Ignore, Fail }; }; + + Session() + : m_cli( makeCommandLineParser() ) { + if( alreadyInstantiated ) { + std::string msg = "Only one instance of Catch::Session can ever be used"; + Catch::cerr() << msg << std::endl; + throw std::logic_error( msg ); + } + alreadyInstantiated = true; + } + ~Session() { + Catch::cleanUp(); + } + + void showHelp( std::string const& processName ) { + Catch::cout() << "\nCatch v" << libraryVersion.majorVersion << "." + << libraryVersion.minorVersion << " build " + << libraryVersion.buildNumber; + if( libraryVersion.branchName != std::string( "master" ) ) + Catch::cout() << " (" << libraryVersion.branchName << " branch)"; + Catch::cout() << "\n"; + + m_cli.usage( Catch::cout(), processName ); + Catch::cout() << "For more detail usage please see the project docs\n" << std::endl; + } + + int applyCommandLine( int argc, char* const argv[], OnUnusedOptions::DoWhat unusedOptionBehaviour = OnUnusedOptions::Fail ) { + try { + m_cli.setThrowOnUnrecognisedTokens( unusedOptionBehaviour == OnUnusedOptions::Fail ); + m_unusedTokens = m_cli.parseInto( argc, argv, m_configData ); + if( m_configData.showHelp ) + showHelp( m_configData.processName ); + m_config.reset(); + } + catch( std::exception& ex ) { + { + Colour colourGuard( Colour::Red ); + Catch::cerr() << "\nError(s) in input:\n" + << Text( ex.what(), TextAttributes().setIndent(2) ) + << "\n\n"; + } + m_cli.usage( Catch::cout(), m_configData.processName ); + return (std::numeric_limits::max)(); + } + return 0; + } + + void useConfigData( ConfigData const& _configData ) { + m_configData = _configData; + m_config.reset(); + } + + int run( int argc, char* const argv[] ) { + + int returnCode = applyCommandLine( argc, argv ); + if( returnCode == 0 ) + returnCode = run(); + return returnCode; + } + + int run() { + if( m_configData.showHelp ) + return 0; + + try + { + config(); // Force config to be constructed + + std::srand( m_configData.rngSeed ); + + Runner runner( m_config ); + + // Handle list request + if( Option listed = list( config() ) ) + return static_cast( *listed ); + + return static_cast( runner.runTests().assertions.failed ); + } + catch( std::exception& ex ) { + Catch::cerr() << ex.what() << std::endl; + return (std::numeric_limits::max)(); + } + } + + Clara::CommandLine const& cli() const { + return m_cli; + } + std::vector const& unusedTokens() const { + return m_unusedTokens; + } + ConfigData& configData() { + return m_configData; + } + Config& config() { + if( !m_config ) + m_config = new Config( m_configData ); + return *m_config; + } + + private: + Clara::CommandLine m_cli; + std::vector m_unusedTokens; + ConfigData m_configData; + Ptr m_config; + }; + + bool Session::alreadyInstantiated = false; + +} // end namespace Catch + +// #included from: catch_registry_hub.hpp +#define TWOBLUECUBES_CATCH_REGISTRY_HUB_HPP_INCLUDED + +// #included from: catch_test_case_registry_impl.hpp +#define TWOBLUECUBES_CATCH_TEST_CASE_REGISTRY_IMPL_HPP_INCLUDED + +#include +#include +#include +#include +#include + +namespace Catch { + + class TestRegistry : public ITestCaseRegistry { + struct LexSort { + bool operator() (TestCase i,TestCase j) const { return (i const& getAllTests() const { + return m_functionsInOrder; + } + + virtual std::vector const& getAllNonHiddenTests() const { + return m_nonHiddenFunctions; + } + + virtual void getFilteredTests( TestSpec const& testSpec, IConfig const& config, std::vector& matchingTestCases, bool negated = false ) const { + + for( std::vector::const_iterator it = m_functionsInOrder.begin(), + itEnd = m_functionsInOrder.end(); + it != itEnd; + ++it ) { + bool includeTest = testSpec.matches( *it ) && ( config.allowThrows() || !it->throws() ); + if( includeTest != negated ) + matchingTestCases.push_back( *it ); + } + sortTests( config, matchingTestCases ); + } + + private: + + static void sortTests( IConfig const& config, std::vector& matchingTestCases ) { + + switch( config.runOrder() ) { + case RunTests::InLexicographicalOrder: + std::sort( matchingTestCases.begin(), matchingTestCases.end(), LexSort() ); + break; + case RunTests::InRandomOrder: + { + RandomNumberGenerator rng; + std::random_shuffle( matchingTestCases.begin(), matchingTestCases.end(), rng ); + } + break; + case RunTests::InDeclarationOrder: + // already in declaration order + break; + } + } + std::set m_functions; + std::vector m_functionsInOrder; + std::vector m_nonHiddenFunctions; + size_t m_unnamedCount; + }; + + /////////////////////////////////////////////////////////////////////////// + + class FreeFunctionTestCase : public SharedImpl { + public: + + FreeFunctionTestCase( TestFunction fun ) : m_fun( fun ) {} + + virtual void invoke() const { + m_fun(); + } + + private: + virtual ~FreeFunctionTestCase(); + + TestFunction m_fun; + }; + + inline std::string extractClassName( std::string const& classOrQualifiedMethodName ) { + std::string className = classOrQualifiedMethodName; + if( startsWith( className, "&" ) ) + { + std::size_t lastColons = className.rfind( "::" ); + std::size_t penultimateColons = className.rfind( "::", lastColons-1 ); + if( penultimateColons == std::string::npos ) + penultimateColons = 1; + className = className.substr( penultimateColons, lastColons-penultimateColons ); + } + return className; + } + + /////////////////////////////////////////////////////////////////////////// + + AutoReg::AutoReg( TestFunction function, + SourceLineInfo const& lineInfo, + NameAndDesc const& nameAndDesc ) { + registerTestCase( new FreeFunctionTestCase( function ), "", nameAndDesc, lineInfo ); + } + + AutoReg::~AutoReg() {} + + void AutoReg::registerTestCase( ITestCase* testCase, + char const* classOrQualifiedMethodName, + NameAndDesc const& nameAndDesc, + SourceLineInfo const& lineInfo ) { + + getMutableRegistryHub().registerTest + ( makeTestCase( testCase, + extractClassName( classOrQualifiedMethodName ), + nameAndDesc.name, + nameAndDesc.description, + lineInfo ) ); + } + +} // end namespace Catch + +// #included from: catch_reporter_registry.hpp +#define TWOBLUECUBES_CATCH_REPORTER_REGISTRY_HPP_INCLUDED + +#include + +namespace Catch { + + class ReporterRegistry : public IReporterRegistry { + + public: + + virtual ~ReporterRegistry() { + deleteAllValues( m_factories ); + } + + virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const { + FactoryMap::const_iterator it = m_factories.find( name ); + if( it == m_factories.end() ) + return NULL; + return it->second->create( ReporterConfig( config ) ); + } + + void registerReporter( std::string const& name, IReporterFactory* factory ) { + m_factories.insert( std::make_pair( name, factory ) ); + } + + FactoryMap const& getFactories() const { + return m_factories; + } + + private: + FactoryMap m_factories; + }; +} + +// #included from: catch_exception_translator_registry.hpp +#define TWOBLUECUBES_CATCH_EXCEPTION_TRANSLATOR_REGISTRY_HPP_INCLUDED + +#ifdef __OBJC__ +#import "Foundation/Foundation.h" +#endif + +namespace Catch { + + class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry { + public: + ~ExceptionTranslatorRegistry() { + deleteAll( m_translators ); + } + + virtual void registerTranslator( const IExceptionTranslator* translator ) { + m_translators.push_back( translator ); + } + + virtual std::string translateActiveException() const { + try { +#ifdef __OBJC__ + // In Objective-C try objective-c exceptions first + @try { + throw; + } + @catch (NSException *exception) { + return Catch::toString( [exception description] ); + } +#else + throw; +#endif + } + catch( TestFailureException& ) { + throw; + } + catch( std::exception& ex ) { + return ex.what(); + } + catch( std::string& msg ) { + return msg; + } + catch( const char* msg ) { + return msg; + } + catch(...) { + return tryTranslators( m_translators.begin() ); + } + } + + std::string tryTranslators( std::vector::const_iterator it ) const { + if( it == m_translators.end() ) + return "Unknown exception"; + + try { + return (*it)->translate(); + } + catch(...) { + return tryTranslators( it+1 ); + } + } + + private: + std::vector m_translators; + }; +} + +namespace Catch { + + namespace { + + class RegistryHub : public IRegistryHub, public IMutableRegistryHub { + + RegistryHub( RegistryHub const& ); + void operator=( RegistryHub const& ); + + public: // IRegistryHub + RegistryHub() { + } + virtual IReporterRegistry const& getReporterRegistry() const { + return m_reporterRegistry; + } + virtual ITestCaseRegistry const& getTestCaseRegistry() const { + return m_testCaseRegistry; + } + virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() { + return m_exceptionTranslatorRegistry; + } + + public: // IMutableRegistryHub + virtual void registerReporter( std::string const& name, IReporterFactory* factory ) { + m_reporterRegistry.registerReporter( name, factory ); + } + virtual void registerTest( TestCase const& testInfo ) { + m_testCaseRegistry.registerTest( testInfo ); + } + virtual void registerTranslator( const IExceptionTranslator* translator ) { + m_exceptionTranslatorRegistry.registerTranslator( translator ); + } + + private: + TestRegistry m_testCaseRegistry; + ReporterRegistry m_reporterRegistry; + ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; + }; + + // Single, global, instance + inline RegistryHub*& getTheRegistryHub() { + static RegistryHub* theRegistryHub = NULL; + if( !theRegistryHub ) + theRegistryHub = new RegistryHub(); + return theRegistryHub; + } + } + + IRegistryHub& getRegistryHub() { + return *getTheRegistryHub(); + } + IMutableRegistryHub& getMutableRegistryHub() { + return *getTheRegistryHub(); + } + void cleanUp() { + delete getTheRegistryHub(); + getTheRegistryHub() = NULL; + cleanUpContext(); + } + std::string translateActiveException() { + return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); + } + +} // end namespace Catch + +// #included from: catch_notimplemented_exception.hpp +#define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_HPP_INCLUDED + +#include + +namespace Catch { + + NotImplementedException::NotImplementedException( SourceLineInfo const& lineInfo ) + : m_lineInfo( lineInfo ) { + std::ostringstream oss; + oss << lineInfo << ": function "; + oss << "not implemented"; + m_what = oss.str(); + } + + const char* NotImplementedException::what() const CATCH_NOEXCEPT { + return m_what.c_str(); + } + +} // end namespace Catch + +// #included from: catch_context_impl.hpp +#define TWOBLUECUBES_CATCH_CONTEXT_IMPL_HPP_INCLUDED + +// #included from: catch_stream.hpp +#define TWOBLUECUBES_CATCH_STREAM_HPP_INCLUDED + +// #included from: catch_streambuf.h +#define TWOBLUECUBES_CATCH_STREAMBUF_H_INCLUDED + +#include + +namespace Catch { + + class StreamBufBase : public std::streambuf { + public: + virtual ~StreamBufBase() CATCH_NOEXCEPT; + }; +} + +#include +#include +#include + +namespace Catch { + + template + class StreamBufImpl : public StreamBufBase { + char data[bufferSize]; + WriterF m_writer; + + public: + StreamBufImpl() { + setp( data, data + sizeof(data) ); + } + + ~StreamBufImpl() CATCH_NOEXCEPT { + sync(); + } + + private: + int overflow( int c ) { + sync(); + + if( c != EOF ) { + if( pbase() == epptr() ) + m_writer( std::string( 1, static_cast( c ) ) ); + else + sputc( static_cast( c ) ); + } + return 0; + } + + int sync() { + if( pbase() != pptr() ) { + m_writer( std::string( pbase(), static_cast( pptr() - pbase() ) ) ); + setp( pbase(), epptr() ); + } + return 0; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + struct OutputDebugWriter { + + void operator()( std::string const&str ) { + writeToDebugConsole( str ); + } + }; + + Stream::Stream() + : streamBuf( NULL ), isOwned( false ) + {} + + Stream::Stream( std::streambuf* _streamBuf, bool _isOwned ) + : streamBuf( _streamBuf ), isOwned( _isOwned ) + {} + + void Stream::release() { + if( isOwned ) { + delete streamBuf; + streamBuf = NULL; + isOwned = false; + } + } + +#ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement this functions + std::ostream& cout() { + return std::cout; + } + std::ostream& cerr() { + return std::cerr; + } +#endif +} + +namespace Catch { + + class Context : public IMutableContext { + + Context() : m_config( NULL ), m_runner( NULL ), m_resultCapture( NULL ) {} + Context( Context const& ); + void operator=( Context const& ); + + public: // IContext + virtual IResultCapture* getResultCapture() { + return m_resultCapture; + } + virtual IRunner* getRunner() { + return m_runner; + } + virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) { + return getGeneratorsForCurrentTest() + .getGeneratorInfo( fileInfo, totalSize ) + .getCurrentIndex(); + } + virtual bool advanceGeneratorsForCurrentTest() { + IGeneratorsForTest* generators = findGeneratorsForCurrentTest(); + return generators && generators->moveNext(); + } + + virtual Ptr getConfig() const { + return m_config; + } + + public: // IMutableContext + virtual void setResultCapture( IResultCapture* resultCapture ) { + m_resultCapture = resultCapture; + } + virtual void setRunner( IRunner* runner ) { + m_runner = runner; + } + virtual void setConfig( Ptr const& config ) { + m_config = config; + } + + friend IMutableContext& getCurrentMutableContext(); + + private: + IGeneratorsForTest* findGeneratorsForCurrentTest() { + std::string testName = getResultCapture()->getCurrentTestName(); + + std::map::const_iterator it = + m_generatorsByTestName.find( testName ); + return it != m_generatorsByTestName.end() + ? it->second + : NULL; + } + + IGeneratorsForTest& getGeneratorsForCurrentTest() { + IGeneratorsForTest* generators = findGeneratorsForCurrentTest(); + if( !generators ) { + std::string testName = getResultCapture()->getCurrentTestName(); + generators = createGeneratorsForTest(); + m_generatorsByTestName.insert( std::make_pair( testName, generators ) ); + } + return *generators; + } + + private: + Ptr m_config; + IRunner* m_runner; + IResultCapture* m_resultCapture; + std::map m_generatorsByTestName; + }; + + namespace { + Context* currentContext = NULL; + } + IMutableContext& getCurrentMutableContext() { + if( !currentContext ) + currentContext = new Context(); + return *currentContext; + } + IContext& getCurrentContext() { + return getCurrentMutableContext(); + } + + Stream createStream( std::string const& streamName ) { + if( streamName == "stdout" ) return Stream( Catch::cout().rdbuf(), false ); + if( streamName == "stderr" ) return Stream( Catch::cerr().rdbuf(), false ); + if( streamName == "debug" ) return Stream( new StreamBufImpl, true ); + + throw std::domain_error( "Unknown stream: " + streamName ); + } + + void cleanUpContext() { + delete currentContext; + currentContext = NULL; + } +} + +// #included from: catch_console_colour_impl.hpp +#define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_IMPL_HPP_INCLUDED + +namespace Catch { + namespace { + + struct IColourImpl { + virtual ~IColourImpl() {} + virtual void use( Colour::Code _colourCode ) = 0; + }; + + struct NoColourImpl : IColourImpl { + void use( Colour::Code ) {} + + static IColourImpl* instance() { + static NoColourImpl s_instance; + return &s_instance; + } + }; + + } // anon namespace +} // namespace Catch + +#if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !defined( CATCH_CONFIG_COLOUR_ANSI ) +# ifdef CATCH_PLATFORM_WINDOWS +# define CATCH_CONFIG_COLOUR_WINDOWS +# else +# define CATCH_CONFIG_COLOUR_ANSI +# endif +#endif + +#if defined ( CATCH_CONFIG_COLOUR_WINDOWS ) ///////////////////////////////////////// + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#ifdef __AFXDLL +#include +#else +#include +#endif + +namespace Catch { +namespace { + + class Win32ColourImpl : public IColourImpl { + public: + Win32ColourImpl() : stdoutHandle( GetStdHandle(STD_OUTPUT_HANDLE) ) + { + CONSOLE_SCREEN_BUFFER_INFO csbiInfo; + GetConsoleScreenBufferInfo( stdoutHandle, &csbiInfo ); + originalAttributes = csbiInfo.wAttributes; + } + + virtual void use( Colour::Code _colourCode ) { + switch( _colourCode ) { + case Colour::None: return setTextAttribute( originalAttributes ); + case Colour::White: return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + case Colour::Red: return setTextAttribute( FOREGROUND_RED ); + case Colour::Green: return setTextAttribute( FOREGROUND_GREEN ); + case Colour::Blue: return setTextAttribute( FOREGROUND_BLUE ); + case Colour::Cyan: return setTextAttribute( FOREGROUND_BLUE | FOREGROUND_GREEN ); + case Colour::Yellow: return setTextAttribute( FOREGROUND_RED | FOREGROUND_GREEN ); + case Colour::Grey: return setTextAttribute( 0 ); + + case Colour::LightGrey: return setTextAttribute( FOREGROUND_INTENSITY ); + case Colour::BrightRed: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED ); + case Colour::BrightGreen: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN ); + case Colour::BrightWhite: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + + case Colour::Bright: throw std::logic_error( "not a colour" ); + } + } + + private: + void setTextAttribute( WORD _textAttribute ) { + SetConsoleTextAttribute( stdoutHandle, _textAttribute ); + } + HANDLE stdoutHandle; + WORD originalAttributes; + }; + + IColourImpl* platformColourInstance() { + static Win32ColourImpl s_instance; + return &s_instance; + } + +} // end anon namespace +} // end namespace Catch + +#elif defined( CATCH_CONFIG_COLOUR_ANSI ) ////////////////////////////////////// + +#include + +namespace Catch { +namespace { + + // use POSIX/ ANSI console terminal codes + // Thanks to Adam Strzelecki for original contribution + // (http://github.com/nanoant) + // https://github.com/philsquared/Catch/pull/131 + class PosixColourImpl : public IColourImpl { + public: + virtual void use( Colour::Code _colourCode ) { + switch( _colourCode ) { + case Colour::None: + case Colour::White: return setColour( "[0m" ); + case Colour::Red: return setColour( "[0;31m" ); + case Colour::Green: return setColour( "[0;32m" ); + case Colour::Blue: return setColour( "[0:34m" ); + case Colour::Cyan: return setColour( "[0;36m" ); + case Colour::Yellow: return setColour( "[0;33m" ); + case Colour::Grey: return setColour( "[1;30m" ); + + case Colour::LightGrey: return setColour( "[0;37m" ); + case Colour::BrightRed: return setColour( "[1;31m" ); + case Colour::BrightGreen: return setColour( "[1;32m" ); + case Colour::BrightWhite: return setColour( "[1;37m" ); + + case Colour::Bright: throw std::logic_error( "not a colour" ); + } + } + static IColourImpl* instance() { + static PosixColourImpl s_instance; + return &s_instance; + } + + private: + void setColour( const char* _escapeCode ) { + Catch::cout() << '\033' << _escapeCode; + } + }; + + IColourImpl* platformColourInstance() { + Ptr config = getCurrentContext().getConfig(); + return (config && config->forceColour()) || isatty(STDOUT_FILENO) + ? PosixColourImpl::instance() + : NoColourImpl::instance(); + } + +} // end anon namespace +} // end namespace Catch + +#else // not Windows or ANSI /////////////////////////////////////////////// + +namespace Catch { + + static IColourImpl* platformColourInstance() { return NoColourImpl::instance(); } + +} // end namespace Catch + +#endif // Windows/ ANSI/ None + +namespace Catch { + + Colour::Colour( Code _colourCode ) : m_moved( false ) { use( _colourCode ); } + Colour::Colour( Colour const& _other ) : m_moved( false ) { const_cast( _other ).m_moved = true; } + Colour::~Colour(){ if( !m_moved ) use( None ); } + + void Colour::use( Code _colourCode ) { + static IColourImpl* impl = isDebuggerActive() + ? NoColourImpl::instance() + : platformColourInstance(); + impl->use( _colourCode ); + } + +} // end namespace Catch + +// #included from: catch_generators_impl.hpp +#define TWOBLUECUBES_CATCH_GENERATORS_IMPL_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { + + struct GeneratorInfo : IGeneratorInfo { + + GeneratorInfo( std::size_t size ) + : m_size( size ), + m_currentIndex( 0 ) + {} + + bool moveNext() { + if( ++m_currentIndex == m_size ) { + m_currentIndex = 0; + return false; + } + return true; + } + + std::size_t getCurrentIndex() const { + return m_currentIndex; + } + + std::size_t m_size; + std::size_t m_currentIndex; + }; + + /////////////////////////////////////////////////////////////////////////// + + class GeneratorsForTest : public IGeneratorsForTest { + + public: + ~GeneratorsForTest() { + deleteAll( m_generatorsInOrder ); + } + + IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) { + std::map::const_iterator it = m_generatorsByName.find( fileInfo ); + if( it == m_generatorsByName.end() ) { + IGeneratorInfo* info = new GeneratorInfo( size ); + m_generatorsByName.insert( std::make_pair( fileInfo, info ) ); + m_generatorsInOrder.push_back( info ); + return *info; + } + return *it->second; + } + + bool moveNext() { + std::vector::const_iterator it = m_generatorsInOrder.begin(); + std::vector::const_iterator itEnd = m_generatorsInOrder.end(); + for(; it != itEnd; ++it ) { + if( (*it)->moveNext() ) + return true; + } + return false; + } + + private: + std::map m_generatorsByName; + std::vector m_generatorsInOrder; + }; + + IGeneratorsForTest* createGeneratorsForTest() + { + return new GeneratorsForTest(); + } + +} // end namespace Catch + +// #included from: catch_assertionresult.hpp +#define TWOBLUECUBES_CATCH_ASSERTIONRESULT_HPP_INCLUDED + +namespace Catch { + + AssertionInfo::AssertionInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + std::string const& _capturedExpression, + ResultDisposition::Flags _resultDisposition ) + : macroName( _macroName ), + lineInfo( _lineInfo ), + capturedExpression( _capturedExpression ), + resultDisposition( _resultDisposition ) + {} + + AssertionResult::AssertionResult() {} + + AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData const& data ) + : m_info( info ), + m_resultData( data ) + {} + + AssertionResult::~AssertionResult() {} + + // Result was a success + bool AssertionResult::succeeded() const { + return Catch::isOk( m_resultData.resultType ); + } + + // Result was a success, or failure is suppressed + bool AssertionResult::isOk() const { + return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition ); + } + + ResultWas::OfType AssertionResult::getResultType() const { + return m_resultData.resultType; + } + + bool AssertionResult::hasExpression() const { + return !m_info.capturedExpression.empty(); + } + + bool AssertionResult::hasMessage() const { + return !m_resultData.message.empty(); + } + + std::string AssertionResult::getExpression() const { + if( isFalseTest( m_info.resultDisposition ) ) + return "!" + m_info.capturedExpression; + else + return m_info.capturedExpression; + } + std::string AssertionResult::getExpressionInMacro() const { + if( m_info.macroName.empty() ) + return m_info.capturedExpression; + else + return m_info.macroName + "( " + m_info.capturedExpression + " )"; + } + + bool AssertionResult::hasExpandedExpression() const { + return hasExpression() && getExpandedExpression() != getExpression(); + } + + std::string AssertionResult::getExpandedExpression() const { + return m_resultData.reconstructedExpression; + } + + std::string AssertionResult::getMessage() const { + return m_resultData.message; + } + SourceLineInfo AssertionResult::getSourceInfo() const { + return m_info.lineInfo; + } + + std::string AssertionResult::getTestMacroName() const { + return m_info.macroName; + } + +} // end namespace Catch + +// #included from: catch_test_case_info.hpp +#define TWOBLUECUBES_CATCH_TEST_CASE_INFO_HPP_INCLUDED + +namespace Catch { + + inline TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) { + if( startsWith( tag, "." ) || + tag == "hide" || + tag == "!hide" ) + return TestCaseInfo::IsHidden; + else if( tag == "!throws" ) + return TestCaseInfo::Throws; + else if( tag == "!shouldfail" ) + return TestCaseInfo::ShouldFail; + else if( tag == "!mayfail" ) + return TestCaseInfo::MayFail; + else + return TestCaseInfo::None; + } + inline bool isReservedTag( std::string const& tag ) { + return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !isalnum( tag[0] ); + } + inline void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) { + if( isReservedTag( tag ) ) { + { + Colour colourGuard( Colour::Red ); + Catch::cerr() + << "Tag name [" << tag << "] not allowed.\n" + << "Tag names starting with non alpha-numeric characters are reserved\n"; + } + { + Colour colourGuard( Colour::FileName ); + Catch::cerr() << _lineInfo << std::endl; + } + exit(1); + } + } + + TestCase makeTestCase( ITestCase* _testCase, + std::string const& _className, + std::string const& _name, + std::string const& _descOrTags, + SourceLineInfo const& _lineInfo ) + { + bool isHidden( startsWith( _name, "./" ) ); // Legacy support + + // Parse out tags + std::set tags; + std::string desc, tag; + bool inTag = false; + for( std::size_t i = 0; i < _descOrTags.size(); ++i ) { + char c = _descOrTags[i]; + if( !inTag ) { + if( c == '[' ) + inTag = true; + else + desc += c; + } + else { + if( c == ']' ) { + TestCaseInfo::SpecialProperties prop = parseSpecialTag( tag ); + if( prop == TestCaseInfo::IsHidden ) + isHidden = true; + else if( prop == TestCaseInfo::None ) + enforceNotReservedTag( tag, _lineInfo ); + + tags.insert( tag ); + tag.clear(); + inTag = false; + } + else + tag += c; + } + } + if( isHidden ) { + tags.insert( "hide" ); + tags.insert( "." ); + } + + TestCaseInfo info( _name, _className, desc, tags, _lineInfo ); + return TestCase( _testCase, info ); + } + + TestCaseInfo::TestCaseInfo( std::string const& _name, + std::string const& _className, + std::string const& _description, + std::set const& _tags, + SourceLineInfo const& _lineInfo ) + : name( _name ), + className( _className ), + description( _description ), + tags( _tags ), + lineInfo( _lineInfo ), + properties( None ) + { + std::ostringstream oss; + for( std::set::const_iterator it = _tags.begin(), itEnd = _tags.end(); it != itEnd; ++it ) { + oss << "[" << *it << "]"; + std::string lcaseTag = toLower( *it ); + properties = static_cast( properties | parseSpecialTag( lcaseTag ) ); + lcaseTags.insert( lcaseTag ); + } + tagsAsString = oss.str(); + } + + TestCaseInfo::TestCaseInfo( TestCaseInfo const& other ) + : name( other.name ), + className( other.className ), + description( other.description ), + tags( other.tags ), + lcaseTags( other.lcaseTags ), + tagsAsString( other.tagsAsString ), + lineInfo( other.lineInfo ), + properties( other.properties ) + {} + + bool TestCaseInfo::isHidden() const { + return ( properties & IsHidden ) != 0; + } + bool TestCaseInfo::throws() const { + return ( properties & Throws ) != 0; + } + bool TestCaseInfo::okToFail() const { + return ( properties & (ShouldFail | MayFail ) ) != 0; + } + bool TestCaseInfo::expectedToFail() const { + return ( properties & (ShouldFail ) ) != 0; + } + + TestCase::TestCase( ITestCase* testCase, TestCaseInfo const& info ) : TestCaseInfo( info ), test( testCase ) {} + + TestCase::TestCase( TestCase const& other ) + : TestCaseInfo( other ), + test( other.test ) + {} + + TestCase TestCase::withName( std::string const& _newName ) const { + TestCase other( *this ); + other.name = _newName; + return other; + } + + void TestCase::swap( TestCase& other ) { + test.swap( other.test ); + name.swap( other.name ); + className.swap( other.className ); + description.swap( other.description ); + tags.swap( other.tags ); + lcaseTags.swap( other.lcaseTags ); + tagsAsString.swap( other.tagsAsString ); + std::swap( TestCaseInfo::properties, static_cast( other ).properties ); + std::swap( lineInfo, other.lineInfo ); + } + + void TestCase::invoke() const { + test->invoke(); + } + + bool TestCase::operator == ( TestCase const& other ) const { + return test.get() == other.test.get() && + name == other.name && + className == other.className; + } + + bool TestCase::operator < ( TestCase const& other ) const { + return name < other.name; + } + TestCase& TestCase::operator = ( TestCase const& other ) { + TestCase temp( other ); + swap( temp ); + return *this; + } + + TestCaseInfo const& TestCase::getTestCaseInfo() const + { + return *this; + } + +} // end namespace Catch + +// #included from: catch_version.hpp +#define TWOBLUECUBES_CATCH_VERSION_HPP_INCLUDED + +namespace Catch { + + // These numbers are maintained by a script + Version libraryVersion( 1, 1, 1, "master" ); +} + +// #included from: catch_message.hpp +#define TWOBLUECUBES_CATCH_MESSAGE_HPP_INCLUDED + +namespace Catch { + + MessageInfo::MessageInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ) + : macroName( _macroName ), + lineInfo( _lineInfo ), + type( _type ), + sequence( ++globalCount ) + {} + + // This may need protecting if threading support is added + unsigned int MessageInfo::globalCount = 0; + + //////////////////////////////////////////////////////////////////////////// + + ScopedMessage::ScopedMessage( MessageBuilder const& builder ) + : m_info( builder.m_info ) + { + m_info.message = builder.m_stream.str(); + getResultCapture().pushScopedMessage( m_info ); + } + ScopedMessage::ScopedMessage( ScopedMessage const& other ) + : m_info( other.m_info ) + {} + + ScopedMessage::~ScopedMessage() { + getResultCapture().popScopedMessage( m_info ); + } + +} // end namespace Catch + +// #included from: catch_legacy_reporter_adapter.hpp +#define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_HPP_INCLUDED + +// #included from: catch_legacy_reporter_adapter.h +#define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_H_INCLUDED + +namespace Catch +{ + // Deprecated + struct IReporter : IShared { + virtual ~IReporter(); + + virtual bool shouldRedirectStdout() const = 0; + + virtual void StartTesting() = 0; + virtual void EndTesting( Totals const& totals ) = 0; + virtual void StartGroup( std::string const& groupName ) = 0; + virtual void EndGroup( std::string const& groupName, Totals const& totals ) = 0; + virtual void StartTestCase( TestCaseInfo const& testInfo ) = 0; + virtual void EndTestCase( TestCaseInfo const& testInfo, Totals const& totals, std::string const& stdOut, std::string const& stdErr ) = 0; + virtual void StartSection( std::string const& sectionName, std::string const& description ) = 0; + virtual void EndSection( std::string const& sectionName, Counts const& assertions ) = 0; + virtual void NoAssertionsInSection( std::string const& sectionName ) = 0; + virtual void NoAssertionsInTestCase( std::string const& testName ) = 0; + virtual void Aborted() = 0; + virtual void Result( AssertionResult const& result ) = 0; + }; + + class LegacyReporterAdapter : public SharedImpl + { + public: + LegacyReporterAdapter( Ptr const& legacyReporter ); + virtual ~LegacyReporterAdapter(); + + virtual ReporterPreferences getPreferences() const; + virtual void noMatchingTestCases( std::string const& ); + virtual void testRunStarting( TestRunInfo const& ); + virtual void testGroupStarting( GroupInfo const& groupInfo ); + virtual void testCaseStarting( TestCaseInfo const& testInfo ); + virtual void sectionStarting( SectionInfo const& sectionInfo ); + virtual void assertionStarting( AssertionInfo const& ); + virtual bool assertionEnded( AssertionStats const& assertionStats ); + virtual void sectionEnded( SectionStats const& sectionStats ); + virtual void testCaseEnded( TestCaseStats const& testCaseStats ); + virtual void testGroupEnded( TestGroupStats const& testGroupStats ); + virtual void testRunEnded( TestRunStats const& testRunStats ); + virtual void skipTest( TestCaseInfo const& ); + + private: + Ptr m_legacyReporter; + }; +} + +namespace Catch +{ + LegacyReporterAdapter::LegacyReporterAdapter( Ptr const& legacyReporter ) + : m_legacyReporter( legacyReporter ) + {} + LegacyReporterAdapter::~LegacyReporterAdapter() {} + + ReporterPreferences LegacyReporterAdapter::getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = m_legacyReporter->shouldRedirectStdout(); + return prefs; + } + + void LegacyReporterAdapter::noMatchingTestCases( std::string const& ) {} + void LegacyReporterAdapter::testRunStarting( TestRunInfo const& ) { + m_legacyReporter->StartTesting(); + } + void LegacyReporterAdapter::testGroupStarting( GroupInfo const& groupInfo ) { + m_legacyReporter->StartGroup( groupInfo.name ); + } + void LegacyReporterAdapter::testCaseStarting( TestCaseInfo const& testInfo ) { + m_legacyReporter->StartTestCase( testInfo ); + } + void LegacyReporterAdapter::sectionStarting( SectionInfo const& sectionInfo ) { + m_legacyReporter->StartSection( sectionInfo.name, sectionInfo.description ); + } + void LegacyReporterAdapter::assertionStarting( AssertionInfo const& ) { + // Not on legacy interface + } + + bool LegacyReporterAdapter::assertionEnded( AssertionStats const& assertionStats ) { + if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) { + for( std::vector::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end(); + it != itEnd; + ++it ) { + if( it->type == ResultWas::Info ) { + ResultBuilder rb( it->macroName.c_str(), it->lineInfo, "", ResultDisposition::Normal ); + rb << it->message; + rb.setResultType( ResultWas::Info ); + AssertionResult result = rb.build(); + m_legacyReporter->Result( result ); + } + } + } + m_legacyReporter->Result( assertionStats.assertionResult ); + return true; + } + void LegacyReporterAdapter::sectionEnded( SectionStats const& sectionStats ) { + if( sectionStats.missingAssertions ) + m_legacyReporter->NoAssertionsInSection( sectionStats.sectionInfo.name ); + m_legacyReporter->EndSection( sectionStats.sectionInfo.name, sectionStats.assertions ); + } + void LegacyReporterAdapter::testCaseEnded( TestCaseStats const& testCaseStats ) { + m_legacyReporter->EndTestCase + ( testCaseStats.testInfo, + testCaseStats.totals, + testCaseStats.stdOut, + testCaseStats.stdErr ); + } + void LegacyReporterAdapter::testGroupEnded( TestGroupStats const& testGroupStats ) { + if( testGroupStats.aborting ) + m_legacyReporter->Aborted(); + m_legacyReporter->EndGroup( testGroupStats.groupInfo.name, testGroupStats.totals ); + } + void LegacyReporterAdapter::testRunEnded( TestRunStats const& testRunStats ) { + m_legacyReporter->EndTesting( testRunStats.totals ); + } + void LegacyReporterAdapter::skipTest( TestCaseInfo const& ) { + } +} + +// #included from: catch_timer.hpp + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc++11-long-long" +#endif + +#ifdef CATCH_PLATFORM_WINDOWS +#include +#else +#include +#endif + +namespace Catch { + + namespace { +#ifdef CATCH_PLATFORM_WINDOWS + uint64_t getCurrentTicks() { + static uint64_t hz=0, hzo=0; + if (!hz) { + QueryPerformanceFrequency( reinterpret_cast( &hz ) ); + QueryPerformanceCounter( reinterpret_cast( &hzo ) ); + } + uint64_t t; + QueryPerformanceCounter( reinterpret_cast( &t ) ); + return ((t-hzo)*1000000)/hz; + } +#else + uint64_t getCurrentTicks() { + timeval t; + gettimeofday(&t,NULL); + return static_cast( t.tv_sec ) * 1000000ull + static_cast( t.tv_usec ); + } +#endif + } + + void Timer::start() { + m_ticks = getCurrentTicks(); + } + unsigned int Timer::getElapsedMicroseconds() const { + return static_cast(getCurrentTicks() - m_ticks); + } + unsigned int Timer::getElapsedMilliseconds() const { + return static_cast(getElapsedMicroseconds()/1000); + } + double Timer::getElapsedSeconds() const { + return getElapsedMicroseconds()/1000000.0; + } + +} // namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +// #included from: catch_common.hpp +#define TWOBLUECUBES_CATCH_COMMON_HPP_INCLUDED + +namespace Catch { + + bool startsWith( std::string const& s, std::string const& prefix ) { + return s.size() >= prefix.size() && s.substr( 0, prefix.size() ) == prefix; + } + bool endsWith( std::string const& s, std::string const& suffix ) { + return s.size() >= suffix.size() && s.substr( s.size()-suffix.size(), suffix.size() ) == suffix; + } + bool contains( std::string const& s, std::string const& infix ) { + return s.find( infix ) != std::string::npos; + } + void toLowerInPlace( std::string& s ) { + std::transform( s.begin(), s.end(), s.begin(), ::tolower ); + } + std::string toLower( std::string const& s ) { + std::string lc = s; + toLowerInPlace( lc ); + return lc; + } + std::string trim( std::string const& str ) { + static char const* whitespaceChars = "\n\r\t "; + std::string::size_type start = str.find_first_not_of( whitespaceChars ); + std::string::size_type end = str.find_last_not_of( whitespaceChars ); + + return start != std::string::npos ? str.substr( start, 1+end-start ) : ""; + } + + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) { + bool replaced = false; + std::size_t i = str.find( replaceThis ); + while( i != std::string::npos ) { + replaced = true; + str = str.substr( 0, i ) + withThis + str.substr( i+replaceThis.size() ); + if( i < str.size()-withThis.size() ) + i = str.find( replaceThis, i+withThis.size() ); + else + i = std::string::npos; + } + return replaced; + } + + pluralise::pluralise( std::size_t count, std::string const& label ) + : m_count( count ), + m_label( label ) + {} + + std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) { + os << pluraliser.m_count << " " << pluraliser.m_label; + if( pluraliser.m_count != 1 ) + os << "s"; + return os; + } + + SourceLineInfo::SourceLineInfo() : line( 0 ){} + SourceLineInfo::SourceLineInfo( char const* _file, std::size_t _line ) + : file( _file ), + line( _line ) + {} + SourceLineInfo::SourceLineInfo( SourceLineInfo const& other ) + : file( other.file ), + line( other.line ) + {} + bool SourceLineInfo::empty() const { + return file.empty(); + } + bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const { + return line == other.line && file == other.file; + } + bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const { + return line < other.line || ( line == other.line && file < other.file ); + } + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) { +#ifndef __GNUG__ + os << info.file << "(" << info.line << ")"; +#else + os << info.file << ":" << info.line; +#endif + return os; + } + + void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ) { + std::ostringstream oss; + oss << locationInfo << ": Internal Catch error: '" << message << "'"; + if( alwaysTrue() ) + throw std::logic_error( oss.str() ); + } +} + +// #included from: catch_section.hpp +#define TWOBLUECUBES_CATCH_SECTION_HPP_INCLUDED + +namespace Catch { + + SectionInfo::SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name, + std::string const& _description ) + : name( _name ), + description( _description ), + lineInfo( _lineInfo ) + {} + + Section::Section( SectionInfo const& info ) + : m_info( info ), + m_sectionIncluded( getResultCapture().sectionStarted( m_info, m_assertions ) ) + { + m_timer.start(); + } + + Section::~Section() { + if( m_sectionIncluded ) + getResultCapture().sectionEnded( m_info, m_assertions, m_timer.getElapsedSeconds() ); + } + + // This indicates whether the section should be executed or not + Section::operator bool() const { + return m_sectionIncluded; + } + +} // end namespace Catch + +// #included from: catch_debugger.hpp +#define TWOBLUECUBES_CATCH_DEBUGGER_HPP_INCLUDED + +#include + +#ifdef CATCH_PLATFORM_MAC + + #include + #include + #include + #include + #include + + namespace Catch{ + + // The following function is taken directly from the following technical note: + // http://developer.apple.com/library/mac/#qa/qa2004/qa1361.html + + // Returns true if the current process is being debugged (either + // running under the debugger or has a debugger attached post facto). + bool isDebuggerActive(){ + + int mib[4]; + struct kinfo_proc info; + size_t size; + + // Initialize the flags so that, if sysctl fails for some bizarre + // reason, we get a predictable result. + + info.kp_proc.p_flag = 0; + + // Initialize mib, which tells sysctl the info we want, in this case + // we're looking for information about a specific process ID. + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + + // Call sysctl. + + size = sizeof(info); + if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0) != 0 ) { + Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl; + return false; + } + + // We're being debugged if the P_TRACED flag is set. + + return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); + } + } // namespace Catch + +#elif defined(_MSC_VER) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } + } +#elif defined(__MINGW32__) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } + } +#else + namespace Catch { + inline bool isDebuggerActive() { return false; } + } +#endif // Platform + +#ifdef CATCH_PLATFORM_WINDOWS + extern "C" __declspec(dllimport) void __stdcall OutputDebugStringA( const char* ); + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + ::OutputDebugStringA( text.c_str() ); + } + } +#else + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + // !TBD: Need a version for Mac/ XCode and other IDEs + Catch::cout() << text; + } + } +#endif // Platform + +// #included from: catch_tostring.hpp +#define TWOBLUECUBES_CATCH_TOSTRING_HPP_INCLUDED + +namespace Catch { + +namespace Detail { + + std::string unprintableString = "{?}"; + + namespace { + struct Endianness { + enum Arch { Big, Little }; + + static Arch which() { + union _{ + int asInt; + char asChar[sizeof (int)]; + } u; + + u.asInt = 1; + return ( u.asChar[sizeof(int)-1] == 1 ) ? Big : Little; + } + }; + } + + std::string rawMemoryToString( const void *object, std::size_t size ) + { + // Reverse order for little endian architectures + int i = 0, end = static_cast( size ), inc = 1; + if( Endianness::which() == Endianness::Little ) { + i = end-1; + end = inc = -1; + } + + unsigned char const *bytes = static_cast(object); + std::ostringstream os; + os << "0x" << std::setfill('0') << std::hex; + for( ; i != end; i += inc ) + os << std::setw(2) << static_cast(bytes[i]); + return os.str(); + } +} + +std::string toString( std::string const& value ) { + std::string s = value; + if( getCurrentContext().getConfig()->showInvisibles() ) { + for(size_t i = 0; i < s.size(); ++i ) { + std::string subs; + switch( s[i] ) { + case '\n': subs = "\\n"; break; + case '\t': subs = "\\t"; break; + default: break; + } + if( !subs.empty() ) { + s = s.substr( 0, i ) + subs + s.substr( i+1 ); + ++i; + } + } + } + return "\"" + s + "\""; +} +std::string toString( std::wstring const& value ) { + + std::string s; + s.reserve( value.size() ); + for(size_t i = 0; i < value.size(); ++i ) + s += value[i] <= 0xff ? static_cast( value[i] ) : '?'; + return Catch::toString( s ); +} + +std::string toString( const char* const value ) { + return value ? Catch::toString( std::string( value ) ) : std::string( "{null string}" ); +} + +std::string toString( char* const value ) { + return Catch::toString( static_cast( value ) ); +} + +std::string toString( const wchar_t* const value ) +{ + return value ? Catch::toString( std::wstring(value) ) : std::string( "{null string}" ); +} + +std::string toString( wchar_t* const value ) +{ + return Catch::toString( static_cast( value ) ); +} + +std::string toString( int value ) { + std::ostringstream oss; + if( value > 8192 ) + oss << "0x" << std::hex << value; + else + oss << value; + return oss.str(); +} + +std::string toString( unsigned long value ) { + std::ostringstream oss; + if( value > 8192 ) + oss << "0x" << std::hex << value; + else + oss << value; + return oss.str(); +} + +std::string toString( unsigned int value ) { + return Catch::toString( static_cast( value ) ); +} + +template +std::string fpToString( T value, int precision ) { + std::ostringstream oss; + oss << std::setprecision( precision ) + << std::fixed + << value; + std::string d = oss.str(); + std::size_t i = d.find_last_not_of( '0' ); + if( i != std::string::npos && i != d.size()-1 ) { + if( d[i] == '.' ) + i++; + d = d.substr( 0, i+1 ); + } + return d; +} + +std::string toString( const double value ) { + return fpToString( value, 10 ); +} +std::string toString( const float value ) { + return fpToString( value, 5 ) + "f"; +} + +std::string toString( bool value ) { + return value ? "true" : "false"; +} + +std::string toString( char value ) { + return value < ' ' + ? toString( static_cast( value ) ) + : Detail::makeString( value ); +} + +std::string toString( signed char value ) { + return toString( static_cast( value ) ); +} + +std::string toString( unsigned char value ) { + return toString( static_cast( value ) ); +} + +#ifdef CATCH_CONFIG_CPP11_NULLPTR +std::string toString( std::nullptr_t ) { + return "nullptr"; +} +#endif + +#ifdef __OBJC__ + std::string toString( NSString const * const& nsstring ) { + if( !nsstring ) + return "nil"; + return "@" + toString([nsstring UTF8String]); + } + std::string toString( NSString * CATCH_ARC_STRONG const& nsstring ) { + if( !nsstring ) + return "nil"; + return "@" + toString([nsstring UTF8String]); + } + std::string toString( NSObject* const& nsObject ) { + return toString( [nsObject description] ); + } +#endif + +} // end namespace Catch + +// #included from: catch_result_builder.hpp +#define TWOBLUECUBES_CATCH_RESULT_BUILDER_HPP_INCLUDED + +namespace Catch { + + ResultBuilder::ResultBuilder( char const* macroName, + SourceLineInfo const& lineInfo, + char const* capturedExpression, + ResultDisposition::Flags resultDisposition ) + : m_assertionInfo( macroName, lineInfo, capturedExpression, resultDisposition ), + m_shouldDebugBreak( false ), + m_shouldThrow( false ) + {} + + ResultBuilder& ResultBuilder::setResultType( ResultWas::OfType result ) { + m_data.resultType = result; + return *this; + } + ResultBuilder& ResultBuilder::setResultType( bool result ) { + m_data.resultType = result ? ResultWas::Ok : ResultWas::ExpressionFailed; + return *this; + } + ResultBuilder& ResultBuilder::setLhs( std::string const& lhs ) { + m_exprComponents.lhs = lhs; + return *this; + } + ResultBuilder& ResultBuilder::setRhs( std::string const& rhs ) { + m_exprComponents.rhs = rhs; + return *this; + } + ResultBuilder& ResultBuilder::setOp( std::string const& op ) { + m_exprComponents.op = op; + return *this; + } + + void ResultBuilder::endExpression() { + m_exprComponents.testFalse = isFalseTest( m_assertionInfo.resultDisposition ); + captureExpression(); + } + + void ResultBuilder::useActiveException( ResultDisposition::Flags resultDisposition ) { + m_assertionInfo.resultDisposition = resultDisposition; + m_stream.oss << Catch::translateActiveException(); + captureResult( ResultWas::ThrewException ); + } + + void ResultBuilder::captureResult( ResultWas::OfType resultType ) { + setResultType( resultType ); + captureExpression(); + } + + void ResultBuilder::captureExpression() { + AssertionResult result = build(); + getResultCapture().assertionEnded( result ); + + if( !result.isOk() ) { + if( getCurrentContext().getConfig()->shouldDebugBreak() ) + m_shouldDebugBreak = true; + if( getCurrentContext().getRunner()->aborting() || m_assertionInfo.resultDisposition == ResultDisposition::Normal ) + m_shouldThrow = true; + } + } + void ResultBuilder::react() { + if( m_shouldThrow ) + throw Catch::TestFailureException(); + } + + bool ResultBuilder::shouldDebugBreak() const { return m_shouldDebugBreak; } + bool ResultBuilder::allowThrows() const { return getCurrentContext().getConfig()->allowThrows(); } + + AssertionResult ResultBuilder::build() const + { + assert( m_data.resultType != ResultWas::Unknown ); + + AssertionResultData data = m_data; + + // Flip bool results if testFalse is set + if( m_exprComponents.testFalse ) { + if( data.resultType == ResultWas::Ok ) + data.resultType = ResultWas::ExpressionFailed; + else if( data.resultType == ResultWas::ExpressionFailed ) + data.resultType = ResultWas::Ok; + } + + data.message = m_stream.oss.str(); + data.reconstructedExpression = reconstructExpression(); + if( m_exprComponents.testFalse ) { + if( m_exprComponents.op == "" ) + data.reconstructedExpression = "!" + data.reconstructedExpression; + else + data.reconstructedExpression = "!(" + data.reconstructedExpression + ")"; + } + return AssertionResult( m_assertionInfo, data ); + } + std::string ResultBuilder::reconstructExpression() const { + if( m_exprComponents.op == "" ) + return m_exprComponents.lhs.empty() ? m_assertionInfo.capturedExpression : m_exprComponents.op + m_exprComponents.lhs; + else if( m_exprComponents.op == "matches" ) + return m_exprComponents.lhs + " " + m_exprComponents.rhs; + else if( m_exprComponents.op != "!" ) { + if( m_exprComponents.lhs.size() + m_exprComponents.rhs.size() < 40 && + m_exprComponents.lhs.find("\n") == std::string::npos && + m_exprComponents.rhs.find("\n") == std::string::npos ) + return m_exprComponents.lhs + " " + m_exprComponents.op + " " + m_exprComponents.rhs; + else + return m_exprComponents.lhs + "\n" + m_exprComponents.op + "\n" + m_exprComponents.rhs; + } + else + return "{can't expand - use " + m_assertionInfo.macroName + "_FALSE( " + m_assertionInfo.capturedExpression.substr(1) + " ) instead of " + m_assertionInfo.macroName + "( " + m_assertionInfo.capturedExpression + " ) for better diagnostics}"; + } + +} // end namespace Catch + +// #included from: catch_tag_alias_registry.hpp +#define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_HPP_INCLUDED + +// #included from: catch_tag_alias_registry.h +#define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_H_INCLUDED + +#include + +namespace Catch { + + class TagAliasRegistry : public ITagAliasRegistry { + public: + virtual ~TagAliasRegistry(); + virtual Option find( std::string const& alias ) const; + virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const; + void add( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + static TagAliasRegistry& get(); + + private: + std::map m_registry; + }; + +} // end namespace Catch + +#include +#include + +namespace Catch { + + TagAliasRegistry::~TagAliasRegistry() {} + + Option TagAliasRegistry::find( std::string const& alias ) const { + std::map::const_iterator it = m_registry.find( alias ); + if( it != m_registry.end() ) + return it->second; + else + return Option(); + } + + std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const { + std::string expandedTestSpec = unexpandedTestSpec; + for( std::map::const_iterator it = m_registry.begin(), itEnd = m_registry.end(); + it != itEnd; + ++it ) { + std::size_t pos = expandedTestSpec.find( it->first ); + if( pos != std::string::npos ) { + expandedTestSpec = expandedTestSpec.substr( 0, pos ) + + it->second.tag + + expandedTestSpec.substr( pos + it->first.size() ); + } + } + return expandedTestSpec; + } + + void TagAliasRegistry::add( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) { + + if( !startsWith( alias, "[@" ) || !endsWith( alias, "]" ) ) { + std::ostringstream oss; + oss << "error: tag alias, \"" << alias << "\" is not of the form [@alias name].\n" << lineInfo; + throw std::domain_error( oss.str().c_str() ); + } + if( !m_registry.insert( std::make_pair( alias, TagAlias( tag, lineInfo ) ) ).second ) { + std::ostringstream oss; + oss << "error: tag alias, \"" << alias << "\" already registered.\n" + << "\tFirst seen at " << find(alias)->lineInfo << "\n" + << "\tRedefined at " << lineInfo; + throw std::domain_error( oss.str().c_str() ); + } + } + + TagAliasRegistry& TagAliasRegistry::get() { + static TagAliasRegistry instance; + return instance; + + } + + ITagAliasRegistry::~ITagAliasRegistry() {} + ITagAliasRegistry const& ITagAliasRegistry::get() { return TagAliasRegistry::get(); } + + RegistrarForTagAliases::RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) { + try { + TagAliasRegistry::get().add( alias, tag, lineInfo ); + } + catch( std::exception& ex ) { + Colour colourGuard( Colour::Red ); + Catch::cerr() << ex.what() << std::endl; + exit(1); + } + } + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_xml.hpp +#define TWOBLUECUBES_CATCH_REPORTER_XML_HPP_INCLUDED + +// #included from: catch_reporter_bases.hpp +#define TWOBLUECUBES_CATCH_REPORTER_BASES_HPP_INCLUDED + +#include + +namespace Catch { + + struct StreamingReporterBase : SharedImpl { + + StreamingReporterBase( ReporterConfig const& _config ) + : m_config( _config.fullConfig() ), + stream( _config.stream() ) + {} + + virtual ~StreamingReporterBase(); + + virtual void noMatchingTestCases( std::string const& ) {} + + virtual void testRunStarting( TestRunInfo const& _testRunInfo ) { + currentTestRunInfo = _testRunInfo; + } + virtual void testGroupStarting( GroupInfo const& _groupInfo ) { + currentGroupInfo = _groupInfo; + } + + virtual void testCaseStarting( TestCaseInfo const& _testInfo ) { + currentTestCaseInfo = _testInfo; + } + virtual void sectionStarting( SectionInfo const& _sectionInfo ) { + m_sectionStack.push_back( _sectionInfo ); + } + + virtual void sectionEnded( SectionStats const& /* _sectionStats */ ) { + m_sectionStack.pop_back(); + } + virtual void testCaseEnded( TestCaseStats const& /* _testCaseStats */ ) { + currentTestCaseInfo.reset(); + } + virtual void testGroupEnded( TestGroupStats const& /* _testGroupStats */ ) { + currentGroupInfo.reset(); + } + virtual void testRunEnded( TestRunStats const& /* _testRunStats */ ) { + currentTestCaseInfo.reset(); + currentGroupInfo.reset(); + currentTestRunInfo.reset(); + } + + virtual void skipTest( TestCaseInfo const& ) { + // Don't do anything with this by default. + // It can optionally be overridden in the derived class. + } + + Ptr m_config; + std::ostream& stream; + + LazyStat currentTestRunInfo; + LazyStat currentGroupInfo; + LazyStat currentTestCaseInfo; + + std::vector m_sectionStack; + }; + + struct CumulativeReporterBase : SharedImpl { + template + struct Node : SharedImpl<> { + explicit Node( T const& _value ) : value( _value ) {} + virtual ~Node() {} + + typedef std::vector > ChildNodes; + T value; + ChildNodes children; + }; + struct SectionNode : SharedImpl<> { + explicit SectionNode( SectionStats const& _stats ) : stats( _stats ) {} + virtual ~SectionNode(); + + bool operator == ( SectionNode const& other ) const { + return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo; + } + bool operator == ( Ptr const& other ) const { + return operator==( *other ); + } + + SectionStats stats; + typedef std::vector > ChildSections; + typedef std::vector Assertions; + ChildSections childSections; + Assertions assertions; + std::string stdOut; + std::string stdErr; + }; + + struct BySectionInfo { + BySectionInfo( SectionInfo const& other ) : m_other( other ) {} + BySectionInfo( BySectionInfo const& other ) : m_other( other.m_other ) {} + bool operator() ( Ptr const& node ) const { + return node->stats.sectionInfo.lineInfo == m_other.lineInfo; + } + private: + void operator=( BySectionInfo const& ); + SectionInfo const& m_other; + }; + + typedef Node TestCaseNode; + typedef Node TestGroupNode; + typedef Node TestRunNode; + + CumulativeReporterBase( ReporterConfig const& _config ) + : m_config( _config.fullConfig() ), + stream( _config.stream() ) + {} + ~CumulativeReporterBase(); + + virtual void testRunStarting( TestRunInfo const& ) {} + virtual void testGroupStarting( GroupInfo const& ) {} + + virtual void testCaseStarting( TestCaseInfo const& ) {} + + virtual void sectionStarting( SectionInfo const& sectionInfo ) { + SectionStats incompleteStats( sectionInfo, Counts(), 0, false ); + Ptr node; + if( m_sectionStack.empty() ) { + if( !m_rootSection ) + m_rootSection = new SectionNode( incompleteStats ); + node = m_rootSection; + } + else { + SectionNode& parentNode = *m_sectionStack.back(); + SectionNode::ChildSections::const_iterator it = + std::find_if( parentNode.childSections.begin(), + parentNode.childSections.end(), + BySectionInfo( sectionInfo ) ); + if( it == parentNode.childSections.end() ) { + node = new SectionNode( incompleteStats ); + parentNode.childSections.push_back( node ); + } + else + node = *it; + } + m_sectionStack.push_back( node ); + m_deepestSection = node; + } + + virtual void assertionStarting( AssertionInfo const& ) {} + + virtual bool assertionEnded( AssertionStats const& assertionStats ) { + assert( !m_sectionStack.empty() ); + SectionNode& sectionNode = *m_sectionStack.back(); + sectionNode.assertions.push_back( assertionStats ); + return true; + } + virtual void sectionEnded( SectionStats const& sectionStats ) { + assert( !m_sectionStack.empty() ); + SectionNode& node = *m_sectionStack.back(); + node.stats = sectionStats; + m_sectionStack.pop_back(); + } + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) { + Ptr node = new TestCaseNode( testCaseStats ); + assert( m_sectionStack.size() == 0 ); + node->children.push_back( m_rootSection ); + m_testCases.push_back( node ); + m_rootSection.reset(); + + assert( m_deepestSection ); + m_deepestSection->stdOut = testCaseStats.stdOut; + m_deepestSection->stdErr = testCaseStats.stdErr; + } + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) { + Ptr node = new TestGroupNode( testGroupStats ); + node->children.swap( m_testCases ); + m_testGroups.push_back( node ); + } + virtual void testRunEnded( TestRunStats const& testRunStats ) { + Ptr node = new TestRunNode( testRunStats ); + node->children.swap( m_testGroups ); + m_testRuns.push_back( node ); + testRunEndedCumulative(); + } + virtual void testRunEndedCumulative() = 0; + + virtual void skipTest( TestCaseInfo const& ) {} + + Ptr m_config; + std::ostream& stream; + std::vector m_assertions; + std::vector > > m_sections; + std::vector > m_testCases; + std::vector > m_testGroups; + + std::vector > m_testRuns; + + Ptr m_rootSection; + Ptr m_deepestSection; + std::vector > m_sectionStack; + + }; + + template + char const* getLineOfChars() { + static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0}; + if( !*line ) { + memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 ); + line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0; + } + return line; + } + +} // end namespace Catch + +// #included from: ../internal/catch_reporter_registrars.hpp +#define TWOBLUECUBES_CATCH_REPORTER_REGISTRARS_HPP_INCLUDED + +namespace Catch { + + template + class LegacyReporterRegistrar { + + class ReporterFactory : public IReporterFactory { + virtual IStreamingReporter* create( ReporterConfig const& config ) const { + return new LegacyReporterAdapter( new T( config ) ); + } + + virtual std::string getDescription() const { + return T::getDescription(); + } + }; + + public: + + LegacyReporterRegistrar( std::string const& name ) { + getMutableRegistryHub().registerReporter( name, new ReporterFactory() ); + } + }; + + template + class ReporterRegistrar { + + class ReporterFactory : public IReporterFactory { + + // *** Please Note ***: + // - If you end up here looking at a compiler error because it's trying to register + // your custom reporter class be aware that the native reporter interface has changed + // to IStreamingReporter. The "legacy" interface, IReporter, is still supported via + // an adapter. Just use REGISTER_LEGACY_REPORTER to take advantage of the adapter. + // However please consider updating to the new interface as the old one is now + // deprecated and will probably be removed quite soon! + // Please contact me via github if you have any questions at all about this. + // In fact, ideally, please contact me anyway to let me know you've hit this - as I have + // no idea who is actually using custom reporters at all (possibly no-one!). + // The new interface is designed to minimise exposure to interface changes in the future. + virtual IStreamingReporter* create( ReporterConfig const& config ) const { + return new T( config ); + } + + virtual std::string getDescription() const { + return T::getDescription(); + } + }; + + public: + + ReporterRegistrar( std::string const& name ) { + getMutableRegistryHub().registerReporter( name, new ReporterFactory() ); + } + }; +} + +#define INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) \ + namespace{ Catch::LegacyReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); } +#define INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) \ + namespace{ Catch::ReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); } + +// #included from: ../internal/catch_xmlwriter.hpp +#define TWOBLUECUBES_CATCH_XMLWRITER_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { + + class XmlWriter { + public: + + class ScopedElement { + public: + ScopedElement( XmlWriter* writer ) + : m_writer( writer ) + {} + + ScopedElement( ScopedElement const& other ) + : m_writer( other.m_writer ){ + other.m_writer = NULL; + } + + ~ScopedElement() { + if( m_writer ) + m_writer->endElement(); + } + + ScopedElement& writeText( std::string const& text, bool indent = true ) { + m_writer->writeText( text, indent ); + return *this; + } + + template + ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { + m_writer->writeAttribute( name, attribute ); + return *this; + } + + private: + mutable XmlWriter* m_writer; + }; + + XmlWriter() + : m_tagIsOpen( false ), + m_needsNewline( false ), + m_os( &Catch::cout() ) + {} + + XmlWriter( std::ostream& os ) + : m_tagIsOpen( false ), + m_needsNewline( false ), + m_os( &os ) + {} + + ~XmlWriter() { + while( !m_tags.empty() ) + endElement(); + } + +//# ifndef CATCH_CPP11_OR_GREATER +// XmlWriter& operator = ( XmlWriter const& other ) { +// XmlWriter temp( other ); +// swap( temp ); +// return *this; +// } +//# else +// XmlWriter( XmlWriter const& ) = default; +// XmlWriter( XmlWriter && ) = default; +// XmlWriter& operator = ( XmlWriter const& ) = default; +// XmlWriter& operator = ( XmlWriter && ) = default; +//# endif +// +// void swap( XmlWriter& other ) { +// std::swap( m_tagIsOpen, other.m_tagIsOpen ); +// std::swap( m_needsNewline, other.m_needsNewline ); +// std::swap( m_tags, other.m_tags ); +// std::swap( m_indent, other.m_indent ); +// std::swap( m_os, other.m_os ); +// } + + XmlWriter& startElement( std::string const& name ) { + ensureTagClosed(); + newlineIfNecessary(); + stream() << m_indent << "<" << name; + m_tags.push_back( name ); + m_indent += " "; + m_tagIsOpen = true; + return *this; + } + + ScopedElement scopedElement( std::string const& name ) { + ScopedElement scoped( this ); + startElement( name ); + return scoped; + } + + XmlWriter& endElement() { + newlineIfNecessary(); + m_indent = m_indent.substr( 0, m_indent.size()-2 ); + if( m_tagIsOpen ) { + stream() << "/>\n"; + m_tagIsOpen = false; + } + else { + stream() << m_indent << "\n"; + } + m_tags.pop_back(); + return *this; + } + + XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ) { + if( !name.empty() && !attribute.empty() ) { + stream() << " " << name << "=\""; + writeEncodedText( attribute ); + stream() << "\""; + } + return *this; + } + + XmlWriter& writeAttribute( std::string const& name, bool attribute ) { + stream() << " " << name << "=\"" << ( attribute ? "true" : "false" ) << "\""; + return *this; + } + + template + XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { + if( !name.empty() ) + stream() << " " << name << "=\"" << attribute << "\""; + return *this; + } + + XmlWriter& writeText( std::string const& text, bool indent = true ) { + if( !text.empty() ){ + bool tagWasOpen = m_tagIsOpen; + ensureTagClosed(); + if( tagWasOpen && indent ) + stream() << m_indent; + writeEncodedText( text ); + m_needsNewline = true; + } + return *this; + } + + XmlWriter& writeComment( std::string const& text ) { + ensureTagClosed(); + stream() << m_indent << ""; + m_needsNewline = true; + return *this; + } + + XmlWriter& writeBlankLine() { + ensureTagClosed(); + stream() << "\n"; + return *this; + } + + void setStream( std::ostream& os ) { + m_os = &os; + } + + private: + XmlWriter( XmlWriter const& ); + void operator=( XmlWriter const& ); + + std::ostream& stream() { + return *m_os; + } + + void ensureTagClosed() { + if( m_tagIsOpen ) { + stream() << ">\n"; + m_tagIsOpen = false; + } + } + + void newlineIfNecessary() { + if( m_needsNewline ) { + stream() << "\n"; + m_needsNewline = false; + } + } + + void writeEncodedText( std::string const& text ) { + static const char* charsToEncode = "<&\""; + std::string mtext = text; + std::string::size_type pos = mtext.find_first_of( charsToEncode ); + while( pos != std::string::npos ) { + stream() << mtext.substr( 0, pos ); + + switch( mtext[pos] ) { + case '<': + stream() << "<"; + break; + case '&': + stream() << "&"; + break; + case '\"': + stream() << """; + break; + } + mtext = mtext.substr( pos+1 ); + pos = mtext.find_first_of( charsToEncode ); + } + stream() << mtext; + } + + bool m_tagIsOpen; + bool m_needsNewline; + std::vector m_tags; + std::string m_indent; + std::ostream* m_os; + }; + +} +namespace Catch { + class XmlReporter : public StreamingReporterBase { + public: + XmlReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ), + m_sectionDepth( 0 ) + {} + + virtual ~XmlReporter(); + + static std::string getDescription() { + return "Reports test results as an XML document"; + } + + public: // StreamingReporterBase + virtual ReporterPreferences getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = true; + return prefs; + } + + virtual void noMatchingTestCases( std::string const& s ) { + StreamingReporterBase::noMatchingTestCases( s ); + } + + virtual void testRunStarting( TestRunInfo const& testInfo ) { + StreamingReporterBase::testRunStarting( testInfo ); + m_xml.setStream( stream ); + m_xml.startElement( "Catch" ); + if( !m_config->name().empty() ) + m_xml.writeAttribute( "name", m_config->name() ); + } + + virtual void testGroupStarting( GroupInfo const& groupInfo ) { + StreamingReporterBase::testGroupStarting( groupInfo ); + m_xml.startElement( "Group" ) + .writeAttribute( "name", groupInfo.name ); + } + + virtual void testCaseStarting( TestCaseInfo const& testInfo ) { + StreamingReporterBase::testCaseStarting(testInfo); + m_xml.startElement( "TestCase" ).writeAttribute( "name", trim( testInfo.name ) ); + + if ( m_config->showDurations() == ShowDurations::Always ) + m_testCaseTimer.start(); + } + + virtual void sectionStarting( SectionInfo const& sectionInfo ) { + StreamingReporterBase::sectionStarting( sectionInfo ); + if( m_sectionDepth++ > 0 ) { + m_xml.startElement( "Section" ) + .writeAttribute( "name", trim( sectionInfo.name ) ) + .writeAttribute( "description", sectionInfo.description ); + } + } + + virtual void assertionStarting( AssertionInfo const& ) { } + + virtual bool assertionEnded( AssertionStats const& assertionStats ) { + const AssertionResult& assertionResult = assertionStats.assertionResult; + + // Print any info messages in tags. + if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) { + for( std::vector::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end(); + it != itEnd; + ++it ) { + if( it->type == ResultWas::Info ) { + m_xml.scopedElement( "Info" ) + .writeText( it->message ); + } else if ( it->type == ResultWas::Warning ) { + m_xml.scopedElement( "Warning" ) + .writeText( it->message ); + } + } + } + + // Drop out if result was successful but we're not printing them. + if( !m_config->includeSuccessfulResults() && isOk(assertionResult.getResultType()) ) + return true; + + // Print the expression if there is one. + if( assertionResult.hasExpression() ) { + m_xml.startElement( "Expression" ) + .writeAttribute( "success", assertionResult.succeeded() ) + .writeAttribute( "type", assertionResult.getTestMacroName() ) + .writeAttribute( "filename", assertionResult.getSourceInfo().file ) + .writeAttribute( "line", assertionResult.getSourceInfo().line ); + + m_xml.scopedElement( "Original" ) + .writeText( assertionResult.getExpression() ); + m_xml.scopedElement( "Expanded" ) + .writeText( assertionResult.getExpandedExpression() ); + } + + // And... Print a result applicable to each result type. + switch( assertionResult.getResultType() ) { + case ResultWas::ThrewException: + m_xml.scopedElement( "Exception" ) + .writeAttribute( "filename", assertionResult.getSourceInfo().file ) + .writeAttribute( "line", assertionResult.getSourceInfo().line ) + .writeText( assertionResult.getMessage() ); + break; + case ResultWas::FatalErrorCondition: + m_xml.scopedElement( "Fatal Error Condition" ) + .writeAttribute( "filename", assertionResult.getSourceInfo().file ) + .writeAttribute( "line", assertionResult.getSourceInfo().line ) + .writeText( assertionResult.getMessage() ); + break; + case ResultWas::Info: + m_xml.scopedElement( "Info" ) + .writeText( assertionResult.getMessage() ); + break; + case ResultWas::Warning: + // Warning will already have been written + break; + case ResultWas::ExplicitFailure: + m_xml.scopedElement( "Failure" ) + .writeText( assertionResult.getMessage() ); + break; + default: + break; + } + + if( assertionResult.hasExpression() ) + m_xml.endElement(); + + return true; + } + + virtual void sectionEnded( SectionStats const& sectionStats ) { + StreamingReporterBase::sectionEnded( sectionStats ); + if( --m_sectionDepth > 0 ) { + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" ); + e.writeAttribute( "successes", sectionStats.assertions.passed ); + e.writeAttribute( "failures", sectionStats.assertions.failed ); + e.writeAttribute( "expectedFailures", sectionStats.assertions.failedButOk ); + + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds", sectionStats.durationInSeconds ); + + m_xml.endElement(); + } + } + + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) { + StreamingReporterBase::testCaseEnded( testCaseStats ); + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" ); + e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() ); + + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() ); + + m_xml.endElement(); + } + + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) { + StreamingReporterBase::testGroupEnded( testGroupStats ); + // TODO: Check testGroupStats.aborting and act accordingly. + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", testGroupStats.totals.assertions.passed ) + .writeAttribute( "failures", testGroupStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk ); + m_xml.endElement(); + } + + virtual void testRunEnded( TestRunStats const& testRunStats ) { + StreamingReporterBase::testRunEnded( testRunStats ); + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", testRunStats.totals.assertions.passed ) + .writeAttribute( "failures", testRunStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk ); + m_xml.endElement(); + } + + private: + Timer m_testCaseTimer; + XmlWriter m_xml; + int m_sectionDepth; + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "xml", XmlReporter ) + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_junit.hpp +#define TWOBLUECUBES_CATCH_REPORTER_JUNIT_HPP_INCLUDED + +#include + +namespace Catch { + + class JunitReporter : public CumulativeReporterBase { + public: + JunitReporter( ReporterConfig const& _config ) + : CumulativeReporterBase( _config ), + xml( _config.stream() ) + {} + + ~JunitReporter(); + + static std::string getDescription() { + return "Reports test results in an XML format that looks like Ant's junitreport target"; + } + + virtual void noMatchingTestCases( std::string const& /*spec*/ ) {} + + virtual ReporterPreferences getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = true; + return prefs; + } + + virtual void testRunStarting( TestRunInfo const& runInfo ) { + CumulativeReporterBase::testRunStarting( runInfo ); + xml.startElement( "testsuites" ); + } + + virtual void testGroupStarting( GroupInfo const& groupInfo ) { + suiteTimer.start(); + stdOutForSuite.str(""); + stdErrForSuite.str(""); + unexpectedExceptions = 0; + CumulativeReporterBase::testGroupStarting( groupInfo ); + } + + virtual bool assertionEnded( AssertionStats const& assertionStats ) { + if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException ) + unexpectedExceptions++; + return CumulativeReporterBase::assertionEnded( assertionStats ); + } + + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) { + stdOutForSuite << testCaseStats.stdOut; + stdErrForSuite << testCaseStats.stdErr; + CumulativeReporterBase::testCaseEnded( testCaseStats ); + } + + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) { + double suiteTime = suiteTimer.getElapsedSeconds(); + CumulativeReporterBase::testGroupEnded( testGroupStats ); + writeGroup( *m_testGroups.back(), suiteTime ); + } + + virtual void testRunEndedCumulative() { + xml.endElement(); + } + + void writeGroup( TestGroupNode const& groupNode, double suiteTime ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" ); + TestGroupStats const& stats = groupNode.value; + xml.writeAttribute( "name", stats.groupInfo.name ); + xml.writeAttribute( "errors", unexpectedExceptions ); + xml.writeAttribute( "failures", stats.totals.assertions.failed-unexpectedExceptions ); + xml.writeAttribute( "tests", stats.totals.assertions.total() ); + xml.writeAttribute( "hostname", "tbd" ); // !TBD + if( m_config->showDurations() == ShowDurations::Never ) + xml.writeAttribute( "time", "" ); + else + xml.writeAttribute( "time", suiteTime ); + xml.writeAttribute( "timestamp", "tbd" ); // !TBD + + // Write test cases + for( TestGroupNode::ChildNodes::const_iterator + it = groupNode.children.begin(), itEnd = groupNode.children.end(); + it != itEnd; + ++it ) + writeTestCase( **it ); + + xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite.str() ), false ); + xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite.str() ), false ); + } + + void writeTestCase( TestCaseNode const& testCaseNode ) { + TestCaseStats const& stats = testCaseNode.value; + + // All test cases have exactly one section - which represents the + // test case itself. That section may have 0-n nested sections + assert( testCaseNode.children.size() == 1 ); + SectionNode const& rootSection = *testCaseNode.children.front(); + + std::string className = stats.testInfo.className; + + if( className.empty() ) { + if( rootSection.childSections.empty() ) + className = "global"; + } + writeSection( className, "", rootSection ); + } + + void writeSection( std::string const& className, + std::string const& rootName, + SectionNode const& sectionNode ) { + std::string name = trim( sectionNode.stats.sectionInfo.name ); + if( !rootName.empty() ) + name = rootName + "/" + name; + + if( !sectionNode.assertions.empty() || + !sectionNode.stdOut.empty() || + !sectionNode.stdErr.empty() ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testcase" ); + if( className.empty() ) { + xml.writeAttribute( "classname", name ); + xml.writeAttribute( "name", "root" ); + } + else { + xml.writeAttribute( "classname", className ); + xml.writeAttribute( "name", name ); + } + xml.writeAttribute( "time", Catch::toString( sectionNode.stats.durationInSeconds ) ); + + writeAssertions( sectionNode ); + + if( !sectionNode.stdOut.empty() ) + xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), false ); + if( !sectionNode.stdErr.empty() ) + xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), false ); + } + for( SectionNode::ChildSections::const_iterator + it = sectionNode.childSections.begin(), + itEnd = sectionNode.childSections.end(); + it != itEnd; + ++it ) + if( className.empty() ) + writeSection( name, "", **it ); + else + writeSection( className, name, **it ); + } + + void writeAssertions( SectionNode const& sectionNode ) { + for( SectionNode::Assertions::const_iterator + it = sectionNode.assertions.begin(), itEnd = sectionNode.assertions.end(); + it != itEnd; + ++it ) + writeAssertion( *it ); + } + void writeAssertion( AssertionStats const& stats ) { + AssertionResult const& result = stats.assertionResult; + if( !result.isOk() ) { + std::string elementName; + switch( result.getResultType() ) { + case ResultWas::ThrewException: + case ResultWas::FatalErrorCondition: + elementName = "error"; + break; + case ResultWas::ExplicitFailure: + elementName = "failure"; + break; + case ResultWas::ExpressionFailed: + elementName = "failure"; + break; + case ResultWas::DidntThrowException: + elementName = "failure"; + break; + + // We should never see these here: + case ResultWas::Info: + case ResultWas::Warning: + case ResultWas::Ok: + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + elementName = "internalError"; + break; + } + + XmlWriter::ScopedElement e = xml.scopedElement( elementName ); + + xml.writeAttribute( "message", result.getExpandedExpression() ); + xml.writeAttribute( "type", result.getTestMacroName() ); + + std::ostringstream oss; + if( !result.getMessage().empty() ) + oss << result.getMessage() << "\n"; + for( std::vector::const_iterator + it = stats.infoMessages.begin(), + itEnd = stats.infoMessages.end(); + it != itEnd; + ++it ) + if( it->type == ResultWas::Info ) + oss << it->message << "\n"; + + oss << "at " << result.getSourceInfo(); + xml.writeText( oss.str(), false ); + } + } + + XmlWriter xml; + Timer suiteTimer; + std::ostringstream stdOutForSuite; + std::ostringstream stdErrForSuite; + unsigned int unexpectedExceptions; + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "junit", JunitReporter ) + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_console.hpp +#define TWOBLUECUBES_CATCH_REPORTER_CONSOLE_HPP_INCLUDED + +namespace Catch { + + struct ConsoleReporter : StreamingReporterBase { + ConsoleReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ), + m_headerPrinted( false ) + {} + + virtual ~ConsoleReporter(); + static std::string getDescription() { + return "Reports test results as plain lines of text"; + } + virtual ReporterPreferences getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = false; + return prefs; + } + + virtual void noMatchingTestCases( std::string const& spec ) { + stream << "No test cases matched '" << spec << "'" << std::endl; + } + + virtual void assertionStarting( AssertionInfo const& ) { + } + + virtual bool assertionEnded( AssertionStats const& _assertionStats ) { + AssertionResult const& result = _assertionStats.assertionResult; + + bool printInfoMessages = true; + + // Drop out if result was successful and we're not printing those + if( !m_config->includeSuccessfulResults() && result.isOk() ) { + if( result.getResultType() != ResultWas::Warning ) + return false; + printInfoMessages = false; + } + + lazyPrint(); + + AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); + printer.print(); + stream << std::endl; + return true; + } + + virtual void sectionStarting( SectionInfo const& _sectionInfo ) { + m_headerPrinted = false; + StreamingReporterBase::sectionStarting( _sectionInfo ); + } + virtual void sectionEnded( SectionStats const& _sectionStats ) { + if( _sectionStats.missingAssertions ) { + lazyPrint(); + Colour colour( Colour::ResultError ); + if( m_sectionStack.size() > 1 ) + stream << "\nNo assertions in section"; + else + stream << "\nNo assertions in test case"; + stream << " '" << _sectionStats.sectionInfo.name << "'\n" << std::endl; + } + if( m_headerPrinted ) { + if( m_config->showDurations() == ShowDurations::Always ) + stream << "Completed in " << _sectionStats.durationInSeconds << "s" << std::endl; + m_headerPrinted = false; + } + else { + if( m_config->showDurations() == ShowDurations::Always ) + stream << _sectionStats.sectionInfo.name << " completed in " << _sectionStats.durationInSeconds << "s" << std::endl; + } + StreamingReporterBase::sectionEnded( _sectionStats ); + } + + virtual void testCaseEnded( TestCaseStats const& _testCaseStats ) { + StreamingReporterBase::testCaseEnded( _testCaseStats ); + m_headerPrinted = false; + } + virtual void testGroupEnded( TestGroupStats const& _testGroupStats ) { + if( currentGroupInfo.used ) { + printSummaryDivider(); + stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n"; + printTotals( _testGroupStats.totals ); + stream << "\n" << std::endl; + } + StreamingReporterBase::testGroupEnded( _testGroupStats ); + } + virtual void testRunEnded( TestRunStats const& _testRunStats ) { + printTotalsDivider( _testRunStats.totals ); + printTotals( _testRunStats.totals ); + stream << std::endl; + StreamingReporterBase::testRunEnded( _testRunStats ); + } + + private: + + class AssertionPrinter { + void operator= ( AssertionPrinter const& ); + public: + AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages ) + : stream( _stream ), + stats( _stats ), + result( _stats.assertionResult ), + colour( Colour::None ), + message( result.getMessage() ), + messages( _stats.infoMessages ), + printInfoMessages( _printInfoMessages ) + { + switch( result.getResultType() ) { + case ResultWas::Ok: + colour = Colour::Success; + passOrFail = "PASSED"; + //if( result.hasMessage() ) + if( _stats.infoMessages.size() == 1 ) + messageLabel = "with message"; + if( _stats.infoMessages.size() > 1 ) + messageLabel = "with messages"; + break; + case ResultWas::ExpressionFailed: + if( result.isOk() ) { + colour = Colour::Success; + passOrFail = "FAILED - but was ok"; + } + else { + colour = Colour::Error; + passOrFail = "FAILED"; + } + if( _stats.infoMessages.size() == 1 ) + messageLabel = "with message"; + if( _stats.infoMessages.size() > 1 ) + messageLabel = "with messages"; + break; + case ResultWas::ThrewException: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "due to unexpected exception with message"; + break; + case ResultWas::FatalErrorCondition: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "due to a fatal error condition"; + break; + case ResultWas::DidntThrowException: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "because no exception was thrown where one was expected"; + break; + case ResultWas::Info: + messageLabel = "info"; + break; + case ResultWas::Warning: + messageLabel = "warning"; + break; + case ResultWas::ExplicitFailure: + passOrFail = "FAILED"; + colour = Colour::Error; + if( _stats.infoMessages.size() == 1 ) + messageLabel = "explicitly with message"; + if( _stats.infoMessages.size() > 1 ) + messageLabel = "explicitly with messages"; + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + passOrFail = "** internal error **"; + colour = Colour::Error; + break; + } + } + + void print() const { + printSourceInfo(); + if( stats.totals.assertions.total() > 0 ) { + if( result.isOk() ) + stream << "\n"; + printResultType(); + printOriginalExpression(); + printReconstructedExpression(); + } + else { + stream << "\n"; + } + printMessage(); + } + + private: + void printResultType() const { + if( !passOrFail.empty() ) { + Colour colourGuard( colour ); + stream << passOrFail << ":\n"; + } + } + void printOriginalExpression() const { + if( result.hasExpression() ) { + Colour colourGuard( Colour::OriginalExpression ); + stream << " "; + stream << result.getExpressionInMacro(); + stream << "\n"; + } + } + void printReconstructedExpression() const { + if( result.hasExpandedExpression() ) { + stream << "with expansion:\n"; + Colour colourGuard( Colour::ReconstructedExpression ); + stream << Text( result.getExpandedExpression(), TextAttributes().setIndent(2) ) << "\n"; + } + } + void printMessage() const { + if( !messageLabel.empty() ) + stream << messageLabel << ":" << "\n"; + for( std::vector::const_iterator it = messages.begin(), itEnd = messages.end(); + it != itEnd; + ++it ) { + // If this assertion is a warning ignore any INFO messages + if( printInfoMessages || it->type != ResultWas::Info ) + stream << Text( it->message, TextAttributes().setIndent(2) ) << "\n"; + } + } + void printSourceInfo() const { + Colour colourGuard( Colour::FileName ); + stream << result.getSourceInfo() << ": "; + } + + std::ostream& stream; + AssertionStats const& stats; + AssertionResult const& result; + Colour::Code colour; + std::string passOrFail; + std::string messageLabel; + std::string message; + std::vector messages; + bool printInfoMessages; + }; + + void lazyPrint() { + + if( !currentTestRunInfo.used ) + lazyPrintRunInfo(); + if( !currentGroupInfo.used ) + lazyPrintGroupInfo(); + + if( !m_headerPrinted ) { + printTestCaseAndSectionHeader(); + m_headerPrinted = true; + } + } + void lazyPrintRunInfo() { + stream << "\n" << getLineOfChars<'~'>() << "\n"; + Colour colour( Colour::SecondaryText ); + stream << currentTestRunInfo->name + << " is a Catch v" << libraryVersion.majorVersion << "." + << libraryVersion.minorVersion << " b" + << libraryVersion.buildNumber; + if( libraryVersion.branchName != std::string( "master" ) ) + stream << " (" << libraryVersion.branchName << ")"; + stream << " host application.\n" + << "Run with -? for options\n\n"; + + if( m_config->rngSeed() != 0 ) + stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n"; + + currentTestRunInfo.used = true; + } + void lazyPrintGroupInfo() { + if( !currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1 ) { + printClosedHeader( "Group: " + currentGroupInfo->name ); + currentGroupInfo.used = true; + } + } + void printTestCaseAndSectionHeader() { + assert( !m_sectionStack.empty() ); + printOpenHeader( currentTestCaseInfo->name ); + + if( m_sectionStack.size() > 1 ) { + Colour colourGuard( Colour::Headers ); + + std::vector::const_iterator + it = m_sectionStack.begin()+1, // Skip first section (test case) + itEnd = m_sectionStack.end(); + for( ; it != itEnd; ++it ) + printHeaderString( it->name, 2 ); + } + + SourceLineInfo lineInfo = m_sectionStack.front().lineInfo; + + if( !lineInfo.empty() ){ + stream << getLineOfChars<'-'>() << "\n"; + Colour colourGuard( Colour::FileName ); + stream << lineInfo << "\n"; + } + stream << getLineOfChars<'.'>() << "\n" << std::endl; + } + + void printClosedHeader( std::string const& _name ) { + printOpenHeader( _name ); + stream << getLineOfChars<'.'>() << "\n"; + } + void printOpenHeader( std::string const& _name ) { + stream << getLineOfChars<'-'>() << "\n"; + { + Colour colourGuard( Colour::Headers ); + printHeaderString( _name ); + } + } + + // if string has a : in first line will set indent to follow it on + // subsequent lines + void printHeaderString( std::string const& _string, std::size_t indent = 0 ) { + std::size_t i = _string.find( ": " ); + if( i != std::string::npos ) + i+=2; + else + i = 0; + stream << Text( _string, TextAttributes() + .setIndent( indent+i) + .setInitialIndent( indent ) ) << "\n"; + } + + struct SummaryColumn { + + SummaryColumn( std::string const& _label, Colour::Code _colour ) + : label( _label ), + colour( _colour ) + {} + SummaryColumn addRow( std::size_t count ) { + std::ostringstream oss; + oss << count; + std::string row = oss.str(); + for( std::vector::iterator it = rows.begin(); it != rows.end(); ++it ) { + while( it->size() < row.size() ) + *it = " " + *it; + while( it->size() > row.size() ) + row = " " + row; + } + rows.push_back( row ); + return *this; + } + + std::string label; + Colour::Code colour; + std::vector rows; + + }; + + void printTotals( Totals const& totals ) { + if( totals.testCases.total() == 0 ) { + stream << Colour( Colour::Warning ) << "No tests ran\n"; + } + else if( totals.assertions.total() > 0 && totals.assertions.allPassed() ) { + stream << Colour( Colour::ResultSuccess ) << "All tests passed"; + stream << " (" + << pluralise( totals.assertions.passed, "assertion" ) << " in " + << pluralise( totals.testCases.passed, "test case" ) << ")" + << "\n"; + } + else { + + std::vector columns; + columns.push_back( SummaryColumn( "", Colour::None ) + .addRow( totals.testCases.total() ) + .addRow( totals.assertions.total() ) ); + columns.push_back( SummaryColumn( "passed", Colour::Success ) + .addRow( totals.testCases.passed ) + .addRow( totals.assertions.passed ) ); + columns.push_back( SummaryColumn( "failed", Colour::ResultError ) + .addRow( totals.testCases.failed ) + .addRow( totals.assertions.failed ) ); + columns.push_back( SummaryColumn( "failed as expected", Colour::ResultExpectedFailure ) + .addRow( totals.testCases.failedButOk ) + .addRow( totals.assertions.failedButOk ) ); + + printSummaryRow( "test cases", columns, 0 ); + printSummaryRow( "assertions", columns, 1 ); + } + } + void printSummaryRow( std::string const& label, std::vector const& cols, std::size_t row ) { + for( std::vector::const_iterator it = cols.begin(); it != cols.end(); ++it ) { + std::string value = it->rows[row]; + if( it->label.empty() ) { + stream << label << ": "; + if( value != "0" ) + stream << value; + else + stream << Colour( Colour::Warning ) << "- none -"; + } + else if( value != "0" ) { + stream << Colour( Colour::LightGrey ) << " | "; + stream << Colour( it->colour ) + << value << " " << it->label; + } + } + stream << "\n"; + } + + static std::size_t makeRatio( std::size_t number, std::size_t total ) { + std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number/ total : 0; + return ( ratio == 0 && number > 0 ) ? 1 : ratio; + } + static std::size_t& findMax( std::size_t& i, std::size_t& j, std::size_t& k ) { + if( i > j && i > k ) + return i; + else if( j > k ) + return j; + else + return k; + } + + void printTotalsDivider( Totals const& totals ) { + if( totals.testCases.total() > 0 ) { + std::size_t failedRatio = makeRatio( totals.testCases.failed, totals.testCases.total() ); + std::size_t failedButOkRatio = makeRatio( totals.testCases.failedButOk, totals.testCases.total() ); + std::size_t passedRatio = makeRatio( totals.testCases.passed, totals.testCases.total() ); + while( failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH-1 ) + findMax( failedRatio, failedButOkRatio, passedRatio )++; + while( failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH-1 ) + findMax( failedRatio, failedButOkRatio, passedRatio )--; + + stream << Colour( Colour::Error ) << std::string( failedRatio, '=' ); + stream << Colour( Colour::ResultExpectedFailure ) << std::string( failedButOkRatio, '=' ); + if( totals.testCases.allPassed() ) + stream << Colour( Colour::ResultSuccess ) << std::string( passedRatio, '=' ); + else + stream << Colour( Colour::Success ) << std::string( passedRatio, '=' ); + } + else { + stream << Colour( Colour::Warning ) << std::string( CATCH_CONFIG_CONSOLE_WIDTH-1, '=' ); + } + stream << "\n"; + } + void printSummaryDivider() { + stream << getLineOfChars<'-'>() << "\n"; + } + + private: + bool m_headerPrinted; + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "console", ConsoleReporter ) + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_compact.hpp +#define TWOBLUECUBES_CATCH_REPORTER_COMPACT_HPP_INCLUDED + +namespace Catch { + + struct CompactReporter : StreamingReporterBase { + + CompactReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ) + {} + + virtual ~CompactReporter(); + + static std::string getDescription() { + return "Reports test results on a single line, suitable for IDEs"; + } + + virtual ReporterPreferences getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = false; + return prefs; + } + + virtual void noMatchingTestCases( std::string const& spec ) { + stream << "No test cases matched '" << spec << "'" << std::endl; + } + + virtual void assertionStarting( AssertionInfo const& ) { + } + + virtual bool assertionEnded( AssertionStats const& _assertionStats ) { + AssertionResult const& result = _assertionStats.assertionResult; + + bool printInfoMessages = true; + + // Drop out if result was successful and we're not printing those + if( !m_config->includeSuccessfulResults() && result.isOk() ) { + if( result.getResultType() != ResultWas::Warning ) + return false; + printInfoMessages = false; + } + + AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); + printer.print(); + + stream << std::endl; + return true; + } + + virtual void testRunEnded( TestRunStats const& _testRunStats ) { + printTotals( _testRunStats.totals ); + stream << "\n" << std::endl; + StreamingReporterBase::testRunEnded( _testRunStats ); + } + + private: + class AssertionPrinter { + void operator= ( AssertionPrinter const& ); + public: + AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages ) + : stream( _stream ) + , stats( _stats ) + , result( _stats.assertionResult ) + , messages( _stats.infoMessages ) + , itMessage( _stats.infoMessages.begin() ) + , printInfoMessages( _printInfoMessages ) + {} + + void print() { + printSourceInfo(); + + itMessage = messages.begin(); + + switch( result.getResultType() ) { + case ResultWas::Ok: + printResultType( Colour::ResultSuccess, passedString() ); + printOriginalExpression(); + printReconstructedExpression(); + if ( ! result.hasExpression() ) + printRemainingMessages( Colour::None ); + else + printRemainingMessages(); + break; + case ResultWas::ExpressionFailed: + if( result.isOk() ) + printResultType( Colour::ResultSuccess, failedString() + std::string( " - but was ok" ) ); + else + printResultType( Colour::Error, failedString() ); + printOriginalExpression(); + printReconstructedExpression(); + printRemainingMessages(); + break; + case ResultWas::ThrewException: + printResultType( Colour::Error, failedString() ); + printIssue( "unexpected exception with message:" ); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::FatalErrorCondition: + printResultType( Colour::Error, failedString() ); + printIssue( "fatal error condition with message:" ); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::DidntThrowException: + printResultType( Colour::Error, failedString() ); + printIssue( "expected exception, got none" ); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::Info: + printResultType( Colour::None, "info" ); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::Warning: + printResultType( Colour::None, "warning" ); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::ExplicitFailure: + printResultType( Colour::Error, failedString() ); + printIssue( "explicitly" ); + printRemainingMessages( Colour::None ); + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + printResultType( Colour::Error, "** internal error **" ); + break; + } + } + + private: + // Colour::LightGrey + + static Colour::Code dimColour() { return Colour::FileName; } + +#ifdef CATCH_PLATFORM_MAC + static const char* failedString() { return "FAILED"; } + static const char* passedString() { return "PASSED"; } +#else + static const char* failedString() { return "failed"; } + static const char* passedString() { return "passed"; } +#endif + + void printSourceInfo() const { + Colour colourGuard( Colour::FileName ); + stream << result.getSourceInfo() << ":"; + } + + void printResultType( Colour::Code colour, std::string passOrFail ) const { + if( !passOrFail.empty() ) { + { + Colour colourGuard( colour ); + stream << " " << passOrFail; + } + stream << ":"; + } + } + + void printIssue( std::string issue ) const { + stream << " " << issue; + } + + void printExpressionWas() { + if( result.hasExpression() ) { + stream << ";"; + { + Colour colour( dimColour() ); + stream << " expression was:"; + } + printOriginalExpression(); + } + } + + void printOriginalExpression() const { + if( result.hasExpression() ) { + stream << " " << result.getExpression(); + } + } + + void printReconstructedExpression() const { + if( result.hasExpandedExpression() ) { + { + Colour colour( dimColour() ); + stream << " for: "; + } + stream << result.getExpandedExpression(); + } + } + + void printMessage() { + if ( itMessage != messages.end() ) { + stream << " '" << itMessage->message << "'"; + ++itMessage; + } + } + + void printRemainingMessages( Colour::Code colour = dimColour() ) { + if ( itMessage == messages.end() ) + return; + + // using messages.end() directly yields compilation error: + std::vector::const_iterator itEnd = messages.end(); + const std::size_t N = static_cast( std::distance( itMessage, itEnd ) ); + + { + Colour colourGuard( colour ); + stream << " with " << pluralise( N, "message" ) << ":"; + } + + for(; itMessage != itEnd; ) { + // If this assertion is a warning ignore any INFO messages + if( printInfoMessages || itMessage->type != ResultWas::Info ) { + stream << " '" << itMessage->message << "'"; + if ( ++itMessage != itEnd ) { + Colour colourGuard( dimColour() ); + stream << " and"; + } + } + } + } + + private: + std::ostream& stream; + AssertionStats const& stats; + AssertionResult const& result; + std::vector messages; + std::vector::const_iterator itMessage; + bool printInfoMessages; + }; + + // Colour, message variants: + // - white: No tests ran. + // - red: Failed [both/all] N test cases, failed [both/all] M assertions. + // - white: Passed [both/all] N test cases (no assertions). + // - red: Failed N tests cases, failed M assertions. + // - green: Passed [both/all] N tests cases with M assertions. + + std::string bothOrAll( std::size_t count ) const { + return count == 1 ? "" : count == 2 ? "both " : "all " ; + } + + void printTotals( const Totals& totals ) const { + if( totals.testCases.total() == 0 ) { + stream << "No tests ran."; + } + else if( totals.testCases.failed == totals.testCases.total() ) { + Colour colour( Colour::ResultError ); + const std::string qualify_assertions_failed = + totals.assertions.failed == totals.assertions.total() ? + bothOrAll( totals.assertions.failed ) : ""; + stream << + "Failed " << bothOrAll( totals.testCases.failed ) + << pluralise( totals.testCases.failed, "test case" ) << ", " + "failed " << qualify_assertions_failed << + pluralise( totals.assertions.failed, "assertion" ) << "."; + } + else if( totals.assertions.total() == 0 ) { + stream << + "Passed " << bothOrAll( totals.testCases.total() ) + << pluralise( totals.testCases.total(), "test case" ) + << " (no assertions)."; + } + else if( totals.assertions.failed ) { + Colour colour( Colour::ResultError ); + stream << + "Failed " << pluralise( totals.testCases.failed, "test case" ) << ", " + "failed " << pluralise( totals.assertions.failed, "assertion" ) << "."; + } + else { + Colour colour( Colour::ResultSuccess ); + stream << + "Passed " << bothOrAll( totals.testCases.passed ) + << pluralise( totals.testCases.passed, "test case" ) << + " with " << pluralise( totals.assertions.passed, "assertion" ) << "."; + } + } + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "compact", CompactReporter ) + +} // end namespace Catch + +namespace Catch { + NonCopyable::~NonCopyable() {} + IShared::~IShared() {} + StreamBufBase::~StreamBufBase() CATCH_NOEXCEPT {} + IContext::~IContext() {} + IResultCapture::~IResultCapture() {} + ITestCase::~ITestCase() {} + ITestCaseRegistry::~ITestCaseRegistry() {} + IRegistryHub::~IRegistryHub() {} + IMutableRegistryHub::~IMutableRegistryHub() {} + IExceptionTranslator::~IExceptionTranslator() {} + IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() {} + IReporter::~IReporter() {} + IReporterFactory::~IReporterFactory() {} + IReporterRegistry::~IReporterRegistry() {} + IStreamingReporter::~IStreamingReporter() {} + AssertionStats::~AssertionStats() {} + SectionStats::~SectionStats() {} + TestCaseStats::~TestCaseStats() {} + TestGroupStats::~TestGroupStats() {} + TestRunStats::~TestRunStats() {} + CumulativeReporterBase::SectionNode::~SectionNode() {} + CumulativeReporterBase::~CumulativeReporterBase() {} + + StreamingReporterBase::~StreamingReporterBase() {} + ConsoleReporter::~ConsoleReporter() {} + CompactReporter::~CompactReporter() {} + IRunner::~IRunner() {} + IMutableContext::~IMutableContext() {} + IConfig::~IConfig() {} + XmlReporter::~XmlReporter() {} + JunitReporter::~JunitReporter() {} + TestRegistry::~TestRegistry() {} + FreeFunctionTestCase::~FreeFunctionTestCase() {} + IGeneratorInfo::~IGeneratorInfo() {} + IGeneratorsForTest::~IGeneratorsForTest() {} + TestSpec::Pattern::~Pattern() {} + TestSpec::NamePattern::~NamePattern() {} + TestSpec::TagPattern::~TagPattern() {} + TestSpec::ExcludedPattern::~ExcludedPattern() {} + + Matchers::Impl::StdString::Equals::~Equals() {} + Matchers::Impl::StdString::Contains::~Contains() {} + Matchers::Impl::StdString::StartsWith::~StartsWith() {} + Matchers::Impl::StdString::EndsWith::~EndsWith() {} + + void Config::dummy() {} +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#endif + +#ifdef CATCH_CONFIG_MAIN +// #included from: internal/catch_default_main.hpp +#define TWOBLUECUBES_CATCH_DEFAULT_MAIN_HPP_INCLUDED + +#ifndef __OBJC__ + +// Standard C/C++ main entry point +int main (int argc, char * const argv[]) { + return Catch::Session().run( argc, argv ); +} + +#else // __OBJC__ + +// Objective-C entry point +int main (int argc, char * const argv[]) { +#if !CATCH_ARC_ENABLED + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; +#endif + + Catch::registerTestMethods(); + int result = Catch::Session().run( argc, (char* const*)argv ); + +#if !CATCH_ARC_ENABLED + [pool drain]; +#endif + + return result; +} + +#endif // __OBJC__ + +#endif + +#ifdef CLARA_CONFIG_MAIN_NOT_DEFINED +# undef CLARA_CONFIG_MAIN +#endif + +////// + +// If this config identifier is defined then all CATCH macros are prefixed with CATCH_ +#ifdef CATCH_CONFIG_PREFIX_ALL + +#define CATCH_REQUIRE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE" ) +#define CATCH_REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, "CATCH_REQUIRE_FALSE" ) + +#define CATCH_REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THROWS" ) +#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THROWS_AS" ) +#define CATCH_REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_NOTHROW" ) + +#define CATCH_CHECK( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK" ) +#define CATCH_CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, "CATCH_CHECK_FALSE" ) +#define CATCH_CHECKED_IF( expr ) INTERNAL_CATCH_IF( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECKED_IF" ) +#define CATCH_CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECKED_ELSE" ) +#define CATCH_CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, "CATCH_CHECK_NOFAIL" ) + +#define CATCH_CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THROWS" ) +#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THROWS_AS" ) +#define CATCH_CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_NOTHROW" ) + +#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THAT" ) +#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THAT" ) + +#define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( msg, "CATCH_INFO" ) +#define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, "CATCH_WARN", msg ) +#define CATCH_SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( msg, "CATCH_INFO" ) +#define CATCH_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CATCH_CAPTURE" ) +#define CATCH_SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CATCH_CAPTURE" ) + +#ifdef CATCH_CONFIG_VARIADIC_MACROS + #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) + #define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) + #define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) + #define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) + #define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "CATCH_FAIL", __VA_ARGS__ ) + #define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "CATCH_SUCCEED", __VA_ARGS__ ) +#else + #define CATCH_TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) + #define CATCH_TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description ) + #define CATCH_METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) + #define CATCH_SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) + #define CATCH_FAIL( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "CATCH_FAIL", msg ) + #define CATCH_SUCCEED( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "CATCH_SUCCEED", msg ) +#endif +#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" ) + +#define CATCH_REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) +#define CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) + +#define CATCH_GENERATE( expr) INTERNAL_CATCH_GENERATE( expr ) + +// "BDD-style" convenience wrappers +#ifdef CATCH_CONFIG_VARIADIC_MACROS +#define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( "Scenario: " __VA_ARGS__ ) +#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) +#else +#define CATCH_SCENARIO( name, tags ) CATCH_TEST_CASE( "Scenario: " name, tags ) +#define CATCH_SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags ) +#endif +#define CATCH_GIVEN( desc ) CATCH_SECTION( "Given: " desc, "" ) +#define CATCH_WHEN( desc ) CATCH_SECTION( " When: " desc, "" ) +#define CATCH_AND_WHEN( desc ) CATCH_SECTION( " And: " desc, "" ) +#define CATCH_THEN( desc ) CATCH_SECTION( " Then: " desc, "" ) +#define CATCH_AND_THEN( desc ) CATCH_SECTION( " And: " desc, "" ) + +// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required +#else + +#define REQUIRE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal, "REQUIRE" ) +#define REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, "REQUIRE_FALSE" ) + +#define REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "REQUIRE_THROWS" ) +#define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::Normal, "REQUIRE_THROWS_AS" ) +#define REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::Normal, "REQUIRE_NOTHROW" ) + +#define CHECK( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK" ) +#define CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, "CHECK_FALSE" ) +#define CHECKED_IF( expr ) INTERNAL_CATCH_IF( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECKED_IF" ) +#define CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECKED_ELSE" ) +#define CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, "CHECK_NOFAIL" ) + +#define CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THROWS" ) +#define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THROWS_AS" ) +#define CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK_NOTHROW" ) + +#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THAT" ) +#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::Normal, "REQUIRE_THAT" ) + +#define INFO( msg ) INTERNAL_CATCH_INFO( msg, "INFO" ) +#define WARN( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, "WARN", msg ) +#define SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( msg, "INFO" ) +#define CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CAPTURE" ) +#define SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CAPTURE" ) + +#ifdef CATCH_CONFIG_VARIADIC_MACROS + #define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) + #define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) + #define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) + #define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) + #define FAIL( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "FAIL", __VA_ARGS__ ) + #define SUCCEED( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "SUCCEED", __VA_ARGS__ ) +#else + #define TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) + #define TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description ) + #define METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) + #define SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) + #define FAIL( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "FAIL", msg ) + #define SUCCEED( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "SUCCEED", msg ) +#endif +#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" ) + +#define REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) +#define REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) + +#define GENERATE( expr) INTERNAL_CATCH_GENERATE( expr ) + +#endif + +#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) + +// "BDD-style" convenience wrappers +#ifdef CATCH_CONFIG_VARIADIC_MACROS +#define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ ) +#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) +#else +#define SCENARIO( name, tags ) TEST_CASE( "Scenario: " name, tags ) +#define SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags ) +#endif +#define GIVEN( desc ) SECTION( " Given: " desc, "" ) +#define WHEN( desc ) SECTION( " When: " desc, "" ) +#define AND_WHEN( desc ) SECTION( "And when: " desc, "" ) +#define THEN( desc ) SECTION( " Then: " desc, "" ) +#define AND_THEN( desc ) SECTION( " And: " desc, "" ) + +using Catch::Detail::Approx; + +// #included from: internal/catch_reenable_warnings.h + +#define TWOBLUECUBES_CATCH_REENABLE_WARNINGS_H_INCLUDED + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(pop) +# else +# pragma clang diagnostic pop +# endif +#elif defined __GNUC__ +# pragma GCC diagnostic pop +#endif + +#endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED + diff --git a/tests/catch.license b/tests/catch.license new file mode 100644 index 0000000..127a5bc --- /dev/null +++ b/tests/catch.license @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +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, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/tests/errors.cpp b/tests/errors.cpp new file mode 100644 index 0000000..40f3723 --- /dev/null +++ b/tests/errors.cpp @@ -0,0 +1,119 @@ +/* + * This content is released under the MIT License as specified in https://raw.githubusercontent.com/gabime/spdlog/master/LICENSE + */ +#include "includes.h" + +#include + +class failing_sink : public spdlog::sinks::base_sink +{ +public: + failing_sink() = default; + ~failing_sink() final = default; + +protected: + void sink_it_(const spdlog::details::log_msg &) final + { + throw std::runtime_error("some error happened during log"); + } + + void flush_() final + { + throw std::runtime_error("some error happened during flush"); + } +}; + +TEST_CASE("default_error_handler", "[errors]]") +{ + prepare_logdir(); + std::string filename = "logs/simple_log.txt"; + + auto logger = spdlog::create("test-error", filename, true); + logger->set_pattern("%v"); + logger->info("Test message {} {}", 1); + logger->info("Test message {}", 2); + logger->flush(); + + REQUIRE(file_contents(filename) == std::string("Test message 2\n")); + REQUIRE(count_lines(filename) == 1); +} + +struct custom_ex +{ +}; +TEST_CASE("custom_error_handler", "[errors]]") +{ + prepare_logdir(); + std::string filename = "logs/simple_log.txt"; + auto logger = spdlog::create("logger", filename, true); + logger->flush_on(spdlog::level::info); + logger->set_error_handler([=](const std::string &) { throw custom_ex(); }); + logger->info("Good message #1"); + + REQUIRE_THROWS_AS(logger->info("Bad format msg {} {}", "xxx"), custom_ex); + logger->info("Good message #2"); + REQUIRE(count_lines(filename) == 2); +} + +TEST_CASE("default_error_handler2", "[errors]]") +{ + spdlog::drop_all(); + auto logger = spdlog::create("failed_logger"); + logger->set_error_handler([=](const std::string &) { throw custom_ex(); }); + REQUIRE_THROWS_AS(logger->info("Some message"), custom_ex); +} + +TEST_CASE("flush_error_handler", "[errors]]") +{ + spdlog::drop_all(); + auto logger = spdlog::create("failed_logger"); + logger->set_error_handler([=](const std::string &) { throw custom_ex(); }); + REQUIRE_THROWS_AS(logger->flush(), custom_ex); +} + +TEST_CASE("async_error_handler", "[errors]]") +{ + prepare_logdir(); + std::string err_msg("log failed with some msg"); + + std::string filename = "logs/simple_async_log.txt"; + { + spdlog::init_thread_pool(128, 1); + auto logger = spdlog::create_async("logger", filename, true); + logger->set_error_handler([=](const std::string &) { + std::ofstream ofs("logs/custom_err.txt"); + if (!ofs) + throw std::runtime_error("Failed open logs/custom_err.txt"); + ofs << err_msg; + }); + logger->info("Good message #1"); + logger->info("Bad format msg {} {}", "xxx"); + logger->info("Good message #2"); + spdlog::drop("logger"); // force logger to drain the queue and shutdown + } + spdlog::init_thread_pool(128, 1); + REQUIRE(count_lines(filename) == 2); + REQUIRE(file_contents("logs/custom_err.txt") == err_msg); +} + +// Make sure async error handler is executed +TEST_CASE("async_error_handler2", "[errors]]") +{ + prepare_logdir(); + std::string err_msg("This is async handler error message"); + { + spdlog::init_thread_pool(128, 1); + auto logger = spdlog::create_async("failed_logger"); + logger->set_error_handler([=](const std::string &) { + std::ofstream ofs("logs/custom_err2.txt"); + if (!ofs) + throw std::runtime_error("Failed open logs/custom_err2.txt"); + ofs << err_msg; + }); + logger->info("Hello failure"); + spdlog::drop("failed_logger"); // force logger to drain the queue and shutdown + } + + spdlog::init_thread_pool(128, 1); + REQUIRE(file_contents("logs/custom_err2.txt") == err_msg); +} diff --git a/tests/file_helper.cpp b/tests/file_helper.cpp new file mode 100644 index 0000000..bfe7eb9 --- /dev/null +++ b/tests/file_helper.cpp @@ -0,0 +1,110 @@ +/* + * This content is released under the MIT License as specified in https://raw.githubusercontent.com/gabime/spdlog/master/LICENSE + */ +#include "includes.h" + +using spdlog::details::file_helper; +using spdlog::details::log_msg; + +static const std::string target_filename = "logs/file_helper_test.txt"; + +static void write_with_helper(file_helper &helper, size_t howmany) +{ + log_msg msg; + fmt::memory_buffer formatted; + fmt::format_to(formatted, "{}", std::string(howmany, '1')); + helper.write(formatted); + helper.flush(); +} + +TEST_CASE("file_helper_filename", "[file_helper::filename()]]") +{ + prepare_logdir(); + + file_helper helper; + helper.open(target_filename); + REQUIRE(helper.filename() == target_filename); +} + +TEST_CASE("file_helper_size", "[file_helper::size()]]") +{ + prepare_logdir(); + size_t expected_size = 123; + { + file_helper helper; + helper.open(target_filename); + write_with_helper(helper, expected_size); + REQUIRE(static_cast(helper.size()) == expected_size); + } + REQUIRE(get_filesize(target_filename) == expected_size); +} + +TEST_CASE("file_helper_exists", "[file_helper::file_exists()]]") +{ + prepare_logdir(); + REQUIRE(!file_helper::file_exists(target_filename)); + file_helper helper; + helper.open(target_filename); + REQUIRE(file_helper::file_exists(target_filename)); +} + +TEST_CASE("file_helper_reopen", "[file_helper::reopen()]]") +{ + prepare_logdir(); + file_helper helper; + helper.open(target_filename); + write_with_helper(helper, 12); + REQUIRE(helper.size() == 12); + helper.reopen(true); + REQUIRE(helper.size() == 0); +} + +TEST_CASE("file_helper_reopen2", "[file_helper::reopen(false)]]") +{ + prepare_logdir(); + size_t expected_size = 14; + file_helper helper; + helper.open(target_filename); + write_with_helper(helper, expected_size); + REQUIRE(helper.size() == expected_size); + helper.reopen(false); + REQUIRE(helper.size() == expected_size); +} + +static void test_split_ext(const char *fname, const char *expect_base, const char *expect_ext) +{ + spdlog::filename_t filename(fname); + spdlog::filename_t expected_base(expect_base); + spdlog::filename_t expected_ext(expect_ext); + +#ifdef _WIN32 // replace folder sep + std::replace(filename.begin(), filename.end(), '/', '\\'); + std::replace(expected_base.begin(), expected_base.end(), '/', '\\'); +#endif + spdlog::filename_t basename, ext; + std::tie(basename, ext) = file_helper::split_by_extenstion(filename); + REQUIRE(basename == expected_base); + REQUIRE(ext == expected_ext); +} + +TEST_CASE("file_helper_split_by_extenstion", "[file_helper::split_by_extenstion()]]") +{ + test_split_ext("mylog.txt", "mylog", ".txt"); + test_split_ext(".mylog.txt", ".mylog", ".txt"); + test_split_ext(".mylog", ".mylog", ""); + test_split_ext("/aaa/bb.d/mylog", "/aaa/bb.d/mylog", ""); + test_split_ext("/aaa/bb.d/mylog.txt", "/aaa/bb.d/mylog", ".txt"); + test_split_ext("aaa/bbb/ccc/mylog.txt", "aaa/bbb/ccc/mylog", ".txt"); + test_split_ext("aaa/bbb/ccc/mylog.", "aaa/bbb/ccc/mylog.", ""); + test_split_ext("aaa/bbb/ccc/.mylog.txt", "aaa/bbb/ccc/.mylog", ".txt"); + test_split_ext("/aaa/bbb/ccc/mylog.txt", "/aaa/bbb/ccc/mylog", ".txt"); + test_split_ext("/aaa/bbb/ccc/.mylog", "/aaa/bbb/ccc/.mylog", ""); + test_split_ext("../mylog.txt", "../mylog", ".txt"); + test_split_ext(".././mylog.txt", ".././mylog", ".txt"); + test_split_ext(".././mylog.txt/xxx", ".././mylog.txt/xxx", ""); + test_split_ext("/mylog.txt", "/mylog", ".txt"); + test_split_ext("//mylog.txt", "//mylog", ".txt"); + test_split_ext("", "", ""); + test_split_ext(".", ".", ""); + test_split_ext("..txt", ".", ".txt"); +} diff --git a/tests/file_log.cpp b/tests/file_log.cpp new file mode 100644 index 0000000..6a4d7ae --- /dev/null +++ b/tests/file_log.cpp @@ -0,0 +1,171 @@ +/* + * This content is released under the MIT License as specified in https://raw.githubusercontent.com/gabime/spdlog/master/LICENSE + */ +#include "includes.h" + +TEST_CASE("simple_file_logger", "[simple_logger]]") +{ + prepare_logdir(); + std::string filename = "logs/simple_log"; + + auto logger = spdlog::create("logger", filename); + logger->set_pattern("%v"); + + logger->info("Test message {}", 1); + logger->info("Test message {}", 2); + + logger->flush(); + REQUIRE(file_contents(filename) == std::string("Test message 1\nTest message 2\n")); + REQUIRE(count_lines(filename) == 2); +} + +TEST_CASE("flush_on", "[flush_on]]") +{ + prepare_logdir(); + std::string filename = "logs/simple_log"; + + auto logger = spdlog::create("logger", filename); + logger->set_pattern("%v"); + logger->set_level(spdlog::level::trace); + logger->flush_on(spdlog::level::info); + logger->trace("Should not be flushed"); + REQUIRE(count_lines(filename) == 0); + + logger->info("Test message {}", 1); + logger->info("Test message {}", 2); + + REQUIRE(file_contents(filename) == std::string("Should not be flushed\nTest message 1\nTest message 2\n")); + REQUIRE(count_lines(filename) == 3); +} + +TEST_CASE("rotating_file_logger1", "[rotating_logger]]") +{ + prepare_logdir(); + size_t max_size = 1024 * 10; + std::string basename = "logs/rotating_log"; + auto logger = spdlog::rotating_logger_mt("logger", basename, max_size, 0); + + for (int i = 0; i < 10; ++i) + { + logger->info("Test message {}", i); + } + + logger->flush(); + auto filename = basename; + REQUIRE(count_lines(filename) == 10); +} + +TEST_CASE("rotating_file_logger2", "[rotating_logger]]") +{ + prepare_logdir(); + size_t max_size = 1024 * 10; + std::string basename = "logs/rotating_log"; + auto logger = spdlog::rotating_logger_mt("logger", basename, max_size, 1); + for (int i = 0; i < 10; ++i) + logger->info("Test message {}", i); + + logger->flush(); + auto filename = basename; + REQUIRE(count_lines(filename) == 10); + for (int i = 0; i < 1000; i++) + { + + logger->info("Test message {}", i); + } + + logger->flush(); + REQUIRE(get_filesize(filename) <= max_size); + auto filename1 = basename + ".1"; + REQUIRE(get_filesize(filename1) <= max_size); +} + +TEST_CASE("daily_logger with dateonly calculator", "[daily_logger_dateonly]]") +{ + using sink_type = spdlog::sinks::daily_file_sink; + + prepare_logdir(); + // calculate filename (time based) + std::string basename = "logs/daily_dateonly"; + std::tm tm = spdlog::details::os::localtime(); + fmt::memory_buffer w; + fmt::format_to(w, "{}_{:04d}-{:02d}-{:02d}", basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); + + auto logger = spdlog::create("logger", basename, 0, 0); + for (int i = 0; i < 10; ++i) + { + + logger->info("Test message {}", i); + } + logger->flush(); + auto filename = fmt::to_string(w); + REQUIRE(count_lines(filename) == 10); +} + +struct custom_daily_file_name_calculator +{ + static spdlog::filename_t calc_filename(const spdlog::filename_t &basename, const tm &now_tm) + { + fmt::memory_buffer w; + fmt::format_to(w, "{}{:04d}{:02d}{:02d}", basename, now_tm.tm_year + 1900, now_tm.tm_mon + 1, now_tm.tm_mday); + return fmt::to_string(w); + } +}; + +TEST_CASE("daily_logger with custom calculator", "[daily_logger_custom]]") +{ + using sink_type = spdlog::sinks::daily_file_sink; + + prepare_logdir(); + // calculate filename (time based) + std::string basename = "logs/daily_dateonly"; + std::tm tm = spdlog::details::os::localtime(); + fmt::memory_buffer w; + fmt::format_to(w, "{}{:04d}{:02d}{:02d}", basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); + + auto logger = spdlog::create("logger", basename, 0, 0); + for (int i = 0; i < 10; ++i) + { + logger->info("Test message {}", i); + } + + logger->flush(); + auto filename = fmt::to_string(w); + REQUIRE(count_lines(filename) == 10); +} + +/* + * File name calculations + */ + +TEST_CASE("rotating_file_sink::calc_filename1", "[rotating_file_sink]]") +{ + auto filename = spdlog::sinks::rotating_file_sink_st::calc_filename("rotated.txt", 3); + REQUIRE(filename == "rotated.3.txt"); +} + +TEST_CASE("rotating_file_sink::calc_filename2", "[rotating_file_sink]]") +{ + auto filename = spdlog::sinks::rotating_file_sink_st::calc_filename("rotated", 3); + REQUIRE(filename == "rotated.3"); +} + +TEST_CASE("rotating_file_sink::calc_filename3", "[rotating_file_sink]]") +{ + auto filename = spdlog::sinks::rotating_file_sink_st::calc_filename("rotated.txt", 0); + REQUIRE(filename == "rotated.txt"); +} + +// regex supported only from gcc 4.9 and above +#if defined(_MSC_VER) || !(__GNUC__ <= 4 && __GNUC_MINOR__ < 9) +#include + +TEST_CASE("daily_file_sink::daily_filename_calculator", "[daily_file_sink]]") +{ + // daily_YYYY-MM-DD_hh-mm.txt + auto filename = spdlog::sinks::daily_filename_calculator::calc_filename("daily.txt", spdlog::details::os::localtime()); + // date regex based on https://www.regular-expressions.info/dates.html + std::regex re(R"(^daily_(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])\.txt$)"); + std::smatch match; + REQUIRE(std::regex_match(filename, match, re)); +} +#endif diff --git a/tests/includes.h b/tests/includes.h new file mode 100644 index 0000000..c9056a0 --- /dev/null +++ b/tests/includes.h @@ -0,0 +1,22 @@ +#pragma once + +#include "catch.hpp" +#include "utils.h" +#include +#include +#include +#include +#include +#include + +#define SPDLOG_TRACE_ON +#define SPDLOG_DEBUG_ON + +#include "spdlog/async.h" +#include "spdlog/sinks/basic_file_sink.h" +#include "spdlog/sinks/daily_file_sink.h" +#include "spdlog/sinks/null_sink.h" +#include "spdlog/sinks/ostream_sink.h" +#include "spdlog/sinks/rotating_file_sink.h" +#include "spdlog/sinks/stdout_color_sinks.h" +#include "spdlog/spdlog.h" diff --git a/tests/main.cpp b/tests/main.cpp new file mode 100644 index 0000000..063e878 --- /dev/null +++ b/tests/main.cpp @@ -0,0 +1,2 @@ +#define CATCH_CONFIG_MAIN +#include "catch.hpp" \ No newline at end of file diff --git a/tests/registry.cpp b/tests/registry.cpp new file mode 100644 index 0000000..e682a93 --- /dev/null +++ b/tests/registry.cpp @@ -0,0 +1,77 @@ +#include "includes.h" + +static const char *tested_logger_name = "null_logger"; +static const char *tested_logger_name2 = "null_logger2"; + +TEST_CASE("register_drop", "[registry]") +{ + spdlog::drop_all(); + spdlog::create(tested_logger_name); + REQUIRE(spdlog::get(tested_logger_name) != nullptr); + // Throw if registring existing name + REQUIRE_THROWS_AS(spdlog::create(tested_logger_name), const spdlog::spdlog_ex&); +} + +TEST_CASE("explicit register" + "[registry]") +{ + spdlog::drop_all(); + auto logger = std::make_shared(tested_logger_name, std::make_shared()); + spdlog::register_logger(logger); + REQUIRE(spdlog::get(tested_logger_name) != nullptr); + // Throw if registring existing name + REQUIRE_THROWS_AS(spdlog::create(tested_logger_name), const spdlog::spdlog_ex&); +} + +TEST_CASE("apply_all" + "[registry]") +{ + spdlog::drop_all(); + auto logger = std::make_shared(tested_logger_name, std::make_shared()); + spdlog::register_logger(logger); + auto logger2 = std::make_shared(tested_logger_name2, std::make_shared()); + spdlog::register_logger(logger2); + + int counter = 0; + spdlog::apply_all([&counter](std::shared_ptr) { counter++; }); + REQUIRE(counter == 2); + + counter = 0; + spdlog::drop(tested_logger_name2); + spdlog::apply_all([&counter](std::shared_ptr l) { + REQUIRE(l->name() == tested_logger_name); + counter++; + }); + REQUIRE(counter == 1); +} + +TEST_CASE("drop" + "[registry]") +{ + spdlog::drop_all(); + spdlog::create(tested_logger_name); + spdlog::drop(tested_logger_name); + REQUIRE_FALSE(spdlog::get(tested_logger_name)); +} + +TEST_CASE("drop_all" + "[registry]") +{ + spdlog::drop_all(); + spdlog::create(tested_logger_name); + spdlog::create(tested_logger_name2); + spdlog::drop_all(); + REQUIRE_FALSE(spdlog::get(tested_logger_name)); + REQUIRE_FALSE(spdlog::get(tested_logger_name)); +} + +TEST_CASE("drop non existing" + "[registry]") +{ + spdlog::drop_all(); + spdlog::create(tested_logger_name); + spdlog::drop("some_name"); + REQUIRE_FALSE(spdlog::get("some_name")); + REQUIRE(spdlog::get(tested_logger_name)); + spdlog::drop_all(); +} diff --git a/tests/test_async.cpp b/tests/test_async.cpp new file mode 100644 index 0000000..6f86cf6 --- /dev/null +++ b/tests/test_async.cpp @@ -0,0 +1,195 @@ +#include "includes.h" +#include "spdlog/async.h" +#include "spdlog/sinks/basic_file_sink.h" +#include "test_sink.h" + +TEST_CASE("basic async test ", "[async]") +{ + using namespace spdlog; + auto test_sink = std::make_shared(); + size_t overrun_counter = 0; + size_t queue_size = 128; + size_t messages = 256; + { + auto tp = std::make_shared(queue_size, 1); + auto logger = std::make_shared("as", test_sink, tp, async_overflow_policy::block); + for (size_t i = 0; i < messages; i++) + { + logger->info("Hello message #{}", i); + } + logger->flush(); + overrun_counter = tp->overrun_counter(); + } + REQUIRE(test_sink->msg_counter() == messages); + REQUIRE(test_sink->flush_counter() == 1); + REQUIRE(overrun_counter == 0); +} + +TEST_CASE("discard policy ", "[async]") +{ + using namespace spdlog; + auto test_sink = std::make_shared(); + test_sink->set_delay(std::chrono::milliseconds(1)); + size_t queue_size = 4; + size_t messages = 1024; + + auto tp = std::make_shared(queue_size, 1); + auto logger = std::make_shared("as", test_sink, tp, async_overflow_policy::overrun_oldest); + for (size_t i = 0; i < messages; i++) + { + logger->info("Hello message"); + } + REQUIRE(test_sink->msg_counter() < messages); + REQUIRE(tp->overrun_counter() > 0); +} + +TEST_CASE("discard policy using factory ", "[async]") +{ + using namespace spdlog; + size_t queue_size = 4; + size_t messages = 1024; + spdlog::init_thread_pool(queue_size, 1); + + auto logger = spdlog::create_async_nb("as2"); + auto test_sink = std::static_pointer_cast(logger->sinks()[0]); + test_sink->set_delay(std::chrono::milliseconds(1)); + + for (size_t i = 0; i < messages; i++) + { + logger->info("Hello message"); + } + + REQUIRE(test_sink->msg_counter() < messages); + spdlog::drop_all(); +} + +TEST_CASE("flush", "[async]") +{ + using namespace spdlog; + auto test_sink = std::make_shared(); + size_t queue_size = 256; + size_t messages = 256; + { + auto tp = std::make_shared(queue_size, 1); + auto logger = std::make_shared("as", test_sink, tp, async_overflow_policy::block); + for (size_t i = 0; i < messages; i++) + { + logger->info("Hello message #{}", i); + } + + logger->flush(); + } + // std::this_thread::sleep_for(std::chrono::milliseconds(250)); + REQUIRE(test_sink->msg_counter() == messages); + REQUIRE(test_sink->flush_counter() == 1); +} + +TEST_CASE("async periodic flush", "[async]") +{ + using namespace spdlog; + + auto logger = spdlog::create_async("as"); + + auto test_sink = std::static_pointer_cast(logger->sinks()[0]); + + spdlog::flush_every(std::chrono::seconds(1)); + std::this_thread::sleep_for(std::chrono::milliseconds(1100)); + REQUIRE(test_sink->flush_counter() == 1); + spdlog::flush_every(std::chrono::seconds(0)); + spdlog::drop_all(); +} + +TEST_CASE("tp->wait_empty() ", "[async]") +{ + using namespace spdlog; + auto test_sink = std::make_shared(); + test_sink->set_delay(std::chrono::milliseconds(5)); + size_t messages = 100; + + auto tp = std::make_shared(messages, 2); + auto logger = std::make_shared("as", test_sink, tp, async_overflow_policy::block); + for (size_t i = 0; i < messages; i++) + { + logger->info("Hello message #{}", i); + } + logger->flush(); + tp.reset(); + + REQUIRE(test_sink->msg_counter() == messages); + REQUIRE(test_sink->flush_counter() == 1); +} + +TEST_CASE("multi threads", "[async]") +{ + using namespace spdlog; + auto test_sink = std::make_shared(); + size_t queue_size = 128; + size_t messages = 256; + size_t n_threads = 10; + { + auto tp = std::make_shared(queue_size, 1); + auto logger = std::make_shared("as", test_sink, tp, async_overflow_policy::block); + + std::vector threads; + for (size_t i = 0; i < n_threads; i++) + { + threads.emplace_back([logger, messages] { + for (size_t j = 0; j < messages; j++) + { + logger->info("Hello message #{}", j); + } + }); + logger->flush(); + } + + for (auto &t : threads) + { + t.join(); + } + } + + REQUIRE(test_sink->msg_counter() == messages * n_threads); + REQUIRE(test_sink->flush_counter() == n_threads); +} + +TEST_CASE("to_file", "[async]") +{ + prepare_logdir(); + size_t messages = 1024; + size_t tp_threads = 1; + std::string filename = "logs/async_test.log"; + { + auto file_sink = std::make_shared(filename, true); + auto tp = std::make_shared(messages, tp_threads); + auto logger = std::make_shared("as", std::move(file_sink), std::move(tp)); + + for (size_t j = 0; j < messages; j++) + { + logger->info("Hello message #{}", j); + } + } + + REQUIRE(count_lines(filename) == messages); + auto contents = file_contents(filename); + REQUIRE(ends_with(contents, std::string("Hello message #1023\n"))); +} + +TEST_CASE("to_file multi-workers", "[async]") +{ + prepare_logdir(); + size_t messages = 1024 * 10; + size_t tp_threads = 10; + std::string filename = "logs/async_test.log"; + { + auto file_sink = std::make_shared(filename, true); + auto tp = std::make_shared(messages, tp_threads); + auto logger = std::make_shared("as", std::move(file_sink), std::move(tp)); + + for (size_t j = 0; j < messages; j++) + { + logger->info("Hello message #{}", j); + } + } + + REQUIRE(count_lines(filename) == messages); +} diff --git a/tests/test_macros.cpp b/tests/test_macros.cpp new file mode 100644 index 0000000..0b3e0b7 --- /dev/null +++ b/tests/test_macros.cpp @@ -0,0 +1,40 @@ +/* + * This content is released under the MIT License as specified in https://raw.githubusercontent.com/gabime/spdlog/master/LICENSE + */ + +#include "includes.h" + +TEST_CASE("debug and trace w/o format string", "[macros]]") +{ + prepare_logdir(); + std::string filename = "logs/simple_log"; + + auto logger = spdlog::create("logger", filename); + logger->set_pattern("%v"); + logger->set_level(spdlog::level::trace); + + SPDLOG_TRACE(logger, "Test message 1"); + SPDLOG_DEBUG(logger, "Test message 2"); + logger->flush(); + + REQUIRE(ends_with(file_contents(filename), "Test message 2\n")); + REQUIRE(count_lines(filename) == 2); +} + +TEST_CASE("debug and trace with format strings", "[macros]]") +{ + prepare_logdir(); + std::string filename = "logs/simple_log"; + + auto logger = spdlog::create("logger", filename); + logger->set_pattern("%v"); + logger->set_level(spdlog::level::trace); + + SPDLOG_TRACE(logger, "Test message {}", 1); + // SPDLOG_DEBUG(logger, "Test message 2"); + SPDLOG_DEBUG(logger, "Test message {}", 222); + logger->flush(); + + REQUIRE(ends_with(file_contents(filename), "Test message 222\n")); + REQUIRE(count_lines(filename) == 2); +} diff --git a/tests/test_misc.cpp b/tests/test_misc.cpp new file mode 100644 index 0000000..2de4fb2 --- /dev/null +++ b/tests/test_misc.cpp @@ -0,0 +1,93 @@ +#include "includes.h" +#include "test_sink.h" + +template +std::string log_info(const T &what, spdlog::level::level_enum logger_level = spdlog::level::info) +{ + + std::ostringstream oss; + auto oss_sink = std::make_shared(oss); + + spdlog::logger oss_logger("oss", oss_sink); + oss_logger.set_level(logger_level); + oss_logger.set_pattern("%v"); + oss_logger.info(what); + + return oss.str().substr(0, oss.str().length() - strlen(spdlog::details::os::default_eol)); +} + +TEST_CASE("basic_logging ", "[basic_logging]") +{ + // const char + REQUIRE(log_info("Hello") == "Hello"); + REQUIRE(log_info("") == ""); + + // std::string + REQUIRE(log_info(std::string("Hello")) == "Hello"); + REQUIRE(log_info(std::string()) == std::string()); + + // Numbers + REQUIRE(log_info(5) == "5"); + REQUIRE(log_info(5.6) == "5.6"); + + // User defined class + // REQUIRE(log_info(some_logged_class("some_val")) == "some_val"); +} + +TEST_CASE("log_levels", "[log_levels]") +{ + REQUIRE(log_info("Hello", spdlog::level::err) == ""); + REQUIRE(log_info("Hello", spdlog::level::critical) == ""); + REQUIRE(log_info("Hello", spdlog::level::info) == "Hello"); + REQUIRE(log_info("Hello", spdlog::level::debug) == "Hello"); + REQUIRE(log_info("Hello", spdlog::level::trace) == "Hello"); +} + +TEST_CASE("to_c_str", "[convert_to_c_str]") +{ + REQUIRE(std::string(spdlog::level::to_c_str(spdlog::level::trace)) == "trace"); + REQUIRE(std::string(spdlog::level::to_c_str(spdlog::level::debug)) == "debug"); + REQUIRE(std::string(spdlog::level::to_c_str(spdlog::level::info)) == "info"); + REQUIRE(std::string(spdlog::level::to_c_str(spdlog::level::warn)) == "warning"); + REQUIRE(std::string(spdlog::level::to_c_str(spdlog::level::err)) == "error"); + REQUIRE(std::string(spdlog::level::to_c_str(spdlog::level::critical)) == "critical"); + REQUIRE(std::string(spdlog::level::to_c_str(spdlog::level::off)) == "off"); +} + +TEST_CASE("to_short_c_str", "[convert_to_short_c_str]") +{ + REQUIRE(std::string(spdlog::level::to_short_c_str(spdlog::level::trace)) == "T"); + REQUIRE(std::string(spdlog::level::to_short_c_str(spdlog::level::debug)) == "D"); + REQUIRE(std::string(spdlog::level::to_short_c_str(spdlog::level::info)) == "I"); + REQUIRE(std::string(spdlog::level::to_short_c_str(spdlog::level::warn)) == "W"); + REQUIRE(std::string(spdlog::level::to_short_c_str(spdlog::level::err)) == "E"); + REQUIRE(std::string(spdlog::level::to_short_c_str(spdlog::level::critical)) == "C"); + REQUIRE(std::string(spdlog::level::to_short_c_str(spdlog::level::off)) == "O"); +} + +TEST_CASE("to_level_enum", "[convert_to_level_enum]") +{ + REQUIRE(spdlog::level::from_str("trace") == spdlog::level::trace); + REQUIRE(spdlog::level::from_str("debug") == spdlog::level::debug); + REQUIRE(spdlog::level::from_str("info") == spdlog::level::info); + REQUIRE(spdlog::level::from_str("warning") == spdlog::level::warn); + REQUIRE(spdlog::level::from_str("error") == spdlog::level::err); + REQUIRE(spdlog::level::from_str("critical") == spdlog::level::critical); + REQUIRE(spdlog::level::from_str("off") == spdlog::level::off); + REQUIRE(spdlog::level::from_str("null") == spdlog::level::off); +} + +TEST_CASE("periodic flush", "[periodic_flush]") +{ + using namespace spdlog; + + auto logger = spdlog::create("periodic_flush"); + + auto test_sink = std::static_pointer_cast(logger->sinks()[0]); + + spdlog::flush_every(std::chrono::seconds(1)); + std::this_thread::sleep_for(std::chrono::milliseconds(1100)); + REQUIRE(test_sink->flush_counter() == 1); + spdlog::flush_every(std::chrono::seconds(0)); + spdlog::drop_all(); +} diff --git a/tests/test_mpmc_q.cpp b/tests/test_mpmc_q.cpp new file mode 100644 index 0000000..21b2e8a --- /dev/null +++ b/tests/test_mpmc_q.cpp @@ -0,0 +1,103 @@ +#include "includes.h" + +using namespace std::chrono; +using std::chrono::milliseconds; +using std::chrono::system_clock; + +system_clock::time_point now_millis() +{ + return time_point_cast(system_clock::now()); +} +TEST_CASE("dequeue-empty-nowait", "[mpmc_blocking_q]") +{ + size_t q_size = 100; + milliseconds tolerance_wait(10); + spdlog::details::mpmc_blocking_queue q(q_size); + int popped_item; + + auto millis_0 = now_millis(); + auto rv = q.dequeue_for(popped_item, milliseconds::zero()); + auto millis_1 = now_millis(); + + REQUIRE(rv == false); + REQUIRE((millis_1 - millis_0) <= tolerance_wait); +} + +TEST_CASE("dequeue-empty-wait", "[mpmc_blocking_q]") +{ + + size_t q_size = 100; + milliseconds wait_ms(250); + milliseconds tolerance_wait(10); + + spdlog::details::mpmc_blocking_queue q(q_size); + int popped_item; + auto millis_0 = now_millis(); + auto rv = q.dequeue_for(popped_item, wait_ms); + auto millis_1 = now_millis(); + auto delta_ms = millis_1 - millis_0; + + REQUIRE(rv == false); + REQUIRE(delta_ms >= wait_ms); + REQUIRE(delta_ms <= wait_ms + tolerance_wait); +} + +TEST_CASE("enqueue_nowait", "[mpmc_blocking_q]") +{ + + size_t q_size = 1; + spdlog::details::mpmc_blocking_queue q(q_size); + milliseconds tolerance_wait(10); + + q.enqueue(1); + REQUIRE(q.overrun_counter() == 0); + + auto millis_0 = now_millis(); + q.enqueue_nowait(2); + auto millis_1 = now_millis(); + REQUIRE((millis_1 - millis_0) <= tolerance_wait); + REQUIRE(q.overrun_counter() == 1); +} + +TEST_CASE("bad_queue", "[mpmc_blocking_q]") +{ + size_t q_size = 0; + spdlog::details::mpmc_blocking_queue q(q_size); + q.enqueue_nowait(1); + REQUIRE(q.overrun_counter() == 1); + int i; + REQUIRE(q.dequeue_for(i, milliseconds(0)) == false); +} + +TEST_CASE("empty_queue", "[mpmc_blocking_q]") +{ + size_t q_size = 10; + spdlog::details::mpmc_blocking_queue q(q_size); + int i; + REQUIRE(q.dequeue_for(i, milliseconds(10)) == false); +} + +TEST_CASE("full_queue", "[mpmc_blocking_q]") +{ + size_t q_size = 100; + spdlog::details::mpmc_blocking_queue q(q_size); + for (int i = 0; i < static_cast(q_size); i++) + { + q.enqueue(std::move(i)); + } + + q.enqueue_nowait(123456); + REQUIRE(q.overrun_counter() == 1); + + for (int i = 1; i < static_cast(q_size); i++) + { + int item = -1; + q.dequeue_for(item, milliseconds(0)); + REQUIRE(item == i); + } + + // last item pushed has overridden the oldest. + int item = -1; + q.dequeue_for(item, milliseconds(0)); + REQUIRE(item == 123456); +} \ No newline at end of file diff --git a/tests/test_pattern_formatter.cpp b/tests/test_pattern_formatter.cpp new file mode 100644 index 0000000..6eb5d7a --- /dev/null +++ b/tests/test_pattern_formatter.cpp @@ -0,0 +1,126 @@ +#include "includes.h" + +// log to str and return it +template + +static std::string log_to_str(const std::string &msg, const Args &... args) +{ + std::ostringstream oss; + auto oss_sink = std::make_shared(oss); + spdlog::logger oss_logger("pattern_tester", oss_sink); + oss_logger.set_level(spdlog::level::info); + + oss_logger.set_formatter(std::unique_ptr(new spdlog::pattern_formatter(args...))); + + oss_logger.info(msg); + return oss.str(); +} + +TEST_CASE("custom eol", "[pattern_formatter]") +{ + std::string msg = "Hello custom eol test"; + std::string eol = ";)"; + // auto formatter = std::make_shared("%v", spdlog::pattern_time_type::local, ";)"); + std::unique_ptr(new spdlog::pattern_formatter("%v", spdlog::pattern_time_type::local, ";)")); + + REQUIRE(log_to_str(msg, "%v", spdlog::pattern_time_type::local, ";)") == msg + eol); +} + +TEST_CASE("empty format", "[pattern_formatter]") +{ + REQUIRE(log_to_str("Some message", "", spdlog::pattern_time_type::local, "") == ""); +} + +TEST_CASE("empty format2", "[pattern_formatter]") +{ + REQUIRE(log_to_str("Some message", "", spdlog::pattern_time_type::local, "\n") == "\n"); +} + +TEST_CASE("level", "[pattern_formatter]") +{ + REQUIRE(log_to_str("Some message", "[%l] %v", spdlog::pattern_time_type::local, "\n") == "[info] Some message\n"); +} + +TEST_CASE("short level", "[pattern_formatter]") +{ + REQUIRE(log_to_str("Some message", "[%L] %v", spdlog::pattern_time_type::local, "\n") == "[I] Some message\n"); +} + +TEST_CASE("name", "[pattern_formatter]") +{ + REQUIRE(log_to_str("Some message", "[%n] %v", spdlog::pattern_time_type::local, "\n") == "[pattern_tester] Some message\n"); +} + +TEST_CASE("date MM/DD/YY ", "[pattern_formatter]") +{ + auto now_tm = spdlog::details::os::localtime(); + std::stringstream oss; + oss << std::setfill('0') << std::setw(2) << now_tm.tm_mon + 1 << "/" << std::setw(2) << now_tm.tm_mday << "/" << std::setw(2) + << (now_tm.tm_year + 1900) % 1000 << " Some message\n"; + REQUIRE(log_to_str("Some message", "%D %v", spdlog::pattern_time_type::local, "\n") == oss.str()); +} + +TEST_CASE("color range test1", "[pattern_formatter]") +{ + auto formatter = std::make_shared("%^%v%$", spdlog::pattern_time_type::local, "\n"); + spdlog::details::log_msg msg; + fmt::format_to(msg.raw, "Hello"); + fmt::memory_buffer formatted; + formatter->format(msg, formatted); + REQUIRE(msg.color_range_start == 0); + REQUIRE(msg.color_range_end == 5); + REQUIRE(log_to_str("hello", "%^%v%$", spdlog::pattern_time_type::local, "\n") == "hello\n"); +} + +TEST_CASE("color range test2", "[pattern_formatter]") +{ + auto formatter = std::make_shared("%^%$", spdlog::pattern_time_type::local, "\n"); + spdlog::details::log_msg msg; + fmt::memory_buffer formatted; + formatter->format(msg, formatted); + REQUIRE(msg.color_range_start == 0); + REQUIRE(msg.color_range_end == 0); + REQUIRE(log_to_str("", "%^%$", spdlog::pattern_time_type::local, "\n") == "\n"); +} + +TEST_CASE("color range test3", "[pattern_formatter]") +{ + auto formatter = std::make_shared("%^***%$"); + spdlog::details::log_msg msg; + fmt::memory_buffer formatted; + formatter->format(msg, formatted); + REQUIRE(msg.color_range_start == 0); + REQUIRE(msg.color_range_end == 3); +} + +TEST_CASE("color range test4", "[pattern_formatter]") +{ + auto formatter = std::make_shared("XX%^YYY%$", spdlog::pattern_time_type::local, "\n"); + spdlog::details::log_msg msg; + fmt::format_to(msg.raw, "ignored"); + fmt::memory_buffer formatted; + formatter->format(msg, formatted); + REQUIRE(msg.color_range_start == 2); + REQUIRE(msg.color_range_end == 5); + REQUIRE(log_to_str("ignored", "XX%^YYY%$", spdlog::pattern_time_type::local, "\n") == "XXYYY\n"); +} + +TEST_CASE("color range test5", "[pattern_formatter]") +{ + auto formatter = std::make_shared("**%^"); + spdlog::details::log_msg msg; + fmt::memory_buffer formatted; + formatter->format(msg, formatted); + REQUIRE(msg.color_range_start == 2); + REQUIRE(msg.color_range_end == 0); +} + +TEST_CASE("color range test6", "[pattern_formatter]") +{ + auto formatter = std::make_shared("**%$"); + spdlog::details::log_msg msg; + fmt::memory_buffer formatted; + formatter->format(msg, formatted); + REQUIRE(msg.color_range_start == 0); + REQUIRE(msg.color_range_end == 2); +} diff --git a/tests/test_sink.h b/tests/test_sink.h new file mode 100644 index 0000000..ae08e98 --- /dev/null +++ b/tests/test_sink.h @@ -0,0 +1,59 @@ +// +// Copyright(c) 2018 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "spdlog/details/null_mutex.h" +#include "spdlog/sinks/base_sink.h" + +#include +#include +#include + +namespace spdlog { +namespace sinks { + +template +class test_sink : public base_sink +{ +public: + size_t msg_counter() + { + std::lock_guard lock(base_sink::mutex_); + return msg_counter_; + } + + size_t flush_counter() + { + std::lock_guard lock(base_sink::mutex_); + return flush_counter_; + } + + void set_delay(std::chrono::milliseconds delay) + { + delay_ = delay; + } + +protected: + void sink_it_(const details::log_msg &) override + { + msg_counter_++; + std::this_thread::sleep_for(delay_); + } + + void flush_() override + { + flush_counter_++; + } + size_t msg_counter_{0}; + size_t flush_counter_{0}; + std::chrono::milliseconds delay_{std::chrono::milliseconds::zero()}; +}; + +using test_sink_mt = test_sink; +using test_sink_st = test_sink; + +} // namespace sinks +} // namespace spdlog diff --git a/tests/tests.sln b/tests/tests.sln new file mode 100644 index 0000000..319bf95 --- /dev/null +++ b/tests/tests.sln @@ -0,0 +1,100 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27428.2037 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tests", "tests.vcxproj", "{59A07559-5F38-4DD6-A7FA-DB4153690B42}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "spdlog", "spdlog", "{0C043010-E40A-43B9-A5EA-B931FC8B6EFB}" + ProjectSection(SolutionItems) = preProject + ..\include\spdlog\async.h = ..\include\spdlog\async.h + ..\include\spdlog\async_logger.h = ..\include\spdlog\async_logger.h + ..\include\spdlog\common.h = ..\include\spdlog\common.h + ..\include\spdlog\formatter.h = ..\include\spdlog\formatter.h + ..\include\spdlog\logger.h = ..\include\spdlog\logger.h + ..\include\spdlog\spdlog.h = ..\include\spdlog\spdlog.h + ..\include\spdlog\tweakme.h = ..\include\spdlog\tweakme.h + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "details", "details", "{C8A207B7-6930-4D28-85BB-EA79ADFD7DAC}" + ProjectSection(SolutionItems) = preProject + ..\include\spdlog\details\async_logger_impl.h = ..\include\spdlog\details\async_logger_impl.h + ..\include\spdlog\details\file_helper.h = ..\include\spdlog\details\file_helper.h + ..\include\spdlog\details\log_msg.h = ..\include\spdlog\details\log_msg.h + ..\include\spdlog\details\logger_impl.h = ..\include\spdlog\details\logger_impl.h + ..\include\spdlog\details\mpmc_bounded_q.h = ..\include\spdlog\details\mpmc_bounded_q.h + ..\include\spdlog\details\null_mutex.h = ..\include\spdlog\details\null_mutex.h + ..\include\spdlog\details\os.h = ..\include\spdlog\details\os.h + ..\include\spdlog\details\pattern_formatter.h = ..\include\spdlog\details\pattern_formatter.h + ..\include\spdlog\details\registry.h = ..\include\spdlog\details\registry.h + ..\include\spdlog\details\spdlog_impl.h = ..\include\spdlog\details\spdlog_impl.h + ..\include\spdlog\details\thread_pool.h = ..\include\spdlog\details\thread_pool.h + ..\include\spdlog\details\traits.h = ..\include\spdlog\details\traits.h + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "fmt", "fmt", "{6512BA57-E9B9-4971-BC3A-83C784CC59A5}" + ProjectSection(SolutionItems) = preProject + ..\include\spdlog\fmt\fmt.h = ..\include\spdlog\fmt\fmt.h + ..\include\spdlog\fmt\ostr.h = ..\include\spdlog\fmt\ostr.h + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "bundled", "bundled", "{F0D4A944-E0C7-4575-8254-06AC92B384E6}" + ProjectSection(SolutionItems) = preProject + ..\include\spdlog\fmt\bundled\format.cc = ..\include\spdlog\fmt\bundled\format.cc + ..\include\spdlog\fmt\bundled\format.h = ..\include\spdlog\fmt\bundled\format.h + ..\include\spdlog\fmt\bundled\LICENSE.rst = ..\include\spdlog\fmt\bundled\LICENSE.rst + ..\include\spdlog\fmt\bundled\ostream.cc = ..\include\spdlog\fmt\bundled\ostream.cc + ..\include\spdlog\fmt\bundled\ostream.h = ..\include\spdlog\fmt\bundled\ostream.h + ..\include\spdlog\fmt\bundled\posix.cc = ..\include\spdlog\fmt\bundled\posix.cc + ..\include\spdlog\fmt\bundled\posix.h = ..\include\spdlog\fmt\bundled\posix.h + ..\include\spdlog\fmt\bundled\time.h = ..\include\spdlog\fmt\bundled\time.h + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sinks", "sinks", "{093AE34A-B617-467E-8F31-3C5A85EF51EB}" + ProjectSection(SolutionItems) = preProject + ..\include\spdlog\sinks\android_sink.h = ..\include\spdlog\sinks\android_sink.h + ..\include\spdlog\sinks\ansicolor_sink.h = ..\include\spdlog\sinks\ansicolor_sink.h + ..\include\spdlog\sinks\base_sink.h = ..\include\spdlog\sinks\base_sink.h + ..\include\spdlog\sinks\dist_sink.h = ..\include\spdlog\sinks\dist_sink.h + ..\include\spdlog\sinks\file_sinks.h = ..\include\spdlog\sinks\file_sinks.h + ..\include\spdlog\sinks\msvc_sink.h = ..\include\spdlog\sinks\msvc_sink.h + ..\include\spdlog\sinks\null_sink.h = ..\include\spdlog\sinks\null_sink.h + ..\include\spdlog\sinks\ostream_sink.h = ..\include\spdlog\sinks\ostream_sink.h + ..\include\spdlog\sinks\sink.h = ..\include\spdlog\sinks\sink.h + ..\include\spdlog\sinks\stdout_color_sinks.h = ..\include\spdlog\sinks\stdout_color_sinks.h + ..\include\spdlog\sinks\stdout_sinks.h = ..\include\spdlog\sinks\stdout_sinks.h + ..\include\spdlog\sinks\syslog_sink.h = ..\include\spdlog\sinks\syslog_sink.h + ..\include\spdlog\sinks\wincolor_sink.h = ..\include\spdlog\sinks\wincolor_sink.h + ..\include\spdlog\sinks\windebug_sink.h = ..\include\spdlog\sinks\windebug_sink.h + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {59A07559-5F38-4DD6-A7FA-DB4153690B42}.Debug|Win32.ActiveCfg = Debug|Win32 + {59A07559-5F38-4DD6-A7FA-DB4153690B42}.Debug|Win32.Build.0 = Debug|Win32 + {59A07559-5F38-4DD6-A7FA-DB4153690B42}.Debug|x64.ActiveCfg = Debug|x64 + {59A07559-5F38-4DD6-A7FA-DB4153690B42}.Debug|x64.Build.0 = Debug|x64 + {59A07559-5F38-4DD6-A7FA-DB4153690B42}.Release|Win32.ActiveCfg = Release|Win32 + {59A07559-5F38-4DD6-A7FA-DB4153690B42}.Release|Win32.Build.0 = Release|Win32 + {59A07559-5F38-4DD6-A7FA-DB4153690B42}.Release|x64.ActiveCfg = Release|x64 + {59A07559-5F38-4DD6-A7FA-DB4153690B42}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {C8A207B7-6930-4D28-85BB-EA79ADFD7DAC} = {0C043010-E40A-43B9-A5EA-B931FC8B6EFB} + {6512BA57-E9B9-4971-BC3A-83C784CC59A5} = {0C043010-E40A-43B9-A5EA-B931FC8B6EFB} + {F0D4A944-E0C7-4575-8254-06AC92B384E6} = {6512BA57-E9B9-4971-BC3A-83C784CC59A5} + {093AE34A-B617-467E-8F31-3C5A85EF51EB} = {0C043010-E40A-43B9-A5EA-B931FC8B6EFB} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {1D363F0E-2DC2-4544-963A-9812679AFF6B} + EndGlobalSection +EndGlobal diff --git a/tests/tests.vcxproj b/tests/tests.vcxproj new file mode 100644 index 0000000..a246fd2 --- /dev/null +++ b/tests/tests.vcxproj @@ -0,0 +1,148 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {59A07559-5F38-4DD6-A7FA-DB4153690B42} + tests + 10.0.16299.0 + + + + Application + true + v141 + MultiByte + + + Application + true + v141 + MultiByte + + + Application + false + v141 + true + MultiByte + + + Application + false + v141 + true + MultiByte + + + + + + + + + + + + + + + + + + + + + Level3 + Disabled + true + $(SolutionDir)\..\include;%(AdditionalIncludeDirectories) + + + true + Console + + + + + Level3 + Disabled + true + _MBCS;%(PreprocessorDefinitions) + $(SolutionDir)..\include;%(AdditionalIncludeDirectories) + + + true + Console + + + + + Level3 + MaxSpeed + true + true + true + $(SolutionDir)\..\include;%(AdditionalIncludeDirectories) + + + true + true + true + Console + + + + + Level3 + MaxSpeed + true + true + true + _MBCS;%(PreprocessorDefinitions) + $(SolutionDir)..\include;%(AdditionalIncludeDirectories) + + + true + true + true + Console + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/tests.vcxproj.filters b/tests/tests.vcxproj.filters new file mode 100644 index 0000000..c70cc49 --- /dev/null +++ b/tests/tests.vcxproj.filters @@ -0,0 +1,60 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/tests/tests.vcxproj.user b/tests/tests.vcxproj.user new file mode 100644 index 0000000..be25078 --- /dev/null +++ b/tests/tests.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/tests/utils.cpp b/tests/utils.cpp new file mode 100644 index 0000000..2c50ce3 --- /dev/null +++ b/tests/utils.cpp @@ -0,0 +1,52 @@ +#include "includes.h" + +void prepare_logdir() +{ + spdlog::drop_all(); +#ifdef _WIN32 + system("if not exist logs mkdir logs"); + system("del /F /Q logs\\*"); +#else + auto rv = system("mkdir -p logs"); + rv = system("rm -f logs/*"); + (void)rv; +#endif +} + +std::string file_contents(const std::string &filename) +{ + std::ifstream ifs(filename); + if (!ifs) + throw std::runtime_error("Failed open file "); + return std::string((std::istreambuf_iterator(ifs)), (std::istreambuf_iterator())); +} + +std::size_t count_lines(const std::string &filename) +{ + std::ifstream ifs(filename); + if (!ifs) + throw std::runtime_error("Failed open file "); + + std::string line; + size_t counter = 0; + while (std::getline(ifs, line)) + counter++; + return counter; +} + +std::size_t get_filesize(const std::string &filename) +{ + std::ifstream ifs(filename, std::ifstream::ate | std::ifstream::binary); + if (!ifs) + throw std::runtime_error("Failed open file "); + + return static_cast(ifs.tellg()); +} + +// source: https://stackoverflow.com/a/2072890/192001 +bool ends_with(std::string const &value, std::string const &ending) +{ + if (ending.size() > value.size()) + return false; + return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); +} diff --git a/tests/utils.h b/tests/utils.h new file mode 100644 index 0000000..e788b50 --- /dev/null +++ b/tests/utils.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +std::size_t count_lines(const std::string &filename); + +void prepare_logdir(); + +std::string file_contents(const std::string &filename); + +std::size_t count_lines(const std::string &filename); + +std::size_t get_filesize(const std::string &filename); + +bool ends_with(std::string const &value, std::string const &ending); \ No newline at end of file -- 2.30.2