1use std::iter::Peekable;
23
24use crate::relations::SyntaxKind::*;
25use crate::relations::{lex, BuildProfile, SyntaxKind, VersionConstraint};
26
27#[derive(Debug, Clone, PartialEq, Eq, Hash)]
29pub struct Relation {
30 pub name: String,
32 pub archqual: Option<String>,
34 pub architectures: Option<Vec<String>>,
36 pub version: Option<(VersionConstraint, debversion::Version)>,
38 pub profiles: Vec<Vec<BuildProfile>>,
40}
41
42impl Default for Relation {
43 fn default() -> Self {
44 Self::new()
45 }
46}
47
48impl Relation {
49 pub fn new() -> Self {
51 Self {
52 name: String::new(),
53 archqual: None,
54 architectures: None,
55 version: None,
56 profiles: Vec::new(),
57 }
58 }
59
60 pub fn build(name: &str) -> RelationBuilder {
62 RelationBuilder::new(name)
63 }
64
65 pub fn satisfied_by(&self, package_version: impl crate::VersionLookup) -> bool {
81 let actual = package_version.lookup_version(self.name.as_str());
82 if let Some((vc, version)) = &self.version {
83 if let Some(actual) = actual {
84 match vc {
85 VersionConstraint::GreaterThanEqual => actual.as_ref() >= version,
86 VersionConstraint::LessThanEqual => actual.as_ref() <= version,
87 VersionConstraint::Equal => actual.as_ref() == version,
88 VersionConstraint::GreaterThan => actual.as_ref() > version,
89 VersionConstraint::LessThan => actual.as_ref() < version,
90 }
91 } else {
92 false
93 }
94 } else {
95 actual.is_some()
96 }
97 }
98}
99
100pub struct RelationBuilder {
102 name: String,
103
104 archqual: Option<String>,
105 architectures: Option<Vec<String>>,
106 version: Option<(VersionConstraint, debversion::Version)>,
107 profiles: Vec<Vec<BuildProfile>>,
108}
109
110impl RelationBuilder {
111 pub fn new(name: &str) -> Self {
113 Self {
114 name: name.to_string(),
115 archqual: None,
116 architectures: None,
117 version: None,
118 profiles: Vec::new(),
119 }
120 }
121
122 pub fn archqual(mut self, archqual: &str) -> Self {
124 self.archqual = Some(archqual.to_string());
125 self
126 }
127
128 pub fn architectures(mut self, architectures: Vec<&str>) -> Self {
130 self.architectures = Some(architectures.into_iter().map(|s| s.to_string()).collect());
131 self
132 }
133
134 pub fn version(mut self, constraint: VersionConstraint, version: &str) -> Self {
136 self.version = Some((constraint, version.parse().unwrap()));
137 self
138 }
139
140 pub fn profile(mut self, profile: Vec<BuildProfile>) -> Self {
142 self.profiles.push(profile);
143 self
144 }
145
146 pub fn build(self) -> Relation {
148 Relation {
149 name: self.name,
150 archqual: self.archqual,
151 architectures: self.architectures,
152 version: self.version,
153 profiles: self.profiles,
154 }
155 }
156}
157
158impl std::fmt::Display for Relation {
159 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
160 write!(f, "{}", self.name)?;
161 if let Some(archqual) = &self.archqual {
162 write!(f, ":{}", archqual)?;
163 }
164 if let Some((constraint, version)) = &self.version {
165 write!(f, " ({} {})", constraint, version)?;
166 }
167 if let Some(archs) = &self.architectures {
168 write!(f, " [{}]", archs.join(" "))?;
169 }
170 for profile in &self.profiles {
171 write!(f, " <")?;
172 for (i, profile) in profile.iter().enumerate() {
173 if i > 0 {
174 write!(f, ", ")?;
175 }
176 write!(f, "{}", profile)?;
177 }
178 write!(f, ">")?;
179 }
180 Ok(())
181 }
182}
183
184#[cfg(feature = "serde")]
185impl<'de> serde::Deserialize<'de> for Relation {
186 fn deserialize<D>(deserializer: D) -> Result<Relation, D::Error>
187 where
188 D: serde::Deserializer<'de>,
189 {
190 let s = String::deserialize(deserializer)?;
191 s.parse().map_err(serde::de::Error::custom)
192 }
193}
194
195#[cfg(feature = "serde")]
196impl serde::Serialize for Relation {
197 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
198 where
199 S: serde::Serializer,
200 {
201 self.to_string().serialize(serializer)
202 }
203}
204
205#[derive(Debug, Clone, PartialEq, Eq, Hash)]
207pub struct Relations(pub Vec<Vec<Relation>>);
208
209impl std::ops::Index<usize> for Relations {
210 type Output = Vec<Relation>;
211
212 fn index(&self, index: usize) -> &Self::Output {
213 &self.0[index]
214 }
215}
216
217impl std::ops::IndexMut<usize> for Relations {
218 fn index_mut(&mut self, index: usize) -> &mut Self::Output {
219 &mut self.0[index]
220 }
221}
222
223impl FromIterator<Vec<Relation>> for Relations {
224 fn from_iter<I: IntoIterator<Item = Vec<Relation>>>(iter: I) -> Self {
225 Self(iter.into_iter().collect())
226 }
227}
228
229impl Default for Relations {
230 fn default() -> Self {
231 Self::new()
232 }
233}
234
235impl Relations {
236 pub fn new() -> Self {
238 Self(Vec::new())
239 }
240
241 pub fn remove(&mut self, index: usize) {
243 self.0.remove(index);
244 }
245
246 pub fn iter(&self) -> impl Iterator<Item = Vec<&Relation>> {
248 self.0.iter().map(|entry| entry.iter().collect())
249 }
250
251 pub fn len(&self) -> usize {
253 self.0.len()
254 }
255
256 pub fn is_empty(&self) -> bool {
258 self.0.is_empty()
259 }
260
261 pub fn satisfied_by(&self, package_version: impl crate::VersionLookup + Copy) -> bool {
263 self.0
264 .iter()
265 .all(|e| e.iter().any(|r| r.satisfied_by(package_version)))
266 }
267}
268
269impl FromIterator<Relation> for Relations {
270 fn from_iter<I: IntoIterator<Item = Relation>>(iter: I) -> Self {
271 Self(iter.into_iter().map(|r| vec![r]).collect())
272 }
273}
274
275impl std::fmt::Display for Relations {
276 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
277 for (i, entry) in self.0.iter().enumerate() {
278 if i > 0 {
279 f.write_str(", ")?;
280 }
281 for (j, relation) in entry.iter().enumerate() {
282 if j > 0 {
283 f.write_str(" | ")?;
284 }
285 write!(f, "{}", relation)?;
286 }
287 }
288 Ok(())
289 }
290}
291
292impl std::str::FromStr for Relation {
293 type Err = String;
294
295 fn from_str(s: &str) -> Result<Self, Self::Err> {
296 let tokens = lex(s);
297 let mut tokens = tokens.into_iter().peekable();
298
299 fn eat_whitespace(tokens: &mut Peekable<impl Iterator<Item = (SyntaxKind, String)>>) {
300 while let Some((WHITESPACE, _)) = tokens.peek() {
301 tokens.next();
302 }
303 }
304
305 let name = match tokens.next() {
306 Some((IDENT, name)) => name,
307 _ => return Err("Expected package name".to_string()),
308 };
309
310 eat_whitespace(&mut tokens);
311
312 let archqual = if let Some((COLON, _)) = tokens.peek() {
313 tokens.next();
314 match tokens.next() {
315 Some((IDENT, s)) => Some(s),
316 _ => return Err("Expected architecture qualifier".to_string()),
317 }
318 } else {
319 None
320 };
321 eat_whitespace(&mut tokens);
322
323 let version = if let Some((L_PARENS, _)) = tokens.peek() {
324 tokens.next();
325 eat_whitespace(&mut tokens);
326 let mut constraint = String::new();
327 while let Some((kind, t)) = tokens.peek() {
328 match kind {
329 EQUAL | L_ANGLE | R_ANGLE => {
330 constraint.push_str(t);
331 tokens.next();
332 }
333 _ => break,
334 }
335 }
336 let constraint = constraint.parse()?;
337 eat_whitespace(&mut tokens);
338 let mut version_string = String::new();
340 while let Some((kind, s)) = tokens.peek() {
341 match kind {
342 R_PARENS => break,
343 IDENT | COLON => version_string.push_str(s),
344 n => return Err(format!("Unexpected token: {:?}", n)),
345 }
346 tokens.next();
347 }
348 let version = version_string
349 .parse()
350 .map_err(|e: debversion::ParseError| e.to_string())?;
351 eat_whitespace(&mut tokens);
352 if let Some((R_PARENS, _)) = tokens.next() {
353 } else {
354 return Err(format!("Expected ')', found {:?}", tokens.next()));
355 }
356 Some((constraint, version))
357 } else {
358 None
359 };
360
361 eat_whitespace(&mut tokens);
362
363 let architectures = if let Some((L_BRACKET, _)) = tokens.peek() {
364 tokens.next();
365 let mut archs = Vec::new();
366 loop {
367 match tokens.next() {
368 Some((IDENT, s)) => archs.push(s),
369 Some((WHITESPACE, _)) => {}
370 Some((R_BRACKET, _)) => break,
371 _ => return Err("Expected architecture name".to_string()),
372 }
373 }
374 Some(archs)
375 } else {
376 None
377 };
378
379 eat_whitespace(&mut tokens);
380
381 let mut profiles = Vec::new();
382 while let Some((L_ANGLE, _)) = tokens.peek() {
383 tokens.next();
384 loop {
385 let mut profile = Vec::new();
386 loop {
387 match tokens.next() {
388 Some((NOT, _)) => {
389 let profile_name = match tokens.next() {
390 Some((IDENT, s)) => s,
391 _ => return Err("Expected profile name".to_string()),
392 };
393 profile.push(BuildProfile::Disabled(profile_name));
394 }
395 Some((IDENT, s)) => profile.push(BuildProfile::Enabled(s)),
396 Some((WHITESPACE, _)) => {}
397 _ => return Err("Expected profile name".to_string()),
398 }
399 if let Some((COMMA, _)) = tokens.peek() {
400 tokens.next();
401 } else {
402 break;
403 }
404 }
405 profiles.push(profile);
406 if let Some((R_ANGLE, _)) = tokens.next() {
407 eat_whitespace(&mut tokens);
408 break;
409 }
410 }
411 }
412
413 eat_whitespace(&mut tokens);
414
415 if let Some((kind, _)) = tokens.next() {
416 return Err(format!("Unexpected token: {:?}", kind));
417 }
418
419 Ok(Relation {
420 name,
421 archqual,
422 architectures,
423 version,
424 profiles,
425 })
426 }
427}
428
429impl std::str::FromStr for Relations {
430 type Err = String;
431
432 fn from_str(s: &str) -> Result<Self, Self::Err> {
433 let mut relations = Vec::new();
434 if s.is_empty() {
435 return Ok(Relations(relations));
436 }
437 for entry in s.split(',') {
438 let entry = entry.trim();
439 if entry.is_empty() {
440 continue;
442 }
443 let entry_relations = entry.split('|').map(|relation| {
444 let relation = relation.trim();
445 if relation.is_empty() {
446 return Err("Empty relation".to_string());
447 }
448 relation.parse()
449 });
450 relations.push(entry_relations.collect::<Result<Vec<_>, _>>()?);
451 }
452 Ok(Relations(relations))
453 }
454}
455
456#[cfg(feature = "serde")]
457impl<'de> serde::Deserialize<'de> for Relations {
458 fn deserialize<D>(deserializer: D) -> Result<Relations, D::Error>
459 where
460 D: serde::Deserializer<'de>,
461 {
462 let s = String::deserialize(deserializer)?;
463 s.parse().map_err(serde::de::Error::custom)
464 }
465}
466
467#[cfg(feature = "serde")]
468impl serde::Serialize for Relations {
469 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
470 where
471 S: serde::Serializer,
472 {
473 self.to_string().serialize(serializer)
474 }
475}
476
477#[cfg(test)]
478mod tests {
479 use super::*;
480
481 #[test]
482 fn test_parse() {
483 let input = "python3-dulwich";
484 let parsed: Relations = input.parse().unwrap();
485 assert_eq!(parsed.to_string(), input);
486 assert_eq!(parsed.len(), 1);
487 let entry = &parsed[0];
488 assert_eq!(entry.len(), 1);
489 let relation = &entry[0];
490 assert_eq!(relation.to_string(), "python3-dulwich");
491 assert_eq!(relation.version, None);
492
493 let input = "python3-dulwich (>= 0.20.21)";
494 let parsed: Relations = input.parse().unwrap();
495 assert_eq!(parsed.to_string(), input);
496 assert_eq!(parsed.len(), 1);
497 let entry = &parsed[0];
498 assert_eq!(entry.len(), 1);
499 let relation = &entry[0];
500 assert_eq!(relation.to_string(), "python3-dulwich (>= 0.20.21)");
501 assert_eq!(
502 relation.version,
503 Some((
504 VersionConstraint::GreaterThanEqual,
505 "0.20.21".parse().unwrap()
506 ))
507 );
508 }
509
510 #[test]
511 fn test_multiple() {
512 let input = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)";
513 let parsed: Relations = input.parse().unwrap();
514 assert_eq!(parsed.to_string(), input);
515 assert_eq!(parsed.len(), 2);
516 let entry = &parsed[0];
517 assert_eq!(entry.len(), 1);
518 let relation = &entry[0];
519 assert_eq!(relation.to_string(), "python3-dulwich (>= 0.20.21)");
520 assert_eq!(
521 relation.version,
522 Some((
523 VersionConstraint::GreaterThanEqual,
524 "0.20.21".parse().unwrap()
525 ))
526 );
527 let entry = &parsed[1];
528 assert_eq!(entry.len(), 1);
529 let relation = &entry[0];
530 assert_eq!(relation.to_string(), "python3-dulwich (<< 0.21)");
531 assert_eq!(
532 relation.version,
533 Some((VersionConstraint::LessThan, "0.21".parse().unwrap()))
534 );
535 }
536
537 #[test]
538 fn test_architectures() {
539 let input = "python3-dulwich [amd64 arm64 armhf i386 mips mips64el mipsel ppc64el s390x]";
540 let parsed: Relations = input.parse().unwrap();
541 assert_eq!(parsed.to_string(), input);
542 assert_eq!(parsed.len(), 1);
543 let entry = &parsed[0];
544 assert_eq!(
545 entry[0].to_string(),
546 "python3-dulwich [amd64 arm64 armhf i386 mips mips64el mipsel ppc64el s390x]"
547 );
548 assert_eq!(entry.len(), 1);
549 let relation = &entry[0];
550 assert_eq!(
551 relation.to_string(),
552 "python3-dulwich [amd64 arm64 armhf i386 mips mips64el mipsel ppc64el s390x]"
553 );
554 assert_eq!(relation.version, None);
555 assert_eq!(
556 relation.architectures.as_ref().unwrap(),
557 &vec![
558 "amd64", "arm64", "armhf", "i386", "mips", "mips64el", "mipsel", "ppc64el", "s390x"
559 ]
560 .into_iter()
561 .map(|s| s.to_string())
562 .collect::<Vec<_>>()
563 );
564 }
565
566 #[test]
567 fn test_profiles() {
568 let input = "foo (>= 1.0) [i386 arm] <!nocheck> <!cross>, bar";
569 let parsed: Relations = input.parse().unwrap();
570 assert_eq!(parsed.to_string(), input);
571 assert_eq!(parsed.iter().count(), 2);
572 let entry = parsed.iter().next().unwrap();
573 assert_eq!(
574 entry[0].to_string(),
575 "foo (>= 1.0) [i386 arm] <!nocheck> <!cross>"
576 );
577 assert_eq!(entry.len(), 1);
578 let relation = entry[0];
579 assert_eq!(
580 relation.to_string(),
581 "foo (>= 1.0) [i386 arm] <!nocheck> <!cross>"
582 );
583 assert_eq!(
584 relation.version,
585 Some((VersionConstraint::GreaterThanEqual, "1.0".parse().unwrap()))
586 );
587 assert_eq!(
588 relation.architectures.as_ref().unwrap(),
589 &["i386", "arm"]
590 .into_iter()
591 .map(|s| s.to_string())
592 .collect::<Vec<_>>()
593 );
594 assert_eq!(
595 relation.profiles,
596 vec![
597 vec![BuildProfile::Disabled("nocheck".to_string())],
598 vec![BuildProfile::Disabled("cross".to_string())]
599 ]
600 );
601 }
602
603 #[cfg(feature = "serde")]
604 #[test]
605 fn test_serde_relations() {
606 let input = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)";
607 let parsed: Relations = input.parse().unwrap();
608 let serialized = serde_json::to_string(&parsed).unwrap();
609 assert_eq!(
610 serialized,
611 r#""python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)""#
612 );
613 let deserialized: Relations = serde_json::from_str(&serialized).unwrap();
614 assert_eq!(deserialized, parsed);
615 }
616
617 #[cfg(feature = "serde")]
618 #[test]
619 fn test_serde_relation() {
620 let input = "python3-dulwich (>= 0.20.21)";
621 let parsed: Relation = input.parse().unwrap();
622 let serialized = serde_json::to_string(&parsed).unwrap();
623 assert_eq!(serialized, r#""python3-dulwich (>= 0.20.21)""#);
624 let deserialized: Relation = serde_json::from_str(&serialized).unwrap();
625 assert_eq!(deserialized, parsed);
626 }
627
628 #[test]
629 fn test_relations_is_empty() {
630 let input = "python3-dulwich (>= 0.20.21)";
631 let parsed: Relations = input.parse().unwrap();
632 assert!(!parsed.is_empty());
633 let input = "";
634 let parsed: Relations = input.parse().unwrap();
635 assert!(parsed.is_empty());
636 }
637
638 #[test]
639 fn test_relations_len() {
640 let input = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)";
641 let parsed: Relations = input.parse().unwrap();
642 assert_eq!(parsed.len(), 2);
643 }
644
645 #[test]
646 fn test_relations_remove() {
647 let input = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)";
648 let mut parsed: Relations = input.parse().unwrap();
649 parsed.remove(1);
650 assert_eq!(parsed.len(), 1);
651 assert_eq!(parsed.to_string(), "python3-dulwich (>= 0.20.21)");
652 }
653
654 #[test]
655 fn test_relations_satisfied_by() {
656 let input = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)";
657 let parsed: Relations = input.parse().unwrap();
658 assert!(
659 parsed.satisfied_by(|name: &str| -> Option<debversion::Version> {
660 match name {
661 "python3-dulwich" => Some("0.20.21".parse().unwrap()),
662 _ => None,
663 }
664 })
665 );
666 assert!(
667 !parsed.satisfied_by(|name: &str| -> Option<debversion::Version> {
668 match name {
669 "python3-dulwich" => Some("0.21".parse().unwrap()),
670 _ => None,
671 }
672 })
673 );
674 }
675
676 #[test]
677 fn test_relation_satisfied_by() {
678 let input = "python3-dulwich (>= 0.20.21)";
679 let parsed: Relation = input.parse().unwrap();
680 assert!(
681 parsed.satisfied_by(|name: &str| -> Option<debversion::Version> {
682 match name {
683 "python3-dulwich" => Some("0.20.21".parse().unwrap()),
684 _ => None,
685 }
686 })
687 );
688 assert!(
689 !parsed.satisfied_by(|name: &str| -> Option<debversion::Version> {
690 match name {
691 "python3-dulwich" => Some("0.20.20".parse().unwrap()),
692 _ => None,
693 }
694 })
695 );
696 }
697
698 #[test]
699 fn test_relations_from_iter() {
700 let relation1 = Relation::build("python3-dulwich")
701 .version(VersionConstraint::GreaterThanEqual, "0.19.0")
702 .build();
703 let relation2 = Relation::build("python3-requests").build();
704
705 let relations: Relations = vec![relation1, relation2].into_iter().collect();
706
707 assert_eq!(
708 relations.to_string(),
709 "python3-dulwich (>= 0.19.0), python3-requests"
710 );
711 }
712}