LeviLamina
Loading...
Searching...
No Matches
Version.h
1#pragma once
2
3#include <algorithm>
4#include <charconv>
5#include <compare>
6#include <cstddef>
7#include <cstdint>
8#include <limits>
9#include <optional>
10#include <stdexcept>
11#include <string>
12#include <string_view>
13#include <system_error>
14#include <utility>
15#include <variant>
16#include <vector>
17
18#include "ll/api/utils/HashUtils.h"
19#include "ll/api/utils/StringUtils.h"
20
21#include "fmt/core.h"
22
23namespace ll::data {
24
25namespace detail {
26
27struct from_chars_result : std::from_chars_result {
28 [[nodiscard]] constexpr operator bool() const noexcept { return ec == std::errc{}; }
29 constexpr void value() const {
30 if (ec != std::errc{}) {
31 throw std::system_error{std::make_error_code(ec)};
32 }
33 }
34};
35
36// Min version string length = 1(<major>) + 1(.) + 1(<minor>) + 1(.) + 1(<patch>) = 5.
37constexpr inline auto min_version_string_length = 5;
38
39constexpr bool is_digit(char c) noexcept { return c >= '0' && c <= '9'; }
40
41constexpr bool is_plus(char c) noexcept { return c == '+'; }
42
43constexpr bool is_letter(char c) noexcept { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); }
44
45constexpr std::uint16_t to_digit(char c) noexcept { return static_cast<std::uint16_t>(c - '0'); }
46
47constexpr from_chars_result from_chars(char const* first, char const* last, std::uint16_t& d) noexcept {
48 if (first != last && is_digit(*first)) {
49 std::int32_t t = 0;
50 for (; first != last && is_digit(*first); ++first) {
51 t = t * 10 + to_digit(*first);
52 }
53 if (t <= (std::numeric_limits<std::uint16_t>::max)()) {
54 d = static_cast<std::uint16_t>(t);
55 return {first, std::errc{}};
56 } else {
57 return {first, std::errc::result_out_of_range};
58 }
59 }
60 return {first, std::errc::invalid_argument};
61}
62constexpr bool check_delimiter(char const* first, char const* last, char d) noexcept {
63 return first != last && first != nullptr && *first == d;
64}
65} // namespace detail
66
67struct PreRelease {
68 std::vector<std::variant<std::string, uint16_t>> values;
69
70 constexpr PreRelease() noexcept = default;
71 constexpr ~PreRelease() = default;
72 constexpr explicit PreRelease(std::string_view s) noexcept { from_string(s); }
73
74 constexpr std::strong_ordering operator<=>(PreRelease const& other) const noexcept {
75 for (std::size_t i = 0; i < std::min(values.size(), other.values.size()); ++i) {
76 if (std::holds_alternative<std::string>(values[i])) {
77 if (std::holds_alternative<std::string>(other.values[i])) {
78 if (std::get<std::string>(values[i]) != std::get<std::string>(other.values[i])) {
79 return std::get<std::string>(values[i]) <=> std::get<std::string>(other.values[i]);
80 }
81 } else {
82 return std::strong_ordering::greater;
83 }
84 } else {
85 if (std::holds_alternative<std::string>(other.values[i])) {
86 return std::strong_ordering::less;
87 } else {
88 if (std::get<std::uint16_t>(values[i]) != std::get<std::uint16_t>(other.values[i])) {
89 return std::get<std::uint16_t>(values[i]) <=> std::get<std::uint16_t>(other.values[i]);
90 }
91 }
92 }
93 }
94 return values.size() <=> other.values.size();
95 }
96
97 constexpr detail::from_chars_result from_chars(char const* first, char const* last) noexcept {
98 auto begin = first;
99 while (first != last && !detail::is_plus(*first)) {
100 first++;
101 }
102 std::string s{begin, first};
103 auto tokens = ll::string_utils::splitByPattern(s, ".");
104 for (auto const& token : tokens) {
105 std::uint16_t value;
106 if (auto result = detail::from_chars(token.data(), token.data() + token.length(), value); result) {
107 values.emplace_back(value);
108 } else {
109 values.emplace_back(std::string{token});
110 }
111 }
112 return {first, std::errc{}};
113 }
114
115 [[nodiscard]] constexpr detail::from_chars_result from_string_noexcept(std::string_view str) noexcept {
116 return from_chars(str.data(), str.data() + str.length());
117 }
118
119 constexpr PreRelease& from_string(std::string_view str) noexcept {
120 from_string_noexcept(str).value();
121 return *this;
122 }
123
124 [[nodiscard]] constexpr std::string to_string() const noexcept {
125 std::string str;
126 for (auto const& value : values) {
127 if (std::holds_alternative<std::string>(value)) {
128 str += std::get<std::string>(value);
129 } else {
130 str += std::to_string(std::get<std::uint16_t>(value));
131 }
132 str += '.';
133 }
134 if (str.ends_with('.')) {
135 str.pop_back();
136 }
137 return str;
138 }
139};
140
141struct Version {
142 // https://semver.org/#how-should-i-deal-with-revisions-in-the-0yz-initial-development-phase
143 std::uint16_t major = 0;
144 std::uint16_t minor = 1;
145 std::uint16_t patch = 0;
146 std::optional<PreRelease> preRelease;
147 std::optional<std::string> build;
148
149 constexpr Version() = default;
150 constexpr ~Version() = default;
151
152 constexpr Version(
153 std::uint16_t mj,
154 std::uint16_t mn,
155 std::uint16_t pt,
156 std::optional<PreRelease> prt = {},
157 std::optional<std::string> bu = {}
158 ) noexcept
159 : major{mj},
160 minor{mn},
161 patch{pt},
162 preRelease{std::move(prt)},
163 build{std::move(bu)} {}
164
165 constexpr Version(
166 std::uint16_t mj,
167 std::uint16_t mn,
168 std::uint16_t pt,
169 std::string_view prt,
170 std::optional<std::string> bu = {}
171 ) noexcept
172 : major{mj},
173 minor{mn},
174 patch{pt},
175 preRelease{PreRelease{prt}},
176 build{std::move(bu)} {}
177
178 explicit constexpr Version(std::string_view str) : Version() { from_string(str); }
179
180 [[nodiscard]] constexpr detail::from_chars_result from_chars(char const* first, char const* last) noexcept {
181 if (first == nullptr || last == nullptr || (last - first) < detail::min_version_string_length) {
182 return {first, std::errc::invalid_argument};
183 }
184 auto next = first;
185 if (auto result = detail::from_chars(next, last, major); result) {
186 next = result.ptr;
187 if (!detail::check_delimiter(next, last, '.')) {
188 return {next, std::errc::invalid_argument};
189 }
190 ++next;
191 } else {
192 return result;
193 }
194 if (auto result = detail::from_chars(next, last, minor); result) {
195 next = result.ptr;
196 if (!detail::check_delimiter(next, last, '.')) {
197 return {next, std::errc::invalid_argument};
198 }
199 ++next;
200 } else {
201 return result;
202 }
203 if (auto result = detail::from_chars(next, last, patch); result) {
204 next = result.ptr;
205 } else {
206 return result;
207 }
208 if (next == last) {
209 return {next, std::errc{}};
210 }
211 if (detail::check_delimiter(next, last, '-')) {
212 PreRelease pre;
213 auto result = pre.from_chars(++next, last);
214 if (!result) return result;
215 if (pre.values.empty()) return {next, std::errc::invalid_argument};
216 preRelease = pre;
217 next = result.ptr;
218 if (result && next == last) {
219 return {next, std::errc{}};
220 }
221 }
222 if (detail::check_delimiter(next, last, '+')) {
223 build = {++next, static_cast<size_t>(last - next)};
224 if (build->empty()) {
225 return {nullptr, std::errc::invalid_argument};
226 }
227 next = last;
228 if (std::any_of(build->begin(), build->end(), [](char c) {
229 return !detail::is_digit(c) && !detail::is_letter(c);
230 })) {
231 return {nullptr, std::errc::invalid_argument};
232 }
233 }
234 if (next == last) {
235 return {next, std::errc{}};
236 }
237
238 return {first, std::errc::invalid_argument};
239 }
240
241 [[nodiscard]] constexpr detail::from_chars_result from_string_noexcept(std::string_view str) noexcept {
242 return from_chars(str.data(), str.data() + str.length());
243 }
244
245 constexpr Version& from_string(std::string_view str) {
246 from_string_noexcept(str).value();
247 return *this;
248 }
249
250 [[nodiscard]] std::string to_string() const {
251 std::string str;
252 str = fmt::format("{}.{}.{}", major, minor, patch);
253 if (preRelease) {
254 str += '-';
255 str += preRelease->to_string();
256 }
257 if (build) {
258 str += '+';
259 str += *build;
260 }
261 return str;
262 }
263
264 [[nodiscard]] constexpr std::strong_ordering operator<=>(Version const& other) const noexcept {
265 if (major != other.major) {
266 return major <=> other.major;
267 }
268 if (minor != other.minor) {
269 return minor <=> other.minor;
270 }
271 if (patch != other.patch) {
272 return patch <=> other.patch;
273 }
274 if (preRelease) {
275 if (other.preRelease) {
276 return *preRelease <=> *other.preRelease;
277 }
278 return std::strong_ordering::less;
279 } else if (other.preRelease) {
280 return std::strong_ordering::greater;
281 }
282 return std::strong_ordering::equal;
283 }
284
285 [[nodiscard]] constexpr bool operator==(Version const& other) const noexcept {
286 return *this <=> other == std::strong_ordering::equal;
287 }
288
289 [[nodiscard]] [[maybe_unused]] static constexpr bool valid(std::string_view str) noexcept {
290 return Version{}.from_string_noexcept(str);
291 }
292};
293
294template <class J, class T>
295[[nodiscard]] inline Expected<J> serialize(T&& ver) noexcept
296 requires(std::same_as<std::remove_cvref_t<T>, Version>)
297try {
298 return ver.to_string();
299} catch (...) {
300 return makeExceptionError();
301}
302template <class T, class J>
303[[nodiscard]] inline Expected<> deserialize(T& ver, J const& j) noexcept
304 requires(std::same_as<T, Version>)
305{
306 if (j.is_string()) {
307 if (auto res = ver.from_string_noexcept((std::string const&)j); res) {
308 return {};
309 } else {
310 return makeErrorCodeError(res.ec);
311 }
312 } else {
313 return makeStringError("field must be a string");
314 }
315}
316
317namespace literals {
318[[nodiscard]] constexpr Version operator""_version(char const* str, std::size_t length) {
319 return Version{
320 std::string_view{str, length}
321 };
322}
323} // namespace literals
324
325} // namespace ll::data
326
327namespace std {
328template <>
329struct hash<ll::data::PreRelease> {
330 size_t operator()(ll::data::PreRelease const& d) const noexcept { return ll::hash_utils::hashType(d.values); }
331};
332template <>
333struct hash<ll::data::Version> {
334 size_t operator()(ll::data::Version const& v) const noexcept {
335 size_t seed{v.major};
336 ll::hash_utils::hashCombine(v.minor, seed);
337 ll::hash_utils::hashCombine(v.patch, seed);
338 ll::hash_utils::hashCombine(std::hash<std::optional<ll::data::PreRelease>>{}(v.preRelease), seed);
339 ll::hash_utils::hashCombine(std::hash<std::optional<std::string>>{}(v.build), seed);
340 return seed;
341 }
342};
343} // namespace std
STL namespace.
Definition Version.h:67
Definition Version.h:141
Definition serialize.h:11