diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..2977282 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,42 @@ +Checks: >- + bugprone-*, + cppcoreguidelines-*, + google-*, + misc-*, + modernize-*, + performance-*, + readability-*, + -bugprone-lambda-function-name, + -bugprone-reserved-identifier, + -cppcoreguidelines-avoid-goto, + -cppcoreguidelines-avoid-magic-numbers, + -cppcoreguidelines-avoid-non-const-global-variables, + -cppcoreguidelines-pro-bounds-array-to-pointer-decay, + -cppcoreguidelines-pro-type-vararg, + -google-readability-braces-around-statements, + -google-readability-function-size, + -misc-no-recursion, + -modernize-return-braced-init-list, + -modernize-use-nodiscard, + -modernize-use-trailing-return-type, + -performance-unnecessary-value-param, + -readability-magic-numbers, + +CheckOptions: + - key: readability-function-cognitive-complexity.Threshold + value: 100 + - key: readability-function-cognitive-complexity.IgnoreMacros + value: true + # Set naming conventions for your style below (there are dozens of naming settings possible): + # See https://clang.llvm.org/extra/clang-tidy/checks/readability/identifier-naming.html + # - key: readability-identifier-naming.ClassCase + # value: CamelCase + # - key: readability-identifier-naming.NamespaceCase + # value: lower_case + # - key: readability-identifier-naming.PrivateMemberSuffix + # value: _ + # - key: readability-identifier-naming.StructCase + # value: CamelCase + +WarningsAsErrors: '*' +HeaderFilterRegex: '.*' \ No newline at end of file diff --git a/.github/actions/build-docs/action.yaml b/.github/actions/build-docs/action.yaml new file mode 100644 index 0000000..f258303 --- /dev/null +++ b/.github/actions/build-docs/action.yaml @@ -0,0 +1,79 @@ +name: "build-docs" +description: "Builds documentation using Doxygen" +inputs: + cmake_target: + description: "CMake documentation target" + required: true + docs_dir: + description: "Path to documentation dir, relative to build_dir" + required: true + github_token: + description: "GitHub token" + required: true + build_dir: + description: "Build directory" + required: false + default: "build" + cmake_configure_args: + description: "Additional CMake configure arguments" + required: false + default: "" + destination_dir: + description: "Directory name for deployed docs" + required: false + default: "" + docs_branch: + description: "Documentation branch" + required: false + default: "gh-pages" + +outputs: + version: + description: "Version of the generated docs" + value: ${{ steps.get-docs-version.outputs.version }} + +runs: + using: "composite" + steps: + - name: Install deps + shell: bash + run: | + sudo apt install -y cmake + sudo apt install -y wget + cd ~ + wget -nv https://www.doxygen.nl/files/doxygen-1.10.0.linux.bin.tar.gz + tar -xzf doxygen-1.10.0.linux.bin.tar.gz + echo "$(pwd)/doxygen-1.10.0/bin" >> $GITHUB_PATH + + - name: CMake configuration + shell: bash + run: cmake ${{ inputs.cmake_configure_args }} -B ${{ inputs.build_dir }} -DENUM_NAME_BUILD_DOCS=ON + + - name: CMake build + shell: bash + run: cmake --build ${{ inputs.build_dir }} --target ${{ inputs.cmake_target }} + + - name: Get docs version + id: get-docs-version + shell: bash + run: | + subdir=$(basename $(find ${{ inputs.build_dir }}/${{ inputs.docs_dir }} -mindepth 1 -maxdepth 1 -type d | head -n 1)) + echo "version=$subdir" >> $GITHUB_OUTPUT + + - name: Deploy docs + if: ${{ inputs.destination_dir != '' }} + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ inputs.github_token }} + publish_dir: ${{ inputs.build_dir }}/${{ inputs.docs_dir }}/${{ steps.get-docs-version.outputs.version }} + destination_dir: ${{ inputs.destination_dir }} + publish_branch: ${{ inputs.docs_branch }} + + - name: Deploy docs + if: ${{ inputs.destination_dir == '' }} + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ inputs.github_token }} + publish_dir: ${{ inputs.build_dir }}/${{ inputs.docs_dir }}/${{ steps.get-docs-version.outputs.version }} + destination_dir: ${{ steps.get-docs-version.outputs.version }} + publish_branch: ${{ inputs.docs_branch }} diff --git a/.github/actions/update-redirect-page/action.yaml b/.github/actions/update-redirect-page/action.yaml new file mode 100644 index 0000000..5040882 --- /dev/null +++ b/.github/actions/update-redirect-page/action.yaml @@ -0,0 +1,54 @@ +name: "update-redirect-page" +description: "Updates redirect HTML page" +inputs: + github_token: + description: "GitHub token" + required: true + target_url: + description: "Redirect target URL" + temp_dir: + description: "Directory where redirect file will be generated" + required: false + default: "redirect-dir" + file_name: + description: "Generated file name" + required: false + default: "index.html" + destination_dir: + description: "Redirect file destination directory" + required: false + default: "" + docs_branch: + description: "Documentation branch" + required: false + default: "gh-pages" + +runs: + using: "composite" + steps: + - name: Generate redirect HTML + shell: bash + run: | + mkdir ${{ inputs.temp_dir }} + cat << EOF > ${{ inputs.temp_dir }}/${{ inputs.file_name }} + + + + + + Redirecting... + + +

If you are not redirected automatically, click here.

+ + + EOF + + - name: Deploy redirect page + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ inputs.github_token }} + publish_dir: ${{ inputs.temp_dir }} + destination_dir: ${{ inputs.destination_dir }} + publish_branch: ${{ inputs.docs_branch }} + keep_files: true diff --git a/.github/actions/update-version-selector/action.yaml b/.github/actions/update-version-selector/action.yaml new file mode 100644 index 0000000..e48c9cf --- /dev/null +++ b/.github/actions/update-version-selector/action.yaml @@ -0,0 +1,56 @@ +name: "update-version-selector" +description: "Updates version selector" +inputs: + github_token: + description: "GitHub token" + required: true + temp_dir: + description: "Directory where version selector file will be generated" + required: false + default: "selector-dir" + file_name: + description: "Selector file name" + required: false + default: "version_selector.html" + selector_id: + description: "select element id" + required: false + default: "versionSelector" + docs_branch: + description: "Documentation branch" + required: false + default: "gh-pages" +outputs: + versions_counter: + description: "Number of existing versions" + value: ${{ steps.discover-versions.outputs.counter }} + +runs: + using: "composite" + steps: + - name: Discover versions + id: discover-versions + shell: bash + run: | + git fetch origin ${{ inputs.docs_branch }} + dirs=$(git ls-tree --name-only -d origin/${{ inputs.docs_branch }} | sort -rV) + echo "counter=$(echo "$dirs" | wc -l | xargs)" >> $GITHUB_OUTPUT + + mkdir ${{ inputs.temp_dir }} + # Create HTML + echo '' >> ${{ inputs.temp_dir }}/${{ inputs.file_name }} + + - name: Deploy version selector + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ inputs.github_token }} + publish_dir: ${{ inputs.temp_dir }} + publish_branch: ${{ inputs.docs_branch }} + keep_files: true diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index 148cd98..8e54a12 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -2,11 +2,31 @@ name: C/C++ CI on: push: - branches: [ "main" ] + branches: ["main"] pull_request: - branches: [ "main" ] + branches: ["main"] jobs: + windows-build: + runs-on: windows-latest + + strategy: + matrix: + cpp_standard: [11, 17, 20, 23] + + steps: + - name: Checkout code + uses: actions/checkout@main + + - name: Set up MSVC and build + run: | + set PATH=%PATH%;C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\MSVC\14.29.30037\bin\Hostx64\x64 + mkdir build + cd build + cmake -DCMAKE_CXX_STANDARD=${{ matrix.cpp_standard }} .. + cmake --build . --config Release + ctest -j2 --output-on-failure + build_linux: runs-on: ubuntu-latest defaults: @@ -19,14 +39,32 @@ jobs: - { compiler: gcc, version: 9, build_type: Release, cppstd: 11 } - { compiler: gcc, version: 9, build_type: Release, cppstd: 14 } - { compiler: gcc, version: 9, build_type: Release, cppstd: 17 } - - { compiler: gcc, version: 11, build_type: Debug, cppstd: 20 } + - { compiler: gcc, version: 10, build_type: Debug, cppstd: 20 } - { compiler: gcc, version: 12, build_type: Release, cppstd: 20 } - { compiler: clang, version: 11, build_type: Release, cppstd: 11 } - { compiler: clang, version: 11, build_type: Release, cppstd: 14 } - { compiler: clang, version: 11, build_type: Release, cppstd: 17 } - - { compiler: clang, version: 11, build_type: Debug, cppstd: 17, asan: OFF } - - { compiler: clang, version: 12, build_type: Debug, cppstd: 17, asan: OFF } - - { compiler: clang, version: 15, build_type: Release, cppstd: 20, asan: OFF } + - { + compiler: clang, + version: 11, + build_type: Debug, + cppstd: 17, + asan: OFF, + } + - { + compiler: clang, + version: 12, + build_type: Debug, + cppstd: 17, + asan: OFF, + } + - { + compiler: clang, + version: 15, + build_type: Release, + cppstd: 20, + asan: OFF, + } container: image: ${{ matrix.config.compiler == 'clang' && 'teeks99/clang-ubuntu' || matrix.config.compiler }}:${{ matrix.config.version }} name: "${{ matrix.config.compiler}} ${{ matrix.config.version }} (C++${{ matrix.config.cppstd }}, ${{ matrix.config.build_type }})" diff --git a/.github/workflows/create-git-main-docs.yml b/.github/workflows/create-git-main-docs.yml new file mode 100644 index 0000000..ccdd865 --- /dev/null +++ b/.github/workflows/create-git-main-docs.yml @@ -0,0 +1,38 @@ +name: Create git-main docs + +permissions: + contents: write + +on: + push: + branches: + - main + +jobs: + create-git-main-docs: + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v4 + + - name: Build docs + id: build-docs + uses: ./.github/actions/build-docs + with: + cmake_target: "doc" + docs_dir: "doc/docs" + destination_dir: git-main + github_token: ${{ secrets.GITHUB_TOKEN }} + + - name: Update version selector + id: update-version-selector + uses: ./.github/actions/update-version-selector + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + + - name: Create redirect page if there are no releases + if: ${{ steps.update-version-selector.outputs.versions_counter == 1}} + uses: ./.github/actions/update-redirect-page + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + target_url: git-main/index.html diff --git a/.github/workflows/trunk-check.yml b/.github/workflows/trunk-check.yml new file mode 100644 index 0000000..3f831eb --- /dev/null +++ b/.github/workflows/trunk-check.yml @@ -0,0 +1,24 @@ +name: Push +on: [push, pull_request, workflow_dispatch] +concurrency: + group: ${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: read-all + +jobs: + trunk_check: + name: Trunk Check Runner + runs-on: ubuntu-latest + permissions: + checks: write # For trunk to post annotations + contents: read # For repo checkout + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Trunk Check + uses: trunk-io/trunk-action@v1 + with: + check-mode: all diff --git a/.trunk/.gitignore b/.trunk/.gitignore new file mode 100644 index 0000000..15966d0 --- /dev/null +++ b/.trunk/.gitignore @@ -0,0 +1,9 @@ +*out +*logs +*actions +*notifications +*tools +plugins +user_trunk.yaml +user.yaml +tmp diff --git a/.trunk/configs/.clang-tidy b/.trunk/configs/.clang-tidy new file mode 100644 index 0000000..e4bd819 --- /dev/null +++ b/.trunk/configs/.clang-tidy @@ -0,0 +1,39 @@ +Checks: >- + bugprone-*, + cppcoreguidelines-*, + google-*, + misc-*, + modernize-*, + performance-*, + readability-*, + -bugprone-lambda-function-name, + -bugprone-reserved-identifier, + -cppcoreguidelines-avoid-goto, + -cppcoreguidelines-avoid-magic-numbers, + -cppcoreguidelines-avoid-non-const-global-variables, + -cppcoreguidelines-pro-bounds-array-to-pointer-decay, + -cppcoreguidelines-pro-type-vararg, + -google-readability-braces-around-statements, + -google-readability-function-size, + -misc-no-recursion, + -modernize-return-braced-init-list, + -modernize-use-nodiscard, + -modernize-use-trailing-return-type, + -performance-unnecessary-value-param, + -readability-magic-numbers, + +CheckOptions: + - key: readability-function-cognitive-complexity.Threshold + value: 100 + - key: readability-function-cognitive-complexity.IgnoreMacros + value: true + # Set naming conventions for your style below (there are dozens of naming settings possible): + # See https://clang.llvm.org/extra/clang-tidy/checks/readability/identifier-naming.html + # - key: readability-identifier-naming.ClassCase + # value: CamelCase + # - key: readability-identifier-naming.NamespaceCase + # value: lower_case + # - key: readability-identifier-naming.PrivateMemberSuffix + # value: _ + # - key: readability-identifier-naming.StructCase + # value: CamelCase diff --git a/.trunk/configs/.markdownlint.yaml b/.trunk/configs/.markdownlint.yaml new file mode 100644 index 0000000..b40ee9d --- /dev/null +++ b/.trunk/configs/.markdownlint.yaml @@ -0,0 +1,2 @@ +# Prettier friendly markdownlint config (all formatting rules disabled) +extends: markdownlint/style/prettier diff --git a/.trunk/configs/.shellcheckrc b/.trunk/configs/.shellcheckrc new file mode 100644 index 0000000..8c7b1ad --- /dev/null +++ b/.trunk/configs/.shellcheckrc @@ -0,0 +1,7 @@ +enable=all +source-path=SCRIPTDIR +disable=SC2154 + +# If you're having issues with shellcheck following source, disable the errors via: +# disable=SC1090 +# disable=SC1091 diff --git a/.trunk/configs/.yamllint.yaml b/.trunk/configs/.yamllint.yaml new file mode 100644 index 0000000..184e251 --- /dev/null +++ b/.trunk/configs/.yamllint.yaml @@ -0,0 +1,7 @@ +rules: + quoted-strings: + required: only-when-needed + extra-allowed: ["{|}"] + key-duplicates: {} + octal-values: + forbid-implicit-octal: true diff --git a/.trunk/setup-ci/action.yaml b/.trunk/setup-ci/action.yaml new file mode 100644 index 0000000..3843930 --- /dev/null +++ b/.trunk/setup-ci/action.yaml @@ -0,0 +1,11 @@ +name: Trunk Check setup +description: Set up dependencies for Trunk Check +runs: + using: composite + steps: + - name: Configure Project + uses: threeal/cmake-action@main + with: + run-build: false + generator: Unix Makefiles + options: CMAKE_EXPORT_COMPILE_COMMANDS=ON diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml new file mode 100644 index 0000000..802326d --- /dev/null +++ b/.trunk/trunk.yaml @@ -0,0 +1,41 @@ +# This file controls the behavior of Trunk: https://docs.trunk.io/cli +# To learn more about the format of this file, see https://docs.trunk.io/reference/trunk-yaml +version: 0.1 +cli: + version: 1.22.1 +# Trunk provides extensibility via plugins. (https://docs.trunk.io/plugins) +plugins: + sources: + - id: trunk + ref: v1.5.0 + uri: https://github.com/trunk-io/plugins +# Many linters and tools depend on runtimes - configure them here. (https://docs.trunk.io/runtimes) +runtimes: + enabled: + - go@1.21.0 + - node@18.12.1 + - python@3.10.8 +# This is the section where you manage your linters. (https://docs.trunk.io/check/configuration) +lint: + enabled: + - cmake-format@0.6.13 + - clang-format@16.0.3 + - clang-tidy@16.0.3 + - actionlint@1.7.1 + - checkov@3.2.128 + - git-diff-check + - markdownlint@0.41.0 + - prettier@3.3.1 + - shellcheck@0.10.0 + - shfmt@3.6.0 + - trivy@0.52.0 + - trufflehog@3.78.0 + - yamllint@1.35.1 + +actions: + disabled: + - trunk-announce + - trunk-check-pre-push + - trunk-fmt-pre-commit + enabled: + - trunk-upgrade-available diff --git a/.vscode/settings.json b/.vscode/settings.json index abcce64..6f51dfa 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,16 @@ { - "files.associations": { - "cmath": "cpp", - "string_view": "cpp" - } -} \ No newline at end of file + "files.associations": { + "cmath": "cpp", + "string_view": "cpp", + "sstream": "cpp", + "optional": "cpp", + "memory": "cpp", + "random": "cpp", + "array": "cpp", + "fstream": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "system_error": "cpp" + }, + "cmake.generator": "Unix Makefiles" +} diff --git a/CMakeLists.txt b/CMakeLists.txt index 10a4c73..f5d5878 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,20 +1,99 @@ cmake_minimum_required(VERSION 3.14) -project(enum_name_example VERSION 0.1 LANGUAGES CXX) -set(CMAKE_CXX_STANDARD 17) +project( + enum_name + VERSION 1.0 + LANGUAGES CXX) -include(FetchContent) +# Define the library +add_library(enum_name INTERFACE) +add_library(mgutility::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) -FetchContent_Declare( - DocTest - GIT_REPOSITORY "https://github.com/onqtam/doctest" - GIT_TAG "v2.4.11" -) +# Specify the include directories for the library +target_include_directories( + enum_name INTERFACE $ + $) -FetchContent_MakeAvailable(DocTest) +# Set the C++ standard +target_compile_features(enum_name INTERFACE cxx_std_11) -add_executable(${PROJECT_NAME} example/main.cpp) -target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_SOURCE_DIR}/include) +if(CMAKE_SYSTEM_NAME STREQUAL Linux) + include(GNUInstallDirs) + set(include_install_dir ${CMAKE_INSTALL_INCLUDEDIR}) + set(config_install_dir "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}") +else() + set(include_install_dir "include") + set(config_install_dir "lib/cmake/${PROJECT_NAME}") +endif() -enable_testing() -add_subdirectory(test) \ No newline at end of file +# Create the package configuration files +include(CMakePackageConfigHelpers) + +write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/enum_nameConfigVersion.cmake" + VERSION ${PROJECT_VERSION} + COMPATIBILITY AnyNewerVersion) + +configure_package_config_file( + ${CMAKE_CURRENT_LIST_DIR}/cmake/enum_nameConfig.cmake.in + "${CMAKE_CURRENT_BINARY_DIR}/enum_nameConfig.cmake" + INSTALL_DESTINATION lib/cmake/enum_name) + +if(NOT ${ENUM_NAME_NO_INSTALL}) + # Install the library + install( + TARGETS enum_name + EXPORT enum_nameTargets + INCLUDES + DESTINATION include) + + install( + EXPORT enum_nameTargets + FILE enum_nameTargets.cmake + NAMESPACE mgutility:: + DESTINATION lib/cmake/enum_name) + + install(DIRECTORY include/ DESTINATION include) + + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/enum_nameConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/enum_nameConfigVersion.cmake" + DESTINATION lib/cmake/enum_name) + + export( + EXPORT enum_nameTargets + FILE "${CMAKE_CURRENT_BINARY_DIR}/enum_nameTargets.cmake" + NAMESPACE mgutility::) +endif() + +# Add example executable +add_executable(example_enum_name example/main.cpp) + +# Link the example executable with the library +target_link_libraries(example_enum_name PRIVATE mgutility::enum_name) + +if(NOT ${ENUM_NAME_NO_TESTS}) + # Enable testing + enable_testing() + + # FetchContent to get the Doctest testing framework + include(FetchContent) + FetchContent_Declare( + doctest + GIT_REPOSITORY https://github.com/doctest/doctest.git + GIT_TAG v2.4.11) + FetchContent_MakeAvailable(doctest) + + # Add test executable + add_executable(test_enum_name tests/test_enum_name.cpp) + + # Link the test executable with the library and Doctest + target_link_libraries(test_enum_name PRIVATE mgutility::enum_name + doctest::doctest) + + # Add tests + add_test(NAME test_enum_name COMMAND test_enum_name) +endif() + +if(${ENUM_NAME_BUILD_DOCS}) + add_subdirectory(doc) +endif() diff --git a/README.md b/README.md index 523b10c..d161fdb 100644 --- a/README.md +++ b/README.md @@ -1,91 +1,144 @@ # enum_name + Converting (scoped)enum values to/from string names written in C++>=11. ## Supported Compilers -* Clang > 5 -* GCC > 8 -* MSVC > 2015 + +- Clang > 5 +- GCC > 8 +- MSVC > 2015 ## Features -* Supports `enum` and `enum class` -* Supports enums in namespaces, classes or structs even templated or not -* Supports compile-time as much as possible using with C++14 and later -* Changing enum range with template parameter (default range: `[0, 256)`) on each call or with your special function for types or adding specialized `enum_range` struct -* Supports and automatically overloaded `operator<<` for Enum types to direct using with ostream objects -* Supports custom enum name output by explicit specialization of `constexpr inline auto mgutility::detail::enum_type::name() noexcept` function -* Supports iterate over enum (names and values) with `mgutility::enum_for_each()` class and it is compatible with standard ranges and views + +- Supports `enum` and `enum class` +- Supports enums in namespaces, classes or structs even templated or not +- Supports full compile-time with C++20 and later +- Changing enum range with template parameter (default range: `[0, 256)`) on each call or with your special function for types or adding specialized `enum_range` struct +- Supports and automatically overloaded `operator<<` and add `std::formatter` specialization for Enum types to direct using with ostream objects and `std::format` (If `fmtlib` is available, simply adding the specialization like this [`template <> struct fmt::formatter : ostream_formatter {};`] is enough.) +- Supports custom enum name(s) input/output by explicit specialization of `template <> struct mgutility::custom_enum` with `static constexpr mgutility::flat_map map{{Enum, const char*},...}` variable. +- Supports bitmasked enums and auto detect them +- Supports iterate over enum (names and values) with `mgutility::enum_for_each()` class and it is compatible with standard ranges and views ## Limitations -* Compiler versions -* Wider range can increase compile time so user responsible to adjusting for enum's range +- Compiler versions +- Wider range can increase compile time so user responsible to adjusting for enum's range -## Usage ([try it!](https://godbolt.org/z/YM5EvY1Y5)) -```C++ -#include -#include "enum_name.hpp" +## Fetch library with CMake -num class rgb_color { red, green, blue, unknown = -1 }; +```CMake +include(FetchContent) -// specialize rgb_color::unknown to make output "UNKNOWN" instead of "unknown" -template <> -constexpr auto mgutility::detail::enum_type::name() noexcept - -> string_view { - return "UNKNOWN"; -} +FetchContent_Declare( + enum_name + GIT_REPOSITORY https://github.com/mguludag/enum_name.git + GIT_TAG main # or the specific tag or branch you want to use +) -// you can specialize enum ranges with overload per enum types (option 1) -template <> -struct mgutility::enum_range { - static constexpr auto min = -1; - static constexpr auto max = 3; -}; +FetchContent_MakeAvailable(enum_name) -// you can specialize enum ranges with overload per enum types (option 2) -auto enum_name = [](rgb_color c) { return mgutility::enum_name<-1, 3>(c); }; +#... + +target_link_libraries(${PROJECT_NAME} PRIVATE mgutility::enum_name) +``` + +## Example usage ([try it!](https://godbolt.org/z/5Ye185MWa)) + +```C++ +#include + +#include "mgutility/reflection/enum_name.hpp" #if defined(__cpp_lib_print) #include #include #endif +enum class Position { + Top = 1 << 0, + Right = 1 << 1, + Bottom = 1 << 2, + Left = 1 << 3 +}; + +// Define bitwise OR operator for Position +auto constexpr operator|(Position lhs, Position rhs) -> Position { + return static_cast(mgutility::enum_to_underlying(lhs) | + mgutility::enum_to_underlying(rhs)); +} + +// Define bitwise AND operator for Position +auto constexpr operator&(Position lhs, Position rhs) -> Position { + return static_cast(mgutility::enum_to_underlying(lhs) & + mgutility::enum_to_underlying(rhs)); +} + +// Define the range for Position enum values (Option 1) +template <> +struct mgutility::enum_range { + static constexpr auto min = 0; // Minimum value + static constexpr auto max = 16; // Maximum value +}; + +// Specialize individual or all enum names +template <> +struct mgutility::custom_enum { + static constexpr mgutility::flat_map map{ + {Position::Top, "TOP"}, + {Position::Right, "RIGHT"}, + {Position::Bottom, "BOTTOM"}, + {Position::Left, "LEFT"}, + {Position::Top | Position::Right | Position::Bottom | Position::Left, + "CENTER"}}; +}; int main() { - auto x = rgb_color::blue; - auto y = mgutility::to_enum("greenn"); + + // Specify enum range when call enum_name function (Option 2) + // Lambda function to get enum name + auto enum_name = [](Position c) { return mgutility::enum_name<0, 16>(c); }; + + auto x = Position::Left; + auto y = mgutility::to_enum("CENTER"); // Convert string to enum + +#if defined(__cpp_lib_constexpr_string) + static_assert(mgutility::enum_name(Position::Top | Position::Right) == + "TOP|RIGHT"); // Compile-time check + static_assert(mgutility::to_enum("BOTTOM|LEFT") == + (Position::Bottom | Position::Left)); // Compile-time check +#endif #if defined(__cpp_lib_print) -// enum_for_each can usable with views and range algorithms -auto colors = mgutility::enum_for_each() | - std::ranges::views::filter( - [](auto &&pair) { return pair.second != "UNKNOWN"; }); + // Print each Position and its underlying value using ranges + auto positions = + mgutility::enum_for_each() | + std::ranges::views::filter([](auto &&pair) { + return !pair.second.empty() && + pair.second.find('|') == mgutility::string_view::npos; + }); - std::ranges::for_each(colors, [](auto &&color) { - std::println("{} \t: {}", color.second, mgutility::enum_to_underlying(color.first)); - }); + std::ranges::for_each(positions, [](auto &&pos) { + std::println("{} \t: {}", mgutility::enum_to_underlying(pos.first), + pos.second); + }); #else - for (auto&& e : mgutility::enum_for_each()) { - if(e.second != "UNKNOWN"){ - std::cout << e.second << " \t: " << mgutility::enum_to_underlying(e.first) << '\n'; - } - // std::pair {enum_type, name_of_enum} + // Print each Position and its underlying value using a for loop + for (auto &&e : mgutility::enum_for_each()) { + if (!e.second.empty() && + e.second.find('|') == mgutility::string_view::npos) { + std::cout << mgutility::enum_to_underlying(e.first) << " \t: " << e.second + << '\n'; } + } #endif - // default signature: enum_name(Enum&&) Changing max_value to not too much greater than enum's - // max value, it will compiles faster - std::cout << mgutility::enum_name(x) << '\n'; // will print "blue" to output - // or - std::cout << x << '\n'; // will print "blue" to output - - // calling specialized enum ranges function for rgb_color type - // will print "green" to output, if y can't convert to rgb_color prints - // "UNKNOWN" - std::cout << enum_name(y.value_or(rgb_color::unknown)) << '\n'; -} + // Print the value of x + std::cout << '\n' << x << '\n'; + // Print the name of y or "TOP" if y is not valid + std::cout << y.value_or(Position::Top) << '\n'; +} ``` diff --git a/cmake/enum_nameConfig.cmake.in b/cmake/enum_nameConfig.cmake.in new file mode 100644 index 0000000..5fbea33 --- /dev/null +++ b/cmake/enum_nameConfig.cmake.in @@ -0,0 +1,3 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/enum_nameTargets.cmake") \ No newline at end of file diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt new file mode 100644 index 0000000..f56bb54 --- /dev/null +++ b/doc/CMakeLists.txt @@ -0,0 +1,11 @@ +find_package(Doxygen REQUIRED) + +configure_file("main.md.in" "main.md" @ONLY) + +set(DOXYGEN_HTML_OUTPUT "${PROJECT_VERSION}") +set(DOXYGEN_OUTPUT_DIRECTORY "docs") +set(DOXYGEN_HTML_HEADER "header.html") +set(DOXYGEN_HTML_EXTRA_FILES "version_selector_handler.js") +set(DOXYGEN_USE_MDFILE_AS_MAINPAGE "${CMAKE_CURRENT_BINARY_DIR}/main.md") + +doxygen_add_docs(doc "${PROJECT_SOURCE_DIR}") diff --git a/doc/header.html b/doc/header.html new file mode 100644 index 0000000..6d97b6e --- /dev/null +++ b/doc/header.html @@ -0,0 +1,102 @@ + + + + + + + + + + $projectname: $title + + + $title + + + + + + + + + + + + + + + + $treeview $search $mathjax $darkmode + + $extrastylesheet + + + + + + +
+ + + + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ $projectname + +
+ +
$projectbrief
+ +
+
$projectbrief
+
$searchbox
$searchbox
+
+ + +
+
+ + diff --git a/doc/main.md.in b/doc/main.md.in new file mode 100644 index 0000000..8b5b604 --- /dev/null +++ b/doc/main.md.in @@ -0,0 +1,5 @@ +# Main page + +Converting (scoped)enum values to string names written in C++>=11 + +Version: @PROJECT_VERSION@ \ No newline at end of file diff --git a/doc/version_selector_handler.js b/doc/version_selector_handler.js new file mode 100644 index 0000000..1f54973 --- /dev/null +++ b/doc/version_selector_handler.js @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2024, Oleksandr Koval + +$(function () { + var repoName = window.location.pathname.split("/")[1]; + $.get("/" + repoName + "/version_selector.html", function (data) { + // Inject version selector HTML into the page + $("#projectnumber").html(data); + + // Event listener to handle version selection + document + .getElementById("versionSelector") + .addEventListener("change", function () { + var selectedVersion = this.value; + window.location.href = + "/" + repoName + "/" + selectedVersion + "/index.html"; + }); + + // Set the selected option based on the current version + var currentVersion = window.location.pathname.split("/")[2]; + $("#versionSelector").val(currentVersion); + }); +}); diff --git a/example/main.cpp b/example/main.cpp index 4f0b051..979c069 100644 --- a/example/main.cpp +++ b/example/main.cpp @@ -1,33 +1,94 @@ #include -#include "enum_name.hpp" +#include "mgutility/reflection/enum_name.hpp" -enum class rgb_color { red, green, blue, unknown = -1}; +#if defined(__cpp_lib_print) +#include +#include +#endif -// you can specialize enum ranges with specialize struct per enum types (option 1) -namespace mgutility{ - template<> - struct enum_range - { - static constexpr auto min = -1; - static constexpr auto max = 3; - }; +enum class Position { + Top = 1 << 0, + Right = 1 << 1, + Bottom = 1 << 2, + Left = 1 << 3 +}; + +// Define bitwise OR operator for Position +auto constexpr operator|(Position lhs, Position rhs) -> Position { + return static_cast(mgutility::enum_to_underlying(lhs) | + mgutility::enum_to_underlying(rhs)); +} + +// Define bitwise AND operator for Position +auto constexpr operator&(Position lhs, Position rhs) -> Position { + return static_cast(mgutility::enum_to_underlying(lhs) & + mgutility::enum_to_underlying(rhs)); } -// you can specialize enum ranges with overload per enum types (option 2) -auto enum_name = [](rgb_color c){ return mgutility::enum_name<-1, 3>(c); }; - - -int main() -{ - auto x = rgb_color::blue; - auto y = mgutility::to_enum("green"); - - // default signature: enum_name(Enum&&) - // Changing max_value to not too much greater than enum's max value, it will compiles faster - std::cout << mgutility::enum_name(x) << '\n'; // will print "blue" to output - - // calling specialized enum ranges function for rgb_color type - // will print "green" to output, if y can't convert to rgb_color prints "unknown" - std::cout << enum_name(y.value_or(rgb_color::unknown)) << '\n'; +// Define the range for Position enum values (Option 1) +template <> struct mgutility::enum_range { + static constexpr auto min = 0; // Minimum value + static constexpr auto max = 16; // Maximum value +}; + +// Specialize individual or all enum names +template <> struct mgutility::custom_enum { + static constexpr mgutility::flat_map map{ + {Position::Top, "TOP"}, + {Position::Right, "RIGHT"}, + {Position::Bottom, "BOTTOM"}, + {Position::Left, "LEFT"}, + {Position::Top | Position::Right | Position::Bottom | Position::Left, + "CENTER"}}; +}; + +int main() { + + // Specify enum range when call enum_name function (Option 2) + // Lambda function to get enum name + auto enum_name = [](Position c) { return mgutility::enum_name<0, 16>(c); }; + + auto x = Position::Left; + auto y = mgutility::to_enum("CENTER"); // Convert string to enum + +#if defined(__cpp_lib_constexpr_string) && defined(__GNUC__) && !defined(__clang__) && __GNUC__ > 11 + static_assert(mgutility::enum_name(Position::Top | Position::Right) == + "TOP|RIGHT"); // Compile-time check + static_assert(mgutility::to_enum("BOTTOM|LEFT") == + (Position::Bottom | Position::Left)); // Compile-time check +#endif + +#if defined(__cpp_lib_print) + + // Print each Position and its underlying value using ranges + auto positions = + mgutility::enum_for_each() | + std::ranges::views::filter([](auto &&pair) { + return !pair.second.empty() && + pair.second.find('|') == mgutility::string_view::npos; + }); + + std::ranges::for_each(positions, [](auto &&pos) { + std::println("{} \t: {}", mgutility::enum_to_underlying(pos.first), + pos.second); + }); + +#else + + // Print each Position and its underlying value using a for loop + for (auto &&e : mgutility::enum_for_each()) { + if (!e.second.empty() && + e.second.find('|') == mgutility::string_view::npos) { + std::cout << mgutility::enum_to_underlying(e.first) << " \t: " << e.second + << '\n'; + } + } +#endif + + // Print the value of x + std::cout << '\n' << x << '\n'; + + // Print the name of y or "TOP" if y is not valid + std::cout << y.value_or(Position::Top) << '\n'; } \ No newline at end of file diff --git a/include/enum_name.hpp b/include/enum_name.hpp deleted file mode 100644 index c7ed6ab..0000000 --- a/include/enum_name.hpp +++ /dev/null @@ -1,504 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023 Muhammed Galib Uludag - * - * 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 - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if defined(_MSC_VER) && _MSC_VER < 1910 -#error "Requires MSVC 2017 or newer!" -#elif defined(__clang__) && __clang_major__ < 6 -#error "Requires clang 6 or newer!" -#elif defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 9 -#error "Requires gcc 9 or newer!" -#elif !defined(_MSC_VER) && !defined(__clang__) && !defined(__GNUC__) -#error "Your compiler is not supported!" -#endif - -#ifdef _MSC_VER -#define MG_ENUM_NAME_CPLUSPLUS _MSVC_LANG -#else -#define MG_ENUM_NAME_CPLUSPLUS __cplusplus -#endif - -#if MG_ENUM_NAME_CPLUSPLUS == 201103L -#define MG_ENUM_NAME_CNSTXPR -#elif MG_ENUM_NAME_CPLUSPLUS > 201103L -#define MG_ENUM_NAME_CNSTXPR constexpr -#elif MG_ENUM_NAME_CPLUSPLUS < 201103L -#error "Standards older than C++11 is not supported!" -#endif - -#if MG_ENUM_NAME_CPLUSPLUS > 201702L -#include -#endif - -namespace mgutility { -namespace detail { - -template -struct is_scoped_enum { - static constexpr auto value = - std::is_enum::value && - !std::is_convertible::type>::value; -}; -#if MG_ENUM_NAME_CPLUSPLUS > 201103L -template -static constexpr bool is_scoped_enum_v = is_scoped_enum::value; -#endif - -template -using underlying_type_t = typename std::underlying_type::type; - -template -using remove_const_t = typename std::remove_const::type; - -#if MG_ENUM_NAME_CPLUSPLUS < 201703L - -template -using enable_if_t = typename std::enable_if::type; - -constexpr auto strlen_constexpr(const char* str, - size_t sz = 0) noexcept -> size_t { - return str[sz] == '\0' ? sz : strlen_constexpr(str, ++sz); -} - -template -class basic_string_view { - public: - constexpr inline basic_string_view() noexcept : data_(""), size_(0) {} - constexpr inline basic_string_view(const Char* str) noexcept - : data_(str), size_(strlen_constexpr(str)) {} - constexpr inline basic_string_view(const Char* str, size_t len) noexcept - : data_(str), size_(len) {} - constexpr inline basic_string_view(const basic_string_view& other) - : data_(other.data_), size_(other.size_) {} - constexpr inline basic_string_view(basic_string_view&& other) noexcept - : data_(std::move(other.data_)), size_(std::move(other.size_)) {} - MG_ENUM_NAME_CNSTXPR inline basic_string_view& operator=( - const basic_string_view& other) noexcept { - data_ = other.data_; - size_ = other.size_; - return *this; - } - MG_ENUM_NAME_CNSTXPR inline basic_string_view& operator=( - basic_string_view&& other) noexcept { - data_ = std::move(other.data_); - size_ = std::move(other.size_); - return *this; - } - constexpr inline const Char operator[](size_t index) const noexcept { - return data_[index]; - } - constexpr inline const Char* begin() const noexcept { return data_; } - constexpr inline const Char* end() const noexcept { - return (data_ + size_); - } - constexpr inline bool empty() const noexcept { return size_ < 1; } - constexpr inline size_t size() const noexcept { return size_; } - constexpr inline const Char* data() const noexcept { return data_; } - constexpr inline basic_string_view substr(size_t begin, - size_t len) const noexcept { - return basic_string_view(data_ + begin, len); - } - constexpr inline size_t rfind(Char c, size_t pos = 0) const noexcept { - return c == data_[pos] ? pos : rfind(c, --pos); - } - - constexpr friend inline bool operator==( - basic_string_view lhs, basic_string_view rhs) noexcept { - return (lhs.size_ == rhs.size_) && - std::strncmp(lhs.data_, rhs.data_, lhs.size_) == 0; - } - - constexpr friend inline bool operator==(basic_string_view lhs, - const Char* rhs) noexcept { - return (lhs.size_ == strlen_constexpr(rhs)) && - std::strncmp(lhs.data_, rhs, lhs.size_) == 0; - } - - constexpr friend inline bool operator!=( - basic_string_view lhs, basic_string_view rhs) noexcept { - return !(lhs == rhs); - } - - constexpr friend inline bool operator!=(basic_string_view lhs, - const Char* rhs) noexcept { - return !(lhs == rhs); - } - - inline operator std::string() { return std::string(data_, size_); } - inline operator std::string() const { return std::string(data_, size_); } - - friend inline std::ostream& operator<<(std::ostream& os, - const basic_string_view& sv) { - for (auto c : sv) { - os << c; - } - return os; - } - - private: - size_t size_; - const Char* data_; -}; - -using string_view = basic_string_view; - -struct bad_optional_access : public std::exception { - const char* what() const noexcept { return "optional has no value"; } -}; - -struct nullopt_t; - -template -class optional { - public: - MG_ENUM_NAME_CNSTXPR inline optional(nullopt_t&) - : dummy_{0}, has_value_{false} {} - MG_ENUM_NAME_CNSTXPR inline optional() : dummy_{0}, has_value_{false} {} - template - MG_ENUM_NAME_CNSTXPR inline optional(Args&&... args) - : value_{T{std::forward(args)...}}, has_value_{true} {} - MG_ENUM_NAME_CNSTXPR inline optional(T&& value) - : value_{value}, has_value_{true} {} - MG_ENUM_NAME_CNSTXPR inline optional(const optional& other) - : value_{other.value_}, has_value_{other.has_value_} {} - MG_ENUM_NAME_CNSTXPR inline optional(optional&& other) - : value_{other.value_}, has_value_{other.has_value_} { - other.reset(); - } - inline ~optional() { has_value_ = false; } - MG_ENUM_NAME_CNSTXPR inline optional& operator=(const optional& other) { - value_ = other.value_; - has_value_ = other.has_value_; - return *this; - } - MG_ENUM_NAME_CNSTXPR inline optional& operator=(optional&& other) { - value_ = other.value_; - has_value_ = other.has_value_; - other.reset(); - return *this; - } - MG_ENUM_NAME_CNSTXPR inline void swap(optional&& other) { - auto val = std::move(other.value_); - other.value_ = std::move(value_); - value_ = std::move(val); - - auto hval = std::move(other.has_value_); - other.has_value_ = std::move(has_value_); - has_value_ = std::move(hval); - } - MG_ENUM_NAME_CNSTXPR inline T& operator*() { return value_; } - MG_ENUM_NAME_CNSTXPR inline T& operator*() const { return value_; } - MG_ENUM_NAME_CNSTXPR inline T& value() { - if (!has_value_) throw detail::bad_optional_access(); - return value_; - } - MG_ENUM_NAME_CNSTXPR inline T& value() const { - if (!has_value_) throw detail::bad_optional_access(); - return value_; - } - MG_ENUM_NAME_CNSTXPR inline T value_or(T&& value) { - return has_value_ ? value_ : value; - } - MG_ENUM_NAME_CNSTXPR inline T value_or(T&& value) const { - return has_value_ ? value_ : value; - } - MG_ENUM_NAME_CNSTXPR inline T value_or(const T& value) { - return has_value_ ? value_ : value; - } - MG_ENUM_NAME_CNSTXPR inline T value_or(const T& value) const { - return has_value_ ? value_ : value; - } - MG_ENUM_NAME_CNSTXPR inline void emplace(T value) { - value_ = std::move(value); - has_value_ = true; - } - template - MG_ENUM_NAME_CNSTXPR inline void emplace(Args&&... args) { - value_ = std::move(T{std::forward(args)...}); - has_value_ = true; - } - MG_ENUM_NAME_CNSTXPR inline bool has_value() const { return has_value_; } - -#if !(defined(__clang__) && __clang_major__ < 11) - template ::value, bool> = true> - MG_ENUM_NAME_CNSTXPR inline void reset() { - T::~T(); - has_value_ = false; - } -#endif - template ::value, bool> = true> - MG_ENUM_NAME_CNSTXPR inline void reset() { - value_ = T{}; - has_value_ = false; - } - - MG_ENUM_NAME_CNSTXPR operator bool() { return has_value_; } - - private: - union { - T value_; - char dummy_[sizeof(T)]; - }; - bool has_value_; -}; - -struct nullopt_t { - template - MG_ENUM_NAME_CNSTXPR operator optional() { - return optional{}; - } -}; - -auto nullopt = nullopt_t{}; - -#else - -template -using optional = std::optional; -inline constexpr auto nullopt{std::nullopt}; -using string_view = std::string_view; -template -using enable_if_t = std::enable_if_t; - -#endif - -template -struct enum_sequence {}; - -template -struct enum_sequence_helper - : enum_sequence_helper {}; - -template -struct enum_sequence_helper { - using type = enum_sequence(Next)...>; -}; - -template -using make_enum_sequence = typename enum_sequence_helper::type; - -struct enum_type { -#if defined(_MSC_VER) -#define __PRETTY_FUNCTION__ __FUNCSIG__ -#endif - template < - typename Enum, Enum e, - detail::enable_if_t::value, bool> = true> - MG_ENUM_NAME_CNSTXPR static inline auto name() noexcept - -> detail::string_view { - auto str = detail::string_view(__PRETTY_FUNCTION__); - auto offset{lastidxenumname[0] + lastidxenumname[1]}; - auto index = - std::max(str.rfind(lastidxenumname[2], str.size() - offset), - str.rfind(lastidxenumname[3], str.size() - offset)); - auto result = str.substr(index + 1, str.size() - offset - index); - return result[0] == '(' ? "" : result; - } - - template < - typename Enum, Enum e, - detail::enable_if_t::value, bool> = true> - MG_ENUM_NAME_CNSTXPR static inline auto name() noexcept - -> detail::string_view { - auto str = detail::string_view(__PRETTY_FUNCTION__); - auto index = - str.rfind(lastidxenumname[3], str.size() - lastidxenumname[0]) + 1; - auto result = - str.substr(index, str.size() - lastidxenumname[0] - index); - return result.size() > 4 ? result[4] == lastidxenumname[4] ? "" : result - : result; - } - - private: - static constexpr int lastidxenumname[] = -#if defined(_MSC_VER) - {22, 0, ',', ':', '<'}; -#elif defined(__clang__) - {1, 1, ' ', ':', '('}; -#elif defined(__GNUC__) - { -#if MG_ENUM_NAME_CPLUSPLUS < 201703L - 179, -#else - 165, -#endif - 5, ' ', ':', '('}; -#endif -}; - -template -using enum_pair = std::pair; - -template -inline auto get_enum_array(detail::enum_sequence) noexcept - -> std::array { - static std::array - arr{"", enum_type::template name()...}; - return arr; -} - -template -inline auto to_enum_impl(detail::string_view str) noexcept - -> detail::optional { - auto arr = get_enum_array(detail::make_enum_sequence()); - const auto index{std::find(arr.begin() + 1, arr.end(), str)}; - return index == arr.end() - ? detail::nullopt - : detail::optional{static_cast( - std::distance(arr.begin(), index) + Min - 1)}; -} - -template -inline auto enum_name_impl(Enum e) noexcept -> detail::string_view { - auto arr = get_enum_array(detail::make_enum_sequence()); - const auto index{std::abs(Min) + static_cast(e) + (Min < 0 ? 1 : 1)}; - return arr[(index < Min || index > arr.size() - 1) ? 0 : index]; -} -} // namespace detail -} // namespace mgutility - -namespace mgutility { -template -struct enum_range { - static constexpr auto min{-128}; - static constexpr auto max{128}; -}; - -template -class enum_for_each { - using value_type = const detail::enum_pair; - using size_type = std::size_t; - - struct enum_iter { - using const_iter_type = decltype(enum_range::min); - 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&; - - enum_iter() : m_pos{} {} - enum_iter(iter_type value) : m_pos{value} {} - - auto operator++() -> enum_iter& { - ++m_pos; - return *this; - } - - auto operator++(int) -> enum_iter { - m_pos++; - return *this; - } - - auto operator!=(const enum_iter& other) const -> bool { - return m_pos != other.m_pos; - } - - auto operator==(const enum_iter& other) const -> bool { - return m_pos == other.m_pos; - } - - auto operator*() const -> value_type; - - private: - iter_type m_pos; - }; - - public: - enum_for_each() {} - auto begin() -> enum_iter& { return m_begin; } - auto end() -> enum_iter& { return m_end; } - auto size() -> std::size_t { - return enum_range::max - enum_range::min; - } - - private: - enum_iter m_begin{enum_range::min}; - enum_iter m_end{enum_range::max}; -}; - -template -constexpr inline auto enum_to_underlying(Enum e) noexcept - -> detail::underlying_type_t { - static_assert(std::is_enum::value, "Value is not an Enum type!"); - return static_cast>(e); -} - -template -MG_ENUM_NAME_CNSTXPR inline auto enum_name(Enum e) noexcept - -> detail::string_view { - static_assert(Min < Max - 1, "Max must be greater than (Min + 1)!"); - static_assert(std::is_enum::value, "Value is not an Enum type!"); - return detail::enum_name_impl(e); -} - -template ::min, - int Max = enum_range::max> -MG_ENUM_NAME_CNSTXPR inline auto enum_name(Enum e) noexcept - -> detail::string_view { - static_assert(Min < Max - 1, "Max must be greater than (Min + 1)!"); - static_assert(std::is_enum::value, "Value is not an Enum type!"); - return detail::enum_name_impl(e); -} - -template -auto enum_for_each::enum_iter::operator*() const -> value_type { - auto value = static_cast(m_pos); - return detail::enum_pair{value, enum_name(value)}; -} - -template ::min, - int Max = enum_range::max> -MG_ENUM_NAME_CNSTXPR inline auto to_enum(detail::string_view str) noexcept - -> detail::optional { - static_assert(Min < Max - 1, "Max must be greater than (Min + 1)!"); - static_assert(std::is_enum::value, "Type is not an Enum type!"); - return detail::to_enum_impl(str); -} -} // namespace mgutility - -template ::value, bool>::type = true> -auto operator<<(std::ostream& os, Enum e) -> std::ostream& { - static_assert(std::is_enum::value, "Value is not an Enum type!"); - os << mgutility::enum_name(e); - return os; -} - -#endif // MGUTILITY_ENUM_NAME_HPP diff --git a/include/mgutility/_common/definitions.hpp b/include/mgutility/_common/definitions.hpp new file mode 100644 index 0000000..cfa242d --- /dev/null +++ b/include/mgutility/_common/definitions.hpp @@ -0,0 +1,86 @@ +/* +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_COMMON_DEFINITIONS_HPP +#define MGUTILITY_COMMON_DEFINITIONS_HPP + +/** + * @file definitions.hpp + * @brief Defines macros for compiler and standard support detection. + */ + +/** + * @brief Defines the MGUTILITY_CPLUSPLUS macro for MSVC and other compilers. + * + * For MSVC, it uses _MSVC_LANG. For other compilers, it uses __cplusplus. + */ +#ifdef _MSC_VER +#define MGUTILITY_CPLUSPLUS _MSVC_LANG +#else +#define MGUTILITY_CPLUSPLUS __cplusplus +#endif + +/** + * @brief Defines the MGUTILITY_CNSTXPR macro based on the C++ standard. + * + * If the C++ standard is C++11, MGUTILITY_CNSTXPR is defined as empty. + * If the C++ standard is newer than C++11, MGUTILITY_CNSTXPR is defined as + * constexpr. If the C++ standard is older than C++11, an error is raised. + */ +#if MGUTILITY_CPLUSPLUS == 201103L +#define MGUTILITY_CNSTXPR +#elif MGUTILITY_CPLUSPLUS > 201103L +#define MGUTILITY_CNSTXPR constexpr +#elif MGUTILITY_CPLUSPLUS < 201103L +#error "Standards older than C++11 is not supported!" +#endif + +/** + * @brief Defines the MGUTILITY_CNSTXPR_CLANG_WA macro based on the C++ + * standard. + * + * If the C++ standard is newer than C++17 and the compiler is not Clang, + * MGUTILITY_CNSTXPR_CLANG_WA is defined as constexpr. Otherwise, it is defined + * as empty. + */ +#if MGUTILITY_CPLUSPLUS > 201703L && defined(__cpp_lib_constexpr_string) +#define MGUTILITY_CNSTXPR_CLANG_WA constexpr +#else +#define MGUTILITY_CNSTXPR_CLANG_WA +#endif + + +/** + * @brief Defines the MGUTILITY_CNSTEVL macro based on the C++ standard. + * + * If the C++ standard is newer than C++17, MGUTILITY_CNSTEVL is defined as + * consteval. Otherwise, it is defined as empty. + */ +#if MGUTILITY_CPLUSPLUS > 201703L +#define MGUTILITY_CNSTEVL consteval +#else +#define MGUTILITY_CNSTEVL +#endif + +#endif // MGUTILITY_COMMON_DEFINITIONS_HPP diff --git a/include/mgutility/reflection/detail/enum_for_each.hpp b/include/mgutility/reflection/detail/enum_for_each.hpp new file mode 100644 index 0000000..1365189 --- /dev/null +++ b/include/mgutility/reflection/detail/enum_for_each.hpp @@ -0,0 +1,180 @@ +/* +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 + +#include "meta.hpp" +#include "mgutility/std/string_view.hpp" + +#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 +using string_or_view_t = + typename std::conditional::value, std::string, + 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 = decltype(enum_range::min); + 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. + */ + 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 enum_range::max - enum_range::min; + } + +private: + enum_iter m_begin{enum_range::min}; /**< The beginning iterator. */ + enum_iter m_end{enum_range::max}; /**< The end iterator. */ +}; +} // namespace mgutility + +#endif // DETAIL_ENUM_FOR_EACH_HPP \ No newline at end of file diff --git a/include/mgutility/reflection/detail/enum_name_impl.hpp b/include/mgutility/reflection/detail/enum_name_impl.hpp new file mode 100644 index 0000000..e594678 --- /dev/null +++ b/include/mgutility/reflection/detail/enum_name_impl.hpp @@ -0,0 +1,320 @@ +/* +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 + +#include "enum_for_each.hpp" +#include "mgutility/std/optional.hpp" +#include "mgutility/std/string_view.hpp" + +#include +#include + +/** + * @brief Checks for MSVC compiler version. + * + * If the MSVC version is less than 2017, an error is raised. + */ +#if defined(_MSC_VER) && _MSC_VER < 1910 +#error "Requires MSVC 2017 or newer!" +/** + * @brief Checks for Clang compiler version. + * + * If the Clang version is less than 6, an error is raised. + */ +#elif defined(__clang__) && __clang_major__ < 6 +#error "Requires clang 6 or newer!" +/** + * @brief Checks for GCC compiler version. + * + * If the GCC version is less than 9 and it is not Clang, an error is raised. + */ +#elif defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 9 +#error "Requires gcc 9 or newer!" +/** + * @brief Checks for unsupported compilers. + * + * If the compiler is not MSVC, Clang, or GCC, an error is raised. + */ +#elif !defined(_MSC_VER) && !defined(__clang__) && !defined(__GNUC__) +#error "Your compiler is not supported!" +#endif + +/** + * @brief Defines the MGUTILITY_CPLUSPLUS macro for MSVC and other compilers. + * + * For MSVC, it uses _MSVC_LANG. For other compilers, it uses __cplusplus. + */ +#ifdef _MSC_VER +#define __PRETTY_FUNCTION__ __FUNCSIG__ +#endif + +namespace mgutility { +namespace detail { + +struct enum_type { + /** + * @brief Gets the name of an unscoped enum value. + * + * @tparam Enum The enum type. + * @tparam e The enum value. + * @return A string view representing the name of the enum value. + */ + template < + typename Enum, Enum e, + detail::enable_if_t::value, bool> = true> + MGUTILITY_CNSTXPR static auto name() noexcept -> mgutility::string_view { + for (auto &pair : mgutility::custom_enum::map) { + if (pair.first == e) { + return pair.second; + } + } + MGUTILITY_CNSTXPR auto str = mgutility::string_view(__PRETTY_FUNCTION__); + MGUTILITY_CNSTXPR auto offset = lastidxenumname[0] + lastidxenumname[1]; + MGUTILITY_CNSTXPR auto index = + std::max(str.rfind(lastidxenumname[2], str.size() - offset), + str.rfind(lastidxenumname[3], str.size() - offset)); + MGUTILITY_CNSTXPR auto result = + str.substr(index + 1, str.size() - offset - index); + return result[0] == '(' ? "" : result; + } + + /** + * @brief Gets the name of a scoped enum value. + * + * @tparam Enum The enum type. + * @tparam e The enum value. + * @return A string view representing the name of the enum value. + */ + template < + typename Enum, Enum e, + detail::enable_if_t::value, bool> = true> + MGUTILITY_CNSTXPR static auto name() noexcept -> mgutility::string_view { + for (auto &pair : mgutility::custom_enum::map) { + if (pair.first == e) { + return pair.second; + } + } + MGUTILITY_CNSTXPR auto str = mgutility::string_view(__PRETTY_FUNCTION__); + MGUTILITY_CNSTXPR auto index = + str.rfind(lastidxenumname[3], str.size() - lastidxenumname[0]) + 1; + MGUTILITY_CNSTXPR auto result = + str.substr(index, str.size() - lastidxenumname[0] - index); + MGUTILITY_CNSTXPR auto is_invalid = + result.rfind(lastidxenumname[5]) != result.npos || (result.size() > 4 && result[4] == lastidxenumname[4]); + return is_invalid ? "" : result; + } + +private: + static constexpr int lastidxenumname[] = +#if defined(__clang__) + {1, 1, ' ', ':', '(', +#if __clang_major__ < 13 + ',' +#else + ')' +#endif + }; +#elif defined(_MSC_VER) + {21, 0, ',', ':', '<', ')'}; +#elif defined(__GNUC__) + { +#if MGUTILITY_CPLUSPLUS < 201703L + 163, +#else + 157, +#endif + 5, ' ', ':', '(', ')'}; +#endif +}; + +/** + * @brief Generates an array of enum names. + * + * @tparam Enum The enum type. + * @tparam Is The enumeration values. + * @return An array of string views representing the enum names. + */ +template +MGUTILITY_CNSTXPR inline auto +get_enum_array(detail::enum_sequence /*unused*/) noexcept + -> std::array { + return std::array{ + "", enum_type::template name()...}; +} + +/** + * @brief Generates an array of enum names. + * + * @tparam Enum The enum type. + * @tparam Min The minimum enum value. + * @tparam Max The maximum enum value. + * @return An array of string views representing the enum names. + */ +template ::min, + int Max = mgutility::enum_range::max> +MGUTILITY_CNSTXPR inline auto get_enum_array() noexcept + -> std::array { + return get_enum_array(detail::make_enum_sequence()); +} + +/** + * @brief Converts a string to an enum value. + * + * @tparam Enum The enum type. + * @tparam Min The minimum enum value. + * @tparam Max The maximum enum value. + * @param str The string view representing the enum name. + * @return An optional enum value. + */ +template +MGUTILITY_CNSTXPR inline auto to_enum_impl(mgutility::string_view str) noexcept + -> mgutility::optional { + MGUTILITY_CNSTXPR_CLANG_WA auto arr = get_enum_array(); + const auto index{std::find(arr.begin() + 1, arr.end(), str)}; + return index == arr.end() ? mgutility::nullopt + : mgutility::optional{static_cast( + std::distance(arr.begin(), index) + Min - 1)}; +} + +/** + * @brief Converts a string to an enum bitmask value. + * + * @tparam Enum The enum type. + * @tparam Min The minimum enum value. + * @tparam Max The maximum enum value. + * @param str The string view representing the enum name. + * @return An optional enum bitmask value. + */ +template +MGUTILITY_CNSTXPR auto to_enum_bitmask_impl(mgutility::string_view str) noexcept + -> mgutility::optional { + + // Check if the string contains a '|' character + if (str.find('|') == mgutility::string_view::npos) { + return to_enum_impl(str); + } + + mgutility::optional result{mgutility::nullopt}; + std::size_t index = 0; + + for (std::size_t i = 0; i < str.size(); ++i) { + if (str[i] == '|') { + auto name = str.substr(index, i - index); + auto maybe_enum = to_enum_impl(name); + + if (!name.empty() && maybe_enum) { + result.emplace(result ? static_cast(*result | *maybe_enum) + : *maybe_enum); + } + + index = i + 1; + } + } + + auto maybe_enum = to_enum_impl(str.substr(index)); + if (result && maybe_enum) { + result.emplace(static_cast(*result | *maybe_enum)); + } else { + result.reset(); + } + + return result; +} + +/** + * @brief Gets the name of an enum value. + * + * @tparam Enum The enum type. + * @tparam Min The minimum enum value. + * @tparam Max The maximum enum value. + * @param e The enum value. + * @return A string view representing the name of the enum value. + */ +template ::value, bool> = true> +MGUTILITY_CNSTXPR auto enum_name_impl(Enum e) noexcept + -> mgutility::string_view { + MGUTILITY_CNSTXPR auto arr = get_enum_array(); + const auto index{(Min < 0 ? -Min : Min) + static_cast(e) + 1}; + return arr[(index < Min || index > arr.size() - 1) ? 0 : index]; +} + +/** + * @brief Gets the name of an enum bitmask value. + * + * @tparam Enum The enum type. + * @tparam Min The minimum enum value. + * @tparam Max The maximum enum value. + * @param e The enum value. + * @return A string view or string representing the name of the enum bitmask + * value. + */ +template ::value, bool> = true> +MGUTILITY_CNSTXPR_CLANG_WA inline auto enum_name_impl(Enum e) noexcept + -> detail::string_or_view_t { + + // Get the array of enum names + MGUTILITY_CNSTXPR_CLANG_WA auto arr = get_enum_array(); + + // Calculate the index in the array + const auto index = (Min < 0 ? -Min : Min) + static_cast(e) + 1; + const auto name = + arr[(index < Min || index >= static_cast(arr.size())) ? 0 : index]; + + // Lambda to check if a character is a digit + const auto is_digit = [](char c) { return c >= '0' && c <= '9'; }; + + // Return the name if it's valid + if (!name.empty() && !is_digit(name[0])) { + return std::string{name}; + } + + // Construct bitmasked name + std::string 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() && + !is_digit(arr[idx][0]) && + (e & static_cast(i)) == static_cast(i)) { + bitmasked_name.append(arr[idx]).append("|"); + } + } + + // Remove the trailing '|' if present + if (!bitmasked_name.empty()) { + bitmasked_name.pop_back(); + } + + if (bitmasked_name.find('|') != std::string::npos) { + return bitmasked_name; + } + return std::string{""}; +} +} // namespace detail +} // namespace mgutility + +#endif // MGUTILITY_REFLECTION_DETAIL_ENUM_NAME_IMPL_HPP \ No newline at end of file diff --git a/include/mgutility/reflection/detail/meta.hpp b/include/mgutility/reflection/detail/meta.hpp new file mode 100644 index 0000000..976e7ba --- /dev/null +++ b/include/mgutility/reflection/detail/meta.hpp @@ -0,0 +1,183 @@ +/* +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 + +#include "mgutility/_common/definitions.hpp" +#include +#include +#include + +namespace mgutility { +namespace detail { +/** + * @brief Trait to check if a type is a scoped enumeration. + * + * @tparam E The type to check. + */ +template struct is_scoped_enum { + /** + * @brief Boolean value indicating if the type is a scoped enumeration. + */ + static constexpr auto value = + std::is_enum::value && + !std::is_convertible::type>::value; +}; + +/** + * @brief Trait to check if a type supports the bitwise OR operator. + * + * @tparam T The type to check. + * @tparam Enable SFINAE parameter, default is void. + */ +template struct has_bit_or : std::false_type {}; + +/** + * @brief Specialization of has_bit_or for types that support the bitwise OR + * operator. + * + * @tparam T The type to check. + */ +template +struct has_bit_or : std::true_type {}; + +#if MGUTILITY_CPLUSPLUS > 201103L +/** + * @brief Helper variable template for is_scoped_enum. + * + * @tparam E The type to check. + */ +template +static constexpr bool is_scoped_enum_v = is_scoped_enum::value; +#endif + +/** + * @brief Alias template for std::enable_if. + * + * This template is used to conditionally enable a type `T` if the boolean + * constant `B` is true. It is a shorthand for `typename std::enable_if::type`. + * + * @tparam B The compile-time boolean condition. + * @tparam T The type to be enabled if `B` is true, default is void. + */ +template +using enable_if_t = typename std::enable_if::type; + +/** + * @brief Alias template for std::underlying_type. + * + * @tparam T The enumeration type. + */ +template +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 +using remove_const_t = typename std::remove_const::type; + +/** + * @brief Represents a sequence of enumeration values. + * + * @tparam Enum The enumeration type. + * @tparam ...EnumValues The enumeration values in the sequence. + */ +template struct enum_sequence {}; + +/** + * @brief Helper for generating a sequence of enumeration values. + * + * @tparam Enum The enumeration type. + * @tparam Min The minimum value in the sequence. + * @tparam Max The maximum value in the sequence. + * @tparam ...Next The next values in the sequence. + */ +template +struct enum_sequence_helper + : enum_sequence_helper {}; + +/** + * @brief Specialization of enum_sequence_helper for the end of the sequence. + * + * @tparam Enum The enumeration type. + * @tparam Min The minimum value in the sequence. + * @tparam ...Next The next values in the sequence. + */ +template +struct enum_sequence_helper { + /** + * @brief The resulting sequence type. + */ + using type = enum_sequence(Next)...>; +}; + +/** + * @brief Generates a sequence of enumeration values. + * + * @tparam Enum The enumeration type. + * @tparam Min The minimum value in the sequence. + * @tparam Max The maximum value in the sequence. + */ +template +using make_enum_sequence = typename enum_sequence_helper::type; +} // namespace detail + +/** + * @brief Provides the range for an enumeration type. + * + * @tparam T The enumeration type. + */ +template struct enum_range { + static constexpr auto min{0}; + static constexpr auto max{256}; +}; + +template struct pair { + T first; + U second; +}; + +template +#if MGUTILITY_CPLUSPLUS > 201103L || defined(__GNUC__) && !defined(__clang__) +using flat_map = std::initializer_list>; +#else +using flat_map = pair[]; +#endif + +/** + * @brief Provides the custom names map for an enumeration type. + * + * @tparam T The enumeration type. + */ +template struct custom_enum { + static constexpr flat_map map = {}; +}; +} // namespace mgutility + +#endif // DETAIL_META_HPP \ No newline at end of file diff --git a/include/mgutility/reflection/enum_name.hpp b/include/mgutility/reflection/enum_name.hpp new file mode 100644 index 0000000..47c2df7 --- /dev/null +++ b/include/mgutility/reflection/enum_name.hpp @@ -0,0 +1,211 @@ +/* +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 + +#include "detail/enum_name_impl.hpp" + +namespace mgutility { + +/** + * @brief Converts an enum value to its underlying integer value. + * + * @tparam Enum The enum type. + * @param e The enum value. + * @return The underlying integer value of the enum. + */ +template +constexpr auto enum_to_underlying(Enum e) noexcept + -> detail::underlying_type_t { + static_assert(std::is_enum::value, "Value is not an Enum type!"); + return static_cast>(e); +} + +/** + * @brief Gets the name of an enum value. + * + * @tparam Min The minimum enum value. + * @tparam Max The maximum enum value. + * @tparam Enum The enum type. + * @param e The enum value. + * @return A string view or string representing the name of the enum value. + */ +template +MGUTILITY_CNSTXPR auto enum_name(Enum e) noexcept + -> detail::string_or_view_t { + static_assert(Min < Max - 1, "Max must be greater than (Min + 1)!"); + static_assert(std::is_enum::value, "Value is not an Enum type!"); + return detail::enum_name_impl(e); +} + +/** + * @brief Gets the name of an enum value. + * + * @tparam Enum The enum type. + * @tparam Min The minimum enum value, default is enum_range::min. + * @tparam Max The maximum enum value, default is enum_range::max. + * @param e The enum value. + * @return A string view or string representing the name of the enum value. + */ +template ::min, + int Max = enum_range::max> +MGUTILITY_CNSTXPR auto enum_name(Enum e) noexcept + -> detail::string_or_view_t { + static_assert(Min < Max - 1, "Max must be greater than (Min + 1)!"); + static_assert(std::is_enum::value, "Value is not an Enum type!"); + return detail::enum_name_impl(e); +} + +/** + * @brief Gets the enum value and its name. + * + * @tparam Enum The enum type. + * @return A pair of the enum value and its name. + */ +template +auto enum_for_each::enum_iter::operator*() const -> value_type { + auto value = static_cast(m_pos); + return detail::enum_pair{value, enum_name(value)}; +} + +/** + * @brief Converts a string to an enum value. + * + * @tparam Enum The enum type. + * @tparam Min The minimum enum value, default is enum_range::min. + * @tparam Max The maximum enum value, default is enum_range::max. + * @param str The string view representing the enum name. + * @return An optional enum value. + */ +template ::min, + int Max = enum_range::max, + detail::enable_if_t::value, bool> = true> +MGUTILITY_CNSTXPR auto to_enum(mgutility::string_view str) noexcept + -> mgutility::optional { + static_assert(Min < Max - 1, "Max must be greater than (Min + 1)!"); + static_assert(std::is_enum::value, "Type is not an Enum type!"); + return detail::to_enum_impl(str); +} + +/** + * @brief Converts a string to an enum bitmask value. + * + * @tparam Enum The enum type. + * @tparam Min The minimum enum value, default is enum_range::min. + * @tparam Max The maximum enum value, default is enum_range::max. + * @param str The string view representing the enum name. + * @return An optional enum bitmask value. + */ +template ::min, + int Max = enum_range::max, + detail::enable_if_t::value, bool> = true> +MGUTILITY_CNSTXPR auto to_enum(mgutility::string_view str) noexcept + -> mgutility::optional { + static_assert(Min < Max - 1, "Max must be greater than (Min + 1)!"); + static_assert(std::is_enum::value, "Type is not an Enum type!"); + return detail::to_enum_bitmask_impl(str); +} + +/** + * @brief Casts an integer value to an enum value. + * + * @tparam Enum The enum type. + * @tparam Min The minimum enum value, default is enum_range::min. + * @tparam Max The maximum enum value, default is enum_range::max. + * @param value The integer value to cast. + * @return An optional enum value. + */ +template ::min, + int Max = enum_range::max> +MGUTILITY_CNSTXPR auto enum_cast(int value) noexcept + -> mgutility::optional { + static_assert(Min < Max - 1, "Max must be greater than (Min + 1)!"); + static_assert(std::is_enum::value, "Type is not an Enum type!"); + if (enum_name(static_cast(value)).empty()) { + return mgutility::nullopt; + } + return static_cast(value); +} + +namespace operators { +template ::value, bool> = true> +inline constexpr auto operator&(const Enum &lhs, const Enum &rhs) -> Enum { + return static_cast(mgutility::enum_to_underlying(lhs) & + mgutility::enum_to_underlying(rhs)); +} + +template ::value, bool> = true> +inline constexpr auto operator|(const Enum &lhs, const Enum &rhs) -> Enum { + return static_cast(mgutility::enum_to_underlying(lhs) | + mgutility::enum_to_underlying(rhs)); +} +} // namespace operators + +} // namespace mgutility + +/** + * @brief Outputs the name of an enum value to an output stream. + * + * @tparam Enum The enum type. + * @param os The output stream. + * @param e The enum value. + * @return The output stream. + */ +template ::value, bool> = true> +auto operator<<(std::ostream &os, Enum e) -> std::ostream & { + static_assert(std::is_enum::value, "Value is not an Enum type!"); + os << mgutility::enum_name(e); + return os; +} + +#if defined(__cpp_lib_format) + +#include + +/** + * @brief Formatter for enum types for use with std::format. + * + * @tparam Enum The enum type. + */ +template + requires std::is_enum_v +struct std::formatter : formatter { + auto constexpr format(Enum e, format_context &ctx) const { + return formatter::format(mgutility::enum_name(e), ctx); + } +}; + +#endif + +#if defined(__has_include) +#if __has_include() +#include +#endif +#endif + +#endif // MGUTILITY_ENUM_NAME_HPP \ No newline at end of file diff --git a/include/mgutility/std/optional.hpp b/include/mgutility/std/optional.hpp new file mode 100644 index 0000000..ef74634 --- /dev/null +++ b/include/mgutility/std/optional.hpp @@ -0,0 +1,334 @@ +/* +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_OPTIONAL_HPP +#define DETAIL_OPTIONAL_HPP + +#include "mgutility/_common/definitions.hpp" +#include "mgutility/reflection/detail/meta.hpp" + +#include + +#if MGUTILITY_CPLUSPLUS >= 201703L +#include +#endif + +namespace mgutility { + +#if MGUTILITY_CPLUSPLUS < 201703L + +/** + * @brief Exception thrown when accessing an empty optional. + */ +struct bad_optional_access : public std::exception { + /** + * @brief Returns the exception message. + * + * @return A C-string with the exception message. + */ + const char *what() const noexcept { return "optional has no value"; } +}; + +struct nullopt_t; + +/** + * @brief A class template that provides optional (nullable) objects. + * + * @tparam T The type of the value. + */ +template class optional { +public: + /** + * @brief Constructs an empty optional. + */ + MGUTILITY_CNSTXPR inline optional(nullopt_t &) + : dummy_{0}, has_value_{false} {} + + /** + * @brief Constructs an empty optional. + */ + MGUTILITY_CNSTXPR inline optional() : dummy_{0}, has_value_{false} {} + + /** + * @brief Constructs an optional with a value. + * + * @tparam Args The types of the arguments. + * @param args The arguments to construct the value. + */ + template + MGUTILITY_CNSTXPR inline optional(Args &&...args) + : value_{T{std::forward(args)...}}, has_value_{true} {} + + /** + * @brief Constructs an optional with a value. + * + * @param value The value to initialize with. + */ + MGUTILITY_CNSTXPR inline optional(T &&value) + : value_{value}, has_value_{true} {} + + /** + * @brief Copy constructor. + * + * @param other The other optional to copy. + */ + MGUTILITY_CNSTXPR inline optional(const optional &other) + : value_{other.value_}, has_value_{other.has_value_} {} + + /** + * @brief Move constructor. + * + * @param other The other optional to move. + */ + MGUTILITY_CNSTXPR inline optional(optional &&other) + : value_{other.value_}, has_value_{other.has_value_} { + other.reset(); + } + + /** + * @brief Destructor. + */ + inline ~optional() { has_value_ = false; } + + /** + * @brief Copy assignment operator. + * + * @param other The other optional to copy. + * @return A reference to this optional. + */ + MGUTILITY_CNSTXPR inline optional &operator=(const optional &other) { + value_ = other.value_; + has_value_ = other.has_value_; + return *this; + } + + /** + * @brief Move assignment operator. + * + * @param other The other optional to move. + * @return A reference to this optional. + */ + MGUTILITY_CNSTXPR inline optional &operator=(optional &&other) { + value_ = other.value_; + has_value_ = other.has_value_; + other.reset(); + return *this; + } + + /** + * @brief Swaps the contents of this optional with another. + * + * @param other The other optional to swap with. + */ + MGUTILITY_CNSTXPR inline void swap(optional &&other) { + auto val = std::move(other.value_); + other.value_ = std::move(value_); + value_ = std::move(val); + + auto hval = std::move(other.has_value_); + other.has_value_ = std::move(has_value_); + has_value_ = std::move(hval); + } + + /** + * @brief Dereferences the stored value. + * + * @return A reference to the stored value. + */ + MGUTILITY_CNSTXPR inline T &operator*() { return value_; } + + /** + * @brief Dereferences the stored value (const version). + * + * @return A reference to the stored value. + */ + MGUTILITY_CNSTXPR inline T &operator*() const { return value_; } + + /** + * @brief Accesses the stored value. + * + * @return A reference to the stored value. + * @throws bad_optional_access if the optional has no value. + */ + MGUTILITY_CNSTXPR inline T &value() { + if (!has_value_) + throw bad_optional_access(); + return value_; + } + + /** + * @brief Accesses the stored value (const version). + * + * @return A reference to the stored value. + * @throws bad_optional_access if the optional has no value. + */ + MGUTILITY_CNSTXPR inline T &value() const { + if (!has_value_) + throw bad_optional_access(); + return value_; + } + + /** + * @brief Returns the stored value or a default value if empty. + * + * @param value The default value to return if empty. + * @return The stored value or the default value. + */ + MGUTILITY_CNSTXPR inline T value_or(T &&value) { + return has_value_ ? value_ : value; + } + + /** + * @brief Returns the stored value or a default value if empty (const + * version). + * + * @param value The default value to return if empty. + * @return The stored value or the default value. + */ + MGUTILITY_CNSTXPR inline T value_or(T &&value) const { + return has_value_ ? value_ : value; + } + + /** + * @brief Returns the stored value or a default value if empty. + * + * @param value The default value to return if empty. + * @return The stored value or the default value. + */ + MGUTILITY_CNSTXPR inline T value_or(const T &value) { + return has_value_ ? value_ : value; + } + + /** + * @brief Returns the stored value or a default value if empty (const + * version). + * + * @param value The default value to return if empty. + * @return The stored value or the default value. + */ + MGUTILITY_CNSTXPR inline T value_or(const T &value) const { + return has_value_ ? value_ : value; + } + + /** + * @brief Constructs the value in-place. + * + * @param value The value to emplace. + */ + MGUTILITY_CNSTXPR inline void emplace(T value) { + value_ = std::move(value); + has_value_ = true; + } + + /** + * @brief Constructs the value in-place with arguments. + * + * @tparam Args The types of the arguments. + * @param args The arguments to construct the value. + */ + template + MGUTILITY_CNSTXPR inline void emplace(Args &&...args) { + value_ = std::move(T{std::forward(args)...}); + has_value_ = true; + } + + /** + * @brief Checks if the optional has a value. + * + * @return True if the optional has a value, otherwise false. + */ + MGUTILITY_CNSTXPR inline bool has_value() const { return has_value_; } + + /** + * @brief Resets the optional, making it empty. + */ + template ::value, bool> = true> + MGUTILITY_CNSTXPR inline void reset() { + value_ = T{}; + has_value_ = false; + } + + /** + * @brief Resets the optional, making it empty. + */ + template ::value, bool> = true> + MGUTILITY_CNSTXPR inline void reset() { + value_.~T(); + has_value_ = false; + } + + /** + * @brief Checks if the optional has a value. + * + * @return True if the optional has a value, otherwise false. + */ + MGUTILITY_CNSTXPR operator bool() { return has_value_; } + +private: + union { + T value_; + char dummy_[sizeof(T)]; + }; + bool has_value_; +}; + +/** + * @brief Represents a null optional. + */ +struct nullopt_t { + /** + * @brief Converts nullopt_t to an empty optional. + * + * @tparam T The type of the optional. + * @return An empty optional. + */ + template MGUTILITY_CNSTXPR operator optional() { + return optional{}; + } +}; + +/** + * @brief A global instance of nullopt_t to represent null optional. + */ +auto nullopt = nullopt_t{}; + +#else +/** + * @brief Alias template for std::optional. + * + * @tparam T The type of the value. + */ +template using optional = std::optional; + +/** + * @brief A global constant representing a null optional. + */ +inline constexpr auto nullopt{std::nullopt}; + +#endif +} // namespace mgutility + +#endif // DETAIL_OPTIONAL_HPP \ No newline at end of file diff --git a/include/mgutility/std/string_view.hpp b/include/mgutility/std/string_view.hpp new file mode 100644 index 0000000..a839770 --- /dev/null +++ b/include/mgutility/std/string_view.hpp @@ -0,0 +1,314 @@ +/* +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 STRING_STRING_VIEW_HPP +#define STRING_STRING_VIEW_HPP + +#include +#include +#include + +#include "mgutility/_common/definitions.hpp" + +namespace mgutility { + +#if MGUTILITY_CPLUSPLUS < 201703L + +namespace detail { +/** + * @brief Computes the length of a C-string at compile-time. + * + * @param str The C-string. + * @param sz The initial size, default is 0. + * @return The length of the C-string. + */ +constexpr auto strlen_constexpr(const char *str, size_t sz = 0) noexcept + -> size_t { + return str[sz] == '\0' ? sz : strlen_constexpr(str, ++sz); +} +} // namespace detail + +/** + * @brief A basic string view class template. + * + * @tparam Char The character type, default is char. + */ +template class basic_string_view { +public: + /** + * @brief Default constructor. + */ + constexpr inline basic_string_view() noexcept : data_(""), size_(0) {} + + /** + * @brief Constructs a basic_string_view from a C-string. + * + * @param str The C-string. + */ + constexpr inline basic_string_view(const Char *str) noexcept + : data_(str), size_(detail::strlen_constexpr(str)) {} + + /** + * @brief Constructs a basic_string_view from a std::string. + * + * @param str The std::string. + */ + constexpr inline basic_string_view( + const std::basic_string &str) noexcept + : data_(str.c_str()), size_(str.size()) {} + + /** + * @brief Constructs a basic_string_view from a C-string and length. + * + * @param str The C-string. + * @param len The length of the string. + */ + constexpr inline basic_string_view(const Char *str, size_t len) noexcept + : data_(str), size_(len) {} + + /** + * @brief Copy constructor. + * + * @param other The other basic_string_view to copy. + */ + constexpr inline basic_string_view(const basic_string_view &other) + : data_(other.data_), size_(other.size_) {} + + /** + * @brief Move constructor. + * + * @param other The other basic_string_view to move. + */ + constexpr inline basic_string_view(basic_string_view &&other) noexcept + : data_(std::move(other.data_)), size_(std::move(other.size_)) {} + + /** + * @brief Copy assignment operator. + * + * @param other The other basic_string_view to copy. + * @return A reference to this object. + */ + MGUTILITY_CNSTXPR inline basic_string_view & + operator=(const basic_string_view &other) noexcept { + data_ = other.data_; + size_ = other.size_; + return *this; + } + + /** + * @brief Move assignment operator. + * + * @param other The other basic_string_view to move. + * @return A reference to this object. + */ + MGUTILITY_CNSTXPR inline basic_string_view & + operator=(basic_string_view &&other) noexcept { + data_ = std::move(other.data_); + size_ = std::move(other.size_); + return *this; + } + + /** + * @brief Accesses the character at the given index. + * + * @param index The index. + * @return The character at the index. + */ + constexpr inline const Char operator[](size_t index) const noexcept { + return data_[index]; + } + + /** + * @brief Returns an iterator to the beginning of the string. + * + * @return A pointer to the first character. + */ + constexpr inline const Char *begin() const noexcept { return data_; } + + /** + * @brief Returns an iterator to the end of the string. + * + * @return A pointer to one past the last character. + */ + constexpr inline const Char *end() const noexcept { return (data_ + size_); } + + /** + * @brief Checks if the string is empty. + * + * @return True if the string is empty, otherwise false. + */ + constexpr inline bool empty() const noexcept { return size_ < 1; } + + /** + * @brief Returns the size of the string. + * + * @return The size of the string. + */ + constexpr inline size_t size() const noexcept { return size_; } + + /** + * @brief Returns a pointer to the underlying data. + * + * @return A pointer to the data. + */ + constexpr inline const Char *data() const noexcept { return data_; } + + /** + * @brief Returns a substring view. + * + * @param begin The starting position. + * @param len The length of the substring. + * @return A basic_string_view representing the substring. + */ + constexpr inline basic_string_view + substr(size_t begin, size_t len = 0U) const noexcept { + return basic_string_view(data_ + begin, + len == 0U ? size_ - begin : len); + } + + /** + * @brief Finds the last occurrence of a character. + * + * @param c The character to find. + * @param pos The position to start from, default is npos. + * @return The position of the character or npos if not found. + */ + constexpr inline size_t rfind(Char c, size_t pos = npos) const noexcept { + return (pos == npos ? pos = size_ : pos = pos), c == data_[pos] ? pos + : pos == 0U + ? npos + : rfind(c, --pos); + } + + /** + * @brief Finds the first occurrence of a character. + * + * @param c The character to find. + * @param pos The position to start from, default is 0. + * @return The position of the character or npos if not found. + */ + constexpr inline size_t find(Char c, size_t pos = 0) const noexcept { + return c == data_[pos] ? pos : pos < size_ ? find(c, ++pos) : npos; + } + + /** + * @brief Equality operator. + * + * @param lhs The left-hand side basic_string_view. + * @param rhs The right-hand side basic_string_view. + * @return True if the strings are equal, otherwise false. + */ + constexpr friend inline bool + operator==(basic_string_view lhs, + basic_string_view rhs) noexcept { + return (lhs.size_ == rhs.size_) && + std::strncmp(lhs.data_, rhs.data_, lhs.size_) == 0; + } + + /** + * @brief Equality operator. + * + * @param lhs The left-hand side basic_string_view. + * @param rhs The right-hand side C-string. + * @return True if the strings are equal, otherwise false. + */ + constexpr friend inline bool operator==(basic_string_view lhs, + const Char *rhs) noexcept { + return (lhs.size_ == detail::strlen_constexpr(rhs)) && + std::strncmp(lhs.data_, rhs, lhs.size_) == 0; + } + + /** + * @brief Inequality operator. + * + * @param lhs The left-hand side basic_string_view. + * @param rhs The right-hand side basic_string_view. + * @return True if the strings are not equal, otherwise false. + */ + constexpr friend inline bool + operator!=(basic_string_view lhs, + basic_string_view rhs) noexcept { + return !(lhs == rhs); + } + + /** + * @brief Inequality operator. + * + * @param lhs The left-hand side basic_string_view. + * @param rhs The right-hand side C-string. + * @return True if the strings are not equal, otherwise false. + */ + constexpr friend inline bool operator!=(basic_string_view lhs, + const Char *rhs) noexcept { + return !(lhs == rhs); + } + + /** + * @brief Converts the string view to an std::string. + * + * @return An std::string representing the same string. + */ + inline operator std::string() { return std::string(data_, size_); } + + /** + * @brief Converts the string view to an std::string (const version). + * + * @return An std::string representing the same string. + */ + inline operator std::string() const { return std::string(data_, size_); } + + /** + * @brief Stream insertion operator. + * + * @param os The output stream. + * @param sv The basic_string_view. + * @return A reference to the output stream. + */ + friend inline std::ostream &operator<<(std::ostream &os, + const basic_string_view &sv) { + for (auto c : sv) { + os << c; + } + return os; + } + + static constexpr auto npos = -1; + +private: + size_t size_; + const Char *data_; +}; + +using string_view = basic_string_view; + +#else +#include + +using string_view = std::string_view; + +#endif + +} // namespace mgutility + +#endif // STRING_STRING_VIEW_HPP \ No newline at end of file diff --git a/scripts/ci_setup_clang.sh b/scripts/ci_setup_clang.sh index faf60fe..4fc6398 100755 --- a/scripts/ci_setup_clang.sh +++ b/scripts/ci_setup_clang.sh @@ -7,6 +7,6 @@ VERSION=$1 apt-get update apt-get install -y libc++-${VERSION}-dev libc++abi-${VERSION}-dev -if [[ "${VERSION}" -ge 12 ]]; then - apt-get install -y --no-install-recommends libunwind-${VERSION}-dev -fi \ No newline at end of file +if [[ ${VERSION} -ge 12 ]]; then + apt-get install -y --no-install-recommends libunwind-${VERSION}-dev +fi diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt deleted file mode 100644 index 3d68426..0000000 --- a/test/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -cmake_minimum_required(VERSION 3.14) -project(enum_name_test VERSION 0.1 LANGUAGES CXX) - -set(CMAKE_CXX_STANDARD 17) - -include_directories(AFTER PUBLIC ${CMAKE_SOURCE_DIR}/include) - -add_executable(${PROJECT_NAME} enum_name_test.cpp) -target_link_libraries(${PROJECT_NAME} doctest) - -add_test(NAME enum_name_test COMMAND enum_name_test) \ No newline at end of file diff --git a/test/enum_name_test.cpp b/test/enum_name_test.cpp deleted file mode 100644 index 266ddfc..0000000 --- a/test/enum_name_test.cpp +++ /dev/null @@ -1,208 +0,0 @@ -#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN -#include "doctest/doctest.h" -#include "enum_name.hpp" - - -enum class color : uint32_t { - alice_blue, // rgb(240,248,255) - antique_white, // rgb(250,235,215) - aqua, // rgb(0,255,255) - aquamarine, // rgb(127,255,212) - azure, // rgb(240,255,255) - beige, // rgb(245,245,220) - bisque, // rgb(255,228,196) - black, // rgb(0,0,0) - blanched_almond, // rgb(255,235,205) - blue, // rgb(0,0,255) - blue_violet, // rgb(138,43,226) - brown, // rgb(165,42,42) - burly_wood, // rgb(222,184,135) - cadet_blue, // rgb(95,158,160) - chartreuse, // rgb(127,255,0) - chocolate, // rgb(210,105,30) - coral, // rgb(255,127,80) - cornflower_blue, // rgb(100,149,237) - cornsilk, // rgb(255,248,220) - crimson, // rgb(220,20,60) - cyan, // rgb(0,255,255) - dark_blue, // rgb(0,0,139) - dark_cyan, // rgb(0,139,139) - dark_golden_rod, // rgb(184,134,11) - dark_gray, // rgb(169,169,169) - dark_green, // rgb(0,100,0) - dark_khaki, // rgb(189,183,107) - dark_magenta, // rgb(139,0,139) - dark_olive_green, // rgb(85,107,47) - dark_orange, // rgb(255,140,0) - dark_orchid, // rgb(153,50,204) - dark_red, // rgb(139,0,0) - dark_salmon, // rgb(233,150,122) - dark_sea_green, // rgb(143,188,143) - dark_slate_blue, // rgb(72,61,139) - dark_slate_gray, // rgb(47,79,79) - dark_turquoise, // rgb(0,206,209) - dark_violet, // rgb(148,0,211) - deep_pink, // rgb(255,20,147) - deep_sky_blue, // rgb(0,191,255) - dim_gray, // rgb(105,105,105) - dodger_blue, // rgb(30,144,255) - fire_brick, // rgb(178,34,34) - floral_white, // rgb(255,250,240) - forest_green, // rgb(34,139,34) - fuchsia, // rgb(255,0,255) - gainsboro, // rgb(220,220,220) - ghost_white, // rgb(248,248,255) - gold, // rgb(255,215,0) - golden_rod, // rgb(218,165,32) - gray, // rgb(128,128,128) - green, // rgb(0,128,0) - green_yellow, // rgb(173,255,47) - honey_dew, // rgb(240,255,240) - hot_pink, // rgb(255,105,180) - indian_red, // rgb(205,92,92) - indigo, // rgb(75,0,130) - ivory, // rgb(255,255,240) - khaki, // rgb(240,230,140) - lavender, // rgb(230,230,250) - lavender_blush, // rgb(255,240,245) - lawn_green, // rgb(124,252,0) - lemon_chiffon, // rgb(255,250,205) - light_blue, // rgb(173,216,230) - light_coral, // rgb(240,128,128) - light_cyan, // rgb(224,255,255) - light_golden_rod_yellow, // rgb(250,250,210) - light_gray, // rgb(211,211,211) - light_green, // rgb(144,238,144) - light_pink, // rgb(255,182,193) - light_salmon, // rgb(255,160,122) - light_sea_green, // rgb(32,178,170) - light_sky_blue, // rgb(135,206,250) - light_slate_gray, // rgb(119,136,153) - light_steel_blue, // rgb(176,196,222) - light_yellow, // rgb(255,255,224) - lime, // rgb(0,255,0) - lime_green, // rgb(50,205,50) - linen, // rgb(250,240,230) - magenta, // rgb(255,0,255) - maroon, // rgb(128,0,0) - medium_aquamarine, // rgb(102,205,170) - medium_blue, // rgb(0,0,205) - medium_orchid, // rgb(186,85,211) - medium_purple, // rgb(147,112,219) - medium_sea_green, // rgb(60,179,113) - medium_slate_blue, // rgb(123,104,238) - medium_spring_green, // rgb(0,250,154) - medium_turquoise, // rgb(72,209,204) - medium_violet_red, // rgb(199,21,133) - midnight_blue, // rgb(25,25,112) - mint_cream, // rgb(245,255,250) - misty_rose, // rgb(255,228,225) - moccasin, // rgb(255,228,181) - navajo_white, // rgb(255,222,173) - navy, // rgb(0,0,128) - old_lace, // rgb(253,245,230) - olive, // rgb(128,128,0) - olive_drab, // rgb(107,142,35) - orange, // rgb(255,165,0) - orange_red, // rgb(255,69,0) - orchid, // rgb(218,112,214) - pale_golden_rod, // rgb(238,232,170) - pale_green, // rgb(152,251,152) - pale_turquoise, // rgb(175,238,238) - pale_violet_red, // rgb(219,112,147) - papaya_whip, // rgb(255,239,213) - peach_puff, // rgb(255,218,185) - peru, // rgb(205,133,63) - pink, // rgb(255,192,203) - plum, // rgb(221,160,221) - powder_blue, // rgb(176,224,230) - purple, // rgb(128,0,128) - rebecca_purple, // rgb(102,51,153) - red, // rgb(255,0,0) - rosy_brown, // rgb(188,143,143) - royal_blue, // rgb(65,105,225) - saddle_brown, // rgb(139,69,19) - salmon, // rgb(250,128,114) - sandy_brown, // rgb(244,164,96) - sea_green, // rgb(46,139,87) - sea_shell, // rgb(255,245,238) - sienna, // rgb(160,82,45) - silver, // rgb(192,192,192) - sky_blue, // rgb(135,206,235) - slate_blue, // rgb(106,90,205) - slate_gray, // rgb(112,128,144) - snow, // rgb(255,250,250) - spring_green, // rgb(0,255,127) - steel_blue, // rgb(70,130,180) - tan, // rgb(210,180,140) - teal, // rgb(0,128,128) - thistle, // rgb(216,191,216) - tomato, // rgb(255,99,71) - turquoise, // rgb(64,224,208) - violet, // rgb(238,130,238) - wheat, // rgb(245,222,179) - white, // rgb(255,255,255) - white_smoke, // rgb(245,245,245) - yellow, // rgb(255,255,0) - yellow_green // rgb(154,205,50) -}; // enum class color - -namespace mgutility{ - template<> - struct enum_range - { - static constexpr auto min = 0; - static constexpr auto max = 150; - }; -} - - - -TEST_CASE("testing the enum name serialization") { - CHECK(mgutility::enum_name(color::blue) == "blue"); - CHECK(mgutility::enum_name(color::white_smoke) == "white_smoke"); - CHECK(mgutility::enum_name(color::yellow_green) == "yellow_green"); - CHECK(mgutility::enum_name(color::steel_blue) == "steel_blue"); - CHECK(mgutility::enum_name(color::pale_turquoise) == "pale_turquoise"); - CHECK(mgutility::enum_name(color::white_smoke) == "white_smoke"); - CHECK(mgutility::enum_name(color::light_golden_rod_yellow) == "light_golden_rod_yellow"); - CHECK(mgutility::enum_name(color::dark_olive_green) == "dark_olive_green"); - CHECK(mgutility::enum_name(color::azure) == "azure"); - CHECK(mgutility::enum_name(color::ghost_white) == "ghost_white"); - CHECK(mgutility::enum_name(color::maroon) == "maroon"); - CHECK(mgutility::enum_name(color::navy) == "navy"); - CHECK(mgutility::enum_name(color::spring_green) == "spring_green"); - CHECK(mgutility::enum_name(color::rebecca_purple) == "rebecca_purple"); - CHECK(mgutility::enum_name(color::red) == "red"); - CHECK(mgutility::enum_name(color::tan) == "tan"); - CHECK(mgutility::enum_name(color::mint_cream) == "mint_cream"); - CHECK(mgutility::enum_name(color::light_green) == "light_green"); - CHECK(mgutility::enum_name(color::dodger_blue) == "dodger_blue"); - CHECK(mgutility::enum_name(color::cornflower_blue) == "cornflower_blue"); - CHECK(mgutility::enum_name<-5, 0>(color::thistle) == ""); -} - -TEST_CASE("testing the enum name deserialization") { - CHECK(mgutility::to_enum("blue").value() == color::blue); - CHECK(mgutility::to_enum("white_smoke").value() == color::white_smoke); - CHECK(mgutility::to_enum("yellow_green").value() == color::yellow_green); - CHECK(mgutility::to_enum("steel_blue").value() == color::steel_blue); - CHECK(mgutility::to_enum( "pale_turquoise").value() == color::pale_turquoise); - CHECK(mgutility::to_enum("white_smoke").value() == color::white_smoke); - CHECK(mgutility::to_enum("light_golden_rod_yellow").value() == color::light_golden_rod_yellow); - CHECK(mgutility::to_enum("dark_olive_green").value() == color::dark_olive_green); - CHECK(mgutility::to_enum("azure").value() == color::azure); - CHECK(mgutility::to_enum("ghost_white").value() == color::ghost_white); - CHECK(mgutility::to_enum("maroon").value() == color::maroon); - CHECK(mgutility::to_enum("navy").value() == color::navy); - CHECK(mgutility::to_enum("spring_green").value() == color::spring_green); - CHECK(mgutility::to_enum("rebecca_purple").value() == color::rebecca_purple); - CHECK(mgutility::to_enum("red").value() == color::red); - CHECK(mgutility::to_enum("tan").value() == color::tan); - CHECK(mgutility::to_enum("mint_cream").value() == color::mint_cream); - CHECK(mgutility::to_enum("light_green").value() == color::light_green); - CHECK(mgutility::to_enum("dodger_blue").value() == color::dodger_blue); - CHECK(mgutility::to_enum("cornflower_blue").value() == color::cornflower_blue); - REQUIRE_THROWS(mgutility::to_enum("thistle").value()); - -} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..a52bc7e --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.14) +project( + enum_name_test + VERSION 0.1 + LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) + +include_directories(AFTER PUBLIC ${CMAKE_SOURCE_DIR}/include) + +add_executable(${PROJECT_NAME} test_enum_name.cpp) +target_link_libraries(${PROJECT_NAME} doctest) + +add_test(NAME enum_name_test COMMAND enum_name_test) diff --git a/tests/test_enum_name.cpp b/tests/test_enum_name.cpp new file mode 100644 index 0000000..39a4b27 --- /dev/null +++ b/tests/test_enum_name.cpp @@ -0,0 +1,210 @@ +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +#include "doctest/doctest.h" +#include "mgutility/reflection/enum_name.hpp" + +enum class color : uint32_t { + alice_blue, // rgb(240,248,255) + antique_white, // rgb(250,235,215) + aqua, // rgb(0,255,255) + aquamarine, // rgb(127,255,212) + azure, // rgb(240,255,255) + beige, // rgb(245,245,220) + bisque, // rgb(255,228,196) + black, // rgb(0,0,0) + blanched_almond, // rgb(255,235,205) + blue, // rgb(0,0,255) + blue_violet, // rgb(138,43,226) + brown, // rgb(165,42,42) + burly_wood, // rgb(222,184,135) + cadet_blue, // rgb(95,158,160) + chartreuse, // rgb(127,255,0) + chocolate, // rgb(210,105,30) + coral, // rgb(255,127,80) + cornflower_blue, // rgb(100,149,237) + cornsilk, // rgb(255,248,220) + crimson, // rgb(220,20,60) + cyan, // rgb(0,255,255) + dark_blue, // rgb(0,0,139) + dark_cyan, // rgb(0,139,139) + dark_golden_rod, // rgb(184,134,11) + dark_gray, // rgb(169,169,169) + dark_green, // rgb(0,100,0) + dark_khaki, // rgb(189,183,107) + dark_magenta, // rgb(139,0,139) + dark_olive_green, // rgb(85,107,47) + dark_orange, // rgb(255,140,0) + dark_orchid, // rgb(153,50,204) + dark_red, // rgb(139,0,0) + dark_salmon, // rgb(233,150,122) + dark_sea_green, // rgb(143,188,143) + dark_slate_blue, // rgb(72,61,139) + dark_slate_gray, // rgb(47,79,79) + dark_turquoise, // rgb(0,206,209) + dark_violet, // rgb(148,0,211) + deep_pink, // rgb(255,20,147) + deep_sky_blue, // rgb(0,191,255) + dim_gray, // rgb(105,105,105) + dodger_blue, // rgb(30,144,255) + fire_brick, // rgb(178,34,34) + floral_white, // rgb(255,250,240) + forest_green, // rgb(34,139,34) + fuchsia, // rgb(255,0,255) + gainsboro, // rgb(220,220,220) + ghost_white, // rgb(248,248,255) + gold, // rgb(255,215,0) + golden_rod, // rgb(218,165,32) + gray, // rgb(128,128,128) + green, // rgb(0,128,0) + green_yellow, // rgb(173,255,47) + honey_dew, // rgb(240,255,240) + hot_pink, // rgb(255,105,180) + indian_red, // rgb(205,92,92) + indigo, // rgb(75,0,130) + ivory, // rgb(255,255,240) + khaki, // rgb(240,230,140) + lavender, // rgb(230,230,250) + lavender_blush, // rgb(255,240,245) + lawn_green, // rgb(124,252,0) + lemon_chiffon, // rgb(255,250,205) + light_blue, // rgb(173,216,230) + light_coral, // rgb(240,128,128) + light_cyan, // rgb(224,255,255) + light_golden_rod_yellow, // rgb(250,250,210) + light_gray, // rgb(211,211,211) + light_green, // rgb(144,238,144) + light_pink, // rgb(255,182,193) + light_salmon, // rgb(255,160,122) + light_sea_green, // rgb(32,178,170) + light_sky_blue, // rgb(135,206,250) + light_slate_gray, // rgb(119,136,153) + light_steel_blue, // rgb(176,196,222) + light_yellow, // rgb(255,255,224) + lime, // rgb(0,255,0) + lime_green, // rgb(50,205,50) + linen, // rgb(250,240,230) + magenta, // rgb(255,0,255) + maroon, // rgb(128,0,0) + medium_aquamarine, // rgb(102,205,170) + medium_blue, // rgb(0,0,205) + medium_orchid, // rgb(186,85,211) + medium_purple, // rgb(147,112,219) + medium_sea_green, // rgb(60,179,113) + medium_slate_blue, // rgb(123,104,238) + medium_spring_green, // rgb(0,250,154) + medium_turquoise, // rgb(72,209,204) + medium_violet_red, // rgb(199,21,133) + midnight_blue, // rgb(25,25,112) + mint_cream, // rgb(245,255,250) + misty_rose, // rgb(255,228,225) + moccasin, // rgb(255,228,181) + navajo_white, // rgb(255,222,173) + navy, // rgb(0,0,128) + old_lace, // rgb(253,245,230) + olive, // rgb(128,128,0) + olive_drab, // rgb(107,142,35) + orange, // rgb(255,165,0) + orange_red, // rgb(255,69,0) + orchid, // rgb(218,112,214) + pale_golden_rod, // rgb(238,232,170) + pale_green, // rgb(152,251,152) + pale_turquoise, // rgb(175,238,238) + pale_violet_red, // rgb(219,112,147) + papaya_whip, // rgb(255,239,213) + peach_puff, // rgb(255,218,185) + peru, // rgb(205,133,63) + pink, // rgb(255,192,203) + plum, // rgb(221,160,221) + powder_blue, // rgb(176,224,230) + purple, // rgb(128,0,128) + rebecca_purple, // rgb(102,51,153) + red, // rgb(255,0,0) + rosy_brown, // rgb(188,143,143) + royal_blue, // rgb(65,105,225) + saddle_brown, // rgb(139,69,19) + salmon, // rgb(250,128,114) + sandy_brown, // rgb(244,164,96) + sea_green, // rgb(46,139,87) + sea_shell, // rgb(255,245,238) + sienna, // rgb(160,82,45) + silver, // rgb(192,192,192) + sky_blue, // rgb(135,206,235) + slate_blue, // rgb(106,90,205) + slate_gray, // rgb(112,128,144) + snow, // rgb(255,250,250) + spring_green, // rgb(0,255,127) + steel_blue, // rgb(70,130,180) + tan, // rgb(210,180,140) + teal, // rgb(0,128,128) + thistle, // rgb(216,191,216) + tomato, // rgb(255,99,71) + turquoise, // rgb(64,224,208) + violet, // rgb(238,130,238) + wheat, // rgb(245,222,179) + white, // rgb(255,255,255) + white_smoke, // rgb(245,245,245) + yellow, // rgb(255,255,0) + yellow_green // rgb(154,205,50) +}; // enum class color + +namespace mgutility { +template <> struct enum_range { + static constexpr auto min = 0; + static constexpr auto max = 150; +}; +} // namespace mgutility + +TEST_CASE("testing the enum name serialization") { + CHECK(mgutility::enum_name(color::blue) == "blue"); + CHECK(mgutility::enum_name(color::white_smoke) == "white_smoke"); + CHECK(mgutility::enum_name(color::yellow_green) == "yellow_green"); + CHECK(mgutility::enum_name(color::steel_blue) == "steel_blue"); + CHECK(mgutility::enum_name(color::pale_turquoise) == "pale_turquoise"); + CHECK(mgutility::enum_name(color::white_smoke) == "white_smoke"); + CHECK(mgutility::enum_name(color::light_golden_rod_yellow) == + "light_golden_rod_yellow"); + CHECK(mgutility::enum_name(color::dark_olive_green) == "dark_olive_green"); + CHECK(mgutility::enum_name(color::azure) == "azure"); + CHECK(mgutility::enum_name(color::ghost_white) == "ghost_white"); + CHECK(mgutility::enum_name(color::maroon) == "maroon"); + CHECK(mgutility::enum_name(color::navy) == "navy"); + CHECK(mgutility::enum_name(color::spring_green) == "spring_green"); + CHECK(mgutility::enum_name(color::rebecca_purple) == "rebecca_purple"); + CHECK(mgutility::enum_name(color::red) == "red"); + CHECK(mgutility::enum_name(color::tan) == "tan"); + CHECK(mgutility::enum_name(color::mint_cream) == "mint_cream"); + CHECK(mgutility::enum_name(color::light_green) == "light_green"); + CHECK(mgutility::enum_name(color::dodger_blue) == "dodger_blue"); + CHECK(mgutility::enum_name(color::cornflower_blue) == "cornflower_blue"); + CHECK(mgutility::enum_name<-5, 0>(color::thistle) == ""); +} + +TEST_CASE("testing the enum name deserialization") { + CHECK(mgutility::to_enum("blue").value() == color::blue); + CHECK(mgutility::to_enum("white_smoke").value() == color::white_smoke); + CHECK(mgutility::to_enum("yellow_green").value() == + color::yellow_green); + CHECK(mgutility::to_enum("steel_blue").value() == color::steel_blue); + CHECK(mgutility::to_enum("pale_turquoise").value() == + color::pale_turquoise); + CHECK(mgutility::to_enum("white_smoke").value() == color::white_smoke); + CHECK(mgutility::to_enum("light_golden_rod_yellow").value() == + color::light_golden_rod_yellow); + CHECK(mgutility::to_enum("dark_olive_green").value() == + color::dark_olive_green); + CHECK(mgutility::to_enum("azure").value() == color::azure); + CHECK(mgutility::to_enum("ghost_white").value() == color::ghost_white); + CHECK(mgutility::to_enum("maroon").value() == color::maroon); + CHECK(mgutility::to_enum("navy").value() == color::navy); + CHECK(mgutility::to_enum("spring_green").value() == + color::spring_green); + CHECK(mgutility::to_enum("rebecca_purple").value() == + color::rebecca_purple); + CHECK(mgutility::to_enum("red").value() == color::red); + CHECK(mgutility::to_enum("tan").value() == color::tan); + CHECK(mgutility::to_enum("mint_cream").value() == color::mint_cream); + CHECK(mgutility::to_enum("light_green").value() == color::light_green); + CHECK(mgutility::to_enum("dodger_blue").value() == color::dodger_blue); + CHECK(mgutility::to_enum("cornflower_blue").value() == + color::cornflower_blue); + REQUIRE_THROWS(mgutility::to_enum("thistle").value()); +} \ No newline at end of file