16 #include <string_view> 17 #include <system_error> 30 bool isIdChar(
char c) {
31 return charutils::islower(c) || charutils::isupper(c) || c ==
'-' ||
32 charutils::isdigit(c) || c ==
'.';
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);
42 if (str[0] ==
'0' && length != 1) {
46 auto numberStr = str.substr(0, length);
48 if (
auto [p, ec] = std::from_chars(
49 numberStr.data(), numberStr.data() + numberStr.size(), number);
51 str.remove_prefix(length);
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);
65 stringutils::SplitBehavior::KeepEmpty);
66 for (
const auto &
id : ids) {
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());
81 preReleaseIds.emplace_back(
id);
84 data.remove_prefix(length);
88 std::optional<std::vector<std::string>> consumeBuild(std::string_view &data) {
89 if (std::all_of(data.begin(), data.end(), isIdChar)) {
91 stringutils::SplitBehavior::KeepEmpty);
92 if (std::any_of(ids.begin(), ids.end(),
93 [](
const auto &id) {
return id.empty(); })) {
96 data.remove_prefix(data.size());
102 const std::string kEmptyString;
106 PreReleaseId::PreReleaseId(uint32_t
id) : value_(id) {}
108 PreReleaseId::PreReleaseId(std::string
id) : value_(
std::move(id)) {}
110 std::string PreReleaseId::toString()
const {
112 return std::to_string(numericId());
117 int PreReleaseId::compare(
const PreReleaseId &other)
const noexcept {
118 auto isNum = isNumeric();
119 auto otherIsNum = other.isNumeric();
120 if (isNum != otherIsNum) {
122 return isNum ? -1 : 1;
124 if (isNum && otherIsNum) {
125 if (numericId() == other.numericId()) {
128 return numericId() < other.numericId() ? -1 : 1;
131 return id().compare(other.id());
134 const std::string &PreReleaseId::id() const noexcept {
135 if (
const auto *value = std::get_if<std::string>(&value_)) {
141 uint32_t PreReleaseId::numericId() const noexcept {
142 if (
const auto *value = std::get_if<uint32_t>(&value_)) {
148 bool PreReleaseId::isNumeric() const noexcept {
149 return std::holds_alternative<uint32_t>(value_);
152 void SemanticVersion::setBuildIds(std::vector<std::string> build) {
153 buildIds_ = std::move(build);
156 void SemanticVersion::setMajor(uint32_t major) { major_ = major; }
158 void SemanticVersion::setMinor(uint32_t minor) { minor_ = minor; }
160 void SemanticVersion::setPatch(uint32_t patch) { patch_ = patch; }
162 std::string SemanticVersion::toString()
const {
163 std::string result = std::format(
"{0}.{1}.{2}", major_, minor_, patch_);
164 if (!preReleaseIds_.empty()) {
166 result.append(preReleaseIds_.front().toString());
167 for (
const auto &item : MakeIterRange(std::next(preReleaseIds_.begin()),
168 preReleaseIds_.end())) {
170 result.append(item.toString());
174 if (!buildIds_.empty()) {
182 void SemanticVersion::setPreReleaseIds(std::vector<PreReleaseId> ids) {
183 preReleaseIds_ = std::move(ids);
186 uint32_t(SemanticVersion::major)()
const {
return major_; }
188 uint32_t(SemanticVersion::minor)()
const {
return minor_; }
190 uint32_t SemanticVersion::patch()
const {
return patch_; }
192 const std::vector<PreReleaseId> &SemanticVersion::preReleaseIds()
const {
193 return preReleaseIds_;
196 const std::vector<std::string> &SemanticVersion::buildIds()
const {
200 bool SemanticVersion::isPreRelease()
const {
return !preReleaseIds_.empty(); }
202 std::optional<SemanticVersion> SemanticVersion::parse(std::string_view data) {
203 SemanticVersion version;
204 if (
auto result = consumeNumericIdentifier(data)) {
205 version.setMajor(result.value());
210 if (data.empty() || data.front() !=
'.') {
213 data.remove_prefix(1);
215 if (
auto result = consumeNumericIdentifier(data)) {
216 version.setMinor(result.value());
221 if (data.empty() || data.front() !=
'.') {
224 data.remove_prefix(1);
226 if (
auto result = consumeNumericIdentifier(data)) {
227 version.setPatch(result.value());
236 if (data[0] ==
'-') {
237 data.remove_prefix(1);
238 if (
auto result = consumePrereleaseIds(data)) {
239 version.setPreReleaseIds(std::move(result.value()));
249 if (data[0] ==
'+') {
250 data.remove_prefix(1);
251 if (
auto result = consumeBuild(data)) {
252 version.setBuildIds(std::move(result.value()));
264 int SemanticVersion::compare(
const SemanticVersion &other)
const noexcept {
265 if (major_ != other.major_) {
266 return major_ < other.major_ ? -1 : 1;
269 if (minor_ != other.minor_) {
270 return minor_ < other.minor_ ? -1 : 1;
273 if (patch_ != other.patch_) {
274 return patch_ < other.patch_ ? -1 : 1;
277 bool preRelease = isPreRelease();
278 bool otherIsPreRelease = other.isPreRelease();
280 if (preRelease != otherIsPreRelease) {
281 return preRelease ? -1 : 1;
288 for (
size_t i = 0, e = std::min(preReleaseIds_.size(),
289 other.preReleaseIds_.size());
291 auto result = preReleaseIds_[i].compare(other.preReleaseIds_[i]);
297 if (preReleaseIds_.size() == other.preReleaseIds_.size()) {
300 return preReleaseIds_.size() < other.preReleaseIds_.size() ? -1 : 1;
size_t length(Iter start, Iter end)
Return the number UTF-8 characters in the string iterator range.
std::vector< std::string > split(std::string_view str, std::string_view delim, SplitBehavior behavior)
Split the string by delim.
std::string join(Iter start, Iter end, T &&delim)
Join a range of string with delim.
Local independent API to detect character type.