From eb73a6e18961a83eb1c920d2043a0cf774d44fde Mon Sep 17 00:00:00 2001 From: Brett Date: Tue, 28 Jan 2025 00:00:47 -0500 Subject: [PATCH 001/101] revert change that breaks graphics lib --- CMakeLists.txt | 2 +- include/blt/std/memory.h | 12 +++++------- libraries/parallel-hashmap | 2 +- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d41de9e..d294dee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 3.0.6) +set(BLT_VERSION 3.0.7) set(BLT_TARGET BLT) diff --git a/include/blt/std/memory.h b/include/blt/std/memory.h index 597f31c..47ffd84 100644 --- a/include/blt/std/memory.h +++ b/include/blt/std/memory.h @@ -33,13 +33,11 @@ namespace blt { public: using element_type = T; - using value_type = typename std::iterator_traits::value_type; - using pointer = typename std::iterator_traits::pointer; - using const_pointer = const typename std::iterator_traits::pointer; - using reference = typename std::iterator_traits::reference; - using const_reference = const typename std::iterator_traits::reference; - using difference_type = typename std::iterator_traits::difference_type; - using iterator_category = typename std::iterator_traits::iterator_category; + using value_type = std::remove_cv_t; + using pointer = T*; + using const_pointer = const T*; + using reference = T&; + using const_reference = const T&; using iterator = ptr_iterator; using const_iterator = ptr_iterator; diff --git a/libraries/parallel-hashmap b/libraries/parallel-hashmap index d88c5e1..2ec7990 160000 --- a/libraries/parallel-hashmap +++ b/libraries/parallel-hashmap @@ -1 +1 @@ -Subproject commit d88c5e15079047777b418132ece5879e7c9aaa2b +Subproject commit 2ec799017610ef831f4dc29c21fb3cce7e4a19b9 From 02b8f54c7e65cd7d1fc1785b52d3fb400ac13a59 Mon Sep 17 00:00:00 2001 From: Brett Date: Tue, 28 Jan 2025 00:35:08 -0500 Subject: [PATCH 002/101] expected should inherit from itself when not copy constructable --- CMakeLists.txt | 2 +- include/blt/std/expected.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d294dee..f8afa32 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 3.0.7) +set(BLT_VERSION 3.0.8) set(BLT_TARGET BLT) diff --git a/include/blt/std/expected.h b/include/blt/std/expected.h index f45dcb5..d444fa2 100644 --- a/include/blt/std/expected.h +++ b/include/blt/std/expected.h @@ -375,12 +375,12 @@ namespace blt }; template - class expected + class expected : public expected { public: using expected::expected; - constexpr expected(const expected& copy) = delete; + constexpr expected(const expected& copy) = delete; expected& operator=(const expected& copy) = delete; }; From 3726f6840f6b8c59af33bc0e9e597c36289f45c6 Mon Sep 17 00:00:00 2001 From: Brett Date: Wed, 12 Feb 2025 02:54:22 -0500 Subject: [PATCH 003/101] starting arge parse --- CMakeLists.txt | 6 +- include/blt/parse/argparse_v2.h | 177 ++++++++++++++++++++++++++++++++ src/blt/parse/argparse_v2.cpp | 149 +++++++++++++++++++++++++++ tests/argparse_tests.cpp | 23 +++++ 4 files changed, 353 insertions(+), 2 deletions(-) create mode 100644 include/blt/parse/argparse_v2.h create mode 100644 src/blt/parse/argparse_v2.cpp create mode 100644 tests/argparse_tests.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f8afa32..f49d90d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -148,8 +148,9 @@ install(TARGETS ${BLT_TARGET} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) -macro(blt_add_project name source type) +macro(blt_add_test name source type) + message("Adding project ${name} of type ${type}" DEBUG) project(${name}-${type}) add_executable(${name}-${type} ${source}) @@ -194,7 +195,8 @@ endmacro() if (${BUILD_TESTS}) message("Building tests for version ${BLT_VERSION}") - blt_add_project(blt-iterator tests/iterator_tests.cpp test) + blt_add_test(blt_iterator tests/iterator_tests.cpp test) + blt_add_test(blt_argparse tests/argparse_tests.cpp test) message("Built tests") endif () diff --git a/include/blt/parse/argparse_v2.h b/include/blt/parse/argparse_v2.h new file mode 100644 index 0000000..82ff1f0 --- /dev/null +++ b/include/blt/parse/argparse_v2.h @@ -0,0 +1,177 @@ +#pragma once +/* + * Copyright (C) 2024 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BLT_PARSE_ARGPARSE_V2_H +#define BLT_PARSE_ARGPARSE_V2_H + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace blt::argparse +{ + namespace detail + { + inline hashset_t allowed_flag_prefixes = {"-", "--", "+"}; + + std::string flag_prefixes_as_string(); + + inline std::string flag_prefix_list_string = flag_prefixes_as_string(); + + class bad_flag final : public std::runtime_error + { + public: + explicit bad_flag(const std::string& message): std::runtime_error(message) + { + } + + explicit bad_flag(const char* message): std::runtime_error(message) + { + } + }; + + template + struct arg_data_helper_t + { + using arg_primitive_data_t = std::variant; + using arg_list_data_t = std::variant...>; + }; + + using data_helper_t = arg_data_helper_t; + + using arg_primitive_data_t = data_helper_t::arg_primitive_data_t; + using arg_list_data_t = data_helper_t::arg_list_data_t; + using arg_data_t = std::variant; + + template + class arg_type_t; + + void test(); + } + + class argument_string_t + { + public: + explicit argument_string_t(const char* input): m_argument(input) + { + if (input == nullptr) + throw detail::bad_flag("Argument cannot be null!"); + if (m_argument.size() == 1) + throw detail::bad_flag("Argument cannot be a single character!"); + } + + [[nodiscard]] std::string_view get_flag() const + { + if (!flag_section) + process_argument(); + return *flag_section; + } + + [[nodiscard]] std::string_view get_name() const + { + if (!name_section) + process_argument(); + return *name_section; + } + + [[nodiscard]] std::string_view value() const + { + return get_name(); + } + + [[nodiscard]] bool is_flag() const + { + if (!m_is_flag) + process_argument(); + return *m_is_flag; + } + + [[nodiscard]] std::string_view get_argument() const + { + return m_argument; + } + + private: + void process_argument() const + { + size_t start = 0; + for (start = 0; start < m_argument.size(); ++start) + { + if (std::isalnum(m_argument[start])) + break; + } + m_is_flag = (start != 0); + flag_section = {m_argument.data(), start}; + name_section = {m_argument.data() + start, m_argument.size() - start}; + + if (!flag_section->empty() && !detail::allowed_flag_prefixes.contains(*flag_section)) + throw detail::bad_flag( + "Invalid flag detected, flag is not in allowed list of flags! Must be one of " + detail::flag_prefix_list_string); + } + + std::string_view m_argument; + mutable std::optional m_is_flag; + mutable std::optional flag_section; + mutable std::optional name_section; + }; + + class argument_consumer_t + { + public: + argument_consumer_t(const i32 argc, const char** argv): argv(argv), argc(argc) + { + } + + [[nodiscard]] std::string_view peek(const i32 offset = 0) const + { + return argv[forward_index + offset]; + } + + std::string_view consume() + { + return argv[forward_index++]; + } + + [[nodiscard]] i32 position() const + { + return argc; + } + + [[nodiscard]] i32 remaining() const + { + return argc - forward_index; + } + + [[nodiscard]] bool has_next(const i32 offset = 0) const + { + return (offset + forward_index) < argc; + } + + private: + const char** argv; + i32 argc; + i32 forward_index = 0; + }; +} + +#endif //BLT_PARSE_ARGPARSE_V2_H diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp new file mode 100644 index 0000000..fcb89a0 --- /dev/null +++ b/src/blt/parse/argparse_v2.cpp @@ -0,0 +1,149 @@ +/* + * + * Copyright (C) 2025 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include + +namespace blt::argparse +{ + namespace detail + { + std::string flag_prefixes_as_string() + { + std::string result; + for (auto [i, v] : enumerate(allowed_flag_prefixes)) + { + result += '\''; + result += v; + result += '\''; + if (i != allowed_flag_prefixes.size() - 2) + result += ", "; + else if (i != allowed_flag_prefixes.size() - 1) + { + if (allowed_flag_prefixes.size() > 2) + result += ','; + result += " or "; + } + } + return result; + } + } + + + namespace detail + { + // Unit Tests for class argument_string_t + // Test Case 1: Ensure the constructor handles flags correctly + void test_argument_string_t_flag_basic() + { + const argument_string_t arg("-f"); + BLT_ASSERT(arg.is_flag() && "Expected argument to be identified as a flag."); + BLT_ASSERT(arg.value() == "-f" && "Flag value should match the input string."); + } + + // Test Case 2: Ensure the constructor handles long flags correctly + void test_argument_string_t_long_flag() + { + const argument_string_t arg("--file"); + BLT_ASSERT(arg.is_flag() && "Expected argument to be identified as a flag."); + BLT_ASSERT(arg.value() == "--file" && "Long flag value should match the input string."); + } + + // Test Case 3: Ensure positional arguments are correctly identified + void test_argument_string_t_positional_argument() + { + const argument_string_t arg("filename.txt"); + BLT_ASSERT(!arg.is_flag() && "Expected argument to be identified as positional."); + BLT_ASSERT(arg.value() == "filename.txt" && "Positional argument value should match the input string."); + } + + // Test Case 4: Test for an edge case where the string starts like a flag but isn't + void test_argument_string_t_invalid_flag() + { + const argument_string_t arg("-notFlagBecauseItIsPositional"); + BLT_ASSERT(!arg.is_flag() && "Expected argument to be identified as positional."); + BLT_ASSERT(arg.value() == "-notFlagBecauseItIsPositional" + && "Argument value should match the input string."); + } + + // Test Case 5: Handle an empty string + void test_argument_string_t_empty_input() + { + const argument_string_t arg(""); + BLT_ASSERT(!arg.is_flag() && "Expected an empty input to be treated as positional, not a flag."); + BLT_ASSERT(arg.value().empty() && "Empty input should have an empty value."); + } + + // Test Case 6: Handle edge case of a single hyphen (`-`) which might be ambiguous + void test_argument_string_t_single_hyphen() + { + const argument_string_t arg("-"); + BLT_ASSERT(arg.is_flag() && "Expected single hyphen (`-`) to be treated as a flag."); + BLT_ASSERT(arg.value() == "-" && "Single hyphen flag should match the input string."); + } + + // Test Case 7: Handle arguments with mixed cases + void test_argument_string_t_mixed_case() + { + const argument_string_t arg("-FlagWithMixedCASE"); + BLT_ASSERT(arg.is_flag() && "Expected argument to be identified as a flag."); + BLT_ASSERT(arg.value() == "-FlagWithMixedCASE" && "Mixed case flag value should match the input string."); + } + + // Test Case 8: Handle arguments with prefix only (like "--") + void test_argument_string_t_double_hyphen() + { + const argument_string_t arg("--"); + BLT_ASSERT(arg.is_flag() && "Double hyphen ('--') should be treated as a flag."); + BLT_ASSERT(arg.value() == "--" && "Double hyphen value should match the input string."); + } + + // Test Case 9: Validate edge case of an argument with spaces + void test_argument_string_t_with_spaces() + { + const argument_string_t arg(" "); + BLT_ASSERT(!arg.is_flag() && "Arguments with spaces should not be treated as flags."); + BLT_ASSERT(arg.value() == " " && "Arguments with spaces should match the input string."); + } + + // Test Case 10: Validate arguments with numeric characters + void test_argument_string_t_numeric_flag() + { + const argument_string_t arg("-123"); + BLT_ASSERT(arg.is_flag() && "Numeric flags should still be treated as flags."); + BLT_ASSERT(arg.value() == "-123" && "Numeric flag value should match the input string."); + } + + void run_all_tests_argument_string_t() + { + test_argument_string_t_flag_basic(); + test_argument_string_t_long_flag(); + test_argument_string_t_positional_argument(); + test_argument_string_t_invalid_flag(); + test_argument_string_t_empty_input(); + test_argument_string_t_single_hyphen(); + test_argument_string_t_mixed_case(); + test_argument_string_t_double_hyphen(); + test_argument_string_t_with_spaces(); + test_argument_string_t_numeric_flag(); + } + void test() + { + run_all_tests_argument_string_t(); + } + } +} diff --git a/tests/argparse_tests.cpp b/tests/argparse_tests.cpp new file mode 100644 index 0000000..124bfc6 --- /dev/null +++ b/tests/argparse_tests.cpp @@ -0,0 +1,23 @@ +/* + * + * Copyright (C) 2025 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +int main(){ + blt::argparse::detail::test(); + return 0; +} \ No newline at end of file From a437935ab0b698d8b9299e87e261f67a1194e80f Mon Sep 17 00:00:00 2001 From: Brett Date: Wed, 12 Feb 2025 15:43:54 -0500 Subject: [PATCH 004/101] Argparse v2 breaking change --- CMakeLists.txt | 2 +- include/blt/parse/argparse_v2.h | 14 ++++++----- src/blt/parse/argparse_v2.cpp | 44 +++++++++++++-------------------- 3 files changed, 26 insertions(+), 34 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f49d90d..2d87cc7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 3.0.8) +set(BLT_VERSION 4.0.0) set(BLT_TARGET BLT) diff --git a/include/blt/parse/argparse_v2.h b/include/blt/parse/argparse_v2.h index 82ff1f0..1914870 100644 --- a/include/blt/parse/argparse_v2.h +++ b/include/blt/parse/argparse_v2.h @@ -76,8 +76,6 @@ namespace blt::argparse { if (input == nullptr) throw detail::bad_flag("Argument cannot be null!"); - if (m_argument.size() == 1) - throw detail::bad_flag("Argument cannot be a single character!"); } [[nodiscard]] std::string_view get_flag() const @@ -114,11 +112,14 @@ namespace blt::argparse private: void process_argument() const { - size_t start = 0; - for (start = 0; start < m_argument.size(); ++start) + size_t start = m_argument.size(); + for (auto [i, c] : enumerate(m_argument)) { - if (std::isalnum(m_argument[start])) + if (std::isalnum(c)) + { + start = i; break; + } } m_is_flag = (start != 0); flag_section = {m_argument.data(), start}; @@ -126,7 +127,8 @@ namespace blt::argparse if (!flag_section->empty() && !detail::allowed_flag_prefixes.contains(*flag_section)) throw detail::bad_flag( - "Invalid flag detected, flag is not in allowed list of flags! Must be one of " + detail::flag_prefix_list_string); + "Invalid flag " + std::string(*flag_section) + " detected, flag is not in allowed list of flags! Must be one of " + + detail::flag_prefix_list_string); } std::string_view m_argument; diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp index fcb89a0..2d9cc8d 100644 --- a/src/blt/parse/argparse_v2.cpp +++ b/src/blt/parse/argparse_v2.cpp @@ -52,7 +52,7 @@ namespace blt::argparse { const argument_string_t arg("-f"); BLT_ASSERT(arg.is_flag() && "Expected argument to be identified as a flag."); - BLT_ASSERT(arg.value() == "-f" && "Flag value should match the input string."); + BLT_ASSERT(arg.value() == "f" && "Flag value should match the input string."); } // Test Case 2: Ensure the constructor handles long flags correctly @@ -60,7 +60,7 @@ namespace blt::argparse { const argument_string_t arg("--file"); BLT_ASSERT(arg.is_flag() && "Expected argument to be identified as a flag."); - BLT_ASSERT(arg.value() == "--file" && "Long flag value should match the input string."); + BLT_ASSERT(arg.value() == "file" && "Long flag value should match the input string."); } // Test Case 3: Ensure positional arguments are correctly identified @@ -71,15 +71,6 @@ namespace blt::argparse BLT_ASSERT(arg.value() == "filename.txt" && "Positional argument value should match the input string."); } - // Test Case 4: Test for an edge case where the string starts like a flag but isn't - void test_argument_string_t_invalid_flag() - { - const argument_string_t arg("-notFlagBecauseItIsPositional"); - BLT_ASSERT(!arg.is_flag() && "Expected argument to be identified as positional."); - BLT_ASSERT(arg.value() == "-notFlagBecauseItIsPositional" - && "Argument value should match the input string."); - } - // Test Case 5: Handle an empty string void test_argument_string_t_empty_input() { @@ -93,15 +84,8 @@ namespace blt::argparse { const argument_string_t arg("-"); BLT_ASSERT(arg.is_flag() && "Expected single hyphen (`-`) to be treated as a flag."); - BLT_ASSERT(arg.value() == "-" && "Single hyphen flag should match the input string."); - } - - // Test Case 7: Handle arguments with mixed cases - void test_argument_string_t_mixed_case() - { - const argument_string_t arg("-FlagWithMixedCASE"); - BLT_ASSERT(arg.is_flag() && "Expected argument to be identified as a flag."); - BLT_ASSERT(arg.value() == "-FlagWithMixedCASE" && "Mixed case flag value should match the input string."); + BLT_ASSERT(arg.value().empty() && "Single hyphen flag should have empty value."); + BLT_ASSERT(arg.get_flag() == "-" && "Single hyphen flag should match the input string."); } // Test Case 8: Handle arguments with prefix only (like "--") @@ -109,15 +93,23 @@ namespace blt::argparse { const argument_string_t arg("--"); BLT_ASSERT(arg.is_flag() && "Double hyphen ('--') should be treated as a flag."); - BLT_ASSERT(arg.value() == "--" && "Double hyphen value should match the input string."); + BLT_ASSERT(arg.value().empty() && "Double hyphen flag should have empty value."); + BLT_ASSERT(arg.get_flag() == "--" && "Double hyphen value should match the input string."); } // Test Case 9: Validate edge case of an argument with spaces void test_argument_string_t_with_spaces() { - const argument_string_t arg(" "); - BLT_ASSERT(!arg.is_flag() && "Arguments with spaces should not be treated as flags."); - BLT_ASSERT(arg.value() == " " && "Arguments with spaces should match the input string."); + try + { + const argument_string_t arg(" "); + BLT_ASSERT(!arg.is_flag() && "Arguments with spaces should not be treated as flags."); + BLT_ASSERT(arg.value() == " " && "Arguments with spaces should match the input string."); + } catch (bad_flag&) + { + return; + } + BLT_ASSERT(false && "Expected an exception to be thrown for arguments with spaces."); } // Test Case 10: Validate arguments with numeric characters @@ -125,7 +117,7 @@ namespace blt::argparse { const argument_string_t arg("-123"); BLT_ASSERT(arg.is_flag() && "Numeric flags should still be treated as flags."); - BLT_ASSERT(arg.value() == "-123" && "Numeric flag value should match the input string."); + BLT_ASSERT(arg.value() == "123" && "Numeric flag value should match the input string."); } void run_all_tests_argument_string_t() @@ -133,10 +125,8 @@ namespace blt::argparse test_argument_string_t_flag_basic(); test_argument_string_t_long_flag(); test_argument_string_t_positional_argument(); - test_argument_string_t_invalid_flag(); test_argument_string_t_empty_input(); test_argument_string_t_single_hyphen(); - test_argument_string_t_mixed_case(); test_argument_string_t_double_hyphen(); test_argument_string_t_with_spaces(); test_argument_string_t_numeric_flag(); From 457dd5203b3f440ba322d809a879164f13af4e13 Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Wed, 12 Feb 2025 19:42:50 -0500 Subject: [PATCH 005/101] hi --- CMakeLists.txt | 2 +- include/blt/parse/argparse_v2.h | 63 ++++++++++++++++++++++++++------- src/blt/parse/argparse_v2.cpp | 53 +++++++++++++++++++++------ 3 files changed, 95 insertions(+), 23 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2d87cc7..5863b5a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 4.0.0) +set(BLT_VERSION 4.0.1) set(BLT_TARGET BLT) diff --git a/include/blt/parse/argparse_v2.h b/include/blt/parse/argparse_v2.h index 1914870..d9bfa37 100644 --- a/include/blt/parse/argparse_v2.h +++ b/include/blt/parse/argparse_v2.h @@ -26,7 +26,10 @@ #include #include #include +#include #include +#include +#include namespace blt::argparse { @@ -35,8 +38,10 @@ namespace blt::argparse inline hashset_t allowed_flag_prefixes = {"-", "--", "+"}; std::string flag_prefixes_as_string(); + hashset_t prefix_characters(); inline std::string flag_prefix_list_string = flag_prefixes_as_string(); + inline auto prefix_characters_set = prefix_characters(); class bad_flag final : public std::runtime_error { @@ -64,7 +69,42 @@ namespace blt::argparse using arg_data_t = std::variant; template - class arg_type_t; + struct arg_type_t + { + static T convert(const std::string_view value) + { + const std::string temp{value}; + + if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) + { + return static_cast(std::stoi(temp)); + } + else if constexpr (std::is_same_v) + { + return static_cast(std::stoll(temp)); + } + else if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) + { + return static_cast(std::stoul(temp)); + } + else if constexpr (std::is_same_v) + { + return static_cast(std::stoull(temp)); + } + else if constexpr (std::is_same_v) + { + return std::stof(temp); + } + else if constexpr (std::is_same_v) + { + return std::stod(temp); + } else + { + static_assert(std::is_arithmetic_v, "Unsupported type for this specialization"); + } + BLT_UNREACHABLE; + } + }; void test(); } @@ -115,7 +155,7 @@ namespace blt::argparse size_t start = m_argument.size(); for (auto [i, c] : enumerate(m_argument)) { - if (std::isalnum(c)) + if (!detail::prefix_characters_set.contains(c)) { start = i; break; @@ -140,38 +180,37 @@ namespace blt::argparse class argument_consumer_t { public: - argument_consumer_t(const i32 argc, const char** argv): argv(argv), argc(argc) + explicit argument_consumer_t(const span& args): args(args) { } - [[nodiscard]] std::string_view peek(const i32 offset = 0) const + [[nodiscard]] argument_string_t peek(const i32 offset = 0) const { - return argv[forward_index + offset]; + return args[forward_index + offset]; } - std::string_view consume() + argument_string_t consume() { - return argv[forward_index++]; + return args[forward_index++]; } [[nodiscard]] i32 position() const { - return argc; + return forward_index; } [[nodiscard]] i32 remaining() const { - return argc - forward_index; + return static_cast(args.size()) - forward_index; } [[nodiscard]] bool has_next(const i32 offset = 0) const { - return (offset + forward_index) < argc; + return (offset + forward_index) < args.size(); } private: - const char** argv; - i32 argc; + span args; i32 forward_index = 0; }; } diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp index 2d9cc8d..09fcee3 100644 --- a/src/blt/parse/argparse_v2.cpp +++ b/src/blt/parse/argparse_v2.cpp @@ -41,6 +41,15 @@ namespace blt::argparse } return result; } + + hashset_t prefix_characters() + { + hashset_t result; + for (auto [i, v] : enumerate(allowed_flag_prefixes)) + for (auto c : v) + result.insert(c); + return result; + } } @@ -100,16 +109,9 @@ namespace blt::argparse // Test Case 9: Validate edge case of an argument with spaces void test_argument_string_t_with_spaces() { - try - { - const argument_string_t arg(" "); - BLT_ASSERT(!arg.is_flag() && "Arguments with spaces should not be treated as flags."); - BLT_ASSERT(arg.value() == " " && "Arguments with spaces should match the input string."); - } catch (bad_flag&) - { - return; - } - BLT_ASSERT(false && "Expected an exception to be thrown for arguments with spaces."); + const argument_string_t arg(" "); + BLT_ASSERT(!arg.is_flag() && "Arguments with spaces should not be treated as flags."); + BLT_ASSERT(arg.value() == " " && "Arguments with spaces should match the input string."); } // Test Case 10: Validate arguments with numeric characters @@ -120,6 +122,33 @@ namespace blt::argparse BLT_ASSERT(arg.value() == "123" && "Numeric flag value should match the input string."); } + + // Test Case 11: Ensure the constructor handles '+' flag correctly + void test_argument_string_t_plus_flag_basic() + { + const argument_string_t arg("+f"); + BLT_ASSERT(arg.is_flag() && "Expected argument to be identified as a flag."); + BLT_ASSERT(arg.value() == "f" && "Plus flag value should match the input string."); + } + + // Test Case 13: Handle edge case of a single plus (`+`) which might be ambiguous + void test_argument_string_t_single_plus() + { + const argument_string_t arg("+"); + BLT_ASSERT(arg.is_flag() && "Expected single plus (`+`) to be treated as a flag."); + BLT_ASSERT(arg.value().empty() && "Single plus flag should have empty value."); + BLT_ASSERT(arg.get_flag() == "+" && "Single plus flag should match the input string."); + } + + // Test Case 14: Handle arguments with prefix only (like '++') + void test_argument_string_t_double_plus() + { + const argument_string_t arg("++"); + BLT_ASSERT(arg.is_flag() && "Double plus ('++') should be treated as a flag."); + BLT_ASSERT(arg.value().empty() && "Double plus flag should have empty value."); + BLT_ASSERT(arg.get_flag() == "++" && "Double plus value should match the input string."); + } + void run_all_tests_argument_string_t() { test_argument_string_t_flag_basic(); @@ -130,7 +159,11 @@ namespace blt::argparse test_argument_string_t_double_hyphen(); test_argument_string_t_with_spaces(); test_argument_string_t_numeric_flag(); + test_argument_string_t_plus_flag_basic(); + test_argument_string_t_single_plus(); + test_argument_string_t_double_plus(); } + void test() { run_all_tests_argument_string_t(); From 174b46ae94514fc7f6f442af67a7c36bf89710b3 Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Wed, 12 Feb 2025 22:17:04 -0500 Subject: [PATCH 006/101] path_helper file provides base_name of a path. Argparse working on --- CMakeLists.txt | 2 +- include/blt/compatibility.h | 8 +++ include/blt/fs/path_helper.h | 33 +++++++++ include/blt/parse/argparse_v2.h | 116 ++++++++++++++++++++++---------- src/blt/fs/path_helper.cpp | 40 +++++++++++ 5 files changed, 161 insertions(+), 38 deletions(-) create mode 100644 include/blt/fs/path_helper.h create mode 100644 src/blt/fs/path_helper.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5863b5a..2176d00 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 4.0.1) +set(BLT_VERSION 4.0.2) set(BLT_TARGET BLT) diff --git a/include/blt/compatibility.h b/include/blt/compatibility.h index b10d4f6..a3dfc45 100644 --- a/include/blt/compatibility.h +++ b/include/blt/compatibility.h @@ -51,4 +51,12 @@ #error Filesystem ops not supported!\ #endif +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) + #define BLT_OS_WINDOWS +#elif defined(__linux__) || defined(__unix__) + #define BLT_OS_LINUX +#else + #define BLT_OS_UNKNOWN +#endif + #endif //BLT_COMPATIBILITY_H diff --git a/include/blt/fs/path_helper.h b/include/blt/fs/path_helper.h new file mode 100644 index 0000000..530eb1a --- /dev/null +++ b/include/blt/fs/path_helper.h @@ -0,0 +1,33 @@ +#pragma once +/* + * Copyright (C) 2024 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BLT_FS_PATH_HELPER_H +#define BLT_FS_PATH_HELPER_H + +#include +#include + +namespace blt::fs +{ + + std::string base_name(const std::string& str); + std::string_view base_name_sv(std::string_view str); + +} + +#endif //BLT_FS_PATH_HELPER_H diff --git a/include/blt/parse/argparse_v2.h b/include/blt/parse/argparse_v2.h index d9bfa37..d047e69 100644 --- a/include/blt/parse/argparse_v2.h +++ b/include/blt/parse/argparse_v2.h @@ -21,11 +21,13 @@ #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -33,6 +35,13 @@ namespace blt::argparse { + class argument_string_t; + class argument_consumer_t; + class argument_parser_t; + class argument_subparser_t; + class parsed_argset_t; + class argument_builder_t; + namespace detail { inline hashset_t allowed_flag_prefixes = {"-", "--", "+"}; @@ -73,34 +82,24 @@ namespace blt::argparse { static T convert(const std::string_view value) { + static_assert(std::is_arithmetic_v, "Type must be arithmetic!"); const std::string temp{value}; - if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) - { - return static_cast(std::stoi(temp)); - } - else if constexpr (std::is_same_v) - { - return static_cast(std::stoll(temp)); - } - else if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) - { - return static_cast(std::stoul(temp)); - } - else if constexpr (std::is_same_v) - { - return static_cast(std::stoull(temp)); - } - else if constexpr (std::is_same_v) + if constexpr (std::is_same_v) { return std::stof(temp); } else if constexpr (std::is_same_v) { return std::stod(temp); - } else + } + else if constexpr (std::is_unsigned_v) { - static_assert(std::is_arithmetic_v, "Unsupported type for this specialization"); + return static_cast(std::stoull(temp)); + } + else if constexpr (std::is_signed_v) + { + return static_cast(std::stoll(temp)); } BLT_UNREACHABLE; } @@ -120,16 +119,16 @@ namespace blt::argparse [[nodiscard]] std::string_view get_flag() const { - if (!flag_section) + if (!m_flag_section) process_argument(); - return *flag_section; + return *m_flag_section; } [[nodiscard]] std::string_view get_name() const { - if (!name_section) + if (!m_name_section) process_argument(); - return *name_section; + return *m_name_section; } [[nodiscard]] std::string_view value() const @@ -162,56 +161,99 @@ namespace blt::argparse } } m_is_flag = (start != 0); - flag_section = {m_argument.data(), start}; - name_section = {m_argument.data() + start, m_argument.size() - start}; + m_flag_section = {m_argument.data(), start}; + m_name_section = {m_argument.data() + start, m_argument.size() - start}; - if (!flag_section->empty() && !detail::allowed_flag_prefixes.contains(*flag_section)) + if (!m_flag_section->empty() && !detail::allowed_flag_prefixes.contains(*m_flag_section)) throw detail::bad_flag( - "Invalid flag " + std::string(*flag_section) + " detected, flag is not in allowed list of flags! Must be one of " + + "Invalid flag " + std::string(*m_flag_section) + " detected, flag is not in allowed list of flags! Must be one of " + detail::flag_prefix_list_string); } std::string_view m_argument; mutable std::optional m_is_flag; - mutable std::optional flag_section; - mutable std::optional name_section; + mutable std::optional m_flag_section; + mutable std::optional m_name_section; }; class argument_consumer_t { public: - explicit argument_consumer_t(const span& args): args(args) + explicit argument_consumer_t(const span& args): m_args(args) { } [[nodiscard]] argument_string_t peek(const i32 offset = 0) const { - return args[forward_index + offset]; + return m_args[m_forward_index + offset]; } argument_string_t consume() { - return args[forward_index++]; + return m_args[m_forward_index++]; } [[nodiscard]] i32 position() const { - return forward_index; + return m_forward_index; } [[nodiscard]] i32 remaining() const { - return static_cast(args.size()) - forward_index; + return static_cast(m_args.size()) - m_forward_index; } [[nodiscard]] bool has_next(const i32 offset = 0) const { - return (offset + forward_index) < args.size(); + return (offset + m_forward_index) < m_args.size(); } private: - span args; - i32 forward_index = 0; + span m_args; + i32 m_forward_index = 0; + }; + + class argument_builder_t + { + public: + + private: + }; + + class parsed_argset_t + { + public: + + private: + }; + + class argument_parser_t + { + public: + argument_parser_t(const std::optional name = {}, const std::optional usage = {}): + m_name(name.value_or(std::optional{})), m_usage(usage.value_or(std::optional{})) + { + } + + argument_parser_t& set_name(const std::string_view name) + { + m_name = name; + return *this; + } + + private: + std::optional m_name; + std::optional m_usage; + std::optional m_description; + std::optional m_epilogue; + }; + + class argument_subparser_t + { + public: + + private: + }; } diff --git a/src/blt/fs/path_helper.cpp b/src/blt/fs/path_helper.cpp new file mode 100644 index 0000000..71efd5c --- /dev/null +++ b/src/blt/fs/path_helper.cpp @@ -0,0 +1,40 @@ +/* + * + * Copyright (C) 2025 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include + +namespace blt::fs +{ +#ifdef BLT_WINDOWS + constexpr static char delim = '\\'; +#else + constexpr static char delim = '/'; +#endif + + std::string base_name(const std::string& str) + { + return std::string(base_name_sv(str)); + } + + std::string_view base_name_sv(const std::string_view str) + { + const auto parts = string::split_sv(str, delim); + return parts.back(); + } +} From 0b2dad0dda7e403c6892541f89154d30f19b1066 Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Wed, 12 Feb 2025 22:18:15 -0500 Subject: [PATCH 007/101] forgot about . in files --- CMakeLists.txt | 2 +- src/blt/fs/path_helper.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2176d00..c7db4fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 4.0.2) +set(BLT_VERSION 4.0.3) set(BLT_TARGET BLT) diff --git a/src/blt/fs/path_helper.cpp b/src/blt/fs/path_helper.cpp index 71efd5c..9f62c16 100644 --- a/src/blt/fs/path_helper.cpp +++ b/src/blt/fs/path_helper.cpp @@ -35,6 +35,7 @@ namespace blt::fs std::string_view base_name_sv(const std::string_view str) { const auto parts = string::split_sv(str, delim); - return parts.back(); + const auto file_parts = string::split_sv(parts.back(), '.'); + return file_parts.front(); } } From 44a57e5ec2708d6c0a509a24c641d7ade78273d4 Mon Sep 17 00:00:00 2001 From: Brett Date: Thu, 13 Feb 2025 01:53:21 -0500 Subject: [PATCH 008/101] i am tired --- CMakeLists.txt | 2 +- include/blt/parse/argparse_v2.h | 62 +++++++++------------ src/blt/parse/argparse_v2.cpp | 95 ++++++++++++--------------------- 3 files changed, 59 insertions(+), 100 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c7db4fe..26d0783 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 4.0.3) +set(BLT_VERSION 4.0.4) set(BLT_TARGET BLT) diff --git a/include/blt/parse/argparse_v2.h b/include/blt/parse/argparse_v2.h index d047e69..3ef50d8 100644 --- a/include/blt/parse/argparse_v2.h +++ b/include/blt/parse/argparse_v2.h @@ -44,14 +44,6 @@ namespace blt::argparse namespace detail { - inline hashset_t allowed_flag_prefixes = {"-", "--", "+"}; - - std::string flag_prefixes_as_string(); - hashset_t prefix_characters(); - - inline std::string flag_prefix_list_string = flag_prefixes_as_string(); - inline auto prefix_characters_set = prefix_characters(); - class bad_flag final : public std::runtime_error { public: @@ -111,24 +103,22 @@ namespace blt::argparse class argument_string_t { public: - explicit argument_string_t(const char* input): m_argument(input) + explicit argument_string_t(const char* input, const hashset_t& allowed_flag_prefix): m_argument(input), + allowed_flag_prefix(&allowed_flag_prefix) { if (input == nullptr) throw detail::bad_flag("Argument cannot be null!"); + process_argument(); } [[nodiscard]] std::string_view get_flag() const { - if (!m_flag_section) - process_argument(); - return *m_flag_section; + return m_flag_section; } [[nodiscard]] std::string_view get_name() const { - if (!m_name_section) - process_argument(); - return *m_name_section; + return m_name_section; } [[nodiscard]] std::string_view value() const @@ -138,9 +128,7 @@ namespace blt::argparse [[nodiscard]] bool is_flag() const { - if (!m_is_flag) - process_argument(); - return *m_is_flag; + return !m_flag_section.empty(); } [[nodiscard]] std::string_view get_argument() const @@ -149,31 +137,21 @@ namespace blt::argparse } private: - void process_argument() const + void process_argument() { - size_t start = m_argument.size(); - for (auto [i, c] : enumerate(m_argument)) + size_t start = 0; + for (; start < m_argument.size() && allowed_flag_prefix->contains(m_argument[start]); start++) { - if (!detail::prefix_characters_set.contains(c)) - { - start = i; - break; - } } - m_is_flag = (start != 0); + m_flag_section = {m_argument.data(), start}; m_name_section = {m_argument.data() + start, m_argument.size() - start}; - - if (!m_flag_section->empty() && !detail::allowed_flag_prefixes.contains(*m_flag_section)) - throw detail::bad_flag( - "Invalid flag " + std::string(*m_flag_section) + " detected, flag is not in allowed list of flags! Must be one of " + - detail::flag_prefix_list_string); } std::string_view m_argument; - mutable std::optional m_is_flag; - mutable std::optional m_flag_section; - mutable std::optional m_name_section; + std::string_view m_flag_section; + std::string_view m_name_section; + const hashset_t* allowed_flag_prefix; }; class argument_consumer_t @@ -230,8 +208,8 @@ namespace blt::argparse class argument_parser_t { public: - argument_parser_t(const std::optional name = {}, const std::optional usage = {}): - m_name(name.value_or(std::optional{})), m_usage(usage.value_or(std::optional{})) + explicit argument_parser_t(const std::optional name = {}, const std::optional usage = {}): + m_name(name), m_usage(usage) { } @@ -252,8 +230,16 @@ namespace blt::argparse { public: - private: + argument_parser_t add_subparser(const std::string_view dest) + { + } + + argument_parser_t add_subparser(std::string& dest) + { + + } + private: }; } diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp index 09fcee3..02cb76b 100644 --- a/src/blt/parse/argparse_v2.cpp +++ b/src/blt/parse/argparse_v2.cpp @@ -22,34 +22,7 @@ namespace blt::argparse { namespace detail { - std::string flag_prefixes_as_string() - { - std::string result; - for (auto [i, v] : enumerate(allowed_flag_prefixes)) - { - result += '\''; - result += v; - result += '\''; - if (i != allowed_flag_prefixes.size() - 2) - result += ", "; - else if (i != allowed_flag_prefixes.size() - 1) - { - if (allowed_flag_prefixes.size() > 2) - result += ','; - result += " or "; - } - } - return result; - } - hashset_t prefix_characters() - { - hashset_t result; - for (auto [i, v] : enumerate(allowed_flag_prefixes)) - for (auto c : v) - result.insert(c); - return result; - } } @@ -57,93 +30,92 @@ namespace blt::argparse { // Unit Tests for class argument_string_t // Test Case 1: Ensure the constructor handles flags correctly - void test_argument_string_t_flag_basic() + void test_argument_string_t_flag_basic(const hashset_t& prefixes) { - const argument_string_t arg("-f"); + const argument_string_t arg("-f", prefixes); BLT_ASSERT(arg.is_flag() && "Expected argument to be identified as a flag."); BLT_ASSERT(arg.value() == "f" && "Flag value should match the input string."); } // Test Case 2: Ensure the constructor handles long flags correctly - void test_argument_string_t_long_flag() + void test_argument_string_t_long_flag(const hashset_t& prefixes) { - const argument_string_t arg("--file"); + const argument_string_t arg("--file", prefixes); BLT_ASSERT(arg.is_flag() && "Expected argument to be identified as a flag."); BLT_ASSERT(arg.value() == "file" && "Long flag value should match the input string."); } // Test Case 3: Ensure positional arguments are correctly identified - void test_argument_string_t_positional_argument() + void test_argument_string_t_positional_argument(const hashset_t& prefixes) { - const argument_string_t arg("filename.txt"); + const argument_string_t arg("filename.txt", prefixes); BLT_ASSERT(!arg.is_flag() && "Expected argument to be identified as positional."); BLT_ASSERT(arg.value() == "filename.txt" && "Positional argument value should match the input string."); } // Test Case 5: Handle an empty string - void test_argument_string_t_empty_input() + void test_argument_string_t_empty_input(const hashset_t& prefixes) { - const argument_string_t arg(""); + const argument_string_t arg("", prefixes); BLT_ASSERT(!arg.is_flag() && "Expected an empty input to be treated as positional, not a flag."); BLT_ASSERT(arg.value().empty() && "Empty input should have an empty value."); } // Test Case 6: Handle edge case of a single hyphen (`-`) which might be ambiguous - void test_argument_string_t_single_hyphen() + void test_argument_string_t_single_hyphen(const hashset_t& prefixes) { - const argument_string_t arg("-"); + const argument_string_t arg("-", prefixes); BLT_ASSERT(arg.is_flag() && "Expected single hyphen (`-`) to be treated as a flag."); BLT_ASSERT(arg.value().empty() && "Single hyphen flag should have empty value."); BLT_ASSERT(arg.get_flag() == "-" && "Single hyphen flag should match the input string."); } // Test Case 8: Handle arguments with prefix only (like "--") - void test_argument_string_t_double_hyphen() + void test_argument_string_t_double_hyphen(const hashset_t& prefixes) { - const argument_string_t arg("--"); + const argument_string_t arg("--", prefixes); BLT_ASSERT(arg.is_flag() && "Double hyphen ('--') should be treated as a flag."); BLT_ASSERT(arg.value().empty() && "Double hyphen flag should have empty value."); BLT_ASSERT(arg.get_flag() == "--" && "Double hyphen value should match the input string."); } // Test Case 9: Validate edge case of an argument with spaces - void test_argument_string_t_with_spaces() + void test_argument_string_t_with_spaces(const hashset_t& prefixes) { - const argument_string_t arg(" "); + const argument_string_t arg(" ", prefixes); BLT_ASSERT(!arg.is_flag() && "Arguments with spaces should not be treated as flags."); BLT_ASSERT(arg.value() == " " && "Arguments with spaces should match the input string."); } // Test Case 10: Validate arguments with numeric characters - void test_argument_string_t_numeric_flag() + void test_argument_string_t_numeric_flag(const hashset_t& prefixes) { - const argument_string_t arg("-123"); + const argument_string_t arg("-123", prefixes); BLT_ASSERT(arg.is_flag() && "Numeric flags should still be treated as flags."); BLT_ASSERT(arg.value() == "123" && "Numeric flag value should match the input string."); } - // Test Case 11: Ensure the constructor handles '+' flag correctly - void test_argument_string_t_plus_flag_basic() + void test_argument_string_t_plus_flag_basic(const hashset_t& prefixes) { - const argument_string_t arg("+f"); + const argument_string_t arg("+f", prefixes); BLT_ASSERT(arg.is_flag() && "Expected argument to be identified as a flag."); BLT_ASSERT(arg.value() == "f" && "Plus flag value should match the input string."); } // Test Case 13: Handle edge case of a single plus (`+`) which might be ambiguous - void test_argument_string_t_single_plus() + void test_argument_string_t_single_plus(const hashset_t& prefixes) { - const argument_string_t arg("+"); + const argument_string_t arg("+", prefixes); BLT_ASSERT(arg.is_flag() && "Expected single plus (`+`) to be treated as a flag."); BLT_ASSERT(arg.value().empty() && "Single plus flag should have empty value."); BLT_ASSERT(arg.get_flag() == "+" && "Single plus flag should match the input string."); } // Test Case 14: Handle arguments with prefix only (like '++') - void test_argument_string_t_double_plus() + void test_argument_string_t_double_plus(const hashset_t& prefixes) { - const argument_string_t arg("++"); + const argument_string_t arg("++", prefixes); BLT_ASSERT(arg.is_flag() && "Double plus ('++') should be treated as a flag."); BLT_ASSERT(arg.value().empty() && "Double plus flag should have empty value."); BLT_ASSERT(arg.get_flag() == "++" && "Double plus value should match the input string."); @@ -151,17 +123,18 @@ namespace blt::argparse void run_all_tests_argument_string_t() { - test_argument_string_t_flag_basic(); - test_argument_string_t_long_flag(); - test_argument_string_t_positional_argument(); - test_argument_string_t_empty_input(); - test_argument_string_t_single_hyphen(); - test_argument_string_t_double_hyphen(); - test_argument_string_t_with_spaces(); - test_argument_string_t_numeric_flag(); - test_argument_string_t_plus_flag_basic(); - test_argument_string_t_single_plus(); - test_argument_string_t_double_plus(); + const hashset_t prefixes = {'-', '+'}; + test_argument_string_t_flag_basic(prefixes); + test_argument_string_t_long_flag(prefixes); + test_argument_string_t_positional_argument(prefixes); + test_argument_string_t_empty_input(prefixes); + test_argument_string_t_single_hyphen(prefixes); + test_argument_string_t_double_hyphen(prefixes); + test_argument_string_t_with_spaces(prefixes); + test_argument_string_t_numeric_flag(prefixes); + test_argument_string_t_plus_flag_basic(prefixes); + test_argument_string_t_single_plus(prefixes); + test_argument_string_t_double_plus(prefixes); } void test() From 89d95dfec4e0edad1d2800fcc3d53eeb6bed8384 Mon Sep 17 00:00:00 2001 From: Brett Date: Thu, 13 Feb 2025 13:59:59 -0500 Subject: [PATCH 009/101] working on subparsers --- CMakeLists.txt | 2 +- include/blt/parse/argparse_v2.h | 93 +++++++++++++++++++++++++++++++-- 2 files changed, 89 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 26d0783..2767fd3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 4.0.4) +set(BLT_VERSION 4.0.5) set(BLT_TARGET BLT) diff --git a/include/blt/parse/argparse_v2.h b/include/blt/parse/argparse_v2.h index 3ef50d8..720db6d 100644 --- a/include/blt/parse/argparse_v2.h +++ b/include/blt/parse/argparse_v2.h @@ -56,6 +56,49 @@ namespace blt::argparse } }; + class subparse_error final : public std::exception + { + public: + explicit subparse_error(const std::string_view found_string, std::vector allowed_strings): m_found_string(found_string), + m_allowed_strings(std::move(allowed_strings)) + { + } + + [[nodiscard]] const std::vector& get_allowed_strings() const + { + return m_allowed_strings; + } + + [[nodiscard]] std::string_view get_found_string() const + { + return m_found_string; + } + + [[nodiscard]] std::string error_string() const + { + std::string message = "Subparser Error: "; + message += m_found_string; + message += " is not a valid command. Allowed commands are: {"; + for (const auto [i, allowed_string] : enumerate(m_allowed_strings)) + { + message += allowed_string; + if (i != m_allowed_strings.size() - 1) + message += ' '; + } + message += "}"; + return message; + } + + [[nodiscard]] const char* what() const override + { + return "Please use error_string() method instead of what(). This exception should *always* be caught!"; + } + + private: + std::string_view m_found_string; + std::vector m_allowed_strings; + }; + template struct arg_data_helper_t { @@ -207,6 +250,8 @@ namespace blt::argparse class argument_parser_t { + friend argument_subparser_t; + public: explicit argument_parser_t(const std::optional name = {}, const std::optional usage = {}): m_name(name), m_usage(usage) @@ -219,6 +264,10 @@ namespace blt::argparse return *this; } + void parse(argument_consumer_t& consumer) // NOLINT + { + } + private: std::optional m_name; std::optional m_usage; @@ -229,17 +278,51 @@ namespace blt::argparse class argument_subparser_t { public: - - argument_parser_t add_subparser(const std::string_view dest) + explicit argument_subparser_t(const argument_parser_t& parent): m_parent(&parent) { - } - argument_parser_t add_subparser(std::string& dest) + argument_parser_t& add_parser(const std::string_view name) { - + m_parsers.emplace(name); + return m_parsers[name]; } + + + /** + * Parses the next argument using the provided argument consumer. + * + * This function uses an argument consumer to extract and process the next argument. + * If the argument is a flag or if it cannot be matched against the available parsers, + * an exception is thrown. + * + * @param consumer Reference to an argument_consumer_t object, which handles argument parsing. + * The consumer provides the next argument to be parsed. + * + * @throws detail::subparse_error If the argument is a flag or does not match any known parser. + */ + void parse(argument_consumer_t& consumer) // NOLINT + { + const auto key = consumer.consume(); + if (key.is_flag()) + throw detail::subparse_error(key.get_argument(), get_allowed_strings()); + const auto it = m_parsers.find(key.get_name()); + if (it == m_parsers.end()) + throw detail::subparse_error(key.get_argument(), get_allowed_strings()); + it->second.parse(consumer); + } + private: + [[nodiscard]] std::vector get_allowed_strings() const + { + std::vector vec; + for (const auto& [key, value] : m_parsers) + vec.push_back(key); + return vec; + } + + const argument_parser_t* m_parent; + hashmap_t m_parsers; }; } From 31b28c77879099d9a358794cef19547cccc8cfb2 Mon Sep 17 00:00:00 2001 From: Brett Date: Thu, 13 Feb 2025 14:04:39 -0500 Subject: [PATCH 010/101] usages in subparsers --- CMakeLists.txt | 2 +- include/blt/parse/argparse_v2.h | 21 +++++++++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2767fd3..9b59e5c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 4.0.5) +set(BLT_VERSION 4.0.6) set(BLT_TARGET BLT) diff --git a/include/blt/parse/argparse_v2.h b/include/blt/parse/argparse_v2.h index 720db6d..5c227b6 100644 --- a/include/blt/parse/argparse_v2.h +++ b/include/blt/parse/argparse_v2.h @@ -253,8 +253,9 @@ namespace blt::argparse friend argument_subparser_t; public: - explicit argument_parser_t(const std::optional name = {}, const std::optional usage = {}): - m_name(name), m_usage(usage) + explicit argument_parser_t(const std::optional name = {}, const std::optional usage = {}, + const std::optional description = {}, const std::optional epilogue = {}): + m_name(name), m_usage(usage), m_description(description), m_epilogue(epilogue) { } @@ -278,13 +279,21 @@ namespace blt::argparse class argument_subparser_t { public: - explicit argument_subparser_t(const argument_parser_t& parent): m_parent(&parent) + explicit argument_subparser_t(const argument_parser_t& parent): m_parent(&parent), m_usage(parent.m_usage), + m_description(parent.m_description), m_epilogue(parent.m_epilogue) + { + } + + explicit argument_subparser_t(const argument_parser_t& parent, const std::optional usage = {}, + const std::optional description = {}, + const std::optional epilogue = {}): m_parent(&parent), m_usage(usage), + m_description(description), m_epilogue(epilogue) { } argument_parser_t& add_parser(const std::string_view name) { - m_parsers.emplace(name); + m_parsers.emplace(name, {}, m_usage, m_description, m_epilogue); return m_parsers[name]; } @@ -309,6 +318,7 @@ namespace blt::argparse const auto it = m_parsers.find(key.get_name()); if (it == m_parsers.end()) throw detail::subparse_error(key.get_argument(), get_allowed_strings()); + it->second.m_name = m_parent->m_name; it->second.parse(consumer); } @@ -322,6 +332,9 @@ namespace blt::argparse } const argument_parser_t* m_parent; + std::optional m_usage; + std::optional m_description; + std::optional m_epilogue; hashmap_t m_parsers; }; } From d7373ac832e37a3c1654947536cadd5d6aa498a0 Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Thu, 13 Feb 2025 17:47:27 -0500 Subject: [PATCH 011/101] more argparse work --- CMakeLists.txt | 2 +- include/blt/parse/argparse_v2.h | 226 ++++++++++++++---- ...v2 (conflicted copy 2025-02-13 150255).cpp | 165 +++++++++++++ src/blt/parse/argparse_v2.cpp | 66 +++++ 4 files changed, 416 insertions(+), 43 deletions(-) create mode 100644 src/blt/parse/argparse_v2 (conflicted copy 2025-02-13 150255).cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 9b59e5c..cfb847b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 4.0.6) +set(BLT_VERSION 4.0.7) set(BLT_TARGET BLT) diff --git a/include/blt/parse/argparse_v2.h b/include/blt/parse/argparse_v2.h index 5c227b6..e72fc0f 100644 --- a/include/blt/parse/argparse_v2.h +++ b/include/blt/parse/argparse_v2.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -39,8 +40,32 @@ namespace blt::argparse class argument_consumer_t; class argument_parser_t; class argument_subparser_t; - class parsed_argset_t; class argument_builder_t; + class argument_storage_t; + + enum class action_t + { + STORE, + STORE_CONST, + STORE_TRUE, + STORE_FALSE, + APPEND, + APPEND_CONST, + COUNT, + HELP, + VERSION, + EXTEND, + SUBCOMMAND + }; + + enum class nargs_t + { + IF_POSSIBLE, + ALL, + ALL_AT_LEAST_ONE + }; + + using nargs_v = std::variant; namespace detail { @@ -56,15 +81,24 @@ namespace blt::argparse } }; + class missing_argument_error final : public std::runtime_error + { + public: + explicit missing_argument_error(const std::string& message): std::runtime_error(message) + { + } + } + class subparse_error final : public std::exception { public: - explicit subparse_error(const std::string_view found_string, std::vector allowed_strings): m_found_string(found_string), + explicit subparse_error(const std::string_view found_string, std::vector> allowed_strings): + m_found_string(found_string), m_allowed_strings(std::move(allowed_strings)) { } - [[nodiscard]] const std::vector& get_allowed_strings() const + [[nodiscard]] const std::vector>& get_allowed_strings() const { return m_allowed_strings; } @@ -81,7 +115,18 @@ namespace blt::argparse message += " is not a valid command. Allowed commands are: {"; for (const auto [i, allowed_string] : enumerate(m_allowed_strings)) { - message += allowed_string; + if (allowed_string.size() > 1) + message += '['; + for (const auto [j, alias] : enumerate(allowed_string)) + { + message += alias; + if (j < alias.size() - 2) + message += ", "; + else if (j < alias.size()) + message += ", or "; + } + if (allowed_string.size() > 1) + message += ']'; if (i != m_allowed_strings.size() - 1) message += ' '; } @@ -96,7 +141,7 @@ namespace blt::argparse private: std::string_view m_found_string; - std::vector m_allowed_strings; + std::vector> m_allowed_strings; }; template @@ -106,7 +151,7 @@ namespace blt::argparse using arg_list_data_t = std::variant...>; }; - using data_helper_t = arg_data_helper_t; + using data_helper_t = arg_data_helper_t; using arg_primitive_data_t = data_helper_t::arg_primitive_data_t; using arg_list_data_t = data_helper_t::arg_list_data_t; @@ -117,7 +162,8 @@ namespace blt::argparse { static T convert(const std::string_view value) { - static_assert(std::is_arithmetic_v, "Type must be arithmetic!"); + static_assert(std::is_arithmetic_v || std::is_same_v || std::is_same_v, + "Type must be arithmetic, string_view or string!"); const std::string temp{value}; if constexpr (std::is_same_v) @@ -136,6 +182,14 @@ namespace blt::argparse { return static_cast(std::stoll(temp)); } + else if constexpr (std::is_same_v) + { + return value; + } + else if constexpr (std::is_same_v) + { + return std::string(value); + } BLT_UNREACHABLE; } }; @@ -234,18 +288,67 @@ namespace blt::argparse i32 m_forward_index = 0; }; - class argument_builder_t + class argument_storage_t { + friend argument_parser_t; + friend argument_subparser_t; + friend argument_builder_t; + public: + template + const T& get(const std::string_view key) + { + return std::get(m_data[key]); + } + + std::string_view get(const std::string_view key) + { + return std::get(m_data[key]); + } + + bool contains(const std::string_view key) + { + return m_data.find(key) != m_data.end(); + } private: + hashmap_t m_data; }; - class parsed_argset_t + class argument_builder_t { + friend argument_parser_t; + public: + argument_builder_t() + { + dest_func = [](const std::string_view dest, argument_storage_t& storage, std::string_view value) + { + storage.m_data[dest] = value; + }; + } + + template + argument_builder_t& as_type() + { + dest_func = [](const std::string_view dest, argument_storage_t& storage, std::string_view value) + { + storage.m_data[dest] = detail::arg_type_t::convert(value); + }; + return *this; + } private: + action_t action = action_t::STORE; + bool required = false; // do we require this argument to be provided as an argument? + nargs_v nargs = 1; // number of arguments to consume + std::optional metavar; // variable name to be used in the help string + std::optional help; // help string to be used in the help string + std::optional> choices; // optional allowed choices for this argument + std::optional default_value; + std::optional const_value; + // dest, storage, value input + std::function dest_func; }; class argument_parser_t @@ -259,14 +362,69 @@ namespace blt::argparse { } + template + argument_builder_t& add_flag(const std::string_view arg, Aliases... aliases) + { + static_assert( + std::conjunction_v, std::is_constructible< + std::string_view, Aliases>>...>, + "Arguments must be of type string_view, convertible to string_view or be string_view constructable"); + m_argument_builders.emplace_back(); + m_flag_arguments[arg] = &m_argument_builders.back(); + ((m_flag_arguments[std::string_view{aliases}] = &m_argument_builders.back()), ...); + return m_argument_builders.back(); + } + + argument_builder_t& add_positional(const std::string_view arg) + { + m_argument_builders.emplace_back(); + m_positional_arguments[arg] = &m_argument_builders.back(); + return m_argument_builders.back(); + } + + argument_subparser_t& add_subparser(std::string_view dest); + + void parse(argument_consumer_t& consumer); // NOLINT + + void print_help(); + argument_parser_t& set_name(const std::string_view name) { m_name = name; return *this; } - void parse(argument_consumer_t& consumer) // NOLINT + argument_parser_t& set_usage(const std::string_view usage) { + m_usage = usage; + return *this; + } + + [[nodiscard]] const std::optional& get_usage() const + { + return m_usage; + } + + argument_parser_t& set_description(const std::string_view description) + { + m_description = description; + return *this; + } + + [[nodiscard]] const std::optional& get_description() const + { + return m_description; + } + + argument_parser_t& set_epilogue(const std::string_view epilogue) + { + m_epilogue = epilogue; + return *this; + } + + [[nodiscard]] const std::optional& get_epilogue() const + { + return m_epilogue; } private: @@ -274,26 +432,28 @@ namespace blt::argparse std::optional m_usage; std::optional m_description; std::optional m_epilogue; + std::vector> m_subparsers; + std::vector m_argument_builders; + hashmap_t m_flag_arguments; + hashmap_t m_positional_arguments; }; class argument_subparser_t { public: - explicit argument_subparser_t(const argument_parser_t& parent): m_parent(&parent), m_usage(parent.m_usage), - m_description(parent.m_description), m_epilogue(parent.m_epilogue) + explicit argument_subparser_t(const argument_parser_t& parent): m_parent(&parent) { } - explicit argument_subparser_t(const argument_parser_t& parent, const std::optional usage = {}, - const std::optional description = {}, - const std::optional epilogue = {}): m_parent(&parent), m_usage(usage), - m_description(description), m_epilogue(epilogue) + template + argument_parser_t& add_parser(const std::string_view name, Aliases... aliases) { - } - - argument_parser_t& add_parser(const std::string_view name) - { - m_parsers.emplace(name, {}, m_usage, m_description, m_epilogue); + static_assert( + std::conjunction_v, std::is_constructible< + std::string_view, Aliases>>...>, + "Arguments must be of type string_view, convertible to string_view or be string_view constructable"); + m_parsers.emplace(name); + ((m_aliases[std::string_view{aliases}] = &m_parsers[name]), ...); return m_parsers[name]; } @@ -310,32 +470,14 @@ namespace blt::argparse * * @throws detail::subparse_error If the argument is a flag or does not match any known parser. */ - void parse(argument_consumer_t& consumer) // NOLINT - { - const auto key = consumer.consume(); - if (key.is_flag()) - throw detail::subparse_error(key.get_argument(), get_allowed_strings()); - const auto it = m_parsers.find(key.get_name()); - if (it == m_parsers.end()) - throw detail::subparse_error(key.get_argument(), get_allowed_strings()); - it->second.m_name = m_parent->m_name; - it->second.parse(consumer); - } + argument_string_t parse(argument_consumer_t& consumer); // NOLINT private: - [[nodiscard]] std::vector get_allowed_strings() const - { - std::vector vec; - for (const auto& [key, value] : m_parsers) - vec.push_back(key); - return vec; - } + [[nodiscard]] std::vector> get_allowed_strings() const; const argument_parser_t* m_parent; - std::optional m_usage; - std::optional m_description; - std::optional m_epilogue; hashmap_t m_parsers; + hashmap_t m_aliases; }; } diff --git a/src/blt/parse/argparse_v2 (conflicted copy 2025-02-13 150255).cpp b/src/blt/parse/argparse_v2 (conflicted copy 2025-02-13 150255).cpp new file mode 100644 index 0000000..6ce898c --- /dev/null +++ b/src/blt/parse/argparse_v2 (conflicted copy 2025-02-13 150255).cpp @@ -0,0 +1,165 @@ +/* + * + * Copyright (C) 2025 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include + +namespace blt::argparse +{ + namespace detail + { + // Unit Tests for class argument_string_t + // Test Case 1: Ensure the constructor handles flags correctly + void test_argument_string_t_flag_basic(const hashset_t& prefixes) + { + const argument_string_t arg("-f", prefixes); + BLT_ASSERT(arg.is_flag() && "Expected argument to be identified as a flag."); + BLT_ASSERT(arg.value() == "f" && "Flag value should match the input string."); + } + + // Test Case 2: Ensure the constructor handles long flags correctly + void test_argument_string_t_long_flag(const hashset_t& prefixes) + { + const argument_string_t arg("--file", prefixes); + BLT_ASSERT(arg.is_flag() && "Expected argument to be identified as a flag."); + BLT_ASSERT(arg.value() == "file" && "Long flag value should match the input string."); + } + + // Test Case 3: Ensure positional arguments are correctly identified + void test_argument_string_t_positional_argument(const hashset_t& prefixes) + { + const argument_string_t arg("filename.txt", prefixes); + BLT_ASSERT(!arg.is_flag() && "Expected argument to be identified as positional."); + BLT_ASSERT(arg.value() == "filename.txt" && "Positional argument value should match the input string."); + } + + // Test Case 5: Handle an empty string + void test_argument_string_t_empty_input(const hashset_t& prefixes) + { + const argument_string_t arg("", prefixes); + BLT_ASSERT(!arg.is_flag() && "Expected an empty input to be treated as positional, not a flag."); + BLT_ASSERT(arg.value().empty() && "Empty input should have an empty value."); + } + + // Test Case 6: Handle edge case of a single hyphen (`-`) which might be ambiguous + void test_argument_string_t_single_hyphen(const hashset_t& prefixes) + { + const argument_string_t arg("-", prefixes); + BLT_ASSERT(arg.is_flag() && "Expected single hyphen (`-`) to be treated as a flag."); + BLT_ASSERT(arg.value().empty() && "Single hyphen flag should have empty value."); + BLT_ASSERT(arg.get_flag() == "-" && "Single hyphen flag should match the input string."); + } + + // Test Case 8: Handle arguments with prefix only (like "--") + void test_argument_string_t_double_hyphen(const hashset_t& prefixes) + { + const argument_string_t arg("--", prefixes); + BLT_ASSERT(arg.is_flag() && "Double hyphen ('--') should be treated as a flag."); + BLT_ASSERT(arg.value().empty() && "Double hyphen flag should have empty value."); + BLT_ASSERT(arg.get_flag() == "--" && "Double hyphen value should match the input string."); + } + + // Test Case 9: Validate edge case of an argument with spaces + void test_argument_string_t_with_spaces(const hashset_t& prefixes) + { + const argument_string_t arg(" ", prefixes); + BLT_ASSERT(!arg.is_flag() && "Arguments with spaces should not be treated as flags."); + BLT_ASSERT(arg.value() == " " && "Arguments with spaces should match the input string."); + } + + // Test Case 10: Validate arguments with numeric characters + void test_argument_string_t_numeric_flag(const hashset_t& prefixes) + { + const argument_string_t arg("-123", prefixes); + BLT_ASSERT(arg.is_flag() && "Numeric flags should still be treated as flags."); + BLT_ASSERT(arg.value() == "123" && "Numeric flag value should match the input string."); + } + + // Test Case 11: Ensure the constructor handles '+' flag correctly + void test_argument_string_t_plus_flag_basic(const hashset_t& prefixes) + { + const argument_string_t arg("+f", prefixes); + BLT_ASSERT(arg.is_flag() && "Expected argument to be identified as a flag."); + BLT_ASSERT(arg.value() == "f" && "Plus flag value should match the input string."); + } + + // Test Case 13: Handle edge case of a single plus (`+`) which might be ambiguous + void test_argument_string_t_single_plus(const hashset_t& prefixes) + { + const argument_string_t arg("+", prefixes); + BLT_ASSERT(arg.is_flag() && "Expected single plus (`+`) to be treated as a flag."); + BLT_ASSERT(arg.value().empty() && "Single plus flag should have empty value."); + BLT_ASSERT(arg.get_flag() == "+" && "Single plus flag should match the input string."); + } + + // Test Case 14: Handle arguments with prefix only (like '++') + void test_argument_string_t_double_plus(const hashset_t& prefixes) + { + const argument_string_t arg("++", prefixes); + BLT_ASSERT(arg.is_flag() && "Double plus ('++') should be treated as a flag."); + BLT_ASSERT(arg.value().empty() && "Double plus flag should have empty value."); + BLT_ASSERT(arg.get_flag() == "++" && "Double plus value should match the input string."); + } + + void run_all_tests_argument_string_t() + { + const hashset_t prefixes = {'-', '+'}; + test_argument_string_t_flag_basic(prefixes); + test_argument_string_t_long_flag(prefixes); + test_argument_string_t_positional_argument(prefixes); + test_argument_string_t_empty_input(prefixes); + test_argument_string_t_single_hyphen(prefixes); + test_argument_string_t_double_hyphen(prefixes); + test_argument_string_t_with_spaces(prefixes); + test_argument_string_t_numeric_flag(prefixes); + test_argument_string_t_plus_flag_basic(prefixes); + test_argument_string_t_single_plus(prefixes); + test_argument_string_t_double_plus(prefixes); + } + + std::string subparse_error::error_string() const + { + std::string message = "Subparser Error: "; + message += m_found_string; + message += " is not a valid command. Allowed commands are: {"; + for (const auto [i, allowed_string] : enumerate(m_allowed_strings)) + { + message += allowed_string; + if (i != m_allowed_strings.size() - 1) + message += ' '; + } + message += "}"; + return message; + } + + void test() + { + run_all_tests_argument_string_t(); + } + } + + void argument_string_t::process_argument() + { + size_t start = 0; + for (; start < m_argument.size() && allowed_flag_prefix->contains(m_argument[start]); start++) + { + } + + m_flag_section = {m_argument.data(), start}; + m_name_section = {m_argument.data() + start, m_argument.size() - start}; + } +} diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp index 02cb76b..dd1b45d 100644 --- a/src/blt/parse/argparse_v2.cpp +++ b/src/blt/parse/argparse_v2.cpp @@ -15,6 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +#include #include #include @@ -121,6 +122,8 @@ namespace blt::argparse BLT_ASSERT(arg.get_flag() == "++" && "Double plus value should match the input string."); } + + void run_all_tests_argument_string_t() { const hashset_t prefixes = {'-', '+'}; @@ -142,4 +145,67 @@ namespace blt::argparse run_all_tests_argument_string_t(); } } + + argument_subparser_t& argument_parser_t::add_subparser(const std::string_view dest) + { + m_subparsers.emplace_back(dest, argument_subparser_t{*this}); + return m_subparsers.back().second; + } + + void argument_parser_t::parse(argument_consumer_t& consumer) + { + if (!consumer.has_next()) + { + if (m_subparsers.size() > 0) + { + std::cout << "" + print_help(); + return; + } + } + } + + void argument_parser_t::print_help() + { + + } + + argument_string_t argument_subparser_t::parse(argument_consumer_t& consumer) + { + if (!consumer.has_next()) + throw detail::missing_argument_error("Subparser requires an argument."); + const auto key = consumer.consume(); + if (key.is_flag()) + throw detail::subparse_error(key.get_argument(), get_allowed_strings()); + const auto it = m_parsers.find(key.get_name()); + if (it == m_parsers.end()) + { + const auto it2 = m_aliases.find(key.get_name()); + if (it2 == m_aliases.end()) + throw detail::subparse_error(key.get_argument(), get_allowed_strings()); + it2->second->m_name = m_parent->m_name; + it2->second->parse(consumer); + return key; + } + it->second.m_name = m_parent->m_name; + it->second.parse(consumer); + return key; + } + + std::vector> argument_subparser_t::get_allowed_strings() const + { + std::vector> vec; + for (const auto& [key, value] : m_parsers) + { + std::vector aliases; + aliases.push_back(key); + for (const auto& [alias, parser] : m_aliases) + { + if (parser == &value) + aliases.push_back(alias); + } + vec.emplace_back(std::move(aliases)); + } + return vec; + } } From 6e5caf3ac57fa6b7e8bd1605a427edf8c39a3890 Mon Sep 17 00:00:00 2001 From: Brett Date: Sun, 16 Feb 2025 23:22:00 -0500 Subject: [PATCH 012/101] good work on argparse --- CMakeLists.txt | 2 +- include/blt/parse/argparse_v2.h | 159 ++++++++------- ...v2 (conflicted copy 2025-02-13 150255).cpp | 2 +- src/blt/parse/argparse_v2.cpp | 181 +++++++++++++++--- 4 files changed, 256 insertions(+), 88 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cfb847b..ae8b177 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 4.0.7) +set(BLT_VERSION 4.0.8) set(BLT_TARGET BLT) diff --git a/include/blt/parse/argparse_v2.h b/include/blt/parse/argparse_v2.h index e72fc0f..359750a 100644 --- a/include/blt/parse/argparse_v2.h +++ b/include/blt/parse/argparse_v2.h @@ -87,7 +87,7 @@ namespace blt::argparse explicit missing_argument_error(const std::string& message): std::runtime_error(message) { } - } + }; class subparse_error final : public std::exception { @@ -108,31 +108,7 @@ namespace blt::argparse return m_found_string; } - [[nodiscard]] std::string error_string() const - { - std::string message = "Subparser Error: "; - message += m_found_string; - message += " is not a valid command. Allowed commands are: {"; - for (const auto [i, allowed_string] : enumerate(m_allowed_strings)) - { - if (allowed_string.size() > 1) - message += '['; - for (const auto [j, alias] : enumerate(allowed_string)) - { - message += alias; - if (j < alias.size() - 2) - message += ", "; - else if (j < alias.size()) - message += ", or "; - } - if (allowed_string.size() > 1) - message += ']'; - if (i != m_allowed_strings.size() - 1) - message += ' '; - } - message += "}"; - return message; - } + [[nodiscard]] std::string error_string() const; [[nodiscard]] const char* what() const override { @@ -147,18 +123,13 @@ namespace blt::argparse template struct arg_data_helper_t { - using arg_primitive_data_t = std::variant; - using arg_list_data_t = std::variant...>; + using variant_t = std::variant...>; }; - using data_helper_t = arg_data_helper_t; - - using arg_primitive_data_t = data_helper_t::arg_primitive_data_t; - using arg_list_data_t = data_helper_t::arg_list_data_t; - using arg_data_t = std::variant; + using arg_data_t = arg_data_helper_t::variant_t; template - struct arg_type_t + struct arg_string_converter_t { static T convert(const std::string_view value) { @@ -201,7 +172,7 @@ namespace blt::argparse { public: explicit argument_string_t(const char* input, const hashset_t& allowed_flag_prefix): m_argument(input), - allowed_flag_prefix(&allowed_flag_prefix) + m_allowed_flag_prefix(&allowed_flag_prefix) { if (input == nullptr) throw detail::bad_flag("Argument cannot be null!"); @@ -234,10 +205,33 @@ namespace blt::argparse } private: + /** + * This function takes the command line argument represented by this class, + * stored in m_argument and converts into flag side and name side arguments. + * + * What this means is in the case of a flag provided, for example passing --foo or --bar, this function will split the argument into + * + * m_flag_section = "--" + * m_name_section = "foo" || "bar" + * + * If the argument is purely positional, meaning there is no flag prefix, + * this function will do nothing and m_flag_section will be true for .empty() + * + * For example, if you provide res/some/folder/or/file as a command line positional argument, + * this function will create the following internal state: + * + * m_flag_section = "" + * m_flag_section.empty() == true + * m_name_section = "res/some/folder/or/file" + * + * m_argument is not modified by this function + */ void process_argument() { + // user provides a list of allowed prefix characters to argument_parser_t, which is then provided to this class at construction time + // it is not the job of this class to validate flag prefixes beyond this. // TODO: requiring this pointer is a code smell. size_t start = 0; - for (; start < m_argument.size() && allowed_flag_prefix->contains(m_argument[start]); start++) + for (; start < m_argument.size() && m_allowed_flag_prefix->contains(m_argument[start]); start++) { } @@ -248,44 +242,54 @@ namespace blt::argparse std::string_view m_argument; std::string_view m_flag_section; std::string_view m_name_section; - const hashset_t* allowed_flag_prefix; + const hashset_t* m_allowed_flag_prefix; }; class argument_consumer_t { public: - explicit argument_consumer_t(const span& args): m_args(args) + explicit argument_consumer_t(const span& args): m_begin(args.data()), m_end(args.data() + args.size()) { } [[nodiscard]] argument_string_t peek(const i32 offset = 0) const { - return m_args[m_forward_index + offset]; + return *(m_begin + offset); + } + + [[nodiscard]] argument_string_t r_peek(const i32 offset = 0) const + { + return *(m_end - 1 - offset); } argument_string_t consume() { - return m_args[m_forward_index++]; + return *(m_begin++); } - [[nodiscard]] i32 position() const + argument_string_t r_consume() { - return m_forward_index; + return *(--m_end); } [[nodiscard]] i32 remaining() const { - return static_cast(m_args.size()) - m_forward_index; + return static_cast(size()); } - [[nodiscard]] bool has_next(const i32 offset = 0) const + [[nodiscard]] bool can_consume(const i32 amount = 0) const { - return (offset + m_forward_index) < m_args.size(); + return amount < remaining(); } private: - span m_args; - i32 m_forward_index = 0; + [[nodiscard]] ptrdiff_t size() const + { + return m_end - m_begin; + } + + argument_string_t* m_begin; + argument_string_t* m_end; }; class argument_storage_t @@ -312,6 +316,12 @@ namespace blt::argparse } private: + void add(const argument_storage_t& values) + { + for (const auto value : values) + m_data.insert(value); + } + hashmap_t m_data; }; @@ -322,33 +332,47 @@ namespace blt::argparse public: argument_builder_t() { - dest_func = [](const std::string_view dest, argument_storage_t& storage, std::string_view value) + m_dest_func = [](const std::string_view dest, argument_storage_t& storage, std::string_view value) { - storage.m_data[dest] = value; + storage.m_data.insert({dest, value}); + }; + m_dest_vec_func = [](const std::string_view dest, argument_storage_t& storage, const std::vector& values) + { + storage.m_data.insert({dest, values}); }; } template argument_builder_t& as_type() { - dest_func = [](const std::string_view dest, argument_storage_t& storage, std::string_view value) + m_dest_func = [](const std::string_view dest, argument_storage_t& storage, std::string_view value) { - storage.m_data[dest] = detail::arg_type_t::convert(value); + storage.m_data.insert({dest, detail::arg_string_converter_t::convert(value)}); + }; + m_dest_vec_func = [](const std::string_view dest, argument_storage_t& storage, const std::vector& values) + { + std::vector converted_values; + for (const auto& value : values) + converted_values.push_back(detail::arg_string_converter_t::convert(value)); + storage.m_data.insert({dest, converted_values}); }; return *this; } private: - action_t action = action_t::STORE; - bool required = false; // do we require this argument to be provided as an argument? - nargs_v nargs = 1; // number of arguments to consume - std::optional metavar; // variable name to be used in the help string - std::optional help; // help string to be used in the help string - std::optional> choices; // optional allowed choices for this argument - std::optional default_value; - std::optional const_value; + action_t m_action = action_t::STORE; + bool m_required = false; // do we require this argument to be provided as an argument? + nargs_v m_nargs = 1; // number of arguments to consume + std::optional m_metavar; // variable name to be used in the help string + std::optional m_help; // help string to be used in the help string + std::optional> m_choices; // optional allowed choices for this argument + std::optional m_default_value; + std::optional m_const_value; + std::optional m_dest; // dest, storage, value input - std::function dest_func; + std::function m_dest_func; + // dest, storage, value input + std::function& values)> m_dest_vec_func; }; class argument_parser_t @@ -370,7 +394,7 @@ namespace blt::argparse std::string_view, Aliases>>...>, "Arguments must be of type string_view, convertible to string_view or be string_view constructable"); m_argument_builders.emplace_back(); - m_flag_arguments[arg] = &m_argument_builders.back(); + m_flag_arguments.insert({arg, &m_argument_builders.back()}); ((m_flag_arguments[std::string_view{aliases}] = &m_argument_builders.back()), ...); return m_argument_builders.back(); } @@ -378,16 +402,18 @@ namespace blt::argparse argument_builder_t& add_positional(const std::string_view arg) { m_argument_builders.emplace_back(); - m_positional_arguments[arg] = &m_argument_builders.back(); + m_positional_arguments.insert({arg, &m_argument_builders.back()}); return m_argument_builders.back(); } argument_subparser_t& add_subparser(std::string_view dest); - void parse(argument_consumer_t& consumer); // NOLINT + argument_storage_t parse(argument_consumer_t& consumer); // NOLINT void print_help(); + void print_usage(); + argument_parser_t& set_name(const std::string_view name) { m_name = name; @@ -428,6 +454,11 @@ namespace blt::argparse } private: + void parse_flag(argument_storage_t& parsed_args, argument_consumer_t& consumer, std::string_view arg); + void parse_positional(argument_storage_t& parsed_args, argument_consumer_t& consumer, std::string_view arg); + static void handle_missing_and_default_args(hashmap_t& arguments, const hashset_t& found, + argument_storage_t& parsed_args, std::string_view type); + std::optional m_name; std::optional m_usage; std::optional m_description; @@ -470,7 +501,7 @@ namespace blt::argparse * * @throws detail::subparse_error If the argument is a flag or does not match any known parser. */ - argument_string_t parse(argument_consumer_t& consumer); // NOLINT + std::pair parse(argument_consumer_t& consumer); // NOLINT private: [[nodiscard]] std::vector> get_allowed_strings() const; diff --git a/src/blt/parse/argparse_v2 (conflicted copy 2025-02-13 150255).cpp b/src/blt/parse/argparse_v2 (conflicted copy 2025-02-13 150255).cpp index 6ce898c..1a32833 100644 --- a/src/blt/parse/argparse_v2 (conflicted copy 2025-02-13 150255).cpp +++ b/src/blt/parse/argparse_v2 (conflicted copy 2025-02-13 150255).cpp @@ -155,7 +155,7 @@ namespace blt::argparse void argument_string_t::process_argument() { size_t start = 0; - for (; start < m_argument.size() && allowed_flag_prefix->contains(m_argument[start]); start++) + for (; start < m_argument.size() && m_allowed_flag_prefix->contains(m_argument[start]); start++) { } diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp index dd1b45d..6eb054c 100644 --- a/src/blt/parse/argparse_v2.cpp +++ b/src/blt/parse/argparse_v2.cpp @@ -21,12 +21,6 @@ namespace blt::argparse { - namespace detail - { - - } - - namespace detail { // Unit Tests for class argument_string_t @@ -122,7 +116,6 @@ namespace blt::argparse BLT_ASSERT(arg.get_flag() == "++" && "Double plus value should match the input string."); } - void run_all_tests_argument_string_t() { @@ -144,6 +137,32 @@ namespace blt::argparse { run_all_tests_argument_string_t(); } + + [[nodiscard]] std::string subparse_error::error_string() const + { + std::string message = "Subparser Error: "; + message += m_found_string; + message += " is not a valid command. Allowed commands are: {"; + for (const auto [i, allowed_string] : enumerate(m_allowed_strings)) + { + if (allowed_string.size() > 1) + message += '['; + for (const auto [j, alias] : enumerate(allowed_string)) + { + message += alias; + if (j < alias.size() - 2) + message += ", "; + else if (j < alias.size()) + message += ", or "; + } + if (allowed_string.size() > 1) + message += ']'; + if (i != m_allowed_strings.size() - 1) + message += ' '; + } + message += "}"; + return message; + } } argument_subparser_t& argument_parser_t::add_subparser(const std::string_view dest) @@ -152,44 +171,162 @@ namespace blt::argparse return m_subparsers.back().second; } - void argument_parser_t::parse(argument_consumer_t& consumer) + argument_storage_t argument_parser_t::parse(argument_consumer_t& consumer) { - if (!consumer.has_next()) + hashset_t found_flags; + hashset_t found_positional; + argument_storage_t parsed_args; + // first, we consume flags which may be part of this parser + while (consumer.can_consume() && consumer.peek().is_flag()) { - if (m_subparsers.size() > 0) + const auto key = consumer.consume(); + const auto flag = m_flag_arguments.find(key.get_argument()); + if (flag == m_flag_arguments.end()) { - std::cout << "" - print_help(); - return; + std::cerr << "Error: Unknown flag: " << key.get_argument() << std::endl; + exit(1); + } + found_flags.insert(key.get_argument()); + parse_flag(parsed_args, consumer, key.get_argument()); + } + try + { + for (auto& [key, subparser] : m_subparsers) + { + auto [parsed_subparser, storage] = subparser.parse(consumer); + storage.m_data.insert({key, detail::arg_data_t{parsed_subparser.get_argument()}}); + parsed_args.add(storage); } } + catch (const detail::missing_argument_error& e) + { + std::cerr << "Error: " << e.what() << std::endl; + print_usage(); + exit(1); + } catch (const detail::subparse_error& e) + { + std::cerr << e.error_string() << std::endl; + exit(1); + } + while (consumer.can_consume()) + { + const auto key = consumer.consume(); + if (key.is_flag()) + { + const auto flag = m_flag_arguments.find(key.get_argument()); + if (flag == m_flag_arguments.end()) + { + std::cerr << "Error: Unknown flag: " << key.get_argument() << std::endl; + exit(1); + } + found_flags.insert(key.get_argument()); + parse_flag(parsed_args, consumer, key.get_argument()); + } + else + { + const auto pos = m_positional_arguments.find(key.get_argument()); + if (pos == m_positional_arguments.end()) + { + std::cerr << "Error: Unknown positional argument: " << key.get_argument() << std::endl; + exit(1); + } + found_positional.insert(key.get_argument()); + parse_positional(parsed_args, consumer, key.get_argument()); + } + } + handle_missing_and_default_args(m_flag_arguments, found_flags, parsed_args, "flag"); + handle_missing_and_default_args(m_positional_arguments, found_positional, parsed_args, "positional"); + + return parsed_args; } void argument_parser_t::print_help() { - } - argument_string_t argument_subparser_t::parse(argument_consumer_t& consumer) + void argument_parser_t::print_usage() { - if (!consumer.has_next()) + } + + void argument_parser_t::parse_flag(argument_storage_t& parsed_args, argument_consumer_t& consumer, const std::string_view arg) + { + auto& flag = m_flag_arguments[arg]; + auto dest = flag->m_dest.value_or(arg); + std::visit(lambda_visitor{ + [&parsed_args, &consumer, &dest, &flag, arg](const nargs_t arg_enum) + { + switch (arg_enum) + { + case nargs_t::IF_POSSIBLE: + if (consumer.can_consume() && !consumer.peek().is_flag()) + flag->m_dest_func(dest, parsed_args, consumer.consume().get_argument()); + else + { + if (flag->m_const_value) + parsed_args.m_data.insert({dest, *flag->m_const_value}); + } + break; + [[fallthrough]] case nargs_t::ALL_AT_LEAST_ONE: + if (!consumer.can_consume()) + std::cout << "Error expected at least one argument to be consumed by '" << arg << '\'' << std::endl; + case nargs_t::ALL: + std::vector args; + while (consumer.can_consume() && !consumer.peek().is_flag()) + args.emplace_back(consumer.consume().get_argument()); + flag->m_dest_vec_func(dest, parsed_args, args); + break; + } + }, + [](const i32 argc) + { + } + }, flag->m_nargs); + } + + void argument_parser_t::parse_positional(argument_storage_t& parsed_args, argument_consumer_t& consumer, const std::string_view arg) + { + } + + void argument_parser_t::handle_missing_and_default_args(hashmap_t& arguments, + const hashset_t& found, argument_storage_t& parsed_args, + const std::string_view type) + { + for (const auto& [key, value] : arguments) + { + if (!found.contains(key)) + { + if (value->m_required) + { + std::cerr << "Error: " << type << " argument '" << key << "' was not found but is required by the program" << std::endl; + exit(1); + } + auto dest = value->m_dest.value_or(key); + if (value->m_default_value && !parsed_args.contains(dest)) + parsed_args.m_data.insert({dest, *value->m_default_value}); + } + } + } + + std::pair argument_subparser_t::parse(argument_consumer_t& consumer) + { + if (!consumer.can_consume()) throw detail::missing_argument_error("Subparser requires an argument."); const auto key = consumer.consume(); if (key.is_flag()) throw detail::subparse_error(key.get_argument(), get_allowed_strings()); const auto it = m_parsers.find(key.get_name()); + argument_parser_t* parser; if (it == m_parsers.end()) { const auto it2 = m_aliases.find(key.get_name()); if (it2 == m_aliases.end()) throw detail::subparse_error(key.get_argument(), get_allowed_strings()); - it2->second->m_name = m_parent->m_name; - it2->second->parse(consumer); - return key; + parser = it2->second; } - it->second.m_name = m_parent->m_name; - it->second.parse(consumer); - return key; + else + parser = &it->second; + parser->m_name = m_parent->m_name; + return {key, parser->parse(consumer)}; } std::vector> argument_subparser_t::get_allowed_strings() const From a78ad584795b698863a28c5e10fbbd1772a00b9e Mon Sep 17 00:00:00 2001 From: Brett Date: Mon, 17 Feb 2025 01:47:42 -0500 Subject: [PATCH 013/101] gotta think of a way of handling the whole "templates are silly" thing --- CMakeLists.txt | 2 +- include/blt/parse/argparse_v2.h | 43 ++++++++++++++--- src/blt/parse/argparse_v2.cpp | 82 +++++++++++++++++++++++++++++++-- 3 files changed, 117 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ae8b177..19ad666 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 4.0.8) +set(BLT_VERSION 4.0.9) set(BLT_TARGET BLT) diff --git a/include/blt/parse/argparse_v2.h b/include/blt/parse/argparse_v2.h index 359750a..184cd7f 100644 --- a/include/blt/parse/argparse_v2.h +++ b/include/blt/parse/argparse_v2.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -51,11 +52,10 @@ namespace blt::argparse STORE_FALSE, APPEND, APPEND_CONST, + EXTEND, COUNT, HELP, - VERSION, - EXTEND, - SUBCOMMAND + VERSION }; enum class nargs_t @@ -120,13 +120,41 @@ namespace blt::argparse std::vector> m_allowed_strings; }; + template + constexpr auto invalid_option_lambda = [](const T) + { + std::cerr << "Invalid type - expected list type, found '" << blt::type_string() << "'" << std::endl; + std::exit(1); + }; + template struct arg_data_helper_t { using variant_t = std::variant...>; + using arg_t = meta::arg_helper; + using arg_vec_t = meta::arg_helper...>; + + template typename... Defaults> + static auto make_lists_only_visitor(Defaults>&&... d) + { + return lambda_visitor{ + invalid_option_lambda, + std::forward(d)... + }; + } + + template typename... Defaults> + static auto make_reject_lists_visitor_t(Defaults&&... d) + { + return lambda_visitor{ + invalid_option_lambda...>, + std::forward(d)... + }; + } }; - using arg_data_t = arg_data_helper_t::variant_t; + using arg_meta_type_helper_t = arg_data_helper_t; + using arg_data_t = arg_meta_type_helper_t::variant_t; template struct arg_string_converter_t @@ -414,6 +442,8 @@ namespace blt::argparse void print_usage(); + void print_version(); + argument_parser_t& set_name(const std::string_view name) { m_name = name; @@ -456,8 +486,9 @@ namespace blt::argparse private: void parse_flag(argument_storage_t& parsed_args, argument_consumer_t& consumer, std::string_view arg); void parse_positional(argument_storage_t& parsed_args, argument_consumer_t& consumer, std::string_view arg); - static void handle_missing_and_default_args(hashmap_t& arguments, const hashset_t& found, - argument_storage_t& parsed_args, std::string_view type); + static void handle_missing_and_default_args(hashmap_t& arguments, + const hashset_t& found, + argument_storage_t& parsed_args, std::string_view type); std::optional m_name; std::optional m_usage; diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp index 6eb054c..c0d3491 100644 --- a/src/blt/parse/argparse_v2.cpp +++ b/src/blt/parse/argparse_v2.cpp @@ -21,6 +21,7 @@ namespace blt::argparse { + namespace detail { // Unit Tests for class argument_string_t @@ -248,6 +249,10 @@ namespace blt::argparse { } + void argument_parser_t::print_version() + { + } + void argument_parser_t::parse_flag(argument_storage_t& parsed_args, argument_consumer_t& consumer, const std::string_view arg) { auto& flag = m_flag_arguments[arg]; @@ -266,9 +271,9 @@ namespace blt::argparse parsed_args.m_data.insert({dest, *flag->m_const_value}); } break; - [[fallthrough]] case nargs_t::ALL_AT_LEAST_ONE: + [[fallthrough]] case nargs_t::ALL_AT_LEAST_ONE: if (!consumer.can_consume()) - std::cout << "Error expected at least one argument to be consumed by '" << arg << '\'' << std::endl; + std::cerr << "Error expected at least one argument to be consumed by '" << arg << '\'' << std::endl; case nargs_t::ALL: std::vector args; while (consumer.can_consume() && !consumer.peek().is_flag()) @@ -277,8 +282,79 @@ namespace blt::argparse break; } }, - [](const i32 argc) + [&parsed_args, &consumer, &dest, &flag, arg, this](const i32 argc) { + std::vector args; + for (i32 i = 0; i < argc; ++i) + { + if (!consumer.can_consume()) + { + std::cerr << "Error expected " << argc << " arguments to be consumed by '" << arg << "' but found " << i << + std::endl; + std::exit(1); + } + if (consumer.peek().is_flag()) + { + std::cerr << "Error expected " << argc << " arguments to be consumed by '" << arg << "' but found a flag '" << + consumer.peek().get_argument() << "' instead!" << std::endl; + std::exit(1); + } + args.push_back(consumer.consume().get_argument()); + } + if (args.size() != argc) + { + std::cerr << + "This error condition should not be possible. " + "Args consumed didn't equal the arguments requested and previous checks didn't fail. " + "Please report as an issue on the GitHub" + << std::endl; + std::exit(1); + } + if (argc == 0) + { + } + else if (argc == 1) + { + switch (flag->m_action) + { + case action_t::STORE: + break; + case action_t::APPEND: + case action_t::EXTEND: + { + break; + } + case action_t::APPEND_CONST: + // if (parsed_args.contains(dest)) + // { + // std::visit(detail::arg_meta_type_helper_t::make_lists_only_visitor(handle_insert), parsed_args.m_data[dest]); + // } + case action_t::STORE_CONST: + std::cerr << "Store const flag called with an argument. This condition doesn't make sense." << std::endl; + print_usage(); + std::exit(1); + case action_t::STORE_TRUE: + std::cerr << "Store true flag called with an argument. This condition doesn't make sense." << std::endl; + print_usage(); + std::exit(1); + case action_t::STORE_FALSE: + std::cerr << "Store false flag called with an argument. This condition doesn't make sense." << std::endl; + print_usage(); + std::exit(1); + case action_t::COUNT: + parsed_args.m_data.insert({dest, args.size()}); + break; + case action_t::HELP: + print_help(); + std::exit(1); + case action_t::VERSION: + print_version(); + break; + } + flag->m_dest_func(dest, parsed_args, args.front()); + } + else + flag->m_dest_vec_func(dest, parsed_args, args); } }, flag->m_nargs); } From fe6ce712e79888f8d9d180f8a18f4e63b47c7c1a Mon Sep 17 00:00:00 2001 From: Brett Date: Mon, 17 Feb 2025 01:56:27 -0500 Subject: [PATCH 014/101] partial solution --- CMakeLists.txt | 2 +- src/blt/parse/argparse_v2.cpp | 25 ++++++++++++++++++++----- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 19ad666..a2318a7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 4.0.9) +set(BLT_VERSION 4.0.10) set(BLT_TARGET BLT) diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp index c0d3491..f095652 100644 --- a/src/blt/parse/argparse_v2.cpp +++ b/src/blt/parse/argparse_v2.cpp @@ -21,7 +21,6 @@ namespace blt::argparse { - namespace detail { // Unit Tests for class argument_string_t @@ -325,10 +324,26 @@ namespace blt::argparse break; } case action_t::APPEND_CONST: - // if (parsed_args.contains(dest)) - // { - // std::visit(detail::arg_meta_type_helper_t::make_lists_only_visitor(handle_insert), parsed_args.m_data[dest]); - // } + if (flag->m_const_value) + { + std::cerr << "Append const chosen as an action but const value not provided for flag '" << arg << '\'' << + std::endl; + std::exit(1); + } + if (parsed_args.contains(dest)) + { + auto& data = parsed_args.m_data[dest]; + if (data.index() != flag->m_const_value->index()) + { + std::cerr << "Constant value for flag '" << arg << "' type doesn't values already present!" << std::endl; + std::exit(1); + } + + } + // if (parsed_args.contains(dest)) + // { + // std::visit(detail::arg_meta_type_helper_t::make_lists_only_visitor(handle_insert), parsed_args.m_data[dest]); + // } case action_t::STORE_CONST: std::cerr << "Store const flag called with an argument. This condition doesn't make sense." << std::endl; print_usage(); From 735371b7bdeab8370e985a1ff2948e954742ff57 Mon Sep 17 00:00:00 2001 From: Brett Date: Mon, 17 Feb 2025 02:20:40 -0500 Subject: [PATCH 015/101] silly works now i need to bed" --- CMakeLists.txt | 2 +- include/blt/parse/argparse_v2.h | 35 +++- ...v2 (conflicted copy 2025-02-13 150255).cpp | 165 ------------------ src/blt/parse/argparse_v2.cpp | 19 +- 4 files changed, 38 insertions(+), 183 deletions(-) delete mode 100644 src/blt/parse/argparse_v2 (conflicted copy 2025-02-13 150255).cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a2318a7..3ae9eac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 4.0.10) +set(BLT_VERSION 4.0.11) set(BLT_TARGET BLT) diff --git a/include/blt/parse/argparse_v2.h b/include/blt/parse/argparse_v2.h index 184cd7f..c5f53cf 100644 --- a/include/blt/parse/argparse_v2.h +++ b/include/blt/parse/argparse_v2.h @@ -110,7 +110,7 @@ namespace blt::argparse [[nodiscard]] std::string error_string() const; - [[nodiscard]] const char* what() const override + [[nodiscard]] const char* what() const noexcept override { return "Please use error_string() method instead of what(). This exception should *always* be caught!"; } @@ -134,21 +134,40 @@ namespace blt::argparse using arg_t = meta::arg_helper; using arg_vec_t = meta::arg_helper...>; - template typename... Defaults> - static auto make_lists_only_visitor(Defaults>&&... d) + template + static auto make_lists_only_visitor_per_object_in_vec(const PerObjectAction& action) { return lambda_visitor{ invalid_option_lambda, - std::forward(d)... + ([&action](std::vector arg_vec) + { + for (const auto& arg : arg_vec) + action(arg_vec, arg); + })... }; } - template typename... Defaults> - static auto make_reject_lists_visitor_t(Defaults&&... d) + template + static auto make_lists_only_visitor_on_vec(const DefaultAction& action) + { + return lambda_visitor{ + invalid_option_lambda..., + ([&action](std::vector arg_vec) + { + action(arg_vec); + })... + }; + } + + template + static auto make_reject_lists_visitor_t(const DefaultAction& action) { return lambda_visitor{ invalid_option_lambda...>, - std::forward(d)... + ([&action](Args arg) + { + action(arg); + })... }; } }; @@ -346,7 +365,7 @@ namespace blt::argparse private: void add(const argument_storage_t& values) { - for (const auto value : values) + for (const auto& value : values.m_data) m_data.insert(value); } diff --git a/src/blt/parse/argparse_v2 (conflicted copy 2025-02-13 150255).cpp b/src/blt/parse/argparse_v2 (conflicted copy 2025-02-13 150255).cpp deleted file mode 100644 index 1a32833..0000000 --- a/src/blt/parse/argparse_v2 (conflicted copy 2025-02-13 150255).cpp +++ /dev/null @@ -1,165 +0,0 @@ -/* - * - * Copyright (C) 2025 Brett Terpstra - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include -#include - -namespace blt::argparse -{ - namespace detail - { - // Unit Tests for class argument_string_t - // Test Case 1: Ensure the constructor handles flags correctly - void test_argument_string_t_flag_basic(const hashset_t& prefixes) - { - const argument_string_t arg("-f", prefixes); - BLT_ASSERT(arg.is_flag() && "Expected argument to be identified as a flag."); - BLT_ASSERT(arg.value() == "f" && "Flag value should match the input string."); - } - - // Test Case 2: Ensure the constructor handles long flags correctly - void test_argument_string_t_long_flag(const hashset_t& prefixes) - { - const argument_string_t arg("--file", prefixes); - BLT_ASSERT(arg.is_flag() && "Expected argument to be identified as a flag."); - BLT_ASSERT(arg.value() == "file" && "Long flag value should match the input string."); - } - - // Test Case 3: Ensure positional arguments are correctly identified - void test_argument_string_t_positional_argument(const hashset_t& prefixes) - { - const argument_string_t arg("filename.txt", prefixes); - BLT_ASSERT(!arg.is_flag() && "Expected argument to be identified as positional."); - BLT_ASSERT(arg.value() == "filename.txt" && "Positional argument value should match the input string."); - } - - // Test Case 5: Handle an empty string - void test_argument_string_t_empty_input(const hashset_t& prefixes) - { - const argument_string_t arg("", prefixes); - BLT_ASSERT(!arg.is_flag() && "Expected an empty input to be treated as positional, not a flag."); - BLT_ASSERT(arg.value().empty() && "Empty input should have an empty value."); - } - - // Test Case 6: Handle edge case of a single hyphen (`-`) which might be ambiguous - void test_argument_string_t_single_hyphen(const hashset_t& prefixes) - { - const argument_string_t arg("-", prefixes); - BLT_ASSERT(arg.is_flag() && "Expected single hyphen (`-`) to be treated as a flag."); - BLT_ASSERT(arg.value().empty() && "Single hyphen flag should have empty value."); - BLT_ASSERT(arg.get_flag() == "-" && "Single hyphen flag should match the input string."); - } - - // Test Case 8: Handle arguments with prefix only (like "--") - void test_argument_string_t_double_hyphen(const hashset_t& prefixes) - { - const argument_string_t arg("--", prefixes); - BLT_ASSERT(arg.is_flag() && "Double hyphen ('--') should be treated as a flag."); - BLT_ASSERT(arg.value().empty() && "Double hyphen flag should have empty value."); - BLT_ASSERT(arg.get_flag() == "--" && "Double hyphen value should match the input string."); - } - - // Test Case 9: Validate edge case of an argument with spaces - void test_argument_string_t_with_spaces(const hashset_t& prefixes) - { - const argument_string_t arg(" ", prefixes); - BLT_ASSERT(!arg.is_flag() && "Arguments with spaces should not be treated as flags."); - BLT_ASSERT(arg.value() == " " && "Arguments with spaces should match the input string."); - } - - // Test Case 10: Validate arguments with numeric characters - void test_argument_string_t_numeric_flag(const hashset_t& prefixes) - { - const argument_string_t arg("-123", prefixes); - BLT_ASSERT(arg.is_flag() && "Numeric flags should still be treated as flags."); - BLT_ASSERT(arg.value() == "123" && "Numeric flag value should match the input string."); - } - - // Test Case 11: Ensure the constructor handles '+' flag correctly - void test_argument_string_t_plus_flag_basic(const hashset_t& prefixes) - { - const argument_string_t arg("+f", prefixes); - BLT_ASSERT(arg.is_flag() && "Expected argument to be identified as a flag."); - BLT_ASSERT(arg.value() == "f" && "Plus flag value should match the input string."); - } - - // Test Case 13: Handle edge case of a single plus (`+`) which might be ambiguous - void test_argument_string_t_single_plus(const hashset_t& prefixes) - { - const argument_string_t arg("+", prefixes); - BLT_ASSERT(arg.is_flag() && "Expected single plus (`+`) to be treated as a flag."); - BLT_ASSERT(arg.value().empty() && "Single plus flag should have empty value."); - BLT_ASSERT(arg.get_flag() == "+" && "Single plus flag should match the input string."); - } - - // Test Case 14: Handle arguments with prefix only (like '++') - void test_argument_string_t_double_plus(const hashset_t& prefixes) - { - const argument_string_t arg("++", prefixes); - BLT_ASSERT(arg.is_flag() && "Double plus ('++') should be treated as a flag."); - BLT_ASSERT(arg.value().empty() && "Double plus flag should have empty value."); - BLT_ASSERT(arg.get_flag() == "++" && "Double plus value should match the input string."); - } - - void run_all_tests_argument_string_t() - { - const hashset_t prefixes = {'-', '+'}; - test_argument_string_t_flag_basic(prefixes); - test_argument_string_t_long_flag(prefixes); - test_argument_string_t_positional_argument(prefixes); - test_argument_string_t_empty_input(prefixes); - test_argument_string_t_single_hyphen(prefixes); - test_argument_string_t_double_hyphen(prefixes); - test_argument_string_t_with_spaces(prefixes); - test_argument_string_t_numeric_flag(prefixes); - test_argument_string_t_plus_flag_basic(prefixes); - test_argument_string_t_single_plus(prefixes); - test_argument_string_t_double_plus(prefixes); - } - - std::string subparse_error::error_string() const - { - std::string message = "Subparser Error: "; - message += m_found_string; - message += " is not a valid command. Allowed commands are: {"; - for (const auto [i, allowed_string] : enumerate(m_allowed_strings)) - { - message += allowed_string; - if (i != m_allowed_strings.size() - 1) - message += ' '; - } - message += "}"; - return message; - } - - void test() - { - run_all_tests_argument_string_t(); - } - } - - void argument_string_t::process_argument() - { - size_t start = 0; - for (; start < m_argument.size() && m_allowed_flag_prefix->contains(m_argument[start]); start++) - { - } - - m_flag_section = {m_argument.data(), start}; - m_name_section = {m_argument.data() + start, m_argument.size() - start}; - } -} diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp index f095652..5ea7735 100644 --- a/src/blt/parse/argparse_v2.cpp +++ b/src/blt/parse/argparse_v2.cpp @@ -255,7 +255,7 @@ namespace blt::argparse void argument_parser_t::parse_flag(argument_storage_t& parsed_args, argument_consumer_t& consumer, const std::string_view arg) { auto& flag = m_flag_arguments[arg]; - auto dest = flag->m_dest.value_or(arg); + auto dest = flag->m_dest.value_or(std::string{arg}); std::visit(lambda_visitor{ [&parsed_args, &consumer, &dest, &flag, arg](const nargs_t arg_enum) { @@ -270,9 +270,10 @@ namespace blt::argparse parsed_args.m_data.insert({dest, *flag->m_const_value}); } break; - [[fallthrough]] case nargs_t::ALL_AT_LEAST_ONE: + case nargs_t::ALL_AT_LEAST_ONE: if (!consumer.can_consume()) std::cerr << "Error expected at least one argument to be consumed by '" << arg << '\'' << std::endl; + [[fallthrough]]; case nargs_t::ALL: std::vector args; while (consumer.can_consume() && !consumer.peek().is_flag()) @@ -300,7 +301,7 @@ namespace blt::argparse } args.push_back(consumer.consume().get_argument()); } - if (args.size() != argc) + if (args.size() != static_cast(argc)) { std::cerr << "This error condition should not be possible. " @@ -338,12 +339,12 @@ namespace blt::argparse std::cerr << "Constant value for flag '" << arg << "' type doesn't values already present!" << std::endl; std::exit(1); } - + auto visitor = detail::arg_meta_type_helper_t::make_lists_only_visitor_on_vec([&flag](auto& vec) + { + vec.push_back(std::get>::value_type>(*flag->m_const_value)); + }); + std::visit(visitor, data); } - // if (parsed_args.contains(dest)) - // { - // std::visit(detail::arg_meta_type_helper_t::make_lists_only_visitor(handle_insert), parsed_args.m_data[dest]); - // } case action_t::STORE_CONST: std::cerr << "Store const flag called with an argument. This condition doesn't make sense." << std::endl; print_usage(); @@ -391,7 +392,7 @@ namespace blt::argparse std::cerr << "Error: " << type << " argument '" << key << "' was not found but is required by the program" << std::endl; exit(1); } - auto dest = value->m_dest.value_or(key); + auto dest = value->m_dest.value_or(std::string{key}); if (value->m_default_value && !parsed_args.contains(dest)) parsed_args.m_data.insert({dest, *value->m_default_value}); } From 7dc19efbaa2aa49c9c733f2f54b51221f63e2538 Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Mon, 17 Feb 2025 21:43:09 -0500 Subject: [PATCH 016/101] more metaprogramming fun --- CMakeLists.txt | 2 +- include/blt/meta/type_traits.h | 31 ++ ...e_v2 (conflicted copy 2025-02-13 174730).h | 484 ++++++++++++++++++ include/blt/parse/argparse_v2.h | 36 +- src/blt/parse/argparse_v2.cpp | 74 ++- 5 files changed, 587 insertions(+), 40 deletions(-) create mode 100644 include/blt/meta/type_traits.h create mode 100644 include/blt/parse/argparse_v2 (conflicted copy 2025-02-13 174730).h diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ae9eac..81e6256 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 4.0.11) +set(BLT_VERSION 4.0.12) set(BLT_TARGET BLT) diff --git a/include/blt/meta/type_traits.h b/include/blt/meta/type_traits.h new file mode 100644 index 0000000..d811aa5 --- /dev/null +++ b/include/blt/meta/type_traits.h @@ -0,0 +1,31 @@ +#pragma once +/* + * Copyright (C) 2024 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BLT_META_TYPE_TRAITS_H +#define BLT_META_TYPE_TRAITS_H + +#include + +namespace blt::meta { + + template + using remove_cvref_t = std::remove_volatile_t>>; + +} + +#endif // BLT_META_TYPE_TRAITS_H diff --git a/include/blt/parse/argparse_v2 (conflicted copy 2025-02-13 174730).h b/include/blt/parse/argparse_v2 (conflicted copy 2025-02-13 174730).h new file mode 100644 index 0000000..e72fc0f --- /dev/null +++ b/include/blt/parse/argparse_v2 (conflicted copy 2025-02-13 174730).h @@ -0,0 +1,484 @@ +#pragma once +/* + * Copyright (C) 2024 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BLT_PARSE_ARGPARSE_V2_H +#define BLT_PARSE_ARGPARSE_V2_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace blt::argparse +{ + class argument_string_t; + class argument_consumer_t; + class argument_parser_t; + class argument_subparser_t; + class argument_builder_t; + class argument_storage_t; + + enum class action_t + { + STORE, + STORE_CONST, + STORE_TRUE, + STORE_FALSE, + APPEND, + APPEND_CONST, + COUNT, + HELP, + VERSION, + EXTEND, + SUBCOMMAND + }; + + enum class nargs_t + { + IF_POSSIBLE, + ALL, + ALL_AT_LEAST_ONE + }; + + using nargs_v = std::variant; + + namespace detail + { + class bad_flag final : public std::runtime_error + { + public: + explicit bad_flag(const std::string& message): std::runtime_error(message) + { + } + + explicit bad_flag(const char* message): std::runtime_error(message) + { + } + }; + + class missing_argument_error final : public std::runtime_error + { + public: + explicit missing_argument_error(const std::string& message): std::runtime_error(message) + { + } + } + + class subparse_error final : public std::exception + { + public: + explicit subparse_error(const std::string_view found_string, std::vector> allowed_strings): + m_found_string(found_string), + m_allowed_strings(std::move(allowed_strings)) + { + } + + [[nodiscard]] const std::vector>& get_allowed_strings() const + { + return m_allowed_strings; + } + + [[nodiscard]] std::string_view get_found_string() const + { + return m_found_string; + } + + [[nodiscard]] std::string error_string() const + { + std::string message = "Subparser Error: "; + message += m_found_string; + message += " is not a valid command. Allowed commands are: {"; + for (const auto [i, allowed_string] : enumerate(m_allowed_strings)) + { + if (allowed_string.size() > 1) + message += '['; + for (const auto [j, alias] : enumerate(allowed_string)) + { + message += alias; + if (j < alias.size() - 2) + message += ", "; + else if (j < alias.size()) + message += ", or "; + } + if (allowed_string.size() > 1) + message += ']'; + if (i != m_allowed_strings.size() - 1) + message += ' '; + } + message += "}"; + return message; + } + + [[nodiscard]] const char* what() const override + { + return "Please use error_string() method instead of what(). This exception should *always* be caught!"; + } + + private: + std::string_view m_found_string; + std::vector> m_allowed_strings; + }; + + template + struct arg_data_helper_t + { + using arg_primitive_data_t = std::variant; + using arg_list_data_t = std::variant...>; + }; + + using data_helper_t = arg_data_helper_t; + + using arg_primitive_data_t = data_helper_t::arg_primitive_data_t; + using arg_list_data_t = data_helper_t::arg_list_data_t; + using arg_data_t = std::variant; + + template + struct arg_type_t + { + static T convert(const std::string_view value) + { + static_assert(std::is_arithmetic_v || std::is_same_v || std::is_same_v, + "Type must be arithmetic, string_view or string!"); + const std::string temp{value}; + + if constexpr (std::is_same_v) + { + return std::stof(temp); + } + else if constexpr (std::is_same_v) + { + return std::stod(temp); + } + else if constexpr (std::is_unsigned_v) + { + return static_cast(std::stoull(temp)); + } + else if constexpr (std::is_signed_v) + { + return static_cast(std::stoll(temp)); + } + else if constexpr (std::is_same_v) + { + return value; + } + else if constexpr (std::is_same_v) + { + return std::string(value); + } + BLT_UNREACHABLE; + } + }; + + void test(); + } + + class argument_string_t + { + public: + explicit argument_string_t(const char* input, const hashset_t& allowed_flag_prefix): m_argument(input), + allowed_flag_prefix(&allowed_flag_prefix) + { + if (input == nullptr) + throw detail::bad_flag("Argument cannot be null!"); + process_argument(); + } + + [[nodiscard]] std::string_view get_flag() const + { + return m_flag_section; + } + + [[nodiscard]] std::string_view get_name() const + { + return m_name_section; + } + + [[nodiscard]] std::string_view value() const + { + return get_name(); + } + + [[nodiscard]] bool is_flag() const + { + return !m_flag_section.empty(); + } + + [[nodiscard]] std::string_view get_argument() const + { + return m_argument; + } + + private: + void process_argument() + { + size_t start = 0; + for (; start < m_argument.size() && allowed_flag_prefix->contains(m_argument[start]); start++) + { + } + + m_flag_section = {m_argument.data(), start}; + m_name_section = {m_argument.data() + start, m_argument.size() - start}; + } + + std::string_view m_argument; + std::string_view m_flag_section; + std::string_view m_name_section; + const hashset_t* allowed_flag_prefix; + }; + + class argument_consumer_t + { + public: + explicit argument_consumer_t(const span& args): m_args(args) + { + } + + [[nodiscard]] argument_string_t peek(const i32 offset = 0) const + { + return m_args[m_forward_index + offset]; + } + + argument_string_t consume() + { + return m_args[m_forward_index++]; + } + + [[nodiscard]] i32 position() const + { + return m_forward_index; + } + + [[nodiscard]] i32 remaining() const + { + return static_cast(m_args.size()) - m_forward_index; + } + + [[nodiscard]] bool has_next(const i32 offset = 0) const + { + return (offset + m_forward_index) < m_args.size(); + } + + private: + span m_args; + i32 m_forward_index = 0; + }; + + class argument_storage_t + { + friend argument_parser_t; + friend argument_subparser_t; + friend argument_builder_t; + + public: + template + const T& get(const std::string_view key) + { + return std::get(m_data[key]); + } + + std::string_view get(const std::string_view key) + { + return std::get(m_data[key]); + } + + bool contains(const std::string_view key) + { + return m_data.find(key) != m_data.end(); + } + + private: + hashmap_t m_data; + }; + + class argument_builder_t + { + friend argument_parser_t; + + public: + argument_builder_t() + { + dest_func = [](const std::string_view dest, argument_storage_t& storage, std::string_view value) + { + storage.m_data[dest] = value; + }; + } + + template + argument_builder_t& as_type() + { + dest_func = [](const std::string_view dest, argument_storage_t& storage, std::string_view value) + { + storage.m_data[dest] = detail::arg_type_t::convert(value); + }; + return *this; + } + + private: + action_t action = action_t::STORE; + bool required = false; // do we require this argument to be provided as an argument? + nargs_v nargs = 1; // number of arguments to consume + std::optional metavar; // variable name to be used in the help string + std::optional help; // help string to be used in the help string + std::optional> choices; // optional allowed choices for this argument + std::optional default_value; + std::optional const_value; + // dest, storage, value input + std::function dest_func; + }; + + class argument_parser_t + { + friend argument_subparser_t; + + public: + explicit argument_parser_t(const std::optional name = {}, const std::optional usage = {}, + const std::optional description = {}, const std::optional epilogue = {}): + m_name(name), m_usage(usage), m_description(description), m_epilogue(epilogue) + { + } + + template + argument_builder_t& add_flag(const std::string_view arg, Aliases... aliases) + { + static_assert( + std::conjunction_v, std::is_constructible< + std::string_view, Aliases>>...>, + "Arguments must be of type string_view, convertible to string_view or be string_view constructable"); + m_argument_builders.emplace_back(); + m_flag_arguments[arg] = &m_argument_builders.back(); + ((m_flag_arguments[std::string_view{aliases}] = &m_argument_builders.back()), ...); + return m_argument_builders.back(); + } + + argument_builder_t& add_positional(const std::string_view arg) + { + m_argument_builders.emplace_back(); + m_positional_arguments[arg] = &m_argument_builders.back(); + return m_argument_builders.back(); + } + + argument_subparser_t& add_subparser(std::string_view dest); + + void parse(argument_consumer_t& consumer); // NOLINT + + void print_help(); + + argument_parser_t& set_name(const std::string_view name) + { + m_name = name; + return *this; + } + + argument_parser_t& set_usage(const std::string_view usage) + { + m_usage = usage; + return *this; + } + + [[nodiscard]] const std::optional& get_usage() const + { + return m_usage; + } + + argument_parser_t& set_description(const std::string_view description) + { + m_description = description; + return *this; + } + + [[nodiscard]] const std::optional& get_description() const + { + return m_description; + } + + argument_parser_t& set_epilogue(const std::string_view epilogue) + { + m_epilogue = epilogue; + return *this; + } + + [[nodiscard]] const std::optional& get_epilogue() const + { + return m_epilogue; + } + + private: + std::optional m_name; + std::optional m_usage; + std::optional m_description; + std::optional m_epilogue; + std::vector> m_subparsers; + std::vector m_argument_builders; + hashmap_t m_flag_arguments; + hashmap_t m_positional_arguments; + }; + + class argument_subparser_t + { + public: + explicit argument_subparser_t(const argument_parser_t& parent): m_parent(&parent) + { + } + + template + argument_parser_t& add_parser(const std::string_view name, Aliases... aliases) + { + static_assert( + std::conjunction_v, std::is_constructible< + std::string_view, Aliases>>...>, + "Arguments must be of type string_view, convertible to string_view or be string_view constructable"); + m_parsers.emplace(name); + ((m_aliases[std::string_view{aliases}] = &m_parsers[name]), ...); + return m_parsers[name]; + } + + + /** + * Parses the next argument using the provided argument consumer. + * + * This function uses an argument consumer to extract and process the next argument. + * If the argument is a flag or if it cannot be matched against the available parsers, + * an exception is thrown. + * + * @param consumer Reference to an argument_consumer_t object, which handles argument parsing. + * The consumer provides the next argument to be parsed. + * + * @throws detail::subparse_error If the argument is a flag or does not match any known parser. + */ + argument_string_t parse(argument_consumer_t& consumer); // NOLINT + + private: + [[nodiscard]] std::vector> get_allowed_strings() const; + + const argument_parser_t* m_parent; + hashmap_t m_parsers; + hashmap_t m_aliases; + }; +} + +#endif //BLT_PARSE_ARGPARSE_V2_H diff --git a/include/blt/parse/argparse_v2.h b/include/blt/parse/argparse_v2.h index c5f53cf..bb35c98 100644 --- a/include/blt/parse/argparse_v2.h +++ b/include/blt/parse/argparse_v2.h @@ -134,39 +134,17 @@ namespace blt::argparse using arg_t = meta::arg_helper; using arg_vec_t = meta::arg_helper...>; - template - static auto make_lists_only_visitor_per_object_in_vec(const PerObjectAction& action) + template + static auto make_visitor(const DefaultPrimitiveAction& primitive_action, const DefaultListAction& list_action) { return lambda_visitor{ - invalid_option_lambda, - ([&action](std::vector arg_vec) + ([&primitive_action](Args& arg) { - for (const auto& arg : arg_vec) - action(arg_vec, arg); - })... - }; - } - - template - static auto make_lists_only_visitor_on_vec(const DefaultAction& action) - { - return lambda_visitor{ - invalid_option_lambda..., - ([&action](std::vector arg_vec) + return primitive_action(arg); + })..., + ([&list_action](std::vector& arg_vec) { - action(arg_vec); - })... - }; - } - - template - static auto make_reject_lists_visitor_t(const DefaultAction& action) - { - return lambda_visitor{ - invalid_option_lambda...>, - ([&action](Args arg) - { - action(arg); + return list_action(arg_vec); })... }; } diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp index 5ea7735..bcaf666 100644 --- a/src/blt/parse/argparse_v2.cpp +++ b/src/blt/parse/argparse_v2.cpp @@ -18,6 +18,7 @@ #include #include #include +#include namespace blt::argparse { @@ -318,10 +319,12 @@ namespace blt::argparse switch (flag->m_action) { case action_t::STORE: + flag->m_dest_func(dest, parsed_args, args.front()); break; case action_t::APPEND: case action_t::EXTEND: { + break; } case action_t::APPEND_CONST: @@ -334,17 +337,44 @@ namespace blt::argparse if (parsed_args.contains(dest)) { auto& data = parsed_args.m_data[dest]; - if (data.index() != flag->m_const_value->index()) - { - std::cerr << "Constant value for flag '" << arg << "' type doesn't values already present!" << std::endl; - std::exit(1); - } - auto visitor = detail::arg_meta_type_helper_t::make_lists_only_visitor_on_vec([&flag](auto& vec) - { - vec.push_back(std::get>::value_type>(*flag->m_const_value)); - }); + auto visitor = detail::arg_meta_type_helper_t::make_visitor( + [arg](auto& primitive) + { + std::cerr << "Invalid type - '" << arg << "' expected list type, found '" + << blt::type_string() << "' with value " << primitive << std::endl; + std::exit(1); + }, + [&flag, arg](auto& vec) + { + using type = typename meta::remove_cvref_t::value_type; + if (!std::holds_alternative(*flag->m_const_value)) + { + std::cerr << "Constant value for flag '" << arg << + "' type doesn't match values already present! Expected to be of type '" << + blt::type_string() << "'!" << std::endl; + std::exit(1); + } + vec.push_back(std::get(*flag->m_const_value)); + }); std::visit(visitor, data); } + else + { + auto visitor = detail::arg_meta_type_helper_t::make_visitor( + [&flag, &parsed_args, &dest](auto& primitive) + { + std::vector> vec; + vec.push_back(primitive); + parsed_args.m_data.insert({dest, std::move(vec)}); + }, + [](auto&) + { + std::cerr << "Append const should not be a list type!" << std::endl; + std::exit(1); + }); + std::visit(visitor, *flag->m_const_value); + } + break; case action_t::STORE_CONST: std::cerr << "Store const flag called with an argument. This condition doesn't make sense." << std::endl; print_usage(); @@ -358,7 +388,31 @@ namespace blt::argparse print_usage(); std::exit(1); case action_t::COUNT: - parsed_args.m_data.insert({dest, args.size()}); + if (parsed_args.m_data.contains(dest)) + { + auto visitor = detail::arg_meta_type_helper_t::make_visitor( + [&args](auto& primitive) -> detail::arg_data_t + { + using type = meta::remove_cvref_t; + if constexpr (std::is_convertible_v) + { + return primitive + static_cast(args.size()); + } else + { + std::cerr << "Error: count called but stored type is " << blt::type_string() << std::endl; + std::exit(1); + } + }, + [](auto&) -> detail::arg_data_t + { + std::cerr << "List present on count. This condition doesn't make any sense! (How did we get here, please report this!)"; + std::exit(1); + } + ); + parsed_args.m_data[dest] = std::visit(visitor, parsed_args.m_data[dest]); + } + else + parsed_args.m_data.insert({dest, args.size()}); break; case action_t::HELP: print_help(); From 96e5343d02146aaaded015084a3050d8b522b7cf Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Tue, 18 Feb 2025 00:46:30 -0500 Subject: [PATCH 017/101] more work on argparse --- CMakeLists.txt | 2 +- include/blt/parse/argparse_v2.h | 24 ++++++++++++++++++++---- src/blt/parse/argparse_v2.cpp | 21 +++++++++++---------- 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 81e6256..dab0d1e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 4.0.12) +set(BLT_VERSION 4.0.13) set(BLT_TARGET BLT) diff --git a/include/blt/parse/argparse_v2.h b/include/blt/parse/argparse_v2.h index bb35c98..50d84b1 100644 --- a/include/blt/parse/argparse_v2.h +++ b/include/blt/parse/argparse_v2.h @@ -376,10 +376,26 @@ namespace blt::argparse }; m_dest_vec_func = [](const std::string_view dest, argument_storage_t& storage, const std::vector& values) { - std::vector converted_values; - for (const auto& value : values) - converted_values.push_back(detail::arg_string_converter_t::convert(value)); - storage.m_data.insert({dest, converted_values}); + if (storage.m_data.contains(dest)) + { + auto& data = storage.m_data[dest]; + if (!std::holds_alternative>(data)) + { + std::cerr << "Invalid type conversion. Trying to add type " << blt::type_string() << + " but this does not match existing type index '" << data.index() << "'!" << std::endl; + std::exit(1); + } + std::vector& converted_values = std::get>(data); + for (const auto& value : values) + converted_values.push_back(detail::arg_string_converter_t::convert(value)); + } + else + { + std::vector converted_values; + for (const auto& value : values) + converted_values.push_back(detail::arg_string_converter_t::convert(value)); + storage.m_data.insert({dest, converted_values}); + } }; return *this; } diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp index bcaf666..79310dd 100644 --- a/src/blt/parse/argparse_v2.cpp +++ b/src/blt/parse/argparse_v2.cpp @@ -313,24 +313,23 @@ namespace blt::argparse } if (argc == 0) { + } else if (argc == 1) { switch (flag->m_action) { case action_t::STORE: - flag->m_dest_func(dest, parsed_args, args.front()); + flag->m_dest_func(dest, parsed_args, args.front()); break; case action_t::APPEND: case action_t::EXTEND: - { - - break; - } + flag->m_dest_vec_func(dest, parsed_args, args); + break; case action_t::APPEND_CONST: if (flag->m_const_value) { - std::cerr << "Append const chosen as an action but const value not provided for flag '" << arg << '\'' << + std::cerr << "Append const chosen as an action but const value not provided for argument '" << arg << '\'' << std::endl; std::exit(1); } @@ -340,7 +339,7 @@ namespace blt::argparse auto visitor = detail::arg_meta_type_helper_t::make_visitor( [arg](auto& primitive) { - std::cerr << "Invalid type - '" << arg << "' expected list type, found '" + std::cerr << "Invalid type for argument '" << arg << "' expected list type, found '" << blt::type_string() << "' with value " << primitive << std::endl; std::exit(1); }, @@ -349,7 +348,7 @@ namespace blt::argparse using type = typename meta::remove_cvref_t::value_type; if (!std::holds_alternative(*flag->m_const_value)) { - std::cerr << "Constant value for flag '" << arg << + std::cerr << "Constant value for argument '" << arg << "' type doesn't match values already present! Expected to be of type '" << blt::type_string() << "'!" << std::endl; std::exit(1); @@ -397,7 +396,8 @@ namespace blt::argparse if constexpr (std::is_convertible_v) { return primitive + static_cast(args.size()); - } else + } + else { std::cerr << "Error: count called but stored type is " << blt::type_string() << std::endl; std::exit(1); @@ -405,7 +405,8 @@ namespace blt::argparse }, [](auto&) -> detail::arg_data_t { - std::cerr << "List present on count. This condition doesn't make any sense! (How did we get here, please report this!)"; + std::cerr << + "List present on count. This condition doesn't make any sense! (How did we get here, please report this!)"; std::exit(1); } ); From e0d36269bfb64e0c7f35d2a6ecea26512eebdd40 Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Tue, 18 Feb 2025 01:32:26 -0500 Subject: [PATCH 018/101] i made some changes --- CMakeLists.txt | 2 +- include/blt/parse/argparse_v2.h | 2 +- src/blt/parse/argparse_v2.cpp | 221 ++++++++++++++++++-------------- 3 files changed, 129 insertions(+), 96 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dab0d1e..b3bef34 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 4.0.13) +set(BLT_VERSION 4.0.14) set(BLT_TARGET BLT) diff --git a/include/blt/parse/argparse_v2.h b/include/blt/parse/argparse_v2.h index 50d84b1..64c5544 100644 --- a/include/blt/parse/argparse_v2.h +++ b/include/blt/parse/argparse_v2.h @@ -150,7 +150,7 @@ namespace blt::argparse } }; - using arg_meta_type_helper_t = arg_data_helper_t; + using arg_meta_type_helper_t = arg_data_helper_t; using arg_data_t = arg_meta_type_helper_t::variant_t; template diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp index 79310dd..2e82a04 100644 --- a/src/blt/parse/argparse_v2.cpp +++ b/src/blt/parse/argparse_v2.cpp @@ -311,121 +311,154 @@ namespace blt::argparse << std::endl; std::exit(1); } - if (argc == 0) - { - } - else if (argc == 1) + switch (flag->m_action) { - switch (flag->m_action) + case action_t::STORE: + if (argc == 0) { - case action_t::STORE: + std::cerr << "Error: argument '" << arg << + "' action is store but takes in no arguments. This condition is invalid!" << std::endl; + std::exit(1); + } + if (argc == 1) flag->m_dest_func(dest, parsed_args, args.front()); - break; - case action_t::APPEND: - case action_t::EXTEND: - flag->m_dest_vec_func(dest, parsed_args, args); - break; - case action_t::APPEND_CONST: - if (flag->m_const_value) - { - std::cerr << "Append const chosen as an action but const value not provided for argument '" << arg << '\'' << - std::endl; - std::exit(1); - } - if (parsed_args.contains(dest)) - { - auto& data = parsed_args.m_data[dest]; - auto visitor = detail::arg_meta_type_helper_t::make_visitor( - [arg](auto& primitive) + else + { + std::cerr << "Error: argument '" << arg << "' action is store but takes in more than one argument. " << + "This condition is invalid, did you mean to use action_t::APPEND or action_t::EXTEND?" << std::endl; + std::exit(1); + } + break; + case action_t::APPEND: + case action_t::EXTEND: + if (argc == 0) + { + std::cerr << "Error: argument '" << arg << + "' action is append or extend but takes in no arguments. This condition is invalid!" << std::endl; + std::exit(1); + } + flag->m_dest_vec_func(dest, parsed_args, args); + break; + case action_t::APPEND_CONST: + if (argc != 0) + { + std::cerr << "Error: argument '" << arg << "' action is append const but takes in arguments. " + "This condition is invalid!" << std::endl; + std::exit(1); + } + if (flag->m_const_value) + { + std::cerr << "Append const chosen as an action but const value not provided for argument '" << arg << '\'' << + std::endl; + std::exit(1); + } + if (parsed_args.contains(dest)) + { + auto& data = parsed_args.m_data[dest]; + auto visitor = detail::arg_meta_type_helper_t::make_visitor( + [arg](auto& primitive) + { + std::cerr << "Invalid type for argument '" << arg << "' expected list type, found '" + << blt::type_string() << "' with value " << primitive << std::endl; + std::exit(1); + }, + [&flag, arg](auto& vec) + { + using type = typename meta::remove_cvref_t::value_type; + if (!std::holds_alternative(*flag->m_const_value)) { - std::cerr << "Invalid type for argument '" << arg << "' expected list type, found '" - << blt::type_string() << "' with value " << primitive << std::endl; + std::cerr << "Constant value for argument '" << arg << + "' type doesn't match values already present! Expected to be of type '" << + blt::type_string() << "'!" << std::endl; std::exit(1); - }, - [&flag, arg](auto& vec) - { - using type = typename meta::remove_cvref_t::value_type; - if (!std::holds_alternative(*flag->m_const_value)) - { - std::cerr << "Constant value for argument '" << arg << - "' type doesn't match values already present! Expected to be of type '" << - blt::type_string() << "'!" << std::endl; - std::exit(1); - } - vec.push_back(std::get(*flag->m_const_value)); - }); - std::visit(visitor, data); - } - else - { - auto visitor = detail::arg_meta_type_helper_t::make_visitor( - [&flag, &parsed_args, &dest](auto& primitive) - { - std::vector> vec; - vec.push_back(primitive); - parsed_args.m_data.insert({dest, std::move(vec)}); - }, - [](auto&) - { - std::cerr << "Append const should not be a list type!" << std::endl; - std::exit(1); - }); - std::visit(visitor, *flag->m_const_value); - } - break; - case action_t::STORE_CONST: + } + vec.push_back(std::get(*flag->m_const_value)); + }); + std::visit(visitor, data); + } + else + { + auto visitor = detail::arg_meta_type_helper_t::make_visitor( + [&flag, &parsed_args, &dest](auto& primitive) + { + std::vector> vec; + vec.push_back(primitive); + parsed_args.m_data.insert({dest, std::move(vec)}); + }, + [](auto&) + { + std::cerr << "Append const should not be a list type!" << std::endl; + std::exit(1); + }); + std::visit(visitor, *flag->m_const_value); + } + break; + case action_t::STORE_CONST: + if (argc != 0) + { std::cerr << "Store const flag called with an argument. This condition doesn't make sense." << std::endl; print_usage(); std::exit(1); - case action_t::STORE_TRUE: + } + if (!flag->m_const_value) + { + std::cerr << "Store const flag called with no value. This condition doesn't make sense." << std::endl; + std::exit(1); + } + parsed_args.m_data.insert({dest, *flag->m_const_value}); + case action_t::STORE_TRUE: + if (argc != 0) + { std::cerr << "Store true flag called with an argument. This condition doesn't make sense." << std::endl; print_usage(); std::exit(1); - case action_t::STORE_FALSE: + } + parsed_args.m_data.insert({dest, true}); + case action_t::STORE_FALSE: + if (argc != 0) + { std::cerr << "Store false flag called with an argument. This condition doesn't make sense." << std::endl; print_usage(); std::exit(1); - case action_t::COUNT: - if (parsed_args.m_data.contains(dest)) - { - auto visitor = detail::arg_meta_type_helper_t::make_visitor( - [&args](auto& primitive) -> detail::arg_data_t + } + parsed_args.m_data.insert({dest, false}); + case action_t::COUNT: + if (parsed_args.m_data.contains(dest)) + { + auto visitor = detail::arg_meta_type_helper_t::make_visitor( + [&args](auto& primitive) -> detail::arg_data_t + { + using type = meta::remove_cvref_t; + if constexpr (std::is_convertible_v) { - using type = meta::remove_cvref_t; - if constexpr (std::is_convertible_v) - { - return primitive + static_cast(args.size()); - } - else - { - std::cerr << "Error: count called but stored type is " << blt::type_string() << std::endl; - std::exit(1); - } - }, - [](auto&) -> detail::arg_data_t + return primitive + static_cast(args.size()); + } + else { - std::cerr << - "List present on count. This condition doesn't make any sense! (How did we get here, please report this!)"; + std::cerr << "Error: count called but stored type is " << blt::type_string() << std::endl; std::exit(1); } - ); - parsed_args.m_data[dest] = std::visit(visitor, parsed_args.m_data[dest]); - } - else - parsed_args.m_data.insert({dest, args.size()}); - break; - case action_t::HELP: - print_help(); - std::exit(1); - case action_t::VERSION: - print_version(); - break; + }, + [](auto&) -> detail::arg_data_t + { + std::cerr << + "List present on count. This condition doesn't make any sense! (How did we get here, please report this!)"; + std::exit(1); + } + ); + parsed_args.m_data[dest] = std::visit(visitor, parsed_args.m_data[dest]); } - flag->m_dest_func(dest, parsed_args, args.front()); + else + parsed_args.m_data.insert({dest, args.size()}); + break; + case action_t::HELP: + print_help(); + std::exit(1); + case action_t::VERSION: + print_version(); + break; } - else - flag->m_dest_vec_func(dest, parsed_args, args); } }, flag->m_nargs); } From 56611d5aeffdf4df51dd8d7a404737d98c3d9b47 Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Wed, 19 Feb 2025 02:26:31 -0500 Subject: [PATCH 019/101] begin testing, flag work. something is really broken though --- CMakeLists.txt | 2 +- include/blt/parse/argparse_v2.h | 151 ++++++++++++++++--- src/blt/parse/argparse_v2.cpp | 250 +++++++++++++++++++++++--------- 3 files changed, 318 insertions(+), 85 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b3bef34..139afa8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 4.0.14) +set(BLT_VERSION 4.0.15) set(BLT_TARGET BLT) diff --git a/include/blt/parse/argparse_v2.h b/include/blt/parse/argparse_v2.h index 64c5544..b11b3d0 100644 --- a/include/blt/parse/argparse_v2.h +++ b/include/blt/parse/argparse_v2.h @@ -19,6 +19,7 @@ #ifndef BLT_PARSE_ARGPARSE_V2_H #define BLT_PARSE_ARGPARSE_V2_H +#include #include #include #include @@ -89,6 +90,30 @@ namespace blt::argparse } }; + class missing_value_error final : public std::runtime_error + { + public: + explicit missing_value_error(const std::string& message): std::runtime_error(message) + { + } + }; + + class type_error final : public std::runtime_error + { + public: + explicit type_error(const std::string& message): std::runtime_error(message) + { + } + }; + + class unexpected_argument_error final : public std::runtime_error + { + public: + explicit unexpected_argument_error(const std::string& message): std::runtime_error(message) + { + } + }; + class subparse_error final : public std::exception { public: @@ -120,13 +145,6 @@ namespace blt::argparse std::vector> m_allowed_strings; }; - template - constexpr auto invalid_option_lambda = [](const T) - { - std::cerr << "Invalid type - expected list type, found '" << blt::type_string() << "'" << std::endl; - std::exit(1); - }; - template struct arg_data_helper_t { @@ -134,6 +152,9 @@ namespace blt::argparse using arg_t = meta::arg_helper; using arg_vec_t = meta::arg_helper...>; + template + constexpr static bool is_type_stored_v = std::disjunction_v...>; + template static auto make_visitor(const DefaultPrimitiveAction& primitive_action, const DefaultListAction& list_action) { @@ -196,14 +217,13 @@ namespace blt::argparse class argument_string_t { public: - explicit argument_string_t(const char* input, const hashset_t& allowed_flag_prefix): m_argument(input), - m_allowed_flag_prefix(&allowed_flag_prefix) + explicit argument_string_t(const std::string_view input, const hashset_t& allowed_flag_prefix): m_argument(input), + m_allowed_flag_prefix(&allowed_flag_prefix) { - if (input == nullptr) - throw detail::bad_flag("Argument cannot be null!"); process_argument(); } + [[nodiscard]] std::string_view get_flag() const { return m_flag_section; @@ -325,14 +345,14 @@ namespace blt::argparse public: template - const T& get(const std::string_view key) + [[nodiscard]] const T& get(const std::string_view key) const { - return std::get(m_data[key]); + return std::get(m_data.at(key)); } - std::string_view get(const std::string_view key) + [[nodiscard]] std::string_view get(const std::string_view key) const { - return std::get(m_data[key]); + return std::get(m_data.at(key)); } bool contains(const std::string_view key) @@ -340,6 +360,11 @@ namespace blt::argparse return m_data.find(key) != m_data.end(); } + [[nodiscard]] size_t size() const + { + return m_data.size(); + } + private: void add(const argument_storage_t& values) { @@ -370,6 +395,7 @@ namespace blt::argparse template argument_builder_t& as_type() { + static_assert(detail::arg_data_helper_t::template is_type_stored_v, "Type is not valid to be stored/converted as an argument"); m_dest_func = [](const std::string_view dest, argument_storage_t& storage, std::string_view value) { storage.m_data.insert({dest, detail::arg_string_converter_t::convert(value)}); @@ -381,9 +407,8 @@ namespace blt::argparse auto& data = storage.m_data[dest]; if (!std::holds_alternative>(data)) { - std::cerr << "Invalid type conversion. Trying to add type " << blt::type_string() << - " but this does not match existing type index '" << data.index() << "'!" << std::endl; - std::exit(1); + throw detail::type_error("Invalid type conversion. Trying to add type " + blt::type_string() + + " but this does not match existing type index '" + std::to_string(data.index()) + "'!"); } std::vector& converted_values = std::get>(data); for (const auto& value : values) @@ -400,6 +425,75 @@ namespace blt::argparse return *this; } + argument_builder_t& set_action(const action_t action) + { + m_action = action; + switch (m_action) + { + case action_t::STORE_TRUE: + set_nargs(0); + as_type(); + set_default(false); + break; + case action_t::STORE_FALSE: + set_nargs(0); + as_type(); + set_default(true); + break; + default: + break; + } + return *this; + } + + argument_builder_t& set_required(const bool required) + { + m_required = required; + return *this; + } + + argument_builder_t& set_nargs(const nargs_v nargs) + { + m_nargs = nargs; + return *this; + } + + argument_builder_t& set_metavar(const std::string& metavar) + { + m_metavar = metavar; + return *this; + } + + argument_builder_t& set_help(const std::string& help) + { + m_help = help; + return *this; + } + + argument_builder_t& set_choices(const std::vector& choices) + { + m_choices = choices; + return *this; + } + + argument_builder_t& set_default(const detail::arg_data_t& default_value) + { + m_default_value = default_value; + return *this; + } + + argument_builder_t& set_const(const detail::arg_data_t& const_value) + { + m_const_value = const_value; + return *this; + } + + argument_builder_t& set_dest(const std::string& dest) + { + m_dest = dest; + return *this; + } + private: action_t m_action = action_t::STORE; bool m_required = false; // do we require this argument to be provided as an argument? @@ -436,7 +530,7 @@ namespace blt::argparse "Arguments must be of type string_view, convertible to string_view or be string_view constructable"); m_argument_builders.emplace_back(); m_flag_arguments.insert({arg, &m_argument_builders.back()}); - ((m_flag_arguments[std::string_view{aliases}] = &m_argument_builders.back()), ...); + (m_flag_arguments.insert({std::string_view{aliases}, &m_argument_builders.back()}), ...); return m_argument_builders.back(); } @@ -451,6 +545,24 @@ namespace blt::argparse argument_storage_t parse(argument_consumer_t& consumer); // NOLINT + argument_storage_t parse(const std::vector& args) + { + std::vector arg_strings; + for (const auto& arg : args) + arg_strings.emplace_back(arg, allowed_flag_prefixes); + argument_consumer_t consumer{arg_strings}; + return parse(consumer); + } + + argument_storage_t parse(const int argc, const char** argv) + { + std::vector arg_strings; + for (int i = 0; i < argc; ++i) + arg_strings.emplace_back(argv[i], allowed_flag_prefixes); + argument_consumer_t consumer{arg_strings}; + return parse(consumer); + } + void print_help(); void print_usage(); @@ -511,6 +623,7 @@ namespace blt::argparse std::vector m_argument_builders; hashmap_t m_flag_arguments; hashmap_t m_positional_arguments; + hashset_t allowed_flag_prefixes = {'-', '+', '/'}; }; class argument_subparser_t diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp index 2e82a04..d6b7f8e 100644 --- a/src/blt/parse/argparse_v2.cpp +++ b/src/blt/parse/argparse_v2.cpp @@ -19,9 +19,49 @@ #include #include #include +#include namespace blt::argparse { + template + size_t get_const_char_size(const T& t) + { + if constexpr (std::is_convertible_v) + { + return std::char_traits::length(t); + } + else if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) + { + return 1; + } + else if constexpr (std::is_same_v || std::is_same_v) + { + return t.size(); + } + else + { + return 0; + } + } + + template + auto ensure_is_string(T&& t) + { + if constexpr (std::is_arithmetic_v>) + return std::to_string(std::forward(t)); + else + return std::forward(t); + } + + template + std::string make_string(Strings&&... strings) + { + std::string out; + out.reserve((get_const_char_size(strings) + ...)); + ((out += ensure_is_string(std::forward(strings))), ...); + return out; + } + namespace detail { // Unit Tests for class argument_string_t @@ -134,9 +174,113 @@ namespace blt::argparse test_argument_string_t_double_plus(prefixes); } + void test_argparse_empty() + { + std::vector const argv; + argument_parser_t parser; + const auto args = parser.parse(argv); + BLT_ASSERT(args.size() == 0 && "Empty argparse should have no args on output"); + } + + void test_single_flag_prefixes() + { + argument_parser_t parser; + parser.add_flag("-a").set_action(action_t::STORE_TRUE); + parser.add_flag("+b").set_action(action_t::STORE_FALSE); + parser.add_flag("/c").as_type().set_action(action_t::STORE); + + const std::vector args = {"-a", "+b", "/c", "42"}; + const auto parsed_args = parser.parse(args); + + BLT_ASSERT(parsed_args.get("-a") == true && "Flag '-a' should store `true`"); + BLT_ASSERT(parsed_args.get("+b") == false && "Flag '+b' should store `false`"); + BLT_ASSERT(parsed_args.get("/c") == 42 && "Flag '/c' should store the value 42"); + } + + // Test: Invalid flag prefixes + void test_invalid_flag_prefixes() + { + argument_parser_t parser; + parser.add_flag("-a"); + parser.add_flag("+b"); + parser.add_flag("/c"); + + const std::vector args = {"!d", "-a"}; + try + { + parser.parse(args); + BLT_ASSERT(false && "Parsing should fail with invalid flag prefix '!'"); + } + catch (const bad_flag& _) + { + BLT_ASSERT(true && "Correctly threw on bad flag prefix"); + } + } + + void test_compound_flags() + { + argument_parser_t parser; + parser.add_flag("-v").as_type().set_action(action_t::COUNT); + + const std::vector args = {"-vvv"}; + const auto parsed_args = parser.parse(args); + + BLT_ASSERT(parsed_args.get("-v") == 3 && "Flag '-v' should count occurrences in compound form"); + } + + void test_combination_of_valid_and_invalid_flags() + { + using namespace argparse; + + argument_parser_t parser; + parser.add_flag("-x").as_type(); + parser.add_flag("/y").as_type(); + + const std::vector args = {"-x", "10", "!z", "/y", "value"}; + try + { + parser.parse(args); + BLT_ASSERT(false && "Parsing should fail due to invalid flag '!z'"); + } + catch (const bad_flag& _) + { + BLT_ASSERT(true && "Correctly threw an exception for invalid flag"); + } + } + + void test_flags_with_different_actions() + { + using namespace argparse; + + argument_parser_t parser; + parser.add_flag("-k").as_type().set_action(action_t::STORE); // STORE action + parser.add_flag("-t").as_type().set_action(action_t::STORE_CONST).set_const(999); // STORE_CONST action + parser.add_flag("-f").set_action(action_t::STORE_FALSE); // STORE_FALSE action + parser.add_flag("-c").set_action(action_t::STORE_TRUE); // STORE_TRUE action + + const std::vector args = {"-k", "100", "-t", "-f", "-c"}; + const auto parsed_args = parser.parse(args); + + BLT_ASSERT(parsed_args.get("-k") == 100 && "Flag '-k' should store 100"); + BLT_ASSERT(parsed_args.get("-t") == 999 && "Flag '-t' should store a const value of 999"); + BLT_ASSERT(parsed_args.get("-f") == false && "Flag '-f' should store `false`"); + BLT_ASSERT(parsed_args.get("-c") == true && "Flag '-c' should store `true`"); + } + + void run_argparse_flag_tests() + { + test_single_flag_prefixes(); + test_invalid_flag_prefixes(); + test_compound_flags(); + test_combination_of_valid_and_invalid_flags(); + test_flags_with_different_actions(); + } + void test() { run_all_tests_argument_string_t(); + test_argparse_empty(); + run_argparse_flag_tests(); } [[nodiscard]] std::string subparse_error::error_string() const @@ -183,10 +327,7 @@ namespace blt::argparse const auto key = consumer.consume(); const auto flag = m_flag_arguments.find(key.get_argument()); if (flag == m_flag_arguments.end()) - { - std::cerr << "Error: Unknown flag: " << key.get_argument() << std::endl; - exit(1); - } + throw detail::bad_flag(make_string("Error: Unknown flag: ", key.get_argument())); found_flags.insert(key.get_argument()); parse_flag(parsed_args, consumer, key.get_argument()); } @@ -255,7 +396,7 @@ namespace blt::argparse void argument_parser_t::parse_flag(argument_storage_t& parsed_args, argument_consumer_t& consumer, const std::string_view arg) { - auto& flag = m_flag_arguments[arg]; + auto flag = m_flag_arguments[arg]; auto dest = flag->m_dest.value_or(std::string{arg}); std::visit(lambda_visitor{ [&parsed_args, &consumer, &dest, &flag, arg](const nargs_t arg_enum) @@ -273,7 +414,8 @@ namespace blt::argparse break; case nargs_t::ALL_AT_LEAST_ONE: if (!consumer.can_consume()) - std::cerr << "Error expected at least one argument to be consumed by '" << arg << '\'' << std::endl; + throw detail::missing_argument_error( + make_string("Error expected at least one argument to be consumed by '", arg, '\'')); [[fallthrough]]; case nargs_t::ALL: std::vector args; @@ -290,68 +432,56 @@ namespace blt::argparse { if (!consumer.can_consume()) { - std::cerr << "Error expected " << argc << " arguments to be consumed by '" << arg << "' but found " << i << - std::endl; - std::exit(1); + throw detail::missing_argument_error( + make_string("Expected ", argc, " arguments to be consumed by '", arg, "' but found ", i)); } if (consumer.peek().is_flag()) { - std::cerr << "Error expected " << argc << " arguments to be consumed by '" << arg << "' but found a flag '" << - consumer.peek().get_argument() << "' instead!" << std::endl; - std::exit(1); + throw detail::unexpected_argument_error(make_string( + "Expected ", argc, " arguments to be consumed by '", arg, "' but found a flag '", + consumer.peek().get_argument(), "' instead!" + )); } args.push_back(consumer.consume().get_argument()); } if (args.size() != static_cast(argc)) { - std::cerr << + throw std::runtime_error( "This error condition should not be possible. " "Args consumed didn't equal the arguments requested and previous checks didn't fail. " - "Please report as an issue on the GitHub" - << std::endl; - std::exit(1); + "Please report as an issue on the GitHub"); } + BLT_TRACE("Running action %d on dest %s", static_cast(flag->m_action), dest.c_str()); + switch (flag->m_action) { case action_t::STORE: if (argc == 0) - { - std::cerr << "Error: argument '" << arg << - "' action is store but takes in no arguments. This condition is invalid!" << std::endl; - std::exit(1); - } + throw detail::missing_argument_error( + make_string("Argument '", arg, "'s action is store but takes in no arguments?")); if (argc == 1) flag->m_dest_func(dest, parsed_args, args.front()); else - { - std::cerr << "Error: argument '" << arg << "' action is store but takes in more than one argument. " << - "This condition is invalid, did you mean to use action_t::APPEND or action_t::EXTEND?" << std::endl; - std::exit(1); - } + throw detail::unexpected_argument_error(make_string("Argument '", arg, + "'s action is store but takes in more than one argument. " + "Did you mean to use action_t::APPEND or action_t::EXTEND?")); break; case action_t::APPEND: case action_t::EXTEND: if (argc == 0) - { - std::cerr << "Error: argument '" << arg << - "' action is append or extend but takes in no arguments. This condition is invalid!" << std::endl; - std::exit(1); - } + throw detail::missing_argument_error( + make_string("Argument '", arg, "'s action is append or extend but takes in no arguments.")); flag->m_dest_vec_func(dest, parsed_args, args); break; case action_t::APPEND_CONST: if (argc != 0) - { - std::cerr << "Error: argument '" << arg << "' action is append const but takes in arguments. " - "This condition is invalid!" << std::endl; - std::exit(1); - } + throw detail::unexpected_argument_error( + make_string("Argument '", arg, "'s action is append const but takes in arguments.")); if (flag->m_const_value) { - std::cerr << "Append const chosen as an action but const value not provided for argument '" << arg << '\'' << - std::endl; - std::exit(1); + throw detail::missing_value_error( + make_string("Append const chosen as an action but const value not provided for argument '", arg, '\'')); } if (parsed_args.contains(dest)) { @@ -359,19 +489,17 @@ namespace blt::argparse auto visitor = detail::arg_meta_type_helper_t::make_visitor( [arg](auto& primitive) { - std::cerr << "Invalid type for argument '" << arg << "' expected list type, found '" - << blt::type_string() << "' with value " << primitive << std::endl; - std::exit(1); + throw detail::type_error(make_string("Invalid type for argument '", arg, "' expected list type, found '", + blt::type_string(), "' with value ", primitive)); }, [&flag, arg](auto& vec) { using type = typename meta::remove_cvref_t::value_type; if (!std::holds_alternative(*flag->m_const_value)) { - std::cerr << "Constant value for argument '" << arg << - "' type doesn't match values already present! Expected to be of type '" << - blt::type_string() << "'!" << std::endl; - std::exit(1); + throw detail::type_error(make_string("Constant value for argument '", arg, + "' type doesn't match values already present! Expected to be of type '", + blt::type_string(), "'!")); } vec.push_back(std::get(*flag->m_const_value)); }); @@ -388,8 +516,7 @@ namespace blt::argparse }, [](auto&) { - std::cerr << "Append const should not be a list type!" << std::endl; - std::exit(1); + throw detail::type_error("Append const should not be a list type!"); }); std::visit(visitor, *flag->m_const_value); } @@ -397,32 +524,29 @@ namespace blt::argparse case action_t::STORE_CONST: if (argc != 0) { - std::cerr << "Store const flag called with an argument. This condition doesn't make sense." << std::endl; print_usage(); - std::exit(1); + throw detail::unexpected_argument_error("Store const flag called with an argument."); } if (!flag->m_const_value) - { - std::cerr << "Store const flag called with no value. This condition doesn't make sense." << std::endl; - std::exit(1); - } + throw detail::missing_value_error("Store const flag called with no value. "); parsed_args.m_data.insert({dest, *flag->m_const_value}); + break; case action_t::STORE_TRUE: if (argc != 0) { - std::cerr << "Store true flag called with an argument. This condition doesn't make sense." << std::endl; print_usage(); - std::exit(1); + throw detail::unexpected_argument_error("Store true flag called with an argument."); } parsed_args.m_data.insert({dest, true}); + break; case action_t::STORE_FALSE: if (argc != 0) { - std::cerr << "Store false flag called with an argument. This condition doesn't make sense." << std::endl; print_usage(); - std::exit(1); + throw detail::unexpected_argument_error("Store false flag called with an argument."); } parsed_args.m_data.insert({dest, false}); + break; case action_t::COUNT: if (parsed_args.m_data.contains(dest)) { @@ -435,16 +559,12 @@ namespace blt::argparse return primitive + static_cast(args.size()); } else - { - std::cerr << "Error: count called but stored type is " << blt::type_string() << std::endl; - std::exit(1); - } + throw detail::type_error("Error: count called but stored type is " + blt::type_string()); }, [](auto&) -> detail::arg_data_t { - std::cerr << - "List present on count. This condition doesn't make any sense! (How did we get here, please report this!)"; - std::exit(1); + throw detail::type_error("List present on count. This condition doesn't make any sense! " + "(How did we get here, please report this!)"); } ); parsed_args.m_data[dest] = std::visit(visitor, parsed_args.m_data[dest]); From c23759ac6d91d52f9fad0e193a4ad8ddf99e68cf Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Wed, 19 Feb 2025 13:28:58 -0500 Subject: [PATCH 020/101] fix a lot of bugs, current testing works --- CMakeLists.txt | 2 +- include/blt/parse/argparse_v2.h | 71 ++++++++++---- include/blt/std/memory_util.h | 2 +- src/blt/parse/argparse_v2.cpp | 166 +++++++++++++++++++------------- 4 files changed, 153 insertions(+), 88 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 139afa8..e054b89 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 4.0.15) +set(BLT_VERSION 4.0.16) set(BLT_TARGET BLT) diff --git a/include/blt/parse/argparse_v2.h b/include/blt/parse/argparse_v2.h index b11b3d0..a49824d 100644 --- a/include/blt/parse/argparse_v2.h +++ b/include/blt/parse/argparse_v2.h @@ -76,8 +76,12 @@ namespace blt::argparse explicit bad_flag(const std::string& message): std::runtime_error(message) { } + }; - explicit bad_flag(const char* message): std::runtime_error(message) + class bad_positional final : public std::runtime_error + { + public: + explicit bad_positional(const std::string& message): std::runtime_error(message) { } }; @@ -114,6 +118,14 @@ namespace blt::argparse } }; + class bad_choice_error final : public std::runtime_error + { + public: + explicit bad_choice_error(const std::string& message): std::runtime_error(message) + { + } + }; + class subparse_error final : public std::exception { public: @@ -137,10 +149,12 @@ namespace blt::argparse [[nodiscard]] const char* what() const noexcept override { - return "Please use error_string() method instead of what(). This exception should *always* be caught!"; + m_error_string = error_string(); + return m_error_string.c_str(); } private: + mutable std::string m_error_string; std::string_view m_found_string; std::vector> m_allowed_strings; }; @@ -372,7 +386,7 @@ namespace blt::argparse m_data.insert(value); } - hashmap_t m_data; + hashmap_t m_data; }; class argument_builder_t @@ -384,11 +398,11 @@ namespace blt::argparse { m_dest_func = [](const std::string_view dest, argument_storage_t& storage, std::string_view value) { - storage.m_data.insert({dest, value}); + storage.m_data.emplace(std::string{dest}, value); }; m_dest_vec_func = [](const std::string_view dest, argument_storage_t& storage, const std::vector& values) { - storage.m_data.insert({dest, values}); + storage.m_data.emplace(std::string{dest}, values); }; } @@ -398,7 +412,7 @@ namespace blt::argparse static_assert(detail::arg_data_helper_t::template is_type_stored_v, "Type is not valid to be stored/converted as an argument"); m_dest_func = [](const std::string_view dest, argument_storage_t& storage, std::string_view value) { - storage.m_data.insert({dest, detail::arg_string_converter_t::convert(value)}); + storage.m_data.emplace(std::string{dest}, detail::arg_string_converter_t::convert(value)); }; m_dest_vec_func = [](const std::string_view dest, argument_storage_t& storage, const std::vector& values) { @@ -410,7 +424,7 @@ namespace blt::argparse throw detail::type_error("Invalid type conversion. Trying to add type " + blt::type_string() + " but this does not match existing type index '" + std::to_string(data.index()) + "'!"); } - std::vector& converted_values = std::get>(data); + auto& converted_values = std::get>(data); for (const auto& value : values) converted_values.push_back(detail::arg_string_converter_t::convert(value)); } @@ -419,7 +433,7 @@ namespace blt::argparse std::vector converted_values; for (const auto& value : values) converted_values.push_back(detail::arg_string_converter_t::convert(value)); - storage.m_data.insert({dest, converted_values}); + storage.m_data.emplace(std::string{dest}, std::move(converted_values)); } }; return *this; @@ -440,6 +454,13 @@ namespace blt::argparse as_type(); set_default(true); break; + case action_t::STORE_CONST: + set_nargs(0); + break; + case action_t::COUNT: + set_nargs(0); + as_type(); + break; default: break; } @@ -471,6 +492,14 @@ namespace blt::argparse } argument_builder_t& set_choices(const std::vector& choices) + { + m_choices = hashset_t{}; + for (const auto& choice : choices) + m_choices->emplace(choice); + return *this; + } + + argument_builder_t& set_choices(const hashset_t& choices) { m_choices = choices; return *this; @@ -500,7 +529,7 @@ namespace blt::argparse nargs_v m_nargs = 1; // number of arguments to consume std::optional m_metavar; // variable name to be used in the help string std::optional m_help; // help string to be used in the help string - std::optional> m_choices; // optional allowed choices for this argument + std::optional> m_choices; // optional allowed choices for this argument std::optional m_default_value; std::optional m_const_value; std::optional m_dest; @@ -525,20 +554,20 @@ namespace blt::argparse argument_builder_t& add_flag(const std::string_view arg, Aliases... aliases) { static_assert( - std::conjunction_v, std::is_constructible< - std::string_view, Aliases>>...>, + std::conjunction_v, std::is_constructible< + std::string, Aliases>>...>, "Arguments must be of type string_view, convertible to string_view or be string_view constructable"); - m_argument_builders.emplace_back(); - m_flag_arguments.insert({arg, &m_argument_builders.back()}); - (m_flag_arguments.insert({std::string_view{aliases}, &m_argument_builders.back()}), ...); - return m_argument_builders.back(); + m_argument_builders.emplace_back(std::make_unique()); + m_flag_arguments.emplace(arg, m_argument_builders.back().get()); + (m_flag_arguments.emplace(aliases, m_argument_builders.back().get()), ...); + return *m_argument_builders.back().get(); } argument_builder_t& add_positional(const std::string_view arg) { - m_argument_builders.emplace_back(); - m_positional_arguments.insert({arg, &m_argument_builders.back()}); - return m_argument_builders.back(); + m_argument_builders.emplace_back(std::make_unique()); + m_positional_arguments.emplace(arg, m_argument_builders.back().get()); + return *m_argument_builders.back(); } argument_subparser_t& add_subparser(std::string_view dest); @@ -609,10 +638,12 @@ namespace blt::argparse } private: + void handle_compound_flags(hashset_t& found_flags, argument_storage_t& parsed_args, argument_consumer_t& consumer, + const argument_string_t& arg); void parse_flag(argument_storage_t& parsed_args, argument_consumer_t& consumer, std::string_view arg); void parse_positional(argument_storage_t& parsed_args, argument_consumer_t& consumer, std::string_view arg); static void handle_missing_and_default_args(hashmap_t& arguments, - const hashset_t& found, + const hashset_t& found, argument_storage_t& parsed_args, std::string_view type); std::optional m_name; @@ -620,7 +651,7 @@ namespace blt::argparse std::optional m_description; std::optional m_epilogue; std::vector> m_subparsers; - std::vector m_argument_builders; + std::vector> m_argument_builders; hashmap_t m_flag_arguments; hashmap_t m_positional_arguments; hashset_t allowed_flag_prefixes = {'-', '+', '/'}; diff --git a/include/blt/std/memory_util.h b/include/blt/std/memory_util.h index 2c73912..c2041a4 100644 --- a/include/blt/std/memory_util.h +++ b/include/blt/std/memory_util.h @@ -134,7 +134,7 @@ namespace blt::mem return fromBytes(in, *out); } - static std::size_t next_byte_allocation(std::size_t prev_size, std::size_t default_allocation_block = 8192, std::size_t default_size = 16) + inline std::size_t next_byte_allocation(std::size_t prev_size, std::size_t default_allocation_block = 8192, std::size_t default_size = 16) { if (prev_size < default_size) return default_size; diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp index d6b7f8e..28c56a3 100644 --- a/src/blt/parse/argparse_v2.cpp +++ b/src/blt/parse/argparse_v2.cpp @@ -23,6 +23,25 @@ namespace blt::argparse { + constexpr static auto printer_primitive = [](const auto& v) + { + std::cout << v; + }; + + constexpr static auto printer_vector = [](const auto& v) + { + std::cout << "["; + for (const auto& [i, a] : enumerate(v)) + { + std::cout << a; + if (i != v.size() - 1) + std::cout << ", "; + } + std::cout << "]"; + }; + + auto print_visitor = detail::arg_meta_type_helper_t::make_visitor(printer_primitive, printer_vector); + template size_t get_const_char_size(const T& t) { @@ -44,7 +63,7 @@ namespace blt::argparse } } - template + template auto ensure_is_string(T&& t) { if constexpr (std::is_arithmetic_v>) @@ -211,7 +230,7 @@ namespace blt::argparse parser.parse(args); BLT_ASSERT(false && "Parsing should fail with invalid flag prefix '!'"); } - catch (const bad_flag& _) + catch (...) { BLT_ASSERT(true && "Correctly threw on bad flag prefix"); } @@ -225,7 +244,7 @@ namespace blt::argparse const std::vector args = {"-vvv"}; const auto parsed_args = parser.parse(args); - BLT_ASSERT(parsed_args.get("-v") == 3 && "Flag '-v' should count occurrences in compound form"); + BLT_ASSERT(parsed_args.get("-v") == 3 && "Flag '-v' should count occurrences in compound form"); } void test_combination_of_valid_and_invalid_flags() @@ -242,7 +261,7 @@ namespace blt::argparse parser.parse(args); BLT_ASSERT(false && "Parsing should fail due to invalid flag '!z'"); } - catch (const bad_flag& _) + catch (...) { BLT_ASSERT(true && "Correctly threw an exception for invalid flag"); } @@ -318,61 +337,31 @@ namespace blt::argparse argument_storage_t argument_parser_t::parse(argument_consumer_t& consumer) { - hashset_t found_flags; - hashset_t found_positional; + hashset_t found_flags; + hashset_t found_positional; argument_storage_t parsed_args; // first, we consume flags which may be part of this parser while (consumer.can_consume() && consumer.peek().is_flag()) + handle_compound_flags(found_flags, parsed_args, consumer, consumer.consume()); + + for (auto& [key, subparser] : m_subparsers) { - const auto key = consumer.consume(); - const auto flag = m_flag_arguments.find(key.get_argument()); - if (flag == m_flag_arguments.end()) - throw detail::bad_flag(make_string("Error: Unknown flag: ", key.get_argument())); - found_flags.insert(key.get_argument()); - parse_flag(parsed_args, consumer, key.get_argument()); - } - try - { - for (auto& [key, subparser] : m_subparsers) - { - auto [parsed_subparser, storage] = subparser.parse(consumer); - storage.m_data.insert({key, detail::arg_data_t{parsed_subparser.get_argument()}}); - parsed_args.add(storage); - } - } - catch (const detail::missing_argument_error& e) - { - std::cerr << "Error: " << e.what() << std::endl; - print_usage(); - exit(1); - } catch (const detail::subparse_error& e) - { - std::cerr << e.error_string() << std::endl; - exit(1); + auto [parsed_subparser, storage] = subparser.parse(consumer); + storage.m_data.emplace(std::string{key}, detail::arg_data_t{parsed_subparser.get_argument()}); + parsed_args.add(storage); } + while (consumer.can_consume()) { const auto key = consumer.consume(); if (key.is_flag()) - { - const auto flag = m_flag_arguments.find(key.get_argument()); - if (flag == m_flag_arguments.end()) - { - std::cerr << "Error: Unknown flag: " << key.get_argument() << std::endl; - exit(1); - } - found_flags.insert(key.get_argument()); - parse_flag(parsed_args, consumer, key.get_argument()); - } + handle_compound_flags(found_flags, parsed_args, consumer, key); else { const auto pos = m_positional_arguments.find(key.get_argument()); if (pos == m_positional_arguments.end()) - { - std::cerr << "Error: Unknown positional argument: " << key.get_argument() << std::endl; - exit(1); - } - found_positional.insert(key.get_argument()); + throw detail::bad_positional(make_string("Error: Unknown positional argument: ", key.get_argument())); + found_positional.insert(std::string{key.get_argument()}); parse_positional(parsed_args, consumer, key.get_argument()); } } @@ -394,10 +383,37 @@ namespace blt::argparse { } + void argument_parser_t::handle_compound_flags(hashset_t& found_flags, argument_storage_t& parsed_args, + argument_consumer_t& consumer, const argument_string_t& arg) + { + // i kinda hate this, TODO? + std::vector compound_flags; + if (arg.get_flag().size() == 1) + { + for (const auto c : arg.get_name()) + compound_flags.emplace_back(std::string{arg.get_flag()} + c); + } + else + { + if (arg.get_flag().size() > 2) + throw detail::bad_flag(make_string("Error: Flag '", arg.get_argument(), "' is too long!")); + compound_flags.emplace_back(arg.get_argument()); + } + + for (const auto& flag_key : compound_flags) + { + const auto flag = m_flag_arguments.find(flag_key); + if (flag == m_flag_arguments.end()) + throw detail::bad_flag(make_string("Error: Unknown flag: ", flag_key)); + found_flags.insert(flag_key); + parse_flag(parsed_args, consumer, flag_key); + } + } + void argument_parser_t::parse_flag(argument_storage_t& parsed_args, argument_consumer_t& consumer, const std::string_view arg) { - auto flag = m_flag_arguments[arg]; - auto dest = flag->m_dest.value_or(std::string{arg}); + auto flag = m_flag_arguments.find(arg)->second; + const auto dest = flag->m_dest.value_or(std::string{arg}); std::visit(lambda_visitor{ [&parsed_args, &consumer, &dest, &flag, arg](const nargs_t arg_enum) { @@ -444,6 +460,26 @@ namespace blt::argparse } args.push_back(consumer.consume().get_argument()); } + if (flag->m_choices) + { + auto& choices = *flag->m_choices; + for (const auto str : args) + { + if (!choices.contains(str)) + { + std::string valid_choices = "{"; + for (const auto& [i, choice] : enumerate(choices)) + { + valid_choices += choice; + if (i != choices.size() - 1) + valid_choices += ", "; + } + valid_choices += "}"; + throw detail::bad_choice_error(make_string('\'', str, "' is not a valid choice for argument '", arg, + "'! Expected one of ", valid_choices)); + } + } + } if (args.size() != static_cast(argc)) { throw std::runtime_error( @@ -452,8 +488,6 @@ namespace blt::argparse "Please report as an issue on the GitHub"); } - BLT_TRACE("Running action %d on dest %s", static_cast(flag->m_action), dest.c_str()); - switch (flag->m_action) { case action_t::STORE: @@ -508,7 +542,7 @@ namespace blt::argparse else { auto visitor = detail::arg_meta_type_helper_t::make_visitor( - [&flag, &parsed_args, &dest](auto& primitive) + [&parsed_args, &dest](auto& primitive) { std::vector> vec; vec.push_back(primitive); @@ -525,11 +559,13 @@ namespace blt::argparse if (argc != 0) { print_usage(); - throw detail::unexpected_argument_error("Store const flag called with an argument."); + throw detail::unexpected_argument_error( + make_string("Argument '", arg, "' is store const but called with an argument.")); } if (!flag->m_const_value) - throw detail::missing_value_error("Store const flag called with no value. "); - parsed_args.m_data.insert({dest, *flag->m_const_value}); + throw detail::missing_value_error( + make_string("Argument '", arg, "' is store const, but const storage has no value.")); + parsed_args.m_data.emplace(dest, *flag->m_const_value); break; case action_t::STORE_TRUE: if (argc != 0) @@ -537,7 +573,7 @@ namespace blt::argparse print_usage(); throw detail::unexpected_argument_error("Store true flag called with an argument."); } - parsed_args.m_data.insert({dest, true}); + parsed_args.m_data.emplace(dest, true); break; case action_t::STORE_FALSE: if (argc != 0) @@ -551,12 +587,12 @@ namespace blt::argparse if (parsed_args.m_data.contains(dest)) { auto visitor = detail::arg_meta_type_helper_t::make_visitor( - [&args](auto& primitive) -> detail::arg_data_t + [](auto& primitive) -> detail::arg_data_t { using type = meta::remove_cvref_t; - if constexpr (std::is_convertible_v) + if constexpr (std::is_convertible_v) { - return primitive + static_cast(args.size()); + return primitive + static_cast(1); } else throw detail::type_error("Error: count called but stored type is " + blt::type_string()); @@ -569,8 +605,8 @@ namespace blt::argparse ); parsed_args.m_data[dest] = std::visit(visitor, parsed_args.m_data[dest]); } - else - parsed_args.m_data.insert({dest, args.size()}); + else // I also hate this! + flag->m_dest_func(dest, parsed_args, "1"); break; case action_t::HELP: print_help(); @@ -588,7 +624,7 @@ namespace blt::argparse } void argument_parser_t::handle_missing_and_default_args(hashmap_t& arguments, - const hashset_t& found, argument_storage_t& parsed_args, + const hashset_t& found, argument_storage_t& parsed_args, const std::string_view type) { for (const auto& [key, value] : arguments) @@ -596,13 +632,11 @@ namespace blt::argparse if (!found.contains(key)) { if (value->m_required) - { - std::cerr << "Error: " << type << " argument '" << key << "' was not found but is required by the program" << std::endl; - exit(1); - } + throw detail::missing_argument_error(make_string("Error: ", type, " argument '", key, + "' was not found but is required by the program")); auto dest = value->m_dest.value_or(std::string{key}); if (value->m_default_value && !parsed_args.contains(dest)) - parsed_args.m_data.insert({dest, *value->m_default_value}); + parsed_args.m_data.emplace(dest, *value->m_default_value); } } } From 5f9ea32671564efaf036f6bf3f6b6d6d6a1629ce Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Wed, 19 Feb 2025 16:29:17 -0500 Subject: [PATCH 021/101] idk what changed --- CMakeLists.txt | 2 +- src/blt/parse/argparse_v2.cpp | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e054b89..3105c43 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 4.0.16) +set(BLT_VERSION 4.0.17) set(BLT_TARGET BLT) diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp index 28c56a3..832263c 100644 --- a/src/blt/parse/argparse_v2.cpp +++ b/src/blt/parse/argparse_v2.cpp @@ -453,10 +453,9 @@ namespace blt::argparse } if (consumer.peek().is_flag()) { - throw detail::unexpected_argument_error(make_string( - "Expected ", argc, " arguments to be consumed by '", arg, "' but found a flag '", - consumer.peek().get_argument(), "' instead!" - )); + std::cout << "Warning: arg '" << arg << "' expects " << argc << + " arguments to be consumed but we found a flag '" << consumer.peek(). + get_argument() << "'. We will comply as this may be desired if this argument is a file." << std::endl; } args.push_back(consumer.consume().get_argument()); } From 188e9dba88c8a9b8d744471230b735ee0e903f52 Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Wed, 19 Feb 2025 20:52:55 -0500 Subject: [PATCH 022/101] postionals may work now? --- CMakeLists.txt | 2 +- include/blt/parse/argparse_v2.h | 13 +- src/blt/parse/argparse_v2.cpp | 206 ++++++++++++++++++++++++-------- 3 files changed, 165 insertions(+), 56 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3105c43..c902cf8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 4.0.17) +set(BLT_VERSION 4.0.18) set(BLT_TARGET BLT) diff --git a/include/blt/parse/argparse_v2.h b/include/blt/parse/argparse_v2.h index a49824d..9da2900 100644 --- a/include/blt/parse/argparse_v2.h +++ b/include/blt/parse/argparse_v2.h @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -455,12 +456,17 @@ namespace blt::argparse set_default(true); break; case action_t::STORE_CONST: + case action_t::APPEND_CONST: set_nargs(0); break; case action_t::COUNT: set_nargs(0); as_type(); break; + case action_t::HELP: + case action_t::VERSION: + set_nargs(0); + break; default: break; } @@ -643,8 +649,11 @@ namespace blt::argparse void parse_flag(argument_storage_t& parsed_args, argument_consumer_t& consumer, std::string_view arg); void parse_positional(argument_storage_t& parsed_args, argument_consumer_t& consumer, std::string_view arg); static void handle_missing_and_default_args(hashmap_t& arguments, - const hashset_t& found, - argument_storage_t& parsed_args, std::string_view type); + const hashset_t& found, argument_storage_t& parsed_args, std::string_view type); + static expected, std::string> consume_until_flag_or_end(argument_consumer_t& consumer, + hashset_t* allowed_choices); + static std::vector consume_argc(i32 argc, argument_consumer_t& consumer, hashset_t* allowed_choices, + std::string_view arg); std::optional m_name; std::optional m_usage; diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp index 832263c..ba632f8 100644 --- a/src/blt/parse/argparse_v2.cpp +++ b/src/blt/parse/argparse_v2.cpp @@ -434,58 +434,18 @@ namespace blt::argparse make_string("Error expected at least one argument to be consumed by '", arg, '\'')); [[fallthrough]]; case nargs_t::ALL: - std::vector args; - while (consumer.can_consume() && !consumer.peek().is_flag()) - args.emplace_back(consumer.consume().get_argument()); - flag->m_dest_vec_func(dest, parsed_args, args); + auto result = consume_until_flag_or_end(consumer, flag->m_choices ? &*flag->m_choices : nullptr); + if (!result) + throw detail::bad_choice_error(make_string('\'', consumer.peek().get_argument(), + "' is not a valid choice for argument '", arg, + "'! Expected one of ", result.error())); + flag->m_dest_vec_func(dest, parsed_args, result.value()); break; } }, [&parsed_args, &consumer, &dest, &flag, arg, this](const i32 argc) { - std::vector args; - for (i32 i = 0; i < argc; ++i) - { - if (!consumer.can_consume()) - { - throw detail::missing_argument_error( - make_string("Expected ", argc, " arguments to be consumed by '", arg, "' but found ", i)); - } - if (consumer.peek().is_flag()) - { - std::cout << "Warning: arg '" << arg << "' expects " << argc << - " arguments to be consumed but we found a flag '" << consumer.peek(). - get_argument() << "'. We will comply as this may be desired if this argument is a file." << std::endl; - } - args.push_back(consumer.consume().get_argument()); - } - if (flag->m_choices) - { - auto& choices = *flag->m_choices; - for (const auto str : args) - { - if (!choices.contains(str)) - { - std::string valid_choices = "{"; - for (const auto& [i, choice] : enumerate(choices)) - { - valid_choices += choice; - if (i != choices.size() - 1) - valid_choices += ", "; - } - valid_choices += "}"; - throw detail::bad_choice_error(make_string('\'', str, "' is not a valid choice for argument '", arg, - "'! Expected one of ", valid_choices)); - } - } - } - if (args.size() != static_cast(argc)) - { - throw std::runtime_error( - "This error condition should not be possible. " - "Args consumed didn't equal the arguments requested and previous checks didn't fail. " - "Please report as an issue on the GitHub"); - } + const auto args = consume_argc(argc, consumer, flag->m_choices ? &*flag->m_choices : nullptr, arg); switch (flag->m_action) { @@ -514,7 +474,8 @@ namespace blt::argparse if (flag->m_const_value) { throw detail::missing_value_error( - make_string("Append const chosen as an action but const value not provided for argument '", arg, '\'')); + make_string("Append const chosen as an action but const value not provided for argument '", arg, + '\'')); } if (parsed_args.contains(dest)) { @@ -522,8 +483,9 @@ namespace blt::argparse auto visitor = detail::arg_meta_type_helper_t::make_visitor( [arg](auto& primitive) { - throw detail::type_error(make_string("Invalid type for argument '", arg, "' expected list type, found '", - blt::type_string(), "' with value ", primitive)); + throw detail::type_error(make_string( + "Invalid type for argument '", arg, "' expected list type, found '", + blt::type_string(), "' with value ", primitive)); }, [&flag, arg](auto& vec) { @@ -594,7 +556,8 @@ namespace blt::argparse return primitive + static_cast(1); } else - throw detail::type_error("Error: count called but stored type is " + blt::type_string()); + throw detail::type_error( + "Error: count called but stored type is " + blt::type_string()); }, [](auto&) -> detail::arg_data_t { @@ -609,10 +572,10 @@ namespace blt::argparse break; case action_t::HELP: print_help(); - std::exit(1); + std::exit(0); case action_t::VERSION: print_version(); - break; + std::exit(0); } } }, flag->m_nargs); @@ -620,6 +583,75 @@ namespace blt::argparse void argument_parser_t::parse_positional(argument_storage_t& parsed_args, argument_consumer_t& consumer, const std::string_view arg) { + auto positional = m_positional_arguments.find(arg)->second; + const auto dest = positional->m_dest.value_or(std::string{arg}); + std::visit(lambda_visitor{ + [&consumer, &positional, &dest, &parsed_args, arg](const nargs_t arg_enum) + { + switch (arg_enum) + { + case nargs_t::IF_POSSIBLE: + throw detail::bad_positional( + "Positional argument asked to consume if possible. We do not consider this to be a valid ask."); + case nargs_t::ALL_AT_LEAST_ONE: + if (!consumer.can_consume()) + throw detail::missing_argument_error( + make_string("Error expected at least one argument to be consumed by '", arg, '\'')); + [[fallthrough]]; + case nargs_t::ALL: + auto result = consume_until_flag_or_end( + consumer, positional->m_choices ? &*positional->m_choices : nullptr); + if (!result) + throw detail::bad_choice_error(make_string('\'', consumer.peek().get_argument(), + "' is not a valid choice for argument '", arg, + "'! Expected one of ", result.error())); + positional->m_dest_vec_func(dest, parsed_args, result.value()); + break; + } + }, + [this, &consumer, &positional, &dest, &parsed_args, arg](const i32 argc) + { + const auto args = consume_argc(argc, consumer, positional->m_choices ? &*positional->m_choices : nullptr, arg); + + switch (positional->m_action) + { + case action_t::STORE: + if (argc == 0) + throw detail::missing_argument_error( + make_string("Argument '", arg, "'s action is store but takes in no arguments?")); + if (argc == 1) + positional->m_dest_func(dest, parsed_args, args.front()); + else + throw detail::unexpected_argument_error(make_string("Argument '", arg, + "'s action is store but takes in more than one argument. " + "Did you mean to use action_t::APPEND or action_t::EXTEND?")); + break; + case action_t::APPEND: + case action_t::EXTEND: + if (argc == 0) + throw detail::missing_argument_error( + make_string("Argument '", arg, "'s action is append or extend but takes in no arguments.")); + positional->m_dest_vec_func(dest, parsed_args, args); + break; + case action_t::APPEND_CONST: + throw detail::bad_positional("action_t::APPEND_CONST does not make sense for positional arguments"); + case action_t::STORE_CONST: + throw detail::bad_positional("action_t::STORE_CONST does not make sense for positional arguments"); + case action_t::STORE_TRUE: + throw detail::bad_positional("action_t::STORE_TRUE does not make sense for positional arguments"); + case action_t::STORE_FALSE: + throw detail::bad_positional("action_t::STORE_FALSE does not make sense for positional arguments"); + case action_t::COUNT: + throw detail::bad_positional("action_t::COUNT does not make sense for positional arguments"); + case action_t::HELP: + print_help(); + std::exit(0); + case action_t::VERSION: + print_version(); + std::exit(0); + } + } + }, positional->m_nargs); } void argument_parser_t::handle_missing_and_default_args(hashmap_t& arguments, @@ -640,6 +672,74 @@ namespace blt::argparse } } + expected, std::string> argument_parser_t::consume_until_flag_or_end(argument_consumer_t& consumer, + hashset_t* allowed_choices) + { + std::vector args; + while (consumer.can_consume() && !consumer.peek().is_flag()) + { + if (allowed_choices != nullptr && !allowed_choices->contains(consumer.peek().get_argument())) + { + std::string valid_choices = "{"; + for (const auto& [i, choice] : enumerate(*allowed_choices)) + { + valid_choices += choice; + if (i != allowed_choices->size() - 1) + valid_choices += ", "; + } + valid_choices += "}"; + return unexpected(valid_choices); + } + args.emplace_back(consumer.consume().get_argument()); + } + return args; + } + + std::vector argument_parser_t::consume_argc(const int argc, argument_consumer_t& consumer, + hashset_t* allowed_choices, + const std::string_view arg) + { + std::vector args; + for (i32 i = 0; i < argc; ++i) + { + if (!consumer.can_consume()) + { + throw detail::missing_argument_error( + make_string("Expected ", argc, " arguments to be consumed by '", arg, "' but found ", i)); + } + if (consumer.peek().is_flag()) + { + std::cout << "Warning: arg '" << arg << "' expects " << argc << + " arguments to be consumed but we found a flag '" << consumer.peek(). + get_argument() << + "'. We will comply as this may be desired if this argument is a file." << std::endl; + } + if (allowed_choices != nullptr && !allowed_choices->contains(consumer.peek().get_argument())) + { + std::string valid_choices = "{"; + for (const auto& [i, choice] : enumerate(*allowed_choices)) + { + valid_choices += choice; + if (i != allowed_choices->size() - 1) + valid_choices += ", "; + } + valid_choices += "}"; + throw detail::bad_choice_error(make_string('\'', consumer.peek().get_argument(), + "' is not a valid choice for argument '", arg, + "'! Expected one of ", valid_choices)); + } + args.push_back(consumer.consume().get_argument()); + } + if (args.size() != static_cast(argc)) + { + throw std::runtime_error( + "This error condition should not be possible. " + "Args consumed didn't equal the arguments requested and previous checks didn't fail. " + "Please report as an issue on the GitHub"); + } + return args; + } + std::pair argument_subparser_t::parse(argument_consumer_t& consumer) { if (!consumer.can_consume()) From 389762e2aefb2f0843e51a9d48189c8cad6c850c Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Thu, 20 Feb 2025 00:47:27 -0500 Subject: [PATCH 023/101] positionals are broken because im silly --- CMakeLists.txt | 2 +- include/blt/parse/argparse_v2.h | 32 +++++++++- src/blt/parse/argparse_v2.cpp | 108 ++++++++++++++++++++++++++++++-- 3 files changed, 132 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c902cf8..a9aa84a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 4.0.18) +set(BLT_VERSION 4.0.19) set(BLT_TARGET BLT) diff --git a/include/blt/parse/argparse_v2.h b/include/blt/parse/argparse_v2.h index 9da2900..9d663f3 100644 --- a/include/blt/parse/argparse_v2.h +++ b/include/blt/parse/argparse_v2.h @@ -32,7 +32,7 @@ #include #include #include -#include +#include #include #include #include @@ -308,8 +308,16 @@ namespace blt::argparse class argument_consumer_t { public: - explicit argument_consumer_t(const span& args): m_begin(args.data()), m_end(args.data() + args.size()) + explicit argument_consumer_t(const span& args): m_absolute_begin(args.data()), m_begin(args.data() + 1), + m_end(args.data() + args.size()) { + BLT_ASSERT(!args.empty() && + "Argument consumer must have at least one argument allocated to it. First argument is always assumed to be program"); + } + + [[nodiscard]] const argument_string_t& absolute_first() const + { + return *m_absolute_begin; } [[nodiscard]] argument_string_t peek(const i32 offset = 0) const @@ -348,6 +356,7 @@ namespace blt::argparse return m_end - m_begin; } + argument_string_t* m_absolute_begin; argument_string_t* m_begin; argument_string_t* m_end; }; @@ -580,9 +589,20 @@ namespace blt::argparse argument_storage_t parse(argument_consumer_t& consumer); // NOLINT + argument_storage_t parse(const std::vector& args) + { + std::vector arg_strings; + arg_strings.reserve(args.size()); + for (const auto& arg : args) + arg_strings.emplace_back(arg, allowed_flag_prefixes); + argument_consumer_t consumer{arg_strings}; + return parse(consumer); + } + argument_storage_t parse(const std::vector& args) { std::vector arg_strings; + arg_strings.reserve(args.size()); for (const auto& arg : args) arg_strings.emplace_back(arg, allowed_flag_prefixes); argument_consumer_t consumer{arg_strings}; @@ -592,6 +612,7 @@ namespace blt::argparse argument_storage_t parse(const int argc, const char** argv) { std::vector arg_strings; + arg_strings.reserve(argc); for (int i = 0; i < argc; ++i) arg_strings.emplace_back(argv[i], allowed_flag_prefixes); argument_consumer_t consumer{arg_strings}; @@ -643,6 +664,11 @@ namespace blt::argparse return m_epilogue; } + [[nodiscard]] const hashset_t& get_allowed_flag_prefixes() const + { + return allowed_flag_prefixes; + } + private: void handle_compound_flags(hashset_t& found_flags, argument_storage_t& parsed_args, argument_consumer_t& consumer, const argument_string_t& arg); @@ -653,7 +679,7 @@ namespace blt::argparse static expected, std::string> consume_until_flag_or_end(argument_consumer_t& consumer, hashset_t* allowed_choices); static std::vector consume_argc(i32 argc, argument_consumer_t& consumer, hashset_t* allowed_choices, - std::string_view arg); + std::string_view arg); std::optional m_name; std::optional m_usage; diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp index ba632f8..7fc9b91 100644 --- a/src/blt/parse/argparse_v2.cpp +++ b/src/blt/parse/argparse_v2.cpp @@ -20,6 +20,7 @@ #include #include #include +#include namespace blt::argparse { @@ -195,7 +196,7 @@ namespace blt::argparse void test_argparse_empty() { - std::vector const argv; + const std::vector argv{"./program"}; argument_parser_t parser; const auto args = parser.parse(argv); BLT_ASSERT(args.size() == 0 && "Empty argparse should have no args on output"); @@ -208,7 +209,7 @@ namespace blt::argparse parser.add_flag("+b").set_action(action_t::STORE_FALSE); parser.add_flag("/c").as_type().set_action(action_t::STORE); - const std::vector args = {"-a", "+b", "/c", "42"}; + const std::vector args = {"./program", "-a", "+b", "/c", "42"}; const auto parsed_args = parser.parse(args); BLT_ASSERT(parsed_args.get("-a") == true && "Flag '-a' should store `true`"); @@ -224,7 +225,7 @@ namespace blt::argparse parser.add_flag("+b"); parser.add_flag("/c"); - const std::vector args = {"!d", "-a"}; + const std::vector args = {"./program", "!d", "-a"}; try { parser.parse(args); @@ -241,7 +242,7 @@ namespace blt::argparse argument_parser_t parser; parser.add_flag("-v").as_type().set_action(action_t::COUNT); - const std::vector args = {"-vvv"}; + const std::vector args = {"./program", "-vvv"}; const auto parsed_args = parser.parse(args); BLT_ASSERT(parsed_args.get("-v") == 3 && "Flag '-v' should count occurrences in compound form"); @@ -255,7 +256,7 @@ namespace blt::argparse parser.add_flag("-x").as_type(); parser.add_flag("/y").as_type(); - const std::vector args = {"-x", "10", "!z", "/y", "value"}; + const std::vector args = {"./program", "-x", "10", "!z", "/y", "value"}; try { parser.parse(args); @@ -277,7 +278,7 @@ namespace blt::argparse parser.add_flag("-f").set_action(action_t::STORE_FALSE); // STORE_FALSE action parser.add_flag("-c").set_action(action_t::STORE_TRUE); // STORE_TRUE action - const std::vector args = {"-k", "100", "-t", "-f", "-c"}; + const std::vector args = {"./program", "-k", "100", "-t", "-f", "-c"}; const auto parsed_args = parser.parse(args); BLT_ASSERT(parsed_args.get("-k") == 100 && "Flag '-k' should store 100"); @@ -286,6 +287,90 @@ namespace blt::argparse BLT_ASSERT(parsed_args.get("-c") == true && "Flag '-c' should store `true`"); } + // Helper function to simulate argument parsing for nargs tests + bool parse_arguments(const std::vector& args, int expected_nargs) + { + argument_parser_t parser; + + std::vector arg_strings; + arg_strings.reserve(args.size()); + for (const auto& arg : args) + arg_strings.emplace_back(arg, parser.get_allowed_flag_prefixes()); + argument_consumer_t consumer{arg_strings}; + + parser.add_positional("positional").set_nargs(expected_nargs); + try + { + auto parsed_args = parser.parse(consumer); + return consumer.remaining() == 0; + } + catch (const std::exception& e) + { + std::cerr << e.what() << std::endl; + return false; + } + } + + // Test case for nargs = 0 + void test_nargs_0() + { + std::cout << "[Running Test: test_nargs_0]\n"; + + // Valid case: No arguments + const std::vector valid_args = {"./program"}; + BLT_ASSERT(parse_arguments(valid_args, 0) && "nargs=0: Should accept no arguments"); + + // Invalid case: 1 argument + const std::vector invalid_args = {"./program", "arg1"}; + BLT_ASSERT(!parse_arguments(invalid_args, 0) && "nargs=0: Should not accept any arguments"); + + std::cout << "Success: test_nargs_0\n"; + } + + // Test case for nargs = 1 + void test_nargs_1() + { + std::cout << "[Running Test: test_nargs_1]\n"; + + // Valid case: 1 argument + const std::vector valid_args = {"./program", "arg1"}; + BLT_ASSERT(parse_arguments(valid_args, 1) && "nargs=1: Should accept exactly 1 argument"); + + // Invalid case: 0 arguments + const std::vector invalid_args_0 = {"./program"}; + BLT_ASSERT(!parse_arguments(invalid_args_0, 1) && "nargs=1: Should not accept 0 arguments"); + + // Invalid case: 2 arguments + const std::vector invalid_args_2 = {"./program", "arg1", "arg2"}; + BLT_ASSERT(!parse_arguments(invalid_args_2, 1) && "nargs=1: Should not accept more than 1 argument"); + + std::cout << "Success: test_nargs_1\n"; + } + + // Test case for nargs = 2 + void test_nargs_2() + { + std::cout << "[Running Test: test_nargs_2]\n"; + + // Valid case: 2 arguments + const std::vector valid_args = {"./program", "arg1", "arg2"}; + BLT_ASSERT(parse_arguments(valid_args, 2) && "nargs=2: Should accept exactly 2 arguments"); + + // Invalid case: 0 arguments + const std::vector invalid_args_0 = {"./program"}; + BLT_ASSERT(!parse_arguments(invalid_args_0, 2) && "nargs=2: Should not accept 0 arguments"); + + // Invalid case: 1 argument + const std::vector invalid_args_1 = {"./program", "arg1"}; + BLT_ASSERT(!parse_arguments(invalid_args_1, 2) && "nargs=2: Should not accept less than 2 arguments"); + + // Invalid case: 3 arguments + const std::vector invalid_args_3 = {"./program", "arg1", "arg2", "arg3"}; + BLT_ASSERT(!parse_arguments(invalid_args_3, 2) && "nargs=2: Should not accept more than 2 arguments"); + + std::cout << "Success: test_nargs_2\n"; + } + void run_argparse_flag_tests() { test_single_flag_prefixes(); @@ -295,11 +380,20 @@ namespace blt::argparse test_flags_with_different_actions(); } + void run_all_nargs_tests() + { + test_nargs_0(); + test_nargs_1(); + test_nargs_2(); + std::cout << "All nargs tests passed successfully.\n"; + } + void test() { run_all_tests_argument_string_t(); test_argparse_empty(); run_argparse_flag_tests(); + run_all_nargs_tests(); } [[nodiscard]] std::string subparse_error::error_string() const @@ -337,6 +431,8 @@ namespace blt::argparse argument_storage_t argument_parser_t::parse(argument_consumer_t& consumer) { + if (!m_name) + m_name = consumer.absolute_first().get_argument(); hashset_t found_flags; hashset_t found_positional; argument_storage_t parsed_args; From e8e891d4fcefd02505bf374048327c2fc5709332 Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Thu, 20 Feb 2025 00:50:49 -0500 Subject: [PATCH 024/101] a few more tests --- CMakeLists.txt | 2 +- src/blt/parse/argparse_v2.cpp | 47 ++++++++++++++++++++++++++++++++++- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a9aa84a..146cd44 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 4.0.19) +set(BLT_VERSION 4.0.20) set(BLT_TARGET BLT) diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp index 7fc9b91..60247a6 100644 --- a/src/blt/parse/argparse_v2.cpp +++ b/src/blt/parse/argparse_v2.cpp @@ -288,7 +288,7 @@ namespace blt::argparse } // Helper function to simulate argument parsing for nargs tests - bool parse_arguments(const std::vector& args, int expected_nargs) + bool parse_arguments(const std::vector& args, const nargs_v expected_nargs) { argument_parser_t parser; @@ -371,6 +371,49 @@ namespace blt::argparse std::cout << "Success: test_nargs_2\n"; } + void test_nargs_all() { + std::cout << "[Running Test: test_nargs_all]\n"; + + // Valid case: No arguments + const std::vector valid_args_0 = {"./program"}; + BLT_ASSERT(parse_arguments(valid_args_0, argparse::nargs_t::ALL), + "nargs=ALL: Should accept all remaining arguments (even if none left)"); + + // Valid case: Multiple arguments + const std::vector valid_args_2 = {"./program", "arg1", "arg2"}; + BLT_ASSERT(parse_arguments(valid_args_2, argparse::nargs_t::ALL), + "nargs=ALL: Should accept all remaining arguments"); + + // Valid case: Many arguments + const std::vector valid_args_many = {"./program", "arg1", "arg2", "arg3", "arg4"}; + BLT_ASSERT(parse_arguments(valid_args_many, argparse::nargs_t::ALL), + "nargs=ALL: Should accept all remaining arguments"); + + std::cout << "Success: test_nargs_all\n"; + } + + // Test case for nargs_t::ALL_AT_LEAST_ONE + void test_nargs_all_at_least_one() { + std::cout << "[Running Test: test_nargs_all_at_least_one]\n"; + + // Valid case: 1 argument + const std::vector valid_args_1 = {"arg1"}; + BLT_ASSERT(parse_arguments(valid_args_1, argparse::nargs_t::ALL_AT_LEAST_ONE), + "nargs=ALL_AT_LEAST_ONE: Should accept at least one argument and consume it"); + + // Valid case: Multiple arguments + const std::vector valid_args_3 = {"arg1", "arg2", "arg3"}; + BLT_ASSERT(parse_arguments(valid_args_3, argparse::nargs_t::ALL_AT_LEAST_ONE), + "nargs=ALL_AT_LEAST_ONE: Should accept at least one argument and consume all remaining arguments"); + + // Invalid case: No arguments + const std::vector invalid_args_0 = {}; + BLT_ASSERT(!parse_arguments(invalid_args_0, argparse::nargs_t::ALL_AT_LEAST_ONE), + "nargs=ALL_AT_LEAST_ONE: Should reject if no arguments are provided"); + + std::cout << "Success: test_nargs_all_at_least_one\n"; + } + void run_argparse_flag_tests() { test_single_flag_prefixes(); @@ -385,6 +428,8 @@ namespace blt::argparse test_nargs_0(); test_nargs_1(); test_nargs_2(); + test_nargs_all(); + test_nargs_all_at_least_one(); std::cout << "All nargs tests passed successfully.\n"; } From f403e8a69b3a84eb0a4b3a47bd3540dbbc0f02d7 Mon Sep 17 00:00:00 2001 From: Brett Date: Thu, 20 Feb 2025 23:19:49 -0500 Subject: [PATCH 025/101] minor work --- CMakeLists.txt | 2 +- include/blt/parse/argparse_v2.h | 50 +++++++++++++++-- src/blt/parse/argparse_v2.cpp | 95 +++++++++++++++++++-------------- 3 files changed, 100 insertions(+), 47 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 146cd44..977ef33 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 4.0.20) +set(BLT_VERSION 4.0.21) set(BLT_TARGET BLT) diff --git a/include/blt/parse/argparse_v2.h b/include/blt/parse/argparse_v2.h index 9d663f3..ee1988d 100644 --- a/include/blt/parse/argparse_v2.h +++ b/include/blt/parse/argparse_v2.h @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -45,6 +46,7 @@ namespace blt::argparse class argument_subparser_t; class argument_builder_t; class argument_storage_t; + class argument_positional_storage_t; enum class action_t { @@ -532,7 +534,7 @@ namespace blt::argparse return *this; } - argument_builder_t& set_dest(const std::string& dest) + argument_builder_t& set_dest(const std::string_view& dest) { m_dest = dest; return *this; @@ -554,6 +556,42 @@ namespace blt::argparse std::function& values)> m_dest_vec_func; }; + class argument_positional_storage_t + { + public: + argument_positional_storage_t() = default; + + argument_builder_t& add(const std::string_view name) + { + positional_arguments.emplace_back(name, argument_builder_t{}); + return positional_arguments.back().second; + } + + argument_builder_t& peek() + { + return positional_arguments[current_positional].second; + } + + argument_builder_t& next() + { + return positional_arguments[current_positional++].second; + } + + [[nodiscard]] bool has_positional() const + { + return current_positional < positional_arguments.size(); + } + + [[nodiscard]] auto remaining() const + { + return iterate(positional_arguments).skip(current_positional); + } + + private: + std::vector> positional_arguments; + size_t current_positional = 0; + }; + class argument_parser_t { friend argument_subparser_t; @@ -580,9 +618,11 @@ namespace blt::argparse argument_builder_t& add_positional(const std::string_view arg) { - m_argument_builders.emplace_back(std::make_unique()); - m_positional_arguments.emplace(arg, m_argument_builders.back().get()); - return *m_argument_builders.back(); + auto& b = m_positional_arguments.add(arg); + b.set_dest(std::string{arg}); + b.set_required(true); + b.set_nargs(1); + return b; } argument_subparser_t& add_subparser(std::string_view dest); @@ -688,7 +728,7 @@ namespace blt::argparse std::vector> m_subparsers; std::vector> m_argument_builders; hashmap_t m_flag_arguments; - hashmap_t m_positional_arguments; + argument_positional_storage_t m_positional_arguments; hashset_t allowed_flag_prefixes = {'-', '+', '/'}; }; diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp index 60247a6..3c81ab8 100644 --- a/src/blt/parse/argparse_v2.cpp +++ b/src/blt/parse/argparse_v2.cpp @@ -306,7 +306,6 @@ namespace blt::argparse } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; return false; } } @@ -318,7 +317,7 @@ namespace blt::argparse // Valid case: No arguments const std::vector valid_args = {"./program"}; - BLT_ASSERT(parse_arguments(valid_args, 0) && "nargs=0: Should accept no arguments"); + BLT_ASSERT(!parse_arguments(valid_args, 0) && "nargs=0: Should fail"); // Invalid case: 1 argument const std::vector invalid_args = {"./program", "arg1"}; @@ -354,7 +353,7 @@ namespace blt::argparse // Valid case: 2 arguments const std::vector valid_args = {"./program", "arg1", "arg2"}; - BLT_ASSERT(parse_arguments(valid_args, 2) && "nargs=2: Should accept exactly 2 arguments"); + BLT_ASSERT(!parse_arguments(valid_args, 2) && "nargs=2: Should fail as action is store"); // Invalid case: 0 arguments const std::vector invalid_args_0 = {"./program"}; @@ -371,45 +370,47 @@ namespace blt::argparse std::cout << "Success: test_nargs_2\n"; } - void test_nargs_all() { + void test_nargs_all() + { std::cout << "[Running Test: test_nargs_all]\n"; // Valid case: No arguments const std::vector valid_args_0 = {"./program"}; - BLT_ASSERT(parse_arguments(valid_args_0, argparse::nargs_t::ALL), - "nargs=ALL: Should accept all remaining arguments (even if none left)"); + BLT_ASSERT(!parse_arguments(valid_args_0, argparse::nargs_t::ALL) && + "nargs=ALL: No arguments present. Should fail.)"); // Valid case: Multiple arguments const std::vector valid_args_2 = {"./program", "arg1", "arg2"}; - BLT_ASSERT(parse_arguments(valid_args_2, argparse::nargs_t::ALL), - "nargs=ALL: Should accept all remaining arguments"); + BLT_ASSERT(parse_arguments(valid_args_2, argparse::nargs_t::ALL) && + "nargs=ALL: Should accept all remaining arguments"); // Valid case: Many arguments const std::vector valid_args_many = {"./program", "arg1", "arg2", "arg3", "arg4"}; - BLT_ASSERT(parse_arguments(valid_args_many, argparse::nargs_t::ALL), - "nargs=ALL: Should accept all remaining arguments"); + BLT_ASSERT(parse_arguments(valid_args_many, argparse::nargs_t::ALL) && + "nargs=ALL: Should accept all remaining arguments"); std::cout << "Success: test_nargs_all\n"; } // Test case for nargs_t::ALL_AT_LEAST_ONE - void test_nargs_all_at_least_one() { + void test_nargs_all_at_least_one() + { std::cout << "[Running Test: test_nargs_all_at_least_one]\n"; // Valid case: 1 argument - const std::vector valid_args_1 = {"arg1"}; - BLT_ASSERT(parse_arguments(valid_args_1, argparse::nargs_t::ALL_AT_LEAST_ONE), - "nargs=ALL_AT_LEAST_ONE: Should accept at least one argument and consume it"); + const std::vector valid_args_1 = {"./program", "arg1"}; + BLT_ASSERT(parse_arguments(valid_args_1, argparse::nargs_t::ALL_AT_LEAST_ONE) && + "nargs=ALL_AT_LEAST_ONE: Should accept at least one argument and consume it"); // Valid case: Multiple arguments - const std::vector valid_args_3 = {"arg1", "arg2", "arg3"}; - BLT_ASSERT(parse_arguments(valid_args_3, argparse::nargs_t::ALL_AT_LEAST_ONE), - "nargs=ALL_AT_LEAST_ONE: Should accept at least one argument and consume all remaining arguments"); + const std::vector valid_args_3 = {"./program", "arg1", "arg2", "arg3"}; + BLT_ASSERT(parse_arguments(valid_args_3, argparse::nargs_t::ALL_AT_LEAST_ONE) && + "nargs=ALL_AT_LEAST_ONE: Should accept at least one argument and consume all remaining arguments"); // Invalid case: No arguments - const std::vector invalid_args_0 = {}; - BLT_ASSERT(!parse_arguments(invalid_args_0, argparse::nargs_t::ALL_AT_LEAST_ONE), - "nargs=ALL_AT_LEAST_ONE: Should reject if no arguments are provided"); + const std::vector invalid_args_0 = {"./program"}; + BLT_ASSERT(!parse_arguments(invalid_args_0, argparse::nargs_t::ALL_AT_LEAST_ONE) && + "nargs=ALL_AT_LEAST_ONE: Should reject if no arguments are provided"); std::cout << "Success: test_nargs_all_at_least_one\n"; } @@ -479,7 +480,6 @@ namespace blt::argparse if (!m_name) m_name = consumer.absolute_first().get_argument(); hashset_t found_flags; - hashset_t found_positional; argument_storage_t parsed_args; // first, we consume flags which may be part of this parser while (consumer.can_consume() && consumer.peek().is_flag()) @@ -494,20 +494,31 @@ namespace blt::argparse while (consumer.can_consume()) { - const auto key = consumer.consume(); - if (key.is_flag()) - handle_compound_flags(found_flags, parsed_args, consumer, key); + if (consumer.peek().is_flag()) + handle_compound_flags(found_flags, parsed_args, consumer, consumer.consume()); else - { - const auto pos = m_positional_arguments.find(key.get_argument()); - if (pos == m_positional_arguments.end()) - throw detail::bad_positional(make_string("Error: Unknown positional argument: ", key.get_argument())); - found_positional.insert(std::string{key.get_argument()}); - parse_positional(parsed_args, consumer, key.get_argument()); - } + parse_positional(parsed_args, consumer, consumer.peek().get_argument()); } handle_missing_and_default_args(m_flag_arguments, found_flags, parsed_args, "flag"); - handle_missing_and_default_args(m_positional_arguments, found_positional, parsed_args, "positional"); + + for (auto& [name, value] : m_positional_arguments.remaining()) + { + std::visit(lambda_visitor{ + [](const nargs_t) + { + }, + [](const int argc) + { + if (argc == 0) + throw detail::bad_positional("Positional Argument takes no values, this is invalid!"); + } + }, value.m_nargs); + + if (value.m_required) + throw detail::missing_argument_error(make_string("Error: argument '", name, "' was not found but is required by the program")); + if (value.m_default_value && !parsed_args.contains(value.m_dest.value_or(name))) + parsed_args.m_data.emplace(value.m_dest.value_or(name), *value.m_default_value); + } return parsed_args; } @@ -724,8 +735,10 @@ namespace blt::argparse void argument_parser_t::parse_positional(argument_storage_t& parsed_args, argument_consumer_t& consumer, const std::string_view arg) { - auto positional = m_positional_arguments.find(arg)->second; - const auto dest = positional->m_dest.value_or(std::string{arg}); + if (!m_positional_arguments.has_positional()) + throw detail::missing_argument_error(make_string("Error: No positional arguments were defined for this parser!")); + auto& positional = m_positional_arguments.next(); + const auto dest = positional.m_dest.value_or(std::string{arg}); std::visit(lambda_visitor{ [&consumer, &positional, &dest, &parsed_args, arg](const nargs_t arg_enum) { @@ -741,27 +754,27 @@ namespace blt::argparse [[fallthrough]]; case nargs_t::ALL: auto result = consume_until_flag_or_end( - consumer, positional->m_choices ? &*positional->m_choices : nullptr); + consumer, positional.m_choices ? &*positional.m_choices : nullptr); if (!result) throw detail::bad_choice_error(make_string('\'', consumer.peek().get_argument(), "' is not a valid choice for argument '", arg, "'! Expected one of ", result.error())); - positional->m_dest_vec_func(dest, parsed_args, result.value()); + positional.m_dest_vec_func(dest, parsed_args, result.value()); break; } }, [this, &consumer, &positional, &dest, &parsed_args, arg](const i32 argc) { - const auto args = consume_argc(argc, consumer, positional->m_choices ? &*positional->m_choices : nullptr, arg); + const auto args = consume_argc(argc, consumer, positional.m_choices ? &*positional.m_choices : nullptr, arg); - switch (positional->m_action) + switch (positional.m_action) { case action_t::STORE: if (argc == 0) throw detail::missing_argument_error( make_string("Argument '", arg, "'s action is store but takes in no arguments?")); if (argc == 1) - positional->m_dest_func(dest, parsed_args, args.front()); + positional.m_dest_func(dest, parsed_args, args.front()); else throw detail::unexpected_argument_error(make_string("Argument '", arg, "'s action is store but takes in more than one argument. " @@ -772,7 +785,7 @@ namespace blt::argparse if (argc == 0) throw detail::missing_argument_error( make_string("Argument '", arg, "'s action is append or extend but takes in no arguments.")); - positional->m_dest_vec_func(dest, parsed_args, args); + positional.m_dest_vec_func(dest, parsed_args, args); break; case action_t::APPEND_CONST: throw detail::bad_positional("action_t::APPEND_CONST does not make sense for positional arguments"); @@ -792,7 +805,7 @@ namespace blt::argparse std::exit(0); } } - }, positional->m_nargs); + }, positional.m_nargs); } void argument_parser_t::handle_missing_and_default_args(hashmap_t& arguments, From 30f975e165a91b3e3662f317ab77df590628b596 Mon Sep 17 00:00:00 2001 From: Brett Date: Fri, 21 Feb 2025 16:37:05 -0500 Subject: [PATCH 026/101] more tests, i think most things work now? --- CMakeLists.txt | 2 +- include/blt/parse/argparse_v2.h | 29 +++-- src/blt/parse/argparse_v2.cpp | 189 ++++++++++++++++++++++++-------- 3 files changed, 166 insertions(+), 54 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 977ef33..864ca1d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 4.0.21) +set(BLT_VERSION 4.0.22) set(BLT_TARGET BLT) diff --git a/include/blt/parse/argparse_v2.h b/include/blt/parse/argparse_v2.h index ee1988d..a4cc3cf 100644 --- a/include/blt/parse/argparse_v2.h +++ b/include/blt/parse/argparse_v2.h @@ -188,7 +188,7 @@ namespace blt::argparse } }; - using arg_meta_type_helper_t = arg_data_helper_t; + using arg_meta_type_helper_t = arg_data_helper_t; using arg_data_t = arg_meta_type_helper_t::variant_t; template @@ -376,9 +376,9 @@ namespace blt::argparse return std::get(m_data.at(key)); } - [[nodiscard]] std::string_view get(const std::string_view key) const + [[nodiscard]] const std::string& get(const std::string_view key) const { - return std::get(m_data.at(key)); + return std::get(m_data.at(key)); } bool contains(const std::string_view key) @@ -410,9 +410,9 @@ namespace blt::argparse { m_dest_func = [](const std::string_view dest, argument_storage_t& storage, std::string_view value) { - storage.m_data.emplace(std::string{dest}, value); + storage.m_data.emplace(std::string{dest}, std::string{value}); }; - m_dest_vec_func = [](const std::string_view dest, argument_storage_t& storage, const std::vector& values) + m_dest_vec_func = [](const std::string_view dest, argument_storage_t& storage, const std::vector& values) { storage.m_data.emplace(std::string{dest}, values); }; @@ -426,7 +426,7 @@ namespace blt::argparse { storage.m_data.emplace(std::string{dest}, detail::arg_string_converter_t::convert(value)); }; - m_dest_vec_func = [](const std::string_view dest, argument_storage_t& storage, const std::vector& values) + m_dest_vec_func = [](const std::string_view dest, argument_storage_t& storage, const std::vector& values) { if (storage.m_data.contains(dest)) { @@ -451,6 +451,11 @@ namespace blt::argparse return *this; } + argument_builder_t& set_flag() + { + return set_action(action_t::STORE_TRUE); + } + argument_builder_t& set_action(const action_t action) { m_action = action; @@ -474,6 +479,9 @@ namespace blt::argparse set_nargs(0); as_type(); break; + case action_t::EXTEND: + set_nargs(nargs_t::ALL); + break; case action_t::HELP: case action_t::VERSION: set_nargs(0); @@ -553,7 +561,7 @@ namespace blt::argparse // dest, storage, value input std::function m_dest_func; // dest, storage, value input - std::function& values)> m_dest_vec_func; + std::function& values)> m_dest_vec_func; }; class argument_positional_storage_t @@ -611,6 +619,7 @@ namespace blt::argparse std::string, Aliases>>...>, "Arguments must be of type string_view, convertible to string_view or be string_view constructable"); m_argument_builders.emplace_back(std::make_unique()); + m_argument_builders.back()->set_dest(arg); m_flag_arguments.emplace(arg, m_argument_builders.back().get()); (m_flag_arguments.emplace(aliases, m_argument_builders.back().get()), ...); return *m_argument_builders.back().get(); @@ -716,9 +725,9 @@ namespace blt::argparse void parse_positional(argument_storage_t& parsed_args, argument_consumer_t& consumer, std::string_view arg); static void handle_missing_and_default_args(hashmap_t& arguments, const hashset_t& found, argument_storage_t& parsed_args, std::string_view type); - static expected, std::string> consume_until_flag_or_end(argument_consumer_t& consumer, + static expected, std::string> consume_until_flag_or_end(argument_consumer_t& consumer, hashset_t* allowed_choices); - static std::vector consume_argc(i32 argc, argument_consumer_t& consumer, hashset_t* allowed_choices, + static std::vector consume_argc(i32 argc, argument_consumer_t& consumer, hashset_t* allowed_choices, std::string_view arg); std::optional m_name; @@ -746,7 +755,7 @@ namespace blt::argparse std::conjunction_v, std::is_constructible< std::string_view, Aliases>>...>, "Arguments must be of type string_view, convertible to string_view or be string_view constructable"); - m_parsers.emplace(name); + m_parsers.emplace(name, argument_parser_t{}); ((m_aliases[std::string_view{aliases}] = &m_parsers[name]), ...); return m_parsers[name]; } diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp index 3c81ab8..84077d7 100644 --- a/src/blt/parse/argparse_v2.cpp +++ b/src/blt/parse/argparse_v2.cpp @@ -67,7 +67,8 @@ namespace blt::argparse template auto ensure_is_string(T&& t) { - if constexpr (std::is_arithmetic_v>) + if constexpr (std::is_arithmetic_v> && !(std::is_same_v + || std::is_same_v || std::is_same_v)) return std::to_string(std::forward(t)); else return std::forward(t); @@ -82,6 +83,12 @@ namespace blt::argparse return out; } + template + std::vector make_arguments(Strings... strings) + { + return std::vector{"./program", strings...}; + } + namespace detail { // Unit Tests for class argument_string_t @@ -254,7 +261,7 @@ namespace blt::argparse argument_parser_t parser; parser.add_flag("-x").as_type(); - parser.add_flag("/y").as_type(); + parser.add_flag("/y").as_type(); const std::vector args = {"./program", "-x", "10", "!z", "/y", "value"}; try @@ -415,6 +422,102 @@ namespace blt::argparse std::cout << "Success: test_nargs_all_at_least_one\n"; } + void run_combined_flag_test() + { + std::cout << "[Running Test: run_combined_flag_test]\n"; + argument_parser_t parser; + + parser.add_flag("-a").set_action(action_t::STORE_TRUE); + parser.add_flag("--deep").set_action(action_t::STORE_FALSE); + parser.add_flag("-b", "--combined").set_action(action_t::STORE_CONST).set_const(50); + parser.add_flag("--append").set_action(action_t::APPEND).as_type(); + parser.add_flag("--required").set_required(true); + parser.add_flag("--default").set_default("I am a default value"); + parser.add_flag("-t").set_action(action_t::APPEND_CONST).set_dest("test").set_const(5); + parser.add_flag("-g").set_action(action_t::APPEND_CONST).set_dest("test").set_const(10); + parser.add_flag("-e").set_action(action_t::APPEND_CONST).set_dest("test").set_const(15); + parser.add_flag("-f").set_action(action_t::APPEND_CONST).set_dest("test").set_const(20); + parser.add_flag("-d").set_action(action_t::APPEND_CONST).set_dest("test").set_const(25); + parser.add_flag("--end").set_action(action_t::EXTEND).set_dest("wow").as_type(); + + const auto a1 = make_arguments("-a", "--required", "hello"); + const auto r1 = parser.parse(a1); + BLT_ASSERT(r1.get("-a") == true && "Flag '-a' should store true"); + BLT_ASSERT(r1.get("--default") == "I am a default value" && "Flag '--default' should store default value"); + BLT_ASSERT(r1.get("--required") == "hello" && "Flag '--required' should store 'hello'"); + + const auto a2 = make_arguments("-a", "--deep", "--required", "soft"); + const auto r2 = parser.parse(a2); + BLT_ASSERT(r2.get("-a") == true && "Flag '-a' should store true"); + BLT_ASSERT(r2.get("--deep") == false && "Flag '--deep' should store false"); + BLT_ASSERT(r2.get("--required") == "soft" && "Flag '--required' should store 'soft'"); + + const auto a3 = make_arguments("--required", "silly", "--combined", "-t", "-f", "-e"); + const auto r3 = parser.parse(a3); + BLT_ASSERT((r3.get>("test") == std::vector{5, 20, 15}) && "Flags should add to vector of {5, 20, 15}"); + BLT_ASSERT(r3.get("-b") == 50 && "Combined flag should store const of 50"); + + const auto a4 = make_arguments("--required", "crazy", "--end", "10", "12.05", "68.11", "100.00", "200532", "-d", "-t", "-g", "-e", "-f"); + const auto r4 = parser.parse(a4); + BLT_ASSERT( + (r4.get>("test") == std::vector{25, 5, 10, 15, 20}) && + "Expected test vector to be filled with all arguments in order of flags"); + BLT_ASSERT( + (r4.get>("wow") == std::vector{10, 12.05, 68.11, 100.00, 200532}) && + "Extend vector expected to contain all elements"); + + std::cout << "Success: run_combined_flag_test\n"; + } + + void run_subparser_test() + { + std::cout << "[Running Test: run_subparser_test]\n"; + argument_parser_t parser; + + parser.add_flag("--open").set_flag(); + + auto& subparser = parser.add_subparser("mode"); + auto& n1 = subparser.add_parser("n1"); + n1.add_flag("--silly").set_flag(); + n1.add_positional("path"); + + auto& n2 = subparser.add_parser("n2"); + n2.add_flag("--crazy").set_flag(); + n2.add_positional("path"); + n2.add_positional("output"); + + auto& n3 = subparser.add_parser("n3"); + n3.add_flag("--deep").set_flag(); + + const auto a1 = make_arguments("n1", "--silly"); + try + { + parser.parse(a1); + BLT_ASSERT(false && "Subparser should throw an error when positional not supplied"); + } + catch (...) + { + } + + const auto a2 = make_arguments("--open"); + try + { + parser.parse(a2); + BLT_ASSERT(false && "Subparser should throw an error when no subparser is supplied"); + } + catch (...) + { + } + + const auto a3 = make_arguments("n1", "--silly", "path"); + const auto r3 = parser.parse(a3); + BLT_ASSERT(r3.get("--open") == false && "Flag '--open' should default to false"); + BLT_ASSERT(r3.get("mode") == "n1" && "Subparser should store 'n1'"); + BLT_ASSERT(r3.get("path") == "path" && "Subparser path should be 'path'"); + + std::cout << "Success: run_subparser_test\n"; + } + void run_argparse_flag_tests() { test_single_flag_prefixes(); @@ -422,6 +525,8 @@ namespace blt::argparse test_compound_flags(); test_combination_of_valid_and_invalid_flags(); test_flags_with_different_actions(); + run_combined_flag_test(); + run_subparser_test(); } void run_all_nargs_tests() @@ -488,7 +593,7 @@ namespace blt::argparse for (auto& [key, subparser] : m_subparsers) { auto [parsed_subparser, storage] = subparser.parse(consumer); - storage.m_data.emplace(std::string{key}, detail::arg_data_t{parsed_subparser.get_argument()}); + storage.m_data.emplace(std::string{key}, detail::arg_data_t{std::string{parsed_subparser.get_argument()}}); parsed_args.add(storage); } @@ -623,7 +728,7 @@ namespace blt::argparse if (argc != 0) throw detail::unexpected_argument_error( make_string("Argument '", arg, "'s action is append const but takes in arguments.")); - if (flag->m_const_value) + if (!flag->m_const_value) { throw detail::missing_value_error( make_string("Append const chosen as an action but const value not provided for argument '", arg, @@ -632,40 +737,38 @@ namespace blt::argparse if (parsed_args.contains(dest)) { auto& data = parsed_args.m_data[dest]; - auto visitor = detail::arg_meta_type_helper_t::make_visitor( - [arg](auto& primitive) - { - throw detail::type_error(make_string( - "Invalid type for argument '", arg, "' expected list type, found '", - blt::type_string(), "' with value ", primitive)); - }, - [&flag, arg](auto& vec) - { - using type = typename meta::remove_cvref_t::value_type; - if (!std::holds_alternative(*flag->m_const_value)) - { - throw detail::type_error(make_string("Constant value for argument '", arg, - "' type doesn't match values already present! Expected to be of type '", - blt::type_string(), "'!")); - } - vec.push_back(std::get(*flag->m_const_value)); - }); - std::visit(visitor, data); + std::visit(detail::arg_meta_type_helper_t::make_visitor( + [arg](auto& primitive) + { + throw detail::type_error(make_string( + "Invalid type for argument '", arg, "' expected list type, found '", + blt::type_string(), "' with value ", primitive)); + }, + [&flag, arg](auto& vec) + { + using type = typename meta::remove_cvref_t::value_type; + if (!std::holds_alternative(*flag->m_const_value)) + { + throw detail::type_error(make_string("Constant value for argument '", arg, + "' type doesn't match values already present! Expected to be of type '", + blt::type_string(), "'!")); + } + vec.push_back(std::get(*flag->m_const_value)); + }), data); } else { - auto visitor = detail::arg_meta_type_helper_t::make_visitor( - [&parsed_args, &dest](auto& primitive) - { - std::vector> vec; - vec.push_back(primitive); - parsed_args.m_data.insert({dest, std::move(vec)}); - }, - [](auto&) - { - throw detail::type_error("Append const should not be a list type!"); - }); - std::visit(visitor, *flag->m_const_value); + std::visit(detail::arg_meta_type_helper_t::make_visitor( + [&parsed_args, &dest](auto& primitive) + { + std::vector> vec; + vec.emplace_back(primitive); + parsed_args.m_data.emplace(dest, std::move(vec)); + }, + [](auto&) + { + throw detail::type_error("Append const should not be a list type!"); + }), *flag->m_const_value); } break; case action_t::STORE_CONST: @@ -826,10 +929,10 @@ namespace blt::argparse } } - expected, std::string> argument_parser_t::consume_until_flag_or_end(argument_consumer_t& consumer, - hashset_t* allowed_choices) + expected, std::string> argument_parser_t::consume_until_flag_or_end(argument_consumer_t& consumer, + hashset_t* allowed_choices) { - std::vector args; + std::vector args; while (consumer.can_consume() && !consumer.peek().is_flag()) { if (allowed_choices != nullptr && !allowed_choices->contains(consumer.peek().get_argument())) @@ -849,11 +952,11 @@ namespace blt::argparse return args; } - std::vector argument_parser_t::consume_argc(const int argc, argument_consumer_t& consumer, - hashset_t* allowed_choices, - const std::string_view arg) + std::vector argument_parser_t::consume_argc(const int argc, argument_consumer_t& consumer, + hashset_t* allowed_choices, + const std::string_view arg) { - std::vector args; + std::vector args; for (i32 i = 0; i < argc; ++i) { if (!consumer.can_consume()) @@ -882,7 +985,7 @@ namespace blt::argparse "' is not a valid choice for argument '", arg, "'! Expected one of ", valid_choices)); } - args.push_back(consumer.consume().get_argument()); + args.push_back(std::string{consumer.consume().get_argument()}); } if (args.size() != static_cast(argc)) { From f8ed21fda5900177ef9d1bdd308f9e22c41ca50c Mon Sep 17 00:00:00 2001 From: Brett Date: Sat, 22 Feb 2025 13:38:36 -0500 Subject: [PATCH 027/101] more tests --- CMakeLists.txt | 2 +- include/blt/parse/argparse_v2.h | 20 +- src/blt/parse/argparse_v2.cpp | 1021 ++++++++++++++++--------------- 3 files changed, 539 insertions(+), 504 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 864ca1d..8ea6c6f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 4.0.22) +set(BLT_VERSION 4.0.23) set(BLT_TARGET BLT) diff --git a/include/blt/parse/argparse_v2.h b/include/blt/parse/argparse_v2.h index a4cc3cf..4eac0ff 100644 --- a/include/blt/parse/argparse_v2.h +++ b/include/blt/parse/argparse_v2.h @@ -314,7 +314,7 @@ namespace blt::argparse m_end(args.data() + args.size()) { BLT_ASSERT(!args.empty() && - "Argument consumer must have at least one argument allocated to it. First argument is always assumed to be program"); + "Argument consumer must have at least one argument allocated to it. First argument is always assumed to be program"); } [[nodiscard]] const argument_string_t& absolute_first() const @@ -567,12 +567,9 @@ namespace blt::argparse class argument_positional_storage_t { public: - argument_positional_storage_t() = default; - - argument_builder_t& add(const std::string_view name) + explicit argument_positional_storage_t(std::vector> storage): positional_arguments( + std::move(storage)) { - positional_arguments.emplace_back(name, argument_builder_t{}); - return positional_arguments.back().second; } argument_builder_t& peek() @@ -627,7 +624,7 @@ namespace blt::argparse argument_builder_t& add_positional(const std::string_view arg) { - auto& b = m_positional_arguments.add(arg); + auto& b = m_positional_arguments.emplace_back(arg, argument_builder_t{}).second; b.set_dest(std::string{arg}); b.set_required(true); b.set_nargs(1); @@ -722,13 +719,14 @@ namespace blt::argparse void handle_compound_flags(hashset_t& found_flags, argument_storage_t& parsed_args, argument_consumer_t& consumer, const argument_string_t& arg); void parse_flag(argument_storage_t& parsed_args, argument_consumer_t& consumer, std::string_view arg); - void parse_positional(argument_storage_t& parsed_args, argument_consumer_t& consumer, std::string_view arg); + void parse_positional(argument_storage_t& parsed_args, argument_consumer_t& consumer, argument_positional_storage_t& storage, + std::string_view arg); static void handle_missing_and_default_args(hashmap_t& arguments, const hashset_t& found, argument_storage_t& parsed_args, std::string_view type); static expected, std::string> consume_until_flag_or_end(argument_consumer_t& consumer, - hashset_t* allowed_choices); + hashset_t* allowed_choices); static std::vector consume_argc(i32 argc, argument_consumer_t& consumer, hashset_t* allowed_choices, - std::string_view arg); + std::string_view arg); std::optional m_name; std::optional m_usage; @@ -737,7 +735,7 @@ namespace blt::argparse std::vector> m_subparsers; std::vector> m_argument_builders; hashmap_t m_flag_arguments; - argument_positional_storage_t m_positional_arguments; + std::vector> m_positional_arguments; hashset_t allowed_flag_prefixes = {'-', '+', '/'}; }; diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp index 84077d7..b4fd55f 100644 --- a/src/blt/parse/argparse_v2.cpp +++ b/src/blt/parse/argparse_v2.cpp @@ -89,491 +89,6 @@ namespace blt::argparse return std::vector{"./program", strings...}; } - namespace detail - { - // Unit Tests for class argument_string_t - // Test Case 1: Ensure the constructor handles flags correctly - void test_argument_string_t_flag_basic(const hashset_t& prefixes) - { - const argument_string_t arg("-f", prefixes); - BLT_ASSERT(arg.is_flag() && "Expected argument to be identified as a flag."); - BLT_ASSERT(arg.value() == "f" && "Flag value should match the input string."); - } - - // Test Case 2: Ensure the constructor handles long flags correctly - void test_argument_string_t_long_flag(const hashset_t& prefixes) - { - const argument_string_t arg("--file", prefixes); - BLT_ASSERT(arg.is_flag() && "Expected argument to be identified as a flag."); - BLT_ASSERT(arg.value() == "file" && "Long flag value should match the input string."); - } - - // Test Case 3: Ensure positional arguments are correctly identified - void test_argument_string_t_positional_argument(const hashset_t& prefixes) - { - const argument_string_t arg("filename.txt", prefixes); - BLT_ASSERT(!arg.is_flag() && "Expected argument to be identified as positional."); - BLT_ASSERT(arg.value() == "filename.txt" && "Positional argument value should match the input string."); - } - - // Test Case 5: Handle an empty string - void test_argument_string_t_empty_input(const hashset_t& prefixes) - { - const argument_string_t arg("", prefixes); - BLT_ASSERT(!arg.is_flag() && "Expected an empty input to be treated as positional, not a flag."); - BLT_ASSERT(arg.value().empty() && "Empty input should have an empty value."); - } - - // Test Case 6: Handle edge case of a single hyphen (`-`) which might be ambiguous - void test_argument_string_t_single_hyphen(const hashset_t& prefixes) - { - const argument_string_t arg("-", prefixes); - BLT_ASSERT(arg.is_flag() && "Expected single hyphen (`-`) to be treated as a flag."); - BLT_ASSERT(arg.value().empty() && "Single hyphen flag should have empty value."); - BLT_ASSERT(arg.get_flag() == "-" && "Single hyphen flag should match the input string."); - } - - // Test Case 8: Handle arguments with prefix only (like "--") - void test_argument_string_t_double_hyphen(const hashset_t& prefixes) - { - const argument_string_t arg("--", prefixes); - BLT_ASSERT(arg.is_flag() && "Double hyphen ('--') should be treated as a flag."); - BLT_ASSERT(arg.value().empty() && "Double hyphen flag should have empty value."); - BLT_ASSERT(arg.get_flag() == "--" && "Double hyphen value should match the input string."); - } - - // Test Case 9: Validate edge case of an argument with spaces - void test_argument_string_t_with_spaces(const hashset_t& prefixes) - { - const argument_string_t arg(" ", prefixes); - BLT_ASSERT(!arg.is_flag() && "Arguments with spaces should not be treated as flags."); - BLT_ASSERT(arg.value() == " " && "Arguments with spaces should match the input string."); - } - - // Test Case 10: Validate arguments with numeric characters - void test_argument_string_t_numeric_flag(const hashset_t& prefixes) - { - const argument_string_t arg("-123", prefixes); - BLT_ASSERT(arg.is_flag() && "Numeric flags should still be treated as flags."); - BLT_ASSERT(arg.value() == "123" && "Numeric flag value should match the input string."); - } - - // Test Case 11: Ensure the constructor handles '+' flag correctly - void test_argument_string_t_plus_flag_basic(const hashset_t& prefixes) - { - const argument_string_t arg("+f", prefixes); - BLT_ASSERT(arg.is_flag() && "Expected argument to be identified as a flag."); - BLT_ASSERT(arg.value() == "f" && "Plus flag value should match the input string."); - } - - // Test Case 13: Handle edge case of a single plus (`+`) which might be ambiguous - void test_argument_string_t_single_plus(const hashset_t& prefixes) - { - const argument_string_t arg("+", prefixes); - BLT_ASSERT(arg.is_flag() && "Expected single plus (`+`) to be treated as a flag."); - BLT_ASSERT(arg.value().empty() && "Single plus flag should have empty value."); - BLT_ASSERT(arg.get_flag() == "+" && "Single plus flag should match the input string."); - } - - // Test Case 14: Handle arguments with prefix only (like '++') - void test_argument_string_t_double_plus(const hashset_t& prefixes) - { - const argument_string_t arg("++", prefixes); - BLT_ASSERT(arg.is_flag() && "Double plus ('++') should be treated as a flag."); - BLT_ASSERT(arg.value().empty() && "Double plus flag should have empty value."); - BLT_ASSERT(arg.get_flag() == "++" && "Double plus value should match the input string."); - } - - - void run_all_tests_argument_string_t() - { - const hashset_t prefixes = {'-', '+'}; - test_argument_string_t_flag_basic(prefixes); - test_argument_string_t_long_flag(prefixes); - test_argument_string_t_positional_argument(prefixes); - test_argument_string_t_empty_input(prefixes); - test_argument_string_t_single_hyphen(prefixes); - test_argument_string_t_double_hyphen(prefixes); - test_argument_string_t_with_spaces(prefixes); - test_argument_string_t_numeric_flag(prefixes); - test_argument_string_t_plus_flag_basic(prefixes); - test_argument_string_t_single_plus(prefixes); - test_argument_string_t_double_plus(prefixes); - } - - void test_argparse_empty() - { - const std::vector argv{"./program"}; - argument_parser_t parser; - const auto args = parser.parse(argv); - BLT_ASSERT(args.size() == 0 && "Empty argparse should have no args on output"); - } - - void test_single_flag_prefixes() - { - argument_parser_t parser; - parser.add_flag("-a").set_action(action_t::STORE_TRUE); - parser.add_flag("+b").set_action(action_t::STORE_FALSE); - parser.add_flag("/c").as_type().set_action(action_t::STORE); - - const std::vector args = {"./program", "-a", "+b", "/c", "42"}; - const auto parsed_args = parser.parse(args); - - BLT_ASSERT(parsed_args.get("-a") == true && "Flag '-a' should store `true`"); - BLT_ASSERT(parsed_args.get("+b") == false && "Flag '+b' should store `false`"); - BLT_ASSERT(parsed_args.get("/c") == 42 && "Flag '/c' should store the value 42"); - } - - // Test: Invalid flag prefixes - void test_invalid_flag_prefixes() - { - argument_parser_t parser; - parser.add_flag("-a"); - parser.add_flag("+b"); - parser.add_flag("/c"); - - const std::vector args = {"./program", "!d", "-a"}; - try - { - parser.parse(args); - BLT_ASSERT(false && "Parsing should fail with invalid flag prefix '!'"); - } - catch (...) - { - BLT_ASSERT(true && "Correctly threw on bad flag prefix"); - } - } - - void test_compound_flags() - { - argument_parser_t parser; - parser.add_flag("-v").as_type().set_action(action_t::COUNT); - - const std::vector args = {"./program", "-vvv"}; - const auto parsed_args = parser.parse(args); - - BLT_ASSERT(parsed_args.get("-v") == 3 && "Flag '-v' should count occurrences in compound form"); - } - - void test_combination_of_valid_and_invalid_flags() - { - using namespace argparse; - - argument_parser_t parser; - parser.add_flag("-x").as_type(); - parser.add_flag("/y").as_type(); - - const std::vector args = {"./program", "-x", "10", "!z", "/y", "value"}; - try - { - parser.parse(args); - BLT_ASSERT(false && "Parsing should fail due to invalid flag '!z'"); - } - catch (...) - { - BLT_ASSERT(true && "Correctly threw an exception for invalid flag"); - } - } - - void test_flags_with_different_actions() - { - using namespace argparse; - - argument_parser_t parser; - parser.add_flag("-k").as_type().set_action(action_t::STORE); // STORE action - parser.add_flag("-t").as_type().set_action(action_t::STORE_CONST).set_const(999); // STORE_CONST action - parser.add_flag("-f").set_action(action_t::STORE_FALSE); // STORE_FALSE action - parser.add_flag("-c").set_action(action_t::STORE_TRUE); // STORE_TRUE action - - const std::vector args = {"./program", "-k", "100", "-t", "-f", "-c"}; - const auto parsed_args = parser.parse(args); - - BLT_ASSERT(parsed_args.get("-k") == 100 && "Flag '-k' should store 100"); - BLT_ASSERT(parsed_args.get("-t") == 999 && "Flag '-t' should store a const value of 999"); - BLT_ASSERT(parsed_args.get("-f") == false && "Flag '-f' should store `false`"); - BLT_ASSERT(parsed_args.get("-c") == true && "Flag '-c' should store `true`"); - } - - // Helper function to simulate argument parsing for nargs tests - bool parse_arguments(const std::vector& args, const nargs_v expected_nargs) - { - argument_parser_t parser; - - std::vector arg_strings; - arg_strings.reserve(args.size()); - for (const auto& arg : args) - arg_strings.emplace_back(arg, parser.get_allowed_flag_prefixes()); - argument_consumer_t consumer{arg_strings}; - - parser.add_positional("positional").set_nargs(expected_nargs); - try - { - auto parsed_args = parser.parse(consumer); - return consumer.remaining() == 0; - } - catch (const std::exception& e) - { - return false; - } - } - - // Test case for nargs = 0 - void test_nargs_0() - { - std::cout << "[Running Test: test_nargs_0]\n"; - - // Valid case: No arguments - const std::vector valid_args = {"./program"}; - BLT_ASSERT(!parse_arguments(valid_args, 0) && "nargs=0: Should fail"); - - // Invalid case: 1 argument - const std::vector invalid_args = {"./program", "arg1"}; - BLT_ASSERT(!parse_arguments(invalid_args, 0) && "nargs=0: Should not accept any arguments"); - - std::cout << "Success: test_nargs_0\n"; - } - - // Test case for nargs = 1 - void test_nargs_1() - { - std::cout << "[Running Test: test_nargs_1]\n"; - - // Valid case: 1 argument - const std::vector valid_args = {"./program", "arg1"}; - BLT_ASSERT(parse_arguments(valid_args, 1) && "nargs=1: Should accept exactly 1 argument"); - - // Invalid case: 0 arguments - const std::vector invalid_args_0 = {"./program"}; - BLT_ASSERT(!parse_arguments(invalid_args_0, 1) && "nargs=1: Should not accept 0 arguments"); - - // Invalid case: 2 arguments - const std::vector invalid_args_2 = {"./program", "arg1", "arg2"}; - BLT_ASSERT(!parse_arguments(invalid_args_2, 1) && "nargs=1: Should not accept more than 1 argument"); - - std::cout << "Success: test_nargs_1\n"; - } - - // Test case for nargs = 2 - void test_nargs_2() - { - std::cout << "[Running Test: test_nargs_2]\n"; - - // Valid case: 2 arguments - const std::vector valid_args = {"./program", "arg1", "arg2"}; - BLT_ASSERT(!parse_arguments(valid_args, 2) && "nargs=2: Should fail as action is store"); - - // Invalid case: 0 arguments - const std::vector invalid_args_0 = {"./program"}; - BLT_ASSERT(!parse_arguments(invalid_args_0, 2) && "nargs=2: Should not accept 0 arguments"); - - // Invalid case: 1 argument - const std::vector invalid_args_1 = {"./program", "arg1"}; - BLT_ASSERT(!parse_arguments(invalid_args_1, 2) && "nargs=2: Should not accept less than 2 arguments"); - - // Invalid case: 3 arguments - const std::vector invalid_args_3 = {"./program", "arg1", "arg2", "arg3"}; - BLT_ASSERT(!parse_arguments(invalid_args_3, 2) && "nargs=2: Should not accept more than 2 arguments"); - - std::cout << "Success: test_nargs_2\n"; - } - - void test_nargs_all() - { - std::cout << "[Running Test: test_nargs_all]\n"; - - // Valid case: No arguments - const std::vector valid_args_0 = {"./program"}; - BLT_ASSERT(!parse_arguments(valid_args_0, argparse::nargs_t::ALL) && - "nargs=ALL: No arguments present. Should fail.)"); - - // Valid case: Multiple arguments - const std::vector valid_args_2 = {"./program", "arg1", "arg2"}; - BLT_ASSERT(parse_arguments(valid_args_2, argparse::nargs_t::ALL) && - "nargs=ALL: Should accept all remaining arguments"); - - // Valid case: Many arguments - const std::vector valid_args_many = {"./program", "arg1", "arg2", "arg3", "arg4"}; - BLT_ASSERT(parse_arguments(valid_args_many, argparse::nargs_t::ALL) && - "nargs=ALL: Should accept all remaining arguments"); - - std::cout << "Success: test_nargs_all\n"; - } - - // Test case for nargs_t::ALL_AT_LEAST_ONE - void test_nargs_all_at_least_one() - { - std::cout << "[Running Test: test_nargs_all_at_least_one]\n"; - - // Valid case: 1 argument - const std::vector valid_args_1 = {"./program", "arg1"}; - BLT_ASSERT(parse_arguments(valid_args_1, argparse::nargs_t::ALL_AT_LEAST_ONE) && - "nargs=ALL_AT_LEAST_ONE: Should accept at least one argument and consume it"); - - // Valid case: Multiple arguments - const std::vector valid_args_3 = {"./program", "arg1", "arg2", "arg3"}; - BLT_ASSERT(parse_arguments(valid_args_3, argparse::nargs_t::ALL_AT_LEAST_ONE) && - "nargs=ALL_AT_LEAST_ONE: Should accept at least one argument and consume all remaining arguments"); - - // Invalid case: No arguments - const std::vector invalid_args_0 = {"./program"}; - BLT_ASSERT(!parse_arguments(invalid_args_0, argparse::nargs_t::ALL_AT_LEAST_ONE) && - "nargs=ALL_AT_LEAST_ONE: Should reject if no arguments are provided"); - - std::cout << "Success: test_nargs_all_at_least_one\n"; - } - - void run_combined_flag_test() - { - std::cout << "[Running Test: run_combined_flag_test]\n"; - argument_parser_t parser; - - parser.add_flag("-a").set_action(action_t::STORE_TRUE); - parser.add_flag("--deep").set_action(action_t::STORE_FALSE); - parser.add_flag("-b", "--combined").set_action(action_t::STORE_CONST).set_const(50); - parser.add_flag("--append").set_action(action_t::APPEND).as_type(); - parser.add_flag("--required").set_required(true); - parser.add_flag("--default").set_default("I am a default value"); - parser.add_flag("-t").set_action(action_t::APPEND_CONST).set_dest("test").set_const(5); - parser.add_flag("-g").set_action(action_t::APPEND_CONST).set_dest("test").set_const(10); - parser.add_flag("-e").set_action(action_t::APPEND_CONST).set_dest("test").set_const(15); - parser.add_flag("-f").set_action(action_t::APPEND_CONST).set_dest("test").set_const(20); - parser.add_flag("-d").set_action(action_t::APPEND_CONST).set_dest("test").set_const(25); - parser.add_flag("--end").set_action(action_t::EXTEND).set_dest("wow").as_type(); - - const auto a1 = make_arguments("-a", "--required", "hello"); - const auto r1 = parser.parse(a1); - BLT_ASSERT(r1.get("-a") == true && "Flag '-a' should store true"); - BLT_ASSERT(r1.get("--default") == "I am a default value" && "Flag '--default' should store default value"); - BLT_ASSERT(r1.get("--required") == "hello" && "Flag '--required' should store 'hello'"); - - const auto a2 = make_arguments("-a", "--deep", "--required", "soft"); - const auto r2 = parser.parse(a2); - BLT_ASSERT(r2.get("-a") == true && "Flag '-a' should store true"); - BLT_ASSERT(r2.get("--deep") == false && "Flag '--deep' should store false"); - BLT_ASSERT(r2.get("--required") == "soft" && "Flag '--required' should store 'soft'"); - - const auto a3 = make_arguments("--required", "silly", "--combined", "-t", "-f", "-e"); - const auto r3 = parser.parse(a3); - BLT_ASSERT((r3.get>("test") == std::vector{5, 20, 15}) && "Flags should add to vector of {5, 20, 15}"); - BLT_ASSERT(r3.get("-b") == 50 && "Combined flag should store const of 50"); - - const auto a4 = make_arguments("--required", "crazy", "--end", "10", "12.05", "68.11", "100.00", "200532", "-d", "-t", "-g", "-e", "-f"); - const auto r4 = parser.parse(a4); - BLT_ASSERT( - (r4.get>("test") == std::vector{25, 5, 10, 15, 20}) && - "Expected test vector to be filled with all arguments in order of flags"); - BLT_ASSERT( - (r4.get>("wow") == std::vector{10, 12.05, 68.11, 100.00, 200532}) && - "Extend vector expected to contain all elements"); - - std::cout << "Success: run_combined_flag_test\n"; - } - - void run_subparser_test() - { - std::cout << "[Running Test: run_subparser_test]\n"; - argument_parser_t parser; - - parser.add_flag("--open").set_flag(); - - auto& subparser = parser.add_subparser("mode"); - auto& n1 = subparser.add_parser("n1"); - n1.add_flag("--silly").set_flag(); - n1.add_positional("path"); - - auto& n2 = subparser.add_parser("n2"); - n2.add_flag("--crazy").set_flag(); - n2.add_positional("path"); - n2.add_positional("output"); - - auto& n3 = subparser.add_parser("n3"); - n3.add_flag("--deep").set_flag(); - - const auto a1 = make_arguments("n1", "--silly"); - try - { - parser.parse(a1); - BLT_ASSERT(false && "Subparser should throw an error when positional not supplied"); - } - catch (...) - { - } - - const auto a2 = make_arguments("--open"); - try - { - parser.parse(a2); - BLT_ASSERT(false && "Subparser should throw an error when no subparser is supplied"); - } - catch (...) - { - } - - const auto a3 = make_arguments("n1", "--silly", "path"); - const auto r3 = parser.parse(a3); - BLT_ASSERT(r3.get("--open") == false && "Flag '--open' should default to false"); - BLT_ASSERT(r3.get("mode") == "n1" && "Subparser should store 'n1'"); - BLT_ASSERT(r3.get("path") == "path" && "Subparser path should be 'path'"); - - std::cout << "Success: run_subparser_test\n"; - } - - void run_argparse_flag_tests() - { - test_single_flag_prefixes(); - test_invalid_flag_prefixes(); - test_compound_flags(); - test_combination_of_valid_and_invalid_flags(); - test_flags_with_different_actions(); - run_combined_flag_test(); - run_subparser_test(); - } - - void run_all_nargs_tests() - { - test_nargs_0(); - test_nargs_1(); - test_nargs_2(); - test_nargs_all(); - test_nargs_all_at_least_one(); - std::cout << "All nargs tests passed successfully.\n"; - } - - void test() - { - run_all_tests_argument_string_t(); - test_argparse_empty(); - run_argparse_flag_tests(); - run_all_nargs_tests(); - } - - [[nodiscard]] std::string subparse_error::error_string() const - { - std::string message = "Subparser Error: "; - message += m_found_string; - message += " is not a valid command. Allowed commands are: {"; - for (const auto [i, allowed_string] : enumerate(m_allowed_strings)) - { - if (allowed_string.size() > 1) - message += '['; - for (const auto [j, alias] : enumerate(allowed_string)) - { - message += alias; - if (j < alias.size() - 2) - message += ", "; - else if (j < alias.size()) - message += ", or "; - } - if (allowed_string.size() > 1) - message += ']'; - if (i != m_allowed_strings.size() - 1) - message += ' '; - } - message += "}"; - return message; - } - } - argument_subparser_t& argument_parser_t::add_subparser(const std::string_view dest) { m_subparsers.emplace_back(dest, argument_subparser_t{*this}); @@ -584,6 +99,7 @@ namespace blt::argparse { if (!m_name) m_name = consumer.absolute_first().get_argument(); + argument_positional_storage_t positional_storage{m_positional_arguments}; hashset_t found_flags; argument_storage_t parsed_args; // first, we consume flags which may be part of this parser @@ -602,11 +118,11 @@ namespace blt::argparse if (consumer.peek().is_flag()) handle_compound_flags(found_flags, parsed_args, consumer, consumer.consume()); else - parse_positional(parsed_args, consumer, consumer.peek().get_argument()); + parse_positional(parsed_args, consumer, positional_storage, consumer.peek().get_argument()); } handle_missing_and_default_args(m_flag_arguments, found_flags, parsed_args, "flag"); - for (auto& [name, value] : m_positional_arguments.remaining()) + for (auto& [name, value] : positional_storage.remaining()) { std::visit(lambda_visitor{ [](const nargs_t) @@ -836,11 +352,12 @@ namespace blt::argparse }, flag->m_nargs); } - void argument_parser_t::parse_positional(argument_storage_t& parsed_args, argument_consumer_t& consumer, const std::string_view arg) + void argument_parser_t::parse_positional(argument_storage_t& parsed_args, argument_consumer_t& consumer, argument_positional_storage_t& storage, + const std::string_view arg) { - if (!m_positional_arguments.has_positional()) - throw detail::missing_argument_error(make_string("Error: No positional arguments were defined for this parser!")); - auto& positional = m_positional_arguments.next(); + if (!storage.has_positional()) + throw detail::missing_argument_error(make_string("Error: '", arg, "' positional argument does not match any defined for this parser")); + auto& positional = storage.next(); const auto dest = positional.m_dest.value_or(std::string{arg}); std::visit(lambda_visitor{ [&consumer, &positional, &dest, &parsed_args, arg](const nargs_t arg_enum) @@ -985,7 +502,7 @@ namespace blt::argparse "' is not a valid choice for argument '", arg, "'! Expected one of ", valid_choices)); } - args.push_back(std::string{consumer.consume().get_argument()}); + args.emplace_back(consumer.consume().get_argument()); } if (args.size() != static_cast(argc)) { @@ -1035,4 +552,524 @@ namespace blt::argparse } return vec; } + + namespace detail + { + // Unit Tests for class argument_string_t + // Test Case 1: Ensure the constructor handles flags correctly + void test_argument_string_t_flag_basic(const hashset_t& prefixes) + { + const argument_string_t arg("-f", prefixes); + BLT_ASSERT(arg.is_flag() && "Expected argument to be identified as a flag."); + BLT_ASSERT(arg.value() == "f" && "Flag value should match the input string."); + } + + // Test Case 2: Ensure the constructor handles long flags correctly + void test_argument_string_t_long_flag(const hashset_t& prefixes) + { + const argument_string_t arg("--file", prefixes); + BLT_ASSERT(arg.is_flag() && "Expected argument to be identified as a flag."); + BLT_ASSERT(arg.value() == "file" && "Long flag value should match the input string."); + } + + // Test Case 3: Ensure positional arguments are correctly identified + void test_argument_string_t_positional_argument(const hashset_t& prefixes) + { + const argument_string_t arg("filename.txt", prefixes); + BLT_ASSERT(!arg.is_flag() && "Expected argument to be identified as positional."); + BLT_ASSERT(arg.value() == "filename.txt" && "Positional argument value should match the input string."); + } + + // Test Case 5: Handle an empty string + void test_argument_string_t_empty_input(const hashset_t& prefixes) + { + const argument_string_t arg("", prefixes); + BLT_ASSERT(!arg.is_flag() && "Expected an empty input to be treated as positional, not a flag."); + BLT_ASSERT(arg.value().empty() && "Empty input should have an empty value."); + } + + // Test Case 6: Handle edge case of a single hyphen (`-`) which might be ambiguous + void test_argument_string_t_single_hyphen(const hashset_t& prefixes) + { + const argument_string_t arg("-", prefixes); + BLT_ASSERT(arg.is_flag() && "Expected single hyphen (`-`) to be treated as a flag."); + BLT_ASSERT(arg.value().empty() && "Single hyphen flag should have empty value."); + BLT_ASSERT(arg.get_flag() == "-" && "Single hyphen flag should match the input string."); + } + + // Test Case 8: Handle arguments with prefix only (like "--") + void test_argument_string_t_double_hyphen(const hashset_t& prefixes) + { + const argument_string_t arg("--", prefixes); + BLT_ASSERT(arg.is_flag() && "Double hyphen ('--') should be treated as a flag."); + BLT_ASSERT(arg.value().empty() && "Double hyphen flag should have empty value."); + BLT_ASSERT(arg.get_flag() == "--" && "Double hyphen value should match the input string."); + } + + // Test Case 9: Validate edge case of an argument with spaces + void test_argument_string_t_with_spaces(const hashset_t& prefixes) + { + const argument_string_t arg(" ", prefixes); + BLT_ASSERT(!arg.is_flag() && "Arguments with spaces should not be treated as flags."); + BLT_ASSERT(arg.value() == " " && "Arguments with spaces should match the input string."); + } + + // Test Case 10: Validate arguments with numeric characters + void test_argument_string_t_numeric_flag(const hashset_t& prefixes) + { + const argument_string_t arg("-123", prefixes); + BLT_ASSERT(arg.is_flag() && "Numeric flags should still be treated as flags."); + BLT_ASSERT(arg.value() == "123" && "Numeric flag value should match the input string."); + } + + // Test Case 11: Ensure the constructor handles '+' flag correctly + void test_argument_string_t_plus_flag_basic(const hashset_t& prefixes) + { + const argument_string_t arg("+f", prefixes); + BLT_ASSERT(arg.is_flag() && "Expected argument to be identified as a flag."); + BLT_ASSERT(arg.value() == "f" && "Plus flag value should match the input string."); + } + + // Test Case 13: Handle edge case of a single plus (`+`) which might be ambiguous + void test_argument_string_t_single_plus(const hashset_t& prefixes) + { + const argument_string_t arg("+", prefixes); + BLT_ASSERT(arg.is_flag() && "Expected single plus (`+`) to be treated as a flag."); + BLT_ASSERT(arg.value().empty() && "Single plus flag should have empty value."); + BLT_ASSERT(arg.get_flag() == "+" && "Single plus flag should match the input string."); + } + + // Test Case 14: Handle arguments with prefix only (like '++') + void test_argument_string_t_double_plus(const hashset_t& prefixes) + { + const argument_string_t arg("++", prefixes); + BLT_ASSERT(arg.is_flag() && "Double plus ('++') should be treated as a flag."); + BLT_ASSERT(arg.value().empty() && "Double plus flag should have empty value."); + BLT_ASSERT(arg.get_flag() == "++" && "Double plus value should match the input string."); + } + + + void run_all_tests_argument_string_t() + { + const hashset_t prefixes = {'-', '+'}; + test_argument_string_t_flag_basic(prefixes); + test_argument_string_t_long_flag(prefixes); + test_argument_string_t_positional_argument(prefixes); + test_argument_string_t_empty_input(prefixes); + test_argument_string_t_single_hyphen(prefixes); + test_argument_string_t_double_hyphen(prefixes); + test_argument_string_t_with_spaces(prefixes); + test_argument_string_t_numeric_flag(prefixes); + test_argument_string_t_plus_flag_basic(prefixes); + test_argument_string_t_single_plus(prefixes); + test_argument_string_t_double_plus(prefixes); + } + + void test_argparse_empty() + { + const std::vector argv{"./program"}; + argument_parser_t parser; + const auto args = parser.parse(argv); + BLT_ASSERT(args.size() == 0 && "Empty argparse should have no args on output"); + } + + void test_single_flag_prefixes() + { + argument_parser_t parser; + parser.add_flag("-a").set_action(action_t::STORE_TRUE); + parser.add_flag("+b").set_action(action_t::STORE_FALSE); + parser.add_flag("/c").as_type().set_action(action_t::STORE); + + const std::vector args = {"./program", "-a", "+b", "/c", "42"}; + const auto parsed_args = parser.parse(args); + + BLT_ASSERT(parsed_args.get("-a") == true && "Flag '-a' should store `true`"); + BLT_ASSERT(parsed_args.get("+b") == false && "Flag '+b' should store `false`"); + BLT_ASSERT(parsed_args.get("/c") == 42 && "Flag '/c' should store the value 42"); + } + + // Test: Invalid flag prefixes + void test_invalid_flag_prefixes() + { + argument_parser_t parser; + parser.add_flag("-a"); + parser.add_flag("+b"); + parser.add_flag("/c"); + + const std::vector args = {"./program", "!d", "-a"}; + try + { + parser.parse(args); + BLT_ASSERT(false && "Parsing should fail with invalid flag prefix '!'"); + } + catch (...) + { + BLT_ASSERT(true && "Correctly threw on bad flag prefix"); + } + } + + void test_compound_flags() + { + argument_parser_t parser; + parser.add_flag("-v").as_type().set_action(action_t::COUNT); + + const std::vector args = {"./program", "-vvv"}; + const auto parsed_args = parser.parse(args); + + BLT_ASSERT(parsed_args.get("-v") == 3 && "Flag '-v' should count occurrences in compound form"); + } + + void test_combination_of_valid_and_invalid_flags() + { + using namespace argparse; + + argument_parser_t parser; + parser.add_flag("-x").as_type(); + parser.add_flag("/y").as_type(); + + const std::vector args = {"./program", "-x", "10", "!z", "/y", "value"}; + try + { + parser.parse(args); + BLT_ASSERT(false && "Parsing should fail due to invalid flag '!z'"); + } + catch (...) + { + BLT_ASSERT(true && "Correctly threw an exception for invalid flag"); + } + } + + void test_flags_with_different_actions() + { + using namespace argparse; + + argument_parser_t parser; + parser.add_flag("-k").as_type().set_action(action_t::STORE); // STORE action + parser.add_flag("-t").as_type().set_action(action_t::STORE_CONST).set_const(999); // STORE_CONST action + parser.add_flag("-f").set_action(action_t::STORE_FALSE); // STORE_FALSE action + parser.add_flag("-c").set_action(action_t::STORE_TRUE); // STORE_TRUE action + + const std::vector args = {"./program", "-k", "100", "-t", "-f", "-c"}; + const auto parsed_args = parser.parse(args); + + BLT_ASSERT(parsed_args.get("-k") == 100 && "Flag '-k' should store 100"); + BLT_ASSERT(parsed_args.get("-t") == 999 && "Flag '-t' should store a const value of 999"); + BLT_ASSERT(parsed_args.get("-f") == false && "Flag '-f' should store `false`"); + BLT_ASSERT(parsed_args.get("-c") == true && "Flag '-c' should store `true`"); + } + + // Helper function to simulate argument parsing for nargs tests + bool parse_arguments(const std::vector& args, const nargs_v expected_nargs) + { + argument_parser_t parser; + + std::vector arg_strings; + arg_strings.reserve(args.size()); + for (const auto& arg : args) + arg_strings.emplace_back(arg, parser.get_allowed_flag_prefixes()); + argument_consumer_t consumer{arg_strings}; + + parser.add_positional("positional").set_nargs(expected_nargs); + try + { + auto parsed_args = parser.parse(consumer); + return consumer.remaining() == 0; + } + catch (const std::exception&) + { + return false; + } + } + + // Test case for nargs = 0 + void test_nargs_0() + { + std::cout << "[Running Test: test_nargs_0]\n"; + + // Valid case: No arguments + const std::vector valid_args = {"./program"}; + BLT_ASSERT(!parse_arguments(valid_args, 0) && "nargs=0: Should fail"); + + // Invalid case: 1 argument + const std::vector invalid_args = {"./program", "arg1"}; + BLT_ASSERT(!parse_arguments(invalid_args, 0) && "nargs=0: Should not accept any arguments"); + + std::cout << "Success: test_nargs_0\n"; + } + + // Test case for nargs = 1 + void test_nargs_1() + { + std::cout << "[Running Test: test_nargs_1]\n"; + + // Valid case: 1 argument + const std::vector valid_args = {"./program", "arg1"}; + BLT_ASSERT(parse_arguments(valid_args, 1) && "nargs=1: Should accept exactly 1 argument"); + + // Invalid case: 0 arguments + const std::vector invalid_args_0 = {"./program"}; + BLT_ASSERT(!parse_arguments(invalid_args_0, 1) && "nargs=1: Should not accept 0 arguments"); + + // Invalid case: 2 arguments + const std::vector invalid_args_2 = {"./program", "arg1", "arg2"}; + BLT_ASSERT(!parse_arguments(invalid_args_2, 1) && "nargs=1: Should not accept more than 1 argument"); + + std::cout << "Success: test_nargs_1\n"; + } + + // Test case for nargs = 2 + void test_nargs_2() + { + std::cout << "[Running Test: test_nargs_2]\n"; + + // Valid case: 2 arguments + const std::vector valid_args = {"./program", "arg1", "arg2"}; + BLT_ASSERT(!parse_arguments(valid_args, 2) && "nargs=2: Should fail as action is store"); + + // Invalid case: 0 arguments + const std::vector invalid_args_0 = {"./program"}; + BLT_ASSERT(!parse_arguments(invalid_args_0, 2) && "nargs=2: Should not accept 0 arguments"); + + // Invalid case: 1 argument + const std::vector invalid_args_1 = {"./program", "arg1"}; + BLT_ASSERT(!parse_arguments(invalid_args_1, 2) && "nargs=2: Should not accept less than 2 arguments"); + + // Invalid case: 3 arguments + const std::vector invalid_args_3 = {"./program", "arg1", "arg2", "arg3"}; + BLT_ASSERT(!parse_arguments(invalid_args_3, 2) && "nargs=2: Should not accept more than 2 arguments"); + + std::cout << "Success: test_nargs_2\n"; + } + + void test_nargs_all() + { + std::cout << "[Running Test: test_nargs_all]\n"; + + // Valid case: No arguments + const std::vector valid_args_0 = {"./program"}; + BLT_ASSERT(!parse_arguments(valid_args_0, argparse::nargs_t::ALL) && + "nargs=ALL: No arguments present. Should fail.)"); + + // Valid case: Multiple arguments + const std::vector valid_args_2 = {"./program", "arg1", "arg2"}; + BLT_ASSERT(parse_arguments(valid_args_2, argparse::nargs_t::ALL) && + "nargs=ALL: Should accept all remaining arguments"); + + // Valid case: Many arguments + const std::vector valid_args_many = {"./program", "arg1", "arg2", "arg3", "arg4"}; + BLT_ASSERT(parse_arguments(valid_args_many, argparse::nargs_t::ALL) && + "nargs=ALL: Should accept all remaining arguments"); + + std::cout << "Success: test_nargs_all\n"; + } + + // Test case for nargs_t::ALL_AT_LEAST_ONE + void test_nargs_all_at_least_one() + { + std::cout << "[Running Test: test_nargs_all_at_least_one]\n"; + + // Valid case: 1 argument + const std::vector valid_args_1 = {"./program", "arg1"}; + BLT_ASSERT(parse_arguments(valid_args_1, argparse::nargs_t::ALL_AT_LEAST_ONE) && + "nargs=ALL_AT_LEAST_ONE: Should accept at least one argument and consume it"); + + // Valid case: Multiple arguments + const std::vector valid_args_3 = {"./program", "arg1", "arg2", "arg3"}; + BLT_ASSERT(parse_arguments(valid_args_3, argparse::nargs_t::ALL_AT_LEAST_ONE) && + "nargs=ALL_AT_LEAST_ONE: Should accept at least one argument and consume all remaining arguments"); + + // Invalid case: No arguments + const std::vector invalid_args_0 = {"./program"}; + BLT_ASSERT(!parse_arguments(invalid_args_0, argparse::nargs_t::ALL_AT_LEAST_ONE) && + "nargs=ALL_AT_LEAST_ONE: Should reject if no arguments are provided"); + + std::cout << "Success: test_nargs_all_at_least_one\n"; + } + + void run_combined_flag_test() + { + std::cout << "[Running Test: run_combined_flag_test]\n"; + argument_parser_t parser; + + parser.add_flag("-a").set_action(action_t::STORE_TRUE); + parser.add_flag("--deep").set_action(action_t::STORE_FALSE); + parser.add_flag("-b", "--combined").set_action(action_t::STORE_CONST).set_const(50); + parser.add_flag("--append").set_action(action_t::APPEND).as_type(); + parser.add_flag("--required").set_required(true); + parser.add_flag("--default").set_default("I am a default value"); + parser.add_flag("-t").set_action(action_t::APPEND_CONST).set_dest("test").set_const(5); + parser.add_flag("-g").set_action(action_t::APPEND_CONST).set_dest("test").set_const(10); + parser.add_flag("-e").set_action(action_t::APPEND_CONST).set_dest("test").set_const(15); + parser.add_flag("-f").set_action(action_t::APPEND_CONST).set_dest("test").set_const(20); + parser.add_flag("-d").set_action(action_t::APPEND_CONST).set_dest("test").set_const(25); + parser.add_flag("--end").set_action(action_t::EXTEND).set_dest("wow").as_type(); + + const auto a1 = make_arguments("-a", "--required", "hello"); + const auto r1 = parser.parse(a1); + BLT_ASSERT(r1.get("-a") == true && "Flag '-a' should store true"); + BLT_ASSERT(r1.get("--default") == "I am a default value" && "Flag '--default' should store default value"); + BLT_ASSERT(r1.get("--required") == "hello" && "Flag '--required' should store 'hello'"); + + const auto a2 = make_arguments("-a", "--deep", "--required", "soft"); + const auto r2 = parser.parse(a2); + BLT_ASSERT(r2.get("-a") == true && "Flag '-a' should store true"); + BLT_ASSERT(r2.get("--deep") == false && "Flag '--deep' should store false"); + BLT_ASSERT(r2.get("--required") == "soft" && "Flag '--required' should store 'soft'"); + + const auto a3 = make_arguments("--required", "silly", "--combined", "-t", "-f", "-e"); + const auto r3 = parser.parse(a3); + BLT_ASSERT((r3.get>("test") == std::vector{5, 20, 15}) && "Flags should add to vector of {5, 20, 15}"); + BLT_ASSERT(r3.get("-b") == 50 && "Combined flag should store const of 50"); + + const auto a4 = make_arguments("--required", "crazy", "--end", "10", "12.05", "68.11", "100.00", "200532", "-d", "-t", "-g", "-e", "-f"); + const auto r4 = parser.parse(a4); + BLT_ASSERT( + (r4.get>("test") == std::vector{25, 5, 10, 15, 20}) && + "Expected test vector to be filled with all arguments in order of flags"); + BLT_ASSERT( + (r4.get>("wow") == std::vector{10, 12.05, 68.11, 100.00, 200532}) && + "Extend vector expected to contain all elements"); + + std::cout << "Success: run_combined_flag_test\n"; + } + + void run_subparser_test() + { + std::cout << "[Running Test: run_subparser_test]\n"; + argument_parser_t parser; + + parser.add_flag("--open").set_flag(); + + auto& subparser = parser.add_subparser("mode"); + + auto& n1 = subparser.add_parser("n1"); + n1.add_flag("--silly").set_flag(); + n1.add_positional("path"); + + auto& n2 = subparser.add_parser("n2"); + n2.add_flag("--crazy").set_flag(); + n2.add_positional("path"); + n2.add_positional("output"); + + auto& n3 = subparser.add_parser("n3"); + n3.add_flag("--deep").set_flag(); + + const auto a1 = make_arguments("n1", "--silly"); + try + { + parser.parse(a1); + BLT_ASSERT(false && "Subparser should throw an error when positional not supplied"); + } + catch (...) + { + } + + const auto a2 = make_arguments("--open"); + try + { + parser.parse(a2); + BLT_ASSERT(false && "Subparser should throw an error when no subparser is supplied"); + } + catch (...) + { + } + + const auto a3 = make_arguments("n1", "--silly", "path_n1"); + const auto r3 = parser.parse(a3); + BLT_ASSERT(r3.get("--open") == false && "Flag '--open' should default to false"); + BLT_ASSERT(r3.get("mode") == "n1" && "Subparser should store 'n1'"); + BLT_ASSERT(r3.get("path") == "path_n1" && "Subparser path should be 'path'"); + + const auto a4 = make_arguments("n2", "--crazy", "path"); + + try + { + parser.parse(a4); + BLT_ASSERT(false && "Subparser should throw an error when second positional is not supplied"); + } + catch (...) + { + } + + std::cout << std::endl; + + const auto a5 = make_arguments("--open", "n2", "path_n2", "output_n2"); + const auto r5 = parser.parse(a5); + BLT_ASSERT(r5.get("--open") == true && "Flag '--open' should store true"); + BLT_ASSERT(r5.get("mode") == "n2" && "Subparser should store 'n2'"); + BLT_ASSERT(r5.get("path") == "path_n2" && "Subparser path should be 'path'"); + BLT_ASSERT(r5.get("output") == "output_n2" && "Subparser output should be 'output'"); + + const auto a6 = make_arguments("not_an_option", "silly"); + + try + { + parser.parse(a6); + BLT_ASSERT(false && "Subparser should throw an error when first positional is not a valid subparser"); + } catch (const std::exception&) + { + } + + const auto a7 = make_arguments("n3"); + const auto r7 = parser.parse(a7); + BLT_ASSERT(r7.get("mode") == "n3" && "Subparser should store 'n3'"); + + std::cout << "Success: run_subparser_test\n"; + } + + void run_argparse_flag_tests() + { + test_single_flag_prefixes(); + test_invalid_flag_prefixes(); + test_compound_flags(); + test_combination_of_valid_and_invalid_flags(); + test_flags_with_different_actions(); + run_combined_flag_test(); + run_subparser_test(); + } + + void run_all_nargs_tests() + { + test_nargs_0(); + test_nargs_1(); + test_nargs_2(); + test_nargs_all(); + test_nargs_all_at_least_one(); + std::cout << "All nargs tests passed successfully.\n"; + } + + void test() + { + run_all_tests_argument_string_t(); + test_argparse_empty(); + run_argparse_flag_tests(); + run_all_nargs_tests(); + } + + [[nodiscard]] std::string subparse_error::error_string() const + { + std::string message = "Subparser Error: "; + message += m_found_string; + message += " is not a valid command. Allowed commands are: {"; + for (const auto [i, allowed_string] : enumerate(m_allowed_strings)) + { + if (allowed_string.size() > 1) + message += '['; + for (const auto [j, alias] : enumerate(allowed_string)) + { + message += alias; + if (static_cast(j) < static_cast(allowed_string.size()) - 2) + message += ", "; + else if (static_cast(j) < static_cast(allowed_string.size()) - 1) + message += ", or "; + } + if (allowed_string.size() > 1) + message += ']'; + if (i != m_allowed_strings.size() - 1) + message += ", "; + } + message += "}"; + return message; + } + } } From 8b03dda1fe56fefda01a663b152c961c1e94bc09 Mon Sep 17 00:00:00 2001 From: Brett Date: Sat, 22 Feb 2025 18:39:51 -0500 Subject: [PATCH 028/101] some help --- CMakeLists.txt | 2 +- include/blt/parse/argparse_v2.h | 80 +++++++---------- src/blt/parse/argparse_v2.cpp | 151 +++++++++++++++++++++++++++++++- 3 files changed, 184 insertions(+), 49 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ea6c6f..43e57f2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 4.0.23) +set(BLT_VERSION 4.0.24) set(BLT_TARGET BLT) diff --git a/include/blt/parse/argparse_v2.h b/include/blt/parse/argparse_v2.h index 4eac0ff..1f42898 100644 --- a/include/blt/parse/argparse_v2.h +++ b/include/blt/parse/argparse_v2.h @@ -22,7 +22,6 @@ #include #include #include -#include #include #include #include @@ -432,10 +431,8 @@ namespace blt::argparse { auto& data = storage.m_data[dest]; if (!std::holds_alternative>(data)) - { throw detail::type_error("Invalid type conversion. Trying to add type " + blt::type_string() + " but this does not match existing type index '" + std::to_string(data.index()) + "'!"); - } auto& converted_values = std::get>(data); for (const auto& value : values) converted_values.push_back(detail::arg_string_converter_t::convert(value)); @@ -456,41 +453,7 @@ namespace blt::argparse return set_action(action_t::STORE_TRUE); } - argument_builder_t& set_action(const action_t action) - { - m_action = action; - switch (m_action) - { - case action_t::STORE_TRUE: - set_nargs(0); - as_type(); - set_default(false); - break; - case action_t::STORE_FALSE: - set_nargs(0); - as_type(); - set_default(true); - break; - case action_t::STORE_CONST: - case action_t::APPEND_CONST: - set_nargs(0); - break; - case action_t::COUNT: - set_nargs(0); - as_type(); - break; - case action_t::EXTEND: - set_nargs(nargs_t::ALL); - break; - case action_t::HELP: - case action_t::VERSION: - set_nargs(0); - break; - default: - break; - } - return *this; - } + argument_builder_t& set_action(action_t action); argument_builder_t& set_required(const bool required) { @@ -602,9 +565,10 @@ namespace blt::argparse friend argument_subparser_t; public: - explicit argument_parser_t(const std::optional name = {}, const std::optional usage = {}, - const std::optional description = {}, const std::optional epilogue = {}): - m_name(name), m_usage(usage), m_description(description), m_epilogue(epilogue) + explicit argument_parser_t(const std::optional description = {}, const std::optional epilogue = {}, + const std::optional version = {}, + const std::optional usage = {}, const std::optional name = {}): + m_name(name), m_usage(usage), m_description(description), m_epilogue(epilogue), m_version(version) { } @@ -633,6 +597,18 @@ namespace blt::argparse argument_subparser_t& add_subparser(std::string_view dest); + argument_parser_t& with_help() + { + add_flag("--help", "-h").set_action(action_t::HELP); + return *this; + } + + argument_parser_t& with_version() + { + add_flag("--version").set_action(action_t::VERSION); + return *this; + } + argument_storage_t parse(argument_consumer_t& consumer); // NOLINT argument_storage_t parse(const std::vector& args) @@ -669,15 +645,15 @@ namespace blt::argparse void print_usage(); - void print_version(); + void print_version() const; - argument_parser_t& set_name(const std::string_view name) + argument_parser_t& set_name(const std::optional& name) { m_name = name; return *this; } - argument_parser_t& set_usage(const std::string_view usage) + argument_parser_t& set_usage(const std::optional& usage) { m_usage = usage; return *this; @@ -688,7 +664,7 @@ namespace blt::argparse return m_usage; } - argument_parser_t& set_description(const std::string_view description) + argument_parser_t& set_description(const std::optional& description) { m_description = description; return *this; @@ -699,7 +675,7 @@ namespace blt::argparse return m_description; } - argument_parser_t& set_epilogue(const std::string_view epilogue) + argument_parser_t& set_epilogue(const std::optional& epilogue) { m_epilogue = epilogue; return *this; @@ -710,6 +686,17 @@ namespace blt::argparse return m_epilogue; } + argument_parser_t& set_version(const std::optional& version) + { + m_epilogue = version; + return *this; + } + + [[nodiscard]] const std::optional& get_version() const + { + return m_version; + } + [[nodiscard]] const hashset_t& get_allowed_flag_prefixes() const { return allowed_flag_prefixes; @@ -732,6 +719,7 @@ namespace blt::argparse std::optional m_usage; std::optional m_description; std::optional m_epilogue; + std::optional m_version; std::vector> m_subparsers; std::vector> m_argument_builders; hashmap_t m_flag_arguments; diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp index b4fd55f..058495b 100644 --- a/src/blt/parse/argparse_v2.cpp +++ b/src/blt/parse/argparse_v2.cpp @@ -21,6 +21,8 @@ #include #include #include +#include +#include namespace blt::argparse { @@ -89,6 +91,60 @@ namespace blt::argparse return std::vector{"./program", strings...}; } + template + void add(std::string& out, T&& value, size_t line_size = 60) + { + const auto lines = string::split(out, '\n'); + auto str = ensure_is_string(std::forward(value)); + if (lines.empty()) + { + out = str; + return; + } + if (lines.back().size() + str.size() > line_size) + { + out += '\n'; + out += '\t'; + } + out += str; + } + + argument_builder_t& argument_builder_t::set_action(const action_t action) + { + m_action = action; + switch (m_action) + { + case action_t::STORE_TRUE: + set_nargs(0); + as_type(); + set_default(false); + break; + case action_t::STORE_FALSE: + set_nargs(0); + as_type(); + set_default(true); + break; + case action_t::STORE_CONST: + case action_t::APPEND_CONST: + set_nargs(0); + break; + case action_t::COUNT: + set_nargs(0); + as_type(); + break; + case action_t::EXTEND: + set_nargs(nargs_t::ALL); + break; + case action_t::HELP: + case action_t::VERSION: + set_nargs(0); + break; + default: + break; + } + return *this; + } + argument_subparser_t& argument_parser_t::add_subparser(const std::string_view dest) { m_subparsers.emplace_back(dest, argument_subparser_t{*this}); @@ -98,7 +154,7 @@ namespace blt::argparse argument_storage_t argument_parser_t::parse(argument_consumer_t& consumer) { if (!m_name) - m_name = consumer.absolute_first().get_argument(); + m_name = fs::base_name_sv(consumer.absolute_first().get_argument()); argument_positional_storage_t positional_storage{m_positional_arguments}; hashset_t found_flags; argument_storage_t parsed_args; @@ -146,14 +202,105 @@ namespace blt::argparse void argument_parser_t::print_help() { + print_usage(); + std::cout << std::endl; + std::string help; + if (!m_flag_arguments.empty()) + { + help += "Options:\n"; + hashmap_t> same_flags; + for (const auto& [key, value] : m_flag_arguments) + same_flags[value].emplace_back(key); + for (const auto& [builder, flag_list] : same_flags) + { + // find max size and algin? + add(help, '\t'); + for (const auto& [i, flag] : enumerate(flag_list)) + { + add(help, flag); + if (i != flag_list.size() - 1) + add(help, ", "); + } + } + } } void argument_parser_t::print_usage() { + if (!m_usage) + { + std::string usage = m_name.value_or(""); + + hashmap_t> singleFlags; + std::vector> compoundFlags; + + for (const auto& [key, value] : m_flag_arguments) + { + argument_string_t arg{key, allowed_flag_prefixes}; + if (arg.get_flag().size() == 1) + { + if (std::holds_alternative(value->m_nargs) && std::get(value->m_nargs) == 0) + singleFlags[arg.get_flag()].emplace_back(arg.get_name()); + else + compoundFlags.emplace_back(arg, value); + } else + compoundFlags.emplace_back(arg, value); + } + + for (const auto& [i, kv] : enumerate(singleFlags)) + { + const auto& [key, value] = kv; + add(usage, "["); + add(usage, key); + for (const auto& name : value) + add(usage, name); + add(usage, "]"); + if (i != singleFlags.size() - 1) + add(usage, " "); + } + + for (const auto& [i, kv] : enumerate(compoundFlags)) + { + const auto& [name, builder] = kv; + add(usage, "["); + add(usage, name.get_argument()); + auto lambda = [&]() + { + add(usage, " "); + add(usage, builder->m_metavar.value_or(string::toUpperCase(name.get_name()))); + }; + std::visit(lambda_visitor{[&](const nargs_t) + { + lambda(); + }, [&](const int argc) + { + if (argc == 0) + return; + lambda(); + }}, builder->m_nargs); + add(usage, "]"); + if (i != compoundFlags.size() - 1) + add(usage, " "); + } + + for (const auto& [i, pair] : enumerate(m_positional_arguments)) + { + const auto& [name, _] = pair; + add(usage, "<"); + add(usage, name); + add(usage, ">"); + if (i != m_positional_arguments.size() - 1) + add(usage, " "); + } + + m_usage = usage; + } + std::cout << "Usage: " << *m_usage << std::endl; } - void argument_parser_t::print_version() + void argument_parser_t::print_version() const { + std::cout << m_name.value_or("NO NAME") << " " << m_version.value_or("NO VERSION") << std::endl; } void argument_parser_t::handle_compound_flags(hashset_t& found_flags, argument_storage_t& parsed_args, From 7129c929eb00aa371900fa6ffebc204b82a3e513 Mon Sep 17 00:00:00 2001 From: Brett Date: Sun, 23 Feb 2025 23:41:34 -0500 Subject: [PATCH 029/101] some update --- CMakeLists.txt | 2 +- src/blt/parse/argparse_v2.cpp | 67 ++++++++++++++++++++++++++++------- 2 files changed, 56 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 43e57f2..23807ca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 4.0.24) +set(BLT_VERSION 4.0.25) set(BLT_TARGET BLT) diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp index 058495b..370a36e 100644 --- a/src/blt/parse/argparse_v2.cpp +++ b/src/blt/parse/argparse_v2.cpp @@ -91,7 +91,45 @@ namespace blt::argparse return std::vector{"./program", strings...}; } - template + class printer_mark_t + { + public: + void reset() + { + + } + + void next() + { + + } + + template + printer_mark_t& operator+=(T&& t) + { + + return *this; + } + private: + + }; + + class aligned_printer_t + { + public: + explicit aligned_printer_t(const size_t max_line_size = 60, const size_t spaces_per_tab = 4): max_line_size(max_line_size), + spaces_per_tab(spaces_per_tab) + { + } + + private: + std::string buffer; + size_t last_newline = 0; + size_t max_line_size; + size_t spaces_per_tab; + }; + + template void add(std::string& out, T&& value, size_t line_size = 60) { const auto lines = string::split(out, '\n'); @@ -243,7 +281,8 @@ namespace blt::argparse singleFlags[arg.get_flag()].emplace_back(arg.get_name()); else compoundFlags.emplace_back(arg, value); - } else + } + else compoundFlags.emplace_back(arg, value); } @@ -269,15 +308,18 @@ namespace blt::argparse add(usage, " "); add(usage, builder->m_metavar.value_or(string::toUpperCase(name.get_name()))); }; - std::visit(lambda_visitor{[&](const nargs_t) - { - lambda(); - }, [&](const int argc) - { - if (argc == 0) - return; - lambda(); - }}, builder->m_nargs); + std::visit(lambda_visitor{ + [&](const nargs_t) + { + lambda(); + }, + [&](const int argc) + { + if (argc == 0) + return; + lambda(); + } + }, builder->m_nargs); add(usage, "]"); if (i != compoundFlags.size() - 1) add(usage, " "); @@ -1153,7 +1195,8 @@ namespace blt::argparse { parser.parse(a6); BLT_ASSERT(false && "Subparser should throw an error when first positional is not a valid subparser"); - } catch (const std::exception&) + } + catch (const std::exception&) { } From 34184d46a373a7b96999de1d4f082f4b9e828dd3 Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Mon, 24 Feb 2025 18:28:38 -0500 Subject: [PATCH 030/101] need to update add --- CMakeLists.txt | 2 +- src/blt/parse/argparse_v2.cpp | 96 ++++++++++++++++++++++------------- 2 files changed, 62 insertions(+), 36 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 23807ca..e4a38f6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 4.0.25) +set(BLT_VERSION 4.0.26) set(BLT_TARGET BLT) diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp index 370a36e..766dacf 100644 --- a/src/blt/parse/argparse_v2.cpp +++ b/src/blt/parse/argparse_v2.cpp @@ -16,6 +16,7 @@ * along with this program. If not, see . */ #include +#include #include #include #include @@ -91,62 +92,87 @@ namespace blt::argparse return std::vector{"./program", strings...}; } - class printer_mark_t + class aligner_t { public: - void reset() + aligner_t(std::vector& buffer, const size_t start_index): + buffer(buffer), start_index(start_index) { - } - void next() + void align(const size_t spaces_between) const { - + size_t aligned_size = 0; + for (const auto& v : iterate(buffer).skip(start_index)) + aligned_size = std::max(aligned_size, v.size()); + aligned_size += spaces_between; + for (auto& v : iterate(buffer).skip(start_index)) + { + for (size_t i = v.size(); i < aligned_size; i++) + v += ' '; + } } - template - printer_mark_t& operator+=(T&& t) - { - - return *this; - } private: - + std::vector& buffer; + size_t start_index; }; class aligned_printer_t { public: - explicit aligned_printer_t(const size_t max_line_size = 60, const size_t spaces_per_tab = 4): max_line_size(max_line_size), - spaces_per_tab(spaces_per_tab) + explicit aligned_printer_t(std::string line_begin = "\t", const size_t max_line_size = 60, const size_t spaces_per_tab = 4): + line_begin(std::move(line_begin)), max_line_size(max_line_size) { + buffer.emplace_back(); + for (size_t i = 0; i < spaces_per_tab; i++) + spaces_from_tab += ' '; + } + + auto mark() + { + return aligner_t{buffer, buffer.size() - 1}; + } + + template + aligned_printer_t& add(T&& value) + { + auto str = ensure_is_string(std::forward(value)); + if (buffer.back().size() + str.size() > max_line_size) + buffer.emplace_back(replace_tabs(line_begin)); + buffer.back() += replace_tabs(str); + return *this; + } + + [[nodiscard]] std::string replace_tabs(std::string str) const + { + string::replaceAll(str, "\t", spaces_from_tab); + return str; + } + + template + aligned_printer_t& operator+=(T&& value) + { + return add(std::forward(value)); + } + + [[nodiscard]] auto iter() + { + return iterate(buffer); + } + + [[nodiscard]] auto iter() const + { + return iterate(buffer); } private: - std::string buffer; - size_t last_newline = 0; + std::vector buffer; + std::string line_begin; + std::string spaces_from_tab; size_t max_line_size; - size_t spaces_per_tab; }; - template - void add(std::string& out, T&& value, size_t line_size = 60) - { - const auto lines = string::split(out, '\n'); - auto str = ensure_is_string(std::forward(value)); - if (lines.empty()) - { - out = str; - return; - } - if (lines.back().size() + str.size() > line_size) - { - out += '\n'; - out += '\t'; - } - out += str; - } - argument_builder_t& argument_builder_t::set_action(const action_t action) { m_action = action; From fc27d7503a2411918d762cac4ea845263619044d Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Mon, 24 Feb 2025 22:22:21 -0500 Subject: [PATCH 031/101] some help --- CMakeLists.txt | 2 +- src/blt/parse/argparse_v2.cpp | 266 ++++++++++++++++++++++++++++------ 2 files changed, 224 insertions(+), 44 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e4a38f6..b01aaf5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 4.0.26) +set(BLT_VERSION 4.0.27) set(BLT_TARGET BLT) diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp index 766dacf..ac7d6e2 100644 --- a/src/blt/parse/argparse_v2.cpp +++ b/src/blt/parse/argparse_v2.cpp @@ -77,6 +77,23 @@ namespace blt::argparse return std::forward(t); } + template + std::string to_string(const T& t) + { + if constexpr (std::is_same_v || std::is_same_v) + { + return std::string(t); + } + else if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) + { + return std::string() + t; + } + else + { + return t; + } + } + template std::string make_string(Strings&&... strings) { @@ -92,36 +109,132 @@ namespace blt::argparse return std::vector{"./program", strings...}; } + class aligned_internal_string_t + { + public: + explicit aligned_internal_string_t(std::string& str, const size_t max_line_length, + const size_t line_start_size): string(str), max_line_size(max_line_length), + line_start_size(line_start_size) + { + } + + void add(const std::string_view str) const + { + const auto lines = string::split(string, '\n'); + if (lines.empty()) + { + string += str; + return; + } + if (lines.back().size() + str.size() > max_line_size) + { + string += '\n'; + for (size_t i = 0; i < line_start_size; i++) + string += ' '; + bool blank = true; + // we don't want to write blank only strings + for (const char c : str) + { + if (!std::isblank(c)) + { + blank = false; + break; + } + } + if (blank) + return; + } + + string += str; + } + + template + aligned_internal_string_t& operator+=(T&& value) + { + const auto str = to_string(ensure_is_string(std::forward(value))); + for (size_t i = 0; i < str.size(); i++) + { + size_t j = i; + for (; j < str.size() && !std::isblank(str[j]); ++j) + { + } + add(std::string_view(str.data() + i, j - i)); + if (j < str.size()) + add(std::string_view(str.data() + j, 1)); + i = j; + } + return *this; + } + + private: + std::string& string; + size_t max_line_size; + size_t line_start_size; + }; + class aligner_t { public: - aligner_t(std::vector& buffer, const size_t start_index): - buffer(buffer), start_index(start_index) + aligner_t(std::vector& buffer, const size_t start_index, const size_t max_line_size): + buffer(buffer), start_index(start_index), max_line_size(max_line_size) { } void align(const size_t spaces_between) const { + const size_t take = compute_take(); size_t aligned_size = 0; - for (const auto& v : iterate(buffer).skip(start_index)) - aligned_size = std::max(aligned_size, v.size()); - aligned_size += spaces_between; - for (auto& v : iterate(buffer).skip(start_index)) + for (const auto& v : iterate(buffer).skip(start_index).take(take)) { - for (size_t i = v.size(); i < aligned_size; i++) + aligned_size = std::max(aligned_size, v.size()); + } + + for (auto& v : iterate(buffer).skip(start_index).take(take)) + { + auto offset_size = aligned_size + spaces_between; + + for (size_t i = v.size(); i < offset_size; i++) v += ' '; } } + [[nodiscard]] auto iter() + { + return iterate(buffer).skip(start_index).take(compute_take()).map([this](std::string& x) + { + return aligned_internal_string_t{x, max_line_size, buffer[start_index].size()}; + }); + } + + [[nodiscard]] auto iter() const + { + return iterate(buffer).skip(start_index).take(compute_take()).map([this](std::string& x) + { + return aligned_internal_string_t{x, max_line_size, buffer[start_index].size()}; + }); + } + + void take(const size_t amount) + { + this->amount = amount; + } + private: + [[nodiscard]] size_t compute_take() const + { + return amount == -1ul ? (buffer.size() - start_index - 1) : amount;; + } + std::vector& buffer; size_t start_index; + size_t max_line_size; + size_t amount = -1; }; class aligned_printer_t { public: - explicit aligned_printer_t(std::string line_begin = "\t", const size_t max_line_size = 60, const size_t spaces_per_tab = 4): + explicit aligned_printer_t(std::string line_begin = "\t", const size_t max_line_size = 120, const size_t spaces_per_tab = 4): line_begin(std::move(line_begin)), max_line_size(max_line_size) { buffer.emplace_back(); @@ -129,28 +242,44 @@ namespace blt::argparse spaces_from_tab += ' '; } + [[nodiscard]] std::string str() const + { + std::string combined; + for (const auto& str : buffer) + { + combined += str; + combined += '\n'; + } + return combined; + } + auto mark() { - return aligner_t{buffer, buffer.size() - 1}; + return aligner_t{buffer, buffer.size() - 1, max_line_size}; } template aligned_printer_t& add(T&& value) { - auto str = ensure_is_string(std::forward(value)); + const auto str = to_string(ensure_is_string(std::forward(value))); if (buffer.back().size() + str.size() > max_line_size) - buffer.emplace_back(replace_tabs(line_begin)); + newline(); buffer.back() += replace_tabs(str); return *this; } + void newline() + { + buffer.emplace_back(replace_tabs(line_begin)); + } + [[nodiscard]] std::string replace_tabs(std::string str) const { string::replaceAll(str, "\t", spaces_from_tab); return str; } - template + template aligned_printer_t& operator+=(T&& value) { return add(std::forward(value)); @@ -267,40 +396,85 @@ namespace blt::argparse void argument_parser_t::print_help() { print_usage(); - std::cout << std::endl; - std::string help; + aligned_printer_t help{""}; if (!m_flag_arguments.empty()) { - help += "Options:\n"; + help += "Options:"; + help.newline(); hashmap_t> same_flags; for (const auto& [key, value] : m_flag_arguments) same_flags[value].emplace_back(key); + auto mark = help.mark(); for (const auto& [builder, flag_list] : same_flags) { - // find max size and algin? - add(help, '\t'); + // find max size and align? + help += '\t'; for (const auto& [i, flag] : enumerate(flag_list)) { - add(help, flag); + help += flag; if (i != flag_list.size() - 1) - add(help, ", "); + help += ", "; } + const argument_string_t arg{flag_list.front(), allowed_flag_prefixes}; + auto metavar = builder->m_metavar.value_or(string::toUpperCase(arg.get_name())); + auto lambda = [&]() + { + help += ' '; + help += metavar; + }; + std::visit(lambda_visitor{ + [&](const nargs_t type) + { + lambda(); + switch (type) + { + case nargs_t::IF_POSSIBLE: + break; + case nargs_t::ALL: + case nargs_t::ALL_AT_LEAST_ONE: + help += "..."; + break; + } + }, + [&](const int argc) + { + if (argc == 0) + return; + lambda(); + if (argc > 1) + { + help += "... x"; + help += std::to_string(argc); + } + } + }, builder->m_nargs); + help.newline(); + } + mark.align(4); + for (auto [str, pair] : mark.iter().zip(same_flags)) + { + auto& [builder, flag_list] = pair; + str += builder->m_help.value_or(""); } } + + std::cout << help.str() << std::endl; } void argument_parser_t::print_usage() { if (!m_usage) { - std::string usage = m_name.value_or(""); + aligned_printer_t aligner; + aligner += m_name.value_or(""); + aligner += ' '; hashmap_t> singleFlags; std::vector> compoundFlags; for (const auto& [key, value] : m_flag_arguments) { - argument_string_t arg{key, allowed_flag_prefixes}; + const argument_string_t arg{key, allowed_flag_prefixes}; if (arg.get_flag().size() == 1) { if (std::holds_alternative(value->m_nargs) && std::get(value->m_nargs) == 0) @@ -315,53 +489,59 @@ namespace blt::argparse for (const auto& [i, kv] : enumerate(singleFlags)) { const auto& [key, value] = kv; - add(usage, "["); - add(usage, key); + aligner += '['; + aligner += key; for (const auto& name : value) - add(usage, name); - add(usage, "]"); - if (i != singleFlags.size() - 1) - add(usage, " "); + aligner += name; + aligner += ']'; + aligner += ' '; } for (const auto& [i, kv] : enumerate(compoundFlags)) { const auto& [name, builder] = kv; - add(usage, "["); - add(usage, name.get_argument()); + aligner += '['; + aligner += name.get_argument(); auto lambda = [&]() { - add(usage, " "); - add(usage, builder->m_metavar.value_or(string::toUpperCase(name.get_name()))); + aligner += ' '; + aligner += builder->m_metavar.value_or(string::toUpperCase(name.get_name())); }; std::visit(lambda_visitor{ - [&](const nargs_t) + [&](const nargs_t type) { lambda(); + switch (type) + { + case nargs_t::IF_POSSIBLE: + break; + case nargs_t::ALL: + case nargs_t::ALL_AT_LEAST_ONE: + aligner += "..."; + break; + } }, [&](const int argc) { - if (argc == 0) - return; - lambda(); + for (int j = 0; j < argc; j++) + lambda(); } }, builder->m_nargs); - add(usage, "]"); - if (i != compoundFlags.size() - 1) - add(usage, " "); + aligner += ']'; + aligner += ' '; } for (const auto& [i, pair] : enumerate(m_positional_arguments)) { const auto& [name, _] = pair; - add(usage, "<"); - add(usage, name); - add(usage, ">"); + aligner += '<'; + aligner += name; + aligner += '>'; if (i != m_positional_arguments.size() - 1) - add(usage, " "); + aligner += ' '; } - m_usage = usage; + m_usage = aligner.str(); } std::cout << "Usage: " << *m_usage << std::endl; } From 1b6d23fad4eb452ecd529f940b9639fa45830fe3 Mon Sep 17 00:00:00 2001 From: Brett Date: Tue, 25 Feb 2025 02:14:19 -0500 Subject: [PATCH 032/101] more help --- CMakeLists.txt | 2 +- src/blt/parse/argparse_v2.cpp | 42 ++++++++++++++++++++++++++++++----- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b01aaf5..2807d26 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 4.0.27) +set(BLT_VERSION 4.0.28) set(BLT_TARGET BLT) diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp index ac7d6e2..1254909 100644 --- a/src/blt/parse/argparse_v2.cpp +++ b/src/blt/parse/argparse_v2.cpp @@ -186,13 +186,16 @@ namespace blt::argparse size_t aligned_size = 0; for (const auto& v : iterate(buffer).skip(start_index).take(take)) { - aligned_size = std::max(aligned_size, v.size()); + auto size = static_cast(v.size()); + for (; size > 0 && std::isblank(v[size - 1]); size--) + { + } + aligned_size = std::max(aligned_size, static_cast(size)); } + const auto offset_size = aligned_size + spaces_between; for (auto& v : iterate(buffer).skip(start_index).take(take)) { - auto offset_size = aligned_size + spaces_between; - for (size_t i = v.size(); i < offset_size; i++) v += ' '; } @@ -456,6 +459,35 @@ namespace blt::argparse auto& [builder, flag_list] = pair; str += builder->m_help.value_or(""); } + mark.align(4); + for (auto [str, pair] : mark.iter().zip(same_flags)) + { + auto& [builder, flag_list] = pair; + if (builder->m_default_value) + { + str += "(Default: "; + std::visit(detail::arg_meta_type_helper_t::make_visitor( + [&](auto& value) + { + str += value; + }, + [&](auto& vec) + { + if constexpr (!std::is_same_v>, std::vector>) + { + str += '['; + for (const auto& [i, v] : enumerate(vec)) + { + str += v; + if (i != vec.size() - 1) + str += ", "; + } + str += ']'; + } + }), *builder->m_default_value); + str += ")"; + } + } } std::cout << help.str() << std::endl; @@ -500,7 +532,7 @@ namespace blt::argparse for (const auto& [i, kv] : enumerate(compoundFlags)) { const auto& [name, builder] = kv; - aligner += '['; + aligner += builder->m_required ? '<' : '['; aligner += name.get_argument(); auto lambda = [&]() { @@ -527,7 +559,7 @@ namespace blt::argparse lambda(); } }, builder->m_nargs); - aligner += ']'; + aligner += builder->m_required ? '>' : ']'; aligner += ' '; } From 0913208e6b409d5ec291996092c677acbb90f0f3 Mon Sep 17 00:00:00 2001 From: Brett Date: Tue, 25 Feb 2025 12:34:14 -0500 Subject: [PATCH 033/101] help with choices --- CMakeLists.txt | 2 +- include/blt/parse/argparse_v2.h | 21 ++++++++++++++++- src/blt/parse/argparse_v2.cpp | 41 ++++++++++++++++++--------------- 3 files changed, 44 insertions(+), 20 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2807d26..614e651 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 4.0.28) +set(BLT_VERSION 4.0.29) set(BLT_TARGET BLT) diff --git a/include/blt/parse/argparse_v2.h b/include/blt/parse/argparse_v2.h index 1f42898..d7dc348 100644 --- a/include/blt/parse/argparse_v2.h +++ b/include/blt/parse/argparse_v2.h @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -227,6 +228,16 @@ namespace blt::argparse } }; + template + auto ensure_is_string(T&& t) + { + if constexpr (std::is_arithmetic_v> && !(std::is_same_v + || std::is_same_v || std::is_same_v)) + return std::to_string(std::forward(t)); + else + return std::forward(t); + } + void test(); } @@ -493,6 +504,14 @@ namespace blt::argparse return *this; } + template + argument_builder_t& set_choices(Args&&... args) + { + m_choices = hashset_t{}; + ((m_choices->emplace(detail::ensure_is_string(std::forward(args)))), ...); + return *this; + } + argument_builder_t& set_default(const detail::arg_data_t& default_value) { m_default_value = default_value; @@ -566,7 +585,7 @@ namespace blt::argparse public: explicit argument_parser_t(const std::optional description = {}, const std::optional epilogue = {}, - const std::optional version = {}, + const std::optional version = {}, const std::optional usage = {}, const std::optional name = {}): m_name(name), m_usage(usage), m_description(description), m_epilogue(epilogue), m_version(version) { diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp index 1254909..d8f298c 100644 --- a/src/blt/parse/argparse_v2.cpp +++ b/src/blt/parse/argparse_v2.cpp @@ -67,16 +67,6 @@ namespace blt::argparse } } - template - auto ensure_is_string(T&& t) - { - if constexpr (std::is_arithmetic_v> && !(std::is_same_v - || std::is_same_v || std::is_same_v)) - return std::to_string(std::forward(t)); - else - return std::forward(t); - } - template std::string to_string(const T& t) { @@ -99,7 +89,7 @@ namespace blt::argparse { std::string out; out.reserve((get_const_char_size(strings) + ...)); - ((out += ensure_is_string(std::forward(strings))), ...); + ((out += detail::ensure_is_string(std::forward(strings))), ...); return out; } @@ -151,7 +141,7 @@ namespace blt::argparse template aligned_internal_string_t& operator+=(T&& value) { - const auto str = to_string(ensure_is_string(std::forward(value))); + const auto str = to_string(detail::ensure_is_string(std::forward(value))); for (size_t i = 0; i < str.size(); i++) { size_t j = i; @@ -166,6 +156,11 @@ namespace blt::argparse return *this; } + [[nodiscard]] std::string& str() const + { + return string; + } + private: std::string& string; size_t max_line_size; @@ -264,7 +259,7 @@ namespace blt::argparse template aligned_printer_t& add(T&& value) { - const auto str = to_string(ensure_is_string(std::forward(value))); + const auto str = to_string(detail::ensure_is_string(std::forward(value))); if (buffer.back().size() + str.size() > max_line_size) newline(); buffer.back() += replace_tabs(str); @@ -458,13 +453,10 @@ namespace blt::argparse { auto& [builder, flag_list] = pair; str += builder->m_help.value_or(""); - } - mark.align(4); - for (auto [str, pair] : mark.iter().zip(same_flags)) - { - auto& [builder, flag_list] = pair; if (builder->m_default_value) { + if (!std::isblank(str.str().back())) + str += " "; str += "(Default: "; std::visit(detail::arg_meta_type_helper_t::make_visitor( [&](auto& value) @@ -487,6 +479,19 @@ namespace blt::argparse }), *builder->m_default_value); str += ")"; } + if (builder->m_choices) + { + if (!std::isblank(str.str().back())) + str += " "; + str += "(Choices: "; + for (const auto& [i, v] : enumerate(*builder->m_choices)) + { + str += v; + if (i != builder->m_choices->size() - 1) + str += ", "; + } + str += ')'; + } } } From 17e6b507eae8232baba4382106a4a14fc776168f Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Tue, 25 Feb 2025 16:29:36 -0500 Subject: [PATCH 034/101] parsing --- ...ts (conflicted copy 2025-02-24 222236).txt | 204 ++++++++++++++++++ CMakeLists.txt | 2 +- include/blt/parse/argparse_v2.h | 8 +- src/blt/parse/argparse_v2.cpp | 77 +++++-- 4 files changed, 269 insertions(+), 22 deletions(-) create mode 100644 CMakeLists (conflicted copy 2025-02-24 222236).txt diff --git a/CMakeLists (conflicted copy 2025-02-24 222236).txt b/CMakeLists (conflicted copy 2025-02-24 222236).txt new file mode 100644 index 0000000..b01aaf5 --- /dev/null +++ b/CMakeLists (conflicted copy 2025-02-24 222236).txt @@ -0,0 +1,204 @@ +cmake_minimum_required(VERSION 3.20) +include(cmake/color.cmake) +set(BLT_VERSION 4.0.27) + +set(BLT_TARGET BLT) + +project(BLT VERSION ${BLT_VERSION}) + +set(CMAKE_CXX_STANDARD 17) + +option(ENABLE_ADDRSAN "Enable the address sanitizer" OFF) +option(ENABLE_UBSAN "Enable the ub sanitizer" OFF) +option(ENABLE_TSAN "Enable the thread data race sanitizer" OFF) + +option(BUILD_STD "Build the BLT standard utilities." ON) +option(BUILD_PROFILING "Build the BLT profiler extension" ON) +option(BUILD_FS "Build the BLT FS utilities including the NBT + eNBT extension" ON) +option(BUILD_PARSE "Build the BLT parsers" ON) +option(BUILD_FORMAT "Build the BLT formatters" ON) + +option(BUILD_TESTS "Build the BLT test set" OFF) + +option(BLT_DISABLE_STATS "Disable tracking stats in certain objects. Enabling this will cause stat functions to return 0" OFF) +option(BLT_DISABLE_LOGGING "Disable blt::logging (all macros and will safely disable logging function!)" OFF) +option(BLT_DISABLE_TRACE "Disable blt::logging BLT_TRACE macro" OFF) +option(BLT_DISABLE_DEBUG "Disable blt::logging BLT_DEBUG macro" OFF) +option(BLT_DISABLE_INFO "Disable blt::logging BLT_INFO macro" OFF) +option(BLT_DISABLE_WARN "Disable blt::logging BLT_WARN macro" OFF) +option(BLT_DISABLE_ERROR "Disable blt::logging BLT_ERROR macro" OFF) +option(BLT_DISABLE_FATAL "Disable blt::logging BLT_FATAL macro" OFF) + +if(${BLT_DISABLE_STATS}) + add_compile_definitions(BLT_DISABLE_STATS) +endif () + +find_program(MOLD "mold") + +configure_file(include/blt/config.h.in config/blt/config.h @ONLY) + +message("Enabling library compilation") +if (${BUILD_STD} OR ${BUILD_PROFILING}) + message(STATUS "Building ${Yellow}standard${ColourReset} cxx files") + file(GLOB_RECURSE STD_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/blt/std/*.cpp") +else () + set(STD_FILES "") +endif () + +if (${BUILD_PROFILING}) + message(STATUS "Building ${Yellow}profiling${ColourReset} cxx files") + file(GLOB_RECURSE PROFILING_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/blt/profiling/*.cpp") +else () + set(PROFILING_FILES "") +endif () + +if (${BUILD_FS}) + message(STATUS "Building ${Yellow}filesystem${ColourReset} cxx files") + file(GLOB_RECURSE FS_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/blt/fs/*.cpp") +else () + set(FS_FILES "") +endif () + +if (${BUILD_PARSE}) + message(STATUS "Building ${Yellow}parser${ColourReset} cxx files") + file(GLOB_RECURSE PARSE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/blt/parse/*.cpp") +else () + set(PARSE_FILES "") +endif () + +if (${BUILD_FORMAT}) + message(STATUS "Building ${Yellow}format${ColourReset} cxx files") + file(GLOB_RECURSE FORMAT_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/blt/format/*.cpp") +else () + set(FORMAT_FILES "" + include/blt/std/iterator.h) +endif () + +if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/libraries/parallel-hashmap) + message("Found Parallel Hashmaps library, using ${Yellow}phmap${ColourReset} over ${Red}std::unordered_map${ColourReset}") + include_directories(${CMAKE_CURRENT_SOURCE_DIR}/libraries/parallel-hashmap) +else() + message("Parallel Hashmaps library not found! using ${Yellow}std::unordered_map${ColourReset}") +endif () + +#include zlib if the user has it. +find_package(ZLIB QUIET) + +if (${ZLIB_FOUND}) + include_directories(${ZLIB_INCLUDE_DIRS}) +else () + message("ZLIB was not found, this is fine however if you wish you use gzip with NBT it is required.") +endif () + +include_directories(include/) +include_directories(${CMAKE_CURRENT_BINARY_DIR}/config/) + +add_library(${BLT_TARGET} ${STD_FILES} ${PROFILING_FILES} ${FS_FILES} ${PARSE_FILES} ${FORMAT_FILES}) + +string(REPLACE "+" "\\+" escaped_source ${CMAKE_CURRENT_SOURCE_DIR}) +string(APPEND escaped_source "/src/blt/.*/") +list(TRANSFORM STD_FILES REPLACE ${escaped_source} "") +list(TRANSFORM PROFILING_FILES REPLACE ${escaped_source} "") +list(TRANSFORM FS_FILES REPLACE ${escaped_source} "") +list(TRANSFORM PARSE_FILES REPLACE ${escaped_source} "") +message("Standard Files ${Magenta}${STD_FILES}${ColourReset}") +message("Profiler Files ${Magenta}${PROFILING_FILES}${ColourReset}") +message("FS Files ${Magenta}${FS_FILES}${ColourReset}") +message("Parser Files ${Magenta}${PARSE_FILES}${ColourReset}") +message("Source: ${CMAKE_SOURCE_DIR}") +message("Current Source: ${CMAKE_CURRENT_SOURCE_DIR}") +message("Binary: ${CMAKE_BINARY_DIR}") +message("Current Binary: ${CMAKE_CURRENT_BINARY_DIR}") + +if (${ZLIB_FOUND}) + target_link_libraries(${BLT_TARGET} PUBLIC ZLIB::ZLIB) +endif () + +include(cmake/warnings.cmake) + +target_include_directories(${BLT_TARGET} PUBLIC include/) +target_include_directories(${BLT_TARGET} PUBLIC ${CMAKE_CURRENT_BINARY_DIR}/config/) +if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/libraries/parallel-hashmap) + message("Including Parallel Hashmap directory") + target_include_directories(${BLT_TARGET} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/libraries/parallel-hashmap) +endif () + +message("BLT ${Yellow}${BLT_VERSION}${ColourReset} Successfully included!") + +message("Installing to ${CMAKE_INSTALL_LIBDIR} with headers at ${CMAKE_INSTALL_INCLUDEDIR}") + +file(GLOB_RECURSE BLT_HEADER_FILES "${CMAKE_CURRENT_SOURCE_DIR}/include/*.h") +foreach (S ${BLT_HEADER_FILES}) + string(REPLACE "${CMAKE_CURRENT_SOURCE_DIR}/include/" "" SO ${S}) + string(REGEX REPLACE "\/[A-Z|a-z|0-9|_|-]*\\.h" "/" SA ${SO}) + list(APPEND BLT_F_HEADERS ${SA}) + install(FILES ${S} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${SA}) +endforeach () + +install(FILES ${CMAKE_BINARY_DIR}/config/blt/config.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/blt/) + +set_target_properties(${BLT_TARGET} PROPERTIES VERSION ${BLT_VERSION}) +set_target_properties(${BLT_TARGET} PROPERTIES SOVERSION ${PROJECT_VERSION_MAJOR}) +if (NOT ${MOLD} STREQUAL MOLD-NOTFOUND) + target_link_options(${BLT_TARGET} PUBLIC -fuse-ld=mold) +endif () + +install(TARGETS ${BLT_TARGET} + CONFIGURATIONS RelWithDebInfo + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + +macro(blt_add_test name source type) + + message("Adding project ${name} of type ${type}" DEBUG) + project(${name}-${type}) + + add_executable(${name}-${type} ${source}) + + if (NOT ${MOLD} STREQUAL MOLD-NOTFOUND) + add_link_options(-fuse-ld=mold) + endif () + + target_link_libraries(${name}-${type} PRIVATE BLT) + + target_compile_options(${name}-${type} PRIVATE -Wall -Wextra -Wpedantic -Wno-comment) + target_link_options(${name}-${type} PRIVATE -Wall -Wextra -Wpedantic -Wno-comment) + target_compile_definitions(${name}-${type} PRIVATE BLT_DEBUG_LEVEL=${DEBUG_LEVEL}) + + if (${TRACK_ALLOCATIONS}) + target_compile_definitions(${name}-${type} PRIVATE BLT_TRACK_ALLOCATIONS=1) + endif () + + if (${ENABLE_ADDRSAN} MATCHES ON) + target_compile_options(${name}-${type} PRIVATE -fsanitize=address) + target_link_options(${name}-${type} PRIVATE -fsanitize=address) + endif () + + if (${ENABLE_UBSAN} MATCHES ON) + target_compile_options(${name}-${type} PRIVATE -fsanitize=undefined) + target_link_options(${name}-${type} PRIVATE -fsanitize=undefined) + endif () + + if (${ENABLE_TSAN} MATCHES ON) + target_compile_options(${name}-${type} PRIVATE -fsanitize=thread) + target_link_options(${name}-${type} PRIVATE -fsanitize=thread) + endif () + + add_test(NAME ${name} COMMAND ${name}-${type}) + + set(failRegex "\\[WARN\\]" "FAIL" "ERROR" "FATAL" "exception") + set_property(TEST ${name} PROPERTY FAIL_REGULAR_EXPRESSION "${failRegex}") + + project(${BLT_TARGET}) +endmacro() + +if (${BUILD_TESTS}) + message("Building tests for version ${BLT_VERSION}") + + blt_add_test(blt_iterator tests/iterator_tests.cpp test) + blt_add_test(blt_argparse tests/argparse_tests.cpp test) + + message("Built tests") +endif () + +project(BLT) diff --git a/CMakeLists.txt b/CMakeLists.txt index 614e651..5d6aa06 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 4.0.29) +set(BLT_VERSION 4.0.30) set(BLT_TARGET BLT) diff --git a/include/blt/parse/argparse_v2.h b/include/blt/parse/argparse_v2.h index d7dc348..6462789 100644 --- a/include/blt/parse/argparse_v2.h +++ b/include/blt/parse/argparse_v2.h @@ -459,7 +459,7 @@ namespace blt::argparse return *this; } - argument_builder_t& set_flag() + argument_builder_t& make_flag() { return set_action(action_t::STORE_TRUE); } @@ -614,7 +614,7 @@ namespace blt::argparse return b; } - argument_subparser_t& add_subparser(std::string_view dest); + argument_subparser_t* add_subparser(std::string_view dest); argument_parser_t& with_help() { @@ -754,7 +754,7 @@ namespace blt::argparse } template - argument_parser_t& add_parser(const std::string_view name, Aliases... aliases) + argument_parser_t* add_parser(const std::string_view name, Aliases... aliases) { static_assert( std::conjunction_v, std::is_constructible< @@ -762,7 +762,7 @@ namespace blt::argparse "Arguments must be of type string_view, convertible to string_view or be string_view constructable"); m_parsers.emplace(name, argument_parser_t{}); ((m_aliases[std::string_view{aliases}] = &m_parsers[name]), ...); - return m_parsers[name]; + return &m_parsers[name]; } diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp index d8f298c..3a2378b 100644 --- a/src/blt/parse/argparse_v2.cpp +++ b/src/blt/parse/argparse_v2.cpp @@ -200,7 +200,7 @@ namespace blt::argparse { return iterate(buffer).skip(start_index).take(compute_take()).map([this](std::string& x) { - return aligned_internal_string_t{x, max_line_size, buffer[start_index].size()}; + return aligned_internal_string_t{x, max_line_size, x.size()}; }); } @@ -208,7 +208,7 @@ namespace blt::argparse { return iterate(buffer).skip(start_index).take(compute_take()).map([this](std::string& x) { - return aligned_internal_string_t{x, max_line_size, buffer[start_index].size()}; + return aligned_internal_string_t{x, max_line_size, x.size()}; }); } @@ -336,10 +336,10 @@ namespace blt::argparse return *this; } - argument_subparser_t& argument_parser_t::add_subparser(const std::string_view dest) + argument_subparser_t* argument_parser_t::add_subparser(const std::string_view dest) { m_subparsers.emplace_back(dest, argument_subparser_t{*this}); - return m_subparsers.back().second; + return &m_subparsers.back().second; } argument_storage_t argument_parser_t::parse(argument_consumer_t& consumer) @@ -395,6 +395,14 @@ namespace blt::argparse { print_usage(); aligned_printer_t help{""}; + + if (!m_subparsers.empty()) + { + help += "Subcommands:"; + help.newline(); + + } + if (!m_flag_arguments.empty()) { help += "Options:"; @@ -1365,26 +1373,62 @@ namespace blt::argparse std::cout << "Success: run_combined_flag_test\n"; } + void run_choice_test() + { + std::cout << "[Running Test: run_choice_test]\n"; + argument_parser_t parser; + + parser.add_flag("--hello").set_choices("silly", "crazy", "soft"); + parser.add_positional("iam").set_choices("different", "choices", "for", "me"); + + const auto a1 = make_arguments("--hello", "crazy", "different"); + const auto r1 = parser.parse(a1); + BLT_ASSERT(r1.get("--hello") == "crazy" && "Flag '--hello' should store 'crazy'"); + BLT_ASSERT(r1.get("iam") == "different" && "Positional 'iam' should store 'different'"); + + const auto a2 = make_arguments("--hello", "not_an_option", "different"); + try + { + parser.parse(a2); + BLT_ASSERT(false && "Parsing should fail due to invalid flag '--hello'"); + } + catch (...) + { + } + + const auto a3 = make_arguments("--hello", "crazy", "not_a_choice"); + try + { + parser.parse(a3); + BLT_ASSERT(false && "Parsing should fail due to invalid positional 'iam'"); + } + catch (...) + { + } + + std::cout << "Success: run_choice_test\n"; + } + void run_subparser_test() { std::cout << "[Running Test: run_subparser_test]\n"; argument_parser_t parser; - parser.add_flag("--open").set_flag(); + parser.add_flag("--open").make_flag(); - auto& subparser = parser.add_subparser("mode"); + const auto subparser = parser.add_subparser("mode"); - auto& n1 = subparser.add_parser("n1"); - n1.add_flag("--silly").set_flag(); - n1.add_positional("path"); + const auto n1 = subparser->add_parser("n1"); + n1->add_flag("--silly").make_flag(); + n1->add_positional("path"); - auto& n2 = subparser.add_parser("n2"); - n2.add_flag("--crazy").set_flag(); - n2.add_positional("path"); - n2.add_positional("output"); + const auto n2 = subparser->add_parser("n2"); + n2->add_flag("--crazy").make_flag(); + n2->add_positional("path"); + n2->add_positional("output"); - auto& n3 = subparser.add_parser("n3"); - n3.add_flag("--deep").set_flag(); + const auto n3 = subparser->add_parser("n3"); + n3->add_flag("--deep").make_flag(); const auto a1 = make_arguments("n1", "--silly"); try @@ -1423,8 +1467,6 @@ namespace blt::argparse { } - std::cout << std::endl; - const auto a5 = make_arguments("--open", "n2", "path_n2", "output_n2"); const auto r5 = parser.parse(a5); BLT_ASSERT(r5.get("--open") == true && "Flag '--open' should store true"); @@ -1458,6 +1500,7 @@ namespace blt::argparse test_combination_of_valid_and_invalid_flags(); test_flags_with_different_actions(); run_combined_flag_test(); + run_choice_test(); run_subparser_test(); } From 6935ae4785489f548482dfb15f8a251b7b44a327 Mon Sep 17 00:00:00 2001 From: Brett Date: Thu, 27 Feb 2025 01:53:22 -0500 Subject: [PATCH 035/101] this doesn't work rn --- CMakeLists.txt | 2 +- include/blt/parse/argparse_v2.h | 35 +++++++++++---- src/blt/parse/argparse_v2.cpp | 79 +++++++++++++++++++++------------ 3 files changed, 78 insertions(+), 38 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d6aa06..2fea85e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 4.0.30) +set(BLT_VERSION 4.0.31) set(BLT_TARGET BLT) diff --git a/include/blt/parse/argparse_v2.h b/include/blt/parse/argparse_v2.h index 6462789..88713b4 100644 --- a/include/blt/parse/argparse_v2.h +++ b/include/blt/parse/argparse_v2.h @@ -231,8 +231,11 @@ namespace blt::argparse template auto ensure_is_string(T&& t) { - if constexpr (std::is_arithmetic_v> && !(std::is_same_v - || std::is_same_v || std::is_same_v)) + using NO_REF = meta::remove_cvref_t; + if constexpr (std::is_same_v) + return t ? "true" : "false"; + else if constexpr (std::is_arithmetic_v && !(std::is_same_v + || std::is_same_v || std::is_same_v)) return std::to_string(std::forward(t)); else return std::forward(t); @@ -582,7 +585,9 @@ namespace blt::argparse class argument_parser_t { friend argument_subparser_t; - + explicit argument_parser_t(const argument_subparser_t* parent): m_parent(parent) + { + } public: explicit argument_parser_t(const std::optional description = {}, const std::optional epilogue = {}, const std::optional version = {}, @@ -739,6 +744,7 @@ namespace blt::argparse std::optional m_description; std::optional m_epilogue; std::optional m_version; + const argument_subparser_t* m_parent = nullptr; std::vector> m_subparsers; std::vector> m_argument_builders; hashmap_t m_flag_arguments; @@ -748,6 +754,7 @@ namespace blt::argparse class argument_subparser_t { + friend argument_parser_t; public: explicit argument_subparser_t(const argument_parser_t& parent): m_parent(&parent) { @@ -760,9 +767,16 @@ namespace blt::argparse std::conjunction_v, std::is_constructible< std::string_view, Aliases>>...>, "Arguments must be of type string_view, convertible to string_view or be string_view constructable"); - m_parsers.emplace(name, argument_parser_t{}); - ((m_aliases[std::string_view{aliases}] = &m_parsers[name]), ...); - return &m_parsers[name]; + m_parsers.emplace_back(new argument_parser_t{this}); + m_aliases[name] = m_parsers.back().get(); + ((m_aliases[std::string_view{aliases}] = m_parsers.back().get()), ...); + return m_parsers.back().get(); + } + + argument_subparser_t* set_help(const std::optional& help) + { + m_help = help; + return this; } @@ -781,10 +795,15 @@ namespace blt::argparse std::pair parse(argument_consumer_t& consumer); // NOLINT private: - [[nodiscard]] std::vector> get_allowed_strings() const; + [[nodiscard]] hashmap_t> get_allowed_strings() const; + + // annoying compatability because im lazy + static std::vector> to_vec(const hashmap_t>& map); const argument_parser_t* m_parent; - hashmap_t m_parsers; + std::optional m_last_parsed_parser; // bad hack + std::optional m_help; + std::vector> m_parsers; hashmap_t m_aliases; }; } diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp index 3a2378b..86e282b 100644 --- a/src/blt/parse/argparse_v2.cpp +++ b/src/blt/parse/argparse_v2.cpp @@ -76,7 +76,9 @@ namespace blt::argparse } else if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) { - return std::string() + t; + std::string str; + str += t; + return str; } else { @@ -400,7 +402,23 @@ namespace blt::argparse { help += "Subcommands:"; help.newline(); - + for (const auto& [key, value] : m_subparsers) + { + help += '\t'; + auto map = value.get_allowed_strings(); + // TODO: make an unzip? + for (const auto& [parser, strings] : map) + { + for (const auto& [i, str] : enumerate(strings)) + { + help += str; + if (i != strings.size() - 1) + help += ", "; + } + help += parser.he + help.newline(); + } + } } if (!m_flag_arguments.empty()) @@ -461,7 +479,7 @@ namespace blt::argparse { auto& [builder, flag_list] = pair; str += builder->m_help.value_or(""); - if (builder->m_default_value) + if (builder->m_default_value && !(builder->m_action == action_t::STORE_TRUE || builder->m_action == action_t::STORE_FALSE)) { if (!std::isblank(str.str().back())) str += " "; @@ -514,6 +532,17 @@ namespace blt::argparse aligner += m_name.value_or(""); aligner += ' '; + auto parent = m_parent; + while (parent != nullptr) + { + if (!parent->m_last_parsed_parser) + throw detail::missing_value_error( + "Error: Help called on subparser but unable to find parser chain. This condition should be impossible."); + aligner += parent->m_last_parsed_parser.value(); + aligner += ' '; + parent = parent->m_parent->m_parent; + } + hashmap_t> singleFlags; std::vector> compoundFlags; @@ -960,36 +989,28 @@ namespace blt::argparse throw detail::missing_argument_error("Subparser requires an argument."); const auto key = consumer.consume(); if (key.is_flag()) - throw detail::subparse_error(key.get_argument(), get_allowed_strings()); - const auto it = m_parsers.find(key.get_name()); - argument_parser_t* parser; - if (it == m_parsers.end()) - { - const auto it2 = m_aliases.find(key.get_name()); - if (it2 == m_aliases.end()) - throw detail::subparse_error(key.get_argument(), get_allowed_strings()); - parser = it2->second; - } - else - parser = &it->second; - parser->m_name = m_parent->m_name; - return {key, parser->parse(consumer)}; + throw detail::subparse_error(key.get_argument(), to_vec(get_allowed_strings())); + const auto it = m_aliases.find(key.get_name()); + if (it == m_aliases.end()) + throw detail::subparse_error(key.get_argument(), to_vec(get_allowed_strings())); + it->second->m_name = m_parent->m_name; + m_last_parsed_parser = key.get_name(); + return {key, it->second->parse(consumer)}; } - std::vector> argument_subparser_t::get_allowed_strings() const + hashmap_t> argument_subparser_t::get_allowed_strings() const + { + hashmap_t> map; + for (const auto& [key, value] : m_aliases) + map[value].emplace_back(key); + return map; + } + + std::vector> argument_subparser_t::to_vec(const hashmap_t>& map) { std::vector> vec; - for (const auto& [key, value] : m_parsers) - { - std::vector aliases; - aliases.push_back(key); - for (const auto& [alias, parser] : m_aliases) - { - if (parser == &value) - aliases.push_back(alias); - } - vec.emplace_back(std::move(aliases)); - } + for (const auto& [key, value] : map) + vec.push_back(value); return vec; } From 58ea39be0817fa6755d092aa7923438a7b48c452 Mon Sep 17 00:00:00 2001 From: Brett Date: Thu, 27 Feb 2025 15:17:59 -0500 Subject: [PATCH 036/101] i am now working on flatten --- CMakeLists.txt | 2 +- include/blt/iterator/common.h | 25 +++-- include/blt/iterator/enumerate.h | 165 ++++++++++++++++--------------- include/blt/iterator/flatten.h | 80 +++++++++++++++ include/blt/iterator/fwddecl.h | 3 + include/blt/meta/meta.h | 2 +- include/blt/meta/type_traits.h | 37 ++++++- src/blt/parse/argparse_v2.cpp | 3 +- 8 files changed, 223 insertions(+), 94 deletions(-) create mode 100644 include/blt/iterator/flatten.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 2fea85e..73252bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 4.0.31) +set(BLT_VERSION 4.0.32) set(BLT_TARGET BLT) diff --git a/include/blt/iterator/common.h b/include/blt/iterator/common.h index d98ddeb..92a5b29 100644 --- a/include/blt/iterator/common.h +++ b/include/blt/iterator/common.h @@ -42,19 +42,21 @@ namespace blt::iterator base_wrapper operator--(int) { - static_assert(meta::is_bidirectional_or_better_category_v, "Iterator must allow random access"); + static_assert(meta::is_bidirectional_or_better_category_v, + "Iterator must allow bidirectional access"); auto tmp = *this; --*this; return tmp; } - auto operator[](blt::ptrdiff_t n) const + auto operator[](ptrdiff_t n) const { - static_assert(meta::is_random_access_iterator_category_v, "Iterator must allow random access"); + static_assert(meta::is_random_access_iterator_category_v, + "Iterator must allow bidirectional access"); return *(*this + n); } - friend base_wrapper operator+(blt::ptrdiff_t n, const base_wrapper& a) + friend base_wrapper operator+(ptrdiff_t n, const base_wrapper& a) { return a + n; } @@ -95,9 +97,8 @@ namespace blt::iterator }; template - struct passthrough_wrapper : public base_wrapper + struct passthrough_wrapper : base_wrapper { - public: explicit passthrough_wrapper(Iter iter): iter(std::move(iter)) { } @@ -107,7 +108,7 @@ namespace blt::iterator return iter; } - friend blt::ptrdiff_t operator-(const passthrough_wrapper& a, const passthrough_wrapper& b) + friend ptrdiff_t operator-(const passthrough_wrapper& a, const passthrough_wrapper& b) { return a.base() - b.base(); } @@ -117,7 +118,7 @@ namespace blt::iterator }; template - struct passthrough_wrapper : public passthrough_wrapper + struct passthrough_wrapper : passthrough_wrapper { using passthrough_wrapper::passthrough_wrapper; @@ -382,6 +383,14 @@ namespace blt::iterator return enumerate_iterator_container{begin(), end(), static_cast(std::distance(begin(), end()))}; } + auto flatten() const + { + return iterator_container>{ + blt::iterator::flatten_wrapper{m_begin}, + blt::iterator::flatten_wrapper{m_end} + }; + } + template auto map(Func func) const { diff --git a/include/blt/iterator/enumerate.h b/include/blt/iterator/enumerate.h index 0a3ea12..3dbc9af 100644 --- a/include/blt/iterator/enumerate.h +++ b/include/blt/iterator/enumerate.h @@ -20,120 +20,123 @@ #define BLT_ITERATOR_ENUMERATE_H #include -#include namespace blt { - namespace iterator { /** * struct which is returned by the enumerator. * @tparam T type to store. */ - template + template struct enumerate_item { - blt::size_t index; + const size_t index; T value; }; - - template + + template class enumerate_wrapper : public passthrough_wrapper> { - public: - enumerate_wrapper(blt::size_t index, Iter iter): passthrough_wrapper>(std::move(iter)), index(index) - {} - - using iterator_category = typename std::iterator_traits::iterator_category; - using value_type = enumerate_item>; - using difference_type = blt::ptrdiff_t; - using pointer = value_type; - using reference = value_type; - - enumerate_item> operator*() const - { - return {index, *this->iter}; - } - - enumerate_wrapper& operator++() - { - ++index; - ++this->iter; - return *this; - } - - enumerate_wrapper& operator--() - { - --index; - --this->iter; - return *this; - } - - friend enumerate_wrapper operator+(const enumerate_wrapper& a, blt::ptrdiff_t n) - { - static_assert(meta::is_random_access_iterator_v, "Iterator must allow random access"); - auto copy = a; - copy.index += n; - copy.iter = copy.iter + n; - return copy; - } - - friend enumerate_wrapper operator-(const enumerate_wrapper& a, blt::ptrdiff_t n) - { - static_assert(meta::is_random_access_iterator_v, "Iterator must allow random access"); - auto copy = a; - copy.index -= n; - copy.iter = copy.iter - n; - return copy; - } - - private: - blt::size_t index; + public: + enumerate_wrapper(const size_t index, Iter iter): passthrough_wrapper>(std::move(iter)), index(index) + { + } + + using iterator_category = typename std::iterator_traits::iterator_category; + using value_type = enumerate_item; + using difference_type = blt::ptrdiff_t; + using pointer = value_type; + using reference = value_type; + + enumerate_item> operator*() const + { + return {index, *this->iter}; + } + + enumerate_wrapper& operator++() + { + ++index; + ++this->iter; + return *this; + } + + enumerate_wrapper& operator--() + { + --index; + --this->iter; + return *this; + } + + friend enumerate_wrapper operator+(const enumerate_wrapper& a, blt::ptrdiff_t n) + { + static_assert(meta::is_random_access_iterator_v, "Iterator must allow random access"); + auto copy = a; + copy.index += n; + copy.iter = copy.iter + n; + return copy; + } + + friend enumerate_wrapper operator-(const enumerate_wrapper& a, blt::ptrdiff_t n) + { + static_assert(meta::is_random_access_iterator_v, "Iterator must allow random access"); + auto copy = a; + copy.index -= n; + copy.iter = copy.iter - n; + return copy; + } + + private: + blt::size_t index; }; } - - template + + template class enumerate_iterator_container : public iterator::iterator_container> { - public: - using iterator::iterator_container>::iterator_container; - - enumerate_iterator_container(Iter begin, Iter end, blt::size_t size): - iterator::iterator_container>( - iterator::enumerate_wrapper{0, std::move(begin)}, iterator::enumerate_wrapper{size, std::move(end)}) - {} + public: + using iterator::iterator_container>::iterator_container; + + enumerate_iterator_container(Iter begin, Iter end, blt::size_t size): + iterator::iterator_container>( + iterator::enumerate_wrapper{0, std::move(begin)}, iterator::enumerate_wrapper{size, std::move(end)}) + { + } }; - - template + + template enumerate_iterator_container(Iter, Iter, blt::size_t) -> enumerate_iterator_container; - - template + + template static inline auto enumerate(T& container) { - return enumerate_iterator_container{container.begin(), container.end(), - static_cast(std::distance(container.begin(), container.end()))}; + return enumerate_iterator_container{ + container.begin(), container.end(), + static_cast(std::distance(container.begin(), container.end())) + }; } - - template + + template static inline auto enumerate(const T& container) { - return enumerate_iterator_container{container.begin(), container.end(), - static_cast(std::distance(container.begin(), container.end()))}; + return enumerate_iterator_container{ + container.begin(), container.end(), + static_cast(std::distance(container.begin(), container.end())) + }; } - - template - static inline auto enumerate(const T(& container)[size]) + + template + static inline auto enumerate(const T (&container)[size]) { return enumerate_iterator_container{&container[0], &container[size], size}; } - - template - static inline auto enumerate(T(& container)[size]) + + template + static inline auto enumerate(T (&container)[size]) { return enumerate_iterator_container{&container[0], &container[size], size}; } - } #endif //BLT_ITERATOR_ENUMERATE_H diff --git a/include/blt/iterator/flatten.h b/include/blt/iterator/flatten.h new file mode 100644 index 0000000..e6b22f9 --- /dev/null +++ b/include/blt/iterator/flatten.h @@ -0,0 +1,80 @@ +#pragma once +/* + * Copyright (C) 2024 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BLT_ITERATOR_FLATTEN_H +#define BLT_ITERATOR_FLATTEN_H + +#include +#include + +namespace blt::iterator +{ + namespace detail + { + template + struct make_tuple + { + using type = std::tuple; + + static auto convert(T&& f) + { + return std::forward_as_tuple(std::forward(f)); + } + }; + + template + struct make_tuple> + { + using type = std::tuple; + + static std::tuple& convert(std::tuple& lvalue) + { + return lvalue; + } + + static std::tuple&& convert(std::tuple&& rvalue) + { + return rvalue; + } + }; + } + + template + class flatten_wrapper : public deref_only_wrapper> + { + public: + using iterator_category = typename std::iterator_traits::iterator_category; + // using value_type = std::invoke_result_t>; + using difference_type = ptrdiff_t; + // using pointer = value_type; + // using reference = value_type; + + explicit flatten_wrapper(Iter iter): + deref_only_wrapper>(std::move(iter)) + { + } + + reference operator*() const + { + return func(*this->iter); + } + }; +} + + +#endif //BLT_ITERATOR_FLATTEN_H diff --git a/include/blt/iterator/fwddecl.h b/include/blt/iterator/fwddecl.h index f0e80ea..612ce36 100644 --- a/include/blt/iterator/fwddecl.h +++ b/include/blt/iterator/fwddecl.h @@ -46,6 +46,9 @@ namespace blt template class filter_wrapper; + + template + class flatten_wrapper; namespace impl { diff --git a/include/blt/meta/meta.h b/include/blt/meta/meta.h index fe502ca..97b2d55 100644 --- a/include/blt/meta/meta.h +++ b/include/blt/meta/meta.h @@ -114,7 +114,7 @@ namespace blt::meta template struct deref_return { - using type = typename std::invoke_result_t; + using type = std::invoke_result_t; }; template diff --git a/include/blt/meta/type_traits.h b/include/blt/meta/type_traits.h index d811aa5..5be8c28 100644 --- a/include/blt/meta/type_traits.h +++ b/include/blt/meta/type_traits.h @@ -20,11 +20,44 @@ #define BLT_META_TYPE_TRAITS_H #include +#include -namespace blt::meta { +namespace blt::meta +{ + template + using remove_cvref_t = std::remove_volatile_t>>; + + template + using add_const_ref_t = std::conditional_t, const std::remove_reference_t&, const U>; + + template + struct is_tuple : std::false_type + { + }; + + template + struct is_tuple> : std::true_type + { + }; + + template + struct is_pair : std::false_type + { + }; + + template + struct is_pair> : std::true_type + { + }; template - using remove_cvref_t = std::remove_volatile_t>>; + static constexpr bool is_tuple_v = is_tuple::value; + + template + static constexpr bool is_pair_v = is_pair::value; + + template + static constexpr bool is_tuple_like_v = is_tuple_v || is_pair_v; } diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp index 86e282b..33ef63d 100644 --- a/src/blt/parse/argparse_v2.cpp +++ b/src/blt/parse/argparse_v2.cpp @@ -24,6 +24,7 @@ #include #include #include +#include namespace blt::argparse { @@ -415,7 +416,7 @@ namespace blt::argparse if (i != strings.size() - 1) help += ", "; } - help += parser.he + // help += parser.he help.newline(); } } From d8f943ba6203d8bfccb340e4c71db8fa6ba02118 Mon Sep 17 00:00:00 2001 From: Brett Date: Thu, 27 Feb 2025 15:19:50 -0500 Subject: [PATCH 037/101] chhange --- CMakeLists.txt | 2 +- include/blt/iterator/flatten.h | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 73252bd..574560d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 4.0.32) +set(BLT_VERSION 4.0.33) set(BLT_TARGET BLT) diff --git a/include/blt/iterator/flatten.h b/include/blt/iterator/flatten.h index e6b22f9..75577dc 100644 --- a/include/blt/iterator/flatten.h +++ b/include/blt/iterator/flatten.h @@ -31,9 +31,14 @@ namespace blt::iterator { using type = std::tuple; + static auto convert(T& f) + { + return std::forward_as_tuple(f); + } + static auto convert(T&& f) { - return std::forward_as_tuple(std::forward(f)); + return std::forward_as_tuple(f); } }; From e48fdf0c86d089db39e4c80918d4e0a7344783a5 Mon Sep 17 00:00:00 2001 From: Brett Date: Fri, 28 Feb 2025 02:29:30 -0500 Subject: [PATCH 038/101] flatten partially works --- CMakeLists.txt | 2 +- include/blt/iterator/enumerate.h | 11 ------ include/blt/iterator/flatten.h | 57 ++++++++++++++------------------ include/blt/iterator/fwddecl.h | 4 ++- include/blt/meta/type_traits.h | 26 ++++++++++++--- 5 files changed, 50 insertions(+), 50 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 574560d..dcdc9e9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 4.0.33) +set(BLT_VERSION 4.0.34) set(BLT_TARGET BLT) diff --git a/include/blt/iterator/enumerate.h b/include/blt/iterator/enumerate.h index 3dbc9af..2f63cc2 100644 --- a/include/blt/iterator/enumerate.h +++ b/include/blt/iterator/enumerate.h @@ -25,17 +25,6 @@ namespace blt { namespace iterator { - /** - * struct which is returned by the enumerator. - * @tparam T type to store. - */ - template - struct enumerate_item - { - const size_t index; - T value; - }; - template class enumerate_wrapper : public passthrough_wrapper> { diff --git a/include/blt/iterator/flatten.h b/include/blt/iterator/flatten.h index 75577dc..79fdca3 100644 --- a/include/blt/iterator/flatten.h +++ b/include/blt/iterator/flatten.h @@ -20,43 +20,34 @@ #define BLT_ITERATOR_FLATTEN_H #include +#include #include namespace blt::iterator { namespace detail { - template - struct make_tuple + template + static auto flatten(Tuple&& tuple) -> decltype(auto) { - using type = std::tuple; - - static auto convert(T& f) + using Decay = std::decay_t; + if constexpr (meta::is_tuple_v || meta::is_pair_v) { - return std::forward_as_tuple(f); - } - - static auto convert(T&& f) + return std::apply([](auto&&... args) + { + return std::tuple_cat(flatten(std::forward(args))...); + }, std::forward(tuple)); + } else { - return std::forward_as_tuple(f); + if constexpr (std::is_lvalue_reference_v) + { + return std::forward_as_tuple(std::forward(tuple)); + } else + { + return std::make_tuple(std::forward(tuple)); + } } - }; - - template - struct make_tuple> - { - using type = std::tuple; - - static std::tuple& convert(std::tuple& lvalue) - { - return lvalue; - } - - static std::tuple&& convert(std::tuple&& rvalue) - { - return rvalue; - } - }; + } } template @@ -64,19 +55,19 @@ namespace blt::iterator { public: using iterator_category = typename std::iterator_traits::iterator_category; - // using value_type = std::invoke_result_t>; + using value_type = std::remove_reference_t()))>; using difference_type = ptrdiff_t; - // using pointer = value_type; - // using reference = value_type; + using pointer = value_type*; + using reference = value_type&; explicit flatten_wrapper(Iter iter): - deref_only_wrapper>(std::move(iter)) + deref_only_wrapper(std::move(iter)) { } - reference operator*() const + auto operator*() const -> decltype(auto) { - return func(*this->iter); + return detail::flatten(*this->iter); } }; } diff --git a/include/blt/iterator/fwddecl.h b/include/blt/iterator/fwddecl.h index 612ce36..6fcf5b3 100644 --- a/include/blt/iterator/fwddecl.h +++ b/include/blt/iterator/fwddecl.h @@ -19,6 +19,8 @@ #ifndef BLT_ITERATOR_FWDDECL_H #define BLT_ITERATOR_FWDDECL_H +#include + namespace blt { template @@ -33,7 +35,7 @@ namespace blt struct iterator_pair; template - struct enumerate_item; + using enumerate_item = std::pair; template class enumerate_wrapper; diff --git a/include/blt/meta/type_traits.h b/include/blt/meta/type_traits.h index 5be8c28..645ebd6 100644 --- a/include/blt/meta/type_traits.h +++ b/include/blt/meta/type_traits.h @@ -24,6 +24,15 @@ namespace blt::meta { + namespace detail + { + template + void empty_apply_function(Args...) + { + + } + } + template using remove_cvref_t = std::remove_volatile_t>>; @@ -50,15 +59,24 @@ namespace blt::meta { }; - template + template static constexpr bool is_tuple_v = is_tuple::value; - template + template static constexpr bool is_pair_v = is_pair::value; - template - static constexpr bool is_tuple_like_v = is_tuple_v || is_pair_v; + template + struct is_tuple_like : std::false_type + { + }; + template + struct is_tuple_like, std::tuple_element<0, T>>> : std::true_type + { + }; + + template + inline constexpr bool is_tuple_like_v = is_tuple_like::value; } #endif // BLT_META_TYPE_TRAITS_H From bac63ae815ec8cb222647f619b52bc33adbadc7b Mon Sep 17 00:00:00 2001 From: Brett Date: Fri, 28 Feb 2025 12:39:13 -0500 Subject: [PATCH 039/101] flatten, and flatten recursive. as_const doesn't work. --- CMakeLists.txt | 2 +- include/blt/iterator/common.h | 50 +++++++++++++++++++++++++++-- include/blt/iterator/flatten.h | 57 +++++++++++++++++++++++++++++----- include/blt/iterator/fwddecl.h | 5 ++- 4 files changed, 101 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dcdc9e9..b518054 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 4.0.34) +set(BLT_VERSION 4.0.35) set(BLT_TARGET BLT) diff --git a/include/blt/iterator/common.h b/include/blt/iterator/common.h index 92a5b29..01e1f13 100644 --- a/include/blt/iterator/common.h +++ b/include/blt/iterator/common.h @@ -216,6 +216,34 @@ namespace blt::iterator Pred func; }; + template + class const_wrapper : public deref_only_wrapper> + { + public: + using ref_return = meta::deref_return_t; + + using iterator_category = typename std::iterator_traits::iterator_category; + using value_type = std::conditional_t, std::remove_reference_t, ref_return>; + using difference_type = ptrdiff_t; + using pointer = const value_type*; + using reference = const value_type&; + + explicit const_wrapper(Iter iter): deref_only_wrapper(std::move(iter)) + { + } + + auto operator*() const + { + if constexpr (std::is_reference_v) + { + return const_cast(*this->iter); + } else + { + return *this->iter; + } + } + }; + namespace impl { template @@ -385,9 +413,25 @@ namespace blt::iterator auto flatten() const { - return iterator_container>{ - blt::iterator::flatten_wrapper{m_begin}, - blt::iterator::flatten_wrapper{m_end} + return iterator_container>{ + blt::iterator::flatten_wrapper{m_begin}, + blt::iterator::flatten_wrapper{m_end} + }; + } + + auto flatten_all() const + { + return iterator_container>{ + blt::iterator::flatten_wrapper{m_begin}, + blt::iterator::flatten_wrapper{m_end} + }; + } + + auto as_const() const + { + return iterator_container>{ + const_wrapper{m_begin}, + const_wrapper{m_end} }; } diff --git a/include/blt/iterator/flatten.h b/include/blt/iterator/flatten.h index 79fdca3..28f3c61 100644 --- a/include/blt/iterator/flatten.h +++ b/include/blt/iterator/flatten.h @@ -28,34 +28,68 @@ namespace blt::iterator namespace detail { template - static auto flatten(Tuple&& tuple) -> decltype(auto) + static auto flatten_recursive(Tuple&& tuple) -> decltype(auto) { using Decay = std::decay_t; if constexpr (meta::is_tuple_v || meta::is_pair_v) { return std::apply([](auto&&... args) { - return std::tuple_cat(flatten(std::forward(args))...); + return std::tuple_cat(flatten_recursive(std::forward(args))...); }, std::forward(tuple)); - } else + } + else { if constexpr (std::is_lvalue_reference_v) { return std::forward_as_tuple(std::forward(tuple)); - } else + } + else { return std::make_tuple(std::forward(tuple)); } } } + + template + static auto ensure_tuple(Tuple&& tuple) -> decltype(auto) + { + using Decay = std::decay_t; + if constexpr (meta::is_tuple_v || meta::is_pair_v) + { + return std::forward(tuple); + } + else + { + if constexpr (std::is_lvalue_reference_v) + { + return std::forward_as_tuple(std::forward(tuple)); + } + else + { + return std::make_tuple(std::forward(tuple)); + } + } + } + + template + static auto flatten(Tuple&& tuple) -> decltype(auto) + { + return std::apply([](auto&&... args) + { + return std::tuple_cat(ensure_tuple(std::forward(args))...); + }, std::forward(tuple)); + } } - template - class flatten_wrapper : public deref_only_wrapper> + template + class flatten_wrapper : public deref_only_wrapper> { public: using iterator_category = typename std::iterator_traits::iterator_category; - using value_type = std::remove_reference_t()))>; + using value_type = std::conditional_t()))>, + std::remove_reference_t()))>>; using difference_type = ptrdiff_t; using pointer = value_type*; using reference = value_type&; @@ -67,7 +101,14 @@ namespace blt::iterator auto operator*() const -> decltype(auto) { - return detail::flatten(*this->iter); + if constexpr (Recursive) + { + return detail::flatten_recursive(*this->iter); + } + else + { + return detail::flatten(*this->iter); + } } }; } diff --git a/include/blt/iterator/fwddecl.h b/include/blt/iterator/fwddecl.h index 6fcf5b3..50f18f1 100644 --- a/include/blt/iterator/fwddecl.h +++ b/include/blt/iterator/fwddecl.h @@ -49,8 +49,11 @@ namespace blt template class filter_wrapper; - template + template class flatten_wrapper; + + template + class const_wrapper; namespace impl { From 8902e17b40219db18a4183601c08a8c505fcc196 Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Fri, 28 Feb 2025 17:10:47 -0500 Subject: [PATCH 040/101] fix issue with compiling on clang, as_const now works with tuple types --- ...ts (conflicted copy 2025-02-24 222236).txt | 204 ------------------ CMakeLists.txt | 2 +- include/blt/iterator/common.h | 55 ++++- include/blt/iterator/flatten.h | 30 +-- include/blt/meta/type_traits.h | 17 +- src/blt/parse/argparse_v2.cpp | 8 +- 6 files changed, 65 insertions(+), 251 deletions(-) delete mode 100644 CMakeLists (conflicted copy 2025-02-24 222236).txt diff --git a/CMakeLists (conflicted copy 2025-02-24 222236).txt b/CMakeLists (conflicted copy 2025-02-24 222236).txt deleted file mode 100644 index b01aaf5..0000000 --- a/CMakeLists (conflicted copy 2025-02-24 222236).txt +++ /dev/null @@ -1,204 +0,0 @@ -cmake_minimum_required(VERSION 3.20) -include(cmake/color.cmake) -set(BLT_VERSION 4.0.27) - -set(BLT_TARGET BLT) - -project(BLT VERSION ${BLT_VERSION}) - -set(CMAKE_CXX_STANDARD 17) - -option(ENABLE_ADDRSAN "Enable the address sanitizer" OFF) -option(ENABLE_UBSAN "Enable the ub sanitizer" OFF) -option(ENABLE_TSAN "Enable the thread data race sanitizer" OFF) - -option(BUILD_STD "Build the BLT standard utilities." ON) -option(BUILD_PROFILING "Build the BLT profiler extension" ON) -option(BUILD_FS "Build the BLT FS utilities including the NBT + eNBT extension" ON) -option(BUILD_PARSE "Build the BLT parsers" ON) -option(BUILD_FORMAT "Build the BLT formatters" ON) - -option(BUILD_TESTS "Build the BLT test set" OFF) - -option(BLT_DISABLE_STATS "Disable tracking stats in certain objects. Enabling this will cause stat functions to return 0" OFF) -option(BLT_DISABLE_LOGGING "Disable blt::logging (all macros and will safely disable logging function!)" OFF) -option(BLT_DISABLE_TRACE "Disable blt::logging BLT_TRACE macro" OFF) -option(BLT_DISABLE_DEBUG "Disable blt::logging BLT_DEBUG macro" OFF) -option(BLT_DISABLE_INFO "Disable blt::logging BLT_INFO macro" OFF) -option(BLT_DISABLE_WARN "Disable blt::logging BLT_WARN macro" OFF) -option(BLT_DISABLE_ERROR "Disable blt::logging BLT_ERROR macro" OFF) -option(BLT_DISABLE_FATAL "Disable blt::logging BLT_FATAL macro" OFF) - -if(${BLT_DISABLE_STATS}) - add_compile_definitions(BLT_DISABLE_STATS) -endif () - -find_program(MOLD "mold") - -configure_file(include/blt/config.h.in config/blt/config.h @ONLY) - -message("Enabling library compilation") -if (${BUILD_STD} OR ${BUILD_PROFILING}) - message(STATUS "Building ${Yellow}standard${ColourReset} cxx files") - file(GLOB_RECURSE STD_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/blt/std/*.cpp") -else () - set(STD_FILES "") -endif () - -if (${BUILD_PROFILING}) - message(STATUS "Building ${Yellow}profiling${ColourReset} cxx files") - file(GLOB_RECURSE PROFILING_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/blt/profiling/*.cpp") -else () - set(PROFILING_FILES "") -endif () - -if (${BUILD_FS}) - message(STATUS "Building ${Yellow}filesystem${ColourReset} cxx files") - file(GLOB_RECURSE FS_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/blt/fs/*.cpp") -else () - set(FS_FILES "") -endif () - -if (${BUILD_PARSE}) - message(STATUS "Building ${Yellow}parser${ColourReset} cxx files") - file(GLOB_RECURSE PARSE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/blt/parse/*.cpp") -else () - set(PARSE_FILES "") -endif () - -if (${BUILD_FORMAT}) - message(STATUS "Building ${Yellow}format${ColourReset} cxx files") - file(GLOB_RECURSE FORMAT_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/blt/format/*.cpp") -else () - set(FORMAT_FILES "" - include/blt/std/iterator.h) -endif () - -if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/libraries/parallel-hashmap) - message("Found Parallel Hashmaps library, using ${Yellow}phmap${ColourReset} over ${Red}std::unordered_map${ColourReset}") - include_directories(${CMAKE_CURRENT_SOURCE_DIR}/libraries/parallel-hashmap) -else() - message("Parallel Hashmaps library not found! using ${Yellow}std::unordered_map${ColourReset}") -endif () - -#include zlib if the user has it. -find_package(ZLIB QUIET) - -if (${ZLIB_FOUND}) - include_directories(${ZLIB_INCLUDE_DIRS}) -else () - message("ZLIB was not found, this is fine however if you wish you use gzip with NBT it is required.") -endif () - -include_directories(include/) -include_directories(${CMAKE_CURRENT_BINARY_DIR}/config/) - -add_library(${BLT_TARGET} ${STD_FILES} ${PROFILING_FILES} ${FS_FILES} ${PARSE_FILES} ${FORMAT_FILES}) - -string(REPLACE "+" "\\+" escaped_source ${CMAKE_CURRENT_SOURCE_DIR}) -string(APPEND escaped_source "/src/blt/.*/") -list(TRANSFORM STD_FILES REPLACE ${escaped_source} "") -list(TRANSFORM PROFILING_FILES REPLACE ${escaped_source} "") -list(TRANSFORM FS_FILES REPLACE ${escaped_source} "") -list(TRANSFORM PARSE_FILES REPLACE ${escaped_source} "") -message("Standard Files ${Magenta}${STD_FILES}${ColourReset}") -message("Profiler Files ${Magenta}${PROFILING_FILES}${ColourReset}") -message("FS Files ${Magenta}${FS_FILES}${ColourReset}") -message("Parser Files ${Magenta}${PARSE_FILES}${ColourReset}") -message("Source: ${CMAKE_SOURCE_DIR}") -message("Current Source: ${CMAKE_CURRENT_SOURCE_DIR}") -message("Binary: ${CMAKE_BINARY_DIR}") -message("Current Binary: ${CMAKE_CURRENT_BINARY_DIR}") - -if (${ZLIB_FOUND}) - target_link_libraries(${BLT_TARGET} PUBLIC ZLIB::ZLIB) -endif () - -include(cmake/warnings.cmake) - -target_include_directories(${BLT_TARGET} PUBLIC include/) -target_include_directories(${BLT_TARGET} PUBLIC ${CMAKE_CURRENT_BINARY_DIR}/config/) -if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/libraries/parallel-hashmap) - message("Including Parallel Hashmap directory") - target_include_directories(${BLT_TARGET} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/libraries/parallel-hashmap) -endif () - -message("BLT ${Yellow}${BLT_VERSION}${ColourReset} Successfully included!") - -message("Installing to ${CMAKE_INSTALL_LIBDIR} with headers at ${CMAKE_INSTALL_INCLUDEDIR}") - -file(GLOB_RECURSE BLT_HEADER_FILES "${CMAKE_CURRENT_SOURCE_DIR}/include/*.h") -foreach (S ${BLT_HEADER_FILES}) - string(REPLACE "${CMAKE_CURRENT_SOURCE_DIR}/include/" "" SO ${S}) - string(REGEX REPLACE "\/[A-Z|a-z|0-9|_|-]*\\.h" "/" SA ${SO}) - list(APPEND BLT_F_HEADERS ${SA}) - install(FILES ${S} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${SA}) -endforeach () - -install(FILES ${CMAKE_BINARY_DIR}/config/blt/config.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/blt/) - -set_target_properties(${BLT_TARGET} PROPERTIES VERSION ${BLT_VERSION}) -set_target_properties(${BLT_TARGET} PROPERTIES SOVERSION ${PROJECT_VERSION_MAJOR}) -if (NOT ${MOLD} STREQUAL MOLD-NOTFOUND) - target_link_options(${BLT_TARGET} PUBLIC -fuse-ld=mold) -endif () - -install(TARGETS ${BLT_TARGET} - CONFIGURATIONS RelWithDebInfo - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) - -macro(blt_add_test name source type) - - message("Adding project ${name} of type ${type}" DEBUG) - project(${name}-${type}) - - add_executable(${name}-${type} ${source}) - - if (NOT ${MOLD} STREQUAL MOLD-NOTFOUND) - add_link_options(-fuse-ld=mold) - endif () - - target_link_libraries(${name}-${type} PRIVATE BLT) - - target_compile_options(${name}-${type} PRIVATE -Wall -Wextra -Wpedantic -Wno-comment) - target_link_options(${name}-${type} PRIVATE -Wall -Wextra -Wpedantic -Wno-comment) - target_compile_definitions(${name}-${type} PRIVATE BLT_DEBUG_LEVEL=${DEBUG_LEVEL}) - - if (${TRACK_ALLOCATIONS}) - target_compile_definitions(${name}-${type} PRIVATE BLT_TRACK_ALLOCATIONS=1) - endif () - - if (${ENABLE_ADDRSAN} MATCHES ON) - target_compile_options(${name}-${type} PRIVATE -fsanitize=address) - target_link_options(${name}-${type} PRIVATE -fsanitize=address) - endif () - - if (${ENABLE_UBSAN} MATCHES ON) - target_compile_options(${name}-${type} PRIVATE -fsanitize=undefined) - target_link_options(${name}-${type} PRIVATE -fsanitize=undefined) - endif () - - if (${ENABLE_TSAN} MATCHES ON) - target_compile_options(${name}-${type} PRIVATE -fsanitize=thread) - target_link_options(${name}-${type} PRIVATE -fsanitize=thread) - endif () - - add_test(NAME ${name} COMMAND ${name}-${type}) - - set(failRegex "\\[WARN\\]" "FAIL" "ERROR" "FATAL" "exception") - set_property(TEST ${name} PROPERTY FAIL_REGULAR_EXPRESSION "${failRegex}") - - project(${BLT_TARGET}) -endmacro() - -if (${BUILD_TESTS}) - message("Building tests for version ${BLT_VERSION}") - - blt_add_test(blt_iterator tests/iterator_tests.cpp test) - blt_add_test(blt_argparse tests/argparse_tests.cpp test) - - message("Built tests") -endif () - -project(BLT) diff --git a/CMakeLists.txt b/CMakeLists.txt index b518054..f51e587 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 4.0.35) +set(BLT_VERSION 4.0.36) set(BLT_TARGET BLT) diff --git a/include/blt/iterator/common.h b/include/blt/iterator/common.h index 01e1f13..2781194 100644 --- a/include/blt/iterator/common.h +++ b/include/blt/iterator/common.h @@ -27,9 +27,42 @@ #include #include #include +#include namespace blt::iterator { + namespace detail + { + template + static auto forward_as_tuple(T&& t) -> decltype(auto) + { + using Decay = std::decay_t; + static_assert(!(meta::is_tuple_v || meta::is_pair_v), "Tuple or pair passed to forward_as_tuple! Must not be a tuple!"); + if constexpr (std::is_lvalue_reference_v) + { + return std::forward_as_tuple(std::forward(t)); + } + else + { + return std::make_tuple(std::forward(t)); + } + } + + template + static auto ensure_tuple(Tuple&& tuple) -> decltype(auto) + { + using Decay = std::decay_t; + if constexpr (meta::is_tuple_v || meta::is_pair_v) + { + return std::forward(tuple); + } + else + { + return forward_as_tuple(std::forward(tuple)); + } + } + } + template struct base_wrapper { @@ -232,12 +265,32 @@ namespace blt::iterator { } + template + static auto make_const(T&& value) -> decltype(auto) + { + using Decay = std::decay_t; + if constexpr (std::is_lvalue_reference_v) + { + return const_cast(value); + } else + { + return static_cast(value); + } + } + auto operator*() const { if constexpr (std::is_reference_v) { return const_cast(*this->iter); - } else + } else if constexpr (meta::is_tuple_v || meta::is_pair_v) + { + return std::apply([](auto&&... args) + { + return std::tuple(args)))...>{make_const(std::forward(args))...}; + }, *this->iter); + } + else { return *this->iter; } diff --git a/include/blt/iterator/flatten.h b/include/blt/iterator/flatten.h index 28f3c61..403bcf3 100644 --- a/include/blt/iterator/flatten.h +++ b/include/blt/iterator/flatten.h @@ -40,35 +40,7 @@ namespace blt::iterator } else { - if constexpr (std::is_lvalue_reference_v) - { - return std::forward_as_tuple(std::forward(tuple)); - } - else - { - return std::make_tuple(std::forward(tuple)); - } - } - } - - template - static auto ensure_tuple(Tuple&& tuple) -> decltype(auto) - { - using Decay = std::decay_t; - if constexpr (meta::is_tuple_v || meta::is_pair_v) - { - return std::forward(tuple); - } - else - { - if constexpr (std::is_lvalue_reference_v) - { - return std::forward_as_tuple(std::forward(tuple)); - } - else - { - return std::make_tuple(std::forward(tuple)); - } + return forward_as_tuple(std::forward(tuple)); } } diff --git a/include/blt/meta/type_traits.h b/include/blt/meta/type_traits.h index 645ebd6..ae4b731 100644 --- a/include/blt/meta/type_traits.h +++ b/include/blt/meta/type_traits.h @@ -31,6 +31,10 @@ namespace blt::meta { } + + inline auto lambda = [](auto...) + { + }; } template @@ -64,19 +68,6 @@ namespace blt::meta template static constexpr bool is_pair_v = is_pair::value; - - template - struct is_tuple_like : std::false_type - { - }; - - template - struct is_tuple_like, std::tuple_element<0, T>>> : std::true_type - { - }; - - template - inline constexpr bool is_tuple_like_v = is_tuple_like::value; } #endif // BLT_META_TYPE_TRAITS_H diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp index 33ef63d..309a9b2 100644 --- a/src/blt/parse/argparse_v2.cpp +++ b/src/blt/parse/argparse_v2.cpp @@ -476,9 +476,10 @@ namespace blt::argparse help.newline(); } mark.align(4); - for (auto [str, pair] : mark.iter().zip(same_flags)) + for (auto zipped_flags : mark.iter().zip(same_flags)) { - auto& [builder, flag_list] = pair; + auto& str = std::get<0>(zipped_flags); + auto& [builder, flag_list] = std::get<1>(zipped_flags); str += builder->m_help.value_or(""); if (builder->m_default_value && !(builder->m_action == action_t::STORE_TRUE || builder->m_action == action_t::STORE_FALSE)) { @@ -574,7 +575,8 @@ namespace blt::argparse for (const auto& [i, kv] : enumerate(compoundFlags)) { - const auto& [name, builder] = kv; + const auto& name = kv.first; + const auto& builder = kv.second; aligner += builder->m_required ? '<' : '['; aligner += name.get_argument(); auto lambda = [&]() From ecd3d1a7010634764257af9491cdcf4fb2774e5b Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Fri, 28 Feb 2025 18:08:20 -0500 Subject: [PATCH 041/101] i think argparse is done --- CMakeLists.txt | 2 +- include/blt/parse/argparse_v2.h | 2 +- src/blt/parse/argparse_v2.cpp | 92 +++++++++++++++++++++++++++++++-- 3 files changed, 89 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f51e587..227960f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 4.0.36) +set(BLT_VERSION 4.0.37) set(BLT_TARGET BLT) diff --git a/include/blt/parse/argparse_v2.h b/include/blt/parse/argparse_v2.h index 88713b4..3ff1fc5 100644 --- a/include/blt/parse/argparse_v2.h +++ b/include/blt/parse/argparse_v2.h @@ -623,7 +623,7 @@ namespace blt::argparse argument_parser_t& with_help() { - add_flag("--help", "-h").set_action(action_t::HELP); + add_flag("--help", "-h").set_action(action_t::HELP).set_help("Show this help menu and exit"); return *this; } diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp index 309a9b2..d4e2104 100644 --- a/src/blt/parse/argparse_v2.cpp +++ b/src/blt/parse/argparse_v2.cpp @@ -405,20 +405,86 @@ namespace blt::argparse help.newline(); for (const auto& [key, value] : m_subparsers) { - help += '\t'; auto map = value.get_allowed_strings(); - // TODO: make an unzip? - for (const auto& [parser, strings] : map) + help += '\t'; + help += key; + help += ": {"; + for (const auto& [i, parser, strings] : enumerate(map).flatten()) { + if (strings.size() > 1) + help += '['; for (const auto& [i, str] : enumerate(strings)) { help += str; if (i != strings.size() - 1) help += ", "; } - // help += parser.he - help.newline(); + if (strings.size() > 1) + help += ']'; + if (i != map.size() - 1) + help += ", "; } + help += "}"; + help.newline(); + } + help.newline(); + } + + if (!m_positional_arguments.empty()) + { + help += "Positional Arguments:"; + help.newline(); + for (auto& [name, builder] : m_positional_arguments) + { + help += '\t'; + if (!builder.m_required) + help += '['; + help += name; + if (!builder.m_required) + help += ']'; + if (builder.m_default_value && !(builder.m_action == action_t::STORE_TRUE || builder.m_action == action_t::STORE_FALSE)) + { + if (!std::isblank(help.str().back())) + help += " "; + help += "(Default: "; + std::visit(detail::arg_meta_type_helper_t::make_visitor( + [&](auto& value) + { + help += value; + }, + [&](auto& vec) + { + if constexpr (!std::is_same_v>, std::vector>) + { + help += '['; + for (const auto& [i, v] : enumerate(vec)) + { + help += v; + if (i != vec.size() - 1) + help += ", "; + } + help += ']'; + } + }), *builder.m_default_value); + help += ")"; + } + if (builder.m_choices) + { + if (!std::isblank(help.str().back())) + help += " "; + help += "(Choices: "; + for (const auto& [i, v] : enumerate(*builder.m_choices)) + { + help += '\''; + help += v; + help += '\''; + if (i != builder.m_choices->size() - 1) + help += ", "; + } + help += ')'; + } + help.newline(); + help.newline(); } } @@ -514,12 +580,20 @@ namespace blt::argparse str += "(Choices: "; for (const auto& [i, v] : enumerate(*builder->m_choices)) { + str += '\''; str += v; + str += '\''; if (i != builder->m_choices->size() - 1) str += ", "; } str += ')'; } + if (builder->m_required) + { + if (!std::isblank(str.str().back())) + str += " "; + str += "(Required)"; + } } } @@ -545,6 +619,14 @@ namespace blt::argparse parent = parent->m_parent->m_parent; } + for (const auto& [key, _] : m_subparsers) + { + aligner += '{'; + aligner += key; + aligner += '}'; + aligner += ' '; + } + hashmap_t> singleFlags; std::vector> compoundFlags; From be721a4e52aa479bf203648d93a07e300ad8b94e Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Fri, 28 Feb 2025 18:08:52 -0500 Subject: [PATCH 042/101] slight change, quotes around strings --- CMakeLists.txt | 2 +- src/blt/parse/argparse_v2.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 227960f..7095893 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 4.0.37) +set(BLT_VERSION 4.0.38) set(BLT_TARGET BLT) diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp index d4e2104..d700b28 100644 --- a/src/blt/parse/argparse_v2.cpp +++ b/src/blt/parse/argparse_v2.cpp @@ -551,7 +551,7 @@ namespace blt::argparse { if (!std::isblank(str.str().back())) str += " "; - str += "(Default: "; + str += "(Default: '"; std::visit(detail::arg_meta_type_helper_t::make_visitor( [&](auto& value) { @@ -571,7 +571,7 @@ namespace blt::argparse str += ']'; } }), *builder->m_default_value); - str += ")"; + str += "')"; } if (builder->m_choices) { From 21f1e66bff62311bd37873f1a1f824860cd6aed4 Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Sat, 1 Mar 2025 20:44:25 -0500 Subject: [PATCH 043/101] logging files --- CMakeLists.txt | 13 +- cloc.sh | 0 include/blt/logging/logging.h | 34 ++ include/blt/logging/status.h | 27 + ...e_v2 (conflicted copy 2025-02-13 174730).h | 484 ------------------ libraries/parallel-hashmap | 2 +- src/blt/logging/logging.cpp | 23 + src/blt/logging/status.cpp | 23 + 8 files changed, 118 insertions(+), 488 deletions(-) mode change 100644 => 100755 cloc.sh create mode 100644 include/blt/logging/logging.h create mode 100644 include/blt/logging/status.h delete mode 100644 include/blt/parse/argparse_v2 (conflicted copy 2025-02-13 174730).h create mode 100644 src/blt/logging/logging.cpp create mode 100644 src/blt/logging/status.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 7095893..c5e4dbb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,7 @@ option(BUILD_PROFILING "Build the BLT profiler extension" ON) option(BUILD_FS "Build the BLT FS utilities including the NBT + eNBT extension" ON) option(BUILD_PARSE "Build the BLT parsers" ON) option(BUILD_FORMAT "Build the BLT formatters" ON) +option(BUILD_LOGGING "Build the BLT logging utilities" ON) option(BUILD_TESTS "Build the BLT test set" OFF) @@ -70,8 +71,14 @@ if (${BUILD_FORMAT}) message(STATUS "Building ${Yellow}format${ColourReset} cxx files") file(GLOB_RECURSE FORMAT_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/blt/format/*.cpp") else () - set(FORMAT_FILES "" - include/blt/std/iterator.h) + set(FORMAT_FILES "") +endif () + +if (${BUILD_LOGGING}) + message(STATUS "Building ${Yellow}logging${ColourReset} cxx files") + file(GLOB_RECURSE LOGGING_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/blt/logging/*.cpp") +else () + set(PARSE_FILES "") endif () if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/libraries/parallel-hashmap) @@ -93,7 +100,7 @@ endif () include_directories(include/) include_directories(${CMAKE_CURRENT_BINARY_DIR}/config/) -add_library(${BLT_TARGET} ${STD_FILES} ${PROFILING_FILES} ${FS_FILES} ${PARSE_FILES} ${FORMAT_FILES}) +add_library(${BLT_TARGET} ${STD_FILES} ${PROFILING_FILES} ${FS_FILES} ${PARSE_FILES} ${FORMAT_FILES} ${LOGGING_FILES}) string(REPLACE "+" "\\+" escaped_source ${CMAKE_CURRENT_SOURCE_DIR}) string(APPEND escaped_source "/src/blt/.*/") diff --git a/cloc.sh b/cloc.sh old mode 100644 new mode 100755 diff --git a/include/blt/logging/logging.h b/include/blt/logging/logging.h new file mode 100644 index 0000000..da4b5f2 --- /dev/null +++ b/include/blt/logging/logging.h @@ -0,0 +1,34 @@ +#pragma once +/* + * Copyright (C) 2024 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BLT_LOGGING_LOGGING_H +#define BLT_LOGGING_LOGGING_H + +#include + +namespace blt::logging +{ + + template + void log(const std::string& str, Args&&... args){ + + } + +} + +#endif // BLT_LOGGING_LOGGING_H diff --git a/include/blt/logging/status.h b/include/blt/logging/status.h new file mode 100644 index 0000000..3f0b999 --- /dev/null +++ b/include/blt/logging/status.h @@ -0,0 +1,27 @@ +#pragma once +/* + * Copyright (C) 2024 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BLT_LOGGING_STATUS_H +#define BLT_LOGGING_STATUS_H + +namespace blt::logging +{ + +} + +#endif //BLT_LOGGING_STATUS_H diff --git a/include/blt/parse/argparse_v2 (conflicted copy 2025-02-13 174730).h b/include/blt/parse/argparse_v2 (conflicted copy 2025-02-13 174730).h deleted file mode 100644 index e72fc0f..0000000 --- a/include/blt/parse/argparse_v2 (conflicted copy 2025-02-13 174730).h +++ /dev/null @@ -1,484 +0,0 @@ -#pragma once -/* - * Copyright (C) 2024 Brett Terpstra - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#ifndef BLT_PARSE_ARGPARSE_V2_H -#define BLT_PARSE_ARGPARSE_V2_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace blt::argparse -{ - class argument_string_t; - class argument_consumer_t; - class argument_parser_t; - class argument_subparser_t; - class argument_builder_t; - class argument_storage_t; - - enum class action_t - { - STORE, - STORE_CONST, - STORE_TRUE, - STORE_FALSE, - APPEND, - APPEND_CONST, - COUNT, - HELP, - VERSION, - EXTEND, - SUBCOMMAND - }; - - enum class nargs_t - { - IF_POSSIBLE, - ALL, - ALL_AT_LEAST_ONE - }; - - using nargs_v = std::variant; - - namespace detail - { - class bad_flag final : public std::runtime_error - { - public: - explicit bad_flag(const std::string& message): std::runtime_error(message) - { - } - - explicit bad_flag(const char* message): std::runtime_error(message) - { - } - }; - - class missing_argument_error final : public std::runtime_error - { - public: - explicit missing_argument_error(const std::string& message): std::runtime_error(message) - { - } - } - - class subparse_error final : public std::exception - { - public: - explicit subparse_error(const std::string_view found_string, std::vector> allowed_strings): - m_found_string(found_string), - m_allowed_strings(std::move(allowed_strings)) - { - } - - [[nodiscard]] const std::vector>& get_allowed_strings() const - { - return m_allowed_strings; - } - - [[nodiscard]] std::string_view get_found_string() const - { - return m_found_string; - } - - [[nodiscard]] std::string error_string() const - { - std::string message = "Subparser Error: "; - message += m_found_string; - message += " is not a valid command. Allowed commands are: {"; - for (const auto [i, allowed_string] : enumerate(m_allowed_strings)) - { - if (allowed_string.size() > 1) - message += '['; - for (const auto [j, alias] : enumerate(allowed_string)) - { - message += alias; - if (j < alias.size() - 2) - message += ", "; - else if (j < alias.size()) - message += ", or "; - } - if (allowed_string.size() > 1) - message += ']'; - if (i != m_allowed_strings.size() - 1) - message += ' '; - } - message += "}"; - return message; - } - - [[nodiscard]] const char* what() const override - { - return "Please use error_string() method instead of what(). This exception should *always* be caught!"; - } - - private: - std::string_view m_found_string; - std::vector> m_allowed_strings; - }; - - template - struct arg_data_helper_t - { - using arg_primitive_data_t = std::variant; - using arg_list_data_t = std::variant...>; - }; - - using data_helper_t = arg_data_helper_t; - - using arg_primitive_data_t = data_helper_t::arg_primitive_data_t; - using arg_list_data_t = data_helper_t::arg_list_data_t; - using arg_data_t = std::variant; - - template - struct arg_type_t - { - static T convert(const std::string_view value) - { - static_assert(std::is_arithmetic_v || std::is_same_v || std::is_same_v, - "Type must be arithmetic, string_view or string!"); - const std::string temp{value}; - - if constexpr (std::is_same_v) - { - return std::stof(temp); - } - else if constexpr (std::is_same_v) - { - return std::stod(temp); - } - else if constexpr (std::is_unsigned_v) - { - return static_cast(std::stoull(temp)); - } - else if constexpr (std::is_signed_v) - { - return static_cast(std::stoll(temp)); - } - else if constexpr (std::is_same_v) - { - return value; - } - else if constexpr (std::is_same_v) - { - return std::string(value); - } - BLT_UNREACHABLE; - } - }; - - void test(); - } - - class argument_string_t - { - public: - explicit argument_string_t(const char* input, const hashset_t& allowed_flag_prefix): m_argument(input), - allowed_flag_prefix(&allowed_flag_prefix) - { - if (input == nullptr) - throw detail::bad_flag("Argument cannot be null!"); - process_argument(); - } - - [[nodiscard]] std::string_view get_flag() const - { - return m_flag_section; - } - - [[nodiscard]] std::string_view get_name() const - { - return m_name_section; - } - - [[nodiscard]] std::string_view value() const - { - return get_name(); - } - - [[nodiscard]] bool is_flag() const - { - return !m_flag_section.empty(); - } - - [[nodiscard]] std::string_view get_argument() const - { - return m_argument; - } - - private: - void process_argument() - { - size_t start = 0; - for (; start < m_argument.size() && allowed_flag_prefix->contains(m_argument[start]); start++) - { - } - - m_flag_section = {m_argument.data(), start}; - m_name_section = {m_argument.data() + start, m_argument.size() - start}; - } - - std::string_view m_argument; - std::string_view m_flag_section; - std::string_view m_name_section; - const hashset_t* allowed_flag_prefix; - }; - - class argument_consumer_t - { - public: - explicit argument_consumer_t(const span& args): m_args(args) - { - } - - [[nodiscard]] argument_string_t peek(const i32 offset = 0) const - { - return m_args[m_forward_index + offset]; - } - - argument_string_t consume() - { - return m_args[m_forward_index++]; - } - - [[nodiscard]] i32 position() const - { - return m_forward_index; - } - - [[nodiscard]] i32 remaining() const - { - return static_cast(m_args.size()) - m_forward_index; - } - - [[nodiscard]] bool has_next(const i32 offset = 0) const - { - return (offset + m_forward_index) < m_args.size(); - } - - private: - span m_args; - i32 m_forward_index = 0; - }; - - class argument_storage_t - { - friend argument_parser_t; - friend argument_subparser_t; - friend argument_builder_t; - - public: - template - const T& get(const std::string_view key) - { - return std::get(m_data[key]); - } - - std::string_view get(const std::string_view key) - { - return std::get(m_data[key]); - } - - bool contains(const std::string_view key) - { - return m_data.find(key) != m_data.end(); - } - - private: - hashmap_t m_data; - }; - - class argument_builder_t - { - friend argument_parser_t; - - public: - argument_builder_t() - { - dest_func = [](const std::string_view dest, argument_storage_t& storage, std::string_view value) - { - storage.m_data[dest] = value; - }; - } - - template - argument_builder_t& as_type() - { - dest_func = [](const std::string_view dest, argument_storage_t& storage, std::string_view value) - { - storage.m_data[dest] = detail::arg_type_t::convert(value); - }; - return *this; - } - - private: - action_t action = action_t::STORE; - bool required = false; // do we require this argument to be provided as an argument? - nargs_v nargs = 1; // number of arguments to consume - std::optional metavar; // variable name to be used in the help string - std::optional help; // help string to be used in the help string - std::optional> choices; // optional allowed choices for this argument - std::optional default_value; - std::optional const_value; - // dest, storage, value input - std::function dest_func; - }; - - class argument_parser_t - { - friend argument_subparser_t; - - public: - explicit argument_parser_t(const std::optional name = {}, const std::optional usage = {}, - const std::optional description = {}, const std::optional epilogue = {}): - m_name(name), m_usage(usage), m_description(description), m_epilogue(epilogue) - { - } - - template - argument_builder_t& add_flag(const std::string_view arg, Aliases... aliases) - { - static_assert( - std::conjunction_v, std::is_constructible< - std::string_view, Aliases>>...>, - "Arguments must be of type string_view, convertible to string_view or be string_view constructable"); - m_argument_builders.emplace_back(); - m_flag_arguments[arg] = &m_argument_builders.back(); - ((m_flag_arguments[std::string_view{aliases}] = &m_argument_builders.back()), ...); - return m_argument_builders.back(); - } - - argument_builder_t& add_positional(const std::string_view arg) - { - m_argument_builders.emplace_back(); - m_positional_arguments[arg] = &m_argument_builders.back(); - return m_argument_builders.back(); - } - - argument_subparser_t& add_subparser(std::string_view dest); - - void parse(argument_consumer_t& consumer); // NOLINT - - void print_help(); - - argument_parser_t& set_name(const std::string_view name) - { - m_name = name; - return *this; - } - - argument_parser_t& set_usage(const std::string_view usage) - { - m_usage = usage; - return *this; - } - - [[nodiscard]] const std::optional& get_usage() const - { - return m_usage; - } - - argument_parser_t& set_description(const std::string_view description) - { - m_description = description; - return *this; - } - - [[nodiscard]] const std::optional& get_description() const - { - return m_description; - } - - argument_parser_t& set_epilogue(const std::string_view epilogue) - { - m_epilogue = epilogue; - return *this; - } - - [[nodiscard]] const std::optional& get_epilogue() const - { - return m_epilogue; - } - - private: - std::optional m_name; - std::optional m_usage; - std::optional m_description; - std::optional m_epilogue; - std::vector> m_subparsers; - std::vector m_argument_builders; - hashmap_t m_flag_arguments; - hashmap_t m_positional_arguments; - }; - - class argument_subparser_t - { - public: - explicit argument_subparser_t(const argument_parser_t& parent): m_parent(&parent) - { - } - - template - argument_parser_t& add_parser(const std::string_view name, Aliases... aliases) - { - static_assert( - std::conjunction_v, std::is_constructible< - std::string_view, Aliases>>...>, - "Arguments must be of type string_view, convertible to string_view or be string_view constructable"); - m_parsers.emplace(name); - ((m_aliases[std::string_view{aliases}] = &m_parsers[name]), ...); - return m_parsers[name]; - } - - - /** - * Parses the next argument using the provided argument consumer. - * - * This function uses an argument consumer to extract and process the next argument. - * If the argument is a flag or if it cannot be matched against the available parsers, - * an exception is thrown. - * - * @param consumer Reference to an argument_consumer_t object, which handles argument parsing. - * The consumer provides the next argument to be parsed. - * - * @throws detail::subparse_error If the argument is a flag or does not match any known parser. - */ - argument_string_t parse(argument_consumer_t& consumer); // NOLINT - - private: - [[nodiscard]] std::vector> get_allowed_strings() const; - - const argument_parser_t* m_parent; - hashmap_t m_parsers; - hashmap_t m_aliases; - }; -} - -#endif //BLT_PARSE_ARGPARSE_V2_H diff --git a/libraries/parallel-hashmap b/libraries/parallel-hashmap index 2ec7990..7ef2e73 160000 --- a/libraries/parallel-hashmap +++ b/libraries/parallel-hashmap @@ -1 +1 @@ -Subproject commit 2ec799017610ef831f4dc29c21fb3cce7e4a19b9 +Subproject commit 7ef2e733416953b222851f9a360d7fc72d068ee5 diff --git a/src/blt/logging/logging.cpp b/src/blt/logging/logging.cpp new file mode 100644 index 0000000..5f723e8 --- /dev/null +++ b/src/blt/logging/logging.cpp @@ -0,0 +1,23 @@ +/* + * + * Copyright (C) 2025 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace blt +{ + +} diff --git a/src/blt/logging/status.cpp b/src/blt/logging/status.cpp new file mode 100644 index 0000000..9ff1411 --- /dev/null +++ b/src/blt/logging/status.cpp @@ -0,0 +1,23 @@ +/* + * + * Copyright (C) 2025 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace blt +{ + +} \ No newline at end of file From dd030a9b5be5641626f869c09843b24a5809b581 Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Sat, 1 Mar 2025 21:56:57 -0500 Subject: [PATCH 044/101] silly code --- CMakeLists.txt | 1 + include/blt/logging/logging.h | 58 +++++++++++++++++++++++++++++++++-- src/blt/logging/logging.cpp | 24 ++++++++++++++- src/blt/logging/status.cpp | 2 +- tests/logger_tests.cpp | 25 +++++++++++++++ 5 files changed, 106 insertions(+), 4 deletions(-) create mode 100644 tests/logger_tests.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index c5e4dbb..71074d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -204,6 +204,7 @@ if (${BUILD_TESTS}) blt_add_test(blt_iterator tests/iterator_tests.cpp test) blt_add_test(blt_argparse tests/argparse_tests.cpp test) + blt_add_test(blt_logging tests/logger_tests.cpp test) message("Built tests") endif () diff --git a/include/blt/logging/logging.h b/include/blt/logging/logging.h index da4b5f2..02535c8 100644 --- a/include/blt/logging/logging.h +++ b/include/blt/logging/logging.h @@ -19,16 +19,70 @@ #ifndef BLT_LOGGING_LOGGING_H #define BLT_LOGGING_LOGGING_H +#include +#include #include +#include namespace blt::logging { + struct logger_t + { + explicit logger_t(std::string fmt): fmt(std::move(fmt)) + { + } + + template + std::string make_string(T&& t) + { + if constexpr (std::is_same_v || std::is_convertible_v) + return std::forward(t); + else if constexpr (std::is_same_v || std::is_same_v, char*> || std::is_convertible_v< + T, std::string_view>) + return std::string(std::forward(t)); + else if constexpr (std::is_same_v) + return std::string() + std::forward(t); + else if constexpr (std::is_arithmetic_v) + return std::to_string(std::forward(t)); + else + { + BLT_UNREACHABLE; + } + } + + void compile(); + + void insert_next_value(const std::string& arg); + + template + const std::string& log(Args&&... args) + { + (insert_next_value(make_string(std::forward(args))), ...); + return fmt; + } + + private: + std::string fmt; + }; + + void print(const std::string& fmt); + + void newline(); template - void log(const std::string& str, Args&&... args){ - + void print(std::string fmt, Args&&... args) + { + logger_t logger{std::move(fmt)}; + logger.compile(); + print(logger.log(std::forward(args)...)); } + template + void println(std::string fmt, Args&&... args) + { + print(std::move(fmt), std::forward(args)...); + newline(); + } } #endif // BLT_LOGGING_LOGGING_H diff --git a/src/blt/logging/logging.cpp b/src/blt/logging/logging.cpp index 5f723e8..6de83f2 100644 --- a/src/blt/logging/logging.cpp +++ b/src/blt/logging/logging.cpp @@ -16,8 +16,30 @@ * along with this program. If not, see . */ #include +#include -namespace blt +namespace blt::logging { + void logger_t::compile() + { + } + + void logger_t::insert_next_value(const std::string& arg) + { + const auto begin = fmt.find('{'); + const auto end = fmt.find('}', begin); + fmt.erase(fmt.begin() + static_cast(begin), fmt.begin() + static_cast(end) + 1); + fmt.insert(begin, arg); + } + + void print(const std::string& fmt) + { + std::cout << fmt; + } + + void newline() + { + std::cout << std::endl; + } } diff --git a/src/blt/logging/status.cpp b/src/blt/logging/status.cpp index 9ff1411..8fc772b 100644 --- a/src/blt/logging/status.cpp +++ b/src/blt/logging/status.cpp @@ -17,7 +17,7 @@ */ #include -namespace blt +namespace blt::logging { } \ No newline at end of file diff --git a/tests/logger_tests.cpp b/tests/logger_tests.cpp new file mode 100644 index 0000000..7e0e946 --- /dev/null +++ b/tests/logger_tests.cpp @@ -0,0 +1,25 @@ +/* + * + * Copyright (C) 2025 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +int main() +{ + blt::logging::println("This is a println!"); + blt::logging::println("This is a println with args '{}'", 42); + blt::logging::println("This is a println with multiple args '{}' '{}' '{}'", 42, 32.34231233, "Hello World!"); +} \ No newline at end of file From ff2d77a1cd492a39fda0ec8ef3b31065e680b194 Mon Sep 17 00:00:00 2001 From: Brett Date: Sun, 2 Mar 2025 21:21:19 -0500 Subject: [PATCH 045/101] this is a breaking change (logging) --- CMakeLists.txt | 2 +- commit.py.save | 74 ---------------------------- include/blt/logging/logging.h | 91 +++++++++++++++++------------------ include/blt/std/logging.h | 5 -- libraries/parallel-hashmap | 2 +- src/blt/logging/logging.cpp | 56 ++++++++++++++------- 6 files changed, 83 insertions(+), 147 deletions(-) delete mode 100644 commit.py.save diff --git a/CMakeLists.txt b/CMakeLists.txt index 71074d0..0b06a48 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 4.0.38) +set(BLT_VERSION 5.0.0) set(BLT_TARGET BLT) diff --git a/commit.py.save b/commit.py.save deleted file mode 100644 index 3ecb2e0..0000000 --- a/commit.py.save +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/python3 - -import subprocess - -#--------------------------------------- -# CONFIG -#--------------------------------------- - -VERSION_BEGIN_STR = "set(BLT_VERSION " -VERSION_END_STR = ")" - -#--------------------------------------- -# DO NOT TOUCH -#--------------------------------------- - -type = input("What kind of commit is this ((M)ajor, (m)inor, (p)atch)? ") - -def load_cmake(): - cmake_file = open("CMakeLists.txt", 'r') - cmake_text = cmake_file.read() - cmake_file.close() - return cmake_text - -def write_cmake(cmake_text): - cmake_file = open("CMakeLists.txt", 'w') - cmake_file.write(cmake_text) - cmake_file.close() - -def get_version(cmake_text): - begin = cmake_text.find(VERSION_BEGIN_STR) + len(find_text) - end = cmake_text.find(VERSION_END_STR, begin) - return (cmake_text[begin:end], begin, end) - -def split_version(cmake_text): - version, begin, end = get_version(cmake_text) - version_parts = version.split('.') - return (version_parts, begin, end) - -def recombine(cmake_text, version_parts, begin, end): - constructed_version = version_parts[0] + '.' + version_parts[1] + '.' + version_parts[2] - constructed_text_begin = cmake_text[0:begin] - constrcuted_text_end = cmake_text[end::] - return constructed_text_begin + constructed_version + constrcuted_text_end - - -def inc_major(cmake_text): - version_parts, begin, end = split_version(cmake_text) - version_parts[0] = str(int(version_parts[0]) + 1) - return recombine(cmake_text, version_parts, begin, end) - -def inc_minor(cmake_text): - version_parts, begin, end = split_version(cmake_text) - version_parts[1] = str(int(version_parts[1]) + 1) - return recombine(cmake_text, version_parts, begin, end) - -def inc_patch(cmake_text): - version_parts, begin, end = split_version(cmake_text) - version_parts[2] = str(int(version_parts[2]) + 1) - return recombine(cmake_text, version_parts, begin, end) - -if type.startswith('M'): - print("Selected major") - write_cmake(inc_major(load_cmake())) -elif type.startswith('m'): - print("Selected minor") - write_cmake(inc_minor(load_cmake())) -elif type.startswith('p') or type.startswith('P') or len(type) == 0: - print("Selected patch") - write_cmake(inc_patch(load_cmake())) - -#subprocess.call("./py_commit_helper.sh") -subprocess.call("git", "add", "*") -subprocess.call("git", "commit") -subprocess.call("sh -e 'git remote | xargs -L1 git push --all'") diff --git a/include/blt/logging/logging.h b/include/blt/logging/logging.h index 02535c8..48648c5 100644 --- a/include/blt/logging/logging.h +++ b/include/blt/logging/logging.h @@ -21,68 +21,63 @@ #include #include +#include #include #include +#include namespace blt::logging { - struct logger_t - { - explicit logger_t(std::string fmt): fmt(std::move(fmt)) - { - } + struct logger_t + { + explicit logger_t() = default; - template - std::string make_string(T&& t) - { - if constexpr (std::is_same_v || std::is_convertible_v) - return std::forward(t); - else if constexpr (std::is_same_v || std::is_same_v, char*> || std::is_convertible_v< - T, std::string_view>) - return std::string(std::forward(t)); - else if constexpr (std::is_same_v) - return std::string() + std::forward(t); - else if constexpr (std::is_arithmetic_v) - return std::to_string(std::forward(t)); - else - { - BLT_UNREACHABLE; - } - } + template + void print_value(T&& t) + { + static_assert(meta::is_streamable_v, "T must be streamable in order to work with blt::logging!"); + m_stream << std::forward(t); + } - void compile(); + template + std::string log(std::string fmt, Args&&... args) + { + compile(std::move(fmt)); + ((consume_until_fmt(), print_value(std::forward(args))), ...); + return to_string(); + } - void insert_next_value(const std::string& arg); + std::string to_string(); - template - const std::string& log(Args&&... args) - { - (insert_next_value(make_string(std::forward(args))), ...); - return fmt; - } + private: + void compile(std::string fmt); - private: - std::string fmt; - }; + void consume_until_fmt(); - void print(const std::string& fmt); + std::string m_fmt; + std::stringstream m_stream; + size_t m_last_fmt_pos = 0; + }; - void newline(); + void print(const std::string& fmt); - template - void print(std::string fmt, Args&&... args) - { - logger_t logger{std::move(fmt)}; - logger.compile(); - print(logger.log(std::forward(args)...)); - } + void newline(); - template - void println(std::string fmt, Args&&... args) - { - print(std::move(fmt), std::forward(args)...); - newline(); - } + logger_t& get_global_logger(); + + template + void print(std::string fmt, Args&&... args) + { + auto& logger = get_global_logger(); + print(logger.log(std::move(fmt), std::forward(args)...)); + } + + template + void println(std::string fmt, Args&&... args) + { + print(std::move(fmt), std::forward(args)...); + newline(); + } } #endif // BLT_LOGGING_LOGGING_H diff --git a/include/blt/std/logging.h b/include/blt/std/logging.h index f03a7a6..42bc3f4 100644 --- a/include/blt/std/logging.h +++ b/include/blt/std/logging.h @@ -828,11 +828,6 @@ void flush() { std::cout.flush(); } -void newline() -{ - std::cout << std::endl; -} - } #endif diff --git a/libraries/parallel-hashmap b/libraries/parallel-hashmap index 7ef2e73..93201da 160000 --- a/libraries/parallel-hashmap +++ b/libraries/parallel-hashmap @@ -1 +1 @@ -Subproject commit 7ef2e733416953b222851f9a360d7fc72d068ee5 +Subproject commit 93201da2ba5a6aba0a6e57ada64973555629b3e3 diff --git a/src/blt/logging/logging.cpp b/src/blt/logging/logging.cpp index 6de83f2..f16eac4 100644 --- a/src/blt/logging/logging.cpp +++ b/src/blt/logging/logging.cpp @@ -20,26 +20,46 @@ namespace blt::logging { - void logger_t::compile() - { + struct logging_thread_context_t + { + logger_t logger; + }; - } + std::string logger_t::to_string() + { + auto str = m_stream.str(); + m_stream.str(""); + m_stream.clear(); + return str; + } - void logger_t::insert_next_value(const std::string& arg) - { - const auto begin = fmt.find('{'); - const auto end = fmt.find('}', begin); - fmt.erase(fmt.begin() + static_cast(begin), fmt.begin() + static_cast(end) + 1); - fmt.insert(begin, arg); - } + void logger_t::compile(std::string fmt) + { + m_fmt = std::move(fmt); + m_last_fmt_pos = 0; + } - void print(const std::string& fmt) - { - std::cout << fmt; - } + void logger_t::consume_until_fmt() + { + const auto begin = m_fmt.find('{', m_last_fmt_pos); + const auto end = m_fmt.find('}', begin); + m_stream << std::string_view(m_fmt.data() + static_cast(m_last_fmt_pos), begin - m_last_fmt_pos); + m_last_fmt_pos = end; + } - void newline() - { - std::cout << std::endl; - } + logger_t& get_global_logger() + { + thread_local logging_thread_context_t context; + return context.logger; + } + + void print(const std::string& fmt) + { + std::cout << fmt; + } + + void newline() + { + std::cout << std::endl; + } } From 0490f50e3cf90f8e001414c9be7ff83827f123ab Mon Sep 17 00:00:00 2001 From: Brett Date: Mon, 3 Mar 2025 01:52:20 -0500 Subject: [PATCH 046/101] tokenizer, basic logging works --- CMakeLists.txt | 2 +- include/blt/logging/fmt_tokenizer.h | 64 +++++++++++++++++++++++ include/blt/logging/logging.h | 36 ++++++++++--- src/blt/logging/fmt_tokenizer.cpp | 81 +++++++++++++++++++++++++++++ src/blt/logging/logging.cpp | 57 ++++++++++++++++++-- tests/logger_tests.cpp | 8 ++- 6 files changed, 237 insertions(+), 11 deletions(-) create mode 100644 include/blt/logging/fmt_tokenizer.h create mode 100644 src/blt/logging/fmt_tokenizer.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 0b06a48..171443e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.0.0) +set(BLT_VERSION 5.1.0) set(BLT_TARGET BLT) diff --git a/include/blt/logging/fmt_tokenizer.h b/include/blt/logging/fmt_tokenizer.h new file mode 100644 index 0000000..ca0d05d --- /dev/null +++ b/include/blt/logging/fmt_tokenizer.h @@ -0,0 +1,64 @@ +#pragma once +/* + * Copyright (C) 2024 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BLT_LOGGING_FMT_TOKENIZER_H +#define BLT_LOGGING_FMT_TOKENIZER_H + +#include +#include +#include +#include + +namespace blt::logging +{ + enum class fmt_token_type : u8 + { + STRING, + NUMBER, + SPACE, + COLON, + DOT, + MINUS, + PLUS + }; + + struct fmt_token_t + { + fmt_token_type type; + std::string_view value; + }; + + class fmt_tokenizer_t + { + public: + explicit fmt_tokenizer_t(const std::string_view fmt): m_fmt(fmt) + {} + + static fmt_token_type get_type(char c); + + std::optional next(); + + std::vector tokenize(); + + private: + size_t pos = 0; + std::string_view m_fmt; + }; +} + +#endif //BLT_LOGGING_FMT_TOKENIZER_H diff --git a/include/blt/logging/logging.h b/include/blt/logging/logging.h index 48648c5..1cd41ee 100644 --- a/include/blt/logging/logging.h +++ b/include/blt/logging/logging.h @@ -19,11 +19,9 @@ #ifndef BLT_LOGGING_LOGGING_H #define BLT_LOGGING_LOGGING_H -#include -#include #include #include -#include +#include #include namespace blt::logging @@ -33,30 +31,56 @@ namespace blt::logging explicit logger_t() = default; template - void print_value(T&& t) + std::string print_value(T&& t) { static_assert(meta::is_streamable_v, "T must be streamable in order to work with blt::logging!"); + m_stream.str(""); + m_stream.clear(); m_stream << std::forward(t); + return m_stream.str(); } template std::string log(std::string fmt, Args&&... args) { compile(std::move(fmt)); - ((consume_until_fmt(), print_value(std::forward(args))), ...); + m_args_to_str.clear(); + m_args_to_str.resize(sizeof...(Args)); + insert(std::make_integer_sequence{}, std::forward(args)...); + finish(); return to_string(); } std::string to_string(); private: + template + void insert(std::integer_sequence, Args&&... args) + { + ((handle_insert(std::forward(args))), ...); + } + + template + void handle_insert(T&& t) + { + m_args_to_str[index] = print_value(std::forward(t)); + } + + void handle_fmt(std::string_view fmt); + + const std::string& get(size_t index); + void compile(std::string fmt); - void consume_until_fmt(); + bool consume_until_fmt(); + + void finish(); std::string m_fmt; std::stringstream m_stream; + std::vector m_args_to_str; size_t m_last_fmt_pos = 0; + size_t m_arg_pos = 0; }; void print(const std::string& fmt); diff --git a/src/blt/logging/fmt_tokenizer.cpp b/src/blt/logging/fmt_tokenizer.cpp new file mode 100644 index 0000000..3784ece --- /dev/null +++ b/src/blt/logging/fmt_tokenizer.cpp @@ -0,0 +1,81 @@ +/* + * + * Copyright (C) 2025 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace blt::logging +{ + fmt_token_type fmt_tokenizer_t::get_type(const char c) + { + switch (c) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return fmt_token_type::NUMBER; + case '+': + return fmt_token_type::PLUS; + case '-': + return fmt_token_type::MINUS; + case '.': + return fmt_token_type::DOT; + case ':': + return fmt_token_type::COLON; + case ' ': + return fmt_token_type::SPACE; + default: + return fmt_token_type::STRING; + } + } + + std::optional fmt_tokenizer_t::next() + { + if (pos >= m_fmt.size()) + return {}; + switch (const auto base_type = get_type(m_fmt[pos])) + { + case fmt_token_type::SPACE: + case fmt_token_type::PLUS: + case fmt_token_type::MINUS: + case fmt_token_type::DOT: + case fmt_token_type::COLON: + return fmt_token_t{base_type, std::string_view{m_fmt.data() + pos++, 1}}; + default: + { + const auto begin = pos; + for (; pos < m_fmt.size() && get_type(m_fmt[pos]) == base_type; ++pos) + {} + return fmt_token_t{base_type, std::string_view{m_fmt.data() + begin, pos - begin}}; + } + } + } + + std::vector fmt_tokenizer_t::tokenize() + { + std::vector tokens; + while (auto token = next()) + tokens.push_back(*token); + return tokens; + } +} diff --git a/src/blt/logging/logging.cpp b/src/blt/logging/logging.cpp index f16eac4..4e9309b 100644 --- a/src/blt/logging/logging.cpp +++ b/src/blt/logging/logging.cpp @@ -15,6 +15,8 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +#include +#include #include #include @@ -33,18 +35,67 @@ namespace blt::logging return str; } + void logger_t::handle_fmt(const std::string_view fmt) + { + std::cout << fmt << std::endl; + } + + const std::string& logger_t::get(const size_t index) + { + if (index >= m_args_to_str.size()) + { + std::cerr << "Insufficient number of arguments provided to format string '" << m_fmt << "' got "; + for (const auto& [i, arg] : enumerate(std::as_const(m_args_to_str))) + { + std::cerr << '\'' << arg << "'"; + if (i != m_args_to_str.size() - 1) + std::cerr << " "; + } + std::exit(EXIT_FAILURE); + } + return m_args_to_str[index]; + } + void logger_t::compile(std::string fmt) { m_fmt = std::move(fmt); m_last_fmt_pos = 0; + m_arg_pos = 0; } - void logger_t::consume_until_fmt() + bool logger_t::consume_until_fmt() { const auto begin = m_fmt.find('{', m_last_fmt_pos); + if (begin == std::string::npos) + return false; const auto end = m_fmt.find('}', begin); - m_stream << std::string_view(m_fmt.data() + static_cast(m_last_fmt_pos), begin - m_last_fmt_pos); - m_last_fmt_pos = end; + if (end == std::string::npos) + { + std::cerr << "Invalid format string, missing closing '}' near " << m_fmt.substr(std::min(static_cast(begin) - 5, 0l)) << std::endl; + std::exit(EXIT_FAILURE); + } + m_stream << std::string_view(m_fmt.data() + static_cast(m_last_fmt_pos), begin - m_last_fmt_pos);\ + if (end - begin > 1) + handle_fmt(std::string_view(m_fmt.data() + static_cast(begin + 1), end - begin - 1)); + else + { + // no arguments, must consume from args + m_stream << get(m_arg_pos++); + } + m_last_fmt_pos = end + 1; + return true; + } + + void logger_t::finish() + { + m_stream.str(""); + m_stream.clear(); + + while (consume_until_fmt()) + {} + + m_stream << std::string_view(m_fmt.data() + static_cast(m_last_fmt_pos), m_fmt.size() - m_last_fmt_pos); + m_last_fmt_pos = m_fmt.size(); } logger_t& get_global_logger() diff --git a/tests/logger_tests.cpp b/tests/logger_tests.cpp index 7e0e946..e0de5c7 100644 --- a/tests/logger_tests.cpp +++ b/tests/logger_tests.cpp @@ -16,10 +16,16 @@ * along with this program. If not, see . */ #include +#include int main() { + + using endl_t = decltype(static_cast(std::endl)); + // blt::logging::println("{} | {} | {} | {}", blt::type_string()); blt::logging::println("This is a println!"); blt::logging::println("This is a println with args '{}'", 42); blt::logging::println("This is a println with multiple args '{}' '{}' '{}'", 42, 32.34231233, "Hello World!"); -} \ No newline at end of file + blt::logging::println("This is a '{1}' fmt string with positionals '{0}'", "I am a string!", "Well so am I except cooler :3"); + // blt::logging::println("This is println {}\twith a std::endl in the middle of it"); +} From 4ac592becad23874de6d17d347f5f95ea8d28b51 Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Mon, 3 Mar 2025 15:44:42 -0500 Subject: [PATCH 047/101] minor work on parser --- CMakeLists.txt | 2 +- include/blt/logging/fmt_tokenizer.h | 129 +++++++++--- libraries/parallel-hashmap | 2 +- src/blt/logging/fmt_tokenizer.cpp | 302 ++++++++++++++++++++++------ 4 files changed, 349 insertions(+), 86 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 171443e..77a14de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.1.0) +set(BLT_VERSION 5.1.1) set(BLT_TARGET BLT) diff --git a/include/blt/logging/fmt_tokenizer.h b/include/blt/logging/fmt_tokenizer.h index ca0d05d..ce436aa 100644 --- a/include/blt/logging/fmt_tokenizer.h +++ b/include/blt/logging/fmt_tokenizer.h @@ -26,39 +26,114 @@ namespace blt::logging { - enum class fmt_token_type : u8 - { - STRING, - NUMBER, - SPACE, - COLON, - DOT, - MINUS, - PLUS - }; + enum class fmt_token_type : u8 + { + STRING, + NUMBER, + SPACE, + COLON, + DOT, + MINUS, + PLUS + }; - struct fmt_token_t - { - fmt_token_type type; - std::string_view value; - }; + enum class fmt_sign_t : u8 + { + SPACE, + PLUS, + MINUS + }; - class fmt_tokenizer_t - { - public: - explicit fmt_tokenizer_t(const std::string_view fmt): m_fmt(fmt) - {} + enum class fmt_type_t : u8 + { + BINARY, // 'b' + CHAR, // 'c' + DECIMAL, // 'd' + OCTAL, // 'o' + HEX, // 'x' + HEX_FLOAT, // 'a' + EXPONENT, // 'e' + FIXED_POINT, // 'f' + GENERAL // 'g' + }; - static fmt_token_type get_type(char c); + struct fmt_spec_t + { + i64 arg_id = -1; + i64 width = -1; + i64 precision = -1; + fmt_type_t type = fmt_type_t::DECIMAL; + fmt_sign_t sign = fmt_sign_t::MINUS; + bool leading_zeros = false; + bool uppercase = false; + }; - std::optional next(); + struct fmt_token_t + { + fmt_token_type type; + std::string_view value; + }; - std::vector tokenize(); + class fmt_tokenizer_t + { + public: + explicit fmt_tokenizer_t() = default; - private: - size_t pos = 0; - std::string_view m_fmt; - }; + static fmt_token_type get_type(char c); + + std::optional next(); + + std::vector tokenize(std::string_view fmt); + + private: + size_t m_pos = 0; + std::string_view m_fmt; + }; + + + class fmt_parser_t + { + public: + explicit fmt_parser_t() = default; + + fmt_token_t& peek() + { + return m_tokens[m_pos]; + } + + [[nodiscard]] bool has_next() const + { + return m_pos < m_tokens.size(); + } + + [[nodiscard]] fmt_token_t& next() + { + return m_tokens[m_pos++]; + } + + void consume() + { + ++m_pos; + } + + const fmt_spec_t& parse(std::string_view fmt); + + private: + void parse_fmt_field(); + void parse_arg_id(); + void parse_fmt_spec_stage_1(); + void parse_fmt_spec_stage_2(); + void parse_fmt_spec_stage_3(); + void parse_sign(); + void parse_width(); + void parse_precision(); + void parse_type(); + + size_t m_pos = 0; + std::vector m_tokens; + fmt_tokenizer_t m_tokenizer; + fmt_spec_t m_spec; + }; } #endif //BLT_LOGGING_FMT_TOKENIZER_H diff --git a/libraries/parallel-hashmap b/libraries/parallel-hashmap index 93201da..7ef2e73 160000 --- a/libraries/parallel-hashmap +++ b/libraries/parallel-hashmap @@ -1 +1 @@ -Subproject commit 93201da2ba5a6aba0a6e57ada64973555629b3e3 +Subproject commit 7ef2e733416953b222851f9a360d7fc72d068ee5 diff --git a/src/blt/logging/fmt_tokenizer.cpp b/src/blt/logging/fmt_tokenizer.cpp index 3784ece..507690c 100644 --- a/src/blt/logging/fmt_tokenizer.cpp +++ b/src/blt/logging/fmt_tokenizer.cpp @@ -15,67 +15,255 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +#include #include namespace blt::logging { - fmt_token_type fmt_tokenizer_t::get_type(const char c) - { - switch (c) - { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - return fmt_token_type::NUMBER; - case '+': - return fmt_token_type::PLUS; - case '-': - return fmt_token_type::MINUS; - case '.': - return fmt_token_type::DOT; - case ':': - return fmt_token_type::COLON; - case ' ': - return fmt_token_type::SPACE; - default: - return fmt_token_type::STRING; - } - } + fmt_token_type fmt_tokenizer_t::get_type(const char c) + { + switch (c) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return fmt_token_type::NUMBER; + case '+': + return fmt_token_type::PLUS; + case '-': + return fmt_token_type::MINUS; + case '.': + return fmt_token_type::DOT; + case ':': + return fmt_token_type::COLON; + case ' ': + return fmt_token_type::SPACE; + default: + return fmt_token_type::STRING; + } + } - std::optional fmt_tokenizer_t::next() - { - if (pos >= m_fmt.size()) - return {}; - switch (const auto base_type = get_type(m_fmt[pos])) - { - case fmt_token_type::SPACE: - case fmt_token_type::PLUS: - case fmt_token_type::MINUS: - case fmt_token_type::DOT: - case fmt_token_type::COLON: - return fmt_token_t{base_type, std::string_view{m_fmt.data() + pos++, 1}}; - default: - { - const auto begin = pos; - for (; pos < m_fmt.size() && get_type(m_fmt[pos]) == base_type; ++pos) - {} - return fmt_token_t{base_type, std::string_view{m_fmt.data() + begin, pos - begin}}; - } - } - } + std::optional fmt_tokenizer_t::next() + { + if (m_pos >= m_fmt.size()) + return {}; + switch (const auto base_type = get_type(m_fmt[m_pos])) + { + case fmt_token_type::SPACE: + case fmt_token_type::PLUS: + case fmt_token_type::MINUS: + case fmt_token_type::DOT: + case fmt_token_type::COLON: + return fmt_token_t{base_type, std::string_view{m_fmt.data() + m_pos++, 1}}; + default: + { + const auto begin = m_pos; + for (; m_pos < m_fmt.size() && get_type(m_fmt[m_pos]) == base_type; ++m_pos) + { + } + return fmt_token_t{base_type, std::string_view{m_fmt.data() + begin, m_pos - begin}}; + } + } + } - std::vector fmt_tokenizer_t::tokenize() - { - std::vector tokens; - while (auto token = next()) - tokens.push_back(*token); - return tokens; - } + std::vector fmt_tokenizer_t::tokenize(const std::string_view fmt) + { + m_fmt = fmt; + m_pos = 0; + std::vector tokens; + while (auto token = next()) + tokens.push_back(*token); + return tokens; + } + + const fmt_spec_t& fmt_parser_t::parse(const std::string_view fmt) + { + m_spec = {}; + m_pos = 0; + m_tokens = m_tokenizer.tokenize(fmt); + + parse_fmt_field(); + + return m_spec; + } + + void fmt_parser_t::parse_fmt_field() + { + if (!has_next()) + { + std::cerr << "Expected token when parsing format field" << std::endl; + std::exit(EXIT_FAILURE); + } + const auto [type, value] = peek(); + if (type == fmt_token_type::COLON) + { + consume(); + parse_fmt_spec_stage_1(); + } + else if (type == fmt_token_type::NUMBER) + { + parse_arg_id(); + if (has_next()) + { + if (peek().type == fmt_token_type::COLON) + { + consume(); + parse_fmt_spec_stage_1(); + }else + { + std::cerr << "Expected ':' when parsing format field after arg id!" << std::endl; + std::exit(EXIT_FAILURE); + } + } + } + else + { + std::cerr << "Expected unknown token '" << static_cast(type) << "' value '" << value << "' when parsing format field" << std::endl; + std::exit(EXIT_FAILURE); + } + if (has_next()) + parse_type(); + } + + void fmt_parser_t::parse_arg_id() + { + if (!has_next()) + { + std::cerr << "Missing token when parsing arg id" << std::endl; + std::exit(EXIT_FAILURE); + } + const auto [type, value] = next(); + if (type != fmt_token_type::NUMBER) + { + std::cerr << "Expected number when parsing arg id, unexpected value '" << value << '\'' << std::endl; + std::exit(EXIT_FAILURE); + } + m_spec.arg_id = std::stoll(std::string(value)); + } + + // handle start of fmt, with sign + void fmt_parser_t::parse_fmt_spec_stage_1() + { + if (!has_next()) + return; + auto [type, value] = peek(); + switch (type) + { + case fmt_token_type::STRING: + case fmt_token_type::COLON: + std::cerr << "(Stage 1) Invalid token type " << static_cast(type) << " value " << value << std::endl; + std::exit(EXIT_FAILURE); + case fmt_token_type::NUMBER: + parse_width(); + parse_fmt_spec_stage_3(); + break; + case fmt_token_type::DOT: + consume(); + parse_precision(); + break; + case fmt_token_type::SPACE: + case fmt_token_type::MINUS: + case fmt_token_type::PLUS: + parse_sign(); + parse_fmt_spec_stage_2(); + break; + } + } + + // handle width parsing + void fmt_parser_t::parse_fmt_spec_stage_2() + { + if (!has_next()) + return; + auto [type, value] = peek(); + switch (type) + { + case fmt_token_type::STRING: + case fmt_token_type::COLON: + case fmt_token_type::MINUS: + case fmt_token_type::PLUS: + case fmt_token_type::SPACE: + std::cerr << "(Stage 2) Invalid token type " << static_cast(type) << " value " << value << std::endl; + std::exit(EXIT_FAILURE); + case fmt_token_type::NUMBER: + parse_width(); + parse_fmt_spec_stage_3(); + break; + case fmt_token_type::DOT: + consume(); + parse_precision(); + break; + } + } + + void fmt_parser_t::parse_fmt_spec_stage_3() + { + if (!has_next()) + return; + auto [type, value] = peek(); + switch (type) + { + case fmt_token_type::STRING: + case fmt_token_type::COLON: + case fmt_token_type::MINUS: + case fmt_token_type::PLUS: + case fmt_token_type::SPACE: + case fmt_token_type::NUMBER: + std::cerr << "(Stage 3) Invalid token type " << static_cast(type) << " value " << value << std::endl; + std::exit(EXIT_FAILURE); + case fmt_token_type::DOT: + consume(); + parse_precision(); + break; + } + } + + void fmt_parser_t::parse_sign() + { + auto [_, value] = next(); + if (value.size() > 1) + { + std::cerr << "Sign contains more than one character, we are not sure how to interpret this. Value '" << value << "'\n"; + std::exit(EXIT_FAILURE); + } + switch (value[0]) + { + case '+': + m_spec.sign = fmt_sign_t::PLUS; + break; + case '-': + m_spec.sign = fmt_sign_t::MINUS; + break; + case ' ': + m_spec.sign = fmt_sign_t::SPACE; + break; + default: + std::cerr << "Invalid sign " << value[0] << std::endl; + std::exit(EXIT_FAILURE); + } + } + + void fmt_parser_t::parse_width() + { + auto [_, value] = next(); + if (value.front() == '0') + m_spec.leading_zeros = true; + m_spec.width = std::stoll(std::string(value)); + } + + void fmt_parser_t::parse_precision() + { + + } + + void fmt_parser_t::parse_type() + { + } } From 74878d6b431abf7ae40851db6a2ddd611c23555f Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Mon, 3 Mar 2025 20:36:10 -0500 Subject: [PATCH 048/101] silly code --- CMakeLists.txt | 2 +- include/blt/logging/logging.h | 154 ++++++++++++++++++------------ src/blt/logging/fmt_tokenizer.cpp | 62 +++++++++++- src/blt/logging/logging.cpp | 90 +++++++++++------ tests/logger_tests.cpp | 5 + 5 files changed, 219 insertions(+), 94 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 77a14de..7d95e2c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.1.1) +set(BLT_VERSION 5.1.2) set(BLT_TARGET BLT) diff --git a/include/blt/logging/logging.h b/include/blt/logging/logging.h index 1cd41ee..cf98092 100644 --- a/include/blt/logging/logging.h +++ b/include/blt/logging/logging.h @@ -23,85 +23,119 @@ #include #include #include +#include namespace blt::logging { - struct logger_t - { - explicit logger_t() = default; + struct logger_t + { + explicit logger_t() = default; - template - std::string print_value(T&& t) - { - static_assert(meta::is_streamable_v, "T must be streamable in order to work with blt::logging!"); - m_stream.str(""); - m_stream.clear(); - m_stream << std::forward(t); - return m_stream.str(); - } + template + std::string log(std::string fmt, Args&&... args) + { + compile(std::move(fmt)); + auto sequence = std::make_integer_sequence{}; + while (auto pair = consume_until_fmt()) + { + auto [begin, end] = *pair; + if (end - begin > 0) + { + auto format_data = handle_fmt(m_fmt.substr(begin + 1, begin - end - 1)); + auto [arg_pos, fmt_type] = format_data; + if (arg_pos == -1) + arg_pos = static_cast(m_arg_pos++); + if (fmt_type) + { + if (fmt_type == fmt_type_t::GENERAL) + { + apply_func([this](auto&& value) + { + if (static_cast(value) > 0xFFFFFFFFFul) + exponential(); + else + fixed(); + m_stream << std::forward(value); + }, arg_pos, sequence, std::forward(args)...); + } else if (fmt_type == fmt_type_t::CHAR) + { - template - std::string log(std::string fmt, Args&&... args) - { - compile(std::move(fmt)); - m_args_to_str.clear(); - m_args_to_str.resize(sizeof...(Args)); - insert(std::make_integer_sequence{}, std::forward(args)...); - finish(); - return to_string(); - } + } else if (fmt_type == fmt_type_t::BINARY) + { - std::string to_string(); + } + } + else + { + apply_func([this](auto&& value) + { + m_stream << std::forward(value); + }, arg_pos, sequence, std::forward(args)...); + } + } + else + apply_func([this](auto&& value) + { + m_stream << std::forward(value); + }, m_arg_pos++, sequence, std::forward(args)...); + } + finish(); + return to_string(); + } - private: - template - void insert(std::integer_sequence, Args&&... args) - { - ((handle_insert(std::forward(args))), ...); - } + std::string to_string(); - template - void handle_insert(T&& t) - { - m_args_to_str[index] = print_value(std::forward(t)); - } + private: + template + void apply_func(const Func& func, const size_t arg, std::integer_sequence, Args&&... args) + { + ((handle_func(func, arg, std::forward(args))), ...); + } - void handle_fmt(std::string_view fmt); + template + void handle_func(const Func& func, const size_t arg, T&& t) + { + if (index == arg) + func(std::forward(t)); + } - const std::string& get(size_t index); + [[nodiscard]] std::pair> handle_fmt(std::string_view fmt); - void compile(std::string fmt); + void exponential(); + void fixed(); - bool consume_until_fmt(); + void compile(std::string fmt); - void finish(); + std::optional> consume_until_fmt(); - std::string m_fmt; - std::stringstream m_stream; - std::vector m_args_to_str; - size_t m_last_fmt_pos = 0; - size_t m_arg_pos = 0; - }; + void finish(); - void print(const std::string& fmt); + std::string m_fmt; + std::stringstream m_stream; + fmt_parser_t m_parser; + size_t m_last_fmt_pos = 0; + size_t m_arg_pos = 0; + }; - void newline(); + void print(const std::string& fmt); - logger_t& get_global_logger(); + void newline(); - template - void print(std::string fmt, Args&&... args) - { - auto& logger = get_global_logger(); - print(logger.log(std::move(fmt), std::forward(args)...)); - } + logger_t& get_global_logger(); - template - void println(std::string fmt, Args&&... args) - { - print(std::move(fmt), std::forward(args)...); - newline(); - } + template + void print(std::string fmt, Args&&... args) + { + auto& logger = get_global_logger(); + print(logger.log(std::move(fmt), std::forward(args)...)); + } + + template + void println(std::string fmt, Args&&... args) + { + print(std::move(fmt), std::forward(args)...); + newline(); + } } #endif // BLT_LOGGING_LOGGING_H diff --git a/src/blt/logging/fmt_tokenizer.cpp b/src/blt/logging/fmt_tokenizer.cpp index 507690c..b2bb522 100644 --- a/src/blt/logging/fmt_tokenizer.cpp +++ b/src/blt/logging/fmt_tokenizer.cpp @@ -116,7 +116,8 @@ namespace blt::logging { consume(); parse_fmt_spec_stage_1(); - }else + } + else { std::cerr << "Expected ':' when parsing format field after arg id!" << std::endl; std::exit(EXIT_FAILURE); @@ -260,10 +261,67 @@ namespace blt::logging void fmt_parser_t::parse_precision() { - + if (!has_next()) + { + std::cerr << "Missing token when parsing precision" << std::endl; + std::exit(EXIT_FAILURE); + } + auto [_, value] = next(); + m_spec.precision = std::stoll(std::string(value)); } void fmt_parser_t::parse_type() { + if (!has_next()) + { + std::cerr << "Missing token when parsing type" << std::endl; + std::exit(EXIT_FAILURE); + } + auto [_, value] = next(); + if (value.size() != 1) + { + std::cerr << "Type contains more than one character, we are not sure how to interpret this value '" << value << "'\n"; + std::exit(EXIT_FAILURE); + } + m_spec.uppercase = std::isupper(value.front()); + switch (value.front()) + { + case 'b': + case 'B': + m_spec.type = fmt_type_t::BINARY; + break; + case 'c': + m_spec.type = fmt_type_t::CHAR; + break; + case 'd': + m_spec.type = fmt_type_t::DECIMAL; + break; + case 'o': + m_spec.type = fmt_type_t::OCTAL; + break; + case 'x': + case 'X': + m_spec.type = fmt_type_t::HEX; + break; + case 'a': + case 'A': + m_spec.type = fmt_type_t::HEX_FLOAT; + break; + case 'e': + case 'E': + m_spec.type = fmt_type_t::EXPONENT; + break; + case 'f': + case 'F': + m_spec.type = fmt_type_t::FIXED_POINT; + break; + case 'g': + case 'G': + m_spec.type = fmt_type_t::GENERAL; + break; + default: + std::cerr << "Invalid type " << value << std::endl; + std::exit(EXIT_FAILURE); + } } } diff --git a/src/blt/logging/logging.cpp b/src/blt/logging/logging.cpp index 4e9309b..971269f 100644 --- a/src/blt/logging/logging.cpp +++ b/src/blt/logging/logging.cpp @@ -15,6 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +#include #include #include #include @@ -35,25 +36,63 @@ namespace blt::logging return str; } - void logger_t::handle_fmt(const std::string_view fmt) + std::pair> logger_t::handle_fmt(const std::string_view fmt) { - std::cout << fmt << std::endl; + const auto spec = m_parser.parse(fmt); + if (spec.leading_zeros) + m_stream << std::setfill('0'); + else + m_stream << std::setfill(' '); + if (spec.width > 0) + m_stream << std::setw(static_cast(spec.width)); + else + m_stream << std::setw(0); + if (spec.precision > 0) + m_stream << std::setprecision(static_cast(spec.precision)); + else + m_stream << std::setprecision(2); + if (spec.uppercase) + m_stream << std::uppercase; + else + m_stream << std::nouppercase; + std::optional type; + switch (spec.type) + { + case fmt_type_t::BINARY: + case fmt_type_t::CHAR: + case fmt_type_t::GENERAL: + type = spec.type; + break; + case fmt_type_t::DECIMAL: + m_stream << std::dec; + break; + case fmt_type_t::OCTAL: + m_stream << std::oct; + break; + case fmt_type_t::HEX: + m_stream << std::hex; + break; + case fmt_type_t::HEX_FLOAT: + m_stream << std::hexfloat; + break; + case fmt_type_t::EXPONENT: + m_stream << std::scientific; + break; + case fmt_type_t::FIXED_POINT: + m_stream << std::fixed; + break; + } + return {spec.arg_id, type}; } - const std::string& logger_t::get(const size_t index) + void logger_t::exponential() { - if (index >= m_args_to_str.size()) - { - std::cerr << "Insufficient number of arguments provided to format string '" << m_fmt << "' got "; - for (const auto& [i, arg] : enumerate(std::as_const(m_args_to_str))) - { - std::cerr << '\'' << arg << "'"; - if (i != m_args_to_str.size() - 1) - std::cerr << " "; - } - std::exit(EXIT_FAILURE); - } - return m_args_to_str[index]; + m_stream << std::scientific; + } + + void logger_t::fixed() + { + m_stream << std::fixed; } void logger_t::compile(std::string fmt) @@ -61,13 +100,15 @@ namespace blt::logging m_fmt = std::move(fmt); m_last_fmt_pos = 0; m_arg_pos = 0; + m_stream.str(""); + m_stream.clear(); } - bool logger_t::consume_until_fmt() + std::optional> logger_t::consume_until_fmt() { const auto begin = m_fmt.find('{', m_last_fmt_pos); if (begin == std::string::npos) - return false; + return {}; const auto end = m_fmt.find('}', begin); if (end == std::string::npos) { @@ -75,25 +116,12 @@ namespace blt::logging std::exit(EXIT_FAILURE); } m_stream << std::string_view(m_fmt.data() + static_cast(m_last_fmt_pos), begin - m_last_fmt_pos);\ - if (end - begin > 1) - handle_fmt(std::string_view(m_fmt.data() + static_cast(begin + 1), end - begin - 1)); - else - { - // no arguments, must consume from args - m_stream << get(m_arg_pos++); - } m_last_fmt_pos = end + 1; - return true; + return std::pair{begin, end}; } void logger_t::finish() { - m_stream.str(""); - m_stream.clear(); - - while (consume_until_fmt()) - {} - m_stream << std::string_view(m_fmt.data() + static_cast(m_last_fmt_pos), m_fmt.size() - m_last_fmt_pos); m_last_fmt_pos = m_fmt.size(); } diff --git a/tests/logger_tests.cpp b/tests/logger_tests.cpp index e0de5c7..77e8046 100644 --- a/tests/logger_tests.cpp +++ b/tests/logger_tests.cpp @@ -27,5 +27,10 @@ int main() blt::logging::println("This is a println with args '{}'", 42); blt::logging::println("This is a println with multiple args '{}' '{}' '{}'", 42, 32.34231233, "Hello World!"); blt::logging::println("This is a '{1}' fmt string with positionals '{0}'", "I am a string!", "Well so am I except cooler :3"); + blt::logging::println("This is a println with a sign {:+}", 4120); + blt::logging::println("This is a println with a space {: }", 4120); + blt::logging::println("This is a println with a with {:3}", 4120); + blt::logging::println("This is a println with a with leading zeros {:010}", 4120); + blt::logging::println("This is a println with a precision {:.3}", 42.232342349); // blt::logging::println("This is println {}\twith a std::endl in the middle of it"); } From b2c3820ed0d6975c722a163b66497250a3b684c1 Mon Sep 17 00:00:00 2001 From: Brett Date: Tue, 4 Mar 2025 00:44:48 -0500 Subject: [PATCH 049/101] basic logger works --- CMakeLists.txt | 2 +- include/blt/logging/logging.h | 155 ++++++++++++------- libraries/parallel-hashmap | 2 +- src/blt/logging/fmt_tokenizer.cpp | 79 +++++----- src/blt/logging/logging.cpp | 241 +++++++++++++++++------------- tests/logger_tests.cpp | 10 +- 6 files changed, 280 insertions(+), 209 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7d95e2c..d876b96 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.1.2) +set(BLT_VERSION 5.1.3) set(BLT_TARGET BLT) diff --git a/include/blt/logging/logging.h b/include/blt/logging/logging.h index cf98092..c3cf7b2 100644 --- a/include/blt/logging/logging.h +++ b/include/blt/logging/logging.h @@ -21,12 +21,18 @@ #include #include +#include #include -#include +#include +#include #include namespace blt::logging { + namespace detail + { + } + struct logger_t { explicit logger_t() = default; @@ -36,83 +42,116 @@ namespace blt::logging { compile(std::move(fmt)); auto sequence = std::make_integer_sequence{}; - while (auto pair = consume_until_fmt()) - { - auto [begin, end] = *pair; - if (end - begin > 0) - { - auto format_data = handle_fmt(m_fmt.substr(begin + 1, begin - end - 1)); - auto [arg_pos, fmt_type] = format_data; - if (arg_pos == -1) - arg_pos = static_cast(m_arg_pos++); - if (fmt_type) - { - if (fmt_type == fmt_type_t::GENERAL) - { - apply_func([this](auto&& value) - { - if (static_cast(value) > 0xFFFFFFFFFul) - exponential(); - else - fixed(); - m_stream << std::forward(value); - }, arg_pos, sequence, std::forward(args)...); - } else if (fmt_type == fmt_type_t::CHAR) - { - - } else if (fmt_type == fmt_type_t::BINARY) - { - - } - } - else - { - apply_func([this](auto&& value) - { - m_stream << std::forward(value); - }, arg_pos, sequence, std::forward(args)...); - } - } - else - apply_func([this](auto&& value) - { - m_stream << std::forward(value); - }, m_arg_pos++, sequence, std::forward(args)...); - } - finish(); + m_arg_print_funcs.clear(); + m_arg_print_funcs.resize(sizeof...(Args)); + create_conv_funcs(sequence, std::forward(args)...); + process_strings(); return to_string(); } std::string to_string(); private: - template - void apply_func(const Func& func, const size_t arg, std::integer_sequence, Args&&... args) + template + void create_conv_funcs(std::integer_sequence, Args&&... args) { - ((handle_func(func, arg, std::forward(args))), ...); + ((handle_func(std::forward(args))), ...); } - template - void handle_func(const Func& func, const size_t arg, T&& t) + template + void handle_func(const T& t) { - if (index == arg) - func(std::forward(t)); + m_arg_print_funcs[index] = [&t, this](std::ostream& stream, const fmt_spec_t& type) + { + switch (type.sign) + { + case fmt_sign_t::SPACE: + if constexpr (std::is_arithmetic_v) + { + if (t >= 0) + stream << ' '; + } + break; + case fmt_sign_t::PLUS: + if constexpr (std::is_arithmetic_v) + { + if (t >= 0) + stream << '+'; + } + break; + case fmt_sign_t::MINUS: + break; + } + switch (type.type) + { + case fmt_type_t::BINARY: + { + if constexpr (std::is_trivially_copyable_v) + { + char buffer[sizeof(T)]; + std::memcpy(buffer, &t, sizeof(T)); + stream << '0' << (type.uppercase ? 'B' : 'b'); + for (size_t i = 0; i < sizeof(T); ++i) + { + for (size_t j = 0; j < 8; ++j) + stream << ((buffer[i] & (1 << j)) ? '1' : '0'); + if (type.sign == fmt_sign_t::SPACE && i != sizeof(T) - 1) + stream << ' '; + } + } else + { + stream << t; + } + break; + } + case fmt_type_t::CHAR: + if constexpr (std::is_arithmetic_v || std::is_convertible_v) + { + stream << static_cast(t); + } else + { + stream << t; + } + break; + case fmt_type_t::GENERAL: + if constexpr (std::is_arithmetic_v) + { + if (static_cast(t) > 10e12) + exponential(stream); + else + fixed(stream); + stream << t; + } else + { + stream << t; + } + break; + default: + handle_type(stream, type.type); + stream << t; + } + }; } - [[nodiscard]] std::pair> handle_fmt(std::string_view fmt); + void setup_stream(const fmt_spec_t& spec); + void process_strings(); + static void handle_type(std::ostream& stream, fmt_type_t type); - void exponential(); - void fixed(); + static void exponential(std::ostream& stream); + static void fixed(std::ostream& stream); void compile(std::string fmt); - std::optional> consume_until_fmt(); - - void finish(); + std::optional> consume_to_next_fmt(); std::string m_fmt; std::stringstream m_stream; fmt_parser_t m_parser; + // normal sections of string + std::vector m_string_sections; + // processed format specs + std::vector m_fmt_specs; + std::vector> m_arg_print_funcs; size_t m_last_fmt_pos = 0; size_t m_arg_pos = 0; }; diff --git a/libraries/parallel-hashmap b/libraries/parallel-hashmap index 7ef2e73..93201da 160000 --- a/libraries/parallel-hashmap +++ b/libraries/parallel-hashmap @@ -1 +1 @@ -Subproject commit 7ef2e733416953b222851f9a360d7fc72d068ee5 +Subproject commit 93201da2ba5a6aba0a6e57ada64973555629b3e3 diff --git a/src/blt/logging/fmt_tokenizer.cpp b/src/blt/logging/fmt_tokenizer.cpp index b2bb522..bfb6426 100644 --- a/src/blt/logging/fmt_tokenizer.cpp +++ b/src/blt/logging/fmt_tokenizer.cpp @@ -16,6 +16,7 @@ * along with this program. If not, see . */ #include +#include #include namespace blt::logging @@ -97,10 +98,7 @@ namespace blt::logging void fmt_parser_t::parse_fmt_field() { if (!has_next()) - { - std::cerr << "Expected token when parsing format field" << std::endl; - std::exit(EXIT_FAILURE); - } + throw std::runtime_error("Expected token when parsing format field"); const auto [type, value] = peek(); if (type == fmt_token_type::COLON) { @@ -118,16 +116,14 @@ namespace blt::logging parse_fmt_spec_stage_1(); } else - { - std::cerr << "Expected ':' when parsing format field after arg id!" << std::endl; - std::exit(EXIT_FAILURE); - } + throw std::runtime_error("Expected ':' when parsing format field after arg id!"); } } else { - std::cerr << "Expected unknown token '" << static_cast(type) << "' value '" << value << "' when parsing format field" << std::endl; - std::exit(EXIT_FAILURE); + std::stringstream ss; + ss << "Expected unknown token '" << static_cast(type) << "' value '" << value << "' when parsing format field"; + throw std::runtime_error(ss.str()); } if (has_next()) parse_type(); @@ -136,15 +132,13 @@ namespace blt::logging void fmt_parser_t::parse_arg_id() { if (!has_next()) - { - std::cerr << "Missing token when parsing arg id" << std::endl; - std::exit(EXIT_FAILURE); - } + throw std::runtime_error("Missing token when parsing arg id"); const auto [type, value] = next(); if (type != fmt_token_type::NUMBER) { - std::cerr << "Expected number when parsing arg id, unexpected value '" << value << '\'' << std::endl; - std::exit(EXIT_FAILURE); + std::stringstream ss; + ss << "Expected number when parsing arg id, unexpected value '" << value << '\''; + throw std::runtime_error(ss.str()); } m_spec.arg_id = std::stoll(std::string(value)); } @@ -159,8 +153,11 @@ namespace blt::logging { case fmt_token_type::STRING: case fmt_token_type::COLON: - std::cerr << "(Stage 1) Invalid token type " << static_cast(type) << " value " << value << std::endl; - std::exit(EXIT_FAILURE); + { + std::stringstream ss; + ss << "(Stage 1) Invalid token type " << static_cast(type) << " value " << value; + throw std::runtime_error(ss.str()); + } case fmt_token_type::NUMBER: parse_width(); parse_fmt_spec_stage_3(); @@ -191,8 +188,11 @@ namespace blt::logging case fmt_token_type::MINUS: case fmt_token_type::PLUS: case fmt_token_type::SPACE: - std::cerr << "(Stage 2) Invalid token type " << static_cast(type) << " value " << value << std::endl; - std::exit(EXIT_FAILURE); + { + std::stringstream ss; + ss << "(Stage 2) Invalid token type " << static_cast(type) << " value " << value; + throw std::runtime_error(ss.str()); + } case fmt_token_type::NUMBER: parse_width(); parse_fmt_spec_stage_3(); @@ -217,8 +217,11 @@ namespace blt::logging case fmt_token_type::PLUS: case fmt_token_type::SPACE: case fmt_token_type::NUMBER: - std::cerr << "(Stage 3) Invalid token type " << static_cast(type) << " value " << value << std::endl; - std::exit(EXIT_FAILURE); + { + std::stringstream ss; + ss << "(Stage 3) Invalid token type " << static_cast(type) << " value " << value; + throw std::runtime_error(ss.str()); + } case fmt_token_type::DOT: consume(); parse_precision(); @@ -231,8 +234,9 @@ namespace blt::logging auto [_, value] = next(); if (value.size() > 1) { - std::cerr << "Sign contains more than one character, we are not sure how to interpret this. Value '" << value << "'\n"; - std::exit(EXIT_FAILURE); + std::stringstream ss; + ss << "Sign contains more than one character, we are not sure how to interpret this. Value '" << value << "'"; + throw std::runtime_error(ss.str()); } switch (value[0]) { @@ -246,8 +250,11 @@ namespace blt::logging m_spec.sign = fmt_sign_t::SPACE; break; default: - std::cerr << "Invalid sign " << value[0] << std::endl; - std::exit(EXIT_FAILURE); + { + std::stringstream ss; + ss << "Invalid sign " << value[0]; + throw std::runtime_error(ss.str()); + } } } @@ -262,10 +269,7 @@ namespace blt::logging void fmt_parser_t::parse_precision() { if (!has_next()) - { - std::cerr << "Missing token when parsing precision" << std::endl; - std::exit(EXIT_FAILURE); - } + throw std::runtime_error("Missing token when parsing precision"); auto [_, value] = next(); m_spec.precision = std::stoll(std::string(value)); } @@ -273,15 +277,13 @@ namespace blt::logging void fmt_parser_t::parse_type() { if (!has_next()) - { - std::cerr << "Missing token when parsing type" << std::endl; - std::exit(EXIT_FAILURE); - } + throw std::runtime_error("Missing token when parsing type"); auto [_, value] = next(); if (value.size() != 1) { - std::cerr << "Type contains more than one character, we are not sure how to interpret this value '" << value << "'\n"; - std::exit(EXIT_FAILURE); + std::stringstream ss; + ss << "Type contains more than one character, we are not sure how to interpret this value '" << value << "'"; + throw std::runtime_error(ss.str()); } m_spec.uppercase = std::isupper(value.front()); switch (value.front()) @@ -320,8 +322,9 @@ namespace blt::logging m_spec.type = fmt_type_t::GENERAL; break; default: - std::cerr << "Invalid type " << value << std::endl; - std::exit(EXIT_FAILURE); + std::stringstream ss; + ss << "Invalid type " << value; + throw std::runtime_error(ss.str()); } } } diff --git a/src/blt/logging/logging.cpp b/src/blt/logging/logging.cpp index 971269f..9a8bb1b 100644 --- a/src/blt/logging/logging.cpp +++ b/src/blt/logging/logging.cpp @@ -23,122 +23,147 @@ namespace blt::logging { - struct logging_thread_context_t - { - logger_t logger; - }; + struct logging_thread_context_t + { + logger_t logger; + }; - std::string logger_t::to_string() - { - auto str = m_stream.str(); - m_stream.str(""); - m_stream.clear(); - return str; - } + std::string logger_t::to_string() + { + auto str = m_stream.str(); + m_stream.str(""); + m_stream.clear(); + return str; + } - std::pair> logger_t::handle_fmt(const std::string_view fmt) - { - const auto spec = m_parser.parse(fmt); - if (spec.leading_zeros) - m_stream << std::setfill('0'); - else - m_stream << std::setfill(' '); - if (spec.width > 0) - m_stream << std::setw(static_cast(spec.width)); - else - m_stream << std::setw(0); - if (spec.precision > 0) - m_stream << std::setprecision(static_cast(spec.precision)); - else - m_stream << std::setprecision(2); - if (spec.uppercase) - m_stream << std::uppercase; - else - m_stream << std::nouppercase; - std::optional type; - switch (spec.type) - { - case fmt_type_t::BINARY: - case fmt_type_t::CHAR: - case fmt_type_t::GENERAL: - type = spec.type; - break; - case fmt_type_t::DECIMAL: - m_stream << std::dec; - break; - case fmt_type_t::OCTAL: - m_stream << std::oct; - break; - case fmt_type_t::HEX: - m_stream << std::hex; - break; - case fmt_type_t::HEX_FLOAT: - m_stream << std::hexfloat; - break; - case fmt_type_t::EXPONENT: - m_stream << std::scientific; - break; - case fmt_type_t::FIXED_POINT: - m_stream << std::fixed; - break; - } - return {spec.arg_id, type}; - } + void logger_t::setup_stream(const fmt_spec_t& spec) + { + if (spec.leading_zeros) + m_stream << std::setfill('0'); + else + m_stream << std::setfill(' '); + if (spec.width > 0) + m_stream << std::setw(static_cast(spec.width)); + else + m_stream << std::setw(0); + if (spec.precision > 0) + m_stream << std::setprecision(static_cast(spec.precision)); + else + m_stream << std::setprecision(static_cast(std::cout.precision())); + if (spec.uppercase) + m_stream << std::uppercase; + else + m_stream << std::nouppercase; + } - void logger_t::exponential() - { - m_stream << std::scientific; - } + void logger_t::process_strings() + { + auto spec_it = m_fmt_specs.begin(); + auto str_it = m_string_sections.begin(); + for (; spec_it != m_fmt_specs.end(); ++spec_it, ++str_it) + { + m_stream << *str_it; + auto arg_pos = spec_it->arg_id; + if (arg_pos == -1) + arg_pos = static_cast(m_arg_pos++); - void logger_t::fixed() - { - m_stream << std::fixed; - } + setup_stream(*spec_it); + m_arg_print_funcs[arg_pos](m_stream, *spec_it); + } + m_stream << *str_it; + } - void logger_t::compile(std::string fmt) - { - m_fmt = std::move(fmt); - m_last_fmt_pos = 0; - m_arg_pos = 0; - m_stream.str(""); - m_stream.clear(); - } + void logger_t::handle_type(std::ostream& stream, const fmt_type_t type) + { + switch (type) + { + case fmt_type_t::DECIMAL: + stream << std::dec; + break; + case fmt_type_t::OCTAL: + stream << std::oct; + break; + case fmt_type_t::HEX: + stream << std::hex; + break; + case fmt_type_t::HEX_FLOAT: + stream << std::hexfloat; + break; + case fmt_type_t::EXPONENT: + stream << std::scientific; + break; + case fmt_type_t::FIXED_POINT: + stream << std::fixed; + break; + default: + break; + } + } - std::optional> logger_t::consume_until_fmt() - { - const auto begin = m_fmt.find('{', m_last_fmt_pos); - if (begin == std::string::npos) - return {}; - const auto end = m_fmt.find('}', begin); - if (end == std::string::npos) - { - std::cerr << "Invalid format string, missing closing '}' near " << m_fmt.substr(std::min(static_cast(begin) - 5, 0l)) << std::endl; - std::exit(EXIT_FAILURE); - } - m_stream << std::string_view(m_fmt.data() + static_cast(m_last_fmt_pos), begin - m_last_fmt_pos);\ - m_last_fmt_pos = end + 1; - return std::pair{begin, end}; - } + void logger_t::exponential(std::ostream& stream) + { + stream << std::scientific; + } - void logger_t::finish() - { - m_stream << std::string_view(m_fmt.data() + static_cast(m_last_fmt_pos), m_fmt.size() - m_last_fmt_pos); - m_last_fmt_pos = m_fmt.size(); - } + void logger_t::fixed(std::ostream& stream) + { + stream << std::fixed; + } - logger_t& get_global_logger() - { - thread_local logging_thread_context_t context; - return context.logger; - } + void logger_t::compile(std::string fmt) + { + m_fmt = std::move(fmt); + m_last_fmt_pos = 0; + m_arg_pos = 0; + m_stream.str(""); + m_stream.clear(); + m_string_sections.clear(); + m_fmt_specs.clear(); + ptrdiff_t last_pos = 0; + while (auto pair = consume_to_next_fmt()) + { + const auto [begin, end] = *pair; + m_string_sections.emplace_back(m_fmt.data() + last_pos, begin - last_pos); + if (end - begin > 1) + m_fmt_specs.push_back(m_parser.parse(std::string_view{m_fmt.data() + static_cast(begin) + 1, end - begin - 1})); + else + m_fmt_specs.emplace_back(); + last_pos = static_cast(end) + 1; + } + m_string_sections.emplace_back(m_fmt.data() + last_pos, m_fmt.size() - last_pos); + m_last_fmt_pos = 0; + } - void print(const std::string& fmt) - { - std::cout << fmt; - } + std::optional> logger_t::consume_to_next_fmt() + { + const auto begin = m_fmt.find('{', m_last_fmt_pos); + if (begin == std::string::npos) + return {}; + const auto next_begin = m_fmt.find('{', begin + 1); + const auto end = m_fmt.find('}', begin); + if (end == std::string::npos || (next_begin != std::string::npos && next_begin < end)) + { + std::stringstream ss; + ss << "Invalid format string, missing closing '}' near " << m_fmt.substr(std::min(static_cast(begin) - 5, 0l)); + throw std::runtime_error(ss.str()); + } + m_last_fmt_pos = end + 1; + return std::pair{begin, end}; + } - void newline() - { - std::cout << std::endl; - } + logger_t& get_global_logger() + { + thread_local logging_thread_context_t context; + return context.logger; + } + + void print(const std::string& fmt) + { + std::cout << fmt; + } + + void newline() + { + std::cout << std::endl; + } } diff --git a/tests/logger_tests.cpp b/tests/logger_tests.cpp index 77e8046..a7f4f62 100644 --- a/tests/logger_tests.cpp +++ b/tests/logger_tests.cpp @@ -25,12 +25,16 @@ int main() // blt::logging::println("{} | {} | {} | {}", blt::type_string()); blt::logging::println("This is a println!"); blt::logging::println("This is a println with args '{}'", 42); - blt::logging::println("This is a println with multiple args '{}' '{}' '{}'", 42, 32.34231233, "Hello World!"); + blt::logging::println("This is a println with multiple args '{}' '{:.100}' '{}'", 42, 32.34231233f, "Hello World!"); blt::logging::println("This is a '{1}' fmt string with positionals '{0}'", "I am a string!", "Well so am I except cooler :3"); blt::logging::println("This is a println with a sign {:+}", 4120); + blt::logging::println("This is a println with a sign {:+}", -4120); blt::logging::println("This is a println with a space {: }", 4120); - blt::logging::println("This is a println with a with {:3}", 4120); + blt::logging::println("This is a println with a space {: }", -4120); + blt::logging::println("This is a println with a minus {:-}", 4120); + blt::logging::println("This is a println with a minus {:-}", -4120); + blt::logging::println("This is a println with a with {:10}", 4120); blt::logging::println("This is a println with a with leading zeros {:010}", 4120); - blt::logging::println("This is a println with a precision {:.3}", 42.232342349); + blt::logging::println("This is a println with a precision {:.10f}", 42.232342349); // blt::logging::println("This is println {}\twith a std::endl in the middle of it"); } From 25187319abc4904ddb9b3a4302d5ce54e7f71963 Mon Sep 17 00:00:00 2001 From: Brett Date: Tue, 4 Mar 2025 01:26:33 -0500 Subject: [PATCH 050/101] alternative form --- CMakeLists.txt | 2 +- include/blt/logging/fmt_tokenizer.h | 15 ++- include/blt/logging/logging.h | 9 +- src/blt/logging/fmt_tokenizer.cpp | 167 ++++++++++++++++++---------- src/blt/logging/logging.cpp | 10 +- tests/logger_tests.cpp | 2 + 6 files changed, 133 insertions(+), 72 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d876b96..e89680c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.1.3) +set(BLT_VERSION 5.1.4) set(BLT_TARGET BLT) diff --git a/include/blt/logging/fmt_tokenizer.h b/include/blt/logging/fmt_tokenizer.h index ce436aa..b34724b 100644 --- a/include/blt/logging/fmt_tokenizer.h +++ b/include/blt/logging/fmt_tokenizer.h @@ -34,7 +34,8 @@ namespace blt::logging COLON, DOT, MINUS, - PLUS + PLUS, + POUND }; enum class fmt_sign_t : u8 @@ -66,6 +67,7 @@ namespace blt::logging fmt_sign_t sign = fmt_sign_t::MINUS; bool leading_zeros = false; bool uppercase = false; + bool alternate_form = false; }; struct fmt_token_t @@ -121,10 +123,15 @@ namespace blt::logging private: void parse_fmt_field(); void parse_arg_id(); - void parse_fmt_spec_stage_1(); - void parse_fmt_spec_stage_2(); - void parse_fmt_spec_stage_3(); + + void parse_fmt_spec(); + void parse_fmt_spec_sign(); + void parse_fmt_spec_form(); + void parse_fmt_spec_width(); + void parse_fmt_spec_precision(); + void parse_sign(); + void parse_form(); void parse_width(); void parse_precision(); void parse_type(); diff --git a/include/blt/logging/logging.h b/include/blt/logging/logging.h index c3cf7b2..c419895 100644 --- a/include/blt/logging/logging.h +++ b/include/blt/logging/logging.h @@ -68,7 +68,7 @@ namespace blt::logging case fmt_sign_t::SPACE: if constexpr (std::is_arithmetic_v) { - if (t >= 0) + if (type.type != fmt_type_t::BINARY && t >= 0) stream << ' '; } break; @@ -90,7 +90,8 @@ namespace blt::logging { char buffer[sizeof(T)]; std::memcpy(buffer, &t, sizeof(T)); - stream << '0' << (type.uppercase ? 'B' : 'b'); + if (type.alternate_form) + stream << '0' << (type.uppercase ? 'B' : 'b'); for (size_t i = 0; i < sizeof(T); ++i) { for (size_t j = 0; j < 8; ++j) @@ -127,7 +128,7 @@ namespace blt::logging } break; default: - handle_type(stream, type.type); + handle_type(stream, type); stream << t; } }; @@ -135,7 +136,7 @@ namespace blt::logging void setup_stream(const fmt_spec_t& spec); void process_strings(); - static void handle_type(std::ostream& stream, fmt_type_t type); + static void handle_type(std::ostream& stream, const fmt_spec_t& spec); static void exponential(std::ostream& stream); static void fixed(std::ostream& stream); diff --git a/src/blt/logging/fmt_tokenizer.cpp b/src/blt/logging/fmt_tokenizer.cpp index bfb6426..a57f553 100644 --- a/src/blt/logging/fmt_tokenizer.cpp +++ b/src/blt/logging/fmt_tokenizer.cpp @@ -46,6 +46,8 @@ namespace blt::logging return fmt_token_type::COLON; case ' ': return fmt_token_type::SPACE; + case '#': + return fmt_token_type::POUND; default: return fmt_token_type::STRING; } @@ -62,6 +64,7 @@ namespace blt::logging case fmt_token_type::MINUS: case fmt_token_type::DOT: case fmt_token_type::COLON: + case fmt_token_type::POUND: return fmt_token_t{base_type, std::string_view{m_fmt.data() + m_pos++, 1}}; default: { @@ -100,30 +103,32 @@ namespace blt::logging if (!has_next()) throw std::runtime_error("Expected token when parsing format field"); const auto [type, value] = peek(); - if (type == fmt_token_type::COLON) - { - consume(); - parse_fmt_spec_stage_1(); - } - else if (type == fmt_token_type::NUMBER) + switch (type) { + case fmt_token_type::NUMBER: parse_arg_id(); if (has_next()) { if (peek().type == fmt_token_type::COLON) - { - consume(); - parse_fmt_spec_stage_1(); - } + parse_fmt_spec(); else throw std::runtime_error("Expected ':' when parsing format field after arg id!"); } - } - else - { - std::stringstream ss; - ss << "Expected unknown token '" << static_cast(type) << "' value '" << value << "' when parsing format field"; - throw std::runtime_error(ss.str()); + break; + case fmt_token_type::COLON: + parse_fmt_spec(); + break; + case fmt_token_type::STRING: + case fmt_token_type::SPACE: + case fmt_token_type::DOT: + case fmt_token_type::MINUS: + case fmt_token_type::PLUS: + case fmt_token_type::POUND: + { + std::stringstream ss; + ss << "Expected unknown token '" << static_cast(type) << "' value '" << value << "' when parsing format field"; + throw std::runtime_error(ss.str()); + } } if (has_next()) parse_type(); @@ -131,8 +136,6 @@ namespace blt::logging void fmt_parser_t::parse_arg_id() { - if (!has_next()) - throw std::runtime_error("Missing token when parsing arg id"); const auto [type, value] = next(); if (type != fmt_token_type::NUMBER) { @@ -143,11 +146,10 @@ namespace blt::logging m_spec.arg_id = std::stoll(std::string(value)); } - // handle start of fmt, with sign - void fmt_parser_t::parse_fmt_spec_stage_1() + void fmt_parser_t::parse_fmt_spec() { - if (!has_next()) - return; + // consume : + consume(); auto [type, value] = peek(); switch (type) { @@ -155,29 +157,89 @@ namespace blt::logging case fmt_token_type::COLON: { std::stringstream ss; - ss << "(Stage 1) Invalid token type " << static_cast(type) << " value " << value; + ss << "(Stage (Begin)) Invalid token type " << static_cast(type) << " value " << value; throw std::runtime_error(ss.str()); } case fmt_token_type::NUMBER: - parse_width(); - parse_fmt_spec_stage_3(); + parse_fmt_spec_width(); break; case fmt_token_type::DOT: - consume(); - parse_precision(); + parse_fmt_spec_precision(); break; case fmt_token_type::SPACE: case fmt_token_type::MINUS: case fmt_token_type::PLUS: - parse_sign(); - parse_fmt_spec_stage_2(); + parse_fmt_spec_sign(); + break; + case fmt_token_type::POUND: + parse_fmt_spec_form(); + break; + } + } + + // handle start of fmt, with sign + void fmt_parser_t::parse_fmt_spec_sign() + { + parse_sign(); + if (!has_next()) + return; + auto [type, value] = peek(); + switch (type) + { + case fmt_token_type::SPACE: + case fmt_token_type::MINUS: + case fmt_token_type::PLUS: + case fmt_token_type::STRING: + case fmt_token_type::COLON: + { + std::stringstream ss; + ss << "(Stage (Sign)) Invalid token type " << static_cast(type) << " value " << value; + throw std::runtime_error(ss.str()); + } + case fmt_token_type::NUMBER: + parse_fmt_spec_width(); + break; + case fmt_token_type::DOT: + parse_fmt_spec_precision(); + break; + case fmt_token_type::POUND: + parse_fmt_spec_form(); + break; + } + } + + void fmt_parser_t::parse_fmt_spec_form() + { + parse_form(); + if (!has_next()) + return; + auto [type, value] = peek(); + switch (type) + { + case fmt_token_type::STRING: + case fmt_token_type::SPACE: + case fmt_token_type::COLON: + case fmt_token_type::MINUS: + case fmt_token_type::PLUS: + case fmt_token_type::POUND: + { + std::stringstream ss; + ss << "(Stage (Form)) Invalid token type " << static_cast(type) << " value " << value; + throw std::runtime_error(ss.str()); + } + case fmt_token_type::NUMBER: + parse_fmt_spec_width(); + break; + case fmt_token_type::DOT: + parse_fmt_spec_precision(); break; } } // handle width parsing - void fmt_parser_t::parse_fmt_spec_stage_2() + void fmt_parser_t::parse_fmt_spec_width() { + parse_width(); if (!has_next()) return; auto [type, value] = peek(); @@ -188,45 +250,24 @@ namespace blt::logging case fmt_token_type::MINUS: case fmt_token_type::PLUS: case fmt_token_type::SPACE: + case fmt_token_type::POUND: + case fmt_token_type::NUMBER: { std::stringstream ss; - ss << "(Stage 2) Invalid token type " << static_cast(type) << " value " << value; + ss << "(Stage (Width)) Invalid token type " << static_cast(type) << " value " << value; throw std::runtime_error(ss.str()); } - case fmt_token_type::NUMBER: - parse_width(); - parse_fmt_spec_stage_3(); - break; case fmt_token_type::DOT: - consume(); - parse_precision(); + parse_fmt_spec_precision(); break; } } - void fmt_parser_t::parse_fmt_spec_stage_3() + void fmt_parser_t::parse_fmt_spec_precision() { - if (!has_next()) - return; - auto [type, value] = peek(); - switch (type) - { - case fmt_token_type::STRING: - case fmt_token_type::COLON: - case fmt_token_type::MINUS: - case fmt_token_type::PLUS: - case fmt_token_type::SPACE: - case fmt_token_type::NUMBER: - { - std::stringstream ss; - ss << "(Stage 3) Invalid token type " << static_cast(type) << " value " << value; - throw std::runtime_error(ss.str()); - } - case fmt_token_type::DOT: - consume(); - parse_precision(); - break; - } + // consume . + consume(); + parse_precision(); } void fmt_parser_t::parse_sign() @@ -258,6 +299,12 @@ namespace blt::logging } } + void fmt_parser_t::parse_form() + { + consume(); + m_spec.alternate_form = true; + } + void fmt_parser_t::parse_width() { auto [_, value] = next(); @@ -276,8 +323,6 @@ namespace blt::logging void fmt_parser_t::parse_type() { - if (!has_next()) - throw std::runtime_error("Missing token when parsing type"); auto [_, value] = next(); if (value.size() != 1) { diff --git a/src/blt/logging/logging.cpp b/src/blt/logging/logging.cpp index 9a8bb1b..94314c2 100644 --- a/src/blt/logging/logging.cpp +++ b/src/blt/logging/logging.cpp @@ -73,20 +73,26 @@ namespace blt::logging m_stream << *str_it; } - void logger_t::handle_type(std::ostream& stream, const fmt_type_t type) + void logger_t::handle_type(std::ostream& stream, const fmt_spec_t& spec) { - switch (type) + switch (spec.type) { case fmt_type_t::DECIMAL: stream << std::dec; break; case fmt_type_t::OCTAL: + if (spec.alternate_form) + stream << "0"; stream << std::oct; break; case fmt_type_t::HEX: + if (spec.alternate_form) + stream << (spec.uppercase ? "0X" : "0x"); stream << std::hex; break; case fmt_type_t::HEX_FLOAT: + if (spec.alternate_form) + stream << (spec.uppercase ? "0X" : "0x"); stream << std::hexfloat; break; case fmt_type_t::EXPONENT: diff --git a/tests/logger_tests.cpp b/tests/logger_tests.cpp index a7f4f62..d28a4e9 100644 --- a/tests/logger_tests.cpp +++ b/tests/logger_tests.cpp @@ -36,5 +36,7 @@ int main() blt::logging::println("This is a println with a with {:10}", 4120); blt::logging::println("This is a println with a with leading zeros {:010}", 4120); blt::logging::println("This is a println with a precision {:.10f}", 42.232342349); + blt::logging::println("This is a println with hex {:.10x}", 4250); + blt::logging::println("This is a println with hex with leading {:#.10x}", 4250); // blt::logging::println("This is println {}\twith a std::endl in the middle of it"); } From a2a60b18b5f234f67afd7cef1cd11cc75248e0cd Mon Sep 17 00:00:00 2001 From: Brett Date: Tue, 4 Mar 2025 11:05:59 -0500 Subject: [PATCH 051/101] fix issue in parser --- CMakeLists.txt | 2 +- src/blt/logging/fmt_tokenizer.cpp | 9 +++++++-- tests/logger_tests.cpp | 2 ++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e89680c..434733e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.1.4) +set(BLT_VERSION 5.1.5) set(BLT_TARGET BLT) diff --git a/src/blt/logging/fmt_tokenizer.cpp b/src/blt/logging/fmt_tokenizer.cpp index a57f553..3bdf7e4 100644 --- a/src/blt/logging/fmt_tokenizer.cpp +++ b/src/blt/logging/fmt_tokenizer.cpp @@ -186,10 +186,11 @@ namespace blt::logging auto [type, value] = peek(); switch (type) { + case fmt_token_type::STRING: + return; case fmt_token_type::SPACE: case fmt_token_type::MINUS: case fmt_token_type::PLUS: - case fmt_token_type::STRING: case fmt_token_type::COLON: { std::stringstream ss; @@ -217,6 +218,7 @@ namespace blt::logging switch (type) { case fmt_token_type::STRING: + return; case fmt_token_type::SPACE: case fmt_token_type::COLON: case fmt_token_type::MINUS: @@ -246,6 +248,7 @@ namespace blt::logging switch (type) { case fmt_token_type::STRING: + return; case fmt_token_type::COLON: case fmt_token_type::MINUS: case fmt_token_type::PLUS: @@ -317,7 +320,9 @@ namespace blt::logging { if (!has_next()) throw std::runtime_error("Missing token when parsing precision"); - auto [_, value] = next(); + auto [type, value] = next(); + if (type != fmt_token_type::NUMBER) + throw std::runtime_error("Expected number when parsing precision"); m_spec.precision = std::stoll(std::string(value)); } diff --git a/tests/logger_tests.cpp b/tests/logger_tests.cpp index d28a4e9..d7729e7 100644 --- a/tests/logger_tests.cpp +++ b/tests/logger_tests.cpp @@ -38,5 +38,7 @@ int main() blt::logging::println("This is a println with a precision {:.10f}", 42.232342349); blt::logging::println("This is a println with hex {:.10x}", 4250); blt::logging::println("This is a println with hex with leading {:#.10x}", 4250); + blt::logging::println("This is a println with binary {:#b}", 6969420); + blt::logging::println("This is a println with binary with space {: #b}", 6969421); // blt::logging::println("This is println {}\twith a std::endl in the middle of it"); } From a1c1f51cb48702cfd723c02924b83d3c29d094dc Mon Sep 17 00:00:00 2001 From: Brett Date: Tue, 4 Mar 2025 11:17:05 -0500 Subject: [PATCH 052/101] parser fix again --- CMakeLists.txt | 2 +- include/blt/logging/logging.h | 5 +++++ src/blt/logging/fmt_tokenizer.cpp | 1 + tests/logger_tests.cpp | 5 ++--- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 434733e..07c110e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.1.5) +set(BLT_VERSION 5.1.6) set(BLT_TARGET BLT) diff --git a/include/blt/logging/logging.h b/include/blt/logging/logging.h index c419895..82649f9 100644 --- a/include/blt/logging/logging.h +++ b/include/blt/logging/logging.h @@ -88,14 +88,19 @@ namespace blt::logging { if constexpr (std::is_trivially_copyable_v) { + // copy bytes of type char buffer[sizeof(T)]; std::memcpy(buffer, &t, sizeof(T)); + // display prefix if (type.alternate_form) stream << '0' << (type.uppercase ? 'B' : 'b'); + // print bytes for (size_t i = 0; i < sizeof(T); ++i) { + // print bits for (size_t j = 0; j < 8; ++j) stream << ((buffer[i] & (1 << j)) ? '1' : '0'); + // special seperator defined via sign (weird hack, change?) if (type.sign == fmt_sign_t::SPACE && i != sizeof(T) - 1) stream << ' '; } diff --git a/src/blt/logging/fmt_tokenizer.cpp b/src/blt/logging/fmt_tokenizer.cpp index 3bdf7e4..abdfcd4 100644 --- a/src/blt/logging/fmt_tokenizer.cpp +++ b/src/blt/logging/fmt_tokenizer.cpp @@ -154,6 +154,7 @@ namespace blt::logging switch (type) { case fmt_token_type::STRING: + return; case fmt_token_type::COLON: { std::stringstream ss; diff --git a/tests/logger_tests.cpp b/tests/logger_tests.cpp index d7729e7..ca342d6 100644 --- a/tests/logger_tests.cpp +++ b/tests/logger_tests.cpp @@ -20,9 +20,6 @@ int main() { - - using endl_t = decltype(static_cast(std::endl)); - // blt::logging::println("{} | {} | {} | {}", blt::type_string()); blt::logging::println("This is a println!"); blt::logging::println("This is a println with args '{}'", 42); blt::logging::println("This is a println with multiple args '{}' '{:.100}' '{}'", 42, 32.34231233f, "Hello World!"); @@ -40,5 +37,7 @@ int main() blt::logging::println("This is a println with hex with leading {:#.10x}", 4250); blt::logging::println("This is a println with binary {:#b}", 6969420); blt::logging::println("This is a println with binary with space {: #b}", 6969421); + blt::logging::println("This is a println with octal {:#o}", 6669); + blt::logging::println("This is a println with hexfloat {:a}", 402.4320); // blt::logging::println("This is println {}\twith a std::endl in the middle of it"); } From 4e6863aafabaef2691df2a4495f2dc2aad1e3581 Mon Sep 17 00:00:00 2001 From: Brett Date: Tue, 4 Mar 2025 11:35:23 -0500 Subject: [PATCH 053/101] streamable testing --- CMakeLists.txt | 2 +- include/blt/logging/fmt_tokenizer.h | 3 +- include/blt/logging/logging.h | 68 +++++++++++++++++++---------- include/blt/meta/is_streamable.h | 49 +++++++++++++++++++++ include/blt/meta/meta.h | 25 +---------- src/blt/logging/fmt_tokenizer.cpp | 4 ++ tests/logger_tests.cpp | 10 +++++ 7 files changed, 111 insertions(+), 50 deletions(-) create mode 100644 include/blt/meta/is_streamable.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 07c110e..14e8a8a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.1.6) +set(BLT_VERSION 5.1.7) set(BLT_TARGET BLT) diff --git a/include/blt/logging/fmt_tokenizer.h b/include/blt/logging/fmt_tokenizer.h index b34724b..2a81740 100644 --- a/include/blt/logging/fmt_tokenizer.h +++ b/include/blt/logging/fmt_tokenizer.h @@ -55,7 +55,8 @@ namespace blt::logging HEX_FLOAT, // 'a' EXPONENT, // 'e' FIXED_POINT, // 'f' - GENERAL // 'g' + GENERAL, // 'g' + TYPE // 't' }; struct fmt_spec_t diff --git a/include/blt/logging/logging.h b/include/blt/logging/logging.h index 82649f9..6e1192f 100644 --- a/include/blt/logging/logging.h +++ b/include/blt/logging/logging.h @@ -26,6 +26,8 @@ #include #include #include +#include +#include namespace blt::logging { @@ -65,22 +67,22 @@ namespace blt::logging { switch (type.sign) { - case fmt_sign_t::SPACE: - if constexpr (std::is_arithmetic_v) - { - if (type.type != fmt_type_t::BINARY && t >= 0) - stream << ' '; - } - break; - case fmt_sign_t::PLUS: - if constexpr (std::is_arithmetic_v) - { - if (t >= 0) - stream << '+'; - } - break; - case fmt_sign_t::MINUS: - break; + case fmt_sign_t::SPACE: + if constexpr (std::is_arithmetic_v) + { + if (type.type != fmt_type_t::BINARY && t >= 0) + stream << ' '; + } + break; + case fmt_sign_t::PLUS: + if constexpr (std::is_arithmetic_v) + { + if (t >= 0) + stream << '+'; + } + break; + case fmt_sign_t::MINUS: + break; } switch (type.type) { @@ -104,9 +106,13 @@ namespace blt::logging if (type.sign == fmt_sign_t::SPACE && i != sizeof(T) - 1) stream << ' '; } - } else + } + else { - stream << t; + if constexpr (blt::meta::is_streamable_v) + stream << t; + else + stream << "{INVALID TYPE}"; } break; } @@ -114,9 +120,13 @@ namespace blt::logging if constexpr (std::is_arithmetic_v || std::is_convertible_v) { stream << static_cast(t); - } else + } + else { - stream << t; + if constexpr (blt::meta::is_streamable_v) + stream << t; + else + stream << "{INVALID TYPE}"; } break; case fmt_type_t::GENERAL: @@ -127,14 +137,24 @@ namespace blt::logging else fixed(stream); stream << t; - } else - { - stream << t; } + else + { + if constexpr (blt::meta::is_streamable_v) + stream << t; + else + stream << "{INVALID TYPE}"; + } + break; + case fmt_type_t::TYPE: + stream << blt::type_string(); break; default: handle_type(stream, type); - stream << t; + if constexpr (blt::meta::is_streamable_v) + stream << t; + else + stream << "{INVALID TYPE}"; } }; } diff --git a/include/blt/meta/is_streamable.h b/include/blt/meta/is_streamable.h new file mode 100644 index 0000000..27756a0 --- /dev/null +++ b/include/blt/meta/is_streamable.h @@ -0,0 +1,49 @@ +#pragma once +/* + * Copyright (C) 2024 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BLT_META_IS_STREAMABLE_H +#define BLT_META_IS_STREAMABLE_H + +namespace blt::meta +{ + // https://stackoverflow.com/questions/66397071/is-it-possible-to-check-if-overloaded-operator-for-type-or-class-exists + template + class is_streamable + { + private: + template + static auto test(int) -> decltype(std::declval() << std::declval(), std::true_type()) + { + return std::declval(); + } + + template + static auto test(...) -> std::false_type + { + return std::declval(); + } + + public: + static constexpr bool value = decltype(test(0))::value; + }; + + template + inline constexpr bool is_streamable_v = is_streamable::value; +} + +#endif //BLT_META_IS_STREAMABLE_H diff --git a/include/blt/meta/meta.h b/include/blt/meta/meta.h index 97b2d55..82c74e7 100644 --- a/include/blt/meta/meta.h +++ b/include/blt/meta/meta.h @@ -23,6 +23,7 @@ #include #include #include +#include namespace blt::meta { @@ -71,30 +72,6 @@ namespace blt::meta template lambda_helper(Lambda) -> lambda_helper; - // https://stackoverflow.com/questions/66397071/is-it-possible-to-check-if-overloaded-operator-for-type-or-class-exists - template - class is_streamable - { - private: - template - static auto test(int) -> decltype(std::declval() << std::declval(), std::true_type()) - { - return std::declval(); - } - - template - static auto test(...) -> std::false_type - { - return std::declval(); - } - - public: - static constexpr bool value = decltype(test(0))::value; - }; - - template - inline constexpr bool is_streamable_v = is_streamable::value; - template struct arrow_return { diff --git a/src/blt/logging/fmt_tokenizer.cpp b/src/blt/logging/fmt_tokenizer.cpp index abdfcd4..05ea4b0 100644 --- a/src/blt/logging/fmt_tokenizer.cpp +++ b/src/blt/logging/fmt_tokenizer.cpp @@ -372,6 +372,10 @@ namespace blt::logging case 'G': m_spec.type = fmt_type_t::GENERAL; break; + case 't': + case 'T': + m_spec.type = fmt_type_t::TYPE; + break; default: std::stringstream ss; ss << "Invalid type " << value; diff --git a/tests/logger_tests.cpp b/tests/logger_tests.cpp index ca342d6..906e835 100644 --- a/tests/logger_tests.cpp +++ b/tests/logger_tests.cpp @@ -18,6 +18,10 @@ #include #include +struct some_silly_type_t +{ +}; + int main() { blt::logging::println("This is a println!"); @@ -39,5 +43,11 @@ int main() blt::logging::println("This is a println with binary with space {: #b}", 6969421); blt::logging::println("This is a println with octal {:#o}", 6669); blt::logging::println("This is a println with hexfloat {:a}", 402.4320); + blt::logging::println("This is a println with exponent {:e}", 44320902.4320); + blt::logging::println("This is a println with exponent {:e}", 9532434234042340); + blt::logging::println("This is a println with exponent {:g}", 953243.49); + blt::logging::println("This is a println with exponent {:g}", 953243324023403240.49); + blt::logging::println("This is a println with a char {:c}", 66); + blt::logging::println("This is a println with type {:t}", some_silly_type_t{}); // blt::logging::println("This is println {}\twith a std::endl in the middle of it"); } From 637b4fa0e6b1dc2ac714cad43e9381c3455b6964 Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Tue, 4 Mar 2025 13:58:14 -0500 Subject: [PATCH 054/101] nice error messages --- CMakeLists.txt | 2 +- include/blt/logging/fmt_tokenizer.h | 5 +++-- include/blt/logging/logging.h | 25 +------------------------ libraries/parallel-hashmap | 2 +- src/blt/logging/fmt_tokenizer.cpp | 18 ++++++++++++++++++ src/blt/logging/logging.cpp | 22 +++++++++++++++------- tests/logger_tests.cpp | 12 +++++++++--- 7 files changed, 48 insertions(+), 38 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 14e8a8a..4f359be 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.1.7) +set(BLT_VERSION 5.1.8) set(BLT_TARGET BLT) diff --git a/include/blt/logging/fmt_tokenizer.h b/include/blt/logging/fmt_tokenizer.h index 2a81740..6e95c18 100644 --- a/include/blt/logging/fmt_tokenizer.h +++ b/include/blt/logging/fmt_tokenizer.h @@ -56,7 +56,8 @@ namespace blt::logging EXPONENT, // 'e' FIXED_POINT, // 'f' GENERAL, // 'g' - TYPE // 't' + TYPE, // 't' + UNSPECIFIED // default }; struct fmt_spec_t @@ -64,7 +65,7 @@ namespace blt::logging i64 arg_id = -1; i64 width = -1; i64 precision = -1; - fmt_type_t type = fmt_type_t::DECIMAL; + fmt_type_t type = fmt_type_t::UNSPECIFIED; fmt_sign_t sign = fmt_sign_t::MINUS; bool leading_zeros = false; bool uppercase = false; diff --git a/include/blt/logging/logging.h b/include/blt/logging/logging.h index 6e1192f..b696550 100644 --- a/include/blt/logging/logging.h +++ b/include/blt/logging/logging.h @@ -70,17 +70,11 @@ namespace blt::logging case fmt_sign_t::SPACE: if constexpr (std::is_arithmetic_v) { - if (type.type != fmt_type_t::BINARY && t >= 0) + if (type.type != fmt_type_t::BINARY && static_cast(t) >= 0l) stream << ' '; } break; case fmt_sign_t::PLUS: - if constexpr (std::is_arithmetic_v) - { - if (t >= 0) - stream << '+'; - } - break; case fmt_sign_t::MINUS: break; } @@ -129,23 +123,6 @@ namespace blt::logging stream << "{INVALID TYPE}"; } break; - case fmt_type_t::GENERAL: - if constexpr (std::is_arithmetic_v) - { - if (static_cast(t) > 10e12) - exponential(stream); - else - fixed(stream); - stream << t; - } - else - { - if constexpr (blt::meta::is_streamable_v) - stream << t; - else - stream << "{INVALID TYPE}"; - } - break; case fmt_type_t::TYPE: stream << blt::type_string(); break; diff --git a/libraries/parallel-hashmap b/libraries/parallel-hashmap index 93201da..7ef2e73 160000 --- a/libraries/parallel-hashmap +++ b/libraries/parallel-hashmap @@ -1 +1 @@ -Subproject commit 93201da2ba5a6aba0a6e57ada64973555629b3e3 +Subproject commit 7ef2e733416953b222851f9a360d7fc72d068ee5 diff --git a/src/blt/logging/fmt_tokenizer.cpp b/src/blt/logging/fmt_tokenizer.cpp index 05ea4b0..dc434ef 100644 --- a/src/blt/logging/fmt_tokenizer.cpp +++ b/src/blt/logging/fmt_tokenizer.cpp @@ -15,6 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +#include #include #include #include @@ -379,6 +380,23 @@ namespace blt::logging default: std::stringstream ss; ss << "Invalid type " << value; + ss << std::endl << std::endl; + ss << "Expected one of: " << std::endl; + ss << std::setw(4) << ' ' << std::left << std::setw(5) << "b | B" << std::setw(6) << ' ' << "Print as binary output" << std::endl; + ss << std::setw(4) << ' ' << std::left << std::setw(5) << "c" << std::setw(6) << ' ' << "Print as character output" << std::endl; + ss << std::setw(4) << ' ' << std::left << std::setw(5) << "d" << std::setw(6) << ' ' << "Print as decimal (base 10) output" << std::endl; + ss << std::setw(4) << ' ' << std::left << std::setw(5) << "o" << std::setw(6) << ' ' << "Print as octal (base 8) output" << std::endl; + ss << std::setw(4) << ' ' << std::left << std::setw(5) << "x | X" << std::setw(6) << ' ' << "Print as hexadecimal (base 16) output" << + std::endl; + ss << std::setw(4) << ' ' << std::left << std::setw(5) << "a | A" << std::setw(6) << ' ' << "Print floats as hexadecimal (base 16) output" + << std::endl; + ss << std::setw(4) << ' ' << std::left << std::setw(5) << "e | E" << std::setw(6) << ' ' << "Print floats in scientific notation" << + std::endl; + ss << std::setw(4) << ' ' << std::left << std::setw(5) << "f | F" << std::setw(6) << ' ' << + "Print floats in fixed point output, useful for setting precision of output" << std::endl; + ss << std::setw(4) << ' ' << std::left << std::setw(5) << "g | G" << std::setw(6) << ' ' << + "Print floats in general output, switching between fixed point and scientific notation based on magnitude" << std::endl; + ss << std::setw(4) << ' ' << std::left << std::setw(5) << "t | T" << std::setw(6) << ' ' << "Print the type as a string" << std::endl; throw std::runtime_error(ss.str()); } } diff --git a/src/blt/logging/logging.cpp b/src/blt/logging/logging.cpp index 94314c2..c480b7e 100644 --- a/src/blt/logging/logging.cpp +++ b/src/blt/logging/logging.cpp @@ -49,11 +49,17 @@ namespace blt::logging if (spec.precision > 0) m_stream << std::setprecision(static_cast(spec.precision)); else - m_stream << std::setprecision(static_cast(std::cout.precision())); + m_stream << std::setprecision(16); + if (spec.alternate_form) + m_stream << std::showbase; if (spec.uppercase) m_stream << std::uppercase; else m_stream << std::nouppercase; + if (spec.sign == fmt_sign_t::PLUS) + m_stream << std::showpos; + else + m_stream << std::noshowpos; } void logger_t::process_strings() @@ -78,21 +84,16 @@ namespace blt::logging switch (spec.type) { case fmt_type_t::DECIMAL: + stream << std::noboolalpha; stream << std::dec; break; case fmt_type_t::OCTAL: - if (spec.alternate_form) - stream << "0"; stream << std::oct; break; case fmt_type_t::HEX: - if (spec.alternate_form) - stream << (spec.uppercase ? "0X" : "0x"); stream << std::hex; break; case fmt_type_t::HEX_FLOAT: - if (spec.alternate_form) - stream << (spec.uppercase ? "0X" : "0x"); stream << std::hexfloat; break; case fmt_type_t::EXPONENT: @@ -101,6 +102,13 @@ namespace blt::logging case fmt_type_t::FIXED_POINT: stream << std::fixed; break; + case fmt_type_t::GENERAL: + stream << std::defaultfloat; + break; + case fmt_type_t::UNSPECIFIED: + stream << std::boolalpha; + stream << std::dec; + break; default: break; } diff --git a/tests/logger_tests.cpp b/tests/logger_tests.cpp index 906e835..c4d7bc5 100644 --- a/tests/logger_tests.cpp +++ b/tests/logger_tests.cpp @@ -41,13 +41,19 @@ int main() blt::logging::println("This is a println with hex with leading {:#.10x}", 4250); blt::logging::println("This is a println with binary {:#b}", 6969420); blt::logging::println("This is a println with binary with space {: #b}", 6969421); + blt::logging::println("This is a println with binary with space {: b}", 69); blt::logging::println("This is a println with octal {:#o}", 6669); blt::logging::println("This is a println with hexfloat {:a}", 402.4320); blt::logging::println("This is a println with exponent {:e}", 44320902.4320); - blt::logging::println("This is a println with exponent {:e}", 9532434234042340); - blt::logging::println("This is a println with exponent {:g}", 953243.49); - blt::logging::println("This is a println with exponent {:g}", 953243324023403240.49); + blt::logging::println("This is a println with exponent {:e}", 9532434234042340.0); + blt::logging::println("This is a println with general {:g}", 953243.49); + blt::logging::println("This is a println with general {:g}", 953243324023403240.49); blt::logging::println("This is a println with a char {:c}", 66); blt::logging::println("This is a println with type {:t}", some_silly_type_t{}); + blt::logging::println("This is a println with boolean {}", true); + blt::logging::println("This is a println with boolean as int {:d}", false); + blt::logging::println("This is a println with boolean as hex {:#x}", true); + blt::logging::println("This is a println with boolean as octal {:o}", true); + blt::logging::println("This is a println with boolean as test {:h}", true); // blt::logging::println("This is println {}\twith a std::endl in the middle of it"); } From 1e30544cff64f87e8ebe76c7ccea194f43425830 Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Tue, 4 Mar 2025 16:17:16 -0500 Subject: [PATCH 055/101] print statements --- CMakeLists.txt | 2 +- include/blt/logging/fmt_tokenizer.h | 34 +++++++- include/blt/logging/logging.h | 27 ++++-- include/blt/meta/is_streamable.h | 2 + src/blt/logging/fmt_tokenizer.cpp | 119 +++++++++++++++++++++++-- src/blt/logging/logging.cpp | 38 +++++--- tests/logger_tests.cpp | 129 +++++++++++++++++++++------- 7 files changed, 291 insertions(+), 60 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4f359be..54387ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.1.8) +set(BLT_VERSION 5.1.9) set(BLT_TARGET BLT) diff --git a/include/blt/logging/fmt_tokenizer.h b/include/blt/logging/fmt_tokenizer.h index 6e95c18..8f1cffe 100644 --- a/include/blt/logging/fmt_tokenizer.h +++ b/include/blt/logging/fmt_tokenizer.h @@ -35,7 +35,17 @@ namespace blt::logging DOT, MINUS, PLUS, - POUND + POUND, + LEFT_CHEVRON, + RIGHT_CHEVRON, + CARET + }; + + enum class fmt_align_t : u8 + { + LEFT, + CENTER, + RIGHT }; enum class fmt_sign_t : u8 @@ -67,7 +77,8 @@ namespace blt::logging i64 precision = -1; fmt_type_t type = fmt_type_t::UNSPECIFIED; fmt_sign_t sign = fmt_sign_t::MINUS; - bool leading_zeros = false; + fmt_align_t alignment = fmt_align_t::RIGHT; + std::optional prefix_char; bool uppercase = false; bool alternate_form = false; }; @@ -100,6 +111,11 @@ namespace blt::logging public: explicit fmt_parser_t() = default; + fmt_token_t& peek(const size_t offset) + { + return m_tokens[m_pos + offset]; + } + fmt_token_t& peek() { return m_tokens[m_pos]; @@ -110,6 +126,11 @@ namespace blt::logging return m_pos < m_tokens.size(); } + [[nodiscard]] bool has_next(const size_t offset) const + { + return (m_pos + offset) < m_tokens.size(); + } + [[nodiscard]] fmt_token_t& next() { return m_tokens[m_pos++]; @@ -120,18 +141,27 @@ namespace blt::logging ++m_pos; } + void consume(const size_t amount) + { + m_pos += amount; + } + const fmt_spec_t& parse(std::string_view fmt); private: + static bool is_align_t(fmt_token_type type); + void parse_fmt_field(); void parse_arg_id(); void parse_fmt_spec(); + void parse_fmt_spec_align(); void parse_fmt_spec_sign(); void parse_fmt_spec_form(); void parse_fmt_spec_width(); void parse_fmt_spec_precision(); + void parse_align(); void parse_sign(); void parse_form(); void parse_width(); diff --git a/include/blt/logging/logging.h b/include/blt/logging/logging.h index b696550..91c577f 100644 --- a/include/blt/logging/logging.h +++ b/include/blt/logging/logging.h @@ -19,7 +19,6 @@ #ifndef BLT_LOGGING_LOGGING_H #define BLT_LOGGING_LOGGING_H -#include #include #include #include @@ -37,7 +36,9 @@ namespace blt::logging struct logger_t { - explicit logger_t() = default; + explicit logger_t(std::ostream& stream): m_stream(stream) + { + } template std::string log(std::string fmt, Args&&... args) @@ -51,7 +52,7 @@ namespace blt::logging return to_string(); } - std::string to_string(); + [[nodiscard]] std::string to_string() const; private: template @@ -136,7 +137,7 @@ namespace blt::logging }; } - void setup_stream(const fmt_spec_t& spec); + void setup_stream(const fmt_spec_t& spec) const; void process_strings(); static void handle_type(std::ostream& stream, const fmt_spec_t& spec); @@ -148,7 +149,7 @@ namespace blt::logging std::optional> consume_to_next_fmt(); std::string m_fmt; - std::stringstream m_stream; + std::ostream& m_stream; fmt_parser_t m_parser; // normal sections of string std::vector m_string_sections; @@ -159,7 +160,7 @@ namespace blt::logging size_t m_arg_pos = 0; }; - void print(const std::string& fmt); + void print(const std::string& str); void newline(); @@ -172,12 +173,26 @@ namespace blt::logging print(logger.log(std::move(fmt), std::forward(args)...)); } + template + void print(std::ostream& stream, std::string fmt, Args&&... args) + { + auto& logger = get_global_logger(); + stream << logger.log(std::move(fmt), std::forward(args)...); + } + template void println(std::string fmt, Args&&... args) { print(std::move(fmt), std::forward(args)...); newline(); } + + template + void println(std::ostream& stream, std::string fmt, Args&&... args) + { + print(stream, std::move(fmt), std::forward(args)...); + stream << std::endl; + } } #endif // BLT_LOGGING_LOGGING_H diff --git a/include/blt/meta/is_streamable.h b/include/blt/meta/is_streamable.h index 27756a0..58bea5a 100644 --- a/include/blt/meta/is_streamable.h +++ b/include/blt/meta/is_streamable.h @@ -19,6 +19,8 @@ #ifndef BLT_META_IS_STREAMABLE_H #define BLT_META_IS_STREAMABLE_H +#include + namespace blt::meta { // https://stackoverflow.com/questions/66397071/is-it-possible-to-check-if-overloaded-operator-for-type-or-class-exists diff --git a/src/blt/logging/fmt_tokenizer.cpp b/src/blt/logging/fmt_tokenizer.cpp index dc434ef..5fe4362 100644 --- a/src/blt/logging/fmt_tokenizer.cpp +++ b/src/blt/logging/fmt_tokenizer.cpp @@ -49,6 +49,12 @@ namespace blt::logging return fmt_token_type::SPACE; case '#': return fmt_token_type::POUND; + case '<': + return fmt_token_type::LEFT_CHEVRON; + case '>': + return fmt_token_type::RIGHT_CHEVRON; + case '^': + return fmt_token_type::CARET; default: return fmt_token_type::STRING; } @@ -66,6 +72,9 @@ namespace blt::logging case fmt_token_type::DOT: case fmt_token_type::COLON: case fmt_token_type::POUND: + case fmt_token_type::LEFT_CHEVRON: + case fmt_token_type::RIGHT_CHEVRON: + case fmt_token_type::CARET: return fmt_token_t{base_type, std::string_view{m_fmt.data() + m_pos++, 1}}; default: { @@ -99,6 +108,19 @@ namespace blt::logging return m_spec; } + bool fmt_parser_t::is_align_t(const fmt_token_type type) + { + switch (type) + { + case fmt_token_type::LEFT_CHEVRON: + case fmt_token_type::RIGHT_CHEVRON: + case fmt_token_type::CARET: + return true; + default: + return false; + } + } + void fmt_parser_t::parse_fmt_field() { if (!has_next()) @@ -119,12 +141,7 @@ namespace blt::logging case fmt_token_type::COLON: parse_fmt_spec(); break; - case fmt_token_type::STRING: - case fmt_token_type::SPACE: - case fmt_token_type::DOT: - case fmt_token_type::MINUS: - case fmt_token_type::PLUS: - case fmt_token_type::POUND: + default: { std::stringstream ss; ss << "Expected unknown token '" << static_cast(type) << "' value '" << value << "' when parsing format field"; @@ -155,7 +172,18 @@ namespace blt::logging switch (type) { case fmt_token_type::STRING: + if (has_next(1)) + { + const auto [next_type, next_value] = peek(1); + if (is_align_t(next_type)) + parse_fmt_spec_align(); + } return; + case fmt_token_type::RIGHT_CHEVRON: + case fmt_token_type::LEFT_CHEVRON: + case fmt_token_type::CARET: + parse_fmt_spec_align(); + break; case fmt_token_type::COLON: { std::stringstream ss; @@ -179,6 +207,42 @@ namespace blt::logging } } + void fmt_parser_t::parse_fmt_spec_align() + { + parse_align(); + if (!has_next()) + return; + auto [type, value] = peek(); + switch (type) + { + case fmt_token_type::STRING: + return; + case fmt_token_type::NUMBER: + parse_fmt_spec_width(); + break; + case fmt_token_type::DOT: + parse_fmt_spec_precision(); + break; + case fmt_token_type::SPACE: + case fmt_token_type::MINUS: + case fmt_token_type::PLUS: + parse_fmt_spec_sign(); + break; + case fmt_token_type::POUND: + parse_fmt_spec_form(); + break; + case fmt_token_type::CARET: + case fmt_token_type::COLON: + case fmt_token_type::LEFT_CHEVRON: + case fmt_token_type::RIGHT_CHEVRON: + { + std::stringstream ss; + ss << "(Stage (Begin)) Invalid token type " << static_cast(type) << " value " << value; + throw std::runtime_error(ss.str()); + } + } + } + // handle start of fmt, with sign void fmt_parser_t::parse_fmt_spec_sign() { @@ -194,6 +258,9 @@ namespace blt::logging case fmt_token_type::MINUS: case fmt_token_type::PLUS: case fmt_token_type::COLON: + case fmt_token_type::CARET: + case fmt_token_type::LEFT_CHEVRON: + case fmt_token_type::RIGHT_CHEVRON: { std::stringstream ss; ss << "(Stage (Sign)) Invalid token type " << static_cast(type) << " value " << value; @@ -226,6 +293,9 @@ namespace blt::logging case fmt_token_type::MINUS: case fmt_token_type::PLUS: case fmt_token_type::POUND: + case fmt_token_type::CARET: + case fmt_token_type::LEFT_CHEVRON: + case fmt_token_type::RIGHT_CHEVRON: { std::stringstream ss; ss << "(Stage (Form)) Invalid token type " << static_cast(type) << " value " << value; @@ -257,6 +327,9 @@ namespace blt::logging case fmt_token_type::SPACE: case fmt_token_type::POUND: case fmt_token_type::NUMBER: + case fmt_token_type::CARET: + case fmt_token_type::LEFT_CHEVRON: + case fmt_token_type::RIGHT_CHEVRON: { std::stringstream ss; ss << "(Stage (Width)) Invalid token type " << static_cast(type) << " value " << value; @@ -275,6 +348,36 @@ namespace blt::logging parse_precision(); } + void fmt_parser_t::parse_align() + { + auto [type, value] = next(); + fmt_token_type process_type = type; + if (type == fmt_token_type::STRING) + { + auto [next_type, next_value] = next(); + process_type = next_type; + m_spec.prefix_char = value.front(); + } + switch (process_type) + { + case fmt_token_type::LEFT_CHEVRON: + m_spec.alignment = fmt_align_t::LEFT; + break; + case fmt_token_type::RIGHT_CHEVRON: + m_spec.alignment = fmt_align_t::RIGHT; + break; + case fmt_token_type::CARET: + m_spec.alignment = fmt_align_t::CENTER; + break; + default: + { + std::stringstream ss; + ss << "Invalid align type " << static_cast(process_type) << " value " << value; + throw std::runtime_error(ss.str()); + } + } + } + void fmt_parser_t::parse_sign() { auto [_, value] = next(); @@ -313,8 +416,8 @@ namespace blt::logging void fmt_parser_t::parse_width() { auto [_, value] = next(); - if (value.front() == '0') - m_spec.leading_zeros = true; + if (value.front() == '0' && !m_spec.prefix_char.has_value()) + m_spec.prefix_char = '0'; m_spec.width = std::stoll(std::string(value)); } diff --git a/src/blt/logging/logging.cpp b/src/blt/logging/logging.cpp index c480b7e..8009f0a 100644 --- a/src/blt/logging/logging.cpp +++ b/src/blt/logging/logging.cpp @@ -17,6 +17,7 @@ */ #include #include +#include #include #include #include @@ -25,23 +26,33 @@ namespace blt::logging { struct logging_thread_context_t { - logger_t logger; + std::stringstream stream; + logger_t logger{stream}; }; - std::string logger_t::to_string() + std::string logger_t::to_string() const { - auto str = m_stream.str(); - m_stream.str(""); - m_stream.clear(); - return str; + return dynamic_cast(m_stream).str(); } - void logger_t::setup_stream(const fmt_spec_t& spec) + void logger_t::setup_stream(const fmt_spec_t& spec) const { - if (spec.leading_zeros) - m_stream << std::setfill('0'); + if (spec.prefix_char) + m_stream << std::setfill(*spec.prefix_char); else m_stream << std::setfill(' '); + switch (spec.alignment) + { + case fmt_align_t::LEFT: + m_stream << std::left; + break; + case fmt_align_t::CENTER: + // TODO? + break; + case fmt_align_t::RIGHT: + m_stream << std::right; + break; + } if (spec.width > 0) m_stream << std::setw(static_cast(spec.width)); else @@ -52,6 +63,8 @@ namespace blt::logging m_stream << std::setprecision(16); if (spec.alternate_form) m_stream << std::showbase; + else + m_stream << std::noshowbase; if (spec.uppercase) m_stream << std::uppercase; else @@ -129,7 +142,8 @@ namespace blt::logging m_fmt = std::move(fmt); m_last_fmt_pos = 0; m_arg_pos = 0; - m_stream.str(""); + auto& ss = dynamic_cast(m_stream); + ss.str(""); m_stream.clear(); m_string_sections.clear(); m_fmt_specs.clear(); @@ -171,9 +185,9 @@ namespace blt::logging return context.logger; } - void print(const std::string& fmt) + void print(const std::string& str) { - std::cout << fmt; + std::cout << str; } void newline() diff --git a/tests/logger_tests.cpp b/tests/logger_tests.cpp index c4d7bc5..64cccb2 100644 --- a/tests/logger_tests.cpp +++ b/tests/logger_tests.cpp @@ -15,45 +15,112 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +#include +#include #include +#include #include struct some_silly_type_t { }; +auto expected_str = std::string(R"(This is a println! +This is a println with args '42' +This is a println with multiple args '42' '32.342311859130859375' 'Hello World!' +This is a 'Well so am I except cooler :3' fmt string with positionals 'I am a string!' +This is a println with a sign +4120 +This is a println with a sign -4120 +This is a println with a space 4120 +This is a println with a space -4120 +This is a println with a minus 4120 +This is a println with a minus -4120 +This is a println with a with 4120 +This is a println with a with leading zeros 0000004120 +This is a println with a precision 42.2323423490 +This is a println with hex 109a +This is a println with hex with leading 0x109a +This is a println with binary 0b00110010000110100101011000000000 +This is a println with binary with space 0b10110010 00011010 01010110 00000000 +This is a println with binary with space 10100010 00000000 00000000 00000000 +This is a println with octal 015015 +This is a println with hexfloat 0x1.926e978d4fdf4p+8 +This is a println with exponent 4.4320902431999996e+07 +This is a println with exponent 9.5324342340423400e+15 +This is a println with general 953243.49 +This is a println with general 9.532433240234033e+17 +This is a println with a char B +This is a println with type some_silly_type_t +This is a println with boolean true +This is a println with boolean as int 0 +This is a println with boolean as hex 0x1 +This is a println with boolean as octal 1 +This is a println with alignment left 64 end value +This is a println with alignment right 46 end value +This is a println with alignment left (fill) 46******** end value +This is a println with alignment right (fill) ********46 end value +)"); + +std::pair compare_strings(const std::string& s1, const std::string& s2) +{ + if (s1.size() != s2.size()) + return {false, "Strings size do not match '" + std::to_string(s1.size()) + "' vs '" + std::to_string(s2.size()) + "'"}; + size_t index = 0; + for (; index < s1.size(); ++index) + { + if (s1[index] != s2[index]) + { + std::stringstream ss; + const auto i1 = std::max(static_cast(index) - 32, 0l); + const auto l1 = std::min(static_cast(s1.size()) - i1, 65l); + ss << "Strings differ at index " << index << "!\n"; + ss << "'" << s1.substr(i1, l1) << "' vs '" << s2.substr(i1, l1) << "'" << std::endl; + return {false, ss.str()}; + } + } + return {true, ""}; +} + int main() { - blt::logging::println("This is a println!"); - blt::logging::println("This is a println with args '{}'", 42); - blt::logging::println("This is a println with multiple args '{}' '{:.100}' '{}'", 42, 32.34231233f, "Hello World!"); - blt::logging::println("This is a '{1}' fmt string with positionals '{0}'", "I am a string!", "Well so am I except cooler :3"); - blt::logging::println("This is a println with a sign {:+}", 4120); - blt::logging::println("This is a println with a sign {:+}", -4120); - blt::logging::println("This is a println with a space {: }", 4120); - blt::logging::println("This is a println with a space {: }", -4120); - blt::logging::println("This is a println with a minus {:-}", 4120); - blt::logging::println("This is a println with a minus {:-}", -4120); - blt::logging::println("This is a println with a with {:10}", 4120); - blt::logging::println("This is a println with a with leading zeros {:010}", 4120); - blt::logging::println("This is a println with a precision {:.10f}", 42.232342349); - blt::logging::println("This is a println with hex {:.10x}", 4250); - blt::logging::println("This is a println with hex with leading {:#.10x}", 4250); - blt::logging::println("This is a println with binary {:#b}", 6969420); - blt::logging::println("This is a println with binary with space {: #b}", 6969421); - blt::logging::println("This is a println with binary with space {: b}", 69); - blt::logging::println("This is a println with octal {:#o}", 6669); - blt::logging::println("This is a println with hexfloat {:a}", 402.4320); - blt::logging::println("This is a println with exponent {:e}", 44320902.4320); - blt::logging::println("This is a println with exponent {:e}", 9532434234042340.0); - blt::logging::println("This is a println with general {:g}", 953243.49); - blt::logging::println("This is a println with general {:g}", 953243324023403240.49); - blt::logging::println("This is a println with a char {:c}", 66); - blt::logging::println("This is a println with type {:t}", some_silly_type_t{}); - blt::logging::println("This is a println with boolean {}", true); - blt::logging::println("This is a println with boolean as int {:d}", false); - blt::logging::println("This is a println with boolean as hex {:#x}", true); - blt::logging::println("This is a println with boolean as octal {:o}", true); - blt::logging::println("This is a println with boolean as test {:h}", true); + std::stringstream ss; + blt::logging::println(ss, "This is a println!"); + blt::logging::println(ss, "This is a println with args '{}'", 42); + blt::logging::println(ss, "This is a println with multiple args '{}' '{:.100}' '{}'", 42, 32.34231233f, "Hello World!"); + blt::logging::println(ss, "This is a '{1}' fmt string with positionals '{0}'", "I am a string!", "Well so am I except cooler :3"); + blt::logging::println(ss, "This is a println with a sign {:+}", 4120); + blt::logging::println(ss, "This is a println with a sign {:+}", -4120); + blt::logging::println(ss, "This is a println with a space {: }", 4120); + blt::logging::println(ss, "This is a println with a space {: }", -4120); + blt::logging::println(ss, "This is a println with a minus {:-}", 4120); + blt::logging::println(ss, "This is a println with a minus {:-}", -4120); + blt::logging::println(ss, "This is a println with a with {:10}", 4120); + blt::logging::println(ss, "This is a println with a with leading zeros {:010}", 4120); + blt::logging::println(ss, "This is a println with a precision {:.10f}", 42.232342349); + blt::logging::println(ss, "This is a println with hex {:.10x}", 4250); + blt::logging::println(ss, "This is a println with hex with leading {:#.10x}", 4250); + blt::logging::println(ss, "This is a println with binary {:#b}", 6969420); + blt::logging::println(ss, "This is a println with binary with space {: #b}", 6969421); + blt::logging::println(ss, "This is a println with binary with space {: b}", 69); + blt::logging::println(ss, "This is a println with octal {:#o}", 6669); + blt::logging::println(ss, "This is a println with hexfloat {:a}", 402.4320); + blt::logging::println(ss, "This is a println with exponent {:e}", 44320902.4320); + blt::logging::println(ss, "This is a println with exponent {:e}", 9532434234042340.0); + blt::logging::println(ss, "This is a println with general {:g}", 953243.49); + blt::logging::println(ss, "This is a println with general {:g}", 953243324023403240.49); + blt::logging::println(ss, "This is a println with a char {:c}", 66); + blt::logging::println(ss, "This is a println with type {:t}", some_silly_type_t{}); + blt::logging::println(ss, "This is a println with boolean {}", true); + blt::logging::println(ss, "This is a println with boolean as int {:d}", false); + blt::logging::println(ss, "This is a println with boolean as hex {:#x}", true); + blt::logging::println(ss, "This is a println with boolean as octal {:o}", true); + blt::logging::println(ss, "This is a println with alignment left {:<10} end value", 64); + blt::logging::println(ss, "This is a println with alignment right {:>10} end value", 46); + blt::logging::println(ss, "This is a println with alignment left (fill) {:*<10} end value", 46); + blt::logging::println(ss, "This is a println with alignment right (fill) {:*>10} end value", 46); + blt::logging::print(ss.str()); + auto [passed, error_msg] = compare_strings(expected_str, ss.str()); + BLT_ASSERT_MSG(passed && "Logger logged string doesn't match precomputed expected string!", error_msg.c_str()); + // blt::logging::println("This is println {}\twith a std::endl in the middle of it"); } From fcbc6b947d901f3cfcc9e41f1887e3af7f80c258 Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Tue, 4 Mar 2025 22:02:51 -0500 Subject: [PATCH 056/101] working on ansi --- CMakeLists.txt | 4 +- include/blt/logging/ansi.h | 184 ++++++++ include/blt/logging/fmt_tokenizer.h | 14 +- include/blt/logging/logging.h | 5 +- include/blt/logging/logging_config.h | 58 +++ include/blt/std/logging.h | 488 --------------------- src/blt/logging/ansi.cpp | 18 + src/blt/logging/fmt_tokenizer.cpp | 127 +++++- src/blt/logging/logging.cpp | 20 +- src/blt/logging/logging_config.cpp | 23 + src/blt/std/logging.cpp | 622 ++++++++++++++++++++++++++- tests/logger_tests.cpp | 21 +- 12 files changed, 1060 insertions(+), 524 deletions(-) create mode 100644 include/blt/logging/ansi.h create mode 100644 include/blt/logging/logging_config.h create mode 100644 src/blt/logging/ansi.cpp create mode 100644 src/blt/logging/logging_config.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 54387ac..6480ed1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.1.9) +set(BLT_VERSION 5.1.10) set(BLT_TARGET BLT) @@ -78,7 +78,7 @@ if (${BUILD_LOGGING}) message(STATUS "Building ${Yellow}logging${ColourReset} cxx files") file(GLOB_RECURSE LOGGING_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/blt/logging/*.cpp") else () - set(PARSE_FILES "") + set(LOGGING_FILES "") endif () if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/libraries/parallel-hashmap) diff --git a/include/blt/logging/ansi.h b/include/blt/logging/ansi.h new file mode 100644 index 0000000..1aa7c83 --- /dev/null +++ b/include/blt/logging/ansi.h @@ -0,0 +1,184 @@ +#pragma once +/* + * Copyright (C) 2024 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BLT_LOGGING_COLORS_H +#define BLT_LOGGING_COLORS_H + +#include +#include + +#define BLT_ANSI_ESCAPE "\x1B" +#define BLT_ANSI_CSI BLT_ANSI_ESCAPE "[" +#define BLT_ANSI_DSC BLT_ANSI_ESCAPE "P" +#define BLT_ANSI_OSC BLT_ANSI_ESCAPE "]" + +// https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797 +namespace blt::logging::ansi +{ + namespace color + { + enum class color_mode : i32 + { + RESET_ALL = 0, + BOLD = 1, + DIM = 2, + ITALIC = 3, + UNDERLINE = 4, + BLINK = 5, + REVERSE = 7, + HIDDEN = 8, + STRIKE_THROUGH = 9, + }; + + enum class color8 : i32 + { + BLACK = 0, + RED = 1, + GREEN = 2, + YELLOW = 3, + BLUE = 4, + MAGENTA = 5, + CYAN = 6, + WHITE = 7, + DEFAULT = 9 + }; + + enum class color8_bright : i32 + { + BLACK = 0, + RED = 1, + GREEN = 2, + YELLOW = 3, + BLUE = 4, + MAGENTA = 5, + CYAN = 6, + WHITE = 7, + }; + + namespace detail + { + template + struct color_converter + { + }; + + template <> + struct color_converter + { + static std::string to_string(color8 color, const bool background) + { + return (background ? "4" : "3") + std::to_string(static_cast(color)); + } + }; + + template <> + struct color_converter + { + static std::string to_string(color8_bright color, const bool background) + { + return (background ? "10" : "9") + std::to_string(static_cast(color)); + } + }; + } + } + + namespace general + { + inline const std::string bell = "\x07"; + inline const std::string bs = "\x08"; + inline const std::string horizontal_tab = "\x09"; + inline const std::string linefeed = "\x0A"; + inline const std::string vertical_tab = "\x0B"; + inline const std::string form_feed = "\x0C"; + inline const std::string carriage_return = "\x0D"; + inline const std::string escape = BLT_ANSI_ESCAPE; + inline const std::string del = "\0x7F"; + inline const std::string csi = BLT_ANSI_CSI; + inline const std::string dsc = BLT_ANSI_DSC; + inline const std::string osc = BLT_ANSI_OSC; + } + + namespace cursor + { + inline const std::string home = BLT_ANSI_CSI "H"; + + template + inline std::string move_to(i64 line, i64 column) + { + if constexpr (UseH) + return std::string(BLT_ANSI_CSI) + std::to_string(line) + ";" + std::to_string(column) + "H"; + else + return std::string(BLT_ANSI_CSI) + std::to_string(line) + ";" + std::to_string(column) + "f"; + } + + inline std::string move_up(i64 lines) + { + return std::string(BLT_ANSI_CSI) + std::to_string(lines) + "A"; + } + + inline std::string move_down(i64 lines) + { + return std::string(BLT_ANSI_CSI) + std::to_string(lines) + "B"; + } + + inline std::string move_right(i64 columns) + { + return std::string(BLT_ANSI_CSI) + std::to_string(columns) + "C"; + } + + inline std::string move_left(i64 columns) + { + return std::string(BLT_ANSI_CSI) + std::to_string(columns) + "D"; + } + + inline std::string move_begin_down(i64 lines) + { + return std::string(BLT_ANSI_CSI) + std::to_string(lines) + "E"; + } + + inline std::string move_begin_up(i64 lines) + { + return std::string(BLT_ANSI_CSI) + std::to_string(lines) + "F"; + } + + inline std::string move_to(i64 column) + { + return std::string(BLT_ANSI_CSI) + std::to_string(column) + "G"; + } + + inline const std::string request_cursor_position = BLT_ANSI_CSI "6n"; + inline const std::string move_up_one_line = BLT_ANSI_ESCAPE " M"; + inline const std::string save_cursor_position_dec = BLT_ANSI_ESCAPE " 7"; + inline const std::string resotre_cursor_position_dec = BLT_ANSI_ESCAPE " 8"; + inline const std::string save_cursor_position_sco = BLT_ANSI_CSI "s"; + inline const std::string resotre_cursor_position_sco = BLT_ANSI_CSI "u"; + } + + namespace erase + { + inline const std::string to_end_of_screen = BLT_ANSI_CSI "0J"; + inline const std::string from_begin_of_screen = BLT_ANSI_CSI "1J"; + inline const std::string entire_screen = BLT_ANSI_CSI "2J"; + inline const std::string saved_lines = BLT_ANSI_CSI "3J"; + inline const std::string to_end_of_line = BLT_ANSI_CSI "0K"; + inline const std::string from_begin_of_line = BLT_ANSI_CSI "1K"; + inline const std::string entire_line = BLT_ANSI_CSI "2K"; + } +} + +#endif //BLT_LOGGING_COLORS_H diff --git a/include/blt/logging/fmt_tokenizer.h b/include/blt/logging/fmt_tokenizer.h index 8f1cffe..7f98833 100644 --- a/include/blt/logging/fmt_tokenizer.h +++ b/include/blt/logging/fmt_tokenizer.h @@ -23,6 +23,7 @@ #include #include #include +#include namespace blt::logging { @@ -38,6 +39,8 @@ namespace blt::logging POUND, LEFT_CHEVRON, RIGHT_CHEVRON, + OPEN_BRACKET, + CLOSE_BRACKET, CARET }; @@ -102,14 +105,16 @@ namespace blt::logging private: size_t m_pos = 0; - std::string_view m_fmt; + std::string_view m_fmt{}; }; class fmt_parser_t { public: - explicit fmt_parser_t() = default; + explicit fmt_parser_t(std::vector>& handlers): m_handlers(handlers) + { + } fmt_token_t& peek(const size_t offset) { @@ -153,14 +158,17 @@ namespace blt::logging void parse_fmt_field(); void parse_arg_id(); + std::string parse_arg_or_number(); void parse_fmt_spec(); + void parse_fmt_spec_fill(); void parse_fmt_spec_align(); void parse_fmt_spec_sign(); void parse_fmt_spec_form(); void parse_fmt_spec_width(); void parse_fmt_spec_precision(); + void parse_fill(); void parse_align(); void parse_sign(); void parse_form(); @@ -172,6 +180,8 @@ namespace blt::logging std::vector m_tokens; fmt_tokenizer_t m_tokenizer; fmt_spec_t m_spec; + + std::vector>& m_handlers; }; } diff --git a/include/blt/logging/logging.h b/include/blt/logging/logging.h index 91c577f..f2d1b3c 100644 --- a/include/blt/logging/logging.h +++ b/include/blt/logging/logging.h @@ -43,11 +43,11 @@ namespace blt::logging template std::string log(std::string fmt, Args&&... args) { - compile(std::move(fmt)); auto sequence = std::make_integer_sequence{}; m_arg_print_funcs.clear(); m_arg_print_funcs.resize(sizeof...(Args)); create_conv_funcs(sequence, std::forward(args)...); + compile(std::move(fmt)); process_strings(); return to_string(); } @@ -137,6 +137,7 @@ namespace blt::logging }; } + [[nodiscard]] size_t find_ending_brace(size_t begin) const; void setup_stream(const fmt_spec_t& spec) const; void process_strings(); static void handle_type(std::ostream& stream, const fmt_spec_t& spec); @@ -150,7 +151,7 @@ namespace blt::logging std::string m_fmt; std::ostream& m_stream; - fmt_parser_t m_parser; + fmt_parser_t m_parser{m_arg_print_funcs}; // normal sections of string std::vector m_string_sections; // processed format specs diff --git a/include/blt/logging/logging_config.h b/include/blt/logging/logging_config.h new file mode 100644 index 0000000..3447e41 --- /dev/null +++ b/include/blt/logging/logging_config.h @@ -0,0 +1,58 @@ +#pragma once +/* + * Copyright (C) 2024 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BLT_LOGGING_LOGGING_CONFIG_H +#define BLT_LOGGING_LOGGING_CONFIG_H + +#include +#include +#include +#include +#include + +namespace blt::logging +{ + enum class log_level_t + { + TRACE, + DEBUG, + INFO, + WARN, + ERROR, + FATAL + }; + + inline constexpr size_t LOG_LEVEL_COUNT = 6; + + class logging_config_t + { + public: + std::optional log_file_path; + std::string log_format = ""; + std::array log_level_colors = { + + }; + log_level_t level = log_level_t::TRACE; + bool use_color = true; + bool log_to_console = true; + + private: + }; +} + +#endif //BLT_LOGGING_LOGGING_CONFIG_H diff --git a/include/blt/std/logging.h b/include/blt/std/logging.h index 42bc3f4..86d6b41 100644 --- a/include/blt/std/logging.h +++ b/include/blt/std/logging.h @@ -344,494 +344,6 @@ namespace blt::logging void setMaxFileSize(size_t fileSize); } -//#define BLT_LOGGING_IMPLEMENTATION -#ifdef BLT_LOGGING_IMPLEMENTATION - - #include - #include - #include - #include - #include - #include - #include - #include - #if defined(CXX17_FILESYSTEM) || defined (CXX17_FILESYSTEM_LIBFS) - #include - #elif defined(CXX11_EXP_FILESYSTEM) || defined (CXX11_EXP_FILESYSTEM_LIBFS) - #include - #else - #include - #endif - #include - #include - -template -using hashmap = std::unordered_map; - -namespace blt::logging { - -/** - * Used to store fast associations between built in tags and their respective values - */ -class tag_map { - private: - tag* tags; - size_t size; - - [[nodiscard]] static inline size_t hash(const tag& t) { - size_t h = t.tag[1] * 3 - t.tag[0]; - return h - 100; - } - - // TODO: fix - void expand() { - auto newSize = size * 2; - auto newTags = new tag[newSize]; - for (size_t i = 0; i < size; i++) - newTags[i] = tags[i]; - delete[] tags; - tags = newTags; - size = newSize; - } - public: - tag_map(std::initializer_list initial_tags){ - size_t max = 0; - for (const auto& t : initial_tags) - max = std::max(max, hash(t)); - tags = new tag[(size = max+1)]; - for (const auto& t : initial_tags) - insert(t); - } - tag_map(const tag_map& copy) { - tags = new tag[(size = copy.size)]; - for (size_t i = 0; i < size; i++) - tags[i] = copy.tags[i]; - } - - void insert(const tag& t) { - auto h = hash(t); - //if (h > size) - // expand(); - if (!tags[h].tag.empty()) - std::cerr << "Tag not empty! " << tags[h].tag << "!!!\n"; - tags[h] = t; - } - - tag& operator[](const std::string& name) const { - auto h = hash(tag{name, nullptr}); - if (h > size) - std::cerr << "Tag out of bounds"; - return tags[h]; - } - - ~tag_map(){ - delete[] tags; - tags = nullptr; - } -}; - -class LogFileWriter { - private: - std::string m_path; - std::fstream* output = nullptr; - public: - explicit LogFileWriter() = default; - - void writeLine(const std::string& path, const std::string& line){ - if (path != m_path || output == nullptr){ - clear(); - delete output; - output = new std::fstream(path, std::ios::out | std::ios::app); - if (!output->good()){ - throw std::runtime_error("Unable to open console filestream!\n"); - } - } - if (!output->good()){ - std::cerr << "There has been an error in the logging file stream!\n"; - output->clear(); - } - *output << line; - } - - void clear(){ - if (output != nullptr) { - try { - output->flush(); - output->close(); - } catch (std::exception& e){ - std::cerr << e.what() << "\n"; - } - } - } - - ~LogFileWriter() { - clear(); - delete(output); - } -}; - -#ifdef WIN32 - #define BLT_NOW() auto t = std::time(nullptr); tm now{}; localtime_s(&now, &t) -#else - #define BLT_NOW() auto t = std::time(nullptr); auto now_ptr = std::localtime(&t); auto& now = *now_ptr -#endif - -//#define BLT_NOW() auto t = std::time(nullptr); tm now; localtime_s(&now, &t); //auto now = std::localtime(&t) - #define BLT_ISO_YEAR(S) auto S = std::to_string(now.tm_year + 1900); \ - S += '-'; \ - S += ensureHasDigits(now.tm_mon+1, 2); \ - S += '-'; \ - S += ensureHasDigits(now.tm_mday, 2); - #define BLT_CUR_TIME(S) auto S = ensureHasDigits(now.tm_hour, 2); \ - S += ':'; \ - S += ensureHasDigits(now.tm_min, 2); \ - S += ':'; \ - S += ensureHasDigits(now.tm_sec, 2); - -static inline std::string ensureHasDigits(int current, int digits) { - std::string asString = std::to_string(current); - auto length = digits - asString.length(); - if (length <= 0) - return asString; - std::string zeros; - zeros.reserve(length); - for (unsigned int i = 0; i < length; i++){ - zeros += '0'; - } - return zeros + asString; -} - -log_format loggingFormat {}; -hashmap loggingThreadNames; -hashmap> loggingStreamLines; -LogFileWriter writer; - -const std::unique_ptr tagMap = std::make_unique(tag_map{ - {"YEAR", [](const tag_func_param&) -> std::string { - BLT_NOW(); - return std::to_string(now.tm_year); - }}, - {"MONTH", [](const tag_func_param&) -> std::string { - BLT_NOW(); - return ensureHasDigits(now.tm_mon+1, 2); - }}, - {"DAY", [](const tag_func_param&) -> std::string { - BLT_NOW(); - return ensureHasDigits(now.tm_mday, 2); - }}, - {"HOUR", [](const tag_func_param&) -> std::string { - BLT_NOW(); - return ensureHasDigits(now.tm_hour, 2); - }}, - {"MINUTE", [](const tag_func_param&) -> std::string { - BLT_NOW(); - return ensureHasDigits(now.tm_min, 2); - }}, - {"SECOND", [](const tag_func_param&) -> std::string { - BLT_NOW(); - return ensureHasDigits(now.tm_sec, 2); - }}, - {"MS", [](const tag_func_param&) -> std::string { - return std::to_string(std::chrono::duration_cast( - std::chrono::high_resolution_clock::now().time_since_epoch()).count() - ); - }}, - {"NS", [](const tag_func_param&) -> std::string { - return std::to_string(std::chrono::duration_cast( - std::chrono::high_resolution_clock::now().time_since_epoch()).count() - ); - }}, - {"ISO_YEAR", [](const tag_func_param&) -> std::string { - BLT_NOW(); - BLT_ISO_YEAR(returnStr); - return returnStr; - }}, - {"TIME", [](const tag_func_param&) -> std::string { - BLT_NOW(); - BLT_CUR_TIME(returnStr); - return returnStr; - }}, - {"FULL_TIME", [](const tag_func_param&) -> std::string { - BLT_NOW(); - BLT_ISO_YEAR(ISO); - BLT_CUR_TIME(TIME); - ISO += ' '; - ISO += TIME; - return ISO; - }}, - {"LF", [](const tag_func_param& f) -> std::string { - return loggingFormat.levelColors[(int)f.level]; - }}, - {"ER", [](const tag_func_param&) -> std::string { - return loggingFormat.levelColors[(int)log_level::ERROR]; - }}, - {"CNR", [](const tag_func_param& f) -> std::string { - return f.level >= log_level::ERROR ? loggingFormat.levelColors[(int)log_level::ERROR] : ""; - }}, - {"RC", [](const tag_func_param&) -> std::string { - return "\033[0m"; - }}, - {"LOG_LEVEL", [](const tag_func_param& f) -> std::string { - return loggingFormat.levelNames[(int)f.level]; - }}, - {"THREAD_NAME", [](const tag_func_param&) -> std::string { - if (loggingThreadNames.find(std::this_thread::get_id()) == loggingThreadNames.end()) - return "UNKNOWN"; - return loggingThreadNames[std::this_thread::get_id()]; - }}, - {"FILE", [](const tag_func_param& f) -> std::string { - return f.file; - }}, - {"LINE", [](const tag_func_param& f) -> std::string { - return f.line; - }}, - {"RAW_STR", [](const tag_func_param& f) -> std::string { - return f.raw_string; - }}, - {"STR", [](const tag_func_param& f) -> std::string { - return f.formatted_string; - }} -}); - -static inline std::vector split(std::string s, const std::string& delim) { - size_t pos = 0; - std::vector tokens; - while ((pos = s.find(delim)) != std::string::npos) { - auto token = s.substr(0, pos); - tokens.push_back(token); - s.erase(0, pos + delim.length()); - } - tokens.push_back(s); - return tokens; -} - -inline std::string filename(const std::string& path){ - if (loggingFormat.printFullFileName) - return path; - auto paths = split(path, "/"); - auto final = paths[paths.size()-1]; - if (final == "/") - return paths[paths.size()-2]; - return final; -} - -class string_parser { - private: - std::string _str; - size_t _pos; - public: - explicit string_parser(std::string str): _str(std::move(str)), _pos(0) {} - - inline char next(){ - return _str[_pos++]; - } - - [[nodiscard]] inline bool has_next() const { - return _pos < _str.size(); - } -}; - -std::string stripANSI(const std::string& str){ - string_parser parser(str); - std::string out; - while (parser.has_next()){ - char c = parser.next(); - if (c == '\033'){ - while (parser.has_next() && parser.next() != 'm'); - } else - out += c; - } - return out; -} - -void applyCFormatting(const std::string& format, std::string& output, std::va_list& args){ - // args must be copied because they will be consumed by the first vsnprintf - va_list args_copy; - va_copy(args_copy, args); - - auto buffer_size = std::vsnprintf(nullptr, 0, format.c_str(), args_copy) + 1; - auto* buffer = new char[static_cast(buffer_size)]; - - vsnprintf(buffer, buffer_size, format.c_str(), args); - output = std::string(buffer); - - delete[] buffer; - - va_end(args_copy); -} - -/** - * Checks if the next character in the parser is a tag opening, if not output the buffer to the out string - */ -inline bool tagOpening(string_parser& parser, std::string& out){ - char c = ' '; - if (parser.has_next() && (c = parser.next()) == '{') - if (parser.has_next() && (c = parser.next()) == '{') - return true; - else - out += c; - else - out += c; - return false; -} - -void parseString(string_parser& parser, std::string& out, const std::string& userStr, log_level level, const char* file, int line){ - while (parser.has_next()){ - char c = parser.next(); - std::string nonTag; - if (c == '$' && tagOpening(parser, nonTag)){ - std::string tag; - while (parser.has_next()){ - c = parser.next(); - if (c == '}') - break; - tag += c; - } - c = parser.next(); - if (parser.has_next() && c != '}') { - std::cerr << "Error processing tag, is not closed with two '}'!\n"; - break; - } - if (loggingFormat.ensureAlignment && tag == "STR") { - auto currentOutputWidth = out.size(); - auto& longestWidth = loggingFormat.currentWidth; - longestWidth = std::max(longestWidth, currentOutputWidth); - // pad with spaces - if (currentOutputWidth != longestWidth){ - for (size_t i = currentOutputWidth; i < longestWidth; i++) - out += ' '; - } - } - tag_func_param param{ - level, filename({file}), std::to_string(line), userStr, userStr - }; - out += (*tagMap)[tag].func(param); - } else { - out += c; - out += nonTag; - } - } -} - -std::string applyFormatString(const std::string& str, log_level level, const char* file, int line){ - // this can be speedup by preprocessing the string into an easily callable class - // where all the variables are ready to be substituted in one step - // and all static information already entered - string_parser parser(loggingFormat.logOutputFormat); - std::string out; - parseString(parser, out, str, level, file, line); - - return out; -} - -void log_internal(const std::string& format, log_level level, const char* file, int line, std::va_list& args) { - std::string withoutLn = format; - auto len = withoutLn.length(); - - if (len > 0 && withoutLn[len - 1] == '\n') - withoutLn = withoutLn.substr(0, len-1); - - std::string out; - - applyCFormatting(withoutLn, out, args); - - if (level == log_level::NONE){ - std::cout << out << std::endl; - return; - } - - std::string finalFormattedOutput = applyFormatString(out, level, file, line); - - if (loggingFormat.logToConsole) - std::cout << finalFormattedOutput; - - - if (loggingFormat.logToFile){ - string_parser parser(loggingFormat.logFileName); - std::string fileName; - parseString(parser, fileName, withoutLn, level, file, line); - - auto path = loggingFormat.logFilePath; - if (!path.empty() && path[path.length()-1] != '/') - path += '/'; - - // if the file has changed (new day in default case) we should reset the rollover count - if (loggingFormat.lastFile != fileName){ - loggingFormat.currentRollover = 0; - loggingFormat.lastFile = fileName; - } - - path += fileName; - path += '-'; - path += std::to_string(loggingFormat.currentRollover); - path += ".log"; - - if (std::filesystem::exists(path)) { - auto fileSize = std::filesystem::file_size(path); - - // will start on next file - if (fileSize > loggingFormat.logMaxFileSize) - loggingFormat.currentRollover++; - } - - writer.writeLine(path, stripANSI(finalFormattedOutput)); - } - //std::cout.flush(); -} - -void log_stream_internal(const std::string& str, const logger& logger) { - auto& s = loggingStreamLines[std::this_thread::get_id()][logger.level]; -// s += str; - for (char c : str){ - s += c; - if (c == '\n'){ - log(s, logger.level, logger.file, logger.line); - s = ""; - } - } -} - -void setThreadName(const std::string& name) { - loggingThreadNames[std::this_thread::get_id()] = name; -} - -void setLogFormat(const log_format& format){ - loggingFormat = format; -} -void setLogColor(log_level level, const std::string& newFormat){ - loggingFormat.levelColors[(int)level] = newFormat; -} -void setLogName(log_level level, const std::string& newFormat){ - loggingFormat.levelNames[(int)level] = newFormat; -} -void setLogOutputFormat(const std::string& newFormat){ - loggingFormat.logOutputFormat = newFormat; -} -void setLogToFile(bool shouldLogToFile){ - loggingFormat.logToFile = shouldLogToFile; -} -void setLogToConsole(bool shouldLogToConsole){ - loggingFormat.logToConsole = shouldLogToConsole; -} -void setLogPath(const std::string& path){ - loggingFormat.logFilePath = path; -} -void setLogFileName(const std::string& fileName){ - loggingFormat.logFileName = fileName; -} -void setMaxFileSize(size_t fileSize) { - loggingFormat.logMaxFileSize = fileSize; -} - -void flush() { - std::cerr.flush(); - std::cout.flush(); -} - -} - -#endif - #if defined(__clang__) || defined(__llvm__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" diff --git a/src/blt/logging/ansi.cpp b/src/blt/logging/ansi.cpp new file mode 100644 index 0000000..6c2e611 --- /dev/null +++ b/src/blt/logging/ansi.cpp @@ -0,0 +1,18 @@ +/* + * + * Copyright (C) 2025 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include \ No newline at end of file diff --git a/src/blt/logging/fmt_tokenizer.cpp b/src/blt/logging/fmt_tokenizer.cpp index 5fe4362..9ff97b5 100644 --- a/src/blt/logging/fmt_tokenizer.cpp +++ b/src/blt/logging/fmt_tokenizer.cpp @@ -55,6 +55,10 @@ namespace blt::logging return fmt_token_type::RIGHT_CHEVRON; case '^': return fmt_token_type::CARET; + case '{': + return fmt_token_type::OPEN_BRACKET; + case '}': + return fmt_token_type::CLOSE_BRACKET; default: return fmt_token_type::STRING; } @@ -64,6 +68,12 @@ namespace blt::logging { if (m_pos >= m_fmt.size()) return {}; + bool is_escaped = false; + if (m_fmt[m_pos] == '\\') + { + is_escaped = true; + ++m_pos; + } switch (const auto base_type = get_type(m_fmt[m_pos])) { case fmt_token_type::SPACE: @@ -75,6 +85,10 @@ namespace blt::logging case fmt_token_type::LEFT_CHEVRON: case fmt_token_type::RIGHT_CHEVRON: case fmt_token_type::CARET: + case fmt_token_type::OPEN_BRACKET: + case fmt_token_type::CLOSE_BRACKET: + if (is_escaped) + return fmt_token_t{fmt_token_type::STRING, std::string_view{m_fmt.data() + m_pos++, 1}}; return fmt_token_t{base_type, std::string_view{m_fmt.data() + m_pos++, 1}}; default: { @@ -164,6 +178,29 @@ namespace blt::logging m_spec.arg_id = std::stoll(std::string(value)); } + std::string fmt_parser_t::parse_arg_or_number() + { + auto [type, value] = next(); + if (type == fmt_token_type::NUMBER) + return std::string(value); + if (type == fmt_token_type::OPEN_BRACKET) + { + auto [next_type, next_value] = next(); + if (next_type != fmt_token_type::NUMBER) + throw std::runtime_error("Expected number when parsing arg or number, unexpected value '" + std::string(next_value) + '\''); + if (next().type != fmt_token_type::CLOSE_BRACKET) + throw std::runtime_error("Expected closing bracket when parsing arg or number, unexpected value '" + std::string(next_value) + '\''); + // TODO: this feels like an evil hack. + const auto arg_id = std::stoul(std::string(next_value)); + if (arg_id >= m_handlers.size()) + throw std::runtime_error("Invalid arg id " + std::to_string(arg_id) + ", max arg supported: " + std::to_string(m_handlers.size())); + std::stringstream ss; + m_handlers[arg_id](ss, fmt_spec_t{}); + return ss.str(); + } + throw std::runtime_error("Expected number when parsing arg or number, unexpected value '" + std::string(value) + '\''); + } + void fmt_parser_t::parse_fmt_spec() { // consume : @@ -172,12 +209,9 @@ namespace blt::logging switch (type) { case fmt_token_type::STRING: + // if there is a token beyond this string, it is not a type string if (has_next(1)) - { - const auto [next_type, next_value] = peek(1); - if (is_align_t(next_type)) - parse_fmt_spec_align(); - } + parse_fmt_spec_fill(); return; case fmt_token_type::RIGHT_CHEVRON: case fmt_token_type::LEFT_CHEVRON: @@ -185,12 +219,14 @@ namespace blt::logging parse_fmt_spec_align(); break; case fmt_token_type::COLON: + case fmt_token_type::CLOSE_BRACKET: { std::stringstream ss; ss << "(Stage (Begin)) Invalid token type " << static_cast(type) << " value " << value; throw std::runtime_error(ss.str()); } case fmt_token_type::NUMBER: + case fmt_token_type::OPEN_BRACKET: parse_fmt_spec_width(); break; case fmt_token_type::DOT: @@ -207,6 +243,46 @@ namespace blt::logging } } + void fmt_parser_t::parse_fmt_spec_fill() + { + parse_fill(); + if (!has_next()) + return; + auto [type, value] = peek(); + switch (type) + { + case fmt_token_type::RIGHT_CHEVRON: + case fmt_token_type::LEFT_CHEVRON: + case fmt_token_type::CARET: + parse_fmt_spec_align(); + break; + case fmt_token_type::NUMBER: + case fmt_token_type::OPEN_BRACKET: + parse_fmt_spec_width(); + break; + case fmt_token_type::DOT: + parse_fmt_spec_precision(); + break; + case fmt_token_type::SPACE: + case fmt_token_type::MINUS: + case fmt_token_type::PLUS: + parse_fmt_spec_sign(); + break; + case fmt_token_type::POUND: + parse_fmt_spec_form(); + break; + case fmt_token_type::STRING: + return; + case fmt_token_type::COLON: + case fmt_token_type::CLOSE_BRACKET: + { + std::stringstream ss; + ss << "(Stage (Fill)) Invalid token type " << static_cast(type) << " value " << value; + throw std::runtime_error(ss.str()); + } + } + } + void fmt_parser_t::parse_fmt_spec_align() { parse_align(); @@ -218,6 +294,7 @@ namespace blt::logging case fmt_token_type::STRING: return; case fmt_token_type::NUMBER: + case fmt_token_type::OPEN_BRACKET: parse_fmt_spec_width(); break; case fmt_token_type::DOT: @@ -235,9 +312,10 @@ namespace blt::logging case fmt_token_type::COLON: case fmt_token_type::LEFT_CHEVRON: case fmt_token_type::RIGHT_CHEVRON: + case fmt_token_type::CLOSE_BRACKET: { std::stringstream ss; - ss << "(Stage (Begin)) Invalid token type " << static_cast(type) << " value " << value; + ss << "(Stage (Align)) Invalid token type " << static_cast(type) << " value " << value; throw std::runtime_error(ss.str()); } } @@ -261,12 +339,14 @@ namespace blt::logging case fmt_token_type::CARET: case fmt_token_type::LEFT_CHEVRON: case fmt_token_type::RIGHT_CHEVRON: + case fmt_token_type::CLOSE_BRACKET: { std::stringstream ss; ss << "(Stage (Sign)) Invalid token type " << static_cast(type) << " value " << value; throw std::runtime_error(ss.str()); } case fmt_token_type::NUMBER: + case fmt_token_type::OPEN_BRACKET: parse_fmt_spec_width(); break; case fmt_token_type::DOT: @@ -296,12 +376,14 @@ namespace blt::logging case fmt_token_type::CARET: case fmt_token_type::LEFT_CHEVRON: case fmt_token_type::RIGHT_CHEVRON: + case fmt_token_type::CLOSE_BRACKET: { std::stringstream ss; ss << "(Stage (Form)) Invalid token type " << static_cast(type) << " value " << value; throw std::runtime_error(ss.str()); } case fmt_token_type::NUMBER: + case fmt_token_type::OPEN_BRACKET: parse_fmt_spec_width(); break; case fmt_token_type::DOT: @@ -330,6 +412,8 @@ namespace blt::logging case fmt_token_type::CARET: case fmt_token_type::LEFT_CHEVRON: case fmt_token_type::RIGHT_CHEVRON: + case fmt_token_type::OPEN_BRACKET: + case fmt_token_type::CLOSE_BRACKET: { std::stringstream ss; ss << "(Stage (Width)) Invalid token type " << static_cast(type) << " value " << value; @@ -348,17 +432,22 @@ namespace blt::logging parse_precision(); } + void fmt_parser_t::parse_fill() + { + auto [type, value] = next(); + if (type != fmt_token_type::STRING) + { + std::stringstream ss; + ss << "Expected string when parsing fill, got " << static_cast(type) << " value " << value; + throw std::runtime_error(ss.str()); + } + m_spec.prefix_char = value.front(); + } + void fmt_parser_t::parse_align() { auto [type, value] = next(); - fmt_token_type process_type = type; - if (type == fmt_token_type::STRING) - { - auto [next_type, next_value] = next(); - process_type = next_type; - m_spec.prefix_char = value.front(); - } - switch (process_type) + switch (type) { case fmt_token_type::LEFT_CHEVRON: m_spec.alignment = fmt_align_t::LEFT; @@ -372,7 +461,7 @@ namespace blt::logging default: { std::stringstream ss; - ss << "Invalid align type " << static_cast(process_type) << " value " << value; + ss << "Invalid align type " << static_cast(type) << " value " << value; throw std::runtime_error(ss.str()); } } @@ -415,19 +504,17 @@ namespace blt::logging void fmt_parser_t::parse_width() { - auto [_, value] = next(); + const auto value = parse_arg_or_number(); if (value.front() == '0' && !m_spec.prefix_char.has_value()) m_spec.prefix_char = '0'; - m_spec.width = std::stoll(std::string(value)); + m_spec.width = std::stoll(value); } void fmt_parser_t::parse_precision() { if (!has_next()) throw std::runtime_error("Missing token when parsing precision"); - auto [type, value] = next(); - if (type != fmt_token_type::NUMBER) - throw std::runtime_error("Expected number when parsing precision"); + auto value = parse_arg_or_number(); m_spec.precision = std::stoll(std::string(value)); } diff --git a/src/blt/logging/logging.cpp b/src/blt/logging/logging.cpp index 8009f0a..18fc5e7 100644 --- a/src/blt/logging/logging.cpp +++ b/src/blt/logging/logging.cpp @@ -35,6 +35,21 @@ namespace blt::logging return dynamic_cast(m_stream).str(); } + size_t logger_t::find_ending_brace(size_t begin) const + { + size_t braces = 0; + for (; begin < m_fmt.size(); ++begin) + { + if (m_fmt[begin] == '{') + ++braces; + else if (m_fmt[begin] == '}') + --braces; + if (braces == 0) + return begin; + } + return std::string::npos; + } + void logger_t::setup_stream(const fmt_spec_t& spec) const { if (spec.prefix_char) @@ -167,9 +182,8 @@ namespace blt::logging const auto begin = m_fmt.find('{', m_last_fmt_pos); if (begin == std::string::npos) return {}; - const auto next_begin = m_fmt.find('{', begin + 1); - const auto end = m_fmt.find('}', begin); - if (end == std::string::npos || (next_begin != std::string::npos && next_begin < end)) + const auto end = find_ending_brace(begin); + if (end == std::string::npos) { std::stringstream ss; ss << "Invalid format string, missing closing '}' near " << m_fmt.substr(std::min(static_cast(begin) - 5, 0l)); diff --git a/src/blt/logging/logging_config.cpp b/src/blt/logging/logging_config.cpp new file mode 100644 index 0000000..06f1880 --- /dev/null +++ b/src/blt/logging/logging_config.cpp @@ -0,0 +1,23 @@ +/* + * + * Copyright (C) 2025 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace blt::logging +{ + +} \ No newline at end of file diff --git a/src/blt/std/logging.cpp b/src/blt/std/logging.cpp index 129d1f2..393f8f9 100644 --- a/src/blt/std/logging.cpp +++ b/src/blt/std/logging.cpp @@ -3,5 +3,625 @@ * Licensed under GNU General Public License V3.0 * See LICENSE file for license detail */ -#define BLT_LOGGING_IMPLEMENTATION #include + +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(CXX17_FILESYSTEM) || defined (CXX17_FILESYSTEM_LIBFS) + #include +#elif defined(CXX11_EXP_FILESYSTEM) || defined (CXX11_EXP_FILESYSTEM_LIBFS) + #include +#else +#include +#endif +#include +#include + +template +using hashmap = std::unordered_map; + +namespace blt::logging +{ + /** + * Used to store fast associations between built in tags and their respective values + */ + class tag_map + { + private: + tag* tags; + size_t size; + + [[nodiscard]] static inline size_t hash(const tag& t) + { + size_t h = t.tag[1] * 3 - t.tag[0]; + return h - 100; + } + + // TODO: fix + void expand() + { + auto newSize = size * 2; + auto newTags = new tag[newSize]; + for (size_t i = 0; i < size; i++) + newTags[i] = tags[i]; + delete[] tags; + tags = newTags; + size = newSize; + } + + public: + tag_map(std::initializer_list initial_tags) + { + size_t max = 0; + for (const auto& t : initial_tags) + max = std::max(max, hash(t)); + tags = new tag[(size = max + 1)]; + for (const auto& t : initial_tags) + insert(t); + } + + tag_map(const tag_map& copy) + { + tags = new tag[(size = copy.size)]; + for (size_t i = 0; i < size; i++) + tags[i] = copy.tags[i]; + } + + void insert(const tag& t) + { + auto h = hash(t); + //if (h > size) + // expand(); + if (!tags[h].tag.empty()) + std::cerr << "Tag not empty! " << tags[h].tag << "!!!\n"; + tags[h] = t; + } + + tag& operator[](const std::string& name) const + { + auto h = hash(tag{name, nullptr}); + if (h > size) + std::cerr << "Tag out of bounds"; + return tags[h]; + } + + ~tag_map() + { + delete[] tags; + tags = nullptr; + } + }; + + class LogFileWriter + { + private: + std::string m_path; + std::fstream* output = nullptr; + + public: + explicit LogFileWriter() = default; + + void writeLine(const std::string& path, const std::string& line) + { + if (path != m_path || output == nullptr) + { + clear(); + delete output; + output = new std::fstream(path, std::ios::out | std::ios::app); + if (!output->good()) + { + throw std::runtime_error("Unable to open console filestream!\n"); + } + } + if (!output->good()) + { + std::cerr << "There has been an error in the logging file stream!\n"; + output->clear(); + } + *output << line; + } + + void clear() + { + if (output != nullptr) + { + try + { + output->flush(); + output->close(); + } + catch (std::exception& e) + { + std::cerr << e.what() << "\n"; + } + } + } + + ~LogFileWriter() + { + clear(); + delete(output); + } + }; + +#ifdef WIN32 + #define BLT_NOW() auto t = std::time(nullptr); tm now{}; localtime_s(&now, &t) +#else +#define BLT_NOW() auto t = std::time(nullptr); auto now_ptr = std::localtime(&t); auto& now = *now_ptr +#endif + + //#define BLT_NOW() auto t = std::time(nullptr); tm now; localtime_s(&now, &t); //auto now = std::localtime(&t) +#define BLT_ISO_YEAR(S) auto S = std::to_string(now.tm_year + 1900); \ + S += '-'; \ + S += ensureHasDigits(now.tm_mon+1, 2); \ + S += '-'; \ + S += ensureHasDigits(now.tm_mday, 2); +#define BLT_CUR_TIME(S) auto S = ensureHasDigits(now.tm_hour, 2); \ + S += ':'; \ + S += ensureHasDigits(now.tm_min, 2); \ + S += ':'; \ + S += ensureHasDigits(now.tm_sec, 2); + + static inline std::string ensureHasDigits(int current, int digits) + { + std::string asString = std::to_string(current); + auto length = digits - asString.length(); + if (length <= 0) + return asString; + std::string zeros; + zeros.reserve(length); + for (unsigned int i = 0; i < length; i++) + { + zeros += '0'; + } + return zeros + asString; + } + + log_format loggingFormat{}; + hashmap loggingThreadNames; + hashmap> loggingStreamLines; + LogFileWriter writer; + + const std::unique_ptr tagMap = std::make_unique(tag_map{ + { + "YEAR", [](const tag_func_param&) -> std::string + { + BLT_NOW(); + return std::to_string(now.tm_year); + } + }, + { + "MONTH", [](const tag_func_param&) -> std::string + { + BLT_NOW(); + return ensureHasDigits(now.tm_mon + 1, 2); + } + }, + { + "DAY", [](const tag_func_param&) -> std::string + { + BLT_NOW(); + return ensureHasDigits(now.tm_mday, 2); + } + }, + { + "HOUR", [](const tag_func_param&) -> std::string + { + BLT_NOW(); + return ensureHasDigits(now.tm_hour, 2); + } + }, + { + "MINUTE", [](const tag_func_param&) -> std::string + { + BLT_NOW(); + return ensureHasDigits(now.tm_min, 2); + } + }, + { + "SECOND", [](const tag_func_param&) -> std::string + { + BLT_NOW(); + return ensureHasDigits(now.tm_sec, 2); + } + }, + { + "MS", [](const tag_func_param&) -> std::string + { + return std::to_string(std::chrono::duration_cast( + std::chrono::high_resolution_clock::now().time_since_epoch()).count() + ); + } + }, + { + "NS", [](const tag_func_param&) -> std::string + { + return std::to_string(std::chrono::duration_cast( + std::chrono::high_resolution_clock::now().time_since_epoch()).count() + ); + } + }, + { + "ISO_YEAR", [](const tag_func_param&) -> std::string + { + BLT_NOW(); + BLT_ISO_YEAR(returnStr); + return returnStr; + } + }, + { + "TIME", [](const tag_func_param&) -> std::string + { + BLT_NOW(); + BLT_CUR_TIME(returnStr); + return returnStr; + } + }, + { + "FULL_TIME", [](const tag_func_param&) -> std::string + { + BLT_NOW(); + BLT_ISO_YEAR(ISO); + BLT_CUR_TIME(TIME); + ISO += ' '; + ISO += TIME; + return ISO; + } + }, + { + "LF", [](const tag_func_param& f) -> std::string + { + return loggingFormat.levelColors[(int)f.level]; + } + }, + { + "ER", [](const tag_func_param&) -> std::string + { + return loggingFormat.levelColors[(int)log_level::ERROR]; + } + }, + { + "CNR", [](const tag_func_param& f) -> std::string + { + return f.level >= log_level::ERROR ? loggingFormat.levelColors[(int)log_level::ERROR] : ""; + } + }, + { + "RC", [](const tag_func_param&) -> std::string + { + return "\033[0m"; + } + }, + { + "LOG_LEVEL", [](const tag_func_param& f) -> std::string + { + return loggingFormat.levelNames[(int)f.level]; + } + }, + { + "THREAD_NAME", [](const tag_func_param&) -> std::string + { + if (loggingThreadNames.find(std::this_thread::get_id()) == loggingThreadNames.end()) + return "UNKNOWN"; + return loggingThreadNames[std::this_thread::get_id()]; + } + }, + { + "FILE", [](const tag_func_param& f) -> std::string + { + return f.file; + } + }, + { + "LINE", [](const tag_func_param& f) -> std::string + { + return f.line; + } + }, + { + "RAW_STR", [](const tag_func_param& f) -> std::string + { + return f.raw_string; + } + }, + { + "STR", [](const tag_func_param& f) -> std::string + { + return f.formatted_string; + } + } + }); + + static inline std::vector split(std::string s, const std::string& delim) + { + size_t pos = 0; + std::vector tokens; + while ((pos = s.find(delim)) != std::string::npos) + { + auto token = s.substr(0, pos); + tokens.push_back(token); + s.erase(0, pos + delim.length()); + } + tokens.push_back(s); + return tokens; + } + + inline std::string filename(const std::string& path) + { + if (loggingFormat.printFullFileName) + return path; + auto paths = split(path, "/"); + auto final = paths[paths.size() - 1]; + if (final == "/") + return paths[paths.size() - 2]; + return final; + } + + class string_parser + { + private: + std::string _str; + size_t _pos; + + public: + explicit string_parser(std::string str): _str(std::move(str)), _pos(0) + { + } + + inline char next() + { + return _str[_pos++]; + } + + [[nodiscard]] inline bool has_next() const + { + return _pos < _str.size(); + } + }; + + std::string stripANSI(const std::string& str) + { + string_parser parser(str); + std::string out; + while (parser.has_next()) + { + char c = parser.next(); + if (c == '\033') + { + while (parser.has_next() && parser.next() != 'm'); + } + else + out += c; + } + return out; + } + + void applyCFormatting(const std::string& format, std::string& output, std::va_list& args) + { + // args must be copied because they will be consumed by the first vsnprintf + va_list args_copy; + va_copy(args_copy, args); + + auto buffer_size = std::vsnprintf(nullptr, 0, format.c_str(), args_copy) + 1; + auto* buffer = new char[static_cast(buffer_size)]; + + vsnprintf(buffer, buffer_size, format.c_str(), args); + output = std::string(buffer); + + delete[] buffer; + + va_end(args_copy); + } + + /** + * Checks if the next character in the parser is a tag opening, if not output the buffer to the out string + */ + inline bool tagOpening(string_parser& parser, std::string& out) + { + char c = ' '; + if (parser.has_next() && (c = parser.next()) == '{') + if (parser.has_next() && (c = parser.next()) == '{') + return true; + else + out += c; + else + out += c; + return false; + } + + void parseString(string_parser& parser, std::string& out, const std::string& userStr, log_level level, const char* file, int line) + { + while (parser.has_next()) + { + char c = parser.next(); + std::string nonTag; + if (c == '$' && tagOpening(parser, nonTag)) + { + std::string tag; + while (parser.has_next()) + { + c = parser.next(); + if (c == '}') + break; + tag += c; + } + c = parser.next(); + if (parser.has_next() && c != '}') + { + std::cerr << "Error processing tag, is not closed with two '}'!\n"; + break; + } + if (loggingFormat.ensureAlignment && tag == "STR") + { + auto currentOutputWidth = out.size(); + auto& longestWidth = loggingFormat.currentWidth; + longestWidth = std::max(longestWidth, currentOutputWidth); + // pad with spaces + if (currentOutputWidth != longestWidth) + { + for (size_t i = currentOutputWidth; i < longestWidth; i++) + out += ' '; + } + } + tag_func_param param{ + level, filename({file}), std::to_string(line), userStr, userStr + }; + out += (*tagMap)[tag].func(param); + } + else + { + out += c; + out += nonTag; + } + } + } + + std::string applyFormatString(const std::string& str, log_level level, const char* file, int line) + { + // this can be speedup by preprocessing the string into an easily callable class + // where all the variables are ready to be substituted in one step + // and all static information already entered + string_parser parser(loggingFormat.logOutputFormat); + std::string out; + parseString(parser, out, str, level, file, line); + + return out; + } + + void log_internal(const std::string& format, log_level level, const char* file, int line, std::va_list& args) + { + std::string withoutLn = format; + auto len = withoutLn.length(); + + if (len > 0 && withoutLn[len - 1] == '\n') + withoutLn = withoutLn.substr(0, len - 1); + + std::string out; + + applyCFormatting(withoutLn, out, args); + + if (level == log_level::NONE) + { + std::cout << out << std::endl; + return; + } + + std::string finalFormattedOutput = applyFormatString(out, level, file, line); + + if (loggingFormat.logToConsole) + std::cout << finalFormattedOutput; + + + if (loggingFormat.logToFile) + { + string_parser parser(loggingFormat.logFileName); + std::string fileName; + parseString(parser, fileName, withoutLn, level, file, line); + + auto path = loggingFormat.logFilePath; + if (!path.empty() && path[path.length() - 1] != '/') + path += '/'; + + // if the file has changed (new day in default case) we should reset the rollover count + if (loggingFormat.lastFile != fileName) + { + loggingFormat.currentRollover = 0; + loggingFormat.lastFile = fileName; + } + + path += fileName; + path += '-'; + path += std::to_string(loggingFormat.currentRollover); + path += ".log"; + + if (std::filesystem::exists(path)) + { + auto fileSize = std::filesystem::file_size(path); + + // will start on next file + if (fileSize > loggingFormat.logMaxFileSize) + loggingFormat.currentRollover++; + } + + writer.writeLine(path, stripANSI(finalFormattedOutput)); + } + //std::cout.flush(); + } + + void log_stream_internal(const std::string& str, const logger& logger) + { + auto& s = loggingStreamLines[std::this_thread::get_id()][logger.level]; + // s += str; + for (char c : str) + { + s += c; + if (c == '\n') + { + log(s, logger.level, logger.file, logger.line); + s = ""; + } + } + } + + void setThreadName(const std::string& name) + { + loggingThreadNames[std::this_thread::get_id()] = name; + } + + void setLogFormat(const log_format& format) + { + loggingFormat = format; + } + + void setLogColor(log_level level, const std::string& newFormat) + { + loggingFormat.levelColors[(int)level] = newFormat; + } + + void setLogName(log_level level, const std::string& newFormat) + { + loggingFormat.levelNames[(int)level] = newFormat; + } + + void setLogOutputFormat(const std::string& newFormat) + { + loggingFormat.logOutputFormat = newFormat; + } + + void setLogToFile(bool shouldLogToFile) + { + loggingFormat.logToFile = shouldLogToFile; + } + + void setLogToConsole(bool shouldLogToConsole) + { + loggingFormat.logToConsole = shouldLogToConsole; + } + + void setLogPath(const std::string& path) + { + loggingFormat.logFilePath = path; + } + + void setLogFileName(const std::string& fileName) + { + loggingFormat.logFileName = fileName; + } + + void setMaxFileSize(const size_t fileSize) + { + loggingFormat.logMaxFileSize = fileSize; + } + + void flush() + { + std::cerr.flush(); + std::cout.flush(); + } +} diff --git a/tests/logger_tests.cpp b/tests/logger_tests.cpp index 64cccb2..bf815c5 100644 --- a/tests/logger_tests.cpp +++ b/tests/logger_tests.cpp @@ -57,27 +57,32 @@ This is a println with boolean as hex 0x1 This is a println with boolean as octal 1 This is a println with alignment left 64 end value This is a println with alignment right 46 end value -This is a println with alignment left (fill) 46******** end value +This is a println with alignment left (fill) 46******** end value This is a println with alignment right (fill) ********46 end value +This is a println with alignment right (fill with reserved character) ^^^^^^^^46 end value +This is a println with fill no alignment %%%%%%%%%%%%%%%%%%46 end value +This is a println with arg reference 46.02 +This is a println with arg reference &&&&&&&&&&&&&&&&&&&& )"); std::pair compare_strings(const std::string& s1, const std::string& s2) { - if (s1.size() != s2.size()) - return {false, "Strings size do not match '" + std::to_string(s1.size()) + "' vs '" + std::to_string(s2.size()) + "'"}; + const auto size = std::min(s1.size(), s2.size()); size_t index = 0; - for (; index < s1.size(); ++index) + for (; index < size; ++index) { if (s1[index] != s2[index]) { std::stringstream ss; const auto i1 = std::max(static_cast(index) - 32, 0l); - const auto l1 = std::min(static_cast(s1.size()) - i1, 65l); + const auto l1 = std::min(static_cast(size) - i1, 65l); ss << "Strings differ at index " << index << "!\n"; ss << "'" << s1.substr(i1, l1) << "' vs '" << s2.substr(i1, l1) << "'" << std::endl; return {false, ss.str()}; } } + if (s1.size() != s2.size()) + return {false, "Strings size do not match '" + std::to_string(s1.size()) + "' vs '" + std::to_string(s2.size()) + "'"}; return {true, ""}; } @@ -116,8 +121,12 @@ int main() blt::logging::println(ss, "This is a println with boolean as octal {:o}", true); blt::logging::println(ss, "This is a println with alignment left {:<10} end value", 64); blt::logging::println(ss, "This is a println with alignment right {:>10} end value", 46); - blt::logging::println(ss, "This is a println with alignment left (fill) {:*<10} end value", 46); + blt::logging::println(ss, "This is a println with alignment left (fill) {:*<10} end value", 46); blt::logging::println(ss, "This is a println with alignment right (fill) {:*>10} end value", 46); + blt::logging::println(ss, "This is a println with alignment right (fill with reserved character) {:\\^>10} end value", 46); + blt::logging::println(ss, "This is a println with fill no alignment {:%20} end value", 46); + blt::logging::println(ss, "This is a println with arg reference {0:{1}.{2}f}", 46.0232, 20, 2); + blt::logging::println(ss, "This is a println with arg reference {0:&{1}}", "", 20); blt::logging::print(ss.str()); auto [passed, error_msg] = compare_strings(expected_str, ss.str()); BLT_ASSERT_MSG(passed && "Logger logged string doesn't match precomputed expected string!", error_msg.c_str()); From 08e19c067e57c92c67a98946a700b680cef95e06 Mon Sep 17 00:00:00 2001 From: Brett Date: Wed, 5 Mar 2025 01:55:14 -0500 Subject: [PATCH 057/101] ansi --- CMakeLists.txt | 2 +- include/blt/logging/ansi.h | 420 +++++++++++++++++++++++----------- include/blt/logging/logging.h | 10 +- libraries/parallel-hashmap | 2 +- tests/logger_tests.cpp | 146 +++++++----- 5 files changed, 382 insertions(+), 198 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6480ed1..b0e6bde 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.1.10) +set(BLT_VERSION 5.1.11) set(BLT_TARGET BLT) diff --git a/include/blt/logging/ansi.h b/include/blt/logging/ansi.h index 1aa7c83..1113332 100644 --- a/include/blt/logging/ansi.h +++ b/include/blt/logging/ansi.h @@ -19,7 +19,10 @@ #ifndef BLT_LOGGING_COLORS_H #define BLT_LOGGING_COLORS_H +#include +#include #include +#include #include #define BLT_ANSI_ESCAPE "\x1B" @@ -30,155 +33,308 @@ // https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797 namespace blt::logging::ansi { - namespace color - { - enum class color_mode : i32 - { - RESET_ALL = 0, - BOLD = 1, - DIM = 2, - ITALIC = 3, - UNDERLINE = 4, - BLINK = 5, - REVERSE = 7, - HIDDEN = 8, - STRIKE_THROUGH = 9, - }; + namespace color + { + inline std::array reset_sequences = {0, 22, 22, 23, 24, 25, 26, 27, 28, 29}; - enum class color8 : i32 - { - BLACK = 0, - RED = 1, - GREEN = 2, - YELLOW = 3, - BLUE = 4, - MAGENTA = 5, - CYAN = 6, - WHITE = 7, - DEFAULT = 9 - }; + enum class color_mode : u8 + { + RESET_ALL = 0, + BOLD = 1, + DIM = 2, + ITALIC = 3, + UNDERLINE = 4, + BLINK = 5, + REVERSE = 7, + HIDDEN = 8, + STRIKE_THROUGH = 9, }; - enum class color8_bright : i32 - { - BLACK = 0, - RED = 1, - GREEN = 2, - YELLOW = 3, - BLUE = 4, - MAGENTA = 5, - CYAN = 6, - WHITE = 7, - }; + enum class color8 : u8 + { + BLACK = 0, + RED = 1, + GREEN = 2, + YELLOW = 3, + BLUE = 4, + MAGENTA = 5, + CYAN = 6, + WHITE = 7, + DEFAULT = 9 + }; - namespace detail - { - template - struct color_converter - { - }; + enum class color8_bright : u8 + { + BLACK = 0, + RED = 1, + GREEN = 2, + YELLOW = 3, + BLUE = 4, + MAGENTA = 5, + CYAN = 6, + WHITE = 7, }; - template <> - struct color_converter - { - static std::string to_string(color8 color, const bool background) - { - return (background ? "4" : "3") + std::to_string(static_cast(color)); - } - }; + struct rgb_t + { + u8 r, g, b; + }; - template <> - struct color_converter - { - static std::string to_string(color8_bright color, const bool background) - { - return (background ? "10" : "9") + std::to_string(static_cast(color)); - } - }; - } - } + struct color256 + { + explicit color256(u8 index) : color(index) + {} - namespace general - { - inline const std::string bell = "\x07"; - inline const std::string bs = "\x08"; - inline const std::string horizontal_tab = "\x09"; - inline const std::string linefeed = "\x0A"; - inline const std::string vertical_tab = "\x0B"; - inline const std::string form_feed = "\x0C"; - inline const std::string carriage_return = "\x0D"; - inline const std::string escape = BLT_ANSI_ESCAPE; - inline const std::string del = "\0x7F"; - inline const std::string csi = BLT_ANSI_CSI; - inline const std::string dsc = BLT_ANSI_DSC; - inline const std::string osc = BLT_ANSI_OSC; - } + color256(const u8 r, const u8 g, const u8 b) : color(rgb_t{r, g, b}) + { + if (r > 5) + throw std::invalid_argument("r must be between 0 and 5"); + if (g > 5) + throw std::invalid_argument("g must be between 0 and 5"); + if (b > 5) + throw std::invalid_argument("b must be between 0 and 5"); + } - namespace cursor - { - inline const std::string home = BLT_ANSI_CSI "H"; + [[nodiscard]] u8 index() const + { + if (std::holds_alternative(color)) + return std::get(color); + const auto [r, g, b] = std::get(color); + return (r * 36) + (g * 6) + b + 16; + } - template - inline std::string move_to(i64 line, i64 column) - { - if constexpr (UseH) - return std::string(BLT_ANSI_CSI) + std::to_string(line) + ";" + std::to_string(column) + "H"; - else - return std::string(BLT_ANSI_CSI) + std::to_string(line) + ";" + std::to_string(column) + "f"; - } + private: + std::variant color; + }; - inline std::string move_up(i64 lines) - { - return std::string(BLT_ANSI_CSI) + std::to_string(lines) + "A"; - } + struct color_rgb + { + color_rgb(const u8 r, const u8 g, const u8 b) : color(rgb_t{r, g, b}) + {} - inline std::string move_down(i64 lines) - { - return std::string(BLT_ANSI_CSI) + std::to_string(lines) + "B"; - } + rgb_t color; + }; - inline std::string move_right(i64 columns) - { - return std::string(BLT_ANSI_CSI) + std::to_string(columns) + "C"; - } + namespace detail + { + template + struct color_holder + { + using value_type = T; - inline std::string move_left(i64 columns) - { - return std::string(BLT_ANSI_CSI) + std::to_string(columns) + "D"; - } + T color; + bool alt = false; + }; - inline std::string move_begin_down(i64 lines) - { - return std::string(BLT_ANSI_CSI) + std::to_string(lines) + "E"; - } + template + struct color_converter + {}; - inline std::string move_begin_up(i64 lines) - { - return std::string(BLT_ANSI_CSI) + std::to_string(lines) + "F"; - } + template <> + struct color_converter + { + static std::string to_string(const color_holder color) + { + return (color.alt ? "4" : "3") + std::to_string(static_cast(color.color)); + } + }; - inline std::string move_to(i64 column) - { - return std::string(BLT_ANSI_CSI) + std::to_string(column) + "G"; - } + template <> + struct color_converter + { + static std::string to_string(const color_holder color) + { + return (color.alt ? "10" : "9") + std::to_string(static_cast(color.color)); + } + }; - inline const std::string request_cursor_position = BLT_ANSI_CSI "6n"; - inline const std::string move_up_one_line = BLT_ANSI_ESCAPE " M"; - inline const std::string save_cursor_position_dec = BLT_ANSI_ESCAPE " 7"; - inline const std::string resotre_cursor_position_dec = BLT_ANSI_ESCAPE " 8"; - inline const std::string save_cursor_position_sco = BLT_ANSI_CSI "s"; - inline const std::string resotre_cursor_position_sco = BLT_ANSI_CSI "u"; - } + template <> + struct color_converter + { + static std::string to_string(const color_holder color) + { + return color.alt ? std::to_string(reset_sequences[static_cast(color.color)]) : std::to_string(static_cast(color.color)); + } + }; - namespace erase - { - inline const std::string to_end_of_screen = BLT_ANSI_CSI "0J"; - inline const std::string from_begin_of_screen = BLT_ANSI_CSI "1J"; - inline const std::string entire_screen = BLT_ANSI_CSI "2J"; - inline const std::string saved_lines = BLT_ANSI_CSI "3J"; - inline const std::string to_end_of_line = BLT_ANSI_CSI "0K"; - inline const std::string from_begin_of_line = BLT_ANSI_CSI "1K"; - inline const std::string entire_line = BLT_ANSI_CSI "2K"; - } + template <> + struct color_converter + { + static std::string to_string(const color_holder color) + { + return (color.alt ? "48;5;" : "38;5;") + std::to_string(color.color.index()); + } + }; + + template <> + struct color_converter + { + static std::string to_string(const color_holder color) + { + return (color.alt ? "48;2;" : "38;2;") + std::to_string(color.color.color.r) + ";" + std::to_string(color.color.color.g) + ";" + + std::to_string(color.color.color.b); + } + }; + + template + struct ensure_holder + { + static color_holder make(const T& color) + { + return color_holder{color, false}; + } + }; + + template + struct ensure_holder> + { + static color_holder make(const color_holder& color) + { + return color; + } + }; + + template + struct decay + { + using type = std::decay_t; + }; + + template + struct decay> + { + using type = std::decay_t; + }; + + template + using decay_t = typename decay::type; + } + + template + auto fg(const T& color) + { + return detail::color_holder{color, false}; + } + + template + auto bg(const T& color) + { + return detail::color_holder{color, true}; + } + + template + std::string build(const Args&... args) + { + std::string result = BLT_ANSI_CSI; + ((result += detail::color_converter>::to_string(detail::ensure_holder::make(args)), result += ';'), ...); + return result.substr(0, result.size() - 1) + "m"; + } + } + + namespace general + { + inline const std::string bell = "\x07"; + inline const std::string bs = "\x08"; + inline const std::string horizontal_tab = "\x09"; + inline const std::string linefeed = "\x0A"; + inline const std::string vertical_tab = "\x0B"; + inline const std::string form_feed = "\x0C"; + inline const std::string carriage_return = "\x0D"; + inline const std::string escape = BLT_ANSI_ESCAPE; + inline const std::string del = "\x7F"; + inline const std::string csi = BLT_ANSI_CSI; + inline const std::string dsc = BLT_ANSI_DSC; + inline const std::string osc = BLT_ANSI_OSC; + } + + namespace cursor + { + inline const std::string home = BLT_ANSI_CSI "H"; + + template + std::string move_to(const i64 line, const i64 column) + { + if constexpr (UseH) + return std::string(BLT_ANSI_CSI) + std::to_string(line) + ";" + std::to_string(column) + "H"; + else + return std::string(BLT_ANSI_CSI) + std::to_string(line) + ";" + std::to_string(column) + "f"; + } + + inline std::string move_up(const i64 lines) + { + return std::string(BLT_ANSI_CSI) + std::to_string(lines) + "A"; + } + + inline std::string move_down(const i64 lines) + { + return std::string(BLT_ANSI_CSI) + std::to_string(lines) + "B"; + } + + inline std::string move_right(const i64 columns) + { + return std::string(BLT_ANSI_CSI) + std::to_string(columns) + "C"; + } + + inline std::string move_left(const i64 columns) + { + return std::string(BLT_ANSI_CSI) + std::to_string(columns) + "D"; + } + + inline std::string move_begin_down(const i64 lines) + { + return std::string(BLT_ANSI_CSI) + std::to_string(lines) + "E"; + } + + inline std::string move_begin_up(const i64 lines) + { + return std::string(BLT_ANSI_CSI) + std::to_string(lines) + "F"; + } + + inline std::string move_to(const i64 column) + { + return std::string(BLT_ANSI_CSI) + std::to_string(column) + "G"; + } + + inline const std::string request_cursor_position = BLT_ANSI_CSI "6n"; + inline const std::string move_up_one_line = BLT_ANSI_ESCAPE " M"; + inline const std::string save_cursor_position_dec = BLT_ANSI_ESCAPE " 7"; + inline const std::string restore_cursor_position_dec = BLT_ANSI_ESCAPE " 8"; + inline const std::string save_cursor_position_sco = BLT_ANSI_CSI "s"; + inline const std::string restore_cursor_position_sco = BLT_ANSI_CSI "u"; + } + + namespace erase + { + inline const std::string to_end_of_screen = BLT_ANSI_CSI "0J"; + inline const std::string from_begin_of_screen = BLT_ANSI_CSI "1J"; + inline const std::string entire_screen = BLT_ANSI_CSI "2J"; + inline const std::string saved_lines = BLT_ANSI_CSI "3J"; + inline const std::string to_end_of_line = BLT_ANSI_CSI "0K"; + inline const std::string from_begin_of_line = BLT_ANSI_CSI "1K"; + inline const std::string entire_line = BLT_ANSI_CSI "2K"; + } + + enum class mode : u8 + { + mono40x25_text = 0, + color40x25_text = 1, + mono80x25_text = 2, + color80x25_text = 3, + color320x200_4color_graphics = 4, + mono320x200_graphics = 5, + mono640x200_graphics = 6, + line_wrapping = 7, + color320x200_graphics = 13, + color640x200_16color_graphics = 14, + mono640x350_2color_graphics = 15, + color640x350_16color_graphics = 16, + mono640x480_2color_graphics = 17, + color640x480_16color_graphics = 18, + color320_200_256color_graphics = 19 + }; + + inline std::string use_mode(const mode mode) + { + return std::string(BLT_ANSI_CSI) + "=" + std::to_string(static_cast(mode)) + "h"; + } } #endif //BLT_LOGGING_COLORS_H diff --git a/include/blt/logging/logging.h b/include/blt/logging/logging.h index f2d1b3c..f8789d4 100644 --- a/include/blt/logging/logging.h +++ b/include/blt/logging/logging.h @@ -130,8 +130,14 @@ namespace blt::logging default: handle_type(stream, type); if constexpr (blt::meta::is_streamable_v) - stream << t; - else + { + if constexpr (std::is_same_v || std::is_same_v) + stream << static_cast(t); + else if constexpr (std::is_same_v) + stream << static_cast(t); + else + stream << t; + }else stream << "{INVALID TYPE}"; } }; diff --git a/libraries/parallel-hashmap b/libraries/parallel-hashmap index 7ef2e73..93201da 160000 --- a/libraries/parallel-hashmap +++ b/libraries/parallel-hashmap @@ -1 +1 @@ -Subproject commit 7ef2e733416953b222851f9a360d7fc72d068ee5 +Subproject commit 93201da2ba5a6aba0a6e57ada64973555629b3e3 diff --git a/tests/logger_tests.cpp b/tests/logger_tests.cpp index bf815c5..0f318cd 100644 --- a/tests/logger_tests.cpp +++ b/tests/logger_tests.cpp @@ -17,13 +17,13 @@ */ #include #include +#include #include #include #include struct some_silly_type_t -{ -}; +{}; auto expected_str = std::string(R"(This is a println! This is a println with args '42' @@ -67,69 +67,91 @@ This is a println with arg reference &&&&&&&&&&&&&&&&&&&& std::pair compare_strings(const std::string& s1, const std::string& s2) { - const auto size = std::min(s1.size(), s2.size()); - size_t index = 0; - for (; index < size; ++index) - { - if (s1[index] != s2[index]) - { - std::stringstream ss; - const auto i1 = std::max(static_cast(index) - 32, 0l); - const auto l1 = std::min(static_cast(size) - i1, 65l); - ss << "Strings differ at index " << index << "!\n"; - ss << "'" << s1.substr(i1, l1) << "' vs '" << s2.substr(i1, l1) << "'" << std::endl; - return {false, ss.str()}; - } - } - if (s1.size() != s2.size()) - return {false, "Strings size do not match '" + std::to_string(s1.size()) + "' vs '" + std::to_string(s2.size()) + "'"}; - return {true, ""}; + const auto size = std::min(s1.size(), s2.size()); + size_t index = 0; + for (; index < size; ++index) + { + if (s1[index] != s2[index]) + { + std::stringstream ss; + const auto i1 = std::max(static_cast(index) - 32, 0l); + const auto l1 = std::min(static_cast(size) - i1, 65l); + ss << "Strings differ at index " << index << "!\n"; + ss << "'" << s1.substr(i1, l1) << "' vs '" << s2.substr(i1, l1) << "'" << std::endl; + return {false, ss.str()}; + } + } + if (s1.size() != s2.size()) + return {false, "Strings size do not match '" + std::to_string(s1.size()) + "' vs '" + std::to_string(s2.size()) + "'"}; + return {true, ""}; } int main() { - std::stringstream ss; - blt::logging::println(ss, "This is a println!"); - blt::logging::println(ss, "This is a println with args '{}'", 42); - blt::logging::println(ss, "This is a println with multiple args '{}' '{:.100}' '{}'", 42, 32.34231233f, "Hello World!"); - blt::logging::println(ss, "This is a '{1}' fmt string with positionals '{0}'", "I am a string!", "Well so am I except cooler :3"); - blt::logging::println(ss, "This is a println with a sign {:+}", 4120); - blt::logging::println(ss, "This is a println with a sign {:+}", -4120); - blt::logging::println(ss, "This is a println with a space {: }", 4120); - blt::logging::println(ss, "This is a println with a space {: }", -4120); - blt::logging::println(ss, "This is a println with a minus {:-}", 4120); - blt::logging::println(ss, "This is a println with a minus {:-}", -4120); - blt::logging::println(ss, "This is a println with a with {:10}", 4120); - blt::logging::println(ss, "This is a println with a with leading zeros {:010}", 4120); - blt::logging::println(ss, "This is a println with a precision {:.10f}", 42.232342349); - blt::logging::println(ss, "This is a println with hex {:.10x}", 4250); - blt::logging::println(ss, "This is a println with hex with leading {:#.10x}", 4250); - blt::logging::println(ss, "This is a println with binary {:#b}", 6969420); - blt::logging::println(ss, "This is a println with binary with space {: #b}", 6969421); - blt::logging::println(ss, "This is a println with binary with space {: b}", 69); - blt::logging::println(ss, "This is a println with octal {:#o}", 6669); - blt::logging::println(ss, "This is a println with hexfloat {:a}", 402.4320); - blt::logging::println(ss, "This is a println with exponent {:e}", 44320902.4320); - blt::logging::println(ss, "This is a println with exponent {:e}", 9532434234042340.0); - blt::logging::println(ss, "This is a println with general {:g}", 953243.49); - blt::logging::println(ss, "This is a println with general {:g}", 953243324023403240.49); - blt::logging::println(ss, "This is a println with a char {:c}", 66); - blt::logging::println(ss, "This is a println with type {:t}", some_silly_type_t{}); - blt::logging::println(ss, "This is a println with boolean {}", true); - blt::logging::println(ss, "This is a println with boolean as int {:d}", false); - blt::logging::println(ss, "This is a println with boolean as hex {:#x}", true); - blt::logging::println(ss, "This is a println with boolean as octal {:o}", true); - blt::logging::println(ss, "This is a println with alignment left {:<10} end value", 64); - blt::logging::println(ss, "This is a println with alignment right {:>10} end value", 46); - blt::logging::println(ss, "This is a println with alignment left (fill) {:*<10} end value", 46); - blt::logging::println(ss, "This is a println with alignment right (fill) {:*>10} end value", 46); - blt::logging::println(ss, "This is a println with alignment right (fill with reserved character) {:\\^>10} end value", 46); - blt::logging::println(ss, "This is a println with fill no alignment {:%20} end value", 46); - blt::logging::println(ss, "This is a println with arg reference {0:{1}.{2}f}", 46.0232, 20, 2); - blt::logging::println(ss, "This is a println with arg reference {0:&{1}}", "", 20); - blt::logging::print(ss.str()); - auto [passed, error_msg] = compare_strings(expected_str, ss.str()); - BLT_ASSERT_MSG(passed && "Logger logged string doesn't match precomputed expected string!", error_msg.c_str()); + std::stringstream ss; + blt::logging::println(ss, "This is a println!"); + blt::logging::println(ss, "This is a println with args '{}'", 42); + blt::logging::println(ss, "This is a println with multiple args '{}' '{:.100}' '{}'", 42, 32.34231233f, "Hello World!"); + blt::logging::println(ss, "This is a '{1}' fmt string with positionals '{0}'", "I am a string!", "Well so am I except cooler :3"); + blt::logging::println(ss, "This is a println with a sign {:+}", 4120); + blt::logging::println(ss, "This is a println with a sign {:+}", -4120); + blt::logging::println(ss, "This is a println with a space {: }", 4120); + blt::logging::println(ss, "This is a println with a space {: }", -4120); + blt::logging::println(ss, "This is a println with a minus {:-}", 4120); + blt::logging::println(ss, "This is a println with a minus {:-}", -4120); + blt::logging::println(ss, "This is a println with a with {:10}", 4120); + blt::logging::println(ss, "This is a println with a with leading zeros {:010}", 4120); + blt::logging::println(ss, "This is a println with a precision {:.10f}", 42.232342349); + blt::logging::println(ss, "This is a println with hex {:.10x}", 4250); + blt::logging::println(ss, "This is a println with hex with leading {:#.10x}", 4250); + blt::logging::println(ss, "This is a println with binary {:#b}", 6969420); + blt::logging::println(ss, "This is a println with binary with space {: #b}", 6969421); + blt::logging::println(ss, "This is a println with binary with space {: b}", 69); + blt::logging::println(ss, "This is a println with octal {:#o}", 6669); + blt::logging::println(ss, "This is a println with hexfloat {:a}", 402.4320); + blt::logging::println(ss, "This is a println with exponent {:e}", 44320902.4320); + blt::logging::println(ss, "This is a println with exponent {:e}", 9532434234042340.0); + blt::logging::println(ss, "This is a println with general {:g}", 953243.49); + blt::logging::println(ss, "This is a println with general {:g}", 953243324023403240.49); + blt::logging::println(ss, "This is a println with a char {:c}", 66); + blt::logging::println(ss, "This is a println with type {:t}", some_silly_type_t{}); + blt::logging::println(ss, "This is a println with boolean {}", true); + blt::logging::println(ss, "This is a println with boolean as int {:d}", false); + blt::logging::println(ss, "This is a println with boolean as hex {:#x}", true); + blt::logging::println(ss, "This is a println with boolean as octal {:o}", true); + blt::logging::println(ss, "This is a println with alignment left {:<10} end value", 64); + blt::logging::println(ss, "This is a println with alignment right {:>10} end value", 46); + blt::logging::println(ss, "This is a println with alignment left (fill) {:*<10} end value", 46); + blt::logging::println(ss, "This is a println with alignment right (fill) {:*>10} end value", 46); + blt::logging::println(ss, "This is a println with alignment right (fill with reserved character) {:\\^>10} end value", 46); + blt::logging::println(ss, "This is a println with fill no alignment {:%20} end value", 46); + blt::logging::println(ss, "This is a println with arg reference {0:{1}.{2}f}", 46.0232, 20, 2); + blt::logging::println(ss, "This is a println with arg reference {0:&{1}}", "", 20); + blt::logging::print(ss.str()); + auto [passed, error_msg] = compare_strings(expected_str, ss.str()); + BLT_ASSERT_MSG(passed && "Logger logged string doesn't match precomputed expected string!", error_msg.c_str()); - // blt::logging::println("This is println {}\twith a std::endl in the middle of it"); + namespace ansi = blt::logging::ansi; + namespace color = ansi::color; + + for (blt::u8 r = 0; r < 6; r++) + { + for (blt::u8 g = 0; g < 6; g++) + { + for (blt::u8 b = 0; b < 6; b++) + { + blt::logging::println("{}This is a println with a color {:#3x} {:#3x} {:#3x}{}", + build(fg(color::color256{r, g, b}), bg(color::color256{ + static_cast(5 - r), + static_cast(5 - g), + static_cast(5 - b) + })), r, g, b, build(color::color_mode::RESET_ALL)); + } + } + } + blt::logging::println("{}This is a color now with background{}", + build(color::color_mode::BOLD, fg(color::color8::CYAN), color::color_mode::DIM, bg(color::color8::YELLOW)), + build(color::color_mode::RESET_ALL)); + + // blt::logging::println("This is println {}\twith a std::endl in the middle of it"); } From c4a29ade1964fecbfdb3a69ec005f903b2e0bdae Mon Sep 17 00:00:00 2001 From: Brett Date: Thu, 6 Mar 2025 01:09:44 -0500 Subject: [PATCH 058/101] ansi test --- CMakeLists.txt | 2 +- tests/logger_tests.cpp | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b0e6bde..3606e16 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.1.11) +set(BLT_VERSION 5.1.12) set(BLT_TARGET BLT) diff --git a/tests/logger_tests.cpp b/tests/logger_tests.cpp index 0f318cd..a120b32 100644 --- a/tests/logger_tests.cpp +++ b/tests/logger_tests.cpp @@ -21,6 +21,9 @@ #include #include #include +#include +#include + struct some_silly_type_t {}; @@ -153,5 +156,21 @@ int main() build(color::color_mode::BOLD, fg(color::color8::CYAN), color::color_mode::DIM, bg(color::color8::YELLOW)), build(color::color_mode::RESET_ALL)); + std::cout << "\033[2J"; + + constexpr int totalRows = 24; + std::cout << "\033[1;" << (totalRows - 1) << "r"; + + for (int i = 1; i <= 10; ++i) + { + std::cout << "\033[1;1H"; + std::cout << "printed line " << i << std::endl; + std::cout << "\033[" << totalRows << ";1H"; + std::cout << "[----status----]" << std::flush; + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + } + + std::cout << "\033[r"; + // blt::logging::println("This is println {}\twith a std::endl in the middle of it"); } From dad09f5f1b945fad89478ec148b9e027a723ed08 Mon Sep 17 00:00:00 2001 From: Brett Date: Thu, 6 Mar 2025 15:15:25 -0500 Subject: [PATCH 059/101] free fun --- CMakeLists.txt | 2 +- include/blt/fs/filesystem.h | 176 ++++++--------------------- include/blt/fs/fwddecl.h | 77 ++++++++++++ include/blt/fs/nbt.h | 56 ++++----- include/blt/logging/fmt_tokenizer.h | 2 +- include/blt/logging/fwddecl.h | 35 ++++++ include/blt/logging/logging.h | 7 +- include/blt/logging/logging_config.h | 64 +++++----- src/blt/fs/filesystem.cpp | 78 ++++-------- src/blt/fs/nbt.cpp | 4 +- src/blt/logging/logging_config.cpp | 1 + tests/logger_tests.cpp | 59 ++++++++- 12 files changed, 301 insertions(+), 260 deletions(-) create mode 100644 include/blt/fs/fwddecl.h create mode 100644 include/blt/logging/fwddecl.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 3606e16..a7c203c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.1.12) +set(BLT_VERSION 5.1.13) set(BLT_TARGET BLT) diff --git a/include/blt/fs/filesystem.h b/include/blt/fs/filesystem.h index 17bdbd2..8695f31 100644 --- a/include/blt/fs/filesystem.h +++ b/include/blt/fs/filesystem.h @@ -19,145 +19,49 @@ #ifndef BLT_FILESYSTEM_H #define BLT_FILESYSTEM_H -#include -#include +#include +#include namespace blt::fs { - /** - * A simple interface which provides a way of reading the next block of data from a resource. - * The interface provides a single function "read" which will read a specified number of bytes into the buffer. - * The implementation for this could be fstreams, zlib deflate, or any method filesystem access. - * Reading of a large number of bytes ( > block size) is guaranteed to not significantly increase the read time and will likely result in a - * direct passthrough to the underlying system. Small reads will be buffered, hence the name "block" reader. - */ - class block_reader - { - protected: - // 32768 block size seems the fastest on my system - unsigned long m_bufferSize; - public: - explicit block_reader(size_t bufferSize): m_bufferSize(bufferSize) - {} - - /** - * Reads bytes from the internal filesystem implementation - * @param buffer buffer to copy the read bytes into - * @param bytes number of bytes to read - * @return status code. non-zero return codes indicates a failure has occurred. - */ - virtual int read(char* buffer, size_t bytes) = 0; - - virtual size_t gcount() = 0; - - virtual char get() - { - char c[1]; - read(c, 1); - return c[0]; - } - }; - - /** - * A buffered block writer without a definite backend implementation. Exactly the same as a block_reader but for writing to the filesystem. - */ - class block_writer - { - protected: - unsigned long m_bufferSize; - public: - explicit block_writer(unsigned long bufferSize): m_bufferSize(bufferSize) - {} - - /** - * Writes the bytes to the filesystem backend implementation - * @param buffer bytes to write - * @param bytes number of bytes to write - * @return non-zero code if failure - */ - virtual int write(char* buffer, size_t bytes) = 0; - - virtual int put(char c) - { - char a[1]; - a[0] = c; - return write(a, 1); - } - - /** - * Ensures that the internal buffer is written to the filesystem. - */ - virtual void flush() = 0; - }; - - /** - * fstream implementation of the block reader. - */ - class fstream_block_reader : public block_reader - { - private: - std::fstream& m_stream; - char* m_buffer = nullptr; - size_t readIndex = 0; - public: - explicit fstream_block_reader(std::fstream& stream, size_t bufferSize = 131072); - - explicit fstream_block_reader(fstream_block_reader& copy) = delete; - - explicit fstream_block_reader(fstream_block_reader&& move) = delete; - - fstream_block_reader& operator=(const fstream_block_reader& copy) = delete; - - fstream_block_reader& operator=(const fstream_block_reader&& move) = delete; - - int read(char* buffer, size_t bytes) override; - - size_t gcount() final - { - return m_stream.gcount(); - } - - ~fstream_block_reader() - { - delete[] m_buffer; - } - }; - - class fstream_block_writer : public block_writer - { - private: - std::fstream& m_stream; - char* m_buffer; - size_t writeIndex = 0; - - void flush_internal(); - - public: - explicit fstream_block_writer(std::fstream& stream, size_t bufferSize = 131072): - block_writer(bufferSize), m_stream(stream), m_buffer(new char[bufferSize]) - {} - - explicit fstream_block_writer(fstream_block_writer& copy) = delete; - - explicit fstream_block_writer(fstream_block_writer&& move) = delete; - - fstream_block_writer& operator=(const fstream_block_writer& copy) = delete; - - fstream_block_writer& operator=(const fstream_block_writer&& move) = delete; - - int write(char* buffer, size_t bytes) override; - - inline void flush() override - { - flush_internal(); - } - - ~fstream_block_writer() - { - flush_internal(); - delete[] m_buffer; - } - }; + /** + * reader_t wrapper for fstream + */ + class fstream_reader_t final : public reader_t + { + public: + explicit fstream_reader_t(std::istream& stream); + + explicit fstream_reader_t(fstream_reader_t& copy) = delete; + + fstream_reader_t& operator=(const fstream_reader_t& copy) = delete; + + i64 read(char* buffer, size_t bytes) override; + private: + std::istream* m_stream; + }; + + class fstream_writer_t final : public writer_t + { + public: + explicit fstream_writer_t(std::ostream& stream); + + explicit fstream_writer_t(fstream_writer_t& copy) = delete; + + fstream_writer_t& operator=(const fstream_writer_t& copy) = delete; + + i64 write(char* buffer, size_t bytes) override; + + void flush() override; + + virtual ~fstream_writer_t() override // NOLINT + { + flush(); + } + + private: + std::ostream* m_stream; + }; } #endif //BLT_FILESYSTEM_H diff --git a/include/blt/fs/fwddecl.h b/include/blt/fs/fwddecl.h new file mode 100644 index 0000000..df91679 --- /dev/null +++ b/include/blt/fs/fwddecl.h @@ -0,0 +1,77 @@ +#pragma once +/* + * Copyright (C) 2024 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BLT_FS_FWDDECL_H +#define BLT_FS_FWDDECL_H + +#include + +namespace blt::fs +{ + /** + * A simple interface which provides a way of reading the next block of data from a resource. This is designed to replace the overly complex + * std::ostream + */ + class reader_t + { + public: + virtual ~reader_t() = default; + explicit reader_t() = default; + + reader_t(const reader_t&) = delete; + reader_t& operator=(const reader_t&) = delete; + + /** + * Reads bytes from the internal filesystem implementation + * @param buffer buffer to copy the read bytes into + * @param bytes number of bytes to read + * @return number of bytes read, or negative value if error. Errors are not required and can just return 0 + */ + virtual i64 read(char* buffer, size_t bytes) = 0; + }; + + /** + * A block writer without a definite backend implementation. Exactly the same as a block_reader but for writing to the filesystem. + * this is designed to replace the overly complex std::istream + */ + class writer_t + { + public: + virtual ~writer_t() = default; + explicit writer_t() = default; + + writer_t(const writer_t&) = delete; + writer_t& operator=(const writer_t&) = delete; + + /** + * Writes the bytes to the filesystem backend implementation + * @param buffer bytes to write + * @param bytes number of bytes to write + * @return number of bytes, or negative value if error. Zero is also a valid return, not indicating error in itself but can be the result of one. + */ + virtual i64 write(char* buffer, size_t bytes) = 0; + + /** + * Optional flush command which syncs the underlying objects + */ + virtual void flush() + {}; + }; +} + +#endif //BLT_FS_FWDDECL_H diff --git a/include/blt/fs/nbt.h b/include/blt/fs/nbt.h index 0a04535..2efa0b7 100644 --- a/include/blt/fs/nbt.h +++ b/include/blt/fs/nbt.h @@ -22,19 +22,19 @@ namespace blt::nbt { - void writeUTF8String(blt::fs::block_writer& stream, const std::string& str); + void writeUTF8String(blt::fs::writer_t& stream, const std::string& str); - std::string readUTF8String(blt::fs::block_reader& stream); + std::string readUTF8String(blt::fs::reader_t& stream); template - inline static void writeData(blt::fs::block_writer& out, const T& d){ + inline static void writeData(blt::fs::writer_t& out, const T& d){ char data[sizeof(T)]; mem::toBytes(d, data); out.write(data, sizeof(T)); } template - inline static void readData(blt::fs::block_reader& in, T& d) { + inline static void readData(blt::fs::reader_t& in, T& d) { char data[sizeof(T)]; in.read(data, sizeof(T)); mem::fromBytes(data, &d); @@ -63,12 +63,12 @@ namespace blt::nbt { public: explicit tag_t(nbt_tag type): type(type) {}; explicit tag_t(nbt_tag type, std::string name): type(type), name(std::move(name)) {} - virtual void writePayload(blt::fs::block_writer& out) = 0; - virtual void readPayload(blt::fs::block_reader& in) = 0; - void writeName(blt::fs::block_writer& out) { + virtual void writePayload(blt::fs::writer_t& out) = 0; + virtual void readPayload(blt::fs::reader_t& in) = 0; + void writeName(blt::fs::writer_t& out) { writeUTF8String(out, name); } - void readName(blt::fs::block_reader& in) { + void readName(blt::fs::reader_t& in) { name = readUTF8String(in); } [[nodiscard]] inline nbt_tag getType() const { @@ -87,11 +87,11 @@ namespace blt::nbt { public: explicit tag(nbt_tag type): tag_t(type) {} tag(nbt_tag type, std::string name, T t): tag_t(type, std::move(name)), t(std::move(t)) {} - void writePayload(blt::fs::block_writer& out) override { + void writePayload(blt::fs::writer_t& out) override { if constexpr(std::is_arithmetic::value) writeData(out, t); } - void readPayload(blt::fs::block_reader& in) override { + void readPayload(blt::fs::reader_t& in) override { if constexpr(std::is_arithmetic::value) readData(in, t); } @@ -102,9 +102,9 @@ namespace blt::nbt { class tag_end : public tag { public: - void writePayload(blt::fs::block_writer&) final {} + void writePayload(blt::fs::writer_t&) final {} // nothing to read - void readPayload(blt::fs::block_reader&) final {} + void readPayload(blt::fs::reader_t&) final {} }; class tag_byte : public tag { @@ -147,13 +147,13 @@ namespace blt::nbt { public: tag_byte_array(): tag(nbt_tag::BYTE_ARRAY) {} tag_byte_array(const std::string& name, const std::vector& v): tag(nbt_tag::BYTE_ARRAY, name, v) {} - void writePayload(blt::fs::block_writer& out) final { + void writePayload(blt::fs::writer_t& out) final { auto length = (int32_t) t.size(); writeData(out, length); // TODO on the writer (remove need for cast + more std::fstream functions) out.write(reinterpret_cast(t.data()), length); } - void readPayload(blt::fs::block_reader& in) final { + void readPayload(blt::fs::reader_t& in) final { int32_t length; readData(in, length); t.reserve(length); @@ -165,10 +165,10 @@ namespace blt::nbt { public: tag_string(): tag(nbt_tag::STRING) {} tag_string(const std::string& name, const std::string& s): tag(nbt_tag::STRING, name, s) {} - void writePayload(blt::fs::block_writer& out) final { + void writePayload(blt::fs::writer_t& out) final { writeUTF8String(out, t); } - void readPayload(blt::fs::block_reader& in) final { + void readPayload(blt::fs::reader_t& in) final { t = readUTF8String(in); } }; @@ -177,13 +177,13 @@ namespace blt::nbt { public: tag_int_array(): tag(nbt_tag::INT_ARRAY) {} tag_int_array(const std::string& name, const std::vector& v): tag(nbt_tag::INT_ARRAY, name, v) {} - void writePayload(blt::fs::block_writer& out) final { + void writePayload(blt::fs::writer_t& out) final { auto length = (int32_t) t.size(); writeData(out, length); for (int i = 0; i < length; i++) writeData(out, t[i]); } - void readPayload(blt::fs::block_reader& in) final { + void readPayload(blt::fs::reader_t& in) final { int32_t length; readData(in, length); t.reserve(length); @@ -196,13 +196,13 @@ namespace blt::nbt { public: tag_long_array(): tag(nbt_tag::LONG_ARRAY) {} tag_long_array(const std::string& name, const std::vector& v): tag(nbt_tag::LONG_ARRAY, name, v) {} - void writePayload(blt::fs::block_writer& out) final { + void writePayload(blt::fs::writer_t& out) final { auto length = (int32_t) t.size(); writeData(out, length); for (int i = 0; i < length; i++) writeData(out, t[i]); } - void readPayload(blt::fs::block_reader& in) final { + void readPayload(blt::fs::reader_t& in) final { int32_t length; readData(in, length); t.reserve(length); @@ -262,7 +262,7 @@ namespace blt::nbt { public: tag_list(): tag(nbt_tag::LIST) {} tag_list(const std::string& name, const std::vector& v): tag(nbt_tag::LIST, name, v) {} - void writePayload(blt::fs::block_writer& out) final { + void writePayload(blt::fs::writer_t& out) final { if (t.empty()) writeData(out, (char)nbt_tag::END); else @@ -273,7 +273,7 @@ namespace blt::nbt { v->writePayload(out); } - void readPayload(blt::fs::block_reader& in) final { + void readPayload(blt::fs::reader_t& in) final { char id; int32_t length; readData(in, id); @@ -356,7 +356,7 @@ namespace blt::nbt { t[tag->getName()] = tag; } - void writePayload(blt::fs::block_writer& out) final { + void writePayload(blt::fs::writer_t& out) final { for (const auto& v : t){ auto tag = v.second; out.put((char) tag->getType()); @@ -365,7 +365,7 @@ namespace blt::nbt { } out.put('\0'); } - void readPayload(blt::fs::block_reader& in) final { + void readPayload(blt::fs::reader_t& in) final { char type; while ((type = in.get()) != (char)nbt_tag::END){ auto* v = _internal_::toType(type); @@ -390,10 +390,10 @@ namespace blt::nbt { class NBTReader { private: - blt::fs::block_reader& reader; + blt::fs::reader_t& reader; tag_compound* root = nullptr; public: - explicit NBTReader(blt::fs::block_reader& reader): reader(reader) {} + explicit NBTReader(blt::fs::reader_t& reader): reader(reader) {} void read(); @@ -419,9 +419,9 @@ namespace blt::nbt { class NBTWriter { private: - blt::fs::block_writer& writer; + blt::fs::writer_t& writer; public: - explicit NBTWriter(blt::fs::block_writer& writer): writer(writer) {} + explicit NBTWriter(blt::fs::writer_t& writer): writer(writer) {} /** * Write a compound tag and then DELETES the tag. If you don't wish for the memory to be freed, please use the reference version! * @param root root NBT tag to write, this function assumes ownership of this pointer. diff --git a/include/blt/logging/fmt_tokenizer.h b/include/blt/logging/fmt_tokenizer.h index 7f98833..136994f 100644 --- a/include/blt/logging/fmt_tokenizer.h +++ b/include/blt/logging/fmt_tokenizer.h @@ -19,11 +19,11 @@ #ifndef BLT_LOGGING_FMT_TOKENIZER_H #define BLT_LOGGING_FMT_TOKENIZER_H +#include #include #include #include #include -#include namespace blt::logging { diff --git a/include/blt/logging/fwddecl.h b/include/blt/logging/fwddecl.h new file mode 100644 index 0000000..5c8b7c0 --- /dev/null +++ b/include/blt/logging/fwddecl.h @@ -0,0 +1,35 @@ +#pragma once +/* + * Copyright (C) 2024 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BLT_LOGGING_FWDDECL_H +#define BLT_LOGGING_FWDDECL_H + +namespace blt::logging +{ + struct logger_t; + enum class fmt_token_type : u8; + enum class fmt_align_t : u8; + enum class fmt_sign_t : u8; + enum class fmt_type_t : u8; + struct fmt_spec_t; + struct fmt_token_t; + class fmt_tokenizer_t; + class fmt_parser_t; +} + +#endif //BLT_LOGGING_FWDDECL_H diff --git a/include/blt/logging/logging.h b/include/blt/logging/logging.h index f8789d4..b09059b 100644 --- a/include/blt/logging/logging.h +++ b/include/blt/logging/logging.h @@ -19,14 +19,15 @@ #ifndef BLT_LOGGING_LOGGING_H #define BLT_LOGGING_LOGGING_H +#include +#include #include #include #include -#include -#include #include -#include +#include #include +#include namespace blt::logging { diff --git a/include/blt/logging/logging_config.h b/include/blt/logging/logging_config.h index 3447e41..29438f3 100644 --- a/include/blt/logging/logging_config.h +++ b/include/blt/logging/logging_config.h @@ -19,40 +19,48 @@ #ifndef BLT_LOGGING_LOGGING_CONFIG_H #define BLT_LOGGING_LOGGING_CONFIG_H -#include -#include -#include -#include #include +#include +#include +#include +#include +#include namespace blt::logging { - enum class log_level_t - { - TRACE, - DEBUG, - INFO, - WARN, - ERROR, - FATAL - }; + enum class log_level_t : u8 + { + TRACE, + DEBUG, + INFO, + WARN, + ERROR, + FATAL + }; - inline constexpr size_t LOG_LEVEL_COUNT = 6; + inline constexpr size_t LOG_LEVEL_COUNT = 6; - class logging_config_t - { - public: - std::optional log_file_path; - std::string log_format = ""; - std::array log_level_colors = { - - }; - log_level_t level = log_level_t::TRACE; - bool use_color = true; - bool log_to_console = true; - - private: - }; + class logging_config_t + { + friend logger_t; + public: + std::vector log_outputs = get_default_log_outputs(); + std::string log_format = get_default_log_format(); + std::array log_level_colors = get_default_log_level_colors(); + std::array log_level_names = get_default_log_level_names(); + log_level_t level = log_level_t::TRACE; + bool use_color = true; + // if true prints the whole path to the file (eg /home/user/.../.../project/src/source.cpp:line#) + bool print_full_name = false; + // this will attempt to use the maximum possible size for each printed element, then align to that. + // This creates output where the user message always starts at the same column. + bool ensure_alignment = true; + private: + static std::string get_default_log_format(); + static std::vector get_default_log_outputs(); + static std::array get_default_log_level_colors(); + static std::array get_default_log_level_names(); + }; } #endif //BLT_LOGGING_LOGGING_CONFIG_H diff --git a/src/blt/fs/filesystem.cpp b/src/blt/fs/filesystem.cpp index 7fa829b..03bba66 100644 --- a/src/blt/fs/filesystem.cpp +++ b/src/blt/fs/filesystem.cpp @@ -21,59 +21,25 @@ namespace blt::fs { - - fstream_block_reader::fstream_block_reader(std::fstream& stream, size_t bufferSize): - block_reader(bufferSize), m_stream(stream), m_buffer(new char[bufferSize]) - { - if (!m_stream.good() || m_stream.fail()) - BLT_WARN("Provided std::fstream is not good! Clearing!"); - m_stream.clear(); - } - - int fstream_block_reader::read(char* buffer, size_t bytes) - { - if (readIndex == 0) - m_stream.read(m_buffer, (long) m_bufferSize); - if (readIndex + bytes >= m_bufferSize) - { - // copy out all the data from the current buffer - auto bytesLeft = m_bufferSize - readIndex; - memcpy(buffer, m_buffer + readIndex, bytesLeft); - readIndex = 0; - // now to prevent large scale reading in small blocks, we should just read the entire thing into the buffer. - m_stream.read(buffer + bytesLeft, (long) (bytes - bytesLeft)); - } else - { - // but in the case that the size of the data read is small, we should read in blocks and copy from that buffer - // that should be quicker since file operations are slow. - std::memcpy(buffer, m_buffer + readIndex, bytes); - readIndex += bytes; - } - return 0; - } - - int fstream_block_writer::write(char* buffer, size_t bytes) - { - if (writeIndex + bytes >= m_bufferSize) - { - // in an attempt to stay efficient we write out the old buffer and the new buffer - // since there is a good chance there is more than a buffer's worth of data being written - // otherwise the buffer is almost full and can be written anyway. (this might be bad for performance especially if the FS wants round numbers) - m_stream.write(m_buffer, (long) writeIndex); - writeIndex = 0; - m_stream.write(buffer, (long) bytes); - } else - { - std::memcpy(m_buffer + writeIndex, buffer, bytes); - writeIndex += bytes; - } - return 0; - } - - void fstream_block_writer::flush_internal() - { - m_stream.write(m_buffer, (long) writeIndex); - writeIndex = 0; - } - -} \ No newline at end of file + fstream_reader_t::fstream_reader_t(std::istream& stream): m_stream{&stream} + {} + + i64 fstream_reader_t::read(char* buffer, const size_t bytes) + { + return m_stream->readsome(buffer, static_cast(bytes)); + } + + fstream_writer_t::fstream_writer_t(std::ostream& stream): m_stream{&stream} + {} + + i64 fstream_writer_t::write(char* buffer, size_t bytes) + { + m_stream->write(buffer, static_cast(bytes)); + return static_cast(bytes); + } + + void fstream_writer_t::flush() + { + m_stream->flush(); + } +} diff --git a/src/blt/fs/nbt.cpp b/src/blt/fs/nbt.cpp index e127f11..2c0dc1a 100644 --- a/src/blt/fs/nbt.cpp +++ b/src/blt/fs/nbt.cpp @@ -10,13 +10,13 @@ #include namespace blt::nbt { - void writeUTF8String(blt::fs::block_writer& stream, const std::string& str) { + void writeUTF8String(blt::fs::writer_t& stream, const std::string& str) { blt::string::utf8_string str8 = blt::string::createUTFString(str); stream.write(str8.characters, str8.size); delete[] str8.characters; } - std::string readUTF8String(blt::fs::block_reader& stream) { + std::string readUTF8String(blt::fs::reader_t& stream) { int16_t utflen; readData(stream, utflen); diff --git a/src/blt/logging/logging_config.cpp b/src/blt/logging/logging_config.cpp index 06f1880..fcb072d 100644 --- a/src/blt/logging/logging_config.cpp +++ b/src/blt/logging/logging_config.cpp @@ -16,6 +16,7 @@ * along with this program. If not, see . */ #include +#include namespace blt::logging { diff --git a/tests/logger_tests.cpp b/tests/logger_tests.cpp index a120b32..cb159c7 100644 --- a/tests/logger_tests.cpp +++ b/tests/logger_tests.cpp @@ -23,7 +23,7 @@ #include #include #include - +#include struct some_silly_type_t {}; @@ -89,6 +89,43 @@ std::pair compare_strings(const std::string& s1, const std::s return {true, ""}; } +struct seqbuf final : std::streambuf +{ + seqbuf(std::streambuf* destBuf, std::size_t& charCount) : m_dest(destBuf), m_count(charCount) + {} + +protected: + int_type overflow(int_type ch) override + { + if (traits_type::eq_int_type(ch, traits_type::eof())) + return traits_type::eof(); + ++m_count; + blt::logging::println("We are printing a character {:c}", ch); + return m_dest->sputc(static_cast(ch)); + } + + std::streamsize xsputn(const char* s, std::streamsize count) override + { + blt::logging::println("We are printing a series of characters {} {}", count, std::string_view{s, static_cast(count)}); + m_count += static_cast(count); + return m_dest->sputn(s, count); + } + + int_type underflow() override + { + return m_dest->sgetc(); + } + + int sync() override + { + return m_dest->pubsync(); + } + +private: + std::streambuf* m_dest; + std::size_t& m_count; +}; + int main() { std::stringstream ss; @@ -153,16 +190,28 @@ int main() } } blt::logging::println("{}This is a color now with background{}", - build(color::color_mode::BOLD, fg(color::color8::CYAN), color::color_mode::DIM, bg(color::color8::YELLOW)), + build(color::color_mode::BOLD, fg(color::color8::RED), color::color_mode::DIM, bg(color::color_rgb(0, 100, 255))), build(color::color_mode::RESET_ALL)); - std::cout << "\033[2J"; + std::ofstream os("test.txt"); + size_t charCount = 0; + seqbuf hi{os.rdbuf(), charCount}; + dynamic_cast(os).rdbuf(&hi); + os << "This is a println with a stream" << std::endl; + os << "This is a mixed print " << 25 << " with multiple types " << 34.23340 << std::endl; + os << "What about just a new line character?\n"; + + blt::logging::println("Logged {} characters", charCount); + + /*std::cout << "\033[2J"; constexpr int totalRows = 24; - std::cout << "\033[1;" << (totalRows - 1) << "r"; + // std::cout << "\033[1;" << (totalRows - 1) << "r"; + std::cout << use_mode(ansi::mode::color80x25_text); for (int i = 1; i <= 10; ++i) { + std::cout << "\033[1;1H"; std::cout << "printed line " << i << std::endl; std::cout << "\033[" << totalRows << ";1H"; @@ -170,7 +219,7 @@ int main() std::this_thread::sleep_for(std::chrono::milliseconds(500)); } - std::cout << "\033[r"; + std::cout << "\033[r";*/ // blt::logging::println("This is println {}\twith a std::endl in the middle of it"); } From 29ffdaaacdb9e5cedec011ec43e735ce6fbed565 Mon Sep 17 00:00:00 2001 From: Brett Date: Thu, 6 Mar 2025 15:33:35 -0500 Subject: [PATCH 060/101] write strings --- CMakeLists.txt | 2 +- include/blt/fs/filesystem.h | 77 ++++++++++++++++++++++++++++++++++++- include/blt/fs/fwddecl.h | 3 +- src/blt/fs/filesystem.cpp | 2 +- tests/logger_tests.cpp | 59 +++++++--------------------- 5 files changed, 94 insertions(+), 49 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a7c203c..782e4ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.1.13) +set(BLT_VERSION 5.1.14) set(BLT_TARGET BLT) diff --git a/include/blt/fs/filesystem.h b/include/blt/fs/filesystem.h index 8695f31..0ff2c62 100644 --- a/include/blt/fs/filesystem.h +++ b/include/blt/fs/filesystem.h @@ -20,6 +20,7 @@ #define BLT_FILESYSTEM_H #include +#include #include namespace blt::fs @@ -37,6 +38,7 @@ namespace blt::fs fstream_reader_t& operator=(const fstream_reader_t& copy) = delete; i64 read(char* buffer, size_t bytes) override; + private: std::istream* m_stream; }; @@ -50,7 +52,7 @@ namespace blt::fs fstream_writer_t& operator=(const fstream_writer_t& copy) = delete; - i64 write(char* buffer, size_t bytes) override; + i64 write(const char* buffer, size_t bytes) override; void flush() override; @@ -62,6 +64,79 @@ namespace blt::fs private: std::ostream* m_stream; }; + + class reader_wrapper_t final + { + public: + explicit reader_wrapper_t(reader_t& reader): m_reader(&reader) + {} + + template + void read(T& out) + { + if (!m_reader->read(reinterpret_cast(&out), sizeof(T))) + throw std::runtime_error("Failed to read from reader"); + } + + template + friend reader_wrapper_t& operator>>(reader_wrapper_t& reader, T& t) + { + reader.read(t); + return reader; + } + + private: + reader_t* m_reader; + }; + + class writer_wrapper_t final + { + public: + explicit writer_wrapper_t(writer_t& writer): m_writer(&writer) + {} + + template + void write(const T& t) + { + m_writer->write(reinterpret_cast(&t), sizeof(T)); + } + + template + friend writer_wrapper_t& operator<<(writer_wrapper_t& writer, const T& t) + { + writer.write(t); + return writer; + } + + private: + writer_t* m_writer; + }; + + class writer_string_wrapper_t final + { + public: + explicit writer_string_wrapper_t(writer_t& writer): m_writer(&writer) + {} + + template + void write(const T& t) + { + std::stringstream ss; + ss << t; + const auto str = ss.str(); + m_writer->write(str.data(), str.size()); + } + + template + friend writer_string_wrapper_t& operator<<(writer_string_wrapper_t& writer, const T& t) + { + writer.write(t); + return writer; + } + + private: + writer_t* m_writer; + }; } #endif //BLT_FILESYSTEM_H diff --git a/include/blt/fs/fwddecl.h b/include/blt/fs/fwddecl.h index df91679..a0c64c5 100644 --- a/include/blt/fs/fwddecl.h +++ b/include/blt/fs/fwddecl.h @@ -19,6 +19,7 @@ #ifndef BLT_FS_FWDDECL_H #define BLT_FS_FWDDECL_H +#include #include namespace blt::fs @@ -64,7 +65,7 @@ namespace blt::fs * @param bytes number of bytes to write * @return number of bytes, or negative value if error. Zero is also a valid return, not indicating error in itself but can be the result of one. */ - virtual i64 write(char* buffer, size_t bytes) = 0; + virtual i64 write(const char* buffer, size_t bytes) = 0; /** * Optional flush command which syncs the underlying objects diff --git a/src/blt/fs/filesystem.cpp b/src/blt/fs/filesystem.cpp index 03bba66..1ea5192 100644 --- a/src/blt/fs/filesystem.cpp +++ b/src/blt/fs/filesystem.cpp @@ -32,7 +32,7 @@ namespace blt::fs fstream_writer_t::fstream_writer_t(std::ostream& stream): m_stream{&stream} {} - i64 fstream_writer_t::write(char* buffer, size_t bytes) + i64 fstream_writer_t::write(const char* buffer, const size_t bytes) { m_stream->write(buffer, static_cast(bytes)); return static_cast(bytes); diff --git a/tests/logger_tests.cpp b/tests/logger_tests.cpp index cb159c7..527dcb2 100644 --- a/tests/logger_tests.cpp +++ b/tests/logger_tests.cpp @@ -15,15 +15,16 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +#include +#include #include #include +#include +#include #include #include #include #include -#include -#include -#include struct some_silly_type_t {}; @@ -89,43 +90,6 @@ std::pair compare_strings(const std::string& s1, const std::s return {true, ""}; } -struct seqbuf final : std::streambuf -{ - seqbuf(std::streambuf* destBuf, std::size_t& charCount) : m_dest(destBuf), m_count(charCount) - {} - -protected: - int_type overflow(int_type ch) override - { - if (traits_type::eq_int_type(ch, traits_type::eof())) - return traits_type::eof(); - ++m_count; - blt::logging::println("We are printing a character {:c}", ch); - return m_dest->sputc(static_cast(ch)); - } - - std::streamsize xsputn(const char* s, std::streamsize count) override - { - blt::logging::println("We are printing a series of characters {} {}", count, std::string_view{s, static_cast(count)}); - m_count += static_cast(count); - return m_dest->sputn(s, count); - } - - int_type underflow() override - { - return m_dest->sgetc(); - } - - int sync() override - { - return m_dest->pubsync(); - } - -private: - std::streambuf* m_dest; - std::size_t& m_count; -}; - int main() { std::stringstream ss; @@ -195,12 +159,17 @@ int main() std::ofstream os("test.txt"); + blt::fs::fstream_writer_t wtr(os); + blt::fs::writer_wrapper_t writer(wtr); + + writer.write("This is a println with a stream\n"); + writer.write("This is a mixed print"); + writer.write(std::to_string(25)); + writer.write(" with multiple types "); + writer.write(std::to_string(34.23340)); + writer.write('\n'); + writer.write("What about just a new line character?\n"); size_t charCount = 0; - seqbuf hi{os.rdbuf(), charCount}; - dynamic_cast(os).rdbuf(&hi); - os << "This is a println with a stream" << std::endl; - os << "This is a mixed print " << 25 << " with multiple types " << 34.23340 << std::endl; - os << "What about just a new line character?\n"; blt::logging::println("Logged {} characters", charCount); From f15ce17215689b73b6504e3686ebc7940ab9860a Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Thu, 6 Mar 2025 18:54:05 -0500 Subject: [PATCH 061/101] silly little boxer --- CMakeLists.txt | 2 +- README.md | 19 ++++++++++++++++--- libraries/parallel-hashmap | 2 +- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 782e4ac..366acb1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.1.14) +set(BLT_VERSION 5.1.15) set(BLT_TARGET BLT) diff --git a/README.md b/README.md index 4c16a4d..1d498f5 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,27 @@ -# **BLT v0.20** +# **BLT v5.1** A C++17 common utilities library to make thing easy! ![Icon](icon_large.png) --- -# ***Features*** +# Features + +## BLT Format + +This module provides general string formatting utilities. *Note: this folder contains mostly outdated library files and will be updated in the future.* + +### Files + +- **boxing.h** + Simple utility for drawing boxes around blocks of text. + +- **format.h** + Legacy library file containing various utilities for creating formatted output. Also includes methods for writing Java UTF8 strings. + - ## blt/fs - ### loader.h - - std::string blt::fs::getFile(std::string_view path) + - `std::string blt::fs::getFile(std::string_view path)` - Gets the entire file as a string. - std::vector\ blt::fs::getLinesFromFile(std::string_view path) - Gets the entire file as a string, then splits on the new line character. Then returns a vector of those lines diff --git a/libraries/parallel-hashmap b/libraries/parallel-hashmap index 93201da..7ef2e73 160000 --- a/libraries/parallel-hashmap +++ b/libraries/parallel-hashmap @@ -1 +1 @@ -Subproject commit 93201da2ba5a6aba0a6e57ada64973555629b3e3 +Subproject commit 7ef2e733416953b222851f9a360d7fc72d068ee5 From f1027d33a414bdab711b78503372ea5f06a558e1 Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Thu, 6 Mar 2025 20:41:18 -0500 Subject: [PATCH 062/101] starting on bounded writers --- CMakeLists.txt | 2 +- README.md | 39 ++++++--- include/blt/fs/filesystem.h | 113 +----------------------- include/blt/fs/fwddecl.h | 1 - include/blt/fs/limited_writers.h | 51 +++++++++++ include/blt/fs/stream_wrappers.h | 143 +++++++++++++++++++++++++++++++ include/blt/logging/logging.h | 1 - src/blt/fs/filesystem.cpp | 24 ------ src/blt/fs/limited_writers.cpp | 27 ++++++ src/blt/fs/stream_wrappers.cpp | 45 ++++++++++ 10 files changed, 295 insertions(+), 151 deletions(-) create mode 100644 include/blt/fs/limited_writers.h create mode 100644 include/blt/fs/stream_wrappers.h create mode 100644 src/blt/fs/limited_writers.cpp create mode 100644 src/blt/fs/stream_wrappers.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 366acb1..8b129ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.1.15) +set(BLT_VERSION 5.1.16) set(BLT_TARGET BLT) diff --git a/README.md b/README.md index 1d498f5..026d561 100644 --- a/README.md +++ b/README.md @@ -19,19 +19,32 @@ This module provides general string formatting utilities. *Note: this folder con - **format.h** Legacy library file containing various utilities for creating formatted output. Also includes methods for writing Java UTF8 strings. -- ## blt/fs - - ### loader.h - - `std::string blt::fs::getFile(std::string_view path)` - - Gets the entire file as a string. - - std::vector\ blt::fs::getLinesFromFile(std::string_view path) - - Gets the entire file as a string, then splits on the new line character. Then returns a vector of those lines - - std::vector\ blt::fs::recursiveInclude(std::string_view path, std::string include_header, std::vector guards); - - Recursively include in order based on the include string (include_header) marked by the include guards - - Defaults to C/C++/GLSL preprocessor style (Was designed for GLSL) - - std::string blt::fs::loadBrainFuckFile(const std::string& path) - - Load a brainfuck file recursively, uses ~ to mark the include path - - ### nbt.h - - probably needs to be remade (TODO) +## BLT Filesystem +This module provides helper classes for filesystem objects. It seeks to offer an interface that is simpler than the one provided by the standard library. +Specifically, the number of functions required to implement is significantly lower, +and the interface is generally cleaner. Eventually, this module aims to support various file formats, +such as Minecraft's NBT system. Currently, there is an existing NBT file, but it was written when I was first learning C++. + +### Files + +- **filesystem.h** + This is the base file which includes all other files. You should use the other options as this can be a heavy file to include + +- **path_helper.h** + This file provides functions for interfacing with paths. Specifically, as of this moment it only provides an interface for getting the base name of a file path. + +- **loader.h** + - `std::string blt::fs::getFile(std::string_view path)` + - Gets the entire file as a string. + - `std::vector\ blt::fs::getLinesFromFile(std::string_view path)` + - Gets the entire file as a string, then splits on the new line character. Then returns a vector of those lines + - `std::vector\ blt::fs::recursiveInclude(std::string_view path, std::string include_header, std::vector guards)` + - Recursively include in order based on the include string (include_header) marked by the include guards + - Defaults to C/C++/GLSL preprocessor style (Was designed for GLSL) + - `std::string blt::fs::loadBrainFuckFile(const std::string& path)` + - Load a brainfuck file recursively, uses ~ to mark the include path +- ### nbt.h + - probably needs to be remade (TODO) - ## blt/math - ### averages.h - blt::averagizer_o_matic diff --git a/include/blt/fs/filesystem.h b/include/blt/fs/filesystem.h index 0ff2c62..4a7f847 100644 --- a/include/blt/fs/filesystem.h +++ b/include/blt/fs/filesystem.h @@ -22,121 +22,12 @@ #include #include #include +#include +#include namespace blt::fs { - /** - * reader_t wrapper for fstream - */ - class fstream_reader_t final : public reader_t - { - public: - explicit fstream_reader_t(std::istream& stream); - explicit fstream_reader_t(fstream_reader_t& copy) = delete; - - fstream_reader_t& operator=(const fstream_reader_t& copy) = delete; - - i64 read(char* buffer, size_t bytes) override; - - private: - std::istream* m_stream; - }; - - class fstream_writer_t final : public writer_t - { - public: - explicit fstream_writer_t(std::ostream& stream); - - explicit fstream_writer_t(fstream_writer_t& copy) = delete; - - fstream_writer_t& operator=(const fstream_writer_t& copy) = delete; - - i64 write(const char* buffer, size_t bytes) override; - - void flush() override; - - virtual ~fstream_writer_t() override // NOLINT - { - flush(); - } - - private: - std::ostream* m_stream; - }; - - class reader_wrapper_t final - { - public: - explicit reader_wrapper_t(reader_t& reader): m_reader(&reader) - {} - - template - void read(T& out) - { - if (!m_reader->read(reinterpret_cast(&out), sizeof(T))) - throw std::runtime_error("Failed to read from reader"); - } - - template - friend reader_wrapper_t& operator>>(reader_wrapper_t& reader, T& t) - { - reader.read(t); - return reader; - } - - private: - reader_t* m_reader; - }; - - class writer_wrapper_t final - { - public: - explicit writer_wrapper_t(writer_t& writer): m_writer(&writer) - {} - - template - void write(const T& t) - { - m_writer->write(reinterpret_cast(&t), sizeof(T)); - } - - template - friend writer_wrapper_t& operator<<(writer_wrapper_t& writer, const T& t) - { - writer.write(t); - return writer; - } - - private: - writer_t* m_writer; - }; - - class writer_string_wrapper_t final - { - public: - explicit writer_string_wrapper_t(writer_t& writer): m_writer(&writer) - {} - - template - void write(const T& t) - { - std::stringstream ss; - ss << t; - const auto str = ss.str(); - m_writer->write(str.data(), str.size()); - } - - template - friend writer_string_wrapper_t& operator<<(writer_string_wrapper_t& writer, const T& t) - { - writer.write(t); - return writer; - } - - private: - writer_t* m_writer; - }; } #endif //BLT_FILESYSTEM_H diff --git a/include/blt/fs/fwddecl.h b/include/blt/fs/fwddecl.h index a0c64c5..06359be 100644 --- a/include/blt/fs/fwddecl.h +++ b/include/blt/fs/fwddecl.h @@ -19,7 +19,6 @@ #ifndef BLT_FS_FWDDECL_H #define BLT_FS_FWDDECL_H -#include #include namespace blt::fs diff --git a/include/blt/fs/limited_writers.h b/include/blt/fs/limited_writers.h new file mode 100644 index 0000000..034a08b --- /dev/null +++ b/include/blt/fs/limited_writers.h @@ -0,0 +1,51 @@ +#pragma once +/* + * Copyright (C) 2024 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BLT_FS_BOUNDED_WRITER_H +#define BLT_FS_BOUNDED_WRITER_H + +#include +#include +#include +#include +#icnlude + +namespace blt::fs +{ + inline auto basic_naming_function = [](size_t invocation, std::optional prefix) { + return prefix.value_or("") + "-" + std::to_string(invocation) + ".txt"; + }; + + /** + * Creates a bounded writer where after a specified number of bytes a new file will be opened and written to instead. + */ + class bounded_writer : public writer_t + { + public: + explicit bounded_writer(std::optional base_name, std::function)> naming_function = basic_naming_function, size_t max_size = 1024 * 1024 * 10); + + private: + std::optional m_base_name; + size_t m_current_invocation = 0; + // inputs: current invocation, optional string provided to the constructor + // returns: name of the file to write to + std::function)> m_naming_function; + }; +} + +#endif //BLT_FS_BOUNDED_WRITER_H diff --git a/include/blt/fs/stream_wrappers.h b/include/blt/fs/stream_wrappers.h new file mode 100644 index 0000000..2a5094d --- /dev/null +++ b/include/blt/fs/stream_wrappers.h @@ -0,0 +1,143 @@ +#pragma once +/* + * Copyright (C) 2024 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BLT_FS_STREAM_WRAPPERS_H +#define BLT_FS_STREAM_WRAPPERS_H + +#include +#include +#include + +namespace blt::fs +{ + /** + * reader_t wrapper for fstream + */ + class fstream_reader_t final : public reader_t + { + public: + explicit fstream_reader_t(std::istream& stream); + + explicit fstream_reader_t(fstream_reader_t& copy) = delete; + + fstream_reader_t& operator=(const fstream_reader_t& copy) = delete; + + i64 read(char* buffer, size_t bytes) override; + + private: + std::istream* m_stream; + }; + + class fstream_writer_t final : public writer_t + { + public: + explicit fstream_writer_t(std::ostream& stream); + + explicit fstream_writer_t(fstream_writer_t& copy) = delete; + + fstream_writer_t& operator=(const fstream_writer_t& copy) = delete; + + i64 write(const char* buffer, size_t bytes) override; + + void flush() override; + + virtual ~fstream_writer_t() override // NOLINT + { + flush(); + } + + private: + std::ostream* m_stream; + }; + + class reader_wrapper_t + { + public: + explicit reader_wrapper_t(reader_t& reader): m_reader(&reader) + {} + + template + void read(T& out) + { + if (!m_reader->read(reinterpret_cast(&out), sizeof(T))) + throw std::runtime_error("Failed to read from reader"); + } + + template + friend reader_wrapper_t& operator>>(reader_wrapper_t& reader, T& t) + { + reader.read(t); + return reader; + } + + private: + reader_t* m_reader; + }; + + class writer_wrapper_t + { + public: + explicit writer_wrapper_t(writer_t& writer): m_writer(&writer) + {} + + template + void write(const T& t) + { + static_assert(std::is_trivially_copyable_v); + m_writer->write(reinterpret_cast(&t), sizeof(T)); + } + + template + friend writer_wrapper_t& operator<<(writer_wrapper_t& writer, const T& t) + { + writer.write(t); + return writer; + } + + private: + writer_t* m_writer; + }; + + class writer_string_wrapper_t + { + public: + explicit writer_string_wrapper_t(writer_t& writer): m_writer(&writer) + {} + + template + void write(const T& t) + { + std::stringstream ss; + ss << t; + const auto str = ss.str(); + m_writer->write(str.data(), str.size()); + } + + template + friend writer_string_wrapper_t& operator<<(writer_string_wrapper_t& writer, const T& t) + { + writer.write(t); + return writer; + } + + private: + writer_t* m_writer; + }; +} + +#endif //BLT_FS_STREAM_WRAPPERS_H diff --git a/include/blt/logging/logging.h b/include/blt/logging/logging.h index b09059b..2c5441a 100644 --- a/include/blt/logging/logging.h +++ b/include/blt/logging/logging.h @@ -25,7 +25,6 @@ #include #include #include -#include #include #include diff --git a/src/blt/fs/filesystem.cpp b/src/blt/fs/filesystem.cpp index 1ea5192..43b9fb2 100644 --- a/src/blt/fs/filesystem.cpp +++ b/src/blt/fs/filesystem.cpp @@ -19,27 +19,3 @@ #include #include -namespace blt::fs -{ - fstream_reader_t::fstream_reader_t(std::istream& stream): m_stream{&stream} - {} - - i64 fstream_reader_t::read(char* buffer, const size_t bytes) - { - return m_stream->readsome(buffer, static_cast(bytes)); - } - - fstream_writer_t::fstream_writer_t(std::ostream& stream): m_stream{&stream} - {} - - i64 fstream_writer_t::write(const char* buffer, const size_t bytes) - { - m_stream->write(buffer, static_cast(bytes)); - return static_cast(bytes); - } - - void fstream_writer_t::flush() - { - m_stream->flush(); - } -} diff --git a/src/blt/fs/limited_writers.cpp b/src/blt/fs/limited_writers.cpp new file mode 100644 index 0000000..7b01c3d --- /dev/null +++ b/src/blt/fs/limited_writers.cpp @@ -0,0 +1,27 @@ +/* + * + * Copyright (C) 2025 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace blt::fs +{ + bounded_writer::bounded_writer(size_t max_size) + { + + } + +} \ No newline at end of file diff --git a/src/blt/fs/stream_wrappers.cpp b/src/blt/fs/stream_wrappers.cpp new file mode 100644 index 0000000..144001c --- /dev/null +++ b/src/blt/fs/stream_wrappers.cpp @@ -0,0 +1,45 @@ +/* + * + * Copyright (C) 2025 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include + +namespace blt::fs +{ + fstream_reader_t::fstream_reader_t(std::istream& stream): m_stream{&stream} + {} + + i64 fstream_reader_t::read(char* buffer, const size_t bytes) + { + return m_stream->readsome(buffer, static_cast(bytes)); + } + + fstream_writer_t::fstream_writer_t(std::ostream& stream): m_stream{&stream} + {} + + i64 fstream_writer_t::write(const char* buffer, const size_t bytes) + { + m_stream->write(buffer, static_cast(bytes)); + return static_cast(bytes); + } + + void fstream_writer_t::flush() + { + m_stream->flush(); + } +} From 83df06f1c030bddcea89f69341c3fd7ebf05594c Mon Sep 17 00:00:00 2001 From: Brett Date: Fri, 7 Mar 2025 00:43:54 -0500 Subject: [PATCH 063/101] file writers --- CMakeLists.txt | 2 +- include/blt/fs/file_writers.h | 95 ++++++++++++++++++++++++++++++++ include/blt/fs/filesystem.h | 2 +- include/blt/fs/limited_writers.h | 51 ----------------- libraries/parallel-hashmap | 2 +- src/blt/fs/file_writers.cpp | 80 +++++++++++++++++++++++++++ src/blt/fs/limited_writers.cpp | 27 --------- 7 files changed, 178 insertions(+), 81 deletions(-) create mode 100644 include/blt/fs/file_writers.h delete mode 100644 include/blt/fs/limited_writers.h create mode 100644 src/blt/fs/file_writers.cpp delete mode 100644 src/blt/fs/limited_writers.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b129ea..d2c94d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.1.16) +set(BLT_VERSION 5.1.17) set(BLT_TARGET BLT) diff --git a/include/blt/fs/file_writers.h b/include/blt/fs/file_writers.h new file mode 100644 index 0000000..d617de5 --- /dev/null +++ b/include/blt/fs/file_writers.h @@ -0,0 +1,95 @@ +#pragma once +/* + * Copyright (C) 2024 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BLT_FS_BOUNDED_WRITER_H +#define BLT_FS_BOUNDED_WRITER_H + +#include +#include +#include +#include +#include +#include + +namespace blt::fs +{ + inline auto basic_naming_function = [](const size_t invocation, const std::optional& prefix) { + return prefix.value_or("") + "-" + std::to_string(invocation) + ".txt"; + }; + + // ReSharper disable once CppClassCanBeFinal + class fwriter_t : public writer_t + { + public: + explicit fwriter_t(const std::string& name, std::string mode = "ab"): m_mode(std::move(mode)) + { + fwriter_t::newfile(name); + } + + i64 write(const char* buffer, size_t bytes) override; + + virtual void newfile(const std::string& new_name); + + void flush() override; + + protected: + std::string m_mode; + FILE* m_file = nullptr; + }; + + // ReSharper disable once CppClassCanBeFinal + class buffered_writer : public fwriter_t + { + public: + explicit buffered_writer(const std::string& name, size_t buffer_size = 1024 * 128); + + i64 write(const char* buffer, size_t bytes) override; + + void flush() override; + + protected: + size_t m_current_pos = 0; + std::vector m_buffer; + }; + + /** + * Creates a bounded writer where after a specified number of bytes a new file will be opened and written to instead. + */ + // ReSharper disable once CppClassCanBeFinal + class bounded_writer : public fwriter_t + { + public: + explicit bounded_writer(fwriter_t& writer, std::optional base_name, + const std::function)>& naming_function = basic_naming_function, + size_t max_size = 1024 * 1024 * 10); + + i64 write(const char* buffer, size_t bytes) override; + + private: + fwriter_t* m_writer = nullptr; + std::optional m_base_name; + size_t m_current_invocation = 0; + size_t m_max_size = 0; + size_t m_currently_written = 0; + // inputs: current invocation, optional string provided to the constructor + // returns: name of the file to write to + std::function&)> m_naming_function; + }; +} + +#endif //BLT_FS_BOUNDED_WRITER_H diff --git a/include/blt/fs/filesystem.h b/include/blt/fs/filesystem.h index 4a7f847..096902c 100644 --- a/include/blt/fs/filesystem.h +++ b/include/blt/fs/filesystem.h @@ -22,7 +22,7 @@ #include #include #include -#include +#include #include namespace blt::fs diff --git a/include/blt/fs/limited_writers.h b/include/blt/fs/limited_writers.h deleted file mode 100644 index 034a08b..0000000 --- a/include/blt/fs/limited_writers.h +++ /dev/null @@ -1,51 +0,0 @@ -#pragma once -/* - * Copyright (C) 2024 Brett Terpstra - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#ifndef BLT_FS_BOUNDED_WRITER_H -#define BLT_FS_BOUNDED_WRITER_H - -#include -#include -#include -#include -#icnlude - -namespace blt::fs -{ - inline auto basic_naming_function = [](size_t invocation, std::optional prefix) { - return prefix.value_or("") + "-" + std::to_string(invocation) + ".txt"; - }; - - /** - * Creates a bounded writer where after a specified number of bytes a new file will be opened and written to instead. - */ - class bounded_writer : public writer_t - { - public: - explicit bounded_writer(std::optional base_name, std::function)> naming_function = basic_naming_function, size_t max_size = 1024 * 1024 * 10); - - private: - std::optional m_base_name; - size_t m_current_invocation = 0; - // inputs: current invocation, optional string provided to the constructor - // returns: name of the file to write to - std::function)> m_naming_function; - }; -} - -#endif //BLT_FS_BOUNDED_WRITER_H diff --git a/libraries/parallel-hashmap b/libraries/parallel-hashmap index 7ef2e73..93201da 160000 --- a/libraries/parallel-hashmap +++ b/libraries/parallel-hashmap @@ -1 +1 @@ -Subproject commit 7ef2e733416953b222851f9a360d7fc72d068ee5 +Subproject commit 93201da2ba5a6aba0a6e57ada64973555629b3e3 diff --git a/src/blt/fs/file_writers.cpp b/src/blt/fs/file_writers.cpp new file mode 100644 index 0000000..fb832a7 --- /dev/null +++ b/src/blt/fs/file_writers.cpp @@ -0,0 +1,80 @@ +/* + * + * Copyright (C) 2025 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include +#include + +namespace blt::fs +{ + i64 fwriter_t::write(const char* buffer, const size_t bytes) + { + return static_cast(std::fwrite(buffer, 1, bytes, m_file)); + } + + void fwriter_t::flush() + { + writer_t::flush(); + std::fflush(m_file); + } + + void fwriter_t::newfile(const std::string& new_name) + { + if (m_file != nullptr) + std::fclose(m_file); + m_file = std::fopen(new_name.c_str(), m_mode.c_str()); + if (!m_file) + throw std::runtime_error("Failed to open file for writing"); + } + + buffered_writer::buffered_writer(const std::string& name, const size_t buffer_size): fwriter_t{name} + { + m_buffer.resize(buffer_size); + } + + i64 buffered_writer::write(const char* buffer, const size_t bytes) + { + if (bytes > m_buffer.size()) + return fwriter_t::write(buffer, bytes); + if (bytes + m_current_pos > m_buffer.size()) + flush(); + std::memcpy(m_buffer.data() + m_current_pos, buffer, bytes); + m_current_pos += bytes; + return static_cast(bytes); + } + + void buffered_writer::flush() + { + fwriter_t::flush(); + fwriter_t::write(m_buffer.data(), m_current_pos); + m_current_pos = 0; + } + + bounded_writer::bounded_writer(fwriter_t& writer, std::optional base_name, + const std::function)>& naming_function, + const size_t max_size): fwriter_t{naming_function(0, base_name)}, m_writer(&writer), + m_base_name(std::move(base_name)), m_max_size(max_size), + m_naming_function(naming_function) + {} + + i64 bounded_writer::write(const char* buffer, size_t bytes) + { + return fwriter_t::write(buffer, bytes); + } +} diff --git a/src/blt/fs/limited_writers.cpp b/src/blt/fs/limited_writers.cpp deleted file mode 100644 index 7b01c3d..0000000 --- a/src/blt/fs/limited_writers.cpp +++ /dev/null @@ -1,27 +0,0 @@ -/* - * - * Copyright (C) 2025 Brett Terpstra - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include - -namespace blt::fs -{ - bounded_writer::bounded_writer(size_t max_size) - { - - } - -} \ No newline at end of file From dbff8fd8b3bb72b3feeb5fc2b20c4d4edfa0f826 Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Fri, 7 Mar 2025 14:55:08 -0500 Subject: [PATCH 064/101] thread safe wrappers + logging config + time rotation --- CMakeLists.txt | 2 +- include/blt/fs/file_writers.h | 70 +++++++++++++++++--- include/blt/fs/threaded_writers.h | 42 ++++++++++++ include/blt/logging/logging_config.h | 7 +- libraries/parallel-hashmap | 2 +- src/blt/fs/file_writers.cpp | 97 ++++++++++++++++++++++++---- src/blt/fs/threaded_wrtiers.cpp | 36 +++++++++++ src/blt/logging/logging_config.cpp | 12 +++- 8 files changed, 240 insertions(+), 28 deletions(-) create mode 100644 include/blt/fs/threaded_writers.h create mode 100644 src/blt/fs/threaded_wrtiers.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d2c94d7..f30080a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.1.17) +set(BLT_VERSION 5.2.0) set(BLT_TARGET BLT) diff --git a/include/blt/fs/file_writers.h b/include/blt/fs/file_writers.h index d617de5..db25ed6 100644 --- a/include/blt/fs/file_writers.h +++ b/include/blt/fs/file_writers.h @@ -28,11 +28,15 @@ namespace blt::fs { - inline auto basic_naming_function = [](const size_t invocation, const std::optional& prefix) { - return prefix.value_or("") + "-" + std::to_string(invocation) + ".txt"; + inline auto basic_naming_function = [](const size_t invocation, std::string prefix) { + prefix += '-'; + prefix += std::to_string(invocation); + prefix += ".txt"; + return prefix; }; - // ReSharper disable once CppClassCanBeFinal + using naming_function_t = std::function; + class fwriter_t : public writer_t { public: @@ -41,6 +45,10 @@ namespace blt::fs fwriter_t::newfile(name); } + // create a writer without creating a new file. Writing without calling newfile is UB + explicit fwriter_t(std::string mode = "ab"): m_mode(std::move(mode)) + {} + i64 write(const char* buffer, size_t bytes) override; virtual void newfile(const std::string& new_name); @@ -62,6 +70,8 @@ namespace blt::fs void flush() override; + void newfile(const std::string& new_name) override; + protected: size_t m_current_pos = 0; std::vector m_buffer; @@ -74,21 +84,61 @@ namespace blt::fs class bounded_writer : public fwriter_t { public: - explicit bounded_writer(fwriter_t& writer, std::optional base_name, - const std::function)>& naming_function = basic_naming_function, - size_t max_size = 1024 * 1024 * 10); + explicit bounded_writer(fwriter_t& writer, std::optional base_name, size_t max_size = 1024 * 1024 * 10, + naming_function_t naming_function = basic_naming_function); i64 write(const char* buffer, size_t bytes) override; + void newfile(const std::string& new_name) override; + + void flush() override; + private: - fwriter_t* m_writer = nullptr; + fwriter_t* m_writer; std::optional m_base_name; size_t m_current_invocation = 0; - size_t m_max_size = 0; + size_t m_max_size; size_t m_currently_written = 0; - // inputs: current invocation, optional string provided to the constructor + // inputs: current invocation, then basename string // returns: name of the file to write to - std::function&)> m_naming_function; + naming_function_t m_naming_function; + }; + + struct time_t + { + i32 year = 0, month = 0, day = 1, hour = -1; + + time_t(const i32 year, const i32 month, const i32 day, const i32 hour) : year{year}, month{month}, day{day}, hour{hour} + {} + + time_t(const i32 year, const i32 month, const i32 day) : year{year}, month{month}, day{day} + {} + + time_t() = default; + }; + + // ReSharper disable once CppClassCanBeFinal + class rotating_writer : public fwriter_t + { + public: + rotating_writer(fwriter_t& writer, time_t period); + + i64 write(const char* buffer, size_t bytes) override; + + void flush() override; + + void newfile(const std::string& new_name) override; + + void newfile(); + + void check_for_time(); + + static time_t get_current_time(); + + private: + fwriter_t* m_writer; + time_t m_period; + time_t m_last_time; }; } diff --git a/include/blt/fs/threaded_writers.h b/include/blt/fs/threaded_writers.h new file mode 100644 index 0000000..293c397 --- /dev/null +++ b/include/blt/fs/threaded_writers.h @@ -0,0 +1,42 @@ +#pragma once +/* + * Copyright (C) 2024 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BLT_FS_THREADED_WRITERS_H +#define BLT_FS_THREADED_WRITERS_H + +#include +#include + +namespace blt::fs +{ + // ReSharper disable once CppClassCanBeFinal + class concurrent_file_writer : public writer_t + { + public: + explicit concurrent_file_writer(writer_t* writer); + + i64 write(const char* buffer, size_t bytes) override; + + void flush() override; + private: + writer_t* m_writer; + std::mutex m_mutex; + }; +} + +#endif //BLT_FS_THREADED_WRITERS_H diff --git a/include/blt/logging/logging_config.h b/include/blt/logging/logging_config.h index 29438f3..c5a843c 100644 --- a/include/blt/logging/logging_config.h +++ b/include/blt/logging/logging_config.h @@ -20,11 +20,11 @@ #define BLT_LOGGING_LOGGING_CONFIG_H #include -#include #include #include #include #include +#include namespace blt::logging { @@ -44,7 +44,8 @@ namespace blt::logging { friend logger_t; public: - std::vector log_outputs = get_default_log_outputs(); + // wrappers for streams exist in blt/fs/stream_wrappers.h + std::vector log_outputs = get_default_log_outputs(); std::string log_format = get_default_log_format(); std::array log_level_colors = get_default_log_level_colors(); std::array log_level_names = get_default_log_level_names(); @@ -57,7 +58,7 @@ namespace blt::logging bool ensure_alignment = true; private: static std::string get_default_log_format(); - static std::vector get_default_log_outputs(); + static std::vector get_default_log_outputs(); static std::array get_default_log_level_colors(); static std::array get_default_log_level_names(); }; diff --git a/libraries/parallel-hashmap b/libraries/parallel-hashmap index 93201da..7ef2e73 160000 --- a/libraries/parallel-hashmap +++ b/libraries/parallel-hashmap @@ -1 +1 @@ -Subproject commit 93201da2ba5a6aba0a6e57ada64973555629b3e3 +Subproject commit 7ef2e733416953b222851f9a360d7fc72d068ee5 diff --git a/src/blt/fs/file_writers.cpp b/src/blt/fs/file_writers.cpp index fb832a7..4c47b7c 100644 --- a/src/blt/fs/file_writers.cpp +++ b/src/blt/fs/file_writers.cpp @@ -15,11 +15,12 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#include #include -#include #include +#include #include +#include +#include namespace blt::fs { @@ -48,6 +49,12 @@ namespace blt::fs m_buffer.resize(buffer_size); } + void buffered_writer::newfile(const std::string& new_name) + { + fwriter_t::newfile(new_name); + setvbuf(this->m_file, nullptr, _IONBF, 0); + } + i64 buffered_writer::write(const char* buffer, const size_t bytes) { if (bytes > m_buffer.size()) @@ -61,20 +68,86 @@ namespace blt::fs void buffered_writer::flush() { - fwriter_t::flush(); fwriter_t::write(m_buffer.data(), m_current_pos); + fwriter_t::flush(); m_current_pos = 0; } - bounded_writer::bounded_writer(fwriter_t& writer, std::optional base_name, - const std::function)>& naming_function, - const size_t max_size): fwriter_t{naming_function(0, base_name)}, m_writer(&writer), - m_base_name(std::move(base_name)), m_max_size(max_size), - m_naming_function(naming_function) - {} - - i64 bounded_writer::write(const char* buffer, size_t bytes) + bounded_writer::bounded_writer(fwriter_t& writer, std::optional base_name, const size_t max_size, naming_function_t naming_function): + m_writer(&writer), m_base_name(std::move(base_name)), m_max_size(max_size), m_naming_function(std::move(naming_function)) { - return fwriter_t::write(buffer, bytes); + bounded_writer::newfile(m_base_name.value_or("")); + } + + i64 bounded_writer::write(const char* buffer, const size_t bytes) + { + m_currently_written += bytes; + if (m_currently_written > m_max_size) + this->newfile(m_base_name.value_or("")); + return m_writer->write(buffer, bytes); + } + + void bounded_writer::newfile(const std::string& new_name) + { + ++m_current_invocation; + m_currently_written = 0; + m_writer->newfile(m_naming_function(m_current_invocation, new_name)); + } + + void bounded_writer::flush() + { + m_writer->flush(); + } + + rotating_writer::rotating_writer(fwriter_t& writer, const time_t period): m_writer{&writer}, m_period{period} + { + newfile(); + } + + i64 rotating_writer::write(const char* buffer, const size_t bytes) + { + check_for_time(); + return m_writer->write(buffer, bytes); + } + + void rotating_writer::flush() + { + check_for_time(); + m_writer->flush(); + } + + void rotating_writer::newfile(const std::string& new_name) + { + m_writer->newfile(new_name); + } + + void rotating_writer::newfile() + { + m_last_time = get_current_time(); + std::string name; + name += std::to_string(m_last_time.year); + name += "-" + std::to_string(m_last_time.month); + name += "-" + std::to_string(m_last_time.day); + if (m_period.hour >= 0) + name += "-" + std::to_string(m_last_time.hour); + name += ".txt"; + newfile(name); + } + + void rotating_writer::check_for_time() + { + const auto current_time = get_current_time(); + if ((m_period.hour > 0 && current_time.hour > m_last_time.hour + m_period.hour) || + (m_period.day > 0 && current_time.day > m_last_time.day + m_period.day) || + (m_period.month > 0 && current_time.month > m_last_time.month + m_period.month) || + (m_period.year > 0 && current_time.year > m_last_time.year + m_period.year)) + newfile(); + } + + time_t rotating_writer::get_current_time() + { + const std::time_t time = std::time(nullptr); + const auto current_time = std::localtime(&time); + return {current_time->tm_year, current_time->tm_mon, current_time->tm_mday, current_time->tm_hour}; } } diff --git a/src/blt/fs/threaded_wrtiers.cpp b/src/blt/fs/threaded_wrtiers.cpp new file mode 100644 index 0000000..7a5cba6 --- /dev/null +++ b/src/blt/fs/threaded_wrtiers.cpp @@ -0,0 +1,36 @@ +/* + * + * Copyright (C) 2025 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace blt::fs +{ + concurrent_file_writer::concurrent_file_writer(writer_t* writer): m_writer{writer} + {} + + i64 concurrent_file_writer::write(const char* buffer, const size_t bytes) + { + std::scoped_lock lock{m_mutex}; + return m_writer->write(buffer, bytes); + } + + void concurrent_file_writer::flush() + { + std::scoped_lock lock{m_mutex}; + m_writer->flush(); + } +} \ No newline at end of file diff --git a/src/blt/logging/logging_config.cpp b/src/blt/logging/logging_config.cpp index fcb072d..10bd0f6 100644 --- a/src/blt/logging/logging_config.cpp +++ b/src/blt/logging/logging_config.cpp @@ -20,5 +20,15 @@ namespace blt::logging { + std::string logging_config_t::get_default_log_format() + {} -} \ No newline at end of file + std::vector logging_config_t::get_default_log_outputs() + {} + + std::array logging_config_t::get_default_log_level_colors() + {} + + std::array logging_config_t::get_default_log_level_names() + {} +} From 66efccf095be845452e0b49793745eb0d82a1b58 Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Fri, 7 Mar 2025 16:42:26 -0500 Subject: [PATCH 065/101] silly cable --- CMakeLists.txt | 2 +- include/blt/fs/file_writers.h | 1 + include/blt/fs/nbt.h | 11 ++-- include/blt/iterator/enumerate.h | 2 +- include/blt/logging/ansi.h | 3 +- include/blt/logging/logging_config.h | 80 +++++++++++++++++++++++++++ src/blt/fs/file_writers.cpp | 9 ++- src/blt/fs/nbt.cpp | 3 +- src/blt/logging/logging_config.cpp | 83 ++++++++++++++++++++++++++-- tests/iterator_tests.cpp | 2 +- 10 files changed, 180 insertions(+), 16 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f30080a..a0effd4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.2.0) +set(BLT_VERSION 5.2.1) set(BLT_TARGET BLT) diff --git a/include/blt/fs/file_writers.h b/include/blt/fs/file_writers.h index db25ed6..52ef476 100644 --- a/include/blt/fs/file_writers.h +++ b/include/blt/fs/file_writers.h @@ -65,6 +65,7 @@ namespace blt::fs { public: explicit buffered_writer(const std::string& name, size_t buffer_size = 1024 * 128); + explicit buffered_writer(size_t buffer_size = 1024 * 128); i64 write(const char* buffer, size_t bytes) override; diff --git a/include/blt/fs/nbt.h b/include/blt/fs/nbt.h index 2efa0b7..2353c42 100644 --- a/include/blt/fs/nbt.h +++ b/include/blt/fs/nbt.h @@ -359,15 +359,17 @@ namespace blt::nbt { void writePayload(blt::fs::writer_t& out) final { for (const auto& v : t){ auto tag = v.second; - out.put((char) tag->getType()); + auto c = (char) tag->getType(); + out.write(&c, 1); tag->writeName(out); tag->writePayload(out); } - out.put('\0'); + const char c = '\0'; + out.write(&c, 1); } void readPayload(blt::fs::reader_t& in) final { char type; - while ((type = in.get()) != (char)nbt_tag::END){ + while ((in.read(&type, 1), type) != (char)nbt_tag::END){ auto* v = _internal_::toType(type); v->readName(in); v->readPayload(in); @@ -431,7 +433,8 @@ namespace blt::nbt { delete root; } void write(tag_compound& root){ - writer.put((char)nbt_tag::COMPOUND); + auto c = (char)nbt_tag::COMPOUND; + writer.write(&c, 1); root.writeName(writer); root.writePayload(writer); } diff --git a/include/blt/iterator/enumerate.h b/include/blt/iterator/enumerate.h index 2f63cc2..71f3687 100644 --- a/include/blt/iterator/enumerate.h +++ b/include/blt/iterator/enumerate.h @@ -34,7 +34,7 @@ namespace blt } using iterator_category = typename std::iterator_traits::iterator_category; - using value_type = enumerate_item; + using value_type = enumerate_item>; using difference_type = blt::ptrdiff_t; using pointer = value_type; using reference = value_type; diff --git a/include/blt/logging/ansi.h b/include/blt/logging/ansi.h index 1113332..d07c7f1 100644 --- a/include/blt/logging/ansi.h +++ b/include/blt/logging/ansi.h @@ -71,7 +71,8 @@ namespace blt::logging::ansi BLUE = 4, MAGENTA = 5, CYAN = 6, - WHITE = 7, }; + WHITE = 7 + }; struct rgb_t { diff --git a/include/blt/logging/logging_config.h b/include/blt/logging/logging_config.h index c5a843c..5ae7b93 100644 --- a/include/blt/logging/logging_config.h +++ b/include/blt/logging/logging_config.h @@ -28,6 +28,82 @@ namespace blt::logging { + namespace tags + { + // Current Year + inline constexpr auto YEAR = "{YEAR}"; + // Current Month + inline constexpr auto MONTH = "{MONTH}"; + // Current Day + inline constexpr auto DAY = "{DAY}"; + // Current Hour + inline constexpr auto HOUR = "{HOUR}"; + // Current Minute + inline constexpr auto MINUTE = "{MINUTE}"; + // Current Second + inline constexpr auto SECOND = "{SECOND}"; + // Current Millisecond + inline constexpr auto MILLISECOND = "{MS}"; + // Current Nanosecond time, This is direct output of nanotime + inline constexpr auto NANOSECOND = "{NS}"; + // Current Unix time in milliseconds + inline constexpr auto UNIX_TIME = "{UNIX}"; + // Formatted ISO year-month-day in a single variable + inline constexpr auto ISO_YEAR = "{ISO_YEAR}"; + // Formatted hour:minute:second in a single variable + inline constexpr auto TIME = "{TIME}"; + // Formatted year-month-day hour:minute:second in a single variable + inline constexpr auto FULL_TIME = "{FULL_TIME}"; + // Color of the current log level, empty string if use_color = false + inline constexpr auto LOG_COLOR = "{LC}"; + // Color of the error color, empty string if use_color = false + inline constexpr auto ERROR_COLOR = "{EC}"; + // Empty is use_color = false or if log level is not an error. Otherwise, {EC} + inline constexpr auto CONDITIONAL_ERROR_COLOR = "{CEC}"; + // Resets all ANSI sequences + inline constexpr auto RESET = "{RESET}"; + // Current log level + inline constexpr auto LOG_LEVEL = "{LL}"; + // Current thread name. Requires you to manually set the thread name using blt::logging::set_thread_name() from that thread. + inline constexpr auto THREAD_NAME = "{TN}"; + // Current file from where the log call was invoked. + inline constexpr auto FILE = "{FILE}"; + // Current line from where the log call was invoked + inline constexpr auto LINE = "{LINE}"; + // User string input, formatted with provided args + inline constexpr auto STR = "{STR}"; + + namespace detail + { + enum class log_tag_token_t : u8 + { + YEAR, + MONTH, + DAY, + HOUR, + MINUTE, + SECOND, + MS, + NS, + UNIX, + ISO_YEAR, + TIME, + FULL_TIME, + LC, + EC, + CEC, + RESET, + LL, + TN, + FILE, + LINE, + STR, + // token used to describe that a non-format token should be consumed. aka a normal string from the file. + CONTENT + }; + } + } + enum class log_level_t : u8 { TRACE, @@ -43,10 +119,12 @@ namespace blt::logging class logging_config_t { friend logger_t; + public: // wrappers for streams exist in blt/fs/stream_wrappers.h std::vector log_outputs = get_default_log_outputs(); std::string log_format = get_default_log_format(); + std::string error_color = get_default_error_color(); std::array log_level_colors = get_default_log_level_colors(); std::array log_level_names = get_default_log_level_names(); log_level_t level = log_level_t::TRACE; @@ -56,11 +134,13 @@ namespace blt::logging // this will attempt to use the maximum possible size for each printed element, then align to that. // This creates output where the user message always starts at the same column. bool ensure_alignment = true; + private: static std::string get_default_log_format(); static std::vector get_default_log_outputs(); static std::array get_default_log_level_colors(); static std::array get_default_log_level_names(); + static std::string get_default_error_color(); }; } diff --git a/src/blt/fs/file_writers.cpp b/src/blt/fs/file_writers.cpp index 4c47b7c..2bc5b94 100644 --- a/src/blt/fs/file_writers.cpp +++ b/src/blt/fs/file_writers.cpp @@ -44,7 +44,12 @@ namespace blt::fs throw std::runtime_error("Failed to open file for writing"); } - buffered_writer::buffered_writer(const std::string& name, const size_t buffer_size): fwriter_t{name} + buffered_writer::buffered_writer(const std::string& name, const size_t buffer_size): fwriter_t{name, "ab"} + { + m_buffer.resize(buffer_size); + } + + buffered_writer::buffered_writer(const size_t buffer_size) { m_buffer.resize(buffer_size); } @@ -148,6 +153,6 @@ namespace blt::fs { const std::time_t time = std::time(nullptr); const auto current_time = std::localtime(&time); - return {current_time->tm_year, current_time->tm_mon, current_time->tm_mday, current_time->tm_hour}; + return {current_time->tm_year + 1900, current_time->tm_mon + 1, current_time->tm_mday, current_time->tm_hour}; } } diff --git a/src/blt/fs/nbt.cpp b/src/blt/fs/nbt.cpp index 2c0dc1a..1679977 100644 --- a/src/blt/fs/nbt.cpp +++ b/src/blt/fs/nbt.cpp @@ -33,7 +33,8 @@ namespace blt::nbt { } void NBTReader::read() { - char t = reader.get(); + char t; + reader.read(&t, 1); if (t != (char)nbt_tag::COMPOUND) { BLT_WARN("Found %d", t); throw std::runtime_error("Incorrectly formatted NBT data! Root tag must be a compound tag!"); diff --git a/src/blt/logging/logging_config.cpp b/src/blt/logging/logging_config.cpp index 10bd0f6..2d1295c 100644 --- a/src/blt/logging/logging_config.cpp +++ b/src/blt/logging/logging_config.cpp @@ -15,20 +15,93 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#include +#include +#include #include +#include +#include namespace blt::logging { + namespace tags::detail + { + hashmap_t make_map() + { + hashmap_t map{}; + map[YEAR] = log_tag_token_t::YEAR; + map[MONTH] = log_tag_token_t::MONTH; + map[DAY] = log_tag_token_t::DAY; + map[HOUR] = log_tag_token_t::HOUR; + map[MINUTE] = log_tag_token_t::MINUTE; + map[SECOND] = log_tag_token_t::SECOND; + map[MILLISECOND] = log_tag_token_t::MS; + map[NANOSECOND] = log_tag_token_t::NS; + map[UNIX_TIME] = log_tag_token_t::UNIX; + map[ISO_YEAR] = log_tag_token_t::ISO_YEAR; + map[TIME] = log_tag_token_t::TIME; + map[FULL_TIME] = log_tag_token_t::FULL_TIME; + map[LOG_COLOR] = log_tag_token_t::LC; + map[ERROR_COLOR] = log_tag_token_t::EC; + map[CONDITIONAL_ERROR_COLOR] = log_tag_token_t::CEC; + map[RESET] = log_tag_token_t::RESET; + map[LOG_LEVEL] = log_tag_token_t::LL; + map[THREAD_NAME] = log_tag_token_t::TN; + map[FILE] = log_tag_token_t::FILE; + map[LINE] = log_tag_token_t::LINE; + map[STR] = log_tag_token_t::STR; + return map; + } + + hashmap_t tag_map = make_map(); + } + std::string logging_config_t::get_default_log_format() - {} + { + return build(fg(ansi::color::color8_bright::CYAN)) + "[" + tags::FULL_TIME + "]" + tags::RESET + " " + tags::LOG_COLOR + "[" + tags::LOG_LEVEL + + "]" + tags::RESET + " " + build(fg(ansi::color::color8::MAGENTA)) + "(" + tags::FILE + ":" + tags::LINE + ")" + tags::RESET + " " + + tags::CONDITIONAL_ERROR_COLOR + tags::STR + tags::RESET + "\n"; + } std::vector logging_config_t::get_default_log_outputs() - {} + { + static fs::fstream_writer_t cout_writer{std::cout}; + std::vector outputs{}; + outputs.push_back(&cout_writer); + return outputs; + } std::array logging_config_t::get_default_log_level_colors() - {} + { + return { + // TRACE + build(fg(ansi::color::color8_bright::WHITE)), + // DEBUG + build(fg(ansi::color::color8::CYAN)), + // INFO + build(fg(ansi::color::color8_bright::GREEN)), + // WARN + build(fg(ansi::color::color8_bright::YELLOW)), + // ERROR + build(fg(ansi::color::color8_bright::RED)), + // FATAL + build(fg(ansi::color::color8_bright::WHITE), bg(ansi::color::color8_bright::RED)), + }; + } std::array logging_config_t::get_default_log_level_names() - {} + { + return { + "TRACE", + "DEBUG", + "INFO", + "WARN", + "ERROR", + "FATAL", + }; + } + + std::string logging_config_t::get_default_error_color() + { + return build(fg(ansi::color::color8_bright::RED)); + } } diff --git a/tests/iterator_tests.cpp b/tests/iterator_tests.cpp index 66f787f..d267ccc 100644 --- a/tests/iterator_tests.cpp +++ b/tests/iterator_tests.cpp @@ -216,7 +216,7 @@ void test_iterate() } BLT_TRACE("================================"); for (auto a : blt::iterate(array_1).map([](const blt::vec2& in) { return in.normalize(); }) - .enumerate().filter([](const auto& f) { return f.value.x() > 0.5; })) + .enumerate().filter([](const auto& f) { return f.second.x() > 0.5; })) { if (!a) continue; From 07a11656fadf32e6e4749e87b86683305f93cfd8 Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Fri, 7 Mar 2025 18:40:43 -0500 Subject: [PATCH 066/101] config works --- CMakeLists.txt | 2 +- include/blt/fs/stream_wrappers.h | 2 +- include/blt/logging/logging.h | 306 +++++++++++++-------------- include/blt/logging/logging_config.h | 78 ++++++- src/blt/logging/logging.cpp | 32 ++- src/blt/logging/logging_config.cpp | 90 +++++++- tests/logger_tests.cpp | 4 +- 7 files changed, 343 insertions(+), 171 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a0effd4..5bc147a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.2.1) +set(BLT_VERSION 5.2.2) set(BLT_TARGET BLT) diff --git a/include/blt/fs/stream_wrappers.h b/include/blt/fs/stream_wrappers.h index 2a5094d..dff7024 100644 --- a/include/blt/fs/stream_wrappers.h +++ b/include/blt/fs/stream_wrappers.h @@ -99,7 +99,7 @@ namespace blt::fs void write(const T& t) { static_assert(std::is_trivially_copyable_v); - m_writer->write(reinterpret_cast(&t), sizeof(T)); + m_writer->write(reinterpret_cast(&t), sizeof(T)); } template diff --git a/include/blt/logging/logging.h b/include/blt/logging/logging.h index 2c5441a..35db5bc 100644 --- a/include/blt/logging/logging.h +++ b/include/blt/logging/logging.h @@ -25,181 +25,181 @@ #include #include #include +#include #include #include namespace blt::logging { - namespace detail - { - } + namespace detail + {} - struct logger_t - { - explicit logger_t(std::ostream& stream): m_stream(stream) - { - } + struct logger_t + { + explicit logger_t(std::ostream& stream): m_stream(stream) + {} - template - std::string log(std::string fmt, Args&&... args) - { - auto sequence = std::make_integer_sequence{}; - m_arg_print_funcs.clear(); - m_arg_print_funcs.resize(sizeof...(Args)); - create_conv_funcs(sequence, std::forward(args)...); - compile(std::move(fmt)); - process_strings(); - return to_string(); - } + template + std::string log(std::string fmt, Args&&... args) + { + auto sequence = std::make_integer_sequence{}; + m_arg_print_funcs.clear(); + m_arg_print_funcs.resize(sizeof...(Args)); + create_conv_funcs(sequence, std::forward(args)...); + compile(std::move(fmt)); + process_strings(); + return to_string(); + } - [[nodiscard]] std::string to_string() const; + [[nodiscard]] std::string to_string() const; - private: - template - void create_conv_funcs(std::integer_sequence, Args&&... args) - { - ((handle_func(std::forward(args))), ...); - } + private: + template + void create_conv_funcs(std::integer_sequence, Args&&... args) + { + ((handle_func(std::forward(args))), ...); + } - template - void handle_func(const T& t) - { - m_arg_print_funcs[index] = [&t, this](std::ostream& stream, const fmt_spec_t& type) - { - switch (type.sign) - { - case fmt_sign_t::SPACE: - if constexpr (std::is_arithmetic_v) - { - if (type.type != fmt_type_t::BINARY && static_cast(t) >= 0l) - stream << ' '; - } - break; - case fmt_sign_t::PLUS: - case fmt_sign_t::MINUS: - break; - } - switch (type.type) - { - case fmt_type_t::BINARY: - { - if constexpr (std::is_trivially_copyable_v) - { - // copy bytes of type - char buffer[sizeof(T)]; - std::memcpy(buffer, &t, sizeof(T)); - // display prefix - if (type.alternate_form) - stream << '0' << (type.uppercase ? 'B' : 'b'); - // print bytes - for (size_t i = 0; i < sizeof(T); ++i) - { - // print bits - for (size_t j = 0; j < 8; ++j) - stream << ((buffer[i] & (1 << j)) ? '1' : '0'); - // special seperator defined via sign (weird hack, change?) - if (type.sign == fmt_sign_t::SPACE && i != sizeof(T) - 1) - stream << ' '; - } - } - else - { - if constexpr (blt::meta::is_streamable_v) - stream << t; - else - stream << "{INVALID TYPE}"; - } - break; - } - case fmt_type_t::CHAR: - if constexpr (std::is_arithmetic_v || std::is_convertible_v) - { - stream << static_cast(t); - } - else - { - if constexpr (blt::meta::is_streamable_v) - stream << t; - else - stream << "{INVALID TYPE}"; - } - break; - case fmt_type_t::TYPE: - stream << blt::type_string(); - break; - default: - handle_type(stream, type); - if constexpr (blt::meta::is_streamable_v) - { - if constexpr (std::is_same_v || std::is_same_v) - stream << static_cast(t); - else if constexpr (std::is_same_v) - stream << static_cast(t); - else - stream << t; - }else - stream << "{INVALID TYPE}"; - } - }; - } + template + void handle_func(const T& t) + { + m_arg_print_funcs[index] = [&t, this](std::ostream& stream, const fmt_spec_t& type) { + switch (type.sign) + { + case fmt_sign_t::SPACE: + if constexpr (std::is_arithmetic_v) + { + if (type.type != fmt_type_t::BINARY && static_cast(t) >= 0l) + stream << ' '; + } + break; + case fmt_sign_t::PLUS: + case fmt_sign_t::MINUS: + break; + } + switch (type.type) + { + case fmt_type_t::BINARY: + { + if constexpr (std::is_trivially_copyable_v) + { + // copy bytes of type + char buffer[sizeof(T)]; + std::memcpy(buffer, &t, sizeof(T)); + // display prefix + if (type.alternate_form) + stream << '0' << (type.uppercase ? 'B' : 'b'); + // print bytes + for (size_t i = 0; i < sizeof(T); ++i) + { + // print bits + for (size_t j = 0; j < 8; ++j) + stream << ((buffer[i] & (1 << j)) ? '1' : '0'); + // special seperator defined via sign (weird hack, change?) + if (type.sign == fmt_sign_t::SPACE && i != sizeof(T) - 1) + stream << ' '; + } + } else + { + if constexpr (blt::meta::is_streamable_v) + stream << t; + else + stream << "{INVALID TYPE}"; + } + break; + } + case fmt_type_t::CHAR: + if constexpr (std::is_arithmetic_v || std::is_convertible_v) + { + stream << static_cast(t); + } else + { + if constexpr (blt::meta::is_streamable_v) + stream << t; + else + stream << "{INVALID TYPE}"; + } + break; + case fmt_type_t::TYPE: + stream << blt::type_string(); + break; + default: + handle_type(stream, type); + if constexpr (blt::meta::is_streamable_v) + { + if constexpr (std::is_same_v || std::is_same_v) + stream << static_cast(t); + else if constexpr (std::is_same_v) + stream << static_cast(t); + else + stream << t; + } else + stream << "{INVALID TYPE}"; + } + }; + } - [[nodiscard]] size_t find_ending_brace(size_t begin) const; - void setup_stream(const fmt_spec_t& spec) const; - void process_strings(); - static void handle_type(std::ostream& stream, const fmt_spec_t& spec); + [[nodiscard]] size_t find_ending_brace(size_t begin) const; + void setup_stream(const fmt_spec_t& spec) const; + void process_strings(); + static void handle_type(std::ostream& stream, const fmt_spec_t& spec); - static void exponential(std::ostream& stream); - static void fixed(std::ostream& stream); + static void exponential(std::ostream& stream); + static void fixed(std::ostream& stream); - void compile(std::string fmt); + void compile(std::string fmt); - std::optional> consume_to_next_fmt(); + std::optional> consume_to_next_fmt(); - std::string m_fmt; - std::ostream& m_stream; - fmt_parser_t m_parser{m_arg_print_funcs}; - // normal sections of string - std::vector m_string_sections; - // processed format specs - std::vector m_fmt_specs; - std::vector> m_arg_print_funcs; - size_t m_last_fmt_pos = 0; - size_t m_arg_pos = 0; - }; + std::string m_fmt; + std::ostream& m_stream; + fmt_parser_t m_parser{m_arg_print_funcs}; + // normal sections of string + std::vector m_string_sections; + // processed format specs + std::vector m_fmt_specs; + std::vector> m_arg_print_funcs; + size_t m_last_fmt_pos = 0; + size_t m_arg_pos = 0; + }; - void print(const std::string& str); + void print(const std::string& str); - void newline(); + void newline(); - logger_t& get_global_logger(); + logger_t& get_global_logger(); - template - void print(std::string fmt, Args&&... args) - { - auto& logger = get_global_logger(); - print(logger.log(std::move(fmt), std::forward(args)...)); - } + logging_config_t& get_global_config(); - template - void print(std::ostream& stream, std::string fmt, Args&&... args) - { - auto& logger = get_global_logger(); - stream << logger.log(std::move(fmt), std::forward(args)...); - } + void set_thread_name(const std::string& name); - template - void println(std::string fmt, Args&&... args) - { - print(std::move(fmt), std::forward(args)...); - newline(); - } + template + void print(std::string fmt, Args&&... args) + { + auto& logger = get_global_logger(); + print(logger.log(std::move(fmt), std::forward(args)...)); + } - template - void println(std::ostream& stream, std::string fmt, Args&&... args) - { - print(stream, std::move(fmt), std::forward(args)...); - stream << std::endl; - } + template + void print(std::ostream& stream, std::string fmt, Args&&... args) + { + auto& logger = get_global_logger(); + stream << logger.log(std::move(fmt), std::forward(args)...); + } + + template + void println(std::string fmt, Args&&... args) + { + print(std::move(fmt), std::forward(args)...); + newline(); + } + + template + void println(std::ostream& stream, std::string fmt, Args&&... args) + { + print(stream, std::move(fmt), std::forward(args)...); + stream << std::endl; + } } #endif // BLT_LOGGING_LOGGING_H diff --git a/include/blt/logging/logging_config.h b/include/blt/logging/logging_config.h index 5ae7b93..afaad32 100644 --- a/include/blt/logging/logging_config.h +++ b/include/blt/logging/logging_config.h @@ -22,9 +22,9 @@ #include #include #include -#include -#include #include +#include +#include namespace blt::logging { @@ -121,6 +121,79 @@ namespace blt::logging friend logger_t; public: + logging_config_t() + { + compile(); + } + + void compile(); + + logging_config_t& add_log_output(fs::writer_t* writer) + { + log_outputs.push_back(writer); + return *this; + } + + logging_config_t& set_log_format(std::string format) + { + log_format = std::move(format); + compile(); + return *this; + } + + logging_config_t& set_error_color(std::string color) + { + error_color = std::move(color); + compile(); + return *this; + } + + logging_config_t& set_log_level_colors(std::array colors) + { + log_level_colors = std::move(colors); + compile(); + return *this; + } + + logging_config_t& set_log_level_names(std::array names) + { + log_level_names = std::move(names); + return *this; + } + + logging_config_t& set_level(const log_level_t level) + { + this->level = level; + return *this; + } + + logging_config_t& set_use_color(const bool use_color) + { + this->use_color = use_color; + compile(); + return *this; + } + + logging_config_t& set_print_full_name(const bool print_full_name) + { + this->print_full_name = print_full_name; + return *this; + } + + logging_config_t& set_ensure_alignment(const bool ensure_alignment) + { + this->ensure_alignment = ensure_alignment; + return *this; + } + + [[nodiscard]] std::pair&, const std::vector&> get_log_tag_tokens() const + { + return {log_tag_tokens, log_tag_content}; + } + + private: + std::vector log_tag_content; + std::vector log_tag_tokens; // wrappers for streams exist in blt/fs/stream_wrappers.h std::vector log_outputs = get_default_log_outputs(); std::string log_format = get_default_log_format(); @@ -135,7 +208,6 @@ namespace blt::logging // This creates output where the user message always starts at the same column. bool ensure_alignment = true; - private: static std::string get_default_log_format(); static std::vector get_default_log_outputs(); static std::array get_default_log_level_colors(); diff --git a/src/blt/logging/logging.cpp b/src/blt/logging/logging.cpp index 18fc5e7..f0cc3ec 100644 --- a/src/blt/logging/logging.cpp +++ b/src/blt/logging/logging.cpp @@ -17,19 +17,37 @@ */ #include #include +#include #include +#include #include #include +#include #include namespace blt::logging { + struct global_context_t + { + logging_config_t global_config; + std::mutex thread_name_mutex; + hashmap_t thread_names; + }; + + static global_context_t global_context; + struct logging_thread_context_t { std::stringstream stream; logger_t logger{stream}; }; + logging_thread_context_t& get_thread_context() + { + thread_local logging_thread_context_t context; + return context; + } + std::string logger_t::to_string() const { return dynamic_cast(m_stream).str(); @@ -195,8 +213,7 @@ namespace blt::logging logger_t& get_global_logger() { - thread_local logging_thread_context_t context; - return context.logger; + return get_thread_context().logger; } void print(const std::string& str) @@ -208,4 +225,15 @@ namespace blt::logging { std::cout << std::endl; } + + logging_config_t& get_global_config() + { + return global_context.global_config; + } + + void set_thread_name(const std::string& name) + { + std::scoped_lock lock{global_context.thread_name_mutex}; + global_context.thread_names[std::this_thread::get_id()] = name; + } } diff --git a/src/blt/logging/logging_config.cpp b/src/blt/logging/logging_config.cpp index 2d1295c..9ca501d 100644 --- a/src/blt/logging/logging_config.cpp +++ b/src/blt/logging/logging_config.cpp @@ -52,7 +52,86 @@ namespace blt::logging return map; } - hashmap_t tag_map = make_map(); + std::array(log_tag_token_t::CONTENT)> tag_known_values{ + // year + false, + // month + false, + // day + false, + // hour + false, + // minute + false, + // second + false, + // ms + false, + // ns + false, + // unix + false, + // iso year + false, + // time + false, + // full_time + false, + // lc + true, + // ec + true, + // conditional error + false, + // reset + true, + // log level + false, + // thread_name + false, + // file + false, + // line + false, + // str + false + }; + } + + void logging_config_t::compile() + { + static hashmap_t tag_map = tags::detail::make_map(); + log_tag_content.clear(); + log_tag_tokens.clear(); + + size_t i = 0; + for (; i < log_format.size(); ++i) + { + size_t start = i; + while (i < log_format.size() && log_format[i] != '{') + ++i; + if (i == log_format.size() || (i < log_format.size() && (i - start) > 0)) + { + log_tag_content.emplace_back(std::string_view(log_format.data() + start, i - start)); + log_tag_tokens.emplace_back(tags::detail::log_tag_token_t::CONTENT); + if (i == log_format.size()) + break; + } + start = i; + while (i < log_format.size() && log_format[i] != '}') + ++i; + const auto tag = std::string_view(log_format.data() + start, i - start + 1); + auto it = tag_map.find(tag); + if (it == tag_map.end()) + throw std::runtime_error("Invalid log tag: " + std::string(tag)); + log_tag_tokens.emplace_back(it->second); + } + + if (i < log_format.size()) + { + log_tag_content.emplace_back(std::string_view(log_format.data() + i, log_format.size() - i)); + log_tag_tokens.emplace_back(tags::detail::log_tag_token_t::CONTENT); + } } std::string logging_config_t::get_default_log_format() @@ -90,14 +169,7 @@ namespace blt::logging std::array logging_config_t::get_default_log_level_names() { - return { - "TRACE", - "DEBUG", - "INFO", - "WARN", - "ERROR", - "FATAL", - }; + return {"TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL",}; } std::string logging_config_t::get_default_error_color() diff --git a/tests/logger_tests.cpp b/tests/logger_tests.cpp index 527dcb2..03870f9 100644 --- a/tests/logger_tests.cpp +++ b/tests/logger_tests.cpp @@ -160,10 +160,10 @@ int main() std::ofstream os("test.txt"); blt::fs::fstream_writer_t wtr(os); - blt::fs::writer_wrapper_t writer(wtr); + blt::fs::writer_string_wrapper_t writer(wtr); writer.write("This is a println with a stream\n"); - writer.write("This is a mixed print"); + writer.write("This is a mixed print "); writer.write(std::to_string(25)); writer.write(" with multiple types "); writer.write(std::to_string(34.23340)); From 60536fd602c6483f91083fc75666d4168d5b18c6 Mon Sep 17 00:00:00 2001 From: Brett Date: Sun, 9 Mar 2025 15:06:31 -0400 Subject: [PATCH 067/101] i think logging is working. need to make macros and test --- CMakeLists.txt | 2 +- include/blt/logging/logging.h | 21 +- include/blt/logging/logging_config.h | 52 ++--- libraries/parallel-hashmap | 2 +- src/blt/logging/logging.cpp | 11 +- src/blt/logging/logging_config.cpp | 276 +++++++++++++++++++++------ 6 files changed, 274 insertions(+), 90 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5bc147a..cba9a6d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.2.2) +set(BLT_VERSION 5.2.3) set(BLT_TARGET BLT) diff --git a/include/blt/logging/logging.h b/include/blt/logging/logging.h index 35db5bc..ba091aa 100644 --- a/include/blt/logging/logging.h +++ b/include/blt/logging/logging.h @@ -31,9 +31,6 @@ namespace blt::logging { - namespace detail - {} - struct logger_t { explicit logger_t(std::ostream& stream): m_stream(stream) @@ -173,6 +170,8 @@ namespace blt::logging void set_thread_name(const std::string& name); + const std::string& get_thread_name(); + template void print(std::string fmt, Args&&... args) { @@ -200,6 +199,22 @@ namespace blt::logging print(stream, std::move(fmt), std::forward(args)...); stream << std::endl; } + + template + void log(log_level_t level, const char* file, const i32 line, std::string fmt, Args&&... args) + { + auto& logger = get_global_logger(); + auto& config = get_global_config(); + auto user_str = logger.log(std::move(fmt), std::forward(args)...); + auto log_fmt_str = config.generate(user_str, get_thread_name(), level, file, line); + if (log_fmt_str) + print(std::move(*log_fmt_str)); + } + + namespace detail + {} } +#define BLT_LOG(level, fmt, ...) blt::logging::log(level, __FILE__, __LINE__, fmt, ##__VA_ARGS__) + #endif // BLT_LOGGING_LOGGING_H diff --git a/include/blt/logging/logging_config.h b/include/blt/logging/logging_config.h index afaad32..1ce8656 100644 --- a/include/blt/logging/logging_config.h +++ b/include/blt/logging/logging_config.h @@ -20,6 +20,7 @@ #define BLT_LOGGING_LOGGING_CONFIG_H #include +#include #include #include #include @@ -44,10 +45,12 @@ namespace blt::logging inline constexpr auto SECOND = "{SECOND}"; // Current Millisecond inline constexpr auto MILLISECOND = "{MS}"; - // Current Nanosecond time, This is direct output of nanotime + // Current Nanosecond inline constexpr auto NANOSECOND = "{NS}"; // Current Unix time in milliseconds inline constexpr auto UNIX_TIME = "{UNIX}"; + // Current Unix time in nanosecond + inline constexpr auto UNIX_TIME_NANO = "{UNIX_NANO}"; // Formatted ISO year-month-day in a single variable inline constexpr auto ISO_YEAR = "{ISO_YEAR}"; // Formatted hour:minute:second in a single variable @@ -86,6 +89,7 @@ namespace blt::logging MS, NS, UNIX, + UNIX_NANO, ISO_YEAR, TIME, FULL_TIME, @@ -130,83 +134,87 @@ namespace blt::logging logging_config_t& add_log_output(fs::writer_t* writer) { - log_outputs.push_back(writer); + m_log_outputs.push_back(writer); return *this; } logging_config_t& set_log_format(std::string format) { - log_format = std::move(format); + m_log_format = std::move(format); compile(); return *this; } logging_config_t& set_error_color(std::string color) { - error_color = std::move(color); + m_error_color = std::move(color); compile(); return *this; } logging_config_t& set_log_level_colors(std::array colors) { - log_level_colors = std::move(colors); + m_log_level_colors = std::move(colors); compile(); return *this; } logging_config_t& set_log_level_names(std::array names) { - log_level_names = std::move(names); + m_log_level_names = std::move(names); return *this; } logging_config_t& set_level(const log_level_t level) { - this->level = level; + this->m_level = level; return *this; } logging_config_t& set_use_color(const bool use_color) { - this->use_color = use_color; + this->m_use_color = use_color; compile(); return *this; } logging_config_t& set_print_full_name(const bool print_full_name) { - this->print_full_name = print_full_name; + this->m_print_full_name = print_full_name; return *this; } logging_config_t& set_ensure_alignment(const bool ensure_alignment) { - this->ensure_alignment = ensure_alignment; + this->m_ensure_alignment = ensure_alignment; return *this; } [[nodiscard]] std::pair&, const std::vector&> get_log_tag_tokens() const { - return {log_tag_tokens, log_tag_content}; + return {m_log_tag_tokens, m_log_tag_content}; } + std::optional generate(const std::string& user_str, const std::string& thread_name, log_level_t level, const char* file, + i32 line) const; + private: - std::vector log_tag_content; - std::vector log_tag_tokens; + std::vector m_log_tag_content; + std::vector m_log_tag_tokens; // wrappers for streams exist in blt/fs/stream_wrappers.h - std::vector log_outputs = get_default_log_outputs(); - std::string log_format = get_default_log_format(); - std::string error_color = get_default_error_color(); - std::array log_level_colors = get_default_log_level_colors(); - std::array log_level_names = get_default_log_level_names(); - log_level_t level = log_level_t::TRACE; - bool use_color = true; + std::vector m_log_outputs = get_default_log_outputs(); + std::string m_log_format = get_default_log_format(); + std::string m_error_color = get_default_error_color(); + std::array m_log_level_colors = get_default_log_level_colors(); + std::array m_log_level_names = get_default_log_level_names(); + log_level_t m_level = log_level_t::TRACE; + + bool m_use_color = true; // if true prints the whole path to the file (eg /home/user/.../.../project/src/source.cpp:line#) - bool print_full_name = false; + bool m_print_full_name = false; // this will attempt to use the maximum possible size for each printed element, then align to that. // This creates output where the user message always starts at the same column. - bool ensure_alignment = true; + bool m_ensure_alignment = true; static std::string get_default_log_format(); static std::vector get_default_log_outputs(); diff --git a/libraries/parallel-hashmap b/libraries/parallel-hashmap index 7ef2e73..93201da 160000 --- a/libraries/parallel-hashmap +++ b/libraries/parallel-hashmap @@ -1 +1 @@ -Subproject commit 7ef2e733416953b222851f9a360d7fc72d068ee5 +Subproject commit 93201da2ba5a6aba0a6e57ada64973555629b3e3 diff --git a/src/blt/logging/logging.cpp b/src/blt/logging/logging.cpp index f0cc3ec..5d52436 100644 --- a/src/blt/logging/logging.cpp +++ b/src/blt/logging/logging.cpp @@ -30,8 +30,6 @@ namespace blt::logging struct global_context_t { logging_config_t global_config; - std::mutex thread_name_mutex; - hashmap_t thread_names; }; static global_context_t global_context; @@ -39,6 +37,7 @@ namespace blt::logging struct logging_thread_context_t { std::stringstream stream; + std::string thread_name; logger_t logger{stream}; }; @@ -233,7 +232,11 @@ namespace blt::logging void set_thread_name(const std::string& name) { - std::scoped_lock lock{global_context.thread_name_mutex}; - global_context.thread_names[std::this_thread::get_id()] = name; + get_thread_context().thread_name = name; + } + + const std::string& get_thread_name() + { + return get_thread_context().thread_name; } } diff --git a/src/blt/logging/logging_config.cpp b/src/blt/logging/logging_config.cpp index 9ca501d..fe15c92 100644 --- a/src/blt/logging/logging_config.cpp +++ b/src/blt/logging/logging_config.cpp @@ -15,11 +15,14 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +#include #include #include #include #include #include +#include +#include namespace blt::logging { @@ -37,6 +40,7 @@ namespace blt::logging map[MILLISECOND] = log_tag_token_t::MS; map[NANOSECOND] = log_tag_token_t::NS; map[UNIX_TIME] = log_tag_token_t::UNIX; + map[UNIX_TIME_NANO] = log_tag_token_t::UNIX_NANO; map[ISO_YEAR] = log_tag_token_t::ISO_YEAR; map[TIME] = log_tag_token_t::TIME; map[FULL_TIME] = log_tag_token_t::FULL_TIME; @@ -51,89 +55,243 @@ namespace blt::logging map[STR] = log_tag_token_t::STR; return map; } - - std::array(log_tag_token_t::CONTENT)> tag_known_values{ - // year - false, - // month - false, - // day - false, - // hour - false, - // minute - false, - // second - false, - // ms - false, - // ns - false, - // unix - false, - // iso year - false, - // time - false, - // full_time - false, - // lc - true, - // ec - true, - // conditional error - false, - // reset - true, - // log level - false, - // thread_name - false, - // file - false, - // line - false, - // str - false - }; } void logging_config_t::compile() { static hashmap_t tag_map = tags::detail::make_map(); - log_tag_content.clear(); - log_tag_tokens.clear(); + m_log_tag_content.clear(); + m_log_tag_tokens.clear(); size_t i = 0; - for (; i < log_format.size(); ++i) + for (; i < m_log_format.size(); ++i) { size_t start = i; - while (i < log_format.size() && log_format[i] != '{') + while (i < m_log_format.size() && m_log_format[i] != '{') ++i; - if (i == log_format.size() || (i < log_format.size() && (i - start) > 0)) + if (i == m_log_format.size() || (i < m_log_format.size() && (i - start) > 0)) { - log_tag_content.emplace_back(std::string_view(log_format.data() + start, i - start)); - log_tag_tokens.emplace_back(tags::detail::log_tag_token_t::CONTENT); - if (i == log_format.size()) + m_log_tag_content.emplace_back(std::string_view(m_log_format.data() + start, i - start)); + m_log_tag_tokens.emplace_back(tags::detail::log_tag_token_t::CONTENT); + if (i == m_log_format.size()) break; } start = i; - while (i < log_format.size() && log_format[i] != '}') + while (i < m_log_format.size() && m_log_format[i] != '}') ++i; - const auto tag = std::string_view(log_format.data() + start, i - start + 1); + const auto tag = std::string_view(m_log_format.data() + start, i - start + 1); auto it = tag_map.find(tag); if (it == tag_map.end()) throw std::runtime_error("Invalid log tag: " + std::string(tag)); - log_tag_tokens.emplace_back(it->second); + m_log_tag_tokens.emplace_back(it->second); } - if (i < log_format.size()) + if (i < m_log_format.size()) { - log_tag_content.emplace_back(std::string_view(log_format.data() + i, log_format.size() - i)); - log_tag_tokens.emplace_back(tags::detail::log_tag_token_t::CONTENT); + m_log_tag_content.emplace_back(std::string_view(m_log_format.data() + i, m_log_format.size() - i)); + m_log_tag_tokens.emplace_back(tags::detail::log_tag_token_t::CONTENT); } } + std::string add_year(const tm* current_time) + { + return std::to_string(current_time->tm_year + 1900); + } + + std::string add_month(const tm* current_time, const bool ensure_alignment) + { + auto str = std::to_string(current_time->tm_mon + 1); + if (ensure_alignment && str.size() < 2) + str.insert(str.begin(), '0'); + return str; + } + + std::string add_day(const tm* current_time, const bool ensure_alignment) + { + auto str = std::to_string(current_time->tm_mday); + if (ensure_alignment && str.size() < 2) + str.insert(str.begin(), '0'); + return str; + } + + std::string add_hour(const tm* current_time, const bool ensure_alignment) + { + auto str = std::to_string(current_time->tm_hour); + if (ensure_alignment && str.size() < 2) + str.insert(str.begin(), '0'); + return str; + } + + std::string add_minute(const tm* current_time, const bool ensure_alignment) + { + auto str = std::to_string(current_time->tm_min); + if (ensure_alignment && str.size() < 2) + str.insert(str.begin(), '0'); + return str; + } + + std::string add_second(const tm* current_time, const bool ensure_alignment) + { + auto str = std::to_string(current_time->tm_sec); + if (ensure_alignment && str.size() < 2) + str.insert(str.begin(), '0'); + return str; + } + + std::optional logging_config_t::generate(const std::string& user_str, const std::string& thread_name, const log_level_t level, + const char* file, const i32 line) const + { + if (level < m_level) + return {}; + + std::string fmt; + + const std::time_t time = std::time(nullptr); + const auto current_time = std::localtime(&time); + const auto millis_time = system::getCurrentTimeMilliseconds(); + const auto nano_time = system::getCurrentTimeNanoseconds(); + + size_t content = 0; + for (const auto& log_tag_token : m_log_tag_tokens) + { + switch (log_tag_token) + { + case tags::detail::log_tag_token_t::YEAR: + fmt += add_year(current_time); + break; + case tags::detail::log_tag_token_t::MONTH: + fmt += add_month(current_time, m_ensure_alignment); + break; + case tags::detail::log_tag_token_t::DAY: + fmt += add_day(current_time, m_ensure_alignment); + break; + case tags::detail::log_tag_token_t::HOUR: + fmt += add_hour(current_time, m_ensure_alignment); + break; + case tags::detail::log_tag_token_t::MINUTE: + fmt += add_minute(current_time, m_ensure_alignment); + break; + case tags::detail::log_tag_token_t::SECOND: + fmt += add_second(current_time, m_ensure_alignment); + break; + case tags::detail::log_tag_token_t::MS: + { + auto str = std::to_string(millis_time % 1000); + if (m_ensure_alignment) + { + for (size_t i = str.size(); i < 4; ++i) + str.insert(str.begin(), '0'); + } + fmt += str; + break; + } + case tags::detail::log_tag_token_t::NS: + { + auto str = std::to_string(nano_time % 1000); + if (m_ensure_alignment) + { + for (size_t i = str.size(); i < 4; ++i) + str.insert(str.begin(), '0'); + } + fmt += str; + break; + } + case tags::detail::log_tag_token_t::UNIX: + { + auto str = std::to_string(millis_time % 1000); + if (m_ensure_alignment) + { + for (size_t i = str.size(); i < 4; ++i) + str.insert(str.begin(), '0'); + } + fmt += str; + break; + } + case tags::detail::log_tag_token_t::UNIX_NANO: + { + auto str = std::to_string(nano_time); + if (m_ensure_alignment) + { + for (size_t i = str.size(); i < 4; ++i) + str.insert(str.begin(), '0'); + } + fmt += str; + break; + } + case tags::detail::log_tag_token_t::ISO_YEAR: + { + fmt += add_year(current_time); + fmt += '-'; + fmt += add_month(current_time, m_ensure_alignment); + fmt += '-'; + fmt += add_day(current_time, m_ensure_alignment); + break; + } + case tags::detail::log_tag_token_t::TIME: + fmt += add_hour(current_time, m_ensure_alignment); + fmt += ':'; + fmt += add_minute(current_time, m_ensure_alignment); + fmt += ':'; + fmt += add_second(current_time, m_ensure_alignment); + break; + case tags::detail::log_tag_token_t::FULL_TIME: + fmt += add_year(current_time); + fmt += '-'; + fmt += add_month(current_time, m_ensure_alignment); + fmt += '-'; + fmt += add_day(current_time, m_ensure_alignment); + fmt += ' '; + fmt += add_hour(current_time, m_ensure_alignment); + fmt += ':'; + fmt += add_minute(current_time, m_ensure_alignment); + fmt += ':'; + fmt += add_second(current_time, m_ensure_alignment); + break; + case tags::detail::log_tag_token_t::LC: + if (!m_use_color) + break; + fmt += m_log_level_colors[static_cast(level)]; + break; + case tags::detail::log_tag_token_t::EC: + if (!m_use_color) + break; + fmt += m_error_color; + break; + case tags::detail::log_tag_token_t::CEC: + if (!m_use_color) + break; + if (static_cast(level) >= static_cast(log_level_t::ERROR)) + fmt += m_error_color; + break; + case tags::detail::log_tag_token_t::RESET: + if (!m_use_color) + break; + fmt += build(ansi::color::color_mode::RESET_ALL); + break; + case tags::detail::log_tag_token_t::LL: + fmt += m_log_level_names[static_cast(level)]; + break; + case tags::detail::log_tag_token_t::TN: + fmt += thread_name; + break; + case tags::detail::log_tag_token_t::FILE: + fmt += file; + break; + case tags::detail::log_tag_token_t::LINE: + fmt += std::to_string(line); + break; + case tags::detail::log_tag_token_t::STR: + fmt += user_str; + break; + case tags::detail::log_tag_token_t::CONTENT: + fmt += m_log_tag_content[content++]; + break; + } + } + + return fmt; + } + std::string logging_config_t::get_default_log_format() { return build(fg(ansi::color::color8_bright::CYAN)) + "[" + tags::FULL_TIME + "]" + tags::RESET + " " + tags::LOG_COLOR + "[" + tags::LOG_LEVEL From be46e8552b5a654d0f0adbb76a2d5ff5142e3f45 Mon Sep 17 00:00:00 2001 From: Brett Date: Sun, 9 Mar 2025 22:47:49 -0400 Subject: [PATCH 068/101] logging library is done --- CMakeLists.txt | 2 +- include/blt/fs/path_helper.h | 6 +++ include/blt/logging/logging.h | 59 +++++++++++++++++++++++++++- include/blt/logging/logging_config.h | 2 + src/blt/fs/path_helper.cpp | 26 ++++++++++++ src/blt/logging/logging.cpp | 8 ++++ src/blt/logging/logging_config.cpp | 32 +++++++-------- tests/logger_tests.cpp | 9 +++++ 8 files changed, 123 insertions(+), 21 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cba9a6d..9ad00ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.2.3) +set(BLT_VERSION 5.2.4) set(BLT_TARGET BLT) diff --git a/include/blt/fs/path_helper.h b/include/blt/fs/path_helper.h index 530eb1a..40a01fe 100644 --- a/include/blt/fs/path_helper.h +++ b/include/blt/fs/path_helper.h @@ -28,6 +28,12 @@ namespace blt::fs std::string base_name(const std::string& str); std::string_view base_name_sv(std::string_view str); + std::string filename(const std::string& str); + std::string_view filename_sv(std::string_view str); + + std::string extension(const std::string& str); + std::string_view extension_sv(std::string_view str); + } #endif //BLT_FS_PATH_HELPER_H diff --git a/include/blt/logging/logging.h b/include/blt/logging/logging.h index ba091aa..0d41cc2 100644 --- a/include/blt/logging/logging.h +++ b/include/blt/logging/logging.h @@ -168,6 +168,8 @@ namespace blt::logging logging_config_t& get_global_config(); + std::ostream& get_local_stream(); + void set_thread_name(const std::string& name); const std::string& get_thread_name(); @@ -200,7 +202,7 @@ namespace blt::logging stream << std::endl; } - template + template void log(log_level_t level, const char* file, const i32 line, std::string fmt, Args&&... args) { auto& logger = get_global_logger(); @@ -212,9 +214,62 @@ namespace blt::logging } namespace detail - {} + { + + } } +#if defined(__clang__) || defined(__llvm__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" +#endif + +#ifdef BLT_DISABLE_LOGGING +#define BLT_LOG(level, fmt, ...) + +#else #define BLT_LOG(level, fmt, ...) blt::logging::log(level, __FILE__, __LINE__, fmt, ##__VA_ARGS__) +#ifdef BLT_DISABLE_TRACE +#define BLT_TRACE(format, ...) +#else +#define BLT_TRACE(format, ...) BLT_LOG(blt::logging::log_level_t::TRACE, format, ##__VA_ARGS__) +#endif + +#ifdef BLT_DISABLE_DEBUG +#define BLT_DEBUG(format, ...) +#else +#define BLT_DEBUG(format, ...) BLT_LOG(blt::logging::log_level_t::DEBUG, format, ##__VA_ARGS__) +#endif + +#ifdef BLT_DISABLE_INFO +#define BLT_INFO(format, ...) +#else +#define BLT_INFO(format, ...) BLT_LOG(blt::logging::log_level_t::INFO, format, ##__VA_ARGS__) +#endif + +#ifdef BLT_DISABLE_WARN +#define BLT_WARN(format, ...) +#else +#define BLT_WARN(format, ...) BLT_LOG(blt::logging::log_level_t::WARN, format, ##__VA_ARGS__) +#endif + +#ifdef BLT_DISABLE_ERROR +#define BLT_ERROR(format, ...) +#else +#define BLT_ERROR(format, ...) BLT_LOG(blt::logging::log_level_t::ERROR, format, ##__VA_ARGS__) +#endif + +#ifdef BLT_DISABLE_FATAL +#define BLT_FATAL(format, ...) +#else +#define BLT_FATAL(format, ...) BLT_LOG(blt::logging::log_level_t::FATAL, format, ##__VA_ARGS__) +#endif + +#endif + +#if defined(__clang__) || defined(__llvm__) +#pragma clang diagnostic pop +#endif + #endif // BLT_LOGGING_LOGGING_H diff --git a/include/blt/logging/logging_config.h b/include/blt/logging/logging_config.h index 1ce8656..3859ad7 100644 --- a/include/blt/logging/logging_config.h +++ b/include/blt/logging/logging_config.h @@ -216,6 +216,8 @@ namespace blt::logging // This creates output where the user message always starts at the same column. bool m_ensure_alignment = true; + size_t m_longest_name_length = 0; + static std::string get_default_log_format(); static std::vector get_default_log_outputs(); static std::array get_default_log_level_colors(); diff --git a/src/blt/fs/path_helper.cpp b/src/blt/fs/path_helper.cpp index 9f62c16..bb2e210 100644 --- a/src/blt/fs/path_helper.cpp +++ b/src/blt/fs/path_helper.cpp @@ -38,4 +38,30 @@ namespace blt::fs const auto file_parts = string::split_sv(parts.back(), '.'); return file_parts.front(); } + + std::string filename(const std::string& str) + { + return std::string(filename_sv(str)); + } + + std::string_view filename_sv(const std::string_view str) + { + const auto parts = string::split_sv(str, delim); + return parts.back(); + } + + std::string extension(const std::string& str) + { + return std::string(extension_sv(str)); + } + + std::string_view extension_sv(const std::string_view str) + { + const auto parts = string::split_sv(str, delim); + const auto file_parts = parts.back().find_first_of('.'); + return parts.back().substr(std::min(file_parts + 1, parts.back().size())); + } } + + + diff --git a/src/blt/logging/logging.cpp b/src/blt/logging/logging.cpp index 5d52436..9cc26fb 100644 --- a/src/blt/logging/logging.cpp +++ b/src/blt/logging/logging.cpp @@ -37,6 +37,7 @@ namespace blt::logging struct logging_thread_context_t { std::stringstream stream; + std::stringstream logging_stream; std::string thread_name; logger_t logger{stream}; }; @@ -239,4 +240,11 @@ namespace blt::logging { return get_thread_context().thread_name; } + + std::ostream& get_local_stream() + { + auto& context = get_thread_context(); + context.logging_stream.str(""); + return context.logging_stream; + } } diff --git a/src/blt/logging/logging_config.cpp b/src/blt/logging/logging_config.cpp index fe15c92..ed29607 100644 --- a/src/blt/logging/logging_config.cpp +++ b/src/blt/logging/logging_config.cpp @@ -17,6 +17,7 @@ */ #include #include +#include #include #include #include @@ -91,6 +92,10 @@ namespace blt::logging m_log_tag_content.emplace_back(std::string_view(m_log_format.data() + i, m_log_format.size() - i)); m_log_tag_tokens.emplace_back(tags::detail::log_tag_token_t::CONTENT); } + + m_longest_name_length = 0; + for (const auto& name : m_log_level_names) + m_longest_name_length = std::max(m_longest_name_length, name.size()); } std::string add_year(const tm* current_time) @@ -187,10 +192,10 @@ namespace blt::logging } case tags::detail::log_tag_token_t::NS: { - auto str = std::to_string(nano_time % 1000); + auto str = std::to_string(nano_time % 1000000000ul); if (m_ensure_alignment) { - for (size_t i = str.size(); i < 4; ++i) + for (size_t i = str.size(); i < 9; ++i) str.insert(str.begin(), '0'); } fmt += str; @@ -198,24 +203,12 @@ namespace blt::logging } case tags::detail::log_tag_token_t::UNIX: { - auto str = std::to_string(millis_time % 1000); - if (m_ensure_alignment) - { - for (size_t i = str.size(); i < 4; ++i) - str.insert(str.begin(), '0'); - } - fmt += str; + fmt += std::to_string(millis_time); break; } case tags::detail::log_tag_token_t::UNIX_NANO: { - auto str = std::to_string(nano_time); - if (m_ensure_alignment) - { - for (size_t i = str.size(); i < 4; ++i) - str.insert(str.begin(), '0'); - } - fmt += str; + fmt += std::to_string(nano_time); break; } case tags::detail::log_tag_token_t::ISO_YEAR: @@ -275,7 +268,10 @@ namespace blt::logging fmt += thread_name; break; case tags::detail::log_tag_token_t::FILE: - fmt += file; + if (m_print_full_name) + fmt += file; + else + fmt += fs::filename_sv(file); break; case tags::detail::log_tag_token_t::LINE: fmt += std::to_string(line); @@ -294,7 +290,7 @@ namespace blt::logging std::string logging_config_t::get_default_log_format() { - return build(fg(ansi::color::color8_bright::CYAN)) + "[" + tags::FULL_TIME + "]" + tags::RESET + " " + tags::LOG_COLOR + "[" + tags::LOG_LEVEL + return build(fg(ansi::color::color8_bright::BLUE)) + "[" + tags::FULL_TIME + "]" + tags::RESET + " " + tags::LOG_COLOR + "[" + tags::LOG_LEVEL + "]" + tags::RESET + " " + build(fg(ansi::color::color8::MAGENTA)) + "(" + tags::FILE + ":" + tags::LINE + ")" + tags::RESET + " " + tags::CONDITIONAL_ERROR_COLOR + tags::STR + tags::RESET + "\n"; } diff --git a/tests/logger_tests.cpp b/tests/logger_tests.cpp index 03870f9..a9f8bb6 100644 --- a/tests/logger_tests.cpp +++ b/tests/logger_tests.cpp @@ -173,6 +173,15 @@ int main() blt::logging::println("Logged {} characters", charCount); + BLT_TRACE("Hello this is am empty trace!"); + BLT_TRACE("This is a trace with data {} {} {}", "bad at code", 413, "boy"); + + BLT_DEBUG("This is complete? {}", "this is working!"); + BLT_INFO("Hello there!"); + BLT_WARN("This is a warning!"); + BLT_ERROR("This is an error!"); + BLT_FATAL("This is a fatal error!"); + /*std::cout << "\033[2J"; constexpr int totalRows = 24; // std::cout << "\033[1;" << (totalRows - 1) << "r"; From b334bbcae408b60909104977271e3f086db16590 Mon Sep 17 00:00:00 2001 From: Brett Date: Sun, 9 Mar 2025 23:04:41 -0400 Subject: [PATCH 069/101] minor fixes, all files now use new logging libray, expect things to be broken! --- CMakeLists.txt | 2 +- include/blt/fs/loader.h | 2 +- include/blt/fs/nbt.h | 2 +- include/blt/logging/logging.h | 15 +- include/blt/logging/logging_config.h | 3 +- include/blt/parse/templating.h | 2 +- include/blt/profiling/profiler.h | 4 +- include/blt/profiling/profiler_v2.h | 6 +- include/blt/std/allocator.h | 1861 +++++++++++++------------- include/blt/std/logging.h | 436 ------ include/blt/std/mmap.h | 6 +- src/blt/format/format.cpp | 2 +- src/blt/fs/filesystem.cpp | 1 - src/blt/fs/nbt.cpp | 2 +- src/blt/parse/argparse_v2.cpp | 2 +- src/blt/parse/obj_loader.cpp | 13 +- src/blt/parse/templating.cpp | 2 +- src/blt/profiling/profiler.cpp | 8 +- src/blt/profiling/profiler_v2.cpp | 6 +- src/blt/std/assert.cpp | 18 +- src/blt/std/error.cpp | 108 +- src/blt/std/logging.cpp | 627 --------- src/blt/std/mmap.cpp | 3 +- src/blt/std/simd.cpp | 1 - src/blt/std/system.cpp | 2 +- 25 files changed, 1034 insertions(+), 2100 deletions(-) delete mode 100644 include/blt/std/logging.h delete mode 100644 src/blt/std/logging.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ad00ae..2b7a3e2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.2.4) +set(BLT_VERSION 5.2.5) set(BLT_TARGET BLT) diff --git a/include/blt/fs/loader.h b/include/blt/fs/loader.h index 991c249..d5eb966 100644 --- a/include/blt/fs/loader.h +++ b/include/blt/fs/loader.h @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include namespace blt::fs diff --git a/include/blt/fs/nbt.h b/include/blt/fs/nbt.h index 2353c42..9d59004 100644 --- a/include/blt/fs/nbt.h +++ b/include/blt/fs/nbt.h @@ -15,7 +15,7 @@ #include "blt/format/format.h" #include "blt/fs/filesystem.h" -#include "blt/std/logging.h" +#include "blt/logging/logging.h" #include "blt/std/memory.h" #include diff --git a/include/blt/logging/logging.h b/include/blt/logging/logging.h index 0d41cc2..b1b75d5 100644 --- a/include/blt/logging/logging.h +++ b/include/blt/logging/logging.h @@ -203,14 +203,21 @@ namespace blt::logging } template - void log(log_level_t level, const char* file, const i32 line, std::string fmt, Args&&... args) + void log(const log_level_t level, const char* file, const i32 line, std::string fmt, Args&&... args) { auto& logger = get_global_logger(); - auto& config = get_global_config(); - auto user_str = logger.log(std::move(fmt), std::forward(args)...); + const auto& config = get_global_config(); + std::string user_str = logger.log(std::move(fmt), std::forward(args)...); + if (!user_str.empty() && user_str.back() == '\n') + user_str.pop_back(); + if (level == log_level_t::NONE) + { + println(user_str); + return; + } auto log_fmt_str = config.generate(user_str, get_thread_name(), level, file, line); if (log_fmt_str) - print(std::move(*log_fmt_str)); + print(*log_fmt_str); } namespace detail diff --git a/include/blt/logging/logging_config.h b/include/blt/logging/logging_config.h index 3859ad7..f5cad5a 100644 --- a/include/blt/logging/logging_config.h +++ b/include/blt/logging/logging_config.h @@ -115,7 +115,8 @@ namespace blt::logging INFO, WARN, ERROR, - FATAL + FATAL, + NONE }; inline constexpr size_t LOG_LEVEL_COUNT = 6; diff --git a/include/blt/parse/templating.h b/include/blt/parse/templating.h index cc9efa5..5f10352 100644 --- a/include/blt/parse/templating.h +++ b/include/blt/parse/templating.h @@ -27,7 +27,7 @@ #include #include #include -#include +#include #include namespace blt diff --git a/include/blt/profiling/profiler.h b/include/blt/profiling/profiler.h index 2320080..74df441 100644 --- a/include/blt/profiling/profiler.h +++ b/include/blt/profiling/profiler.h @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include #include @@ -54,7 +54,7 @@ namespace blt::profiling { profile getProfile(const std::string& profileName); void printProfile( - const std::string& profileName, logging::log_level loggingLevel = logging::log_level::NONE, + const std::string& profileName, logging::log_level_t loggingLevel = logging::log_level_t::NONE, bool averageHistory = false ); diff --git a/include/blt/profiling/profiler_v2.h b/include/blt/profiling/profiler_v2.h index dd080ec..42290af 100644 --- a/include/blt/profiling/profiler_v2.h +++ b/include/blt/profiling/profiler_v2.h @@ -11,7 +11,7 @@ #include #include #include -#include +#include namespace blt { @@ -98,7 +98,7 @@ namespace blt void endInterval(interval_t* interval); void printProfile(profile_t& profiler, std::uint32_t flags = AVERAGE_HISTORY | PRINT_CYCLES | PRINT_THREAD | PRINT_WALL, - sort_by sort = sort_by::CYCLES, blt::logging::log_level log_level = blt::logging::log_level::NONE); + sort_by sort = sort_by::CYCLES, blt::logging::log_level_t log_level = blt::logging::log_level_t::NONE); void writeProfile(std::ostream& stream, profile_t& profiler, std::uint32_t flags = AVERAGE_HISTORY | PRINT_CYCLES | PRINT_THREAD | PRINT_WALL, @@ -113,7 +113,7 @@ namespace blt void endInterval(const std::string& profile_name, const std::string& interval_name); void printProfile(const std::string& profile_name, std::uint32_t flags = AVERAGE_HISTORY | PRINT_CYCLES | PRINT_THREAD | PRINT_WALL, - sort_by sort = sort_by::CYCLES, blt::logging::log_level log_level = blt::logging::log_level::NONE); + sort_by sort = sort_by::CYCLES, blt::logging::log_level_t log_level = blt::logging::log_level_t::NONE); void writeProfile(std::ostream& stream, const std::string& profile_name, std::uint32_t flags = AVERAGE_HISTORY | PRINT_CYCLES | PRINT_THREAD | PRINT_WALL, diff --git a/include/blt/std/allocator.h b/include/blt/std/allocator.h index 03e466e..1f7b8f8 100644 --- a/include/blt/std/allocator.h +++ b/include/blt/std/allocator.h @@ -17,944 +17,949 @@ */ #ifndef BLT_ALLOCATOR_H - - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include "logging.h" - #include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "blt/logging/logging.h" +#include #ifdef __unix__ - - #include + +#include #endif namespace blt { - - template - class allocator_base - { - public: - template - inline void construct(U* p, Args&& ... args) - { - ::new((void*) p) U(std::forward(args)...); - } - - template - inline void destroy(U* p) - { - if (p != nullptr) - p->~U(); - } - - [[nodiscard]] inline size_t max_size() const - { - return std::numeric_limits::max(); - } - - inline const_pointer address(const value_type& val) - { - return std::addressof(val); - } - - inline pointer address(value_type& val) - { - return std::addressof(val); - } - }; - - template - class area_allocator : public allocator_base - { - public: - using value = T; - using type = T; - using value_type = type; - using pointer = type*; - using const_pointer = const type*; - using void_pointer = void*; - using const_void_pointer = const void*; - using reference = value_type&; - using const_reference = const value_type&; - using size_type = size_t; - using difference_type = size_t; - using propagate_on_container_move_assignment = std::false_type; - template - struct rebind - { - typedef blt::area_allocator other; - }; - using allocator_base::allocator_base; - private: - /** - * Stores a view to a region of memory that has been deallocated - * This is a non-owning reference to the memory block - * - * pointer p is the pointer to the beginning of the block of memory - * size_t n is the number of elements that this block can hold - */ - struct pointer_view - { - pointer p; - size_t n; - }; - - /** - * Stores the actual data for allocated blocks. Since we would like to be able to allocate an arbitrary number of items - * we need a way of storing that data. The block storage holds an owning pointer to a region of memory with used elements - * Only up to used has to have their destructors called, which should be handled by the deallocate function - * it is UB to not deallocate memory allocated by this allocator - * - * an internal vector is used to store the regions of memory which have been deallocated. the allocate function will search for - * free blocks with sufficient size in order to maximize memory usage. In the future more advanced methods should be used - * for both faster access to deallocated blocks of sufficient size and to ensure coherent memory. - */ - struct block_storage - { - pointer data; - size_t used = 0; - // TODO: b-tree? - std::vector unallocated_blocks; - }; - - /** - * Stores an index to a pointer_view along with the amount of memory leftover after the allocation - * it also stores the block being allocated to in question. The new inserted leftover should start at old_ptr + size - */ - struct block_view - { - block_storage* blk; - size_t index; - size_t leftover; - - block_view(block_storage* blk, size_t index, size_t leftover): blk(blk), index(index), leftover(leftover) - {} - }; - - /** - * Allocate a new block of memory and push it to the back of blocks. - */ - inline void allocate_block() - { - //BLT_INFO("Allocating a new block of size %d", BLOCK_SIZE); - auto* blk = new block_storage(); - blk->data = static_cast(malloc(sizeof(T) * BLOCK_SIZE)); - blocks.push_back(blk); - } - - /** - * Searches for a free block inside the block storage with sufficient space and returns an optional view to it - * The optional will be empty if no open block can be found. - */ - inline std::optional search_for_block(block_storage* blk, size_t n) - { - for (auto [index, item] : blt::enumerate(blk->unallocated_blocks)) - { - if (item.n >= n) - return block_view{blk, index, item.n - n}; - } - return {}; - } - - /** - * removes the block of memory from the unallocated_blocks storage in the underlying block, inserting a new unallocated block if - * there was any leftover. Returns a pointer to the beginning of the new block. - */ - inline pointer swap_pop_resize_if(const block_view& view, size_t n) - { - pointer_view ptr = view.blk->unallocated_blocks[view.index]; - std::iter_swap(view.blk->unallocated_blocks.begin() + view.index, view.blk->unallocated_blocks.end() - 1); - view.blk->unallocated_blocks.pop_back(); - if (view.leftover > 0) - view.blk->unallocated_blocks.push_back({ptr.p + n, view.leftover}); - return ptr.p; - } - - /** - * Finds the next available unallocated block of memory, or empty if there is none which meet size requirements - */ - inline std::optional find_available_block(size_t n) - { - for (auto* blk : blocks) - { - if (auto view = search_for_block(blk, n)) - return swap_pop_resize_if(view.value(), n); - } - return {}; - } - - /** - * returns a pointer to a block of memory along with an offset into that pointer that the requested block can be found at - */ - inline std::pair getBlock(size_t n) - { - if (auto blk = find_available_block(n)) - return {blk.value(), 0}; - - if (blocks.back()->used + n > BLOCK_SIZE) - allocate_block(); - - auto ptr = std::pair{blocks.back()->data, blocks.back()->used}; - blocks.back()->used += n; - return ptr; - } - - /** - * Calls the constructor on elements if they require construction, otherwise constructor will not be called and this function is useless - * - * ALLOCATORS RETURN UNINIT STORAGE!! THIS HAS BEEN DISABLED. - */ - inline void allocate_in_block(pointer, size_t) - { -// if constexpr (std::is_default_constructible_v && !std::is_trivially_default_constructible_v) -// { -// for (size_t i = 0; i < n; i++) -// new(&begin[i]) T(); -// } - } - - public: - area_allocator() - { - allocate_block(); - } - - area_allocator(const area_allocator& copy) = delete; - - area_allocator(area_allocator&& move) noexcept - { - blocks = move.blocks; - } - - area_allocator& operator=(const area_allocator& copy) = delete; - - area_allocator& operator=(area_allocator&& move) noexcept - { - std::swap(move.blocks, blocks); - } - - [[nodiscard]] pointer allocate(size_t n) - { - if (n > BLOCK_SIZE) - throw std::runtime_error("Requested allocation is too large!"); - - auto block_info = getBlock(n); - - auto* ptr = &block_info.first[block_info.second]; - // call constructors on the objects if they require it - allocate_in_block(ptr, n); - - return ptr; - } - - void deallocate(pointer p, size_t n) noexcept - { - if (p == nullptr) - return; -// for (size_t i = 0; i < n; i++) -// p[i].~T(); - for (auto*& blk : blocks) - { - if (p >= blk->data && p <= (blk->data + BLOCK_SIZE)) - { - blk->unallocated_blocks.push_back(pointer_view{p, n}); - break; - } - } - } - - ~area_allocator() - { - for (auto*& blk : blocks) - { - free(blk->data); - delete blk; - } - } - - private: - std::vector blocks; - }; + template + class allocator_base + { + public: + template + inline void construct(U* p, Args&&... args) + { + ::new((void*) p) U(std::forward(args)...); + } -// template -// class bump_allocator : public allocator_base -// { -// public: -// using value = T; -// using type = T; -// using value_type = type; -// using pointer = type*; -// using const_pointer = const type*; -// using void_pointer = void*; -// using const_void_pointer = const void*; -// using reference = value_type&; -// using const_reference = const value_type&; -// using size_type = size_t; -// using difference_type = size_t; -// using propagate_on_container_move_assignment = std::false_type; -// template -// struct rebind -// { -// typedef blt::bump_allocator other; -// }; -// using allocator_base::allocator_base; -// private: -// pointer buffer_; -// blt::size_t offset_; -// blt::size_t size_; -// public: -// explicit bump_allocator(blt::size_t size): buffer_(static_cast(malloc(size * sizeof(T)))), offset_(0), size_(size) -// {} -// -// template -// explicit bump_allocator(blt::size_t size, Args&& ... defaults): -// buffer_(static_cast(malloc(size * sizeof(type)))), offset_(0), size_(size) -// { -// for (blt::size_t i = 0; i < size_; i++) -// ::new(&buffer_[i]) T(std::forward(defaults)...); -// } -// -// bump_allocator(pointer buffer, blt::size_t size): buffer_(buffer), offset_(0), size_(size) -// {} -// -// bump_allocator(const bump_allocator& copy) = delete; -// -// bump_allocator(bump_allocator&& move) noexcept -// { -// buffer_ = move.buffer_; -// size_ = move.size_; -// offset_ = move.offset_; -// } -// -// bump_allocator& operator=(const bump_allocator& copy) = delete; -// -// bump_allocator& operator=(bump_allocator&& move) noexcept -// { -// std::swap(move.buffer_, buffer_); -// std::swap(move.size_, size_); -// std::swap(move.offset_, offset_); -// } -// -// pointer allocate(blt::size_t n) -// { -// auto nv = offset_ + n; -// if (nv > size_) -// throw std::bad_alloc(); -// pointer b = &buffer_[offset_]; -// offset_ = nv; -// return b; -// } -// -// void deallocate(pointer, blt::size_t) -// {} -// -// ~bump_allocator() -// { -// free(buffer_); -// } -// }; - - /** - * The bump allocator is meant to be a faster area allocator which will only allocate forward through either a supplied buffer or size - * or will create a linked list type data structure of buffered blocks. - * @tparam ALLOC allocator to use for any allocations. In the case of the non-linked variant, this will be used if a size is supplied. The supplied buffer must be allocated with this allocator! - * @tparam linked use a linked list to allocate with the allocator or just use the supplied buffer and throw an exception of we cannot allocate - */ - template typename ALLOC = std::allocator> - class bump_allocator_old; - - template typename ALLOC> - class bump_allocator_old - { - private: - ALLOC allocator; - blt::u8* buffer_; - blt::u8* offset_; - blt::size_t size_; - public: - explicit bump_allocator_old(blt::size_t size): buffer_(static_cast(allocator.allocate(size))), offset_(buffer_), size_(size) - {} - - explicit bump_allocator_old(blt::u8* buffer, blt::size_t size): buffer_(buffer), offset_(buffer), size_(size) - {} - - template - [[nodiscard]] T* allocate() - { - size_t remaining_num_bytes = size_ - static_cast(buffer_ - offset_); - auto pointer = static_cast(offset_); - const auto aligned_address = std::align(alignof(T), sizeof(T), pointer, remaining_num_bytes); - if (aligned_address == nullptr) - throw std::bad_alloc{}; - offset_ = static_cast(aligned_address) + sizeof(T); - return static_cast(aligned_address); - } - - template - [[nodiscard]] T* emplace(Args&& ... args) - { - const auto allocated_memory = allocate(); - return new(allocated_memory) T{std::forward(args)...}; - } - - template - inline void construct(U* p, Args&& ... args) - { - ::new((void*) p) U(std::forward(args)...); - } - - template - inline void destroy(U* p) - { - if (p != nullptr) - p->~U(); - } - - ~bump_allocator_old() - { - allocator.deallocate(buffer_, size_); - } - }; - - template typename ALLOC> - class bump_allocator_old - { - private: - struct block - { - blt::size_t allocated_objects = 0; - blt::u8* buffer = nullptr; - blt::u8* offset = nullptr; - - explicit block(blt::u8* buffer): buffer(buffer), offset(buffer) - {} - }; - - ALLOC allocator; - std::vector> blocks; - blt::size_t size_; - blt::size_t allocations = 0; - blt::size_t deallocations = 0; - - void expand() - { - auto ptr = static_cast(allocator.allocate(size_)); - blocks.push_back(block{ptr}); - allocations++; - } - - template - T* allocate_back() - { - auto& back = blocks.back(); - size_t remaining_bytes = size_ - static_cast(back.offset - back.buffer); - auto pointer = static_cast(back.offset); - const auto aligned_address = std::align(alignof(T), sizeof(T), pointer, remaining_bytes); - if (aligned_address != nullptr) - { - back.offset = static_cast(aligned_address) + sizeof(T); - back.allocated_objects++; - } - - return static_cast(aligned_address); - } - - public: - /** - * @param size of the list blocks - */ - explicit bump_allocator_old(blt::size_t size): size_(size) - { - expand(); - } - - template - [[nodiscard]] T* allocate() - { - if (auto ptr = allocate_back(); ptr == nullptr) - expand(); - else - return ptr; - if (auto ptr = allocate_back(); ptr == nullptr) - throw std::bad_alloc(); - else - return ptr; - } - - template - void deallocate(T* p) - { - auto* ptr = reinterpret_cast(p); - for (auto e : blt::enumerate(blocks)) - { - auto& block = e.second; - if (ptr >= block.buffer && ptr <= block.offset) - { - block.allocated_objects--; - if (block.allocated_objects == 0) - { - std::iter_swap(blocks.begin() + e.first, blocks.end() - 1); - allocator.deallocate(blocks.back().buffer, size_); - blocks.pop_back(); - deallocations++; - } - return; - } - } - } - - template - [[nodiscard]] T* emplace(Args&& ... args) - { - const auto allocated_memory = allocate(); - return new(allocated_memory) T{std::forward(args)...}; - } - - template - inline void construct(U* p, Args&& ... args) - { - ::new((void*) p) U(std::forward(args)...); - } - - template - inline void destroy(U* p) - { - if (p != nullptr) - p->~U(); - } - - ~bump_allocator_old() - { - if (allocations != deallocations) - BLT_WARN("Allocator has blocks which have not been deallocated! Destructors might not have been called!"); - for (auto& v : blocks) - allocator.deallocate(v.buffer, size_); - } - }; - - template - static inline T* allocate_huge_page(blt::size_t BLOCK_SIZE, blt::size_t HUGE_PAGE_SIZE = BLT_2MB_SIZE) - { -#ifdef __unix__ - BLT_ASSERT((BLOCK_SIZE & (HUGE_PAGE_SIZE - 1)) == 0 && "Must be multiple of the huge page size!"); - T* buffer = static_cast(mmap(nullptr, BLOCK_SIZE, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB | MAP_POPULATE, -1, 0)); - // if we fail to allocate a huge page we can try to allocate normally - if (buffer == MAP_FAILED) - { - if constexpr (WARN_ON_FAIL) - { - BLT_WARN_STREAM << "We failed to allocate huge pages\n"; - BLT_WARN_STREAM << handle_mmap_error(); - BLT_WARN_STREAM << "\033[1;31mYou should attempt to enable " - "huge pages as this will allocate normal pages and double the memory usage!\033[22m\n"; - } - blt::size_t bytes = BLOCK_SIZE * 2; - buffer = static_cast(mmap(nullptr, bytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE, -1, 0)); - if (buffer == MAP_FAILED) - { - BLT_ERROR_STREAM << "Failed to allocate normal pages\n"; - throw bad_alloc_t(handle_mmap_error()); - } - if constexpr (WARN_ON_FAIL) - { - if (((size_t) buffer & (HUGE_PAGE_SIZE - 1)) != 0) - BLT_ERROR("Pointer is not aligned! %p", buffer); - } - auto* ptr = static_cast(buffer); - auto ptr_size = reinterpret_cast(ptr); - buffer = static_cast(std::align(BLOCK_SIZE, BLOCK_SIZE, ptr, bytes)); - if constexpr (WARN_ON_FAIL) - BLT_ERROR("Offset by %ld pages, resulting: %p", (reinterpret_cast(buffer) - ptr_size) / 4096, buffer); - } - return buffer; -#endif - return malloc(BLOCK_SIZE); - } - - /** - * blt::bump_allocator. Allocates blocks of BLOCK_SIZE with zero reuse. When all objects from a block are fully deallocated the block will be freed - * @tparam BLOCK_SIZE size of block to use. recommended to be multiple of page size or huge page size. - * @tparam USE_HUGE allocate using mmap and huge pages. If this fails it will use mmap to allocate normally. defaults to off because linux has parent huge pages. - * @tparam HUGE_PAGE_SIZE size the system allows huge pages to be. defaults to 2mb - * @tparam WARN_ON_FAIL print warning messages if allocating huge pages fail - */ - template - class bump_allocator - { - // ensure power of two - static_assert(((BLOCK_SIZE & (BLOCK_SIZE - 1)) == 0) && "Must be a power of two!"); - public: - /** - * convert any pointer back into a pointer its block - */ - template - static inline auto to_block(T* p) - { - return reinterpret_cast(reinterpret_cast(p) & static_cast(~(BLOCK_SIZE - 1))); - } - - class stats_t - { - friend bump_allocator; - private: - blt::size_t allocated_blocks = 0; - blt::size_t allocated_bytes = 0; - blt::size_t peak_blocks = 0; - blt::size_t peak_bytes = 0; - protected: - inline void incrementBlocks() - { - allocated_blocks++; - if (allocated_blocks > peak_blocks) - peak_blocks = allocated_blocks; - } - - inline void decrementBlocks() - { - allocated_blocks--; - } - - inline void incrementBytes(blt::size_t bytes) - { - allocated_bytes += bytes; - if (allocated_bytes > peak_bytes) - peak_bytes = allocated_bytes; - } - - inline void decrementBytes(blt::size_t bytes) - { - allocated_bytes -= bytes; - } - - public: - - inline auto getAllocatedBlocks() const - { - return allocated_blocks; - } - - inline auto getAllocatedBytes() const - { - return allocated_bytes; - } - - inline auto getPeakBlocks() const - { - return peak_blocks; - } - - inline auto getPeakBytes() const - { - return peak_bytes; - } - }; - - private: - stats_t stats; - //blt::hashset_t deletes; - - struct block - { - struct block_metadata_t - { - blt::size_t allocated_objects = 0; - block* next = nullptr; - block* prev = nullptr; - blt::u8* offset = nullptr; - } metadata; - blt::u8 buffer[BLOCK_SIZE - sizeof(block_metadata_t)]{}; - - block() - { - metadata.offset = buffer; - } - }; - - // remaining space inside the block after accounting for the metadata - static constexpr blt::size_t BLOCK_REMAINDER = BLOCK_SIZE - sizeof(typename block::block_metadata_t); - - block* base = nullptr; - block* head = nullptr; - - /** - * Handles the allocation of the bytes for the block. - * This function will either use mmap to allocate huge pages if requested - * or use std::align_alloc to create an aligned allocation - * @return pointer to a constructed block - */ - block* allocate_block() - { - block* buffer; -#ifdef __unix__ - if constexpr (USE_HUGE) - { - buffer = allocate_huge_page(BLOCK_SIZE, HUGE_PAGE_SIZE); - } else - buffer = reinterpret_cast(std::aligned_alloc(BLOCK_SIZE, BLOCK_SIZE)); -#else + template + inline void destroy(U* p) + { + if (p != nullptr) + p->~U(); + } + + [[nodiscard]] inline size_t max_size() const + { + return std::numeric_limits::max(); + } + + inline const_pointer address(const value_type& val) + { + return std::addressof(val); + } + + inline pointer address(value_type& val) + { + return std::addressof(val); + } + }; + + template + class area_allocator : public allocator_base + { + public: + using value = T; + using type = T; + using value_type = type; + using pointer = type*; + using const_pointer = const type*; + using void_pointer = void*; + using const_void_pointer = const void*; + using reference = value_type&; + using const_reference = const value_type&; + using size_type = size_t; + using difference_type = size_t; + using propagate_on_container_move_assignment = std::false_type; + + template + struct rebind + { + typedef blt::area_allocator other; + }; + + using allocator_base::allocator_base; + + private: + /** + * Stores a view to a region of memory that has been deallocated + * This is a non-owning reference to the memory block + * + * pointer p is the pointer to the beginning of the block of memory + * size_t n is the number of elements that this block can hold + */ + struct pointer_view + { + pointer p; + size_t n; + }; + + /** + * Stores the actual data for allocated blocks. Since we would like to be able to allocate an arbitrary number of items + * we need a way of storing that data. The block storage holds an owning pointer to a region of memory with used elements + * Only up to used has to have their destructors called, which should be handled by the deallocate function + * it is UB to not deallocate memory allocated by this allocator + * + * an internal vector is used to store the regions of memory which have been deallocated. the allocate function will search for + * free blocks with sufficient size in order to maximize memory usage. In the future more advanced methods should be used + * for both faster access to deallocated blocks of sufficient size and to ensure coherent memory. + */ + struct block_storage + { + pointer data; + size_t used = 0; + // TODO: b-tree? + std::vector unallocated_blocks; + }; + + /** + * Stores an index to a pointer_view along with the amount of memory leftover after the allocation + * it also stores the block being allocated to in question. The new inserted leftover should start at old_ptr + size + */ + struct block_view + { + block_storage* blk; + size_t index; + size_t leftover; + + block_view(block_storage* blk, size_t index, size_t leftover): blk(blk), index(index), leftover(leftover) + {} + }; + + /** + * Allocate a new block of memory and push it to the back of blocks. + */ + inline void allocate_block() + { + //BLT_INFO("Allocating a new block of size %d", BLOCK_SIZE); + auto* blk = new block_storage(); + blk->data = static_cast(malloc(sizeof(T) * BLOCK_SIZE)); + blocks.push_back(blk); + } + + /** + * Searches for a free block inside the block storage with sufficient space and returns an optional view to it + * The optional will be empty if no open block can be found. + */ + inline std::optional search_for_block(block_storage* blk, size_t n) + { + for (auto [index, item] : blt::enumerate(blk->unallocated_blocks)) + { + if (item.n >= n) + return block_view{blk, index, item.n - n}; + } + return {}; + } + + /** + * removes the block of memory from the unallocated_blocks storage in the underlying block, inserting a new unallocated block if + * there was any leftover. Returns a pointer to the beginning of the new block. + */ + inline pointer swap_pop_resize_if(const block_view& view, size_t n) + { + pointer_view ptr = view.blk->unallocated_blocks[view.index]; + std::iter_swap(view.blk->unallocated_blocks.begin() + view.index, view.blk->unallocated_blocks.end() - 1); + view.blk->unallocated_blocks.pop_back(); + if (view.leftover > 0) + view.blk->unallocated_blocks.push_back({ptr.p + n, view.leftover}); + return ptr.p; + } + + /** + * Finds the next available unallocated block of memory, or empty if there is none which meet size requirements + */ + inline std::optional find_available_block(size_t n) + { + for (auto* blk : blocks) + { + if (auto view = search_for_block(blk, n)) + return swap_pop_resize_if(view.value(), n); + } + return {}; + } + + /** + * returns a pointer to a block of memory along with an offset into that pointer that the requested block can be found at + */ + inline std::pair getBlock(size_t n) + { + if (auto blk = find_available_block(n)) + return {blk.value(), 0}; + + if (blocks.back()->used + n > BLOCK_SIZE) + allocate_block(); + + auto ptr = std::pair{blocks.back()->data, blocks.back()->used}; + blocks.back()->used += n; + return ptr; + } + + /** + * Calls the constructor on elements if they require construction, otherwise constructor will not be called and this function is useless + * + * ALLOCATORS RETURN UNINIT STORAGE!! THIS HAS BEEN DISABLED. + */ + inline void allocate_in_block(pointer, size_t) + { + // if constexpr (std::is_default_constructible_v && !std::is_trivially_default_constructible_v) + // { + // for (size_t i = 0; i < n; i++) + // new(&begin[i]) T(); + // } + } + + public: + area_allocator() + { + allocate_block(); + } + + area_allocator(const area_allocator& copy) = delete; + + area_allocator(area_allocator&& move) noexcept + { + blocks = move.blocks; + } + + area_allocator& operator=(const area_allocator& copy) = delete; + + area_allocator& operator=(area_allocator&& move) noexcept + { + std::swap(move.blocks, blocks); + } + + [[nodiscard]] pointer allocate(size_t n) + { + if (n > BLOCK_SIZE) + throw std::runtime_error("Requested allocation is too large!"); + + auto block_info = getBlock(n); + + auto* ptr = &block_info.first[block_info.second]; + // call constructors on the objects if they require it + allocate_in_block(ptr, n); + + return ptr; + } + + void deallocate(pointer p, size_t n) noexcept + { + if (p == nullptr) + return; + // for (size_t i = 0; i < n; i++) + // p[i].~T(); + for (auto*& blk : blocks) + { + if (p >= blk->data && p <= (blk->data + BLOCK_SIZE)) + { + blk->unallocated_blocks.push_back(pointer_view{p, n}); + break; + } + } + } + + ~area_allocator() + { + for (auto*& blk : blocks) + { + free(blk->data); + delete blk; + } + } + + private: + std::vector blocks; + }; + + // template + // class bump_allocator : public allocator_base + // { + // public: + // using value = T; + // using type = T; + // using value_type = type; + // using pointer = type*; + // using const_pointer = const type*; + // using void_pointer = void*; + // using const_void_pointer = const void*; + // using reference = value_type&; + // using const_reference = const value_type&; + // using size_type = size_t; + // using difference_type = size_t; + // using propagate_on_container_move_assignment = std::false_type; + // template + // struct rebind + // { + // typedef blt::bump_allocator other; + // }; + // using allocator_base::allocator_base; + // private: + // pointer buffer_; + // blt::size_t offset_; + // blt::size_t size_; + // public: + // explicit bump_allocator(blt::size_t size): buffer_(static_cast(malloc(size * sizeof(T)))), offset_(0), size_(size) + // {} + // + // template + // explicit bump_allocator(blt::size_t size, Args&& ... defaults): + // buffer_(static_cast(malloc(size * sizeof(type)))), offset_(0), size_(size) + // { + // for (blt::size_t i = 0; i < size_; i++) + // ::new(&buffer_[i]) T(std::forward(defaults)...); + // } + // + // bump_allocator(pointer buffer, blt::size_t size): buffer_(buffer), offset_(0), size_(size) + // {} + // + // bump_allocator(const bump_allocator& copy) = delete; + // + // bump_allocator(bump_allocator&& move) noexcept + // { + // buffer_ = move.buffer_; + // size_ = move.size_; + // offset_ = move.offset_; + // } + // + // bump_allocator& operator=(const bump_allocator& copy) = delete; + // + // bump_allocator& operator=(bump_allocator&& move) noexcept + // { + // std::swap(move.buffer_, buffer_); + // std::swap(move.size_, size_); + // std::swap(move.offset_, offset_); + // } + // + // pointer allocate(blt::size_t n) + // { + // auto nv = offset_ + n; + // if (nv > size_) + // throw std::bad_alloc(); + // pointer b = &buffer_[offset_]; + // offset_ = nv; + // return b; + // } + // + // void deallocate(pointer, blt::size_t) + // {} + // + // ~bump_allocator() + // { + // free(buffer_); + // } + // }; + + /** + * The bump allocator is meant to be a faster area allocator which will only allocate forward through either a supplied buffer or size + * or will create a linked list type data structure of buffered blocks. + * @tparam ALLOC allocator to use for any allocations. In the case of the non-linked variant, this will be used if a size is supplied. The supplied buffer must be allocated with this allocator! + * @tparam linked use a linked list to allocate with the allocator or just use the supplied buffer and throw an exception of we cannot allocate + */ + template typename ALLOC = std::allocator> + class bump_allocator_old; + + template typename ALLOC> + class bump_allocator_old + { + private: + ALLOC allocator; + blt::u8* buffer_; + blt::u8* offset_; + blt::size_t size_; + + public: + explicit bump_allocator_old(blt::size_t size): buffer_(static_cast(allocator.allocate(size))), offset_(buffer_), size_(size) + {} + + explicit bump_allocator_old(blt::u8* buffer, blt::size_t size): buffer_(buffer), offset_(buffer), size_(size) + {} + + template + [[nodiscard]] T* allocate() + { + size_t remaining_num_bytes = size_ - static_cast(buffer_ - offset_); + auto pointer = static_cast(offset_); + const auto aligned_address = std::align(alignof(T), sizeof(T), pointer, remaining_num_bytes); + if (aligned_address == nullptr) + throw std::bad_alloc{}; + offset_ = static_cast(aligned_address) + sizeof(T); + return static_cast(aligned_address); + } + + template + [[nodiscard]] T* emplace(Args&&... args) + { + const auto allocated_memory = allocate(); + return new(allocated_memory) T{std::forward(args)...}; + } + + template + inline void construct(U* p, Args&&... args) + { + ::new((void*) p) U(std::forward(args)...); + } + + template + inline void destroy(U* p) + { + if (p != nullptr) + p->~U(); + } + + ~bump_allocator_old() + { + allocator.deallocate(buffer_, size_); + } + }; + + template typename ALLOC> + class bump_allocator_old + { + private: + struct block + { + blt::size_t allocated_objects = 0; + blt::u8* buffer = nullptr; + blt::u8* offset = nullptr; + + explicit block(blt::u8* buffer): buffer(buffer), offset(buffer) + {} + }; + + ALLOC allocator; + std::vector> blocks; + blt::size_t size_; + blt::size_t allocations = 0; + blt::size_t deallocations = 0; + + void expand() + { + auto ptr = static_cast(allocator.allocate(size_)); + blocks.push_back(block{ptr}); + allocations++; + } + + template + T* allocate_back() + { + auto& back = blocks.back(); + size_t remaining_bytes = size_ - static_cast(back.offset - back.buffer); + auto pointer = static_cast(back.offset); + const auto aligned_address = std::align(alignof(T), sizeof(T), pointer, remaining_bytes); + if (aligned_address != nullptr) + { + back.offset = static_cast(aligned_address) + sizeof(T); + back.allocated_objects++; + } + + return static_cast(aligned_address); + } + + public: + /** + * @param size of the list blocks + */ + explicit bump_allocator_old(blt::size_t size): size_(size) + { + expand(); + } + + template + [[nodiscard]] T* allocate() + { + if (auto ptr = allocate_back(); ptr == nullptr) + expand(); + else + return ptr; + if (auto ptr = allocate_back(); ptr == nullptr) + throw std::bad_alloc(); + else + return ptr; + } + + template + void deallocate(T* p) + { + auto* ptr = reinterpret_cast(p); + for (auto e : blt::enumerate(blocks)) + { + auto& block = e.second; + if (ptr >= block.buffer && ptr <= block.offset) + { + block.allocated_objects--; + if (block.allocated_objects == 0) + { + std::iter_swap(blocks.begin() + e.first, blocks.end() - 1); + allocator.deallocate(blocks.back().buffer, size_); + blocks.pop_back(); + deallocations++; + } + return; + } + } + } + + template + [[nodiscard]] T* emplace(Args&&... args) + { + const auto allocated_memory = allocate(); + return new(allocated_memory) T{std::forward(args)...}; + } + + template + inline void construct(U* p, Args&&... args) + { + ::new((void*) p) U(std::forward(args)...); + } + + template + inline void destroy(U* p) + { + if (p != nullptr) + p->~U(); + } + + ~bump_allocator_old() + { + if (allocations != deallocations) + BLT_WARN("Allocator has blocks which have not been deallocated! Destructors might not have been called!"); + for (auto& v : blocks) + allocator.deallocate(v.buffer, size_); + } + }; + + template + static inline T* allocate_huge_page(blt::size_t BLOCK_SIZE, blt::size_t HUGE_PAGE_SIZE = BLT_2MB_SIZE) + { + #ifdef __unix__ + BLT_ASSERT((BLOCK_SIZE & (HUGE_PAGE_SIZE - 1)) == 0 && "Must be multiple of the huge page size!"); + T* buffer = static_cast(mmap(nullptr, BLOCK_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB | MAP_POPULATE, -1, + 0)); + // if we fail to allocate a huge page we can try to allocate normally + if (buffer == MAP_FAILED) + { + if constexpr (WARN_ON_FAIL) + { + BLT_WARN("We failed to allocate huge pages\n{}{}", handle_mmap_error(), + "\033[1;31mYou should attempt to enable " + "huge pages as this will allocate normal pages and double the memory usage!\033[22m\n"); + } + blt::size_t bytes = BLOCK_SIZE * 2; + buffer = static_cast(mmap(nullptr, bytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE, -1, 0)); + if (buffer == MAP_FAILED) + { + BLT_ERROR("Failed to allocate normal pages"); + throw bad_alloc_t(handle_mmap_error()); + } + if constexpr (WARN_ON_FAIL) + { + if (((size_t) buffer & (HUGE_PAGE_SIZE - 1)) != 0) + BLT_ERROR("Pointer is not aligned! %p", buffer); + } + auto* ptr = static_cast(buffer); + auto ptr_size = reinterpret_cast(ptr); + buffer = static_cast(std::align(BLOCK_SIZE, BLOCK_SIZE, ptr, bytes)); + if constexpr (WARN_ON_FAIL) + BLT_ERROR("Offset by %ld pages, resulting: %p", (reinterpret_cast(buffer) - ptr_size) / 4096, buffer); + } + return buffer; + #endif + return malloc(BLOCK_SIZE); + } + + /** + * blt::bump_allocator. Allocates blocks of BLOCK_SIZE with zero reuse. When all objects from a block are fully deallocated the block will be freed + * @tparam BLOCK_SIZE size of block to use. recommended to be multiple of page size or huge page size. + * @tparam USE_HUGE allocate using mmap and huge pages. If this fails it will use mmap to allocate normally. defaults to off because linux has parent huge pages. + * @tparam HUGE_PAGE_SIZE size the system allows huge pages to be. defaults to 2mb + * @tparam WARN_ON_FAIL print warning messages if allocating huge pages fail + */ + template + class bump_allocator + { + // ensure power of two + static_assert(((BLOCK_SIZE & (BLOCK_SIZE - 1)) == 0) && "Must be a power of two!"); + + public: + /** + * convert any pointer back into a pointer its block + */ + template + static inline auto to_block(T* p) + { + return reinterpret_cast(reinterpret_cast(p) & static_cast(~(BLOCK_SIZE - 1))); + } + + class stats_t + { + friend bump_allocator; + + private: + blt::size_t allocated_blocks = 0; + blt::size_t allocated_bytes = 0; + blt::size_t peak_blocks = 0; + blt::size_t peak_bytes = 0; + + protected: + inline void incrementBlocks() + { + allocated_blocks++; + if (allocated_blocks > peak_blocks) + peak_blocks = allocated_blocks; + } + + inline void decrementBlocks() + { + allocated_blocks--; + } + + inline void incrementBytes(blt::size_t bytes) + { + allocated_bytes += bytes; + if (allocated_bytes > peak_bytes) + peak_bytes = allocated_bytes; + } + + inline void decrementBytes(blt::size_t bytes) + { + allocated_bytes -= bytes; + } + + public: + inline auto getAllocatedBlocks() const + { + return allocated_blocks; + } + + inline auto getAllocatedBytes() const + { + return allocated_bytes; + } + + inline auto getPeakBlocks() const + { + return peak_blocks; + } + + inline auto getPeakBytes() const + { + return peak_bytes; + } + }; + + private: + stats_t stats; + //blt::hashset_t deletes; + + struct block + { + struct block_metadata_t + { + blt::size_t allocated_objects = 0; + block* next = nullptr; + block* prev = nullptr; + blt::u8* offset = nullptr; + } metadata; + + blt::u8 buffer[BLOCK_SIZE - sizeof(block_metadata_t)]{}; + + block() + { + metadata.offset = buffer; + } + }; + + // remaining space inside the block after accounting for the metadata + static constexpr blt::size_t BLOCK_REMAINDER = BLOCK_SIZE - sizeof(typename block::block_metadata_t); + + block* base = nullptr; + block* head = nullptr; + + /** + * Handles the allocation of the bytes for the block. + * This function will either use mmap to allocate huge pages if requested + * or use std::align_alloc to create an aligned allocation + * @return pointer to a constructed block + */ + block* allocate_block() + { + block* buffer; + #ifdef __unix__ + if constexpr (USE_HUGE) + { + buffer = allocate_huge_page(BLOCK_SIZE, HUGE_PAGE_SIZE); + } else + buffer = reinterpret_cast(std::aligned_alloc(BLOCK_SIZE, BLOCK_SIZE)); + #else buffer = static_cast(_aligned_malloc(BLOCK_SIZE, BLOCK_SIZE)); -#endif - construct(buffer); -#ifndef BLT_DISABLE_STATS - stats.incrementBlocks(); -#endif - return buffer; - } - - /** - * Allocates a new block and pushes it to the front of the linked listed - */ - void allocate_forward() - { - auto* block = allocate_block(); - if (head == nullptr) - { - base = head = block; - return; - } - block->metadata.prev = head; - head->metadata.next = block; - head = block; - } - - /** - * handles the actual allocation and alignment of memory - * @param bytes number of bytes to allocate - * @param alignment alignment required - * @return aligned pointer - */ - void* allocate_bytes(blt::size_t bytes, blt::size_t alignment) - { - if (head == nullptr) - return nullptr; - blt::size_t remaining_bytes = BLOCK_REMAINDER - static_cast(head->metadata.offset - head->buffer); - auto pointer = static_cast(head->metadata.offset); - return std::align(alignment, bytes, pointer, remaining_bytes); - } - - /** - * allocate an object starting from the next available address - * @tparam T type to allocate for - * @param count number of elements to allocate - * @return nullptr if the object could not be allocated, pointer to the object if it could, pointer to the start if count != 1 - */ - template - T* allocate_object(blt::size_t count) - { - blt::size_t bytes = sizeof(T) * count; - const auto aligned_address = allocate_bytes(bytes, alignof(T)); - if (aligned_address != nullptr) - { - head->metadata.allocated_objects++; - head->metadata.offset = static_cast(aligned_address) + bytes; - } - return static_cast(aligned_address); - } - - /** - * Frees a block - * @param p pointer to the block to free - */ - inline void delete_block(block* p) - { -#ifndef BLT_DISABLE_STATS - stats.decrementBlocks(); -#endif - if constexpr (USE_HUGE) - { - if (munmap(p, BLOCK_SIZE)) - { - BLT_ERROR_STREAM << "FAILED TO DEALLOCATE BLOCK\n"; - throw bad_alloc_t(handle_mmap_error()); - } - } else - free(p); - } - - public: - bump_allocator() = default; - - /** - * Takes an unused size parameter. Purely used for compatibility with the old bump_allocator - */ - explicit bump_allocator(blt::size_t) - {} - - /** - * Allocate bytes for a type - * @tparam T type to allocate - * @param count number of elements to allocate for - * @throws std::bad_alloc - * @return aligned pointer to the beginning of the allocated memory - */ - template - [[nodiscard]] T* allocate(blt::size_t count = 1) - { - if constexpr (sizeof(T) > BLOCK_REMAINDER) - throw std::bad_alloc(); + #endif + construct(buffer); + #ifndef BLT_DISABLE_STATS + stats.incrementBlocks(); + #endif + return buffer; + } -#ifndef BLT_DISABLE_STATS - stats.incrementBytes(sizeof(T) * count); -#endif - - T* ptr = allocate_object(count); - if (ptr != nullptr) - return ptr; - allocate_forward(); - ptr = allocate_object(count); - if (ptr == nullptr) - throw std::bad_alloc(); - return ptr; - } - - /** - * Deallocate a pointer, does not call the destructor - * @tparam T type of pointer - * @param p pointer to deallocate - */ - template - void deallocate(T* p, blt::size_t count = 1) - { - if (p == nullptr) - return; -#ifndef BLT_DISABLE_STATS - stats.decrementBytes(sizeof(T) * count); -#endif -// if (deletes.contains(p)) -// { -// BLT_FATAL("pointer %p has already been freed", p); -// throw std::bad_alloc(); -// }else -// deletes.insert(static_cast(p)); - - auto blk = to_block(p); - blk->metadata.allocated_objects--; - if (blk->metadata.allocated_objects == 0) - { - //BLT_INFO("Deallocating block from %p in (1) %p current head %p, based: %p", p, blk, head, base); - if (blk == base) - { - base = base->metadata.next; - // if they were equal (single allocated block) we also need to move the head forward - if (blk == head) - head = base; - } else if (blk == head) // else, need to make sure the head ptr gets moved back, otherwise we will use a head that has been freed - head = blk->metadata.prev; - else if (blk->metadata.prev != nullptr) // finally if it wasn't the head we need to bridge the gap in the list - blk->metadata.prev->metadata.next = blk->metadata.next; - - //BLT_INFO("Deallocating block from %p in (2) %p current head %p, based: %p", p, blk, head, base); - delete_block(blk); - } - } - - /** - * allocate a type then call its constructor with arguments - * @tparam T type to construct - * @tparam Args type of args to construct with - * @param args args to construct with - * @return aligned pointer to the constructed type - */ - template - [[nodiscard]] T* emplace(Args&& ... args) - { - const auto allocated_memory = allocate(); - return new(allocated_memory) T{std::forward(args)...}; - } - - /** - * allocate an array of count T with argument(s) args and call T's constructor - * @tparam T class to construct - * @tparam Args argument types to supply to construction - * @param count size of the array to allocate in number of elements. Note calling this with count = 0 is equivalent to calling emplace - * @param args the args to supply to construction - * @return aligned pointer to the beginning of the array of T - */ - template - [[nodiscard]] T* emplace_many(blt::size_t count, Args&& ... args) - { - if (count == 0) - return nullptr; - const auto allocated_memory = allocate(count); - for (blt::size_t i = 0; i < count; i++) - new(allocated_memory + i) T{std::forward(args)...}; - return allocated_memory; - } - - /** - * Used to construct a class U with parameters Args - * @tparam U class to construct - * @tparam Args args to use - * @param p pointer to non-constructed memory - * @param args list of arguments to build the class with - */ - template - inline void construct(U* p, Args&& ... args) - { - ::new((void*) p) U(std::forward(args)...); - } - - /** - * Call the destructor for class U with pointer p - * @tparam U class to call destructor on, this will not do anything if the type is std::trivially_destructible - * @param p - */ - template - inline void destroy(U* p) - { - if constexpr (!std::is_trivially_destructible_v) - { - if (p != nullptr) - p->~U(); - } - } - - /** - * Calls destroy on pointer p - * Then calls deallocate on p - * @tparam U class to destroy - * @param p pointer to deallocate - */ - template - inline void destruct(U* p) - { - destroy(p); - deallocate(p); - } - - inline void resetStats() - { - stats = {}; - } - - inline const auto& getStats() const - { - return stats; - } - - ~bump_allocator() - { - block* next = base; - while (next != nullptr) - { - auto* after = next->metadata.next; - delete_block(next); - next = after; - } - } - }; + /** + * Allocates a new block and pushes it to the front of the linked listed + */ + void allocate_forward() + { + auto* block = allocate_block(); + if (head == nullptr) + { + base = head = block; + return; + } + block->metadata.prev = head; + head->metadata.next = block; + head = block; + } + + /** + * handles the actual allocation and alignment of memory + * @param bytes number of bytes to allocate + * @param alignment alignment required + * @return aligned pointer + */ + void* allocate_bytes(blt::size_t bytes, blt::size_t alignment) + { + if (head == nullptr) + return nullptr; + blt::size_t remaining_bytes = BLOCK_REMAINDER - static_cast(head->metadata.offset - head->buffer); + auto pointer = static_cast(head->metadata.offset); + return std::align(alignment, bytes, pointer, remaining_bytes); + } + + /** + * allocate an object starting from the next available address + * @tparam T type to allocate for + * @param count number of elements to allocate + * @return nullptr if the object could not be allocated, pointer to the object if it could, pointer to the start if count != 1 + */ + template + T* allocate_object(blt::size_t count) + { + blt::size_t bytes = sizeof(T) * count; + const auto aligned_address = allocate_bytes(bytes, alignof(T)); + if (aligned_address != nullptr) + { + head->metadata.allocated_objects++; + head->metadata.offset = static_cast(aligned_address) + bytes; + } + return static_cast(aligned_address); + } + + /** + * Frees a block + * @param p pointer to the block to free + */ + inline void delete_block(block* p) + { + #ifndef BLT_DISABLE_STATS + stats.decrementBlocks(); + #endif + if constexpr (USE_HUGE) + { + if (munmap(p, BLOCK_SIZE)) + { + BLT_ERROR("FAILED TO DEALLOCATE BLOCK"); + throw bad_alloc_t(handle_mmap_error()); + } + } else + free(p); + } + + public: + bump_allocator() = default; + + /** + * Takes an unused size parameter. Purely used for compatibility with the old bump_allocator + */ + explicit bump_allocator(blt::size_t) + {} + + /** + * Allocate bytes for a type + * @tparam T type to allocate + * @param count number of elements to allocate for + * @throws std::bad_alloc + * @return aligned pointer to the beginning of the allocated memory + */ + template + [[nodiscard]] T* allocate(blt::size_t count = 1) + { + if constexpr (sizeof(T) > BLOCK_REMAINDER) + throw std::bad_alloc(); + + #ifndef BLT_DISABLE_STATS + stats.incrementBytes(sizeof(T) * count); + #endif + + T* ptr = allocate_object(count); + if (ptr != nullptr) + return ptr; + allocate_forward(); + ptr = allocate_object(count); + if (ptr == nullptr) + throw std::bad_alloc(); + return ptr; + } + + /** + * Deallocate a pointer, does not call the destructor + * @tparam T type of pointer + * @param p pointer to deallocate + */ + template + void deallocate(T* p, blt::size_t count = 1) + { + if (p == nullptr) + return; + #ifndef BLT_DISABLE_STATS + stats.decrementBytes(sizeof(T) * count); + #endif + // if (deletes.contains(p)) + // { + // BLT_FATAL("pointer %p has already been freed", p); + // throw std::bad_alloc(); + // }else + // deletes.insert(static_cast(p)); + + auto blk = to_block(p); + blk->metadata.allocated_objects--; + if (blk->metadata.allocated_objects == 0) + { + //BLT_INFO("Deallocating block from %p in (1) %p current head %p, based: %p", p, blk, head, base); + if (blk == base) + { + base = base->metadata.next; + // if they were equal (single allocated block) we also need to move the head forward + if (blk == head) + head = base; + } else if (blk == head) // else, need to make sure the head ptr gets moved back, otherwise we will use a head that has been freed + head = blk->metadata.prev; + else if (blk->metadata.prev != nullptr) // finally if it wasn't the head we need to bridge the gap in the list + blk->metadata.prev->metadata.next = blk->metadata.next; + + //BLT_INFO("Deallocating block from %p in (2) %p current head %p, based: %p", p, blk, head, base); + delete_block(blk); + } + } + + /** + * allocate a type then call its constructor with arguments + * @tparam T type to construct + * @tparam Args type of args to construct with + * @param args args to construct with + * @return aligned pointer to the constructed type + */ + template + [[nodiscard]] T* emplace(Args&&... args) + { + const auto allocated_memory = allocate(); + return new(allocated_memory) T{std::forward(args)...}; + } + + /** + * allocate an array of count T with argument(s) args and call T's constructor + * @tparam T class to construct + * @tparam Args argument types to supply to construction + * @param count size of the array to allocate in number of elements. Note calling this with count = 0 is equivalent to calling emplace + * @param args the args to supply to construction + * @return aligned pointer to the beginning of the array of T + */ + template + [[nodiscard]] T* emplace_many(blt::size_t count, Args&&... args) + { + if (count == 0) + return nullptr; + const auto allocated_memory = allocate(count); + for (blt::size_t i = 0; i < count; i++) + new(allocated_memory + i) T{std::forward(args)...}; + return allocated_memory; + } + + /** + * Used to construct a class U with parameters Args + * @tparam U class to construct + * @tparam Args args to use + * @param p pointer to non-constructed memory + * @param args list of arguments to build the class with + */ + template + inline void construct(U* p, Args&&... args) + { + ::new((void*) p) U(std::forward(args)...); + } + + /** + * Call the destructor for class U with pointer p + * @tparam U class to call destructor on, this will not do anything if the type is std::trivially_destructible + * @param p + */ + template + inline void destroy(U* p) + { + if constexpr (!std::is_trivially_destructible_v) + { + if (p != nullptr) + p->~U(); + } + } + + /** + * Calls destroy on pointer p + * Then calls deallocate on p + * @tparam U class to destroy + * @param p pointer to deallocate + */ + template + inline void destruct(U* p) + { + destroy(p); + deallocate(p); + } + + inline void resetStats() + { + stats = {}; + } + + inline const auto& getStats() const + { + return stats; + } + + ~bump_allocator() + { + block* next = base; + while (next != nullptr) + { + auto* after = next->metadata.next; + delete_block(next); + next = after; + } + } + }; } #define BLT_ALLOCATOR_H diff --git a/include/blt/std/logging.h b/include/blt/std/logging.h deleted file mode 100644 index 86d6b41..0000000 --- a/include/blt/std/logging.h +++ /dev/null @@ -1,436 +0,0 @@ -/* - * Created by Brett on 20/07/23. - * Licensed under GNU General Public License V3.0 - * See LICENSE file for license detail - */ - -#ifndef BLT_TESTS_LOGGING2_H -#define BLT_TESTS_LOGGING2_H - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace blt::logging -{ - - namespace ansi - { - inline auto ESC(std::string_view str) - { - return std::string("\033") += str; - } - - inline const auto CUR_HOME = ESC("[H"); - - inline auto CUR_MOVE(blt::size_t line, blt::size_t column) - { - return ESC("[{" + std::to_string(line) + "};{" + std::to_string(column) + "}H"); - } - - inline auto CUR_UP(blt::size_t lines) - { - return ESC("[" + std::to_string(lines) + "A"); - } - - inline auto CUR_DOWN(blt::size_t lines) - { - return ESC("[" + std::to_string(lines) + "B"); - } - - inline auto CUR_RIGHT(blt::size_t columns) - { - return ESC("[" + std::to_string(columns) + "C"); - } - - inline auto CUR_LEFT(blt::size_t columns) - { - return ESC("[" + std::to_string(columns) + "D"); - } - - inline auto CUR_BEGIN_NEXT(blt::size_t lines_down) - { - return ESC("[" + std::to_string(lines_down) + "E"); - } - - inline auto CUR_BEGIN_PREV(blt::size_t lines_up) - { - return ESC("[" + std::to_string(lines_up) + "F"); - } - - inline auto CUR_COLUMN(blt::size_t column) - { - return ESC("[" + std::to_string(column) + "G"); - } - - inline auto CUR_POS() - { - return ESC("[6n"); - } - - inline auto CUR_SCROLL_UP() - { - return ESC(" M"); - } - - inline auto CUR_SAVE_DEC() - { - return ESC(" 7"); - } - - inline auto CUR_LOAD_DEC() - { - return ESC(" 8"); - } - - inline auto CUR_SAVE_SCO() - { - return ESC("[s"); - } - - inline auto CUR_LOAD_SCO() - { - return ESC("[u"); - } - - inline auto RESET = ESC("[0m"); - inline auto BOLD = "1"; - inline auto RESET_BOLD = "22"; - inline auto DIM = "2"; - inline auto RESET_DIM = "22"; - inline auto ITALIC = "3"; - inline auto RESET_ITALIC = "23"; - inline auto UNDERLINE = "4"; - inline auto RESET_UNDERLINE = "24"; - inline auto BLINKING = "5"; - inline auto RESET_BLINKING = "25"; - inline auto INVERSE = "7"; - inline auto RESET_INVERSE = "27"; - inline auto HIDDEN = "8"; - inline auto RESET_HIDDEN = "28"; - inline auto STRIKETHROUGH = "9"; - inline auto RESET_STRIKETHROUGH = "29"; - - inline auto COLOR_DEFAULT = "39"; - inline auto BACKGROUND_DEFAULT = "49"; - - inline auto BLACK = "30"; - inline auto RED = "31"; - inline auto GREEN = "32"; - inline auto YELLOW = "33"; - inline auto BLUE = "34"; - inline auto MAGENTA = "35"; - inline auto CYAN = "36"; - inline auto WHITE = "37"; - inline auto BLACK_BACKGROUND = "40"; - inline auto RED_BACKGROUND = "41"; - inline auto GREEN_BACKGROUND = "42"; - inline auto YELLOW_BACKGROUND = "43"; - inline auto BLUE_BACKGROUND = "44"; - inline auto MAGENTA_BACKGROUND = "45"; - inline auto CYAN_BACKGROUND = "46"; - inline auto WHITE_BACKGROUND = "47"; - - inline auto BRIGHT_BLACK = "90"; - inline auto BRIGHT_RED = "91"; - inline auto BRIGHT_GREEN = "92"; - inline auto BRIGHT_YELLOW = "93"; - inline auto BRIGHT_BLUE = "94"; - inline auto BRIGHT_MAGENTA = "95"; - inline auto BRIGHT_CYAN = "96"; - inline auto BRIGHT_WHITE = "97"; - inline auto BRIGHT_BLACK_BACKGROUND = "100"; - inline auto BRIGHT_RED_BACKGROUND = "101"; - inline auto BRIGHT_GREEN_BACKGROUND = "102"; - inline auto BRIGHT_YELLOW_BACKGROUND = "103"; - inline auto BRIGHT_BLUE_BACKGROUND = "104"; - inline auto BRIGHT_MAGENTA_BACKGROUND = "105"; - inline auto BRIGHT_CYAN_BACKGROUND = "106"; - inline auto BRIGHT_WHITE_BACKGROUND = "107"; - - template - inline auto make_color(Args... colors) - { - std::string mode; - ((mode += std::string(colors) + ";"), ...); - return ESC("[" + mode.substr(0, mode.size() - 1) + "m"); - } - } - - enum class log_level - { - // default - NONE, - // low level - TRACE0, TRACE1, TRACE2, TRACE3, - // normal - TRACE, DEBUG, INFO, - // errors - WARN, ERROR, FATAL, - }; - - struct tag_func_param - { - blt::logging::log_level level; - const std::string& file, line, raw_string, formatted_string; - }; - - struct tag - { - // tag without the ${{ or }} - std::string tag; - // function to run: log level, file, line, and raw user input string are provided - std::function func; - }; - - struct log_format - { - /** - * the log output format is the string which will be used to create the log output string - * - * Available tags: - * - ${{YEAR}} // current year - * - ${{MONTH}} // current month - * - ${{DAY}} // current day - * - ${{HOUR}} // current hour - * - ${{MINUTE}} // current minute - * - ${{SECOND}} // current second - * - ${{MS}} // current unix time - * - ${{NS}} // current ns from the high resolution system timer - * - ${{ISO_YEAR}} // ISO formatted 'year-month-day' in a single variable - * - ${{TIME}} // 'hour:minute:second' formatted string in a single variable - * - ${{FULL_TIME}} // 'year-month-day hour:minute:second' in a single variable - * - ${{LF}} // log level color (ANSI color code) - * - ${{ER}} // Error red - * - ${{CNR}} // conditional error red (only outputs if log level is an error!) - * - ${{RC}} // ANSI color reset - * - ${{LOG_LEVEL}} // current log level - * - ${{THREAD_NAME}} // current thread name, NOTE: thread names must be set by calling "setThreadName()" from the thread in question! - * - ${{FILE}} // file from which the macro is invoked - * - ${{LINE}} // line in the from which the macro is invoked - * - ${{RAW_STR}} // raw user string without formatting applied (NOTE: format args are not provided!) - * - ${{STR}} // the user supplied string (format applied!) - */ - std::string logOutputFormat = "\033[94m[${{TIME}}]${{RC}} ${{LF}}[${{LOG_LEVEL}}]${{RC}} \033[35m(${{FILE}}:${{LINE}})${{RC}} ${{CNR}}${{STR}}${{RC}}\n"; - std::string levelNames[11] = {"STDOUT", "TRACE0", "TRACE1", "TRACE2", "TRACE3", "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"}; - std::string levelColors[11] = {"\033[0m", "\033[22;97m", "\033[97m", "\033[97m", "\033[97m", "\033[97m", "\033[36m", "\033[92m", "\033[93m", - "\033[91m", "\033[97;41m"}; - // if true prints the whole path to the file (eg /home/user/.../.../project/src/source.cpp:line#) - bool printFullFileName = false; - // the logging lib will keep track of the largest line found so far and try to keep the spacing accordingly - // this is not thread safe! - bool ensureAlignment = false; - // should we log to file? - bool logToFile = false; - // should we log to console? - bool logToConsole = true; - // where should we log? (empty for current binary directory) should end in a / if not empty! - std::string logFilePath; - // logs to a file called $fileName_$count.log where count is the number of rollover files - // this accepts any of the macros above, using level names and colors should work but isn't supported. - std::string logFileName = "${{ISO_YEAR}}"; - // default limit on file size: 10mb; - size_t logMaxFileSize = 1024 * 1024 * 10; - /** - * Variables below this line should never be changed by the user! - */ - // the current alignment width found (you shouldn't chance this variable!) - size_t currentWidth = 0; - // current number of file roll-overs. you shouldn't change this either. - size_t currentRollover = 0; - std::string lastFile; - }; - - struct logger - { - log_level level; - const char* file; - int line; - }; - - struct empty_logger - { - - }; - - void log_internal(const std::string& format, log_level level, const char* file, int line, std::va_list& args); - - void log_stream_internal(const std::string& str, const logger& logger); - - template - inline std::string to_string_stream(const T& t) - { - std::stringstream stream; - stream << t; - return stream.str(); - } - - template - inline static void log_stream(const T& t, const logger& logger) - { - if constexpr (std::is_arithmetic_v && !std::is_same_v) - { - log_stream_internal(std::to_string(t), logger); - } else if constexpr (std::is_same_v || std::is_same_v) - { - log_stream_internal(t, logger); - } else - { - log_stream_internal(to_string_stream(t), logger); - } - } - - template - inline void log(T t, log_level level, const char* file, int line, ...) - { - std::va_list args; - va_start(args, line); - if constexpr (std::is_arithmetic_v) - { - log_internal(std::to_string(t), level, file, line, args); - } else if constexpr (std::is_same_v) - { - log_internal(t, level, file, line, args); - } else if constexpr (std::is_same_v) - { - log_internal(std::string(t), level, file, line, args); - } else - { - log_internal(to_string_stream(t), level, file, line, args); - } - va_end(args); - } - - template - static inline const blt::logging::logger& operator<<(const blt::logging::logger& out, const T& t) - { - log_stream(t, out); - return out; - } - - template - static inline const blt::logging::empty_logger& operator<<(const blt::logging::empty_logger& out, const T&) - { - return out; - } - - void flush(); - - void newline(); - - void setThreadName(const std::string& name); - - void setLogFormat(const log_format& format); - - void setLogColor(log_level level, const std::string& newFormat); - - void setLogName(log_level level, const std::string& newFormat); - - void setLogOutputFormat(const std::string& newFormat); - - void setLogToFile(bool shouldLogToFile); - - void setLogToConsole(bool shouldLogToConsole); - - void setLogPath(const std::string& path); - - void setLogFileName(const std::string& fileName); - - void setMaxFileSize(size_t fileSize); -} - -#if defined(__clang__) || defined(__llvm__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" -#endif - -#ifdef BLT_DISABLE_LOGGING - #define BLT_LOG(format, level, ...) - #define BLT_LOG_STREAM(level) - #define BLT_TRACE0_STREAM - #define BLT_TRACE1_STREAM - #define BLT_TRACE2_STREAM - #define BLT_TRACE3_STREAM - #define BLT_TRACE_STREAM - #define BLT_DEBUG_STREAM - #define BLT_INFO_STREAM - #define BLT_WARN_STREAM - #define BLT_ERROR_STREAM - #define BLT_FATAL_STREAM - #define BLT_TRACE(format, ...) - #define BLT_DEBUG(format, ...) - #define BLT_INFO(format, ...) - #define BLT_WARN(format, ...) - #define BLT_ERROR(format, ...) - #define BLT_FATAL(format, ...) -#else - #define BLT_NEWLINE() blt::logging::newline() - #define BLT_LOG(format, level, ...) blt::logging::log(format, level, __FILE__, __LINE__, ##__VA_ARGS__) - #define BLT_LOG_STREAM(level) blt::logging::logger{level, __FILE__, __LINE__} - #ifdef BLT_DISABLE_TRACE - #define BLT_TRACE(format, ...) - #define BLT_TRACE0_STREAM blt::logging::empty_logger{} - #define BLT_TRACE1_STREAM blt::logging::empty_logger{} - #define BLT_TRACE2_STREAM blt::logging::empty_logger{} - #define BLT_TRACE3_STREAM blt::logging::empty_logger{} - #define BLT_TRACE_STREAM blt::logging::empty_logger{} - #else - #define BLT_TRACE(format, ...) BLT_LOG(format, blt::logging::log_level::TRACE, ##__VA_ARGS__) - #define BLT_TRACE0_STREAM BLT_LOG_STREAM(blt::logging::log_level::TRACE0) - #define BLT_TRACE1_STREAM BLT_LOG_STREAM(blt::logging::log_level::TRACE1) - #define BLT_TRACE2_STREAM BLT_LOG_STREAM(blt::logging::log_level::TRACE2) - #define BLT_TRACE3_STREAM BLT_LOG_STREAM(blt::logging::log_level::TRACE3) - #define BLT_TRACE_STREAM BLT_LOG_STREAM(blt::logging::log_level::TRACE) - #endif - - #ifdef BLT_DISABLE_DEBUG - #define BLT_DEBUG(format, ...) - #define BLT_DEBUG_STREAM blt::logging::empty_logger{} - #else - #define BLT_DEBUG(format, ...) BLT_LOG(format, blt::logging::log_level::DEBUG, ##__VA_ARGS__) - #define BLT_DEBUG_STREAM BLT_LOG_STREAM(blt::logging::log_level::DEBUG) - #endif - - #ifdef BLT_DISABLE_INFO - #define BLT_INFO(format, ...) - #define BLT_INFO_STREAM blt::logging::empty_logger{} - #else - #define BLT_INFO(format, ...) BLT_LOG(format, blt::logging::log_level::INFO, ##__VA_ARGS__) - #define BLT_INFO_STREAM BLT_LOG_STREAM(blt::logging::log_level::INFO) - #endif - - #ifdef BLT_DISABLE_WARN - #define BLT_WARN(format, ...) - #define BLT_WARN_STREAM blt::logging::empty_logger{} - #else - #define BLT_WARN(format, ...) BLT_LOG(format, blt::logging::log_level::WARN, ##__VA_ARGS__) - #define BLT_WARN_STREAM BLT_LOG_STREAM(blt::logging::log_level::WARN) - #endif - - #ifdef BLT_DISABLE_ERROR - #define BLT_ERROR(format, ...) - #define BLT_ERROR_STREAM blt::logging::empty_logger{} - #else - #define BLT_ERROR(format, ...) BLT_LOG(format, blt::logging::log_level::ERROR, ##__VA_ARGS__) - #define BLT_ERROR_STREAM BLT_LOG_STREAM(blt::logging::log_level::ERROR) - #endif - - #ifdef BLT_DISABLE_FATAL - #define BLT_FATAL(format, ...) - #define BLT_FATAL_STREAM blt::logging::empty_logger{} - #else - #define BLT_FATAL(format, ...) BLT_LOG(format, blt::logging::log_level::FATAL, ##__VA_ARGS__) - #define BLT_FATAL_STREAM BLT_LOG_STREAM(blt::logging::log_level::FATAL) - #endif -#endif - -#if defined(__clang__) || defined(__llvm__) -#pragma clang diagnostic pop -#endif - -#endif //BLT_TESTS_LOGGING2_H diff --git a/include/blt/std/mmap.h b/include/blt/std/mmap.h index e1b7810..c0fb7ee 100644 --- a/include/blt/std/mmap.h +++ b/include/blt/std/mmap.h @@ -19,9 +19,11 @@ #ifndef BLT_MMAP_H #define BLT_MMAP_H -#include #include #include +#include +#include +#include // size of 2mb in bytes inline constexpr blt::size_t BLT_2MB_SIZE = 2048 * 1024; @@ -41,7 +43,7 @@ namespace blt public: bad_alloc_t() = default; - explicit bad_alloc_t(std::string_view str): str(str) + explicit bad_alloc_t(const std::string_view str): str(str) {} explicit bad_alloc_t(std::string str): str(std::move(str)) diff --git a/src/blt/format/format.cpp b/src/blt/format/format.cpp index 3d79234..bf15857 100644 --- a/src/blt/format/format.cpp +++ b/src/blt/format/format.cpp @@ -6,7 +6,7 @@ #include #include #include -#include "blt/std/logging.h" +#include "blt/logging/logging.h" #include "blt/std/assert.h" #include "blt/std/utility.h" #include diff --git a/src/blt/fs/filesystem.cpp b/src/blt/fs/filesystem.cpp index 43b9fb2..9718d9b 100644 --- a/src/blt/fs/filesystem.cpp +++ b/src/blt/fs/filesystem.cpp @@ -17,5 +17,4 @@ */ #include #include -#include diff --git a/src/blt/fs/nbt.cpp b/src/blt/fs/nbt.cpp index 1679977..5ec5c33 100644 --- a/src/blt/fs/nbt.cpp +++ b/src/blt/fs/nbt.cpp @@ -4,7 +4,7 @@ * See LICENSE file for license detail */ #include -#include +#include #include #include diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp index d700b28..446ac86 100644 --- a/src/blt/parse/argparse_v2.cpp +++ b/src/blt/parse/argparse_v2.cpp @@ -20,7 +20,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/blt/parse/obj_loader.cpp b/src/blt/parse/obj_loader.cpp index 82a3c2d..5433876 100644 --- a/src/blt/parse/obj_loader.cpp +++ b/src/blt/parse/obj_loader.cpp @@ -26,7 +26,7 @@ #include #include "blt/std/assert.h" #include "blt/std/utility.h" -#include +#include namespace blt::parse { @@ -101,24 +101,23 @@ namespace blt::parse return; } - auto elements = blt::string::split(std::string(tokenizer.read_fully()), " "); + auto elements = string::split(tokenizer.read_fully(), " "); BLT_ASSERT(elements.size() >= 2 && "Current line doesn't have enough arguments to process!"); float x = get(elements[0]), y = get(elements[1]); - BLT_DEBUG_STREAM << "Loaded value of (" << x << ", " << y << ")"; + BLT_DEBUG("Loaded value of ({}, {})", x, y); if (elements.size() < 3) { if (type == 't') uvs.push_back(uv_t{x, y}); else - BLT_ERROR("Unable to parse line '%s' type '%c' not recognized for arg count", std::string(tokenizer.read_fully()).c_str(), type); + BLT_ERROR("Unable to parse line '{}' type '{:c}' not recognized for arg count", tokenizer.read_fully(), type); } else { float z = get(elements[2]); - BLT_DEBUG_STREAM << " with z: " << z; + BLT_DEBUG(" with z: {}", z); if (!handle_vertex_and_normals(x, y, z, type)) - BLT_ERROR("Unable to parse line '%s' type '%c' not recognized", std::string(tokenizer.read_fully()).c_str(), type); + BLT_ERROR("Unable to parse line '{}' type '{:c}' not recognized", tokenizer.read_fully(), type); } - BLT_DEBUG_STREAM << "\n"; } bool obj_loader::handle_vertex_and_normals(float x, float y, float z, char type) diff --git a/src/blt/parse/templating.cpp b/src/blt/parse/templating.cpp index b56bef9..6d0f962 100644 --- a/src/blt/parse/templating.cpp +++ b/src/blt/parse/templating.cpp @@ -18,7 +18,7 @@ #include #include #include -#include "blt/std/logging.h" +#include "blt/logging/logging.h" namespace blt { diff --git a/src/blt/profiling/profiler.cpp b/src/blt/profiling/profiler.cpp index b088016..68e5cdb 100644 --- a/src/blt/profiling/profiler.cpp +++ b/src/blt/profiling/profiler.cpp @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include #include #include @@ -32,9 +32,9 @@ namespace blt::profiling { difference(difference), name(std::move(name)), total(total) {} }; - inline void println(const std::vector&& lines, logging::log_level level) { + inline void println(const std::vector&& lines, const logging::log_level_t level) { for (const auto& line : lines) - BLT_LOG_STREAM(level) << line << "\n"; + BLT_LOG(level, "{}", line); // auto& logger = logging::getLoggerFromLevel(level); // for (const auto& line : lines) // logger << line << "\n"; @@ -108,7 +108,7 @@ namespace blt::profiling { } void printProfile( - const std::string& profileName, logging::log_level loggingLevel, bool averageHistory + const std::string& profileName, const logging::log_level_t loggingLevel, const bool averageHistory ) { auto& profile = profiles[profileName]; const auto& intervals = profile.intervals; diff --git a/src/blt/profiling/profiler_v2.cpp b/src/blt/profiling/profiler_v2.cpp index 00dd5f7..75e12ee 100644 --- a/src/blt/profiling/profiler_v2.cpp +++ b/src/blt/profiling/profiler_v2.cpp @@ -187,11 +187,11 @@ namespace blt stream << line << "\n"; } - void printProfile(profile_t& profiler, const std::uint32_t flags, sort_by sort, blt::logging::log_level log_level) + void printProfile(profile_t& profiler, const std::uint32_t flags, sort_by sort, blt::logging::log_level_t log_level) { std::stringstream stream; writeProfile(stream, profiler, flags, sort); - BLT_LOG_STREAM(log_level) << stream.str(); + BLT_LOG(log_level, "{}", stream.str()); } profile_t::~profile_t() @@ -237,7 +237,7 @@ namespace blt profiles.erase(profile_name); } - void _internal::printProfile(const std::string& profile_name, std::uint32_t flags, sort_by sort, blt::logging::log_level log_level) + void _internal::printProfile(const std::string& profile_name, std::uint32_t flags, sort_by sort, blt::logging::log_level_t log_level) { if (profiles.find(profile_name) == profiles.end()) return; diff --git a/src/blt/std/assert.cpp b/src/blt/std/assert.cpp index 5ed4440..ebc99e5 100644 --- a/src/blt/std/assert.cpp +++ b/src/blt/std/assert.cpp @@ -3,15 +3,15 @@ * Licensed under GNU General Public License V3.0 * See LICENSE file for license detail */ -#include -#include -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include struct abort_exception final : public std::exception { @@ -48,8 +48,8 @@ struct abort_exception final : public std::exception #ifdef IS_GNU_BACKTRACE - #include #include +#include #endif diff --git a/src/blt/std/error.cpp b/src/blt/std/error.cpp index c733e0e..bc658c7 100644 --- a/src/blt/std/error.cpp +++ b/src/blt/std/error.cpp @@ -15,68 +15,52 @@ * along with this program. If not, see . */ #include -#include +#include namespace blt::error { - void print_socket_error() - { - switch (errno) - { - case EINVAL: - BLT_WARN("Invalid argument"); - break; - case EACCES: - BLT_WARN("Permission denied"); - break; - case EPERM: - BLT_WARN("Operation not permitted"); - break; - case EADDRINUSE: - BLT_WARN("Address already in use"); - break; - case EADDRNOTAVAIL: - BLT_WARN("Cannot copy_fast requested address"); - break; - case EAFNOSUPPORT: - BLT_WARN("Address family not supported by protocol"); - break; - case EAGAIN: - BLT_WARN("Try again"); - break; - case EALREADY: - BLT_WARN("Operation already in progress"); - break; - case EBADF: - BLT_WARN("Bad file number"); - break; - case ECONNREFUSED: - BLT_WARN("Connection refused"); - break; - case EFAULT: - BLT_WARN("Bad address"); - break; - case EINPROGRESS: - BLT_WARN("Operation now in progress"); - break; - case EINTR: - BLT_WARN("Interrupted system call"); - break; - case EISCONN: - BLT_WARN("Transport endpoint is already connected"); - break; - case ENETUNREACH: - BLT_WARN("Network is unreachable"); - break; - case ENOTSOCK: - BLT_WARN("Socket operation_t on non-socket"); - break; - case EPROTOTYPE: - BLT_WARN("Protocol wrong type for socket"); - break; - case ETIMEDOUT: - BLT_WARN("Connection timed out"); - break; - } - } -} \ No newline at end of file + void print_socket_error() + { + switch (errno) + { + case EINVAL: BLT_WARN("Invalid argument"); + break; + case EACCES: BLT_WARN("Permission denied"); + break; + case EPERM: BLT_WARN("Operation not permitted"); + break; + case EADDRINUSE: BLT_WARN("Address already in use"); + break; + case EADDRNOTAVAIL: BLT_WARN("Cannot copy_fast requested address"); + break; + case EAFNOSUPPORT: BLT_WARN("Address family not supported by protocol"); + break; + case EAGAIN: BLT_WARN("Try again"); + break; + case EALREADY: BLT_WARN("Operation already in progress"); + break; + case EBADF: BLT_WARN("Bad file number"); + break; + case ECONNREFUSED: BLT_WARN("Connection refused"); + break; + case EFAULT: BLT_WARN("Bad address"); + break; + case EINPROGRESS: BLT_WARN("Operation now in progress"); + break; + case EINTR: BLT_WARN("Interrupted system call"); + break; + case EISCONN: BLT_WARN("Transport endpoint is already connected"); + break; + case ENETUNREACH: BLT_WARN("Network is unreachable"); + break; + case ENOTSOCK: BLT_WARN("Socket operation_t on non-socket"); + break; + case EPROTOTYPE: BLT_WARN("Protocol wrong type for socket"); + break; + case ETIMEDOUT: BLT_WARN("Connection timed out"); + break; + default: + break; + } + } +} diff --git a/src/blt/std/logging.cpp b/src/blt/std/logging.cpp deleted file mode 100644 index 393f8f9..0000000 --- a/src/blt/std/logging.cpp +++ /dev/null @@ -1,627 +0,0 @@ -/* - * Created by Brett on 20/07/23. - * Licensed under GNU General Public License V3.0 - * See LICENSE file for license detail - */ -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#if defined(CXX17_FILESYSTEM) || defined (CXX17_FILESYSTEM_LIBFS) - #include -#elif defined(CXX11_EXP_FILESYSTEM) || defined (CXX11_EXP_FILESYSTEM_LIBFS) - #include -#else -#include -#endif -#include -#include - -template -using hashmap = std::unordered_map; - -namespace blt::logging -{ - /** - * Used to store fast associations between built in tags and their respective values - */ - class tag_map - { - private: - tag* tags; - size_t size; - - [[nodiscard]] static inline size_t hash(const tag& t) - { - size_t h = t.tag[1] * 3 - t.tag[0]; - return h - 100; - } - - // TODO: fix - void expand() - { - auto newSize = size * 2; - auto newTags = new tag[newSize]; - for (size_t i = 0; i < size; i++) - newTags[i] = tags[i]; - delete[] tags; - tags = newTags; - size = newSize; - } - - public: - tag_map(std::initializer_list initial_tags) - { - size_t max = 0; - for (const auto& t : initial_tags) - max = std::max(max, hash(t)); - tags = new tag[(size = max + 1)]; - for (const auto& t : initial_tags) - insert(t); - } - - tag_map(const tag_map& copy) - { - tags = new tag[(size = copy.size)]; - for (size_t i = 0; i < size; i++) - tags[i] = copy.tags[i]; - } - - void insert(const tag& t) - { - auto h = hash(t); - //if (h > size) - // expand(); - if (!tags[h].tag.empty()) - std::cerr << "Tag not empty! " << tags[h].tag << "!!!\n"; - tags[h] = t; - } - - tag& operator[](const std::string& name) const - { - auto h = hash(tag{name, nullptr}); - if (h > size) - std::cerr << "Tag out of bounds"; - return tags[h]; - } - - ~tag_map() - { - delete[] tags; - tags = nullptr; - } - }; - - class LogFileWriter - { - private: - std::string m_path; - std::fstream* output = nullptr; - - public: - explicit LogFileWriter() = default; - - void writeLine(const std::string& path, const std::string& line) - { - if (path != m_path || output == nullptr) - { - clear(); - delete output; - output = new std::fstream(path, std::ios::out | std::ios::app); - if (!output->good()) - { - throw std::runtime_error("Unable to open console filestream!\n"); - } - } - if (!output->good()) - { - std::cerr << "There has been an error in the logging file stream!\n"; - output->clear(); - } - *output << line; - } - - void clear() - { - if (output != nullptr) - { - try - { - output->flush(); - output->close(); - } - catch (std::exception& e) - { - std::cerr << e.what() << "\n"; - } - } - } - - ~LogFileWriter() - { - clear(); - delete(output); - } - }; - -#ifdef WIN32 - #define BLT_NOW() auto t = std::time(nullptr); tm now{}; localtime_s(&now, &t) -#else -#define BLT_NOW() auto t = std::time(nullptr); auto now_ptr = std::localtime(&t); auto& now = *now_ptr -#endif - - //#define BLT_NOW() auto t = std::time(nullptr); tm now; localtime_s(&now, &t); //auto now = std::localtime(&t) -#define BLT_ISO_YEAR(S) auto S = std::to_string(now.tm_year + 1900); \ - S += '-'; \ - S += ensureHasDigits(now.tm_mon+1, 2); \ - S += '-'; \ - S += ensureHasDigits(now.tm_mday, 2); -#define BLT_CUR_TIME(S) auto S = ensureHasDigits(now.tm_hour, 2); \ - S += ':'; \ - S += ensureHasDigits(now.tm_min, 2); \ - S += ':'; \ - S += ensureHasDigits(now.tm_sec, 2); - - static inline std::string ensureHasDigits(int current, int digits) - { - std::string asString = std::to_string(current); - auto length = digits - asString.length(); - if (length <= 0) - return asString; - std::string zeros; - zeros.reserve(length); - for (unsigned int i = 0; i < length; i++) - { - zeros += '0'; - } - return zeros + asString; - } - - log_format loggingFormat{}; - hashmap loggingThreadNames; - hashmap> loggingStreamLines; - LogFileWriter writer; - - const std::unique_ptr tagMap = std::make_unique(tag_map{ - { - "YEAR", [](const tag_func_param&) -> std::string - { - BLT_NOW(); - return std::to_string(now.tm_year); - } - }, - { - "MONTH", [](const tag_func_param&) -> std::string - { - BLT_NOW(); - return ensureHasDigits(now.tm_mon + 1, 2); - } - }, - { - "DAY", [](const tag_func_param&) -> std::string - { - BLT_NOW(); - return ensureHasDigits(now.tm_mday, 2); - } - }, - { - "HOUR", [](const tag_func_param&) -> std::string - { - BLT_NOW(); - return ensureHasDigits(now.tm_hour, 2); - } - }, - { - "MINUTE", [](const tag_func_param&) -> std::string - { - BLT_NOW(); - return ensureHasDigits(now.tm_min, 2); - } - }, - { - "SECOND", [](const tag_func_param&) -> std::string - { - BLT_NOW(); - return ensureHasDigits(now.tm_sec, 2); - } - }, - { - "MS", [](const tag_func_param&) -> std::string - { - return std::to_string(std::chrono::duration_cast( - std::chrono::high_resolution_clock::now().time_since_epoch()).count() - ); - } - }, - { - "NS", [](const tag_func_param&) -> std::string - { - return std::to_string(std::chrono::duration_cast( - std::chrono::high_resolution_clock::now().time_since_epoch()).count() - ); - } - }, - { - "ISO_YEAR", [](const tag_func_param&) -> std::string - { - BLT_NOW(); - BLT_ISO_YEAR(returnStr); - return returnStr; - } - }, - { - "TIME", [](const tag_func_param&) -> std::string - { - BLT_NOW(); - BLT_CUR_TIME(returnStr); - return returnStr; - } - }, - { - "FULL_TIME", [](const tag_func_param&) -> std::string - { - BLT_NOW(); - BLT_ISO_YEAR(ISO); - BLT_CUR_TIME(TIME); - ISO += ' '; - ISO += TIME; - return ISO; - } - }, - { - "LF", [](const tag_func_param& f) -> std::string - { - return loggingFormat.levelColors[(int)f.level]; - } - }, - { - "ER", [](const tag_func_param&) -> std::string - { - return loggingFormat.levelColors[(int)log_level::ERROR]; - } - }, - { - "CNR", [](const tag_func_param& f) -> std::string - { - return f.level >= log_level::ERROR ? loggingFormat.levelColors[(int)log_level::ERROR] : ""; - } - }, - { - "RC", [](const tag_func_param&) -> std::string - { - return "\033[0m"; - } - }, - { - "LOG_LEVEL", [](const tag_func_param& f) -> std::string - { - return loggingFormat.levelNames[(int)f.level]; - } - }, - { - "THREAD_NAME", [](const tag_func_param&) -> std::string - { - if (loggingThreadNames.find(std::this_thread::get_id()) == loggingThreadNames.end()) - return "UNKNOWN"; - return loggingThreadNames[std::this_thread::get_id()]; - } - }, - { - "FILE", [](const tag_func_param& f) -> std::string - { - return f.file; - } - }, - { - "LINE", [](const tag_func_param& f) -> std::string - { - return f.line; - } - }, - { - "RAW_STR", [](const tag_func_param& f) -> std::string - { - return f.raw_string; - } - }, - { - "STR", [](const tag_func_param& f) -> std::string - { - return f.formatted_string; - } - } - }); - - static inline std::vector split(std::string s, const std::string& delim) - { - size_t pos = 0; - std::vector tokens; - while ((pos = s.find(delim)) != std::string::npos) - { - auto token = s.substr(0, pos); - tokens.push_back(token); - s.erase(0, pos + delim.length()); - } - tokens.push_back(s); - return tokens; - } - - inline std::string filename(const std::string& path) - { - if (loggingFormat.printFullFileName) - return path; - auto paths = split(path, "/"); - auto final = paths[paths.size() - 1]; - if (final == "/") - return paths[paths.size() - 2]; - return final; - } - - class string_parser - { - private: - std::string _str; - size_t _pos; - - public: - explicit string_parser(std::string str): _str(std::move(str)), _pos(0) - { - } - - inline char next() - { - return _str[_pos++]; - } - - [[nodiscard]] inline bool has_next() const - { - return _pos < _str.size(); - } - }; - - std::string stripANSI(const std::string& str) - { - string_parser parser(str); - std::string out; - while (parser.has_next()) - { - char c = parser.next(); - if (c == '\033') - { - while (parser.has_next() && parser.next() != 'm'); - } - else - out += c; - } - return out; - } - - void applyCFormatting(const std::string& format, std::string& output, std::va_list& args) - { - // args must be copied because they will be consumed by the first vsnprintf - va_list args_copy; - va_copy(args_copy, args); - - auto buffer_size = std::vsnprintf(nullptr, 0, format.c_str(), args_copy) + 1; - auto* buffer = new char[static_cast(buffer_size)]; - - vsnprintf(buffer, buffer_size, format.c_str(), args); - output = std::string(buffer); - - delete[] buffer; - - va_end(args_copy); - } - - /** - * Checks if the next character in the parser is a tag opening, if not output the buffer to the out string - */ - inline bool tagOpening(string_parser& parser, std::string& out) - { - char c = ' '; - if (parser.has_next() && (c = parser.next()) == '{') - if (parser.has_next() && (c = parser.next()) == '{') - return true; - else - out += c; - else - out += c; - return false; - } - - void parseString(string_parser& parser, std::string& out, const std::string& userStr, log_level level, const char* file, int line) - { - while (parser.has_next()) - { - char c = parser.next(); - std::string nonTag; - if (c == '$' && tagOpening(parser, nonTag)) - { - std::string tag; - while (parser.has_next()) - { - c = parser.next(); - if (c == '}') - break; - tag += c; - } - c = parser.next(); - if (parser.has_next() && c != '}') - { - std::cerr << "Error processing tag, is not closed with two '}'!\n"; - break; - } - if (loggingFormat.ensureAlignment && tag == "STR") - { - auto currentOutputWidth = out.size(); - auto& longestWidth = loggingFormat.currentWidth; - longestWidth = std::max(longestWidth, currentOutputWidth); - // pad with spaces - if (currentOutputWidth != longestWidth) - { - for (size_t i = currentOutputWidth; i < longestWidth; i++) - out += ' '; - } - } - tag_func_param param{ - level, filename({file}), std::to_string(line), userStr, userStr - }; - out += (*tagMap)[tag].func(param); - } - else - { - out += c; - out += nonTag; - } - } - } - - std::string applyFormatString(const std::string& str, log_level level, const char* file, int line) - { - // this can be speedup by preprocessing the string into an easily callable class - // where all the variables are ready to be substituted in one step - // and all static information already entered - string_parser parser(loggingFormat.logOutputFormat); - std::string out; - parseString(parser, out, str, level, file, line); - - return out; - } - - void log_internal(const std::string& format, log_level level, const char* file, int line, std::va_list& args) - { - std::string withoutLn = format; - auto len = withoutLn.length(); - - if (len > 0 && withoutLn[len - 1] == '\n') - withoutLn = withoutLn.substr(0, len - 1); - - std::string out; - - applyCFormatting(withoutLn, out, args); - - if (level == log_level::NONE) - { - std::cout << out << std::endl; - return; - } - - std::string finalFormattedOutput = applyFormatString(out, level, file, line); - - if (loggingFormat.logToConsole) - std::cout << finalFormattedOutput; - - - if (loggingFormat.logToFile) - { - string_parser parser(loggingFormat.logFileName); - std::string fileName; - parseString(parser, fileName, withoutLn, level, file, line); - - auto path = loggingFormat.logFilePath; - if (!path.empty() && path[path.length() - 1] != '/') - path += '/'; - - // if the file has changed (new day in default case) we should reset the rollover count - if (loggingFormat.lastFile != fileName) - { - loggingFormat.currentRollover = 0; - loggingFormat.lastFile = fileName; - } - - path += fileName; - path += '-'; - path += std::to_string(loggingFormat.currentRollover); - path += ".log"; - - if (std::filesystem::exists(path)) - { - auto fileSize = std::filesystem::file_size(path); - - // will start on next file - if (fileSize > loggingFormat.logMaxFileSize) - loggingFormat.currentRollover++; - } - - writer.writeLine(path, stripANSI(finalFormattedOutput)); - } - //std::cout.flush(); - } - - void log_stream_internal(const std::string& str, const logger& logger) - { - auto& s = loggingStreamLines[std::this_thread::get_id()][logger.level]; - // s += str; - for (char c : str) - { - s += c; - if (c == '\n') - { - log(s, logger.level, logger.file, logger.line); - s = ""; - } - } - } - - void setThreadName(const std::string& name) - { - loggingThreadNames[std::this_thread::get_id()] = name; - } - - void setLogFormat(const log_format& format) - { - loggingFormat = format; - } - - void setLogColor(log_level level, const std::string& newFormat) - { - loggingFormat.levelColors[(int)level] = newFormat; - } - - void setLogName(log_level level, const std::string& newFormat) - { - loggingFormat.levelNames[(int)level] = newFormat; - } - - void setLogOutputFormat(const std::string& newFormat) - { - loggingFormat.logOutputFormat = newFormat; - } - - void setLogToFile(bool shouldLogToFile) - { - loggingFormat.logToFile = shouldLogToFile; - } - - void setLogToConsole(bool shouldLogToConsole) - { - loggingFormat.logToConsole = shouldLogToConsole; - } - - void setLogPath(const std::string& path) - { - loggingFormat.logFilePath = path; - } - - void setLogFileName(const std::string& fileName) - { - loggingFormat.logFileName = fileName; - } - - void setMaxFileSize(const size_t fileSize) - { - loggingFormat.logMaxFileSize = fileSize; - } - - void flush() - { - std::cerr.flush(); - std::cout.flush(); - } -} diff --git a/src/blt/std/mmap.cpp b/src/blt/std/mmap.cpp index ef7a978..c65d9b7 100644 --- a/src/blt/std/mmap.cpp +++ b/src/blt/std/mmap.cpp @@ -15,6 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +#include #include #ifdef __unix__ @@ -112,7 +113,7 @@ namespace blt #ifdef __unix__ if (munmap(ptr, bytes)) { - BLT_ERROR_STREAM << "Failed to deallocate\n"; + BLT_ERROR("Failed to deallocate"); throw bad_alloc_t(handle_mmap_error()); } #else diff --git a/src/blt/std/simd.cpp b/src/blt/std/simd.cpp index 1c89acb..85b4e8c 100644 --- a/src/blt/std/simd.cpp +++ b/src/blt/std/simd.cpp @@ -16,7 +16,6 @@ * along with this program. If not, see . */ #include -#include namespace blt { diff --git a/src/blt/std/system.cpp b/src/blt/std/system.cpp index 546bac6..3dbed6e 100644 --- a/src/blt/std/system.cpp +++ b/src/blt/std/system.cpp @@ -4,7 +4,7 @@ * See LICENSE file for license detail */ #include -#include +#include #if !defined(_MSC_VER) && !defined(WIN32) #include /* for struct timeval */ From deacad5358e3e73f97c58fa1ff7d76708c49aa12 Mon Sep 17 00:00:00 2001 From: Brett Date: Sun, 9 Mar 2025 23:14:27 -0400 Subject: [PATCH 070/101] stupid work log stuff --- CMakeLists.txt | 2 +- include/blt/format/boxing.h | 21 +-------------- include/blt/math/log_util.h | 2 +- tests/iterator_tests.cpp | 52 ++++++++++++++++++------------------- 4 files changed, 29 insertions(+), 48 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2b7a3e2..700ed57 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.2.5) +set(BLT_VERSION 5.2.6) set(BLT_TARGET BLT) diff --git a/include/blt/format/boxing.h b/include/blt/format/boxing.h index aaaa1bd..0df292e 100644 --- a/include/blt/format/boxing.h +++ b/include/blt/format/boxing.h @@ -20,7 +20,7 @@ #define BLT_BOXING_H #include -#include +#include #include namespace blt @@ -84,25 +84,6 @@ namespace blt Logger& logger; }; - template<> - class log_box_t : detail::log_box_base_t - { - public: - log_box_t(blt::logging::logger logger, std::string_view title, blt::size_t padding = 0): detail::log_box_base_t(title, padding), logger(logger) - { - make_full_title(logger); - logger << '\n'; - } - - ~log_box_t() - { - make_full_width_line(logger); - logger << '\n'; - } - private: - blt::logging::logger logger; - }; - template log_box_t(Logger&& logger, std::string_view, blt::size_t) -> log_box_t; diff --git a/include/blt/math/log_util.h b/include/blt/math/log_util.h index fb2510c..6b20836 100644 --- a/include/blt/math/log_util.h +++ b/include/blt/math/log_util.h @@ -9,7 +9,7 @@ #include #include -#include +#include #include #include "blt/std/string.h" diff --git a/tests/iterator_tests.cpp b/tests/iterator_tests.cpp index d267ccc..0c4a2db 100644 --- a/tests/iterator_tests.cpp +++ b/tests/iterator_tests.cpp @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#include +#include #include #include #include @@ -64,18 +64,18 @@ void test_enumerate() { blt::log_box_t box(std::cout, "Enumerate Tests", 25); for (const auto& [index, item] : blt::enumerate(array_1)) - BLT_TRACE_STREAM << index << " : " << item << "\n"; + BLT_TRACE("{}, : {}", index, item); BLT_TRACE(""); for (const auto& [index, item] : blt::enumerate(array_1).rev()) - BLT_TRACE_STREAM << index << " : " << item << "\n"; + BLT_TRACE("{}, : {}", index, item); BLT_TRACE(""); for (const auto& [index, item] : blt::enumerate(array_1).take(3)) { - BLT_TRACE_STREAM << index << " : " << item << "\n"; + BLT_TRACE("{}, : {}", index, item); BLT_ASSERT(index < 3); } @@ -83,7 +83,7 @@ void test_enumerate() for (const auto& [index, item] : blt::enumerate(array_1).take(3).rev()) { - BLT_TRACE_STREAM << index << " : " << item << "\n"; + BLT_TRACE("{}, : {}", index, item); BLT_ASSERT(index < 3); } @@ -91,7 +91,7 @@ void test_enumerate() for (const auto& [index, item] : blt::enumerate(array_1).skip(3)) { - BLT_TRACE_STREAM << index << " : " << item << "\n"; + BLT_TRACE("{}, : {}", index, item); BLT_ASSERT(index >= 3); } @@ -99,7 +99,7 @@ void test_enumerate() for (const auto& [index, item] : blt::enumerate(array_1).skip(3).rev()) { - BLT_TRACE_STREAM << index << " : " << item << "\n"; + BLT_TRACE("{}, : {}", index, item); BLT_ASSERT(index >= 3); } @@ -107,7 +107,7 @@ void test_enumerate() for (const auto& [index, item] : blt::enumerate(array_1).skip(3).take(5)) { - BLT_TRACE_STREAM << index << " : " << item << "\n"; + BLT_TRACE("{}, : {}", index, item); BLT_ASSERT(index >= 3 && index < (array_1.size() - 5) + 3); } @@ -115,7 +115,7 @@ void test_enumerate() for (const auto& [index, item] : blt::enumerate(array_1).skip(3).rev().take(5)) { - BLT_TRACE_STREAM << index << " : " << item << "\n"; + BLT_TRACE("{}, : {}", index, item); BLT_ASSERT(index >= 5); } } @@ -125,7 +125,7 @@ void test_pairs() blt::log_box_t box(std::cout, "Pairs Tests", 25); for (auto [a1, a2] : blt::in_pairs(array_1, array_2)) { - BLT_TRACE_STREAM << a1 << " : " << a2 << "\n"; + BLT_TRACE("{}, : {}", a1, a2); } } @@ -134,32 +134,32 @@ void test_zip() blt::log_box_t box(std::cout, "Zip Tests", 25); for (auto [a1, a2, a3] : blt::zip(array_1, array_2, list_1)) { - BLT_TRACE_STREAM << a1 << " : " << a2 << " : " << a3 << "\n"; + BLT_TRACE("{} : {} : {}", a1, a2, a3); } BLT_TRACE("================================"); for (auto [a1, a2, a3] : blt::zip(array_1, array_2, list_1).take(3)) { - BLT_TRACE_STREAM << a1 << " : " << a2 << " : " << a3 << "\n"; + BLT_TRACE("{} : {} : {}", a1, a2, a3); } BLT_TRACE("================================"); for (auto [a1, a2, a3] : blt::zip(array_1, array_2, array_3).take(3).rev()) { - BLT_TRACE_STREAM << a1 << " : " << a2 << " : " << a3 << "\n"; + BLT_TRACE("{} : {} : {}", a1, a2, a3); } BLT_TRACE("================================"); for (auto [a1, a2, a3] : blt::zip(array_1, array_2, array_3).take_or(13)) { - BLT_TRACE_STREAM << a1 << " : " << a2 << " : " << a3 << "\n"; + BLT_TRACE("{} : {} : {}", a1, a2, a3); } BLT_TRACE("================================"); for (auto [a1, a2, a3] : blt::zip(array_1, array_2, array_3).rev().take(3)) { - BLT_TRACE_STREAM << a1 << " : " << a2 << " : " << a3 << "\n"; + BLT_TRACE("{} : {} : {}", a1, a2, a3); } BLT_TRACE("================================"); for (auto [a1, a2, a3] : blt::zip(array_1, array_2, array_3).skip(2).rev()) { - BLT_TRACE_STREAM << a1 << " : " << a2 << " : " << a3 << "\n"; + BLT_TRACE("{} : {} : {}", a1, a2, a3); } } @@ -168,33 +168,33 @@ void test_iterate() blt::log_box_t box(std::cout, "Iterate Tests", 25); for (auto v : blt::iterate(array_1)) { - BLT_TRACE_STREAM << "Element: " << v << "\n"; + BLT_TRACE("Element: {}", v); } BLT_TRACE("================================"); for (auto v : blt::iterate(array_1).skip(5)) { - BLT_TRACE_STREAM << "Element: " << v << "\n"; + BLT_TRACE("Element: {}", v); } BLT_TRACE("================================"); for (auto v : blt::iterate(array_1).take(5)) { - BLT_TRACE_STREAM << "Element: " << v << "\n"; + BLT_TRACE("Element: {}", v); } BLT_TRACE("================================"); for (auto v : blt::iterate(array_1).rev()) { - BLT_TRACE_STREAM << "Element: " << v << "\n"; + BLT_TRACE("Element: {}", v); } BLT_TRACE("================================"); for (auto [a, b] : blt::iterate(array_1).zip(list_1)) { - BLT_TRACE_STREAM << "Zip: " << a << " " << b << "\n"; + BLT_TRACE("Zip: {} {}", a, b); } BLT_TRACE("================================"); for (auto [i, data] : blt::iterate(array_1).map([](const blt::vec2& in) { return in.normalize(); }).zip(list_1).skip(3).take(4).enumerate()) { auto [a, b] = data; - BLT_TRACE_STREAM << "Map + Zip + Skip + Take + Enumerate (Index: " << i << ")> " << a << " " << b << "\n"; + BLT_TRACE("Map + Zip + Skip + Take + Enumerate (Index: {})> {} {}", i, a, b); } BLT_TRACE("================================"); for (auto [i, data] : blt::iterate(array_1).map( @@ -203,7 +203,7 @@ void test_iterate() }).zip(list_1).skip(3).take(4).enumerate()) { auto [a, b] = data; - BLT_TRACE_STREAM << "Map + Zip + Skip + Take + Enumerate (Index: " << i << ")> " << a << " " << b << "\n"; + BLT_TRACE("Map + Zip + Skip + Take + Enumerate (Index: {})> {} {}", i, a, b); } BLT_TRACE("================================"); for (auto a : blt::iterate(array_1).map([](const blt::vec2& in) { return in.normalize(); }) @@ -212,7 +212,7 @@ void test_iterate() if (!a) continue; auto v = *a; - BLT_TRACE_STREAM << " So this one works? " << v << "\n"; + BLT_TRACE(" So this one works? {}", v); } BLT_TRACE("================================"); for (auto a : blt::iterate(array_1).map([](const blt::vec2& in) { return in.normalize(); }) @@ -221,7 +221,7 @@ void test_iterate() if (!a) continue; auto [index, v] = *a; - BLT_TRACE_STREAM << " So this one works? (" << index << ")" << v << "\n"; + BLT_TRACE(" So this one works? ({}) {}", index, v); } BLT_TRACE("================================"); // for (auto a : blt::iterate(array_1).filter([](const auto& f) { return f.x() > 3 && f.y() < 6; }).take(2)) @@ -233,7 +233,7 @@ void test_iterate() // } for (auto a : blt::iterate(array_1).map([](const auto& f) { return f.x() > 3 && f.y() < 6; })) { - BLT_TRACE_STREAM << " How about this one?? " << a << "\n"; + BLT_TRACE(" How about this one?? ({}) {}", a); } // for (auto [value, a] : blt::iterate(array_1).map( From 78a4ad9f39e98f9047a41cce9a380825cbfd18b6 Mon Sep 17 00:00:00 2001 From: Brett Date: Mon, 10 Mar 2025 00:29:20 -0400 Subject: [PATCH 071/101] i think all the old logging is replaced --- CMakeLists.txt | 2 +- include/blt/fs/nbt.h | 6 +- include/blt/std/allocator.h | 12 +- src/blt/fs/loader.cpp | 4 +- src/blt/fs/nbt.cpp | 2 +- src/blt/parse/argparse.cpp | 2 +- src/blt/parse/obj_loader.cpp | 445 ++++++++++++++++++----------------- src/blt/parse/templating.cpp | 2 +- src/blt/std/assert.cpp | 8 +- src/blt/std/system.cpp | 4 +- tests/logger_tests.cpp | 1 + 11 files changed, 245 insertions(+), 243 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 700ed57..b86fef5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.2.6) +set(BLT_VERSION 5.2.7) set(BLT_TARGET BLT) diff --git a/include/blt/fs/nbt.h b/include/blt/fs/nbt.h index 9d59004..d94d988 100644 --- a/include/blt/fs/nbt.h +++ b/include/blt/fs/nbt.h @@ -305,7 +305,7 @@ namespace blt::nbt { auto& tag = t[i]; T t; if (tag->getType() != t.getType()) { - BLT_WARN("Expected tag of type %d but got tag of type %d", (char)t.getType(), (char)tag->getType()); + BLT_WARN("Expected tag of type {:d} but got tag of type {:d}", (char)t.getType(), (char)tag->getType()); throw std::runtime_error("Requested Tag does not match stored type!"); } return dynamic_cast(tag); @@ -342,7 +342,7 @@ namespace blt::nbt { auto& tag = t[name]; T t; if (tag->getType() != t.getType()) { - BLT_WARN("Expected tag of type %d but got tag of type %d", (char)t.getType(), (char)tag->getType()); + BLT_WARN("Expected tag of type {:d} but got tag of type {:d}", (char)t.getType(), (char)tag->getType()); throw std::runtime_error("Requested Tag does not match stored type!"); } return dynamic_cast(tag); @@ -409,7 +409,7 @@ namespace blt::nbt { auto& tag = root->get()[name]; T t; if (tag->getType() != t.getType()) { - BLT_WARN("Expected tag of type %d but got tag of type %d", (char)t.getType(), (char)tag->getType()); + BLT_WARN("Expected tag of type {:d} but got tag of type {:d}", (char)t.getType(), (char)tag->getType()); throw std::runtime_error("Requested Tag does not match stored type!"); } return dynamic_cast(tag); diff --git a/include/blt/std/allocator.h b/include/blt/std/allocator.h index 1f7b8f8..e812428 100644 --- a/include/blt/std/allocator.h +++ b/include/blt/std/allocator.h @@ -149,7 +149,7 @@ namespace blt */ inline void allocate_block() { - //BLT_INFO("Allocating a new block of size %d", BLOCK_SIZE); + //BLT_INFO("Allocating a new block of size {:d}", BLOCK_SIZE); auto* blk = new block_storage(); blk->data = static_cast(malloc(sizeof(T) * BLOCK_SIZE)); blocks.push_back(blk); @@ -572,13 +572,13 @@ namespace blt if constexpr (WARN_ON_FAIL) { if (((size_t) buffer & (HUGE_PAGE_SIZE - 1)) != 0) - BLT_ERROR("Pointer is not aligned! %p", buffer); + BLT_ERROR("Pointer is not aligned! {:#x}", buffer); } auto* ptr = static_cast(buffer); auto ptr_size = reinterpret_cast(ptr); buffer = static_cast(std::align(BLOCK_SIZE, BLOCK_SIZE, ptr, bytes)); if constexpr (WARN_ON_FAIL) - BLT_ERROR("Offset by %ld pages, resulting: %p", (reinterpret_cast(buffer) - ptr_size) / 4096, buffer); + BLT_ERROR("Offset by {} pages, resulting: {:#x}", (reinterpret_cast(buffer) - ptr_size) / 4096, buffer); } return buffer; #endif @@ -839,7 +839,7 @@ namespace blt #endif // if (deletes.contains(p)) // { - // BLT_FATAL("pointer %p has already been freed", p); + // BLT_FATAL("pointer {:#x} has already been freed", p); // throw std::bad_alloc(); // }else // deletes.insert(static_cast(p)); @@ -848,7 +848,7 @@ namespace blt blk->metadata.allocated_objects--; if (blk->metadata.allocated_objects == 0) { - //BLT_INFO("Deallocating block from %p in (1) %p current head %p, based: %p", p, blk, head, base); + //BLT_INFO("Deallocating block from {:#x} in (1) {:#x} current head {:#x}, based: {:#x}", p, blk, head, base); if (blk == base) { base = base->metadata.next; @@ -860,7 +860,7 @@ namespace blt else if (blk->metadata.prev != nullptr) // finally if it wasn't the head we need to bridge the gap in the list blk->metadata.prev->metadata.next = blk->metadata.next; - //BLT_INFO("Deallocating block from %p in (2) %p current head %p, based: %p", p, blk, head, base); + //BLT_INFO("Deallocating block from {:#x} in (2) {:#x} current head {:#x}, based: {:#x}", p, blk, head, base); delete_block(blk); } } diff --git a/src/blt/fs/loader.cpp b/src/blt/fs/loader.cpp index c078630..fd39673 100644 --- a/src/blt/fs/loader.cpp +++ b/src/blt/fs/loader.cpp @@ -82,8 +82,8 @@ std::string blt::fs::getFile(std::string_view path) file_contents = file_stream.str(); } catch (std::ifstream::failure& e) { - BLT_WARN("Unable to read file '%s'!\n", std::string(path).c_str()); - BLT_WARN("Exception: %s", e.what()); + BLT_WARN("Unable to read file '{}'!\n", std::string(path).c_str()); + BLT_WARN("Exception: {}", e.what()); BLT_THROW(std::runtime_error("Failed to read file!\n")); } return file_contents; diff --git a/src/blt/fs/nbt.cpp b/src/blt/fs/nbt.cpp index 5ec5c33..ad00e12 100644 --- a/src/blt/fs/nbt.cpp +++ b/src/blt/fs/nbt.cpp @@ -36,7 +36,7 @@ namespace blt::nbt { char t; reader.read(&t, 1); if (t != (char)nbt_tag::COMPOUND) { - BLT_WARN("Found %d", t); + BLT_WARN("Found {:d}", t); throw std::runtime_error("Incorrectly formatted NBT data! Root tag must be a compound tag!"); } root = new tag_compound; diff --git a/src/blt/parse/argparse.cpp b/src/blt/parse/argparse.cpp index 4f8477c..19c91c6 100644 --- a/src/blt/parse/argparse.cpp +++ b/src/blt/parse/argparse.cpp @@ -196,7 +196,7 @@ namespace blt printUsage(); std::cout << getProgramName() << ": error: flag '" << flag << "' expected " << properties.a_nargs.args << " argument(s) but found '" << tokenizer.get() << "' instead!\n"; - //BLT_WARN("Expected %d arguments, found flag instead!", properties.a_nargs.args); + //BLT_WARN("Expected {:d} arguments, found flag instead!", properties.a_nargs.args); return false; } // get the value and advance diff --git a/src/blt/parse/obj_loader.cpp b/src/blt/parse/obj_loader.cpp index 5433876..1449d60 100644 --- a/src/blt/parse/obj_loader.cpp +++ b/src/blt/parse/obj_loader.cpp @@ -30,226 +30,227 @@ namespace blt::parse { - class char_tokenizer - { - private: - std::string_view string; - std::size_t current_pos = 0; - public: - explicit char_tokenizer(std::string_view view): string(view) - {} - - inline char advance() - { - return string[current_pos++]; - } - - inline bool has_next(size_t offset = 0) - { - return current_pos + offset < string.size(); - } - - inline std::string_view read_fully() - { - return blt::string::trim(string.substr(current_pos)); - } - }; - - template - T get(std::string_view str) - { - T x; - // TODO: GCC version. C++17 supports from_chars but GCC8.5 doesn't have floating point. -#if __cplusplus >= BLT_CPP20 + class char_tokenizer + { + private: + std::string_view string; + std::size_t current_pos = 0; + + public: + explicit char_tokenizer(std::string_view view): string(view) + {} + + inline char advance() + { + return string[current_pos++]; + } + + inline bool has_next(size_t offset = 0) + { + return current_pos + offset < string.size(); + } + + inline std::string_view read_fully() + { + return blt::string::trim(string.substr(current_pos)); + } + }; + + template + T get(std::string_view str) + { + T x; + // TODO: GCC version. C++17 supports from_chars but GCC8.5 doesn't have floating point. + #if __cplusplus >= BLT_CPP20 const auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), x); -#else - auto ec = std::errc(); - if constexpr (std::is_floating_point_v) - { - x = static_cast(std::stod(std::string(str))); - } else if constexpr (std::is_integral_v) - { - x = static_cast(std::stoll(std::string(str))); - } else - static_assert( - "You are using a c++ version which does not support the required std::from_chars, manual conversion has failed to find a type!"); -#endif - // probably not needed. - if (ec != std::errc()) - { -// int i; -// const auto [ptr2, ec2] = std::from_chars(str.data(), str.data() + str.size(), i); -// if (ec2 == std::errc()) -// { -// x = static_cast(i); -// } else -// { - BLT_WARN("Unable to parse string '%s' into number!", std::string(str).c_str()); - x = 0; -// } - } - return x; - } - - void obj_loader::parse_vertex_line(char_tokenizer& tokenizer) - { - char type = tokenizer.advance(); - - if (type == 'p') - { - BLT_WARN("Unexpected type '%c' (not supported)", type); - return; - } - - auto elements = string::split(tokenizer.read_fully(), " "); - BLT_ASSERT(elements.size() >= 2 && "Current line doesn't have enough arguments to process!"); - float x = get(elements[0]), y = get(elements[1]); - BLT_DEBUG("Loaded value of ({}, {})", x, y); - if (elements.size() < 3) - { - if (type == 't') - uvs.push_back(uv_t{x, y}); - else - BLT_ERROR("Unable to parse line '{}' type '{:c}' not recognized for arg count", tokenizer.read_fully(), type); - } else - { - float z = get(elements[2]); - BLT_DEBUG(" with z: {}", z); - if (!handle_vertex_and_normals(x, y, z, type)) - BLT_ERROR("Unable to parse line '{}' type '{:c}' not recognized", tokenizer.read_fully(), type); - } - } - - bool obj_loader::handle_vertex_and_normals(float x, float y, float z, char type) - { - if (std::isspace(type)) - { - vertices.push_back(vertex_t{x, y, z}); - } else if (type == 'n') - { - normals.push_back(normal_t{x, y, z}); - } else - return false; - return true; - } - - obj_model_t quick_load(std::string_view file) - { - return obj_loader().parseFile(file); - } - - obj_model_t obj_loader::parseFile(std::string_view file) - { - auto lines = blt::fs::getLinesFromFile(std::string(file)); - for (const auto& [index, line] : blt::enumerate(lines)) - { - current_line = index; - char_tokenizer token(line); - if (!token.has_next() || token.read_fully().empty()) - continue; - switch (token.advance()) - { - case '#': - continue; - case 'f': - parse_face(token); - break; - case 'v': - parse_vertex_line(token); - break; - case 'o': - { - current_object.object_names.emplace_back(token.read_fully()); - BLT_TRACE("Setting object '%s'", std::string(current_object.object_name).c_str()); - break; - } - case 'm': - { - while (token.has_next() && token.advance() != ' ') - {} - BLT_WARN("Material '%s' needs to be loaded!", std::string(token.read_fully()).c_str()); - break; - } - case 'u': - { - if (!current_object.indices.empty()) - data.push_back(current_object); - current_object = {}; - while (token.has_next() && token.advance() != ' ') - {} - current_object.material = token.read_fully(); - //BLT_WARN("Using material '%s'", std::string(token.read_fully()).c_str()); - break; - } - case 's': - //BLT_WARN("Using shading: %s", std::string(token.read_fully()).c_str()); - break; - } - } - data.push_back(current_object); - return {std::move(vertex_data), std::move(data), std::move(materials)}; - } - - void obj_loader::parse_face(char_tokenizer& tokenizer) - { - auto faces = blt::string::split(std::string(tokenizer.read_fully()), ' '); - if (faces.size() == 3) - { - triangle_t triangle{}; - handle_face_vertex(faces, triangle.v); - current_object.indices.push_back(triangle); - } else if (faces.size() == 4) - { - quad_t quad{}; - handle_face_vertex(faces, quad.v); - triangle_t t1{}; - triangle_t t2{}; - - for (int i = 0; i < 3; i++) - t1.v[i] = quad.v[i]; - t2.v[0] = quad.v[0]; - t2.v[1] = quad.v[2]; - t2.v[2] = quad.v[3]; - - current_object.indices.push_back(t1); - current_object.indices.push_back(t2); - } else - BLT_WARN("Unsupported face vertex count of %d on line %d!", faces.size(), current_line); - } - - void obj_loader::handle_face_vertex(const std::vector& face_list, int32_t* arr) - { - for (const auto& [e_index, value] : blt::enumerate(face_list)) - { - auto indices = blt::string::split(value, '/'); - BLT_ASSERT(indices.size() == 3 && "Must have vertex, uv, and normal indices!!"); - - auto vi = get(indices[0]) - 1; - auto ui = get(indices[1]) - 1; - auto ni = get(indices[2]) - 1; - - BLT_DEBUG("Found vertex: %d, UV: %d, and normal: %d", vi, ui, ni); - - face_t face{vi, ui, ni}; - - auto loc = vertex_map.find(face); - if (loc == vertex_map.end()) - { - BLT_DEBUG("DID NOT FIND FACE!"); - auto index = static_cast(vertex_data.size()); - vertex_data.push_back({vertices[vi], uvs[ui], normals[ni]}); - BLT_DEBUG("Vertex: (%f, %f, %f), UV: (%f, %f), Normal: (%f, %f, %f)", vertices[vi].x(), vertices[vi].y(), vertices[vi].z(), - uvs[ui].x(), uvs[ui].y(), normals[ni].x(), normals[ni].y(), normals[ni].z()); - vertex_map.insert({face, index}); - arr[e_index] = index; - } else - { - BLT_TRACE("Using cached data; %d; map size: %d", loc->second, vertex_data.size()); - //const auto& d = vertex_data[loc->second]; - BLT_TRACE("Vertex: (%f, %f, %f), UV: (%f, %f), Normal: (%f, %f, %f)", d.vertex.x(), d.vertex.y(), d.vertex.z(), - d.uv.x(), d.uv.y(), d.normal.x(), d.normal.y(), d.normal.z()); - arr[e_index] = loc->second; - } - } - } -} \ No newline at end of file + #else + auto ec = std::errc(); + if constexpr (std::is_floating_point_v) + { + x = static_cast(std::stod(std::string(str))); + } else if constexpr (std::is_integral_v) + { + x = static_cast(std::stoll(std::string(str))); + } else + static_assert( + "You are using a c++ version which does not support the required std::from_chars, manual conversion has failed to find a type!"); + #endif + // probably not needed. + if (ec != std::errc()) + { + // int i; + // const auto [ptr2, ec2] = std::from_chars(str.data(), str.data() + str.size(), i); + // if (ec2 == std::errc()) + // { + // x = static_cast(i); + // } else + // { + BLT_WARN("Unable to parse string '{}' into number!", std::string(str).c_str()); + x = 0; + // } + } + return x; + } + + void obj_loader::parse_vertex_line(char_tokenizer& tokenizer) + { + char type = tokenizer.advance(); + + if (type == 'p') + { + BLT_WARN("Unexpected type '{:c}' (not supported)", type); + return; + } + + auto elements = string::split(tokenizer.read_fully(), " "); + BLT_ASSERT(elements.size() >= 2 && "Current line doesn't have enough arguments to process!"); + float x = get(elements[0]), y = get(elements[1]); + BLT_DEBUG("Loaded value of ({}, {})", x, y); + if (elements.size() < 3) + { + if (type == 't') + uvs.push_back(uv_t{x, y}); + else + BLT_ERROR("Unable to parse line '{}' type '{:c}' not recognized for arg count", tokenizer.read_fully(), type); + } else + { + float z = get(elements[2]); + BLT_DEBUG(" with z: {}", z); + if (!handle_vertex_and_normals(x, y, z, type)) + BLT_ERROR("Unable to parse line '{}' type '{:c}' not recognized", tokenizer.read_fully(), type); + } + } + + bool obj_loader::handle_vertex_and_normals(float x, float y, float z, char type) + { + if (std::isspace(type)) + { + vertices.push_back(vertex_t{x, y, z}); + } else if (type == 'n') + { + normals.push_back(normal_t{x, y, z}); + } else + return false; + return true; + } + + obj_model_t quick_load(std::string_view file) + { + return obj_loader().parseFile(file); + } + + obj_model_t obj_loader::parseFile(std::string_view file) + { + auto lines = blt::fs::getLinesFromFile(std::string(file)); + for (const auto& [index, line] : blt::enumerate(lines)) + { + current_line = index; + char_tokenizer token(line); + if (!token.has_next() || token.read_fully().empty()) + continue; + switch (token.advance()) + { + case '#': + continue; + case 'f': + parse_face(token); + break; + case 'v': + parse_vertex_line(token); + break; + case 'o': + { + current_object.object_names.emplace_back(token.read_fully()); + BLT_TRACE("Setting object '{}'", std::string(current_object.object_name).c_str()); + break; + } + case 'm': + { + while (token.has_next() && token.advance() != ' ') + {} + BLT_WARN("Material '{}' needs to be loaded!", std::string(token.read_fully()).c_str()); + break; + } + case 'u': + { + if (!current_object.indices.empty()) + data.push_back(current_object); + current_object = {}; + while (token.has_next() && token.advance() != ' ') + {} + current_object.material = token.read_fully(); + //BLT_WARN("Using material '{}'", std::string(token.read_fully()).c_str()); + break; + } + case 's': + //BLT_WARN("Using shading: {}", std::string(token.read_fully()).c_str()); + break; + } + } + data.push_back(current_object); + return {std::move(vertex_data), std::move(data), std::move(materials)}; + } + + void obj_loader::parse_face(char_tokenizer& tokenizer) + { + auto faces = blt::string::split(std::string(tokenizer.read_fully()), ' '); + if (faces.size() == 3) + { + triangle_t triangle{}; + handle_face_vertex(faces, triangle.v); + current_object.indices.push_back(triangle); + } else if (faces.size() == 4) + { + quad_t quad{}; + handle_face_vertex(faces, quad.v); + triangle_t t1{}; + triangle_t t2{}; + + for (int i = 0; i < 3; i++) + t1.v[i] = quad.v[i]; + t2.v[0] = quad.v[0]; + t2.v[1] = quad.v[2]; + t2.v[2] = quad.v[3]; + + current_object.indices.push_back(t1); + current_object.indices.push_back(t2); + } else + BLT_WARN("Unsupported face vertex count of {:d} on line {:d}!", faces.size(), current_line); + } + + void obj_loader::handle_face_vertex(const std::vector& face_list, int32_t* arr) + { + for (const auto& [e_index, value] : blt::enumerate(face_list)) + { + auto indices = blt::string::split(value, '/'); + BLT_ASSERT(indices.size() == 3 && "Must have vertex, uv, and normal indices!!"); + + auto vi = get(indices[0]) - 1; + auto ui = get(indices[1]) - 1; + auto ni = get(indices[2]) - 1; + + BLT_DEBUG("Found vertex: {:d}, UV: {:d}, and normal: {:d}", vi, ui, ni); + + face_t face{vi, ui, ni}; + + auto loc = vertex_map.find(face); + if (loc == vertex_map.end()) + { + BLT_DEBUG("DID NOT FIND FACE!"); + auto index = static_cast(vertex_data.size()); + vertex_data.push_back({vertices[vi], uvs[ui], normals[ni]}); + BLT_DEBUG("Vertex: ({.4f}, {.4f}, {.4f}), UV: ({.4f}, {.4f}), Normal: ({.4f}, {.4f}, {:.4f})", vertices[vi].x(), vertices[vi].y(), + vertices[vi].z(), uvs[ui].x(), uvs[ui].y(), normals[ni].x(), normals[ni].y(), normals[ni].z()); + vertex_map.insert({face, index}); + arr[e_index] = index; + } else + { + BLT_TRACE("Using cached data; {:d}; map size: {:d}", loc->second, vertex_data.size()); + //const auto& d = vertex_data[loc->second]; + BLT_TRACE("Vertex: ({.4f}, {.4f}, {.4f}), UV: ({.4f}, {.4f}), Normal: ({.4f}, {.4f}, {:.4f})", d.vertex.x(), d.vertex.y(), + d.vertex.z(), d.uv.x(), d.uv.y(), d.normal.x(), d.normal.y(), d.normal.z()); + arr[e_index] = loc->second; + } + } + } +} diff --git a/src/blt/parse/templating.cpp b/src/blt/parse/templating.cpp index 6d0f962..f2221c1 100644 --- a/src/blt/parse/templating.cpp +++ b/src/blt/parse/templating.cpp @@ -275,7 +275,7 @@ namespace blt values.push_back(b1 ^ b2); break; default: - BLT_WARN("Unexpected token '%s'", std::string(next.token).c_str()); + BLT_WARN("Unexpected token '{}'", std::string(next.token).c_str()); return blt::unexpected(template_parser_failure_t::BOOL_TYPE_NOT_FOUND); } } diff --git a/src/blt/std/assert.cpp b/src/blt/std/assert.cpp index ebc99e5..68db4df 100644 --- a/src/blt/std/assert.cpp +++ b/src/blt/std/assert.cpp @@ -86,7 +86,7 @@ namespace blt #ifdef IS_GNU_BACKTRACE BLT_STACK_TRACE(50); - BLT_ERROR("An exception '%s' has occurred in file '%s:%d'", what, path, line); + BLT_ERROR("An exception '{}' has occurred in file '{}:{:d}'", what, path, line); BLT_ERROR("Stack Trace:"); printStacktrace(messages, size, path, line); @@ -103,7 +103,7 @@ namespace blt #ifdef IS_GNU_BACKTRACE BLT_STACK_TRACE(50); - BLT_ERROR("The assertion '%s' has failed in file '%s:%d'", expression, path, line); + BLT_ERROR("The assertion '{}' has failed in file '{}:{:d}'", expression, path, line); if (msg != nullptr) BLT_ERROR(msg); BLT_ERROR("Stack Trace:"); @@ -175,8 +175,8 @@ namespace blt BLT_STACK_TRACE(50); #endif BLT_FATAL("----{BLT ABORT}----"); - BLT_FATAL("\tWhat: %s", what); - BLT_FATAL("\tCalled from %s:%d", path, line); + BLT_FATAL("\tWhat: {}", what); + BLT_FATAL("\tCalled from {}:{:d}", path, line); #ifdef IS_GNU_BACKTRACE printStacktrace(messages, size, path, line); diff --git a/src/blt/std/system.cpp b/src/blt/std/system.cpp index 3dbed6e..9147178 100644 --- a/src/blt/std/system.cpp +++ b/src/blt/std/system.cpp @@ -94,7 +94,7 @@ namespace blt if (GetProcessTimes(GetCurrentProcess(), &starttime, &exittime, &kerneltime, &usertime) == 0) { - BLT_WARN("Unable to get process resource usage, error: %d", GetLastError()); + BLT_WARN("Unable to get process resource usage, error: {:d}", GetLastError()); return {}; } @@ -111,7 +111,7 @@ namespace blt #else if (getrusage(who, (struct rusage*) &usage) != 0) { - BLT_ERROR("Failed to get rusage %d", errno); + BLT_ERROR("Failed to get rusage {:d}", errno); return {}; } #endif diff --git a/tests/logger_tests.cpp b/tests/logger_tests.cpp index a9f8bb6..6741770 100644 --- a/tests/logger_tests.cpp +++ b/tests/logger_tests.cpp @@ -181,6 +181,7 @@ int main() BLT_WARN("This is a warning!"); BLT_ERROR("This is an error!"); BLT_FATAL("This is a fatal error!"); + BLT_TRACE("This is a pointer {:f}", &charCount); /*std::cout << "\033[2J"; constexpr int totalRows = 24; From fa0e70bda4e64d333f9df8f68858fc2dcef74598 Mon Sep 17 00:00:00 2001 From: Brett Date: Mon, 10 Mar 2025 00:41:35 -0400 Subject: [PATCH 072/101] minor change --- CMakeLists.txt | 2 +- tests/iterator_tests.cpp | 32 ++++++++++++++++---------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b86fef5..672292b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.2.7) +set(BLT_VERSION 5.2.8) set(BLT_TARGET BLT) diff --git a/tests/iterator_tests.cpp b/tests/iterator_tests.cpp index 0c4a2db..347141f 100644 --- a/tests/iterator_tests.cpp +++ b/tests/iterator_tests.cpp @@ -134,32 +134,32 @@ void test_zip() blt::log_box_t box(std::cout, "Zip Tests", 25); for (auto [a1, a2, a3] : blt::zip(array_1, array_2, list_1)) { - BLT_TRACE("{} : {} : {}", a1, a2, a3); + BLT_TRACE("{:.4} : {:.4} : {:.4}", a1, a2, a3); } BLT_TRACE("================================"); for (auto [a1, a2, a3] : blt::zip(array_1, array_2, list_1).take(3)) { - BLT_TRACE("{} : {} : {}", a1, a2, a3); + BLT_TRACE("{:.4} : {:.4} : {:.4}", a1, a2, a3); } BLT_TRACE("================================"); for (auto [a1, a2, a3] : blt::zip(array_1, array_2, array_3).take(3).rev()) { - BLT_TRACE("{} : {} : {}", a1, a2, a3); + BLT_TRACE("{:.4} : {:.4} : {:.4}", a1, a2, a3); } BLT_TRACE("================================"); for (auto [a1, a2, a3] : blt::zip(array_1, array_2, array_3).take_or(13)) { - BLT_TRACE("{} : {} : {}", a1, a2, a3); + BLT_TRACE("{:.4} : {:.4} : {:.4}", a1, a2, a3); } BLT_TRACE("================================"); for (auto [a1, a2, a3] : blt::zip(array_1, array_2, array_3).rev().take(3)) { - BLT_TRACE("{} : {} : {}", a1, a2, a3); + BLT_TRACE("{:.4} : {:.4} : {:.4}", a1, a2, a3); } BLT_TRACE("================================"); for (auto [a1, a2, a3] : blt::zip(array_1, array_2, array_3).skip(2).rev()) { - BLT_TRACE("{} : {} : {}", a1, a2, a3); + BLT_TRACE("{:.4} : {:.4} : {:.4}", a1, a2, a3); } } @@ -168,33 +168,33 @@ void test_iterate() blt::log_box_t box(std::cout, "Iterate Tests", 25); for (auto v : blt::iterate(array_1)) { - BLT_TRACE("Element: {}", v); + BLT_TRACE("Element: {:.4f}", v); } BLT_TRACE("================================"); for (auto v : blt::iterate(array_1).skip(5)) { - BLT_TRACE("Element: {}", v); + BLT_TRACE("Element: {:.4f}", v); } BLT_TRACE("================================"); for (auto v : blt::iterate(array_1).take(5)) { - BLT_TRACE("Element: {}", v); + BLT_TRACE("Element: {:.4f}", v); } BLT_TRACE("================================"); for (auto v : blt::iterate(array_1).rev()) { - BLT_TRACE("Element: {}", v); + BLT_TRACE("Element: {:.4f}", v); } BLT_TRACE("================================"); for (auto [a, b] : blt::iterate(array_1).zip(list_1)) { - BLT_TRACE("Zip: {} {}", a, b); + BLT_TRACE("Zip: {:.4f} {:.4f}", a, b); } BLT_TRACE("================================"); for (auto [i, data] : blt::iterate(array_1).map([](const blt::vec2& in) { return in.normalize(); }).zip(list_1).skip(3).take(4).enumerate()) { auto [a, b] = data; - BLT_TRACE("Map + Zip + Skip + Take + Enumerate (Index: {})> {} {}", i, a, b); + BLT_TRACE("Map + Zip + Skip + Take + Enumerate (Index: {})> {:.4f} {:.4f}", i, a, b); } BLT_TRACE("================================"); for (auto [i, data] : blt::iterate(array_1).map( @@ -203,7 +203,7 @@ void test_iterate() }).zip(list_1).skip(3).take(4).enumerate()) { auto [a, b] = data; - BLT_TRACE("Map + Zip + Skip + Take + Enumerate (Index: {})> {} {}", i, a, b); + BLT_TRACE("Map + Zip + Skip + Take + Enumerate (Index: {})> {:.4f} {:.4f}", i, a, b); } BLT_TRACE("================================"); for (auto a : blt::iterate(array_1).map([](const blt::vec2& in) { return in.normalize(); }) @@ -212,7 +212,7 @@ void test_iterate() if (!a) continue; auto v = *a; - BLT_TRACE(" So this one works? {}", v); + BLT_TRACE(" So this one works? {:.4f}", v); } BLT_TRACE("================================"); for (auto a : blt::iterate(array_1).map([](const blt::vec2& in) { return in.normalize(); }) @@ -221,7 +221,7 @@ void test_iterate() if (!a) continue; auto [index, v] = *a; - BLT_TRACE(" So this one works? ({}) {}", index, v); + BLT_TRACE(" So this one works? ({}) {:.4f}", index, v); } BLT_TRACE("================================"); // for (auto a : blt::iterate(array_1).filter([](const auto& f) { return f.x() > 3 && f.y() < 6; }).take(2)) @@ -233,7 +233,7 @@ void test_iterate() // } for (auto a : blt::iterate(array_1).map([](const auto& f) { return f.x() > 3 && f.y() < 6; })) { - BLT_TRACE(" How about this one?? ({}) {}", a); + BLT_TRACE(" How about this one?? ({}) {:.4f}", a); } // for (auto [value, a] : blt::iterate(array_1).map( From 2b1086ee15a2969c3422d8ba1d85454b23d65bbd Mon Sep 17 00:00:00 2001 From: Brett Date: Mon, 10 Mar 2025 10:27:08 -0400 Subject: [PATCH 073/101] injectors --- CMakeLists.txt | 2 +- include/blt/logging/fwddecl.h | 2 + include/blt/logging/injector.h | 41 +++ include/blt/logging/logging_config.h | 17 +- src/blt/logging/logging.cpp | 402 +++++++++++++-------------- 5 files changed, 258 insertions(+), 206 deletions(-) create mode 100644 include/blt/logging/injector.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 672292b..1be4e27 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.2.8) +set(BLT_VERSION 5.2.9) set(BLT_TARGET BLT) diff --git a/include/blt/logging/fwddecl.h b/include/blt/logging/fwddecl.h index 5c8b7c0..47ecdd2 100644 --- a/include/blt/logging/fwddecl.h +++ b/include/blt/logging/fwddecl.h @@ -30,6 +30,8 @@ namespace blt::logging struct fmt_token_t; class fmt_tokenizer_t; class fmt_parser_t; + + class injector_t; } #endif //BLT_LOGGING_FWDDECL_H diff --git a/include/blt/logging/injector.h b/include/blt/logging/injector.h new file mode 100644 index 0000000..313f5bd --- /dev/null +++ b/include/blt/logging/injector.h @@ -0,0 +1,41 @@ +#pragma once +/* + * Copyright (C) 2024 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BLT_LOGGING_INJECTOR_H +#define BLT_LOGGING_INJECTOR_H + +namespace blt::logging +{ + struct injector_output_t + { + std::string new_logging_output; + // should we continue processing the injector call chain? + bool should_continue; + // should we log the resulting string at the end of the injector call chain? If false for any injector, it becomes false for all injectors. + bool should_log; + }; + + class injector_t + { + public: + virtual ~injector_t() = default; + virtual injector_output_t inject(const std::string& input) = 0; + }; +} + +#endif //BLT_LOGGING_INJECTOR_H diff --git a/include/blt/logging/logging_config.h b/include/blt/logging/logging_config.h index f5cad5a..199a7e6 100644 --- a/include/blt/logging/logging_config.h +++ b/include/blt/logging/logging_config.h @@ -20,6 +20,7 @@ #define BLT_LOGGING_LOGGING_CONFIG_H #include +#include #include #include #include @@ -133,9 +134,15 @@ namespace blt::logging void compile(); - logging_config_t& add_log_output(fs::writer_t* writer) + logging_config_t& add_log_output(fs::writer_t& writer) { - m_log_outputs.push_back(writer); + m_log_outputs.push_back(&writer); + return *this; + } + + logging_config_t& add_injector(injector_t* injector) + { + m_injectors.push_back(std::unique_ptr(injector)); return *this; } @@ -200,8 +207,7 @@ namespace blt::logging i32 line) const; private: - std::vector m_log_tag_content; - std::vector m_log_tag_tokens; + std::vector> m_injectors; // wrappers for streams exist in blt/fs/stream_wrappers.h std::vector m_log_outputs = get_default_log_outputs(); std::string m_log_format = get_default_log_format(); @@ -219,6 +225,9 @@ namespace blt::logging size_t m_longest_name_length = 0; + std::vector m_log_tag_content; + std::vector m_log_tag_tokens; + static std::string get_default_log_format(); static std::vector get_default_log_outputs(); static std::array get_default_log_level_colors(); diff --git a/src/blt/logging/logging.cpp b/src/blt/logging/logging.cpp index 9cc26fb..7ff8311 100644 --- a/src/blt/logging/logging.cpp +++ b/src/blt/logging/logging.cpp @@ -20,231 +20,231 @@ #include #include #include -#include +#include #include #include #include namespace blt::logging { - struct global_context_t - { - logging_config_t global_config; - }; + struct global_context_t + { + logging_config_t global_config; + }; - static global_context_t global_context; + static global_context_t global_context; - struct logging_thread_context_t - { - std::stringstream stream; - std::stringstream logging_stream; - std::string thread_name; - logger_t logger{stream}; - }; + struct logging_thread_context_t + { + std::stringstream stream; + std::stringstream logging_stream; + std::string thread_name; + logger_t logger{stream}; + }; - logging_thread_context_t& get_thread_context() - { - thread_local logging_thread_context_t context; - return context; - } + logging_thread_context_t& get_thread_context() + { + thread_local logging_thread_context_t context; + return context; + } - std::string logger_t::to_string() const - { - return dynamic_cast(m_stream).str(); - } + std::string logger_t::to_string() const + { + return dynamic_cast(m_stream).str(); + } - size_t logger_t::find_ending_brace(size_t begin) const - { - size_t braces = 0; - for (; begin < m_fmt.size(); ++begin) - { - if (m_fmt[begin] == '{') - ++braces; - else if (m_fmt[begin] == '}') - --braces; - if (braces == 0) - return begin; - } - return std::string::npos; - } + size_t logger_t::find_ending_brace(size_t begin) const + { + size_t braces = 0; + for (; begin < m_fmt.size(); ++begin) + { + if (m_fmt[begin] == '{') + ++braces; + else if (m_fmt[begin] == '}') + --braces; + if (braces == 0) + return begin; + } + return std::string::npos; + } - void logger_t::setup_stream(const fmt_spec_t& spec) const - { - if (spec.prefix_char) - m_stream << std::setfill(*spec.prefix_char); - else - m_stream << std::setfill(' '); - switch (spec.alignment) - { - case fmt_align_t::LEFT: - m_stream << std::left; - break; - case fmt_align_t::CENTER: - // TODO? - break; - case fmt_align_t::RIGHT: - m_stream << std::right; - break; - } - if (spec.width > 0) - m_stream << std::setw(static_cast(spec.width)); - else - m_stream << std::setw(0); - if (spec.precision > 0) - m_stream << std::setprecision(static_cast(spec.precision)); - else - m_stream << std::setprecision(16); - if (spec.alternate_form) - m_stream << std::showbase; - else - m_stream << std::noshowbase; - if (spec.uppercase) - m_stream << std::uppercase; - else - m_stream << std::nouppercase; - if (spec.sign == fmt_sign_t::PLUS) - m_stream << std::showpos; - else - m_stream << std::noshowpos; - } + void logger_t::setup_stream(const fmt_spec_t& spec) const + { + if (spec.prefix_char) + m_stream << std::setfill(*spec.prefix_char); + else + m_stream << std::setfill(' '); + switch (spec.alignment) + { + case fmt_align_t::LEFT: + m_stream << std::left; + break; + case fmt_align_t::CENTER: + // TODO? + break; + case fmt_align_t::RIGHT: + m_stream << std::right; + break; + } + if (spec.width > 0) + m_stream << std::setw(static_cast(spec.width)); + else + m_stream << std::setw(0); + if (spec.precision > 0) + m_stream << std::setprecision(static_cast(spec.precision)); + else + m_stream << std::setprecision(16); + if (spec.alternate_form) + m_stream << std::showbase; + else + m_stream << std::noshowbase; + if (spec.uppercase) + m_stream << std::uppercase; + else + m_stream << std::nouppercase; + if (spec.sign == fmt_sign_t::PLUS) + m_stream << std::showpos; + else + m_stream << std::noshowpos; + } - void logger_t::process_strings() - { - auto spec_it = m_fmt_specs.begin(); - auto str_it = m_string_sections.begin(); - for (; spec_it != m_fmt_specs.end(); ++spec_it, ++str_it) - { - m_stream << *str_it; - auto arg_pos = spec_it->arg_id; - if (arg_pos == -1) - arg_pos = static_cast(m_arg_pos++); + void logger_t::process_strings() + { + auto spec_it = m_fmt_specs.begin(); + auto str_it = m_string_sections.begin(); + for (; spec_it != m_fmt_specs.end(); ++spec_it, ++str_it) + { + m_stream << *str_it; + auto arg_pos = spec_it->arg_id; + if (arg_pos == -1) + arg_pos = static_cast(m_arg_pos++); - setup_stream(*spec_it); - m_arg_print_funcs[arg_pos](m_stream, *spec_it); - } - m_stream << *str_it; - } + setup_stream(*spec_it); + m_arg_print_funcs[arg_pos](m_stream, *spec_it); + } + m_stream << *str_it; + } - void logger_t::handle_type(std::ostream& stream, const fmt_spec_t& spec) - { - switch (spec.type) - { - case fmt_type_t::DECIMAL: - stream << std::noboolalpha; - stream << std::dec; - break; - case fmt_type_t::OCTAL: - stream << std::oct; - break; - case fmt_type_t::HEX: - stream << std::hex; - break; - case fmt_type_t::HEX_FLOAT: - stream << std::hexfloat; - break; - case fmt_type_t::EXPONENT: - stream << std::scientific; - break; - case fmt_type_t::FIXED_POINT: - stream << std::fixed; - break; - case fmt_type_t::GENERAL: - stream << std::defaultfloat; - break; - case fmt_type_t::UNSPECIFIED: - stream << std::boolalpha; - stream << std::dec; - break; - default: - break; - } - } + void logger_t::handle_type(std::ostream& stream, const fmt_spec_t& spec) + { + switch (spec.type) + { + case fmt_type_t::DECIMAL: + stream << std::noboolalpha; + stream << std::dec; + break; + case fmt_type_t::OCTAL: + stream << std::oct; + break; + case fmt_type_t::HEX: + stream << std::hex; + break; + case fmt_type_t::HEX_FLOAT: + stream << std::hexfloat; + break; + case fmt_type_t::EXPONENT: + stream << std::scientific; + break; + case fmt_type_t::FIXED_POINT: + stream << std::fixed; + break; + case fmt_type_t::GENERAL: + stream << std::defaultfloat; + break; + case fmt_type_t::UNSPECIFIED: + stream << std::boolalpha; + stream << std::dec; + break; + default: + break; + } + } - void logger_t::exponential(std::ostream& stream) - { - stream << std::scientific; - } + void logger_t::exponential(std::ostream& stream) + { + stream << std::scientific; + } - void logger_t::fixed(std::ostream& stream) - { - stream << std::fixed; - } + void logger_t::fixed(std::ostream& stream) + { + stream << std::fixed; + } - void logger_t::compile(std::string fmt) - { - m_fmt = std::move(fmt); - m_last_fmt_pos = 0; - m_arg_pos = 0; - auto& ss = dynamic_cast(m_stream); - ss.str(""); - m_stream.clear(); - m_string_sections.clear(); - m_fmt_specs.clear(); - ptrdiff_t last_pos = 0; - while (auto pair = consume_to_next_fmt()) - { - const auto [begin, end] = *pair; - m_string_sections.emplace_back(m_fmt.data() + last_pos, begin - last_pos); - if (end - begin > 1) - m_fmt_specs.push_back(m_parser.parse(std::string_view{m_fmt.data() + static_cast(begin) + 1, end - begin - 1})); - else - m_fmt_specs.emplace_back(); - last_pos = static_cast(end) + 1; - } - m_string_sections.emplace_back(m_fmt.data() + last_pos, m_fmt.size() - last_pos); - m_last_fmt_pos = 0; - } + void logger_t::compile(std::string fmt) + { + m_fmt = std::move(fmt); + m_last_fmt_pos = 0; + m_arg_pos = 0; + auto& ss = dynamic_cast(m_stream); + ss.str(""); + m_stream.clear(); + m_string_sections.clear(); + m_fmt_specs.clear(); + ptrdiff_t last_pos = 0; + while (auto pair = consume_to_next_fmt()) + { + const auto [begin, end] = *pair; + m_string_sections.emplace_back(m_fmt.data() + last_pos, begin - last_pos); + if (end - begin > 1) + m_fmt_specs.push_back(m_parser.parse(std::string_view{m_fmt.data() + static_cast(begin) + 1, end - begin - 1})); + else + m_fmt_specs.emplace_back(); + last_pos = static_cast(end) + 1; + } + m_string_sections.emplace_back(m_fmt.data() + last_pos, m_fmt.size() - last_pos); + m_last_fmt_pos = 0; + } - std::optional> logger_t::consume_to_next_fmt() - { - const auto begin = m_fmt.find('{', m_last_fmt_pos); - if (begin == std::string::npos) - return {}; - const auto end = find_ending_brace(begin); - if (end == std::string::npos) - { - std::stringstream ss; - ss << "Invalid format string, missing closing '}' near " << m_fmt.substr(std::min(static_cast(begin) - 5, 0l)); - throw std::runtime_error(ss.str()); - } - m_last_fmt_pos = end + 1; - return std::pair{begin, end}; - } + std::optional> logger_t::consume_to_next_fmt() + { + const auto begin = m_fmt.find('{', m_last_fmt_pos); + if (begin == std::string::npos) + return {}; + const auto end = find_ending_brace(begin); + if (end == std::string::npos) + { + std::stringstream ss; + ss << "Invalid format string, missing closing '}' near " << m_fmt.substr(std::min(static_cast(begin) - 5, 0l)); + throw std::runtime_error(ss.str()); + } + m_last_fmt_pos = end + 1; + return std::pair{begin, end}; + } - logger_t& get_global_logger() - { - return get_thread_context().logger; - } + logger_t& get_global_logger() + { + return get_thread_context().logger; + } - void print(const std::string& str) - { - std::cout << str; - } + void print(const std::string& str) + { + std::cout << str; + } - void newline() - { - std::cout << std::endl; - } + void newline() + { + std::cout << std::endl; + } - logging_config_t& get_global_config() - { - return global_context.global_config; - } + logging_config_t& get_global_config() + { + return global_context.global_config; + } - void set_thread_name(const std::string& name) - { - get_thread_context().thread_name = name; - } + void set_thread_name(const std::string& name) + { + get_thread_context().thread_name = name; + } - const std::string& get_thread_name() - { - return get_thread_context().thread_name; - } + const std::string& get_thread_name() + { + return get_thread_context().thread_name; + } - std::ostream& get_local_stream() - { - auto& context = get_thread_context(); - context.logging_stream.str(""); - return context.logging_stream; - } + std::ostream& get_local_stream() + { + auto& context = get_thread_context(); + context.logging_stream.str(""); + return context.logging_stream; + } } From e885df622ab473fde28dcefa87289c0760edb262 Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Mon, 10 Mar 2025 11:27:04 -0400 Subject: [PATCH 074/101] injectors --- CMakeLists.txt | 2 +- include/blt/logging/logging.h | 7 ++++--- include/blt/logging/logging_config.h | 5 +++++ libraries/parallel-hashmap | 2 +- src/blt/logging/logging.cpp | 19 +++++++++++++++++-- 5 files changed, 28 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1be4e27..7c65b6c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.2.9) +set(BLT_VERSION 5.2.10) set(BLT_TARGET BLT) diff --git a/include/blt/logging/logging.h b/include/blt/logging/logging.h index b1b75d5..f91d666 100644 --- a/include/blt/logging/logging.h +++ b/include/blt/logging/logging.h @@ -160,7 +160,7 @@ namespace blt::logging size_t m_arg_pos = 0; }; - void print(const std::string& str); + void print(std::string str); void newline(); @@ -212,12 +212,13 @@ namespace blt::logging user_str.pop_back(); if (level == log_level_t::NONE) { - println(user_str); + print(user_str); + newline(); return; } auto log_fmt_str = config.generate(user_str, get_thread_name(), level, file, line); if (log_fmt_str) - print(*log_fmt_str); + print(std::move(*log_fmt_str)); } namespace detail diff --git a/include/blt/logging/logging_config.h b/include/blt/logging/logging_config.h index 199a7e6..b9b09f8 100644 --- a/include/blt/logging/logging_config.h +++ b/include/blt/logging/logging_config.h @@ -206,6 +206,11 @@ namespace blt::logging std::optional generate(const std::string& user_str, const std::string& thread_name, log_level_t level, const char* file, i32 line) const; + [[nodiscard]] const std::vector>& get_injectors() const + { + return m_injectors; + } + private: std::vector> m_injectors; // wrappers for streams exist in blt/fs/stream_wrappers.h diff --git a/libraries/parallel-hashmap b/libraries/parallel-hashmap index 93201da..7ef2e73 160000 --- a/libraries/parallel-hashmap +++ b/libraries/parallel-hashmap @@ -1 +1 @@ -Subproject commit 93201da2ba5a6aba0a6e57ada64973555629b3e3 +Subproject commit 7ef2e733416953b222851f9a360d7fc72d068ee5 diff --git a/src/blt/logging/logging.cpp b/src/blt/logging/logging.cpp index 7ff8311..a668d0f 100644 --- a/src/blt/logging/logging.cpp +++ b/src/blt/logging/logging.cpp @@ -216,9 +216,24 @@ namespace blt::logging return get_thread_context().logger; } - void print(const std::string& str) + void print(std::string str) { - std::cout << str; + const auto& config = get_global_config(); + bool should_print = true; + if (!config.get_injectors().empty()) + { + for (const auto& injector : config.get_injectors()) + { + auto [new_logging_output, should_continue, should_log] = injector->inject(str); + if (!should_continue) + break; + if (!should_log) + should_print = false; + str = std::move(new_logging_output); + } + } + if (should_print) + std::cout << str; } void newline() From 3f59528ecc11f3c8c821a71bef0ad9ec290a90a5 Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Mon, 10 Mar 2025 15:51:38 -0400 Subject: [PATCH 075/101] silly --- CMakeLists.txt | 2 +- include/blt/logging/ansi.h | 14 ++++- include/blt/logging/injector.h | 4 +- include/blt/logging/logging_config.h | 9 ++- include/blt/logging/status.h | 29 +++++++++ src/blt/logging/logging.cpp | 4 +- src/blt/logging/status.cpp | 90 +++++++++++++++++++++++++++- test.txt | 0 tests/logger_tests.cpp | 76 ++++++++++++++--------- 9 files changed, 188 insertions(+), 40 deletions(-) create mode 100644 test.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 7c65b6c..30af81c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.2.10) +set(BLT_VERSION 5.2.11) set(BLT_TARGET BLT) diff --git a/include/blt/logging/ansi.h b/include/blt/logging/ansi.h index d07c7f1..22b4cd9 100644 --- a/include/blt/logging/ansi.h +++ b/include/blt/logging/ansi.h @@ -249,8 +249,12 @@ namespace blt::logging::ansi namespace cursor { inline const std::string home = BLT_ANSI_CSI "H"; + inline const std::string lower_left_corner = BLT_ANSI_ESCAPE " F"; + inline const std::string hide_cursor = BLT_ANSI_CSI "?2 5l"; + inline const std::string show_cursor = BLT_ANSI_CSI "?2 5h"; + inline const std::string report_position = BLT_ANSI_CSI "6n"; - template + template std::string move_to(const i64 line, const i64 column) { if constexpr (UseH) @@ -302,6 +306,14 @@ namespace blt::logging::ansi inline const std::string restore_cursor_position_sco = BLT_ANSI_CSI "u"; } + namespace scroll + { + inline std::string scroll_up(const int lines) + { + return std::string(BLT_ANSI_CSI) + std::to_string(lines) + "S"; + }; + } + namespace erase { inline const std::string to_end_of_screen = BLT_ANSI_CSI "0J"; diff --git a/include/blt/logging/injector.h b/include/blt/logging/injector.h index 313f5bd..2d886ed 100644 --- a/include/blt/logging/injector.h +++ b/include/blt/logging/injector.h @@ -25,9 +25,9 @@ namespace blt::logging { std::string new_logging_output; // should we continue processing the injector call chain? - bool should_continue; + bool should_continue = true; // should we log the resulting string at the end of the injector call chain? If false for any injector, it becomes false for all injectors. - bool should_log; + bool should_log = true; }; class injector_t diff --git a/include/blt/logging/logging_config.h b/include/blt/logging/logging_config.h index b9b09f8..8acf31b 100644 --- a/include/blt/logging/logging_config.h +++ b/include/blt/logging/logging_config.h @@ -20,7 +20,6 @@ #define BLT_LOGGING_LOGGING_CONFIG_H #include -#include #include #include #include @@ -140,9 +139,9 @@ namespace blt::logging return *this; } - logging_config_t& add_injector(injector_t* injector) + logging_config_t& add_injector(injector_t& injector) { - m_injectors.push_back(std::unique_ptr(injector)); + m_injectors.push_back(&injector); return *this; } @@ -206,13 +205,13 @@ namespace blt::logging std::optional generate(const std::string& user_str, const std::string& thread_name, log_level_t level, const char* file, i32 line) const; - [[nodiscard]] const std::vector>& get_injectors() const + [[nodiscard]] const std::vector& get_injectors() const { return m_injectors; } private: - std::vector> m_injectors; + std::vector m_injectors; // wrappers for streams exist in blt/fs/stream_wrappers.h std::vector m_log_outputs = get_default_log_outputs(); std::string m_log_format = get_default_log_format(); diff --git a/include/blt/logging/status.h b/include/blt/logging/status.h index 3f0b999..8644888 100644 --- a/include/blt/logging/status.h +++ b/include/blt/logging/status.h @@ -19,9 +19,38 @@ #ifndef BLT_LOGGING_STATUS_H #define BLT_LOGGING_STATUS_H +#include +#include +#include + namespace blt::logging { + class status_item_t + { + public: + virtual ~status_item_t() = default; + + [[nodiscard]] virtual i32 lines_used() const + { + return 1; + } + + virtual std::string print(); + }; + + class status_bar_t final : public injector_t + { + public: + explicit status_bar_t(i32 status_size); + + injector_output_t inject(const std::string& input) override; + + virtual ~status_bar_t() override; + private: + i32 m_status_size; + }; + } #endif //BLT_LOGGING_STATUS_H diff --git a/src/blt/logging/logging.cpp b/src/blt/logging/logging.cpp index a668d0f..67e5a66 100644 --- a/src/blt/logging/logging.cpp +++ b/src/blt/logging/logging.cpp @@ -225,11 +225,11 @@ namespace blt::logging for (const auto& injector : config.get_injectors()) { auto [new_logging_output, should_continue, should_log] = injector->inject(str); - if (!should_continue) - break; if (!should_log) should_print = false; str = std::move(new_logging_output); + if (!should_continue) + break; } } if (should_print) diff --git a/src/blt/logging/status.cpp b/src/blt/logging/status.cpp index 8fc772b..aa73d26 100644 --- a/src/blt/logging/status.cpp +++ b/src/blt/logging/status.cpp @@ -15,9 +15,97 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +#include +#include +#include +#include +#include +#include #include +#include namespace blt::logging { + vec2i get_cursor_position() + { + termios save{}, raw{}; -} \ No newline at end of file + tcgetattr(0, &save); + cfmakeraw(&raw); + tcsetattr(0,TCSANOW, &raw); + + char buf[32]; + char cmd[] = "\033[6n"; + + int row = 0; + int col = 0; + + if (isatty(fileno(stdin))) + { + write(1, cmd, sizeof(cmd)); + read(0, buf, sizeof(buf)); + + int sep = 0; + int end = 0; + for (int i = 2; i < 8; i++) + { + if (buf[i] == ';') + sep = i; + if (buf[i] == 'R') + { + end = i; + break; + } + } + row = std::stoi(std::string(buf + 2, buf + sep)); + col = std::stoi(std::string(buf + sep + 1, buf + end)); + // printf("{Row: %d, Col: %d}", row, col); + } + + tcsetattr(0,TCSANOW, &save); + + return vec2i{row, col}; + } + + vec2i get_screen_size() + { + std::cout << ansi::cursor::move_to(9999, 9999); + const auto pos = get_cursor_position(); + std::cout << ansi::cursor::lower_left_corner; + std::cout << " "; + return pos; + } + + status_bar_t::status_bar_t(const i32 status_size): m_status_size(status_size) + { + std::cout << ansi::cursor::hide_cursor; + } + + std::string status_item_t::print() + {} + + injector_output_t status_bar_t::inject(const std::string& input) + { + injector_output_t output{input, false, false}; + + std::cout << ansi::cursor::lower_left_corner; + std::cout << ansi::scroll::scroll_up(1); + for (int i = 0; i < m_status_size; i++) + std::cout << ansi::erase::entire_line << ansi::cursor::move_begin_up(1); + std::cout << output.new_logging_output; + std::cout << ansi::erase::entire_line; + std::cout << "[----status----]"; + std::cout << ansi::cursor::move_begin_down(1) << ansi::erase::entire_line; + std::cout << "[----Second Line----]"; + std::cout << std::flush; + + return output; + } + + status_bar_t::~status_bar_t() + { + // std::cout << "\033[" << m_scrolling_region + 1 << ";1H"; + std::cout << ansi::cursor::lower_left_corner; + std::cout << ansi::cursor::show_cursor; + } +} diff --git a/test.txt b/test.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/logger_tests.cpp b/tests/logger_tests.cpp index 6741770..319d3cb 100644 --- a/tests/logger_tests.cpp +++ b/tests/logger_tests.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -131,31 +132,31 @@ int main() blt::logging::println(ss, "This is a println with fill no alignment {:%20} end value", 46); blt::logging::println(ss, "This is a println with arg reference {0:{1}.{2}f}", 46.0232, 20, 2); blt::logging::println(ss, "This is a println with arg reference {0:&{1}}", "", 20); - blt::logging::print(ss.str()); + // blt::logging::print(ss.str()); auto [passed, error_msg] = compare_strings(expected_str, ss.str()); BLT_ASSERT_MSG(passed && "Logger logged string doesn't match precomputed expected string!", error_msg.c_str()); namespace ansi = blt::logging::ansi; namespace color = ansi::color; - for (blt::u8 r = 0; r < 6; r++) - { - for (blt::u8 g = 0; g < 6; g++) - { - for (blt::u8 b = 0; b < 6; b++) - { - blt::logging::println("{}This is a println with a color {:#3x} {:#3x} {:#3x}{}", - build(fg(color::color256{r, g, b}), bg(color::color256{ - static_cast(5 - r), - static_cast(5 - g), - static_cast(5 - b) - })), r, g, b, build(color::color_mode::RESET_ALL)); - } - } - } - blt::logging::println("{}This is a color now with background{}", - build(color::color_mode::BOLD, fg(color::color8::RED), color::color_mode::DIM, bg(color::color_rgb(0, 100, 255))), - build(color::color_mode::RESET_ALL)); + // for (blt::u8 r = 0; r < 6; r++) + // { + // for (blt::u8 g = 0; g < 6; g++) + // { + // for (blt::u8 b = 0; b < 6; b++) + // { + // blt::logging::println("{}This is a println with a color {:#3x} {:#3x} {:#3x}{}", + // build(fg(color::color256{r, g, b}), bg(color::color256{ + // static_cast(5 - r), + // static_cast(5 - g), + // static_cast(5 - b) + // })), r, g, b, build(color::color_mode::RESET_ALL)); + // } + // } + // } + // blt::logging::println("{}This is a color now with background{}", + // build(color::color_mode::BOLD, fg(color::color8::RED), color::color_mode::DIM, bg(color::color_rgb(0, 100, 255))), + // build(color::color_mode::RESET_ALL)); std::ofstream os("test.txt"); @@ -171,17 +172,36 @@ int main() writer.write("What about just a new line character?\n"); size_t charCount = 0; - blt::logging::println("Logged {} characters", charCount); + // blt::logging::println("Logged {} characters", charCount); + // + // BLT_TRACE("Hello this is am empty trace!"); + // BLT_TRACE("This is a trace with data {} {} {}", "bad at code", 413, "boy"); + // + // BLT_DEBUG("This is complete? {}", "this is working!"); + // BLT_INFO("Hello there!"); + // BLT_WARN("This is a warning!"); + // BLT_ERROR("This is an error!"); + // BLT_FATAL("This is a fatal error!"); + // BLT_TRACE("This is a pointer {:f}", &charCount); + // + // BLT_TRACE("Now time to test the logger status box"); - BLT_TRACE("Hello this is am empty trace!"); - BLT_TRACE("This is a trace with data {} {} {}", "bad at code", 413, "boy"); + blt::logging::status_bar_t status{2}; + blt::logging::get_global_config().add_injector(status); - BLT_DEBUG("This is complete? {}", "this is working!"); - BLT_INFO("Hello there!"); - BLT_WARN("This is a warning!"); - BLT_ERROR("This is an error!"); - BLT_FATAL("This is a fatal error!"); - BLT_TRACE("This is a pointer {:f}", &charCount); + BLT_TRACE("Hello There!"); + std::this_thread::sleep_for(std::chrono::milliseconds(1500)); + BLT_TRACE("I am printing stuff!"); + std::this_thread::sleep_for(std::chrono::milliseconds(1500)); + BLT_TRACE("How are you!?"); + + for (int i = 0; i < 100; i++) + { + BLT_INFO("I am printing some output {} times!", i + 1); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + std::this_thread::sleep_for(std::chrono::milliseconds(5000)); /*std::cout << "\033[2J"; constexpr int totalRows = 24; From 767bd95fbe4e587e198a3d461772e72c9e96d103 Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Mon, 10 Mar 2025 17:42:49 -0400 Subject: [PATCH 076/101] parker is a stupid faggot --- CMakeLists.txt | 2 +- include/blt/logging/status.h | 1 + include/blt/math/vectors.h | 5 +++++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 30af81c..571efa3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.2.11) +set(BLT_VERSION 5.2.12) set(BLT_TARGET BLT) diff --git a/include/blt/logging/status.h b/include/blt/logging/status.h index 8644888..5366add 100644 --- a/include/blt/logging/status.h +++ b/include/blt/logging/status.h @@ -49,6 +49,7 @@ namespace blt::logging virtual ~status_bar_t() override; private: i32 m_status_size; + i32 m_last_position[2]; }; } diff --git a/include/blt/math/vectors.h b/include/blt/math/vectors.h index 7ab4853..52d909f 100644 --- a/include/blt/math/vectors.h +++ b/include/blt/math/vectors.h @@ -107,6 +107,11 @@ namespace blt } } + [[nodiscard]] const std::array& to_array() const + { + return elements; + } + [[nodiscard]] constexpr inline T x() const { return elements[0]; From 6bcb5ff2f5eaa041b34e7002b9899b65ddec7abc Mon Sep 17 00:00:00 2001 From: Brett Date: Mon, 10 Mar 2025 23:54:44 -0400 Subject: [PATCH 077/101] part --- CMakeLists.txt | 2 +- include/blt/logging/status.h | 10 ++++++--- libraries/parallel-hashmap | 2 +- src/blt/logging/status.cpp | 41 +++++++++++++++++++++++------------- test.txt | 3 +++ tests/logger_tests.cpp | 1 - 6 files changed, 38 insertions(+), 21 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 571efa3..17dae83 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.2.12) +set(BLT_VERSION 5.2.13) set(BLT_TARGET BLT) diff --git a/include/blt/logging/status.h b/include/blt/logging/status.h index 5366add..0e8201a 100644 --- a/include/blt/logging/status.h +++ b/include/blt/logging/status.h @@ -22,6 +22,7 @@ #include #include #include +#include namespace blt::logging { @@ -36,7 +37,7 @@ namespace blt::logging return 1; } - virtual std::string print(); + virtual std::string print() = 0; }; class status_bar_t final : public injector_t @@ -46,10 +47,13 @@ namespace blt::logging injector_output_t inject(const std::string& input) override; - virtual ~status_bar_t() override; + void redraw(); + + ~status_bar_t() override; private: i32 m_status_size; - i32 m_last_position[2]; + vec2i m_last_log_position; + vec2i m_begin_position; }; } diff --git a/libraries/parallel-hashmap b/libraries/parallel-hashmap index 7ef2e73..93201da 160000 --- a/libraries/parallel-hashmap +++ b/libraries/parallel-hashmap @@ -1 +1 @@ -Subproject commit 7ef2e733416953b222851f9a360d7fc72d068ee5 +Subproject commit 93201da2ba5a6aba0a6e57ada64973555629b3e3 diff --git a/src/blt/logging/status.cpp b/src/blt/logging/status.cpp index aa73d26..ecd5d02 100644 --- a/src/blt/logging/status.cpp +++ b/src/blt/logging/status.cpp @@ -21,8 +21,10 @@ #include #include #include +#include #include #include +#include namespace blt::logging { @@ -78,34 +80,43 @@ namespace blt::logging status_bar_t::status_bar_t(const i32 status_size): m_status_size(status_size) { + std::cout << ansi::cursor::home << std::flush; + std::cout << ansi::erase::entire_screen << std::flush; + m_begin_position = m_last_log_position = get_cursor_position(); std::cout << ansi::cursor::hide_cursor; } - std::string status_item_t::print() - {} - injector_output_t status_bar_t::inject(const std::string& input) { injector_output_t output{input, false, false}; + if (output.new_logging_output.back() != '\n') + output.new_logging_output += '\n'; - std::cout << ansi::cursor::lower_left_corner; - std::cout << ansi::scroll::scroll_up(1); - for (int i = 0; i < m_status_size; i++) - std::cout << ansi::erase::entire_line << ansi::cursor::move_begin_up(1); - std::cout << output.new_logging_output; - std::cout << ansi::erase::entire_line; - std::cout << "[----status----]"; - std::cout << ansi::cursor::move_begin_down(1) << ansi::erase::entire_line; - std::cout << "[----Second Line----]"; - std::cout << std::flush; + if (get_cursor_position() != m_begin_position) + { + for (int i = 0; i < m_status_size; i++) + std::cout << ansi::erase::entire_line << ansi::cursor::move_begin_up(1) << std::flush; + } + std::cout << ansi::erase::entire_line << std::flush; + std::cout << output.new_logging_output << std::flush; + m_last_log_position = get_cursor_position(); + redraw(); return output; } + void status_bar_t::redraw() + { + std::cout << ansi::cursor::move_to(m_last_log_position.x(), m_last_log_position.y()); + std::cout << ansi::erase::entire_line << std::flush; + std::cout << "[----status----]" << std::endl; + std::cout << ansi::erase::entire_line << std::flush; + std::cout << "[----Second Line----]" << std::endl; + std::cout << std::flush; + } + status_bar_t::~status_bar_t() { - // std::cout << "\033[" << m_scrolling_region + 1 << ";1H"; - std::cout << ansi::cursor::lower_left_corner; std::cout << ansi::cursor::show_cursor; } } diff --git a/test.txt b/test.txt index e69de29..8416185 100644 --- a/test.txt +++ b/test.txt @@ -0,0 +1,3 @@ +This is a println with a stream +This is a mixed print 25 with multiple types 34.233400 +What about just a new line character? diff --git a/tests/logger_tests.cpp b/tests/logger_tests.cpp index 319d3cb..d18ef60 100644 --- a/tests/logger_tests.cpp +++ b/tests/logger_tests.cpp @@ -170,7 +170,6 @@ int main() writer.write(std::to_string(34.23340)); writer.write('\n'); writer.write("What about just a new line character?\n"); - size_t charCount = 0; // blt::logging::println("Logged {} characters", charCount); // From 8c88c6296f817d7c86d5089b4050fac258eb337a Mon Sep 17 00:00:00 2001 From: Brett Date: Tue, 11 Mar 2025 00:23:05 -0400 Subject: [PATCH 078/101] screen size --- CMakeLists.txt | 2 +- include/blt/logging/status.h | 1 + src/blt/logging/status.cpp | 54 ++++++++++++++++++++++++++++++------ 3 files changed, 48 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 17dae83..b9a7268 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.2.13) +set(BLT_VERSION 5.2.14) set(BLT_TARGET BLT) diff --git a/include/blt/logging/status.h b/include/blt/logging/status.h index 0e8201a..8ce363d 100644 --- a/include/blt/logging/status.h +++ b/include/blt/logging/status.h @@ -52,6 +52,7 @@ namespace blt::logging ~status_bar_t() override; private: i32 m_status_size; + vec2i m_screen_size; vec2i m_last_log_position; vec2i m_begin_position; }; diff --git a/src/blt/logging/status.cpp b/src/blt/logging/status.cpp index ecd5d02..f114911 100644 --- a/src/blt/logging/status.cpp +++ b/src/blt/logging/status.cpp @@ -44,8 +44,10 @@ namespace blt::logging if (isatty(fileno(stdin))) { - write(1, cmd, sizeof(cmd)); - read(0, buf, sizeof(buf)); + ssize_t i = write(1, cmd, sizeof(cmd)); + (void) i; + i = read(0, buf, sizeof(buf)); + (void) i; int sep = 0; int end = 0; @@ -61,7 +63,6 @@ namespace blt::logging } row = std::stoi(std::string(buf + 2, buf + sep)); col = std::stoi(std::string(buf + sep + 1, buf + end)); - // printf("{Row: %d, Col: %d}", row, col); } tcsetattr(0,TCSANOW, &save); @@ -69,17 +70,54 @@ namespace blt::logging return vec2i{row, col}; } +#define SIZE 100 + vec2i get_screen_size() { - std::cout << ansi::cursor::move_to(9999, 9999); - const auto pos = get_cursor_position(); - std::cout << ansi::cursor::lower_left_corner; - std::cout << " "; - return pos; + char in[SIZE] = ""; + int each = 0; + int ch = 0; + int rows = 0; + int cols = 0; + termios original, changed; + + // change terminal settings + tcgetattr( STDIN_FILENO, &original); + changed = original; + changed.c_lflag &= ~( ICANON | ECHO); + changed.c_cc[VMIN] = 1; + changed.c_cc[VTIME] = 0; + tcsetattr( STDIN_FILENO, TCSANOW, &changed); + + // printf ( "\033[2J"); //clear screen + + printf ( "\033[9999;9999H"); // cursor should move as far as it can + + printf ( "\033[6n"); // ask for cursor position + while ( ( ch = getchar ()) != 'R') { // R terminates the response + if ( EOF == ch) { + break; + } + if ( isprint ( ch)) { + if ( each + 1 < SIZE) { + in[each] = ch; + each++; + in[each] = '\0'; + } + } + } + + printf ( "\033[1;1H"); // move to upper left corner + if ( 2 == sscanf ( in, "[%d;%d", &rows, &cols)) { + tcsetattr( STDIN_FILENO, TCSANOW, &original); + return {rows, cols}; + } + throw std::runtime_error("Could not get screen size"); } status_bar_t::status_bar_t(const i32 status_size): m_status_size(status_size) { + m_screen_size = get_screen_size(); std::cout << ansi::cursor::home << std::flush; std::cout << ansi::erase::entire_screen << std::flush; m_begin_position = m_last_log_position = get_cursor_position(); From a968a285e788272d502f5e9143365a871aaf79b7 Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Wed, 12 Mar 2025 12:19:19 -0400 Subject: [PATCH 079/101] status --- CMakeLists.txt | 2 +- include/blt/logging/status.h | 53 +++++++++++++++++++++++++--- libraries/parallel-hashmap | 2 +- src/blt/logging/status.cpp | 67 ++++++++++++++++++++++++++++++++---- tests/logger_tests.cpp | 8 ++++- 5 files changed, 117 insertions(+), 15 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b9a7268..69221d9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.2.14) +set(BLT_VERSION 5.2.15) set(BLT_TARGET BLT) diff --git a/include/blt/logging/status.h b/include/blt/logging/status.h index 8ce363d..b6d2fe2 100644 --- a/include/blt/logging/status.h +++ b/include/blt/logging/status.h @@ -19,42 +19,85 @@ #ifndef BLT_LOGGING_STATUS_H #define BLT_LOGGING_STATUS_H +#include #include #include -#include #include +#include namespace blt::logging { + class status_bar_t; class status_item_t { public: virtual ~status_item_t() = default; + void assign(status_bar_t* bar) + { + m_status = bar; + } + [[nodiscard]] virtual i32 lines_used() const { return 1; } - virtual std::string print() = 0; + [[nodiscard]] virtual std::string print(vec2i screen_size, i32 max_printed_length) const = 0; + protected: + status_bar_t* m_status = nullptr; + }; + + class status_progress_bar_t final : public status_item_t + { + public: + [[nodiscard]] std::string print(vec2i screen_size, i32 max_printed_length) const override; + + void set_progress(double progress); + + void add_progress(const double progress) + { + set_progress(get_progress() + progress); + } + + [[nodiscard]] double get_progress() const + { + return m_progress; + } + private: + double m_progress = 0.0; }; class status_bar_t final : public injector_t { public: - explicit status_bar_t(i32 status_size); + explicit status_bar_t(); injector_output_t inject(const std::string& input) override; - void redraw(); + status_bar_t& add(status_item_t& item) + { + item.assign(this); + m_status_items.push_back(&item); + compute_size(); + return *this; + } + + void redraw() const; ~status_bar_t() override; private: - i32 m_status_size; + void compute_size(); + + std::vector m_status_items; + i32 m_status_size = 0; + i32 m_max_printed_length = 0; vec2i m_screen_size; vec2i m_last_log_position; vec2i m_begin_position; + + std::mutex m_print_mutex; }; } diff --git a/libraries/parallel-hashmap b/libraries/parallel-hashmap index 93201da..7ef2e73 160000 --- a/libraries/parallel-hashmap +++ b/libraries/parallel-hashmap @@ -1 +1 @@ -Subproject commit 93201da2ba5a6aba0a6e57ada64973555629b3e3 +Subproject commit 7ef2e733416953b222851f9a360d7fc72d068ee5 diff --git a/src/blt/logging/status.cpp b/src/blt/logging/status.cpp index f114911..258dbbb 100644 --- a/src/blt/logging/status.cpp +++ b/src/blt/logging/status.cpp @@ -89,7 +89,7 @@ namespace blt::logging changed.c_cc[VTIME] = 0; tcsetattr( STDIN_FILENO, TCSANOW, &changed); - // printf ( "\033[2J"); //clear screen + printf ( "\033[2J"); //clear screen printf ( "\033[9999;9999H"); // cursor should move as far as it can @@ -115,7 +115,53 @@ namespace blt::logging throw std::runtime_error("Could not get screen size"); } - status_bar_t::status_bar_t(const i32 status_size): m_status_size(status_size) + i32 get_size_no_ansi(const std::string& str) + { + i32 size = 0; + for (size_t i = 0; i < str.size(); i++) + { + if (str[i] == BLT_ANSI_ESCAPE[0]) + { + while (i < str.size()) + { + if (std::isalpha(str[i++])) + break; + } + } + ++size; + } + return size; + } + + std::string status_progress_bar_t::print(const vec2i screen_size, const i32 max_printed_length) const + { + std::string output = "["; + output.reserve(max_printed_length); + const auto amount_filled = (max_printed_length - 2) * m_progress; + auto amount_filled_int = static_cast(amount_filled); + const auto frac = amount_filled - static_cast(amount_filled_int); + + for (i64 i = 0; i < amount_filled_int; i++) + output += '#'; + if (frac >= 0.5) + { + output += '|'; + ++amount_filled_int; + } + for (i64 i = amount_filled_int; i < max_printed_length - 2; i++) + output += ' '; + + output += ']'; + return output; + } + + void status_progress_bar_t::set_progress(const double progress) + { + m_progress = progress; + // m_status->redraw(); + } + + status_bar_t::status_bar_t() { m_screen_size = get_screen_size(); std::cout << ansi::cursor::home << std::flush; @@ -126,6 +172,7 @@ namespace blt::logging injector_output_t status_bar_t::inject(const std::string& input) { + std::scoped_lock lock{m_print_mutex}; injector_output_t output{input, false, false}; if (output.new_logging_output.back() != '\n') output.new_logging_output += '\n'; @@ -137,19 +184,25 @@ namespace blt::logging } std::cout << ansi::erase::entire_line << std::flush; std::cout << output.new_logging_output << std::flush; + m_max_printed_length = std::max(get_size_no_ansi(output.new_logging_output), m_max_printed_length); m_last_log_position = get_cursor_position(); redraw(); return output; } - void status_bar_t::redraw() + void status_bar_t::compute_size() + { + m_status_size = 0; + for (const auto* ptr : m_status_items) + m_status_size += ptr->lines_used(); + } + + void status_bar_t::redraw() const { std::cout << ansi::cursor::move_to(m_last_log_position.x(), m_last_log_position.y()); - std::cout << ansi::erase::entire_line << std::flush; - std::cout << "[----status----]" << std::endl; - std::cout << ansi::erase::entire_line << std::flush; - std::cout << "[----Second Line----]" << std::endl; + for (const auto* ptr : m_status_items) + std::cout << ansi::erase::entire_line << ptr->print(m_screen_size, m_max_printed_length) << std::endl; std::cout << std::flush; } diff --git a/tests/logger_tests.cpp b/tests/logger_tests.cpp index d18ef60..c00d01d 100644 --- a/tests/logger_tests.cpp +++ b/tests/logger_tests.cpp @@ -185,17 +185,23 @@ int main() // // BLT_TRACE("Now time to test the logger status box"); - blt::logging::status_bar_t status{2}; + blt::logging::status_progress_bar_t progress; + blt::logging::status_bar_t status; + status.add(progress); blt::logging::get_global_config().add_injector(status); + progress.set_progress(1.0 / 103.0); BLT_TRACE("Hello There!"); std::this_thread::sleep_for(std::chrono::milliseconds(1500)); + progress.set_progress(2.0 / 103.0); BLT_TRACE("I am printing stuff!"); std::this_thread::sleep_for(std::chrono::milliseconds(1500)); + progress.set_progress(3.0 / 103.0); BLT_TRACE("How are you!?"); for (int i = 0; i < 100; i++) { + progress.set_progress((4.0 + i) / 103.0); BLT_INFO("I am printing some output {} times!", i + 1); std::this_thread::sleep_for(std::chrono::milliseconds(100)); } From 363ce6a5db5af77d021ec1759e08abde32dff104 Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Wed, 12 Mar 2025 16:26:13 -0400 Subject: [PATCH 080/101] silly --- CMakeLists.txt | 2 +- src/blt/logging/status.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 69221d9..e0a31c6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.2.15) +set(BLT_VERSION 5.2.16) set(BLT_TARGET BLT) diff --git a/src/blt/logging/status.cpp b/src/blt/logging/status.cpp index 258dbbb..683964e 100644 --- a/src/blt/logging/status.cpp +++ b/src/blt/logging/status.cpp @@ -130,10 +130,10 @@ namespace blt::logging } ++size; } - return size; + return size - 1; } - std::string status_progress_bar_t::print(const vec2i screen_size, const i32 max_printed_length) const + std::string status_progress_bar_t::print(const vec2i, const i32 max_printed_length) const { std::string output = "["; output.reserve(max_printed_length); @@ -167,7 +167,7 @@ namespace blt::logging std::cout << ansi::cursor::home << std::flush; std::cout << ansi::erase::entire_screen << std::flush; m_begin_position = m_last_log_position = get_cursor_position(); - std::cout << ansi::cursor::hide_cursor; + std::cout << ansi::cursor::hide_cursor << std::flush; } injector_output_t status_bar_t::inject(const std::string& input) From 0890663f7af68329b86da96e36634b0896f0e7fc Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Wed, 12 Mar 2025 17:30:49 -0400 Subject: [PATCH 081/101] add flush --- CMakeLists.txt | 2 +- libraries/parallel-hashmap | 2 +- src/blt/logging/status.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e0a31c6..9e13993 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.2.16) +set(BLT_VERSION 5.2.17) set(BLT_TARGET BLT) diff --git a/libraries/parallel-hashmap b/libraries/parallel-hashmap index 7ef2e73..154c634 160000 --- a/libraries/parallel-hashmap +++ b/libraries/parallel-hashmap @@ -1 +1 @@ -Subproject commit 7ef2e733416953b222851f9a360d7fc72d068ee5 +Subproject commit 154c63489e84d5569d3b466342a2ae8fd99e4734 diff --git a/src/blt/logging/status.cpp b/src/blt/logging/status.cpp index 683964e..44df3d2 100644 --- a/src/blt/logging/status.cpp +++ b/src/blt/logging/status.cpp @@ -208,6 +208,6 @@ namespace blt::logging status_bar_t::~status_bar_t() { - std::cout << ansi::cursor::show_cursor; + std::cout << ansi::cursor::show_cursor << std::flush; } } From afab2f8043b68047acb3e23c2d1ca1a97916b97a Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Wed, 12 Mar 2025 18:13:33 -0400 Subject: [PATCH 082/101] status bounds check --- CMakeLists.txt | 2 +- src/blt/logging/status.cpp | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e13993..23ad674 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.2.17) +set(BLT_VERSION 5.2.18) set(BLT_TARGET BLT) diff --git a/src/blt/logging/status.cpp b/src/blt/logging/status.cpp index 44df3d2..69be8f2 100644 --- a/src/blt/logging/status.cpp +++ b/src/blt/logging/status.cpp @@ -138,7 +138,7 @@ namespace blt::logging std::string output = "["; output.reserve(max_printed_length); const auto amount_filled = (max_printed_length - 2) * m_progress; - auto amount_filled_int = static_cast(amount_filled); + auto amount_filled_int = static_cast(amount_filled); const auto frac = amount_filled - static_cast(amount_filled_int); for (i64 i = 0; i < amount_filled_int; i++) @@ -157,6 +157,8 @@ namespace blt::logging void status_progress_bar_t::set_progress(const double progress) { + if (std::isnan(progress) || progress < 0 || progress > 1 || std::isinf(progress)) + throw std::invalid_argument("progress must be between 0 and 1"); m_progress = progress; // m_status->redraw(); } From e8b62100345b9c09610e8bd1fd3df850c5378bcf Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Wed, 12 Mar 2025 18:14:10 -0400 Subject: [PATCH 083/101] better status --- CMakeLists.txt | 2 +- src/blt/logging/status.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 23ad674..155d11e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.2.18) +set(BLT_VERSION 5.2.19) set(BLT_TARGET BLT) diff --git a/src/blt/logging/status.cpp b/src/blt/logging/status.cpp index 69be8f2..90696b4 100644 --- a/src/blt/logging/status.cpp +++ b/src/blt/logging/status.cpp @@ -158,7 +158,7 @@ namespace blt::logging void status_progress_bar_t::set_progress(const double progress) { if (std::isnan(progress) || progress < 0 || progress > 1 || std::isinf(progress)) - throw std::invalid_argument("progress must be between 0 and 1"); + throw std::invalid_argument("Progress must be between 0 and 1 (got: " + std::to_string(progress) + ")"); m_progress = progress; // m_status->redraw(); } From 8416b69f5d6f15f95019571ce8dbd9c4434ed8ea Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Wed, 12 Mar 2025 18:33:51 -0400 Subject: [PATCH 084/101] contains is const --- CMakeLists.txt | 2 +- include/blt/parse/argparse_v2.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 155d11e..3a74ff5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.2.19) +set(BLT_VERSION 5.2.20) set(BLT_TARGET BLT) diff --git a/include/blt/parse/argparse_v2.h b/include/blt/parse/argparse_v2.h index 3ff1fc5..0105bf5 100644 --- a/include/blt/parse/argparse_v2.h +++ b/include/blt/parse/argparse_v2.h @@ -394,7 +394,7 @@ namespace blt::argparse return std::get(m_data.at(key)); } - bool contains(const std::string_view key) + bool contains(const std::string_view key) const { return m_data.find(key) != m_data.end(); } From 04a5c01704bbf126ef623d57af61359cdf3d659c Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Wed, 12 Mar 2025 18:34:04 -0400 Subject: [PATCH 085/101] const --- CMakeLists.txt | 2 +- include/blt/parse/argparse_v2.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3a74ff5..618e338 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.2.20) +set(BLT_VERSION 5.2.21) set(BLT_TARGET BLT) diff --git a/include/blt/parse/argparse_v2.h b/include/blt/parse/argparse_v2.h index 0105bf5..f44bf4e 100644 --- a/include/blt/parse/argparse_v2.h +++ b/include/blt/parse/argparse_v2.h @@ -394,7 +394,7 @@ namespace blt::argparse return std::get(m_data.at(key)); } - bool contains(const std::string_view key) const + [[nodiscard]] bool contains(const std::string_view key) const { return m_data.find(key) != m_data.end(); } From 4fab2595bc8895307b12aa086f5a24440b0b1e13 Mon Sep 17 00:00:00 2001 From: Brett Date: Wed, 12 Mar 2025 23:26:44 -0400 Subject: [PATCH 086/101] fix argparse help --- CMakeLists.txt | 2 +- libraries/parallel-hashmap | 2 +- src/blt/parse/argparse_v2.cpp | 3124 ++++++++++++++++----------------- 3 files changed, 1531 insertions(+), 1597 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 618e338..ca38425 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.2.21) +set(BLT_VERSION 5.2.22) set(BLT_TARGET BLT) diff --git a/libraries/parallel-hashmap b/libraries/parallel-hashmap index 154c634..93201da 160000 --- a/libraries/parallel-hashmap +++ b/libraries/parallel-hashmap @@ -1 +1 @@ -Subproject commit 154c63489e84d5569d3b466342a2ae8fd99e4734 +Subproject commit 93201da2ba5a6aba0a6e57ada64973555629b3e3 diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp index 446ac86..5694868 100644 --- a/src/blt/parse/argparse_v2.cpp +++ b/src/blt/parse/argparse_v2.cpp @@ -28,1630 +28,1564 @@ namespace blt::argparse { - constexpr static auto printer_primitive = [](const auto& v) - { - std::cout << v; - }; + constexpr static auto printer_primitive = [](const auto& v) { + std::cout << v; + }; - constexpr static auto printer_vector = [](const auto& v) - { - std::cout << "["; - for (const auto& [i, a] : enumerate(v)) - { - std::cout << a; - if (i != v.size() - 1) - std::cout << ", "; - } - std::cout << "]"; - }; + constexpr static auto printer_vector = [](const auto& v) { + std::cout << "["; + for (const auto& [i, a] : enumerate(v)) + { + std::cout << a; + if (i != v.size() - 1) + std::cout << ", "; + } + std::cout << "]"; + }; - auto print_visitor = detail::arg_meta_type_helper_t::make_visitor(printer_primitive, printer_vector); + auto print_visitor = detail::arg_meta_type_helper_t::make_visitor(printer_primitive, printer_vector); - template - size_t get_const_char_size(const T& t) - { - if constexpr (std::is_convertible_v) - { - return std::char_traits::length(t); - } - else if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) - { - return 1; - } - else if constexpr (std::is_same_v || std::is_same_v) - { - return t.size(); - } - else - { - return 0; - } - } + template + size_t get_const_char_size(const T& t) + { + if constexpr (std::is_convertible_v) + { + return std::char_traits::length(t); + } else if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) + { + return 1; + } else if constexpr (std::is_same_v || std::is_same_v) + { + return t.size(); + } else + { + return 0; + } + } - template - std::string to_string(const T& t) - { - if constexpr (std::is_same_v || std::is_same_v) - { - return std::string(t); - } - else if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) - { - std::string str; - str += t; - return str; - } - else - { - return t; - } - } + template + std::string to_string(const T& t) + { + if constexpr (std::is_same_v || std::is_same_v) + { + return std::string(t); + } else if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) + { + std::string str; + str += t; + return str; + } else + { + return t; + } + } - template - std::string make_string(Strings&&... strings) - { - std::string out; - out.reserve((get_const_char_size(strings) + ...)); - ((out += detail::ensure_is_string(std::forward(strings))), ...); - return out; - } + template + std::string make_string(Strings&&... strings) + { + std::string out; + out.reserve((get_const_char_size(strings) + ...)); + ((out += detail::ensure_is_string(std::forward(strings))), ...); + return out; + } - template - std::vector make_arguments(Strings... strings) - { - return std::vector{"./program", strings...}; - } + template + std::vector make_arguments(Strings... strings) + { + return std::vector{"./program", strings...}; + } - class aligned_internal_string_t - { - public: - explicit aligned_internal_string_t(std::string& str, const size_t max_line_length, - const size_t line_start_size): string(str), max_line_size(max_line_length), - line_start_size(line_start_size) - { - } + class aligned_internal_string_t + { + public: + explicit aligned_internal_string_t(std::string& str, const size_t max_line_length, const size_t line_start_size): string(str), + max_line_size( + max_line_length), + line_start_size( + line_start_size) + {} - void add(const std::string_view str) const - { - const auto lines = string::split(string, '\n'); - if (lines.empty()) - { - string += str; - return; - } - if (lines.back().size() + str.size() > max_line_size) - { - string += '\n'; - for (size_t i = 0; i < line_start_size; i++) - string += ' '; - bool blank = true; - // we don't want to write blank only strings - for (const char c : str) + void add(const std::string_view str) const + { + const auto lines = string::split(string, '\n'); + if (lines.empty()) + { + string += str; + return; + } + if (lines.back().size() + str.size() > max_line_size) + { + string += '\n'; + for (size_t i = 0; i < line_start_size; i++) + string += ' '; + bool blank = true; + // we don't want to write blank only strings + for (const char c : str) + { + if (!std::isblank(c)) + { + blank = false; + break; + } + } + if (blank) + return; + } + + string += str; + } + + template + aligned_internal_string_t& operator+=(T&& value) + { + const auto str = to_string(detail::ensure_is_string(std::forward(value))); + for (size_t i = 0; i < str.size(); i++) + { + size_t j = i; + for (; j < str.size() && !std::isblank(str[j]); ++j) + {} + add(std::string_view(str.data() + i, j - i)); + if (j < str.size()) + add(std::string_view(str.data() + j, 1)); + i = j; + } + return *this; + } + + [[nodiscard]] std::string& str() const + { + return string; + } + + private: + std::string& string; + size_t max_line_size; + size_t line_start_size; + }; + + class aligner_t + { + public: + aligner_t(std::vector& buffer, const size_t start_index, const size_t max_line_size): buffer(buffer), start_index(start_index), + max_line_size(max_line_size) + {} + + void align(const size_t spaces_between) const + { + const size_t take = compute_take(); + size_t aligned_size = 0; + for (const auto& v : iterate(buffer).skip(start_index).take(take)) + { + auto size = static_cast(v.size()); + for (; size > 0 && std::isblank(v[size - 1]); size--) + {} + aligned_size = std::max(aligned_size, static_cast(size)); + } + const auto offset_size = aligned_size + spaces_between; + + for (auto& v : iterate(buffer).skip(start_index).take(take)) + { + for (size_t i = v.size(); i < offset_size; i++) + v += ' '; + } + } + + [[nodiscard]] auto iter() + { + return iterate(buffer).skip(start_index).take(compute_take()).map([this](std::string& x) { + return aligned_internal_string_t{x, max_line_size, x.size()}; + }); + } + + [[nodiscard]] auto iter() const + { + return iterate(buffer).skip(start_index).take(compute_take()).map([this](std::string& x) { + return aligned_internal_string_t{x, max_line_size, x.size()}; + }); + } + + void take(const size_t amount) + { + this->amount = amount; + } + + private: + [[nodiscard]] size_t compute_take() const + { + return amount == -1ul ? (buffer.size() - start_index - 1) : amount;; + } + + std::vector& buffer; + size_t start_index; + size_t max_line_size; + size_t amount = -1; + }; + + class aligned_printer_t + { + public: + explicit aligned_printer_t(std::string line_begin = "\t", const size_t max_line_size = 120, const size_t spaces_per_tab = 4): + line_begin(std::move(line_begin)), max_line_size(max_line_size) + { + buffer.emplace_back(); + for (size_t i = 0; i < spaces_per_tab; i++) + spaces_from_tab += ' '; + } + + [[nodiscard]] std::string str() const + { + std::string combined; + for (const auto& str : buffer) + { + combined += str; + combined += '\n'; + } + return combined; + } + + auto mark() + { + return aligner_t{buffer, buffer.size() - 1, max_line_size}; + } + + template + aligned_printer_t& add(T&& value) + { + const auto str = to_string(detail::ensure_is_string(std::forward(value))); + if (buffer.back().size() + str.size() > max_line_size) + newline(); + buffer.back() += replace_tabs(str); + return *this; + } + + void newline() + { + buffer.emplace_back(replace_tabs(line_begin)); + } + + [[nodiscard]] std::string replace_tabs(std::string str) const + { + string::replaceAll(str, "\t", spaces_from_tab); + return str; + } + + template + aligned_printer_t& operator+=(T&& value) + { + return add(std::forward(value)); + } + + [[nodiscard]] auto iter() + { + return iterate(buffer); + } + + [[nodiscard]] auto iter() const + { + return iterate(buffer); + } + + private: + std::vector buffer; + std::string line_begin; + std::string spaces_from_tab; + size_t max_line_size; + }; + + argument_builder_t& argument_builder_t::set_action(const action_t action) + { + m_action = action; + switch (m_action) + { + case action_t::STORE_TRUE: + set_nargs(0); + as_type(); + set_default(false); + break; + case action_t::STORE_FALSE: + set_nargs(0); + as_type(); + set_default(true); + break; + case action_t::STORE_CONST: + case action_t::APPEND_CONST: + set_nargs(0); + break; + case action_t::COUNT: + set_nargs(0); + as_type(); + break; + case action_t::EXTEND: + set_nargs(nargs_t::ALL); + break; + case action_t::HELP: + case action_t::VERSION: + set_nargs(0); + break; + default: + break; + } + return *this; + } + + argument_subparser_t* argument_parser_t::add_subparser(const std::string_view dest) + { + m_subparsers.emplace_back(dest, argument_subparser_t{*this}); + return &m_subparsers.back().second; + } + + argument_storage_t argument_parser_t::parse(argument_consumer_t& consumer) + { + if (!m_name) + m_name = fs::base_name_sv(consumer.absolute_first().get_argument()); + argument_positional_storage_t positional_storage{m_positional_arguments}; + hashset_t found_flags; + argument_storage_t parsed_args; + // first, we consume flags which may be part of this parser + while (consumer.can_consume() && consumer.peek().is_flag()) + handle_compound_flags(found_flags, parsed_args, consumer, consumer.consume()); + + for (auto& [key, subparser] : m_subparsers) + { + auto [parsed_subparser, storage] = subparser.parse(consumer); + storage.m_data.emplace(std::string{key}, detail::arg_data_t{std::string{parsed_subparser.get_argument()}}); + parsed_args.add(storage); + } + + while (consumer.can_consume()) + { + if (consumer.peek().is_flag()) + handle_compound_flags(found_flags, parsed_args, consumer, consumer.consume()); + else + parse_positional(parsed_args, consumer, positional_storage, consumer.peek().get_argument()); + } + handle_missing_and_default_args(m_flag_arguments, found_flags, parsed_args, "flag"); + + for (auto& [name, value] : positional_storage.remaining()) + { + std::visit(lambda_visitor{ + [](const nargs_t) {}, + [](const int argc) { + if (argc == 0) + throw detail::bad_positional("Positional Argument takes no values, this is invalid!"); + } + }, value.m_nargs); + + if (value.m_required) + throw detail::missing_argument_error(make_string("Error: argument '", name, "' was not found but is required by the program")); + if (value.m_default_value && !parsed_args.contains(value.m_dest.value_or(name))) + parsed_args.m_data.emplace(value.m_dest.value_or(name), *value.m_default_value); + } + + return parsed_args; + } + + void argument_parser_t::print_help() + { + print_usage(); + aligned_printer_t help{""}; + + if (!m_subparsers.empty()) + { + help += "Subcommands:"; + help.newline(); + for (const auto& [key, value] : m_subparsers) + { + auto map = value.get_allowed_strings(); + help += '\t'; + help += key; + help += ": {"; + for (const auto& [i, parser, strings] : enumerate(map).flatten()) + { + if (strings.size() > 1) + help += '['; + for (const auto& [i, str] : enumerate(strings)) + { + help += str; + if (i != strings.size() - 1) + help += ", "; + } + if (strings.size() > 1) + help += ']'; + if (i != map.size() - 1) + help += ", "; + } + help += "}"; + help.newline(); + } + help.newline(); + } + + if (!m_positional_arguments.empty()) + { + help += "Positional Arguments:"; + help.newline(); + auto mark = help.mark(); + for (auto& [name, builder] : m_positional_arguments) + { + help += '\t'; + if (!builder.m_required) + help += '['; + help += name; + if (!builder.m_required) + help += ']'; + help.newline(); + } + mark.align(4); + for (auto zipped_positionals : mark.iter().zip(m_positional_arguments)) + { + auto& line = std::get<0>(zipped_positionals); + auto& [name, builder] = std::get<1>(zipped_positionals); + line += builder.m_help.value_or(""); + if (builder.m_default_value && !(builder.m_action == action_t::STORE_TRUE || builder.m_action == action_t::STORE_FALSE)) { - if (!std::isblank(c)) - { - blank = false; - break; - } - } - if (blank) - return; - } - - string += str; - } - - template - aligned_internal_string_t& operator+=(T&& value) - { - const auto str = to_string(detail::ensure_is_string(std::forward(value))); - for (size_t i = 0; i < str.size(); i++) - { - size_t j = i; - for (; j < str.size() && !std::isblank(str[j]); ++j) - { - } - add(std::string_view(str.data() + i, j - i)); - if (j < str.size()) - add(std::string_view(str.data() + j, 1)); - i = j; - } - return *this; - } - - [[nodiscard]] std::string& str() const - { - return string; - } - - private: - std::string& string; - size_t max_line_size; - size_t line_start_size; - }; - - class aligner_t - { - public: - aligner_t(std::vector& buffer, const size_t start_index, const size_t max_line_size): - buffer(buffer), start_index(start_index), max_line_size(max_line_size) - { - } - - void align(const size_t spaces_between) const - { - const size_t take = compute_take(); - size_t aligned_size = 0; - for (const auto& v : iterate(buffer).skip(start_index).take(take)) - { - auto size = static_cast(v.size()); - for (; size > 0 && std::isblank(v[size - 1]); size--) - { - } - aligned_size = std::max(aligned_size, static_cast(size)); - } - const auto offset_size = aligned_size + spaces_between; - - for (auto& v : iterate(buffer).skip(start_index).take(take)) - { - for (size_t i = v.size(); i < offset_size; i++) - v += ' '; - } - } - - [[nodiscard]] auto iter() - { - return iterate(buffer).skip(start_index).take(compute_take()).map([this](std::string& x) - { - return aligned_internal_string_t{x, max_line_size, x.size()}; - }); - } - - [[nodiscard]] auto iter() const - { - return iterate(buffer).skip(start_index).take(compute_take()).map([this](std::string& x) - { - return aligned_internal_string_t{x, max_line_size, x.size()}; - }); - } - - void take(const size_t amount) - { - this->amount = amount; - } - - private: - [[nodiscard]] size_t compute_take() const - { - return amount == -1ul ? (buffer.size() - start_index - 1) : amount;; - } - - std::vector& buffer; - size_t start_index; - size_t max_line_size; - size_t amount = -1; - }; - - class aligned_printer_t - { - public: - explicit aligned_printer_t(std::string line_begin = "\t", const size_t max_line_size = 120, const size_t spaces_per_tab = 4): - line_begin(std::move(line_begin)), max_line_size(max_line_size) - { - buffer.emplace_back(); - for (size_t i = 0; i < spaces_per_tab; i++) - spaces_from_tab += ' '; - } - - [[nodiscard]] std::string str() const - { - std::string combined; - for (const auto& str : buffer) - { - combined += str; - combined += '\n'; - } - return combined; - } - - auto mark() - { - return aligner_t{buffer, buffer.size() - 1, max_line_size}; - } - - template - aligned_printer_t& add(T&& value) - { - const auto str = to_string(detail::ensure_is_string(std::forward(value))); - if (buffer.back().size() + str.size() > max_line_size) - newline(); - buffer.back() += replace_tabs(str); - return *this; - } - - void newline() - { - buffer.emplace_back(replace_tabs(line_begin)); - } - - [[nodiscard]] std::string replace_tabs(std::string str) const - { - string::replaceAll(str, "\t", spaces_from_tab); - return str; - } - - template - aligned_printer_t& operator+=(T&& value) - { - return add(std::forward(value)); - } - - [[nodiscard]] auto iter() - { - return iterate(buffer); - } - - [[nodiscard]] auto iter() const - { - return iterate(buffer); - } - - private: - std::vector buffer; - std::string line_begin; - std::string spaces_from_tab; - size_t max_line_size; - }; - - argument_builder_t& argument_builder_t::set_action(const action_t action) - { - m_action = action; - switch (m_action) - { - case action_t::STORE_TRUE: - set_nargs(0); - as_type(); - set_default(false); - break; - case action_t::STORE_FALSE: - set_nargs(0); - as_type(); - set_default(true); - break; - case action_t::STORE_CONST: - case action_t::APPEND_CONST: - set_nargs(0); - break; - case action_t::COUNT: - set_nargs(0); - as_type(); - break; - case action_t::EXTEND: - set_nargs(nargs_t::ALL); - break; - case action_t::HELP: - case action_t::VERSION: - set_nargs(0); - break; - default: - break; - } - return *this; - } - - argument_subparser_t* argument_parser_t::add_subparser(const std::string_view dest) - { - m_subparsers.emplace_back(dest, argument_subparser_t{*this}); - return &m_subparsers.back().second; - } - - argument_storage_t argument_parser_t::parse(argument_consumer_t& consumer) - { - if (!m_name) - m_name = fs::base_name_sv(consumer.absolute_first().get_argument()); - argument_positional_storage_t positional_storage{m_positional_arguments}; - hashset_t found_flags; - argument_storage_t parsed_args; - // first, we consume flags which may be part of this parser - while (consumer.can_consume() && consumer.peek().is_flag()) - handle_compound_flags(found_flags, parsed_args, consumer, consumer.consume()); - - for (auto& [key, subparser] : m_subparsers) - { - auto [parsed_subparser, storage] = subparser.parse(consumer); - storage.m_data.emplace(std::string{key}, detail::arg_data_t{std::string{parsed_subparser.get_argument()}}); - parsed_args.add(storage); - } - - while (consumer.can_consume()) - { - if (consumer.peek().is_flag()) - handle_compound_flags(found_flags, parsed_args, consumer, consumer.consume()); - else - parse_positional(parsed_args, consumer, positional_storage, consumer.peek().get_argument()); - } - handle_missing_and_default_args(m_flag_arguments, found_flags, parsed_args, "flag"); - - for (auto& [name, value] : positional_storage.remaining()) - { - std::visit(lambda_visitor{ - [](const nargs_t) - { - }, - [](const int argc) - { - if (argc == 0) - throw detail::bad_positional("Positional Argument takes no values, this is invalid!"); - } - }, value.m_nargs); - - if (value.m_required) - throw detail::missing_argument_error(make_string("Error: argument '", name, "' was not found but is required by the program")); - if (value.m_default_value && !parsed_args.contains(value.m_dest.value_or(name))) - parsed_args.m_data.emplace(value.m_dest.value_or(name), *value.m_default_value); - } - - return parsed_args; - } - - void argument_parser_t::print_help() - { - print_usage(); - aligned_printer_t help{""}; - - if (!m_subparsers.empty()) - { - help += "Subcommands:"; - help.newline(); - for (const auto& [key, value] : m_subparsers) - { - auto map = value.get_allowed_strings(); - help += '\t'; - help += key; - help += ": {"; - for (const auto& [i, parser, strings] : enumerate(map).flatten()) - { - if (strings.size() > 1) - help += '['; - for (const auto& [i, str] : enumerate(strings)) - { - help += str; - if (i != strings.size() - 1) - help += ", "; - } - if (strings.size() > 1) - help += ']'; - if (i != map.size() - 1) - help += ", "; - } - help += "}"; - help.newline(); - } - help.newline(); - } - - if (!m_positional_arguments.empty()) - { - help += "Positional Arguments:"; - help.newline(); - for (auto& [name, builder] : m_positional_arguments) - { - help += '\t'; - if (!builder.m_required) - help += '['; - help += name; - if (!builder.m_required) - help += ']'; - if (builder.m_default_value && !(builder.m_action == action_t::STORE_TRUE || builder.m_action == action_t::STORE_FALSE)) - { - if (!std::isblank(help.str().back())) - help += " "; - help += "(Default: "; + if (!std::isblank(line.str().back())) + line += " "; + line += "(Default: "; std::visit(detail::arg_meta_type_helper_t::make_visitor( [&](auto& value) { - help += value; + line += value; }, [&](auto& vec) { if constexpr (!std::is_same_v>, std::vector>) { - help += '['; + line += '['; for (const auto& [i, v] : enumerate(vec)) { - help += v; + line += v; if (i != vec.size() - 1) - help += ", "; + line += ", "; } - help += ']'; + line += ']'; } }), *builder.m_default_value); - help += ")"; + line += ")"; } if (builder.m_choices) { - if (!std::isblank(help.str().back())) - help += " "; - help += "(Choices: "; + if (!std::isblank(line.str().back())) + line += " "; + line += "(Choices: "; for (const auto& [i, v] : enumerate(*builder.m_choices)) { - help += '\''; - help += v; - help += '\''; + line += '\''; + line += v; + line += '\''; if (i != builder.m_choices->size() - 1) - help += ", "; + line += ", "; } - help += ')'; + line += ')'; } - help.newline(); - help.newline(); - } - } - - if (!m_flag_arguments.empty()) - { - help += "Options:"; - help.newline(); - hashmap_t> same_flags; - for (const auto& [key, value] : m_flag_arguments) - same_flags[value].emplace_back(key); - auto mark = help.mark(); - for (const auto& [builder, flag_list] : same_flags) - { - // find max size and align? - help += '\t'; - for (const auto& [i, flag] : enumerate(flag_list)) - { - help += flag; - if (i != flag_list.size() - 1) - help += ", "; - } - const argument_string_t arg{flag_list.front(), allowed_flag_prefixes}; - auto metavar = builder->m_metavar.value_or(string::toUpperCase(arg.get_name())); - auto lambda = [&]() - { - help += ' '; - help += metavar; - }; - std::visit(lambda_visitor{ - [&](const nargs_t type) - { - lambda(); - switch (type) - { - case nargs_t::IF_POSSIBLE: - break; - case nargs_t::ALL: - case nargs_t::ALL_AT_LEAST_ONE: - help += "..."; - break; - } - }, - [&](const int argc) - { - if (argc == 0) - return; - lambda(); - if (argc > 1) - { - help += "... x"; - help += std::to_string(argc); - } - } - }, builder->m_nargs); - help.newline(); - } - mark.align(4); - for (auto zipped_flags : mark.iter().zip(same_flags)) - { - auto& str = std::get<0>(zipped_flags); - auto& [builder, flag_list] = std::get<1>(zipped_flags); - str += builder->m_help.value_or(""); - if (builder->m_default_value && !(builder->m_action == action_t::STORE_TRUE || builder->m_action == action_t::STORE_FALSE)) - { - if (!std::isblank(str.str().back())) - str += " "; - str += "(Default: '"; - std::visit(detail::arg_meta_type_helper_t::make_visitor( - [&](auto& value) - { - str += value; - }, - [&](auto& vec) - { - if constexpr (!std::is_same_v>, std::vector>) - { - str += '['; - for (const auto& [i, v] : enumerate(vec)) - { - str += v; - if (i != vec.size() - 1) - str += ", "; - } - str += ']'; - } - }), *builder->m_default_value); - str += "')"; - } - if (builder->m_choices) - { - if (!std::isblank(str.str().back())) - str += " "; - str += "(Choices: "; - for (const auto& [i, v] : enumerate(*builder->m_choices)) - { - str += '\''; - str += v; - str += '\''; - if (i != builder->m_choices->size() - 1) - str += ", "; - } - str += ')'; - } - if (builder->m_required) - { - if (!std::isblank(str.str().back())) - str += " "; - str += "(Required)"; - } - } - } - - std::cout << help.str() << std::endl; - } - - void argument_parser_t::print_usage() - { - if (!m_usage) - { - aligned_printer_t aligner; - aligner += m_name.value_or(""); - aligner += ' '; - - auto parent = m_parent; - while (parent != nullptr) - { - if (!parent->m_last_parsed_parser) - throw detail::missing_value_error( - "Error: Help called on subparser but unable to find parser chain. This condition should be impossible."); - aligner += parent->m_last_parsed_parser.value(); - aligner += ' '; - parent = parent->m_parent->m_parent; - } - - for (const auto& [key, _] : m_subparsers) - { - aligner += '{'; - aligner += key; - aligner += '}'; - aligner += ' '; - } - - hashmap_t> singleFlags; - std::vector> compoundFlags; - - for (const auto& [key, value] : m_flag_arguments) - { - const argument_string_t arg{key, allowed_flag_prefixes}; - if (arg.get_flag().size() == 1) - { - if (std::holds_alternative(value->m_nargs) && std::get(value->m_nargs) == 0) - singleFlags[arg.get_flag()].emplace_back(arg.get_name()); - else - compoundFlags.emplace_back(arg, value); - } - else - compoundFlags.emplace_back(arg, value); - } - - for (const auto& [i, kv] : enumerate(singleFlags)) - { - const auto& [key, value] = kv; - aligner += '['; - aligner += key; - for (const auto& name : value) - aligner += name; - aligner += ']'; - aligner += ' '; - } - - for (const auto& [i, kv] : enumerate(compoundFlags)) - { - const auto& name = kv.first; - const auto& builder = kv.second; - aligner += builder->m_required ? '<' : '['; - aligner += name.get_argument(); - auto lambda = [&]() - { - aligner += ' '; - aligner += builder->m_metavar.value_or(string::toUpperCase(name.get_name())); - }; - std::visit(lambda_visitor{ - [&](const nargs_t type) - { - lambda(); - switch (type) - { - case nargs_t::IF_POSSIBLE: - break; - case nargs_t::ALL: - case nargs_t::ALL_AT_LEAST_ONE: - aligner += "..."; - break; - } - }, - [&](const int argc) - { - for (int j = 0; j < argc; j++) - lambda(); - } - }, builder->m_nargs); - aligner += builder->m_required ? '>' : ']'; - aligner += ' '; - } - - for (const auto& [i, pair] : enumerate(m_positional_arguments)) - { - const auto& [name, _] = pair; - aligner += '<'; - aligner += name; - aligner += '>'; - if (i != m_positional_arguments.size() - 1) - aligner += ' '; - } - - m_usage = aligner.str(); - } - std::cout << "Usage: " << *m_usage << std::endl; - } - - void argument_parser_t::print_version() const - { - std::cout << m_name.value_or("NO NAME") << " " << m_version.value_or("NO VERSION") << std::endl; - } - - void argument_parser_t::handle_compound_flags(hashset_t& found_flags, argument_storage_t& parsed_args, - argument_consumer_t& consumer, const argument_string_t& arg) - { - // i kinda hate this, TODO? - std::vector compound_flags; - if (arg.get_flag().size() == 1) - { - for (const auto c : arg.get_name()) - compound_flags.emplace_back(std::string{arg.get_flag()} + c); - } - else - { - if (arg.get_flag().size() > 2) - throw detail::bad_flag(make_string("Error: Flag '", arg.get_argument(), "' is too long!")); - compound_flags.emplace_back(arg.get_argument()); - } - - for (const auto& flag_key : compound_flags) - { - const auto flag = m_flag_arguments.find(flag_key); - if (flag == m_flag_arguments.end()) - throw detail::bad_flag(make_string("Error: Unknown flag: ", flag_key)); - found_flags.insert(flag_key); - parse_flag(parsed_args, consumer, flag_key); - } - } - - void argument_parser_t::parse_flag(argument_storage_t& parsed_args, argument_consumer_t& consumer, const std::string_view arg) - { - auto flag = m_flag_arguments.find(arg)->second; - const auto dest = flag->m_dest.value_or(std::string{arg}); - std::visit(lambda_visitor{ - [&parsed_args, &consumer, &dest, &flag, arg](const nargs_t arg_enum) - { - switch (arg_enum) - { - case nargs_t::IF_POSSIBLE: - if (consumer.can_consume() && !consumer.peek().is_flag()) - flag->m_dest_func(dest, parsed_args, consumer.consume().get_argument()); - else - { - if (flag->m_const_value) - parsed_args.m_data.insert({dest, *flag->m_const_value}); - } - break; - case nargs_t::ALL_AT_LEAST_ONE: - if (!consumer.can_consume()) - throw detail::missing_argument_error( - make_string("Error expected at least one argument to be consumed by '", arg, '\'')); - [[fallthrough]]; - case nargs_t::ALL: - auto result = consume_until_flag_or_end(consumer, flag->m_choices ? &*flag->m_choices : nullptr); - if (!result) - throw detail::bad_choice_error(make_string('\'', consumer.peek().get_argument(), - "' is not a valid choice for argument '", arg, - "'! Expected one of ", result.error())); - flag->m_dest_vec_func(dest, parsed_args, result.value()); - break; - } - }, - [&parsed_args, &consumer, &dest, &flag, arg, this](const i32 argc) - { - const auto args = consume_argc(argc, consumer, flag->m_choices ? &*flag->m_choices : nullptr, arg); - - switch (flag->m_action) - { - case action_t::STORE: - if (argc == 0) - throw detail::missing_argument_error( - make_string("Argument '", arg, "'s action is store but takes in no arguments?")); - if (argc == 1) - flag->m_dest_func(dest, parsed_args, args.front()); - else - throw detail::unexpected_argument_error(make_string("Argument '", arg, - "'s action is store but takes in more than one argument. " - "Did you mean to use action_t::APPEND or action_t::EXTEND?")); - break; - case action_t::APPEND: - case action_t::EXTEND: - if (argc == 0) - throw detail::missing_argument_error( - make_string("Argument '", arg, "'s action is append or extend but takes in no arguments.")); - flag->m_dest_vec_func(dest, parsed_args, args); - break; - case action_t::APPEND_CONST: - if (argc != 0) - throw detail::unexpected_argument_error( - make_string("Argument '", arg, "'s action is append const but takes in arguments.")); - if (!flag->m_const_value) - { - throw detail::missing_value_error( - make_string("Append const chosen as an action but const value not provided for argument '", arg, - '\'')); - } - if (parsed_args.contains(dest)) - { - auto& data = parsed_args.m_data[dest]; - std::visit(detail::arg_meta_type_helper_t::make_visitor( - [arg](auto& primitive) - { - throw detail::type_error(make_string( - "Invalid type for argument '", arg, "' expected list type, found '", - blt::type_string(), "' with value ", primitive)); - }, - [&flag, arg](auto& vec) - { - using type = typename meta::remove_cvref_t::value_type; - if (!std::holds_alternative(*flag->m_const_value)) - { - throw detail::type_error(make_string("Constant value for argument '", arg, - "' type doesn't match values already present! Expected to be of type '", - blt::type_string(), "'!")); - } - vec.push_back(std::get(*flag->m_const_value)); - }), data); - } - else - { - std::visit(detail::arg_meta_type_helper_t::make_visitor( - [&parsed_args, &dest](auto& primitive) - { - std::vector> vec; - vec.emplace_back(primitive); - parsed_args.m_data.emplace(dest, std::move(vec)); - }, - [](auto&) - { - throw detail::type_error("Append const should not be a list type!"); - }), *flag->m_const_value); - } - break; - case action_t::STORE_CONST: - if (argc != 0) - { - print_usage(); - throw detail::unexpected_argument_error( - make_string("Argument '", arg, "' is store const but called with an argument.")); - } - if (!flag->m_const_value) - throw detail::missing_value_error( - make_string("Argument '", arg, "' is store const, but const storage has no value.")); - parsed_args.m_data.emplace(dest, *flag->m_const_value); - break; - case action_t::STORE_TRUE: - if (argc != 0) - { - print_usage(); - throw detail::unexpected_argument_error("Store true flag called with an argument."); - } - parsed_args.m_data.emplace(dest, true); - break; - case action_t::STORE_FALSE: - if (argc != 0) - { - print_usage(); - throw detail::unexpected_argument_error("Store false flag called with an argument."); - } - parsed_args.m_data.insert({dest, false}); - break; - case action_t::COUNT: - if (parsed_args.m_data.contains(dest)) - { - auto visitor = detail::arg_meta_type_helper_t::make_visitor( - [](auto& primitive) -> detail::arg_data_t - { - using type = meta::remove_cvref_t; - if constexpr (std::is_convertible_v) - { - return primitive + static_cast(1); - } - else - throw detail::type_error( - "Error: count called but stored type is " + blt::type_string()); - }, - [](auto&) -> detail::arg_data_t - { - throw detail::type_error("List present on count. This condition doesn't make any sense! " - "(How did we get here, please report this!)"); - } - ); - parsed_args.m_data[dest] = std::visit(visitor, parsed_args.m_data[dest]); - } - else // I also hate this! - flag->m_dest_func(dest, parsed_args, "1"); - break; - case action_t::HELP: - print_help(); - std::exit(0); - case action_t::VERSION: - print_version(); - std::exit(0); - } - } - }, flag->m_nargs); - } - - void argument_parser_t::parse_positional(argument_storage_t& parsed_args, argument_consumer_t& consumer, argument_positional_storage_t& storage, - const std::string_view arg) - { - if (!storage.has_positional()) - throw detail::missing_argument_error(make_string("Error: '", arg, "' positional argument does not match any defined for this parser")); - auto& positional = storage.next(); - const auto dest = positional.m_dest.value_or(std::string{arg}); - std::visit(lambda_visitor{ - [&consumer, &positional, &dest, &parsed_args, arg](const nargs_t arg_enum) - { - switch (arg_enum) - { - case nargs_t::IF_POSSIBLE: - throw detail::bad_positional( - "Positional argument asked to consume if possible. We do not consider this to be a valid ask."); - case nargs_t::ALL_AT_LEAST_ONE: - if (!consumer.can_consume()) - throw detail::missing_argument_error( - make_string("Error expected at least one argument to be consumed by '", arg, '\'')); - [[fallthrough]]; - case nargs_t::ALL: - auto result = consume_until_flag_or_end( - consumer, positional.m_choices ? &*positional.m_choices : nullptr); - if (!result) - throw detail::bad_choice_error(make_string('\'', consumer.peek().get_argument(), - "' is not a valid choice for argument '", arg, - "'! Expected one of ", result.error())); - positional.m_dest_vec_func(dest, parsed_args, result.value()); - break; - } - }, - [this, &consumer, &positional, &dest, &parsed_args, arg](const i32 argc) - { - const auto args = consume_argc(argc, consumer, positional.m_choices ? &*positional.m_choices : nullptr, arg); - - switch (positional.m_action) - { - case action_t::STORE: - if (argc == 0) - throw detail::missing_argument_error( - make_string("Argument '", arg, "'s action is store but takes in no arguments?")); - if (argc == 1) - positional.m_dest_func(dest, parsed_args, args.front()); - else - throw detail::unexpected_argument_error(make_string("Argument '", arg, - "'s action is store but takes in more than one argument. " - "Did you mean to use action_t::APPEND or action_t::EXTEND?")); - break; - case action_t::APPEND: - case action_t::EXTEND: - if (argc == 0) - throw detail::missing_argument_error( - make_string("Argument '", arg, "'s action is append or extend but takes in no arguments.")); - positional.m_dest_vec_func(dest, parsed_args, args); - break; - case action_t::APPEND_CONST: - throw detail::bad_positional("action_t::APPEND_CONST does not make sense for positional arguments"); - case action_t::STORE_CONST: - throw detail::bad_positional("action_t::STORE_CONST does not make sense for positional arguments"); - case action_t::STORE_TRUE: - throw detail::bad_positional("action_t::STORE_TRUE does not make sense for positional arguments"); - case action_t::STORE_FALSE: - throw detail::bad_positional("action_t::STORE_FALSE does not make sense for positional arguments"); - case action_t::COUNT: - throw detail::bad_positional("action_t::COUNT does not make sense for positional arguments"); - case action_t::HELP: - print_help(); - std::exit(0); - case action_t::VERSION: - print_version(); - std::exit(0); - } - } - }, positional.m_nargs); - } - - void argument_parser_t::handle_missing_and_default_args(hashmap_t& arguments, - const hashset_t& found, argument_storage_t& parsed_args, - const std::string_view type) - { - for (const auto& [key, value] : arguments) - { - if (!found.contains(key)) - { - if (value->m_required) - throw detail::missing_argument_error(make_string("Error: ", type, " argument '", key, - "' was not found but is required by the program")); - auto dest = value->m_dest.value_or(std::string{key}); - if (value->m_default_value && !parsed_args.contains(dest)) - parsed_args.m_data.emplace(dest, *value->m_default_value); - } - } - } - - expected, std::string> argument_parser_t::consume_until_flag_or_end(argument_consumer_t& consumer, - hashset_t* allowed_choices) - { - std::vector args; - while (consumer.can_consume() && !consumer.peek().is_flag()) - { - if (allowed_choices != nullptr && !allowed_choices->contains(consumer.peek().get_argument())) - { - std::string valid_choices = "{"; - for (const auto& [i, choice] : enumerate(*allowed_choices)) - { - valid_choices += choice; - if (i != allowed_choices->size() - 1) - valid_choices += ", "; - } - valid_choices += "}"; - return unexpected(valid_choices); - } - args.emplace_back(consumer.consume().get_argument()); - } - return args; - } - - std::vector argument_parser_t::consume_argc(const int argc, argument_consumer_t& consumer, - hashset_t* allowed_choices, - const std::string_view arg) - { - std::vector args; - for (i32 i = 0; i < argc; ++i) - { - if (!consumer.can_consume()) - { - throw detail::missing_argument_error( - make_string("Expected ", argc, " arguments to be consumed by '", arg, "' but found ", i)); - } - if (consumer.peek().is_flag()) - { - std::cout << "Warning: arg '" << arg << "' expects " << argc << - " arguments to be consumed but we found a flag '" << consumer.peek(). - get_argument() << - "'. We will comply as this may be desired if this argument is a file." << std::endl; - } - if (allowed_choices != nullptr && !allowed_choices->contains(consumer.peek().get_argument())) - { - std::string valid_choices = "{"; - for (const auto& [i, choice] : enumerate(*allowed_choices)) - { - valid_choices += choice; - if (i != allowed_choices->size() - 1) - valid_choices += ", "; - } - valid_choices += "}"; - throw detail::bad_choice_error(make_string('\'', consumer.peek().get_argument(), - "' is not a valid choice for argument '", arg, - "'! Expected one of ", valid_choices)); - } - args.emplace_back(consumer.consume().get_argument()); - } - if (args.size() != static_cast(argc)) - { - throw std::runtime_error( - "This error condition should not be possible. " - "Args consumed didn't equal the arguments requested and previous checks didn't fail. " - "Please report as an issue on the GitHub"); - } - return args; - } - - std::pair argument_subparser_t::parse(argument_consumer_t& consumer) - { - if (!consumer.can_consume()) - throw detail::missing_argument_error("Subparser requires an argument."); - const auto key = consumer.consume(); - if (key.is_flag()) - throw detail::subparse_error(key.get_argument(), to_vec(get_allowed_strings())); - const auto it = m_aliases.find(key.get_name()); - if (it == m_aliases.end()) - throw detail::subparse_error(key.get_argument(), to_vec(get_allowed_strings())); - it->second->m_name = m_parent->m_name; - m_last_parsed_parser = key.get_name(); - return {key, it->second->parse(consumer)}; - } - - hashmap_t> argument_subparser_t::get_allowed_strings() const - { - hashmap_t> map; - for (const auto& [key, value] : m_aliases) - map[value].emplace_back(key); - return map; - } - - std::vector> argument_subparser_t::to_vec(const hashmap_t>& map) - { - std::vector> vec; - for (const auto& [key, value] : map) - vec.push_back(value); - return vec; - } - - namespace detail - { - // Unit Tests for class argument_string_t - // Test Case 1: Ensure the constructor handles flags correctly - void test_argument_string_t_flag_basic(const hashset_t& prefixes) - { - const argument_string_t arg("-f", prefixes); - BLT_ASSERT(arg.is_flag() && "Expected argument to be identified as a flag."); - BLT_ASSERT(arg.value() == "f" && "Flag value should match the input string."); - } - - // Test Case 2: Ensure the constructor handles long flags correctly - void test_argument_string_t_long_flag(const hashset_t& prefixes) - { - const argument_string_t arg("--file", prefixes); - BLT_ASSERT(arg.is_flag() && "Expected argument to be identified as a flag."); - BLT_ASSERT(arg.value() == "file" && "Long flag value should match the input string."); - } - - // Test Case 3: Ensure positional arguments are correctly identified - void test_argument_string_t_positional_argument(const hashset_t& prefixes) - { - const argument_string_t arg("filename.txt", prefixes); - BLT_ASSERT(!arg.is_flag() && "Expected argument to be identified as positional."); - BLT_ASSERT(arg.value() == "filename.txt" && "Positional argument value should match the input string."); - } - - // Test Case 5: Handle an empty string - void test_argument_string_t_empty_input(const hashset_t& prefixes) - { - const argument_string_t arg("", prefixes); - BLT_ASSERT(!arg.is_flag() && "Expected an empty input to be treated as positional, not a flag."); - BLT_ASSERT(arg.value().empty() && "Empty input should have an empty value."); - } - - // Test Case 6: Handle edge case of a single hyphen (`-`) which might be ambiguous - void test_argument_string_t_single_hyphen(const hashset_t& prefixes) - { - const argument_string_t arg("-", prefixes); - BLT_ASSERT(arg.is_flag() && "Expected single hyphen (`-`) to be treated as a flag."); - BLT_ASSERT(arg.value().empty() && "Single hyphen flag should have empty value."); - BLT_ASSERT(arg.get_flag() == "-" && "Single hyphen flag should match the input string."); - } - - // Test Case 8: Handle arguments with prefix only (like "--") - void test_argument_string_t_double_hyphen(const hashset_t& prefixes) - { - const argument_string_t arg("--", prefixes); - BLT_ASSERT(arg.is_flag() && "Double hyphen ('--') should be treated as a flag."); - BLT_ASSERT(arg.value().empty() && "Double hyphen flag should have empty value."); - BLT_ASSERT(arg.get_flag() == "--" && "Double hyphen value should match the input string."); - } - - // Test Case 9: Validate edge case of an argument with spaces - void test_argument_string_t_with_spaces(const hashset_t& prefixes) - { - const argument_string_t arg(" ", prefixes); - BLT_ASSERT(!arg.is_flag() && "Arguments with spaces should not be treated as flags."); - BLT_ASSERT(arg.value() == " " && "Arguments with spaces should match the input string."); - } - - // Test Case 10: Validate arguments with numeric characters - void test_argument_string_t_numeric_flag(const hashset_t& prefixes) - { - const argument_string_t arg("-123", prefixes); - BLT_ASSERT(arg.is_flag() && "Numeric flags should still be treated as flags."); - BLT_ASSERT(arg.value() == "123" && "Numeric flag value should match the input string."); - } - - // Test Case 11: Ensure the constructor handles '+' flag correctly - void test_argument_string_t_plus_flag_basic(const hashset_t& prefixes) - { - const argument_string_t arg("+f", prefixes); - BLT_ASSERT(arg.is_flag() && "Expected argument to be identified as a flag."); - BLT_ASSERT(arg.value() == "f" && "Plus flag value should match the input string."); - } - - // Test Case 13: Handle edge case of a single plus (`+`) which might be ambiguous - void test_argument_string_t_single_plus(const hashset_t& prefixes) - { - const argument_string_t arg("+", prefixes); - BLT_ASSERT(arg.is_flag() && "Expected single plus (`+`) to be treated as a flag."); - BLT_ASSERT(arg.value().empty() && "Single plus flag should have empty value."); - BLT_ASSERT(arg.get_flag() == "+" && "Single plus flag should match the input string."); - } - - // Test Case 14: Handle arguments with prefix only (like '++') - void test_argument_string_t_double_plus(const hashset_t& prefixes) - { - const argument_string_t arg("++", prefixes); - BLT_ASSERT(arg.is_flag() && "Double plus ('++') should be treated as a flag."); - BLT_ASSERT(arg.value().empty() && "Double plus flag should have empty value."); - BLT_ASSERT(arg.get_flag() == "++" && "Double plus value should match the input string."); - } - - - void run_all_tests_argument_string_t() - { - const hashset_t prefixes = {'-', '+'}; - test_argument_string_t_flag_basic(prefixes); - test_argument_string_t_long_flag(prefixes); - test_argument_string_t_positional_argument(prefixes); - test_argument_string_t_empty_input(prefixes); - test_argument_string_t_single_hyphen(prefixes); - test_argument_string_t_double_hyphen(prefixes); - test_argument_string_t_with_spaces(prefixes); - test_argument_string_t_numeric_flag(prefixes); - test_argument_string_t_plus_flag_basic(prefixes); - test_argument_string_t_single_plus(prefixes); - test_argument_string_t_double_plus(prefixes); - } - - void test_argparse_empty() - { - const std::vector argv{"./program"}; - argument_parser_t parser; - const auto args = parser.parse(argv); - BLT_ASSERT(args.size() == 0 && "Empty argparse should have no args on output"); - } - - void test_single_flag_prefixes() - { - argument_parser_t parser; - parser.add_flag("-a").set_action(action_t::STORE_TRUE); - parser.add_flag("+b").set_action(action_t::STORE_FALSE); - parser.add_flag("/c").as_type().set_action(action_t::STORE); - - const std::vector args = {"./program", "-a", "+b", "/c", "42"}; - const auto parsed_args = parser.parse(args); - - BLT_ASSERT(parsed_args.get("-a") == true && "Flag '-a' should store `true`"); - BLT_ASSERT(parsed_args.get("+b") == false && "Flag '+b' should store `false`"); - BLT_ASSERT(parsed_args.get("/c") == 42 && "Flag '/c' should store the value 42"); - } - - // Test: Invalid flag prefixes - void test_invalid_flag_prefixes() - { - argument_parser_t parser; - parser.add_flag("-a"); - parser.add_flag("+b"); - parser.add_flag("/c"); - - const std::vector args = {"./program", "!d", "-a"}; - try - { - parser.parse(args); - BLT_ASSERT(false && "Parsing should fail with invalid flag prefix '!'"); - } - catch (...) - { - BLT_ASSERT(true && "Correctly threw on bad flag prefix"); - } - } - - void test_compound_flags() - { - argument_parser_t parser; - parser.add_flag("-v").as_type().set_action(action_t::COUNT); - - const std::vector args = {"./program", "-vvv"}; - const auto parsed_args = parser.parse(args); - - BLT_ASSERT(parsed_args.get("-v") == 3 && "Flag '-v' should count occurrences in compound form"); - } - - void test_combination_of_valid_and_invalid_flags() - { - using namespace argparse; - - argument_parser_t parser; - parser.add_flag("-x").as_type(); - parser.add_flag("/y").as_type(); - - const std::vector args = {"./program", "-x", "10", "!z", "/y", "value"}; - try - { - parser.parse(args); - BLT_ASSERT(false && "Parsing should fail due to invalid flag '!z'"); - } - catch (...) - { - BLT_ASSERT(true && "Correctly threw an exception for invalid flag"); - } - } - - void test_flags_with_different_actions() - { - using namespace argparse; - - argument_parser_t parser; - parser.add_flag("-k").as_type().set_action(action_t::STORE); // STORE action - parser.add_flag("-t").as_type().set_action(action_t::STORE_CONST).set_const(999); // STORE_CONST action - parser.add_flag("-f").set_action(action_t::STORE_FALSE); // STORE_FALSE action - parser.add_flag("-c").set_action(action_t::STORE_TRUE); // STORE_TRUE action - - const std::vector args = {"./program", "-k", "100", "-t", "-f", "-c"}; - const auto parsed_args = parser.parse(args); - - BLT_ASSERT(parsed_args.get("-k") == 100 && "Flag '-k' should store 100"); - BLT_ASSERT(parsed_args.get("-t") == 999 && "Flag '-t' should store a const value of 999"); - BLT_ASSERT(parsed_args.get("-f") == false && "Flag '-f' should store `false`"); - BLT_ASSERT(parsed_args.get("-c") == true && "Flag '-c' should store `true`"); - } - - // Helper function to simulate argument parsing for nargs tests - bool parse_arguments(const std::vector& args, const nargs_v expected_nargs) - { - argument_parser_t parser; - - std::vector arg_strings; - arg_strings.reserve(args.size()); - for (const auto& arg : args) - arg_strings.emplace_back(arg, parser.get_allowed_flag_prefixes()); - argument_consumer_t consumer{arg_strings}; - - parser.add_positional("positional").set_nargs(expected_nargs); - try - { - auto parsed_args = parser.parse(consumer); - return consumer.remaining() == 0; - } - catch (const std::exception&) - { - return false; - } - } - - // Test case for nargs = 0 - void test_nargs_0() - { - std::cout << "[Running Test: test_nargs_0]\n"; - - // Valid case: No arguments - const std::vector valid_args = {"./program"}; - BLT_ASSERT(!parse_arguments(valid_args, 0) && "nargs=0: Should fail"); - - // Invalid case: 1 argument - const std::vector invalid_args = {"./program", "arg1"}; - BLT_ASSERT(!parse_arguments(invalid_args, 0) && "nargs=0: Should not accept any arguments"); - - std::cout << "Success: test_nargs_0\n"; - } - - // Test case for nargs = 1 - void test_nargs_1() - { - std::cout << "[Running Test: test_nargs_1]\n"; - - // Valid case: 1 argument - const std::vector valid_args = {"./program", "arg1"}; - BLT_ASSERT(parse_arguments(valid_args, 1) && "nargs=1: Should accept exactly 1 argument"); - - // Invalid case: 0 arguments - const std::vector invalid_args_0 = {"./program"}; - BLT_ASSERT(!parse_arguments(invalid_args_0, 1) && "nargs=1: Should not accept 0 arguments"); - - // Invalid case: 2 arguments - const std::vector invalid_args_2 = {"./program", "arg1", "arg2"}; - BLT_ASSERT(!parse_arguments(invalid_args_2, 1) && "nargs=1: Should not accept more than 1 argument"); - - std::cout << "Success: test_nargs_1\n"; - } - - // Test case for nargs = 2 - void test_nargs_2() - { - std::cout << "[Running Test: test_nargs_2]\n"; - - // Valid case: 2 arguments - const std::vector valid_args = {"./program", "arg1", "arg2"}; - BLT_ASSERT(!parse_arguments(valid_args, 2) && "nargs=2: Should fail as action is store"); - - // Invalid case: 0 arguments - const std::vector invalid_args_0 = {"./program"}; - BLT_ASSERT(!parse_arguments(invalid_args_0, 2) && "nargs=2: Should not accept 0 arguments"); - - // Invalid case: 1 argument - const std::vector invalid_args_1 = {"./program", "arg1"}; - BLT_ASSERT(!parse_arguments(invalid_args_1, 2) && "nargs=2: Should not accept less than 2 arguments"); - - // Invalid case: 3 arguments - const std::vector invalid_args_3 = {"./program", "arg1", "arg2", "arg3"}; - BLT_ASSERT(!parse_arguments(invalid_args_3, 2) && "nargs=2: Should not accept more than 2 arguments"); - - std::cout << "Success: test_nargs_2\n"; - } - - void test_nargs_all() - { - std::cout << "[Running Test: test_nargs_all]\n"; - - // Valid case: No arguments - const std::vector valid_args_0 = {"./program"}; - BLT_ASSERT(!parse_arguments(valid_args_0, argparse::nargs_t::ALL) && - "nargs=ALL: No arguments present. Should fail.)"); - - // Valid case: Multiple arguments - const std::vector valid_args_2 = {"./program", "arg1", "arg2"}; - BLT_ASSERT(parse_arguments(valid_args_2, argparse::nargs_t::ALL) && - "nargs=ALL: Should accept all remaining arguments"); - - // Valid case: Many arguments - const std::vector valid_args_many = {"./program", "arg1", "arg2", "arg3", "arg4"}; - BLT_ASSERT(parse_arguments(valid_args_many, argparse::nargs_t::ALL) && - "nargs=ALL: Should accept all remaining arguments"); - - std::cout << "Success: test_nargs_all\n"; - } - - // Test case for nargs_t::ALL_AT_LEAST_ONE - void test_nargs_all_at_least_one() - { - std::cout << "[Running Test: test_nargs_all_at_least_one]\n"; - - // Valid case: 1 argument - const std::vector valid_args_1 = {"./program", "arg1"}; - BLT_ASSERT(parse_arguments(valid_args_1, argparse::nargs_t::ALL_AT_LEAST_ONE) && - "nargs=ALL_AT_LEAST_ONE: Should accept at least one argument and consume it"); - - // Valid case: Multiple arguments - const std::vector valid_args_3 = {"./program", "arg1", "arg2", "arg3"}; - BLT_ASSERT(parse_arguments(valid_args_3, argparse::nargs_t::ALL_AT_LEAST_ONE) && - "nargs=ALL_AT_LEAST_ONE: Should accept at least one argument and consume all remaining arguments"); - - // Invalid case: No arguments - const std::vector invalid_args_0 = {"./program"}; - BLT_ASSERT(!parse_arguments(invalid_args_0, argparse::nargs_t::ALL_AT_LEAST_ONE) && - "nargs=ALL_AT_LEAST_ONE: Should reject if no arguments are provided"); - - std::cout << "Success: test_nargs_all_at_least_one\n"; - } - - void run_combined_flag_test() - { - std::cout << "[Running Test: run_combined_flag_test]\n"; - argument_parser_t parser; - - parser.add_flag("-a").set_action(action_t::STORE_TRUE); - parser.add_flag("--deep").set_action(action_t::STORE_FALSE); - parser.add_flag("-b", "--combined").set_action(action_t::STORE_CONST).set_const(50); - parser.add_flag("--append").set_action(action_t::APPEND).as_type(); - parser.add_flag("--required").set_required(true); - parser.add_flag("--default").set_default("I am a default value"); - parser.add_flag("-t").set_action(action_t::APPEND_CONST).set_dest("test").set_const(5); - parser.add_flag("-g").set_action(action_t::APPEND_CONST).set_dest("test").set_const(10); - parser.add_flag("-e").set_action(action_t::APPEND_CONST).set_dest("test").set_const(15); - parser.add_flag("-f").set_action(action_t::APPEND_CONST).set_dest("test").set_const(20); - parser.add_flag("-d").set_action(action_t::APPEND_CONST).set_dest("test").set_const(25); - parser.add_flag("--end").set_action(action_t::EXTEND).set_dest("wow").as_type(); - - const auto a1 = make_arguments("-a", "--required", "hello"); - const auto r1 = parser.parse(a1); - BLT_ASSERT(r1.get("-a") == true && "Flag '-a' should store true"); - BLT_ASSERT(r1.get("--default") == "I am a default value" && "Flag '--default' should store default value"); - BLT_ASSERT(r1.get("--required") == "hello" && "Flag '--required' should store 'hello'"); - - const auto a2 = make_arguments("-a", "--deep", "--required", "soft"); - const auto r2 = parser.parse(a2); - BLT_ASSERT(r2.get("-a") == true && "Flag '-a' should store true"); - BLT_ASSERT(r2.get("--deep") == false && "Flag '--deep' should store false"); - BLT_ASSERT(r2.get("--required") == "soft" && "Flag '--required' should store 'soft'"); - - const auto a3 = make_arguments("--required", "silly", "--combined", "-t", "-f", "-e"); - const auto r3 = parser.parse(a3); - BLT_ASSERT((r3.get>("test") == std::vector{5, 20, 15}) && "Flags should add to vector of {5, 20, 15}"); - BLT_ASSERT(r3.get("-b") == 50 && "Combined flag should store const of 50"); - - const auto a4 = make_arguments("--required", "crazy", "--end", "10", "12.05", "68.11", "100.00", "200532", "-d", "-t", "-g", "-e", "-f"); - const auto r4 = parser.parse(a4); - BLT_ASSERT( - (r4.get>("test") == std::vector{25, 5, 10, 15, 20}) && - "Expected test vector to be filled with all arguments in order of flags"); - BLT_ASSERT( - (r4.get>("wow") == std::vector{10, 12.05, 68.11, 100.00, 200532}) && - "Extend vector expected to contain all elements"); - - std::cout << "Success: run_combined_flag_test\n"; - } - - void run_choice_test() - { - std::cout << "[Running Test: run_choice_test]\n"; - argument_parser_t parser; - - parser.add_flag("--hello").set_choices("silly", "crazy", "soft"); - parser.add_positional("iam").set_choices("different", "choices", "for", "me"); - - const auto a1 = make_arguments("--hello", "crazy", "different"); - const auto r1 = parser.parse(a1); - BLT_ASSERT(r1.get("--hello") == "crazy" && "Flag '--hello' should store 'crazy'"); - BLT_ASSERT(r1.get("iam") == "different" && "Positional 'iam' should store 'different'"); - - const auto a2 = make_arguments("--hello", "not_an_option", "different"); - try - { - parser.parse(a2); - BLT_ASSERT(false && "Parsing should fail due to invalid flag '--hello'"); - } - catch (...) - { - } - - const auto a3 = make_arguments("--hello", "crazy", "not_a_choice"); - try - { - parser.parse(a3); - BLT_ASSERT(false && "Parsing should fail due to invalid positional 'iam'"); - } - catch (...) - { - } - - std::cout << "Success: run_choice_test\n"; - } - - void run_subparser_test() - { - std::cout << "[Running Test: run_subparser_test]\n"; - argument_parser_t parser; - - parser.add_flag("--open").make_flag(); - - const auto subparser = parser.add_subparser("mode"); - - const auto n1 = subparser->add_parser("n1"); - n1->add_flag("--silly").make_flag(); - n1->add_positional("path"); - - const auto n2 = subparser->add_parser("n2"); - n2->add_flag("--crazy").make_flag(); - n2->add_positional("path"); - n2->add_positional("output"); - - const auto n3 = subparser->add_parser("n3"); - n3->add_flag("--deep").make_flag(); - - const auto a1 = make_arguments("n1", "--silly"); - try - { - parser.parse(a1); - BLT_ASSERT(false && "Subparser should throw an error when positional not supplied"); - } - catch (...) - { - } - - const auto a2 = make_arguments("--open"); - try - { - parser.parse(a2); - BLT_ASSERT(false && "Subparser should throw an error when no subparser is supplied"); - } - catch (...) - { - } - - const auto a3 = make_arguments("n1", "--silly", "path_n1"); - const auto r3 = parser.parse(a3); - BLT_ASSERT(r3.get("--open") == false && "Flag '--open' should default to false"); - BLT_ASSERT(r3.get("mode") == "n1" && "Subparser should store 'n1'"); - BLT_ASSERT(r3.get("path") == "path_n1" && "Subparser path should be 'path'"); - - const auto a4 = make_arguments("n2", "--crazy", "path"); - - try - { - parser.parse(a4); - BLT_ASSERT(false && "Subparser should throw an error when second positional is not supplied"); - } - catch (...) - { - } - - const auto a5 = make_arguments("--open", "n2", "path_n2", "output_n2"); - const auto r5 = parser.parse(a5); - BLT_ASSERT(r5.get("--open") == true && "Flag '--open' should store true"); - BLT_ASSERT(r5.get("mode") == "n2" && "Subparser should store 'n2'"); - BLT_ASSERT(r5.get("path") == "path_n2" && "Subparser path should be 'path'"); - BLT_ASSERT(r5.get("output") == "output_n2" && "Subparser output should be 'output'"); - - const auto a6 = make_arguments("not_an_option", "silly"); - - try - { - parser.parse(a6); - BLT_ASSERT(false && "Subparser should throw an error when first positional is not a valid subparser"); - } - catch (const std::exception&) - { - } - - const auto a7 = make_arguments("n3"); - const auto r7 = parser.parse(a7); - BLT_ASSERT(r7.get("mode") == "n3" && "Subparser should store 'n3'"); - - std::cout << "Success: run_subparser_test\n"; - } - - void run_argparse_flag_tests() - { - test_single_flag_prefixes(); - test_invalid_flag_prefixes(); - test_compound_flags(); - test_combination_of_valid_and_invalid_flags(); - test_flags_with_different_actions(); - run_combined_flag_test(); - run_choice_test(); - run_subparser_test(); - } - - void run_all_nargs_tests() - { - test_nargs_0(); - test_nargs_1(); - test_nargs_2(); - test_nargs_all(); - test_nargs_all_at_least_one(); - std::cout << "All nargs tests passed successfully.\n"; - } - - void test() - { - run_all_tests_argument_string_t(); - test_argparse_empty(); - run_argparse_flag_tests(); - run_all_nargs_tests(); - } - - [[nodiscard]] std::string subparse_error::error_string() const - { - std::string message = "Subparser Error: "; - message += m_found_string; - message += " is not a valid command. Allowed commands are: {"; - for (const auto [i, allowed_string] : enumerate(m_allowed_strings)) - { - if (allowed_string.size() > 1) - message += '['; - for (const auto [j, alias] : enumerate(allowed_string)) - { - message += alias; - if (static_cast(j) < static_cast(allowed_string.size()) - 2) - message += ", "; - else if (static_cast(j) < static_cast(allowed_string.size()) - 1) - message += ", or "; - } - if (allowed_string.size() > 1) - message += ']'; - if (i != m_allowed_strings.size() - 1) - message += ", "; - } - message += "}"; - return message; - } - } + } + } + + if (!m_flag_arguments.empty()) + { + help += "Options:"; + help.newline(); + hashmap_t> same_flags; + for (const auto& [key, value] : m_flag_arguments) + same_flags[value].emplace_back(key); + auto mark = help.mark(); + for (const auto& [builder, flag_list] : same_flags) + { + // find max size and align? + help += '\t'; + for (const auto& [i, flag] : enumerate(flag_list)) + { + help += flag; + if (i != flag_list.size() - 1) + help += ", "; + } + const argument_string_t arg{flag_list.front(), allowed_flag_prefixes}; + auto metavar = builder->m_metavar.value_or(string::toUpperCase(arg.get_name())); + auto lambda = [&]() { + help += ' '; + help += metavar; + }; + std::visit(lambda_visitor{ + [&](const nargs_t type) { + lambda(); + switch (type) + { + case nargs_t::IF_POSSIBLE: + break; + case nargs_t::ALL: + case nargs_t::ALL_AT_LEAST_ONE: + help += "..."; + break; + } + }, + [&](const int argc) { + if (argc == 0) + return; + lambda(); + if (argc > 1) + { + help += "... x"; + help += std::to_string(argc); + } + } + }, builder->m_nargs); + help.newline(); + } + mark.align(4); + for (auto zipped_flags : mark.iter().zip(same_flags)) + { + auto& str = std::get<0>(zipped_flags); + auto& [builder, flag_list] = std::get<1>(zipped_flags); + str += builder->m_help.value_or(""); + if (builder->m_default_value && !(builder->m_action == action_t::STORE_TRUE || builder->m_action == action_t::STORE_FALSE)) + { + if (!std::isblank(str.str().back())) + str += " "; + str += "(Default: '"; + std::visit(detail::arg_meta_type_helper_t::make_visitor([&](auto& value) { + str += value; + }, [&](auto& vec) { + if constexpr (!std::is_same_v>, std::vector>) + { + str += '['; + for (const auto& [i, v] : enumerate(vec)) + { + str += v; + if (i != vec.size() - 1) + str += ", "; + } + str += ']'; + } + }), *builder->m_default_value); + str += "')"; + } + if (builder->m_choices) + { + if (!std::isblank(str.str().back())) + str += " "; + str += "(Choices: "; + for (const auto& [i, v] : enumerate(*builder->m_choices)) + { + str += '\''; + str += v; + str += '\''; + if (i != builder->m_choices->size() - 1) + str += ", "; + } + str += ')'; + } + if (builder->m_required) + { + if (!std::isblank(str.str().back())) + str += " "; + str += "(Required)"; + } + } + } + + std::cout << help.str() << std::endl; + } + + void argument_parser_t::print_usage() + { + if (!m_usage) + { + aligned_printer_t aligner; + aligner += m_name.value_or(""); + aligner += ' '; + + auto parent = m_parent; + while (parent != nullptr) + { + if (!parent->m_last_parsed_parser) + throw detail::missing_value_error( + "Error: Help called on subparser but unable to find parser chain. This condition should be impossible."); + aligner += parent->m_last_parsed_parser.value(); + aligner += ' '; + parent = parent->m_parent->m_parent; + } + + for (const auto& [key, _] : m_subparsers) + { + aligner += '{'; + aligner += key; + aligner += '}'; + aligner += ' '; + } + + hashmap_t> singleFlags; + std::vector> compoundFlags; + + for (const auto& [key, value] : m_flag_arguments) + { + const argument_string_t arg{key, allowed_flag_prefixes}; + if (arg.get_flag().size() == 1) + { + if (std::holds_alternative(value->m_nargs) && std::get(value->m_nargs) == 0) + singleFlags[arg.get_flag()].emplace_back(arg.get_name()); + else + compoundFlags.emplace_back(arg, value); + } else + compoundFlags.emplace_back(arg, value); + } + + for (const auto& [i, kv] : enumerate(singleFlags)) + { + const auto& [key, value] = kv; + aligner += '['; + aligner += key; + for (const auto& name : value) + aligner += name; + aligner += ']'; + aligner += ' '; + } + + for (const auto& [i, kv] : enumerate(compoundFlags)) + { + const auto& name = kv.first; + const auto& builder = kv.second; + aligner += builder->m_required ? '<' : '['; + aligner += name.get_argument(); + auto lambda = [&]() { + aligner += ' '; + aligner += builder->m_metavar.value_or(string::toUpperCase(name.get_name())); + }; + std::visit(lambda_visitor{ + [&](const nargs_t type) { + lambda(); + switch (type) + { + case nargs_t::IF_POSSIBLE: + break; + case nargs_t::ALL: + case nargs_t::ALL_AT_LEAST_ONE: + aligner += "..."; + break; + } + }, + [&](const int argc) { + for (int j = 0; j < argc; j++) + lambda(); + } + }, builder->m_nargs); + aligner += builder->m_required ? '>' : ']'; + aligner += ' '; + } + + for (const auto& [i, pair] : enumerate(m_positional_arguments)) + { + const auto& [name, _] = pair; + aligner += '<'; + aligner += name; + aligner += '>'; + if (i != m_positional_arguments.size() - 1) + aligner += ' '; + } + + m_usage = aligner.str(); + } + std::cout << "Usage: " << *m_usage << std::endl; + } + + void argument_parser_t::print_version() const + { + std::cout << m_name.value_or("NO NAME") << " " << m_version.value_or("NO VERSION") << std::endl; + } + + void argument_parser_t::handle_compound_flags(hashset_t& found_flags, argument_storage_t& parsed_args, argument_consumer_t& consumer, + const argument_string_t& arg) + { + // i kinda hate this, TODO? + std::vector compound_flags; + if (arg.get_flag().size() == 1) + { + for (const auto c : arg.get_name()) + compound_flags.emplace_back(std::string{arg.get_flag()} + c); + } else + { + if (arg.get_flag().size() > 2) + throw detail::bad_flag(make_string("Error: Flag '", arg.get_argument(), "' is too long!")); + compound_flags.emplace_back(arg.get_argument()); + } + + for (const auto& flag_key : compound_flags) + { + const auto flag = m_flag_arguments.find(flag_key); + if (flag == m_flag_arguments.end()) + throw detail::bad_flag(make_string("Error: Unknown flag: ", flag_key)); + found_flags.insert(flag_key); + parse_flag(parsed_args, consumer, flag_key); + } + } + + void argument_parser_t::parse_flag(argument_storage_t& parsed_args, argument_consumer_t& consumer, const std::string_view arg) + { + auto flag = m_flag_arguments.find(arg)->second; + const auto dest = flag->m_dest.value_or(std::string{arg}); + std::visit(lambda_visitor{ + [&parsed_args, &consumer, &dest, &flag, arg](const nargs_t arg_enum) { + switch (arg_enum) + { + case nargs_t::IF_POSSIBLE: + if (consumer.can_consume() && !consumer.peek().is_flag()) + flag->m_dest_func(dest, parsed_args, consumer.consume().get_argument()); + else + { + if (flag->m_const_value) + parsed_args.m_data.insert({dest, *flag->m_const_value}); + } + break; + case nargs_t::ALL_AT_LEAST_ONE: + if (!consumer.can_consume()) + throw detail::missing_argument_error( + make_string("Error expected at least one argument to be consumed by '", arg, '\'')); + [[fallthrough]]; + case nargs_t::ALL: + auto result = consume_until_flag_or_end(consumer, flag->m_choices ? &*flag->m_choices : nullptr); + if (!result) + throw detail::bad_choice_error(make_string('\'', consumer.peek().get_argument(), + "' is not a valid choice for argument '", arg, + "'! Expected one of ", result.error())); + flag->m_dest_vec_func(dest, parsed_args, result.value()); + break; + } + }, + [&parsed_args, &consumer, &dest, &flag, arg, this](const i32 argc) { + const auto args = consume_argc(argc, consumer, flag->m_choices ? &*flag->m_choices : nullptr, arg); + + switch (flag->m_action) + { + case action_t::STORE: + if (argc == 0) + throw detail::missing_argument_error( + make_string("Argument '", arg, "'s action is store but takes in no arguments?")); + if (argc == 1) + flag->m_dest_func(dest, parsed_args, args.front()); + else + throw detail::unexpected_argument_error(make_string("Argument '", arg, + "'s action is store but takes in more than one argument. " + "Did you mean to use action_t::APPEND or action_t::EXTEND?")); + break; + case action_t::APPEND: + case action_t::EXTEND: + if (argc == 0) + throw detail::missing_argument_error( + make_string("Argument '", arg, "'s action is append or extend but takes in no arguments.")); + flag->m_dest_vec_func(dest, parsed_args, args); + break; + case action_t::APPEND_CONST: + if (argc != 0) + throw detail::unexpected_argument_error( + make_string("Argument '", arg, "'s action is append const but takes in arguments.")); + if (!flag->m_const_value) + { + throw detail::missing_value_error(make_string( + "Append const chosen as an action but const value not provided for argument '", arg, '\'')); + } + if (parsed_args.contains(dest)) + { + auto& data = parsed_args.m_data[dest]; + std::visit(detail::arg_meta_type_helper_t::make_visitor([arg](auto& primitive) { + throw detail::type_error(make_string("Invalid type for argument '", arg, "' expected list type, found '", + blt::type_string(), "' with value ", primitive)); + }, [&flag, arg](auto& vec) { + using type = typename meta::remove_cvref_t::value_type; + if (!std::holds_alternative(*flag->m_const_value)) + { + throw detail::type_error(make_string("Constant value for argument '", arg, + "' type doesn't match values already present! Expected to be of type '", + blt::type_string(), "'!")); + } + vec.push_back(std::get(*flag->m_const_value)); + }), data); + } else + { + std::visit(detail::arg_meta_type_helper_t::make_visitor([&parsed_args, &dest](auto& primitive) { + std::vector> vec; + vec.emplace_back(primitive); + parsed_args.m_data.emplace(dest, std::move(vec)); + }, [](auto&) { + throw detail::type_error("Append const should not be a list type!"); + }), *flag->m_const_value); + } + break; + case action_t::STORE_CONST: + if (argc != 0) + { + print_usage(); + throw detail::unexpected_argument_error( + make_string("Argument '", arg, "' is store const but called with an argument.")); + } + if (!flag->m_const_value) + throw detail::missing_value_error( + make_string("Argument '", arg, "' is store const, but const storage has no value.")); + parsed_args.m_data.emplace(dest, *flag->m_const_value); + break; + case action_t::STORE_TRUE: + if (argc != 0) + { + print_usage(); + throw detail::unexpected_argument_error("Store true flag called with an argument."); + } + parsed_args.m_data.emplace(dest, true); + break; + case action_t::STORE_FALSE: + if (argc != 0) + { + print_usage(); + throw detail::unexpected_argument_error("Store false flag called with an argument."); + } + parsed_args.m_data.insert({dest, false}); + break; + case action_t::COUNT: + if (parsed_args.m_data.contains(dest)) + { + auto visitor = detail::arg_meta_type_helper_t::make_visitor([](auto& primitive) -> detail::arg_data_t { + using type = meta::remove_cvref_t; + if constexpr (std::is_convertible_v) + { + return primitive + static_cast(1); + } else + throw detail::type_error("Error: count called but stored type is " + blt::type_string()); + }, [](auto&) -> detail::arg_data_t { + throw detail::type_error( + "List present on count. This condition doesn't make any sense! " + "(How did we get here, please report this!)"); + }); + parsed_args.m_data[dest] = std::visit(visitor, parsed_args.m_data[dest]); + } else // I also hate this! + flag->m_dest_func(dest, parsed_args, "1"); + break; + case action_t::HELP: + print_help(); + std::exit(0); + case action_t::VERSION: + print_version(); + std::exit(0); + } + } + }, flag->m_nargs); + } + + void argument_parser_t::parse_positional(argument_storage_t& parsed_args, argument_consumer_t& consumer, argument_positional_storage_t& storage, + const std::string_view arg) + { + if (!storage.has_positional()) + throw detail::missing_argument_error(make_string("Error: '", arg, "' positional argument does not match any defined for this parser")); + auto& positional = storage.next(); + const auto dest = positional.m_dest.value_or(std::string{arg}); + std::visit(lambda_visitor{ + [&consumer, &positional, &dest, &parsed_args, arg](const nargs_t arg_enum) { + switch (arg_enum) + { + case nargs_t::IF_POSSIBLE: + throw detail::bad_positional( + "Positional argument asked to consume if possible. We do not consider this to be a valid ask."); + case nargs_t::ALL_AT_LEAST_ONE: + if (!consumer.can_consume()) + throw detail::missing_argument_error( + make_string("Error expected at least one argument to be consumed by '", arg, '\'')); + [[fallthrough]]; + case nargs_t::ALL: + auto result = consume_until_flag_or_end(consumer, positional.m_choices ? &*positional.m_choices : nullptr); + if (!result) + throw detail::bad_choice_error(make_string('\'', consumer.peek().get_argument(), + "' is not a valid choice for argument '", arg, + "'! Expected one of ", result.error())); + positional.m_dest_vec_func(dest, parsed_args, result.value()); + break; + } + }, + [this, &consumer, &positional, &dest, &parsed_args, arg](const i32 argc) { + const auto args = consume_argc(argc, consumer, positional.m_choices ? &*positional.m_choices : nullptr, arg); + + switch (positional.m_action) + { + case action_t::STORE: + if (argc == 0) + throw detail::missing_argument_error( + make_string("Argument '", arg, "'s action is store but takes in no arguments?")); + if (argc == 1) + positional.m_dest_func(dest, parsed_args, args.front()); + else + throw detail::unexpected_argument_error(make_string("Argument '", arg, + "'s action is store but takes in more than one argument. " + "Did you mean to use action_t::APPEND or action_t::EXTEND?")); + break; + case action_t::APPEND: + case action_t::EXTEND: + if (argc == 0) + throw detail::missing_argument_error( + make_string("Argument '", arg, "'s action is append or extend but takes in no arguments.")); + positional.m_dest_vec_func(dest, parsed_args, args); + break; + case action_t::APPEND_CONST: + throw detail::bad_positional("action_t::APPEND_CONST does not make sense for positional arguments"); + case action_t::STORE_CONST: + throw detail::bad_positional("action_t::STORE_CONST does not make sense for positional arguments"); + case action_t::STORE_TRUE: + throw detail::bad_positional("action_t::STORE_TRUE does not make sense for positional arguments"); + case action_t::STORE_FALSE: + throw detail::bad_positional("action_t::STORE_FALSE does not make sense for positional arguments"); + case action_t::COUNT: + throw detail::bad_positional("action_t::COUNT does not make sense for positional arguments"); + case action_t::HELP: + print_help(); + std::exit(0); + case action_t::VERSION: + print_version(); + std::exit(0); + } + } + }, positional.m_nargs); + } + + void argument_parser_t::handle_missing_and_default_args(hashmap_t& arguments, + const hashset_t& found, argument_storage_t& parsed_args, + const std::string_view type) + { + for (const auto& [key, value] : arguments) + { + if (!found.contains(key)) + { + if (value->m_required) + throw detail::missing_argument_error(make_string("Error: ", type, " argument '", key, + "' was not found but is required by the program")); + auto dest = value->m_dest.value_or(std::string{key}); + if (value->m_default_value && !parsed_args.contains(dest)) + parsed_args.m_data.emplace(dest, *value->m_default_value); + } + } + } + + expected, std::string> argument_parser_t::consume_until_flag_or_end(argument_consumer_t& consumer, + hashset_t* allowed_choices) + { + std::vector args; + while (consumer.can_consume() && !consumer.peek().is_flag()) + { + if (allowed_choices != nullptr && !allowed_choices->contains(consumer.peek().get_argument())) + { + std::string valid_choices = "{"; + for (const auto& [i, choice] : enumerate(*allowed_choices)) + { + valid_choices += choice; + if (i != allowed_choices->size() - 1) + valid_choices += ", "; + } + valid_choices += "}"; + return unexpected(valid_choices); + } + args.emplace_back(consumer.consume().get_argument()); + } + return args; + } + + std::vector argument_parser_t::consume_argc(const int argc, argument_consumer_t& consumer, hashset_t* allowed_choices, + const std::string_view arg) + { + std::vector args; + for (i32 i = 0; i < argc; ++i) + { + if (!consumer.can_consume()) + { + throw detail::missing_argument_error(make_string("Expected ", argc, " arguments to be consumed by '", arg, "' but found ", i)); + } + if (consumer.peek().is_flag()) + { + std::cout << "Warning: arg '" << arg << "' expects " << argc << " arguments to be consumed but we found a flag '" << consumer.peek(). + get_argument() + << "'. We will comply as this may be desired if this argument is a file." << std::endl; + } + if (allowed_choices != nullptr && !allowed_choices->contains(consumer.peek().get_argument())) + { + std::string valid_choices = "{"; + for (const auto& [i, choice] : enumerate(*allowed_choices)) + { + valid_choices += choice; + if (i != allowed_choices->size() - 1) + valid_choices += ", "; + } + valid_choices += "}"; + throw detail::bad_choice_error(make_string('\'', consumer.peek().get_argument(), "' is not a valid choice for argument '", arg, + "'! Expected one of ", valid_choices)); + } + args.emplace_back(consumer.consume().get_argument()); + } + if (args.size() != static_cast(argc)) + { + throw std::runtime_error( + "This error condition should not be possible. " "Args consumed didn't equal the arguments requested and previous checks didn't fail. " + "Please report as an issue on the GitHub"); + } + return args; + } + + std::pair argument_subparser_t::parse(argument_consumer_t& consumer) + { + if (!consumer.can_consume()) + throw detail::missing_argument_error("Subparser requires an argument."); + const auto key = consumer.consume(); + if (key.is_flag()) + throw detail::subparse_error(key.get_argument(), to_vec(get_allowed_strings())); + const auto it = m_aliases.find(key.get_name()); + if (it == m_aliases.end()) + throw detail::subparse_error(key.get_argument(), to_vec(get_allowed_strings())); + it->second->m_name = m_parent->m_name; + m_last_parsed_parser = key.get_name(); + return {key, it->second->parse(consumer)}; + } + + hashmap_t> argument_subparser_t::get_allowed_strings() const + { + hashmap_t> map; + for (const auto& [key, value] : m_aliases) + map[value].emplace_back(key); + return map; + } + + std::vector> argument_subparser_t::to_vec(const hashmap_t>& map) + { + std::vector> vec; + for (const auto& [key, value] : map) + vec.push_back(value); + return vec; + } + + namespace detail + { + // Unit Tests for class argument_string_t + // Test Case 1: Ensure the constructor handles flags correctly + void test_argument_string_t_flag_basic(const hashset_t& prefixes) + { + const argument_string_t arg("-f", prefixes); + BLT_ASSERT(arg.is_flag() && "Expected argument to be identified as a flag."); + BLT_ASSERT(arg.value() == "f" && "Flag value should match the input string."); + } + + // Test Case 2: Ensure the constructor handles long flags correctly + void test_argument_string_t_long_flag(const hashset_t& prefixes) + { + const argument_string_t arg("--file", prefixes); + BLT_ASSERT(arg.is_flag() && "Expected argument to be identified as a flag."); + BLT_ASSERT(arg.value() == "file" && "Long flag value should match the input string."); + } + + // Test Case 3: Ensure positional arguments are correctly identified + void test_argument_string_t_positional_argument(const hashset_t& prefixes) + { + const argument_string_t arg("filename.txt", prefixes); + BLT_ASSERT(!arg.is_flag() && "Expected argument to be identified as positional."); + BLT_ASSERT(arg.value() == "filename.txt" && "Positional argument value should match the input string."); + } + + // Test Case 5: Handle an empty string + void test_argument_string_t_empty_input(const hashset_t& prefixes) + { + const argument_string_t arg("", prefixes); + BLT_ASSERT(!arg.is_flag() && "Expected an empty input to be treated as positional, not a flag."); + BLT_ASSERT(arg.value().empty() && "Empty input should have an empty value."); + } + + // Test Case 6: Handle edge case of a single hyphen (`-`) which might be ambiguous + void test_argument_string_t_single_hyphen(const hashset_t& prefixes) + { + const argument_string_t arg("-", prefixes); + BLT_ASSERT(arg.is_flag() && "Expected single hyphen (`-`) to be treated as a flag."); + BLT_ASSERT(arg.value().empty() && "Single hyphen flag should have empty value."); + BLT_ASSERT(arg.get_flag() == "-" && "Single hyphen flag should match the input string."); + } + + // Test Case 8: Handle arguments with prefix only (like "--") + void test_argument_string_t_double_hyphen(const hashset_t& prefixes) + { + const argument_string_t arg("--", prefixes); + BLT_ASSERT(arg.is_flag() && "Double hyphen ('--') should be treated as a flag."); + BLT_ASSERT(arg.value().empty() && "Double hyphen flag should have empty value."); + BLT_ASSERT(arg.get_flag() == "--" && "Double hyphen value should match the input string."); + } + + // Test Case 9: Validate edge case of an argument with spaces + void test_argument_string_t_with_spaces(const hashset_t& prefixes) + { + const argument_string_t arg(" ", prefixes); + BLT_ASSERT(!arg.is_flag() && "Arguments with spaces should not be treated as flags."); + BLT_ASSERT(arg.value() == " " && "Arguments with spaces should match the input string."); + } + + // Test Case 10: Validate arguments with numeric characters + void test_argument_string_t_numeric_flag(const hashset_t& prefixes) + { + const argument_string_t arg("-123", prefixes); + BLT_ASSERT(arg.is_flag() && "Numeric flags should still be treated as flags."); + BLT_ASSERT(arg.value() == "123" && "Numeric flag value should match the input string."); + } + + // Test Case 11: Ensure the constructor handles '+' flag correctly + void test_argument_string_t_plus_flag_basic(const hashset_t& prefixes) + { + const argument_string_t arg("+f", prefixes); + BLT_ASSERT(arg.is_flag() && "Expected argument to be identified as a flag."); + BLT_ASSERT(arg.value() == "f" && "Plus flag value should match the input string."); + } + + // Test Case 13: Handle edge case of a single plus (`+`) which might be ambiguous + void test_argument_string_t_single_plus(const hashset_t& prefixes) + { + const argument_string_t arg("+", prefixes); + BLT_ASSERT(arg.is_flag() && "Expected single plus (`+`) to be treated as a flag."); + BLT_ASSERT(arg.value().empty() && "Single plus flag should have empty value."); + BLT_ASSERT(arg.get_flag() == "+" && "Single plus flag should match the input string."); + } + + // Test Case 14: Handle arguments with prefix only (like '++') + void test_argument_string_t_double_plus(const hashset_t& prefixes) + { + const argument_string_t arg("++", prefixes); + BLT_ASSERT(arg.is_flag() && "Double plus ('++') should be treated as a flag."); + BLT_ASSERT(arg.value().empty() && "Double plus flag should have empty value."); + BLT_ASSERT(arg.get_flag() == "++" && "Double plus value should match the input string."); + } + + void run_all_tests_argument_string_t() + { + const hashset_t prefixes = {'-', '+'}; + test_argument_string_t_flag_basic(prefixes); + test_argument_string_t_long_flag(prefixes); + test_argument_string_t_positional_argument(prefixes); + test_argument_string_t_empty_input(prefixes); + test_argument_string_t_single_hyphen(prefixes); + test_argument_string_t_double_hyphen(prefixes); + test_argument_string_t_with_spaces(prefixes); + test_argument_string_t_numeric_flag(prefixes); + test_argument_string_t_plus_flag_basic(prefixes); + test_argument_string_t_single_plus(prefixes); + test_argument_string_t_double_plus(prefixes); + } + + void test_argparse_empty() + { + const std::vector argv{"./program"}; + argument_parser_t parser; + const auto args = parser.parse(argv); + BLT_ASSERT(args.size() == 0 && "Empty argparse should have no args on output"); + } + + void test_single_flag_prefixes() + { + argument_parser_t parser; + parser.add_flag("-a").set_action(action_t::STORE_TRUE); + parser.add_flag("+b").set_action(action_t::STORE_FALSE); + parser.add_flag("/c").as_type().set_action(action_t::STORE); + + const std::vector args = {"./program", "-a", "+b", "/c", "42"}; + const auto parsed_args = parser.parse(args); + + BLT_ASSERT(parsed_args.get("-a") == true && "Flag '-a' should store `true`"); + BLT_ASSERT(parsed_args.get("+b") == false && "Flag '+b' should store `false`"); + BLT_ASSERT(parsed_args.get("/c") == 42 && "Flag '/c' should store the value 42"); + } + + // Test: Invalid flag prefixes + void test_invalid_flag_prefixes() + { + argument_parser_t parser; + parser.add_flag("-a"); + parser.add_flag("+b"); + parser.add_flag("/c"); + + const std::vector args = {"./program", "!d", "-a"}; + try + { + parser.parse(args); + BLT_ASSERT(false && "Parsing should fail with invalid flag prefix '!'"); + } catch (...) + { + BLT_ASSERT(true && "Correctly threw on bad flag prefix"); + } + } + + void test_compound_flags() + { + argument_parser_t parser; + parser.add_flag("-v").as_type().set_action(action_t::COUNT); + + const std::vector args = {"./program", "-vvv"}; + const auto parsed_args = parser.parse(args); + + BLT_ASSERT(parsed_args.get("-v") == 3 && "Flag '-v' should count occurrences in compound form"); + } + + void test_combination_of_valid_and_invalid_flags() + { + using namespace argparse; + + argument_parser_t parser; + parser.add_flag("-x").as_type(); + parser.add_flag("/y").as_type(); + + const std::vector args = {"./program", "-x", "10", "!z", "/y", "value"}; + try + { + parser.parse(args); + BLT_ASSERT(false && "Parsing should fail due to invalid flag '!z'"); + } catch (...) + { + BLT_ASSERT(true && "Correctly threw an exception for invalid flag"); + } + } + + void test_flags_with_different_actions() + { + using namespace argparse; + + argument_parser_t parser; + parser.add_flag("-k").as_type().set_action(action_t::STORE); // STORE action + parser.add_flag("-t").as_type().set_action(action_t::STORE_CONST).set_const(999); // STORE_CONST action + parser.add_flag("-f").set_action(action_t::STORE_FALSE); // STORE_FALSE action + parser.add_flag("-c").set_action(action_t::STORE_TRUE); // STORE_TRUE action + + const std::vector args = {"./program", "-k", "100", "-t", "-f", "-c"}; + const auto parsed_args = parser.parse(args); + + BLT_ASSERT(parsed_args.get("-k") == 100 && "Flag '-k' should store 100"); + BLT_ASSERT(parsed_args.get("-t") == 999 && "Flag '-t' should store a const value of 999"); + BLT_ASSERT(parsed_args.get("-f") == false && "Flag '-f' should store `false`"); + BLT_ASSERT(parsed_args.get("-c") == true && "Flag '-c' should store `true`"); + } + + // Helper function to simulate argument parsing for nargs tests + bool parse_arguments(const std::vector& args, const nargs_v expected_nargs) + { + argument_parser_t parser; + + std::vector arg_strings; + arg_strings.reserve(args.size()); + for (const auto& arg : args) + arg_strings.emplace_back(arg, parser.get_allowed_flag_prefixes()); + argument_consumer_t consumer{arg_strings}; + + parser.add_positional("positional").set_nargs(expected_nargs); + try + { + auto parsed_args = parser.parse(consumer); + return consumer.remaining() == 0; + } catch (const std::exception&) + { + return false; + } + } + + // Test case for nargs = 0 + void test_nargs_0() + { + std::cout << "[Running Test: test_nargs_0]\n"; + + // Valid case: No arguments + const std::vector valid_args = {"./program"}; + BLT_ASSERT(!parse_arguments(valid_args, 0) && "nargs=0: Should fail"); + + // Invalid case: 1 argument + const std::vector invalid_args = {"./program", "arg1"}; + BLT_ASSERT(!parse_arguments(invalid_args, 0) && "nargs=0: Should not accept any arguments"); + + std::cout << "Success: test_nargs_0\n"; + } + + // Test case for nargs = 1 + void test_nargs_1() + { + std::cout << "[Running Test: test_nargs_1]\n"; + + // Valid case: 1 argument + const std::vector valid_args = {"./program", "arg1"}; + BLT_ASSERT(parse_arguments(valid_args, 1) && "nargs=1: Should accept exactly 1 argument"); + + // Invalid case: 0 arguments + const std::vector invalid_args_0 = {"./program"}; + BLT_ASSERT(!parse_arguments(invalid_args_0, 1) && "nargs=1: Should not accept 0 arguments"); + + // Invalid case: 2 arguments + const std::vector invalid_args_2 = {"./program", "arg1", "arg2"}; + BLT_ASSERT(!parse_arguments(invalid_args_2, 1) && "nargs=1: Should not accept more than 1 argument"); + + std::cout << "Success: test_nargs_1\n"; + } + + // Test case for nargs = 2 + void test_nargs_2() + { + std::cout << "[Running Test: test_nargs_2]\n"; + + // Valid case: 2 arguments + const std::vector valid_args = {"./program", "arg1", "arg2"}; + BLT_ASSERT(!parse_arguments(valid_args, 2) && "nargs=2: Should fail as action is store"); + + // Invalid case: 0 arguments + const std::vector invalid_args_0 = {"./program"}; + BLT_ASSERT(!parse_arguments(invalid_args_0, 2) && "nargs=2: Should not accept 0 arguments"); + + // Invalid case: 1 argument + const std::vector invalid_args_1 = {"./program", "arg1"}; + BLT_ASSERT(!parse_arguments(invalid_args_1, 2) && "nargs=2: Should not accept less than 2 arguments"); + + // Invalid case: 3 arguments + const std::vector invalid_args_3 = {"./program", "arg1", "arg2", "arg3"}; + BLT_ASSERT(!parse_arguments(invalid_args_3, 2) && "nargs=2: Should not accept more than 2 arguments"); + + std::cout << "Success: test_nargs_2\n"; + } + + void test_nargs_all() + { + std::cout << "[Running Test: test_nargs_all]\n"; + + // Valid case: No arguments + const std::vector valid_args_0 = {"./program"}; + BLT_ASSERT(!parse_arguments(valid_args_0, argparse::nargs_t::ALL) && "nargs=ALL: No arguments present. Should fail.)"); + + // Valid case: Multiple arguments + const std::vector valid_args_2 = {"./program", "arg1", "arg2"}; + BLT_ASSERT(parse_arguments(valid_args_2, argparse::nargs_t::ALL) && "nargs=ALL: Should accept all remaining arguments"); + + // Valid case: Many arguments + const std::vector valid_args_many = {"./program", "arg1", "arg2", "arg3", "arg4"}; + BLT_ASSERT(parse_arguments(valid_args_many, argparse::nargs_t::ALL) && "nargs=ALL: Should accept all remaining arguments"); + + std::cout << "Success: test_nargs_all\n"; + } + + // Test case for nargs_t::ALL_AT_LEAST_ONE + void test_nargs_all_at_least_one() + { + std::cout << "[Running Test: test_nargs_all_at_least_one]\n"; + + // Valid case: 1 argument + const std::vector valid_args_1 = {"./program", "arg1"}; + BLT_ASSERT(parse_arguments(valid_args_1, argparse::nargs_t::ALL_AT_LEAST_ONE) && + "nargs=ALL_AT_LEAST_ONE: Should accept at least one argument and consume it"); + + // Valid case: Multiple arguments + const std::vector valid_args_3 = {"./program", "arg1", "arg2", "arg3"}; + BLT_ASSERT(parse_arguments(valid_args_3, argparse::nargs_t::ALL_AT_LEAST_ONE) && + "nargs=ALL_AT_LEAST_ONE: Should accept at least one argument and consume all remaining arguments"); + + // Invalid case: No arguments + const std::vector invalid_args_0 = {"./program"}; + BLT_ASSERT(!parse_arguments(invalid_args_0, argparse::nargs_t::ALL_AT_LEAST_ONE) && + "nargs=ALL_AT_LEAST_ONE: Should reject if no arguments are provided"); + + std::cout << "Success: test_nargs_all_at_least_one\n"; + } + + void run_combined_flag_test() + { + std::cout << "[Running Test: run_combined_flag_test]\n"; + argument_parser_t parser; + + parser.add_flag("-a").set_action(action_t::STORE_TRUE); + parser.add_flag("--deep").set_action(action_t::STORE_FALSE); + parser.add_flag("-b", "--combined").set_action(action_t::STORE_CONST).set_const(50); + parser.add_flag("--append").set_action(action_t::APPEND).as_type(); + parser.add_flag("--required").set_required(true); + parser.add_flag("--default").set_default("I am a default value"); + parser.add_flag("-t").set_action(action_t::APPEND_CONST).set_dest("test").set_const(5); + parser.add_flag("-g").set_action(action_t::APPEND_CONST).set_dest("test").set_const(10); + parser.add_flag("-e").set_action(action_t::APPEND_CONST).set_dest("test").set_const(15); + parser.add_flag("-f").set_action(action_t::APPEND_CONST).set_dest("test").set_const(20); + parser.add_flag("-d").set_action(action_t::APPEND_CONST).set_dest("test").set_const(25); + parser.add_flag("--end").set_action(action_t::EXTEND).set_dest("wow").as_type(); + + const auto a1 = make_arguments("-a", "--required", "hello"); + const auto r1 = parser.parse(a1); + BLT_ASSERT(r1.get("-a") == true && "Flag '-a' should store true"); + BLT_ASSERT(r1.get("--default") == "I am a default value" && "Flag '--default' should store default value"); + BLT_ASSERT(r1.get("--required") == "hello" && "Flag '--required' should store 'hello'"); + + const auto a2 = make_arguments("-a", "--deep", "--required", "soft"); + const auto r2 = parser.parse(a2); + BLT_ASSERT(r2.get("-a") == true && "Flag '-a' should store true"); + BLT_ASSERT(r2.get("--deep") == false && "Flag '--deep' should store false"); + BLT_ASSERT(r2.get("--required") == "soft" && "Flag '--required' should store 'soft'"); + + const auto a3 = make_arguments("--required", "silly", "--combined", "-t", "-f", "-e"); + const auto r3 = parser.parse(a3); + BLT_ASSERT((r3.get>("test") == std::vector{5, 20, 15}) && "Flags should add to vector of {5, 20, 15}"); + BLT_ASSERT(r3.get("-b") == 50 && "Combined flag should store const of 50"); + + const auto a4 = make_arguments("--required", "crazy", "--end", "10", "12.05", "68.11", "100.00", "200532", "-d", "-t", "-g", "-e", "-f"); + const auto r4 = parser.parse(a4); + BLT_ASSERT( + (r4.get>("test") == std::vector{25, 5, 10, 15, 20}) && + "Expected test vector to be filled with all arguments in order of flags"); + BLT_ASSERT( + (r4.get>("wow") == std::vector{10, 12.05, 68.11, 100.00, 200532}) && + "Extend vector expected to contain all elements"); + + std::cout << "Success: run_combined_flag_test\n"; + } + + void run_choice_test() + { + std::cout << "[Running Test: run_choice_test]\n"; + argument_parser_t parser; + + parser.add_flag("--hello").set_choices("silly", "crazy", "soft"); + parser.add_positional("iam").set_choices("different", "choices", "for", "me"); + + const auto a1 = make_arguments("--hello", "crazy", "different"); + const auto r1 = parser.parse(a1); + BLT_ASSERT(r1.get("--hello") == "crazy" && "Flag '--hello' should store 'crazy'"); + BLT_ASSERT(r1.get("iam") == "different" && "Positional 'iam' should store 'different'"); + + const auto a2 = make_arguments("--hello", "not_an_option", "different"); + try + { + parser.parse(a2); + BLT_ASSERT(false && "Parsing should fail due to invalid flag '--hello'"); + } catch (...) + {} + + const auto a3 = make_arguments("--hello", "crazy", "not_a_choice"); + try + { + parser.parse(a3); + BLT_ASSERT(false && "Parsing should fail due to invalid positional 'iam'"); + } catch (...) + {} + + std::cout << "Success: run_choice_test\n"; + } + + void run_subparser_test() + { + std::cout << "[Running Test: run_subparser_test]\n"; + argument_parser_t parser; + + parser.add_flag("--open").make_flag(); + + const auto subparser = parser.add_subparser("mode"); + + const auto n1 = subparser->add_parser("n1"); + n1->add_flag("--silly").make_flag(); + n1->add_positional("path"); + + const auto n2 = subparser->add_parser("n2"); + n2->add_flag("--crazy").make_flag(); + n2->add_positional("path"); + n2->add_positional("output"); + + const auto n3 = subparser->add_parser("n3"); + n3->add_flag("--deep").make_flag(); + + const auto a1 = make_arguments("n1", "--silly"); + try + { + parser.parse(a1); + BLT_ASSERT(false && "Subparser should throw an error when positional not supplied"); + } catch (...) + {} + + const auto a2 = make_arguments("--open"); + try + { + parser.parse(a2); + BLT_ASSERT(false && "Subparser should throw an error when no subparser is supplied"); + } catch (...) + {} + + const auto a3 = make_arguments("n1", "--silly", "path_n1"); + const auto r3 = parser.parse(a3); + BLT_ASSERT(r3.get("--open") == false && "Flag '--open' should default to false"); + BLT_ASSERT(r3.get("mode") == "n1" && "Subparser should store 'n1'"); + BLT_ASSERT(r3.get("path") == "path_n1" && "Subparser path should be 'path'"); + + const auto a4 = make_arguments("n2", "--crazy", "path"); + + try + { + parser.parse(a4); + BLT_ASSERT(false && "Subparser should throw an error when second positional is not supplied"); + } catch (...) + {} + + const auto a5 = make_arguments("--open", "n2", "path_n2", "output_n2"); + const auto r5 = parser.parse(a5); + BLT_ASSERT(r5.get("--open") == true && "Flag '--open' should store true"); + BLT_ASSERT(r5.get("mode") == "n2" && "Subparser should store 'n2'"); + BLT_ASSERT(r5.get("path") == "path_n2" && "Subparser path should be 'path'"); + BLT_ASSERT(r5.get("output") == "output_n2" && "Subparser output should be 'output'"); + + const auto a6 = make_arguments("not_an_option", "silly"); + + try + { + parser.parse(a6); + BLT_ASSERT(false && "Subparser should throw an error when first positional is not a valid subparser"); + } catch (const std::exception&) + {} + + const auto a7 = make_arguments("n3"); + const auto r7 = parser.parse(a7); + BLT_ASSERT(r7.get("mode") == "n3" && "Subparser should store 'n3'"); + + std::cout << "Success: run_subparser_test\n"; + } + + void run_argparse_flag_tests() + { + test_single_flag_prefixes(); + test_invalid_flag_prefixes(); + test_compound_flags(); + test_combination_of_valid_and_invalid_flags(); + test_flags_with_different_actions(); + run_combined_flag_test(); + run_choice_test(); + run_subparser_test(); + } + + void run_all_nargs_tests() + { + test_nargs_0(); + test_nargs_1(); + test_nargs_2(); + test_nargs_all(); + test_nargs_all_at_least_one(); + std::cout << "All nargs tests passed successfully.\n"; + } + + void test() + { + run_all_tests_argument_string_t(); + test_argparse_empty(); + run_argparse_flag_tests(); + run_all_nargs_tests(); + } + + [[nodiscard]] std::string subparse_error::error_string() const + { + std::string message = "Subparser Error: "; + message += m_found_string; + message += " is not a valid command. Allowed commands are: {"; + for (const auto [i, allowed_string] : enumerate(m_allowed_strings)) + { + if (allowed_string.size() > 1) + message += '['; + for (const auto [j, alias] : enumerate(allowed_string)) + { + message += alias; + if (static_cast(j) < static_cast(allowed_string.size()) - 2) + message += ", "; + else if (static_cast(j) < static_cast(allowed_string.size()) - 1) + message += ", or "; + } + if (allowed_string.size() > 1) + message += ']'; + if (i != m_allowed_strings.size() - 1) + message += ", "; + } + message += "}"; + return message; + } + } } From 2f8a0bba91088d9db114a4dfdf2b8c8d17343bba Mon Sep 17 00:00:00 2001 From: Brett Date: Wed, 12 Mar 2025 23:57:08 -0400 Subject: [PATCH 087/101] fix missing logging depend --- CMakeLists.txt | 2 +- include/blt/std/thread.h | 2 +- libraries/parallel-hashmap | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ca38425..22ca9ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.2.22) +set(BLT_VERSION 5.2.23) set(BLT_TARGET BLT) diff --git a/include/blt/std/thread.h b/include/blt/std/thread.h index 4ee56f9..559bd78 100644 --- a/include/blt/std/thread.h +++ b/include/blt/std/thread.h @@ -31,7 +31,7 @@ #include #include #include -#include +#include #include namespace blt diff --git a/libraries/parallel-hashmap b/libraries/parallel-hashmap index 93201da..154c634 160000 --- a/libraries/parallel-hashmap +++ b/libraries/parallel-hashmap @@ -1 +1 @@ -Subproject commit 93201da2ba5a6aba0a6e57ada64973555629b3e3 +Subproject commit 154c63489e84d5569d3b466342a2ae8fd99e4734 From 6d444779582b74924aad7856dc3b2e3d16c70755 Mon Sep 17 00:00:00 2001 From: Tri11Paragon Date: Thu, 13 Mar 2025 13:06:18 -0700 Subject: [PATCH 088/101] add msvc check --- CMakeLists.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 618e338..5d3d71e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -168,8 +168,10 @@ macro(blt_add_test name source type) target_link_libraries(${name}-${type} PRIVATE BLT) - target_compile_options(${name}-${type} PRIVATE -Wall -Wextra -Wpedantic -Wno-comment) - target_link_options(${name}-${type} PRIVATE -Wall -Wextra -Wpedantic -Wno-comment) + if (NOT MSVC) + target_compile_options(${name}-${type} PRIVATE -Wall -Wextra -Wpedantic -Wno-comment) + target_link_options(${name}-${type} PRIVATE -Wall -Wextra -Wpedantic -Wno-comment) + endif() target_compile_definitions(${name}-${type} PRIVATE BLT_DEBUG_LEVEL=${DEBUG_LEVEL}) if (${TRACK_ALLOCATIONS}) From 0bd3519e7e6e56c9199acf5d08ee3ea0c16e93a4 Mon Sep 17 00:00:00 2001 From: Tri11Paragon Date: Thu, 13 Mar 2025 13:47:44 -0700 Subject: [PATCH 089/101] fix for windows --- cmake/warnings.cmake | 4 +++- include/blt/iterator/enumerate.h | 8 ++++---- include/blt/iterator/zip.h | 8 ++++---- include/blt/parse/templating.h | 8 ++++++-- src/blt/logging/logging.cpp | 2 +- src/blt/logging/status.cpp | 10 ++++++++++ 6 files changed, 28 insertions(+), 12 deletions(-) diff --git a/cmake/warnings.cmake b/cmake/warnings.cmake index 035c0df..315d5fb 100644 --- a/cmake/warnings.cmake +++ b/cmake/warnings.cmake @@ -23,7 +23,9 @@ elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") message(STATUS "GCC libs: ${Green}stdc++fs${ColourReset}") target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -Wpedantic -fdiagnostics-color=always) target_link_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -Wpedantic -fdiagnostics-color=always) - target_link_options(${PROJECT_NAME} PUBLIC -rdynamic) + if (NOT WIN32) + target_link_options(${PROJECT_NAME} PUBLIC -rdynamic) + endif() target_link_libraries(${PROJECT_NAME} PUBLIC stdc++fs) include(GNUInstallDirs) elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Intel") diff --git a/include/blt/iterator/enumerate.h b/include/blt/iterator/enumerate.h index 71f3687..7dd6293 100644 --- a/include/blt/iterator/enumerate.h +++ b/include/blt/iterator/enumerate.h @@ -82,14 +82,14 @@ namespace blt } template - class enumerate_iterator_container : public iterator::iterator_container> + class enumerate_iterator_container : public blt::iterator::iterator_container> { public: - using iterator::iterator_container>::iterator_container; + using blt::iterator::iterator_container>::iterator_container; enumerate_iterator_container(Iter begin, Iter end, blt::size_t size): - iterator::iterator_container>( - iterator::enumerate_wrapper{0, std::move(begin)}, iterator::enumerate_wrapper{size, std::move(end)}) + blt::iterator::iterator_container>( + blt::iterator::enumerate_wrapper{0, std::move(begin)}, blt::iterator::enumerate_wrapper{size, std::move(end)}) { } }; diff --git a/include/blt/iterator/zip.h b/include/blt/iterator/zip.h index df26e43..27a3760 100644 --- a/include/blt/iterator/zip.h +++ b/include/blt/iterator/zip.h @@ -112,11 +112,11 @@ namespace blt class zip_iterator_container : public iterator::iterator_container> { public: - using iterator::iterator_container>::iterator_container; + using blt::iterator::iterator_container>::iterator_container; - explicit zip_iterator_container(iterator::iterator_pair... iterator_pairs): - iterator::iterator_container>(iterator::zip_wrapper{std::move(iterator_pairs.begin)...}, - iterator::zip_wrapper{std::move(iterator_pairs.end)...}) + explicit zip_iterator_container(blt::iterator::iterator_pair... iterator_pairs): + blt::iterator::iterator_container>(blt::iterator::zip_wrapper{std::move(iterator_pairs.begin)...}, + blt::iterator::zip_wrapper{std::move(iterator_pairs.end)...}) {} }; diff --git a/include/blt/parse/templating.h b/include/blt/parse/templating.h index 5f10352..7cd9d30 100644 --- a/include/blt/parse/templating.h +++ b/include/blt/parse/templating.h @@ -223,8 +223,12 @@ namespace blt std::string_view from_last() { - if (!hasNext()) - return std::string_view(&raw_string[last_read_index], raw_string.size() - last_read_index); + if (!hasNext()) { + auto size = raw_string.size() - last_read_index; + if (size > 0) + return std::string_view(&raw_string[last_read_index], size); + return ""; + } auto token = storage[getCurrentIndex()]; auto len = ((&token.token.back()) - &raw_string[last_read_index]); auto str = std::string_view(&raw_string[last_read_index], len); diff --git a/src/blt/logging/logging.cpp b/src/blt/logging/logging.cpp index 67e5a66..d9339e0 100644 --- a/src/blt/logging/logging.cpp +++ b/src/blt/logging/logging.cpp @@ -204,7 +204,7 @@ namespace blt::logging if (end == std::string::npos) { std::stringstream ss; - ss << "Invalid format string, missing closing '}' near " << m_fmt.substr(std::min(static_cast(begin) - 5, 0l)); + ss << "Invalid format string, missing closing '}' near " << m_fmt.substr(std::min(static_cast(begin) - 5, 0ll)); throw std::runtime_error(ss.str()); } m_last_fmt_pos = end + 1; diff --git a/src/blt/logging/status.cpp b/src/blt/logging/status.cpp index 90696b4..1f3edfa 100644 --- a/src/blt/logging/status.cpp +++ b/src/blt/logging/status.cpp @@ -17,8 +17,10 @@ */ #include #include +#ifdef unix #include #include +#endif #include #include #include @@ -30,6 +32,7 @@ namespace blt::logging { vec2i get_cursor_position() { +#ifdef unix termios save{}, raw{}; tcgetattr(0, &save); @@ -68,12 +71,16 @@ namespace blt::logging tcsetattr(0,TCSANOW, &save); return vec2i{row, col}; +#else + return {0,0}; +#endif } #define SIZE 100 vec2i get_screen_size() { +#ifdef unix char in[SIZE] = ""; int each = 0; int ch = 0; @@ -113,6 +120,9 @@ namespace blt::logging return {rows, cols}; } throw std::runtime_error("Could not get screen size"); +#else + return {0,0}; +#endif } i32 get_size_no_ansi(const std::string& str) From f8cf71e15208ea54cf6e12719e3c526a3637afef Mon Sep 17 00:00:00 2001 From: Tri11Paragon Date: Thu, 13 Mar 2025 13:59:23 -0700 Subject: [PATCH 090/101] add flatten to iterator --- include/blt/iterator/iterator.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/blt/iterator/iterator.h b/include/blt/iterator/iterator.h index 48c1070..b758417 100644 --- a/include/blt/iterator/iterator.h +++ b/include/blt/iterator/iterator.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include From 24de97acdddce3cebfc869caf56514604cefce3a Mon Sep 17 00:00:00 2001 From: Brett Date: Thu, 13 Mar 2025 14:57:16 -0400 Subject: [PATCH 091/101] stupid error --- CMakeLists.txt | 2 +- src/blt/logging/logging.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 75d2508..7528446 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.2.23) +set(BLT_VERSION 5.2.24) set(BLT_TARGET BLT) diff --git a/src/blt/logging/logging.cpp b/src/blt/logging/logging.cpp index d9339e0..dae74f2 100644 --- a/src/blt/logging/logging.cpp +++ b/src/blt/logging/logging.cpp @@ -204,7 +204,7 @@ namespace blt::logging if (end == std::string::npos) { std::stringstream ss; - ss << "Invalid format string, missing closing '}' near " << m_fmt.substr(std::min(static_cast(begin) - 5, 0ll)); + ss << "Invalid format string, missing closing '}' near " << m_fmt.substr(std::min(static_cast(begin) - 5, static_cast(0))); throw std::runtime_error(ss.str()); } m_last_fmt_pos = end + 1; From 28225224848ff295abc0dc85b7ca5fc2ba84617f Mon Sep 17 00:00:00 2001 From: Brett Date: Thu, 13 Mar 2025 14:58:11 -0400 Subject: [PATCH 092/101] zip needs limits now? --- CMakeLists.txt | 2 +- include/blt/iterator/zip.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7528446..714721a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.2.24) +set(BLT_VERSION 5.2.25) set(BLT_TARGET BLT) diff --git a/include/blt/iterator/zip.h b/include/blt/iterator/zip.h index 27a3760..bf7c069 100644 --- a/include/blt/iterator/zip.h +++ b/include/blt/iterator/zip.h @@ -21,6 +21,7 @@ #include #include +#include namespace blt { From ebf0a80774fba3b4c6e7ccfc51193b9a3299cdbc Mon Sep 17 00:00:00 2001 From: Brett Date: Thu, 13 Mar 2025 19:44:17 -0400 Subject: [PATCH 093/101] fix empty log --- CMakeLists.txt | 2 +- include/blt/logging/logging.h | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 714721a..b88d6e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.2.25) +set(BLT_VERSION 5.2.26) set(BLT_TARGET BLT) diff --git a/include/blt/logging/logging.h b/include/blt/logging/logging.h index f91d666..778a2b4 100644 --- a/include/blt/logging/logging.h +++ b/include/blt/logging/logging.h @@ -39,6 +39,8 @@ namespace blt::logging template std::string log(std::string fmt, Args&&... args) { + if (fmt.empty()) + return fmt; auto sequence = std::make_integer_sequence{}; m_arg_print_funcs.clear(); m_arg_print_funcs.resize(sizeof...(Args)); From 79148a8506b1e26a5197977b4ece1091f026b052 Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Wed, 19 Mar 2025 19:38:29 -0400 Subject: [PATCH 094/101] compairsion operators on vectors --- CMakeLists.txt | 2 +- include/blt/math/vectors.h | 44 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b88d6e7..fc7b86d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.2.26) +set(BLT_VERSION 5.2.27) set(BLT_TARGET BLT) diff --git a/include/blt/math/vectors.h b/include/blt/math/vectors.h index 52d909f..75820bb 100644 --- a/include/blt/math/vectors.h +++ b/include/blt/math/vectors.h @@ -428,6 +428,50 @@ namespace blt return true; } + template + constexpr bool operator>=(const vec& left, const vec& right) + { + for (u32 i = 0; i < size; i++) + { + if (left[i] < right[i]) + return false; + } + return true; + } + + template + constexpr bool operator>(const vec& left, const vec& right) + { + for (u32 i = 0; i < size; i++) + { + if (left[i] <= right[i]) + return false; + } + return true; + } + + template + constexpr bool operator<(const vec& left, const vec& right) + { + for (u32 i = 0; i < size; i++) + { + if (left[i] >= right[i]) + return false; + } + return true; + } + + template + constexpr bool operator<=(const vec& left, const vec& right) + { + for (u32 i = 0; i < size; i++) + { + if (left[i] > right[i]) + return false; + } + return true; + } + template inline constexpr bool operator!=(const vec& left, const vec& right) { From 8922a9e78c1a19e4008a6a6116acb325019da27f Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Tue, 25 Mar 2025 18:37:39 -0400 Subject: [PATCH 095/101] silly bounding boxes --- CMakeLists.txt | 2 +- include/blt/math/aabb.h | 165 ++++++++++++++++++++++++++++++++ include/blt/math/bounding_box.h | 28 ++++++ 3 files changed, 194 insertions(+), 1 deletion(-) create mode 100644 include/blt/math/aabb.h create mode 100644 include/blt/math/bounding_box.h diff --git a/CMakeLists.txt b/CMakeLists.txt index fc7b86d..41318a2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.2.27) +set(BLT_VERSION 5.2.28) set(BLT_TARGET BLT) diff --git a/include/blt/math/aabb.h b/include/blt/math/aabb.h new file mode 100644 index 0000000..4fdc0a3 --- /dev/null +++ b/include/blt/math/aabb.h @@ -0,0 +1,165 @@ +#pragma once +/* + * Copyright (C) 2024 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BLT_MATH_AABB_H +#define BLT_MATH_AABB_H + +#include +#include +#include + +namespace blt +{ + // yes I could use the vector (see tower defense game commit log for this) + // this feels nicer + template + class axis_t + { + public: + axis_t(const T min, const T max): m_min(min), m_max(max) + {} + + [[nodiscard]] bool intersects(const T p) const + { + return p >= m_min && p <= m_max; + } + + template + [[nodiscard]] bool intersects(const axis_t& other) const + { + return static_cast(other.m_min) <= m_max && static_cast(other.m_max) >= m_min; + } + + [[nodiscard]] T min() const + { + return m_min; + } + + [[nodiscard]] T max() const + { + return m_max; + } + + private: + T m_min, m_max; + }; + + namespace detail + { + template + class axis_aligned_bounding_box_base_t + { + public: + [[nodiscard]] vec get_center() const + { + vec min; + for (u32 i = 0; i < Axis; i++) + min[i] = m_axes[i].min(); + const auto center = get_size() / 2.0f; + return min + center; + } + + [[nodiscard]] vec get_size() const + { + vec size; + for (u32 i = 0; i < Axis; i++) + size[i] = m_axes[i].max() - m_axes[i].min(); + return size; + } + + template + [[nodiscard]] bool intersects(const axis_aligned_bounding_box_base_t& other) const + { + for (u32 i = 0; i < Axis; i++) + if (!m_axes[i].intersects(other.m_axes[i])) + return false; + return true; + } + + template + [[nodiscard]] bool intersects(const vec& point) const + { + for (u32 i = 0; i < Axis; i++) + if (!m_axes[i].intersects(point[i])) + return false; + return true; + } + + axis_t& operator[](u32 i) + { + return m_axes[i]; + } + + axis_t& axis(u32 i) + { + if (i >= Axis) + throw std::out_of_range("Axis index out of range"); + return m_axes[i]; + } + + protected: + std::array, Axis> m_axes; + }; + } + + template + class axis_aligned_bounding_box_t : public detail::axis_aligned_bounding_box_base_t + { + public: + using detail::axis_aligned_bounding_box_base_t::axis_aligned_bounding_box_base_t; + }; + + template + class axis_aligned_bounding_box_t<2, T> : public detail::axis_aligned_bounding_box_base_t<2, T> + { + public: + using detail::axis_aligned_bounding_box_base_t<2, T>::axis_aligned_bounding_box_base_t; + + [[nodiscard]] vec2 min() const + { + return {this->m_axes[0].min(), this->m_axes[1].min()}; + } + + [[nodiscard]] vec2 max() const + { + return {this->m_axes[0].max(), this->m_axes[1].max()}; + } + }; + + template + class axis_aligned_bounding_box_t<3, T> : public detail::axis_aligned_bounding_box_base_t<3, T> + { + public: + using detail::axis_aligned_bounding_box_base_t<2, T>::axis_aligned_bounding_box_base_t; + + [[nodiscard]] vec3 min() const + { + return {this->m_axes[0].min(), this->m_axes[1].min(), this->m_axes[2].min()}; + } + + [[nodiscard]] vec3s max() const + { + return {this->m_axes[0].max(), this->m_axes[1].max(), this->m_axes[2].max()}; + } + }; + + using aabb_2d_t = axis_aligned_bounding_box_t<2, float>; + using aabb_3d_t = axis_aligned_bounding_box_t<3, float>; +} + +#endif //BLT_MATH_AABB_H diff --git a/include/blt/math/bounding_box.h b/include/blt/math/bounding_box.h new file mode 100644 index 0000000..24cbcb0 --- /dev/null +++ b/include/blt/math/bounding_box.h @@ -0,0 +1,28 @@ +#pragma once +/* + * Copyright (C) 2024 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BLT_MATH_BOUNDING_BOX_H +#define BLT_MATH_BOUNDING_BOX_H + +#include + +namespace blt { + +} + +#endif //BLT_MATH_BOUNDING_BOX_H From 9a05c86b02c9c45c2b384c531007416148ec4b56 Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Tue, 25 Mar 2025 19:30:35 -0400 Subject: [PATCH 096/101] length function --- CMakeLists.txt | 2 +- include/blt/math/aabb.h | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 41318a2..a994628 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.2.28) +set(BLT_VERSION 5.2.29) set(BLT_TARGET BLT) diff --git a/include/blt/math/aabb.h b/include/blt/math/aabb.h index 4fdc0a3..e0c94c4 100644 --- a/include/blt/math/aabb.h +++ b/include/blt/math/aabb.h @@ -55,6 +55,11 @@ namespace blt return m_max; } + [[nodiscard]] T length() const + { + return m_max - m_min; + } + private: T m_min, m_max; }; @@ -78,7 +83,7 @@ namespace blt { vec size; for (u32 i = 0; i < Axis; i++) - size[i] = m_axes[i].max() - m_axes[i].min(); + size[i] = m_axes[i].length(); return size; } From 4f9f61d63a34420e758ce22eddb771df5c888fb9 Mon Sep 17 00:00:00 2001 From: Brett Date: Mon, 31 Mar 2025 17:52:04 -0400 Subject: [PATCH 097/101] silly --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a994628..06e5268 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.2.29) +set(BLT_VERSION 5.2.30) set(BLT_TARGET BLT) From 0ebbc198c5b80ca99e537460ef92fd806864fa3e Mon Sep 17 00:00:00 2001 From: Brett Date: Mon, 31 Mar 2025 17:53:18 -0400 Subject: [PATCH 098/101] fix bump --- CMakeLists.txt | 2 +- include/blt/std/bump_allocator.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 06e5268..4a6a7ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.2.30) +set(BLT_VERSION 5.2.31) set(BLT_TARGET BLT) diff --git a/include/blt/std/bump_allocator.h b/include/blt/std/bump_allocator.h index f713a14..8065671 100644 --- a/include/blt/std/bump_allocator.h +++ b/include/blt/std/bump_allocator.h @@ -29,7 +29,7 @@ #include #include #include -#include "logging.h" +#include "blt/logging/logging.h" #include #include From 8b23715ddd46049ec54b9f6ae08190b77d2e6fd2 Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Tue, 1 Apr 2025 19:58:06 -0400 Subject: [PATCH 099/101] logger patch hack --- CMakeLists.txt | 2 +- include/blt/logging/logging.h | 1 + src/blt/logging/logging.cpp | 25 ++++++++++++++++++++++--- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4a6a7ac..af6a982 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.2.31) +set(BLT_VERSION 5.2.32) set(BLT_TARGET BLT) diff --git a/include/blt/logging/logging.h b/include/blt/logging/logging.h index 778a2b4..bc2e7a4 100644 --- a/include/blt/logging/logging.h +++ b/include/blt/logging/logging.h @@ -140,6 +140,7 @@ namespace blt::logging [[nodiscard]] size_t find_ending_brace(size_t begin) const; void setup_stream(const fmt_spec_t& spec) const; + std::string process_string(std::string_view str); void process_strings(); static void handle_type(std::ostream& stream, const fmt_spec_t& spec); diff --git a/src/blt/logging/logging.cpp b/src/blt/logging/logging.cpp index dae74f2..a2f2d68 100644 --- a/src/blt/logging/logging.cpp +++ b/src/blt/logging/logging.cpp @@ -108,13 +108,30 @@ namespace blt::logging m_stream << std::noshowpos; } + std::string logger_t::process_string(const std::string_view str) + { + auto result = std::string(str); + size_t pos = 0; + while (pos = result.find('{', pos), pos != std::string::npos) + { + if (pos > 0 && result[pos - 1] == '\\') + { + auto before = result.substr(0, pos - 1); + auto after = result.substr(pos); + result = before + after; + } else + ++pos; + } + return result; + } + void logger_t::process_strings() { auto spec_it = m_fmt_specs.begin(); auto str_it = m_string_sections.begin(); for (; spec_it != m_fmt_specs.end(); ++spec_it, ++str_it) { - m_stream << *str_it; + m_stream << process_string(*str_it); auto arg_pos = spec_it->arg_id; if (arg_pos == -1) arg_pos = static_cast(m_arg_pos++); @@ -122,7 +139,7 @@ namespace blt::logging setup_stream(*spec_it); m_arg_print_funcs[arg_pos](m_stream, *spec_it); } - m_stream << *str_it; + m_stream << process_string(*str_it); } void logger_t::handle_type(std::ostream& stream, const fmt_spec_t& spec) @@ -197,7 +214,9 @@ namespace blt::logging std::optional> logger_t::consume_to_next_fmt() { - const auto begin = m_fmt.find('{', m_last_fmt_pos); + auto begin = m_fmt.find('{', m_last_fmt_pos); + while (begin > 0 && m_fmt[begin - 1] == '\\') + begin = m_fmt.find('{', begin + 1);; if (begin == std::string::npos) return {}; const auto end = find_ending_brace(begin); From 284743c683aebae3277f64a07cdd0deedf629039 Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Wed, 2 Apr 2025 18:33:47 -0400 Subject: [PATCH 100/101] add range to f_equal --- CMakeLists.txt | 2 +- include/blt/math/vectors.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index af6a982..cc07fd1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.2.32) +set(BLT_VERSION 5.2.33) set(BLT_TARGET BLT) diff --git a/include/blt/math/vectors.h b/include/blt/math/vectors.h index 75820bb..81107db 100644 --- a/include/blt/math/vectors.h +++ b/include/blt/math/vectors.h @@ -21,9 +21,9 @@ namespace blt constexpr float EPSILON = std::numeric_limits::epsilon(); - static inline constexpr bool f_equal(float v1, float v2) + static constexpr bool f_equal(const float v1, const float v2, const float range = 1) { - return v1 >= v2 - EPSILON && v1 <= v2 + EPSILON; + return v1 >= v2 - (EPSILON * range) && v1 <= v2 + (EPSILON * range); } template From 729a16ab574e31bf1b44446a777e4ee834518c6e Mon Sep 17 00:00:00 2001 From: Brett Date: Thu, 3 Apr 2025 00:42:09 -0400 Subject: [PATCH 101/101] logging? --- CMakeLists.txt | 2 +- src/blt/logging/logging.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cc07fd1..8fbc9ee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.2.33) +set(BLT_VERSION 5.2.34) set(BLT_TARGET BLT) diff --git a/src/blt/logging/logging.cpp b/src/blt/logging/logging.cpp index a2f2d68..4bc5cb2 100644 --- a/src/blt/logging/logging.cpp +++ b/src/blt/logging/logging.cpp @@ -215,7 +215,7 @@ namespace blt::logging std::optional> logger_t::consume_to_next_fmt() { auto begin = m_fmt.find('{', m_last_fmt_pos); - while (begin > 0 && m_fmt[begin - 1] == '\\') + while (begin != std::string::npos && begin > 0 && m_fmt[begin - 1] == '\\') begin = m_fmt.find('{', begin + 1);; if (begin == std::string::npos) return {};