Collect Version Histories For Vendor Products
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

534 lines
16 KiB

#ifndef CPP_SEMVER_HPP
#define CPP_SEMVER_HPP
#include "base/type.h"
#include "base/util.h"
#ifdef USE_PEGTL
#include "parser/peg.hpp" // PETGTL parser
#define PARSER peg::parser
#else
#include "parser/parser.h"
#define PARSER semver::parser
#endif
#include <string>
#include <vector>
#include <memory>
namespace semver
{
namespace detail
{
/* parse version range in string into syntactical representation @c syntax::range_set */
syntax::range_set parse(const std::string& input)
{
return PARSER(input);
}
/* parse syntactical representation @c syntax::simple and convert it to sementical representation @c semantic::interval */
semantic::interval parse(const syntax::simple& input)
{
// default result * := [min, max]
semantic::interval result;
semantic::boundary b; // associate boundary from input x.y.z-pre
// replace * with 0
b.major = (!input.major.is_wildcard ? input.major.value : 0);
b.minor = (!input.minor.is_wildcard ? input.minor.value : 0);
b.patch = (!input.patch.is_wildcard ? input.patch.value : 0);
b.pre = input.pre;
switch (input.cmp)
{
case syntax::comparator::lt: // <x.y.z-pre := [min, x.y.z-pre)
result.to_inclusive = false;
result.to = b;
if (input.major.is_wildcard ||
input.minor.is_wildcard ||
input.patch.is_wildcard) // <x.*-pre := [min, x.0.0)
result.to.pre = ""; // <x.y.*-pre := [min, x,y.0)
break;
case syntax::comparator::lte: // <=x.y.z.pre := [min, x.y.z-pre]
result.to = b;
if (input.major.is_wildcard ||
input.minor.is_wildcard ||
input.patch.is_wildcard) // <=x.*-pre := [min, 'x+1'.0.0)
{ // <=x.y.*-pre := [min, x,'y+1'.0)
result.to_inclusive = false;
result.to.pre = "";
}
if (input.major.is_wildcard) // <=* := [min, max]
{
result = semantic::interval();
}
else if (input.minor.is_wildcard) // <=x.*-pre := [min, 'x+1'.0.0)
{
result.to.major += 1;
result.to.patch = 0;
}
else if (input.patch.is_wildcard) // <=x.y.*-pre := [min, x,'y+1'.0)
{
result.to.minor += 1;
}
break;
case syntax::comparator::gt: // >x.y.z-pre := (x.y.z-pre, max]
result.from_inclusive = false;
result.from = b;
if (input.major.is_wildcard ||
input.minor.is_wildcard ||
input.patch.is_wildcard) // >x.*-pre := ['x+1', max]
{ // >x.y.*-pre := [x.'y+1', max]
result.from_inclusive = true;
result.from.pre = "";
}
if (input.major.is_wildcard)
{
result.from = semantic::boundary::max(); // invalid set
}
else if (input.minor.is_wildcard) // >x.*-pre := ['x+1'.0.0, max]
{
result.from.major += 1;
result.from.patch = 0;
}
else if (input.patch.is_wildcard) // >x.y.*-pre := [x,'y+1'.0, max]
{
result.from.minor += 1;
}
break;
case syntax::comparator::gte: // >=x.y.z-pre := [x.y.z-pre, max]
result.from = b;
if (input.major.is_wildcard ||
input.minor.is_wildcard ||
input.patch.is_wildcard) // >=x.*-pre := [x.0.0, max]
result.from.pre = ""; // >=x.y.*-pre := [x.y.0, max]
break;
case syntax::comparator::caret:
result.from = b;
result.to = b;
if (input.major.is_wildcard ||
input.minor.is_wildcard ||
input.patch.is_wildcard)
result.from.pre = "";
result.to_inclusive = false;
result.to.pre = "";
if (input.major.is_wildcard) // ^* := [min, max]
{
result = semantic::interval();
}
else if (input.major.value != 0 || // ^x.y.z-pre := [x.y.z-pre, 'x+1'.0.0)
input.minor.is_wildcard) // ^x.y := [x.y.0, 'x+1'.0.0)
{ // ^x := [x.0.0, 'x+1'.0.0)
result.to.major += 1; // ^0 := [0.0.0, 1.0.0)
result.to.minor = 0;
result.to.patch = 0;
}
else if (input.minor.value != 0 || // ^0.y.z := [0.y.z, 0.'y+1'.z)
input.patch.is_wildcard) // ^0.y := [0.y.0, 0.'y+1'.0)
{
result.to.minor += 1;
result.to.patch = 0;
}
else if (input.patch.value != 0)
{
result.to.patch += 1; // ^0.0.z := [0.0.z, 0.0.'z+1')
}
break;
case syntax::comparator::tilde:
result.from = b;
result.to = b;
if (input.major.is_wildcard ||
input.minor.is_wildcard ||
input.patch.is_wildcard)
result.from.pre = "";
result.to_inclusive = false;
result.to.pre = "";
if (input.major.is_wildcard) // ~* := [min, max]
{
result = semantic::interval();
}
else if (input.minor.is_wildcard) // ~x-pre := [x.0.0, 'x+1'.0.0)
{
result.from.pre = "";
result.to.major += 1;
result.to.minor = 0;
result.to.patch = 0;
}
else // ~x.y.z-pre := [x.y.z-pre, x.'y+1'.0)
{
result.to.minor += 1;
result.to.patch = 0;
}
break;
case syntax::comparator::eq:
default:
result.from = b; // x.y.z := [x.y.z, x.y.z]
result.to = b;
if (input.major.is_wildcard ||
input.minor.is_wildcard ||
input.patch.is_wildcard) // =x.*-pre := [x.0.0, 'x+1'.0.0)
{ // =x.y.*-pre := [x.y.0, x.'y+1'.0)
result.from.pre = "";
result.to_inclusive = false;
result.to.pre = "";
}
if (input.major.is_wildcard) // * := [min, max]
{
result = semantic::interval();
}
else if (input.minor.is_wildcard) // x := [x.0.0, 'x+1'.0.0)
{
result.from.patch = 0;
result.to.major += 1;
result.to.patch = 0;
}
else if (input.patch.is_wildcard) // x.y := [x.y.0, x.'y+1'.0)
{
result.to.minor += 1;
}
}
return result;
};
/* calculate AND-conjunction from a list of @c semantic::interval */
std::unique_ptr<semantic::interval> and_conj(const std::vector< semantic::interval >& input)
{
const size_t size = input.size();
if (size == 0)
return nullptr;
semantic::interval result = input.at(0);
for (size_t i = 1; i < size; i++)
{
const semantic::interval& span = input.at(i);
// prerelease tag case
{
const bool result_is_range = (result.from != result.to || result.from_inclusive != result.to_inclusive);
const bool span_is_range = (span.from != span.to || span.from_inclusive != span.to_inclusive);
if (result_is_range != span_is_range)
{
const semantic::interval& range = (result_is_range ? result : span);
const semantic::interval& target = (result_is_range ? span : result);
const bool range_from_has_pre = !range.from.pre.empty();
const bool range_to_has_pre = !range.to.pre.empty();
const bool target_has_pre = !target.from.pre.empty();
if (target_has_pre)
{
// 1-rc not in '0 - 10'
if (!range_from_has_pre && !range_to_has_pre)
return nullptr;
// 1-rc in '1-beta - 10'
if (range_from_has_pre && !range_to_has_pre)
if (range.from.major != target.from.major ||
range.from.minor != target.from.minor ||
range.from.patch != target.from.patch)
return nullptr;
// 10-beta in '1 - 10-rc'
if (!range_from_has_pre && range_to_has_pre)
if (range.to.major != target.to.major ||
range.to.minor != target.to.minor ||
range.to.patch != target.to.patch)
return nullptr;
// 1-rc or 10-beta in '1-beta - 10-rc'
if (range_from_has_pre && range_to_has_pre)
if (!((range.from.major == target.from.major &&
range.from.minor == target.from.minor &&
range.from.patch == target.from.patch) ||
(range.to.major == target.to.major &&
range.to.minor == target.to.minor &&
range.to.patch == target.to.patch)))
return nullptr;
}
}
} // prerelease tag case
if ((span.from > result.from) ||
((span.from == result.from) && !span.from_inclusive))
{
result.from = span.from;
result.from_inclusive = span.from_inclusive;
}
if ((span.to < result.to) ||
((span.to == result.to) && !span.to_inclusive))
{
result.to = span.to;
result.to_inclusive = span.to_inclusive;
}
}
if ((result.from > result.to) ||
((result.from == result.to) && (result.from_inclusive != result.to_inclusive)))
return nullptr;
return std::unique_ptr<semantic::interval>(new semantic::interval(result));
}
/* parse syntactical representation @c syntax::range_set and convert it to sementical representation @c semantic::interval_set */
semantic::interval_set parse(const syntax::range_set& input)
{
semantic::interval_set or_set;
for (const syntax::range& range : input)
{
std::vector<semantic::interval> and_set;
for (const syntax::simple& simple : range)
and_set.emplace_back(parse(simple));
const std::unique_ptr<semantic::interval> conj = and_conj(and_set);
if (conj)
or_set.emplace_back(std::move(*conj));
}
return or_set;
}
/* check if two @c semantic::interval_set intersect with each other */
bool intersects(const semantic::interval_set& s1, const semantic::interval_set& s2)
{
if (s1.empty() || s2.empty())
return false;
for (const semantic::interval& intval_1 : s1)
for (const semantic::interval& intval_2 : s2)
if (and_conj({ intval_1, intval_2 }))
return true;
return false;
}
/* check if two @c syntax::range_set intersect with each other */
bool intersects(const syntax::range_set& rs1, const syntax::range_set& rs2)
{
return intersects(parse(rs1), parse(rs2));
}
/** parse or cast as a syntax::simple if possible */
syntax::simple as_simple(const std::string& s)
{
syntax::range_set parsed = parse(s);
if (!parsed.empty() && !parsed.at(0).empty())
return std::move(parsed.at(0).at(0));
return{};
}
}
// *************** API **************************************** //
/** Return true if the comparator or range intersect */
bool intersects(const std::string& range)
{
return detail::intersects(detail::parse(range), detail::parse("*"));
}
/** Return true if the two supplied ranges or comparators intersect. */
bool intersects(const std::string& range1, const std::string& range2)
{
return detail::intersects(detail::parse(range1), detail::parse(range2));
}
/** Return true if the version satisfies the range */
bool satisfies(const std::string& version, const std::string& range)
{
return detail::intersects(detail::parse(version), detail::parse(range));
}
/** v1 == v2 */
bool eq(const std::string& v1, const std::string& v2)
{
semantic::interval_set interval_set_1 = detail::parse(detail::parse(v1));
semantic::interval_set interval_set_2 = detail::parse(detail::parse(v2));
if (interval_set_1.empty() && interval_set_2.empty())
return true;
else if (interval_set_1.empty() != interval_set_2.empty())
return false;
for (const semantic::interval& interval_1 : interval_set_1)
for (const semantic::interval& interval_2 : interval_set_2)
if (interval_1 == interval_2)
return true;
return false;
}
/** v1 != v2 */
bool neq(const std::string& v1, const std::string& v2)
{
return !eq(v1, v2);
}
/** v1 > v2 */
bool gt(const std::string& v1, const std::string& v2)
{
semantic::interval_set interval_set_1 = detail::parse(detail::parse(v1));
semantic::interval_set interval_set_2 = detail::parse(detail::parse(v2));
if (!interval_set_1.empty() && interval_set_2.empty())
return true;
else if (interval_set_1.empty() || interval_set_2.empty())
return false;
for (const semantic::interval& interval_1 : interval_set_1)
for (const semantic::interval& interval_2 : interval_set_2)
if (interval_1 > interval_2)
return true;
return false;
}
/** v1 >= v2 */
bool gte(const std::string& v1, const std::string& v2)
{
if (eq(v1, v2) || gt(v1, v2))
return true;
return false;
}
/** v1 < v2 */
bool lt(const std::string& v1, const std::string& v2)
{
if (!eq(v1, v2) && !gt(v1, v2))
return true;
return false;
}
/** v1 <= v2 */
bool lte(const std::string& v1, const std::string& v2)
{
if (eq(v1, v2) || lt(v1, v2))
return true;
return false;
}
/** Return true if version is greater than all the versions possible in the range. */
bool gtr(const std::string& version, const std::string& range)
{
semantic::interval_set interval_set_v = detail::parse(detail::parse(version));
semantic::interval_set interval_set_r = detail::parse(detail::parse(range));
if (!interval_set_v.empty() && interval_set_r.empty())
return true;
else if (interval_set_v.empty() || interval_set_r.empty())
return false;
for (const semantic::interval& interval_v : interval_set_v)
for (const semantic::interval& interval_r : interval_set_r)
if (interval_v <= interval_r)
return false;
return true;
}
/** Return true if version is less than all the versions possible in the range. */
bool ltr(const std::string& version, const std::string& range)
{
semantic::interval_set interval_set_v = detail::parse(detail::parse(version));
semantic::interval_set interval_set_r = detail::parse(detail::parse(range));
if (interval_set_v.empty() && !interval_set_r.empty())
return true;
else if (interval_set_v.empty() || interval_set_r.empty())
return false;
for (const semantic::interval& interval_v : interval_set_v)
for (const semantic::interval& interval_r : interval_set_r)
if (interval_v >= interval_r)
return false;
return true;
}
/** Return true if the version or range is valid */
bool valid(const std::string& s)
{
try
{
semantic::interval_set interval_set_s = detail::parse(detail::parse(s));
return !interval_set_s.empty();
}
catch (semver_error const&)
{
return false;
}
}
/** Return the major version number. */
int major(const std::string& version)
{
const auto& major = detail::as_simple(version).major;
return !major.is_wildcard ? major.value : 0;
}
/** Return the minor version number. */
int minor(const std::string& version)
{
const auto& minor = detail::as_simple(version).minor;
return !minor.is_wildcard ? minor.value : 0;
}
/** Return the patch version number. */
int patch(const std::string& version)
{
const auto& patch = detail::as_simple(version).patch;
return !patch.is_wildcard ? patch.value : 0;
}
/** Returns an array of prerelease components. */
std::vector<std::string> prerelease(const std::string& version)
{
const auto& pre = detail::as_simple(version).pre;
if (pre.empty())
return {};
return split(pre, ".");
}
}
#endif