commons 0.1.5
Header-only C++23 library of common/shared types for the C++ libraries
Loading...
Searching...
No Matches
version_constraint.hpp
Go to the documentation of this file.
1#pragma once
2
32
33#include <commons/semver.hpp>
34#include <commons/types.hpp>
35
36#include <algorithm>
37#include <format>
38#include <functional>
39#include <ostream>
40#include <stdexcept>
41#include <string>
42#include <string_view>
43#include <vector>
44
45namespace comms {
46
50 enum class Op { Any, Eq, Neq, Gt, Gte, Lt, Lte, Caret, Tilde };
51
52 struct Matcher {
53 Op op = Op::Any;
54 SemVer ver;
55 };
56
57 std::string raw_;
58 std::vector<Matcher> matchers_;
59
60public:
61 VersionConstraint() = default;
62
65 [[nodiscard]] static VersionConstraint parse(const std::string_view s) {
67 vc.raw_ = std::string{s};
68
69 for (const auto part : split(s)) {
70 vc.matchers_.push_back(parse_single(part));
71 }
72 if (vc.matchers_.empty()) {
73 vc.matchers_.push_back(Matcher{}); // Op::Any by default
74 }
75 return vc;
76 }
77
79 [[nodiscard]] bool satisfies(const SemVer& v) const {
80 return std::ranges::all_of(matchers_, [&v](const Matcher& m) { return matches(m, v); });
81 }
82
84 [[nodiscard]] const std::string& raw() const noexcept {
85 return raw_;
86 }
87
89 [[nodiscard]] std::string to_string() const {
90 return raw_;
91 }
92
94 [[nodiscard]] bool operator==(const VersionConstraint& o) const {
95 return raw_ == o.raw_;
96 }
97
98private:
99 [[nodiscard]] static bool matches(const Matcher& m, const SemVer& v) {
100 switch (m.op) {
101 case Op::Any:
102 return true;
103 case Op::Eq:
104 return v == m.ver;
105 case Op::Neq:
106 return v != m.ver;
107 case Op::Gt:
108 return v > m.ver;
109 case Op::Gte:
110 return v >= m.ver;
111 case Op::Lt:
112 return v < m.ver;
113 case Op::Lte:
114 return v <= m.ver;
115 case Op::Caret:
116 return caret_matches(m.ver, v);
117 case Op::Tilde:
118 return tilde_matches(m.ver, v);
119 }
120 return false;
121 }
122
125 [[nodiscard]] static bool caret_matches(const SemVer& constraint, const SemVer& v) {
126 if (v < constraint) {
127 return false;
128 }
129 if (constraint.major != 0) {
130 return v.major == constraint.major;
131 }
132 if (constraint.minor != 0) {
133 return v.major == 0 && v.minor == constraint.minor;
134 }
135 return v.major == 0 && v.minor == 0 && v.patch == constraint.patch;
136 }
137
139 [[nodiscard]] static bool tilde_matches(const SemVer& constraint, const SemVer& v) {
140 if (v < constraint) {
141 return false;
142 }
143 return v.major == constraint.major && v.minor == constraint.minor;
144 }
145
147 [[nodiscard]] static Matcher parse_single(std::string_view s) {
148 while (!s.empty() && s.front() == ' ') {
149 s.remove_prefix(1);
150 }
151 while (!s.empty() && s.back() == ' ') {
152 s.remove_suffix(1);
153 }
154
155 if (s.empty() || s == "*") {
156 return Matcher{}; // Op::Any by default
157 }
158
159 // Two-char operators are checked before their one-char prefixes.
160 if (s.starts_with(">=")) {
161 return Matcher{.op = Op::Gte, .ver = require(s.substr(2), s)};
162 }
163 if (s.starts_with("<=")) {
164 return Matcher{.op = Op::Lte, .ver = require(s.substr(2), s)};
165 }
166 if (s.starts_with("!=")) {
167 return Matcher{.op = Op::Neq, .ver = require(s.substr(2), s)};
168 }
169 if (s.starts_with("^")) {
170 return Matcher{.op = Op::Caret, .ver = require(s.substr(1), s)};
171 }
172 if (s.starts_with("~")) {
173 return Matcher{.op = Op::Tilde, .ver = require(s.substr(1), s)};
174 }
175 if (s.starts_with(">")) {
176 return Matcher{.op = Op::Gt, .ver = require(s.substr(1), s)};
177 }
178 if (s.starts_with("<")) {
179 return Matcher{.op = Op::Lt, .ver = require(s.substr(1), s)};
180 }
181 return Matcher{.op = Op::Eq, .ver = require(s, s)};
182 }
183
185 [[nodiscard]] static SemVer require(const std::string_view ver, const std::string_view token) {
186 const auto parsed = SemVer::parse(ver);
187 if (!parsed) {
188 throw std::invalid_argument{"commons: invalid version in constraint: " +
189 std::string{token}};
190 }
191 return *parsed;
192 }
193
197 [[nodiscard]] static std::vector<std::string_view> split(std::string_view s) {
198 std::vector<std::string_view> parts;
199 while (!s.empty()) {
200 while (!s.empty() && s.front() == ' ') {
201 s.remove_prefix(1);
202 }
203 if (s.empty()) {
204 break;
205 }
206
207 usize end = 0;
208 bool in_version = false;
209 for (usize i = 0; i < s.size(); ++i) {
210 if (s[i] != ' ') {
211 continue;
212 }
213 usize next = i + 1;
214 while (next < s.size() && s[next] == ' ') {
215 ++next;
216 }
217 if (next < s.size()) {
218 if (const char nc = s[next]; nc == '^' || nc == '~' || nc == '>' || nc == '<' ||
219 nc == '!' || nc == '=' ||
220 (nc >= '0' && nc <= '9')) {
221 end = i;
222 in_version = true;
223 break;
224 }
225 }
226 }
227
228 if (in_version) {
229 parts.push_back(s.substr(0, end));
230 s.remove_prefix(end);
231 } else {
232 parts.push_back(s);
233 break;
234 }
235 }
236 return parts;
237 }
238};
239
240// ---------------------------------------------------------------------------
241// Text output: to_string + std::ostream insertion. (std::format support is the
242// std::formatter specialization below, outside namespace comms.)
243// ---------------------------------------------------------------------------
244
246[[nodiscard]] inline std::string to_string(const VersionConstraint& v) {
247 return v.to_string();
248}
249
250inline std::ostream& operator<<(std::ostream& os, const VersionConstraint& v) {
251 return os << v.raw();
252}
253
254} // namespace comms
255
256// ---------------------------------------------------------------------------
257// std::format and std::hash support. The specializations live in namespace std
258// (the primary templates are visible), like nlohmann's adl_serializer route in
259// json.hpp.
260// ---------------------------------------------------------------------------
261
262// This spec-less formatter reads no member state, but `std::formatter` requires
263// `parse`/`format` to be non-static members — so silence the convert-to-static
264// suggestion here.
265// NOLINTBEGIN(readability-convert-member-functions-to-static)
266
268template <>
269struct std::formatter<comms::VersionConstraint> {
270 constexpr auto parse(const std::format_parse_context& ctx) {
271 const auto* it = ctx.begin();
272 if (it != ctx.end() && *it != '}') {
273 throw std::format_error("commons: VersionConstraint takes no format spec");
274 }
275 return it;
276 }
277
278 auto format(const comms::VersionConstraint& v, std::format_context& ctx) const {
279 return std::format_to(ctx.out(), "{}", v.raw());
280 }
281};
282
283// NOLINTEND(readability-convert-member-functions-to-static)
284
286template <>
287struct std::hash<comms::VersionConstraint> {
288 [[nodiscard]] std::size_t operator()(const comms::VersionConstraint& v) const noexcept {
289 return std::hash<std::string>{}(v.raw());
290 }
291};
A semver range constraint (npm ^/~, comparisons, space-separated intersection).
Definition version_constraint.hpp:49
bool satisfies(const SemVer &v) const
True when v satisfies every matcher (logical AND).
Definition version_constraint.hpp:79
static VersionConstraint parse(const std::string_view s)
Parse a range string into a constraint.
Definition version_constraint.hpp:65
std::string to_string() const
The canonical text form — the raw range string.
Definition version_constraint.hpp:89
const std::string & raw() const noexcept
The original range string this constraint was parsed from.
Definition version_constraint.hpp:84
bool operator==(const VersionConstraint &o) const
Equality by raw string (there is no semantic normalization).
Definition version_constraint.hpp:94
std::string to_string(const Color &c)
Color as its canonical hex string (#RRGGBB, or #RRGGBBAA when not opaque).
Definition color.hpp:1388
A value type for a Semantic Versioning 2.0.0 version — major.minor.patch with optional prerelease and...
A semantic version: major.minor.patch with optional prerelease/build.
Definition semver.hpp:54
static std::optional< SemVer > parse(std::string_view s)
Non-throwing parse.
Definition semver.hpp:67
Fixed-width numeric aliases shared across the C++ libraries.
std::size_t usize
Unsigned size type (std::size_t).
Definition types.hpp:43