13#ifndef PLUGIFY_STRING_NO_STD_FORMAT
14# include "compat_format.hpp"
21 enum struct prerelease : uint8_t {
29 constexpr operator bool()
const noexcept {
return ec == std::errc{}; }
33 constexpr operator bool()
const noexcept {
return ec == std::errc{}; }
37 inline constexpr auto max_version_string_length = std::size_t{29};
40 inline constexpr auto alpha = std::string_view{
"alpha", 5};
41 inline constexpr auto beta = std::string_view{
"beta", 4};
42 inline constexpr auto rc = std::string_view{
"rc", 2};
45 inline constexpr auto min_version_string_length = 5;
47 constexpr char to_lower(
char c)
noexcept {
48 return (c >=
'A' && c <=
'Z') ?
static_cast<char>(c + (
'a' -
'A')) : c;
51 constexpr bool is_digit(
char c)
noexcept {
52 return c >=
'0' && c <=
'9';
55 constexpr bool is_space(
char c)
noexcept {
59 constexpr bool is_operator(
char c)
noexcept {
60 return c ==
'<' || c ==
'>' || c ==
'=';
63 constexpr bool is_dot(
char c)
noexcept {
67 constexpr bool is_logical_or(
char c)
noexcept {
71 constexpr bool is_hyphen(
char c)
noexcept {
75 constexpr bool is_letter(
char c)
noexcept {
76 return (c >=
'A' && c <=
'Z') || (c >=
'a' && c <=
'z');
79 constexpr uint16_t to_digit(
char c)
noexcept {
80 return static_cast<uint16_t
>(c -
'0');
83 constexpr uint8_t length(uint16_t x)
noexcept {
99 constexpr uint8_t length(prerelease t)
noexcept {
100 if (t == prerelease::alpha) {
101 return static_cast<uint8_t
>(alpha.length());
102 }
else if (t == prerelease::beta) {
103 return static_cast<uint8_t
>(beta.length());
104 }
else if (t == prerelease::rc) {
105 return static_cast<uint8_t
>(rc.length());
111 constexpr bool equals(
const char* first,
const char* last, std::string_view str)
noexcept {
112 for (std::size_t i = 0; first != last && i < str.length(); ++i, ++first) {
113 if (to_lower(*first) != to_lower(str[i])) {
121 constexpr char* to_chars(
char* str, uint16_t x,
bool dot =
true) noexcept {
123 *(--str) =
static_cast<char>(
'0' + (x % 10));
134 constexpr char* to_chars(
char* str, prerelease t)
noexcept {
135 const auto p = t == prerelease::alpha
137 : t == prerelease::beta
139 : t == prerelease::rc
141 : std::string_view{};
144 for (
auto it = p.rbegin(); it != p.rend(); ++it) {
153 constexpr const char* from_chars(
const char* first,
const char* last, uint16_t& d)
noexcept {
154 if (first != last && is_digit(*first)) {
156 for (; first != last && is_digit(*first); ++first) {
157 t = t * 10 + to_digit(*first);
159 if (t <= (std::numeric_limits<uint16_t>::max)()) {
160 d =
static_cast<uint16_t
>(t);
168 constexpr const char* from_chars(
const char* first,
const char* last, std::optional<uint16_t>& d)
noexcept {
169 if (first != last && is_digit(*first)) {
171 for (; first != last && is_digit(*first); ++first) {
172 t = t * 10 + to_digit(*first);
174 if (t <= (std::numeric_limits<uint16_t>::max)()) {
175 d =
static_cast<uint16_t
>(t);
183 constexpr const char* from_chars(
const char* first,
const char* last, prerelease& p)
noexcept {
184 if (is_hyphen(*first)) {
188 if (equals(first, last, alpha)) {
189 p = prerelease::alpha;
190 return first + alpha.length();
191 }
else if (equals(first, last, beta)) {
192 p = prerelease::beta;
193 return first + beta.length();
194 }
else if (equals(first, last, rc)) {
196 return first + rc.length();
202 constexpr bool check_delimiter(
const char* first,
const char* last,
char d)
noexcept {
203 return first != last && first !=
nullptr && *first == d;
206 template <
typename T,
typename =
void>
208 static auto resize(
T&
str, std::size_t size) -> std::void_t<
decltype(
str.resize(size))> {
213 template <
typename T>
215 static void resize(
T&
str, std::size_t size) {
216 str.__resize_default_init(size);
226 prerelease prerelease_type = prerelease::none;
227 std::optional<uint16_t> prerelease_number = std::nullopt;
232 prerelease
prt = prerelease::none,
233 std::optional<uint16_t>
prn = std::nullopt)
noexcept
237 prerelease_type{
prt},
238 prerelease_number{
prt == prerelease::none ? std::nullopt :
prn} {
249 prerelease_type{
prt},
250 prerelease_number{
prt == prerelease::none ? std::nullopt : std::make_optional<uint16_t>(
prn)} {
253 explicit constexpr version(std::string_view
str) :
version(0, 0, 0, prerelease::none, std::nullopt) {
270 if (
first ==
nullptr ||
last ==
nullptr || (
last -
first) < detail::min_version_string_length) {
271 return {
first, std::errc::invalid_argument};
278 prerelease_type = prerelease::none;
279 prerelease_number = {};
280 return {
next, std::errc{}};
281 }
else if (detail::check_delimiter(
next,
last,
'-')) {
283 prerelease_number = {};
284 return {
next, std::errc{}};
285 }
else if (detail::check_delimiter(
next,
last,
'.')) {
287 return {
next, std::errc{}};
294 return {
first, std::errc::invalid_argument};
298 const auto length = string_length();
300 return {
last, std::errc::value_too_large};
304 if (prerelease_type != prerelease::none) {
305 if (prerelease_number.has_value()) {
306 next = detail::to_chars(
next, prerelease_number.value());
308 next = detail::to_chars(
next, prerelease_type);
310 next = detail::to_chars(
next, patch);
311 next = detail::to_chars(
next, minor);
312 next = detail::to_chars(
next, major,
false);
314 return {
first + length, std::errc{}};
317 constexpr bool from_string_noexcept(std::string_view
str)
noexcept {
318 return from_chars(
str.data(),
str.data() +
str.length());
321 constexpr version& from_string(std::string_view
str) {
322 PLUGIFY_ASSERT(from_string_noexcept(
str),
"plg::version::from_string invalid version.", std::invalid_argument);
326 std::string to_string_noexcept()
const noexcept {
329 if (to_chars(
str.data(),
str.data() +
str.length())) {
335 std::string to_string()
const {
338 PLUGIFY_ASSERT(to_chars(
str.data(),
str.data() +
str.length()),
"plg::version::to_string invalid version.", std::invalid_argument);
342 constexpr uint8_t string_length()
const noexcept {
344 auto length = detail::length(major) + detail::length(minor) + detail::length(patch) + 2;
345 if (prerelease_type != prerelease::none) {
347 length += detail::length(prerelease_type) + 1;
348 if (prerelease_number.has_value()) {
350 length += detail::length(prerelease_number.value()) + 1;
354 return static_cast<uint8_t>(length);
357 constexpr int compare(
const version&
other)
const noexcept {
358 if (major !=
other.major) {
359 return major -
other.major;
362 if (minor !=
other.minor) {
363 return minor -
other.minor;
366 if (patch !=
other.patch) {
367 return patch -
other.patch;
370 if (prerelease_type !=
other.prerelease_type) {
371 return static_cast<uint8_t>(prerelease_type) -
static_cast<uint8_t>(
other.prerelease_type);
374 if (prerelease_number.has_value()) {
375 if (
other.prerelease_number.has_value()) {
376 return prerelease_number.value() -
other.prerelease_number.value();
379 }
else if (
other.prerelease_number.has_value()) {
388 return lhs.compare(
rhs) == 0;
391 constexpr bool operator!=(
const version& lhs,
const version& rhs)
noexcept {
392 return lhs.compare(rhs) != 0;
395 constexpr bool operator>(
const version& lhs,
const version& rhs)
noexcept {
396 return lhs.compare(rhs) > 0;
399 constexpr bool operator>=(
const version& lhs,
const version& rhs)
noexcept {
400 return lhs.compare(rhs) >= 0;
403 constexpr bool operator<(
const version& lhs,
const version& rhs)
noexcept {
404 return lhs.compare(rhs) < 0;
407 constexpr bool operator<=(
const version& lhs,
const version& rhs)
noexcept {
408 return lhs.compare(rhs) <= 0;
411#if __cpp_impl_three_way_comparison
412 constexpr std::strong_ordering operator<=>(
const version& lhs,
const version& rhs) {
413 int compare = lhs.compare(rhs);
415 return std::strong_ordering::equal;
417 return std::strong_ordering::greater;
418 return std::strong_ordering::less;
422 constexpr version
operator""_version(
const char* str, std::size_t length) {
423 return version{std::string_view{str, length}};
426 constexpr bool valid(std::string_view str)
noexcept {
427 return version{}.from_string_noexcept(str);
430 constexpr from_chars_result from_chars(
const char* first,
const char* last, version& v)
noexcept {
431 return v.from_chars(first, last);
434 constexpr to_chars_result to_chars(
char* first,
char* last,
const version& v)
noexcept {
435 return v.to_chars(first, last);
438 constexpr std::optional<version> from_string_noexcept(std::string_view str)
noexcept {
439 if (version v{}; v.from_string_noexcept(str)) {
446 constexpr version from_string(std::string_view str) {
450 inline std::string to_string(
const version& v) {
451 return v.to_string();
454 template <
typename Char,
typename Traits>
455 inline std::basic_ostream<Char, Traits>& operator<<(std::basic_ostream<Char, Traits>& os,
const version& v) {
456 for (
const auto c : v.to_string()) {
463 inline namespace comparators {
464 enum struct comparators_option : uint8_t {
469 constexpr int compare(
const version& lhs,
const version& rhs, comparators_option option = comparators_option::include_prerelease)
noexcept {
470 if (option == comparators_option::exclude_prerelease) {
471 return version{lhs.major, lhs.minor, lhs.patch}.compare(version{rhs.major, rhs.minor, rhs.patch});
473 return lhs.compare(rhs);
476 constexpr bool equal_to(
const version& lhs,
const version& rhs, comparators_option option = comparators_option::include_prerelease)
noexcept {
477 return compare(lhs, rhs, option) == 0;
480 constexpr bool not_equal_to(
const version& lhs,
const version& rhs, comparators_option option = comparators_option::include_prerelease)
noexcept {
481 return compare(lhs, rhs, option) != 0;
484 constexpr bool greater(
const version& lhs,
const version& rhs, comparators_option option = comparators_option::include_prerelease)
noexcept {
485 return compare(lhs, rhs, option) > 0;
488 constexpr bool greater_equal(
const version& lhs,
const version& rhs, comparators_option option = comparators_option::include_prerelease)
noexcept {
489 return compare(lhs, rhs, option) >= 0;
492 constexpr bool less(
const version& lhs,
const version& rhs, comparators_option option = comparators_option::include_prerelease)
noexcept {
493 return compare(lhs, rhs, option) < 0;
496 constexpr bool less_equal(
const version& lhs,
const version& rhs, comparators_option option = comparators_option::include_prerelease)
noexcept {
497 return compare(lhs, rhs, option) <= 0;
503 using namespace plg::detail;
509 constexpr bool satisfies(
const version& ver,
bool include_prerelease) {
510 const bool has_prerelease = ver.prerelease_type != prerelease::none;
513 if (is_logical_or_token()) {
514 parser.advance_token(range_token_type::logical_or);
517 bool contains =
true;
520 while (is_operator_token() || is_number_token()) {
538 }
else if (contains) {
542 }
while (is_logical_or_token());
548 enum struct range_operator :
uint8_t {
556 struct range_comparator {
562 case range_operator::equal:
564 case range_operator::greater:
566 case range_operator::greater_or_equal:
568 case range_operator::less:
570 case range_operator::less_or_equal:
573 PLUGIFY_ASSERT(
false,
"plg::range unexpected operator.", std::invalid_argument);
578 enum struct range_token_type :
uint8_t {
590 range_token_type type = range_token_type::none;
592 range_operator op = range_operator::equal;
593 prerelease prerelease_type = prerelease::none;
597 std::string_view text;
600 constexpr explicit range_lexer(std::string_view
_text)
noexcept : text{
_text}, pos{0} {}
602 constexpr range_token get_next_token()
noexcept {
603 while (!end_of_line()) {
605 if (is_space(text[pos])) {
610 if (is_logical_or(text[pos])) {
612 return {range_token_type::logical_or};
615 if (is_operator(text[pos])) {
616 const auto op = get_operator();
617 return {range_token_type::range_operator, 0, op};
620 if (is_digit(text[pos])) {
621 const auto number = get_number();
622 return {range_token_type::number, number};
625 if (is_dot(text[pos])) {
627 return {range_token_type::dot};
630 if (is_hyphen(text[pos])) {
632 return {range_token_type::hyphen};
635 if (is_letter(text[pos])) {
636 const auto prerelease = get_prerelease();
637 return {range_token_type::prerelease, 0, range_operator::equal, prerelease};
641 return {range_token_type::end_of_line};
644 constexpr bool end_of_line()
const noexcept {
return pos >= text.length(); }
646 constexpr void advance(std::size_t
i)
noexcept {
650 constexpr range_operator get_operator()
noexcept {
651 if (text[pos] ==
'<') {
653 if (text[pos] ==
'=') {
655 return range_operator::less_or_equal;
657 return range_operator::less;
658 }
else if (text[pos] ==
'>') {
660 if (text[pos] ==
'=') {
662 return range_operator::greater_or_equal;
664 return range_operator::greater;
665 }
else if (text[pos] ==
'=') {
667 return range_operator::equal;
670 return range_operator::equal;
673 constexpr uint16_t get_number()
noexcept {
674 const auto first = text.data() + pos;
675 const auto last = text.data() + text.length();
684 constexpr prerelease get_prerelease()
noexcept {
685 const auto first = text.data() + pos;
686 const auto last = text.data() + text.length();
689 return prerelease::none;
692 if (prerelease
p{}; from_chars(
first,
last,
p) !=
nullptr) {
699 return prerelease::none;
703 struct range_parser {
705 range_token current_token;
707 constexpr explicit range_parser(std::string_view
str) : lexer{
str}, current_token{range_token_type::none} {
708 advance_token(range_token_type::none);
711 constexpr void advance_token(range_token_type
token_type) {
712 PLUGIFY_ASSERT(current_token.type ==
token_type,
"plg::range unexpected token.", std::invalid_argument);
713 current_token = lexer.get_next_token();
716 constexpr range_comparator parse_range() {
717 if (current_token.type == range_token_type::number) {
718 const auto version = parse_version();
719 return {range_operator::equal,
version};
720 }
else if (current_token.type == range_token_type::range_operator) {
721 const auto range_operator = current_token.op;
722 advance_token(range_token_type::range_operator);
723 const auto version = parse_version();
724 return {range_operator,
version};
727 return {range_operator::equal,
version{}};
730 constexpr version parse_version() {
731 const auto major = parse_number();
733 advance_token(range_token_type::dot);
734 const auto minor = parse_number();
736 advance_token(range_token_type::dot);
737 const auto patch = parse_number();
739 prerelease prerelease = prerelease::none;
740 std::optional<uint16_t> prerelease_number = std::nullopt;
742 if (current_token.type == range_token_type::hyphen) {
743 advance_token(range_token_type::hyphen);
744 prerelease = parse_prerelease();
745 if (current_token.type == range_token_type::dot) {
746 advance_token(range_token_type::dot);
747 prerelease_number = parse_number();
751 return {major, minor, patch, prerelease, prerelease_number};
755 const auto token = current_token;
756 advance_token(range_token_type::number);
761 constexpr prerelease parse_prerelease() {
762 const auto token = current_token;
763 advance_token(range_token_type::prerelease);
765 return token.prerelease_type;
769 constexpr bool is_logical_or_token()
const noexcept {
770 return parser.current_token.type == range_token_type::logical_or;
773 constexpr bool is_operator_token()
const noexcept {
774 return parser.current_token.type == range_token_type::range_operator;
777 constexpr bool is_number_token()
const noexcept {
778 return parser.current_token.type == range_token_type::number;
786 enum struct satisfies_option :
uint8_t {
791 constexpr bool satisfies(
const version& ver, std::string_view
str, satisfies_option
option = satisfies_option::exclude_prerelease) {
793 case satisfies_option::exclude_prerelease:
794 return detail::range{
str}.satisfies(ver,
false);
795 case satisfies_option::include_prerelease:
796 return detail::range{
str}.satisfies(ver,
true);
798 PLUGIFY_ASSERT(
false,
"plg::range unexpected satisfies_option.", std::invalid_argument);
804#ifndef PLUGIFY_STRING_NO_STD_FORMAT
806#ifdef FMT_HEADER_ONLY
813 constexpr auto parse(std::format_parse_context&
ctx) {
817 template<
class FormatContext>
819 return std::format_to(
ctx.out(),
"{}", ver.to_string_noexcept());
Represents the absence of a value.