Skip to content

Commit 02ee9ff

Browse files
authored
fix: improve ordering so that it is closer to the specification (#9)
1 parent 99763ed commit 02ee9ff

File tree

3 files changed

+92
-20
lines changed

3 files changed

+92
-20
lines changed

include/version_weaver.h

Lines changed: 67 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ namespace version_weaver {
1111
static constexpr size_t MAX_VERSION_LENGTH = 256;
1212

1313
bool validate(std::string_view version);
14+
1415
bool satisfies(std::string_view version, std::string_view range);
1516
std::string coerce(std::string_view version);
1617
std::string minimum(std::string_view range);
@@ -58,32 +59,81 @@ enum ParseError {
5859
// This will return a cleaned and trimmed semver version.
5960
// If the provided version is not valid a null will be returned.
6061
// This does not work for ranges.
61-
std::expected<Version, ParseError> clean(std::string_view range);
62+
std::expected<Version, ParseError> clean(std::string_view input);
6263

6364
std::expected<Version, ParseError> parse(std::string_view version);
6465
} // namespace version_weaver
6566

66-
inline bool operator!=(const version_weaver::Version& first,
67-
const version_weaver::Version& second) {
68-
if (first.major != second.major) return first.major != second.major;
69-
if (first.minor != second.minor) return first.minor != second.minor;
70-
return first.patch != second.patch;
71-
}
72-
inline bool operator==(const version_weaver::Version& first,
73-
const version_weaver::Version& second) {
74-
if (first.major != second.major) return first.major == second.major;
75-
if (first.minor != second.minor) return first.minor == second.minor;
76-
return first.patch == second.patch;
77-
}
67+
// https://semver.org/#spec-item-11
7868
inline auto operator<=>(const version_weaver::Version& first,
7969
const version_weaver::Version& second) {
70+
auto number_string_compare = [](std::string_view first,
71+
std::string_view second) {
72+
if (first.size() > second.size()) {
73+
return std::strong_ordering::greater;
74+
} else if (first.size() < second.size()) {
75+
return std::strong_ordering::less;
76+
}
77+
for (size_t i = 0; i < first.size(); i++) {
78+
if (first[i] > second[i]) {
79+
return std::strong_ordering::greater;
80+
} else if (first[i] < second[i]) {
81+
return std::strong_ordering::less;
82+
}
83+
}
84+
return std::strong_ordering::equal;
85+
};
8086
if (first.major != second.major) {
81-
return first.major <=> second.major;
87+
return number_string_compare(first.major, second.major);
8288
}
8389
if (first.minor != second.minor) {
84-
return first.minor <=> second.minor;
90+
return number_string_compare(first.minor, second.minor);
91+
}
92+
if (first.patch != second.patch) {
93+
return number_string_compare(first.patch, second.patch);
94+
}
95+
if (second.pre_release.has_value() && !first.pre_release.has_value()) {
96+
return std::strong_ordering::greater;
97+
}
98+
if (first.pre_release.has_value() && !second.pre_release.has_value()) {
99+
return std::strong_ordering::less;
100+
}
101+
if (!first.pre_release.has_value() && !second.pre_release.has_value()) {
102+
return std::strong_ordering::equal;
103+
}
104+
if (first.pre_release.value() == second.pre_release.value()) {
105+
return std::strong_ordering::equal;
106+
}
107+
auto only_digits = [](std::string_view first) {
108+
for (auto c : first) {
109+
if (c < '0' || c > '9') {
110+
return false;
111+
}
112+
}
113+
return true;
114+
};
115+
bool first_numeric = only_digits(first.pre_release.value());
116+
bool second_numeric = only_digits(second.pre_release.value());
117+
if (first_numeric && !second_numeric) {
118+
return std::strong_ordering::greater;
119+
}
120+
if (!first_numeric && second_numeric) {
121+
return std::strong_ordering::less;
122+
}
123+
if (first_numeric && second_numeric) {
124+
return number_string_compare(first.pre_release.value(),
125+
second.pre_release.value());
126+
}
127+
size_t min_size = std::min(first.pre_release.value().size(),
128+
second.pre_release.value().size());
129+
for (size_t i = 0; i < min_size; i++) {
130+
if (first.pre_release.value()[i] > second.pre_release.value()[i]) {
131+
return std::strong_ordering::greater;
132+
} else if (first.pre_release.value()[i] < second.pre_release.value()[i]) {
133+
return std::strong_ordering::less;
134+
}
85135
}
86-
return first.patch <=> second.patch;
136+
return first.pre_release.value().size() <=> second.pre_release.value().size();
87137
}
88138

89-
#endif // VERSION_WEAVER_H
139+
#endif // VERSION_WEAVER_H

src/version_weaver.cpp

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@
33
#include <cctype>
44
namespace version_weaver {
55
bool validate(std::string_view version) { return parse(version).has_value(); }
6-
bool satisfies(std::string_view version, std::string_view range) {
7-
return true;
8-
}
96
std::string coerce(std::string_view version) { return ""; }
107
std::string minimum(std::string_view range) { return ""; }
118

tests/basictests.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,28 @@ TEST(basictests, clean) {
8484
}
8585
}
8686
}
87+
88+
using OrderingData = std::tuple<std::string, std::string, std::strong_ordering>;
89+
90+
std::vector<OrderingData> ordering_values = {
91+
{"1.0.0", "1.0.0", std::strong_ordering::equal},
92+
{"1.0.0", "1.0.1", std::strong_ordering::less},
93+
{"1.0.0", "1.1.0", std::strong_ordering::less},
94+
{"1.0.0", "2.0.0", std::strong_ordering::less},
95+
{"1.0.0", "1.0.0-alpha", std::strong_ordering::greater},
96+
{"1.0.0-alpha", "1.0.0-alpha", std::strong_ordering::equal},
97+
{"1.0.0-alpha", "1.0.0-alpha.1", std::strong_ordering::less},
98+
{"1.0.0-alpha.1", "1.0.0-beta", std::strong_ordering::less},
99+
{"1.0.0-beta", "1.0.0-beta.2", std::strong_ordering::less},
100+
{"1.0.0-beta.2", "1.0.0-beta.11", std::strong_ordering::greater},
101+
{"1.0.0-beta.11", "1.0.0-rc.1", std::strong_ordering::less},
102+
{"1.0.0-rc.1", "1.0.0", std::strong_ordering::less},
103+
};
104+
105+
TEST(basictests, order) {
106+
for (const auto& [view1, view2, order] : ordering_values) {
107+
auto v1 = version_weaver::parse(view1).value();
108+
auto v2 = version_weaver::parse(view2).value();
109+
ASSERT_EQ(v1 <=> v2, order);
110+
}
111+
}

0 commit comments

Comments
 (0)