Uses: Niebler/range-v3 | Uses: Niebler/range-v3 | ||||
Uses: nlohmann/json | Uses: nlohmann/json | ||||
Uses: neo/buffer | Uses: neo/buffer | ||||
Uses: neo/sqlite3 | |||||
Uses: neo/sqlite3 | |||||
Uses: semver/semver |
Depends: range-v3 0.9.1 | Depends: range-v3 0.9.1 | ||||
Depends: nlohmann-json 3.7.1 | Depends: nlohmann-json 3.7.1 | ||||
Depends: neo-sqlite3 0.2.0 | Depends: neo-sqlite3 0.2.0 | ||||
Depends: semver 0.1.0 | |||||
Test-Driver: Catch-Main | Test-Driver: Catch-Main |
# XXX: Don't depend on a moving revision! | # XXX: Don't depend on a moving revision! | ||||
Remote-Package: neo-buffer 0.1.0; git url=https://github.com/vector-of-bool/neo-buffer.git ref=develop | Remote-Package: neo-buffer 0.1.0; git url=https://github.com/vector-of-bool/neo-buffer.git ref=develop | ||||
Remote-Package: neo-sqlite3 0.2.0; git url=https://github.com/vector-of-bool/neo-sqlite3.git ref=0.2.0 | Remote-Package: neo-sqlite3 0.2.0; git url=https://github.com/vector-of-bool/neo-sqlite3.git ref=0.2.0 | ||||
Remote-Package: semver 0.1.0; git url=https://github.com/vector-of-bool/semver.git ref=0.1.0 |
#include <dds/util/fs.hpp> | #include <dds/util/fs.hpp> | ||||
#include <string> | #include <string> | ||||
#include <vector> | |||||
#include <string_view> | #include <string_view> | ||||
namespace dds { | namespace dds { |
#pragma once | |||||
#include <semver/ident.hpp> | |||||
namespace semver { | |||||
class build_metadata { | |||||
std::vector<ident> _ids; | |||||
public: | |||||
build_metadata() = default; | |||||
[[nodiscard]] bool empty() const noexcept { return _ids.empty(); } | |||||
void add_ident(ident id) noexcept { _ids.push_back(id); } | |||||
void add_ident(std::string_view s) { add_ident(ident(s)); } | |||||
auto& idents() const noexcept { return _ids; } | |||||
static build_metadata parse(std::string_view s) { | |||||
build_metadata ret; | |||||
ret._ids = ident::parse_dotted_seq(s); | |||||
return ret; | |||||
} | |||||
}; | |||||
} // namespace semver |
#include "./ident.hpp" | |||||
#include <cassert> | |||||
#include <charconv> | |||||
using namespace semver; | |||||
ident::ident(std::string_view str) { | |||||
const auto str_begin = str.data(); | |||||
auto ptr = str_begin; | |||||
const auto str_end = str_begin + str.size(); | |||||
bool any_alpha = false; | |||||
if (ptr == str_end) { | |||||
throw invalid_ident(std::string(str)); | |||||
} | |||||
for (; ptr != str_end; ++ptr) { | |||||
any_alpha = any_alpha || (*ptr == '-' || std::isalpha(*ptr)); | |||||
if (!std::isalnum(*ptr) && *ptr != '-') { | |||||
break; | |||||
} | |||||
} | |||||
if (ptr != str_end) { | |||||
throw invalid_ident(std::string(str)); | |||||
} | |||||
_str.assign(str_begin, ptr); | |||||
if (any_alpha) { | |||||
_kind = ident_kind::alphanumeric; | |||||
} else if (_str.size() > 1 && _str[0] == '0') { | |||||
_kind = ident_kind::digits; | |||||
} else { | |||||
_kind = ident_kind::numeric; | |||||
// Check that the integer is representable on this system | |||||
std::uint64_t n; | |||||
auto res = std::from_chars(str_begin, str_end, n); | |||||
if (res.ptr != str_end) { | |||||
throw invalid_ident(_str); | |||||
} | |||||
} | |||||
} | |||||
std::vector<ident> ident::parse_dotted_seq(const std::string_view s) { | |||||
std::vector<ident> acc; | |||||
auto remaining = s; | |||||
while (!remaining.empty()) { | |||||
auto next_dot = remaining.find('.'); | |||||
auto id_sub = remaining.substr(0, next_dot); | |||||
if (id_sub.empty()) { | |||||
throw invalid_ident(std::string(s)); | |||||
} | |||||
acc.emplace_back(id_sub); | |||||
if (next_dot == remaining.npos) { | |||||
break; | |||||
} | |||||
remaining = remaining.substr(next_dot + 1); | |||||
if (remaining.empty()) { | |||||
throw invalid_ident(std::string(s)); | |||||
} | |||||
} | |||||
if (acc.empty()) { | |||||
throw invalid_ident(std::string(s)); | |||||
} | |||||
return acc; | |||||
} | |||||
order semver::compare(ident_kind lhs, ident_kind rhs) noexcept { | |||||
assert(lhs != ident_kind::digits && "Ordering with digit identifiers is undefined"); | |||||
assert(rhs != ident_kind::digits && "Ordering with digit identifiers is undefined"); | |||||
if (lhs == rhs) { | |||||
return order::equivalent; | |||||
} else if (lhs == ident_kind::numeric) { | |||||
// numeric is less than alnum | |||||
return order::less; | |||||
} else { | |||||
return order::greater; | |||||
} | |||||
} | |||||
order semver::compare(const ident& lhs, const ident& rhs) noexcept { | |||||
auto ord = compare(lhs.kind(), rhs.kind()); | |||||
if (ord != order::equivalent) { | |||||
return ord; | |||||
} | |||||
assert(lhs.kind() == rhs.kind() && "[semver library bug]"); | |||||
if (lhs.kind() == ident_kind::numeric) { | |||||
auto as_int = [](auto& str) { | |||||
std::uint64_t value; | |||||
auto fc_res = std::from_chars(str.data(), str.data() + str.size(), value); | |||||
assert(fc_res.ptr == str.data() + str.size()); | |||||
return value; | |||||
}; | |||||
auto lhs_num = as_int(lhs.string()); | |||||
auto rhs_num = as_int(rhs.string()); | |||||
if (lhs_num == rhs_num) { | |||||
return order::equivalent; | |||||
} else if (lhs_num < rhs_num) { | |||||
return order::less; | |||||
} else { | |||||
return order::greater; | |||||
} | |||||
} | |||||
assert(lhs.kind() == ident_kind::alphanumeric && "[semver library bug]"); | |||||
auto comp = lhs.string().compare(rhs.string()); | |||||
if (comp == 0) { | |||||
return order::equivalent; | |||||
} else if (comp < 0) { | |||||
return order::less; | |||||
} else { | |||||
return order::greater; | |||||
} | |||||
} |
#pragma once | |||||
#include <semver/order.hpp> | |||||
#include <stdexcept> | |||||
#include <string> | |||||
#include <string_view> | |||||
#include <vector> | |||||
namespace semver { | |||||
class invalid_ident : public std::runtime_error { | |||||
std::string _str; | |||||
public: | |||||
invalid_ident(std::string s) | |||||
: std::runtime_error::runtime_error("Invalid metadata identifier: " + s) | |||||
, _str(s) {} | |||||
auto& string() const noexcept { return _str; } | |||||
}; | |||||
enum class ident_kind { | |||||
alphanumeric, | |||||
numeric, | |||||
digits, | |||||
}; | |||||
order compare(ident_kind lhs, ident_kind rhs) noexcept; | |||||
class ident; | |||||
order compare(const ident& lhs, const ident& rhs) noexcept; | |||||
class ident { | |||||
std::string _str; | |||||
ident_kind _kind; | |||||
public: | |||||
explicit ident(std::string_view str); | |||||
auto kind() const noexcept { return _kind; } | |||||
const auto& string() const noexcept { return _str; } | |||||
#define DEF_OP(op, expr) \ | |||||
inline friend bool operator op(const ident& lhs, const ident& rhs) noexcept { \ | |||||
auto o = compare(lhs, rhs); \ | |||||
return (expr); \ | |||||
} \ | |||||
static_assert(true) | |||||
DEF_OP(==, (o == order::equivalent)); | |||||
DEF_OP(!=, (o != order::equivalent)); | |||||
DEF_OP(<, (o == order::less)); | |||||
DEF_OP(>, (o == order::greater)); | |||||
DEF_OP(<=, (o == order::less || o == order::equivalent)); | |||||
DEF_OP(>=, (o == order::greater || o == order::equivalent)); | |||||
#undef DEF_OP | |||||
static std::vector<ident> parse_dotted_seq(std::string_view s); | |||||
}; | |||||
} // namespace semver |
#include <semver/ident.hpp> | |||||
#include <catch2/catch.hpp> | |||||
TEST_CASE("Parse identifiers") { | |||||
struct case_ { | |||||
std::string str; | |||||
semver::ident_kind expect_kind; | |||||
}; | |||||
case_ cases[] = { | |||||
{"foo", semver::ident_kind::alphanumeric}, | |||||
{"12", semver::ident_kind::numeric}, | |||||
{"03412", semver::ident_kind::digits}, | |||||
{"0", semver::ident_kind::numeric}, | |||||
{"00", semver::ident_kind::digits}, | |||||
}; | |||||
for (auto [str, expect] : cases) { | |||||
INFO("Checking parsing of '" << str << "'"); | |||||
auto id = semver::ident(str); | |||||
CHECK(id.string() == str); | |||||
CHECK(id.kind() == expect); | |||||
} | |||||
} | |||||
TEST_CASE("Invalid identifiers") { | |||||
#define BAD_IDENT(x) CHECK_THROWS_AS(semver::ident(x), semver::invalid_ident) | |||||
BAD_IDENT(""); | |||||
BAD_IDENT("="); | |||||
BAD_IDENT("asdf-_"); | |||||
BAD_IDENT("."); | |||||
BAD_IDENT(" "); | |||||
BAD_IDENT("124a["); | |||||
} | |||||
TEST_CASE("Ident comparison") { | |||||
using semver::order; | |||||
struct comp_test { | |||||
std::string lhs; | |||||
std::string rhs; | |||||
order expect_ordering; | |||||
}; | |||||
comp_test comparisons[] = { | |||||
{"foo", "bar", order::greater}, | |||||
{"foo", "foo", order::equivalent}, | |||||
{"bar", "foo", order::less}, | |||||
{"12", "333", order::less}, | |||||
{"fooood", "3", order::greater}, | |||||
{"0", "0", order::equivalent}, | |||||
{"34", "f", order::less}, | |||||
{"aaaaaaaaaaa", "z", order::less}, | |||||
}; | |||||
for (auto [lhs, rhs, expect] : comparisons) { | |||||
auto lhs_id = semver::ident(lhs); | |||||
auto rhs_id = semver::ident(rhs); | |||||
INFO("Comparing '" << lhs << "' to '" << rhs << "'"); | |||||
auto result = semver::compare(lhs_id, rhs_id); | |||||
CHECK(result == expect); | |||||
} | |||||
} | |||||
TEST_CASE("Parse dotted sequence") { | |||||
struct case_ { | |||||
std::string str; | |||||
std::vector<std::string> expected; | |||||
}; | |||||
case_ cases[] = { | |||||
{"foo", {"foo"}}, | |||||
{"foo.bar", {"foo", "bar"}}, | |||||
}; | |||||
for (auto& [str, expected] : cases) { | |||||
INFO("Parsing dotted-ident-sequence '" << str << "'"); | |||||
auto actual = semver::ident::parse_dotted_seq(str); | |||||
REQUIRE(actual.size() == expected.size()); | |||||
} | |||||
} | |||||
TEST_CASE("Invalid dotted sequence") { | |||||
std::string_view bad_seqs[] = { | |||||
"", | |||||
".", | |||||
".foo", | |||||
"foo.bar.", | |||||
"foo..bar", | |||||
}; | |||||
for (auto s : bad_seqs) { | |||||
INFO("Checking bad ident sequence '" << s << "'"); | |||||
CHECK_THROWS_AS(semver::ident::parse_dotted_seq(s), semver::invalid_ident); | |||||
} | |||||
} |
#pragma once | |||||
namespace semver { | |||||
enum class order { | |||||
less = -1, | |||||
equivalent = 0, | |||||
greater = 1, | |||||
}; | |||||
} // namespace semver |
#include "./prerelease.hpp" | |||||
using namespace semver; | |||||
prerelease prerelease::parse(std::string_view s) { | |||||
auto ids = ident::parse_dotted_seq(s); | |||||
for (auto& id : ids) { | |||||
if (id.kind() == ident_kind::digits) { | |||||
throw invalid_ident("Prerelease strings may not have plain-digit identifiers: " | |||||
+ std::string(s)); | |||||
} | |||||
} | |||||
prerelease ret; | |||||
ret._ids = std::move(ids); | |||||
return ret; | |||||
} | |||||
order semver::compare(const prerelease& lhs, const prerelease& rhs) noexcept { | |||||
auto lhs_iter = lhs.idents().cbegin(); | |||||
auto rhs_iter = rhs.idents().cbegin(); | |||||
const auto lhs_end = lhs.idents().cend(); | |||||
const auto rhs_end = rhs.idents().cend(); | |||||
for (; lhs_iter != lhs_end && rhs_iter != rhs_end; ++lhs_iter, ++rhs_iter) { | |||||
auto ord = compare(*lhs_iter, *rhs_iter); | |||||
if (ord != order::equivalent) { | |||||
return ord; | |||||
} | |||||
} | |||||
if (lhs_iter != lhs_end) { | |||||
// Left-hand is longer | |||||
return order::greater; | |||||
} else if (rhs_iter != rhs_end) { | |||||
// Right-hand is longer | |||||
return order::less; | |||||
} else { | |||||
// They are equivalent | |||||
return order::equivalent; | |||||
} | |||||
} |
#pragma once | |||||
#include <semver/ident.hpp> | |||||
#include <semver/order.hpp> | |||||
#include <stdexcept> | |||||
#include <string_view> | |||||
#include <vector> | |||||
namespace semver { | |||||
class prerelease; | |||||
order compare(const prerelease& lhs, const prerelease& rhs) noexcept; | |||||
class prerelease { | |||||
std::vector<ident> _ids; | |||||
public: | |||||
prerelease() = default; | |||||
[[nodiscard]] bool empty() const noexcept { return _ids.empty(); } | |||||
void add_ident(ident id); | |||||
void add_ident(std::string_view str) { add_ident(ident(str)); } | |||||
auto& idents() const noexcept { return _ids; } | |||||
static prerelease parse(std::string_view str); | |||||
#define DEF_OP(op, expr) \ | |||||
inline friend bool operator op(const prerelease& lhs, const prerelease& rhs) noexcept { \ | |||||
auto o = compare(lhs, rhs); \ | |||||
return (expr); \ | |||||
} \ | |||||
static_assert(true) | |||||
DEF_OP(==, (o == order::equivalent)); | |||||
DEF_OP(!=, (o != order::equivalent)); | |||||
DEF_OP(<, (o == order::less)); | |||||
DEF_OP(>, (o == order::greater)); | |||||
DEF_OP(<=, (o == order::less || o == order::equivalent)); | |||||
DEF_OP(>=, (o == order::greater || o == order::equivalent)); | |||||
#undef DEF_OP | |||||
}; | |||||
} // namespace semver |
#include "./prerelease.hpp" | |||||
#include <catch2/catch.hpp> | |||||
TEST_CASE("Parse a prerelease") { | |||||
auto pre = semver::prerelease::parse("foo.bar"); | |||||
CHECK(pre.idents() == semver::ident::parse_dotted_seq("foo.bar")); | |||||
pre = pre.parse("foo.0"); | |||||
CHECK(pre.idents() == semver::ident::parse_dotted_seq("foo.0")); | |||||
} | |||||
TEST_CASE("Invalid prereleases") { | |||||
std::string_view bad_tags[] = { | |||||
"foo.", // Just plain bad | |||||
"foo.122.03", // Leading zero | |||||
}; | |||||
for (auto bad : bad_tags) { | |||||
CHECK_THROWS_AS(semver::prerelease::parse(bad), semver::invalid_ident); | |||||
} | |||||
} | |||||
TEST_CASE("Compare prereleases") { | |||||
using semver::order; | |||||
struct case_ { | |||||
std::string lhs; | |||||
std::string rhs; | |||||
order expected_ord; | |||||
}; | |||||
case_ cases[] = { | |||||
{"foo.bar", "foo.bar", order::equivalent}, | |||||
{"foo", "foo.bar", order::less}, | |||||
{"foo.bar", "foo", order::greater}, | |||||
{"foo", "foo", order::equivalent}, | |||||
{"foo.1", "foo.bar", order::less}, // numeric is less | |||||
{"foo.foo", "foo.bar", order::greater}, | |||||
{"alpha", "beta", order::less}, | |||||
{"alpha.1", "alpha.2", order::less}, | |||||
}; | |||||
for (auto& [lhs, rhs, exp] : cases) { | |||||
INFO("Comparing prerelease '" << lhs << "' to '" << rhs << "'"); | |||||
auto lhs_pre = semver::prerelease::parse(lhs); | |||||
auto rhs_pre = semver::prerelease::parse(rhs); | |||||
auto actual = semver::compare(lhs_pre, rhs_pre); | |||||
CHECK(actual == exp); | |||||
} | |||||
} |
#include "./version.hpp" | |||||
#include <algorithm> | |||||
#include <array> | |||||
#include <cassert> | |||||
#include <charconv> | |||||
using namespace semver; | |||||
using siter = std::string_view::iterator; | |||||
namespace { | |||||
version parse(const char* ptr, std::size_t size) { | |||||
version ret; | |||||
// const auto str_begin = ptr; | |||||
const auto str_begin = ptr; | |||||
const auto str_end = ptr + size; | |||||
auto get_str = [=] { return std::string(str_begin, size); }; | |||||
auto cur_off = [&] { return ptr - str_begin; }; | |||||
std::from_chars_result fc_res; | |||||
auto did_error = [&](int elem) { return fc_res.ec == std::errc::invalid_argument || elem < 0; }; | |||||
// Parse major | |||||
fc_res = std::from_chars(ptr, str_end, ret.major); | |||||
ptr = fc_res.ptr; | |||||
if (did_error(ret.major) || fc_res.ptr == str_end || *fc_res.ptr != '.') { | |||||
throw invalid_version(get_str(), cur_off()); | |||||
} | |||||
// Parse minor | |||||
ptr = fc_res.ptr + 1; | |||||
fc_res = std::from_chars(ptr, str_end, ret.minor); | |||||
ptr = fc_res.ptr; | |||||
if (did_error(ret.minor) || fc_res.ptr == str_end || *fc_res.ptr != '.') { | |||||
throw invalid_version(get_str(), cur_off()); | |||||
} | |||||
// Parse patch | |||||
ptr = fc_res.ptr + 1; | |||||
fc_res = std::from_chars(ptr, str_end, ret.patch); | |||||
ptr = fc_res.ptr; | |||||
if (did_error(ret.patch)) { | |||||
throw invalid_version(get_str(), cur_off()); | |||||
} | |||||
auto remaining = std::string_view(ptr, str_end - ptr); | |||||
if (!remaining.empty() && remaining[0] == '-') { | |||||
auto plus_pos = remaining.find('+'); | |||||
auto prerelease_str = remaining.substr(1, plus_pos - 1); | |||||
ret.prerelease = prerelease::parse(prerelease_str); | |||||
remaining = remaining.substr(prerelease_str.size() + 1); | |||||
} | |||||
if (!remaining.empty() && remaining[0] == '+') { | |||||
auto bmeta_str = remaining.substr(1); | |||||
ret.build_metadata = build_metadata::parse(bmeta_str); | |||||
remaining = remaining.substr(bmeta_str.size() + 1); | |||||
} | |||||
if (!remaining.empty()) { | |||||
throw invalid_version(get_str(), remaining.data() - str_begin); | |||||
} | |||||
return ret; | |||||
} // namespace | |||||
} // namespace | |||||
version version::parse(std::string_view s) { return ::parse(s.data(), s.size()); } | |||||
std::string version::to_string() const noexcept { | |||||
std::array<char, 256> buffer; | |||||
const auto buf_begin = buffer.data(); | |||||
auto buf_ptr = buf_begin; | |||||
const auto buf_end = buf_ptr + buffer.size(); | |||||
auto conv_one = [&](auto n) { | |||||
auto tc_res = std::to_chars(buf_ptr, buf_end, n); | |||||
if (tc_res.ec == std::errc::value_too_large || tc_res.ptr == buf_end) { | |||||
assert(false && "Our buffers weren't big enough! This is a bug!"); | |||||
std::terminate(); | |||||
} | |||||
buf_ptr = tc_res.ptr; | |||||
}; | |||||
conv_one(major); | |||||
*buf_ptr++ = '.'; | |||||
conv_one(minor); | |||||
*buf_ptr++ = '.'; | |||||
conv_one(patch); | |||||
auto main_ver = std::string(buf_begin, (buf_ptr - buf_begin)); | |||||
auto format_ids = [](auto& ids) { | |||||
std::string acc; | |||||
auto it = ids.cbegin(); | |||||
auto stop = ids.cend(); | |||||
while (it != stop) { | |||||
acc += it->string(); | |||||
++it; | |||||
if (it != stop) { | |||||
acc += "."; | |||||
} | |||||
} | |||||
return acc; | |||||
}; | |||||
if (!prerelease.empty()) { | |||||
main_ver += "-" + format_ids(prerelease.idents()); | |||||
} | |||||
if (!build_metadata.empty()) { | |||||
main_ver += "+" + format_ids(build_metadata.idents()); | |||||
} | |||||
return main_ver; | |||||
} | |||||
order semver::compare(const version& lhs, const version& rhs) noexcept { | |||||
auto lhs_tup = std::tie(lhs.major, lhs.minor, lhs.patch); | |||||
auto rhs_tup = std::tie(rhs.major, rhs.minor, rhs.patch); | |||||
if (lhs_tup < rhs_tup) { | |||||
return order::less; | |||||
} else if (lhs_tup > rhs_tup) { | |||||
return order::greater; | |||||
} else { | |||||
if (!lhs.is_prerelease() && rhs.is_prerelease()) { | |||||
// No prerelease is greater than any prerelease | |||||
return order::greater; | |||||
} else if (lhs.is_prerelease() && !rhs.is_prerelease()) { | |||||
// A prerelease version is lesser than any non-prerelease version | |||||
return order::less; | |||||
} else { | |||||
// Compare the prerelease tags | |||||
return compare(lhs.prerelease, rhs.prerelease); | |||||
} | |||||
} | |||||
} |
#pragma once | |||||
#include <semver/build_metadata.hpp> | |||||
#include <semver/prerelease.hpp> | |||||
#include <stdexcept> | |||||
#include <string> | |||||
#include <string_view> | |||||
#include <tuple> | |||||
#include <vector> | |||||
namespace semver { | |||||
class invalid_version : public std::runtime_error { | |||||
std::string _string; | |||||
std::ptrdiff_t _offset = 0; | |||||
public: | |||||
invalid_version(std::string string, std::ptrdiff_t n) | |||||
: runtime_error("Invalid semantic version: " + string) | |||||
, _string(string) | |||||
, _offset(n) {} | |||||
auto& string() const noexcept { return _string; } | |||||
auto offset() const noexcept { return _offset; } | |||||
}; | |||||
class version; | |||||
order compare(const version& lhs, const version& rhs) noexcept; | |||||
struct version { | |||||
int major = 0; | |||||
int minor = 0; | |||||
int patch = 0; | |||||
// Prerelease tag is optional: | |||||
class prerelease prerelease = {}; | |||||
// Build metadata is optional: | |||||
class build_metadata build_metadata = {}; | |||||
static version parse(std::string_view s); | |||||
std::string to_string() const noexcept; | |||||
bool is_prerelease() const noexcept { return !prerelease.empty(); } | |||||
#define DEF_OP(op, expr) \ | |||||
inline friend bool operator op(const version& lhs, const version& rhs) noexcept { \ | |||||
auto o = compare(lhs, rhs); \ | |||||
return (expr); \ | |||||
} \ | |||||
static_assert(true) | |||||
DEF_OP(==, (o == order::equivalent)); | |||||
DEF_OP(!=, (o != order::equivalent)); | |||||
DEF_OP(<, (o == order::less)); | |||||
DEF_OP(>, (o == order::greater)); | |||||
DEF_OP(<=, (o == order::less || o == order::equivalent)); | |||||
DEF_OP(>=, (o == order::greater || o == order::equivalent)); | |||||
#undef DEF_OP | |||||
friend inline std::string to_string(const version& ver) noexcept { return ver.to_string(); } | |||||
}; | |||||
} // namespace semver |
#include "./version.hpp" | |||||
#include <catch2/catch.hpp> | |||||
TEST_CASE("Parsing") { | |||||
auto v1 = semver::version::parse("1.2.3"); | |||||
CHECK(v1.major == 1); | |||||
CHECK(v1.minor == 2); | |||||
CHECK(v1.patch == 3); | |||||
CHECK(v1.to_string() == "1.2.3"); | |||||
v1.patch = 55; | |||||
CHECK(v1.to_string() == "1.2.55"); | |||||
v1.major = 999999; | |||||
CHECK(v1.to_string() == "999999.2.55"); | |||||
v1 = semver::version::parse("1.2.3-r1"); | |||||
CHECK(v1.prerelease == semver::prerelease::parse("r1")); | |||||
CHECK(v1.to_string() == "1.2.3-r1"); | |||||
v1 = semver::version::parse("1.2.3-3"); | |||||
CHECK(v1.prerelease == semver::prerelease::parse("3")); | |||||
v1 = semver::version::parse("1.2.3-0"); | |||||
CHECK(v1.prerelease == semver::prerelease::parse("0")); | |||||
v1 = semver::version::parse("1.2.3--fasdf"); | |||||
CHECK(v1.prerelease == semver::prerelease::parse("-fasdf")); | |||||
v1 = semver::version::parse("1.2.3-foo.bar"); | |||||
CHECK(v1.prerelease == semver::prerelease::parse("foo.bar")); | |||||
v1 = semver::version::parse("1.2.3-foo.bar+cats"); | |||||
CHECK(v1.prerelease == semver::prerelease::parse("foo.bar")); | |||||
CHECK(v1.build_metadata.idents() == semver::ident::parse_dotted_seq("cats")); | |||||
} | |||||
TEST_CASE("Compare versions") { | |||||
using semver::order; | |||||
struct case_ { | |||||
std::string_view lhs; | |||||
std::string_view rhs; | |||||
order expect_ord; | |||||
}; | |||||
case_ cases[] = { | |||||
{"1.2.3", "1.2.3", order::equivalent}, | |||||
{"1.2.3-alpha", "1.2.3-alpha", order::equivalent}, | |||||
{"1.2.3-alpha", "1.2.3", order::less}, | |||||
{"1.2.3-alpha.1", "1.2.3-beta", order::less}, | |||||
{"1.2.3-alpha.1", "1.2.3-alpha.2", order::less}, | |||||
{"1.2.3-alpha.4", "1.2.3-alpha.2", order::greater}, | |||||
{"1.2.1", "1.2.2-alpha.2", order::less}, | |||||
{"1.2.1", "1.2.1+foo", order::equivalent}, // Build metadata has no effect | |||||
}; | |||||
for (auto [lhs, rhs, exp] : cases) { | |||||
INFO("Compare version '" << lhs << "' to '" << rhs << "'"); | |||||
auto lhs_ver = semver::version::parse(lhs); | |||||
auto rhs_ver = semver::version::parse(rhs); | |||||
auto actual = semver::compare(lhs_ver, rhs_ver); | |||||
CHECK(actual == exp); | |||||
} | |||||
} | |||||
TEST_CASE("Invalid versions") { | |||||
struct invalid_version { | |||||
std::string str; | |||||
int bad_offset; | |||||
}; | |||||
invalid_version versions[] = { | |||||
{"", 0}, | |||||
{"1.", 2}, | |||||
{"1.2", 3}, | |||||
{"1.2.", 4}, | |||||
{"1.e", 2}, | |||||
{"lol", 0}, | |||||
{"1e.3.1", 1}, | |||||
{"1.2.5-", 6}, | |||||
{"1.2.5-02", 6}, | |||||
{"1.2.3-1..3", 8}, | |||||
}; | |||||
for (auto&& [str, bad_offset] : versions) { | |||||
INFO("Checking for failure while parsing bad version string '" << str << "'"); | |||||
try { | |||||
auto ver = semver::version::parse(str); | |||||
FAIL_CHECK("Parsing didn't throw! Produced version: " << ver.to_string()); | |||||
} catch (const semver::invalid_version& e) { | |||||
CHECK(e.offset() == bad_offset); | |||||
} catch (const semver::invalid_ident&) { | |||||
// Nothing to check, but a valid error | |||||
} | |||||
} | |||||
} |
Compiler-ID: GNU | Compiler-ID: GNU | ||||
C++-Version: C++17 | C++-Version: C++17 | ||||
C-Compiler: gcc-8 | |||||
C++-Compiler: g++-8 | |||||
C-Compiler: gcc-9 | |||||
C++-Compiler: g++-9 | |||||
Flags: -D SPDLOG_COMPILED_LIB -fconcepts -Werror=return-type | Flags: -D SPDLOG_COMPILED_LIB -fconcepts -Werror=return-type | ||||
Optimize: True | |||||
# Debug: True | |||||
# Optimize: True | |||||
Debug: True | |||||
# Link-Flags: -fsanitize=address | # Link-Flags: -fsanitize=address | ||||
Link-Flags: -fuse-ld=lld | |||||
Compiler-Launcher: ccache | Compiler-Launcher: ccache |