From 69c083a0d1cc74127711896ea60913629c6eb481 Mon Sep 17 00:00:00 2001 From: mguludag Date: Sun, 17 May 2026 14:01:24 +0000 Subject: [PATCH 1/4] auto generate single_include header --- .github/workflows/c-cpp.yml | 28 + .gitignore | 3 +- CMakeLists.txt | 14 + scripts/amalgamate.py | 247 ++++++ single_include/mgutility_enum_name.hpp | 1111 +++++++++++++++--------- 5 files changed, 1014 insertions(+), 389 deletions(-) create mode 100644 scripts/amalgamate.py diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index fe4fbd0..9d178aa 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -101,3 +101,31 @@ jobs: -DCMAKE_CXX_STANDARD=${{ matrix.config.cppstd }} make -j2 ctest -j2 --output-on-failure + + regenerate-single-include: + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + steps: + - uses: actions/checkout@v4 + with: + submodules: true + token: ${{ secrets.GITHUB_TOKEN }} + - uses: actions/setup-python@v5 + with: + python-version: '3.x' + - name: Regenerate single-include header + run: | + python3 scripts/amalgamate.py \ + --main include/mgutility/reflection/enum_name.hpp \ + --source-dir include \ + --source-dir mgutility/include \ + --output single_include/mgutility_enum_name.hpp + - name: Commit and push if changed + run: | + git diff --quiet single_include/mgutility_enum_name.hpp || { + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add single_include/mgutility_enum_name.hpp + git commit -m "Auto-regenerate single-include header" + git push + } diff --git a/.gitignore b/.gitignore index db06724..77d4b3e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ -build/** +build*/** .cache + diff --git a/CMakeLists.txt b/CMakeLists.txt index a0bae13..ba47987 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -114,3 +114,17 @@ endif() if(${ENUM_NAME_BUILD_DOCS}) add_subdirectory(doc) endif() + +# Single-include header generation +find_package(Python3 QUIET) +if(Python3_FOUND) + add_custom_target(single_include + COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/scripts/amalgamate.py + --main ${CMAKE_SOURCE_DIR}/include/mgutility/reflection/enum_name.hpp + --source-dir ${CMAKE_SOURCE_DIR}/include + --source-dir ${CMAKE_SOURCE_DIR}/mgutility/include + --output ${CMAKE_SOURCE_DIR}/single_include/mgutility_enum_name.hpp + COMMENT "Regenerating single-include header from source files" + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + ) +endif() diff --git a/scripts/amalgamate.py b/scripts/amalgamate.py new file mode 100644 index 0000000..549521f --- /dev/null +++ b/scripts/amalgamate.py @@ -0,0 +1,247 @@ +#!/usr/bin/env python3 +""" +Script to amalgamate the enum_name library into a single header file. + +Usage: + python3 scripts/amalgamate.py \ + --main include/mgutility/reflection/enum_name.hpp \ + --source-dir include \ + --source-dir mgutility/include \ + --output single_include/mgutility_enum_name.hpp +""" + +import argparse +import os +import re +import sys +from typing import List, Optional, Set, Tuple + + +# Regex to match #include directives +RE_INCLUDE_LOCAL = re.compile(r'#\s*include\s+"([^"]+)"') +RE_INCLUDE_SYSTEM = re.compile(r'#\s*include\s+<([^>]+)>') + +# Regex to match include guards +RE_IFNDEF = re.compile(r'#\s*ifndef\s+(\w+)') +RE_DEFINE = re.compile(r'#\s*define\s+(\w+)') +RE_PRAGMA_ONCE = re.compile(r'#\s*pragma\s+once') + + +def find_file(include_path: str, source_dirs: List[str], + current_dir: Optional[str] = None) -> Optional[str]: + """Try to find an included file. + + First tries relative to the including file's directory (current_dir), + then tries in each source directory. + """ + # Try relative to the including file's directory first + if current_dir: + candidate = os.path.normpath(os.path.join(current_dir, include_path)) + if os.path.isfile(candidate): + return candidate + + # Then try in source directories + for src_dir in source_dirs: + full_path = os.path.normpath(os.path.join(src_dir, include_path)) + if os.path.isfile(full_path): + return full_path + return None + + +def read_file_content(filepath: str) -> Tuple[List[str], Optional[str]]: + """ + Read a file and return (lines, guard_macro_or_none). + Also detects #pragma once and returns it as a pseudo-guard. + """ + with open(filepath, 'r', encoding='utf-8') as f: + content = f.read() + lines = content.splitlines(keepends=True) + + # Try to find a header guard: #ifndef X / #define X at the top + guard = None + for i in range(min(len(lines), 20)): + stripped = lines[i].strip() + if RE_PRAGMA_ONCE.match(stripped): + guard = f"__PRAGMA_ONCE_{os.path.basename(filepath)}__" + break + if RE_IFNDEF.match(stripped): + m = RE_IFNDEF.match(stripped) + guard_candidate = m.group(1) + # Check next non-empty, non-comment line for #define + for j in range(i + 1, min(len(lines), i + 5)): + m2 = RE_DEFINE.match(lines[j].strip()) + if m2 and m2.group(1) == guard_candidate: + guard = guard_candidate + break + break + + return lines, guard + + +def inline_file( + filepath: str, + source_dirs: List[str], + seen_guards: Set[str], + processed_files: Set[str], +) -> List[str]: + """ + Inline the content of a project header file, resolving its local includes. + Returns a list of strings (lines). + """ + abs_path = os.path.abspath(filepath) + + lines, guard = read_file_content(abs_path) + + if guard and guard in seen_guards: + return [] + if abs_path in processed_files: + return [] + + if guard: + seen_guards.add(guard) + processed_files.add(abs_path) + + result: List[str] = [] + + i = 0 + while i < len(lines): + line = lines[i] + stripped = line.strip() + + # Check for local include + m_local = RE_INCLUDE_LOCAL.match(stripped) + if m_local: + inc_path = m_local.group(1) + resolved = find_file(inc_path, source_dirs, + os.path.dirname(abs_path)) + + if resolved and os.path.isfile(resolved): + sub_lines = inline_file(resolved, source_dirs, seen_guards, + processed_files) + # Add a separator blank line before the inlined content + if sub_lines: + result.append('\n') + result.extend(sub_lines) + i += 1 + continue + + # Not found in project dirs - keep as-is + result.append(line) + i += 1 + continue + + # Everything else + result.append(line) + i += 1 + + return result + + +def main(): + parser = argparse.ArgumentParser( + description="Amalgamate enum_name headers into a single include file." + ) + parser.add_argument( + '--main', + required=True, + help='Main entry point header file ' + '(e.g. include/mgutility/reflection/enum_name.hpp)' + ) + parser.add_argument( + '--source-dir', + required=True, + action='append', + dest='source_dirs', + help='Include root directories to resolve local includes ' + '(can be specified multiple times)' + ) + parser.add_argument( + '--output', + required=True, + help='Output file path for the amalgamated header' + ) + + args = parser.parse_args() + + if not os.path.isfile(args.main): + print(f"Error: Main file '{args.main}' not found.", file=sys.stderr) + sys.exit(1) + + for sd in args.source_dirs: + if not os.path.isdir(sd): + print(f"Error: Source directory '{sd}' not found.", file=sys.stderr) + sys.exit(1) + + seen_guards: Set[str] = set() + processed_files: Set[str] = set() + + abs_main = os.path.abspath(args.main) + lines, guard = read_file_content(abs_main) + + # Register the main file's guard + if guard: + seen_guards.add(guard) + processed_files.add(abs_main) + + output_lines: List[str] = [] + + i = 0 + while i < len(lines): + line = lines[i] + stripped = line.strip() + + # Check for local include: #include "..." + m_local = RE_INCLUDE_LOCAL.match(stripped) + if m_local: + inc_path = m_local.group(1) + resolved = find_file(inc_path, args.source_dirs, + os.path.dirname(abs_main)) + + if resolved and os.path.isfile(resolved): + # Pre-check: if the included file has a guard already seen + _, inc_guard = read_file_content(resolved) + + if inc_guard and inc_guard in seen_guards: + i += 1 + continue + + # Mark guard as seen now so recursive processing skips it + if inc_guard: + seen_guards.add(inc_guard) + + # Inline the content + sub_lines = inline_file(resolved, args.source_dirs, + seen_guards, processed_files) + if sub_lines: + output_lines.append('\n') + output_lines.extend(sub_lines) + + i += 1 + continue + + # Not found in project dirs - keep the include line as-is + output_lines.append(line) + i += 1 + continue + + # Check for system include: #include <...> + m_sys = RE_INCLUDE_SYSTEM.match(stripped) + if m_sys: + output_lines.append(line) + i += 1 + continue + + # Everything else: comments, code, blank lines, etc. + output_lines.append(line) + i += 1 + + # Write output + os.makedirs(os.path.dirname(args.output), exist_ok=True) + with open(args.output, 'w', encoding='utf-8') as f: + f.writelines(output_lines) + + print(f"Amalgamated header written to: {args.output}") + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/single_include/mgutility_enum_name.hpp b/single_include/mgutility_enum_name.hpp index a486571..480fdbc 100644 --- a/single_include/mgutility_enum_name.hpp +++ b/single_include/mgutility_enum_name.hpp @@ -1,6 +1,119 @@ /* MIT License +Copyright (c) 2023 mguludag + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef MGUTILITY_ENUM_NAME_HPP +#define MGUTILITY_ENUM_NAME_HPP + + +/* +MIT License + +Copyright (c) 2024 mguludag + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef MGUTILITY_REFLECTION_DETAIL_ENUM_NAME_IMPL_HPP +#define MGUTILITY_REFLECTION_DETAIL_ENUM_NAME_IMPL_HPP + +// NOLINTNEXTLINE [unused-includes] + +/* +MIT License + +Copyright (c) 2024 mguludag + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef DETAIL_ENUM_FOR_EACH_HPP +#define DETAIL_ENUM_FOR_EACH_HPP + + +/* +MIT License + +Copyright (c) 2024 mguludag + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef DETAIL_META_HPP +#define DETAIL_META_HPP + + +/* +MIT License + Copyright (c) 2024 mguludag Permission is hereby granted, free of charge, to any person obtaining a copy @@ -98,10 +211,34 @@ SOFTWARE. #define MGUTILITY_HAS_HAS_INCLUDE #endif + #endif // MGUTILITY_COMMON_DEFINITIONS_HPP +/* +MIT License -#ifndef DETAIL_META_HPP -#define DETAIL_META_HPP +Copyright (c) 2024 mguludag + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef MGUTILITY_DETAIL_UTILITY_HPP +#define MGUTILITY_DETAIL_UTILITY_HPP #include #include @@ -110,6 +247,78 @@ SOFTWARE. namespace mgutility { namespace detail { +/** + * @brief Represents a compile-time sequence of indices. + * + * @tparam Ints The sequence of indices. + */ +template struct index_sequence {}; + +/** + * @brief Concatenates two index sequences. + * + * @tparam Seq1 The first index sequence. + * @tparam Seq2 The second index sequence. + */ +template struct index_sequence_concat; + +template +struct index_sequence_concat, index_sequence> { + using type = index_sequence; +}; + +/** + * @brief Implementation helper for creating index sequences. + * + * @tparam N The size of the index sequence to create. + */ +template struct make_index_sequence_impl; + +template struct make_index_sequence_impl { +private: + static constexpr std::size_t half = N / 2; + + using first = typename make_index_sequence_impl::type; + using second = typename make_index_sequence_impl::type; + +public: + using type = typename index_sequence_concat::type; +}; + +// base cases +/** + * @brief Base case for index sequence of size 0. + */ +template <> struct make_index_sequence_impl<0> { + using type = index_sequence<>; +}; + +/** + * @brief Base case for index sequence of size 1. + */ +template <> struct make_index_sequence_impl<1> { + using type = index_sequence<0>; +}; + +/** + * @brief Alias for creating an index sequence of size N. + * + * @tparam N The size of the index sequence. + */ +template +using make_index_sequence = typename make_index_sequence_impl::type; +} // namespace detail + +} // namespace mgutility + +#endif // DETAIL_META_HPP +#include +#include +#include + +namespace mgutility { +namespace detail { + #ifndef MGUTILITY_ENUM_RANGE_MIN /** * @brief Defines the MGUTILITY_ENUM_RANGE_MIN macro. @@ -137,7 +346,28 @@ namespace detail { */ #ifndef MGUTILITY_ENUM_NAME_BUFFER_SIZE // NOLINTNEXTLINE [cppcoreguidelines-macro-usage] -#define MGUTILITY_ENUM_NAME_BUFFER_SIZE 128U +#define MGUTILITY_ENUM_NAME_BUFFER_SIZE 32U +#endif + +/** + * @brief Defines the MGUTILITY_GLOBAL_ENUM_BLOB_SIZE macro. + * + * This macro defines the size of the global fixed-size buffer shared by + * all enum types in enum_name_parse_result. All enum names for all enums + * with the same underlying type are stored in this shared buffer, reducing + * template bloat compared to per-type fixed_string instantiations. + */ +#ifndef MGUTILITY_GLOBAL_ENUM_BLOB_SIZE +// NOLINTNEXTLINE [cppcoreguidelines-macro-usage] +#define MGUTILITY_GLOBAL_ENUM_BLOB_SIZE 8192 +#endif + +#ifndef MGUTILITY_INLINE +#if MGUTILITY_CPLUSPLUS > 201402L +#define MGUTILITY_INLINE inline +#else +#define MGUTILITY_INLINE +#endif #endif /** @@ -209,72 +439,11 @@ using underlying_type_t = typename std::underlying_type::type; /** * @brief Alias template for std::remove_const. * - * @tparam T The type to remove const from. - */ -template -// NOLINTNEXTLINE [modernize-type-traits] -using remove_const_t = typename std::remove_const::type; - -/** - * @brief Represents a compile-time sequence of indices. - * - * @tparam Ints The sequence of indices. - */ -template struct index_sequence {}; - -/** - * @brief Concatenates two index sequences. - * - * @tparam Seq1 The first index sequence. - * @tparam Seq2 The second index sequence. - */ -template struct index_sequence_concat; - -template -struct index_sequence_concat, index_sequence> { - using type = index_sequence; -}; - -/** - * @brief Implementation helper for creating index sequences. - * - * @tparam N The size of the index sequence to create. - */ -template struct make_index_sequence_impl; - -template struct make_index_sequence_impl { -private: - static constexpr std::size_t half = N / 2; - - using first = typename make_index_sequence_impl::type; - using second = typename make_index_sequence_impl::type; - -public: - using type = typename index_sequence_concat::type; -}; - -// base cases -/** - * @brief Base case for index sequence of size 0. - */ -template <> struct make_index_sequence_impl<0> { - using type = index_sequence<>; -}; - -/** - * @brief Base case for index sequence of size 1. - */ -template <> struct make_index_sequence_impl<1> { - using type = index_sequence<0>; -}; - -/** - * @brief Alias for creating an index sequence of size N. - * - * @tparam N The size of the index sequence. + * @tparam T The type to remove const from. */ -template -using make_index_sequence = typename make_index_sequence_impl::type; +template +// NOLINTNEXTLINE [modernize-type-traits] +using remove_const_t = typename std::remove_const::type; /** * @brief Represents a sequence of enumeration values. @@ -355,11 +524,11 @@ using flat_map = pair[]; */ template struct custom_enum { // #if MGUTILITY_CPLUSPLUS > 201402L - static constexpr flat_map map = {}; + static MGUTILITY_INLINE constexpr flat_map map = {}; // #else - // static constexpr flat_map map() noexcept { - // return {}; // default: empty map - // } + // static constexpr flat_map map() noexcept { + // return {}; // default: empty map + // } // #endif }; @@ -375,6 +544,57 @@ template struct enum_name_buffer { } // namespace mgutility #endif // DETAIL_META_HPP +/* +MIT License + +Copyright (c) 2025 mguludag + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef MGUTILITY_FIXED_STRING_HPP +#define MGUTILITY_FIXED_STRING_HPP + + +/* +MIT License + +Copyright (c) 2024 mguludag + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ #ifndef MGUTILITY_STRING_VIEW_HPP #define MGUTILITY_STRING_VIEW_HPP @@ -385,6 +605,7 @@ template struct enum_name_buffer { #include #include + #if MGUTILITY_CPLUSPLUS > 201402L #include #endif @@ -558,6 +779,20 @@ template class basic_string_view { */ constexpr const Char *end() const noexcept { return (data_ + size_); } + /** + * @brief Returns a reference to the first character. + * + * @return A reference to the first character. + */ + constexpr const Char &front() const noexcept { return data_[0]; } + + /** + * @brief Returns a reference to the last character. + * + * @return A reference to the last character. + */ + constexpr const Char &back() const noexcept { return data_[size_ - 1]; } + /** * @brief Checks if the string is empty. * @@ -587,9 +822,9 @@ template class basic_string_view { * @return A basic_string_view representing the substring. */ constexpr basic_string_view substr(size_t begin, - size_t len = 0U) const noexcept { + size_t len = npos) const noexcept { return basic_string_view(data_ + begin, - len == 0U ? size_ - begin : len); + len == npos ? size_ - begin : len); } /** @@ -601,12 +836,21 @@ template class basic_string_view { */ // NOLINTNEXTLINE [readability-identifier-length] constexpr size_t rfind(Char c, size_t pos = npos) const noexcept { +#if MGUTILITY_CPLUSPLUS < 201402L // NOLINTNEXTLINE [cppcoreguidelines-pro-bounds-pointer-arithmetic] return (pos == npos ? pos = size_ : pos = pos), c == data_[pos] ? pos // NOLINTNEXTLINE [readability-avoid-nested-conditional-operator] : pos == 0U ? npos : rfind(c, --pos); +#else + for (size_t i = (pos == npos ? size_ : pos) - 1; i < size_; --i) { + if (data_[i] == c) { + return i; + } + } + return npos; +#endif } /** @@ -618,8 +862,17 @@ template class basic_string_view { */ // NOLINTNEXTLINE [readability-identifier-length] constexpr size_t find(Char c, size_t pos = 0) const noexcept { +#if MGUTILITY_CPLUSPLUS < 201402L // NOLINTNEXTLINE [readability-avoid-nested-conditional-operator] return c == data_[pos] ? pos : pos < size_ ? find(c, ++pos) : npos; +#else + for (size_t i = pos; i < size_; ++i) { + if (data_[i] == c) { + return i; + } + } + return npos; +#endif } /** @@ -728,12 +981,11 @@ using string_view = std::string_view; #endif // STRING_STRING_VIEW_HPP -#ifndef MGUTILITY_FIXED_STRING_HPP -#define MGUTILITY_FIXED_STRING_HPP - namespace mgutility { template class fixed_string { + template friend class fixed_string; + public: template // NOLINTNEXTLINE [cppcoreguidelines-avoid-c-arrays] @@ -743,14 +995,36 @@ template class fixed_string { MGUTILITY_CNSTXPR fixed_string() = default; + // --- safe pack expansion (no OOB) --- + template + constexpr fixed_string(const char (&str)[M], detail::index_sequence) + : data_{(Is < M ? str[Is] : '\0')...}, cursor_(M ? M - 1 : 0) {} + + // single literal constructor (handles all M) + template + constexpr fixed_string(const char (&str)[M]) + : fixed_string(str, typename detail::make_index_sequence{}) {} + + MGUTILITY_CNSTXPR fixed_string(const char *str, std::size_t len) + : data_{}, cursor_(len < N ? len + : N ? N - 1 + : 0) { + for (std::size_t i = 0; i < N; ++i) { + data_[i] = (i < len) ? str[i] : '\0'; + } + } + + MGUTILITY_CNSTXPR fixed_string(mgutility::string_view str) + : fixed_string(str.data(), str.size()) {} + // Constructor to initialize from a string literal // NOLINTNEXTLINE [cppcoreguidelines-avoid-c-arrays] MGUTILITY_CNSTXPR explicit fixed_string(const char (&str)[N]) { for (size_t i = 0; i < N - 1; ++i) { - data[i] = str[i]; + data_[i] = str[i]; } - cursor = N - 1; - data[cursor] = '\0'; + cursor_ = N - 1; + data_[cursor_] = '\0'; } // Concatenation operator @@ -759,17 +1033,17 @@ template class fixed_string { -> fixed_string { fixed_string result{}; size_t idx = 0; - for (; idx < N - 1; ++idx) { + for (; idx < cursor_; ++idx) { // NOLINTNEXTLINE [cppcoreguidelines-pro-bounds-constant-array-index] - result.data[idx] = data[idx]; + result.data_[idx] = data_[idx]; } - for (size_t j = 0; j < M; ++j) { + for (size_t j = 0; j < other.size(); ++j) { // NOLINTNEXTLINE [cppcoreguidelines-pro-bounds-constant-array-index] - result.data[idx + j] = other.data[j]; + result.data_[idx + j] = other.data_[j]; } - result.cursor = N + M - 2; + result.cursor_ = cursor_ + other.size(); // NOLINTNEXTLINE [cppcoreguidelines-pro-bounds-constant-array-index] - result.data[result.cursor] = '\0'; + result.data_[result.cursor_] = '\0'; return result; } @@ -788,53 +1062,56 @@ template class fixed_string { "Capacity needs to be greater than string to be appended!"); for (size_t i = 0; i < M - 1; ++i) { // NOLINTNEXTLINE [cppcoreguidelines-pro-bounds-constant-array-index] - data[cursor++] = str[i]; + data_[cursor_++] = str[i]; } // NOLINTNEXTLINE [cppcoreguidelines-pro-bounds-constant-array-index] - data[cursor] = '\0'; + data_[cursor_] = '\0'; return *this; } MGUTILITY_CNSTXPR auto append(string_view str) -> fixed_string & { - for (const char chr : str) { + auto len = str.size() > N - cursor_ ? N - cursor_ : str.size(); + for (std::size_t i = 0; i < len; ++i) { // NOLINTNEXTLINE [cppcoreguidelines-pro-bounds-constant-array-index] - data[cursor++] = chr; + data_[cursor_++] = str[i]; } // NOLINTNEXTLINE [cppcoreguidelines-pro-bounds-constant-array-index] - data[cursor] = '\0'; + data_[cursor_] = '\0'; return *this; } MGUTILITY_CNSTXPR auto pop_back() -> void { - if (cursor > 0) { + if (cursor_ > 0) { // NOLINTNEXTLINE [cppcoreguidelines-pro-bounds-constant-array-index] - data[--cursor] = '\0'; + data_[--cursor_] = '\0'; } } - MGUTILITY_CNSTXPR auto size() const -> size_t { return cursor; } + MGUTILITY_CNSTXPR auto size() const -> size_t { return cursor_; } // NOLINTNEXTLINE [readability-identifier-length] constexpr size_t find(char c, size_t pos = 0) const noexcept { // NOLINTNEXTLINE [readability-avoid-nested-conditional-operator] - return c == data[pos] ? pos : (pos < cursor ? find(c, ++pos) : npos); + return c == data_[pos] ? pos : (pos < cursor_ ? find(c, ++pos) : npos); } // Conversion to std::string_view for easy printing // NOLINTNEXTLINE[google-explicit-constructor] MGUTILITY_CNSTXPR operator string_view() const { - return string_view(data, cursor); + return string_view(data_, cursor_); } MGUTILITY_CNSTXPR auto view() const -> string_view { - return string_view(data, cursor); + return string_view(data_, cursor_); } - constexpr bool empty() const noexcept { return cursor == 0; } + constexpr const char *data() const noexcept { return data_; } + + constexpr bool empty() const noexcept { return cursor_ == 0; } constexpr const char &operator[](size_t index) const noexcept { // NOLINTNEXTLINE [cppcoreguidelines-pro-bounds-constant-array-index] - return data[index]; + return data_[index]; } MGUTILITY_CNSTXPR inline bool operator==(const char *rhs) const { @@ -844,9 +1121,9 @@ template class fixed_string { // NOLINTNEXTLINE [readability-identifier-length] friend std::ostream &operator<<(std::ostream &os, const fixed_string &str) { - for (size_t i = 0; i < str.cursor; ++i) { + for (size_t i = 0; i < str.cursor_; ++i) { // NOLINTNEXTLINE [cppcoreguidelines-pro-bounds-constant-array-index] - os << str.data[i]; + os << str.data_[i]; } return os; } @@ -855,8 +1132,8 @@ template class fixed_string { private: // NOLINTNEXTLINE [cppcoreguidelines-avoid-c-arrays] - char data[N]{'\0'}; - size_t cursor{}; + char data_[N]{'\0'}; + size_t cursor_{}; }; } // namespace mgutility @@ -885,20 +1162,202 @@ struct std::formatter> (defined(MGUTILITY_HAS_HAS_INCLUDE) && __has_include()) #include -template -struct fmt::formatter> : formatter { - auto format(const mgutility::fixed_string &str, format_context &ctx) const - -> appender { - return formatter::format(str.view().data(), ctx); - } -}; -#endif // MGUTILITY_USE_FMT || __has_include() +template +struct fmt::formatter> : formatter { + auto format(const mgutility::fixed_string &str, format_context &ctx) const + -> appender { + return formatter::format(str.view().data(), ctx); + } +}; +#endif // MGUTILITY_USE_FMT || __has_include() + +#endif // MGUTILITY_FIXED_STRING_HPP + +// NOLINTNEXTLINE [unused-includes] +#include +#include + +namespace mgutility { +namespace detail { + +/** + * @brief Alias template for a string or string view type based on the presence + * of a bitwise OR operator. + * + * If the type T supports the bitwise OR operator, the alias is a std::string. + * Otherwise, it is a mgutility::string_view. + * + * @tparam T The type to check. + */ +template +// NOLINTNEXTLINE [modernize-type-traits] +using string_or_view_t = typename std::conditional< + has_bit_or::value, mgutility::fixed_string::size>, + mgutility::string_view>::type; + +/** + * @brief A pair consisting of an enum value and its corresponding string or + * string view. + * + * @tparam Enum The enum type. + */ +template +using enum_pair = std::pair>; +} // namespace detail + +/** + * @brief A class template for iterating over enum values. + * + * @tparam Enum The enum type. + */ +template class enum_for_each { + using value_type = const detail::enum_pair; + using size_type = std::size_t; + + /** + * @brief An iterator for enum values. + */ + struct enum_iter { + using const_iter_type = int; + using iter_type = detail::remove_const_t; + using iterator_category = std::forward_iterator_tag; + using value_type = const detail::enum_pair; + using difference_type = std::ptrdiff_t; + using pointer = value_type *; + using reference = value_type &; + + /** + * @brief Default constructor initializing the iterator to the default + * position. + */ + enum_iter() : m_pos{} {} + + /** + * @brief Constructor initializing the iterator to a specific position. + * + * @param value The initial position of the iterator. + */ + explicit enum_iter(iter_type value) : m_pos{value} {} + + /** + * @brief Pre-increment operator. + * + * @return A reference to the incremented iterator. + */ + auto operator++() -> enum_iter & { + ++m_pos; + return *this; + } + + /** + * @brief Post-increment operator. + * + * @return A copy of the iterator before incrementing. + */ + auto operator++(int) -> enum_iter { + m_pos++; + return *this; + } + + /** + * @brief Inequality comparison operator. + * + * @param other The other iterator to compare with. + * @return True if the iterators are not equal, otherwise false. + */ + auto operator!=(const enum_iter &other) const -> bool { + return m_pos != other.m_pos; + } + + /** + * @brief Equality comparison operator. + * + * @param other The other iterator to compare with. + * @return True if the iterators are equal, otherwise false. + */ + auto operator==(const enum_iter &other) const -> bool { + return m_pos == other.m_pos; + } + + /** + * @brief Dereference operator. + * + * @return The current enum pair. + */ + auto operator*() const -> value_type; + + private: + iter_type m_pos; /**< The current position of the iterator. */ + }; + +public: + /** + * @brief Default constructor. + */ + enum_for_each() = default; + + /** + * @brief Returns an iterator to the beginning of the enum range. + * + * @return A reference to the beginning iterator. + */ + auto begin() -> enum_iter & { return m_begin; } + + /** + * @brief Returns an iterator to the end of the enum range. + * + * @return A reference to the end iterator. + */ + auto end() -> enum_iter & { return m_end; } + + /** + * @brief Returns the size of the enum range. + * + * @return The size of the enum range. + */ + auto size() -> std::size_t { + return static_cast(enum_range::max) - + static_cast(enum_range::min) + 1; + } + +private: + enum_iter m_begin{ + static_cast(enum_range::min)}; /**< The beginning iterator. */ + enum_iter m_end{static_cast(enum_range::max) + + 1}; /**< The end iterator. */ +}; +} // namespace mgutility + +#endif // DETAIL_ENUM_FOR_EACH_HPP +/* +MIT License + +Copyright (c) 2024 mguludag + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -#endif // MGUTILITY_FIXED_STRING_HPP +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ #ifndef DETAIL_OPTIONAL_HPP #define DETAIL_OPTIONAL_HPP +// NOLINTNEXTLINE [unused-includes] + // NOLINTNEXTLINE [unused-includes] #include @@ -1206,172 +1665,10 @@ inline constexpr auto nullopt{std::nullopt}; } // namespace mgutility #endif // DETAIL_OPTIONAL_HPP - -#ifndef DETAIL_ENUM_FOR_EACH_HPP -#define DETAIL_ENUM_FOR_EACH_HPP - -// NOLINTNEXTLINE [unused-includes] -#include -#include - -namespace mgutility { -namespace detail { -/** - * @brief Alias template for a string or string view type based on the presence - * of a bitwise OR operator. - * - * If the type T supports the bitwise OR operator, the alias is a std::string. - * Otherwise, it is a mgutility::string_view. - * - * @tparam T The type to check. - */ -template -// NOLINTNEXTLINE [modernize-type-traits] -using string_or_view_t = typename std::conditional< - has_bit_or::value, mgutility::fixed_string::size>, - mgutility::string_view>::type; - -/** - * @brief A pair consisting of an enum value and its corresponding string or - * string view. - * - * @tparam Enum The enum type. - */ -template -using enum_pair = std::pair>; -} // namespace detail - -/** - * @brief A class template for iterating over enum values. - * - * @tparam Enum The enum type. - */ -template class enum_for_each { - using value_type = const detail::enum_pair; - using size_type = std::size_t; - - /** - * @brief An iterator for enum values. - */ - struct enum_iter { - using const_iter_type = int; - using iter_type = detail::remove_const_t; - using iterator_category = std::forward_iterator_tag; - using value_type = const detail::enum_pair; - using difference_type = std::ptrdiff_t; - using pointer = value_type *; - using reference = value_type &; - - /** - * @brief Default constructor initializing the iterator to the default - * position. - */ - enum_iter() : m_pos{} {} - - /** - * @brief Constructor initializing the iterator to a specific position. - * - * @param value The initial position of the iterator. - */ - explicit enum_iter(iter_type value) : m_pos{value} {} - - /** - * @brief Pre-increment operator. - * - * @return A reference to the incremented iterator. - */ - auto operator++() -> enum_iter & { - ++m_pos; - return *this; - } - - /** - * @brief Post-increment operator. - * - * @return A copy of the iterator before incrementing. - */ - auto operator++(int) -> enum_iter { - m_pos++; - return *this; - } - - /** - * @brief Inequality comparison operator. - * - * @param other The other iterator to compare with. - * @return True if the iterators are not equal, otherwise false. - */ - auto operator!=(const enum_iter &other) const -> bool { - return m_pos != other.m_pos; - } - - /** - * @brief Equality comparison operator. - * - * @param other The other iterator to compare with. - * @return True if the iterators are equal, otherwise false. - */ - auto operator==(const enum_iter &other) const -> bool { - return m_pos == other.m_pos; - } - - /** - * @brief Dereference operator. - * - * @return The current enum pair. - */ - auto operator*() const -> value_type; - - private: - iter_type m_pos; /**< The current position of the iterator. */ - }; - -public: - /** - * @brief Default constructor. - */ - enum_for_each() = default; - - /** - * @brief Returns an iterator to the beginning of the enum range. - * - * @return A reference to the beginning iterator. - */ - auto begin() -> enum_iter & { return m_begin; } - - /** - * @brief Returns an iterator to the end of the enum range. - * - * @return A reference to the end iterator. - */ - auto end() -> enum_iter & { return m_end; } - - /** - * @brief Returns the size of the enum range. - * - * @return The size of the enum range. - */ - auto size() -> std::size_t { - return static_cast(enum_range::max) - - static_cast(enum_range::min) + 1; - } - -private: - enum_iter m_begin{ - static_cast(enum_range::min)}; /**< The beginning iterator. */ - enum_iter m_end{static_cast(enum_range::max) + - 1}; /**< The end iterator. */ -}; -} // namespace mgutility - -#endif // DETAIL_ENUM_FOR_EACH_HPP - -#ifndef MGUTILITY_REFLECTION_DETAIL_ENUM_NAME_IMPL_HPP -#define MGUTILITY_REFLECTION_DETAIL_ENUM_NAME_IMPL_HPP - -// NOLINTNEXTLINE [unused-includes] #include #include +#include +#include /** * @brief Checks for MSVC compiler version. @@ -1421,6 +1718,29 @@ namespace detail { #define MGUTILITY_STRLEN(x) sizeof(x) - 1 #endif +/** + * @brief Parse result for enum names. + * + * Templated on the underlying type U instead of the enum type itself, + * so all enums with the same underlying type (e.g., all 'int'-based enums) + * share the same struct type. The strings buffer is a fixed size + * (MGUTILITY_GLOBAL_ENUM_BLOB_SIZE) rather than a per-enum-type + * fixed_string, reducing template bloat. + * + * @tparam U The underlying type of the enum (e.g., int, unsigned int). + * @tparam Min The minimum enum value. + * @tparam Max The maximum enum value. + */ +template struct enum_name_parse_result { + static constexpr auto size = std::size_t{Max - Min}; + fixed_string strings; + std::array, size> ranges; +}; + +template +using enum_name_array = + std::array(Max - Min)>; + /** * @brief Provides functionality to extract and parse enum names at * compile-time. @@ -1434,16 +1754,20 @@ struct enum_type { * @tparam e The enum value. * @return The raw string_view from __PRETTY_FUNCTION__. */ - template - MGUTILITY_CNSTXPR static mgutility::string_view raw_name() noexcept { + template + MGUTILITY_CNSTXPR static auto + raw_name(detail::enum_sequence /*unused*/) noexcept + -> mgutility::string_view { #if defined(__GNUC__) && !defined(__clang__) && MGUTILITY_CPLUSPLUS >= 201402L #define PREFIX \ MGUTILITY_STRLEN("static constexpr mgutility::string_view " \ - "mgutility::detail::enum_type::raw_name()") + "mgutility::detail::enum_type::raw_name(mgutility::detail:" \ + ":enum_sequence) [with Enum = ") #elif defined(__clang__) || defined(__GNUC__) #define PREFIX \ MGUTILITY_STRLEN("static mgutility::string_view " \ - "mgutility::detail::enum_type::raw_name()") + "mgutility::detail::enum_type::raw_name(mgutility::detail:" \ + ":enum_sequence) [with Enum = ") #elif defined(_MSC_VER) #if MGUTILITY_CPLUSPLUS > 201402L #define PREFIX \ @@ -1470,50 +1794,88 @@ struct enum_type { * @param str The raw string from __PRETTY_FUNCTION__. * @return The parsed enum name. */ - MGUTILITY_CNSTXPR static mgutility::string_view - parse(mgutility::string_view str) noexcept { + template + MGUTILITY_CNSTXPR static auto parse() noexcept + -> enum_name_parse_result, Min, Max> { + using U = detail::underlying_type_t; + using result_type = enum_name_parse_result; + + MGUTILITY_CNSTXPR auto str = + raw_name(detail::make_enum_sequence{}); + #if defined(__clang__) || defined(__GNUC__) #if defined(__clang__) auto end = str.rfind(']'); #elif defined(__GNUC__) && !defined(__clang__) auto end = str.rfind(';'); #endif - // Typical form: - // "Enum = MyEnum::Value]" - auto pos = str.rfind('=', end); - if (pos == mgutility::string_view::npos) { - return {}; - } - pos += 2; // skip "::" - - auto result = str.substr(pos, end - pos); + auto enum_names = str.substr(0, end); #elif defined(_MSC_VER) // MSVC: different format - auto pos = str.rfind(','); + auto pos = str.find(','); if (pos == mgutility::string_view::npos) return {}; ++pos; auto end = str.rfind('>'); - auto result = str.substr(pos, end - pos); + auto enum_names = str.substr(pos, end - pos); #else return {}; #endif - if (result.empty()) { + if (enum_names.empty()) { return {}; } - // invalid cases look like "(Enum)123" - if (result[0] == '(') { - return {}; + result_type result{}; + + std::size_t idx = 0; + + while (!enum_names.empty() && idx < result.ranges.size()) { + auto pos = enum_names.find(','); + if (pos != mgutility::string_view::npos) { + auto token = enum_names.substr(0, pos); + + // remove whitespace + while (!token.empty() && token.front() == ' ') { + token = token.substr(1); + } + + while (!token.empty() && token.back() == ' ') { + token = token.substr(0, token.size() - 1); + } + + std::size_t begin = token.find('('); + std::size_t end = token.rfind(')'); + if (begin != mgutility::string_view::npos || + end != mgutility::string_view::npos) { + result.ranges[idx++] = {0, 0}; + enum_names = enum_names.substr(pos + 1); + continue; + } + + std::size_t start = 0; + if (token.find(':') != mgutility::string_view::npos) { + start = token.find(':') + 2; + token = token.substr(start); + } + + // Write into the fixed-size global buffer + auto offset = result.strings.size(); + result.strings.append(token); + result.ranges[idx++] = {offset, token.size()}; + + enum_names = enum_names.substr(pos + 1); + continue; + } + enum_names = {}; } - return result.substr(result.rfind(':') + 1); + return result; } public: @@ -1524,9 +1886,10 @@ struct enum_type { * @tparam e The enum value. * @return The name of the enum value. */ - template - MGUTILITY_CNSTXPR static mgutility::string_view name() noexcept { - return parse(raw_name()); + template + MGUTILITY_CNSTXPR static auto name() noexcept + -> enum_name_parse_result, Min, Max> { + return parse(); } }; @@ -1536,7 +1899,7 @@ struct enum_type { * @tparam Enum The enum type. * @tparam Seq The enum sequence. */ -template struct enum_array_cache; +template struct enum_array_cache; /** * @brief Specialization of enum_array_cache for enum_sequence. @@ -1544,75 +1907,49 @@ template struct enum_array_cache; * @tparam Enum The enum type. * @tparam Is The enum values. */ -template -struct enum_array_cache> { -#if MGUTILITY_CPLUSPLUS >= 201402L - // C++17+: fully constexpr - // NOLINTNEXTLINE [readability-redundant-inline-specifier] - static inline constexpr std::array - value() { - std::array arr{ - "", enum_type::template name()...}; +template struct enum_array_cache { + using underlying = detail::underlying_type_t; + using parse_result_t = enum_name_parse_result; - constexpr auto map = mgutility::custom_enum::map; - - for (const auto &pair : map) { - const int raw = static_cast(pair.first); - const auto idx = - static_cast(raw - mgutility::enum_range::min + 1); +#if MGUTILITY_CPLUSPLUS > 201402L - if (idx >= 1 && idx < arr.size()) { - arr[idx] = pair.second; - } - } + static constexpr auto parse_result = + enum_type::template name(); - return arr; - } #else // C++11: lazy runtime array - static const std::array &value() { - static const std::array arr = - [] { - std::array tmp{ - "", enum_type::template name()...}; - - for (const auto &pair : mgutility::custom_enum::map) { - auto idx = - static_cast(static_cast(pair.first) - - mgutility::enum_range::min + 1); - - if (idx >= 1 && idx < tmp.size()) { - tmp[idx] = pair.second; - } - } - - return tmp; - }(); + static parse_result_t &value() { + static parse_result_t arr = enum_type::template name(); return arr; } #endif -}; -/** - * @brief Gets an array of enum names for the given sequence. - * - * @tparam Enum The enum type. - * @tparam Is The enum values. - * @param unused The enum sequence (unused parameter). - * @return An array of string_views containing the enum names. - */ -template -MGUTILITY_CNSTXPR auto -get_enum_array(detail::enum_sequence /*unused*/) noexcept + static MGUTILITY_CNSTXPR auto + apply_custom(const parse_result_t &result) noexcept + -> enum_name_array { + enum_name_array arr{}; + + for (std::size_t idx = 0; idx < result.ranges.size(); ++idx) { + arr[idx] = result.strings.view().substr(result.ranges[idx].first, + result.ranges[idx].second); + } + #if MGUTILITY_CPLUSPLUS >= 201402L - -> std::array { - return enum_array_cache>::value(); + constexpr auto map = mgutility::custom_enum::map; + for (const auto &pair : map) { #else - -> const std::array & { - return enum_array_cache>::value(); + for (const auto &pair : mgutility::custom_enum::map) { #endif -} + if (pair.first >= static_cast(Min) && + pair.first < static_cast(Max)) { + arr[static_cast(pair.first) - Min] = pair.second; + } + } + + return arr; + } +}; /** * @brief Gets an array of enum names for the enum type within the specified @@ -1626,12 +1963,13 @@ get_enum_array(detail::enum_sequence /*unused*/) noexcept template ::min, int Max = mgutility::enum_range::max> MGUTILITY_CNSTXPR auto get_enum_array() noexcept -#if MGUTILITY_CPLUSPLUS >= 201402L - -> std::array { - return get_enum_array(detail::make_enum_sequence()); + -> enum_name_array { +#if MGUTILITY_CPLUSPLUS > 201402L + constexpr auto &cache = enum_array_cache::parse_result; + return enum_array_cache::apply_custom(cache); #else - -> const std::array & { - return get_enum_array(detail::make_enum_sequence()); + return enum_array_cache::apply_custom( + enum_array_cache::value()); #endif } @@ -1650,9 +1988,8 @@ MGUTILITY_CNSTXPR inline auto to_enum_impl(mgutility::string_view str) noexcept MGUTILITY_CNSTXPR_CLANG_WA auto arr = get_enum_array(); const auto index{detail::find(arr, str)}; - return index == 0 - ? mgutility::nullopt - : mgutility::optional{static_cast(index + Min - 1)}; + return index == 0 ? mgutility::nullopt + : mgutility::optional{static_cast(index + Min)}; } /** @@ -1714,9 +2051,12 @@ template mgutility::string_view { MGUTILITY_CNSTXPR_CLANG_WA auto arr = get_enum_array(); - const auto index{(Min < 0 ? -Min : Min) + static_cast(enumValue) + 1}; - return arr[static_cast( - (index < Min || index > static_cast(arr.size()) - 1) ? 0 : index)]; + const auto index{(Min < 0 ? -Min : Min) + static_cast(enumValue)}; + if (index < Min || index > static_cast(arr.size()) - 1) { + return mgutility::string_view{}; + } + + return arr[static_cast(index)]; } /** @@ -1739,23 +2079,22 @@ MGUTILITY_CNSTXPR_CLANG_WA auto enum_name_impl(Enum enumValue) noexcept MGUTILITY_CNSTXPR_CLANG_WA auto arr = get_enum_array(); // Calculate the index in the array - const auto index = (Min < 0 ? -Min : Min) + static_cast(enumValue) + 1; - const auto name = - arr[(index < Min || index >= static_cast(arr.size())) ? 0 : index]; + const auto index = (Min < 0 ? -Min : Min) + static_cast(enumValue); + + mgutility::fixed_string::size> bitmasked_name; - // Return the name if it's valid - if (!name.empty() && !is_digit(name[0])) { - return mgutility::fixed_string::size>{}.append(name); + if (index >= 0 && index < static_cast(arr.size())) { + bitmasked_name.append(arr[static_cast(index)]); } - // Construct bitmasked name - mgutility::fixed_string::size> bitmasked_name; - for (auto i = Min; i < Max; ++i) { - const auto idx = (Min < 0 ? -Min : Min) + i + 1; - if (idx >= 0 && idx < static_cast(arr.size()) && !arr[idx].empty() && - !detail::is_digit(arr[idx][0]) && + if (!bitmasked_name.empty()) { + return bitmasked_name; + } + + for (auto i = 0; i < Max - Min; ++i) { + if (i >= 0 && i < static_cast(arr.size()) && arr[i].size() > 0 && (enumValue & static_cast(i)) == static_cast(i)) { - bitmasked_name.append(arr[idx]).append("|"); + bitmasked_name.append(arr[i]).append("|"); } } @@ -1770,10 +2109,6 @@ MGUTILITY_CNSTXPR_CLANG_WA auto enum_name_impl(Enum enumValue) noexcept } // namespace mgutility #endif // MGUTILITY_REFLECTION_DETAIL_ENUM_NAME_IMPL_HPP - -#ifndef MGUTILITY_ENUM_NAME_HPP -#define MGUTILITY_ENUM_NAME_HPP - namespace mgutility { /** @@ -1901,7 +2236,7 @@ MGUTILITY_CNSTXPR auto enum_cast(int value) noexcept if (enum_name(static_cast(value)).empty()) { return mgutility::nullopt; } - return static_cast(value); + return mgutility::optional{static_cast(value)}; } namespace operators { @@ -1980,4 +2315,4 @@ struct fmt::formatter) -#endif // MGUTILITY_ENUM_NAME_HPP \ No newline at end of file +#endif // MGUTILITY_ENUM_NAME_HPP From 78c3d2c608a8b6e7cc89b8cc0cee8463b9cd63a8 Mon Sep 17 00:00:00 2001 From: mguludag Date: Sun, 17 May 2026 14:06:46 +0000 Subject: [PATCH 2/4] fix fmt --- CMakeLists.txt | 17 +++++++++-------- single_include/mgutility_enum_name.hpp | 6 ------ 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ba47987..dd9ae4f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -118,13 +118,14 @@ endif() # Single-include header generation find_package(Python3 QUIET) if(Python3_FOUND) - add_custom_target(single_include - COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/scripts/amalgamate.py - --main ${CMAKE_SOURCE_DIR}/include/mgutility/reflection/enum_name.hpp - --source-dir ${CMAKE_SOURCE_DIR}/include - --source-dir ${CMAKE_SOURCE_DIR}/mgutility/include - --output ${CMAKE_SOURCE_DIR}/single_include/mgutility_enum_name.hpp + add_custom_target( + single_include + COMMAND + ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/scripts/amalgamate.py --main + ${CMAKE_SOURCE_DIR}/include/mgutility/reflection/enum_name.hpp + --source-dir ${CMAKE_SOURCE_DIR}/include --source-dir + ${CMAKE_SOURCE_DIR}/mgutility/include --output + ${CMAKE_SOURCE_DIR}/single_include/mgutility_enum_name.hpp COMMENT "Regenerating single-include header from source files" - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - ) + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) endif() diff --git a/single_include/mgutility_enum_name.hpp b/single_include/mgutility_enum_name.hpp index 480fdbc..4abe1c9 100644 --- a/single_include/mgutility_enum_name.hpp +++ b/single_include/mgutility_enum_name.hpp @@ -25,7 +25,6 @@ SOFTWARE. #ifndef MGUTILITY_ENUM_NAME_HPP #define MGUTILITY_ENUM_NAME_HPP - /* MIT License @@ -82,7 +81,6 @@ SOFTWARE. #ifndef DETAIL_ENUM_FOR_EACH_HPP #define DETAIL_ENUM_FOR_EACH_HPP - /* MIT License @@ -110,7 +108,6 @@ SOFTWARE. #ifndef DETAIL_META_HPP #define DETAIL_META_HPP - /* MIT License @@ -211,7 +208,6 @@ SOFTWARE. #define MGUTILITY_HAS_HAS_INCLUDE #endif - #endif // MGUTILITY_COMMON_DEFINITIONS_HPP /* MIT License @@ -571,7 +567,6 @@ SOFTWARE. #ifndef MGUTILITY_FIXED_STRING_HPP #define MGUTILITY_FIXED_STRING_HPP - /* MIT License @@ -605,7 +600,6 @@ SOFTWARE. #include #include - #if MGUTILITY_CPLUSPLUS > 201402L #include #endif From 47f9693d285d3cba932d2f612bafca827b1ff866 Mon Sep 17 00:00:00 2001 From: mguludag Date: Sun, 17 May 2026 14:11:47 +0000 Subject: [PATCH 3/4] run clang-format after codegen --- scripts/amalgamate.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/scripts/amalgamate.py b/scripts/amalgamate.py index 549521f..2763f85 100644 --- a/scripts/amalgamate.py +++ b/scripts/amalgamate.py @@ -13,6 +13,7 @@ import argparse import os import re +import subprocess import sys from typing import List, Optional, Set, Tuple @@ -240,6 +241,20 @@ def main(): with open(args.output, 'w', encoding='utf-8') as f: f.writelines(output_lines) + # Run clang-format on the output file (if available) + try: + subprocess.run( + ['clang-format', '-i', '-style=file', args.output], + check=True, capture_output=True, timeout=30 + ) + print(f"Formatted: {args.output}") + except FileNotFoundError: + print("Warning: clang-format not found, skipping formatting", + file=sys.stderr) + except subprocess.CalledProcessError as e: + print(f"Warning: clang-format failed: {e.stderr.decode().strip()}", + file=sys.stderr) + print(f"Amalgamated header written to: {args.output}") From d22c19bd0f31c33100f0fd82327af2ad811fd1c4 Mon Sep 17 00:00:00 2001 From: mguludag Date: Sun, 17 May 2026 14:20:00 +0000 Subject: [PATCH 4/4] chore: update single-include regeneration workflow to create pull request on changes --- .github/workflows/c-cpp.yml | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index 9d178aa..706135d 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -104,12 +104,11 @@ jobs: regenerate-single-include: runs-on: ubuntu-latest - if: github.ref == 'refs/heads/main' && github.event_name == 'push' + if: github.event_name == 'push' steps: - uses: actions/checkout@v4 with: submodules: true - token: ${{ secrets.GITHUB_TOKEN }} - uses: actions/setup-python@v5 with: python-version: '3.x' @@ -120,12 +119,15 @@ jobs: --source-dir include \ --source-dir mgutility/include \ --output single_include/mgutility_enum_name.hpp - - name: Commit and push if changed - run: | - git diff --quiet single_include/mgutility_enum_name.hpp || { - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git add single_include/mgutility_enum_name.hpp - git commit -m "Auto-regenerate single-include header" - git push - } + - name: Create Pull Request if changed + uses: peter-evans/create-pull-request@v7 + with: + add-paths: single_include/mgutility_enum_name.hpp + commit-message: "Auto-regenerate single-include header" + branch: auto-update/single-include + title: "chore: auto-regenerate single-include header" + body: | + Automated regeneration of `single_include/mgutility_enum_name.hpp` + from source headers after recent changes to `${{ github.ref }}`. + delete-branch: true + token: ${{ secrets.GITHUB_TOKEN }}