diff --git a/CMakeLists.txt b/CMakeLists.txt index b8508f0..10c804d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,18 +2,19 @@ cmake_minimum_required(VERSION 3.12) project(errors) -# Import dependencies +if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) + set(NOT_SUBPROJECT TRUE) +endif() + +# Initialize CPM.cmake include(cmake/CPM.cmake) -cpmaddpackage("gh:fmtlib/fmt#10.0.0") # Build the main library add_library(errors src/error.cpp) target_include_directories(errors PUBLIC include) -target_link_libraries(errors PUBLIC fmt) -target_compile_features(errors PRIVATE cxx_std_20) # Check if this project is the main project -if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) +if(NOT_SUBPROJECT) option(BUILD_DOCS "Enable documentations build" OFF) # Statically analyze code by checking for warnings @@ -37,14 +38,11 @@ if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) # Append the main library properties instead of linking the library. get_target_property(errors_SOURCES errors SOURCES) get_target_property(errors_INCLUDES errors INCLUDE_DIRECTORIES) - get_target_property(errors_LIBRARIES errors LINK_LIBRARIES) - get_target_property(errors_FEATURES errors COMPILE_FEATURES) # Build tests for the main library add_executable(errors_test test/error_test.cpp ${errors_SOURCES}) target_include_directories(errors_test PRIVATE ${errors_INCLUDES}) - target_link_libraries(errors_test PRIVATE Catch2::Catch2WithMain ${errors_LIBRARIES}) - target_compile_features(errors_test PRIVATE ${errors_FEATURES}) + target_link_libraries(errors_test PRIVATE Catch2::Catch2WithMain) # Enable support to check for test coverage if(NOT MSVC) @@ -58,6 +56,8 @@ if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) # Build XML documentation if(BUILD_DOCS) include(cmake/add_xml_docs.cmake) - add_xml_docs(docs include/errors/error.hpp) + add_xml_docs(errors_docs include/errors/error.hpp) endif() endif() + +add_subdirectory(components) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt new file mode 100644 index 0000000..1db9315 --- /dev/null +++ b/components/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(format) diff --git a/components/format/CMakeLists.txt b/components/format/CMakeLists.txt new file mode 100644 index 0000000..381e060 --- /dev/null +++ b/components/format/CMakeLists.txt @@ -0,0 +1,35 @@ +cpmaddpackage("gh:fmtlib/fmt#10.0.0") + +add_library(errors_format src/format.cpp) +target_include_directories(errors_format PUBLIC include) +target_link_libraries(errors_format PUBLIC errors fmt) +target_compile_features(errors_format PRIVATE cxx_std_20) + +if(NOT_SUBPROJECT) + if (BUILD_TESTING) + # Append the main library properties instead of linking the library. + get_target_property(errors_format_SOURCES errors_format SOURCES) + get_target_property(errors_format_INCLUDES errors_format INCLUDE_DIRECTORIES) + get_target_property(errors_format_LIBRARIES errors_format LINK_LIBRARIES) + get_target_property(errors_format_FEATURES errors_format COMPILE_FEATURES) + + # Build tests for the main library + add_executable(errors_format_test test/format_test.cpp ${errors_format_SOURCES}) + target_include_directories(errors_format_test PRIVATE ${errors_format_INCLUDES}) + target_link_libraries(errors_format_test PRIVATE Catch2::Catch2WithMain ${errors_format_LIBRARIES}) + target_compile_features(errors_format_test PRIVATE ${errors_format_FEATURES}) + + # Enable support to check for test coverage + if(NOT MSVC) + target_compile_options(errors_format_test PRIVATE --coverage -O0 -fno-exceptions) + target_link_options(errors_format_test PRIVATE --coverage) + endif() + + catch_discover_tests(errors_format_test) + endif() + + # Build XML documentation + if(BUILD_DOCS) + add_xml_docs(errors_format_docs include/errors/format.hpp) + endif() +endif() diff --git a/components/format/include/errors/format.hpp b/components/format/include/errors/format.hpp new file mode 100644 index 0000000..5d5c3a3 --- /dev/null +++ b/components/format/include/errors/format.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include +#include + +namespace errors { + +/** + * @brief Creates a new error object with a formatted message. + * @tparam T Variadic template parameter pack for format arguments. + * @param fmt A format string for the message. + * @param args Format arguments. + * @return A new error object. + */ +template +Error format(fmt::format_string fmt, T&&... args); + +} // namespace errors + +namespace fmt { + +template <> +struct formatter { + format_parse_context::iterator parse(format_parse_context& ctx) const; + format_context::iterator format(const errors::Error& err, + format_context& ctx) const; +}; + +} // namespace fmt + +#include "format.ipp" diff --git a/components/format/include/errors/format.ipp b/components/format/include/errors/format.ipp new file mode 100644 index 0000000..4b0401f --- /dev/null +++ b/components/format/include/errors/format.ipp @@ -0,0 +1,8 @@ +namespace errors { + +template +Error format(fmt::format_string fmt, T&&... args) { + return errors::make(fmt::format(fmt, std::forward(args)...)); +} + +} // namespace errors diff --git a/components/format/src/format.cpp b/components/format/src/format.cpp new file mode 100644 index 0000000..9efe137 --- /dev/null +++ b/components/format/src/format.cpp @@ -0,0 +1,15 @@ +#include + +namespace fmt { + +format_parse_context::iterator formatter::parse( + format_parse_context& ctx) const { + return ctx.begin(); +} + +format_context::iterator formatter::format( + const errors::Error& err, format_context& ctx) const { + return format_to(ctx.out(), "error: {}", err.message()); +} + +} // namespace fmt diff --git a/components/format/test/format_test.cpp b/components/format/test/format_test.cpp new file mode 100644 index 0000000..ff6e2c0 --- /dev/null +++ b/components/format/test/format_test.cpp @@ -0,0 +1,14 @@ +#include + +#include +#include + +TEST_CASE("Error Construction With Formatting") { + const errors::Error err = errors::format("HTTP error {}", 404); + REQUIRE(err.message() == "HTTP error 404"); +} + +TEST_CASE("Error Printing Using fmtlib") { + const auto err = errors::make("unknown error"); + REQUIRE(fmt::format("{}", err) == "error: unknown error"); +} diff --git a/docs/conf.py b/docs/conf.py index 7e000c6..7ce3b03 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -7,10 +7,12 @@ extensions = ['breathe'] subprocess.call('cmake .. -B ../build -D BUILD_DOCS=ON', shell=True) -subprocess.call('cmake --build ../build --target docs', shell=True) +subprocess.call('cmake --build ../build --target errors_docs --target errors_format_docs', shell=True) -breathe_projects = {"errors": "../build/docs"} -breathe_default_project = "errors" +breathe_projects = { + "errors": "../build/errors_docs", + "errors_format": "../build/components/format/errors_format_docs" +} html_theme = 'furo' html_static_path = ['_static'] diff --git a/docs/index.rst b/docs/index.rst index 011eb1c..353b6c0 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -9,8 +9,15 @@ API Docs -------- .. doxygenclass:: errors::Error + :project: errors :members: +Format Component +^^^^^^^^^^^^^^^^ + +.. doxygenfunction:: errors::format + :project: errors_format + License ------- diff --git a/include/errors/error.hpp b/include/errors/error.hpp index 2b54d1c..8c158a9 100644 --- a/include/errors/error.hpp +++ b/include/errors/error.hpp @@ -1,7 +1,5 @@ #pragma once -#include - #include #include #include @@ -33,9 +31,6 @@ class Error { friend Error make(const std::string& msg); - template - friend Error format(fmt::format_string fmt, T&&... args); - /** * @brief Writes the string representation of an error object to the given * output stream. @@ -64,23 +59,4 @@ class Error { */ Error make(const std::string& msg); -/** - * @brief Creates a new error object with a formatted message. - * @tparam T Variadic template parameter pack for format arguments. - * @param fmt A format string for the message. - * @param args Format arguments. - * @return A new error object. - */ -template -Error format(fmt::format_string fmt, T&&... args) { - return errors::make(fmt::format(fmt, std::forward(args)...)); -} - } // namespace error - -template <> -struct fmt::formatter { - format_parse_context::iterator parse(format_parse_context& ctx) const; - format_context::iterator format(const errors::Error& err, - format_context& ctx) const; -}; diff --git a/src/error.cpp b/src/error.cpp index 02c0a5e..6eb82c7 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -18,17 +18,3 @@ Error make(const std::string& msg) { } } // namespace error - -namespace fmt { - -format_parse_context::iterator formatter::parse( - format_parse_context& ctx) const { - return ctx.begin(); -} - -format_context::iterator formatter::format( - const errors::Error& err, format_context& ctx) const { - return format_to(ctx.out(), "error: {}", err.message()); -} - -} // namespace fmt diff --git a/test/error_test.cpp b/test/error_test.cpp index 589e8ba..c83e8f5 100644 --- a/test/error_test.cpp +++ b/test/error_test.cpp @@ -1,29 +1,14 @@ -#include - #include #include #include -#include TEST_CASE("Error Construction") { const errors::Error err = errors::make("unknown error"); REQUIRE(err.message() == "unknown error"); } -TEST_CASE("Error Construction With Formatting") { - const errors::Error err = errors::format("HTTP error {}", 404); - REQUIRE(err.message() == "HTTP error 404"); -} - -TEST_CASE("Error Printing") { +TEST_CASE("Error Printing Using OStream") { const auto err = errors::make("unknown error"); - - SECTION("Using ostream") { - const auto ss = std::stringstream() << err; - REQUIRE(ss.str() == "error: unknown error"); - } - - SECTION("Using fmtlib") { - REQUIRE(fmt::format("{}", err) == "error: unknown error"); - } + const auto ss = std::stringstream() << err; + REQUIRE(ss.str() == "error: unknown error"); }