xrpld
Loading...
Searching...
No Matches
SemanticVersion.cpp
1#include <xrpl/beast/core/SemanticVersion.h>
2
3#include <xrpl/beast/core/LexicalCast.h>
4#include <xrpl/beast/utility/instrumentation.h>
5
6#include <algorithm>
7#include <cctype>
8#include <limits>
9#include <locale>
10#include <ranges>
11#include <stdexcept>
12#include <string>
13#include <string_view>
14
15namespace beast {
16
17std::string
19{
20 std::string ret;
21
22 for (auto const& x : list)
23 {
24 if (!ret.empty())
25 ret += ".";
26 ret += x;
27 }
28
29 return ret;
30}
31
32bool
34{
35 int n = 0;
36
37 // Must be convertible to an integer
38 if (!lexicalCastChecked(n, s))
39 return false;
40
41 // Must not have leading zeroes
42 return std::to_string(n) == s;
43}
44
45bool
46chop(std::string const& what, std::string& input)
47{
48 auto ret = input.find(what);
49
50 if (ret != 0)
51 return false;
52
53 input.erase(0, what.size());
54 return true;
55}
56
57bool
58chopUInt(int& value, int limit, std::string& input)
59{
60 // Must not be empty
61 if (input.empty())
62 return false;
63
64 auto leftIter = std::ranges::find_if_not(
65 input, [](std::string::value_type c) { return std::isdigit(c, std::locale::classic()); });
66
67 std::string const item(input.begin(), leftIter);
68
69 // Must not be empty
70 if (item.empty())
71 return false;
72
73 int n = 0;
74
75 // Must be convertible to an integer
76 if (!lexicalCastChecked(n, item))
77 return false;
78
79 // Must not have leading zeroes
80 if (std::to_string(n) != item)
81 return false;
82
83 // Must not be out of range
84 if (n < 0 || n > limit)
85 return false;
86
87 input.erase(input.begin(), leftIter);
88 value = n;
89
90 return true;
91}
92
93bool
94extractIdentifier(std::string& value, bool allowLeadingZeroes, std::string& input)
95{
96 // Must not be empty
97 if (input.empty())
98 return false;
99
100 // Must not have a leading 0
101 if (!allowLeadingZeroes && input[0] == '0')
102 return false;
103
104 auto last =
105 input.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-");
106
107 // Must not be empty
108 if (last == 0)
109 return false;
110
111 value = input.substr(0, last);
112 input.erase(0, last);
113 return true;
114}
115
116bool
119 bool allowLeadingZeroes,
120 std::string& input)
121{
122 if (input.empty())
123 return false;
124
125 do
126 {
127 std::string s;
128
129 if (!extractIdentifier(s, allowLeadingZeroes, input))
130 return false;
131 identifiers.push_back(s);
132 } while (chop(".", input));
133
134 return true;
135}
136
137//------------------------------------------------------------------------------
138
142
144{
145 if (!parse(version))
146 throw std::invalid_argument("invalid version string");
147}
148
149bool
151{
152 // May not have leading or trailing whitespace
153 auto leftIter = std::ranges::find_if_not(
154 input, [](std::string::value_type c) { return std::isspace(c, std::locale::classic()); });
155
156 auto rightIter =
157 std::ranges::find_if_not(std::ranges::reverse_view(input), [](std::string::value_type c) {
158 return std::isspace(c, std::locale::classic());
159 }).base();
160
161 // Must not be empty!
162 if (leftIter >= rightIter)
163 return false;
164
165 std::string version(leftIter, rightIter);
166
167 // May not have leading or trailing whitespace
168 if (version != input)
169 return false;
170
171 // Must have major version number
173 return false;
174 if (!chop(".", version))
175 return false;
176
177 // Must have minor version number
179 return false;
180 if (!chop(".", version))
181 return false;
182
183 // Must have patch version number
185 return false;
186
187 // May have pre-release identifier list
188 if (chop("-", version))
189 {
190 if (!extractIdentifiers(preReleaseIdentifiers, false, version))
191 return false;
192
193 // Must not be empty
194 if (preReleaseIdentifiers.empty())
195 return false;
196 }
197
198 // May have metadata identifier list
199 if (chop("+", version))
200 {
201 if (!extractIdentifiers(metaData, true, version))
202 return false;
203
204 // Must not be empty
205 if (metaData.empty())
206 return false;
207 }
208
209 return version.empty();
210}
211
214{
215 std::string s;
216
219
220 if (!preReleaseIdentifiers.empty())
221 {
222 s += "-";
224 }
225
226 if (!metaData.empty())
227 {
228 s += "+";
230 }
231
232 return s;
233}
234
235int
237{
238 if (lhs.majorVersion > rhs.majorVersion)
239 {
240 return 1;
241 }
242 if (lhs.majorVersion < rhs.majorVersion)
243 {
244 return -1;
245 }
246
247 if (lhs.minorVersion > rhs.minorVersion)
248 {
249 return 1;
250 }
251 if (lhs.minorVersion < rhs.minorVersion)
252 {
253 return -1;
254 }
255
256 if (lhs.patchVersion > rhs.patchVersion)
257 {
258 return 1;
259 }
260 if (lhs.patchVersion < rhs.patchVersion)
261 {
262 return -1;
263 }
264
265 if (lhs.isPreRelease() || rhs.isPreRelease())
266 {
267 // Pre-releases have a lower precedence
268 if (lhs.isRelease() && rhs.isPreRelease())
269 {
270 return 1;
271 }
272 if (lhs.isPreRelease() && rhs.isRelease())
273 {
274 return -1;
275 }
276
277 // Compare pre-release identifiers
278 for (int i = 0;
280 ++i)
281 {
282 // A larger list of identifiers has a higher precedence
283 if (i >= rhs.preReleaseIdentifiers.size())
284 {
285 return 1;
286 }
287 if (i >= lhs.preReleaseIdentifiers.size())
288 {
289 return -1;
290 }
291
292 std::string const& left(lhs.preReleaseIdentifiers[i]);
293 std::string const& right(rhs.preReleaseIdentifiers[i]);
294
295 // Numeric identifiers have lower precedence
296 if (!isNumeric(left) && isNumeric(right))
297 {
298 return 1;
299 }
300 if (isNumeric(left) && !isNumeric(right))
301 {
302 return -1;
303 }
304
305 if (isNumeric(left))
306 {
307 XRPL_ASSERT(isNumeric(right), "beast::compare : both inputs numeric");
308
309 int const iLeft(lexicalCastThrow<int>(left));
310 int const iRight(lexicalCastThrow<int>(right));
311
312 if (iLeft > iRight)
313 {
314 return 1;
315 }
316 if (iLeft < iRight)
317 {
318 return -1;
319 }
320 }
321 else
322 {
323 XRPL_ASSERT(!isNumeric(right), "beast::compare : both inputs non-numeric");
324
325 int const result = left.compare(right);
326
327 if (result != 0)
328 return result;
329 }
330 }
331 }
332
333 // metadata is ignored
334
335 return 0;
336}
337
338} // namespace beast
T begin(T... args)
A Semantic Version number.
identifier_list preReleaseIdentifiers
bool isPreRelease() const noexcept
bool parse(std::string_view input)
Parse a semantic version string.
std::string print() const
Produce a string from semantic version components.
std::vector< std::string > identifier_list
bool isRelease() const noexcept
identifier_list metaData
T classic(T... args)
T empty(T... args)
T erase(T... args)
T find_first_not_of(T... args)
T find(T... args)
T max(T... args)
Out lexicalCastThrow(In in)
Convert from one type to another, throw on error.
int compare(SemanticVersion const &lhs, SemanticVersion const &rhs)
Compare two SemanticVersions against each other.
bool isNumeric(std::string const &s)
std::string printIdentifiers(SemanticVersion::identifier_list const &list)
bool chop(std::string const &what, std::string &input)
bool extractIdentifier(std::string &value, bool allowLeadingZeroes, std::string &input)
bool extractIdentifiers(SemanticVersion::identifier_list &identifiers, bool allowLeadingZeroes, std::string &input)
bool lexicalCastChecked(Out &out, In in)
Intelligently convert from one type to another.
bool chopUInt(int &value, int limit, std::string &input)
T push_back(T... args)
T size(T... args)
T substr(T... args)
T to_string(T... args)