diff options
-rw-r--r-- | include/jau/util/VersionNumber.hpp | 257 | ||||
-rw-r--r-- | test/test_versionnumber01.cpp | 289 |
2 files changed, 546 insertions, 0 deletions
diff --git a/include/jau/util/VersionNumber.hpp b/include/jau/util/VersionNumber.hpp new file mode 100644 index 0000000..52be60c --- /dev/null +++ b/include/jau/util/VersionNumber.hpp @@ -0,0 +1,257 @@ +/* + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2010-2024 Gothel Software e.K. + * + * 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 JAU_VERSIONNUMBER_HPP_ +#define JAU_VERSIONNUMBER_HPP_ + +#include <compare> +#include <ostream> +#include <regex> +#include <string> + +#include <jau/int_types.hpp> + +namespace jau::util { + + /** + * Simple version number class containing a version number + * either being {@link #VersionNumber(int, int, int) defined explicit} + * or {@link #VersionNumber(String, String) derived from a string}. + * + * For the latter case, you can query whether a component has been defined explicitly by the given <code>versionString</code>, + * via {@link #hasMajor()}, {@link #hasMinor()} and {@link #hasSub()}. + * + * The state whether a component is defined explicitly <i>is not considered</i> + * in the {@link #hashCode()}, {@link #equals(Object)} or {@link #compareTo(Object)} methods, + * since the version number itself is treated regardless. + */ + class VersionNumber { + private: + int m_major, m_minor, m_sub; + ssize_t m_strEnd; + std::string m_version_str; + + uint16_t state; + + constexpr static const uint16_t HAS_MAJOR = 1U << 0; + constexpr static const uint16_t HAS_MINOR = 1U << 1; + constexpr static const uint16_t HAS_SUB = 1U << 2; + + protected: + VersionNumber(int majorRev, int minorRev, int subMinorRev, ssize_t strEnd, uint16_t _state) noexcept + : m_major(majorRev), m_minor(minorRev), m_sub(subMinorRev), m_strEnd(strEnd), m_version_str(), state(_state) {} + + public: + static std::regex getPattern(const std::string& delim) { + // "\D*(\d+)[^\.\s]*(?:\.\D*(\d+)[^\.\s]*(?:\.\D*(\d+))?)?"); + // return std::regex("\\D*(\\d+)[^\\"+delim+"\\s]*(?:\\"+delim+"\\D*(\\d+)[^\\"+delim+"\\s]*(?:\\"+delim+"\\D*(\\d+))?)?"); + return std::regex(R"(\D*(\d+)[^\)" + delim + "\\s]*(?:\\" + delim + R"(\D*(\d+)[^\)" + delim + "\\s]*(?:\\" + delim + "\\D*(\\d+))?)?"); + } + + static const std::regex& getDefaultPattern() noexcept { // NOLINT(bugprone-exception-escape) + static std::regex defPattern = getPattern("."); + return defPattern; + } + + /** + * Explicit version number instantiation, with all components defined explicitly. + * @see #hasMajor() + * @see #hasMinor() + * @see #hasSub() + */ + VersionNumber(int majorRev, int minorRev, int subMinorRev) noexcept + : VersionNumber(majorRev, minorRev, subMinorRev, -1, HAS_MAJOR | HAS_MINOR | HAS_SUB) + {} + + /** + * Default ctor for zero version. + * @see #hasMajor() + * @see #hasMinor() + * @see #hasSub() + */ + VersionNumber() noexcept + : VersionNumber(0, 0, 0, -1, HAS_MAJOR | HAS_MINOR | HAS_SUB) + {} + + VersionNumber(const VersionNumber&) noexcept = default; + VersionNumber(VersionNumber&&) noexcept = default; + VersionNumber& operator=(const VersionNumber&) noexcept = default; + VersionNumber& operator=(VersionNumber&&) noexcept = default; + + /** + * String derived version number instantiation. + * <p> + * You can query whether a component has been defined explicitly by the given <code>versionString</code>, + * via {@link #hasMajor()}, {@link #hasMinor()} and {@link #hasSub()}. + * </p> + * @param versionString should be given as [MAJOR[.MINOR[.SUB]]] + * @param versionPattern the {@link java.util.regex.Pattern pattern} parser, must be compatible w/ {@link #getVersionNumberPattern(String)} + * + * @see #hasMajor() + * @see #hasMinor() + * @see #hasSub() + */ + VersionNumber(const std::string& versionString, const std::regex& versionPattern) noexcept + : m_major(0), m_minor(0), m_sub(0), m_strEnd(0), m_version_str(versionString), state(0) + { + // group1: \d* == digits major + // group2: \d* == digits minor + // group3: \d* == digits sub + std::smatch match; + if( std::regex_search(versionString, match, versionPattern) ) { + m_strEnd = match.position() + match.length(); + if( match.size() >= 2 && match[1].length() > 0 ) { + m_major = std::stoi(match[1]); + state |= HAS_MAJOR; + if( match.size() >= 3 && match[2].length() > 0 ) { + m_minor = std::stoi(match[2]); + state |= HAS_MINOR; + if( match.size() >= 4 && match[3].length() > 0 ) { + m_sub = std::stoi(match[3]); + state |= HAS_SUB; + } + } + } + } + } + + /** + * String derived version number instantiation. + * <p> + * Utilizing the default {@link java.util.regex.Pattern pattern} parser with delimiter "<b>.</b>", see {@link #getDefaultVersionNumberPattern()}. + * </p> + * <p> + * You can query whether a component has been defined explicitly by the given <code>versionString</code>, + * via {@link #hasMajor()}, {@link #hasMinor()} and {@link #hasSub()}. + * </p> + * @param versionString should be given as [MAJOR[.MINOR[.SUB]]] + * + * @see #hasMajor() + * @see #hasMinor() + * @see #hasSub() + */ + VersionNumber(const std::string& versionString) noexcept + : VersionNumber(versionString, getDefaultPattern()) + { } + + /** + * String derived version number instantiation. + * <p> + * Utilizing {@link java.util.regex.Pattern pattern} parser created via {@link #getVersionNumberPattern(String)}. + * </p> + * <p> + * You can query whether a component has been defined explicitly by the given <code>versionString</code>, + * via {@link #hasMajor()}, {@link #hasMinor()} and {@link #hasSub()}. + * </p> + * @param versionString should be given as [MAJOR[.MINOR[.SUB]]] + * @param delim the delimiter, e.g. "." + * + * @see #hasMajor() + * @see #hasMinor() + * @see #hasSub() + */ + VersionNumber(const std::string& versionString, const std::string& delim) noexcept + : VersionNumber(versionString, getPattern(delim)) + { } + + + /** Returns <code>true</code>, if all version components are zero, otherwise <code>false</code>. */ + bool isZero() const noexcept { + return m_major == 0 && m_minor == 0 && m_sub == 0; + } + + /** Returns <code>true</code>, if the major component is defined explicitly, otherwise <code>false</code>. Undefined components has the value <code>0</code>. */ + constexpr bool hasMajor() const noexcept { return 0 != ( HAS_MAJOR & state ); } + /** Returns <code>true</code>, if the optional minor component is defined explicitly, otherwise <code>false</code>. Undefined components has the value <code>0</code>. */ + constexpr bool hasMinor() const noexcept { return 0 != ( HAS_MINOR & state ); } + /** Returns <code>true</code>, if the optional sub component is defined explicitly, otherwise <code>false</code>. Undefined components has the value <code>0</code>. */ + constexpr bool hasSub() const noexcept { return 0 != ( HAS_SUB & state ); } + + /** Returns true if constructed with a `version-string`, otherwise false. */ + constexpr bool hasString() const noexcept { return m_version_str.length() > 0; } + /** Returns the used `version-string`, empty if not constructed with such. */ + constexpr const std::string& versionString() const noexcept { return m_version_str; } + + /** + * If constructed with `version-string`, returns the string offset <i>after</i> the last matching character, + * or <code>0</code> if none matched, or <code>-1</code> if not constructed with a string. + */ + constexpr ssize_t endOfStringMatch() const noexcept { return m_strEnd; } + + constexpr int major() const noexcept { return m_major; } + constexpr int minor() const noexcept { return m_minor; } + constexpr int sub() const noexcept { return m_sub; } + + /** Two way comparison operator */ + constexpr bool operator==(const VersionNumber& vo) const noexcept { + return m_major == vo.m_major && m_minor == vo.m_minor && m_sub == vo.m_sub; + } + + /** Three way std::strong_ordering comparison operator */ + constexpr std::strong_ordering operator<=>(const VersionNumber& vo) const noexcept { + if( m_major > vo.m_major ) { + return std::strong_ordering::greater; + } else if( m_major < vo.m_major ) { + return std::strong_ordering::less; + } else if( m_minor > vo.m_minor ) { + return std::strong_ordering::greater; + } else if( m_minor < vo.m_minor ) { + return std::strong_ordering::less; + } else if( m_sub > vo.m_sub ) { + return std::strong_ordering::greater; + } else if( m_sub < vo.m_sub ) { + return std::strong_ordering::less; + } + return std::strong_ordering::equal; + } + + std::string toString() const noexcept { + std::string res = std::to_string(m_major) + "." + std::to_string(m_minor) + "." + std::to_string(m_sub); + if( hasString() ) { + res.append(" (").append(m_version_str).append(")"); + } + return res; + } + + }; + + std::ostream& operator<<(std::ostream& out, const VersionNumber& v) noexcept { + return out << v.toString(); + } + +} // namespace jau::util + +namespace std { + template<> struct hash<jau::util::VersionNumber> + { + std::size_t operator()(const jau::util::VersionNumber& v) const noexcept { + // 31 * x == (x << 5) - x + std::size_t h = 31 + v.major(); + h = ((h << 5) - h) + v.minor(); + return ((h << 5) - h) + v.sub(); + } + }; +} + +#endif /* JAU_VERSIONNUMBER_HPP_ */ diff --git a/test/test_versionnumber01.cpp b/test/test_versionnumber01.cpp new file mode 100644 index 0000000..b55666f --- /dev/null +++ b/test/test_versionnumber01.cpp @@ -0,0 +1,289 @@ +/* + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2012-2024 Gothel Software e.K. + * + * 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. + */ +#include <cassert> +#include <cstring> +#include <iostream> + +#include <jau/test/catch2_ext.hpp> + +#include <jau/util/VersionNumber.hpp> + +using namespace jau::util; + +TEST_CASE( "VersionNumber Test 01a", "[version][util]" ) { + std::string vs00 = "1.0.16"; + std::string vs01 = "OpenGL ES GLSL ES 1.0.16"; + std::string vs02 = "1.0.16 OpenGL ES GLSL ES"; + + VersionNumber vn0(1, 0, 16); + std::cout << "vn0: " << vn0 << std::endl; + REQUIRE(true == vn0.hasMajor()); + REQUIRE(true == vn0.hasMinor()); + REQUIRE(true == vn0.hasSub()); + + VersionNumber vn(vs00); + std::cout << "vn.00: " << vn << std::endl; + REQUIRE(true == vn.hasMajor()); + REQUIRE(true == vn.hasMinor()); + REQUIRE(true == vn.hasSub()); + REQUIRE(vn0 == vn); + + vn = VersionNumber(vs01); + std::cout << "vn.01: " << vn << std::endl; + REQUIRE(true == vn.hasMajor()); + REQUIRE(true == vn.hasMinor()); + REQUIRE(true == vn.hasSub()); + REQUIRE(vn0 == vn); + + vn = VersionNumber(vs02); + std::cout << "vn.02: " << vn << std::endl; + REQUIRE(true == vn.hasMajor()); + REQUIRE(true == vn.hasMinor()); + REQUIRE(true == vn.hasSub()); + REQUIRE(vn0 == vn); +} + +TEST_CASE( "VersionNumber Test 01b", "[version][util]" ) { + const std::string delim = ","; + + const std::string vs00 = "1,0,16"; + const std::string vs01 = "OpenGL ES GLSL ES 1,0,16"; + const std::string vs02 = "1,0,16 OpenGL ES GLSL ES"; + const VersionNumber vn0 = VersionNumber(1, 0, 16); + std::cout << "vn0: " << vn0 << std::endl; + REQUIRE(true == vn0.hasMajor()); + REQUIRE(true == vn0.hasMinor()); + REQUIRE(true == vn0.hasSub()); + + VersionNumber vn; + vn = VersionNumber(vs00, delim); + std::cout << "vn.00: " << vn << std::endl; + REQUIRE(true == vn.hasMajor()); + REQUIRE(true == vn.hasMinor()); + REQUIRE(true == vn.hasSub()); + REQUIRE(vn0 == vn); + + vn = VersionNumber(vs01, delim); + std::cout << "vn.01: " << vn << std::endl; + REQUIRE(true == vn.hasMajor()); + REQUIRE(true == vn.hasMinor()); + REQUIRE(true == vn.hasSub()); + REQUIRE(vn0 == vn); + + vn = VersionNumber(vs02, delim); + std::cout << "vn.02: " << vn << std::endl; + REQUIRE(true == vn.hasMajor()); + REQUIRE(true == vn.hasMinor()); + REQUIRE(true == vn.hasSub()); + REQUIRE(vn0 == vn); +} + +TEST_CASE( "VersionNumber Test 02a", "[version][util]" ) { + const std::string vs00 = "4.20"; + const std::string vs01 = "COMPANY via Stupid tool 4.20"; + const std::string vs02 = "4.20 COMPANY via Stupid tool"; + const VersionNumber vn0 = VersionNumber(4, 20, 0); + std::cout << "vn0: " << vn0 << std::endl; + REQUIRE(true == vn0.hasMajor()); + REQUIRE(true == vn0.hasMinor()); + REQUIRE(true == vn0.hasSub()); + + VersionNumber vn; + vn = VersionNumber(vs00); + std::cout << "vn.00: " << vn << std::endl; + REQUIRE(true == vn.hasMajor()); + REQUIRE(true == vn.hasMinor()); + REQUIRE(true == !vn.hasSub()); + REQUIRE(vn0 == vn); + + vn = VersionNumber(vs01); + std::cout << "vn.01: " << vn << std::endl; + REQUIRE(true == vn.hasMajor()); + REQUIRE(true == vn.hasMinor()); + REQUIRE(true == !vn.hasSub()); + REQUIRE(vn0 == vn); + + vn = VersionNumber(vs02); + std::cout << "vn.02: " << vn << std::endl; + REQUIRE(true == vn.hasMajor()); + REQUIRE(true == vn.hasMinor()); + REQUIRE(true == !vn.hasSub()); + REQUIRE(vn0 == vn); +} + +TEST_CASE( "VersionNumber Test 02b", "[version][util]" ) { + const std::string delim = ","; + + const std::string vs00 = "4,20"; + const std::string vs01 = "COMPANY via Stupid tool 4,20"; + const std::string vs02 = "4,20 COMPANY via Stupid tool"; + const VersionNumber vn0 = VersionNumber(4, 20, 0); + REQUIRE(true == vn0.hasMajor()); + REQUIRE(true == vn0.hasMinor()); + REQUIRE(true == vn0.hasSub()); + + VersionNumber vn; + + vn = VersionNumber(vs00, delim); + REQUIRE(true == vn.hasMajor()); + REQUIRE(true == vn.hasMinor()); + REQUIRE(true == !vn.hasSub()); + REQUIRE(vn0 == vn); + + vn = VersionNumber(vs01, delim); + REQUIRE(true == vn.hasMajor()); + REQUIRE(true == vn.hasMinor()); + REQUIRE(true == !vn.hasSub()); + REQUIRE(vn0 == vn); + + vn = VersionNumber(vs02, delim); + REQUIRE(true == vn.hasMajor()); + REQUIRE(true == vn.hasMinor()); + REQUIRE(true == !vn.hasSub()); + REQUIRE(vn0 == vn); +} + +TEST_CASE( "VersionNumber Test 03a", "[version][util]" ) { + const std::string vs00 = "A10.11.12b"; + const std::string vs01 = "Prelim Text 10.Funny11.Weird12 Something is odd"; + const std::string vs02 = "Prelim Text 10.Funny11l1.Weird12 2 Something is odd"; + const VersionNumber vn0 = VersionNumber(10, 11, 12); + REQUIRE(true == vn0.hasMajor()); + REQUIRE(true == vn0.hasMinor()); + REQUIRE(true == vn0.hasSub()); + + VersionNumber vn; + + vn = VersionNumber(vs00); + REQUIRE(true == vn.hasMajor()); + REQUIRE(true == vn.hasMinor()); + REQUIRE(true == vn.hasSub()); + REQUIRE(vn0 == vn); + + vn = VersionNumber(vs01); + REQUIRE(true == vn.hasMajor()); + REQUIRE(true == vn.hasMinor()); + REQUIRE(true == vn.hasSub()); + REQUIRE(vn0 == vn); + + vn = VersionNumber(vs02); + REQUIRE(true == vn.hasMajor()); + REQUIRE(true == vn.hasMinor()); + REQUIRE(true == vn.hasSub()); + REQUIRE(vn0 == vn); +} + +TEST_CASE( "VersionNumber Test 03b", "[version][util]" ) { + const std::string delim = ","; + + const std::string vs00 = "A10,11,12b"; + const std::string vs01 = "Prelim Text 10,Funny11,Weird12 Something is odd"; + const std::string vs02 = "Prelim Text 10,Funny11l1,Weird12 2 Something is odd"; + const VersionNumber vn0 = VersionNumber(10, 11, 12); + REQUIRE(true == vn0.hasMajor()); + REQUIRE(true == vn0.hasMinor()); + REQUIRE(true == vn0.hasSub()); + + VersionNumber vn; + + vn = VersionNumber(vs00, delim); + REQUIRE(true == vn.hasMajor()); + REQUIRE(true == vn.hasMinor()); + REQUIRE(true == vn.hasSub()); + REQUIRE(vn0 == vn); + + vn = VersionNumber(vs01, delim); + REQUIRE(true == vn.hasMajor()); + REQUIRE(true == vn.hasMinor()); + REQUIRE(true == vn.hasSub()); + REQUIRE(vn0 == vn); + + vn = VersionNumber(vs02, delim); + REQUIRE(true == vn.hasMajor()); + REQUIRE(true == vn.hasMinor()); + REQUIRE(true == vn.hasSub()); + REQUIRE(vn0 == vn); +} + +TEST_CASE( "VersionNumber Test 04a", "[version][util]" ) { + const std::string vs00 = "A10.11.12b (git-d6c318e)"; + const std::string vs01 = "Prelim Text 10.Funny11.Weird12 Something is odd (git-d6c318e)"; + const std::string vs02 = "Prelim Text 10.Funny11l1.Weird12 2 Something is odd (git-d6c318e)"; + const VersionNumber vn0 = VersionNumber(10, 11, 12); + REQUIRE(true == vn0.hasMajor()); + REQUIRE(true == vn0.hasMinor()); + REQUIRE(true == vn0.hasSub()); + + VersionNumber vn; + + vn = VersionNumber(vs00); + REQUIRE(true == vn.hasMajor()); + REQUIRE(true == vn.hasMinor()); + REQUIRE(true == vn.hasSub()); + REQUIRE(vn0 == vn); + + vn = VersionNumber(vs01); + REQUIRE(true == vn.hasMajor()); + REQUIRE(true == vn.hasMinor()); + REQUIRE(true == vn.hasSub()); + REQUIRE(vn0 == vn); + + vn = VersionNumber(vs02); + REQUIRE(true == vn.hasMajor()); + REQUIRE(true == vn.hasMinor()); + REQUIRE(true == vn.hasSub()); + REQUIRE(vn0 == vn); +} + +TEST_CASE( "VersionNumber Test 04b", "[version][util]" ) { + const std::string delim = ","; + + const std::string vs00 = "A10,11,12b (git-d6c318e)"; + const std::string vs01 = "Prelim Text 10,Funny11,Weird12 Something is odd (git-d6c318e)"; + const std::string vs02 = "Prelim Text 10,Funny11l1,Weird12 2 Something is odd (git-d6c318e)"; + const VersionNumber vn0 = VersionNumber(10, 11, 12); + REQUIRE(true == vn0.hasMajor()); + REQUIRE(true == vn0.hasMinor()); + REQUIRE(true == vn0.hasSub()); + + VersionNumber vn; + + vn = VersionNumber(vs00, delim); + REQUIRE(true == vn.hasMajor()); + REQUIRE(true == vn.hasMinor()); + REQUIRE(true == vn.hasSub()); + REQUIRE(vn0 == vn); + + vn = VersionNumber(vs01, delim); + REQUIRE(true == vn.hasMajor()); + REQUIRE(true == vn.hasMinor()); + REQUIRE(true == vn.hasSub()); + REQUIRE(vn0 == vn); + + vn = VersionNumber(vs02, delim); + REQUIRE(true == vn.hasMajor()); + REQUIRE(true == vn.hasMinor()); + REQUIRE(true == vn.hasSub()); + REQUIRE(vn0 == vn); +} |