debian_control/lossless/
relations.rs

1//! Parser for relationship fields like `Depends`, `Recommends`, etc.
2//!
3//! # Example
4//! ```
5//! use debian_control::lossless::relations::{Relations, Relation};
6//! use debian_control::relations::VersionConstraint;
7//!
8//! let mut relations: Relations = r"python3-dulwich (>= 0.19.0), python3-requests, python3-urllib3 (<< 1.26.0)".parse().unwrap();
9//! assert_eq!(relations.to_string(), "python3-dulwich (>= 0.19.0), python3-requests, python3-urllib3 (<< 1.26.0)");
10//! assert!(relations.satisfied_by(|name: &str| -> Option<debversion::Version> {
11//!    match name {
12//!    "python3-dulwich" => Some("0.19.0".parse().unwrap()),
13//!    "python3-requests" => Some("2.25.1".parse().unwrap()),
14//!    "python3-urllib3" => Some("1.25.11".parse().unwrap()),
15//!    _ => None
16//!    }}));
17//! relations.remove_entry(1);
18//! relations.get_entry(0).unwrap().get_relation(0).unwrap().set_archqual("amd64");
19//! assert_eq!(relations.to_string(), "python3-dulwich:amd64 (>= 0.19.0), python3-urllib3 (<< 1.26.0)");
20//! ```
21use crate::relations::SyntaxKind::{self, *};
22use crate::relations::{BuildProfile, VersionConstraint};
23use debversion::Version;
24use rowan::{Direction, NodeOrToken};
25use std::collections::HashSet;
26
27/// Error type for parsing relations fields
28#[derive(Debug, Clone, PartialEq, Eq, Hash)]
29pub struct ParseError(Vec<String>);
30
31impl std::fmt::Display for ParseError {
32    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
33        for err in &self.0 {
34            writeln!(f, "{}", err)?;
35        }
36        Ok(())
37    }
38}
39
40impl std::error::Error for ParseError {}
41
42/// Second, implementing the `Language` trait teaches rowan to convert between
43/// these two SyntaxKind types, allowing for a nicer SyntaxNode API where
44/// "kinds" are values from our `enum SyntaxKind`, instead of plain u16 values.
45#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
46enum Lang {}
47impl rowan::Language for Lang {
48    type Kind = SyntaxKind;
49    fn kind_from_raw(raw: rowan::SyntaxKind) -> Self::Kind {
50        unsafe { std::mem::transmute::<u16, SyntaxKind>(raw.0) }
51    }
52    fn kind_to_raw(kind: Self::Kind) -> rowan::SyntaxKind {
53        kind.into()
54    }
55}
56
57/// GreenNode is an immutable tree, which is cheap to change,
58/// but doesn't contain offsets and parent pointers.
59use rowan::{GreenNode, GreenToken};
60
61/// You can construct GreenNodes by hand, but a builder
62/// is helpful for top-down parsers: it maintains a stack
63/// of currently in-progress nodes
64use rowan::GreenNodeBuilder;
65
66/// The parse results are stored as a "green tree".
67/// We'll discuss working with the results later
68struct Parse {
69    green_node: GreenNode,
70    #[allow(unused)]
71    errors: Vec<String>,
72}
73
74fn parse(text: &str, allow_substvar: bool) -> Parse {
75    struct Parser {
76        /// input tokens, including whitespace,
77        /// in *reverse* order.
78        tokens: Vec<(SyntaxKind, String)>,
79        /// the in-progress tree.
80        builder: GreenNodeBuilder<'static>,
81        /// the list of syntax errors we've accumulated
82        /// so far.
83        errors: Vec<String>,
84        /// whether to allow substvars
85        allow_substvar: bool,
86    }
87
88    impl Parser {
89        fn parse_substvar(&mut self) {
90            self.builder.start_node(SyntaxKind::SUBSTVAR.into());
91            self.bump();
92            if self.current() != Some(L_CURLY) {
93                self.error(format!("expected {{ but got {:?}", self.current()).to_string());
94            } else {
95                self.bump();
96            }
97            loop {
98                match self.current() {
99                    Some(IDENT) | Some(COLON) => {
100                        self.bump();
101                    }
102                    Some(R_CURLY) => {
103                        break;
104                    }
105                    e => {
106                        self.error(format!("expected identifier or : but got {:?}", e).to_string());
107                    }
108                }
109            }
110            if self.current() != Some(R_CURLY) {
111                self.error(format!("expected }} but got {:?}", self.current()).to_string());
112            } else {
113                self.bump();
114            }
115            self.builder.finish_node();
116        }
117
118        fn parse_entry(&mut self) {
119            self.skip_ws();
120            self.builder.start_node(SyntaxKind::ENTRY.into());
121            loop {
122                self.parse_relation();
123                match self.peek_past_ws() {
124                    Some(COMMA) => {
125                        break;
126                    }
127                    Some(PIPE) => {
128                        self.skip_ws();
129                        self.bump();
130                        self.skip_ws();
131                    }
132                    None => {
133                        self.skip_ws();
134                        break;
135                    }
136                    _ => {
137                        self.skip_ws();
138                        self.builder.start_node(SyntaxKind::ERROR.into());
139                        match self.tokens.pop() {
140                            Some((k, t)) => {
141                                self.builder.token(k.into(), t.as_str());
142                                self.errors
143                                    .push(format!("Expected comma or pipe, not {:?}", (k, t)));
144                            }
145                            None => {
146                                self.errors
147                                    .push("Expected comma or pipe, got end of file".to_string());
148                            }
149                        }
150                        self.builder.finish_node();
151                    }
152                }
153            }
154            self.builder.finish_node();
155        }
156
157        fn error(&mut self, error: String) {
158            self.errors.push(error);
159            self.builder.start_node(SyntaxKind::ERROR.into());
160            if self.current().is_some() {
161                self.bump();
162            }
163            self.builder.finish_node();
164        }
165
166        fn parse_relation(&mut self) {
167            self.builder.start_node(SyntaxKind::RELATION.into());
168            if self.current() == Some(IDENT) {
169                self.bump();
170            } else {
171                self.error("Expected package name".to_string());
172            }
173            match self.peek_past_ws() {
174                Some(COLON) => {
175                    self.skip_ws();
176                    self.builder.start_node(ARCHQUAL.into());
177                    self.bump();
178                    self.skip_ws();
179                    if self.current() == Some(IDENT) {
180                        self.bump();
181                    } else {
182                        self.error("Expected architecture name".to_string());
183                    }
184                    self.builder.finish_node();
185                    self.skip_ws();
186                }
187                Some(PIPE) | Some(COMMA) => {}
188                None | Some(L_PARENS) | Some(L_BRACKET) | Some(L_ANGLE) => {
189                    self.skip_ws();
190                }
191                e => {
192                    self.skip_ws();
193                    self.error(format!(
194                        "Expected ':' or '|' or '[' or '<' or ',' but got {:?}",
195                        e
196                    ));
197                }
198            }
199
200            if self.peek_past_ws() == Some(L_PARENS) {
201                self.skip_ws();
202                self.builder.start_node(VERSION.into());
203                self.bump();
204                self.skip_ws();
205
206                self.builder.start_node(CONSTRAINT.into());
207
208                while self.current() == Some(L_ANGLE)
209                    || self.current() == Some(R_ANGLE)
210                    || self.current() == Some(EQUAL)
211                {
212                    self.bump();
213                }
214
215                self.builder.finish_node();
216
217                self.skip_ws();
218
219                if self.current() == Some(IDENT) {
220                    self.bump();
221                } else {
222                    self.error("Expected version".to_string());
223                }
224
225                if self.current() == Some(R_PARENS) {
226                    self.bump();
227                } else {
228                    self.error("Expected ')'".to_string());
229                }
230
231                self.builder.finish_node();
232            }
233
234            if self.peek_past_ws() == Some(L_BRACKET) {
235                self.skip_ws();
236                self.builder.start_node(ARCHITECTURES.into());
237                self.bump();
238                loop {
239                    self.skip_ws();
240                    match self.current() {
241                        Some(NOT) => {
242                            self.bump();
243                        }
244                        Some(IDENT) => {
245                            self.bump();
246                        }
247                        Some(R_BRACKET) => {
248                            self.bump();
249                            break;
250                        }
251                        _ => {
252                            self.error("Expected architecture name or '!' or ']'".to_string());
253                        }
254                    }
255                }
256                self.builder.finish_node();
257            }
258
259            while self.peek_past_ws() == Some(L_ANGLE) {
260                self.skip_ws();
261                self.builder.start_node(PROFILES.into());
262                self.bump();
263
264                loop {
265                    self.skip_ws();
266                    match self.current() {
267                        Some(IDENT) => {
268                            self.bump();
269                        }
270                        Some(NOT) => {
271                            self.bump();
272                            self.skip_ws();
273                            if self.current() == Some(IDENT) {
274                                self.bump();
275                            } else {
276                                self.error("Expected profile".to_string());
277                            }
278                        }
279                        Some(R_ANGLE) => {
280                            self.bump();
281                            break;
282                        }
283                        None => {
284                            self.error("Expected profile or '>'".to_string());
285                            break;
286                        }
287                        _ => {
288                            self.error("Expected profile or '!' or '>'".to_string());
289                        }
290                    }
291                }
292
293                self.builder.finish_node();
294            }
295
296            self.builder.finish_node();
297        }
298
299        fn parse(mut self) -> Parse {
300            self.builder.start_node(SyntaxKind::ROOT.into());
301
302            self.skip_ws();
303
304            while self.current().is_some() {
305                match self.current() {
306                    Some(IDENT) => self.parse_entry(),
307                    Some(DOLLAR) => {
308                        if self.allow_substvar {
309                            self.parse_substvar()
310                        } else {
311                            self.error("Substvars are not allowed".to_string());
312                        }
313                    }
314                    Some(COMMA) => {
315                        // Empty entry, but that's okay - probably?
316                    }
317                    Some(c) => {
318                        self.error(format!("expected $ or identifier but got {:?}", c));
319                    }
320                    None => {
321                        self.error("expected identifier but got end of file".to_string());
322                    }
323                }
324
325                self.skip_ws();
326                match self.current() {
327                    Some(COMMA) => {
328                        self.bump();
329                    }
330                    None => {
331                        break;
332                    }
333                    c => {
334                        self.error(format!("expected comma or end of file but got {:?}", c));
335                    }
336                }
337                self.skip_ws();
338            }
339
340            self.builder.finish_node();
341            // Turn the builder into a GreenNode
342            Parse {
343                green_node: self.builder.finish(),
344                errors: self.errors,
345            }
346        }
347        /// Advance one token, adding it to the current branch of the tree builder.
348        fn bump(&mut self) {
349            let (kind, text) = self.tokens.pop().unwrap();
350            self.builder.token(kind.into(), text.as_str());
351        }
352        /// Peek at the first unprocessed token
353        fn current(&self) -> Option<SyntaxKind> {
354            self.tokens.last().map(|(kind, _)| *kind)
355        }
356        fn skip_ws(&mut self) {
357            while self.current() == Some(WHITESPACE) || self.current() == Some(NEWLINE) {
358                self.bump()
359            }
360        }
361
362        fn peek_past_ws(&self) -> Option<SyntaxKind> {
363            let mut i = self.tokens.len();
364            while i > 0 {
365                i -= 1;
366                match self.tokens[i].0 {
367                    WHITESPACE | NEWLINE => {}
368                    _ => return Some(self.tokens[i].0),
369                }
370            }
371            None
372        }
373    }
374
375    let mut tokens = crate::relations::lex(text);
376    tokens.reverse();
377    Parser {
378        tokens,
379        builder: GreenNodeBuilder::new(),
380        errors: Vec::new(),
381        allow_substvar,
382    }
383    .parse()
384}
385
386/// To work with the parse results we need a view into the
387/// green tree - the Syntax tree.
388/// It is also immutable, like a GreenNode,
389/// but it contains parent pointers, offsets, and
390/// has identity semantics.
391
392type SyntaxNode = rowan::SyntaxNode<Lang>;
393#[allow(unused)]
394type SyntaxToken = rowan::SyntaxToken<Lang>;
395#[allow(unused)]
396type SyntaxElement = rowan::NodeOrToken<SyntaxNode, SyntaxToken>;
397
398impl Parse {
399    fn root_mut(&self) -> Relations {
400        Relations::cast(SyntaxNode::new_root_mut(self.green_node.clone())).unwrap()
401    }
402}
403
404macro_rules! ast_node {
405    ($ast:ident, $kind:ident) => {
406        /// A node in the syntax tree representing a $ast
407        #[repr(transparent)]
408        pub struct $ast(SyntaxNode);
409        impl $ast {
410            #[allow(unused)]
411            fn cast(node: SyntaxNode) -> Option<Self> {
412                if node.kind() == $kind {
413                    Some(Self(node))
414                } else {
415                    None
416                }
417            }
418        }
419
420        impl std::fmt::Display for $ast {
421            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
422                f.write_str(&self.0.text().to_string())
423            }
424        }
425    };
426}
427
428ast_node!(Relations, ROOT);
429ast_node!(Entry, ENTRY);
430ast_node!(Relation, RELATION);
431ast_node!(Substvar, SUBSTVAR);
432
433impl PartialEq for Relations {
434    fn eq(&self, other: &Self) -> bool {
435        self.entries().collect::<Vec<_>>() == other.entries().collect::<Vec<_>>()
436    }
437}
438
439impl PartialEq for Entry {
440    fn eq(&self, other: &Self) -> bool {
441        self.relations().collect::<Vec<_>>() == other.relations().collect::<Vec<_>>()
442    }
443}
444
445impl PartialEq for Relation {
446    fn eq(&self, other: &Self) -> bool {
447        self.name() == other.name()
448            && self.version() == other.version()
449            && self.archqual() == other.archqual()
450            && self.architectures().map(|x| x.collect::<HashSet<_>>())
451                == other.architectures().map(|x| x.collect::<HashSet<_>>())
452            && self.profiles().eq(other.profiles())
453    }
454}
455
456#[cfg(feature = "serde")]
457impl serde::Serialize for Relations {
458    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
459        let rep = self.to_string();
460        serializer.serialize_str(&rep)
461    }
462}
463
464#[cfg(feature = "serde")]
465impl<'de> serde::Deserialize<'de> for Relations {
466    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
467        let s = String::deserialize(deserializer)?;
468        let relations = s.parse().map_err(serde::de::Error::custom)?;
469        Ok(relations)
470    }
471}
472
473impl std::fmt::Debug for Relations {
474    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
475        let mut s = f.debug_struct("Relations");
476
477        for entry in self.entries() {
478            s.field("entry", &entry);
479        }
480
481        s.finish()
482    }
483}
484
485impl std::fmt::Debug for Entry {
486    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
487        let mut s = f.debug_struct("Entry");
488
489        for relation in self.relations() {
490            s.field("relation", &relation);
491        }
492
493        s.finish()
494    }
495}
496
497#[cfg(feature = "serde")]
498impl serde::Serialize for Entry {
499    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
500        let rep = self.to_string();
501        serializer.serialize_str(&rep)
502    }
503}
504
505#[cfg(feature = "serde")]
506impl<'de> serde::Deserialize<'de> for Entry {
507    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
508        let s = String::deserialize(deserializer)?;
509        let entry = s.parse().map_err(serde::de::Error::custom)?;
510        Ok(entry)
511    }
512}
513
514impl std::fmt::Debug for Relation {
515    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
516        let mut s = f.debug_struct("Relation");
517
518        s.field("name", &self.name());
519
520        if let Some((vc, version)) = self.version() {
521            s.field("version", &vc);
522            s.field("version", &version);
523        }
524
525        s.finish()
526    }
527}
528
529#[cfg(feature = "serde")]
530impl serde::Serialize for Relation {
531    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
532        let rep = self.to_string();
533        serializer.serialize_str(&rep)
534    }
535}
536
537#[cfg(feature = "serde")]
538impl<'de> serde::Deserialize<'de> for Relation {
539    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
540        let s = String::deserialize(deserializer)?;
541        let relation = s.parse().map_err(serde::de::Error::custom)?;
542        Ok(relation)
543    }
544}
545
546impl Default for Relations {
547    fn default() -> Self {
548        Self::new()
549    }
550}
551
552impl Relations {
553    /// Create a new relations field
554    pub fn new() -> Self {
555        Self::from(vec![])
556    }
557
558    /// Wrap and sort this relations field
559    #[must_use]
560    pub fn wrap_and_sort(self) -> Self {
561        let mut entries = self
562            .entries()
563            .map(|e| e.wrap_and_sort())
564            .collect::<Vec<_>>();
565        entries.sort();
566        // TODO: preserve comments
567        Self::from(entries)
568    }
569
570    /// Iterate over the entries in this relations field
571    pub fn entries(&self) -> impl Iterator<Item = Entry> + '_ {
572        self.0.children().filter_map(Entry::cast)
573    }
574
575    /// Iterate over the entries in this relations field
576    pub fn iter(&self) -> impl Iterator<Item = Entry> + '_ {
577        self.entries()
578    }
579
580    /// Remove the entry at the given index
581    pub fn get_entry(&self, idx: usize) -> Option<Entry> {
582        self.entries().nth(idx)
583    }
584
585    /// Remove the entry at the given index
586    pub fn remove_entry(&mut self, idx: usize) -> Entry {
587        let mut entry = self.get_entry(idx).unwrap();
588        entry.remove();
589        entry
590    }
591
592    /// Insert a new entry at the given index
593    pub fn insert(&mut self, idx: usize, entry: Entry) {
594        let is_empty = !self.0.children_with_tokens().any(|n| n.kind() == COMMA);
595        let (position, new_children) = if let Some(current_entry) = self.entries().nth(idx) {
596            let to_insert: Vec<NodeOrToken<GreenNode, GreenToken>> = if idx == 0 && is_empty {
597                vec![entry.0.green().into()]
598            } else {
599                vec![
600                    entry.0.green().into(),
601                    NodeOrToken::Token(GreenToken::new(COMMA.into(), ",")),
602                    NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), " ")),
603                ]
604            };
605
606            (current_entry.0.index(), to_insert)
607        } else {
608            let child_count = self.0.children_with_tokens().count();
609            (
610                child_count,
611                if idx == 0 {
612                    vec![entry.0.green().into()]
613                } else {
614                    vec![
615                        NodeOrToken::Token(GreenToken::new(COMMA.into(), ",")),
616                        NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), " ")),
617                        entry.0.green().into(),
618                    ]
619                },
620            )
621        };
622        // We can safely replace the root here since Relations is a root node
623        self.0 = SyntaxNode::new_root_mut(
624            self.0.replace_with(
625                self.0
626                    .green()
627                    .splice_children(position..position, new_children),
628            ),
629        );
630    }
631
632    /// Replace the entry at the given index
633    pub fn replace(&mut self, idx: usize, entry: Entry) {
634        let current_entry = self.get_entry(idx).unwrap();
635        self.0.splice_children(
636            current_entry.0.index()..current_entry.0.index() + 1,
637            vec![entry.0.into()],
638        );
639    }
640
641    /// Push a new entry to the relations field
642    pub fn push(&mut self, entry: Entry) {
643        let pos = self.entries().count();
644        self.insert(pos, entry);
645    }
646
647    /// Return the names of substvars in this relations field
648    pub fn substvars(&self) -> impl Iterator<Item = String> + '_ {
649        self.0
650            .children()
651            .filter_map(Substvar::cast)
652            .map(|s| s.to_string())
653    }
654
655    /// Parse a relations field from a string, allowing syntax errors
656    pub fn parse_relaxed(s: &str, allow_substvar: bool) -> (Relations, Vec<String>) {
657        let parse = parse(s, allow_substvar);
658        (parse.root_mut(), parse.errors)
659    }
660
661    /// Check if this relations field is satisfied by the given package versions.
662    pub fn satisfied_by(&self, package_version: impl crate::VersionLookup + Copy) -> bool {
663        self.entries().all(|e| e.satisfied_by(package_version))
664    }
665
666    /// Check if this relations field is empty
667    pub fn is_empty(&self) -> bool {
668        self.entries().count() == 0
669    }
670
671    /// Get the number of entries in this relations field
672    pub fn len(&self) -> usize {
673        self.entries().count()
674    }
675}
676
677impl From<Vec<Entry>> for Relations {
678    fn from(entries: Vec<Entry>) -> Self {
679        let mut builder = GreenNodeBuilder::new();
680        builder.start_node(ROOT.into());
681        for (i, entry) in entries.into_iter().enumerate() {
682            if i > 0 {
683                builder.token(COMMA.into(), ",");
684                builder.token(WHITESPACE.into(), " ");
685            }
686            inject(&mut builder, entry.0);
687        }
688        builder.finish_node();
689        Relations(SyntaxNode::new_root_mut(builder.finish()))
690    }
691}
692
693impl From<Entry> for Relations {
694    fn from(entry: Entry) -> Self {
695        Self::from(vec![entry])
696    }
697}
698
699impl Default for Entry {
700    fn default() -> Self {
701        Self::new()
702    }
703}
704
705impl PartialOrd for Entry {
706    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
707        let mut rels_a = self.relations();
708        let mut rels_b = other.relations();
709        while let (Some(a), Some(b)) = (rels_a.next(), rels_b.next()) {
710            match a.cmp(&b) {
711                std::cmp::Ordering::Equal => continue,
712                x => return Some(x),
713            }
714        }
715
716        if rels_a.next().is_some() {
717            return Some(std::cmp::Ordering::Greater);
718        }
719
720        if rels_b.next().is_some() {
721            return Some(std::cmp::Ordering::Less);
722        }
723
724        Some(std::cmp::Ordering::Equal)
725    }
726}
727
728impl Eq for Entry {}
729
730impl Ord for Entry {
731    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
732        self.partial_cmp(other).unwrap()
733    }
734}
735
736impl Entry {
737    /// Create a new entry
738    pub fn new() -> Self {
739        let mut builder = GreenNodeBuilder::new();
740        builder.start_node(SyntaxKind::ENTRY.into());
741        builder.finish_node();
742        Entry(SyntaxNode::new_root_mut(builder.finish()))
743    }
744
745    /// Replace the relation at the given index
746    pub fn replace(&mut self, idx: usize, relation: Relation) {
747        let current_relation = self.get_relation(idx).unwrap();
748
749        let old_root = current_relation.0;
750        let new_root = relation.0;
751        // Preserve white the current relation has
752        let mut prev = new_root.first_child_or_token();
753        let mut new_head_len = 0;
754        // First, strip off any whitespace from the new relation
755        while let Some(p) = prev {
756            if p.kind() == WHITESPACE || p.kind() == NEWLINE {
757                new_head_len += 1;
758                prev = p.next_sibling_or_token();
759            } else {
760                break;
761            }
762        }
763        let mut new_tail_len = 0;
764        let mut next = new_root.last_child_or_token();
765        while let Some(n) = next {
766            if n.kind() == WHITESPACE || n.kind() == NEWLINE {
767                new_tail_len += 1;
768                next = n.prev_sibling_or_token();
769            } else {
770                break;
771            }
772        }
773        // Then, inherit the whitespace from the old relation
774        let mut prev = old_root.first_child_or_token();
775        let mut old_head = vec![];
776        while let Some(p) = prev {
777            if p.kind() == WHITESPACE || p.kind() == NEWLINE {
778                old_head.push(p.clone());
779                prev = p.next_sibling_or_token();
780            } else {
781                break;
782            }
783        }
784        let mut old_tail = vec![];
785        let mut next = old_root.last_child_or_token();
786        while let Some(n) = next {
787            if n.kind() == WHITESPACE || n.kind() == NEWLINE {
788                old_tail.push(n.clone());
789                next = n.prev_sibling_or_token();
790            } else {
791                break;
792            }
793        }
794        new_root.splice_children(0..new_head_len, old_head);
795        let tail_pos = new_root.children_with_tokens().count() - new_tail_len;
796        new_root.splice_children(
797            tail_pos - new_tail_len..tail_pos,
798            old_tail.into_iter().rev(),
799        );
800        let index = old_root.index();
801        self.0
802            .splice_children(index..index + 1, vec![new_root.into()]);
803    }
804
805    /// Wrap and sort the relations in this entry
806    #[must_use]
807    pub fn wrap_and_sort(&self) -> Self {
808        let mut relations = self
809            .relations()
810            .map(|r| r.wrap_and_sort())
811            .collect::<Vec<_>>();
812        // TODO: preserve comments
813        relations.sort();
814        Self::from(relations)
815    }
816
817    /// Iterate over the relations in this entry
818    pub fn relations(&self) -> impl Iterator<Item = Relation> + '_ {
819        self.0.children().filter_map(Relation::cast)
820    }
821
822    /// Iterate over the relations in this entry
823    pub fn iter(&self) -> impl Iterator<Item = Relation> + '_ {
824        self.relations()
825    }
826
827    /// Get the relation at the given index
828    pub fn get_relation(&self, idx: usize) -> Option<Relation> {
829        self.relations().nth(idx)
830    }
831
832    /// Remove the relation at the given index
833    ///
834    /// # Arguments
835    /// * `idx` - The index of the relation to remove
836    ///
837    /// # Example
838    /// ```
839    /// use debian_control::lossless::relations::{Relation,Entry};
840    /// let mut entry: Entry = r"python3-dulwich (>= 0.19.0) | python3-requests".parse().unwrap();
841    /// entry.remove_relation(1);
842    /// assert_eq!(entry.to_string(), "python3-dulwich (>= 0.19.0)");
843    /// ```
844    pub fn remove_relation(&self, idx: usize) -> Relation {
845        let mut relation = self.get_relation(idx).unwrap();
846        relation.remove();
847        relation
848    }
849
850    /// Check if this entry is satisfied by the given package versions.
851    ///
852    /// # Arguments
853    /// * `package_version` - A function that returns the version of a package.
854    ///
855    /// # Example
856    /// ```
857    /// use debian_control::lossless::relations::{Relation,Entry};
858    /// let entry = Entry::from(vec!["samba (>= 2.0)".parse::<Relation>().unwrap()]);
859    /// assert!(entry.satisfied_by(|name: &str| -> Option<debversion::Version> {
860    ///    match name {
861    ///    "samba" => Some("2.0".parse().unwrap()),
862    ///    _ => None
863    /// }}));
864    /// ```
865    pub fn satisfied_by(&self, package_version: impl crate::VersionLookup + Copy) -> bool {
866        self.relations().any(|r| {
867            let actual = package_version.lookup_version(r.name().as_str());
868            if let Some((vc, version)) = r.version() {
869                if let Some(actual) = actual {
870                    match vc {
871                        VersionConstraint::GreaterThanEqual => *actual >= version,
872                        VersionConstraint::LessThanEqual => *actual <= version,
873                        VersionConstraint::Equal => *actual == version,
874                        VersionConstraint::GreaterThan => *actual > version,
875                        VersionConstraint::LessThan => *actual < version,
876                    }
877                } else {
878                    false
879                }
880            } else {
881                actual.is_some()
882            }
883        })
884    }
885
886    /// Remove this entry
887    ///
888    /// # Example
889    /// ```
890    /// use debian_control::lossless::relations::{Relations,Entry};
891    /// let mut relations: Relations = r"python3-dulwich (>= 0.19.0), python3-urllib3 (<< 1.26.0)".parse().unwrap();
892    /// let mut entry = relations.get_entry(0).unwrap();
893    /// entry.remove();
894    /// assert_eq!(relations.to_string(), "python3-urllib3 (<< 1.26.0)");
895    /// ```
896    pub fn remove(&mut self) {
897        let mut removed_comma = false;
898        let is_first = !self
899            .0
900            .siblings(Direction::Prev)
901            .skip(1)
902            .any(|n| n.kind() == ENTRY);
903        while let Some(n) = self.0.next_sibling_or_token() {
904            if n.kind() == WHITESPACE || n.kind() == NEWLINE {
905                n.detach();
906            } else if n.kind() == COMMA {
907                n.detach();
908                removed_comma = true;
909                break;
910            } else {
911                panic!("Unexpected node: {:?}", n);
912            }
913        }
914        if !is_first {
915            while let Some(n) = self.0.prev_sibling_or_token() {
916                if n.kind() == WHITESPACE || n.kind() == NEWLINE {
917                    n.detach();
918                } else if !removed_comma && n.kind() == COMMA {
919                    n.detach();
920                    break;
921                } else {
922                    break;
923                }
924            }
925        } else {
926            while let Some(n) = self.0.next_sibling_or_token() {
927                if n.kind() == WHITESPACE || n.kind() == NEWLINE {
928                    n.detach();
929                } else {
930                    break;
931                }
932            }
933        }
934        self.0.detach();
935    }
936
937    /// Check if this entry is empty
938    pub fn is_empty(&self) -> bool {
939        self.relations().count() == 0
940    }
941
942    /// Get the number of relations in this entry
943    pub fn len(&self) -> usize {
944        self.relations().count()
945    }
946
947    /// Push a new relation to the entry
948    ///
949    /// # Arguments
950    /// * `relation` - The relation to push
951    ///
952    /// # Example
953    /// ```
954    /// use debian_control::lossless::relations::{Relation,Entry};
955    /// let mut entry: Entry = "samba (>= 2.0)".parse().unwrap();
956    /// entry.push("python3-requests".parse().unwrap());
957    /// assert_eq!(entry.to_string(), "samba (>= 2.0) | python3-requests");
958    /// ```
959    pub fn push(&mut self, relation: Relation) {
960        let is_empty = !self
961            .0
962            .children_with_tokens()
963            .any(|n| n.kind() == PIPE || n.kind() == RELATION);
964
965        let (position, new_children) = if let Some(current_relation) = self.relations().last() {
966            let to_insert: Vec<NodeOrToken<GreenNode, GreenToken>> = if is_empty {
967                vec![relation.0.green().into()]
968            } else {
969                vec![
970                    NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), " ")),
971                    NodeOrToken::Token(GreenToken::new(PIPE.into(), "|")),
972                    NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), " ")),
973                    relation.0.green().into(),
974                ]
975            };
976
977            (current_relation.0.index() + 1, to_insert)
978        } else {
979            let child_count = self.0.children_with_tokens().count();
980            (
981                child_count,
982                if is_empty {
983                    vec![relation.0.green().into()]
984                } else {
985                    vec![
986                        NodeOrToken::Token(GreenToken::new(PIPE.into(), "|")),
987                        NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), " ")),
988                        relation.0.green().into(),
989                    ]
990                },
991            )
992        };
993
994        let new_root = SyntaxNode::new_root_mut(
995            self.0.replace_with(
996                self.0
997                    .green()
998                    .splice_children(position..position, new_children),
999            ),
1000        );
1001
1002        if let Some(parent) = self.0.parent() {
1003            parent.splice_children(self.0.index()..self.0.index() + 1, vec![new_root.into()]);
1004            self.0 = parent
1005                .children_with_tokens()
1006                .nth(self.0.index())
1007                .unwrap()
1008                .clone()
1009                .into_node()
1010                .unwrap();
1011        } else {
1012            self.0 = new_root;
1013        }
1014    }
1015}
1016
1017fn inject(builder: &mut GreenNodeBuilder, node: SyntaxNode) {
1018    builder.start_node(node.kind().into());
1019    for child in node.children_with_tokens() {
1020        match child {
1021            rowan::NodeOrToken::Node(child) => {
1022                inject(builder, child);
1023            }
1024            rowan::NodeOrToken::Token(token) => {
1025                builder.token(token.kind().into(), token.text());
1026            }
1027        }
1028    }
1029    builder.finish_node();
1030}
1031
1032impl From<Vec<Relation>> for Entry {
1033    fn from(relations: Vec<Relation>) -> Self {
1034        let mut builder = GreenNodeBuilder::new();
1035        builder.start_node(SyntaxKind::ENTRY.into());
1036        for (i, relation) in relations.into_iter().enumerate() {
1037            if i > 0 {
1038                builder.token(WHITESPACE.into(), " ");
1039                builder.token(COMMA.into(), "|");
1040                builder.token(WHITESPACE.into(), " ");
1041            }
1042            inject(&mut builder, relation.0);
1043        }
1044        builder.finish_node();
1045        Entry(SyntaxNode::new_root_mut(builder.finish()))
1046    }
1047}
1048
1049impl From<Relation> for Entry {
1050    fn from(relation: Relation) -> Self {
1051        Self::from(vec![relation])
1052    }
1053}
1054
1055impl Relation {
1056    /// Create a new relation
1057    ///
1058    /// # Arguments
1059    /// * `name` - The name of the package
1060    /// * `version_constraint` - The version constraint and version to use
1061    ///
1062    /// # Example
1063    /// ```
1064    /// use debian_control::lossless::relations::{Relation};
1065    /// use debian_control::relations::VersionConstraint;
1066    /// let relation = Relation::new("samba", Some((VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap())));
1067    /// assert_eq!(relation.to_string(), "samba (>= 2.0)");
1068    /// ```
1069    pub fn new(name: &str, version_constraint: Option<(VersionConstraint, Version)>) -> Self {
1070        let mut builder = GreenNodeBuilder::new();
1071        builder.start_node(SyntaxKind::RELATION.into());
1072        builder.token(IDENT.into(), name);
1073        if let Some((vc, version)) = version_constraint {
1074            builder.token(WHITESPACE.into(), " ");
1075            builder.start_node(SyntaxKind::VERSION.into());
1076            builder.token(L_PARENS.into(), "(");
1077            builder.start_node(SyntaxKind::CONSTRAINT.into());
1078            for c in vc.to_string().chars() {
1079                builder.token(
1080                    match c {
1081                        '>' => R_ANGLE.into(),
1082                        '<' => L_ANGLE.into(),
1083                        '=' => EQUAL.into(),
1084                        _ => unreachable!(),
1085                    },
1086                    c.to_string().as_str(),
1087                );
1088            }
1089            builder.finish_node();
1090
1091            builder.token(WHITESPACE.into(), " ");
1092
1093            builder.token(IDENT.into(), version.to_string().as_str());
1094
1095            builder.token(R_PARENS.into(), ")");
1096
1097            builder.finish_node();
1098        }
1099
1100        builder.finish_node();
1101        Relation(SyntaxNode::new_root_mut(builder.finish()))
1102    }
1103
1104    /// Wrap and sort this relation
1105    ///
1106    /// # Example
1107    /// ```
1108    /// use debian_control::lossless::relations::Relation;
1109    /// let relation = "  samba  (  >= 2.0) ".parse::<Relation>().unwrap();
1110    /// assert_eq!(relation.wrap_and_sort().to_string(), "samba (>= 2.0)");
1111    /// ```
1112    #[must_use]
1113    pub fn wrap_and_sort(&self) -> Self {
1114        let mut builder = GreenNodeBuilder::new();
1115        builder.start_node(SyntaxKind::RELATION.into());
1116        builder.token(IDENT.into(), self.name().as_str());
1117        if let Some(archqual) = self.archqual() {
1118            builder.token(COLON.into(), ":");
1119            builder.token(IDENT.into(), archqual.as_str());
1120        }
1121        if let Some((vc, version)) = self.version() {
1122            builder.token(WHITESPACE.into(), " ");
1123            builder.start_node(SyntaxKind::VERSION.into());
1124            builder.token(L_PARENS.into(), "(");
1125            builder.start_node(SyntaxKind::CONSTRAINT.into());
1126            builder.token(
1127                match vc {
1128                    VersionConstraint::GreaterThanEqual => R_ANGLE.into(),
1129                    VersionConstraint::LessThanEqual => L_ANGLE.into(),
1130                    VersionConstraint::Equal => EQUAL.into(),
1131                    VersionConstraint::GreaterThan => R_ANGLE.into(),
1132                    VersionConstraint::LessThan => L_ANGLE.into(),
1133                },
1134                vc.to_string().as_str(),
1135            );
1136            builder.finish_node();
1137            builder.token(WHITESPACE.into(), " ");
1138            builder.token(IDENT.into(), version.to_string().as_str());
1139            builder.token(R_PARENS.into(), ")");
1140            builder.finish_node();
1141        }
1142        if let Some(architectures) = self.architectures() {
1143            builder.token(WHITESPACE.into(), " ");
1144            builder.start_node(ARCHITECTURES.into());
1145            builder.token(L_BRACKET.into(), "[");
1146            for (i, arch) in architectures.enumerate() {
1147                if i > 0 {
1148                    builder.token(WHITESPACE.into(), " ");
1149                }
1150                builder.token(IDENT.into(), arch.as_str());
1151            }
1152            builder.token(R_BRACKET.into(), "]");
1153            builder.finish_node();
1154        }
1155        for profiles in self.profiles() {
1156            builder.token(WHITESPACE.into(), " ");
1157            builder.start_node(PROFILES.into());
1158            builder.token(L_ANGLE.into(), "<");
1159            for (i, profile) in profiles.into_iter().enumerate() {
1160                if i > 0 {
1161                    builder.token(WHITESPACE.into(), " ");
1162                }
1163                match profile {
1164                    BuildProfile::Disabled(name) => {
1165                        builder.token(NOT.into(), "!");
1166                        builder.token(IDENT.into(), name.as_str());
1167                    }
1168                    BuildProfile::Enabled(name) => {
1169                        builder.token(IDENT.into(), name.as_str());
1170                    }
1171                }
1172            }
1173            builder.token(R_ANGLE.into(), ">");
1174            builder.finish_node();
1175        }
1176        builder.finish_node();
1177        Relation(SyntaxNode::new_root_mut(builder.finish()))
1178    }
1179
1180    /// Create a new simple relation, without any version constraints.
1181    ///
1182    /// # Example
1183    /// ```
1184    /// use debian_control::lossless::relations::Relation;
1185    /// let relation = Relation::simple("samba");
1186    /// assert_eq!(relation.to_string(), "samba");
1187    /// ```
1188    pub fn simple(name: &str) -> Self {
1189        Self::new(name, None)
1190    }
1191
1192    /// Remove the version constraint from the relation.
1193    ///
1194    /// # Example
1195    /// ```
1196    /// use debian_control::lossless::relations::{Relation};
1197    /// use debian_control::relations::VersionConstraint;
1198    /// let mut relation = Relation::new("samba", Some((VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap())));
1199    /// relation.drop_constraint();
1200    /// assert_eq!(relation.to_string(), "samba");
1201    /// ```
1202    pub fn drop_constraint(&mut self) -> bool {
1203        let version_token = self.0.children().find(|n| n.kind() == VERSION);
1204        if let Some(version_token) = version_token {
1205            // Remove any whitespace before the version token
1206            while let Some(prev) = version_token.prev_sibling_or_token() {
1207                if prev.kind() == WHITESPACE || prev.kind() == NEWLINE {
1208                    prev.detach();
1209                } else {
1210                    break;
1211                }
1212            }
1213            version_token.detach();
1214            return true;
1215        }
1216
1217        false
1218    }
1219
1220    /// Return the name of the package in the relation.
1221    ///
1222    /// # Example
1223    /// ```
1224    /// use debian_control::lossless::relations::Relation;
1225    /// let relation = Relation::simple("samba");
1226    /// assert_eq!(relation.name(), "samba");
1227    /// ```
1228    pub fn name(&self) -> String {
1229        self.0
1230            .children_with_tokens()
1231            .find_map(|it| match it {
1232                SyntaxElement::Token(token) if token.kind() == IDENT => Some(token),
1233                _ => None,
1234            })
1235            .unwrap()
1236            .text()
1237            .to_string()
1238    }
1239
1240    /// Return the archqual
1241    ///
1242    /// # Example
1243    /// ```
1244    /// use debian_control::lossless::relations::Relation;
1245    /// let relation: Relation = "samba:any".parse().unwrap();
1246    /// assert_eq!(relation.archqual(), Some("any".to_string()));
1247    /// ```
1248    pub fn archqual(&self) -> Option<String> {
1249        let archqual = self.0.children().find(|n| n.kind() == ARCHQUAL);
1250        let node = if let Some(archqual) = archqual {
1251            archqual.children_with_tokens().find_map(|it| match it {
1252                SyntaxElement::Token(token) if token.kind() == IDENT => Some(token),
1253                _ => None,
1254            })
1255        } else {
1256            None
1257        };
1258        node.map(|n| n.text().to_string())
1259    }
1260
1261    /// Set the architecture qualifier for this relation.
1262    ///
1263    /// # Example
1264    /// ```
1265    /// use debian_control::lossless::relations::Relation;
1266    /// let mut relation = Relation::simple("samba");
1267    /// relation.set_archqual("any");
1268    /// assert_eq!(relation.to_string(), "samba:any");
1269    /// ```
1270    pub fn set_archqual(&mut self, archqual: &str) {
1271        let mut builder = GreenNodeBuilder::new();
1272        builder.start_node(ARCHQUAL.into());
1273        builder.token(COLON.into(), ":");
1274        builder.token(IDENT.into(), archqual);
1275        builder.finish_node();
1276
1277        let node_archqual = self.0.children().find(|n| n.kind() == ARCHQUAL);
1278        if let Some(node_archqual) = node_archqual {
1279            self.0.splice_children(
1280                node_archqual.index()..node_archqual.index() + 1,
1281                vec![SyntaxNode::new_root_mut(builder.finish()).into()],
1282            );
1283        } else {
1284            let name_node = self.0.children_with_tokens().find(|n| n.kind() == IDENT);
1285            let idx = if let Some(name_node) = name_node {
1286                name_node.index() + 1
1287            } else {
1288                0
1289            };
1290            self.0.splice_children(
1291                idx..idx,
1292                vec![SyntaxNode::new_root_mut(builder.finish()).into()],
1293            );
1294        }
1295    }
1296
1297    /// Return the version constraint and the version it is constrained to.
1298    pub fn version(&self) -> Option<(VersionConstraint, Version)> {
1299        let vc = self.0.children().find(|n| n.kind() == VERSION);
1300        let vc = vc.as_ref()?;
1301        let constraint = vc.children().find(|n| n.kind() == CONSTRAINT);
1302
1303        let version = vc.children_with_tokens().find_map(|it| match it {
1304            SyntaxElement::Token(token) if token.kind() == IDENT => Some(token),
1305            _ => None,
1306        });
1307
1308        if let (Some(constraint), Some(version)) = (constraint, version) {
1309            let vc: VersionConstraint = constraint.to_string().parse().unwrap();
1310            Some((vc, (version.text().to_string()).parse().unwrap()))
1311        } else {
1312            None
1313        }
1314    }
1315
1316    /// Set the version constraint for this relation
1317    ///
1318    /// # Example
1319    /// ```
1320    /// use debian_control::lossless::relations::{Relation};
1321    /// use debian_control::relations::VersionConstraint;
1322    /// let mut relation = Relation::simple("samba");
1323    /// relation.set_version(Some((VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap())));
1324    /// assert_eq!(relation.to_string(), "samba (>= 2.0)");
1325    /// ```
1326    pub fn set_version(&mut self, version_constraint: Option<(VersionConstraint, Version)>) {
1327        let current_version = self.0.children().find(|n| n.kind() == VERSION);
1328        if let Some((vc, version)) = version_constraint {
1329            let mut builder = GreenNodeBuilder::new();
1330            builder.start_node(VERSION.into());
1331            builder.token(L_PARENS.into(), "(");
1332            builder.start_node(CONSTRAINT.into());
1333            match vc {
1334                VersionConstraint::GreaterThanEqual => {
1335                    builder.token(R_ANGLE.into(), ">");
1336                    builder.token(EQUAL.into(), "=");
1337                }
1338                VersionConstraint::LessThanEqual => {
1339                    builder.token(L_ANGLE.into(), "<");
1340                    builder.token(EQUAL.into(), "=");
1341                }
1342                VersionConstraint::Equal => {
1343                    builder.token(EQUAL.into(), "=");
1344                }
1345                VersionConstraint::GreaterThan => {
1346                    builder.token(R_ANGLE.into(), ">");
1347                }
1348                VersionConstraint::LessThan => {
1349                    builder.token(L_ANGLE.into(), "<");
1350                }
1351            }
1352            builder.finish_node(); // CONSTRAINT
1353            builder.token(WHITESPACE.into(), " ");
1354            builder.token(IDENT.into(), version.to_string().as_str());
1355            builder.token(R_PARENS.into(), ")");
1356            builder.finish_node(); // VERSION
1357
1358            if let Some(current_version) = current_version {
1359                self.0.splice_children(
1360                    current_version.index()..current_version.index() + 1,
1361                    vec![SyntaxNode::new_root_mut(builder.finish()).into()],
1362                );
1363            } else {
1364                let name_node = self.0.children_with_tokens().find(|n| n.kind() == IDENT);
1365                let idx = if let Some(name_node) = name_node {
1366                    name_node.index() + 1
1367                } else {
1368                    0
1369                };
1370                let new_children = vec![
1371                    GreenToken::new(WHITESPACE.into(), " ").into(),
1372                    builder.finish().into(),
1373                ];
1374                let new_root = SyntaxNode::new_root_mut(
1375                    self.0.green().splice_children(idx..idx, new_children),
1376                );
1377                if let Some(parent) = self.0.parent() {
1378                    parent
1379                        .splice_children(self.0.index()..self.0.index() + 1, vec![new_root.into()]);
1380                    self.0 = parent
1381                        .children_with_tokens()
1382                        .nth(self.0.index())
1383                        .unwrap()
1384                        .clone()
1385                        .into_node()
1386                        .unwrap();
1387                } else {
1388                    self.0 = new_root;
1389                }
1390            }
1391        } else if let Some(current_version) = current_version {
1392            // Remove any whitespace before the version token
1393            while let Some(prev) = current_version.prev_sibling_or_token() {
1394                if prev.kind() == WHITESPACE || prev.kind() == NEWLINE {
1395                    prev.detach();
1396                } else {
1397                    break;
1398                }
1399            }
1400            current_version.detach();
1401        }
1402    }
1403
1404    /// Return an iterator over the architectures for this relation
1405    ///
1406    /// # Example
1407    /// ```
1408    /// use debian_control::lossless::relations::Relation;
1409    /// let relation: Relation = "samba [amd64]".parse().unwrap();
1410    /// assert_eq!(relation.architectures().unwrap().collect::<Vec<_>>(), vec!["amd64".to_string()]);
1411    /// ```
1412    pub fn architectures(&self) -> Option<impl Iterator<Item = String> + '_> {
1413        let architectures = self.0.children().find(|n| n.kind() == ARCHITECTURES)?;
1414
1415        Some(architectures.children_with_tokens().filter_map(|node| {
1416            let token = node.as_token()?;
1417            if token.kind() == IDENT {
1418                Some(token.text().to_string())
1419            } else {
1420                None
1421            }
1422        }))
1423    }
1424
1425    /// Returns an iterator over the build profiles for this relation
1426    ///
1427    /// # Example
1428    /// ```
1429    /// use debian_control::lossless::relations::{Relation};
1430    /// use debian_control::relations::{BuildProfile};
1431    /// let relation: Relation = "samba <!nocheck>".parse().unwrap();
1432    /// assert_eq!(relation.profiles().collect::<Vec<_>>(), vec![vec![BuildProfile::Disabled("nocheck".to_string())]]);
1433    /// ```
1434    pub fn profiles(&self) -> impl Iterator<Item = Vec<BuildProfile>> + '_ {
1435        let profiles = self.0.children().filter(|n| n.kind() == PROFILES);
1436
1437        profiles.map(|profile| {
1438            // iterate over nodes separated by whitespace tokens
1439            let mut ret = vec![];
1440            let mut current = vec![];
1441            for token in profile.children_with_tokens() {
1442                match token.kind() {
1443                    WHITESPACE | NEWLINE => {
1444                        if !current.is_empty() {
1445                            ret.push(current.join("").parse::<BuildProfile>().unwrap());
1446                            current = vec![];
1447                        }
1448                    }
1449                    L_ANGLE | R_ANGLE => {}
1450                    _ => {
1451                        current.push(token.to_string());
1452                    }
1453                }
1454            }
1455            if !current.is_empty() {
1456                ret.push(current.concat().parse().unwrap());
1457            }
1458            ret
1459        })
1460    }
1461
1462    /// Remove this relation
1463    ///
1464    /// # Example
1465    /// ```
1466    /// use debian_control::lossless::relations::{Relation,Entry};
1467    /// let mut entry: Entry = r"python3-dulwich (>= 0.19.0) | python3-urllib3 (<< 1.26.0)".parse().unwrap();
1468    /// let mut relation = entry.get_relation(0).unwrap();
1469    /// relation.remove();
1470    /// assert_eq!(entry.to_string(), "python3-urllib3 (<< 1.26.0)");
1471    /// ```
1472    pub fn remove(&mut self) {
1473        let is_first = !self
1474            .0
1475            .siblings(Direction::Prev)
1476            .skip(1)
1477            .any(|n| n.kind() == RELATION);
1478        if !is_first {
1479            // Not the first item in the list. Remove whitespace backwards to the previous
1480            // pipe, the pipe and any whitespace until the previous relation
1481            while let Some(n) = self.0.prev_sibling_or_token() {
1482                if n.kind() == WHITESPACE || n.kind() == NEWLINE {
1483                    n.detach();
1484                } else if n.kind() == PIPE {
1485                    n.detach();
1486                    break;
1487                } else {
1488                    break;
1489                }
1490            }
1491            while let Some(n) = self.0.prev_sibling_or_token() {
1492                if n.kind() == WHITESPACE || n.kind() == NEWLINE {
1493                    n.detach();
1494                } else {
1495                    break;
1496                }
1497            }
1498        } else {
1499            // First item in the list. Remove whitespace up to the pipe, the pipe and anything
1500            // before the next relation
1501            while let Some(n) = self.0.next_sibling_or_token() {
1502                if n.kind() == WHITESPACE || n.kind() == NEWLINE {
1503                    n.detach();
1504                } else if n.kind() == PIPE {
1505                    n.detach();
1506                    break;
1507                } else {
1508                    panic!("Unexpected node: {:?}", n);
1509                }
1510            }
1511
1512            while let Some(n) = self.0.next_sibling_or_token() {
1513                if n.kind() == WHITESPACE || n.kind() == NEWLINE {
1514                    n.detach();
1515                } else {
1516                    break;
1517                }
1518            }
1519        }
1520        // If this was the last relation in the entry, remove the entire entry
1521        if let Some(mut parent) = self.0.parent().and_then(Entry::cast) {
1522            if parent.is_empty() {
1523                parent.remove();
1524            } else {
1525                self.0.detach();
1526            }
1527        } else {
1528            self.0.detach();
1529        }
1530    }
1531
1532    /// Set the architectures for this relation
1533    ///
1534    /// # Example
1535    /// ```
1536    /// use debian_control::lossless::relations::Relation;
1537    /// let mut relation = Relation::simple("samba");
1538    /// relation.set_architectures(vec!["amd64", "i386"].into_iter());
1539    /// assert_eq!(relation.to_string(), "samba [amd64 i386]");
1540    /// ```
1541    pub fn set_architectures<'a>(&mut self, architectures: impl Iterator<Item = &'a str>) {
1542        let mut builder = GreenNodeBuilder::new();
1543        builder.start_node(ARCHITECTURES.into());
1544        builder.token(L_BRACKET.into(), "[");
1545        for (i, arch) in architectures.enumerate() {
1546            if i > 0 {
1547                builder.token(WHITESPACE.into(), " ");
1548            }
1549            builder.token(IDENT.into(), arch);
1550        }
1551        builder.token(R_BRACKET.into(), "]");
1552        builder.finish_node();
1553
1554        let node_architectures = self.0.children().find(|n| n.kind() == ARCHITECTURES);
1555        if let Some(node_architectures) = node_architectures {
1556            let new_root = SyntaxNode::new_root_mut(builder.finish());
1557            self.0.splice_children(
1558                node_architectures.index()..node_architectures.index() + 1,
1559                vec![new_root.into()],
1560            );
1561        } else {
1562            let profiles = self.0.children().find(|n| n.kind() == PROFILES);
1563            let idx = if let Some(profiles) = profiles {
1564                profiles.index()
1565            } else {
1566                self.0.children_with_tokens().count()
1567            };
1568            let new_root = SyntaxNode::new_root(self.0.green().splice_children(
1569                idx..idx,
1570                vec![
1571                    GreenToken::new(WHITESPACE.into(), " ").into(),
1572                    builder.finish().into(),
1573                ],
1574            ));
1575            if let Some(parent) = self.0.parent() {
1576                parent.splice_children(self.0.index()..self.0.index() + 1, vec![new_root.into()]);
1577                self.0 = parent
1578                    .children_with_tokens()
1579                    .nth(self.0.index())
1580                    .unwrap()
1581                    .clone()
1582                    .into_node()
1583                    .unwrap();
1584            } else {
1585                self.0 = new_root;
1586            }
1587        }
1588    }
1589
1590    /// Add a build profile to this relation
1591    ///
1592    /// # Example
1593    /// ```
1594    /// use debian_control::lossless::relations::Relation;
1595    /// use debian_control::relations::BuildProfile;
1596    /// let mut relation = Relation::simple("samba");
1597    /// relation.add_profile(&[BuildProfile::Disabled("nocheck".to_string())]);
1598    /// assert_eq!(relation.to_string(), "samba <!nocheck>");
1599    /// ```
1600    pub fn add_profile(&mut self, profile: &[BuildProfile]) {
1601        let mut builder = GreenNodeBuilder::new();
1602        builder.start_node(PROFILES.into());
1603        builder.token(L_ANGLE.into(), "<");
1604        for (i, profile) in profile.iter().enumerate() {
1605            if i > 0 {
1606                builder.token(WHITESPACE.into(), " ");
1607            }
1608            match profile {
1609                BuildProfile::Disabled(name) => {
1610                    builder.token(NOT.into(), "!");
1611                    builder.token(IDENT.into(), name.as_str());
1612                }
1613                BuildProfile::Enabled(name) => {
1614                    builder.token(IDENT.into(), name.as_str());
1615                }
1616            }
1617        }
1618        builder.token(R_ANGLE.into(), ">");
1619        builder.finish_node();
1620
1621        let node_profiles = self.0.children().find(|n| n.kind() == PROFILES);
1622        if let Some(node_profiles) = node_profiles {
1623            let new_root = SyntaxNode::new_root_mut(builder.finish());
1624            self.0.splice_children(
1625                node_profiles.index()..node_profiles.index() + 1,
1626                vec![new_root.into()],
1627            );
1628        } else {
1629            let idx = self.0.children_with_tokens().count();
1630            let new_root = SyntaxNode::new_root(self.0.green().splice_children(
1631                idx..idx,
1632                vec![
1633                    GreenToken::new(WHITESPACE.into(), " ").into(),
1634                    builder.finish().into(),
1635                ],
1636            ));
1637            if let Some(parent) = self.0.parent() {
1638                parent.splice_children(self.0.index()..self.0.index() + 1, vec![new_root.into()]);
1639                self.0 = parent
1640                    .children_with_tokens()
1641                    .nth(self.0.index())
1642                    .unwrap()
1643                    .clone()
1644                    .into_node()
1645                    .unwrap();
1646            } else {
1647                self.0 = new_root;
1648            }
1649        }
1650    }
1651
1652    /// Build a new relation
1653    pub fn build(name: &str) -> RelationBuilder {
1654        RelationBuilder::new(name)
1655    }
1656}
1657
1658/// A builder for creating a `Relation`
1659///
1660/// # Example
1661/// ```
1662/// use debian_control::lossless::relations::{Relation};
1663/// use debian_control::relations::VersionConstraint;
1664/// let relation = Relation::build("samba")
1665///    .version_constraint(VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap())
1666///    .archqual("any")
1667///    .architectures(vec!["amd64".to_string(), "i386".to_string()])
1668///    .build();
1669/// assert_eq!(relation.to_string(), "samba:any (>= 2.0) [amd64 i386]");
1670/// ```
1671pub struct RelationBuilder {
1672    name: String,
1673    version_constraint: Option<(VersionConstraint, Version)>,
1674    archqual: Option<String>,
1675    architectures: Vec<String>,
1676    profiles: Vec<Vec<BuildProfile>>,
1677}
1678
1679impl RelationBuilder {
1680    /// Create a new `RelationBuilder` with the given package name
1681    fn new(name: &str) -> Self {
1682        Self {
1683            name: name.to_string(),
1684            version_constraint: None,
1685            archqual: None,
1686            architectures: vec![],
1687            profiles: vec![],
1688        }
1689    }
1690
1691    /// Set the version constraint for this relation
1692    pub fn version_constraint(mut self, vc: VersionConstraint, version: Version) -> Self {
1693        self.version_constraint = Some((vc, version));
1694        self
1695    }
1696
1697    /// Set the architecture qualifier for this relation
1698    pub fn archqual(mut self, archqual: &str) -> Self {
1699        self.archqual = Some(archqual.to_string());
1700        self
1701    }
1702
1703    /// Set the architectures for this relation
1704    pub fn architectures(mut self, architectures: Vec<String>) -> Self {
1705        self.architectures = architectures;
1706        self
1707    }
1708
1709    /// Set the build profiles for this relation
1710    pub fn profiles(mut self, profiles: Vec<Vec<BuildProfile>>) -> Self {
1711        self.profiles = profiles;
1712        self
1713    }
1714
1715    /// Add a build profile to this relation
1716    pub fn add_profile(mut self, profile: Vec<BuildProfile>) -> Self {
1717        self.profiles.push(profile);
1718        self
1719    }
1720
1721    /// Build the `Relation`
1722    pub fn build(self) -> Relation {
1723        let mut relation = Relation::new(&self.name, self.version_constraint);
1724        if let Some(archqual) = &self.archqual {
1725            relation.set_archqual(archqual);
1726        }
1727        relation.set_architectures(self.architectures.iter().map(|s| s.as_str()));
1728        for profile in &self.profiles {
1729            relation.add_profile(profile);
1730        }
1731        relation
1732    }
1733}
1734
1735impl PartialOrd for Relation {
1736    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1737        // Compare by name first, then by version
1738        let name_cmp = self.name().cmp(&other.name());
1739        if name_cmp != std::cmp::Ordering::Equal {
1740            return Some(name_cmp);
1741        }
1742
1743        let self_version = self.version();
1744        let other_version = other.version();
1745
1746        match (self_version, other_version) {
1747            (Some((self_vc, self_version)), Some((other_vc, other_version))) => {
1748                let vc_cmp = self_vc.cmp(&other_vc);
1749                if vc_cmp != std::cmp::Ordering::Equal {
1750                    return Some(vc_cmp);
1751                }
1752
1753                Some(self_version.cmp(&other_version))
1754            }
1755            (Some(_), None) => Some(std::cmp::Ordering::Greater),
1756            (None, Some(_)) => Some(std::cmp::Ordering::Less),
1757            (None, None) => Some(std::cmp::Ordering::Equal),
1758        }
1759    }
1760}
1761
1762impl Eq for Relation {}
1763
1764impl Ord for Relation {
1765    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1766        self.partial_cmp(other).unwrap()
1767    }
1768}
1769
1770impl std::str::FromStr for Relations {
1771    type Err = String;
1772
1773    fn from_str(s: &str) -> Result<Self, Self::Err> {
1774        let parse = parse(s, false);
1775        if parse.errors.is_empty() {
1776            Ok(parse.root_mut())
1777        } else {
1778            Err(parse.errors.join("\n"))
1779        }
1780    }
1781}
1782
1783impl std::str::FromStr for Entry {
1784    type Err = String;
1785
1786    fn from_str(s: &str) -> Result<Self, Self::Err> {
1787        let root: Relations = s.parse()?;
1788
1789        let mut entries = root.entries();
1790        let entry = if let Some(entry) = entries.next() {
1791            entry
1792        } else {
1793            return Err("No entry found".to_string());
1794        };
1795
1796        if entries.next().is_some() {
1797            return Err("Multiple entries found".to_string());
1798        }
1799
1800        Ok(entry)
1801    }
1802}
1803
1804impl std::str::FromStr for Relation {
1805    type Err = String;
1806
1807    fn from_str(s: &str) -> Result<Self, Self::Err> {
1808        let entry: Entry = s.parse()?;
1809
1810        let mut relations = entry.relations();
1811        let relation = if let Some(relation) = relations.next() {
1812            relation
1813        } else {
1814            return Err("No relation found".to_string());
1815        };
1816
1817        if relations.next().is_some() {
1818            return Err("Multiple relations found".to_string());
1819        }
1820
1821        Ok(relation)
1822    }
1823}
1824
1825impl From<crate::lossy::Relation> for Relation {
1826    fn from(relation: crate::lossy::Relation) -> Self {
1827        let mut builder = Relation::build(&relation.name);
1828
1829        if let Some((vc, version)) = relation.version {
1830            builder = builder.version_constraint(vc, version);
1831        }
1832
1833        if let Some(archqual) = relation.archqual {
1834            builder = builder.archqual(&archqual);
1835        }
1836
1837        if let Some(architectures) = relation.architectures {
1838            builder = builder.architectures(architectures);
1839        }
1840
1841        builder = builder.profiles(relation.profiles);
1842
1843        builder.build()
1844    }
1845}
1846
1847impl From<Relation> for crate::lossy::Relation {
1848    fn from(relation: Relation) -> Self {
1849        crate::lossy::Relation {
1850            name: relation.name(),
1851            version: relation.version(),
1852            archqual: relation.archqual(),
1853            architectures: relation.architectures().map(|a| a.collect()),
1854            profiles: relation.profiles().collect(),
1855        }
1856    }
1857}
1858
1859impl From<Entry> for Vec<crate::lossy::Relation> {
1860    fn from(entry: Entry) -> Self {
1861        entry.relations().map(|r| r.into()).collect()
1862    }
1863}
1864
1865impl From<Vec<crate::lossy::Relation>> for Entry {
1866    fn from(relations: Vec<crate::lossy::Relation>) -> Self {
1867        let relations: Vec<Relation> = relations.into_iter().map(|r| r.into()).collect();
1868        Entry::from(relations)
1869    }
1870}
1871
1872#[cfg(test)]
1873mod tests {
1874    use super::*;
1875
1876    #[test]
1877    fn test_parse() {
1878        let input = "python3-dulwich";
1879        let parsed: Relations = input.parse().unwrap();
1880        assert_eq!(parsed.to_string(), input);
1881        assert_eq!(parsed.entries().count(), 1);
1882        let entry = parsed.entries().next().unwrap();
1883        assert_eq!(entry.to_string(), "python3-dulwich");
1884        assert_eq!(entry.relations().count(), 1);
1885        let relation = entry.relations().next().unwrap();
1886        assert_eq!(relation.to_string(), "python3-dulwich");
1887        assert_eq!(relation.version(), None);
1888
1889        let input = "python3-dulwich (>= 0.20.21)";
1890        let parsed: Relations = input.parse().unwrap();
1891        assert_eq!(parsed.to_string(), input);
1892        assert_eq!(parsed.entries().count(), 1);
1893        let entry = parsed.entries().next().unwrap();
1894        assert_eq!(entry.to_string(), "python3-dulwich (>= 0.20.21)");
1895        assert_eq!(entry.relations().count(), 1);
1896        let relation = entry.relations().next().unwrap();
1897        assert_eq!(relation.to_string(), "python3-dulwich (>= 0.20.21)");
1898        assert_eq!(
1899            relation.version(),
1900            Some((
1901                VersionConstraint::GreaterThanEqual,
1902                "0.20.21".parse().unwrap()
1903            ))
1904        );
1905    }
1906
1907    #[test]
1908    fn test_multiple() {
1909        let input = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)";
1910        let parsed: Relations = input.parse().unwrap();
1911        assert_eq!(parsed.to_string(), input);
1912        assert_eq!(parsed.entries().count(), 2);
1913        let entry = parsed.entries().next().unwrap();
1914        assert_eq!(entry.to_string(), "python3-dulwich (>= 0.20.21)");
1915        assert_eq!(entry.relations().count(), 1);
1916        let relation = entry.relations().next().unwrap();
1917        assert_eq!(relation.to_string(), "python3-dulwich (>= 0.20.21)");
1918        assert_eq!(
1919            relation.version(),
1920            Some((
1921                VersionConstraint::GreaterThanEqual,
1922                "0.20.21".parse().unwrap()
1923            ))
1924        );
1925        let entry = parsed.entries().nth(1).unwrap();
1926        assert_eq!(entry.to_string(), "python3-dulwich (<< 0.21)");
1927        assert_eq!(entry.relations().count(), 1);
1928        let relation = entry.relations().next().unwrap();
1929        assert_eq!(relation.to_string(), "python3-dulwich (<< 0.21)");
1930        assert_eq!(
1931            relation.version(),
1932            Some((VersionConstraint::LessThan, "0.21".parse().unwrap()))
1933        );
1934    }
1935
1936    #[test]
1937    fn test_architectures() {
1938        let input = "python3-dulwich [amd64 arm64 armhf i386 mips mips64el mipsel ppc64el s390x]";
1939        let parsed: Relations = input.parse().unwrap();
1940        assert_eq!(parsed.to_string(), input);
1941        assert_eq!(parsed.entries().count(), 1);
1942        let entry = parsed.entries().next().unwrap();
1943        assert_eq!(
1944            entry.to_string(),
1945            "python3-dulwich [amd64 arm64 armhf i386 mips mips64el mipsel ppc64el s390x]"
1946        );
1947        assert_eq!(entry.relations().count(), 1);
1948        let relation = entry.relations().next().unwrap();
1949        assert_eq!(
1950            relation.to_string(),
1951            "python3-dulwich [amd64 arm64 armhf i386 mips mips64el mipsel ppc64el s390x]"
1952        );
1953        assert_eq!(relation.version(), None);
1954        assert_eq!(
1955            relation.architectures().unwrap().collect::<Vec<_>>(),
1956            vec![
1957                "amd64", "arm64", "armhf", "i386", "mips", "mips64el", "mipsel", "ppc64el", "s390x"
1958            ]
1959            .into_iter()
1960            .map(|s| s.to_string())
1961            .collect::<Vec<_>>()
1962        );
1963    }
1964
1965    #[test]
1966    fn test_profiles() {
1967        let input = "foo (>= 1.0) [i386 arm] <!nocheck> <!cross>, bar";
1968        let parsed: Relations = input.parse().unwrap();
1969        assert_eq!(parsed.to_string(), input);
1970        assert_eq!(parsed.entries().count(), 2);
1971        let entry = parsed.entries().next().unwrap();
1972        assert_eq!(
1973            entry.to_string(),
1974            "foo (>= 1.0) [i386 arm] <!nocheck> <!cross>"
1975        );
1976        assert_eq!(entry.relations().count(), 1);
1977        let relation = entry.relations().next().unwrap();
1978        assert_eq!(
1979            relation.to_string(),
1980            "foo (>= 1.0) [i386 arm] <!nocheck> <!cross>"
1981        );
1982        assert_eq!(
1983            relation.version(),
1984            Some((VersionConstraint::GreaterThanEqual, "1.0".parse().unwrap()))
1985        );
1986        assert_eq!(
1987            relation.architectures().unwrap().collect::<Vec<_>>(),
1988            vec!["i386", "arm"]
1989                .into_iter()
1990                .map(|s| s.to_string())
1991                .collect::<Vec<_>>()
1992        );
1993        assert_eq!(
1994            relation.profiles().collect::<Vec<_>>(),
1995            vec![
1996                vec![BuildProfile::Disabled("nocheck".to_string())],
1997                vec![BuildProfile::Disabled("cross".to_string())]
1998            ]
1999        );
2000    }
2001
2002    #[test]
2003    fn test_substvar() {
2004        let input = "${shlibs:Depends}";
2005
2006        let (parsed, errors) = Relations::parse_relaxed(input, true);
2007        assert_eq!(errors, Vec::<String>::new());
2008        assert_eq!(parsed.to_string(), input);
2009        assert_eq!(parsed.entries().count(), 0);
2010
2011        assert_eq!(
2012            parsed.substvars().collect::<Vec<_>>(),
2013            vec!["${shlibs:Depends}"]
2014        );
2015    }
2016
2017    #[test]
2018    fn test_new() {
2019        let r = Relation::new(
2020            "samba",
2021            Some((VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap())),
2022        );
2023
2024        assert_eq!(r.to_string(), "samba (>= 2.0)");
2025    }
2026
2027    #[test]
2028    fn test_drop_constraint() {
2029        let mut r = Relation::new(
2030            "samba",
2031            Some((VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap())),
2032        );
2033
2034        r.drop_constraint();
2035
2036        assert_eq!(r.to_string(), "samba");
2037    }
2038
2039    #[test]
2040    fn test_simple() {
2041        let r = Relation::simple("samba");
2042
2043        assert_eq!(r.to_string(), "samba");
2044    }
2045
2046    #[test]
2047    fn test_remove_first_entry() {
2048        let mut rels: Relations = r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"#
2049            .parse()
2050            .unwrap();
2051        let removed = rels.remove_entry(0);
2052        assert_eq!(removed.to_string(), "python3-dulwich (>= 0.20.21)");
2053        assert_eq!(rels.to_string(), "python3-dulwich (<< 0.21)");
2054    }
2055
2056    #[test]
2057    fn test_remove_last_entry() {
2058        let mut rels: Relations = r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"#
2059            .parse()
2060            .unwrap();
2061        rels.remove_entry(1);
2062        assert_eq!(rels.to_string(), "python3-dulwich (>= 0.20.21)");
2063    }
2064
2065    #[test]
2066    fn test_remove_middle() {
2067        let mut rels: Relations =
2068            r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21), python3-dulwich (<< 0.22)"#
2069                .parse()
2070                .unwrap();
2071        rels.remove_entry(1);
2072        assert_eq!(
2073            rels.to_string(),
2074            "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.22)"
2075        );
2076    }
2077
2078    #[test]
2079    fn test_remove_added() {
2080        let mut rels: Relations = r#"python3-dulwich (>= 0.20.21)"#.parse().unwrap();
2081        let entry = Entry::from(vec![Relation::simple("python3-dulwich")]);
2082        rels.push(entry);
2083        rels.remove_entry(1);
2084        assert_eq!(rels.to_string(), "python3-dulwich (>= 0.20.21)");
2085    }
2086
2087    #[test]
2088    fn test_push() {
2089        let mut rels: Relations = r#"python3-dulwich (>= 0.20.21)"#.parse().unwrap();
2090        let entry = Entry::from(vec![Relation::simple("python3-dulwich")]);
2091        rels.push(entry);
2092        assert_eq!(
2093            rels.to_string(),
2094            "python3-dulwich (>= 0.20.21), python3-dulwich"
2095        );
2096    }
2097
2098    #[test]
2099    fn test_push_from_empty() {
2100        let mut rels: Relations = "".parse().unwrap();
2101        let entry = Entry::from(vec![Relation::simple("python3-dulwich")]);
2102        rels.push(entry);
2103        assert_eq!(rels.to_string(), "python3-dulwich");
2104    }
2105
2106    #[test]
2107    fn test_insert() {
2108        let mut rels: Relations = r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"#
2109            .parse()
2110            .unwrap();
2111        let entry = Entry::from(vec![Relation::simple("python3-dulwich")]);
2112        rels.insert(1, entry);
2113        assert_eq!(
2114            rels.to_string(),
2115            "python3-dulwich (>= 0.20.21), python3-dulwich, python3-dulwich (<< 0.21)"
2116        );
2117    }
2118
2119    #[test]
2120    fn test_insert_at_start() {
2121        let mut rels: Relations = r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"#
2122            .parse()
2123            .unwrap();
2124        let entry = Entry::from(vec![Relation::simple("python3-dulwich")]);
2125        rels.insert(0, entry);
2126        assert_eq!(
2127            rels.to_string(),
2128            "python3-dulwich, python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"
2129        );
2130    }
2131
2132    #[test]
2133    fn test_insert_after_error() {
2134        let (mut rels, errors) = Relations::parse_relaxed("@foo@, debhelper (>= 1.0)", false);
2135        assert_eq!(
2136            errors,
2137            vec![
2138                "expected $ or identifier but got ERROR",
2139                "expected comma or end of file but got Some(IDENT)",
2140                "expected $ or identifier but got ERROR"
2141            ]
2142        );
2143        let entry = Entry::from(vec![Relation::simple("bar")]);
2144        rels.push(entry);
2145        assert_eq!(rels.to_string(), "@foo@, debhelper (>= 1.0), bar");
2146    }
2147
2148    #[test]
2149    fn test_insert_before_error() {
2150        let (mut rels, errors) = Relations::parse_relaxed("debhelper (>= 1.0), @foo@, bla", false);
2151        assert_eq!(
2152            errors,
2153            vec![
2154                "expected $ or identifier but got ERROR",
2155                "expected comma or end of file but got Some(IDENT)",
2156                "expected $ or identifier but got ERROR"
2157            ]
2158        );
2159        let entry = Entry::from(vec![Relation::simple("bar")]);
2160        rels.insert(0, entry);
2161        assert_eq!(rels.to_string(), "bar, debhelper (>= 1.0), @foo@, bla");
2162    }
2163
2164    #[test]
2165    fn test_replace() {
2166        let mut rels: Relations = r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"#
2167            .parse()
2168            .unwrap();
2169        let entry = Entry::from(vec![Relation::simple("python3-dulwich")]);
2170        rels.replace(1, entry);
2171        assert_eq!(
2172            rels.to_string(),
2173            "python3-dulwich (>= 0.20.21), python3-dulwich"
2174        );
2175    }
2176
2177    #[test]
2178    fn test_relation_from_entries() {
2179        let entries = vec![
2180            Entry::from(vec![Relation::simple("python3-dulwich")]),
2181            Entry::from(vec![Relation::simple("python3-breezy")]),
2182        ];
2183        let rels: Relations = entries.into();
2184        assert_eq!(rels.entries().count(), 2);
2185        assert_eq!(rels.to_string(), "python3-dulwich, python3-breezy");
2186    }
2187
2188    #[test]
2189    fn test_entry_from_relations() {
2190        let relations = vec![
2191            Relation::simple("python3-dulwich"),
2192            Relation::simple("python3-breezy"),
2193        ];
2194        let entry: Entry = relations.into();
2195        assert_eq!(entry.relations().count(), 2);
2196        assert_eq!(entry.to_string(), "python3-dulwich | python3-breezy");
2197    }
2198
2199    #[test]
2200    fn test_parse_entry() {
2201        let parsed: Entry = "python3-dulwich (>= 0.20.21) | bar".parse().unwrap();
2202        assert_eq!(parsed.to_string(), "python3-dulwich (>= 0.20.21) | bar");
2203        assert_eq!(parsed.relations().count(), 2);
2204
2205        assert_eq!(
2206            "foo, bar".parse::<Entry>().unwrap_err(),
2207            "Multiple entries found"
2208        );
2209        assert_eq!("".parse::<Entry>().unwrap_err(), "No entry found");
2210    }
2211
2212    #[test]
2213    fn test_parse_relation() {
2214        let parsed: Relation = "python3-dulwich (>= 0.20.21)".parse().unwrap();
2215        assert_eq!(parsed.to_string(), "python3-dulwich (>= 0.20.21)");
2216        assert_eq!(
2217            parsed.version(),
2218            Some((
2219                VersionConstraint::GreaterThanEqual,
2220                "0.20.21".parse().unwrap()
2221            ))
2222        );
2223        assert_eq!(
2224            "foo | bar".parse::<Relation>().unwrap_err(),
2225            "Multiple relations found"
2226        );
2227        assert_eq!("".parse::<Relation>().unwrap_err(), "No entry found");
2228    }
2229
2230    #[test]
2231    fn test_special() {
2232        let parsed: Relation = "librust-breezyshim+dirty-tracker-dev:amd64 (>= 0.1.138-~~)"
2233            .parse()
2234            .unwrap();
2235        assert_eq!(
2236            parsed.to_string(),
2237            "librust-breezyshim+dirty-tracker-dev:amd64 (>= 0.1.138-~~)"
2238        );
2239        assert_eq!(
2240            parsed.version(),
2241            Some((
2242                VersionConstraint::GreaterThanEqual,
2243                "0.1.138-~~".parse().unwrap()
2244            ))
2245        );
2246        assert_eq!(parsed.archqual(), Some("amd64".to_string()));
2247        assert_eq!(parsed.name(), "librust-breezyshim+dirty-tracker-dev");
2248    }
2249
2250    #[test]
2251    fn test_relations_satisfied_by() {
2252        let rels: Relations = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"
2253            .parse()
2254            .unwrap();
2255        let satisfied = |name: &str| -> Option<debversion::Version> {
2256            match name {
2257                "python3-dulwich" => Some("0.20.21".parse().unwrap()),
2258                _ => None,
2259            }
2260        };
2261        assert!(rels.satisfied_by(satisfied));
2262
2263        let satisfied = |name: &str| match name {
2264            "python3-dulwich" => Some("0.21".parse().unwrap()),
2265            _ => None,
2266        };
2267        assert!(!rels.satisfied_by(satisfied));
2268
2269        let satisfied = |name: &str| match name {
2270            "python3-dulwich" => Some("0.20.20".parse().unwrap()),
2271            _ => None,
2272        };
2273        assert!(!rels.satisfied_by(satisfied));
2274    }
2275
2276    #[test]
2277    fn test_entry_satisfied_by() {
2278        let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
2279            .parse()
2280            .unwrap();
2281        let satisfied = |name: &str| -> Option<debversion::Version> {
2282            match name {
2283                "python3-dulwich" => Some("0.20.21".parse().unwrap()),
2284                _ => None,
2285            }
2286        };
2287        assert!(entry.satisfied_by(satisfied));
2288        let satisfied = |name: &str| -> Option<debversion::Version> {
2289            match name {
2290                "python3-dulwich" => Some("0.18".parse().unwrap()),
2291                _ => None,
2292            }
2293        };
2294        assert!(!entry.satisfied_by(satisfied));
2295    }
2296
2297    #[test]
2298    fn test_wrap_and_sort_relation() {
2299        let relation: Relation = "   python3-dulwich   (>= 11) [  amd64 ] <  lala>"
2300            .parse()
2301            .unwrap();
2302
2303        let wrapped = relation.wrap_and_sort();
2304
2305        assert_eq!(
2306            wrapped.to_string(),
2307            "python3-dulwich (>= 11) [amd64] <lala>"
2308        );
2309    }
2310
2311    #[test]
2312    fn test_wrap_and_sort_relations() {
2313        let entry: Relations =
2314            "python3-dulwich (>= 0.20.21)   | bar, \n\n\n\npython3-dulwich (<< 0.21)"
2315                .parse()
2316                .unwrap();
2317
2318        let wrapped = entry.wrap_and_sort();
2319
2320        assert_eq!(
2321            wrapped.to_string(),
2322            "bar | python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"
2323        );
2324    }
2325
2326    #[cfg(feature = "serde")]
2327    #[test]
2328    fn test_serialize_relations() {
2329        let relations: Relations = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"
2330            .parse()
2331            .unwrap();
2332        let serialized = serde_json::to_string(&relations).unwrap();
2333        assert_eq!(
2334            serialized,
2335            r#""python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)""#
2336        );
2337    }
2338
2339    #[cfg(feature = "serde")]
2340    #[test]
2341    fn test_deserialize_relations() {
2342        let relations: Relations = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"
2343            .parse()
2344            .unwrap();
2345        let serialized = serde_json::to_string(&relations).unwrap();
2346        let deserialized: Relations = serde_json::from_str(&serialized).unwrap();
2347        assert_eq!(deserialized.to_string(), relations.to_string());
2348    }
2349
2350    #[cfg(feature = "serde")]
2351    #[test]
2352    fn test_serialize_relation() {
2353        let relation: Relation = "python3-dulwich (>= 0.20.21)".parse().unwrap();
2354        let serialized = serde_json::to_string(&relation).unwrap();
2355        assert_eq!(serialized, r#""python3-dulwich (>= 0.20.21)""#);
2356    }
2357
2358    #[cfg(feature = "serde")]
2359    #[test]
2360    fn test_deserialize_relation() {
2361        let relation: Relation = "python3-dulwich (>= 0.20.21)".parse().unwrap();
2362        let serialized = serde_json::to_string(&relation).unwrap();
2363        let deserialized: Relation = serde_json::from_str(&serialized).unwrap();
2364        assert_eq!(deserialized.to_string(), relation.to_string());
2365    }
2366
2367    #[cfg(feature = "serde")]
2368    #[test]
2369    fn test_serialize_entry() {
2370        let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
2371            .parse()
2372            .unwrap();
2373        let serialized = serde_json::to_string(&entry).unwrap();
2374        assert_eq!(
2375            serialized,
2376            r#""python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)""#
2377        );
2378    }
2379
2380    #[cfg(feature = "serde")]
2381    #[test]
2382    fn test_deserialize_entry() {
2383        let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
2384            .parse()
2385            .unwrap();
2386        let serialized = serde_json::to_string(&entry).unwrap();
2387        let deserialized: Entry = serde_json::from_str(&serialized).unwrap();
2388        assert_eq!(deserialized.to_string(), entry.to_string());
2389    }
2390
2391    #[test]
2392    fn test_remove_first_relation() {
2393        let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
2394            .parse()
2395            .unwrap();
2396        let mut rel = entry.relations().next().unwrap();
2397        rel.remove();
2398        assert_eq!(entry.to_string(), "python3-dulwich (<< 0.18)");
2399    }
2400
2401    #[test]
2402    fn test_remove_last_relation() {
2403        let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
2404            .parse()
2405            .unwrap();
2406        let mut rel = entry.relations().nth(1).unwrap();
2407        rel.remove();
2408        assert_eq!(entry.to_string(), "python3-dulwich (>= 0.20.21)");
2409    }
2410
2411    #[test]
2412    fn test_remove_only_relation() {
2413        let entry: Entry = "python3-dulwich (>= 0.20.21)".parse().unwrap();
2414        let mut rel = entry.relations().next().unwrap();
2415        rel.remove();
2416        assert_eq!(entry.to_string(), "");
2417    }
2418
2419    #[test]
2420    fn test_relations_is_empty() {
2421        let entry: Relations = "python3-dulwich (>= 0.20.21)".parse().unwrap();
2422        assert!(!entry.is_empty());
2423        assert_eq!(1, entry.len());
2424        let mut rel = entry.entries().next().unwrap();
2425        rel.remove();
2426        assert!(entry.is_empty());
2427        assert_eq!(0, entry.len());
2428    }
2429
2430    #[test]
2431    fn test_entry_is_empty() {
2432        let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
2433            .parse()
2434            .unwrap();
2435        assert!(!entry.is_empty());
2436        assert_eq!(2, entry.len());
2437        let mut rel = entry.relations().next().unwrap();
2438        rel.remove();
2439        assert!(!entry.is_empty());
2440        assert_eq!(1, entry.len());
2441        let mut rel = entry.relations().next().unwrap();
2442        rel.remove();
2443        assert!(entry.is_empty());
2444        assert_eq!(0, entry.len());
2445    }
2446
2447    #[test]
2448    fn test_relation_set_version() {
2449        let mut rel: Relation = "samba".parse().unwrap();
2450        rel.set_version(None);
2451        assert_eq!("samba", rel.to_string());
2452        rel.set_version(Some((
2453            VersionConstraint::GreaterThanEqual,
2454            "2.0".parse().unwrap(),
2455        )));
2456        assert_eq!("samba (>= 2.0)", rel.to_string());
2457        rel.set_version(None);
2458        assert_eq!("samba", rel.to_string());
2459        rel.set_version(Some((
2460            VersionConstraint::GreaterThanEqual,
2461            "2.0".parse().unwrap(),
2462        )));
2463        rel.set_version(Some((
2464            VersionConstraint::GreaterThanEqual,
2465            "1.1".parse().unwrap(),
2466        )));
2467        assert_eq!("samba (>= 1.1)", rel.to_string());
2468    }
2469
2470    #[test]
2471    fn test_replace_relation() {
2472        let mut entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
2473            .parse()
2474            .unwrap();
2475        let new_rel = Relation::simple("python3-breezy");
2476        entry.replace(0, new_rel);
2477        assert_eq!(
2478            entry.to_string(),
2479            "python3-breezy | python3-dulwich (<< 0.18)"
2480        );
2481    }
2482
2483    #[test]
2484    fn test_entry_push_relation() {
2485        let relations: Relations = "python3-dulwich (>= 0.20.21)".parse().unwrap();
2486        let new_rel = Relation::simple("python3-breezy");
2487        let mut entry = relations.entries().next().unwrap();
2488        entry.push(new_rel);
2489        assert_eq!(
2490            entry.to_string(),
2491            "python3-dulwich (>= 0.20.21) | python3-breezy"
2492        );
2493        assert_eq!(
2494            relations.to_string(),
2495            "python3-dulwich (>= 0.20.21) | python3-breezy"
2496        );
2497    }
2498
2499    #[test]
2500    fn test_relations_remove_empty_entry() {
2501        let (mut relations, errors) = Relations::parse_relaxed("foo, , bar, ", false);
2502        assert_eq!(errors, Vec::<String>::new());
2503        assert_eq!(relations.to_string(), "foo, , bar, ");
2504        assert_eq!(relations.len(), 2);
2505        assert_eq!(
2506            relations.entries().next().unwrap().to_string(),
2507            "foo".to_string()
2508        );
2509        assert_eq!(
2510            relations.entries().nth(1).unwrap().to_string(),
2511            "bar".to_string()
2512        );
2513        relations.remove_entry(1);
2514        assert_eq!(relations.to_string(), "foo, , ");
2515    }
2516
2517    #[test]
2518    fn test_entry_remove_relation() {
2519        let entry: Entry = "python3-dulwich | samba".parse().unwrap();
2520        let removed = entry.remove_relation(0);
2521        assert_eq!(removed.to_string(), "python3-dulwich");
2522        assert_eq!(entry.to_string(), "samba");
2523    }
2524
2525    #[test]
2526    fn test_wrap_and_sort_removes_empty_entries() {
2527        let relations: Relations = "foo, , bar, ".parse().unwrap();
2528        let wrapped = relations.wrap_and_sort();
2529        assert_eq!(wrapped.to_string(), "bar, foo");
2530    }
2531
2532    #[test]
2533    fn test_set_archqual() {
2534        let entry: Entry = "python3-dulwich | samba".parse().unwrap();
2535        let mut rel = entry.relations().next().unwrap();
2536        rel.set_archqual("amd64");
2537        assert_eq!(rel.to_string(), "python3-dulwich:amd64");
2538        assert_eq!(rel.archqual(), Some("amd64".to_string()));
2539        assert_eq!(entry.to_string(), "python3-dulwich:amd64 | samba");
2540        rel.set_archqual("i386");
2541        assert_eq!(rel.to_string(), "python3-dulwich:i386");
2542        assert_eq!(rel.archqual(), Some("i386".to_string()));
2543        assert_eq!(entry.to_string(), "python3-dulwich:i386 | samba");
2544    }
2545
2546    #[test]
2547    fn test_set_architectures() {
2548        let mut relation = Relation::simple("samba");
2549        relation.set_architectures(vec!["amd64", "i386"].into_iter());
2550        assert_eq!(relation.to_string(), "samba [amd64 i386]");
2551    }
2552}