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