23struct VersionRequirement {
24 enum class Operator : std::uint8_t {
36 [[nodiscard]]
constexpr bool operator==(
Comparator const&)
const noexcept =
default;
39 using ComparatorSet = std::vector<Comparator>;
42 struct PartialVersion {
43 std::optional<std::uint16_t> major;
44 std::optional<std::uint16_t> minor;
45 std::optional<std::uint16_t> patch;
46 std::optional<Version> exact;
47 std::uint8_t precision{};
50 std::vector<ComparatorSet> alternatives{{}};
52 static constexpr bool isWhitespace(
char c)
noexcept {
return c ==
' ' || c ==
'\t' || c ==
'\r' || c ==
'\n'; }
54 static constexpr bool isWildcard(std::string_view value)
noexcept {
55 return value ==
"*" || value ==
"x" || value ==
"X";
58 static constexpr std::string_view trim(std::string_view value)
noexcept {
59 while (!value.empty() && isWhitespace(value.front())) {
60 value.remove_prefix(1);
62 while (!value.empty() && isWhitespace(value.back())) {
63 value.remove_suffix(1);
68 static constexpr bool parseNumber(std::string_view value, std::uint16_t& result)
noexcept {
69 if (value.empty() || (value.size() > 1 && value.front() ==
'0')) {
72 auto parsed = detail::from_chars(value.data(), value.data() + value.size(), result);
73 return parsed && parsed.ptr == value.data() + value.size();
76 static constexpr bool parsePartial(std::string_view value, PartialVersion& result)
noexcept {
82 auto suffix = value.find_first_of(
"-+");
83 if (suffix != std::string_view::npos) {
84 if (!Version::valid(value)) {
87 Version version{value};
88 result.major = version.major;
89 result.minor = version.minor;
90 result.patch = version.patch;
91 result.exact = std::move(version);
96 std::vector<std::string_view> parts;
98 while (begin <= value.size()) {
99 auto end = value.find(
'.', begin);
100 if (end == std::string_view::npos) {
103 parts.emplace_back(value.substr(begin, end - begin));
104 if (end == value.size()) {
109 if (parts.empty() || parts.size() > 3) {
113 bool wildcardFound =
false;
114 for (
size_t i = 0; i < parts.size(); ++i) {
115 if (isWildcard(parts[i])) {
116 wildcardFound =
true;
123 std::uint16_t number{};
124 if (!parseNumber(parts[i], number)) {
128 result.major = number;
130 result.minor = number;
132 result.patch = number;
134 result.precision =
static_cast<std::uint8_t
>(i + 1);
137 if (result.precision == 3) {
138 result.exact = Version{*result.major, *result.minor, *result.patch};
143 static constexpr Version floor(PartialVersion
const& partial)
noexcept {
144 return Version{partial.major.value_or(0), partial.minor.value_or(0), partial.patch.value_or(0)};
147 static constexpr std::optional<Version> nextMajor(Version
const& version)
noexcept {
148 if (version.major == (std::numeric_limits<std::uint16_t>::max)()) {
151 return Version{
static_cast<std::uint16_t
>(version.major + 1), 0, 0};
154 static constexpr std::optional<Version> nextMinor(Version
const& version)
noexcept {
155 if (version.minor != (std::numeric_limits<std::uint16_t>::max)()) {
156 return Version{version.major,
static_cast<std::uint16_t
>(version.minor + 1), 0};
158 return nextMajor(version);
161 static constexpr std::optional<Version> nextPatch(Version
const& version)
noexcept {
162 if (version.patch != (std::numeric_limits<std::uint16_t>::max)()) {
163 return Version{version.major, version.minor,
static_cast<std::uint16_t
>(version.patch + 1)};
165 return nextMinor(version);
168 static constexpr void addComparator(ComparatorSet& set, Operator operation, Version version) {
169 version.build.reset();
170 set.emplace_back(operation, std::move(version));
173 static constexpr void addUpperBound(ComparatorSet& set, std::optional<Version> upper) {
175 addComparator(set, Operator::Less, std::move(*upper));
179 static constexpr bool expandPartial(ComparatorSet& set, PartialVersion
const& partial) {
180 if (!partial.major) {
183 auto lower = floor(partial);
185 addComparator(set, Operator::Equal, *partial.exact);
187 addComparator(set, Operator::GreaterEqual, lower);
188 if (!partial.minor) {
189 addUpperBound(set, nextMajor(lower));
191 addUpperBound(set, nextMinor(lower));
197 static constexpr bool expandComparator(ComparatorSet& set, std::string_view token)
noexcept try {
209 Prefix prefix = Prefix::Bare;
210 if (token.starts_with(
">=")) {
211 prefix = Prefix::GreaterEqual;
212 token.remove_prefix(2);
213 }
else if (token.starts_with(
"<=")) {
214 prefix = Prefix::LessEqual;
215 token.remove_prefix(2);
216 }
else if (token.starts_with(
'>')) {
217 prefix = Prefix::Greater;
218 token.remove_prefix(1);
219 }
else if (token.starts_with(
'<')) {
220 prefix = Prefix::Less;
221 token.remove_prefix(1);
222 }
else if (token.starts_with(
'=')) {
223 prefix = Prefix::Equal;
224 token.remove_prefix(1);
225 }
else if (token.starts_with(
'^')) {
226 prefix = Prefix::Caret;
227 token.remove_prefix(1);
228 }
else if (token.starts_with(
'~')) {
229 prefix = Prefix::Tilde;
230 token.remove_prefix(1);
236 PartialVersion partial;
237 if (!parsePartial(token, partial)) {
240 if (!partial.major) {
241 return prefix == Prefix::Bare || prefix == Prefix::Equal;
244 auto lower = floor(partial);
249 addComparator(set, Operator::GreaterEqual, *partial.exact);
250 addUpperBound(set, nextMajor(*partial.exact));
253 return expandPartial(set, partial);
255 return expandPartial(set, partial);
256 case Prefix::GreaterEqual:
257 addComparator(set, Operator::GreaterEqual, partial.exact.value_or(lower));
259 case Prefix::Greater:
261 addComparator(set, Operator::Greater, *partial.exact);
263 auto upper = !partial.minor ? nextMajor(lower) : nextMinor(lower);
267 addComparator(set, Operator::GreaterEqual, *upper);
271 addComparator(set, Operator::Less, partial.exact.value_or(lower));
273 case Prefix::LessEqual:
275 addComparator(set, Operator::LessEqual, *partial.exact);
277 auto upper = !partial.minor ? nextMajor(lower) : nextMinor(lower);
281 addComparator(set, Operator::Less, *upper);
284 case Prefix::Caret: {
285 addComparator(set, Operator::GreaterEqual, partial.exact.value_or(lower));
286 if (!partial.minor || lower.major != 0) {
287 addUpperBound(set, nextMajor(lower));
288 }
else if (!partial.patch || lower.minor != 0) {
289 addUpperBound(set, nextMinor(lower));
291 addUpperBound(set, nextPatch(lower));
296 addComparator(set, Operator::GreaterEqual, partial.exact.value_or(lower));
297 if (!partial.minor) {
298 addUpperBound(set, nextMajor(lower));
300 addUpperBound(set, nextMinor(lower));
309 static constexpr bool
310 expandHyphen(ComparatorSet& set, std::string_view lowerText, std::string_view upperText)
noexcept try {
311 PartialVersion lowerPartial;
312 PartialVersion upperPartial;
313 if (!parsePartial(lowerText, lowerPartial) || !parsePartial(upperText, upperPartial) || !lowerPartial.major
314 || !upperPartial.major) {
318 addComparator(set, Operator::GreaterEqual, lowerPartial.exact.value_or(floor(lowerPartial)));
319 auto upper = floor(upperPartial);
320 if (upperPartial.exact) {
321 addComparator(set, Operator::LessEqual, *upperPartial.exact);
322 }
else if (!upperPartial.minor) {
323 addUpperBound(set, nextMajor(upper));
325 addUpperBound(set, nextMinor(upper));
332 static constexpr std::vector<std::string_view> tokenize(std::string_view value) {
333 std::vector<std::string_view> result;
335 while (current < value.size()) {
336 while (current < value.size() && isWhitespace(value[current])) {
339 if (current == value.size()) {
342 auto begin = current;
343 while (current < value.size() && !isWhitespace(value[current])) {
346 result.emplace_back(value.substr(begin, current - begin));
352 if (lhs.operation != rhs.operation) {
353 return lhs.operation < rhs.operation;
355 return lhs.version < rhs.version;
358 constexpr void normalize() {
359 for (
auto& set : alternatives) {
360 std::ranges::sort(set, comparatorLess);
361 set.erase(std::unique(set.begin(), set.end()), set.end());
363 std::ranges::sort(alternatives, [](ComparatorSet
const& lhs, ComparatorSet
const& rhs) {
364 return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), comparatorLess);
366 alternatives.erase(std::unique(alternatives.begin(), alternatives.end()), alternatives.end());
369 [[nodiscard]]
static constexpr bool compare(Version
const& candidate,
Comparator const& comparator)
noexcept {
370 switch (comparator.operation) {
371 case Operator::Equal:
372 return candidate == comparator.version;
373 case Operator::Greater:
374 return candidate > comparator.version;
375 case Operator::GreaterEqual:
376 return candidate >= comparator.version;
378 return candidate < comparator.version;
379 case Operator::LessEqual:
380 return candidate <= comparator.version;
385 [[nodiscard]]
static constexpr bool allowsPreRelease(ComparatorSet
const& set, Version
const& candidate)
noexcept {
386 if (!candidate.preRelease) {
389 return std::ranges::any_of(set, [&](
Comparator const& comparator) {
390 auto const& version = comparator.version;
391 return version.preRelease && version.major == candidate.major && version.minor == candidate.minor
392 && version.patch == candidate.patch;
397 constexpr VersionRequirement() =
default;
398 constexpr ~VersionRequirement() =
default;
399 explicit constexpr VersionRequirement(std::string_view str) { from_string(str); }
401 [[nodiscard]]
constexpr detail::from_chars_result from_chars(
char const* first,
char const* last)
noexcept {
402 if (first ==
nullptr || last ==
nullptr || first > last) {
403 return {first, std::errc::invalid_argument};
406 std::string_view input{first,
static_cast<size_t>(last - first)};
407 std::vector<ComparatorSet> parsedAlternatives;
409 if (trim(input).empty()) {
410 parsedAlternatives.emplace_back();
413 while (begin <= input.size()) {
414 auto end = input.find(
"||", begin);
415 if (end == std::string_view::npos) {
418 auto group = trim(input.substr(begin, end - begin));
420 return {first + begin, std::errc::invalid_argument};
423 auto tokens = tokenize(group);
424 ComparatorSet comparators;
425 for (
size_t i = 0; i < tokens.size();) {
426 if (i + 2 < tokens.size() && tokens[i + 1] ==
"-") {
427 if (!expandHyphen(comparators, tokens[i], tokens[i + 2])) {
428 return {first + begin, std::errc::invalid_argument};
434 std::string combined;
435 auto token = tokens[i];
436 if ((token ==
">" || token ==
">=" || token ==
"<" || token ==
"<=" || token ==
"="
437 || token ==
"^" || token ==
"~")
438 && i + 1 < tokens.size()) {
439 combined = std::string{token} + std::string{tokens[++i]};
442 if (!expandComparator(comparators, token)) {
443 return {first + begin, std::errc::invalid_argument};
447 parsedAlternatives.emplace_back(std::move(comparators));
448 if (end == input.size()) {
455 alternatives = std::move(parsedAlternatives);
457 return {last, std::errc{}};
459 return {first, std::errc::not_enough_memory};
463 [[nodiscard]]
constexpr detail::from_chars_result from_string_noexcept(std::string_view str)
noexcept {
464 return from_chars(str.data(), str.data() + str.size());
467 constexpr VersionRequirement& from_string(std::string_view str) {
468 from_string_noexcept(str).value();
472 [[nodiscard]]
constexpr bool matches(Version
const& version)
const noexcept {
473 return std::ranges::any_of(alternatives, [&](ComparatorSet
const& set) {
474 return allowsPreRelease(set, version)
475 && std::ranges::all_of(set, [&](
Comparator const& comparator) {
return compare(version, comparator); });
479 [[nodiscard]]
constexpr std::vector<ComparatorSet>
const& comparatorSets() const noexcept {
return alternatives; }
481 [[nodiscard]]
constexpr std::string to_string()
const {
482 auto operatorString = [](Operator operation) -> std::string_view {
484 case Operator::Equal:
486 case Operator::Greater:
488 case Operator::GreaterEqual:
492 case Operator::LessEqual:
499 for (
auto const& set : alternatives) {
500 if (!result.empty()) {
507 for (
auto const& comparator : set) {
508 if (!result.empty() && !result.ends_with(
" || ")) {
511 result += operatorString(comparator.operation);
512 result += comparator.version.to_string();
518 [[nodiscard]]
constexpr bool operator==(VersionRequirement
const& other)
const noexcept {
519 return alternatives == other.alternatives;
522 [[nodiscard]]
static constexpr bool valid(std::string_view str)
noexcept {
523 return VersionRequirement{}.from_string_noexcept(str);