reapack

Package manager for REAPER
Log | Files | Refs | Submodules | README | LICENSE

commit 785babf9f396615309b0307c1c28e5d76adb5e81
parent f79b8e1f0fbe1b0c0019989f68069107e8880f7a
Author: cfillion <cfillion@users.noreply.github.com>
Date:   Wed, 20 Apr 2016 01:36:47 -0400

rewrite the version parser to support prereleases

inspired from RubyGems

Diffstat:
Msrc/version.cpp | 125++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Msrc/version.hpp | 29++++++++++++++++++-----------
Mtest/version.cpp | 261+++++++++++++++++++++++++++++++++++++++----------------------------------------
3 files changed, 219 insertions(+), 196 deletions(-)

diff --git a/src/version.cpp b/src/version.cpp @@ -21,26 +21,26 @@ #include "package.hpp" #include "source.hpp" -#include <algorithm> -#include <cmath> +#include <boost/lexical_cast.hpp> +#include <cctype> #include <regex> using namespace std; Version::Version() - : m_code(0), m_time(), m_package(nullptr), m_mainSource(nullptr) + : m_prerelease(false), m_time(), m_package(nullptr), m_mainSource(nullptr) { } Version::Version(const string &str, const Package *pkg) - : m_code(0), m_time(), m_package(pkg), m_mainSource(nullptr) + : m_time(), m_package(pkg), m_mainSource(nullptr) { parse(str); } Version::Version(const Version &o, const Package *pkg) - : m_name(o.name()), m_code(o.code()), - m_author(o.author()), m_changelog(o.changelog()), m_time(o.time()), + : m_name(o.m_name), m_segments(o.m_segments), m_prerelease(o.m_prerelease), + m_author(o.m_author), m_changelog(o.m_changelog), m_time(o.m_time), m_package(pkg), m_mainSource(nullptr) { } @@ -53,30 +53,39 @@ Version::~Version() void Version::parse(const string &str) { - static const regex pattern("(\\d+)"); - - auto begin = sregex_iterator(str.begin(), str.end(), pattern); - auto end = sregex_iterator(); - - // set the major version by default - // even if there are less than 4 numeric components in the string - const size_t size = max((size_t)4, (size_t)distance(begin, end)); + static const regex pattern("\\d+|[a-zA-Z]+"); + + const auto &begin = sregex_iterator(str.begin(), str.end(), pattern); + const sregex_iterator end; + + size_t numeric = 0, alpha = 0; + vector<Segment> segments; + + for(sregex_iterator it = begin; it != end; it++) { + const string match = it->str(0); + const char first = tolower(match[0]); + + if(first >= 'a' || first >= 'z') { + segments.push_back(match); + alpha++; + } + else { + try { + segments.push_back(boost::lexical_cast<uint64_t>(match)); + numeric++; + } + catch(const boost::bad_lexical_cast &) { + throw reapack_error("version segment overflow"); + } + } + } - if(begin == end || size > 4L) + if(!numeric) throw reapack_error("invalid version name"); - size_t index = 0; - - for(sregex_iterator it = begin; it != end; it++, index++) { - const string match = it->str(1); - - if(match.size() > 4) - throw reapack_error("version component overflow"); - - m_code += stoi(match) * (Code)pow(10000, size - index - 1); - } - m_name = str; + swap(m_segments, segments); + m_prerelease = alpha > 0; } bool Version::tryParse(const string &str) @@ -183,32 +192,44 @@ const Source *Version::source(const size_t index) const return it->second; } -bool Version::operator<(const Version &o) const -{ - return m_code < o.code(); -} - -bool Version::operator<=(const Version &o) const -{ - return !(m_code > o.code()); -} - -bool Version::operator>(const Version &o) const -{ - return m_code > o.code(); -} - -bool Version::operator>=(const Version &o) const -{ - return !(m_code < o.code()); -} - -bool Version::operator==(const Version &o) const +auto Version::segment(const size_t index) const -> Segment { - return m_code == o.code(); -} + if(index < size()) + return m_segments[index]; + else + return 0; +} + +int Version::compare(const Version &o) const +{ + const size_t biggest = max(size(), o.size()); + + for(size_t i = 0; i < biggest; i++) { + const Segment &lseg = segment(i); + const uint64_t *lnum = boost::get<uint64_t>(&lseg); + const string *lstr = boost::get<string>(&lseg); + + const Segment &rseg = o.segment(i); + const uint64_t *rnum = boost::get<uint64_t>(&rseg); + const string *rstr = boost::get<string>(&rseg); + + if(lnum && rnum) { + if(*lnum < *rnum) + return -1; + else if(*lnum > *rnum) + return 1; + } + else if(lstr && rstr) { + if(*lstr < *rstr) + return -1; + else if(*lstr > *rstr) + return 1; + } + else if(lnum && rstr) + return 1; + else if(lstr && rnum) + return -1; + } -bool Version::operator!=(const Version &o) const -{ - return !(*this == o); + return 0; } diff --git a/src/version.hpp b/src/version.hpp @@ -18,6 +18,7 @@ #ifndef REAPACK_VERSION_HPP #define REAPACK_VERSION_HPP +#include <boost/variant.hpp> #include <cstdint> #include <ctime> #include <map> @@ -31,7 +32,6 @@ class Path; class Version { public: - typedef uint64_t Code; typedef std::vector<Source *> SourceList; typedef std::multimap<Path, const Source *> SourceMap; @@ -45,7 +45,8 @@ public: const std::string &name() const { return m_name; } std::string fullName() const; - uint64_t code() const { return m_code; } + size_t size() const { return m_segments.size(); } + bool isPrerelease() const { return m_prerelease; } const Package *package() const { return m_package; } @@ -67,16 +68,22 @@ public: const std::set<Path> &files() const { return m_files; } - bool operator<(const Version &) const; - bool operator<=(const Version &) const; - bool operator>(const Version &) const; - bool operator>=(const Version &) const; - bool operator==(const Version &) const; - bool operator!=(const Version &) const; + int compare(const Version &) const; + bool operator<(const Version &o) const { return compare(o) < 0; } + bool operator<=(const Version &o) const { return compare(o) <= 0; } + bool operator>(const Version &o) const { return compare(o) > 0; } + bool operator>=(const Version &o) const { return compare(o) >= 0; } + bool operator==(const Version &o) const { return compare(o) == 0; } + bool operator!=(const Version &o) const { return compare(o) != 0; } private: + typedef boost::variant<uint64_t, std::string> Segment; + + Segment segment(size_t i) const; + std::string m_name; - Code m_code; + std::vector<Segment> m_segments; + bool m_prerelease; std::string m_author; std::string m_changelog; @@ -89,7 +96,7 @@ private: std::set<Path> m_files; }; -class VersionCompare { +class VersionPtrCompare { public: bool operator()(const Version *l, const Version *r) const { @@ -97,6 +104,6 @@ public: } }; -typedef std::set<const Version *, VersionCompare> VersionSet; +typedef std::set<const Version *, VersionPtrCompare> VersionSet; #endif diff --git a/test/version.cpp b/test/version.cpp @@ -25,52 +25,68 @@ static Source *mksource(Source::Platform p, Version *parent) static const char *M = "[version]"; -TEST_CASE("invalid", M) { - try { - Version ver("hello"); - FAIL(); +TEST_CASE("construct null version", M) { + const Version ver; + + REQUIRE(ver.size() == 0); + REQUIRE_FALSE(ver.isPrerelease()); + REQUIRE(ver.displayTime().empty()); + REQUIRE(ver.package() == nullptr); + REQUIRE(ver.mainSource() == nullptr); +} + +TEST_CASE("parse version", M) { + Version ver; + + SECTION("valid") { + ver.parse("1.0.1"); + REQUIRE(ver.name() == "1.0.1"); + REQUIRE(ver.size() == 3); } - catch(const reapack_error &e) { - REQUIRE(string(e.what()) == "invalid version name"); + + SECTION("prerelease set/unset") { + REQUIRE_FALSE(ver.isPrerelease()); + ver.parse("1.0beta"); + REQUIRE(ver.isPrerelease()); + ver.parse("1.0"); + REQUIRE_FALSE(ver.isPrerelease()); } -} -TEST_CASE("major minor patch version", M) { - Version ver("1.2.3"); - REQUIRE(ver.name() == "1.2.3"); - REQUIRE(ver.code() == UINT64_C(1000200030000)); -} + SECTION("invalid") { + try { ver.parse("hello"); FAIL(); } + catch(const reapack_error &) {} -TEST_CASE("major minor version", M) { - Version ver("1.2"); - REQUIRE(ver.name() == "1.2"); - REQUIRE(ver.code() == UINT64_C(1000200000000)); + REQUIRE(ver.name().empty()); + REQUIRE(ver.size() == 0); + } } -TEST_CASE("major version", M) { - Version ver("1"); - REQUIRE(ver.name() == "1"); - REQUIRE(ver.code() == UINT64_C(1000000000000)); -} +TEST_CASE("parse version failsafe", M) { + Version ver; -TEST_CASE("version with string suffix", M) { - Version ver("1.2pre3"); - REQUIRE(ver.name() == "1.2pre3"); - REQUIRE(ver.code() == UINT64_C(1000200030000)); -} + SECTION("valid") { + REQUIRE(ver.tryParse("1.0")); -TEST_CASE("version with 4 components", M) { - Version ver("1.2.3.4"); - REQUIRE(ver.name() == "1.2.3.4"); - REQUIRE(ver.code() == UINT64_C(1000200030004)); - REQUIRE(ver < Version("1.2.4")); + REQUIRE(ver.name() == "1.0"); + REQUIRE(ver.size() == 2); + } + + SECTION("invalid") { + REQUIRE_FALSE(ver.tryParse("hello")); + + REQUIRE(ver.name().empty()); + REQUIRE(ver.size() == 0); + } } -TEST_CASE("version with repeated digits", M) { - Version ver("1.1.1"); - REQUIRE(ver.name() == "1.1.1"); - REQUIRE(ver.code() == UINT64_C(1000100010000)); - REQUIRE(ver < Version("1.1.2")); +TEST_CASE("construct invalid version", M) { + try { + Version ver("hello"); + FAIL(); + } + catch(const reapack_error &e) { + REQUIRE(string(e.what()) == "invalid version name"); + } } TEST_CASE("decimal version", M) { @@ -79,29 +95,88 @@ TEST_CASE("decimal version", M) { REQUIRE(ver < Version("5.50")); } -TEST_CASE("4 digits version component", M) { - Version ver("0.2015.12.25"); - REQUIRE(ver.name() == "0.2015.12.25"); - REQUIRE(ver.code() == UINT64_C(201500120025)); +TEST_CASE("5 version segments", M) { + REQUIRE(Version("1.1.1.1.0") < Version("1.1.1.1.1")); + REQUIRE(Version("1.1.1.1.1") == Version("1.1.1.1.1")); + REQUIRE(Version("1.1.1.1.1") < Version("1.1.1.1.2")); + REQUIRE(Version("1.1.1.1.1") < Version("1.1.1.2.0")); } -TEST_CASE("5 digits version component", M) { +TEST_CASE("version segment overflow", M) { try { - Version ver("12345.1"); + Version ver("9999999999999999999999"); FAIL(); } catch(const reapack_error &e) { - REQUIRE(string(e.what()) == "version component overflow"); + REQUIRE(string(e.what()) == "version segment overflow"); } } -TEST_CASE("version with 5 components", M) { - try { - Version ver("1.2.3.4.5"); - FAIL(); +TEST_CASE("compare versions", M) { + SECTION("equality") { + REQUIRE(Version("1.0").compare(Version("1.0")) == 0); + + REQUIRE(Version("1.0") == Version("1.0")); + REQUIRE_FALSE(Version("1.0") == Version("1.1")); } - catch(const reapack_error &e) { - REQUIRE(string(e.what()) == "invalid version name"); + + SECTION("inequality") { + REQUIRE_FALSE(Version("1.0") != Version("1.0")); + REQUIRE(Version("1.0") != Version("1.1")); + } + + SECTION("less than") { + REQUIRE(Version("1.0").compare(Version("1.1")) == -1); + + REQUIRE(Version("1.0") < Version("1.1")); + REQUIRE_FALSE(Version("1.0") < Version("1.0")); + REQUIRE_FALSE(Version("1.1") < Version("1.0")); + } + + SECTION("less than or equal") { + REQUIRE(Version("1.0") <= Version("1.1")); + REQUIRE(Version("1.0") <= Version("1.0")); + REQUIRE_FALSE(Version("1.1") <= Version("1.0")); + } + + SECTION("greater than") { + REQUIRE(Version("1.1").compare(Version("1.0")) == 1); + + REQUIRE_FALSE(Version("1.0") > Version("1.1")); + REQUIRE_FALSE(Version("1.0") > Version("1.0")); + REQUIRE(Version("1.1") > Version("1.0")); + } + + SECTION("greater than or equal") { + REQUIRE_FALSE(Version("1.0") >= Version("1.1")); + REQUIRE(Version("1.0") >= Version("1.0")); + REQUIRE(Version("1.1") >= Version("1.0")); + } +} + +TEST_CASE("compare versions with more or less segments", M) { + REQUIRE(Version("1") == Version("1.0.0.0")); + REQUIRE(Version("1") != Version("1.0.0.1")); + + REQUIRE(Version("1.0.0.0") == Version("1")); + REQUIRE(Version("1.0.0.1") != Version("1")); +} + +TEST_CASE("prerelease versions", M) { + SECTION("detect") { + REQUIRE_FALSE(Version("1.0").isPrerelease()); + REQUIRE(Version("1.0b").isPrerelease()); + REQUIRE(Version("1.0-beta").isPrerelease()); + REQUIRE(Version("1.0-beta1").isPrerelease()); + } + + SECTION("compare") { + REQUIRE(Version("0.9") < Version("1.0a")); + REQUIRE(Version("1.0a.2") < Version("1.0b.1")); + REQUIRE(Version("1.0-beta1") < Version("1.0")); + + REQUIRE(Version("1.0b") < Version("1.0.1")); + REQUIRE(Version("1.0.1") > Version("1.0b")); } } @@ -236,62 +311,18 @@ TEST_CASE("version date", M) { } } -TEST_CASE("construct null version", M) { - const Version ver; - - REQUIRE(ver.code() == 0); - REQUIRE(ver.displayTime().empty()); - REQUIRE(ver.package() == nullptr); - REQUIRE(ver.mainSource() == nullptr); -} - -TEST_CASE("parse version", M) { - Version ver; - - SECTION("valid") { - ver.parse("1.0"); - REQUIRE(ver.name() == "1.0"); - REQUIRE(ver.code() == UINT64_C(1000000000000)); - } - - SECTION("invalid") { - try { ver.parse("hello"); FAIL(); } - catch(const reapack_error &) {} - - REQUIRE(ver.name().empty()); - REQUIRE(ver.code() == 0); - } -} - -TEST_CASE("parse version failsafe", M) { - Version ver; - - SECTION("valid") { - REQUIRE(ver.tryParse("1.0")); - - REQUIRE(ver.name() == "1.0"); - REQUIRE(ver.code() == UINT64_C(1000000000000)); - } - - SECTION("invalid") { - REQUIRE_FALSE(ver.tryParse("hello")); - - REQUIRE(ver.name().empty()); - REQUIRE(ver.code() == 0); - } -} - TEST_CASE("copy version constructor", M) { const Package pkg(Package::UnknownType, "Hello"); - Version original("1.1", &pkg); + Version original("1.1b", &pkg); original.setAuthor("John Doe"); original.setChangelog("Initial release"); original.setTime("2016-02-12T01:16:40Z"); const Version copy1(original); - REQUIRE(copy1.name() == "1.1"); - REQUIRE(copy1.code() == original.code()); + REQUIRE(copy1.name() == "1.1b"); + REQUIRE(copy1.size() == original.size()); + REQUIRE(copy1.isPrerelease() == original.isPrerelease()); REQUIRE(copy1.author() == original.author()); REQUIRE(copy1.changelog() == original.changelog()); REQUIRE(copy1.displayTime() == original.displayTime()); @@ -303,42 +334,6 @@ TEST_CASE("copy version constructor", M) { REQUIRE(copy2.package() == &pkg); } -TEST_CASE("version operators", M) { - SECTION("equality") { - REQUIRE(Version("1.0") == Version("1.0")); - REQUIRE_FALSE(Version("1.0") == Version("1.1")); - } - - SECTION("inequality") { - REQUIRE_FALSE(Version("1.0") != Version("1.0")); - REQUIRE(Version("1.0") != Version("1.1")); - } - - SECTION("less than") { - REQUIRE(Version("1.0") < Version("1.1")); - REQUIRE_FALSE(Version("1.0") < Version("1.0")); - REQUIRE_FALSE(Version("1.1") < Version("1.0")); - } - - SECTION("less than or equal") { - REQUIRE(Version("1.0") <= Version("1.1")); - REQUIRE(Version("1.0") <= Version("1.0")); - REQUIRE_FALSE(Version("1.1") <= Version("1.0")); - } - - SECTION("greater than") { - REQUIRE_FALSE(Version("1.0") > Version("1.1")); - REQUIRE_FALSE(Version("1.0") > Version("1.0")); - REQUIRE(Version("1.1") > Version("1.0")); - } - - SECTION("greater than or equal") { - REQUIRE_FALSE(Version("1.0") >= Version("1.1")); - REQUIRE(Version("1.0") >= Version("1.0")); - REQUIRE(Version("1.1") >= Version("1.0")); - } -} - #ifdef __APPLE__ TEST_CASE("drop windows sources on os x", M) { MAKE_VERSION