From 0fadbb8a3eae0d93884e31f6cc132f2164b4c0a2 Mon Sep 17 00:00:00 2001 From: Dhanya Thattil Date: Tue, 20 Oct 2026 17:22:04 +0200 Subject: [PATCH] using structs and refactoring --- slsSupportLib/include/sls/Version.h | 39 ++++-- slsSupportLib/src/Version.cpp | 171 +++++++++++++++++---------- slsSupportLib/tests/test-Version.cpp | 42 ++++++- 3 files changed, 180 insertions(+), 72 deletions(-) diff --git a/slsSupportLib/include/sls/Version.h b/slsSupportLib/include/sls/Version.h index 9fe4a13cf..5ff23b5ee 100644 --- a/slsSupportLib/include/sls/Version.h +++ b/slsSupportLib/include/sls/Version.h @@ -2,36 +2,57 @@ // Copyright (C) 2021 Contributors to the SLS Detector Package #pragma once +#include #include #include namespace sls { +struct SemanticVersion { + int major{0}; + int minor{0}; + int patch{0}; + std::string prerelease{}; + + /** + * If empty, return {} + * If 0.0.0, only prerelease is returned (eg. developer, 0.0.0, other non + * semantic versioning) */ + std::string toString() const; + bool operator==(const SemanticVersion &other) const; + bool operator<=(const SemanticVersion &other) const; +}; + class Version { private: - std::string version_; + /** Note: "0.0.0" is considered not semantic versioning here */ + bool hasSemanticVersioning_{false}; + SemanticVersion version_{}; std::string date_; - inline static const std::string defaultVersion_[] = {"developer", "0.0.0"}; + SemanticVersion parseSemanticVersion(const std::string &version); public: explicit Version(const std::string &s); - - static bool parseSemanticVersion(const std::string &version, int &major, - int &minor, int &patch, - std::string &extra); - + std::string toString() const; bool hasSemanticVersioning() const; + std::string getVersion() const; std::string getDate() const; std::string concise() const; - int getMajorVersion() const; - // expects semantic versioning + // throws if not semantic versioning + SemanticVersion getSemanticVersion() const; + int getMajorVersion() const; + int getMinorVersion() const; + int getPatchVersion() const; + std::string getPrereleaseVersion() const; bool isBackwardCompatible(const Version &other) const; + bool operator!=(const Version &other) const; bool operator==(const Version &other) const; bool operator<=(const Version &other) const; }; +std::ostream &operator<<(std::ostream &out, const SemanticVersion &v); std::ostream &operator<<(std::ostream &out, const Version &v); } // namespace sls diff --git a/slsSupportLib/src/Version.cpp b/slsSupportLib/src/Version.cpp index 1aaa762f5..cf80ccdc1 100644 --- a/slsSupportLib/src/Version.cpp +++ b/slsSupportLib/src/Version.cpp @@ -9,109 +9,160 @@ namespace sls { -bool Version::parseSemanticVersion(const std::string &version, int &major, - int &minor, int &patch, std::string &extra) { - char extraBuffer[256] = {'\0'}; - // Parse major.minor.patch and capture any optional trailing suffix. - const int scanned = sscanf(version.c_str(), "%d.%d.%d%255s", &major, &minor, - &patch, extraBuffer); +std::string SemanticVersion::toString() const { + std::ostringstream oss; + oss << major << "." << minor << "." << patch; + if (!prerelease.empty()) { + oss << prerelease; + } + return oss.str(); +} - if (scanned == 3) { - extra.clear(); - return true; +bool SemanticVersion::operator==(const SemanticVersion &other) const { + return major == other.major && minor == other.minor && + patch == other.patch && prerelease == other.prerelease; +} + +bool SemanticVersion::operator<=(const SemanticVersion &other) const { + if (major != other.major) + return major < other.major; + if (minor != other.minor) + return minor < other.minor; + if (patch != other.patch) + return patch < other.patch; + return prerelease <= other.prerelease; +} + +SemanticVersion Version::parseSemanticVersion(const std::string &version) { + SemanticVersion ver; + + // assuming only released versions have semantic versioning + if (version == "developer" || version == "0.0.0") { + return ver; } - if (scanned == 4) { - extra = extraBuffer; - return true; + + char prereleaseBuffer[256] = {'\0'}; + // Parse major.minor.patch and capture any optional trailing suffix. + const int scanned = sscanf(version.c_str(), "%d.%d.%d%255s", &ver.major, + &ver.minor, &ver.patch, prereleaseBuffer); + + if (scanned == 3 || scanned == 4) { + hasSemanticVersioning_ = true; + if (scanned == 4) + ver.prerelease = prereleaseBuffer; + else + ver.prerelease.clear(); } - return false; + return ver; } Version::Version(const std::string &s) { auto list = split(s, ' '); + // only date from previous releases if (list.size() == 1) { date_ = list[0]; } + // semantic versioning + date else { - version_ = list[0]; + auto version = list[0]; date_ = list[1]; + version_ = parseSemanticVersion(version); } } -bool Version::hasSemanticVersioning() const { - - return (version_ != defaultVersion_[0]) && (version_ != defaultVersion_[1]); +std::string Version::toString() const { + std::ostringstream oss; + oss << version_ << " " << getDate(); + return oss.str(); } -std::string Version::getVersion() const { return version_; } +bool Version::hasSemanticVersioning() const { return hasSemanticVersioning_; } + +std::string Version::getVersion() const { return version_.toString(); } + std::string Version::getDate() const { return date_; } std::string Version::concise() const { - if (hasSemanticVersioning()) - return version_; + if (hasSemanticVersioning_) + return version_.toString(); return date_; } -int Version::getMajorVersion() const { - int major = 0, minor = 0, patch = 0; - if (sscanf(version_.c_str(), "%d.%d.%d", &major, &minor, &patch) == 3) { - return major; +SemanticVersion Version::getSemanticVersion() const { + if (!hasSemanticVersioning_) { + throw sls::RuntimeError("No semantic versioning available for " + + toString()); } - throw sls::RuntimeError("Could not get major version from " + version_); + return version_; +} + +int Version::getMajorVersion() const { + if (!hasSemanticVersioning_) { + throw sls::RuntimeError("Could not get major version from " + + toString()); + } + return version_.major; +} + +int Version::getMinorVersion() const { + if (!hasSemanticVersioning_) { + throw sls::RuntimeError("Could not get minor version from " + + toString()); + } + return version_.minor; +} + +int Version::getPatchVersion() const { + if (!hasSemanticVersioning_) { + throw sls::RuntimeError("Could not get patch version from " + + toString()); + } + return version_.patch; +} + +std::string Version::getPrereleaseVersion() const { + if (!hasSemanticVersioning_) { + throw sls::RuntimeError("Could not get prerelease version from " + + toString()); + } + return version_.prerelease; } bool Version::isBackwardCompatible(const Version &other) const { + if (!hasSemanticVersioning_ || !other.hasSemanticVersioning()) { + throw sls::RuntimeError("Cannot check backward compatibility without " + "semantic versioning for " + + toString() + " and " + other.toString()); + } return getMajorVersion() == other.getMajorVersion(); } +bool Version::operator==(const Version &other) const { + if (hasSemanticVersioning() && other.hasSemanticVersioning()) { + return version_ == other.getSemanticVersion(); + } + return date_ == other.getDate(); +} + bool Version::operator!=(const Version &other) const { return !(*this == other); } -bool Version::operator==(const Version &other) const { - // both have semantic versioning - if (hasSemanticVersioning() && other.hasSemanticVersioning()) { - return version_ == other.getVersion(); - } - // compare dates - return date_ == other.getDate(); -} - bool Version::operator<=(const Version &other) const { - // both have semantic versioning if (hasSemanticVersioning() && other.hasSemanticVersioning()) { - int major = 0, minor = 0, patch = 0; - int otherMajor = 0, otherMinor = 0, otherPatch = 0; - std::string extra; - std::string otherExtra; - - if (Version::parseSemanticVersion(version_, major, minor, patch, - extra) && - Version::parseSemanticVersion(other.getVersion(), otherMajor, - otherMinor, otherPatch, otherExtra)) { - if (major != otherMajor) - return major < otherMajor; - if (minor != otherMinor) - return minor < otherMinor; - if (patch != otherPatch) - return patch < otherPatch; - return extra <= otherExtra; - } - - LOG(logWARNING) << "Could not compare semantic versions: " << version_ - << " and " << other.getVersion() - << ". Comparing dates instead."; + return version_ <= other.getSemanticVersion(); } - // compare dates return date_ <= other.getDate(); } +std::ostream &operator<<(std::ostream &out, const SemanticVersion &v) { + return out << v.toString(); +} + std::ostream &operator<<(std::ostream &out, const Version &v) { - std::ostringstream oss; - oss << v.getVersion() << " " << v.getDate(); - return out << oss.str(); + return out << v.toString(); } } // namespace sls diff --git a/slsSupportLib/tests/test-Version.cpp b/slsSupportLib/tests/test-Version.cpp index e0e465e93..0770137f5 100644 --- a/slsSupportLib/tests/test-Version.cpp +++ b/slsSupportLib/tests/test-Version.cpp @@ -9,7 +9,9 @@ TEST_CASE("check if version is semantic", "[version]") { auto [version_string, has_semantic_version] = GENERATE(std::make_tuple("developer 0x250512", false), - std::make_tuple("0.0.0 0x250512", false)); + std::make_tuple("0.0.0 0x250512", false), + std::make_tuple("10.1.1 0x250512", true), + std::make_tuple("0x250512", false)); Version version(version_string); @@ -24,7 +26,7 @@ TEST_CASE("semantic version ordering compares numerically", "[version]") { CHECK_FALSE(v10 <= v9); } -TEST_CASE("semantic version compares extra suffix lexicographically", +TEST_CASE("semantic version compares prerelease lexicographically", "[version]") { Version rc("10.1.1.rc 0x250512"); Version zz("10.1.1.zz 0x250512"); @@ -33,6 +35,15 @@ TEST_CASE("semantic version compares extra suffix lexicographically", CHECK_FALSE(zz <= rc); } +TEST_CASE("semantic version compares prerelease with no prerelease", + "[version]") { + Version prerelease("10.1.1.rc 0x250512"); + Version release("10.1.1 0x250512"); + + CHECK_FALSE(prerelease <= release); + CHECK(release <= prerelease); +} + TEST_CASE( "semantic comparison falls back to date on malformed semantic version", "[version]") { @@ -43,7 +54,7 @@ TEST_CASE( CHECK_FALSE(valid <= malformed); } -TEST_CASE("semantic version compares extra suffix and without", "[version]") { +TEST_CASE("semantic version compares prerelease and without", "[version]") { Version v1011rc("10.1.1.rc 0x250512"); Version v1010("10.1.0 0x250512"); @@ -51,4 +62,29 @@ TEST_CASE("semantic version compares extra suffix and without", "[version]") { CHECK(v1010 <= v1011rc); } +TEST_CASE("semantic version", "[version]") { + auto version = Version("10.1.1.rc 0x250512"); + CHECK(version.hasSemanticVersioning()); + CHECK(version.getDate() == "0x250512"); + CHECK(version.toString() == "10.1.1.rc 0x250512"); + auto semver = version.getSemanticVersion(); + CHECK(semver.toString() == "10.1.1.rc"); + CHECK(semver.major == 10); + CHECK(semver.minor == 1); + CHECK(semver.patch == 1); + CHECK(semver.prerelease == "rc"); + + version = Version("0.0.0 0x250512"); + CHECK_FALSE(version.hasSemanticVersioning()); + CHECK(version.getDate() == "0x250512"); + CHECK(version.toString() == "0.0.0 0x250512"); + REQUIRE_THROWS(version.getSemanticVersion()); + + version = Version("0x250512"); + CHECK_FALSE(version.hasSemanticVersioning()); + CHECK(version.getDate() == "0x250512"); + CHECK(version.toString() == "0.0.0 0x250512"); + REQUIRE_THROWS(version.getSemanticVersion()); +} + } // namespace sls