From 5a95e7bde3970c7fbc8125d30f111488a3f01ccb Mon Sep 17 00:00:00 2001 From: Vladimir Voitenko Date: Wed, 27 Aug 2025 17:17:03 +0500 Subject: [PATCH] remove-fmt-dependency --- .gitignore | 1 + .gitmodules | 3 -- CMakeLists.txt | 5 +-- .../libenvpp_custom_environment_example.cpp | 5 ++- examples/libenvpp_testing_example.cpp | 7 +++- external/fmt | 1 - include/libenvpp/detail/check.hpp | 8 ++-- include/libenvpp/detail/parser.hpp | 33 ++++++++------- include/libenvpp/env.hpp | 40 +++++++++++-------- source/libenvpp_errors.cpp | 12 ++++-- source/libenvpp_testing.cpp | 8 ++-- test/libenvpp_parser_test.cpp | 13 +++--- test/libenvpp_test.cpp | 4 +- test/libenvpp_testing_test.cpp | 17 ++++---- 14 files changed, 86 insertions(+), 71 deletions(-) delete mode 160000 external/fmt diff --git a/.gitignore b/.gitignore index c0be205..f4d0426 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /build* .vscode .cache +CMakeUserPresets.json diff --git a/.gitmodules b/.gitmodules index 1d734de..800345d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "external/fmt"] - path = external/fmt - url = https://github.com/fmtlib/fmt.git [submodule "external/Catch2"] path = external/Catch2 url = https://github.com/catchorg/Catch2.git diff --git a/CMakeLists.txt b/CMakeLists.txt index e1be3db..08b2bc4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,7 +33,7 @@ include(cmake/libenvpp_mt_utils.cmake) function(libenvpp_set_compiler_parameters TARGET) set_target_properties(${TARGET} PROPERTIES VERSION ${LIBENVPP_VERSION} - CXX_STANDARD 17 + CXX_STANDARD 23 CXX_EXTENSIONS OFF ) target_compile_options(${TARGET} PRIVATE @@ -77,8 +77,6 @@ macro(fetch_content_from_submodule DEPNAME RELPATH) endif() endmacro() -fetch_content_from_submodule(fmt external/fmt) - if(LIBENVPP_TESTS) fetch_content_from_submodule(Catch2 external/Catch2) list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras) @@ -101,7 +99,6 @@ add_library(libenvpp STATIC ${LIBENVPP_SOURCES} ${LIBENVPP_INCLUDES}) add_library(libenvpp::libenvpp ALIAS libenvpp) libenvpp_set_compiler_parameters(libenvpp) set_target_properties(libenvpp PROPERTIES PREFIX "") -target_link_libraries(libenvpp PUBLIC fmt::fmt) target_include_directories(libenvpp PUBLIC $ $ diff --git a/examples/libenvpp_custom_environment_example.cpp b/examples/libenvpp_custom_environment_example.cpp index f274e67..bbab749 100644 --- a/examples/libenvpp_custom_environment_example.cpp +++ b/examples/libenvpp_custom_environment_example.cpp @@ -1,6 +1,7 @@ -#include #include #include +#include +#include #include @@ -29,5 +30,5 @@ int main() std::cout << parsed_and_validated_pre.error_message(); } - return EXIT_SUCCESS; + return 0; } diff --git a/examples/libenvpp_testing_example.cpp b/examples/libenvpp_testing_example.cpp index 7ef434f..bd9fcb1 100644 --- a/examples/libenvpp_testing_example.cpp +++ b/examples/libenvpp_testing_example.cpp @@ -1,12 +1,15 @@ +#include "libenvpp/detail/testing.hpp" +#include "libenvpp/env.hpp" #include #include #include -#include +#include +#include int main() { - const auto _ = env::scoped_test_environment({ + const auto _ = env::scoped_test_environment(std::unordered_map{ {"MYPROG_LOG_FILE_PATH", "/dev/null"}, {"MYPROG_NUM_THREADS", "8"}, }); diff --git a/external/fmt b/external/fmt deleted file mode 160000 index 0c9fce2..0000000 --- a/external/fmt +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0c9fce2ffefecfdce794e1859584e25877b7b592 diff --git a/include/libenvpp/detail/check.hpp b/include/libenvpp/detail/check.hpp index 5e3fb60..7659153 100644 --- a/include/libenvpp/detail/check.hpp +++ b/include/libenvpp/detail/check.hpp @@ -3,8 +3,8 @@ #include #include #include - -#include +#include +#include #ifndef LIBENVPP_STRINGIFY #define LIBENVPP_STRINGIFY_HELPER(expression) #expression @@ -22,7 +22,7 @@ class check_failed : public std::runtime_error { #define LIBENVPP_CHECK(condition) \ do { \ if (!(condition)) { \ - ::fmt::print(stderr, "{}:{}: {}(): 'LIBENVPP_CHECK(" LIBENVPP_STRINGIFY(condition) ")' failed.", __FILE__, \ + ::std::print(stderr, "{}:{}: {}(): 'LIBENVPP_CHECK(" LIBENVPP_STRINGIFY(condition) ")' failed.", __FILE__, \ __LINE__, __func__); \ ::std::abort(); \ } \ @@ -32,7 +32,7 @@ class check_failed : public std::runtime_error { do { \ if (!(condition)) { \ throw ::env::detail::check_failed{ \ - ::fmt::format("{}:{}: {}(): 'LIBENVPP_CHECK(" LIBENVPP_STRINGIFY(condition) ")' failed.", __FILE__, \ + ::std::format("{}:{}: {}(): 'LIBENVPP_CHECK(" LIBENVPP_STRINGIFY(condition) ")' failed.", __FILE__, \ __LINE__, __func__)}; \ } \ } while (false) diff --git a/include/libenvpp/detail/parser.hpp b/include/libenvpp/detail/parser.hpp index ad3784c..2684080 100644 --- a/include/libenvpp/detail/parser.hpp +++ b/include/libenvpp/detail/parser.hpp @@ -4,14 +4,13 @@ #include #include #include +#include #include #include #include #include #include -#include - #include #include #include @@ -79,7 +78,7 @@ inline constexpr auto is_stringstream_constructible_v = is_stringstream_construc || equal_case_insensitive(str, "no")) { return false; } else { - throw parser_error{fmt::format("Failed to parse '{}' as boolean", str)}; + throw parser_error{std::format("Failed to parse '{}' as boolean", str)}; } } @@ -92,9 +91,9 @@ template } catch (const parser_error&) { throw; } catch (const std::exception& e) { - throw parser_error{fmt::format("String constructor failed for input '{}' with '{}'", str, e.what())}; + throw parser_error{std::format("String constructor failed for input '{}' with '{}'", str, e.what())}; } catch (...) { - throw parser_error{fmt::format("String constructor failed for input '{}' with unknown error", str)}; + throw parser_error{std::format("String constructor failed for input '{}' with unknown error", str)}; } } else if constexpr (is_stringstream_constructible_v) { auto stream = std::istringstream(std::string(str)); @@ -117,15 +116,15 @@ template } catch (const parser_error&) { throw; } catch (const std::exception& e) { - throw parser_error{fmt::format("Stream operator>> failed for input '{}' with '{}'", str, e.what())}; + throw parser_error{std::format("Stream operator>> failed for input '{}' with '{}'", str, e.what())}; } catch (...) { - throw parser_error{fmt::format("Stream operator>> failed for input '{}' with unknown error", str)}; + throw parser_error{std::format("Stream operator>> failed for input '{}' with unknown error", str)}; } if (stream.fail()) { - throw parser_error{fmt::format("Stream operator>> failed for input '{}'", str)}; + throw parser_error{std::format("Stream operator>> failed for input '{}'", str)}; } if (static_cast(stream.tellg()) < str.size()) { - throw parser_error{fmt::format("Input '{}' was only parsed partially with remaining data '{}'", str, + throw parser_error{std::format("Input '{}' was only parsed partially with remaining data '{}'", str, stream.str().substr(stream.tellg()))}; } if constexpr (!std::is_same_v && std::is_unsigned_v) { @@ -137,10 +136,10 @@ template } if (signed_stream.fail() || static_cast(signed_stream.tellg()) < str.size()) { throw parser_error{ - fmt::format("Failed to validate whether '{}' was correctly parsed as '{}'", str, parsed)}; + std::format("Failed to validate whether '{}' was correctly parsed as '{}'", str, parsed)}; } if (signed_parsed < 0) { - throw parser_error{fmt::format("Cannot parse negative number '{}' as unsigned type", str)}; + throw parser_error{std::format("Cannot parse negative number '{}' as unsigned type", str)}; } } return parsed; @@ -187,19 +186,19 @@ template try { return expected_t{parser_and_validator(env_var_value)}; } catch (const parser_error& e) { - error_msg = fmt::format("Parser error for environment variable '{}': {}", env_var_name, e.what()); + error_msg = std::format("Parser error for environment variable '{}': {}", env_var_name, e.what()); } catch (const validation_error& e) { - error_msg = fmt::format("Validation error for environment variable '{}': {}", env_var_name, e.what()); + error_msg = std::format("Validation error for environment variable '{}': {}", env_var_name, e.what()); } catch (const range_error& e) { - error_msg = fmt::format("Range error for environment variable '{}': {}", env_var_name, e.what()); + error_msg = std::format("Range error for environment variable '{}': {}", env_var_name, e.what()); } catch (const option_error& e) { - error_msg = fmt::format("Option error for environment variable '{}': {}", env_var_name, e.what()); + error_msg = std::format("Option error for environment variable '{}': {}", env_var_name, e.what()); } catch (const std::exception& e) { error_msg = - fmt::format("Failed to parse or validate environment variable '{}' with: {}", env_var_name, e.what()); + std::format("Failed to parse or validate environment variable '{}' with: {}", env_var_name, e.what()); } catch (...) { error_msg = - fmt::format("Failed to parse or validate environment variable '{}' with unknown error", env_var_name); + std::format("Failed to parse or validate environment variable '{}' with unknown error", env_var_name); } return expected_t{unexpected_t{error_msg}}; } diff --git a/include/libenvpp/env.hpp b/include/libenvpp/env.hpp index e5a72dc..8203906 100644 --- a/include/libenvpp/env.hpp +++ b/include/libenvpp/env.hpp @@ -2,9 +2,13 @@ #include #include +#include #include +#include #include #include +#include +#include #include #include #include @@ -13,9 +17,6 @@ #include #include -#include -#include - #include #include #include @@ -129,7 +130,7 @@ class parsed_and_validated_prefix { const auto& value = m_prefix.m_registered_vars[var_id.m_idx].m_value; if constexpr (IsRequired) { if (!value.has_value()) { - throw value_error{fmt::format("Variable '{}' does not hold a value", + throw value_error{std::format("Variable '{}' does not hold a value", m_prefix.m_registered_vars[var_id.m_idx].m_name)}; } return std::any_cast(value); @@ -242,7 +243,7 @@ class parsed_and_validated_prefix { for (auto&& unused_var : find_unused_env_vars(environment)) { m_warnings.emplace_back(-1, unused_var, - fmt::format("Prefix environment variable '{}' specified but unused", unused_var)); + std::format("Prefix environment variable '{}' specified but unused", unused_var)); } } @@ -254,7 +255,7 @@ class parsed_and_validated_prefix { } auto msg = std::string(); for (std::size_t i = 0; i < errors_or_warnings.size(); ++i) { - msg += fmt::format("{:<7}: {}\n", message_type, errors_or_warnings[i].what()); + msg += std::format("{:<7}: {}\n", message_type, errors_or_warnings[i].what()); } return msg; } @@ -389,14 +390,14 @@ class prefix { throw_if_invalid(); if (m_registered_vars.empty()) { - return fmt::format("There are no supported environment variables for the prefix '{}'\n", m_prefix_name); + return std::format("There are no supported environment variables for the prefix '{}'\n", m_prefix_name); } - auto msg = fmt::format("Prefix '{}' supports the following {} environment variable(s):\n", m_prefix_name, + auto msg = std::format("Prefix '{}' supports the following {} environment variable(s):\n", m_prefix_name, m_registered_vars.size()); for (std::size_t i = 0; i < m_registered_vars.size(); ++i) { const auto& var = m_registered_vars[i]; const auto var_name = get_full_env_var_name(i); - msg += fmt::format("\t'{}' {}\n", var_name, var.m_is_required ? "required" : "optional"); + msg += std::format("\t'{}' {}\n", var_name, var.m_is_required ? "required" : "optional"); } return msg; } @@ -442,7 +443,7 @@ class prefix { [[nodiscard]] auto registration_range_helper(const std::string_view name, const T min, const T max) { if (min > max) { - throw invalid_range{fmt::format("Invalid range [{}, {}] for '{}', min must be less or equal to max", min, + throw invalid_range{std::format("Invalid range [{}, {}] for '{}', min must be less or equal to max", min, max, get_full_env_var_name(name))}; } @@ -450,7 +451,7 @@ class prefix { const auto value = default_parser{}(str); default_validator{}(value); if (value < min || value > max) { - throw range_error{fmt::format("Value {} outside of range [{}, {}]", value, min, max)}; + throw range_error{std::format("Value {} outside of range [{}, {}]", value, min, max)}; } return value; }; @@ -462,26 +463,31 @@ class prefix { const std::vector option_strings = {}) { if (options.size() == 0) { - throw empty_option{fmt::format("No options provided for '{}'", get_full_env_var_name(name))}; + throw empty_option{std::format("No options provided for '{}'", get_full_env_var_name(name))}; } const auto options_set = std::set(options.begin(), options.end()); if (options_set.size() != options.size()) { - throw duplicate_option{fmt::format("Duplicate option specified for '{}'", get_full_env_var_name(name))}; + throw duplicate_option{std::format("Duplicate option specified for '{}'", get_full_env_var_name(name))}; } const auto parser_and_validator = [options = std::move(options), strings = std::move(option_strings)](const std::string_view str) { const auto value = [&]() { if constexpr (SimpleParsing) { if (strings.size() != options.size()) { - throw option_error{fmt::format("Option strings must be provided for simple option parsing")}; + throw option_error{std::format("Option strings must be provided for simple option parsing")}; } const auto it = std::find(strings.begin(), strings.end(), str); if (it != strings.end()) { return options.at(std::distance(strings.begin(), it)); } else { - throw option_error{fmt::format("Unrecognized option '{}', should be one of [{}]", str, - fmt::join(strings, ", "))}; + assert(strings.size() != 0); + throw option_error{ + std::format("Unrecognized option '{}', should be one of [{}]", str, + std::accumulate(std::next(strings.begin()), strings.end(), strings[0], + [](const std::string& buffer, const std::string& element) { + return buffer + ", " + element; + }))}; } } else { return default_parser{}(str); @@ -489,7 +495,7 @@ class prefix { }(); default_validator{}(value); if (std::all_of(options.begin(), options.end(), [&value](const auto& option) { return option != value; })) { - throw option_error{fmt::format("Unrecognized option '{}'", str)}; + throw option_error{std::format("Unrecognized option '{}'", str)}; } return value; }; diff --git a/source/libenvpp_errors.cpp b/source/libenvpp_errors.cpp index ea509db..94a67b0 100644 --- a/source/libenvpp_errors.cpp +++ b/source/libenvpp_errors.cpp @@ -1,8 +1,12 @@ +#include +#include #include -#include - #include +#include +#include +#include +#include namespace env::detail { @@ -13,7 +17,7 @@ namespace env::detail { const auto similar_var = find_similar_env_var(env_var_name, environment, edit_dist_cutoff); if (similar_var.has_value()) { const auto msg = - fmt::format("Unrecognized environment variable '{}' set, did you mean '{}'?", *similar_var, env_var_name); + std::format("Unrecognized environment variable '{}' set, did you mean '{}'?", *similar_var, env_var_name); pop_from_environment(*similar_var, environment); return {error(id, env_var_name, msg)}; } @@ -22,7 +26,7 @@ namespace env::detail { [[nodiscard]] error get_unset_env_var_error(const std::size_t id, const std::string_view env_var_name) { - return error(id, env_var_name, fmt::format("Environment variable '{}' not set", env_var_name)); + return error(id, env_var_name, std::format("Environment variable '{}' not set", env_var_name)); } } // namespace env::detail diff --git a/source/libenvpp_testing.cpp b/source/libenvpp_testing.cpp index f37282e..f95be22 100644 --- a/source/libenvpp_testing.cpp +++ b/source/libenvpp_testing.cpp @@ -1,8 +1,10 @@ +#include #include -#include - #include +#include +#include +#include namespace env { @@ -28,7 +30,7 @@ scoped_test_environment::scoped_test_environment(const std::unordered_mapsecond, name, value)}; } diff --git a/test/libenvpp_parser_test.cpp b/test/libenvpp_parser_test.cpp index 4c61425..ab29222 100644 --- a/test/libenvpp_parser_test.cpp +++ b/test/libenvpp_parser_test.cpp @@ -1,13 +1,16 @@ +#include +#include #include +#include #include #include -#include #include +#include #include -#include #include +#include namespace env::detail { @@ -239,7 +242,7 @@ TEST_CASE("Parsing well-formed input of built-in type", "[libenvpp_parser]") test_parser("", ""); } - SECTION("Class types"){} + SECTION("Class types") {} { test_parser("foo", "foo"); test_parser("BAR", "BAR"); @@ -343,7 +346,7 @@ struct not_string_constructible_1 { struct not_string_constructible_2 { not_string_constructible_2(const std::string_view str) { - throw parser_error{fmt::format("Failed to construct '{}'", str)}; + throw parser_error{std::format("Failed to construct '{}'", str)}; } }; @@ -369,7 +372,7 @@ std::istringstream& operator>>(std::istringstream&, not_stream_constructible_2&) struct not_stream_constructible_3 {}; std::istringstream& operator>>(std::istringstream& stream, not_stream_constructible_3&) { - throw parser_error{fmt::format("Failed to construct '{}'", stream.str())}; + throw parser_error{std::format("Failed to construct '{}'", stream.str())}; } TEST_CASE("Parsing ill-formed input of user-defined type", "[libenvpp_parser]") diff --git a/test/libenvpp_test.cpp b/test/libenvpp_test.cpp index a3e56be..82c0dbf 100644 --- a/test/libenvpp_test.cpp +++ b/test/libenvpp_test.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -5,7 +6,6 @@ #include #include #include -#include #include #include @@ -59,7 +59,7 @@ struct default_parser { } else if (str == "THIRD_OPTION") { value = testing_option::THIRD_OPTION; } else { - throw parser_error{fmt::format("Cannot construct testing_option from '{}'", str)}; + throw parser_error{std::format("Cannot construct testing_option from '{}'", str)}; } return value; } diff --git a/test/libenvpp_testing_test.cpp b/test/libenvpp_testing_test.cpp index bfad658..23b044c 100644 --- a/test/libenvpp_testing_test.cpp +++ b/test/libenvpp_testing_test.cpp @@ -1,13 +1,16 @@ -#include #include #include #include #include +#include #include -#include +#include #include +#include +#include +#include #include namespace env { @@ -74,12 +77,12 @@ TEST_CASE("Retrieving integer with get_or from testing environment", "[libenvpp_ TEST_CASE("Multiple scoped test environments", "[libenvpp_testing]") { - const auto scoped_env1 = env::scoped_test_environment({ + const auto scoped_env1 = env::scoped_test_environment(std::unordered_map{ {"LIBENVPP_TESTING1_INT", "42"}, {"LIBENVPP_TESTING1_FLOAT", "3.1415"}, }); - const auto scoped_env2 = env::scoped_test_environment({ + const auto scoped_env2 = env::scoped_test_environment(std::unordered_map{ {"LIBENVPP_TESTING2_INT", "24"}, {"LIBENVPP_TESTING2_FLOAT", "6.28318"}, }); @@ -115,7 +118,7 @@ TEST_CASE("Multiple scoped test environments", "[libenvpp_testing]") TEST_CASE("Duplicate test environment entries are detected", "[libenvpp_testing]") { - const auto scoped_env1 = env::scoped_test_environment({ + const auto scoped_env1 = env::scoped_test_environment(std::unordered_map{ {"LIBENVPP_TESTING_INT", "42"}, {"LIBENVPP_TESTING_FLOAT", "3.1415"}, }); @@ -157,7 +160,7 @@ TEST_CASE("Global testing environment is modified correctly", "[libenvpp_testing }; { - const auto scoped_env1 = env::scoped_test_environment({ + const auto scoped_env1 = env::scoped_test_environment(std::unordered_map{ {"LIBENVPP_TESTING1_INT", "42"}, {"LIBENVPP_TESTING1_FLOAT", "3.1415"}, }); @@ -167,7 +170,7 @@ TEST_CASE("Global testing environment is modified correctly", "[libenvpp_testing check_env2(false); { - const auto scoped_env2 = env::scoped_test_environment({ + const auto scoped_env2 = env::scoped_test_environment(std::unordered_map{ {"LIBENVPP_TESTING2_INT", "24"}, {"LIBENVPP_TESTING2_FLOAT", "6.28318"}, });