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
552/// Check if a package name should be treated as special for sorting purposes.
553///
554/// Special names include substitution variables (like `${misc:Depends}`)
555/// and template variables (like `@cdbs@`).
556fn is_special_package_name(name: &str) -> bool {
557    // Substitution variables like ${misc:Depends}
558    if name.starts_with("${") && name.ends_with('}') {
559        return true;
560    }
561    // Template variables like @cdbs@
562    if name.starts_with('@') && name.ends_with('@') {
563        return true;
564    }
565    false
566}
567
568/// Trait for defining sorting order of package relations.
569pub trait SortingOrder {
570    /// Compare two package names for sorting.
571    ///
572    /// Returns true if `name1` should come before `name2`.
573    fn lt(&self, name1: &str, name2: &str) -> bool;
574
575    /// Check if a package name should be ignored for sorting purposes.
576    fn ignore(&self, name: &str) -> bool;
577}
578
579/// Default sorting order (lexicographical with special items last).
580#[derive(Debug, Clone, Copy, Default)]
581pub struct DefaultSortingOrder;
582
583impl SortingOrder for DefaultSortingOrder {
584    fn lt(&self, name1: &str, name2: &str) -> bool {
585        let special1 = is_special_package_name(name1);
586        let special2 = is_special_package_name(name2);
587
588        // Special items always come last
589        if special1 && !special2 {
590            return false;
591        }
592        if !special1 && special2 {
593            return true;
594        }
595        if special1 && special2 {
596            // Both special - maintain original order
597            return false;
598        }
599
600        // Both are regular packages, use alphabetical order
601        name1 < name2
602    }
603
604    fn ignore(&self, name: &str) -> bool {
605        is_special_package_name(name)
606    }
607}
608
609/// Sorting order matching wrap-and-sort behavior.
610///
611/// This sorting order matches the behavior of the devscripts wrap-and-sort tool.
612/// It sorts packages into three groups:
613/// 1. Build-system packages (debhelper-compat, cdbs, etc.) - sorted first
614/// 2. Regular packages starting with [a-z0-9] - sorted in the middle
615/// 3. Substvars and other special packages - sorted last
616///
617/// Within each group, packages are sorted lexicographically.
618#[derive(Debug, Clone, Copy, Default)]
619pub struct WrapAndSortOrder;
620
621impl WrapAndSortOrder {
622    /// Build systems that should be sorted first, matching wrap-and-sort
623    const BUILD_SYSTEMS: &'static [&'static str] = &[
624        "cdbs",
625        "debhelper-compat",
626        "debhelper",
627        "debputy",
628        "dpkg-build-api",
629        "dpkg-dev",
630    ];
631
632    fn get_sort_key<'a>(&self, name: &'a str) -> (i32, &'a str) {
633        // Check if it's a build system (including dh-* packages)
634        if Self::BUILD_SYSTEMS.contains(&name) || name.starts_with("dh-") {
635            return (-1, name);
636        }
637
638        // Check if it starts with a regular character
639        if name
640            .chars()
641            .next()
642            .is_some_and(|c| c.is_ascii_lowercase() || c.is_ascii_digit())
643        {
644            return (0, name);
645        }
646
647        // Special packages (substvars, etc.) go last
648        (1, name)
649    }
650}
651
652impl SortingOrder for WrapAndSortOrder {
653    fn lt(&self, name1: &str, name2: &str) -> bool {
654        self.get_sort_key(name1) < self.get_sort_key(name2)
655    }
656
657    fn ignore(&self, _name: &str) -> bool {
658        // wrap-and-sort doesn't ignore any packages - it sorts everything
659        false
660    }
661}
662
663impl Relations {
664    /// Create a new relations field
665    pub fn new() -> Self {
666        Self::from(vec![])
667    }
668
669    /// Wrap and sort this relations field
670    #[must_use]
671    pub fn wrap_and_sort(self) -> Self {
672        let mut entries = self
673            .entries()
674            .map(|e| e.wrap_and_sort())
675            .collect::<Vec<_>>();
676        entries.sort();
677        // TODO: preserve comments
678        Self::from(entries)
679    }
680
681    /// Iterate over the entries in this relations field
682    pub fn entries(&self) -> impl Iterator<Item = Entry> + '_ {
683        self.0.children().filter_map(Entry::cast)
684    }
685
686    /// Iterate over the entries in this relations field
687    pub fn iter(&self) -> impl Iterator<Item = Entry> + '_ {
688        self.entries()
689    }
690
691    /// Remove the entry at the given index
692    pub fn get_entry(&self, idx: usize) -> Option<Entry> {
693        self.entries().nth(idx)
694    }
695
696    /// Remove the entry at the given index
697    pub fn remove_entry(&mut self, idx: usize) -> Entry {
698        let mut entry = self.get_entry(idx).unwrap();
699        entry.remove();
700        entry
701    }
702
703    /// Helper to collect all consecutive WHITESPACE/NEWLINE tokens starting from a node
704    fn collect_whitespace(start: Option<NodeOrToken<SyntaxNode, SyntaxToken>>) -> String {
705        let mut pattern = String::new();
706        let mut current = start;
707        while let Some(token) = current {
708            if matches!(token.kind(), WHITESPACE | NEWLINE) {
709                if let NodeOrToken::Token(t) = &token {
710                    pattern.push_str(t.text());
711                }
712                current = token.next_sibling_or_token();
713            } else {
714                break;
715            }
716        }
717        pattern
718    }
719
720    /// Helper to convert a NodeOrToken to its green equivalent
721    fn to_green(node: &NodeOrToken<SyntaxNode, SyntaxToken>) -> NodeOrToken<GreenNode, GreenToken> {
722        match node {
723            NodeOrToken::Node(n) => NodeOrToken::Node(n.green().into()),
724            NodeOrToken::Token(t) => NodeOrToken::Token(t.green().to_owned()),
725        }
726    }
727
728    /// Helper to check if a token is whitespace
729    fn is_whitespace_token(token: &GreenToken) -> bool {
730        token.kind() == rowan::SyntaxKind(WHITESPACE as u16)
731            || token.kind() == rowan::SyntaxKind(NEWLINE as u16)
732    }
733
734    /// Helper to strip trailing whitespace tokens from a list of green children
735    fn strip_trailing_ws_from_children(
736        mut children: Vec<NodeOrToken<GreenNode, GreenToken>>,
737    ) -> Vec<NodeOrToken<GreenNode, GreenToken>> {
738        while let Some(last) = children.last() {
739            if let NodeOrToken::Token(t) = last {
740                if Self::is_whitespace_token(t) {
741                    children.pop();
742                } else {
743                    break;
744                }
745            } else {
746                break;
747            }
748        }
749        children
750    }
751
752    /// Helper to strip trailing whitespace from a RELATION node's children
753    fn strip_relation_trailing_ws(relation: &SyntaxNode) -> GreenNode {
754        let children: Vec<_> = relation
755            .children_with_tokens()
756            .map(|c| Self::to_green(&c))
757            .collect();
758        let stripped = Self::strip_trailing_ws_from_children(children);
759        GreenNode::new(relation.kind().into(), stripped)
760    }
761
762    /// Helper to build nodes for insertion with odd syntax
763    fn build_odd_syntax_nodes(
764        before_ws: &str,
765        after_ws: &str,
766    ) -> Vec<NodeOrToken<GreenNode, GreenToken>> {
767        [
768            (!before_ws.is_empty())
769                .then(|| NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), before_ws))),
770            Some(NodeOrToken::Token(GreenToken::new(COMMA.into(), ","))),
771            (!after_ws.is_empty())
772                .then(|| NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), after_ws))),
773        ]
774        .into_iter()
775        .flatten()
776        .collect()
777    }
778
779    /// Detect if relations use odd syntax (whitespace before comma) and return whitespace parts
780    fn detect_odd_syntax(&self) -> Option<(String, String)> {
781        for entry_node in self.entries() {
782            let mut node = entry_node.0.next_sibling_or_token()?;
783
784            // Skip whitespace tokens and collect their text
785            let mut before = String::new();
786            while matches!(node.kind(), WHITESPACE | NEWLINE) {
787                if let NodeOrToken::Token(t) = &node {
788                    before.push_str(t.text());
789                }
790                node = node.next_sibling_or_token()?;
791            }
792
793            // Check if we found a comma after whitespace
794            if node.kind() == COMMA && !before.is_empty() {
795                let after = Self::collect_whitespace(node.next_sibling_or_token());
796                return Some((before, after));
797            }
798        }
799        None
800    }
801
802    /// Detect the most common whitespace pattern after commas in the relations.
803    ///
804    /// This matches debmutate's behavior of analyzing existing whitespace
805    /// patterns to preserve formatting when adding entries.
806    ///
807    /// # Arguments
808    /// * `default` - The default whitespace pattern to use if no pattern is detected
809    fn detect_whitespace_pattern(&self, default: &str) -> String {
810        use std::collections::HashMap;
811
812        let entries: Vec<_> = self.entries().collect();
813        let num_entries = entries.len();
814
815        if num_entries == 0 {
816            return String::from(""); // Empty list - first entry gets no prefix
817        }
818
819        if num_entries == 1 {
820            // Single entry - check if there's a pattern after it
821            if let Some(node) = entries[0].0.next_sibling_or_token() {
822                if node.kind() == COMMA {
823                    let pattern = Self::collect_whitespace(node.next_sibling_or_token());
824                    if !pattern.is_empty() {
825                        return pattern;
826                    }
827                }
828            }
829            return default.to_string(); // Use default for single entry with no pattern
830        }
831
832        // Count whitespace patterns after commas (excluding the last entry)
833        let mut whitespace_counts: HashMap<String, usize> = HashMap::new();
834
835        for (i, entry) in entries.iter().enumerate() {
836            if i == num_entries - 1 {
837                break; // Skip the last entry
838            }
839
840            // Look for comma and whitespace after this entry
841            if let Some(mut node) = entry.0.next_sibling_or_token() {
842                // Skip any whitespace/newlines before the comma (odd syntax)
843                while matches!(node.kind(), WHITESPACE | NEWLINE) {
844                    if let Some(next) = node.next_sibling_or_token() {
845                        node = next;
846                    } else {
847                        break;
848                    }
849                }
850
851                // Found comma, collect all whitespace/newlines after it
852                if node.kind() == COMMA {
853                    let pattern = Self::collect_whitespace(node.next_sibling_or_token());
854                    if !pattern.is_empty() {
855                        *whitespace_counts.entry(pattern).or_insert(0) += 1;
856                    }
857                }
858            }
859        }
860
861        // If there's exactly one pattern, use it
862        if whitespace_counts.len() == 1 {
863            if let Some((ws, _)) = whitespace_counts.iter().next() {
864                return ws.clone();
865            }
866        }
867
868        // Multiple patterns - use the most common
869        if let Some((ws, _)) = whitespace_counts.iter().max_by_key(|(_, count)| *count) {
870            return ws.clone();
871        }
872
873        // Use the provided default
874        default.to_string()
875    }
876
877    /// Insert a new entry at the given index
878    ///
879    /// # Arguments
880    /// * `idx` - The index to insert at
881    /// * `entry` - The entry to insert
882    /// * `default_sep` - Optional default separator to use if no pattern is detected (defaults to " ")
883    pub fn insert_with_separator(&mut self, idx: usize, entry: Entry, default_sep: Option<&str>) {
884        let is_empty = !self.0.children_with_tokens().any(|n| n.kind() == COMMA);
885        let whitespace = self.detect_whitespace_pattern(default_sep.unwrap_or(" "));
886
887        // Strip trailing whitespace first
888        self.strip_trailing_whitespace();
889
890        // Detect odd syntax (whitespace before comma)
891        let odd_syntax = self.detect_odd_syntax();
892
893        let (position, new_children) = if let Some(current_entry) = self.entries().nth(idx) {
894            let to_insert = if idx == 0 && is_empty {
895                vec![entry.0.green().into()]
896            } else if let Some((before_ws, after_ws)) = &odd_syntax {
897                let mut nodes = vec![entry.0.green().into()];
898                nodes.extend(Self::build_odd_syntax_nodes(before_ws, after_ws));
899                nodes
900            } else {
901                vec![
902                    entry.0.green().into(),
903                    NodeOrToken::Token(GreenToken::new(COMMA.into(), ",")),
904                    NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), whitespace.as_str())),
905                ]
906            };
907
908            (current_entry.0.index(), to_insert)
909        } else {
910            let child_count = self.0.children_with_tokens().count();
911            let to_insert = if idx == 0 {
912                vec![entry.0.green().into()]
913            } else if let Some((before_ws, after_ws)) = &odd_syntax {
914                let mut nodes = Self::build_odd_syntax_nodes(before_ws, after_ws);
915                nodes.push(entry.0.green().into());
916                nodes
917            } else {
918                vec![
919                    NodeOrToken::Token(GreenToken::new(COMMA.into(), ",")),
920                    NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), whitespace.as_str())),
921                    entry.0.green().into(),
922                ]
923            };
924
925            (child_count, to_insert)
926        };
927        // We can safely replace the root here since Relations is a root node
928        self.0 = SyntaxNode::new_root_mut(
929            self.0.replace_with(
930                self.0
931                    .green()
932                    .splice_children(position..position, new_children),
933            ),
934        );
935    }
936
937    /// Insert a new entry at the given index with default separator
938    pub fn insert(&mut self, idx: usize, entry: Entry) {
939        self.insert_with_separator(idx, entry, None);
940    }
941
942    /// Helper to recursively strip trailing whitespace from an ENTRY node
943    fn strip_entry_trailing_ws(entry: &SyntaxNode) -> GreenNode {
944        let mut children: Vec<_> = entry
945            .children_with_tokens()
946            .map(|c| Self::to_green(&c))
947            .collect();
948
949        // Strip trailing whitespace from the last RELATION if present
950        if let Some(NodeOrToken::Node(last)) = children.last() {
951            if last.kind() == rowan::SyntaxKind(RELATION as u16) {
952                // Replace last child with stripped version
953                let relation_node = entry.children().last().unwrap();
954                children.pop();
955                children.push(NodeOrToken::Node(
956                    Self::strip_relation_trailing_ws(&relation_node).into(),
957                ));
958            }
959        }
960
961        // Strip trailing whitespace tokens at entry level
962        let stripped = Self::strip_trailing_ws_from_children(children);
963        GreenNode::new(ENTRY.into(), stripped)
964    }
965
966    fn strip_trailing_whitespace(&mut self) {
967        let mut children: Vec<_> = self
968            .0
969            .children_with_tokens()
970            .map(|c| Self::to_green(&c))
971            .collect();
972
973        // Strip trailing whitespace from the last ENTRY if present
974        if let Some(NodeOrToken::Node(last)) = children.last() {
975            if last.kind() == rowan::SyntaxKind(ENTRY as u16) {
976                let last_entry = self.0.children().last().unwrap();
977                children.pop();
978                children.push(NodeOrToken::Node(
979                    Self::strip_entry_trailing_ws(&last_entry).into(),
980                ));
981            }
982        }
983
984        // Strip trailing whitespace tokens at root level
985        let stripped = Self::strip_trailing_ws_from_children(children);
986
987        let nc = self.0.children_with_tokens().count();
988        self.0 = SyntaxNode::new_root_mut(
989            self.0
990                .replace_with(self.0.green().splice_children(0..nc, stripped)),
991        );
992    }
993
994    /// Replace the entry at the given index
995    pub fn replace(&mut self, idx: usize, entry: Entry) {
996        let current_entry = self.get_entry(idx).unwrap();
997        self.0.splice_children(
998            current_entry.0.index()..current_entry.0.index() + 1,
999            vec![entry.0.into()],
1000        );
1001    }
1002
1003    /// Push a new entry to the relations field
1004    pub fn push(&mut self, entry: Entry) {
1005        let pos = self.entries().count();
1006        self.insert(pos, entry);
1007    }
1008
1009    /// Return the names of substvars in this relations field
1010    pub fn substvars(&self) -> impl Iterator<Item = String> + '_ {
1011        self.0
1012            .children()
1013            .filter_map(Substvar::cast)
1014            .map(|s| s.to_string())
1015    }
1016
1017    /// Parse a relations field from a string, allowing syntax errors
1018    pub fn parse_relaxed(s: &str, allow_substvar: bool) -> (Relations, Vec<String>) {
1019        let parse = parse(s, allow_substvar);
1020        (parse.root_mut(), parse.errors)
1021    }
1022
1023    /// Check if this relations field is satisfied by the given package versions.
1024    pub fn satisfied_by(&self, package_version: impl crate::VersionLookup + Copy) -> bool {
1025        self.entries().all(|e| e.satisfied_by(package_version))
1026    }
1027
1028    /// Check if this relations field is empty
1029    pub fn is_empty(&self) -> bool {
1030        self.entries().count() == 0
1031    }
1032
1033    /// Get the number of entries in this relations field
1034    pub fn len(&self) -> usize {
1035        self.entries().count()
1036    }
1037
1038    /// Ensure that a package has at least a minimum version constraint.
1039    ///
1040    /// If the package already exists with a version constraint that satisfies
1041    /// the minimum version, it is left unchanged. Otherwise, the constraint
1042    /// is updated or added.
1043    ///
1044    /// # Arguments
1045    /// * `package` - The package name
1046    /// * `minimum_version` - The minimum version required
1047    ///
1048    /// # Example
1049    /// ```
1050    /// use debian_control::lossless::relations::Relations;
1051    ///
1052    /// let mut relations: Relations = "debhelper (>= 9)".parse().unwrap();
1053    /// relations.ensure_minimum_version("debhelper", &"12".parse().unwrap());
1054    /// assert_eq!(relations.to_string(), "debhelper (>= 12)");
1055    ///
1056    /// let mut relations: Relations = "python3".parse().unwrap();
1057    /// relations.ensure_minimum_version("debhelper", &"12".parse().unwrap());
1058    /// assert!(relations.to_string().contains("debhelper (>= 12)"));
1059    /// ```
1060    pub fn ensure_minimum_version(&mut self, package: &str, minimum_version: &Version) {
1061        let mut found = false;
1062        let mut obsolete_indices = vec![];
1063        let mut update_idx = None;
1064
1065        let entries: Vec<_> = self.entries().collect();
1066        for (idx, entry) in entries.iter().enumerate() {
1067            let relations: Vec<_> = entry.relations().collect();
1068
1069            // Check if this entry has multiple alternatives with our package
1070            let names: Vec<_> = relations.iter().map(|r| r.name()).collect();
1071            if names.len() > 1 && names.contains(&package.to_string()) {
1072                // This is a complex alternative relation, mark for removal if obsolete
1073                let is_obsolete = relations.iter().any(|r| {
1074                    if r.name() != package {
1075                        return false;
1076                    }
1077                    if let Some((vc, ver)) = r.version() {
1078                        matches!(vc, VersionConstraint::GreaterThan if &ver < minimum_version)
1079                            || matches!(vc, VersionConstraint::GreaterThanEqual if &ver <= minimum_version)
1080                    } else {
1081                        false
1082                    }
1083                });
1084                if is_obsolete {
1085                    obsolete_indices.push(idx);
1086                }
1087                continue;
1088            }
1089
1090            // Single package entry
1091            if names.len() == 1 && names[0] == package {
1092                found = true;
1093                let relation = relations.into_iter().next().unwrap();
1094
1095                // Check if update is needed
1096                let should_update = if let Some((vc, ver)) = relation.version() {
1097                    match vc {
1098                        VersionConstraint::GreaterThanEqual | VersionConstraint::GreaterThan => {
1099                            &ver < minimum_version
1100                        }
1101                        _ => false,
1102                    }
1103                } else {
1104                    true
1105                };
1106
1107                if should_update {
1108                    update_idx = Some(idx);
1109                }
1110                break;
1111            }
1112        }
1113
1114        // Perform updates after iteration
1115        if let Some(idx) = update_idx {
1116            let relation = Relation::new(
1117                package,
1118                Some((VersionConstraint::GreaterThanEqual, minimum_version.clone())),
1119            );
1120            self.replace(idx, Entry::from(relation));
1121        }
1122
1123        // Remove obsolete entries
1124        for idx in obsolete_indices.into_iter().rev() {
1125            self.remove_entry(idx);
1126        }
1127
1128        // Add if not found
1129        if !found {
1130            let relation = Relation::new(
1131                package,
1132                Some((VersionConstraint::GreaterThanEqual, minimum_version.clone())),
1133            );
1134            self.push(Entry::from(relation));
1135        }
1136    }
1137
1138    /// Ensure that a package has an exact version constraint.
1139    ///
1140    /// # Arguments
1141    /// * `package` - The package name
1142    /// * `version` - The exact version required
1143    ///
1144    /// # Example
1145    /// ```
1146    /// use debian_control::lossless::relations::Relations;
1147    ///
1148    /// let mut relations: Relations = "debhelper (>= 9)".parse().unwrap();
1149    /// relations.ensure_exact_version("debhelper", &"12".parse().unwrap());
1150    /// assert_eq!(relations.to_string(), "debhelper (= 12)");
1151    /// ```
1152    pub fn ensure_exact_version(&mut self, package: &str, version: &Version) {
1153        let mut found = false;
1154        let mut update_idx = None;
1155
1156        let entries: Vec<_> = self.entries().collect();
1157        for (idx, entry) in entries.iter().enumerate() {
1158            let relations: Vec<_> = entry.relations().collect();
1159            let names: Vec<_> = relations.iter().map(|r| r.name()).collect();
1160
1161            if names.len() > 1 && names[0] == package {
1162                panic!("Complex rule for {}, aborting", package);
1163            }
1164
1165            if names.len() == 1 && names[0] == package {
1166                found = true;
1167                let relation = relations.into_iter().next().unwrap();
1168
1169                let should_update = if let Some((vc, ver)) = relation.version() {
1170                    vc != VersionConstraint::Equal || &ver != version
1171                } else {
1172                    true
1173                };
1174
1175                if should_update {
1176                    update_idx = Some(idx);
1177                }
1178                break;
1179            }
1180        }
1181
1182        // Perform update after iteration
1183        if let Some(idx) = update_idx {
1184            let relation =
1185                Relation::new(package, Some((VersionConstraint::Equal, version.clone())));
1186            self.replace(idx, Entry::from(relation));
1187        }
1188
1189        if !found {
1190            let relation =
1191                Relation::new(package, Some((VersionConstraint::Equal, version.clone())));
1192            self.push(Entry::from(relation));
1193        }
1194    }
1195
1196    /// Ensure that a package dependency exists, without specifying a version.
1197    ///
1198    /// If the package already exists (with or without a version constraint),
1199    /// the relations field is left unchanged. Otherwise, the package is added
1200    /// without a version constraint.
1201    ///
1202    /// # Arguments
1203    /// * `package` - The package name
1204    ///
1205    /// # Example
1206    /// ```
1207    /// use debian_control::lossless::relations::Relations;
1208    ///
1209    /// let mut relations: Relations = "python3".parse().unwrap();
1210    /// relations.ensure_some_version("debhelper");
1211    /// assert!(relations.to_string().contains("debhelper"));
1212    /// ```
1213    pub fn ensure_some_version(&mut self, package: &str) {
1214        for entry in self.entries() {
1215            let relations: Vec<_> = entry.relations().collect();
1216            let names: Vec<_> = relations.iter().map(|r| r.name()).collect();
1217
1218            if names.len() > 1 && names[0] == package {
1219                panic!("Complex rule for {}, aborting", package);
1220            }
1221
1222            if names.len() == 1 && names[0] == package {
1223                // Package already exists, don't modify
1224                return;
1225            }
1226        }
1227
1228        // Package not found, add it
1229        let relation = Relation::simple(package);
1230        self.push(Entry::from(relation));
1231    }
1232
1233    /// Ensure that a substitution variable is present in the relations.
1234    ///
1235    /// If the substvar already exists, it is left unchanged. Otherwise, it is added
1236    /// at the end of the relations list.
1237    ///
1238    /// # Arguments
1239    /// * `substvar` - The substitution variable (e.g., "${misc:Depends}")
1240    ///
1241    /// # Returns
1242    /// `Ok(())` on success, or `Err` with an error message if parsing fails
1243    ///
1244    /// # Example
1245    /// ```
1246    /// use debian_control::lossless::relations::Relations;
1247    ///
1248    /// let mut relations: Relations = "python3".parse().unwrap();
1249    /// relations.ensure_substvar("${misc:Depends}").unwrap();
1250    /// assert_eq!(relations.to_string(), "python3, ${misc:Depends}");
1251    /// ```
1252    pub fn ensure_substvar(&mut self, substvar: &str) -> Result<(), String> {
1253        // Check if the substvar already exists
1254        for existing in self.substvars() {
1255            if existing.trim() == substvar.trim() {
1256                return Ok(());
1257            }
1258        }
1259
1260        // Parse the substvar
1261        let (parsed, errors) = Relations::parse_relaxed(substvar, true);
1262        if !errors.is_empty() {
1263            return Err(errors.join("\n"));
1264        }
1265
1266        // Detect whitespace pattern to preserve formatting
1267        let whitespace = self.detect_whitespace_pattern(" ");
1268
1269        // Find the substvar node and inject it
1270        for substvar_node in parsed.0.children().filter(|n| n.kind() == SUBSTVAR) {
1271            let has_content = self.entries().next().is_some() || self.substvars().next().is_some();
1272
1273            let mut builder = GreenNodeBuilder::new();
1274            builder.start_node(ROOT.into());
1275
1276            // Copy existing content
1277            for child in self.0.children_with_tokens() {
1278                match child {
1279                    NodeOrToken::Node(n) => inject(&mut builder, n),
1280                    NodeOrToken::Token(t) => builder.token(t.kind().into(), t.text()),
1281                }
1282            }
1283
1284            // Add separator if needed, using detected whitespace pattern
1285            if has_content {
1286                builder.token(COMMA.into(), ",");
1287                builder.token(WHITESPACE.into(), whitespace.as_str());
1288            }
1289
1290            // Inject the substvar node
1291            inject(&mut builder, substvar_node);
1292
1293            builder.finish_node();
1294            self.0 = SyntaxNode::new_root_mut(builder.finish());
1295        }
1296
1297        Ok(())
1298    }
1299
1300    /// Filter entries based on a predicate function.
1301    ///
1302    /// # Arguments
1303    /// * `keep` - A function that returns true for entries to keep
1304    ///
1305    /// # Example
1306    /// ```
1307    /// use debian_control::lossless::relations::Relations;
1308    ///
1309    /// let mut relations: Relations = "python3, debhelper, rustc".parse().unwrap();
1310    /// relations.filter_entries(|entry| {
1311    ///     entry.relations().any(|r| r.name().starts_with("python"))
1312    /// });
1313    /// assert_eq!(relations.to_string(), "python3");
1314    /// ```
1315    pub fn filter_entries<F>(&mut self, keep: F)
1316    where
1317        F: Fn(&Entry) -> bool,
1318    {
1319        let indices_to_remove: Vec<_> = self
1320            .entries()
1321            .enumerate()
1322            .filter_map(|(idx, entry)| if keep(&entry) { None } else { Some(idx) })
1323            .collect();
1324
1325        // Remove in reverse order to maintain correct indices
1326        for idx in indices_to_remove.into_iter().rev() {
1327            self.remove_entry(idx);
1328        }
1329    }
1330
1331    /// Check whether the relations are sorted according to a given sorting order.
1332    ///
1333    /// # Arguments
1334    /// * `sorting_order` - The sorting order to check against
1335    ///
1336    /// # Example
1337    /// ```
1338    /// use debian_control::lossless::relations::{Relations, WrapAndSortOrder};
1339    ///
1340    /// let relations: Relations = "debhelper, python3, rustc".parse().unwrap();
1341    /// assert!(relations.is_sorted(&WrapAndSortOrder));
1342    ///
1343    /// let relations: Relations = "rustc, debhelper, python3".parse().unwrap();
1344    /// assert!(!relations.is_sorted(&WrapAndSortOrder));
1345    /// ```
1346    pub fn is_sorted(&self, sorting_order: &impl SortingOrder) -> bool {
1347        let mut last_name: Option<String> = None;
1348        for entry in self.entries() {
1349            // Skip empty entries
1350            let mut relations = entry.relations();
1351            let Some(relation) = relations.next() else {
1352                continue;
1353            };
1354
1355            let name = relation.name();
1356
1357            // Skip items that should be ignored
1358            if sorting_order.ignore(&name) {
1359                continue;
1360            }
1361
1362            // Check if this breaks the sort order
1363            if let Some(ref last) = last_name {
1364                if sorting_order.lt(&name, last) {
1365                    return false;
1366                }
1367            }
1368
1369            last_name = Some(name);
1370        }
1371        true
1372    }
1373
1374    /// Find the position to insert an entry while maintaining sort order.
1375    ///
1376    /// This method detects the current sorting order and returns the appropriate
1377    /// insertion position. If there are fewer than 2 entries, it defaults to
1378    /// WrapAndSortOrder. If no sorting order is detected, it returns the end position.
1379    ///
1380    /// # Arguments
1381    /// * `entry` - The entry to insert
1382    ///
1383    /// # Returns
1384    /// The index where the entry should be inserted
1385    fn find_insert_position(&self, entry: &Entry) -> usize {
1386        // Get the package name from the first relation in the entry
1387        let Some(relation) = entry.relations().next() else {
1388            // Empty entry, just append at the end
1389            return self.len();
1390        };
1391        let package_name = relation.name();
1392
1393        // Count non-empty entries
1394        let count = self.entries().filter(|e| !e.is_empty()).count();
1395
1396        // If there are less than 2 items, default to WrapAndSortOrder
1397        let sorting_order: Box<dyn SortingOrder> = if count < 2 {
1398            Box::new(WrapAndSortOrder)
1399        } else {
1400            // Try to detect which sorting order is being used
1401            // Try WrapAndSortOrder first, then DefaultSortingOrder
1402            if self.is_sorted(&WrapAndSortOrder) {
1403                Box::new(WrapAndSortOrder)
1404            } else if self.is_sorted(&DefaultSortingOrder) {
1405                Box::new(DefaultSortingOrder)
1406            } else {
1407                // No sorting order detected, just append at the end
1408                return self.len();
1409            }
1410        };
1411
1412        // If adding a special item that should be ignored by this sort order, append at the end
1413        if sorting_order.ignore(&package_name) {
1414            return self.len();
1415        }
1416
1417        // Insert in sorted order among regular items
1418        let mut position = 0;
1419        for (idx, existing_entry) in self.entries().enumerate() {
1420            let mut existing_relations = existing_entry.relations();
1421            let Some(existing_relation) = existing_relations.next() else {
1422                // Empty entry, skip
1423                position += 1;
1424                continue;
1425            };
1426
1427            let existing_name = existing_relation.name();
1428
1429            // Skip special items when finding insertion position
1430            if sorting_order.ignore(&existing_name) {
1431                position += 1;
1432                continue;
1433            }
1434
1435            // Compare with regular items only
1436            if sorting_order.lt(&package_name, &existing_name) {
1437                return idx;
1438            }
1439            position += 1;
1440        }
1441
1442        position
1443    }
1444
1445    /// Drop a dependency from the relations by package name.
1446    ///
1447    /// # Arguments
1448    /// * `package` - The package name to remove
1449    ///
1450    /// # Returns
1451    /// `true` if the package was found and removed, `false` otherwise
1452    ///
1453    /// # Example
1454    /// ```
1455    /// use debian_control::lossless::relations::Relations;
1456    ///
1457    /// let mut relations: Relations = "python3, debhelper, rustc".parse().unwrap();
1458    /// assert!(relations.drop_dependency("debhelper"));
1459    /// assert_eq!(relations.to_string(), "python3, rustc");
1460    /// assert!(!relations.drop_dependency("nonexistent"));
1461    /// ```
1462    pub fn drop_dependency(&mut self, package: &str) -> bool {
1463        let indices_to_remove: Vec<_> = self
1464            .entries()
1465            .enumerate()
1466            .filter_map(|(idx, entry)| {
1467                let relations: Vec<_> = entry.relations().collect();
1468                let names: Vec<_> = relations.iter().map(|r| r.name()).collect();
1469                if names == vec![package] {
1470                    Some(idx)
1471                } else {
1472                    None
1473                }
1474            })
1475            .collect();
1476
1477        let found = !indices_to_remove.is_empty();
1478
1479        // Remove in reverse order to maintain correct indices
1480        for idx in indices_to_remove.into_iter().rev() {
1481            self.remove_entry(idx);
1482        }
1483
1484        found
1485    }
1486
1487    /// Add a dependency at a specific position or auto-detect the position.
1488    ///
1489    /// If `position` is `None`, the position is automatically determined based
1490    /// on the detected sorting order. If a sorting order is detected, the entry
1491    /// is inserted in the appropriate position to maintain that order. Otherwise,
1492    /// it is appended at the end.
1493    ///
1494    /// # Arguments
1495    /// * `entry` - The entry to add
1496    /// * `position` - Optional position to insert at
1497    ///
1498    /// # Example
1499    /// ```
1500    /// use debian_control::lossless::relations::{Relations, Relation, Entry};
1501    ///
1502    /// let mut relations: Relations = "python3, rustc".parse().unwrap();
1503    /// let entry = Entry::from(Relation::simple("debhelper"));
1504    /// relations.add_dependency(entry, None);
1505    /// // debhelper is inserted in sorted order (if order is detected)
1506    /// ```
1507    pub fn add_dependency(&mut self, entry: Entry, position: Option<usize>) {
1508        let pos = position.unwrap_or_else(|| self.find_insert_position(&entry));
1509        self.insert(pos, entry);
1510    }
1511
1512    /// Get the entry containing a specific package.
1513    ///
1514    /// This returns the first entry that contains exactly one relation with the
1515    /// specified package name (no alternatives).
1516    ///
1517    /// # Arguments
1518    /// * `package` - The package name to search for
1519    ///
1520    /// # Returns
1521    /// A tuple of (index, Entry) if found
1522    ///
1523    /// # Errors
1524    /// Returns `Err` with a message if the package is found in a complex rule (with alternatives)
1525    ///
1526    /// # Example
1527    /// ```
1528    /// use debian_control::lossless::relations::Relations;
1529    ///
1530    /// let relations: Relations = "python3, debhelper (>= 12), rustc".parse().unwrap();
1531    /// let (idx, entry) = relations.get_relation("debhelper").unwrap();
1532    /// assert_eq!(idx, 1);
1533    /// assert_eq!(entry.to_string(), "debhelper (>= 12)");
1534    /// ```
1535    pub fn get_relation(&self, package: &str) -> Result<(usize, Entry), String> {
1536        for (idx, entry) in self.entries().enumerate() {
1537            let relations: Vec<_> = entry.relations().collect();
1538            let names: Vec<_> = relations.iter().map(|r| r.name()).collect();
1539
1540            if names.len() > 1 && names.contains(&package.to_string()) {
1541                return Err(format!("Complex rule for {}, aborting", package));
1542            }
1543
1544            if names.len() == 1 && names[0] == package {
1545                return Ok((idx, entry));
1546            }
1547        }
1548        Err(format!("Package {} not found", package))
1549    }
1550
1551    /// Iterate over all entries containing a specific package.
1552    ///
1553    /// # Arguments
1554    /// * `package` - The package name to search for
1555    ///
1556    /// # Returns
1557    /// An iterator over tuples of (index, Entry)
1558    ///
1559    /// # Example
1560    /// ```
1561    /// use debian_control::lossless::relations::Relations;
1562    ///
1563    /// let relations: Relations = "python3 | python3-minimal, python3-dev".parse().unwrap();
1564    /// let entries: Vec<_> = relations.iter_relations_for("python3").collect();
1565    /// assert_eq!(entries.len(), 1);
1566    /// ```
1567    pub fn iter_relations_for(&self, package: &str) -> impl Iterator<Item = (usize, Entry)> + '_ {
1568        let package = package.to_string();
1569        self.entries().enumerate().filter(move |(_, entry)| {
1570            let names: Vec<_> = entry.relations().map(|r| r.name()).collect();
1571            names.contains(&package)
1572        })
1573    }
1574
1575    /// Check whether a package exists in the relations.
1576    ///
1577    /// # Arguments
1578    /// * `package` - The package name to search for
1579    ///
1580    /// # Returns
1581    /// `true` if the package is found, `false` otherwise
1582    ///
1583    /// # Example
1584    /// ```
1585    /// use debian_control::lossless::relations::Relations;
1586    ///
1587    /// let relations: Relations = "python3, debhelper, rustc".parse().unwrap();
1588    /// assert!(relations.has_relation("debhelper"));
1589    /// assert!(!relations.has_relation("nonexistent"));
1590    /// ```
1591    pub fn has_relation(&self, package: &str) -> bool {
1592        self.entries()
1593            .any(|entry| entry.relations().any(|r| r.name() == package))
1594    }
1595}
1596
1597impl From<Vec<Entry>> for Relations {
1598    fn from(entries: Vec<Entry>) -> Self {
1599        let mut builder = GreenNodeBuilder::new();
1600        builder.start_node(ROOT.into());
1601        for (i, entry) in entries.into_iter().enumerate() {
1602            if i > 0 {
1603                builder.token(COMMA.into(), ",");
1604                builder.token(WHITESPACE.into(), " ");
1605            }
1606            inject(&mut builder, entry.0);
1607        }
1608        builder.finish_node();
1609        Relations(SyntaxNode::new_root_mut(builder.finish()))
1610    }
1611}
1612
1613impl From<Entry> for Relations {
1614    fn from(entry: Entry) -> Self {
1615        Self::from(vec![entry])
1616    }
1617}
1618
1619impl Default for Entry {
1620    fn default() -> Self {
1621        Self::new()
1622    }
1623}
1624
1625impl PartialOrd for Entry {
1626    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1627        let mut rels_a = self.relations();
1628        let mut rels_b = other.relations();
1629        while let (Some(a), Some(b)) = (rels_a.next(), rels_b.next()) {
1630            match a.cmp(&b) {
1631                std::cmp::Ordering::Equal => continue,
1632                x => return Some(x),
1633            }
1634        }
1635
1636        if rels_a.next().is_some() {
1637            return Some(std::cmp::Ordering::Greater);
1638        }
1639
1640        if rels_b.next().is_some() {
1641            return Some(std::cmp::Ordering::Less);
1642        }
1643
1644        Some(std::cmp::Ordering::Equal)
1645    }
1646}
1647
1648impl Eq for Entry {}
1649
1650impl Ord for Entry {
1651    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1652        self.partial_cmp(other).unwrap()
1653    }
1654}
1655
1656impl Entry {
1657    /// Create a new entry
1658    pub fn new() -> Self {
1659        let mut builder = GreenNodeBuilder::new();
1660        builder.start_node(SyntaxKind::ENTRY.into());
1661        builder.finish_node();
1662        Entry(SyntaxNode::new_root_mut(builder.finish()))
1663    }
1664
1665    /// Replace the relation at the given index
1666    pub fn replace(&mut self, idx: usize, relation: Relation) {
1667        let current_relation = self.get_relation(idx).unwrap();
1668
1669        let old_root = current_relation.0;
1670        let new_root = relation.0;
1671        // Preserve white the current relation has
1672        let mut prev = new_root.first_child_or_token();
1673        let mut new_head_len = 0;
1674        // First, strip off any whitespace from the new relation
1675        while let Some(p) = prev {
1676            if p.kind() == WHITESPACE || p.kind() == NEWLINE {
1677                new_head_len += 1;
1678                prev = p.next_sibling_or_token();
1679            } else {
1680                break;
1681            }
1682        }
1683        let mut new_tail_len = 0;
1684        let mut next = new_root.last_child_or_token();
1685        while let Some(n) = next {
1686            if n.kind() == WHITESPACE || n.kind() == NEWLINE {
1687                new_tail_len += 1;
1688                next = n.prev_sibling_or_token();
1689            } else {
1690                break;
1691            }
1692        }
1693        // Then, inherit the whitespace from the old relation
1694        let mut prev = old_root.first_child_or_token();
1695        let mut old_head = vec![];
1696        while let Some(p) = prev {
1697            if p.kind() == WHITESPACE || p.kind() == NEWLINE {
1698                old_head.push(p.clone());
1699                prev = p.next_sibling_or_token();
1700            } else {
1701                break;
1702            }
1703        }
1704        let mut old_tail = vec![];
1705        let mut next = old_root.last_child_or_token();
1706        while let Some(n) = next {
1707            if n.kind() == WHITESPACE || n.kind() == NEWLINE {
1708                old_tail.push(n.clone());
1709                next = n.prev_sibling_or_token();
1710            } else {
1711                break;
1712            }
1713        }
1714        new_root.splice_children(0..new_head_len, old_head);
1715        let tail_pos = new_root.children_with_tokens().count() - new_tail_len;
1716        new_root.splice_children(
1717            tail_pos - new_tail_len..tail_pos,
1718            old_tail.into_iter().rev(),
1719        );
1720        let index = old_root.index();
1721        self.0
1722            .splice_children(index..index + 1, vec![new_root.into()]);
1723    }
1724
1725    /// Wrap and sort the relations in this entry
1726    #[must_use]
1727    pub fn wrap_and_sort(&self) -> Self {
1728        let mut relations = self
1729            .relations()
1730            .map(|r| r.wrap_and_sort())
1731            .collect::<Vec<_>>();
1732        // TODO: preserve comments
1733        relations.sort();
1734        Self::from(relations)
1735    }
1736
1737    /// Iterate over the relations in this entry
1738    pub fn relations(&self) -> impl Iterator<Item = Relation> + '_ {
1739        self.0.children().filter_map(Relation::cast)
1740    }
1741
1742    /// Iterate over the relations in this entry
1743    pub fn iter(&self) -> impl Iterator<Item = Relation> + '_ {
1744        self.relations()
1745    }
1746
1747    /// Get the relation at the given index
1748    pub fn get_relation(&self, idx: usize) -> Option<Relation> {
1749        self.relations().nth(idx)
1750    }
1751
1752    /// Remove the relation at the given index
1753    ///
1754    /// # Arguments
1755    /// * `idx` - The index of the relation to remove
1756    ///
1757    /// # Example
1758    /// ```
1759    /// use debian_control::lossless::relations::{Relation,Entry};
1760    /// let mut entry: Entry = r"python3-dulwich (>= 0.19.0) | python3-requests".parse().unwrap();
1761    /// entry.remove_relation(1);
1762    /// assert_eq!(entry.to_string(), "python3-dulwich (>= 0.19.0)");
1763    /// ```
1764    pub fn remove_relation(&self, idx: usize) -> Relation {
1765        let mut relation = self.get_relation(idx).unwrap();
1766        relation.remove();
1767        relation
1768    }
1769
1770    /// Check if this entry is satisfied by the given package versions.
1771    ///
1772    /// # Arguments
1773    /// * `package_version` - A function that returns the version of a package.
1774    ///
1775    /// # Example
1776    /// ```
1777    /// use debian_control::lossless::relations::{Relation,Entry};
1778    /// let entry = Entry::from(vec!["samba (>= 2.0)".parse::<Relation>().unwrap()]);
1779    /// assert!(entry.satisfied_by(|name: &str| -> Option<debversion::Version> {
1780    ///    match name {
1781    ///    "samba" => Some("2.0".parse().unwrap()),
1782    ///    _ => None
1783    /// }}));
1784    /// ```
1785    pub fn satisfied_by(&self, package_version: impl crate::VersionLookup + Copy) -> bool {
1786        self.relations().any(|r| {
1787            let actual = package_version.lookup_version(r.name().as_str());
1788            if let Some((vc, version)) = r.version() {
1789                if let Some(actual) = actual {
1790                    match vc {
1791                        VersionConstraint::GreaterThanEqual => *actual >= version,
1792                        VersionConstraint::LessThanEqual => *actual <= version,
1793                        VersionConstraint::Equal => *actual == version,
1794                        VersionConstraint::GreaterThan => *actual > version,
1795                        VersionConstraint::LessThan => *actual < version,
1796                    }
1797                } else {
1798                    false
1799                }
1800            } else {
1801                actual.is_some()
1802            }
1803        })
1804    }
1805
1806    /// Remove this entry
1807    ///
1808    /// # Example
1809    /// ```
1810    /// use debian_control::lossless::relations::{Relations,Entry};
1811    /// let mut relations: Relations = r"python3-dulwich (>= 0.19.0), python3-urllib3 (<< 1.26.0)".parse().unwrap();
1812    /// let mut entry = relations.get_entry(0).unwrap();
1813    /// entry.remove();
1814    /// assert_eq!(relations.to_string(), "python3-urllib3 (<< 1.26.0)");
1815    /// ```
1816    pub fn remove(&mut self) {
1817        let mut removed_comma = false;
1818        let is_first = !self
1819            .0
1820            .siblings(Direction::Prev)
1821            .skip(1)
1822            .any(|n| n.kind() == ENTRY);
1823        while let Some(n) = self.0.next_sibling_or_token() {
1824            if n.kind() == WHITESPACE || n.kind() == NEWLINE {
1825                n.detach();
1826            } else if n.kind() == COMMA {
1827                n.detach();
1828                removed_comma = true;
1829                break;
1830            } else {
1831                panic!("Unexpected node: {:?}", n);
1832            }
1833        }
1834        if !is_first {
1835            while let Some(n) = self.0.prev_sibling_or_token() {
1836                if n.kind() == WHITESPACE || n.kind() == NEWLINE {
1837                    n.detach();
1838                } else if !removed_comma && n.kind() == COMMA {
1839                    n.detach();
1840                    break;
1841                } else {
1842                    break;
1843                }
1844            }
1845        } else {
1846            while let Some(n) = self.0.next_sibling_or_token() {
1847                if n.kind() == WHITESPACE || n.kind() == NEWLINE {
1848                    n.detach();
1849                } else {
1850                    break;
1851                }
1852            }
1853        }
1854        self.0.detach();
1855    }
1856
1857    /// Check if this entry is empty
1858    pub fn is_empty(&self) -> bool {
1859        self.relations().count() == 0
1860    }
1861
1862    /// Get the number of relations in this entry
1863    pub fn len(&self) -> usize {
1864        self.relations().count()
1865    }
1866
1867    /// Push a new relation to the entry
1868    ///
1869    /// # Arguments
1870    /// * `relation` - The relation to push
1871    ///
1872    /// # Example
1873    /// ```
1874    /// use debian_control::lossless::relations::{Relation,Entry};
1875    /// let mut entry: Entry = "samba (>= 2.0)".parse().unwrap();
1876    /// entry.push("python3-requests".parse().unwrap());
1877    /// assert_eq!(entry.to_string(), "samba (>= 2.0) | python3-requests");
1878    /// ```
1879    pub fn push(&mut self, relation: Relation) {
1880        let is_empty = !self
1881            .0
1882            .children_with_tokens()
1883            .any(|n| n.kind() == PIPE || n.kind() == RELATION);
1884
1885        let (position, new_children) = if let Some(current_relation) = self.relations().last() {
1886            let to_insert: Vec<NodeOrToken<GreenNode, GreenToken>> = if is_empty {
1887                vec![relation.0.green().into()]
1888            } else {
1889                vec![
1890                    NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), " ")),
1891                    NodeOrToken::Token(GreenToken::new(PIPE.into(), "|")),
1892                    NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), " ")),
1893                    relation.0.green().into(),
1894                ]
1895            };
1896
1897            (current_relation.0.index() + 1, to_insert)
1898        } else {
1899            let child_count = self.0.children_with_tokens().count();
1900            (
1901                child_count,
1902                if is_empty {
1903                    vec![relation.0.green().into()]
1904                } else {
1905                    vec![
1906                        NodeOrToken::Token(GreenToken::new(PIPE.into(), "|")),
1907                        NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), " ")),
1908                        relation.0.green().into(),
1909                    ]
1910                },
1911            )
1912        };
1913
1914        let new_root = SyntaxNode::new_root_mut(
1915            self.0.replace_with(
1916                self.0
1917                    .green()
1918                    .splice_children(position..position, new_children),
1919            ),
1920        );
1921
1922        if let Some(parent) = self.0.parent() {
1923            parent.splice_children(self.0.index()..self.0.index() + 1, vec![new_root.into()]);
1924            self.0 = parent
1925                .children_with_tokens()
1926                .nth(self.0.index())
1927                .unwrap()
1928                .clone()
1929                .into_node()
1930                .unwrap();
1931        } else {
1932            self.0 = new_root;
1933        }
1934    }
1935}
1936
1937fn inject(builder: &mut GreenNodeBuilder, node: SyntaxNode) {
1938    builder.start_node(node.kind().into());
1939    for child in node.children_with_tokens() {
1940        match child {
1941            rowan::NodeOrToken::Node(child) => {
1942                inject(builder, child);
1943            }
1944            rowan::NodeOrToken::Token(token) => {
1945                builder.token(token.kind().into(), token.text());
1946            }
1947        }
1948    }
1949    builder.finish_node();
1950}
1951
1952impl From<Vec<Relation>> for Entry {
1953    fn from(relations: Vec<Relation>) -> Self {
1954        let mut builder = GreenNodeBuilder::new();
1955        builder.start_node(SyntaxKind::ENTRY.into());
1956        for (i, relation) in relations.into_iter().enumerate() {
1957            if i > 0 {
1958                builder.token(WHITESPACE.into(), " ");
1959                builder.token(COMMA.into(), "|");
1960                builder.token(WHITESPACE.into(), " ");
1961            }
1962            inject(&mut builder, relation.0);
1963        }
1964        builder.finish_node();
1965        Entry(SyntaxNode::new_root_mut(builder.finish()))
1966    }
1967}
1968
1969impl From<Relation> for Entry {
1970    fn from(relation: Relation) -> Self {
1971        Self::from(vec![relation])
1972    }
1973}
1974
1975impl Relation {
1976    /// Create a new relation
1977    ///
1978    /// # Arguments
1979    /// * `name` - The name of the package
1980    /// * `version_constraint` - The version constraint and version to use
1981    ///
1982    /// # Example
1983    /// ```
1984    /// use debian_control::lossless::relations::{Relation};
1985    /// use debian_control::relations::VersionConstraint;
1986    /// let relation = Relation::new("samba", Some((VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap())));
1987    /// assert_eq!(relation.to_string(), "samba (>= 2.0)");
1988    /// ```
1989    pub fn new(name: &str, version_constraint: Option<(VersionConstraint, Version)>) -> Self {
1990        let mut builder = GreenNodeBuilder::new();
1991        builder.start_node(SyntaxKind::RELATION.into());
1992        builder.token(IDENT.into(), name);
1993        if let Some((vc, version)) = version_constraint {
1994            builder.token(WHITESPACE.into(), " ");
1995            builder.start_node(SyntaxKind::VERSION.into());
1996            builder.token(L_PARENS.into(), "(");
1997            builder.start_node(SyntaxKind::CONSTRAINT.into());
1998            for c in vc.to_string().chars() {
1999                builder.token(
2000                    match c {
2001                        '>' => R_ANGLE.into(),
2002                        '<' => L_ANGLE.into(),
2003                        '=' => EQUAL.into(),
2004                        _ => unreachable!(),
2005                    },
2006                    c.to_string().as_str(),
2007                );
2008            }
2009            builder.finish_node();
2010
2011            builder.token(WHITESPACE.into(), " ");
2012
2013            builder.token(IDENT.into(), version.to_string().as_str());
2014
2015            builder.token(R_PARENS.into(), ")");
2016
2017            builder.finish_node();
2018        }
2019
2020        builder.finish_node();
2021        Relation(SyntaxNode::new_root_mut(builder.finish()))
2022    }
2023
2024    /// Wrap and sort this relation
2025    ///
2026    /// # Example
2027    /// ```
2028    /// use debian_control::lossless::relations::Relation;
2029    /// let relation = "  samba  (  >= 2.0) ".parse::<Relation>().unwrap();
2030    /// assert_eq!(relation.wrap_and_sort().to_string(), "samba (>= 2.0)");
2031    /// ```
2032    #[must_use]
2033    pub fn wrap_and_sort(&self) -> Self {
2034        let mut builder = GreenNodeBuilder::new();
2035        builder.start_node(SyntaxKind::RELATION.into());
2036        builder.token(IDENT.into(), self.name().as_str());
2037        if let Some(archqual) = self.archqual() {
2038            builder.token(COLON.into(), ":");
2039            builder.token(IDENT.into(), archqual.as_str());
2040        }
2041        if let Some((vc, version)) = self.version() {
2042            builder.token(WHITESPACE.into(), " ");
2043            builder.start_node(SyntaxKind::VERSION.into());
2044            builder.token(L_PARENS.into(), "(");
2045            builder.start_node(SyntaxKind::CONSTRAINT.into());
2046            builder.token(
2047                match vc {
2048                    VersionConstraint::GreaterThanEqual => R_ANGLE.into(),
2049                    VersionConstraint::LessThanEqual => L_ANGLE.into(),
2050                    VersionConstraint::Equal => EQUAL.into(),
2051                    VersionConstraint::GreaterThan => R_ANGLE.into(),
2052                    VersionConstraint::LessThan => L_ANGLE.into(),
2053                },
2054                vc.to_string().as_str(),
2055            );
2056            builder.finish_node();
2057            builder.token(WHITESPACE.into(), " ");
2058            builder.token(IDENT.into(), version.to_string().as_str());
2059            builder.token(R_PARENS.into(), ")");
2060            builder.finish_node();
2061        }
2062        if let Some(architectures) = self.architectures() {
2063            builder.token(WHITESPACE.into(), " ");
2064            builder.start_node(ARCHITECTURES.into());
2065            builder.token(L_BRACKET.into(), "[");
2066            for (i, arch) in architectures.enumerate() {
2067                if i > 0 {
2068                    builder.token(WHITESPACE.into(), " ");
2069                }
2070                builder.token(IDENT.into(), arch.as_str());
2071            }
2072            builder.token(R_BRACKET.into(), "]");
2073            builder.finish_node();
2074        }
2075        for profiles in self.profiles() {
2076            builder.token(WHITESPACE.into(), " ");
2077            builder.start_node(PROFILES.into());
2078            builder.token(L_ANGLE.into(), "<");
2079            for (i, profile) in profiles.into_iter().enumerate() {
2080                if i > 0 {
2081                    builder.token(WHITESPACE.into(), " ");
2082                }
2083                match profile {
2084                    BuildProfile::Disabled(name) => {
2085                        builder.token(NOT.into(), "!");
2086                        builder.token(IDENT.into(), name.as_str());
2087                    }
2088                    BuildProfile::Enabled(name) => {
2089                        builder.token(IDENT.into(), name.as_str());
2090                    }
2091                }
2092            }
2093            builder.token(R_ANGLE.into(), ">");
2094            builder.finish_node();
2095        }
2096        builder.finish_node();
2097        Relation(SyntaxNode::new_root_mut(builder.finish()))
2098    }
2099
2100    /// Create a new simple relation, without any version constraints.
2101    ///
2102    /// # Example
2103    /// ```
2104    /// use debian_control::lossless::relations::Relation;
2105    /// let relation = Relation::simple("samba");
2106    /// assert_eq!(relation.to_string(), "samba");
2107    /// ```
2108    pub fn simple(name: &str) -> Self {
2109        Self::new(name, None)
2110    }
2111
2112    /// Remove the version constraint from the relation.
2113    ///
2114    /// # Example
2115    /// ```
2116    /// use debian_control::lossless::relations::{Relation};
2117    /// use debian_control::relations::VersionConstraint;
2118    /// let mut relation = Relation::new("samba", Some((VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap())));
2119    /// relation.drop_constraint();
2120    /// assert_eq!(relation.to_string(), "samba");
2121    /// ```
2122    pub fn drop_constraint(&mut self) -> bool {
2123        let version_token = self.0.children().find(|n| n.kind() == VERSION);
2124        if let Some(version_token) = version_token {
2125            // Remove any whitespace before the version token
2126            while let Some(prev) = version_token.prev_sibling_or_token() {
2127                if prev.kind() == WHITESPACE || prev.kind() == NEWLINE {
2128                    prev.detach();
2129                } else {
2130                    break;
2131                }
2132            }
2133            version_token.detach();
2134            return true;
2135        }
2136
2137        false
2138    }
2139
2140    /// Return the name of the package in the relation.
2141    ///
2142    /// # Example
2143    /// ```
2144    /// use debian_control::lossless::relations::Relation;
2145    /// let relation = Relation::simple("samba");
2146    /// assert_eq!(relation.name(), "samba");
2147    /// ```
2148    pub fn name(&self) -> String {
2149        self.0
2150            .children_with_tokens()
2151            .find_map(|it| match it {
2152                SyntaxElement::Token(token) if token.kind() == IDENT => Some(token),
2153                _ => None,
2154            })
2155            .unwrap()
2156            .text()
2157            .to_string()
2158    }
2159
2160    /// Return the archqual
2161    ///
2162    /// # Example
2163    /// ```
2164    /// use debian_control::lossless::relations::Relation;
2165    /// let relation: Relation = "samba:any".parse().unwrap();
2166    /// assert_eq!(relation.archqual(), Some("any".to_string()));
2167    /// ```
2168    pub fn archqual(&self) -> Option<String> {
2169        let archqual = self.0.children().find(|n| n.kind() == ARCHQUAL);
2170        let node = if let Some(archqual) = archqual {
2171            archqual.children_with_tokens().find_map(|it| match it {
2172                SyntaxElement::Token(token) if token.kind() == IDENT => Some(token),
2173                _ => None,
2174            })
2175        } else {
2176            None
2177        };
2178        node.map(|n| n.text().to_string())
2179    }
2180
2181    /// Set the architecture qualifier for this relation.
2182    ///
2183    /// # Example
2184    /// ```
2185    /// use debian_control::lossless::relations::Relation;
2186    /// let mut relation = Relation::simple("samba");
2187    /// relation.set_archqual("any");
2188    /// assert_eq!(relation.to_string(), "samba:any");
2189    /// ```
2190    pub fn set_archqual(&mut self, archqual: &str) {
2191        let mut builder = GreenNodeBuilder::new();
2192        builder.start_node(ARCHQUAL.into());
2193        builder.token(COLON.into(), ":");
2194        builder.token(IDENT.into(), archqual);
2195        builder.finish_node();
2196
2197        let node_archqual = self.0.children().find(|n| n.kind() == ARCHQUAL);
2198        if let Some(node_archqual) = node_archqual {
2199            self.0.splice_children(
2200                node_archqual.index()..node_archqual.index() + 1,
2201                vec![SyntaxNode::new_root_mut(builder.finish()).into()],
2202            );
2203        } else {
2204            let name_node = self.0.children_with_tokens().find(|n| n.kind() == IDENT);
2205            let idx = if let Some(name_node) = name_node {
2206                name_node.index() + 1
2207            } else {
2208                0
2209            };
2210            self.0.splice_children(
2211                idx..idx,
2212                vec![SyntaxNode::new_root_mut(builder.finish()).into()],
2213            );
2214        }
2215    }
2216
2217    /// Return the version constraint and the version it is constrained to.
2218    pub fn version(&self) -> Option<(VersionConstraint, Version)> {
2219        let vc = self.0.children().find(|n| n.kind() == VERSION);
2220        let vc = vc.as_ref()?;
2221        let constraint = vc.children().find(|n| n.kind() == CONSTRAINT);
2222
2223        let version = vc.children_with_tokens().find_map(|it| match it {
2224            SyntaxElement::Token(token) if token.kind() == IDENT => Some(token),
2225            _ => None,
2226        });
2227
2228        if let (Some(constraint), Some(version)) = (constraint, version) {
2229            let vc: VersionConstraint = constraint.to_string().parse().unwrap();
2230            Some((vc, (version.text().to_string()).parse().unwrap()))
2231        } else {
2232            None
2233        }
2234    }
2235
2236    /// Set the version constraint for this relation
2237    ///
2238    /// # Example
2239    /// ```
2240    /// use debian_control::lossless::relations::{Relation};
2241    /// use debian_control::relations::VersionConstraint;
2242    /// let mut relation = Relation::simple("samba");
2243    /// relation.set_version(Some((VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap())));
2244    /// assert_eq!(relation.to_string(), "samba (>= 2.0)");
2245    /// ```
2246    pub fn set_version(&mut self, version_constraint: Option<(VersionConstraint, Version)>) {
2247        let current_version = self.0.children().find(|n| n.kind() == VERSION);
2248        if let Some((vc, version)) = version_constraint {
2249            let mut builder = GreenNodeBuilder::new();
2250            builder.start_node(VERSION.into());
2251            builder.token(L_PARENS.into(), "(");
2252            builder.start_node(CONSTRAINT.into());
2253            match vc {
2254                VersionConstraint::GreaterThanEqual => {
2255                    builder.token(R_ANGLE.into(), ">");
2256                    builder.token(EQUAL.into(), "=");
2257                }
2258                VersionConstraint::LessThanEqual => {
2259                    builder.token(L_ANGLE.into(), "<");
2260                    builder.token(EQUAL.into(), "=");
2261                }
2262                VersionConstraint::Equal => {
2263                    builder.token(EQUAL.into(), "=");
2264                }
2265                VersionConstraint::GreaterThan => {
2266                    builder.token(R_ANGLE.into(), ">");
2267                }
2268                VersionConstraint::LessThan => {
2269                    builder.token(L_ANGLE.into(), "<");
2270                }
2271            }
2272            builder.finish_node(); // CONSTRAINT
2273            builder.token(WHITESPACE.into(), " ");
2274            builder.token(IDENT.into(), version.to_string().as_str());
2275            builder.token(R_PARENS.into(), ")");
2276            builder.finish_node(); // VERSION
2277
2278            if let Some(current_version) = current_version {
2279                self.0.splice_children(
2280                    current_version.index()..current_version.index() + 1,
2281                    vec![SyntaxNode::new_root_mut(builder.finish()).into()],
2282                );
2283            } else {
2284                let name_node = self.0.children_with_tokens().find(|n| n.kind() == IDENT);
2285                let idx = if let Some(name_node) = name_node {
2286                    name_node.index() + 1
2287                } else {
2288                    0
2289                };
2290                let new_children = vec![
2291                    GreenToken::new(WHITESPACE.into(), " ").into(),
2292                    builder.finish().into(),
2293                ];
2294                let new_root = SyntaxNode::new_root_mut(
2295                    self.0.green().splice_children(idx..idx, new_children),
2296                );
2297                if let Some(parent) = self.0.parent() {
2298                    parent
2299                        .splice_children(self.0.index()..self.0.index() + 1, vec![new_root.into()]);
2300                    self.0 = parent
2301                        .children_with_tokens()
2302                        .nth(self.0.index())
2303                        .unwrap()
2304                        .clone()
2305                        .into_node()
2306                        .unwrap();
2307                } else {
2308                    self.0 = new_root;
2309                }
2310            }
2311        } else if let Some(current_version) = current_version {
2312            // Remove any whitespace before the version token
2313            while let Some(prev) = current_version.prev_sibling_or_token() {
2314                if prev.kind() == WHITESPACE || prev.kind() == NEWLINE {
2315                    prev.detach();
2316                } else {
2317                    break;
2318                }
2319            }
2320            current_version.detach();
2321        }
2322    }
2323
2324    /// Return an iterator over the architectures for this relation
2325    ///
2326    /// # Example
2327    /// ```
2328    /// use debian_control::lossless::relations::Relation;
2329    /// let relation: Relation = "samba [amd64]".parse().unwrap();
2330    /// assert_eq!(relation.architectures().unwrap().collect::<Vec<_>>(), vec!["amd64".to_string()]);
2331    /// ```
2332    pub fn architectures(&self) -> Option<impl Iterator<Item = String> + '_> {
2333        let architectures = self.0.children().find(|n| n.kind() == ARCHITECTURES)?;
2334
2335        Some(architectures.children_with_tokens().filter_map(|node| {
2336            let token = node.as_token()?;
2337            if token.kind() == IDENT {
2338                Some(token.text().to_string())
2339            } else {
2340                None
2341            }
2342        }))
2343    }
2344
2345    /// Returns an iterator over the build profiles for this relation
2346    ///
2347    /// # Example
2348    /// ```
2349    /// use debian_control::lossless::relations::{Relation};
2350    /// use debian_control::relations::{BuildProfile};
2351    /// let relation: Relation = "samba <!nocheck>".parse().unwrap();
2352    /// assert_eq!(relation.profiles().collect::<Vec<_>>(), vec![vec![BuildProfile::Disabled("nocheck".to_string())]]);
2353    /// ```
2354    pub fn profiles(&self) -> impl Iterator<Item = Vec<BuildProfile>> + '_ {
2355        let profiles = self.0.children().filter(|n| n.kind() == PROFILES);
2356
2357        profiles.map(|profile| {
2358            // iterate over nodes separated by whitespace tokens
2359            let mut ret = vec![];
2360            let mut current = vec![];
2361            for token in profile.children_with_tokens() {
2362                match token.kind() {
2363                    WHITESPACE | NEWLINE => {
2364                        if !current.is_empty() {
2365                            ret.push(current.join("").parse::<BuildProfile>().unwrap());
2366                            current = vec![];
2367                        }
2368                    }
2369                    L_ANGLE | R_ANGLE => {}
2370                    _ => {
2371                        current.push(token.to_string());
2372                    }
2373                }
2374            }
2375            if !current.is_empty() {
2376                ret.push(current.concat().parse().unwrap());
2377            }
2378            ret
2379        })
2380    }
2381
2382    /// Remove this relation
2383    ///
2384    /// # Example
2385    /// ```
2386    /// use debian_control::lossless::relations::{Relation,Entry};
2387    /// let mut entry: Entry = r"python3-dulwich (>= 0.19.0) | python3-urllib3 (<< 1.26.0)".parse().unwrap();
2388    /// let mut relation = entry.get_relation(0).unwrap();
2389    /// relation.remove();
2390    /// assert_eq!(entry.to_string(), "python3-urllib3 (<< 1.26.0)");
2391    /// ```
2392    pub fn remove(&mut self) {
2393        let is_first = !self
2394            .0
2395            .siblings(Direction::Prev)
2396            .skip(1)
2397            .any(|n| n.kind() == RELATION);
2398        if !is_first {
2399            // Not the first item in the list. Remove whitespace backwards to the previous
2400            // pipe, the pipe and any whitespace until the previous relation
2401            while let Some(n) = self.0.prev_sibling_or_token() {
2402                if n.kind() == WHITESPACE || n.kind() == NEWLINE {
2403                    n.detach();
2404                } else if n.kind() == PIPE {
2405                    n.detach();
2406                    break;
2407                } else {
2408                    break;
2409                }
2410            }
2411            while let Some(n) = self.0.prev_sibling_or_token() {
2412                if n.kind() == WHITESPACE || n.kind() == NEWLINE {
2413                    n.detach();
2414                } else {
2415                    break;
2416                }
2417            }
2418        } else {
2419            // First item in the list. Remove whitespace up to the pipe, the pipe and anything
2420            // before the next relation
2421            while let Some(n) = self.0.next_sibling_or_token() {
2422                if n.kind() == WHITESPACE || n.kind() == NEWLINE {
2423                    n.detach();
2424                } else if n.kind() == PIPE {
2425                    n.detach();
2426                    break;
2427                } else {
2428                    panic!("Unexpected node: {:?}", n);
2429                }
2430            }
2431
2432            while let Some(n) = self.0.next_sibling_or_token() {
2433                if n.kind() == WHITESPACE || n.kind() == NEWLINE {
2434                    n.detach();
2435                } else {
2436                    break;
2437                }
2438            }
2439        }
2440        // If this was the last relation in the entry, remove the entire entry
2441        if let Some(mut parent) = self.0.parent().and_then(Entry::cast) {
2442            if parent.is_empty() {
2443                parent.remove();
2444            } else {
2445                self.0.detach();
2446            }
2447        } else {
2448            self.0.detach();
2449        }
2450    }
2451
2452    /// Set the architectures for this relation
2453    ///
2454    /// # Example
2455    /// ```
2456    /// use debian_control::lossless::relations::Relation;
2457    /// let mut relation = Relation::simple("samba");
2458    /// relation.set_architectures(vec!["amd64", "i386"].into_iter());
2459    /// assert_eq!(relation.to_string(), "samba [amd64 i386]");
2460    /// ```
2461    pub fn set_architectures<'a>(&mut self, architectures: impl Iterator<Item = &'a str>) {
2462        let mut builder = GreenNodeBuilder::new();
2463        builder.start_node(ARCHITECTURES.into());
2464        builder.token(L_BRACKET.into(), "[");
2465        for (i, arch) in architectures.enumerate() {
2466            if i > 0 {
2467                builder.token(WHITESPACE.into(), " ");
2468            }
2469            builder.token(IDENT.into(), arch);
2470        }
2471        builder.token(R_BRACKET.into(), "]");
2472        builder.finish_node();
2473
2474        let node_architectures = self.0.children().find(|n| n.kind() == ARCHITECTURES);
2475        if let Some(node_architectures) = node_architectures {
2476            let new_root = SyntaxNode::new_root_mut(builder.finish());
2477            self.0.splice_children(
2478                node_architectures.index()..node_architectures.index() + 1,
2479                vec![new_root.into()],
2480            );
2481        } else {
2482            let profiles = self.0.children().find(|n| n.kind() == PROFILES);
2483            let idx = if let Some(profiles) = profiles {
2484                profiles.index()
2485            } else {
2486                self.0.children_with_tokens().count()
2487            };
2488            let new_root = SyntaxNode::new_root(self.0.green().splice_children(
2489                idx..idx,
2490                vec![
2491                    GreenToken::new(WHITESPACE.into(), " ").into(),
2492                    builder.finish().into(),
2493                ],
2494            ));
2495            if let Some(parent) = self.0.parent() {
2496                parent.splice_children(self.0.index()..self.0.index() + 1, vec![new_root.into()]);
2497                self.0 = parent
2498                    .children_with_tokens()
2499                    .nth(self.0.index())
2500                    .unwrap()
2501                    .clone()
2502                    .into_node()
2503                    .unwrap();
2504            } else {
2505                self.0 = new_root;
2506            }
2507        }
2508    }
2509
2510    /// Add a build profile to this relation
2511    ///
2512    /// # Example
2513    /// ```
2514    /// use debian_control::lossless::relations::Relation;
2515    /// use debian_control::relations::BuildProfile;
2516    /// let mut relation = Relation::simple("samba");
2517    /// relation.add_profile(&[BuildProfile::Disabled("nocheck".to_string())]);
2518    /// assert_eq!(relation.to_string(), "samba <!nocheck>");
2519    /// ```
2520    pub fn add_profile(&mut self, profile: &[BuildProfile]) {
2521        let mut builder = GreenNodeBuilder::new();
2522        builder.start_node(PROFILES.into());
2523        builder.token(L_ANGLE.into(), "<");
2524        for (i, profile) in profile.iter().enumerate() {
2525            if i > 0 {
2526                builder.token(WHITESPACE.into(), " ");
2527            }
2528            match profile {
2529                BuildProfile::Disabled(name) => {
2530                    builder.token(NOT.into(), "!");
2531                    builder.token(IDENT.into(), name.as_str());
2532                }
2533                BuildProfile::Enabled(name) => {
2534                    builder.token(IDENT.into(), name.as_str());
2535                }
2536            }
2537        }
2538        builder.token(R_ANGLE.into(), ">");
2539        builder.finish_node();
2540
2541        let node_profiles = self.0.children().find(|n| n.kind() == PROFILES);
2542        if let Some(node_profiles) = node_profiles {
2543            let new_root = SyntaxNode::new_root_mut(builder.finish());
2544            self.0.splice_children(
2545                node_profiles.index()..node_profiles.index() + 1,
2546                vec![new_root.into()],
2547            );
2548        } else {
2549            let idx = self.0.children_with_tokens().count();
2550            let new_root = SyntaxNode::new_root(self.0.green().splice_children(
2551                idx..idx,
2552                vec![
2553                    GreenToken::new(WHITESPACE.into(), " ").into(),
2554                    builder.finish().into(),
2555                ],
2556            ));
2557            if let Some(parent) = self.0.parent() {
2558                parent.splice_children(self.0.index()..self.0.index() + 1, vec![new_root.into()]);
2559                self.0 = parent
2560                    .children_with_tokens()
2561                    .nth(self.0.index())
2562                    .unwrap()
2563                    .clone()
2564                    .into_node()
2565                    .unwrap();
2566            } else {
2567                self.0 = new_root;
2568            }
2569        }
2570    }
2571
2572    /// Build a new relation
2573    pub fn build(name: &str) -> RelationBuilder {
2574        RelationBuilder::new(name)
2575    }
2576}
2577
2578/// A builder for creating a `Relation`
2579///
2580/// # Example
2581/// ```
2582/// use debian_control::lossless::relations::{Relation};
2583/// use debian_control::relations::VersionConstraint;
2584/// let relation = Relation::build("samba")
2585///    .version_constraint(VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap())
2586///    .archqual("any")
2587///    .architectures(vec!["amd64".to_string(), "i386".to_string()])
2588///    .build();
2589/// assert_eq!(relation.to_string(), "samba:any (>= 2.0) [amd64 i386]");
2590/// ```
2591pub struct RelationBuilder {
2592    name: String,
2593    version_constraint: Option<(VersionConstraint, Version)>,
2594    archqual: Option<String>,
2595    architectures: Option<Vec<String>>,
2596    profiles: Vec<Vec<BuildProfile>>,
2597}
2598
2599impl RelationBuilder {
2600    /// Create a new `RelationBuilder` with the given package name
2601    fn new(name: &str) -> Self {
2602        Self {
2603            name: name.to_string(),
2604            version_constraint: None,
2605            archqual: None,
2606            architectures: None,
2607            profiles: vec![],
2608        }
2609    }
2610
2611    /// Set the version constraint for this relation
2612    pub fn version_constraint(mut self, vc: VersionConstraint, version: Version) -> Self {
2613        self.version_constraint = Some((vc, version));
2614        self
2615    }
2616
2617    /// Set the architecture qualifier for this relation
2618    pub fn archqual(mut self, archqual: &str) -> Self {
2619        self.archqual = Some(archqual.to_string());
2620        self
2621    }
2622
2623    /// Set the architectures for this relation
2624    pub fn architectures(mut self, architectures: Vec<String>) -> Self {
2625        self.architectures = Some(architectures);
2626        self
2627    }
2628
2629    /// Set the build profiles for this relation
2630    pub fn profiles(mut self, profiles: Vec<Vec<BuildProfile>>) -> Self {
2631        self.profiles = profiles;
2632        self
2633    }
2634
2635    /// Add a build profile to this relation
2636    pub fn add_profile(mut self, profile: Vec<BuildProfile>) -> Self {
2637        self.profiles.push(profile);
2638        self
2639    }
2640
2641    /// Build the `Relation`
2642    pub fn build(self) -> Relation {
2643        let mut relation = Relation::new(&self.name, self.version_constraint);
2644        if let Some(archqual) = &self.archqual {
2645            relation.set_archqual(archqual);
2646        }
2647        if let Some(architectures) = &self.architectures {
2648            relation.set_architectures(architectures.iter().map(|s| s.as_str()));
2649        }
2650        for profile in &self.profiles {
2651            relation.add_profile(profile);
2652        }
2653        relation
2654    }
2655}
2656
2657impl PartialOrd for Relation {
2658    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
2659        // Compare by name first, then by version
2660        let name_cmp = self.name().cmp(&other.name());
2661        if name_cmp != std::cmp::Ordering::Equal {
2662            return Some(name_cmp);
2663        }
2664
2665        let self_version = self.version();
2666        let other_version = other.version();
2667
2668        match (self_version, other_version) {
2669            (Some((self_vc, self_version)), Some((other_vc, other_version))) => {
2670                let vc_cmp = self_vc.cmp(&other_vc);
2671                if vc_cmp != std::cmp::Ordering::Equal {
2672                    return Some(vc_cmp);
2673                }
2674
2675                Some(self_version.cmp(&other_version))
2676            }
2677            (Some(_), None) => Some(std::cmp::Ordering::Greater),
2678            (None, Some(_)) => Some(std::cmp::Ordering::Less),
2679            (None, None) => Some(std::cmp::Ordering::Equal),
2680        }
2681    }
2682}
2683
2684impl Eq for Relation {}
2685
2686impl Ord for Relation {
2687    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
2688        self.partial_cmp(other).unwrap()
2689    }
2690}
2691
2692impl std::str::FromStr for Relations {
2693    type Err = String;
2694
2695    fn from_str(s: &str) -> Result<Self, Self::Err> {
2696        let parse = parse(s, false);
2697        if parse.errors.is_empty() {
2698            Ok(parse.root_mut())
2699        } else {
2700            Err(parse.errors.join("\n"))
2701        }
2702    }
2703}
2704
2705impl std::str::FromStr for Entry {
2706    type Err = String;
2707
2708    fn from_str(s: &str) -> Result<Self, Self::Err> {
2709        let root: Relations = s.parse()?;
2710
2711        let mut entries = root.entries();
2712        let entry = if let Some(entry) = entries.next() {
2713            entry
2714        } else {
2715            return Err("No entry found".to_string());
2716        };
2717
2718        if entries.next().is_some() {
2719            return Err("Multiple entries found".to_string());
2720        }
2721
2722        Ok(entry)
2723    }
2724}
2725
2726impl std::str::FromStr for Relation {
2727    type Err = String;
2728
2729    fn from_str(s: &str) -> Result<Self, Self::Err> {
2730        let entry: Entry = s.parse()?;
2731
2732        let mut relations = entry.relations();
2733        let relation = if let Some(relation) = relations.next() {
2734            relation
2735        } else {
2736            return Err("No relation found".to_string());
2737        };
2738
2739        if relations.next().is_some() {
2740            return Err("Multiple relations found".to_string());
2741        }
2742
2743        Ok(relation)
2744    }
2745}
2746
2747impl From<crate::lossy::Relation> for Relation {
2748    fn from(relation: crate::lossy::Relation) -> Self {
2749        let mut builder = Relation::build(&relation.name);
2750
2751        if let Some((vc, version)) = relation.version {
2752            builder = builder.version_constraint(vc, version);
2753        }
2754
2755        if let Some(archqual) = relation.archqual {
2756            builder = builder.archqual(&archqual);
2757        }
2758
2759        if let Some(architectures) = relation.architectures {
2760            builder = builder.architectures(architectures);
2761        }
2762
2763        builder = builder.profiles(relation.profiles);
2764
2765        builder.build()
2766    }
2767}
2768
2769impl From<Relation> for crate::lossy::Relation {
2770    fn from(relation: Relation) -> Self {
2771        crate::lossy::Relation {
2772            name: relation.name(),
2773            version: relation.version(),
2774            archqual: relation.archqual(),
2775            architectures: relation.architectures().map(|a| a.collect()),
2776            profiles: relation.profiles().collect(),
2777        }
2778    }
2779}
2780
2781impl From<Entry> for Vec<crate::lossy::Relation> {
2782    fn from(entry: Entry) -> Self {
2783        entry.relations().map(|r| r.into()).collect()
2784    }
2785}
2786
2787impl From<Vec<crate::lossy::Relation>> for Entry {
2788    fn from(relations: Vec<crate::lossy::Relation>) -> Self {
2789        let relations: Vec<Relation> = relations.into_iter().map(|r| r.into()).collect();
2790        Entry::from(relations)
2791    }
2792}
2793
2794#[cfg(test)]
2795mod tests {
2796    use super::*;
2797
2798    #[test]
2799    fn test_parse() {
2800        let input = "python3-dulwich";
2801        let parsed: Relations = input.parse().unwrap();
2802        assert_eq!(parsed.to_string(), input);
2803        assert_eq!(parsed.entries().count(), 1);
2804        let entry = parsed.entries().next().unwrap();
2805        assert_eq!(entry.to_string(), "python3-dulwich");
2806        assert_eq!(entry.relations().count(), 1);
2807        let relation = entry.relations().next().unwrap();
2808        assert_eq!(relation.to_string(), "python3-dulwich");
2809        assert_eq!(relation.version(), None);
2810
2811        let input = "python3-dulwich (>= 0.20.21)";
2812        let parsed: Relations = input.parse().unwrap();
2813        assert_eq!(parsed.to_string(), input);
2814        assert_eq!(parsed.entries().count(), 1);
2815        let entry = parsed.entries().next().unwrap();
2816        assert_eq!(entry.to_string(), "python3-dulwich (>= 0.20.21)");
2817        assert_eq!(entry.relations().count(), 1);
2818        let relation = entry.relations().next().unwrap();
2819        assert_eq!(relation.to_string(), "python3-dulwich (>= 0.20.21)");
2820        assert_eq!(
2821            relation.version(),
2822            Some((
2823                VersionConstraint::GreaterThanEqual,
2824                "0.20.21".parse().unwrap()
2825            ))
2826        );
2827    }
2828
2829    #[test]
2830    fn test_multiple() {
2831        let input = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)";
2832        let parsed: Relations = input.parse().unwrap();
2833        assert_eq!(parsed.to_string(), input);
2834        assert_eq!(parsed.entries().count(), 2);
2835        let entry = parsed.entries().next().unwrap();
2836        assert_eq!(entry.to_string(), "python3-dulwich (>= 0.20.21)");
2837        assert_eq!(entry.relations().count(), 1);
2838        let relation = entry.relations().next().unwrap();
2839        assert_eq!(relation.to_string(), "python3-dulwich (>= 0.20.21)");
2840        assert_eq!(
2841            relation.version(),
2842            Some((
2843                VersionConstraint::GreaterThanEqual,
2844                "0.20.21".parse().unwrap()
2845            ))
2846        );
2847        let entry = parsed.entries().nth(1).unwrap();
2848        assert_eq!(entry.to_string(), "python3-dulwich (<< 0.21)");
2849        assert_eq!(entry.relations().count(), 1);
2850        let relation = entry.relations().next().unwrap();
2851        assert_eq!(relation.to_string(), "python3-dulwich (<< 0.21)");
2852        assert_eq!(
2853            relation.version(),
2854            Some((VersionConstraint::LessThan, "0.21".parse().unwrap()))
2855        );
2856    }
2857
2858    #[test]
2859    fn test_architectures() {
2860        let input = "python3-dulwich [amd64 arm64 armhf i386 mips mips64el mipsel ppc64el s390x]";
2861        let parsed: Relations = input.parse().unwrap();
2862        assert_eq!(parsed.to_string(), input);
2863        assert_eq!(parsed.entries().count(), 1);
2864        let entry = parsed.entries().next().unwrap();
2865        assert_eq!(
2866            entry.to_string(),
2867            "python3-dulwich [amd64 arm64 armhf i386 mips mips64el mipsel ppc64el s390x]"
2868        );
2869        assert_eq!(entry.relations().count(), 1);
2870        let relation = entry.relations().next().unwrap();
2871        assert_eq!(
2872            relation.to_string(),
2873            "python3-dulwich [amd64 arm64 armhf i386 mips mips64el mipsel ppc64el s390x]"
2874        );
2875        assert_eq!(relation.version(), None);
2876        assert_eq!(
2877            relation.architectures().unwrap().collect::<Vec<_>>(),
2878            vec![
2879                "amd64", "arm64", "armhf", "i386", "mips", "mips64el", "mipsel", "ppc64el", "s390x"
2880            ]
2881            .into_iter()
2882            .map(|s| s.to_string())
2883            .collect::<Vec<_>>()
2884        );
2885    }
2886
2887    #[test]
2888    fn test_profiles() {
2889        let input = "foo (>= 1.0) [i386 arm] <!nocheck> <!cross>, bar";
2890        let parsed: Relations = input.parse().unwrap();
2891        assert_eq!(parsed.to_string(), input);
2892        assert_eq!(parsed.entries().count(), 2);
2893        let entry = parsed.entries().next().unwrap();
2894        assert_eq!(
2895            entry.to_string(),
2896            "foo (>= 1.0) [i386 arm] <!nocheck> <!cross>"
2897        );
2898        assert_eq!(entry.relations().count(), 1);
2899        let relation = entry.relations().next().unwrap();
2900        assert_eq!(
2901            relation.to_string(),
2902            "foo (>= 1.0) [i386 arm] <!nocheck> <!cross>"
2903        );
2904        assert_eq!(
2905            relation.version(),
2906            Some((VersionConstraint::GreaterThanEqual, "1.0".parse().unwrap()))
2907        );
2908        assert_eq!(
2909            relation.architectures().unwrap().collect::<Vec<_>>(),
2910            vec!["i386", "arm"]
2911                .into_iter()
2912                .map(|s| s.to_string())
2913                .collect::<Vec<_>>()
2914        );
2915        assert_eq!(
2916            relation.profiles().collect::<Vec<_>>(),
2917            vec![
2918                vec![BuildProfile::Disabled("nocheck".to_string())],
2919                vec![BuildProfile::Disabled("cross".to_string())]
2920            ]
2921        );
2922    }
2923
2924    #[test]
2925    fn test_substvar() {
2926        let input = "${shlibs:Depends}";
2927
2928        let (parsed, errors) = Relations::parse_relaxed(input, true);
2929        assert_eq!(errors, Vec::<String>::new());
2930        assert_eq!(parsed.to_string(), input);
2931        assert_eq!(parsed.entries().count(), 0);
2932
2933        assert_eq!(
2934            parsed.substvars().collect::<Vec<_>>(),
2935            vec!["${shlibs:Depends}"]
2936        );
2937    }
2938
2939    #[test]
2940    fn test_new() {
2941        let r = Relation::new(
2942            "samba",
2943            Some((VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap())),
2944        );
2945
2946        assert_eq!(r.to_string(), "samba (>= 2.0)");
2947    }
2948
2949    #[test]
2950    fn test_drop_constraint() {
2951        let mut r = Relation::new(
2952            "samba",
2953            Some((VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap())),
2954        );
2955
2956        r.drop_constraint();
2957
2958        assert_eq!(r.to_string(), "samba");
2959    }
2960
2961    #[test]
2962    fn test_simple() {
2963        let r = Relation::simple("samba");
2964
2965        assert_eq!(r.to_string(), "samba");
2966    }
2967
2968    #[test]
2969    fn test_remove_first_entry() {
2970        let mut rels: Relations = r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"#
2971            .parse()
2972            .unwrap();
2973        let removed = rels.remove_entry(0);
2974        assert_eq!(removed.to_string(), "python3-dulwich (>= 0.20.21)");
2975        assert_eq!(rels.to_string(), "python3-dulwich (<< 0.21)");
2976    }
2977
2978    #[test]
2979    fn test_remove_last_entry() {
2980        let mut rels: Relations = r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"#
2981            .parse()
2982            .unwrap();
2983        rels.remove_entry(1);
2984        assert_eq!(rels.to_string(), "python3-dulwich (>= 0.20.21)");
2985    }
2986
2987    #[test]
2988    fn test_remove_middle() {
2989        let mut rels: Relations =
2990            r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21), python3-dulwich (<< 0.22)"#
2991                .parse()
2992                .unwrap();
2993        rels.remove_entry(1);
2994        assert_eq!(
2995            rels.to_string(),
2996            "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.22)"
2997        );
2998    }
2999
3000    #[test]
3001    fn test_remove_added() {
3002        let mut rels: Relations = r#"python3-dulwich (>= 0.20.21)"#.parse().unwrap();
3003        let entry = Entry::from(vec![Relation::simple("python3-dulwich")]);
3004        rels.push(entry);
3005        rels.remove_entry(1);
3006        assert_eq!(rels.to_string(), "python3-dulwich (>= 0.20.21)");
3007    }
3008
3009    #[test]
3010    fn test_push() {
3011        let mut rels: Relations = r#"python3-dulwich (>= 0.20.21)"#.parse().unwrap();
3012        let entry = Entry::from(vec![Relation::simple("python3-dulwich")]);
3013        rels.push(entry);
3014        assert_eq!(
3015            rels.to_string(),
3016            "python3-dulwich (>= 0.20.21), python3-dulwich"
3017        );
3018    }
3019
3020    #[test]
3021    fn test_insert_with_custom_separator() {
3022        let mut rels: Relations = "python3".parse().unwrap();
3023        let entry = Entry::from(vec![Relation::simple("debhelper")]);
3024        rels.insert_with_separator(1, entry, Some("\n "));
3025        assert_eq!(rels.to_string(), "python3,\n debhelper");
3026    }
3027
3028    #[test]
3029    fn test_insert_with_wrap_and_sort_separator() {
3030        let mut rels: Relations = "python3".parse().unwrap();
3031        let entry = Entry::from(vec![Relation::simple("rustc")]);
3032        // Simulate wrap-and-sort -a style with field name "Depends: " (9 chars)
3033        rels.insert_with_separator(1, entry, Some("\n         "));
3034        assert_eq!(rels.to_string(), "python3,\n         rustc");
3035    }
3036
3037    #[test]
3038    fn test_push_from_empty() {
3039        let mut rels: Relations = "".parse().unwrap();
3040        let entry = Entry::from(vec![Relation::simple("python3-dulwich")]);
3041        rels.push(entry);
3042        assert_eq!(rels.to_string(), "python3-dulwich");
3043    }
3044
3045    #[test]
3046    fn test_insert() {
3047        let mut rels: Relations = r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"#
3048            .parse()
3049            .unwrap();
3050        let entry = Entry::from(vec![Relation::simple("python3-dulwich")]);
3051        rels.insert(1, entry);
3052        assert_eq!(
3053            rels.to_string(),
3054            "python3-dulwich (>= 0.20.21), python3-dulwich, python3-dulwich (<< 0.21)"
3055        );
3056    }
3057
3058    #[test]
3059    fn test_insert_at_start() {
3060        let mut rels: Relations = r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"#
3061            .parse()
3062            .unwrap();
3063        let entry = Entry::from(vec![Relation::simple("python3-dulwich")]);
3064        rels.insert(0, entry);
3065        assert_eq!(
3066            rels.to_string(),
3067            "python3-dulwich, python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"
3068        );
3069    }
3070
3071    #[test]
3072    fn test_insert_after_error() {
3073        let (mut rels, errors) = Relations::parse_relaxed("@foo@, debhelper (>= 1.0)", false);
3074        assert_eq!(
3075            errors,
3076            vec![
3077                "expected $ or identifier but got ERROR",
3078                "expected comma or end of file but got Some(IDENT)",
3079                "expected $ or identifier but got ERROR"
3080            ]
3081        );
3082        let entry = Entry::from(vec![Relation::simple("bar")]);
3083        rels.push(entry);
3084        assert_eq!(rels.to_string(), "@foo@, debhelper (>= 1.0), bar");
3085    }
3086
3087    #[test]
3088    fn test_insert_before_error() {
3089        let (mut rels, errors) = Relations::parse_relaxed("debhelper (>= 1.0), @foo@, bla", false);
3090        assert_eq!(
3091            errors,
3092            vec![
3093                "expected $ or identifier but got ERROR",
3094                "expected comma or end of file but got Some(IDENT)",
3095                "expected $ or identifier but got ERROR"
3096            ]
3097        );
3098        let entry = Entry::from(vec![Relation::simple("bar")]);
3099        rels.insert(0, entry);
3100        assert_eq!(rels.to_string(), "bar, debhelper (>= 1.0), @foo@, bla");
3101    }
3102
3103    #[test]
3104    fn test_replace() {
3105        let mut rels: Relations = r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"#
3106            .parse()
3107            .unwrap();
3108        let entry = Entry::from(vec![Relation::simple("python3-dulwich")]);
3109        rels.replace(1, entry);
3110        assert_eq!(
3111            rels.to_string(),
3112            "python3-dulwich (>= 0.20.21), python3-dulwich"
3113        );
3114    }
3115
3116    #[test]
3117    fn test_relation_from_entries() {
3118        let entries = vec![
3119            Entry::from(vec![Relation::simple("python3-dulwich")]),
3120            Entry::from(vec![Relation::simple("python3-breezy")]),
3121        ];
3122        let rels: Relations = entries.into();
3123        assert_eq!(rels.entries().count(), 2);
3124        assert_eq!(rels.to_string(), "python3-dulwich, python3-breezy");
3125    }
3126
3127    #[test]
3128    fn test_entry_from_relations() {
3129        let relations = vec![
3130            Relation::simple("python3-dulwich"),
3131            Relation::simple("python3-breezy"),
3132        ];
3133        let entry: Entry = relations.into();
3134        assert_eq!(entry.relations().count(), 2);
3135        assert_eq!(entry.to_string(), "python3-dulwich | python3-breezy");
3136    }
3137
3138    #[test]
3139    fn test_parse_entry() {
3140        let parsed: Entry = "python3-dulwich (>= 0.20.21) | bar".parse().unwrap();
3141        assert_eq!(parsed.to_string(), "python3-dulwich (>= 0.20.21) | bar");
3142        assert_eq!(parsed.relations().count(), 2);
3143
3144        assert_eq!(
3145            "foo, bar".parse::<Entry>().unwrap_err(),
3146            "Multiple entries found"
3147        );
3148        assert_eq!("".parse::<Entry>().unwrap_err(), "No entry found");
3149    }
3150
3151    #[test]
3152    fn test_parse_relation() {
3153        let parsed: Relation = "python3-dulwich (>= 0.20.21)".parse().unwrap();
3154        assert_eq!(parsed.to_string(), "python3-dulwich (>= 0.20.21)");
3155        assert_eq!(
3156            parsed.version(),
3157            Some((
3158                VersionConstraint::GreaterThanEqual,
3159                "0.20.21".parse().unwrap()
3160            ))
3161        );
3162        assert_eq!(
3163            "foo | bar".parse::<Relation>().unwrap_err(),
3164            "Multiple relations found"
3165        );
3166        assert_eq!("".parse::<Relation>().unwrap_err(), "No entry found");
3167    }
3168
3169    #[test]
3170    fn test_special() {
3171        let parsed: Relation = "librust-breezyshim+dirty-tracker-dev:amd64 (>= 0.1.138-~~)"
3172            .parse()
3173            .unwrap();
3174        assert_eq!(
3175            parsed.to_string(),
3176            "librust-breezyshim+dirty-tracker-dev:amd64 (>= 0.1.138-~~)"
3177        );
3178        assert_eq!(
3179            parsed.version(),
3180            Some((
3181                VersionConstraint::GreaterThanEqual,
3182                "0.1.138-~~".parse().unwrap()
3183            ))
3184        );
3185        assert_eq!(parsed.archqual(), Some("amd64".to_string()));
3186        assert_eq!(parsed.name(), "librust-breezyshim+dirty-tracker-dev");
3187    }
3188
3189    #[test]
3190    fn test_relations_satisfied_by() {
3191        let rels: Relations = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"
3192            .parse()
3193            .unwrap();
3194        let satisfied = |name: &str| -> Option<debversion::Version> {
3195            match name {
3196                "python3-dulwich" => Some("0.20.21".parse().unwrap()),
3197                _ => None,
3198            }
3199        };
3200        assert!(rels.satisfied_by(satisfied));
3201
3202        let satisfied = |name: &str| match name {
3203            "python3-dulwich" => Some("0.21".parse().unwrap()),
3204            _ => None,
3205        };
3206        assert!(!rels.satisfied_by(satisfied));
3207
3208        let satisfied = |name: &str| match name {
3209            "python3-dulwich" => Some("0.20.20".parse().unwrap()),
3210            _ => None,
3211        };
3212        assert!(!rels.satisfied_by(satisfied));
3213    }
3214
3215    #[test]
3216    fn test_entry_satisfied_by() {
3217        let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
3218            .parse()
3219            .unwrap();
3220        let satisfied = |name: &str| -> Option<debversion::Version> {
3221            match name {
3222                "python3-dulwich" => Some("0.20.21".parse().unwrap()),
3223                _ => None,
3224            }
3225        };
3226        assert!(entry.satisfied_by(satisfied));
3227        let satisfied = |name: &str| -> Option<debversion::Version> {
3228            match name {
3229                "python3-dulwich" => Some("0.18".parse().unwrap()),
3230                _ => None,
3231            }
3232        };
3233        assert!(!entry.satisfied_by(satisfied));
3234    }
3235
3236    #[test]
3237    fn test_wrap_and_sort_relation() {
3238        let relation: Relation = "   python3-dulwich   (>= 11) [  amd64 ] <  lala>"
3239            .parse()
3240            .unwrap();
3241
3242        let wrapped = relation.wrap_and_sort();
3243
3244        assert_eq!(
3245            wrapped.to_string(),
3246            "python3-dulwich (>= 11) [amd64] <lala>"
3247        );
3248    }
3249
3250    #[test]
3251    fn test_wrap_and_sort_relations() {
3252        let entry: Relations =
3253            "python3-dulwich (>= 0.20.21)   | bar, \n\n\n\npython3-dulwich (<< 0.21)"
3254                .parse()
3255                .unwrap();
3256
3257        let wrapped = entry.wrap_and_sort();
3258
3259        assert_eq!(
3260            wrapped.to_string(),
3261            "bar | python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"
3262        );
3263    }
3264
3265    #[cfg(feature = "serde")]
3266    #[test]
3267    fn test_serialize_relations() {
3268        let relations: Relations = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"
3269            .parse()
3270            .unwrap();
3271        let serialized = serde_json::to_string(&relations).unwrap();
3272        assert_eq!(
3273            serialized,
3274            r#""python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)""#
3275        );
3276    }
3277
3278    #[cfg(feature = "serde")]
3279    #[test]
3280    fn test_deserialize_relations() {
3281        let relations: Relations = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"
3282            .parse()
3283            .unwrap();
3284        let serialized = serde_json::to_string(&relations).unwrap();
3285        let deserialized: Relations = serde_json::from_str(&serialized).unwrap();
3286        assert_eq!(deserialized.to_string(), relations.to_string());
3287    }
3288
3289    #[cfg(feature = "serde")]
3290    #[test]
3291    fn test_serialize_relation() {
3292        let relation: Relation = "python3-dulwich (>= 0.20.21)".parse().unwrap();
3293        let serialized = serde_json::to_string(&relation).unwrap();
3294        assert_eq!(serialized, r#""python3-dulwich (>= 0.20.21)""#);
3295    }
3296
3297    #[cfg(feature = "serde")]
3298    #[test]
3299    fn test_deserialize_relation() {
3300        let relation: Relation = "python3-dulwich (>= 0.20.21)".parse().unwrap();
3301        let serialized = serde_json::to_string(&relation).unwrap();
3302        let deserialized: Relation = serde_json::from_str(&serialized).unwrap();
3303        assert_eq!(deserialized.to_string(), relation.to_string());
3304    }
3305
3306    #[cfg(feature = "serde")]
3307    #[test]
3308    fn test_serialize_entry() {
3309        let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
3310            .parse()
3311            .unwrap();
3312        let serialized = serde_json::to_string(&entry).unwrap();
3313        assert_eq!(
3314            serialized,
3315            r#""python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)""#
3316        );
3317    }
3318
3319    #[cfg(feature = "serde")]
3320    #[test]
3321    fn test_deserialize_entry() {
3322        let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
3323            .parse()
3324            .unwrap();
3325        let serialized = serde_json::to_string(&entry).unwrap();
3326        let deserialized: Entry = serde_json::from_str(&serialized).unwrap();
3327        assert_eq!(deserialized.to_string(), entry.to_string());
3328    }
3329
3330    #[test]
3331    fn test_remove_first_relation() {
3332        let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
3333            .parse()
3334            .unwrap();
3335        let mut rel = entry.relations().next().unwrap();
3336        rel.remove();
3337        assert_eq!(entry.to_string(), "python3-dulwich (<< 0.18)");
3338    }
3339
3340    #[test]
3341    fn test_remove_last_relation() {
3342        let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
3343            .parse()
3344            .unwrap();
3345        let mut rel = entry.relations().nth(1).unwrap();
3346        rel.remove();
3347        assert_eq!(entry.to_string(), "python3-dulwich (>= 0.20.21)");
3348    }
3349
3350    #[test]
3351    fn test_remove_only_relation() {
3352        let entry: Entry = "python3-dulwich (>= 0.20.21)".parse().unwrap();
3353        let mut rel = entry.relations().next().unwrap();
3354        rel.remove();
3355        assert_eq!(entry.to_string(), "");
3356    }
3357
3358    #[test]
3359    fn test_relations_is_empty() {
3360        let entry: Relations = "python3-dulwich (>= 0.20.21)".parse().unwrap();
3361        assert!(!entry.is_empty());
3362        assert_eq!(1, entry.len());
3363        let mut rel = entry.entries().next().unwrap();
3364        rel.remove();
3365        assert!(entry.is_empty());
3366        assert_eq!(0, entry.len());
3367    }
3368
3369    #[test]
3370    fn test_entry_is_empty() {
3371        let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
3372            .parse()
3373            .unwrap();
3374        assert!(!entry.is_empty());
3375        assert_eq!(2, entry.len());
3376        let mut rel = entry.relations().next().unwrap();
3377        rel.remove();
3378        assert!(!entry.is_empty());
3379        assert_eq!(1, entry.len());
3380        let mut rel = entry.relations().next().unwrap();
3381        rel.remove();
3382        assert!(entry.is_empty());
3383        assert_eq!(0, entry.len());
3384    }
3385
3386    #[test]
3387    fn test_relation_set_version() {
3388        let mut rel: Relation = "samba".parse().unwrap();
3389        rel.set_version(None);
3390        assert_eq!("samba", rel.to_string());
3391        rel.set_version(Some((
3392            VersionConstraint::GreaterThanEqual,
3393            "2.0".parse().unwrap(),
3394        )));
3395        assert_eq!("samba (>= 2.0)", rel.to_string());
3396        rel.set_version(None);
3397        assert_eq!("samba", rel.to_string());
3398        rel.set_version(Some((
3399            VersionConstraint::GreaterThanEqual,
3400            "2.0".parse().unwrap(),
3401        )));
3402        rel.set_version(Some((
3403            VersionConstraint::GreaterThanEqual,
3404            "1.1".parse().unwrap(),
3405        )));
3406        assert_eq!("samba (>= 1.1)", rel.to_string());
3407    }
3408
3409    #[test]
3410    fn test_replace_relation() {
3411        let mut entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
3412            .parse()
3413            .unwrap();
3414        let new_rel = Relation::simple("python3-breezy");
3415        entry.replace(0, new_rel);
3416        assert_eq!(
3417            entry.to_string(),
3418            "python3-breezy | python3-dulwich (<< 0.18)"
3419        );
3420    }
3421
3422    #[test]
3423    fn test_entry_push_relation() {
3424        let relations: Relations = "python3-dulwich (>= 0.20.21)".parse().unwrap();
3425        let new_rel = Relation::simple("python3-breezy");
3426        let mut entry = relations.entries().next().unwrap();
3427        entry.push(new_rel);
3428        assert_eq!(
3429            entry.to_string(),
3430            "python3-dulwich (>= 0.20.21) | python3-breezy"
3431        );
3432        assert_eq!(
3433            relations.to_string(),
3434            "python3-dulwich (>= 0.20.21) | python3-breezy"
3435        );
3436    }
3437
3438    #[test]
3439    fn test_relations_remove_empty_entry() {
3440        let (mut relations, errors) = Relations::parse_relaxed("foo, , bar, ", false);
3441        assert_eq!(errors, Vec::<String>::new());
3442        assert_eq!(relations.to_string(), "foo, , bar, ");
3443        assert_eq!(relations.len(), 2);
3444        assert_eq!(
3445            relations.entries().next().unwrap().to_string(),
3446            "foo".to_string()
3447        );
3448        assert_eq!(
3449            relations.entries().nth(1).unwrap().to_string(),
3450            "bar".to_string()
3451        );
3452        relations.remove_entry(1);
3453        assert_eq!(relations.to_string(), "foo, , ");
3454    }
3455
3456    #[test]
3457    fn test_entry_remove_relation() {
3458        let entry: Entry = "python3-dulwich | samba".parse().unwrap();
3459        let removed = entry.remove_relation(0);
3460        assert_eq!(removed.to_string(), "python3-dulwich");
3461        assert_eq!(entry.to_string(), "samba");
3462    }
3463
3464    #[test]
3465    fn test_wrap_and_sort_removes_empty_entries() {
3466        let relations: Relations = "foo, , bar, ".parse().unwrap();
3467        let wrapped = relations.wrap_and_sort();
3468        assert_eq!(wrapped.to_string(), "bar, foo");
3469    }
3470
3471    #[test]
3472    fn test_set_archqual() {
3473        let entry: Entry = "python3-dulwich | samba".parse().unwrap();
3474        let mut rel = entry.relations().next().unwrap();
3475        rel.set_archqual("amd64");
3476        assert_eq!(rel.to_string(), "python3-dulwich:amd64");
3477        assert_eq!(rel.archqual(), Some("amd64".to_string()));
3478        assert_eq!(entry.to_string(), "python3-dulwich:amd64 | samba");
3479        rel.set_archqual("i386");
3480        assert_eq!(rel.to_string(), "python3-dulwich:i386");
3481        assert_eq!(rel.archqual(), Some("i386".to_string()));
3482        assert_eq!(entry.to_string(), "python3-dulwich:i386 | samba");
3483    }
3484
3485    #[test]
3486    fn test_set_architectures() {
3487        let mut relation = Relation::simple("samba");
3488        relation.set_architectures(vec!["amd64", "i386"].into_iter());
3489        assert_eq!(relation.to_string(), "samba [amd64 i386]");
3490    }
3491
3492    #[test]
3493    fn test_relation_builder_no_architectures() {
3494        // Test that building a relation without architectures doesn't add empty brackets
3495        let relation = Relation::build("debhelper").build();
3496        assert_eq!(relation.to_string(), "debhelper");
3497    }
3498
3499    #[test]
3500    fn test_relation_builder_with_architectures() {
3501        // Test that building a relation with architectures works correctly
3502        let relation = Relation::build("samba")
3503            .architectures(vec!["amd64".to_string(), "i386".to_string()])
3504            .build();
3505        assert_eq!(relation.to_string(), "samba [amd64 i386]");
3506    }
3507
3508    #[test]
3509    fn test_ensure_minimum_version_add_new() {
3510        let mut relations: Relations = "python3".parse().unwrap();
3511        relations.ensure_minimum_version("debhelper", &"12".parse().unwrap());
3512        assert_eq!(relations.to_string(), "python3, debhelper (>= 12)");
3513    }
3514
3515    #[test]
3516    fn test_ensure_minimum_version_update() {
3517        let mut relations: Relations = "debhelper (>= 9)".parse().unwrap();
3518        relations.ensure_minimum_version("debhelper", &"12".parse().unwrap());
3519        assert_eq!(relations.to_string(), "debhelper (>= 12)");
3520    }
3521
3522    #[test]
3523    fn test_ensure_minimum_version_no_change() {
3524        let mut relations: Relations = "debhelper (>= 13)".parse().unwrap();
3525        relations.ensure_minimum_version("debhelper", &"12".parse().unwrap());
3526        assert_eq!(relations.to_string(), "debhelper (>= 13)");
3527    }
3528
3529    #[test]
3530    fn test_ensure_minimum_version_no_version() {
3531        let mut relations: Relations = "debhelper".parse().unwrap();
3532        relations.ensure_minimum_version("debhelper", &"12".parse().unwrap());
3533        assert_eq!(relations.to_string(), "debhelper (>= 12)");
3534    }
3535
3536    #[test]
3537    fn test_ensure_exact_version_add_new() {
3538        let mut relations: Relations = "python3".parse().unwrap();
3539        relations.ensure_exact_version("debhelper", &"12".parse().unwrap());
3540        assert_eq!(relations.to_string(), "python3, debhelper (= 12)");
3541    }
3542
3543    #[test]
3544    fn test_ensure_exact_version_update() {
3545        let mut relations: Relations = "debhelper (>= 9)".parse().unwrap();
3546        relations.ensure_exact_version("debhelper", &"12".parse().unwrap());
3547        assert_eq!(relations.to_string(), "debhelper (= 12)");
3548    }
3549
3550    #[test]
3551    fn test_ensure_exact_version_no_change() {
3552        let mut relations: Relations = "debhelper (= 12)".parse().unwrap();
3553        relations.ensure_exact_version("debhelper", &"12".parse().unwrap());
3554        assert_eq!(relations.to_string(), "debhelper (= 12)");
3555    }
3556
3557    #[test]
3558    fn test_ensure_some_version_add_new() {
3559        let mut relations: Relations = "python3".parse().unwrap();
3560        relations.ensure_some_version("debhelper");
3561        assert_eq!(relations.to_string(), "python3, debhelper");
3562    }
3563
3564    #[test]
3565    fn test_ensure_some_version_exists_with_version() {
3566        let mut relations: Relations = "debhelper (>= 12)".parse().unwrap();
3567        relations.ensure_some_version("debhelper");
3568        assert_eq!(relations.to_string(), "debhelper (>= 12)");
3569    }
3570
3571    #[test]
3572    fn test_ensure_some_version_exists_no_version() {
3573        let mut relations: Relations = "debhelper".parse().unwrap();
3574        relations.ensure_some_version("debhelper");
3575        assert_eq!(relations.to_string(), "debhelper");
3576    }
3577
3578    #[test]
3579    fn test_ensure_substvar() {
3580        let mut relations: Relations = "python3".parse().unwrap();
3581        relations.ensure_substvar("${misc:Depends}").unwrap();
3582        assert_eq!(relations.to_string(), "python3, ${misc:Depends}");
3583    }
3584
3585    #[test]
3586    fn test_ensure_substvar_already_exists() {
3587        let (mut relations, _) = Relations::parse_relaxed("python3, ${misc:Depends}", true);
3588        relations.ensure_substvar("${misc:Depends}").unwrap();
3589        assert_eq!(relations.to_string(), "python3, ${misc:Depends}");
3590    }
3591
3592    #[test]
3593    fn test_ensure_substvar_empty_relations() {
3594        let mut relations: Relations = Relations::new();
3595        relations.ensure_substvar("${misc:Depends}").unwrap();
3596        assert_eq!(relations.to_string(), "${misc:Depends}");
3597    }
3598
3599    #[test]
3600    fn test_ensure_substvar_preserves_whitespace() {
3601        // Test with non-standard whitespace (multiple spaces)
3602        let (mut relations, _) = Relations::parse_relaxed("python3,  rustc", false);
3603        relations.ensure_substvar("${misc:Depends}").unwrap();
3604        // Should preserve the double-space pattern
3605        assert_eq!(relations.to_string(), "python3,  rustc,  ${misc:Depends}");
3606    }
3607
3608    #[test]
3609    fn test_filter_entries_basic() {
3610        let mut relations: Relations = "python3, debhelper, rustc".parse().unwrap();
3611        relations.filter_entries(|entry| entry.relations().any(|r| r.name().starts_with("python")));
3612        assert_eq!(relations.to_string(), "python3");
3613    }
3614
3615    #[test]
3616    fn test_filter_entries_keep_all() {
3617        let mut relations: Relations = "python3, debhelper".parse().unwrap();
3618        relations.filter_entries(|_| true);
3619        assert_eq!(relations.to_string(), "python3, debhelper");
3620    }
3621
3622    #[test]
3623    fn test_filter_entries_remove_all() {
3624        let mut relations: Relations = "python3, debhelper".parse().unwrap();
3625        relations.filter_entries(|_| false);
3626        assert_eq!(relations.to_string(), "");
3627    }
3628
3629    #[test]
3630    fn test_filter_entries_keep_middle() {
3631        let mut relations: Relations = "aaa, bbb, ccc".parse().unwrap();
3632        relations.filter_entries(|entry| entry.relations().any(|r| r.name() == "bbb"));
3633        assert_eq!(relations.to_string(), "bbb");
3634    }
3635
3636    // Tests for new convenience methods
3637
3638    #[test]
3639    fn test_is_sorted_wrap_and_sort_order() {
3640        // Sorted according to WrapAndSortOrder
3641        let relations: Relations = "debhelper, python3, rustc".parse().unwrap();
3642        assert!(relations.is_sorted(&WrapAndSortOrder));
3643
3644        // Not sorted
3645        let relations: Relations = "rustc, debhelper, python3".parse().unwrap();
3646        assert!(!relations.is_sorted(&WrapAndSortOrder));
3647
3648        // Build systems first (sorted alphabetically within their group)
3649        let (relations, _) =
3650            Relations::parse_relaxed("cdbs, debhelper-compat, python3, ${misc:Depends}", true);
3651        assert!(relations.is_sorted(&WrapAndSortOrder));
3652    }
3653
3654    #[test]
3655    fn test_is_sorted_default_order() {
3656        // Sorted alphabetically
3657        let relations: Relations = "aaa, bbb, ccc".parse().unwrap();
3658        assert!(relations.is_sorted(&DefaultSortingOrder));
3659
3660        // Not sorted
3661        let relations: Relations = "ccc, aaa, bbb".parse().unwrap();
3662        assert!(!relations.is_sorted(&DefaultSortingOrder));
3663
3664        // Special items at end
3665        let (relations, _) = Relations::parse_relaxed("aaa, bbb, ${misc:Depends}", true);
3666        assert!(relations.is_sorted(&DefaultSortingOrder));
3667    }
3668
3669    #[test]
3670    fn test_is_sorted_with_substvars() {
3671        // Substvars should be ignored by DefaultSortingOrder
3672        let (relations, _) = Relations::parse_relaxed("python3, ${misc:Depends}, rustc", true);
3673        // This is considered sorted because ${misc:Depends} is ignored
3674        assert!(relations.is_sorted(&DefaultSortingOrder));
3675    }
3676
3677    #[test]
3678    fn test_drop_dependency_exists() {
3679        let mut relations: Relations = "python3, debhelper, rustc".parse().unwrap();
3680        assert!(relations.drop_dependency("debhelper"));
3681        assert_eq!(relations.to_string(), "python3, rustc");
3682    }
3683
3684    #[test]
3685    fn test_drop_dependency_not_exists() {
3686        let mut relations: Relations = "python3, rustc".parse().unwrap();
3687        assert!(!relations.drop_dependency("nonexistent"));
3688        assert_eq!(relations.to_string(), "python3, rustc");
3689    }
3690
3691    #[test]
3692    fn test_drop_dependency_only_item() {
3693        let mut relations: Relations = "python3".parse().unwrap();
3694        assert!(relations.drop_dependency("python3"));
3695        assert_eq!(relations.to_string(), "");
3696    }
3697
3698    #[test]
3699    fn test_add_dependency_to_empty() {
3700        let mut relations: Relations = "".parse().unwrap();
3701        let entry = Entry::from(Relation::simple("python3"));
3702        relations.add_dependency(entry, None);
3703        assert_eq!(relations.to_string(), "python3");
3704    }
3705
3706    #[test]
3707    fn test_add_dependency_sorted_position() {
3708        let mut relations: Relations = "debhelper, rustc".parse().unwrap();
3709        let entry = Entry::from(Relation::simple("python3"));
3710        relations.add_dependency(entry, None);
3711        // Should be inserted in sorted position
3712        assert_eq!(relations.to_string(), "debhelper, python3, rustc");
3713    }
3714
3715    #[test]
3716    fn test_add_dependency_explicit_position() {
3717        let mut relations: Relations = "python3, rustc".parse().unwrap();
3718        let entry = Entry::from(Relation::simple("debhelper"));
3719        relations.add_dependency(entry, Some(0));
3720        assert_eq!(relations.to_string(), "debhelper, python3, rustc");
3721    }
3722
3723    #[test]
3724    fn test_add_dependency_build_system_first() {
3725        let mut relations: Relations = "python3, rustc".parse().unwrap();
3726        let entry = Entry::from(Relation::simple("debhelper-compat"));
3727        relations.add_dependency(entry, None);
3728        // debhelper-compat should be inserted first (build system)
3729        assert_eq!(relations.to_string(), "debhelper-compat, python3, rustc");
3730    }
3731
3732    #[test]
3733    fn test_add_dependency_at_end() {
3734        let mut relations: Relations = "debhelper, python3".parse().unwrap();
3735        let entry = Entry::from(Relation::simple("zzz-package"));
3736        relations.add_dependency(entry, None);
3737        // Should be added at the end (alphabetically after python3)
3738        assert_eq!(relations.to_string(), "debhelper, python3, zzz-package");
3739    }
3740
3741    #[test]
3742    fn test_get_relation_exists() {
3743        let relations: Relations = "python3, debhelper (>= 12), rustc".parse().unwrap();
3744        let result = relations.get_relation("debhelper");
3745        assert!(result.is_ok());
3746        let (idx, entry) = result.unwrap();
3747        assert_eq!(idx, 1);
3748        assert_eq!(entry.to_string(), "debhelper (>= 12)");
3749    }
3750
3751    #[test]
3752    fn test_get_relation_not_exists() {
3753        let relations: Relations = "python3, rustc".parse().unwrap();
3754        let result = relations.get_relation("nonexistent");
3755        assert_eq!(result, Err("Package nonexistent not found".to_string()));
3756    }
3757
3758    #[test]
3759    fn test_get_relation_complex_rule() {
3760        let relations: Relations = "python3 | python3-minimal, rustc".parse().unwrap();
3761        let result = relations.get_relation("python3");
3762        assert_eq!(
3763            result,
3764            Err("Complex rule for python3, aborting".to_string())
3765        );
3766    }
3767
3768    #[test]
3769    fn test_iter_relations_for_simple() {
3770        let relations: Relations = "python3, debhelper, python3-dev".parse().unwrap();
3771        let entries: Vec<_> = relations.iter_relations_for("python3").collect();
3772        assert_eq!(entries.len(), 1);
3773        assert_eq!(entries[0].0, 0);
3774        assert_eq!(entries[0].1.to_string(), "python3");
3775    }
3776
3777    #[test]
3778    fn test_iter_relations_for_alternatives() {
3779        let relations: Relations = "python3 | python3-minimal, python3-dev".parse().unwrap();
3780        let entries: Vec<_> = relations.iter_relations_for("python3").collect();
3781        // Should find both the alternative entry and python3-dev is not included
3782        assert_eq!(entries.len(), 1);
3783        assert_eq!(entries[0].0, 0);
3784    }
3785
3786    #[test]
3787    fn test_iter_relations_for_not_found() {
3788        let relations: Relations = "python3, rustc".parse().unwrap();
3789        let entries: Vec<_> = relations.iter_relations_for("debhelper").collect();
3790        assert_eq!(entries.len(), 0);
3791    }
3792
3793    #[test]
3794    fn test_has_relation_exists() {
3795        let relations: Relations = "python3, debhelper, rustc".parse().unwrap();
3796        assert!(relations.has_relation("debhelper"));
3797        assert!(relations.has_relation("python3"));
3798        assert!(relations.has_relation("rustc"));
3799    }
3800
3801    #[test]
3802    fn test_has_relation_not_exists() {
3803        let relations: Relations = "python3, rustc".parse().unwrap();
3804        assert!(!relations.has_relation("debhelper"));
3805    }
3806
3807    #[test]
3808    fn test_has_relation_in_alternative() {
3809        let relations: Relations = "python3 | python3-minimal".parse().unwrap();
3810        assert!(relations.has_relation("python3"));
3811        assert!(relations.has_relation("python3-minimal"));
3812    }
3813
3814    #[test]
3815    fn test_sorting_order_wrap_and_sort_build_systems() {
3816        let order = WrapAndSortOrder;
3817        // Build systems should come before regular packages
3818        assert!(order.lt("debhelper", "python3"));
3819        assert!(order.lt("debhelper-compat", "rustc"));
3820        assert!(order.lt("cdbs", "aaa"));
3821        assert!(order.lt("dh-python", "python3"));
3822    }
3823
3824    #[test]
3825    fn test_sorting_order_wrap_and_sort_regular_packages() {
3826        let order = WrapAndSortOrder;
3827        // Regular packages sorted alphabetically
3828        assert!(order.lt("aaa", "bbb"));
3829        assert!(order.lt("python3", "rustc"));
3830        assert!(!order.lt("rustc", "python3"));
3831    }
3832
3833    #[test]
3834    fn test_sorting_order_wrap_and_sort_substvars() {
3835        let order = WrapAndSortOrder;
3836        // Substvars should come after regular packages
3837        assert!(order.lt("python3", "${misc:Depends}"));
3838        assert!(!order.lt("${misc:Depends}", "python3"));
3839        // But wrap-and-sort doesn't ignore them
3840        assert!(!order.ignore("${misc:Depends}"));
3841    }
3842
3843    #[test]
3844    fn test_sorting_order_default_special_items() {
3845        let order = DefaultSortingOrder;
3846        // Special items should come after regular items
3847        assert!(order.lt("python3", "${misc:Depends}"));
3848        assert!(order.lt("aaa", "@cdbs@"));
3849        // And should be ignored
3850        assert!(order.ignore("${misc:Depends}"));
3851        assert!(order.ignore("@cdbs@"));
3852        assert!(!order.ignore("python3"));
3853    }
3854
3855    #[test]
3856    fn test_is_special_package_name() {
3857        assert!(is_special_package_name("${misc:Depends}"));
3858        assert!(is_special_package_name("${shlibs:Depends}"));
3859        assert!(is_special_package_name("@cdbs@"));
3860        assert!(!is_special_package_name("python3"));
3861        assert!(!is_special_package_name("debhelper"));
3862    }
3863
3864    #[test]
3865    fn test_add_dependency_with_explicit_position() {
3866        // Test that add_dependency works with explicit position and preserves whitespace
3867        let mut relations: Relations = "python3,  rustc".parse().unwrap();
3868        let entry = Entry::from(Relation::simple("debhelper"));
3869        relations.add_dependency(entry, Some(1));
3870        // Should preserve the 2-space pattern from the original
3871        assert_eq!(relations.to_string(), "python3,  debhelper,  rustc");
3872    }
3873
3874    #[test]
3875    fn test_whitespace_detection_single_space() {
3876        let mut relations: Relations = "python3, rustc".parse().unwrap();
3877        let entry = Entry::from(Relation::simple("debhelper"));
3878        relations.add_dependency(entry, Some(1));
3879        assert_eq!(relations.to_string(), "python3, debhelper, rustc");
3880    }
3881
3882    #[test]
3883    fn test_whitespace_detection_multiple_spaces() {
3884        let mut relations: Relations = "python3,  rustc,  gcc".parse().unwrap();
3885        let entry = Entry::from(Relation::simple("debhelper"));
3886        relations.add_dependency(entry, Some(1));
3887        // Should detect and use the 2-space pattern
3888        assert_eq!(relations.to_string(), "python3,  debhelper,  rustc,  gcc");
3889    }
3890
3891    #[test]
3892    fn test_whitespace_detection_mixed_patterns() {
3893        // When patterns differ, use the most common one
3894        let mut relations: Relations = "a, b, c,  d, e".parse().unwrap();
3895        let entry = Entry::from(Relation::simple("x"));
3896        relations.push(entry);
3897        // Three single-space (after a, b, d), one double-space (after c)
3898        // Should use single space as it's most common
3899        assert_eq!(relations.to_string(), "a, b, c,  d, e, x");
3900    }
3901
3902    #[test]
3903    fn test_whitespace_detection_newlines() {
3904        let mut relations: Relations = "python3,\n rustc".parse().unwrap();
3905        let entry = Entry::from(Relation::simple("debhelper"));
3906        relations.add_dependency(entry, Some(1));
3907        // Detects full pattern including newline
3908        assert_eq!(relations.to_string(), "python3,\n debhelper,\n rustc");
3909    }
3910
3911    #[test]
3912    fn test_append_with_newline_no_trailing() {
3913        let mut relations: Relations = "foo,\n bar".parse().unwrap();
3914        let entry = Entry::from(Relation::simple("blah"));
3915        relations.add_dependency(entry, None);
3916        assert_eq!(relations.to_string(), "foo,\n bar,\n blah");
3917    }
3918
3919    #[test]
3920    fn test_append_with_trailing_newline() {
3921        let mut relations: Relations = "foo,\n bar\n".parse().unwrap();
3922        let entry = Entry::from(Relation::simple("blah"));
3923        relations.add_dependency(entry, None);
3924        assert_eq!(relations.to_string(), "foo,\n bar,\n blah");
3925    }
3926
3927    #[test]
3928    fn test_append_with_4_space_indent() {
3929        let mut relations: Relations = "foo,\n    bar".parse().unwrap();
3930        let entry = Entry::from(Relation::simple("blah"));
3931        relations.add_dependency(entry, None);
3932        assert_eq!(relations.to_string(), "foo,\n    bar,\n    blah");
3933    }
3934
3935    #[test]
3936    fn test_append_with_4_space_and_trailing_newline() {
3937        let mut relations: Relations = "foo,\n    bar\n".parse().unwrap();
3938        let entry = Entry::from(Relation::simple("blah"));
3939        relations.add_dependency(entry, None);
3940        assert_eq!(relations.to_string(), "foo,\n    bar,\n    blah");
3941    }
3942
3943    #[test]
3944    fn test_odd_syntax_append_no_trailing() {
3945        let mut relations: Relations = "\n foo\n , bar".parse().unwrap();
3946        let entry = Entry::from(Relation::simple("blah"));
3947        relations.add_dependency(entry, None);
3948        assert_eq!(relations.to_string(), "\n foo\n , bar\n , blah");
3949    }
3950
3951    #[test]
3952    fn test_odd_syntax_append_with_trailing() {
3953        let mut relations: Relations = "\n foo\n , bar\n".parse().unwrap();
3954        let entry = Entry::from(Relation::simple("blah"));
3955        relations.add_dependency(entry, None);
3956        assert_eq!(relations.to_string(), "\n foo\n , bar\n , blah");
3957    }
3958
3959    #[test]
3960    fn test_insert_at_1_no_trailing() {
3961        let mut relations: Relations = "foo,\n bar".parse().unwrap();
3962        let entry = Entry::from(Relation::simple("blah"));
3963        relations.add_dependency(entry, Some(1));
3964        assert_eq!(relations.to_string(), "foo,\n blah,\n bar");
3965    }
3966
3967    #[test]
3968    fn test_insert_at_1_with_trailing() {
3969        let mut relations: Relations = "foo,\n bar\n".parse().unwrap();
3970        let entry = Entry::from(Relation::simple("blah"));
3971        relations.add_dependency(entry, Some(1));
3972        assert_eq!(relations.to_string(), "foo,\n blah,\n bar");
3973    }
3974
3975    #[test]
3976    fn test_odd_syntax_insert_at_1() {
3977        let mut relations: Relations = "\n foo\n , bar\n".parse().unwrap();
3978        let entry = Entry::from(Relation::simple("blah"));
3979        relations.add_dependency(entry, Some(1));
3980        assert_eq!(relations.to_string(), "\n foo\n , blah\n , bar");
3981    }
3982}