Fcitx
semver.cpp
1 /*
2  * SPDX-FileCopyrightText: 2021~2021 CSSlayer <wengxt@gmail.com>
3  *
4  * SPDX-License-Identifier: LGPL-2.1-or-later
5  *
6  */
7 
8 #include "semver.h"
9 #include <algorithm>
10 #include <charconv>
11 #include <cstddef>
12 #include <cstdint>
13 #include <iterator>
14 #include <optional>
15 #include <string>
16 #include <string_view>
17 #include <system_error>
18 #include <utility>
19 #include <variant>
20 #include <vector>
21 #include <format>
22 #include "charutils.h"
23 #include "misc.h"
24 #include "stringutils.h"
25 
26 namespace fcitx {
27 
28 namespace {
29 
30 bool isIdChar(char c) {
31  return charutils::islower(c) || charutils::isupper(c) || c == '-' ||
32  charutils::isdigit(c) || c == '.';
33 }
34 
35 std::optional<uint32_t> consumeNumericIdentifier(std::string_view &str) {
36  std::string_view::iterator endOfNum =
37  std::find_if_not(str.begin(), str.end(), charutils::isdigit);
38  auto length = std::distance(str.begin(), endOfNum);
39  if (length == 0) {
40  return std::nullopt;
41  }
42  if (str[0] == '0' && length != 1) {
43  return std::nullopt;
44  }
45 
46  auto numberStr = str.substr(0, length);
47  uint32_t number;
48  if (auto [p, ec] = std::from_chars(
49  numberStr.data(), numberStr.data() + numberStr.size(), number);
50  ec == std::errc()) {
51  str.remove_prefix(length);
52  return number;
53  }
54  return std::nullopt;
55 }
56 
57 std::optional<std::vector<PreReleaseId>>
58 consumePrereleaseIds(std::string_view &data) {
59  std::vector<PreReleaseId> preReleaseIds;
60  std::string_view::const_iterator endOfVersion =
61  std::find_if_not(data.begin(), data.end(), isIdChar);
62  auto length = std::distance(data.begin(), endOfVersion);
63  auto idString = data.substr(0, length);
64  auto ids = stringutils::split(idString, ".",
65  stringutils::SplitBehavior::KeepEmpty);
66  for (const auto &id : ids) {
67  if (id.empty()) {
68  return std::nullopt;
69  }
70  // If it's numeric, it need to be a valid numeric.
71  // Otherwise it can be anything.
72  if (std::all_of(id.begin(), id.end(), charutils::isdigit)) {
73  std::string_view idView(id);
74  auto result = consumeNumericIdentifier(idView);
75  if (result && idView.empty()) {
76  preReleaseIds.emplace_back(result.value());
77  } else {
78  return std::nullopt;
79  }
80  } else {
81  preReleaseIds.emplace_back(id);
82  }
83  }
84  data.remove_prefix(length);
85  return preReleaseIds;
86 }
87 
88 std::optional<std::vector<std::string>> consumeBuild(std::string_view &data) {
89  if (std::all_of(data.begin(), data.end(), isIdChar)) {
90  auto ids = stringutils::split(data, ".",
91  stringutils::SplitBehavior::KeepEmpty);
92  if (std::any_of(ids.begin(), ids.end(),
93  [](const auto &id) { return id.empty(); })) {
94  return std::nullopt;
95  }
96  data.remove_prefix(data.size());
97  return ids;
98  }
99  return std::nullopt;
100 }
101 
102 const std::string kEmptyString;
103 
104 } // namespace
105 
106 PreReleaseId::PreReleaseId(uint32_t id) : value_(id) {}
107 
108 PreReleaseId::PreReleaseId(std::string id) : value_(std::move(id)) {}
109 
110 std::string PreReleaseId::toString() const {
111  if (isNumeric()) {
112  return std::to_string(numericId());
113  }
114  return id();
115 }
116 
117 int PreReleaseId::compare(const PreReleaseId &other) const noexcept {
118  auto isNum = isNumeric();
119  auto otherIsNum = other.isNumeric();
120  if (isNum != otherIsNum) {
121  // this is num and other is not num, return -1;
122  return isNum ? -1 : 1;
123  }
124  if (isNum && otherIsNum) {
125  if (numericId() == other.numericId()) {
126  return 0;
127  }
128  return numericId() < other.numericId() ? -1 : 1;
129  }
130 
131  return id().compare(other.id());
132 }
133 
134 const std::string &PreReleaseId::id() const noexcept {
135  if (const auto *value = std::get_if<std::string>(&value_)) {
136  return *value;
137  }
138  return kEmptyString;
139 }
140 
141 uint32_t PreReleaseId::numericId() const noexcept {
142  if (const auto *value = std::get_if<uint32_t>(&value_)) {
143  return *value;
144  }
145  return 0;
146 }
147 
148 bool PreReleaseId::isNumeric() const noexcept {
149  return std::holds_alternative<uint32_t>(value_);
150 }
151 
152 void SemanticVersion::setBuildIds(std::vector<std::string> build) {
153  buildIds_ = std::move(build);
154 }
155 
156 void SemanticVersion::setMajor(uint32_t major) { major_ = major; }
157 
158 void SemanticVersion::setMinor(uint32_t minor) { minor_ = minor; }
159 
160 void SemanticVersion::setPatch(uint32_t patch) { patch_ = patch; }
161 
162 std::string SemanticVersion::toString() const {
163  std::string result = std::format("{0}.{1}.{2}", major_, minor_, patch_);
164  if (!preReleaseIds_.empty()) {
165  result.append("-");
166  result.append(preReleaseIds_.front().toString());
167  for (const auto &item : MakeIterRange(std::next(preReleaseIds_.begin()),
168  preReleaseIds_.end())) {
169  result.append(".");
170  result.append(item.toString());
171  }
172  }
173 
174  if (!buildIds_.empty()) {
175  result.append("+");
176  result.append(stringutils::join(buildIds_, "."));
177  }
178 
179  return result;
180 }
181 
182 void SemanticVersion::setPreReleaseIds(std::vector<PreReleaseId> ids) {
183  preReleaseIds_ = std::move(ids);
184 }
185 
186 uint32_t(SemanticVersion::major)() const { return major_; }
187 
188 uint32_t(SemanticVersion::minor)() const { return minor_; }
189 
190 uint32_t SemanticVersion::patch() const { return patch_; }
191 
192 const std::vector<PreReleaseId> &SemanticVersion::preReleaseIds() const {
193  return preReleaseIds_;
194 }
195 
196 const std::vector<std::string> &SemanticVersion::buildIds() const {
197  return buildIds_;
198 }
199 
200 bool SemanticVersion::isPreRelease() const { return !preReleaseIds_.empty(); }
201 
202 std::optional<SemanticVersion> SemanticVersion::parse(std::string_view data) {
203  SemanticVersion version;
204  if (auto result = consumeNumericIdentifier(data)) {
205  version.setMajor(result.value());
206  } else {
207  return std::nullopt;
208  }
209 
210  if (data.empty() || data.front() != '.') {
211  return std::nullopt;
212  }
213  data.remove_prefix(1);
214 
215  if (auto result = consumeNumericIdentifier(data)) {
216  version.setMinor(result.value());
217  } else {
218  return std::nullopt;
219  }
220 
221  if (data.empty() || data.front() != '.') {
222  return std::nullopt;
223  }
224  data.remove_prefix(1);
225 
226  if (auto result = consumeNumericIdentifier(data)) {
227  version.setPatch(result.value());
228  } else {
229  return std::nullopt;
230  }
231 
232  if (data.empty()) {
233  return version;
234  }
235 
236  if (data[0] == '-') {
237  data.remove_prefix(1);
238  if (auto result = consumePrereleaseIds(data)) {
239  version.setPreReleaseIds(std::move(result.value()));
240  } else {
241  return std::nullopt;
242  }
243  }
244 
245  if (data.empty()) {
246  return version;
247  }
248 
249  if (data[0] == '+') {
250  data.remove_prefix(1);
251  if (auto result = consumeBuild(data)) {
252  version.setBuildIds(std::move(result.value()));
253  } else {
254  return std::nullopt;
255  }
256  }
257 
258  if (!data.empty()) {
259  return std::nullopt;
260  }
261  return version;
262 }
263 
264 int SemanticVersion::compare(const SemanticVersion &other) const noexcept {
265  if (major_ != other.major_) {
266  return major_ < other.major_ ? -1 : 1;
267  }
268 
269  if (minor_ != other.minor_) {
270  return minor_ < other.minor_ ? -1 : 1;
271  }
272 
273  if (patch_ != other.patch_) {
274  return patch_ < other.patch_ ? -1 : 1;
275  }
276 
277  bool preRelease = isPreRelease();
278  bool otherIsPreRelease = other.isPreRelease();
279 
280  if (preRelease != otherIsPreRelease) {
281  return preRelease ? -1 : 1;
282  }
283 
284  if (!preRelease) {
285  return 0;
286  }
287 
288  for (size_t i = 0, e = std::min(preReleaseIds_.size(),
289  other.preReleaseIds_.size());
290  i < e; i++) {
291  auto result = preReleaseIds_[i].compare(other.preReleaseIds_[i]);
292  if (result != 0) {
293  return result;
294  }
295  }
296 
297  if (preReleaseIds_.size() == other.preReleaseIds_.size()) {
298  return 0;
299  }
300  return preReleaseIds_.size() < other.preReleaseIds_.size() ? -1 : 1;
301 }
302 
303 } // namespace fcitx
size_t length(Iter start, Iter end)
Return the number UTF-8 characters in the string iterator range.
Definition: utf8.h:33
Definition: action.cpp:17
Definition: matchrule.h:78
std::vector< std::string > split(std::string_view str, std::string_view delim, SplitBehavior behavior)
Split the string by delim.
String handle utilities.
std::string join(Iter start, Iter end, T &&delim)
Join a range of string with delim.
Definition: stringutils.h:108
Local independent API to detect character type.