Mercurial > minori
view dep/semver/semver.hpp @ 348:6b0768158dcd
text: redesign almost every widget
i.e. Paragraph is now a QLabel, etc etc, some things will probably
break, idc
author | Paper <paper@paper.us.eu.org> |
---|---|
date | Tue, 25 Jun 2024 11:19:54 -0400 |
parents | b9f111d84d95 |
children |
line wrap: on
line source
// _____ _ _ // / ____| | | (_) // | (___ ___ _ __ ___ __ _ _ __ | |_ _ ___ // \___ \ / _ \ '_ ` _ \ / _` | '_ \| __| |/ __| // ____) | __/ | | | | | (_| | | | | |_| | (__ // |_____/ \___|_| |_| |_|\__,_|_| |_|\__|_|\___| // __ __ _ _ _____ // \ \ / / (_) (_) / ____|_ _ // \ \ / /__ _ __ ___ _ ___ _ __ _ _ __ __ _ | | _| |_ _| |_ // \ \/ / _ \ '__/ __| |/ _ \| '_ \| | '_ \ / _` | | | |_ _|_ _| // \ / __/ | \__ \ | (_) | | | | | | | | (_| | | |____|_| |_| // \/ \___|_| |___/_|\___/|_| |_|_|_| |_|\__, | \_____| // https://github.com/Neargye/semver __/ | // version 0.3.0 |___/ // // Licensed under the MIT License <http://opensource.org/licenses/MIT>. // SPDX-License-Identifier: MIT // Copyright (c) 2018 - 2021 Daniil Goncharov <neargye@gmail.com>. // Copyright (c) 2020 - 2021 Alexander Gorbunov <naratzul@gmail.com>. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #ifndef NEARGYE_SEMANTIC_VERSIONING_HPP #define NEARGYE_SEMANTIC_VERSIONING_HPP #define SEMVER_VERSION_MAJOR 0 #define SEMVER_VERSION_MINOR 3 #define SEMVER_VERSION_PATCH 0 #include <cstddef> #include <cstdint> #include <iosfwd> #include <limits> #include <optional> #include <string> #include <string_view> #if __has_include(<charconv>) #include <charconv> #else #include <system_error> #endif #if defined(SEMVER_CONFIG_FILE) #include SEMVER_CONFIG_FILE #endif #if defined(SEMVER_THROW) // define SEMVER_THROW(msg) to override semver throw behavior. #elif defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND) # include <stdexcept> # define SEMVER_THROW(msg) (throw std::invalid_argument{msg}) #else # include <cassert> # include <cstdlib> # define SEMVER_THROW(msg) (assert(!msg), std::abort()) #endif #if defined(__clang__) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wmissing-braces" // Ignore warning: suggest braces around initialization of subobject 'return {first, std::errc::invalid_argument};'. #endif #if __cpp_impl_three_way_comparison >= 201907L #include <compare> #endif namespace semver { enum struct prerelease : std::uint8_t { alpha = 0, beta = 1, rc = 2, none = 3 }; #if __has_include(<charconv>) struct from_chars_result : std::from_chars_result { [[nodiscard]] constexpr operator bool() const noexcept { return ec == std::errc{}; } }; struct to_chars_result : std::to_chars_result { [[nodiscard]] constexpr operator bool() const noexcept { return ec == std::errc{}; } }; #else struct from_chars_result { const char* ptr; std::errc ec; [[nodiscard]] constexpr operator bool() const noexcept { return ec == std::errc{}; } }; struct to_chars_result { char* ptr; std::errc ec; [[nodiscard]] constexpr operator bool() const noexcept { return ec == std::errc{}; } }; #endif // Max version string length = 5(<major>) + 1(.) + 5(<minor>) + 1(.) + 5(<patch>) + 1(-) + 5(<prerelease>) + 1(.) + 5(<prereleaseversion>) = 29. inline constexpr auto max_version_string_length = std::size_t{29}; namespace detail { inline constexpr auto alpha = std::string_view{"alpha", 5}; inline constexpr auto beta = std::string_view{"beta", 4}; inline constexpr auto rc = std::string_view{"rc", 2}; // Min version string length = 1(<major>) + 1(.) + 1(<minor>) + 1(.) + 1(<patch>) = 5. inline constexpr auto min_version_string_length = 5; constexpr char to_lower(char c) noexcept { return (c >= 'A' && c <= 'Z') ? static_cast<char>(c + ('a' - 'A')) : c; } constexpr bool is_digit(char c) noexcept { return c >= '0' && c <= '9'; } constexpr bool is_space(char c) noexcept { return c == ' '; } constexpr bool is_operator(char c) noexcept { return c == '<' || c == '>' || c == '='; } constexpr bool is_dot(char c) noexcept { return c == '.'; } constexpr bool is_logical_or(char c) noexcept { return c == '|'; } constexpr bool is_hyphen(char c) noexcept { return c == '-'; } constexpr bool is_letter(char c) noexcept { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); } constexpr std::uint16_t to_digit(char c) noexcept { return static_cast<std::uint16_t>(c - '0'); } constexpr std::uint8_t length(std::uint16_t x) noexcept { if (x < 10) { return 1; } if (x < 100) { return 2; } if (x < 1000) { return 3; } if (x < 10000) { return 4; } return 5; } constexpr std::uint8_t length(prerelease t) noexcept { if (t == prerelease::alpha) { return static_cast<std::uint8_t>(alpha.length()); } else if (t == prerelease::beta) { return static_cast<std::uint8_t>(beta.length()); } else if (t == prerelease::rc) { return static_cast<std::uint8_t>(rc.length()); } return 0; } constexpr bool equals(const char* first, const char* last, std::string_view str) noexcept { for (std::size_t i = 0; first != last && i < str.length(); ++i, ++first) { if (to_lower(*first) != to_lower(str[i])) { return false; } } return true; } constexpr char* to_chars(char* str, std::uint16_t x, bool dot = true) noexcept { do { *(--str) = static_cast<char>('0' + (x % 10)); x /= 10; } while (x != 0); if (dot) { *(--str) = '.'; } return str; } constexpr char* to_chars(char* str, prerelease t) noexcept { const auto p = t == prerelease::alpha ? alpha : t == prerelease::beta ? beta : t == prerelease::rc ? rc : std::string_view{}; if (p.size() > 0) { for (auto it = p.rbegin(); it != p.rend(); ++it) { *(--str) = *it; } *(--str) = '-'; } return str; } constexpr const char* from_chars(const char* first, const char* last, std::uint16_t& d) noexcept { if (first != last && is_digit(*first)) { std::int32_t t = 0; for (; first != last && is_digit(*first); ++first) { t = t * 10 + to_digit(*first); } if (t <= (std::numeric_limits<std::uint16_t>::max)()) { d = static_cast<std::uint16_t>(t); return first; } } return nullptr; } constexpr const char* from_chars(const char* first, const char* last, std::optional<std::uint16_t>& d) noexcept { if (first != last && is_digit(*first)) { std::int32_t t = 0; for (; first != last && is_digit(*first); ++first) { t = t * 10 + to_digit(*first); } if (t <= (std::numeric_limits<std::uint16_t>::max)()) { d = static_cast<std::uint16_t>(t); return first; } } return nullptr; } constexpr const char* from_chars(const char* first, const char* last, prerelease& p) noexcept { if (is_hyphen(*first)) { ++first; } if (equals(first, last, alpha)) { p = prerelease::alpha; return first + alpha.length(); } else if (equals(first, last, beta)) { p = prerelease::beta; return first + beta.length(); } else if (equals(first, last, rc)) { p = prerelease::rc; return first + rc.length(); } return nullptr; } constexpr bool check_delimiter(const char* first, const char* last, char d) noexcept { return first != last && first != nullptr && *first == d; } template <typename T, typename = void> struct resize_uninitialized { static auto resize(T& str, std::size_t size) -> std::void_t<decltype(str.resize(size))> { str.resize(size); } }; template <typename T> struct resize_uninitialized<T, std::void_t<decltype(std::declval<T>().__resize_default_init(42))>> { static void resize(T& str, std::size_t size) { str.__resize_default_init(size); } }; } // namespace semver::detail struct version { std::uint16_t major = 0; std::uint16_t minor = 1; std::uint16_t patch = 0; prerelease prerelease_type = prerelease::none; std::optional<std::uint16_t> prerelease_number = std::nullopt; constexpr version(std::uint16_t mj, std::uint16_t mn, std::uint16_t pt, prerelease prt = prerelease::none, std::optional<std::uint16_t> prn = std::nullopt) noexcept : major{mj}, minor{mn}, patch{pt}, prerelease_type{prt}, prerelease_number{prt == prerelease::none ? std::nullopt : prn} { } constexpr version(std::uint16_t mj, std::uint16_t mn, std::uint16_t pt, prerelease prt, std::uint16_t prn) noexcept : major{mj}, minor{mn}, patch{pt}, prerelease_type{prt}, prerelease_number{prt == prerelease::none ? std::nullopt : std::make_optional<std::uint16_t>(prn)} { } explicit constexpr version(std::string_view str) : version(0, 0, 0, prerelease::none, std::nullopt) { from_string(str); } constexpr version() = default; // https://semver.org/#how-should-i-deal-with-revisions-in-the-0yz-initial-development-phase constexpr version(const version&) = default; constexpr version(version&&) = default; ~version() = default; version& operator=(const version&) = default; version& operator=(version&&) = default; [[nodiscard]] constexpr from_chars_result from_chars(const char* first, const char* last) noexcept { if (first == nullptr || last == nullptr || (last - first) < detail::min_version_string_length) { return {first, std::errc::invalid_argument}; } auto next = first; if (next = detail::from_chars(next, last, major); detail::check_delimiter(next, last, '.')) { if (next = detail::from_chars(++next, last, minor); detail::check_delimiter(next, last, '.')) { if (next = detail::from_chars(++next, last, patch); next == last) { prerelease_type = prerelease::none; prerelease_number = {}; return {next, std::errc{}}; } else if (detail::check_delimiter(next, last, '-')) { if (next = detail::from_chars(next, last, prerelease_type); next == last) { prerelease_number = {}; return {next, std::errc{}}; } else if (detail::check_delimiter(next, last, '.')) { if (next = detail::from_chars(++next, last, prerelease_number); next == last) { return {next, std::errc{}}; } } } } } return {first, std::errc::invalid_argument}; } [[nodiscard]] constexpr to_chars_result to_chars(char* first, char* last) const noexcept { const auto length = string_length(); if (first == nullptr || last == nullptr || (last - first) < length) { return {last, std::errc::value_too_large}; } auto next = first + length; if (prerelease_type != prerelease::none) { if (prerelease_number.has_value()) { next = detail::to_chars(next, prerelease_number.value()); } next = detail::to_chars(next, prerelease_type); } next = detail::to_chars(next, patch); next = detail::to_chars(next, minor); next = detail::to_chars(next, major, false); return {first + length, std::errc{}}; } [[nodiscard]] constexpr bool from_string_noexcept(std::string_view str) noexcept { return from_chars(str.data(), str.data() + str.length()); } constexpr version& from_string(std::string_view str) { if (!from_string_noexcept(str)) { SEMVER_THROW("semver::version::from_string invalid version."); } return *this; } [[nodiscard]] std::string to_string() const { auto str = std::string{}; detail::resize_uninitialized<std::string>::resize(str, string_length()); if (!to_chars(str.data(), str.data() + str.length())) { SEMVER_THROW("semver::version::to_string invalid version."); } return str; } [[nodiscard]] constexpr std::uint8_t string_length() const noexcept { // (<major>) + 1(.) + (<minor>) + 1(.) + (<patch>) auto length = detail::length(major) + detail::length(minor) + detail::length(patch) + 2; if (prerelease_type != prerelease::none) { // + 1(-) + (<prerelease>) length += detail::length(prerelease_type) + 1; if (prerelease_number.has_value()) { // + 1(.) + (<prereleaseversion>) length += detail::length(prerelease_number.value()) + 1; } } return static_cast<std::uint8_t>(length); } [[nodiscard]] constexpr int compare(const version& other) const noexcept { if (major != other.major) { return major - other.major; } if (minor != other.minor) { return minor - other.minor; } if (patch != other.patch) { return patch - other.patch; } if (prerelease_type != other.prerelease_type) { return static_cast<std::uint8_t>(prerelease_type) - static_cast<std::uint8_t>(other.prerelease_type); } if (prerelease_number.has_value()) { if (other.prerelease_number.has_value()) { return prerelease_number.value() - other.prerelease_number.value(); } return 1; } else if (other.prerelease_number.has_value()) { return -1; } return 0; } }; [[nodiscard]] constexpr bool operator==(const version& lhs, const version& rhs) noexcept { return lhs.compare(rhs) == 0; } [[nodiscard]] constexpr bool operator!=(const version& lhs, const version& rhs) noexcept { return lhs.compare(rhs) != 0; } [[nodiscard]] constexpr bool operator>(const version& lhs, const version& rhs) noexcept { return lhs.compare(rhs) > 0; } [[nodiscard]] constexpr bool operator>=(const version& lhs, const version& rhs) noexcept { return lhs.compare(rhs) >= 0; } [[nodiscard]] constexpr bool operator<(const version& lhs, const version& rhs) noexcept { return lhs.compare(rhs) < 0; } [[nodiscard]] constexpr bool operator<=(const version& lhs, const version& rhs) noexcept { return lhs.compare(rhs) <= 0; } #if __cpp_impl_three_way_comparison >= 201907L [[nodiscard]] constexpr std::strong_ordering operator<=>(const version& lhs, const version& rhs) { int compare = lhs.compare(rhs); if ( compare == 0 ) return std::strong_ordering::equal; if ( compare > 0 ) return std::strong_ordering::greater; return std::strong_ordering::less; } #endif [[nodiscard]] constexpr version operator""_version(const char* str, std::size_t length) { return version{std::string_view{str, length}}; } [[nodiscard]] constexpr bool valid(std::string_view str) noexcept { return version{}.from_string_noexcept(str); } [[nodiscard]] constexpr from_chars_result from_chars(const char* first, const char* last, version& v) noexcept { return v.from_chars(first, last); } [[nodiscard]] constexpr to_chars_result to_chars(char* first, char* last, const version& v) noexcept { return v.to_chars(first, last); } [[nodiscard]] constexpr std::optional<version> from_string_noexcept(std::string_view str) noexcept { if (version v{}; v.from_string_noexcept(str)) { return v; } return std::nullopt; } [[nodiscard]] constexpr version from_string(std::string_view str) { return version{str}; } [[nodiscard]] inline std::string to_string(const version& v) { return v.to_string(); } template <typename Char, typename Traits> inline std::basic_ostream<Char, Traits>& operator<<(std::basic_ostream<Char, Traits>& os, const version& v) { for (const auto c : v.to_string()) { os.put(c); } return os; } inline namespace comparators { enum struct comparators_option : std::uint8_t { exclude_prerelease, include_prerelease }; [[nodiscard]] constexpr int compare(const version& lhs, const version& rhs, comparators_option option = comparators_option::include_prerelease) noexcept { if (option == comparators_option::exclude_prerelease) { return version{lhs.major, lhs.minor, lhs.patch}.compare(version{rhs.major, rhs.minor, rhs.patch}); } return lhs.compare(rhs); } [[nodiscard]] constexpr bool equal_to(const version& lhs, const version& rhs, comparators_option option = comparators_option::include_prerelease) noexcept { return compare(lhs, rhs, option) == 0; } [[nodiscard]] constexpr bool not_equal_to(const version& lhs, const version& rhs, comparators_option option = comparators_option::include_prerelease) noexcept { return compare(lhs, rhs, option) != 0; } [[nodiscard]] constexpr bool greater(const version& lhs, const version& rhs, comparators_option option = comparators_option::include_prerelease) noexcept { return compare(lhs, rhs, option) > 0; } [[nodiscard]] constexpr bool greater_equal(const version& lhs, const version& rhs, comparators_option option = comparators_option::include_prerelease) noexcept { return compare(lhs, rhs, option) >= 0; } [[nodiscard]] constexpr bool less(const version& lhs, const version& rhs, comparators_option option = comparators_option::include_prerelease) noexcept { return compare(lhs, rhs, option) < 0; } [[nodiscard]] constexpr bool less_equal(const version& lhs, const version& rhs, comparators_option option = comparators_option::include_prerelease) noexcept { return compare(lhs, rhs, option) <= 0; } } // namespace semver::comparators namespace range { namespace detail { using namespace semver::detail; class range { public: constexpr explicit range(std::string_view str) noexcept : parser{str} {} constexpr bool satisfies(const version& ver, bool include_prerelease) { const bool has_prerelease = ver.prerelease_type != prerelease::none; do { if (is_logical_or_token()) { parser.advance_token(range_token_type::logical_or); } bool contains = true; bool allow_compare = include_prerelease; while (is_operator_token() || is_number_token()) { const auto range = parser.parse_range(); const bool equal_without_tags = equal_to(range.ver, ver, comparators_option::exclude_prerelease); if (has_prerelease && equal_without_tags) { allow_compare = true; } if (!range.satisfies(ver)) { contains = false; break; } } if (has_prerelease) { if (allow_compare && contains) { return true; } } else if (contains) { return true; } } while (is_logical_or_token()); return false; } private: enum struct range_operator : std::uint8_t { less, less_or_equal, greater, greater_or_equal, equal }; struct range_comparator { range_operator op; version ver; constexpr bool satisfies(const version& version) const { switch (op) { case range_operator::equal: return version == ver; case range_operator::greater: return version > ver; case range_operator::greater_or_equal: return version >= ver; case range_operator::less: return version < ver; case range_operator::less_or_equal: return version <= ver; default: SEMVER_THROW("semver::range unexpected operator."); } } }; enum struct range_token_type : std::uint8_t { none, number, range_operator, dot, logical_or, hyphen, prerelease, end_of_line }; struct range_token { range_token_type type = range_token_type::none; std::uint16_t number = 0; range_operator op = range_operator::equal; prerelease prerelease_type = prerelease::none; }; struct range_lexer { std::string_view text; std::size_t pos; constexpr explicit range_lexer(std::string_view text) noexcept : text{text}, pos{0} {} constexpr range_token get_next_token() noexcept { while (!end_of_line()) { if (is_space(text[pos])) { advance(1); continue; } if (is_logical_or(text[pos])) { advance(2); return {range_token_type::logical_or}; } if (is_operator(text[pos])) { const auto op = get_operator(); return {range_token_type::range_operator, 0, op}; } if (is_digit(text[pos])) { const auto number = get_number(); return {range_token_type::number, number}; } if (is_dot(text[pos])) { advance(1); return {range_token_type::dot}; } if (is_hyphen(text[pos])) { advance(1); return {range_token_type::hyphen}; } if (is_letter(text[pos])) { const auto prerelease = get_prerelease(); return {range_token_type::prerelease, 0, range_operator::equal, prerelease}; } } return {range_token_type::end_of_line}; } constexpr bool end_of_line() const noexcept { return pos >= text.length(); } constexpr void advance(std::size_t i) noexcept { pos += i; } constexpr range_operator get_operator() noexcept { if (text[pos] == '<') { advance(1); if (text[pos] == '=') { advance(1); return range_operator::less_or_equal; } return range_operator::less; } else if (text[pos] == '>') { advance(1); if (text[pos] == '=') { advance(1); return range_operator::greater_or_equal; } return range_operator::greater; } else if (text[pos] == '=') { advance(1); return range_operator::equal; } return range_operator::equal; } constexpr std::uint16_t get_number() noexcept { const auto first = text.data() + pos; const auto last = text.data() + text.length(); if (std::uint16_t n{}; from_chars(first, last, n) != nullptr) { advance(length(n)); return n; } return 0; } constexpr prerelease get_prerelease() noexcept { const auto first = text.data() + pos; const auto last = text.data() + text.length(); if (first > last) { advance(1); return prerelease::none; } if (prerelease p{}; from_chars(first, last, p) != nullptr) { advance(length(p)); return p; } advance(1); return prerelease::none; } }; struct range_parser { range_lexer lexer; range_token current_token; constexpr explicit range_parser(std::string_view str) : lexer{str}, current_token{range_token_type::none} { advance_token(range_token_type::none); } constexpr void advance_token(range_token_type token_type) { if (current_token.type != token_type) { SEMVER_THROW("semver::range unexpected token."); } current_token = lexer.get_next_token(); } constexpr range_comparator parse_range() { if (current_token.type == range_token_type::number) { const auto version = parse_version(); return {range_operator::equal, version}; } else if (current_token.type == range_token_type::range_operator) { const auto range_operator = current_token.op; advance_token(range_token_type::range_operator); const auto version = parse_version(); return {range_operator, version}; } return {range_operator::equal, version{}}; } constexpr version parse_version() { const auto major = parse_number(); advance_token(range_token_type::dot); const auto minor = parse_number(); advance_token(range_token_type::dot); const auto patch = parse_number(); prerelease prerelease = prerelease::none; std::optional<std::uint16_t> prerelease_number = std::nullopt; if (current_token.type == range_token_type::hyphen) { advance_token(range_token_type::hyphen); prerelease = parse_prerelease(); if (current_token.type == range_token_type::dot) { advance_token(range_token_type::dot); prerelease_number = parse_number(); } } return {major, minor, patch, prerelease, prerelease_number}; } constexpr std::uint16_t parse_number() { const auto token = current_token; advance_token(range_token_type::number); return token.number; } constexpr prerelease parse_prerelease() { const auto token = current_token; advance_token(range_token_type::prerelease); return token.prerelease_type; } }; [[nodiscard]] constexpr bool is_logical_or_token() const noexcept { return parser.current_token.type == range_token_type::logical_or; } [[nodiscard]] constexpr bool is_operator_token() const noexcept { return parser.current_token.type == range_token_type::range_operator; } [[nodiscard]] constexpr bool is_number_token() const noexcept { return parser.current_token.type == range_token_type::number; } range_parser parser; }; } // namespace semver::range::detail enum struct satisfies_option : std::uint8_t { exclude_prerelease, include_prerelease }; constexpr bool satisfies(const version& ver, std::string_view str, satisfies_option option = satisfies_option::exclude_prerelease) { switch (option) { case satisfies_option::exclude_prerelease: return detail::range{str}.satisfies(ver, false); case satisfies_option::include_prerelease: return detail::range{str}.satisfies(ver, true); default: SEMVER_THROW("semver::range unexpected satisfies_option."); } } } // namespace semver::range // Version lib semver. inline constexpr auto semver_version = version{SEMVER_VERSION_MAJOR, SEMVER_VERSION_MINOR, SEMVER_VERSION_PATCH}; } // namespace semver #if defined(__clang__) # pragma clang diagnostic pop #endif #endif // NEARGYE_SEMANTIC_VERSIONING_HPP