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:
M | src/version.cpp | | | 125 | ++++++++++++++++++++++++++++++++++++++++++++++--------------------------------- |
M | src/version.hpp | | | 29 | ++++++++++++++++++----------- |
M | test/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