67 [[nodiscard]]
static std::optional<SemVer>
parse(std::string_view s) {
68 if (!s.empty() && (s.front() ==
'v' || s.front() ==
'V')) {
78 std::string_view
build;
79 bool has_build =
false;
80 if (
const auto plus = s.find(
'+'); plus != std::string_view::npos) {
82 build = s.substr(plus + 1);
83 s = s.substr(0, plus);
87 bool has_prerelease =
false;
88 if (
const auto hyphen = s.find(
'-'); hyphen != std::string_view::npos) {
89 has_prerelease =
true;
91 s = s.substr(0, hyphen);
98 std::array<comms::u32, 3> parts{0, 0, 0};
100 std::string_view core = s;
105 const auto dot = core.find(
'.');
106 const std::string_view token =
107 (dot == std::string_view::npos) ? core : core.substr(0, dot);
108 const auto value = parse_uint(token);
112 parts[index++] = *value;
113 if (dot == std::string_view::npos) {
116 core = core.substr(dot + 1);
119 if (has_prerelease && !valid_identifiers(
prerelease,
true)) {
122 if (has_build && !valid_identifiers(
build,
false)) {
141 std::to_string(major) +
"." + std::to_string(minor) +
"." + std::to_string(patch);
145 if (!
build.empty()) {
156 if (
const auto c = major <=> o.major; c != 0) {
159 if (
const auto c = minor <=> o.minor; c != 0) {
162 if (
const auto c = patch <=> o.patch; c != 0) {
171 return std::is_eq(*this <=> o);
178 [[nodiscard]]
static std::optional<comms::u32> parse_uint(
const std::string_view s) {
182 constexpr comms::u64 u32_max = ~static_cast<comms::u32>(0);
184 for (
const char c : s) {
185 if (c <
'0' || c >
'9') {
188 result = result * 10 +
static_cast<comms::u64>(c -
'0');
189 if (result > u32_max) {
197 [[nodiscard]]
static bool is_numeric(std::string_view s) {
199 std::ranges::all_of(s, [](
const char c) {
return c >=
'0' && c <=
'9'; });
205 [[nodiscard]]
static bool valid_identifiers(
const std::string_view s,
206 const bool numeric_no_leading_zero) {
209 const auto dot = s.find(
'.', start);
210 const std::string_view
id =
211 (dot == std::string_view::npos) ? s.substr(start) : s.substr(start, dot - start);
215 bool all_digits =
true;
216 for (
const char c : id) {
217 const bool ok = (c >=
'0' && c <=
'9') || (c >=
'A' && c <=
'Z') ||
218 (c >=
'a' && c <=
'z') || c ==
'-';
222 if (c <
'0' || c >
'9') {
226 if (numeric_no_leading_zero && all_digits &&
id.size() > 1 &&
id.front() ==
'0') {
229 if (dot == std::string_view::npos) {
238 [[nodiscard]]
static std::strong_ordering compare_prerelease(
const std::string_view a,
239 const std::string_view b) {
240 if (a.empty() && b.empty()) {
241 return std::strong_ordering::equal;
244 return std::strong_ordering::greater;
247 return std::strong_ordering::less;
252 while (ai < a.size() && bi < b.size()) {
253 const auto ad = a.find(
'.', ai);
254 const auto bd = b.find(
'.', bi);
255 const std::string_view ida =
256 (ad == std::string_view::npos) ? a.substr(ai) : a.substr(ai, ad - ai);
257 const std::string_view idb =
258 (bd == std::string_view::npos) ? b.substr(bi) : b.substr(bi, bd - bi);
260 const bool a_num = is_numeric(ida);
261 if (
const bool b_num = is_numeric(idb); a_num && b_num) {
264 if (ida.size() != idb.size()) {
265 return ida.size() <=> idb.size();
267 if (
const auto c = ida <=> idb; c != 0) {
270 }
else if (a_num != b_num) {
271 return a_num ? std::strong_ordering::less : std::strong_ordering::greater;
273 if (
const auto c = ida <=> idb; c != 0) {
278 ai = (ad == std::string_view::npos) ? a.size() : ad + 1;
279 bi = (bd == std::string_view::npos) ? b.size() : bd + 1;
283 const bool a_more = ai < a.size();
284 if (
const bool b_more = bi < b.size(); a_more == b_more) {
285 return std::strong_ordering::equal;
287 return a_more ? std::strong_ordering::greater : std::strong_ordering::less;
320struct std::formatter<comms::SemVer> {
321 constexpr auto parse(
const std::format_parse_context& ctx) {
322 const auto* it = ctx.begin();
323 if (it != ctx.end() && *it !=
'}') {
324 throw std::format_error(
"commons: SemVer takes no format spec");
329 auto format(
const comms::SemVer& v, std::format_context& ctx)
const {
330 return std::format_to(ctx.out(),
"{}", comms::to_string(v));
339struct std::hash<comms::SemVer> {
340 [[nodiscard]] std::size_t operator()(
const comms::SemVer& v)
const noexcept {
341 std::size_t seed = 0;
342 const auto mix = [&seed](
const std::size_t h) {
343 seed ^= h + 0x9e3779b9 + (seed << 6) + (seed >> 2);
345 mix(std::hash<comms::u32>{}(v.major));
346 mix(std::hash<comms::u32>{}(v.minor));
347 mix(std::hash<comms::u32>{}(v.patch));
std::string to_string(const Color &c)
Color as its canonical hex string (#RRGGBB, or #RRGGBBAA when not opaque).
Definition color.hpp:1388
std::strong_ordering operator<=>(const SemVer &o) const
Total order over major, minor, patch, then prerelease per §11.
Definition semver.hpp:155
std::string to_string() const
The canonical string: major.minor.patch, then -prerelease if set, then +build if set.
Definition semver.hpp:139
bool operator==(const SemVer &o) const
Equality consistent with <=> (so it likewise ignores build metadata).
Definition semver.hpp:170