├── .github └── workflows │ ├── macos.yml │ ├── ubuntu.yml │ └── windows.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── example ├── CMakeLists.txt ├── basic_example.cpp └── range_example.cpp ├── include └── semver.hpp └── test ├── 3rdparty └── Catch2 │ ├── LICENSE │ └── catch.hpp ├── CMakeLists.txt ├── test.cpp ├── test_operators.cpp ├── test_parse.cpp ├── test_ranges.cpp ├── test_to_string.cpp ├── test_utils.hpp └── test_validation.cpp /.github/workflows/macos.yml: -------------------------------------------------------------------------------- 1 | name: macos 2 | 3 | on: [push, pull_request] 4 | 5 | permissions: read-all 6 | 7 | jobs: 8 | build: 9 | runs-on: ${{ matrix.config.os }} 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | config: 14 | - { os: macos-13 } # https://github.com/actions/virtual-environments/blob/main/images/macos/macos-13-Readme.md#xcode 15 | - { os: macos-14 } # https://github.com/actions/virtual-environments/blob/main/images/macos/macos-14-Readme.md#xcode 16 | 17 | name: "${{ matrix.config.os }}" 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - name: Build Release 22 | run: | 23 | rm -rf build 24 | mkdir build 25 | cd build 26 | cmake .. -DCMAKE_BUILD_TYPE=Release 27 | cmake --build . -j 4 --config Release 28 | ctest --output-on-failure -C Release 29 | 30 | - name: Build Debug 31 | run: | 32 | rm -rf build 33 | mkdir build 34 | cd build 35 | cmake .. -DCMAKE_BUILD_TYPE=Debug 36 | cmake --build . -j 4 --config Debug 37 | ctest --output-on-failure -C Debug 38 | -------------------------------------------------------------------------------- /.github/workflows/ubuntu.yml: -------------------------------------------------------------------------------- 1 | name: ubuntu 2 | 3 | on: [push, pull_request] 4 | 5 | permissions: read-all 6 | 7 | jobs: 8 | ubuntu: 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | compiler: 13 | - { cc: "gcc-9", cxx: "g++-9", os: "ubuntu-20.04" } 14 | - { cc: "gcc-10", cxx: "g++-10", os: "ubuntu-20.04" } 15 | - { cc: "gcc-10", cxx: "g++-10", os: "ubuntu-20.04" } 16 | - { cc: "gcc-11", cxx: "g++-11", os: "ubuntu-20.04" } 17 | - { cc: "gcc-11", cxx: "g++-11", os: "ubuntu-20.04" } 18 | - { cc: "gcc-12", cxx: "g++-12", os: "ubuntu-22.04" } 19 | - { cc: "gcc-13", cxx: "g++-12", os: "ubuntu-22.04" } 20 | - { cc: "gcc-14", cxx: "g++-12", os: "ubuntu-22.04" } 21 | - { cc: "clang-9", cxx: "clang++-9", os: "ubuntu-20.04" } 22 | - { cc: "clang-10", cxx: "clang++-10", os: "ubuntu-20.04" } 23 | - { cc: "clang-11", cxx: "clang++-11", os: "ubuntu-20.04" } 24 | - { cc: "clang-12", cxx: "clang++-12", os: "ubuntu-20.04" } 25 | - { cc: "clang-13", cxx: "clang++-13", os: "ubuntu-20.04" } 26 | - { cc: "clang-14", cxx: "clang++-14", os: "ubuntu-20.04" } 27 | - { cc: "clang-15", cxx: "clang++-15", os: "ubuntu-20.04" } 28 | - { cc: "clang-16", cxx: "clang++-16", os: "ubuntu-20.04" } 29 | 30 | name: "${{ matrix.compiler.cc }}" 31 | runs-on: ${{ matrix.compiler.os }} 32 | steps: 33 | - uses: actions/checkout@v4 34 | 35 | - name: Configure clang 36 | run: | 37 | if [[ "${{ matrix.compiler.cc }}" == "clang"* ]]; then 38 | wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add - 39 | sudo apt-add-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-9 main" 40 | sudo apt-add-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-10 main" 41 | sudo apt-add-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-11 main" 42 | sudo apt-add-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-12 main" 43 | sudo apt-add-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-13 main" 44 | sudo apt-add-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-14 main" 45 | sudo apt-add-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-15 main" 46 | sudo apt-add-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-16 main" 47 | sudo apt-add-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal main" 48 | sudo apt update 49 | sudo apt install ${{ matrix.compiler.cc }} -y 50 | fi 51 | 52 | - name: Configure gcc 53 | run: | 54 | if [[ "${{ matrix.compiler.cc }}" == "gcc"* ]]; then 55 | sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y 56 | sudo apt update 57 | sudo apt install ${{ matrix.compiler.cxx }} -y 58 | fi 59 | 60 | - name: Build Release 61 | run: | 62 | rm -rf build 63 | mkdir build 64 | cd build 65 | cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=${{ matrix.compiler.cxx }} 66 | cmake --build . -j 4 --config Release 67 | ctest --output-on-failure -C Release 68 | 69 | - name: Build Debug 70 | run: | 71 | rm -rf build 72 | mkdir build 73 | cd build 74 | cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=${{ matrix.compiler.cxx }} 75 | cmake --build . -j 4 --config Debug 76 | ctest --output-on-failure -C Debug 77 | -------------------------------------------------------------------------------- /.github/workflows/windows.yml: -------------------------------------------------------------------------------- 1 | name: windows 2 | 3 | on: [push, pull_request] 4 | 5 | permissions: read-all 6 | 7 | jobs: 8 | build: 9 | runs-on: ${{ matrix.config.os }} 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | config: 14 | - { os: windows-2019, vs: "Visual Studio 2019" } # https://github.com/actions/virtual-environments/blob/main/images/win/Windows2019-Readme.md#visual-studio-enterprise-2019 15 | - { os: windows-2022, vs: "Visual Studio 2022" } # https://github.com/actions/virtual-environments/blob/main/images/win/Windows2022-Readme.md#visual-studio-enterprise-2022 16 | 17 | name: "${{ matrix.config.vs }}" 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - name: Build Win32 22 | shell: bash 23 | run: | 24 | rm -rf build 25 | mkdir build 26 | cd build 27 | cmake .. -A Win32 28 | cmake --build . -j 4 --config Release 29 | ctest --output-on-failure -C Release 30 | cmake --build . -j 4 --config Debug 31 | ctest --output-on-failure -C Debug 32 | 33 | - name: Build x64 34 | shell: bash 35 | run: | 36 | rm -rf build 37 | mkdir build 38 | cd build 39 | cmake .. -A x64 40 | cmake --build . -j 4 --config Release 41 | ctest --output-on-failure -C Release 42 | cmake --build . -j 4 --config Debug 43 | ctest --output-on-failure -C Debug 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .vscode/ 3 | .vs/ 4 | 5 | ### C++ gitignore ### 6 | # Prerequisites 7 | *.d 8 | 9 | # Compiled Object files 10 | *.slo 11 | *.lo 12 | *.o 13 | *.obj 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Compiled Dynamic libraries 20 | *.so 21 | *.dylib 22 | *.dll 23 | 24 | # Fortran module files 25 | *.mod 26 | *.smod 27 | 28 | # Compiled Static libraries 29 | *.lai 30 | *.la 31 | *.a 32 | *.lib 33 | 34 | # Executables 35 | *.exe 36 | *.out 37 | *.app 38 | 39 | ### CMake gitignore ### 40 | CMakeLists.txt.user 41 | CMakeCache.txt 42 | CMakeFiles 43 | CMakeScripts 44 | Testing 45 | Makefile 46 | cmake_install.cmake 47 | install_manifest.txt 48 | compile_commands.json 49 | CTestTestfile.cmake 50 | _deps 51 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | 3 | project(semver VERSION "0.3.1" LANGUAGES CXX) 4 | 5 | if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) 6 | set(IS_TOPLEVEL_PROJECT TRUE) 7 | else() 8 | set(IS_TOPLEVEL_PROJECT FALSE) 9 | endif() 10 | 11 | option(SEMVER_OPT_BUILD_EXAMPLES "Build semver examples" ${IS_TOPLEVEL_PROJECT}) 12 | option(SEMVER_OPT_BUILD_TESTS "Build and perform semver tests" ${IS_TOPLEVEL_PROJECT}) 13 | option(SEMVER_OPT_INSTALL "Generate and install semver target" ${IS_TOPLEVEL_PROJECT}) 14 | 15 | if(SEMVER_OPT_BUILD_EXAMPLES) 16 | add_subdirectory(example) 17 | endif() 18 | 19 | if(SEMVER_OPT_BUILD_TESTS) 20 | enable_testing() 21 | add_subdirectory(test) 22 | endif() 23 | 24 | include(GNUInstallDirs) 25 | include(CMakePackageConfigHelpers) 26 | 27 | add_library(${PROJECT_NAME} INTERFACE) 28 | add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) 29 | target_include_directories(${PROJECT_NAME} 30 | INTERFACE 31 | $ 32 | $) 33 | 34 | write_basic_package_version_file(${PROJECT_NAME}ConfigVersion.cmake 35 | VERSION ${PROJECT_VERSION} 36 | COMPATIBILITY AnyNewerVersion) 37 | 38 | if(SEMVER_OPT_INSTALL) 39 | install(TARGETS ${PROJECT_NAME} 40 | EXPORT ${PROJECT_NAME}Config) 41 | 42 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake 43 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}) 44 | 45 | install(EXPORT ${PROJECT_NAME}Config 46 | NAMESPACE ${PROJECT_NAME}:: 47 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}) 48 | 49 | install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/ 50 | DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) 51 | 52 | export(EXPORT ${PROJECT_NAME}Config 53 | NAMESPACE ${PROJECT_NAME}::) 54 | endif() 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 - 2025 Daniil Goncharov 4 | Copyright (c) 2020 - 2025 Alexander Gorbunov 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Github releases](https://img.shields.io/github/release/Neargye/semver.svg)](https://github.com/Neargye/semver/releases) 2 | [![Vcpkg package](https://img.shields.io/badge/Vcpkg-package-blueviolet)](https://github.com/microsoft/vcpkg/tree/master/ports/neargye-semver) 3 | [![Conan package](https://img.shields.io/badge/Conan-package-blueviolet)](https://conan.io/center/recipes/neargye-semver) 4 | [![License](https://img.shields.io/github/license/Neargye/semver.svg)](LICENSE) 5 | 6 | C++ library compare and manipulate versions are available as extensions to the `..-+` format complying with [Semantic Versioning 2.0.0](https://semver.org) 7 | 8 | ## [Features & Examples](example/) 9 | 10 | * Parse 11 | 12 | ```cpp 13 | semver::version v1; 14 | if (semver::parse("1.4.3", v1)) { 15 | const int patch = v1.patch(); // 3 16 | // do something... 17 | } 18 | 19 | semver::version v2; 20 | if (semver::parse("1.2.4-alpha.10")) { 21 | const std::string prerelease_tag = v2.prerelease_tag() // alpha.10 22 | // do something... 23 | } 24 | ``` 25 | 26 | * Сomparison 27 | 28 | ```cpp 29 | assert(v1 != v2); 30 | assert(v1 > v2); 31 | assert(v1 >= v2); 32 | assert(v2 < v1); 33 | assert(v2 <= v1); 34 | ``` 35 | 36 | * Validate 37 | 38 | ```cpp 39 | const bool result = semver::valid("1.2.3-alpha+build"); 40 | assert(result); 41 | ``` 42 | 43 | * Range matching 44 | 45 | ```cpp 46 | semver::range_set range; 47 | if (semver::parse(">=1.0.0 <2.0.0 || >3.2.1", range)) { 48 | semver::version version; 49 | if (semver::parse("1.2.3", version)) { 50 | assert(range.contains(version)); 51 | } 52 | } 53 | ``` 54 | 55 | Check the *examples* folder to see more various usage examples 56 | 57 | ## Integration 58 | 59 | You should add required file [semver.hpp](include/semver.hpp). 60 | 61 | If you are using [vcpkg](https://github.com/Microsoft/vcpkg/) on your project for external dependencies, then you can use the [neargye-semver](https://github.com/microsoft/vcpkg/tree/master/ports/neargye-semver). 62 | 63 | If you are using [Conan](https://www.conan.io/) to manage your dependencies, merely add `neargye-semver/x.y.z` to your conan's requires, where `x.y.z` is the release version you want to use. 64 | 65 | 66 | Alternatively, you can use something like [CPM](https://github.com/TheLartians/CPM) which is based on CMake's `Fetch_Content` module. 67 | 68 | ```cmake 69 | CPMAddPackage( 70 | NAME semver 71 | GITHUB_REPOSITORY Neargye/semver 72 | GIT_TAG x.y.z # Where `x.y.z` is the release version you want to use. 73 | ) 74 | ``` 75 | 76 | ## Compiler compatibility 77 | 78 | * Clang/LLVM >= 5 79 | * MSVC++ >= 14.11 / Visual Studio >= 2017 80 | * Xcode >= 10 81 | * GCC >= 7 82 | 83 | ## Licensed under the [MIT License](LICENSE) 84 | -------------------------------------------------------------------------------- /example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(CheckCXXCompilerFlag) 2 | 3 | if((CMAKE_CXX_COMPILER_ID MATCHES "GNU") OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang")) 4 | set(OPTIONS -Wall -Wextra -pedantic-errors -Werror) 5 | elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") 6 | set(OPTIONS /W4 /WX) 7 | if(HAS_PERMISSIVE_FLAG) 8 | set(OPTIONS ${OPTIONS} /permissive-) 9 | endif() 10 | endif() 11 | 12 | function(make_example target) 13 | add_executable(${target} ${target}.cpp) 14 | set_target_properties(${target} PROPERTIES CXX_EXTENSIONS OFF) 15 | target_compile_features(${target} PRIVATE cxx_std_17) 16 | target_compile_options(${target} PRIVATE ${OPTIONS}) 17 | target_link_libraries(${target} PRIVATE ${CMAKE_PROJECT_NAME}) 18 | endfunction() 19 | 20 | make_example(basic_example) 21 | make_example(range_example) 22 | -------------------------------------------------------------------------------- /example/basic_example.cpp: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT License . 2 | // SPDX-License-Identifier: MIT 3 | // Copyright (c) 2018 - 2025 Daniil Goncharov . 4 | // Copyright (c) 2020 - 2025 Alexander Gorbunov . 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | 24 | #include 25 | #include 26 | #include "semver.hpp" 27 | 28 | int main() { 29 | constexpr std::string_view raw_version = "1.2.3"; 30 | semver::version version; 31 | const auto [ptr, ec] = semver::parse(raw_version, version); 32 | if (ec == std::errc{}) { 33 | assert(ptr == (raw_version.data() + raw_version.size())); 34 | std::cout << version.major() << std::endl; // 1 35 | std::cout << version.minor() << std::endl; // 2 36 | std::cout << version.patch() << std::endl; // 3 37 | } 38 | 39 | semver::version version2; 40 | if (semver::parse("1.2.3-alpha0.1+build", version2)) { 41 | std::cout << version2.major() << std::endl; // 1 42 | std::cout << version2.minor() << std::endl; // 2 43 | std::cout << version2.patch() << std::endl; // 3 44 | std::cout << version2.prerelease_tag() << std::endl; // "alpha0.1" 45 | std::cout << version2.build_metadata() << std::endl; // "build" 46 | } 47 | 48 | assert(version > version2); 49 | assert(version2 < version); 50 | 51 | // use 64 bit integer for numbers 52 | semver::version version3; 53 | if (semver::parse("0.0.999999999999", version3)) { 54 | std::cout << version3.major() << std::endl; // 0 55 | std::cout << version3.minor() << std::endl; // 0 56 | std::cout << version3.patch() << std::endl; // 999999999999 57 | } 58 | 59 | const bool result = semver::valid("0.0.1-beta"); 60 | std::cout << std::boolalpha << result << std::endl; // true 61 | } 62 | -------------------------------------------------------------------------------- /example/range_example.cpp: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT License . 2 | // SPDX-License-Identifier: MIT 3 | // Copyright (c) 2020 - 2025 Daniil Goncharov . 4 | // Copyright (c) 2020 - 2025 Alexander Gorbunov . 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | 24 | #include 25 | #include 26 | #include "semver.hpp" 27 | 28 | int main() { 29 | std::cout << std::boolalpha; 30 | 31 | constexpr std::string_view raw_range = ">=1.2.9 <2.0.0"; 32 | semver::range_set range; 33 | const auto [ptr, ec] = semver::parse(raw_range, range); 34 | if (ec == std::errc{}) { 35 | assert(ptr == (raw_range.data() + raw_range.size())); 36 | 37 | semver::version version; 38 | if (semver::parse("1.3.0", version)) { 39 | const bool result = range.contains(version); 40 | std::cout << result << std::endl; // true 41 | } 42 | } 43 | 44 | semver::range_set range2; 45 | if (semver::parse(">=1.0.0 <=2.0.0 || >=3.0.0", range2)) { 46 | semver::version version; 47 | if (semver::parse("3.5.0", version)) { 48 | const bool result = range2.contains(version); 49 | std::cout << result << std::endl; // true 50 | } 51 | } 52 | 53 | semver::range_set range3; 54 | if (semver::parse(">1.2.3-alpha.3", range3)) { 55 | semver::version version; 56 | if (semver::parse("1.2.3-alpha.7", version)) { 57 | const bool result = range3.contains(version); 58 | std::cout << result << std::endl; // true 59 | } 60 | 61 | if (semver::parse("3.4.5-alpha.9", version)) { 62 | // By default, we exclude prerelease tag from comparison. 63 | bool result = range3.contains(version); 64 | std::cout << result << std::endl; // false 65 | 66 | // But we can suppress this behavior by passing semver::range::option::include_prerelease. 67 | // For details see: https://github.com/npm/node-semver#prerelease-tags 68 | result = range3.contains(version, semver::version_compare_option::include_prerelease); 69 | std::cout << result << std::endl; // true 70 | } 71 | } 72 | 73 | return 0; 74 | } 75 | -------------------------------------------------------------------------------- /include/semver.hpp: -------------------------------------------------------------------------------- 1 | // _____ _ _ 2 | // / ____| | | (_) 3 | // | (___ ___ _ __ ___ __ _ _ __ | |_ _ ___ 4 | // \___ \ / _ \ '_ ` _ \ / _` | '_ \| __| |/ __| 5 | // ____) | __/ | | | | | (_| | | | | |_| | (__ 6 | // |_____/ \___|_| |_| |_|\__,_|_| |_|\__|_|\___| 7 | // __ __ _ _ _____ 8 | // \ \ / / (_) (_) / ____|_ _ 9 | // \ \ / /__ _ __ ___ _ ___ _ __ _ _ __ __ _ | | _| |_ _| |_ 10 | // \ \/ / _ \ '__/ __| |/ _ \| '_ \| | '_ \ / _` | | | |_ _|_ _| 11 | // \ / __/ | \__ \ | (_) | | | | | | | | (_| | | |____|_| |_| 12 | // \/ \___|_| |___/_|\___/|_| |_|_|_| |_|\__, | \_____| 13 | // https://github.com/Neargye/semver __/ | 14 | // version 1.0.0 |___/ 15 | // 16 | // Licensed under the MIT License . 17 | // SPDX-License-Identifier: MIT 18 | // Copyright (c) 2018 - 2025 Daniil Goncharov . 19 | // Copyright (c) 2020 - 2025 Alexander Gorbunov . 20 | // 21 | // Permission is hereby granted, free of charge, to any person obtaining a copy 22 | // of this software and associated documentation files (the "Software"), to deal 23 | // in the Software without restriction, including without limitation the rights 24 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 25 | // copies of the Software, and to permit persons to whom the Software is 26 | // furnished to do so, subject to the following conditions: 27 | // 28 | // The above copyright notice and this permission notice shall be included in all 29 | // copies or substantial portions of the Software. 30 | // 31 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 32 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 34 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 35 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 36 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 37 | // SOFTWARE. 38 | 39 | #ifndef NEARGYE_SEMANTIC_VERSIONING_HPP 40 | #define NEARGYE_SEMANTIC_VERSIONING_HPP 41 | 42 | #define SEMVER_VERSION_MAJOR 1 43 | #define SEMVER_VERSION_MINOR 0 44 | #define SEMVER_VERSION_PATCH 0 45 | 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | #if __has_include() 57 | #include 58 | #else 59 | #include 60 | #endif 61 | 62 | #if defined(SEMVER_CONFIG_FILE) 63 | #include SEMVER_CONFIG_FILE 64 | #endif 65 | 66 | #if defined(__clang__) 67 | # pragma clang diagnostic push 68 | # pragma clang diagnostic ignored "-Wmissing-braces" // Ignore warning: suggest braces around initialization of subobject 'return {first, std::errc::invalid_argument};'. 69 | #endif 70 | 71 | #if __cpp_impl_three_way_comparison >= 201907L 72 | #include 73 | #endif 74 | 75 | #if __cpp_lib_constexpr_string >= 201907L 76 | #define SEMVER_CONSTEXPR constexpr 77 | #else 78 | #define SEMVER_CONSTEXPR inline 79 | #endif 80 | 81 | namespace semver { 82 | 83 | namespace detail { 84 | 85 | template 86 | struct resize_uninitialized { 87 | SEMVER_CONSTEXPR static auto resize(T& str, std::size_t size) -> std::void_t { 88 | str.resize(size); 89 | } 90 | }; 91 | 92 | template 93 | struct resize_uninitialized().__resize_default_init(42))>> { 94 | SEMVER_CONSTEXPR static void resize(T& str, std::size_t size) { 95 | str.__resize_default_init(size); 96 | } 97 | }; 98 | 99 | template 100 | SEMVER_CONSTEXPR std::size_t length(Int n) noexcept { 101 | std::size_t digits = 0; 102 | do { 103 | digits++; 104 | n /= 10; 105 | } while (n != 0); 106 | return digits; 107 | } 108 | 109 | template 110 | SEMVER_CONSTEXPR OutputIt to_chars(OutputIt dest, Int n) noexcept { 111 | do { 112 | *(--dest) = static_cast('0' + (n % 10)); 113 | n /= 10; 114 | } while (n != 0); 115 | return dest; 116 | } 117 | 118 | enum struct prerelease_identifier_type { 119 | numeric, 120 | alphanumeric 121 | }; 122 | 123 | struct prerelease_identifier { 124 | prerelease_identifier_type type; 125 | std::string identifier; 126 | }; 127 | 128 | class version_parser; 129 | class prerelease_comparator; 130 | } 131 | 132 | template 133 | class version { 134 | friend class detail::version_parser; 135 | friend class detail::prerelease_comparator; 136 | 137 | public: 138 | SEMVER_CONSTEXPR version() = default; // https://semver.org/#how-should-i-deal-with-revisions-in-the-0yz-initial-development-phase 139 | SEMVER_CONSTEXPR version(const version&) = default; 140 | SEMVER_CONSTEXPR version(version&&) = default; 141 | SEMVER_CONSTEXPR ~version() = default; 142 | 143 | SEMVER_CONSTEXPR version& operator=(const version&) = default; 144 | SEMVER_CONSTEXPR version& operator=(version&&) = default; 145 | 146 | SEMVER_CONSTEXPR I1 major() const noexcept { return major_; } 147 | SEMVER_CONSTEXPR I2 minor() const noexcept { return minor_; } 148 | SEMVER_CONSTEXPR I3 patch() const noexcept { return patch_; } 149 | 150 | SEMVER_CONSTEXPR const std::string& prerelease_tag() const { return prerelease_tag_; } 151 | SEMVER_CONSTEXPR const std::string& build_metadata() const { return build_metadata_; } 152 | 153 | SEMVER_CONSTEXPR std::string to_string() const; 154 | 155 | private: 156 | I1 major_ = 0; 157 | I2 minor_ = 1; 158 | I3 patch_ = 0; 159 | std::string prerelease_tag_; 160 | std::string build_metadata_; 161 | 162 | std::vector prerelease_identifiers; 163 | 164 | SEMVER_CONSTEXPR std::size_t length() const noexcept { 165 | return detail::length(major_) + detail::length(minor_) + detail::length(patch_) + 2 166 | + (prerelease_tag_.empty() ? 0 : prerelease_tag_.length() + 1) 167 | + (build_metadata_.empty() ? 0 : build_metadata_.length() + 1); 168 | } 169 | 170 | SEMVER_CONSTEXPR void clear() noexcept { 171 | major_ = 0; 172 | minor_ = 1; 173 | patch_ = 0; 174 | 175 | prerelease_tag_.clear(); 176 | prerelease_identifiers.clear(); 177 | build_metadata_.clear(); 178 | } 179 | }; 180 | 181 | template 182 | SEMVER_CONSTEXPR std::string version::to_string() const { 183 | std::string result; 184 | detail::resize_uninitialized{}.resize(result, length()); 185 | 186 | auto it = result.end(); 187 | if (!build_metadata_.empty()) { 188 | it = std::copy_backward(build_metadata_.begin(), build_metadata_.end(), it); 189 | *(--it) = '+'; 190 | } 191 | 192 | if (!prerelease_tag_.empty()) { 193 | it = std::copy_backward(prerelease_tag_.begin(), prerelease_tag_.end(), it); 194 | *(--it) = '-'; 195 | } 196 | 197 | it = detail::to_chars(it, patch_); 198 | *(--it) = '.'; 199 | 200 | it = detail::to_chars(it, minor_); 201 | *(--it) = '.'; 202 | 203 | it = detail::to_chars(it, major_); 204 | 205 | return result; 206 | } 207 | 208 | #if __has_include() 209 | struct from_chars_result : std::from_chars_result { 210 | [[nodiscard]] SEMVER_CONSTEXPR operator bool() const noexcept { return ec == std::errc{}; } 211 | }; 212 | #else 213 | struct from_chars_result { 214 | const char* ptr; 215 | std::errc ec; 216 | 217 | [[nodiscard]] SEMVER_CONSTEXPR operator bool() const noexcept { return ec == std::errc{}; } 218 | }; 219 | #endif 220 | 221 | enum class version_compare_option : std::uint8_t { 222 | exclude_prerelease, 223 | include_prerelease 224 | }; 225 | 226 | namespace detail { 227 | 228 | SEMVER_CONSTEXPR from_chars_result success(const char* ptr) noexcept { 229 | return from_chars_result{ ptr, std::errc{} }; 230 | } 231 | 232 | SEMVER_CONSTEXPR from_chars_result failure(const char* ptr, std::errc error_code = std::errc::invalid_argument) noexcept { 233 | return from_chars_result{ ptr, error_code }; 234 | } 235 | 236 | SEMVER_CONSTEXPR bool is_digit(char c) noexcept { 237 | return c >= '0' && c <= '9'; 238 | } 239 | 240 | SEMVER_CONSTEXPR bool is_letter(char c) noexcept { 241 | return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); 242 | } 243 | 244 | SEMVER_CONSTEXPR std::uint8_t to_digit(char c) noexcept { 245 | return static_cast(c - '0'); 246 | } 247 | 248 | SEMVER_CONSTEXPR char to_char(int i) noexcept { 249 | return '0' + (char)i; 250 | } 251 | 252 | template 253 | SEMVER_CONSTEXPR bool cmp_less(T t, U u) noexcept 254 | { 255 | if constexpr (std::is_signed_v == std::is_signed_v) 256 | return t < u; 257 | else if constexpr (std::is_signed_v) 258 | return t < 0 || std::make_unsigned_t(t) < u; 259 | else 260 | return u >= 0 && t < std::make_unsigned_t(u); 261 | } 262 | 263 | template 264 | SEMVER_CONSTEXPR bool cmp_less_equal(T t, U u) noexcept 265 | { 266 | return !cmp_less(u, t); 267 | } 268 | 269 | template 270 | SEMVER_CONSTEXPR bool cmp_greater_equal(T t, U u) noexcept 271 | { 272 | return !cmp_less(t, u); 273 | } 274 | 275 | template 276 | SEMVER_CONSTEXPR bool number_in_range(T t) noexcept { 277 | return cmp_greater_equal(t, std::numeric_limits::min()) && cmp_less_equal(t, std::numeric_limits::max()); 278 | } 279 | 280 | SEMVER_CONSTEXPR int compare(std::string_view lhs, std::string_view rhs) { 281 | #if defined(_MSC_VER) && _MSC_VER < 1920 && !defined(__clang__) 282 | // https://developercommunity.visualstudio.com/content/problem/360432/vs20178-regression-c-failed-in-test.html 283 | // https://developercommunity.visualstudio.com/content/problem/232218/c-SEMVER_CONSTEXPR-string-view.html 284 | constexpr bool workaround = true; 285 | #else 286 | constexpr bool workaround = false; 287 | #endif 288 | 289 | if constexpr (workaround) { 290 | const auto size = std::min(lhs.size(), rhs.size()); 291 | for (std::size_t i = 0; i < size; ++i) { 292 | if (lhs[i] < rhs[i]) { 293 | return -1; 294 | } else if (lhs[i] > rhs[i]) { 295 | return 1; 296 | } 297 | } 298 | 299 | return static_cast(lhs.size() - rhs.size()); 300 | } else { 301 | return lhs.compare(rhs); 302 | } 303 | } 304 | 305 | SEMVER_CONSTEXPR int compare_numerically(std::string_view lhs, std::string_view rhs) { 306 | // assume that strings don't have leading zeros (we've already checked it at parsing stage). 307 | 308 | if (lhs.size() != rhs.size()) { 309 | return static_cast(lhs.size() - rhs.size()); 310 | } 311 | 312 | for (std::size_t i = 0; i < lhs.size(); ++i) { 313 | int a = lhs[i] - '0'; 314 | int b = rhs[i] - '0'; 315 | if (a != b) { 316 | return a - b; 317 | } 318 | } 319 | 320 | return 0; 321 | } 322 | 323 | enum class token_type : std::uint8_t { 324 | eol, 325 | space, 326 | dot, 327 | plus, 328 | hyphen, 329 | letter, 330 | digit, 331 | range_operator, 332 | logical_or 333 | }; 334 | 335 | enum class range_operator : std::uint8_t { 336 | less, 337 | less_or_equal, 338 | greater, 339 | greater_or_equal, 340 | equal 341 | }; 342 | 343 | struct token { 344 | using value_t = std::variant; 345 | token_type type; 346 | value_t value; 347 | const char* lexeme; 348 | }; 349 | 350 | class token_stream { 351 | public: 352 | SEMVER_CONSTEXPR token_stream() = default; 353 | SEMVER_CONSTEXPR explicit token_stream(std::vector tokens) noexcept : tokens(std::move(tokens)) {} 354 | 355 | SEMVER_CONSTEXPR void push(const token& token) noexcept { 356 | tokens.push_back(token); 357 | } 358 | 359 | SEMVER_CONSTEXPR token advance() noexcept { 360 | const token token = get(current); 361 | ++current; 362 | return token; 363 | } 364 | 365 | SEMVER_CONSTEXPR token peek(std::size_t k = 0) const noexcept { 366 | return get(current + k); 367 | } 368 | 369 | SEMVER_CONSTEXPR token previous() const noexcept { 370 | return get(current - 1); 371 | } 372 | 373 | SEMVER_CONSTEXPR bool advanceIfMatch(token& token, token_type type) noexcept { 374 | if (get(current).type != type) { 375 | return false; 376 | } 377 | 378 | token = advance(); 379 | return true; 380 | } 381 | 382 | SEMVER_CONSTEXPR bool advanceIfMatch(token_type type) noexcept { 383 | token token; 384 | return advanceIfMatch(token, type); 385 | } 386 | 387 | SEMVER_CONSTEXPR bool consume(token_type type) noexcept { 388 | return advance().type == type; 389 | } 390 | 391 | SEMVER_CONSTEXPR bool check(token_type type) const noexcept { 392 | return peek().type == type; 393 | } 394 | 395 | private: 396 | std::size_t current = 0; 397 | std::vector tokens; 398 | 399 | SEMVER_CONSTEXPR token get(std::size_t i) const noexcept { 400 | return tokens[i]; 401 | } 402 | }; 403 | 404 | class lexer { 405 | public: 406 | explicit SEMVER_CONSTEXPR lexer(std::string_view text) noexcept : text_{text}, current_pos_{0} {} 407 | 408 | SEMVER_CONSTEXPR from_chars_result scan_tokens(token_stream& token_stream) noexcept { 409 | from_chars_result result{ text_.data(), std::errc{} }; 410 | 411 | while (!is_eol()) { 412 | result = scan_token(token_stream); 413 | if (!result) { 414 | return result; 415 | } 416 | } 417 | 418 | token_stream.push({ token_type::eol, {}, text_.data() + text_.size() }); 419 | 420 | return result; 421 | } 422 | 423 | private: 424 | std::string_view text_; 425 | std::size_t current_pos_; 426 | 427 | SEMVER_CONSTEXPR from_chars_result scan_token(token_stream& stream) noexcept { 428 | const char c = advance(); 429 | 430 | switch (c) { 431 | case ' ': 432 | add_token(stream, token_type::space); 433 | break; 434 | case '.': 435 | add_token(stream, token_type::dot); 436 | break; 437 | case '-': 438 | add_token(stream, token_type::hyphen); 439 | break; 440 | case '+': 441 | add_token(stream, token_type::plus); 442 | break; 443 | case '|': 444 | if (advanceIfMatch('|')) { 445 | add_token(stream, token_type::logical_or); 446 | break; 447 | } 448 | return failure(get_prev_symbol()); 449 | case '<': 450 | add_token(stream, token_type::range_operator, advanceIfMatch('=') ? range_operator::less_or_equal : range_operator::less); 451 | break; 452 | case '>': 453 | add_token(stream, token_type::range_operator, advanceIfMatch('=') ? range_operator::greater_or_equal : range_operator::greater); 454 | break; 455 | case '=': 456 | add_token(stream, token_type::range_operator, range_operator::equal); 457 | break; 458 | default: 459 | if (is_digit(c)) { 460 | add_token(stream, token_type::digit, to_digit(c)); 461 | break; 462 | } 463 | else if (is_letter(c)) { 464 | add_token(stream, token_type::letter, c); 465 | break; 466 | } 467 | return failure(get_prev_symbol()); 468 | } 469 | 470 | return success(get_prev_symbol()); 471 | } 472 | 473 | SEMVER_CONSTEXPR void add_token(token_stream& stream, token_type type, token::value_t value = {}) noexcept { 474 | const char* lexeme = get_prev_symbol(); 475 | stream.push({ type, value, lexeme}); 476 | } 477 | 478 | SEMVER_CONSTEXPR char advance() noexcept { 479 | char c = text_[current_pos_]; 480 | current_pos_ += 1; 481 | return c; 482 | } 483 | 484 | SEMVER_CONSTEXPR bool advanceIfMatch(char c) noexcept { 485 | if (is_eol()) { 486 | return false; 487 | } 488 | 489 | if (text_[current_pos_] != c) { 490 | return false; 491 | } 492 | 493 | current_pos_ += 1; 494 | 495 | return true; 496 | } 497 | 498 | SEMVER_CONSTEXPR const char* get_prev_symbol() const noexcept { 499 | return text_.data() + current_pos_ - 1; 500 | } 501 | 502 | SEMVER_CONSTEXPR bool is_eol() const noexcept { return current_pos_ >= text_.size(); } 503 | }; 504 | 505 | class prerelease_comparator { 506 | public: 507 | template 508 | [[nodiscard]] SEMVER_CONSTEXPR int compare(const version& lhs, const version& rhs) const noexcept { 509 | if (lhs.prerelease_identifiers.empty() != rhs.prerelease_identifiers.empty()) { 510 | return static_cast(rhs.prerelease_identifiers.size()) - static_cast(lhs.prerelease_identifiers.size()); 511 | } 512 | 513 | const std::size_t count = std::min(lhs.prerelease_identifiers.size(), rhs.prerelease_identifiers.size()); 514 | 515 | for (std::size_t i = 0; i < count; ++i) { 516 | const int compare_result = compare_identifier(lhs.prerelease_identifiers[i], rhs.prerelease_identifiers[i]); 517 | if (compare_result != 0) { 518 | return compare_result; 519 | } 520 | } 521 | 522 | return static_cast(lhs.prerelease_identifiers.size()) - static_cast(rhs.prerelease_identifiers.size()); 523 | } 524 | 525 | private: 526 | [[nodiscard]] SEMVER_CONSTEXPR int compare_identifier(const prerelease_identifier& lhs, const prerelease_identifier& rhs) const noexcept { 527 | if (lhs.type == prerelease_identifier_type::numeric && rhs.type == prerelease_identifier_type::numeric) { 528 | return compare_numerically(lhs.identifier, rhs.identifier); 529 | } else if (lhs.type == prerelease_identifier_type::alphanumeric && rhs.type == prerelease_identifier_type::alphanumeric) { 530 | return detail::compare(lhs.identifier, rhs.identifier); 531 | } 532 | 533 | return lhs.type == prerelease_identifier_type::alphanumeric ? 1 : -1; 534 | } 535 | }; 536 | 537 | class version_parser { 538 | public: 539 | SEMVER_CONSTEXPR explicit version_parser(token_stream& stream) : stream{stream} { 540 | } 541 | 542 | template 543 | SEMVER_CONSTEXPR from_chars_result parse(version& out) noexcept { 544 | out.clear(); 545 | 546 | from_chars_result result = parse_number(out.major_); 547 | if (!result) { 548 | return result; 549 | } 550 | 551 | if (!stream.consume(token_type::dot)) { 552 | return failure(stream.previous().lexeme); 553 | } 554 | 555 | result = parse_number(out.minor_); 556 | if (!result) { 557 | return result; 558 | } 559 | 560 | if (!stream.consume(token_type::dot)) { 561 | return failure(stream.previous().lexeme); 562 | } 563 | 564 | result = parse_number(out.patch_); 565 | if (!result) { 566 | return result; 567 | } 568 | 569 | if (stream.advanceIfMatch(token_type::hyphen)) { 570 | result = parse_prerelease_tag(out.prerelease_tag_, out.prerelease_identifiers); 571 | if (!result) { 572 | return result; 573 | } 574 | } 575 | 576 | if (stream.advanceIfMatch(token_type::plus)) { 577 | result = parse_build_metadata(out.build_metadata_); 578 | if (!result) { 579 | return result; 580 | } 581 | } 582 | 583 | return result; 584 | } 585 | 586 | 587 | private: 588 | token_stream& stream; 589 | 590 | template 591 | SEMVER_CONSTEXPR from_chars_result parse_number(Int& out) { 592 | token token = stream.advance(); 593 | 594 | if (!is_digit(token)) { 595 | return failure(token.lexeme); 596 | } 597 | 598 | const auto first_digit = std::get(token.value); 599 | std::uint64_t result = first_digit; 600 | 601 | if (first_digit == 0) { 602 | out = static_cast(result); 603 | return success(stream.peek().lexeme); 604 | } 605 | 606 | while (stream.advanceIfMatch(token, token_type::digit)) { 607 | result = result * 10 + std::get(token.value); 608 | } 609 | 610 | if (detail::number_in_range(result)) { 611 | out = static_cast(result); 612 | return success(stream.peek().lexeme); 613 | } 614 | 615 | return failure(token.lexeme, std::errc::result_out_of_range); 616 | } 617 | 618 | SEMVER_CONSTEXPR from_chars_result parse_prerelease_tag(std::string& out, std::vector& out_identifiers) { 619 | std::string result; 620 | 621 | do { 622 | if (!result.empty()) { 623 | result.push_back('.'); 624 | } 625 | 626 | std::string identifier; 627 | if (const auto res = parse_prerelease_identifier(identifier); !res) { 628 | return res; 629 | } 630 | 631 | result.append(identifier); 632 | out_identifiers.push_back(make_prerelease_identifier(identifier)); 633 | 634 | } while (stream.advanceIfMatch(token_type::dot)); 635 | 636 | out = result; 637 | return success(stream.peek().lexeme); 638 | } 639 | 640 | SEMVER_CONSTEXPR from_chars_result parse_build_metadata(std::string& out) { 641 | std::string result; 642 | 643 | do { 644 | if (!result.empty()) { 645 | result.push_back('.'); 646 | } 647 | 648 | std::string identifier; 649 | if (const auto res = parse_build_identifier(identifier); !res) { 650 | return res; 651 | } 652 | 653 | result.append(identifier); 654 | } while (stream.advanceIfMatch(token_type::dot)); 655 | 656 | out = result; 657 | return success(stream.peek().lexeme); 658 | } 659 | 660 | SEMVER_CONSTEXPR from_chars_result parse_prerelease_identifier(std::string& out) { 661 | std::string result; 662 | token token = stream.advance(); 663 | 664 | do { 665 | switch (token.type) { 666 | case token_type::hyphen: 667 | result.push_back('-'); 668 | break; 669 | case token_type::letter: 670 | result.push_back(std::get(token.value)); 671 | break; 672 | case token_type::digit: 673 | { 674 | const auto digit = std::get(token.value); 675 | 676 | // numerical prerelease identifier doesn't allow leading zero 677 | // 1.2.3-1.alpha is valid, 678 | // 1.2.3-01b is valid as well, but 679 | // 1.2.3-01.alpha is not valid 680 | 681 | if (is_leading_zero(digit)) { 682 | return failure(token.lexeme); 683 | } 684 | 685 | result.push_back(to_char(digit)); 686 | break; 687 | } 688 | default: 689 | return failure(token.lexeme); 690 | } 691 | } while (stream.advanceIfMatch(token, token_type::hyphen) || stream.advanceIfMatch(token, token_type::letter) || stream.advanceIfMatch(token, token_type::digit)); 692 | 693 | out = result; 694 | return success(stream.peek().lexeme); 695 | } 696 | 697 | SEMVER_CONSTEXPR detail::prerelease_identifier make_prerelease_identifier(const std::string& identifier) { 698 | auto type = detail::prerelease_identifier_type::numeric; 699 | for (char c : identifier) { 700 | if (c == '-' || detail::is_letter(c)) { 701 | type = detail::prerelease_identifier_type::alphanumeric; 702 | break; 703 | } 704 | } 705 | return detail::prerelease_identifier{ type, identifier }; 706 | } 707 | 708 | SEMVER_CONSTEXPR from_chars_result parse_build_identifier(std::string& out) { 709 | std::string result; 710 | token token = stream.advance(); 711 | 712 | do { 713 | switch (token.type) { 714 | case token_type::hyphen: 715 | result.push_back('-'); 716 | break; 717 | case token_type::letter: 718 | result.push_back(std::get(token.value)); 719 | break; 720 | case token_type::digit: 721 | { 722 | const auto digit = std::get(token.value); 723 | result.push_back(to_char(digit)); 724 | break; 725 | } 726 | default: 727 | return failure(token.lexeme); 728 | } 729 | } while (stream.advanceIfMatch(token, token_type::hyphen) || stream.advanceIfMatch(token, token_type::letter) || stream.advanceIfMatch(token, token_type::digit)); 730 | 731 | out = result; 732 | return success(stream.peek().lexeme); 733 | } 734 | 735 | SEMVER_CONSTEXPR bool is_leading_zero(int digit) noexcept { 736 | if (digit != 0) { 737 | return false; 738 | } 739 | 740 | int k = 0; 741 | int alpha_numerics = 0; 742 | int digits = 0; 743 | 744 | while (true) { 745 | const token token = stream.peek(k); 746 | 747 | if (!is_alphanumeric(token)) { 748 | break; 749 | } 750 | 751 | ++alpha_numerics; 752 | 753 | if (is_digit(token)) { 754 | ++digits; 755 | } 756 | 757 | ++k; 758 | } 759 | 760 | return digits > 0 && digits == alpha_numerics; 761 | } 762 | 763 | SEMVER_CONSTEXPR bool is_digit(const token& token) const noexcept { 764 | return token.type == token_type::digit; 765 | } 766 | 767 | SEMVER_CONSTEXPR bool is_eol(const token& token) const noexcept { 768 | return token.type == token_type::eol; 769 | } 770 | 771 | SEMVER_CONSTEXPR bool is_alphanumeric(const token& token) const noexcept { 772 | return token.type == token_type::hyphen || token.type == token_type::letter || token.type == token_type::digit; 773 | } 774 | }; 775 | 776 | template 777 | SEMVER_CONSTEXPR int compare_prerelease(const version& lhs, const version& rhs) noexcept { 778 | return prerelease_comparator{}.compare(lhs, rhs); 779 | } 780 | 781 | template 782 | SEMVER_CONSTEXPR int compare_parsed(const version& lhs, const version& rhs, version_compare_option compare_option) { 783 | int result = lhs.major() - rhs.major(); 784 | if (result != 0) { 785 | return result; 786 | } 787 | 788 | result = lhs.minor() - rhs.minor(); 789 | if (result != 0) { 790 | return result; 791 | } 792 | 793 | result = lhs.patch() - rhs.patch(); 794 | if (result != 0) { 795 | return result; 796 | } 797 | 798 | if (compare_option == version_compare_option::include_prerelease) { 799 | result = detail::compare_prerelease(lhs, rhs); 800 | } 801 | 802 | return result; 803 | } 804 | 805 | template 806 | SEMVER_CONSTEXPR from_chars_result parse(std::string_view str, version& out) { 807 | token_stream token_stream; 808 | from_chars_result result = lexer{ str }.scan_tokens(token_stream); 809 | if (!result) { 810 | return result; 811 | } 812 | 813 | result = version_parser{ token_stream }.parse(out); 814 | if (!result) { 815 | return result; 816 | } 817 | 818 | if (!token_stream.consume(token_type::eol)) { 819 | return failure(token_stream.previous().lexeme); 820 | } 821 | 822 | return success(token_stream.previous().lexeme); 823 | } 824 | 825 | } // namespace semver::detail 826 | 827 | template 828 | [[nodiscard]] SEMVER_CONSTEXPR bool operator==(const version& lhs, const version& rhs) noexcept { 829 | return detail::compare_parsed(lhs, rhs, version_compare_option::include_prerelease) == 0; 830 | } 831 | 832 | template 833 | [[nodiscard]] SEMVER_CONSTEXPR bool operator!=(const version& lhs, const version& rhs) noexcept { 834 | return detail::compare_parsed(lhs, rhs, version_compare_option::include_prerelease) != 0; 835 | } 836 | 837 | template 838 | [[nodiscard]] SEMVER_CONSTEXPR bool operator>(const version& lhs, const version& rhs) noexcept { 839 | return detail::compare_parsed(lhs, rhs, version_compare_option::include_prerelease) > 0; 840 | } 841 | 842 | template 843 | [[nodiscard]] SEMVER_CONSTEXPR bool operator>=(const version& lhs, const version& rhs) noexcept { 844 | return detail::compare_parsed(lhs, rhs, version_compare_option::include_prerelease) >= 0; 845 | } 846 | 847 | template 848 | [[nodiscard]] SEMVER_CONSTEXPR bool operator<(const version& lhs, const version& rhs) noexcept { 849 | return detail::compare_parsed(lhs, rhs, version_compare_option::include_prerelease) < 0; 850 | } 851 | 852 | template 853 | [[nodiscard]] SEMVER_CONSTEXPR bool operator<=(const version& lhs, const version& rhs) noexcept { 854 | return detail::compare_parsed(lhs, rhs, version_compare_option::include_prerelease) <= 0; 855 | } 856 | 857 | #if __cpp_impl_three_way_comparison >= 201907L 858 | template 859 | [[nodiscard]] SEMVER_CONSTEXPR std::strong_ordering operator<=>(const version& lhs, const version& rhs) { 860 | int compare = detail::compare_parsed(lhs, rhs, version_compare_option::include_prerelease); 861 | if (compare == 0) 862 | return std::strong_ordering::equal; 863 | if (compare > 0) 864 | return std::strong_ordering::greater; 865 | return std::strong_ordering::less; 866 | } 867 | #endif 868 | 869 | template 870 | SEMVER_CONSTEXPR from_chars_result parse(std::string_view str, version& output) { 871 | return detail::parse(str, output); 872 | } 873 | 874 | SEMVER_CONSTEXPR bool valid(std::string_view str) { 875 | version v{}; 876 | return detail::parse(str, v); 877 | } 878 | 879 | namespace detail { 880 | template 881 | class range_comparator { 882 | public: 883 | SEMVER_CONSTEXPR range_comparator(const version& v, range_operator op) noexcept : v(v), op(op) {} 884 | 885 | SEMVER_CONSTEXPR bool contains(const version& other) const noexcept { 886 | switch (op) { 887 | case range_operator::less: 888 | return detail::compare_parsed(other, v, version_compare_option::include_prerelease) < 0; 889 | case range_operator::less_or_equal: 890 | return detail::compare_parsed(other, v, version_compare_option::include_prerelease) <= 0; 891 | case range_operator::greater: 892 | return detail::compare_parsed(other, v, version_compare_option::include_prerelease) > 0; 893 | case range_operator::greater_or_equal: 894 | return detail::compare_parsed(other, v, version_compare_option::include_prerelease) >= 0; 895 | case range_operator::equal: 896 | return detail::compare_parsed(other, v, version_compare_option::include_prerelease) == 0; 897 | } 898 | return false; 899 | } 900 | 901 | SEMVER_CONSTEXPR const version& get_version() const noexcept { return v; } 902 | 903 | private: 904 | version v; 905 | range_operator op; 906 | }; 907 | 908 | class range_parser; 909 | 910 | template 911 | class range { 912 | public: 913 | friend class detail::range_parser; 914 | 915 | SEMVER_CONSTEXPR bool contains(const version& v, version_compare_option option) const noexcept { 916 | if (option == version_compare_option::exclude_prerelease) { 917 | if (!match_at_least_one_comparator_with_prerelease(v)) { 918 | return false; 919 | } 920 | } 921 | 922 | return std::all_of(ranges_comparators.begin(), ranges_comparators.end(), [&](const auto& ranges_comparator) { 923 | return ranges_comparator.contains(v); 924 | }); 925 | } 926 | private: 927 | std::vector> ranges_comparators; 928 | 929 | SEMVER_CONSTEXPR bool match_at_least_one_comparator_with_prerelease(const version& v) const noexcept { 930 | if (v.prerelease_tag().empty()) { 931 | return true; 932 | } 933 | 934 | return std::any_of(ranges_comparators.begin(), ranges_comparators.end(), [&](const auto& ranges_comparator) { 935 | const bool has_prerelease = !ranges_comparator.get_version().prerelease_tag().empty(); 936 | const bool equal_without_prerelease = detail::compare_parsed(v, ranges_comparator.get_version(), version_compare_option::exclude_prerelease) == 0; 937 | return has_prerelease && equal_without_prerelease; 938 | }); 939 | } 940 | }; 941 | } 942 | 943 | template 944 | class range_set { 945 | public: 946 | friend class detail::range_parser; 947 | 948 | SEMVER_CONSTEXPR bool contains(const version& v, version_compare_option option = version_compare_option::exclude_prerelease) noexcept { 949 | return std::any_of(ranges.begin(), ranges.end(), [&](const auto& range) { 950 | return range.contains(v, option); 951 | }); 952 | } 953 | 954 | private: 955 | std::vector> ranges; 956 | }; 957 | 958 | namespace detail { 959 | class range_parser { 960 | public: 961 | SEMVER_CONSTEXPR explicit range_parser(token_stream stream) noexcept : stream(std::move(stream)) {} 962 | 963 | template 964 | SEMVER_CONSTEXPR from_chars_result parse(range_set& out) noexcept { 965 | std::vector> ranges; 966 | 967 | do { 968 | 969 | detail::range range; 970 | if (const auto res = parse_range(range); !res) { 971 | return res; 972 | } 973 | 974 | ranges.push_back(range); 975 | skip_whitespaces(); 976 | 977 | } while (stream.advanceIfMatch(token_type::logical_or)); 978 | 979 | out.ranges = std::move(ranges); 980 | 981 | return success(stream.peek().lexeme); 982 | } 983 | 984 | private: 985 | token_stream stream; 986 | 987 | template 988 | SEMVER_CONSTEXPR from_chars_result parse_range(detail::range& out) noexcept { 989 | do { 990 | skip_whitespaces(); 991 | 992 | if (const auto res = parse_range_comparator(out.ranges_comparators); !res) { 993 | return res; 994 | } 995 | 996 | skip_whitespaces(); 997 | 998 | } while (stream.check(token_type::range_operator) || stream.check(token_type::digit)); 999 | 1000 | return success(stream.peek().lexeme); 1001 | } 1002 | 1003 | template 1004 | SEMVER_CONSTEXPR from_chars_result parse_range_comparator(std::vector>& out) noexcept { 1005 | range_operator op = range_operator::equal; 1006 | token token; 1007 | if (stream.advanceIfMatch(token, token_type::range_operator)) { 1008 | op = std::get(token.value); 1009 | } 1010 | 1011 | skip_whitespaces(); 1012 | 1013 | version ver; 1014 | version_parser parser{ stream }; 1015 | if (const auto res = parser.parse(ver); !res) { 1016 | return res; 1017 | } 1018 | 1019 | out.emplace_back(ver, op); 1020 | return success(stream.peek().lexeme); 1021 | } 1022 | 1023 | SEMVER_CONSTEXPR void skip_whitespaces() noexcept { 1024 | while (stream.advanceIfMatch(token_type::space)) { 1025 | ; 1026 | } 1027 | } 1028 | }; 1029 | } // namespace semver::detail 1030 | 1031 | 1032 | template 1033 | SEMVER_CONSTEXPR from_chars_result parse(std::string_view str, range_set& out) { 1034 | detail::token_stream token_stream; 1035 | const from_chars_result result = detail::lexer{ str }.scan_tokens(token_stream); 1036 | if (!result) { 1037 | return result; 1038 | } 1039 | 1040 | return detail::range_parser{ std::move(token_stream) }.parse(out); 1041 | } 1042 | 1043 | #undef SEMVER_CONSTEXPR 1044 | 1045 | } // namespace semver 1046 | 1047 | #if defined(__clang__) 1048 | # pragma clang diagnostic pop 1049 | #endif 1050 | 1051 | #endif // NEARGYE_SEMANTIC_VERSIONING_HPP 1052 | -------------------------------------------------------------------------------- /test/3rdparty/Catch2/LICENSE: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(CheckCXXCompilerFlag) 2 | 3 | set(SOURCES test.cpp test_operators.cpp test_parse.cpp test_ranges.cpp test_to_string.cpp test_validation.cpp) 4 | 5 | if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") 6 | set(OPTIONS /W4 /WX) 7 | check_cxx_compiler_flag(/permissive HAS_PERMISSIVE_FLAG) 8 | if(HAS_PERMISSIVE_FLAG) 9 | set(OPTIONS ${OPTIONS} /permissive-) 10 | endif() 11 | 12 | check_cxx_compiler_flag(/std:c++20 HAS_CPP20_FLAG) 13 | check_cxx_compiler_flag(/std:c++latest HAS_CPPLATEST_FLAG) 14 | elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") 15 | set(CMAKE_VERBOSE_MAKEFILE ON) 16 | set(OPTIONS -Wall -Wextra -pedantic-errors -Werror) 17 | 18 | check_cxx_compiler_flag(-std=c++20 HAS_CPP20_FLAG) 19 | endif() 20 | 21 | function(make_test target std) 22 | add_executable(${target} ${SOURCES}) 23 | target_compile_options(${target} PRIVATE ${OPTIONS}) 24 | target_include_directories(${target} PRIVATE 3rdparty/Catch2) 25 | target_link_libraries(${target} PRIVATE ${CMAKE_PROJECT_NAME}) 26 | set_target_properties(${target} PROPERTIES CXX_EXTENSIONS OFF) 27 | if(std) 28 | if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") 29 | target_compile_options(${target} PRIVATE /std:${std}) 30 | else() 31 | target_compile_options(${target} PRIVATE -std=${std}) 32 | endif() 33 | endif() 34 | add_test(NAME ${target} COMMAND ${target}) 35 | endfunction() 36 | 37 | make_test(${CMAKE_PROJECT_NAME}-cpp17.t c++17) 38 | 39 | if(HAS_CPP20_FLAG) 40 | make_test(${CMAKE_PROJECT_NAME}-cpp20.t c++20) 41 | endif() 42 | 43 | if(HAS_CPPLATEST_FLAG) 44 | make_test(${CMAKE_PROJECT_NAME}-cpplatest.t c++latest) 45 | endif() 46 | -------------------------------------------------------------------------------- /test/test.cpp: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT License . 2 | // SPDX-License-Identifier: MIT 3 | // Copyright (c) 2018 - 2025 Daniil Goncharov . 4 | // Copyright (c) 2020 - 2025 Alexander Gorbunov . 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | 24 | #define CATCH_CONFIG_MAIN 25 | #include 26 | #include 27 | 28 | //static_assert(semver::equal(semver::semver_version, "0.4.0")); 29 | -------------------------------------------------------------------------------- /test/test_operators.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | template 6 | static void test_parse_and_compare_reverse(const std::string_view v1, const std::string_view v2, Operator op) { 7 | semver::version parsed_v1; 8 | REQUIRE(semver::parse(v1, parsed_v1)); 9 | 10 | semver::version parsed_v2; 11 | REQUIRE(semver::parse(v2, parsed_v2)); 12 | 13 | REQUIRE(op(parsed_v1, parsed_v2)); 14 | REQUIRE(op(parsed_v2, parsed_v1)); 15 | } 16 | 17 | template 18 | static void test_parse_and_compare_reverse_false(const std::string_view v1, const std::string_view v2, Operator op) { 19 | INFO(v1); 20 | INFO(v2); 21 | 22 | semver::version parsed_v1; 23 | REQUIRE(semver::parse(v1, parsed_v1)); 24 | 25 | semver::version parsed_v2; 26 | REQUIRE(semver::parse(v2, parsed_v2)); 27 | 28 | REQUIRE(op(parsed_v1, parsed_v2)); 29 | REQUIRE_FALSE(op(parsed_v2, parsed_v1)); 30 | } 31 | 32 | TEST_CASE("operators") { 33 | constexpr std::array versions = { { 34 | std::string_view{"0.0.0-alpha.0"}, 35 | std::string_view{"0.0.0-alpha.1"}, 36 | std::string_view{"0.0.0-beta.0"}, 37 | std::string_view{"0.0.0-beta.1"}, 38 | std::string_view{"0.0.0-rc.0"}, 39 | std::string_view{"0.0.0-rc.1"}, 40 | std::string_view{"0.0.0"}, 41 | 42 | std::string_view{"0.0.1-alpha.0"}, 43 | std::string_view{"0.0.1-alpha.1"}, 44 | std::string_view{"0.0.1-beta.0"}, 45 | std::string_view{"0.0.1-beta.1"}, 46 | std::string_view{"0.0.1-rc.0"}, 47 | std::string_view{"0.0.1-rc.1"}, 48 | std::string_view{"0.0.1"}, 49 | 50 | std::string_view{"0.1.0-alpha.0"}, 51 | std::string_view{"0.1.0-alpha.1"}, 52 | std::string_view{"0.1.0-beta.0"}, 53 | std::string_view{"0.1.0-beta.1"}, 54 | std::string_view{"0.1.0-rc.0"}, 55 | std::string_view{"0.1.0-rc.1"}, 56 | std::string_view{"0.1.0"}, 57 | 58 | std::string_view{"0.1.1-alpha.0"}, 59 | std::string_view{"0.1.1-alpha.1"}, 60 | std::string_view{"0.1.1-beta.0"}, 61 | std::string_view{"0.1.1-beta.1"}, 62 | std::string_view{"0.1.1-rc.0"}, 63 | std::string_view{"0.1.1-rc.1"}, 64 | std::string_view{"0.1.1"}, 65 | 66 | std::string_view{"1.0.0-alpha.0"}, 67 | std::string_view{"1.0.0-alpha.1"}, 68 | std::string_view{"1.0.0-beta.0"}, 69 | std::string_view{"1.0.0-beta.1"}, 70 | std::string_view{"1.0.0-rc.0"}, 71 | std::string_view{"1.0.0-rc.1"}, 72 | std::string_view{"1.0.0"}, 73 | 74 | std::string_view{"1.0.1-alpha.0"}, 75 | std::string_view{"1.0.1-alpha.1"}, 76 | std::string_view{"1.0.1-beta.0"}, 77 | std::string_view{"1.0.1-beta.1"}, 78 | std::string_view{"1.0.1-rc.0"}, 79 | std::string_view{"1.0.1-rc.1"}, 80 | std::string_view{"1.0.1"}, 81 | 82 | std::string_view{"1.1.0-alpha.0"}, 83 | std::string_view{"1.1.0-alpha.1"}, 84 | std::string_view{"1.1.0-beta.0"}, 85 | std::string_view{"1.1.0-beta.1"}, 86 | std::string_view{"1.1.0-rc.0"}, 87 | std::string_view{"1.1.0-rc.1"}, 88 | std::string_view{"1.1.0"}, 89 | 90 | std::string_view{"1.1.1-alpha.0"}, 91 | std::string_view{"1.1.1-alpha.1"}, 92 | std::string_view{"1.1.1-beta.0"}, 93 | std::string_view{"1.1.1-beta.1"}, 94 | std::string_view{"1.1.1-rc.0"}, 95 | std::string_view{"1.1.1-rc.1"}, 96 | std::string_view{"1.1.1"}, 97 | } }; 98 | 99 | SECTION("operator ==") { 100 | for (auto version : versions) { 101 | test_parse_and_compare_reverse(version, version, semver::operator==); 102 | } 103 | } 104 | 105 | SECTION("operator !=") { 106 | for (std::size_t i = 1; i < versions.size(); ++i) { 107 | for (std::size_t j = 1; j < i; ++j) { 108 | test_parse_and_compare_reverse(versions[i], versions[i - j], semver::operator!=); 109 | } 110 | } 111 | } 112 | 113 | SECTION("operator >") { 114 | for (std::size_t i = 1; i < versions.size(); ++i) { 115 | for (std::size_t j = 1; j < i; ++j) { 116 | test_parse_and_compare_reverse_false(versions[i], versions[i - j], semver::operator>); 117 | } 118 | } 119 | } 120 | 121 | SECTION("operator >=") { 122 | for (std::size_t i = 1; i < versions.size(); ++i) { 123 | for (std::size_t j = 1; j < i; ++j) { 124 | test_parse_and_compare_reverse_false(versions[i], versions[i - j], semver::operator>=); 125 | test_parse_and_compare_reverse(versions[i], versions[i], semver::operator>=); 126 | } 127 | } 128 | } 129 | 130 | SECTION("operator <") { 131 | for (std::size_t i = 1; i < versions.size(); ++i) { 132 | for (std::size_t j = 1; j < i; ++j) { 133 | test_parse_and_compare_reverse_false(versions[i - j], versions[i], semver::operator< ); 134 | } 135 | } 136 | } 137 | 138 | SECTION("operator <=") { 139 | for (std::size_t i = 1; i < versions.size(); ++i) { 140 | for (std::size_t j = 1; j < i; ++j) { 141 | test_parse_and_compare_reverse_false(versions[i - j], versions[i], semver::operator<=); 142 | test_parse_and_compare_reverse(versions[i - j], versions[i - j], semver::operator<=); 143 | } 144 | } 145 | } 146 | 147 | SECTION("prerelease compare") { 148 | // 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0. 149 | constexpr std::string_view v1 = "1.0.0-alpha"; 150 | constexpr std::string_view v2 = "1.0.0-alpha.1"; 151 | constexpr std::string_view v3 = "1.0.0-alpha.beta"; 152 | constexpr std::string_view v4 = "1.0.0-beta"; 153 | constexpr std::string_view v5 = "1.0.0-beta.2"; 154 | constexpr std::string_view v6 = "1.0.0-beta.11"; 155 | constexpr std::string_view v7 = "1.0.0-rc.1"; 156 | constexpr std::string_view v8 = "1.0.0"; 157 | 158 | test_parse_and_compare_reverse_false(v1, v2, semver::operator< ); 159 | test_parse_and_compare_reverse_false(v2, v3, semver::operator< ); 160 | test_parse_and_compare_reverse_false(v3, v4, semver::operator< ); 161 | test_parse_and_compare_reverse_false(v4, v5, semver::operator< ); 162 | test_parse_and_compare_reverse_false(v5, v6, semver::operator< ); 163 | test_parse_and_compare_reverse_false(v6, v7, semver::operator< ); 164 | test_parse_and_compare_reverse_false(v7, v8, semver::operator< ); 165 | 166 | test_parse_and_compare_reverse_false(v2, v1, semver::operator>); 167 | test_parse_and_compare_reverse_false(v3, v2, semver::operator>); 168 | test_parse_and_compare_reverse_false(v4, v3, semver::operator>); 169 | test_parse_and_compare_reverse_false(v5, v4, semver::operator>); 170 | test_parse_and_compare_reverse_false(v6, v5, semver::operator>); 171 | test_parse_and_compare_reverse_false(v7, v6, semver::operator>); 172 | test_parse_and_compare_reverse_false(v8, v7, semver::operator>); 173 | 174 | constexpr std::string_view v9 = "1.0.0-alpha.5"; 175 | constexpr std::string_view v10 = "1.0.0-alpha.10"; 176 | test_parse_and_compare_reverse_false(v9, v10, semver::operator< ); 177 | test_parse_and_compare_reverse_false(v10, v9, semver::operator>); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /test/test_parse.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace semver; 6 | 7 | TEST_CASE("parse") { 8 | SECTION("simple") { 9 | struct version { 10 | int major, minor, patch; 11 | }; 12 | 13 | constexpr std::array, 8> versions = {{ 14 | {"0.0.0", {0,0,0}}, 15 | {"0.0.1", {0,0,1}}, 16 | {"0.1.0", {0,1,0}}, 17 | {"0.1.1", {0,1,1}}, 18 | {"1.0.0", {1,0,0}}, 19 | {"1.0.1", {1,0,1}}, 20 | {"1.1.0", {1,1,0}}, 21 | {"1.1.1", {1,1,1}} 22 | }}; 23 | 24 | for (const auto& [version, expected]: versions) { 25 | semver::version result; 26 | REQUIRE(parse(version, result)); 27 | REQUIRE(result.major() == expected.major); 28 | REQUIRE(result.minor() == expected.minor); 29 | REQUIRE(result.patch() == expected.patch); 30 | } 31 | } 32 | 33 | SECTION("negative") { 34 | constexpr std::array versions = {{ 35 | {"0.0.-1"}, 36 | {"0.-1.0"}, 37 | {"-1.0.0"} 38 | }}; 39 | 40 | for (auto version: versions) { 41 | semver::version result; 42 | REQUIRE_FALSE(parse(version, result)); 43 | } 44 | } 45 | 46 | SECTION("leading zero") { 47 | constexpr std::array versions = {{ 48 | {"0.0.01"}, 49 | {"0.01.0"}, 50 | {"01.0.0"} 51 | }}; 52 | 53 | for (auto version: versions) { 54 | semver::version result; 55 | REQUIRE_FALSE(parse(version, result)); 56 | } 57 | } 58 | 59 | SECTION("incomplete") { 60 | constexpr std::array versions = {{ 61 | "", 62 | "1.", 63 | "1.*", 64 | "1.0", 65 | "1.0.", 66 | "1.0.*", 67 | "*" 68 | }}; 69 | 70 | for (auto version: versions) { 71 | semver::version result; 72 | REQUIRE_FALSE(parse(version, result)); 73 | } 74 | } 75 | 76 | SECTION("overflow") { 77 | constexpr std::string_view v = "0.0.128"; 78 | 79 | semver::version result; 80 | REQUIRE_FALSE(parse(v, result)); 81 | 82 | semver::version result2; 83 | REQUIRE(parse(v, result2)); 84 | REQUIRE(result2.major() == 0); 85 | REQUIRE(result2.minor() == 0); 86 | REQUIRE(result2.patch() == 128); 87 | 88 | constexpr std::string_view v2 = "0.4294967296.0"; 89 | semver::version result3; 90 | REQUIRE_FALSE(parse(v2, result3)); 91 | 92 | semver::version result4; 93 | REQUIRE(parse(v2, result4)); 94 | REQUIRE(result4.major() == 0); 95 | REQUIRE(result4.minor() == 4294967296); 96 | REQUIRE(result4.patch() == 0); 97 | } 98 | 99 | SECTION("prerelease") { 100 | struct version { 101 | int major, minor, patch; 102 | std::string_view prerelease_tag; 103 | }; 104 | 105 | constexpr std::array, 3> versions = {{ 106 | {"0.0.1-alpha.128", {0, 0, 1, "alpha.128"}}, 107 | {"1.2.3-alpha.beta.rc-45.42", {1, 2, 3, "alpha.beta.rc-45.42"}}, 108 | {"0.0.1-alpha-beta", {0, 0, 1, "alpha-beta"}} 109 | }}; 110 | 111 | for (const auto& [version, expected]: versions) { 112 | semver::version result; 113 | REQUIRE(parse(version, result)); 114 | REQUIRE(result.major() == expected.major); 115 | REQUIRE(result.minor() == expected.minor); 116 | REQUIRE(result.patch() == expected.patch); 117 | REQUIRE(result.prerelease_tag() == expected.prerelease_tag); 118 | } 119 | } 120 | 121 | SECTION("build-metadata") { 122 | struct version { 123 | int major, minor, patch; 124 | std::string_view build_metadata; 125 | }; 126 | 127 | constexpr std::array, 3> versions = {{ 128 | {"0.0.1+123", {0, 0, 1, "123"}}, 129 | {"1.2.3+sha.42089", {1, 2, 3, "sha.42089"}}, 130 | {"0.0.1+001-meta-info", {0, 0, 1, "001-meta-info"}} 131 | }}; 132 | 133 | for (const auto& [version, expected]: versions) { 134 | semver::version result; 135 | REQUIRE(parse(version, result)); 136 | REQUIRE(result.major() == expected.major); 137 | REQUIRE(result.minor() == expected.minor); 138 | REQUIRE(result.patch() == expected.patch); 139 | REQUIRE(result.prerelease_tag().empty()); 140 | REQUIRE(result.build_metadata() == expected.build_metadata); 141 | } 142 | } 143 | 144 | SECTION("prerelease + build-metadata") { 145 | struct version { 146 | int major, minor, patch; 147 | std::string_view prerelease_tag, build_metadata; 148 | }; 149 | 150 | constexpr std::array, 3> versions = {{ 151 | {"0.0.1-alpha.128+123", {0, 0, 1, "alpha.128", "123"}}, 152 | {"1.2.3-alpha.beta.rc-45.42+sha.42089", {1, 2, 3, "alpha.beta.rc-45.42", "sha.42089"}}, 153 | {"0.0.1-alpha-beta+001-meta-info", {0, 0, 1, "alpha-beta", "001-meta-info"}} 154 | }}; 155 | 156 | for (const auto& [version, expected]: versions) { 157 | semver::version result; 158 | REQUIRE(parse(version, result)); 159 | REQUIRE(result.major() == expected.major); 160 | REQUIRE(result.minor() == expected.minor); 161 | REQUIRE(result.patch() == expected.patch); 162 | REQUIRE(result.prerelease_tag() == expected.prerelease_tag); 163 | REQUIRE(result.build_metadata() == expected.build_metadata); 164 | } 165 | } 166 | } -------------------------------------------------------------------------------- /test/test_ranges.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | static void test_parse_and_check(std::string_view range_str, std::string_view ver_str, 6 | semver::version_compare_option option = semver::version_compare_option::exclude_prerelease) 7 | { 8 | INFO(range_str << " : " << ver_str); 9 | 10 | semver::version v; 11 | REQUIRE(semver::parse(ver_str, v)); 12 | 13 | semver::range_set rs; 14 | REQUIRE(semver::parse(range_str, rs)); 15 | 16 | REQUIRE(rs.contains(v, option)); 17 | } 18 | 19 | static void test_parse_and_check_false(std::string_view range_str, std::string_view ver_str, 20 | semver::version_compare_option option = semver::version_compare_option::exclude_prerelease) 21 | { 22 | INFO(range_str << " : " << ver_str); 23 | 24 | semver::version v; 25 | REQUIRE(semver::parse(ver_str, v)); 26 | 27 | semver::range_set rs; 28 | REQUIRE(semver::parse(range_str, rs)); 29 | 30 | REQUIRE_FALSE(rs.contains(v, option)); 31 | } 32 | 33 | TEST_CASE("ranges") { 34 | SECTION("constructor") { 35 | constexpr std::string_view v1{"1.2.3"}; 36 | constexpr std::string_view r1{">1.0.0 <=2.0.0"}; 37 | test_parse_and_check(r1, v1); 38 | 39 | constexpr std::string_view v2{"2.1.0"}; 40 | test_parse_and_check_false(r1, v2); 41 | 42 | constexpr std::string_view r2{"1.1.1"}; 43 | constexpr std::string_view v3{"1.1.1"}; 44 | test_parse_and_check(r2, v3); 45 | } 46 | 47 | struct range_test_case { 48 | std::string_view range; 49 | std::string_view ver; 50 | bool contains; 51 | }; 52 | 53 | SECTION("one comparator set") { 54 | constexpr std::array tests = {{ 55 | {"> 1.2.3", {"1.2.5"}, true}, 56 | {"> 1.2.3", {"1.1.0"}, false}, 57 | {">=1.2.0 <2.0.0", {"1.2.5"}, true}, 58 | {">=1.2.0 <2.0.0", {"2.3.0"}, false}, 59 | {"1.0.0", {"1.0.0"}, true}, 60 | {"1.0.0 < 2.0.0", {"1.5.0"}, false} 61 | }}; 62 | 63 | for (const auto& test : tests) { 64 | if (test.contains) { 65 | test_parse_and_check(test.range, test.ver); 66 | } 67 | else { 68 | test_parse_and_check_false(test.range, test.ver); 69 | } 70 | } 71 | } 72 | 73 | SECTION("multiple comparators set") { 74 | constexpr std::string_view range{"1.2.7 || >=1.2.9 <2.0.0"}; 75 | constexpr std::string_view v1{"1.2.7"}; 76 | constexpr std::string_view v2{"1.2.9"}; 77 | constexpr std::string_view v3{"1.4.6"}; 78 | constexpr std::string_view v4{"1.2.8"}; 79 | constexpr std::string_view v5{"2.0.0"}; 80 | 81 | test_parse_and_check(range, v1); 82 | test_parse_and_check(range, v2); 83 | test_parse_and_check(range, v3); 84 | test_parse_and_check_false(range, v4); 85 | test_parse_and_check_false(range, v5); 86 | } 87 | } 88 | 89 | TEST_CASE("ranges with prerelease tags") { 90 | SECTION("prerelease tags") { 91 | constexpr std::string_view r1{">1.2.3-alpha.3"}; 92 | constexpr std::string_view r2{">=1.2.3 < 2.0.0"}; 93 | constexpr std::string_view r3{">=1.2.3-alpha.7 <2.0.0"}; 94 | constexpr std::string_view r4{">1.2.3 <2.0.0-alpha.10"}; 95 | constexpr std::string_view r5{">1.2.3 <2.0.0-alpha.1 || <=2.0.0-alpha.5"}; 96 | constexpr std::string_view r6{"<=2.0.0-alpha.4"}; 97 | 98 | constexpr std::string_view v1{"1.2.3-alpha.7"}; 99 | constexpr std::string_view v2{"3.4.5-alpha.9"}; 100 | constexpr std::string_view v3{"3.4.5"}; 101 | constexpr std::string_view v4{"1.2.3-alpha.4"}; 102 | constexpr std::string_view v5{"2.0.0-alpha.5"}; 103 | 104 | SECTION("exclude prerelease") { 105 | test_parse_and_check(r1, v1); 106 | test_parse_and_check_false(r1, v2); 107 | test_parse_and_check(r1, v3); 108 | test_parse_and_check(r1, v4); 109 | test_parse_and_check_false(r2, v1); 110 | test_parse_and_check(r3, v1); 111 | test_parse_and_check(r4, v5); 112 | test_parse_and_check_false(r4, v1); 113 | test_parse_and_check(r5, v5); 114 | test_parse_and_check_false(r6, v5); 115 | } 116 | 117 | SECTION("include prerelease") { 118 | test_parse_and_check(r1, v1, semver::version_compare_option::include_prerelease); 119 | test_parse_and_check(r1, v2, semver::version_compare_option::include_prerelease); 120 | test_parse_and_check(r1, v3, semver::version_compare_option::include_prerelease); 121 | test_parse_and_check(r1, v4, semver::version_compare_option::include_prerelease); 122 | test_parse_and_check_false(r2, v1, semver::version_compare_option::include_prerelease); 123 | test_parse_and_check(r3, v1, semver::version_compare_option::include_prerelease); 124 | test_parse_and_check(r4, v5, semver::version_compare_option::include_prerelease); 125 | test_parse_and_check_false(r4, v1, semver::version_compare_option::include_prerelease); 126 | test_parse_and_check(r5, v5, semver::version_compare_option::include_prerelease); 127 | test_parse_and_check_false(r6, v5, semver::version_compare_option::include_prerelease); 128 | } 129 | } 130 | 131 | SECTION("prerelease type comparison") { 132 | constexpr std::string_view v1{"1.0.0-alpha.123"}; 133 | constexpr std::string_view v2{"1.0.0-beta.123"}; 134 | constexpr std::string_view v3{"1.0.0-rc.123"}; 135 | 136 | constexpr std::string_view r1{"<=1.0.0-alpha.123"}; 137 | constexpr std::string_view r2{"<=1.0.0-beta.123"}; 138 | constexpr std::string_view r3{"<=1.0.0-rc.123"}; 139 | 140 | SECTION("exclude prerelease") { 141 | test_parse_and_check(r1, v1); 142 | test_parse_and_check_false(r1, v2); 143 | test_parse_and_check_false(r1, v3); 144 | 145 | test_parse_and_check(r2, v1); 146 | test_parse_and_check(r2, v2); 147 | test_parse_and_check_false(r2, v3); 148 | 149 | test_parse_and_check(r3, v1); 150 | test_parse_and_check(r3, v2); 151 | test_parse_and_check(r3, v3); 152 | } 153 | 154 | SECTION("include prerelease") { 155 | test_parse_and_check(r1, v1, semver::version_compare_option::include_prerelease); 156 | test_parse_and_check_false(r1, v2, semver::version_compare_option::include_prerelease); 157 | test_parse_and_check_false(r1, v3, semver::version_compare_option::include_prerelease); 158 | 159 | test_parse_and_check(r2, v1, semver::version_compare_option::include_prerelease); 160 | test_parse_and_check(r2, v2, semver::version_compare_option::include_prerelease); 161 | test_parse_and_check_false(r2, v3, semver::version_compare_option::include_prerelease); 162 | 163 | test_parse_and_check(r3, v1, semver::version_compare_option::include_prerelease); 164 | test_parse_and_check(r3, v2, semver::version_compare_option::include_prerelease); 165 | test_parse_and_check(r3, v3, semver::version_compare_option::include_prerelease); 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /test/test_to_string.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "test_utils.hpp" 4 | 5 | using namespace semver; 6 | 7 | TEST_CASE("to string") { 8 | version version; 9 | CHECK(version.to_string() == "0.1.0"); 10 | 11 | for (auto str : valid_versions) { 12 | REQUIRE(parse(str, version)); 13 | CHECK(version.to_string() == str); 14 | } 15 | } -------------------------------------------------------------------------------- /test/test_utils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #if __cpp_lib_constexpr_string >= 201907L 6 | #define SEMVER_CONSTEXPR_SUPPORT 7 | #endif 8 | 9 | inline constexpr std::array valid_versions = { { 10 | {"0.0.4"}, 11 | {"1.2.3"}, 12 | {"10.20.30"}, 13 | {"1.1.2-prerelease+meta"}, 14 | {"1.1.2+meta"}, 15 | {"1.1.2+meta-valid"}, 16 | {"1.0.0-alpha"}, 17 | {"1.0.0-beta"}, 18 | {"1.0.0-alpha.beta"}, 19 | {"1.0.0-alpha.beta.1"}, 20 | {"1.0.0-alpha.1"}, 21 | {"1.0.0-alpha0.valid"}, 22 | {"1.0.0-alpha.0valid"}, 23 | {"1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay"}, 24 | {"1.2.3-rc.4"}, 25 | {"1.0.0-rc.1+build.1"}, 26 | {"2.0.0-rc.1+build.123"}, 27 | {"1.2.3-beta"}, 28 | {"10.2.3-DEV-SNAPSHOT"}, 29 | {"1.2.3-SNAPSHOT-123"}, 30 | {"1.0.0"}, 31 | {"2.0.0"}, 32 | {"1.1.7"}, 33 | {"2.0.0+build.1848"}, 34 | {"2.0.1-alpha.1227"}, 35 | {"1.0.0-alpha+beta"}, 36 | {"1.2.3----RC-SNAPSHOT.12.9.1--.12+788"}, 37 | {"1.2.3----R-S.12.9.1--.12+meta"}, 38 | {"1.2.3----RC-SNAPSHOT.12.9.1--.12"}, 39 | {"1.0.0+0.build.1-rc.10000aaa-kk-0.1"}, 40 | {"999999999.999999999.999999999"}, 41 | {"1.0.0-0A.is.legal"} 42 | }}; 43 | 44 | inline constexpr std::array invalid_versions = { { 45 | {""}, 46 | {"1"}, 47 | {"1.2"}, 48 | {"1.*"}, 49 | {"1.2.3-0123"}, 50 | {"1.2.3-0123.0123"}, 51 | {"1.0.0-"}, 52 | {"1.0.0+"}, 53 | {"1.0.0-."}, 54 | {"1.0.0+."}, 55 | {"1.0.0-.+."}, 56 | {"1.1.2+.123"}, 57 | {"+invalid"}, 58 | {"-invalid"}, 59 | {"-invalid+invalid"}, 60 | {"-invalid.01"}, 61 | {"alpha"}, 62 | {"alpha.beta"}, 63 | {"alpha.beta.1"}, 64 | {"alpha.1"}, 65 | {"alpha+beta"}, 66 | {"alpha_beta"}, 67 | {"alpha."}, 68 | {"alpha.."}, 69 | {"beta"}, 70 | {"1.0.0-alpha_beta"}, 71 | {"-alpha."}, 72 | {"1.0.0-alpha.."}, 73 | {"1.0.0-alpha..1"}, 74 | {"1.0.0-alpha...1"}, 75 | {"1.0.0-alpha....1"}, 76 | {"1.0.0-alpha.....1"}, 77 | {"1.0.0-alpha......1"}, 78 | {"1.0.0-alpha.......1"}, 79 | {"01.1.1"}, 80 | {"1.01.1"}, 81 | {"1.1.01"}, 82 | {"1.2."}, 83 | {"1.2.3.DEV"}, 84 | {"1.2-SNAPSHOT"}, 85 | {"1.2.31.2.3----RC-SNAPSHOT.12.09.1--..12+788"}, 86 | {"1.2-RC-SNAPSHOT"}, 87 | {"-1.0.3-gamma+b7718"}, 88 | {"+justmeta"}, 89 | {"9.8.7+meta+meta"}, 90 | {"9.8.7-whatever+meta+meta"}, 91 | {"99999999999999999999999.999999999999999999.99999999999999999----RC-SNAPSHOT.12.09.1--------------------------------..12"} 92 | } }; -------------------------------------------------------------------------------- /test/test_validation.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "test_utils.hpp" 4 | 5 | using namespace semver; 6 | 7 | TEST_CASE("validation") { 8 | #ifdef SEMVER_CONSTEXPR_SUPPORT 9 | SECTION("constexpr valid") { 10 | constexpr std::string_view v1 = "0.0.1"; 11 | static_assert(valid(v1)); 12 | 13 | constexpr std::string_view v2 = "1.2.3-rc.4"; 14 | static_assert(valid(v2)); 15 | 16 | constexpr std::string_view v3 = "1.1.2-prerelease+meta"; 17 | static_assert(valid(v3)); 18 | } 19 | 20 | SECTION("constexpr invalid") { 21 | constexpr std::string_view v1 = ""; 22 | static_assert(!valid(v1)); 23 | 24 | constexpr std::string_view v2 = "1.01.*"; 25 | static_assert(!valid(v2)); 26 | 27 | constexpr std::string_view v3 = "1.1.2-prerelease_meta"; 28 | static_assert(!valid(v3)); 29 | } 30 | #endif 31 | 32 | SECTION("runtime valid") { 33 | for (auto version: valid_versions) { 34 | REQUIRE(valid(version)); 35 | } 36 | } 37 | 38 | SECTION("runtime invalid") { 39 | for (auto version: invalid_versions) { 40 | REQUIRE_FALSE(valid(version)); 41 | } 42 | } 43 | } 44 | --------------------------------------------------------------------------------