From b19d0fd3ca3fcdadff75eb93388f6f3ac57a4222 Mon Sep 17 00:00:00 2001 From: sascha Date: Tue, 28 Jul 2026 09:53:25 +0200 Subject: [PATCH] improved version::str() and version::from_string() --- include/matador/utils/version.hpp | 6 +- source/core/utils/version.cpp | 124 ++++++++++++++++++------------ test/core/utils/VersionTest.cpp | 25 +++++- 3 files changed, 99 insertions(+), 56 deletions(-) diff --git a/include/matador/utils/version.hpp b/include/matador/utils/version.hpp index 644d87c..d09f7ac 100644 --- a/include/matador/utils/version.hpp +++ b/include/matador/utils/version.hpp @@ -3,16 +3,16 @@ #include "matador/utils/result.hpp" #include "matador/utils/error.hpp" +#include "matador/utils/export.hpp" #include -#include namespace matador::utils { -class version { +class MATADOR_UTILS_API version final { public: version() = default; - ~version() =default; + ~version() = default; version(unsigned int major, unsigned int minor, unsigned int patch); version(const version& x) = default; version& operator=(const version& x) = default; diff --git a/source/core/utils/version.cpp b/source/core/utils/version.cpp index fcfc95b..7ff0b04 100644 --- a/source/core/utils/version.cpp +++ b/source/core/utils/version.cpp @@ -3,98 +3,122 @@ #include namespace matador::utils { +namespace { -version::version(unsigned int major, unsigned int minor, unsigned int patch) -: major_(major) -, minor_(minor) -, patch_(patch) -{} +bool parse_uint_component(const std::string &text, std::size_t &pos, unsigned int &value) { + if (pos >= text.size() || text[pos] < '0' || text[pos] > '9') { + return false; + } -bool version::operator==(const version &x) const -{ - return major_ == x.major_ && - minor_ == x.minor_ && - patch_ == x.patch_; + unsigned int result{}; + + while (pos < text.size() && text[pos] >= '0' && text[pos] <= '9') { + const auto digit = static_cast(text[pos] - '0'); + + if (result > (std::numeric_limits::max() - digit) / 10) { + return false; + } + + result = result * 10 + digit; + ++pos; + } + + value = result; + return true; } -bool version::operator!=(const version &x) const -{ +bool parse_dot(const std::string &text, std::size_t &pos) { + if (pos >= text.size() || text[pos] != '.') { + return false; + } + + ++pos; + return true; +} + +} // namespace +version::version(const unsigned int major, const unsigned int minor, const unsigned int patch) +: major_(major) +, minor_(minor) +, patch_(patch) { +} + +bool version::operator==(const version& x) const { + return major_ == x.major_ && + minor_ == x.minor_ && + patch_ == x.patch_; +} + +bool version::operator!=(const version& x) const { return !(*this == x); } -bool version::operator>(const version &x) const -{ +bool version::operator>(const version& x) const { return !(*this <= x); } -bool version::operator>=(const version &x) const -{ +bool version::operator>=(const version& x) const { return !(*this < x); } -bool version::operator<(const version &x) const -{ +bool version::operator<(const version& x) const { return (major_ < x.major_) || - (major_ == x.major_ && minor_ < x.minor_) || - (major_ == x.major_ && minor_ == x.minor_ && patch_ < x.patch_); + (major_ == x.major_ && minor_ < x.minor_) || + (major_ == x.major_ && minor_ == x.minor_ && patch_ < x.patch_); } -bool version::operator<=(const version &x) const -{ +bool version::operator<=(const version& x) const { return *this < x || *this == x; } -std::string version::str() const -{ - char buf[32]; - sprintf(buf, "%d.%d.%d", major_, minor_, patch_); - return buf; +std::string version::str() const { + return std::to_string(major_) + "." + + std::to_string(minor_) + "." + + std::to_string(patch_); } -std::ostream &operator<<(std::ostream &out, const version &v) -{ +std::ostream& operator<<(std::ostream& out, const version& v) { out << v.str(); return out; } -result version::from_string(const std::string &version_string) -{ - version result; - if (const auto ret = sscanf(version_string.c_str(), "%u.%u.%u", &result.major_, &result.minor_, &result.patch_); ret != 3) { +result version::from_string(const std::string& version_string) { + unsigned int major{}; + unsigned int minor{}; + unsigned int patch{}; + + if (std::size_t pos{}; !parse_uint_component(version_string, pos, major) || + !parse_dot(version_string, pos) || + !parse_uint_component(version_string, pos, minor) || + !parse_dot(version_string, pos) || + !parse_uint_component(version_string, pos, patch) || + pos != version_string.size()) { return failure(error(utils_error::InvalidVersionString, version_string)); - } + } - return ok(result); -} + return ok(version{major, minor, patch});} -unsigned int version::major() const -{ +unsigned int version::major() const { return major_; } -unsigned int version::minor() const -{ +unsigned int version::minor() const { return minor_; } -unsigned int version::patch() const -{ +unsigned int version::patch() const { return patch_; } -void version::major(unsigned int m) -{ +void version::major(unsigned int m) { major_ = m; } -void version::minor(unsigned int m) -{ +void version::minor(unsigned int m) { minor_ = m; } -void version::patch(unsigned int p) -{ +void version::patch(unsigned int p) { patch_ = p; } - -} \ No newline at end of file +} diff --git a/test/core/utils/VersionTest.cpp b/test/core/utils/VersionTest.cpp index 82f4dac..c07ec54 100644 --- a/test/core/utils/VersionTest.cpp +++ b/test/core/utils/VersionTest.cpp @@ -4,7 +4,26 @@ using namespace matador::utils; -TEST_CASE("Test version interface", "[version][interface]") { +TEST_CASE("Version: Test form string", "[version][from_string]") { + REQUIRE(version::from_string("1.2.3").is_ok()); + REQUIRE(version::from_string("0.0.0").is_ok()); + + REQUIRE(version::from_string("1.2").is_error()); + REQUIRE(version::from_string("1.2.3.4").is_error()); + REQUIRE(version::from_string("1.2.3abc").is_error()); + REQUIRE(version::from_string("abc").is_error()); + REQUIRE(version::from_string("-1.2.3").is_error()); +} + +TEST_CASE("Version: Test comparison", "[version][comparison]") { + REQUIRE(version(1, 2, 3) == version(1, 2, 3)); + REQUIRE(version(1, 2, 3) < version(1, 2, 4)); + REQUIRE(version(1, 2, 3) < version(1, 3, 0)); + REQUIRE(version(1, 2, 3) < version(2, 0, 0)); + REQUIRE(version(2, 0, 0) > version(1, 9, 9)); +} + +TEST_CASE("Version: Test interface", "[version][interface]") { version v0; REQUIRE(v0.major() == 0); @@ -38,8 +57,8 @@ TEST_CASE("Test version interface", "[version][interface]") { REQUIRE(v2 == v1); } -TEST_CASE("Test version parsing", "[version][parse]") { - const auto version_str{"13.67.34"}; +TEST_CASE("Version: Test parsing", "[version][parse]") { + constexpr auto version_str{"13.67.34"}; const auto v1 = version::from_string(version_str); REQUIRE(v1.is_ok());