1use std::str::FromStr;
7
8use pubgrub::range::Range;
9use pubgrub::version::Version as PubgrubVersion;
10
11use crate::pep::pep440::Version;
12use crate::Error;
13
14fn next_version(v: &Version) -> Version {
16 PubgrubVersion::bump(v)
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum Operator {
22 Equal,
24 NotEqual,
26 LessThan,
28 LessThanOrEqual,
30 GreaterThan,
32 GreaterThanOrEqual,
34 Compatible,
36 ArbitraryEqual,
38}
39
40impl Operator {
41 fn parse(s: &str) -> Option<(Self, usize)> {
43 if s.starts_with("===") {
44 Some((Operator::ArbitraryEqual, 3))
45 } else if s.starts_with("==") {
46 Some((Operator::Equal, 2))
47 } else if s.starts_with("!=") {
48 Some((Operator::NotEqual, 2))
49 } else if s.starts_with("~=") {
50 Some((Operator::Compatible, 2))
51 } else if s.starts_with("<=") {
52 Some((Operator::LessThanOrEqual, 2))
53 } else if s.starts_with(">=") {
54 Some((Operator::GreaterThanOrEqual, 2))
55 } else if s.starts_with('<') {
56 Some((Operator::LessThan, 1))
57 } else if s.starts_with('>') {
58 Some((Operator::GreaterThan, 1))
59 } else {
60 None
61 }
62 }
63}
64
65#[derive(Debug, Clone, PartialEq, Eq)]
67pub struct VersionSpecifier {
68 pub operator: Operator,
70 pub version: Version,
72 pub wildcard: bool,
74}
75
76impl VersionSpecifier {
77 pub fn new(operator: Operator, version: Version) -> Self {
79 Self {
80 operator,
81 version,
82 wildcard: false,
83 }
84 }
85
86 pub fn parse(s: &str) -> Result<Self, Error> {
88 let s = s.trim();
89
90 let (operator, op_len) =
91 Operator::parse(s).ok_or_else(|| Error::InvalidSpecifier(s.to_string()))?;
92
93 let version_str = s[op_len..].trim();
94
95 let (version_str, wildcard) = if let Some(stripped) = version_str.strip_suffix(".*") {
97 (stripped, true)
98 } else if let Some(stripped) = version_str.strip_suffix('*') {
99 (stripped, true)
100 } else {
101 (version_str, false)
102 };
103
104 let version = Version::parse(version_str)?;
105
106 Ok(Self {
107 operator,
108 version,
109 wildcard,
110 })
111 }
112
113 pub fn contains(&self, v: &Version) -> bool {
115 match self.operator {
116 Operator::Equal => {
117 if self.wildcard {
118 self.matches_prefix(v)
119 } else {
120 v == &self.version
121 }
122 }
123 Operator::NotEqual => {
124 if self.wildcard {
125 !self.matches_prefix(v)
126 } else {
127 v != &self.version
128 }
129 }
130 Operator::LessThan => v < &self.version,
131 Operator::LessThanOrEqual => v <= &self.version,
132 Operator::GreaterThan => v > &self.version,
133 Operator::GreaterThanOrEqual => v >= &self.version,
134 Operator::Compatible => {
135 if self.version.release.len() < 2 {
137 v >= &self.version
138 } else {
139 let mut upper = self.version.clone();
140 let len = upper.release.len();
142 if len >= 2 {
143 upper.release[len - 2] += 1;
144 upper.release.truncate(len - 1);
145 upper.pre = None;
146 upper.post = None;
147 upper.dev = None;
148 upper.local = None;
149 }
150 v >= &self.version && v < &upper
151 }
152 }
153 Operator::ArbitraryEqual => {
154 v.to_string() == self.version.to_string()
156 }
157 }
158 }
159
160 fn matches_prefix(&self, v: &Version) -> bool {
162 if v.epoch != self.version.epoch {
164 return false;
165 }
166
167 for (i, seg) in self.version.release.iter().enumerate() {
169 if v.release.get(i) != Some(seg) {
170 return false;
171 }
172 }
173
174 true
175 }
176
177 pub fn to_range(&self) -> Range<Version> {
179 match self.operator {
180 Operator::Equal => {
181 if self.wildcard {
182 let mut upper = self.version.clone();
184 if let Some(last) = upper.release.last_mut() {
185 *last += 1;
186 }
187 Range::between(self.version.clone(), upper)
188 } else {
189 Range::exact(self.version.clone())
191 }
192 }
193 Operator::NotEqual => {
194 Range::exact(self.version.clone()).negate()
196 }
197 Operator::LessThan => Range::strictly_lower_than(self.version.clone()),
198 Operator::LessThanOrEqual => {
199 Range::strictly_lower_than(next_version(&self.version))
201 }
202 Operator::GreaterThan => {
203 Range::higher_than(next_version(&self.version))
205 }
206 Operator::GreaterThanOrEqual => Range::higher_than(self.version.clone()),
207 Operator::Compatible => {
208 if self.version.release.len() < 2 {
210 Range::higher_than(self.version.clone())
211 } else {
212 let mut upper = self.version.clone();
213 let len = upper.release.len();
214 upper.release[len - 2] += 1;
215 upper.release.truncate(len - 1);
216 upper.pre = None;
217 upper.post = None;
218 upper.dev = None;
219 upper.local = None;
220 Range::between(self.version.clone(), upper)
221 }
222 }
223 Operator::ArbitraryEqual => {
224 Range::exact(self.version.clone())
226 }
227 }
228 }
229}
230
231impl FromStr for VersionSpecifier {
232 type Err = Error;
233
234 fn from_str(s: &str) -> Result<Self, Self::Err> {
235 Self::parse(s)
236 }
237}
238
239#[derive(Debug, Clone, PartialEq, Eq)]
241pub struct VersionSpecifiers(pub Vec<VersionSpecifier>);
242
243impl VersionSpecifiers {
244 pub fn any() -> Self {
246 Self(vec![])
247 }
248
249 pub fn parse(s: &str) -> Result<Self, Error> {
251 let s = s.trim();
252
253 if s.is_empty() {
254 return Ok(Self::any());
255 }
256
257 let specifiers: Result<Vec<_>, _> = s
258 .split(',')
259 .map(|part| VersionSpecifier::parse(part.trim()))
260 .collect();
261
262 Ok(Self(specifiers?))
263 }
264
265 pub fn contains(&self, v: &Version) -> bool {
267 self.0.iter().all(|spec| spec.contains(v))
268 }
269
270 pub fn is_any(&self) -> bool {
272 self.0.is_empty()
273 }
274
275 pub fn to_pubgrub_range(&self) -> Range<Version> {
277 if self.0.is_empty() {
278 return Range::any();
279 }
280
281 let mut result = Range::any();
282
283 for spec in &self.0 {
284 let range = spec.to_range();
285 result = result.intersection(&range);
286 }
287
288 result
289 }
290}
291
292impl FromStr for VersionSpecifiers {
293 type Err = Error;
294
295 fn from_str(s: &str) -> Result<Self, Self::Err> {
296 Self::parse(s)
297 }
298}
299
300impl std::fmt::Display for VersionSpecifiers {
301 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
302 let parts: Vec<String> = self
303 .0
304 .iter()
305 .map(|spec| {
306 let op = match spec.operator {
307 Operator::Equal => "==",
308 Operator::NotEqual => "!=",
309 Operator::LessThan => "<",
310 Operator::LessThanOrEqual => "<=",
311 Operator::GreaterThan => ">",
312 Operator::GreaterThanOrEqual => ">=",
313 Operator::Compatible => "~=",
314 Operator::ArbitraryEqual => "===",
315 };
316 let wildcard = if spec.wildcard { ".*" } else { "" };
317 format!("{}{}{}", op, spec.version, wildcard)
318 })
319 .collect();
320 write!(f, "{}", parts.join(","))
321 }
322}
323
324#[cfg(test)]
325mod tests {
326 use super::*;
327
328 #[test]
329 fn test_parse_single_specifier() {
330 let spec = VersionSpecifier::parse(">=1.0").unwrap();
331 assert_eq!(spec.operator, Operator::GreaterThanOrEqual);
332 assert_eq!(spec.version.release, vec![1, 0]);
333 }
334
335 #[test]
336 fn test_parse_multiple_specifiers() {
337 let specs = VersionSpecifiers::parse(">=1.0,<2.0").unwrap();
338 assert_eq!(specs.0.len(), 2);
339 assert_eq!(specs.0[0].operator, Operator::GreaterThanOrEqual);
340 assert_eq!(specs.0[1].operator, Operator::LessThan);
341 }
342
343 #[test]
344 fn test_parse_wildcard() {
345 let spec = VersionSpecifier::parse("==1.0.*").unwrap();
346 assert_eq!(spec.operator, Operator::Equal);
347 assert!(spec.wildcard);
348 assert_eq!(spec.version.release, vec![1, 0]);
349 }
350
351 #[test]
352 fn test_contains_ge() {
353 let spec = VersionSpecifier::parse(">=1.0").unwrap();
354 assert!(spec.contains(&Version::parse("1.0").unwrap()));
355 assert!(spec.contains(&Version::parse("1.5").unwrap()));
356 assert!(spec.contains(&Version::parse("2.0").unwrap()));
357 assert!(!spec.contains(&Version::parse("0.9").unwrap()));
358 }
359
360 #[test]
361 fn test_contains_lt() {
362 let spec = VersionSpecifier::parse("<2.0").unwrap();
363 assert!(spec.contains(&Version::parse("1.0").unwrap()));
364 assert!(spec.contains(&Version::parse("1.9.9").unwrap()));
365 assert!(!spec.contains(&Version::parse("2.0").unwrap()));
366 assert!(!spec.contains(&Version::parse("2.1").unwrap()));
367 }
368
369 #[test]
370 fn test_contains_range() {
371 let specs = VersionSpecifiers::parse(">=1.0,<2.0").unwrap();
372 assert!(specs.contains(&Version::parse("1.0").unwrap()));
373 assert!(specs.contains(&Version::parse("1.5").unwrap()));
374 assert!(!specs.contains(&Version::parse("0.9").unwrap()));
375 assert!(!specs.contains(&Version::parse("2.0").unwrap()));
376 }
377
378 #[test]
379 fn test_contains_compatible() {
380 let spec = VersionSpecifier::parse("~=1.4.2").unwrap();
381 assert!(spec.contains(&Version::parse("1.4.2").unwrap()));
382 assert!(spec.contains(&Version::parse("1.4.5").unwrap()));
383 assert!(!spec.contains(&Version::parse("1.5.0").unwrap()));
384 assert!(!spec.contains(&Version::parse("1.4.1").unwrap()));
385 }
386
387 #[test]
388 fn test_contains_wildcard() {
389 let spec = VersionSpecifier::parse("==1.0.*").unwrap();
390 assert!(spec.contains(&Version::parse("1.0.0").unwrap()));
391 assert!(spec.contains(&Version::parse("1.0.5").unwrap()));
392 assert!(!spec.contains(&Version::parse("1.1.0").unwrap()));
393 }
394
395 #[test]
396 fn test_to_pubgrub_range_ge() {
397 let spec = VersionSpecifier::parse(">=1.0").unwrap();
398 let range = spec.to_range();
399 assert!(range.contains(&Version::parse("1.0").unwrap()));
400 assert!(range.contains(&Version::parse("2.0").unwrap()));
401 assert!(!range.contains(&Version::parse("0.9").unwrap()));
402 }
403
404 #[test]
405 fn test_to_pubgrub_range_lt() {
406 let spec = VersionSpecifier::parse("<2.0").unwrap();
407 let range = spec.to_range();
408 assert!(range.contains(&Version::parse("1.0").unwrap()));
409 assert!(!range.contains(&Version::parse("2.0").unwrap()));
410 }
411
412 #[test]
413 fn test_to_pubgrub_range_combined() {
414 let specs = VersionSpecifiers::parse(">=1.0,<2.0").unwrap();
415 let range = specs.to_pubgrub_range();
416 assert!(range.contains(&Version::parse("1.0").unwrap()));
417 assert!(range.contains(&Version::parse("1.5").unwrap()));
418 assert!(!range.contains(&Version::parse("0.9").unwrap()));
419 assert!(!range.contains(&Version::parse("2.0").unwrap()));
420 }
421
422 #[test]
423 fn test_any_specifiers() {
424 let specs = VersionSpecifiers::any();
425 assert!(specs.is_any());
426 assert!(specs.contains(&Version::parse("1.0.0").unwrap()));
427 assert!(specs.contains(&Version::parse("999.999.999").unwrap()));
428 }
429
430 #[test]
431 fn test_display() {
432 let specs = VersionSpecifiers::parse(">=1.0,<2.0").unwrap();
433 assert_eq!(specs.to_string(), ">=1.0,<2.0");
434 }
435}