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    /// Filter entries based on a predicate function.
1234    ///
1235    /// # Arguments
1236    /// * `keep` - A function that returns true for entries to keep
1237    ///
1238    /// # Example
1239    /// ```
1240    /// use debian_control::lossless::relations::Relations;
1241    ///
1242    /// let mut relations: Relations = "python3, debhelper, rustc".parse().unwrap();
1243    /// relations.filter_entries(|entry| {
1244    ///     entry.relations().any(|r| r.name().starts_with("python"))
1245    /// });
1246    /// assert_eq!(relations.to_string(), "python3");
1247    /// ```
1248    pub fn filter_entries<F>(&mut self, keep: F)
1249    where
1250        F: Fn(&Entry) -> bool,
1251    {
1252        let indices_to_remove: Vec<_> = self
1253            .entries()
1254            .enumerate()
1255            .filter_map(|(idx, entry)| if keep(&entry) { None } else { Some(idx) })
1256            .collect();
1257
1258        // Remove in reverse order to maintain correct indices
1259        for idx in indices_to_remove.into_iter().rev() {
1260            self.remove_entry(idx);
1261        }
1262    }
1263
1264    /// Check whether the relations are sorted according to a given sorting order.
1265    ///
1266    /// # Arguments
1267    /// * `sorting_order` - The sorting order to check against
1268    ///
1269    /// # Example
1270    /// ```
1271    /// use debian_control::lossless::relations::{Relations, WrapAndSortOrder};
1272    ///
1273    /// let relations: Relations = "debhelper, python3, rustc".parse().unwrap();
1274    /// assert!(relations.is_sorted(&WrapAndSortOrder));
1275    ///
1276    /// let relations: Relations = "rustc, debhelper, python3".parse().unwrap();
1277    /// assert!(!relations.is_sorted(&WrapAndSortOrder));
1278    /// ```
1279    pub fn is_sorted(&self, sorting_order: &impl SortingOrder) -> bool {
1280        let mut last_name: Option<String> = None;
1281        for entry in self.entries() {
1282            // Skip empty entries
1283            let mut relations = entry.relations();
1284            let Some(relation) = relations.next() else {
1285                continue;
1286            };
1287
1288            let name = relation.name();
1289
1290            // Skip items that should be ignored
1291            if sorting_order.ignore(&name) {
1292                continue;
1293            }
1294
1295            // Check if this breaks the sort order
1296            if let Some(ref last) = last_name {
1297                if sorting_order.lt(&name, last) {
1298                    return false;
1299                }
1300            }
1301
1302            last_name = Some(name);
1303        }
1304        true
1305    }
1306
1307    /// Find the position to insert an entry while maintaining sort order.
1308    ///
1309    /// This method detects the current sorting order and returns the appropriate
1310    /// insertion position. If there are fewer than 2 entries, it defaults to
1311    /// WrapAndSortOrder. If no sorting order is detected, it returns the end position.
1312    ///
1313    /// # Arguments
1314    /// * `entry` - The entry to insert
1315    ///
1316    /// # Returns
1317    /// The index where the entry should be inserted
1318    fn find_insert_position(&self, entry: &Entry) -> usize {
1319        // Get the package name from the first relation in the entry
1320        let Some(relation) = entry.relations().next() else {
1321            // Empty entry, just append at the end
1322            return self.len();
1323        };
1324        let package_name = relation.name();
1325
1326        // Count non-empty entries
1327        let count = self.entries().filter(|e| !e.is_empty()).count();
1328
1329        // If there are less than 2 items, default to WrapAndSortOrder
1330        let sorting_order: Box<dyn SortingOrder> = if count < 2 {
1331            Box::new(WrapAndSortOrder)
1332        } else {
1333            // Try to detect which sorting order is being used
1334            // Try WrapAndSortOrder first, then DefaultSortingOrder
1335            if self.is_sorted(&WrapAndSortOrder) {
1336                Box::new(WrapAndSortOrder)
1337            } else if self.is_sorted(&DefaultSortingOrder) {
1338                Box::new(DefaultSortingOrder)
1339            } else {
1340                // No sorting order detected, just append at the end
1341                return self.len();
1342            }
1343        };
1344
1345        // If adding a special item that should be ignored by this sort order, append at the end
1346        if sorting_order.ignore(&package_name) {
1347            return self.len();
1348        }
1349
1350        // Insert in sorted order among regular items
1351        let mut position = 0;
1352        for (idx, existing_entry) in self.entries().enumerate() {
1353            let mut existing_relations = existing_entry.relations();
1354            let Some(existing_relation) = existing_relations.next() else {
1355                // Empty entry, skip
1356                position += 1;
1357                continue;
1358            };
1359
1360            let existing_name = existing_relation.name();
1361
1362            // Skip special items when finding insertion position
1363            if sorting_order.ignore(&existing_name) {
1364                position += 1;
1365                continue;
1366            }
1367
1368            // Compare with regular items only
1369            if sorting_order.lt(&package_name, &existing_name) {
1370                return idx;
1371            }
1372            position += 1;
1373        }
1374
1375        position
1376    }
1377
1378    /// Drop a dependency from the relations by package name.
1379    ///
1380    /// # Arguments
1381    /// * `package` - The package name to remove
1382    ///
1383    /// # Returns
1384    /// `true` if the package was found and removed, `false` otherwise
1385    ///
1386    /// # Example
1387    /// ```
1388    /// use debian_control::lossless::relations::Relations;
1389    ///
1390    /// let mut relations: Relations = "python3, debhelper, rustc".parse().unwrap();
1391    /// assert!(relations.drop_dependency("debhelper"));
1392    /// assert_eq!(relations.to_string(), "python3, rustc");
1393    /// assert!(!relations.drop_dependency("nonexistent"));
1394    /// ```
1395    pub fn drop_dependency(&mut self, package: &str) -> bool {
1396        let indices_to_remove: Vec<_> = self
1397            .entries()
1398            .enumerate()
1399            .filter_map(|(idx, entry)| {
1400                let relations: Vec<_> = entry.relations().collect();
1401                let names: Vec<_> = relations.iter().map(|r| r.name()).collect();
1402                if names == vec![package] {
1403                    Some(idx)
1404                } else {
1405                    None
1406                }
1407            })
1408            .collect();
1409
1410        let found = !indices_to_remove.is_empty();
1411
1412        // Remove in reverse order to maintain correct indices
1413        for idx in indices_to_remove.into_iter().rev() {
1414            self.remove_entry(idx);
1415        }
1416
1417        found
1418    }
1419
1420    /// Add a dependency at a specific position or auto-detect the position.
1421    ///
1422    /// If `position` is `None`, the position is automatically determined based
1423    /// on the detected sorting order. If a sorting order is detected, the entry
1424    /// is inserted in the appropriate position to maintain that order. Otherwise,
1425    /// it is appended at the end.
1426    ///
1427    /// # Arguments
1428    /// * `entry` - The entry to add
1429    /// * `position` - Optional position to insert at
1430    ///
1431    /// # Example
1432    /// ```
1433    /// use debian_control::lossless::relations::{Relations, Relation, Entry};
1434    ///
1435    /// let mut relations: Relations = "python3, rustc".parse().unwrap();
1436    /// let entry = Entry::from(Relation::simple("debhelper"));
1437    /// relations.add_dependency(entry, None);
1438    /// // debhelper is inserted in sorted order (if order is detected)
1439    /// ```
1440    pub fn add_dependency(&mut self, entry: Entry, position: Option<usize>) {
1441        let pos = position.unwrap_or_else(|| self.find_insert_position(&entry));
1442        self.insert(pos, entry);
1443    }
1444
1445    /// Get the entry containing a specific package.
1446    ///
1447    /// This returns the first entry that contains exactly one relation with the
1448    /// specified package name (no alternatives).
1449    ///
1450    /// # Arguments
1451    /// * `package` - The package name to search for
1452    ///
1453    /// # Returns
1454    /// A tuple of (index, Entry) if found
1455    ///
1456    /// # Errors
1457    /// Returns `Err` with a message if the package is found in a complex rule (with alternatives)
1458    ///
1459    /// # Example
1460    /// ```
1461    /// use debian_control::lossless::relations::Relations;
1462    ///
1463    /// let relations: Relations = "python3, debhelper (>= 12), rustc".parse().unwrap();
1464    /// let (idx, entry) = relations.get_relation("debhelper").unwrap();
1465    /// assert_eq!(idx, 1);
1466    /// assert_eq!(entry.to_string(), "debhelper (>= 12)");
1467    /// ```
1468    pub fn get_relation(&self, package: &str) -> Result<(usize, Entry), String> {
1469        for (idx, entry) in self.entries().enumerate() {
1470            let relations: Vec<_> = entry.relations().collect();
1471            let names: Vec<_> = relations.iter().map(|r| r.name()).collect();
1472
1473            if names.len() > 1 && names.contains(&package.to_string()) {
1474                return Err(format!("Complex rule for {}, aborting", package));
1475            }
1476
1477            if names.len() == 1 && names[0] == package {
1478                return Ok((idx, entry));
1479            }
1480        }
1481        Err(format!("Package {} not found", package))
1482    }
1483
1484    /// Iterate over all entries containing a specific package.
1485    ///
1486    /// # Arguments
1487    /// * `package` - The package name to search for
1488    ///
1489    /// # Returns
1490    /// An iterator over tuples of (index, Entry)
1491    ///
1492    /// # Example
1493    /// ```
1494    /// use debian_control::lossless::relations::Relations;
1495    ///
1496    /// let relations: Relations = "python3 | python3-minimal, python3-dev".parse().unwrap();
1497    /// let entries: Vec<_> = relations.iter_relations_for("python3").collect();
1498    /// assert_eq!(entries.len(), 1);
1499    /// ```
1500    pub fn iter_relations_for(&self, package: &str) -> impl Iterator<Item = (usize, Entry)> + '_ {
1501        let package = package.to_string();
1502        self.entries().enumerate().filter(move |(_, entry)| {
1503            let names: Vec<_> = entry.relations().map(|r| r.name()).collect();
1504            names.contains(&package)
1505        })
1506    }
1507
1508    /// Check whether a package exists in the relations.
1509    ///
1510    /// # Arguments
1511    /// * `package` - The package name to search for
1512    ///
1513    /// # Returns
1514    /// `true` if the package is found, `false` otherwise
1515    ///
1516    /// # Example
1517    /// ```
1518    /// use debian_control::lossless::relations::Relations;
1519    ///
1520    /// let relations: Relations = "python3, debhelper, rustc".parse().unwrap();
1521    /// assert!(relations.has_relation("debhelper"));
1522    /// assert!(!relations.has_relation("nonexistent"));
1523    /// ```
1524    pub fn has_relation(&self, package: &str) -> bool {
1525        self.entries()
1526            .any(|entry| entry.relations().any(|r| r.name() == package))
1527    }
1528}
1529
1530impl From<Vec<Entry>> for Relations {
1531    fn from(entries: Vec<Entry>) -> Self {
1532        let mut builder = GreenNodeBuilder::new();
1533        builder.start_node(ROOT.into());
1534        for (i, entry) in entries.into_iter().enumerate() {
1535            if i > 0 {
1536                builder.token(COMMA.into(), ",");
1537                builder.token(WHITESPACE.into(), " ");
1538            }
1539            inject(&mut builder, entry.0);
1540        }
1541        builder.finish_node();
1542        Relations(SyntaxNode::new_root_mut(builder.finish()))
1543    }
1544}
1545
1546impl From<Entry> for Relations {
1547    fn from(entry: Entry) -> Self {
1548        Self::from(vec![entry])
1549    }
1550}
1551
1552impl Default for Entry {
1553    fn default() -> Self {
1554        Self::new()
1555    }
1556}
1557
1558impl PartialOrd for Entry {
1559    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1560        let mut rels_a = self.relations();
1561        let mut rels_b = other.relations();
1562        while let (Some(a), Some(b)) = (rels_a.next(), rels_b.next()) {
1563            match a.cmp(&b) {
1564                std::cmp::Ordering::Equal => continue,
1565                x => return Some(x),
1566            }
1567        }
1568
1569        if rels_a.next().is_some() {
1570            return Some(std::cmp::Ordering::Greater);
1571        }
1572
1573        if rels_b.next().is_some() {
1574            return Some(std::cmp::Ordering::Less);
1575        }
1576
1577        Some(std::cmp::Ordering::Equal)
1578    }
1579}
1580
1581impl Eq for Entry {}
1582
1583impl Ord for Entry {
1584    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1585        self.partial_cmp(other).unwrap()
1586    }
1587}
1588
1589impl Entry {
1590    /// Create a new entry
1591    pub fn new() -> Self {
1592        let mut builder = GreenNodeBuilder::new();
1593        builder.start_node(SyntaxKind::ENTRY.into());
1594        builder.finish_node();
1595        Entry(SyntaxNode::new_root_mut(builder.finish()))
1596    }
1597
1598    /// Replace the relation at the given index
1599    pub fn replace(&mut self, idx: usize, relation: Relation) {
1600        let current_relation = self.get_relation(idx).unwrap();
1601
1602        let old_root = current_relation.0;
1603        let new_root = relation.0;
1604        // Preserve white the current relation has
1605        let mut prev = new_root.first_child_or_token();
1606        let mut new_head_len = 0;
1607        // First, strip off any whitespace from the new relation
1608        while let Some(p) = prev {
1609            if p.kind() == WHITESPACE || p.kind() == NEWLINE {
1610                new_head_len += 1;
1611                prev = p.next_sibling_or_token();
1612            } else {
1613                break;
1614            }
1615        }
1616        let mut new_tail_len = 0;
1617        let mut next = new_root.last_child_or_token();
1618        while let Some(n) = next {
1619            if n.kind() == WHITESPACE || n.kind() == NEWLINE {
1620                new_tail_len += 1;
1621                next = n.prev_sibling_or_token();
1622            } else {
1623                break;
1624            }
1625        }
1626        // Then, inherit the whitespace from the old relation
1627        let mut prev = old_root.first_child_or_token();
1628        let mut old_head = vec![];
1629        while let Some(p) = prev {
1630            if p.kind() == WHITESPACE || p.kind() == NEWLINE {
1631                old_head.push(p.clone());
1632                prev = p.next_sibling_or_token();
1633            } else {
1634                break;
1635            }
1636        }
1637        let mut old_tail = vec![];
1638        let mut next = old_root.last_child_or_token();
1639        while let Some(n) = next {
1640            if n.kind() == WHITESPACE || n.kind() == NEWLINE {
1641                old_tail.push(n.clone());
1642                next = n.prev_sibling_or_token();
1643            } else {
1644                break;
1645            }
1646        }
1647        new_root.splice_children(0..new_head_len, old_head);
1648        let tail_pos = new_root.children_with_tokens().count() - new_tail_len;
1649        new_root.splice_children(
1650            tail_pos - new_tail_len..tail_pos,
1651            old_tail.into_iter().rev(),
1652        );
1653        let index = old_root.index();
1654        self.0
1655            .splice_children(index..index + 1, vec![new_root.into()]);
1656    }
1657
1658    /// Wrap and sort the relations in this entry
1659    #[must_use]
1660    pub fn wrap_and_sort(&self) -> Self {
1661        let mut relations = self
1662            .relations()
1663            .map(|r| r.wrap_and_sort())
1664            .collect::<Vec<_>>();
1665        // TODO: preserve comments
1666        relations.sort();
1667        Self::from(relations)
1668    }
1669
1670    /// Iterate over the relations in this entry
1671    pub fn relations(&self) -> impl Iterator<Item = Relation> + '_ {
1672        self.0.children().filter_map(Relation::cast)
1673    }
1674
1675    /// Iterate over the relations in this entry
1676    pub fn iter(&self) -> impl Iterator<Item = Relation> + '_ {
1677        self.relations()
1678    }
1679
1680    /// Get the relation at the given index
1681    pub fn get_relation(&self, idx: usize) -> Option<Relation> {
1682        self.relations().nth(idx)
1683    }
1684
1685    /// Remove the relation at the given index
1686    ///
1687    /// # Arguments
1688    /// * `idx` - The index of the relation to remove
1689    ///
1690    /// # Example
1691    /// ```
1692    /// use debian_control::lossless::relations::{Relation,Entry};
1693    /// let mut entry: Entry = r"python3-dulwich (>= 0.19.0) | python3-requests".parse().unwrap();
1694    /// entry.remove_relation(1);
1695    /// assert_eq!(entry.to_string(), "python3-dulwich (>= 0.19.0)");
1696    /// ```
1697    pub fn remove_relation(&self, idx: usize) -> Relation {
1698        let mut relation = self.get_relation(idx).unwrap();
1699        relation.remove();
1700        relation
1701    }
1702
1703    /// Check if this entry is satisfied by the given package versions.
1704    ///
1705    /// # Arguments
1706    /// * `package_version` - A function that returns the version of a package.
1707    ///
1708    /// # Example
1709    /// ```
1710    /// use debian_control::lossless::relations::{Relation,Entry};
1711    /// let entry = Entry::from(vec!["samba (>= 2.0)".parse::<Relation>().unwrap()]);
1712    /// assert!(entry.satisfied_by(|name: &str| -> Option<debversion::Version> {
1713    ///    match name {
1714    ///    "samba" => Some("2.0".parse().unwrap()),
1715    ///    _ => None
1716    /// }}));
1717    /// ```
1718    pub fn satisfied_by(&self, package_version: impl crate::VersionLookup + Copy) -> bool {
1719        self.relations().any(|r| {
1720            let actual = package_version.lookup_version(r.name().as_str());
1721            if let Some((vc, version)) = r.version() {
1722                if let Some(actual) = actual {
1723                    match vc {
1724                        VersionConstraint::GreaterThanEqual => *actual >= version,
1725                        VersionConstraint::LessThanEqual => *actual <= version,
1726                        VersionConstraint::Equal => *actual == version,
1727                        VersionConstraint::GreaterThan => *actual > version,
1728                        VersionConstraint::LessThan => *actual < version,
1729                    }
1730                } else {
1731                    false
1732                }
1733            } else {
1734                actual.is_some()
1735            }
1736        })
1737    }
1738
1739    /// Remove this entry
1740    ///
1741    /// # Example
1742    /// ```
1743    /// use debian_control::lossless::relations::{Relations,Entry};
1744    /// let mut relations: Relations = r"python3-dulwich (>= 0.19.0), python3-urllib3 (<< 1.26.0)".parse().unwrap();
1745    /// let mut entry = relations.get_entry(0).unwrap();
1746    /// entry.remove();
1747    /// assert_eq!(relations.to_string(), "python3-urllib3 (<< 1.26.0)");
1748    /// ```
1749    pub fn remove(&mut self) {
1750        let mut removed_comma = false;
1751        let is_first = !self
1752            .0
1753            .siblings(Direction::Prev)
1754            .skip(1)
1755            .any(|n| n.kind() == ENTRY);
1756        while let Some(n) = self.0.next_sibling_or_token() {
1757            if n.kind() == WHITESPACE || n.kind() == NEWLINE {
1758                n.detach();
1759            } else if n.kind() == COMMA {
1760                n.detach();
1761                removed_comma = true;
1762                break;
1763            } else {
1764                panic!("Unexpected node: {:?}", n);
1765            }
1766        }
1767        if !is_first {
1768            while let Some(n) = self.0.prev_sibling_or_token() {
1769                if n.kind() == WHITESPACE || n.kind() == NEWLINE {
1770                    n.detach();
1771                } else if !removed_comma && n.kind() == COMMA {
1772                    n.detach();
1773                    break;
1774                } else {
1775                    break;
1776                }
1777            }
1778        } else {
1779            while let Some(n) = self.0.next_sibling_or_token() {
1780                if n.kind() == WHITESPACE || n.kind() == NEWLINE {
1781                    n.detach();
1782                } else {
1783                    break;
1784                }
1785            }
1786        }
1787        self.0.detach();
1788    }
1789
1790    /// Check if this entry is empty
1791    pub fn is_empty(&self) -> bool {
1792        self.relations().count() == 0
1793    }
1794
1795    /// Get the number of relations in this entry
1796    pub fn len(&self) -> usize {
1797        self.relations().count()
1798    }
1799
1800    /// Push a new relation to the entry
1801    ///
1802    /// # Arguments
1803    /// * `relation` - The relation to push
1804    ///
1805    /// # Example
1806    /// ```
1807    /// use debian_control::lossless::relations::{Relation,Entry};
1808    /// let mut entry: Entry = "samba (>= 2.0)".parse().unwrap();
1809    /// entry.push("python3-requests".parse().unwrap());
1810    /// assert_eq!(entry.to_string(), "samba (>= 2.0) | python3-requests");
1811    /// ```
1812    pub fn push(&mut self, relation: Relation) {
1813        let is_empty = !self
1814            .0
1815            .children_with_tokens()
1816            .any(|n| n.kind() == PIPE || n.kind() == RELATION);
1817
1818        let (position, new_children) = if let Some(current_relation) = self.relations().last() {
1819            let to_insert: Vec<NodeOrToken<GreenNode, GreenToken>> = if is_empty {
1820                vec![relation.0.green().into()]
1821            } else {
1822                vec![
1823                    NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), " ")),
1824                    NodeOrToken::Token(GreenToken::new(PIPE.into(), "|")),
1825                    NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), " ")),
1826                    relation.0.green().into(),
1827                ]
1828            };
1829
1830            (current_relation.0.index() + 1, to_insert)
1831        } else {
1832            let child_count = self.0.children_with_tokens().count();
1833            (
1834                child_count,
1835                if is_empty {
1836                    vec![relation.0.green().into()]
1837                } else {
1838                    vec![
1839                        NodeOrToken::Token(GreenToken::new(PIPE.into(), "|")),
1840                        NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), " ")),
1841                        relation.0.green().into(),
1842                    ]
1843                },
1844            )
1845        };
1846
1847        let new_root = SyntaxNode::new_root_mut(
1848            self.0.replace_with(
1849                self.0
1850                    .green()
1851                    .splice_children(position..position, new_children),
1852            ),
1853        );
1854
1855        if let Some(parent) = self.0.parent() {
1856            parent.splice_children(self.0.index()..self.0.index() + 1, vec![new_root.into()]);
1857            self.0 = parent
1858                .children_with_tokens()
1859                .nth(self.0.index())
1860                .unwrap()
1861                .clone()
1862                .into_node()
1863                .unwrap();
1864        } else {
1865            self.0 = new_root;
1866        }
1867    }
1868}
1869
1870fn inject(builder: &mut GreenNodeBuilder, node: SyntaxNode) {
1871    builder.start_node(node.kind().into());
1872    for child in node.children_with_tokens() {
1873        match child {
1874            rowan::NodeOrToken::Node(child) => {
1875                inject(builder, child);
1876            }
1877            rowan::NodeOrToken::Token(token) => {
1878                builder.token(token.kind().into(), token.text());
1879            }
1880        }
1881    }
1882    builder.finish_node();
1883}
1884
1885impl From<Vec<Relation>> for Entry {
1886    fn from(relations: Vec<Relation>) -> Self {
1887        let mut builder = GreenNodeBuilder::new();
1888        builder.start_node(SyntaxKind::ENTRY.into());
1889        for (i, relation) in relations.into_iter().enumerate() {
1890            if i > 0 {
1891                builder.token(WHITESPACE.into(), " ");
1892                builder.token(COMMA.into(), "|");
1893                builder.token(WHITESPACE.into(), " ");
1894            }
1895            inject(&mut builder, relation.0);
1896        }
1897        builder.finish_node();
1898        Entry(SyntaxNode::new_root_mut(builder.finish()))
1899    }
1900}
1901
1902impl From<Relation> for Entry {
1903    fn from(relation: Relation) -> Self {
1904        Self::from(vec![relation])
1905    }
1906}
1907
1908impl Relation {
1909    /// Create a new relation
1910    ///
1911    /// # Arguments
1912    /// * `name` - The name of the package
1913    /// * `version_constraint` - The version constraint and version to use
1914    ///
1915    /// # Example
1916    /// ```
1917    /// use debian_control::lossless::relations::{Relation};
1918    /// use debian_control::relations::VersionConstraint;
1919    /// let relation = Relation::new("samba", Some((VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap())));
1920    /// assert_eq!(relation.to_string(), "samba (>= 2.0)");
1921    /// ```
1922    pub fn new(name: &str, version_constraint: Option<(VersionConstraint, Version)>) -> Self {
1923        let mut builder = GreenNodeBuilder::new();
1924        builder.start_node(SyntaxKind::RELATION.into());
1925        builder.token(IDENT.into(), name);
1926        if let Some((vc, version)) = version_constraint {
1927            builder.token(WHITESPACE.into(), " ");
1928            builder.start_node(SyntaxKind::VERSION.into());
1929            builder.token(L_PARENS.into(), "(");
1930            builder.start_node(SyntaxKind::CONSTRAINT.into());
1931            for c in vc.to_string().chars() {
1932                builder.token(
1933                    match c {
1934                        '>' => R_ANGLE.into(),
1935                        '<' => L_ANGLE.into(),
1936                        '=' => EQUAL.into(),
1937                        _ => unreachable!(),
1938                    },
1939                    c.to_string().as_str(),
1940                );
1941            }
1942            builder.finish_node();
1943
1944            builder.token(WHITESPACE.into(), " ");
1945
1946            builder.token(IDENT.into(), version.to_string().as_str());
1947
1948            builder.token(R_PARENS.into(), ")");
1949
1950            builder.finish_node();
1951        }
1952
1953        builder.finish_node();
1954        Relation(SyntaxNode::new_root_mut(builder.finish()))
1955    }
1956
1957    /// Wrap and sort this relation
1958    ///
1959    /// # Example
1960    /// ```
1961    /// use debian_control::lossless::relations::Relation;
1962    /// let relation = "  samba  (  >= 2.0) ".parse::<Relation>().unwrap();
1963    /// assert_eq!(relation.wrap_and_sort().to_string(), "samba (>= 2.0)");
1964    /// ```
1965    #[must_use]
1966    pub fn wrap_and_sort(&self) -> Self {
1967        let mut builder = GreenNodeBuilder::new();
1968        builder.start_node(SyntaxKind::RELATION.into());
1969        builder.token(IDENT.into(), self.name().as_str());
1970        if let Some(archqual) = self.archqual() {
1971            builder.token(COLON.into(), ":");
1972            builder.token(IDENT.into(), archqual.as_str());
1973        }
1974        if let Some((vc, version)) = self.version() {
1975            builder.token(WHITESPACE.into(), " ");
1976            builder.start_node(SyntaxKind::VERSION.into());
1977            builder.token(L_PARENS.into(), "(");
1978            builder.start_node(SyntaxKind::CONSTRAINT.into());
1979            builder.token(
1980                match vc {
1981                    VersionConstraint::GreaterThanEqual => R_ANGLE.into(),
1982                    VersionConstraint::LessThanEqual => L_ANGLE.into(),
1983                    VersionConstraint::Equal => EQUAL.into(),
1984                    VersionConstraint::GreaterThan => R_ANGLE.into(),
1985                    VersionConstraint::LessThan => L_ANGLE.into(),
1986                },
1987                vc.to_string().as_str(),
1988            );
1989            builder.finish_node();
1990            builder.token(WHITESPACE.into(), " ");
1991            builder.token(IDENT.into(), version.to_string().as_str());
1992            builder.token(R_PARENS.into(), ")");
1993            builder.finish_node();
1994        }
1995        if let Some(architectures) = self.architectures() {
1996            builder.token(WHITESPACE.into(), " ");
1997            builder.start_node(ARCHITECTURES.into());
1998            builder.token(L_BRACKET.into(), "[");
1999            for (i, arch) in architectures.enumerate() {
2000                if i > 0 {
2001                    builder.token(WHITESPACE.into(), " ");
2002                }
2003                builder.token(IDENT.into(), arch.as_str());
2004            }
2005            builder.token(R_BRACKET.into(), "]");
2006            builder.finish_node();
2007        }
2008        for profiles in self.profiles() {
2009            builder.token(WHITESPACE.into(), " ");
2010            builder.start_node(PROFILES.into());
2011            builder.token(L_ANGLE.into(), "<");
2012            for (i, profile) in profiles.into_iter().enumerate() {
2013                if i > 0 {
2014                    builder.token(WHITESPACE.into(), " ");
2015                }
2016                match profile {
2017                    BuildProfile::Disabled(name) => {
2018                        builder.token(NOT.into(), "!");
2019                        builder.token(IDENT.into(), name.as_str());
2020                    }
2021                    BuildProfile::Enabled(name) => {
2022                        builder.token(IDENT.into(), name.as_str());
2023                    }
2024                }
2025            }
2026            builder.token(R_ANGLE.into(), ">");
2027            builder.finish_node();
2028        }
2029        builder.finish_node();
2030        Relation(SyntaxNode::new_root_mut(builder.finish()))
2031    }
2032
2033    /// Create a new simple relation, without any version constraints.
2034    ///
2035    /// # Example
2036    /// ```
2037    /// use debian_control::lossless::relations::Relation;
2038    /// let relation = Relation::simple("samba");
2039    /// assert_eq!(relation.to_string(), "samba");
2040    /// ```
2041    pub fn simple(name: &str) -> Self {
2042        Self::new(name, None)
2043    }
2044
2045    /// Remove the version constraint from the relation.
2046    ///
2047    /// # Example
2048    /// ```
2049    /// use debian_control::lossless::relations::{Relation};
2050    /// use debian_control::relations::VersionConstraint;
2051    /// let mut relation = Relation::new("samba", Some((VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap())));
2052    /// relation.drop_constraint();
2053    /// assert_eq!(relation.to_string(), "samba");
2054    /// ```
2055    pub fn drop_constraint(&mut self) -> bool {
2056        let version_token = self.0.children().find(|n| n.kind() == VERSION);
2057        if let Some(version_token) = version_token {
2058            // Remove any whitespace before the version token
2059            while let Some(prev) = version_token.prev_sibling_or_token() {
2060                if prev.kind() == WHITESPACE || prev.kind() == NEWLINE {
2061                    prev.detach();
2062                } else {
2063                    break;
2064                }
2065            }
2066            version_token.detach();
2067            return true;
2068        }
2069
2070        false
2071    }
2072
2073    /// Return the name of the package in the relation.
2074    ///
2075    /// # Example
2076    /// ```
2077    /// use debian_control::lossless::relations::Relation;
2078    /// let relation = Relation::simple("samba");
2079    /// assert_eq!(relation.name(), "samba");
2080    /// ```
2081    pub fn name(&self) -> String {
2082        self.0
2083            .children_with_tokens()
2084            .find_map(|it| match it {
2085                SyntaxElement::Token(token) if token.kind() == IDENT => Some(token),
2086                _ => None,
2087            })
2088            .unwrap()
2089            .text()
2090            .to_string()
2091    }
2092
2093    /// Return the archqual
2094    ///
2095    /// # Example
2096    /// ```
2097    /// use debian_control::lossless::relations::Relation;
2098    /// let relation: Relation = "samba:any".parse().unwrap();
2099    /// assert_eq!(relation.archqual(), Some("any".to_string()));
2100    /// ```
2101    pub fn archqual(&self) -> Option<String> {
2102        let archqual = self.0.children().find(|n| n.kind() == ARCHQUAL);
2103        let node = if let Some(archqual) = archqual {
2104            archqual.children_with_tokens().find_map(|it| match it {
2105                SyntaxElement::Token(token) if token.kind() == IDENT => Some(token),
2106                _ => None,
2107            })
2108        } else {
2109            None
2110        };
2111        node.map(|n| n.text().to_string())
2112    }
2113
2114    /// Set the architecture qualifier for this relation.
2115    ///
2116    /// # Example
2117    /// ```
2118    /// use debian_control::lossless::relations::Relation;
2119    /// let mut relation = Relation::simple("samba");
2120    /// relation.set_archqual("any");
2121    /// assert_eq!(relation.to_string(), "samba:any");
2122    /// ```
2123    pub fn set_archqual(&mut self, archqual: &str) {
2124        let mut builder = GreenNodeBuilder::new();
2125        builder.start_node(ARCHQUAL.into());
2126        builder.token(COLON.into(), ":");
2127        builder.token(IDENT.into(), archqual);
2128        builder.finish_node();
2129
2130        let node_archqual = self.0.children().find(|n| n.kind() == ARCHQUAL);
2131        if let Some(node_archqual) = node_archqual {
2132            self.0.splice_children(
2133                node_archqual.index()..node_archqual.index() + 1,
2134                vec![SyntaxNode::new_root_mut(builder.finish()).into()],
2135            );
2136        } else {
2137            let name_node = self.0.children_with_tokens().find(|n| n.kind() == IDENT);
2138            let idx = if let Some(name_node) = name_node {
2139                name_node.index() + 1
2140            } else {
2141                0
2142            };
2143            self.0.splice_children(
2144                idx..idx,
2145                vec![SyntaxNode::new_root_mut(builder.finish()).into()],
2146            );
2147        }
2148    }
2149
2150    /// Return the version constraint and the version it is constrained to.
2151    pub fn version(&self) -> Option<(VersionConstraint, Version)> {
2152        let vc = self.0.children().find(|n| n.kind() == VERSION);
2153        let vc = vc.as_ref()?;
2154        let constraint = vc.children().find(|n| n.kind() == CONSTRAINT);
2155
2156        let version = vc.children_with_tokens().find_map(|it| match it {
2157            SyntaxElement::Token(token) if token.kind() == IDENT => Some(token),
2158            _ => None,
2159        });
2160
2161        if let (Some(constraint), Some(version)) = (constraint, version) {
2162            let vc: VersionConstraint = constraint.to_string().parse().unwrap();
2163            Some((vc, (version.text().to_string()).parse().unwrap()))
2164        } else {
2165            None
2166        }
2167    }
2168
2169    /// Set the version constraint for this relation
2170    ///
2171    /// # Example
2172    /// ```
2173    /// use debian_control::lossless::relations::{Relation};
2174    /// use debian_control::relations::VersionConstraint;
2175    /// let mut relation = Relation::simple("samba");
2176    /// relation.set_version(Some((VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap())));
2177    /// assert_eq!(relation.to_string(), "samba (>= 2.0)");
2178    /// ```
2179    pub fn set_version(&mut self, version_constraint: Option<(VersionConstraint, Version)>) {
2180        let current_version = self.0.children().find(|n| n.kind() == VERSION);
2181        if let Some((vc, version)) = version_constraint {
2182            let mut builder = GreenNodeBuilder::new();
2183            builder.start_node(VERSION.into());
2184            builder.token(L_PARENS.into(), "(");
2185            builder.start_node(CONSTRAINT.into());
2186            match vc {
2187                VersionConstraint::GreaterThanEqual => {
2188                    builder.token(R_ANGLE.into(), ">");
2189                    builder.token(EQUAL.into(), "=");
2190                }
2191                VersionConstraint::LessThanEqual => {
2192                    builder.token(L_ANGLE.into(), "<");
2193                    builder.token(EQUAL.into(), "=");
2194                }
2195                VersionConstraint::Equal => {
2196                    builder.token(EQUAL.into(), "=");
2197                }
2198                VersionConstraint::GreaterThan => {
2199                    builder.token(R_ANGLE.into(), ">");
2200                }
2201                VersionConstraint::LessThan => {
2202                    builder.token(L_ANGLE.into(), "<");
2203                }
2204            }
2205            builder.finish_node(); // CONSTRAINT
2206            builder.token(WHITESPACE.into(), " ");
2207            builder.token(IDENT.into(), version.to_string().as_str());
2208            builder.token(R_PARENS.into(), ")");
2209            builder.finish_node(); // VERSION
2210
2211            if let Some(current_version) = current_version {
2212                self.0.splice_children(
2213                    current_version.index()..current_version.index() + 1,
2214                    vec![SyntaxNode::new_root_mut(builder.finish()).into()],
2215                );
2216            } else {
2217                let name_node = self.0.children_with_tokens().find(|n| n.kind() == IDENT);
2218                let idx = if let Some(name_node) = name_node {
2219                    name_node.index() + 1
2220                } else {
2221                    0
2222                };
2223                let new_children = vec![
2224                    GreenToken::new(WHITESPACE.into(), " ").into(),
2225                    builder.finish().into(),
2226                ];
2227                let new_root = SyntaxNode::new_root_mut(
2228                    self.0.green().splice_children(idx..idx, new_children),
2229                );
2230                if let Some(parent) = self.0.parent() {
2231                    parent
2232                        .splice_children(self.0.index()..self.0.index() + 1, vec![new_root.into()]);
2233                    self.0 = parent
2234                        .children_with_tokens()
2235                        .nth(self.0.index())
2236                        .unwrap()
2237                        .clone()
2238                        .into_node()
2239                        .unwrap();
2240                } else {
2241                    self.0 = new_root;
2242                }
2243            }
2244        } else if let Some(current_version) = current_version {
2245            // Remove any whitespace before the version token
2246            while let Some(prev) = current_version.prev_sibling_or_token() {
2247                if prev.kind() == WHITESPACE || prev.kind() == NEWLINE {
2248                    prev.detach();
2249                } else {
2250                    break;
2251                }
2252            }
2253            current_version.detach();
2254        }
2255    }
2256
2257    /// Return an iterator over the architectures for this relation
2258    ///
2259    /// # Example
2260    /// ```
2261    /// use debian_control::lossless::relations::Relation;
2262    /// let relation: Relation = "samba [amd64]".parse().unwrap();
2263    /// assert_eq!(relation.architectures().unwrap().collect::<Vec<_>>(), vec!["amd64".to_string()]);
2264    /// ```
2265    pub fn architectures(&self) -> Option<impl Iterator<Item = String> + '_> {
2266        let architectures = self.0.children().find(|n| n.kind() == ARCHITECTURES)?;
2267
2268        Some(architectures.children_with_tokens().filter_map(|node| {
2269            let token = node.as_token()?;
2270            if token.kind() == IDENT {
2271                Some(token.text().to_string())
2272            } else {
2273                None
2274            }
2275        }))
2276    }
2277
2278    /// Returns an iterator over the build profiles for this relation
2279    ///
2280    /// # Example
2281    /// ```
2282    /// use debian_control::lossless::relations::{Relation};
2283    /// use debian_control::relations::{BuildProfile};
2284    /// let relation: Relation = "samba <!nocheck>".parse().unwrap();
2285    /// assert_eq!(relation.profiles().collect::<Vec<_>>(), vec![vec![BuildProfile::Disabled("nocheck".to_string())]]);
2286    /// ```
2287    pub fn profiles(&self) -> impl Iterator<Item = Vec<BuildProfile>> + '_ {
2288        let profiles = self.0.children().filter(|n| n.kind() == PROFILES);
2289
2290        profiles.map(|profile| {
2291            // iterate over nodes separated by whitespace tokens
2292            let mut ret = vec![];
2293            let mut current = vec![];
2294            for token in profile.children_with_tokens() {
2295                match token.kind() {
2296                    WHITESPACE | NEWLINE => {
2297                        if !current.is_empty() {
2298                            ret.push(current.join("").parse::<BuildProfile>().unwrap());
2299                            current = vec![];
2300                        }
2301                    }
2302                    L_ANGLE | R_ANGLE => {}
2303                    _ => {
2304                        current.push(token.to_string());
2305                    }
2306                }
2307            }
2308            if !current.is_empty() {
2309                ret.push(current.concat().parse().unwrap());
2310            }
2311            ret
2312        })
2313    }
2314
2315    /// Remove this relation
2316    ///
2317    /// # Example
2318    /// ```
2319    /// use debian_control::lossless::relations::{Relation,Entry};
2320    /// let mut entry: Entry = r"python3-dulwich (>= 0.19.0) | python3-urllib3 (<< 1.26.0)".parse().unwrap();
2321    /// let mut relation = entry.get_relation(0).unwrap();
2322    /// relation.remove();
2323    /// assert_eq!(entry.to_string(), "python3-urllib3 (<< 1.26.0)");
2324    /// ```
2325    pub fn remove(&mut self) {
2326        let is_first = !self
2327            .0
2328            .siblings(Direction::Prev)
2329            .skip(1)
2330            .any(|n| n.kind() == RELATION);
2331        if !is_first {
2332            // Not the first item in the list. Remove whitespace backwards to the previous
2333            // pipe, the pipe and any whitespace until the previous relation
2334            while let Some(n) = self.0.prev_sibling_or_token() {
2335                if n.kind() == WHITESPACE || n.kind() == NEWLINE {
2336                    n.detach();
2337                } else if n.kind() == PIPE {
2338                    n.detach();
2339                    break;
2340                } else {
2341                    break;
2342                }
2343            }
2344            while let Some(n) = self.0.prev_sibling_or_token() {
2345                if n.kind() == WHITESPACE || n.kind() == NEWLINE {
2346                    n.detach();
2347                } else {
2348                    break;
2349                }
2350            }
2351        } else {
2352            // First item in the list. Remove whitespace up to the pipe, the pipe and anything
2353            // before the next relation
2354            while let Some(n) = self.0.next_sibling_or_token() {
2355                if n.kind() == WHITESPACE || n.kind() == NEWLINE {
2356                    n.detach();
2357                } else if n.kind() == PIPE {
2358                    n.detach();
2359                    break;
2360                } else {
2361                    panic!("Unexpected node: {:?}", n);
2362                }
2363            }
2364
2365            while let Some(n) = self.0.next_sibling_or_token() {
2366                if n.kind() == WHITESPACE || n.kind() == NEWLINE {
2367                    n.detach();
2368                } else {
2369                    break;
2370                }
2371            }
2372        }
2373        // If this was the last relation in the entry, remove the entire entry
2374        if let Some(mut parent) = self.0.parent().and_then(Entry::cast) {
2375            if parent.is_empty() {
2376                parent.remove();
2377            } else {
2378                self.0.detach();
2379            }
2380        } else {
2381            self.0.detach();
2382        }
2383    }
2384
2385    /// Set the architectures for this relation
2386    ///
2387    /// # Example
2388    /// ```
2389    /// use debian_control::lossless::relations::Relation;
2390    /// let mut relation = Relation::simple("samba");
2391    /// relation.set_architectures(vec!["amd64", "i386"].into_iter());
2392    /// assert_eq!(relation.to_string(), "samba [amd64 i386]");
2393    /// ```
2394    pub fn set_architectures<'a>(&mut self, architectures: impl Iterator<Item = &'a str>) {
2395        let mut builder = GreenNodeBuilder::new();
2396        builder.start_node(ARCHITECTURES.into());
2397        builder.token(L_BRACKET.into(), "[");
2398        for (i, arch) in architectures.enumerate() {
2399            if i > 0 {
2400                builder.token(WHITESPACE.into(), " ");
2401            }
2402            builder.token(IDENT.into(), arch);
2403        }
2404        builder.token(R_BRACKET.into(), "]");
2405        builder.finish_node();
2406
2407        let node_architectures = self.0.children().find(|n| n.kind() == ARCHITECTURES);
2408        if let Some(node_architectures) = node_architectures {
2409            let new_root = SyntaxNode::new_root_mut(builder.finish());
2410            self.0.splice_children(
2411                node_architectures.index()..node_architectures.index() + 1,
2412                vec![new_root.into()],
2413            );
2414        } else {
2415            let profiles = self.0.children().find(|n| n.kind() == PROFILES);
2416            let idx = if let Some(profiles) = profiles {
2417                profiles.index()
2418            } else {
2419                self.0.children_with_tokens().count()
2420            };
2421            let new_root = SyntaxNode::new_root(self.0.green().splice_children(
2422                idx..idx,
2423                vec![
2424                    GreenToken::new(WHITESPACE.into(), " ").into(),
2425                    builder.finish().into(),
2426                ],
2427            ));
2428            if let Some(parent) = self.0.parent() {
2429                parent.splice_children(self.0.index()..self.0.index() + 1, vec![new_root.into()]);
2430                self.0 = parent
2431                    .children_with_tokens()
2432                    .nth(self.0.index())
2433                    .unwrap()
2434                    .clone()
2435                    .into_node()
2436                    .unwrap();
2437            } else {
2438                self.0 = new_root;
2439            }
2440        }
2441    }
2442
2443    /// Add a build profile to this relation
2444    ///
2445    /// # Example
2446    /// ```
2447    /// use debian_control::lossless::relations::Relation;
2448    /// use debian_control::relations::BuildProfile;
2449    /// let mut relation = Relation::simple("samba");
2450    /// relation.add_profile(&[BuildProfile::Disabled("nocheck".to_string())]);
2451    /// assert_eq!(relation.to_string(), "samba <!nocheck>");
2452    /// ```
2453    pub fn add_profile(&mut self, profile: &[BuildProfile]) {
2454        let mut builder = GreenNodeBuilder::new();
2455        builder.start_node(PROFILES.into());
2456        builder.token(L_ANGLE.into(), "<");
2457        for (i, profile) in profile.iter().enumerate() {
2458            if i > 0 {
2459                builder.token(WHITESPACE.into(), " ");
2460            }
2461            match profile {
2462                BuildProfile::Disabled(name) => {
2463                    builder.token(NOT.into(), "!");
2464                    builder.token(IDENT.into(), name.as_str());
2465                }
2466                BuildProfile::Enabled(name) => {
2467                    builder.token(IDENT.into(), name.as_str());
2468                }
2469            }
2470        }
2471        builder.token(R_ANGLE.into(), ">");
2472        builder.finish_node();
2473
2474        let node_profiles = self.0.children().find(|n| n.kind() == PROFILES);
2475        if let Some(node_profiles) = node_profiles {
2476            let new_root = SyntaxNode::new_root_mut(builder.finish());
2477            self.0.splice_children(
2478                node_profiles.index()..node_profiles.index() + 1,
2479                vec![new_root.into()],
2480            );
2481        } else {
2482            let idx = self.0.children_with_tokens().count();
2483            let new_root = SyntaxNode::new_root(self.0.green().splice_children(
2484                idx..idx,
2485                vec![
2486                    GreenToken::new(WHITESPACE.into(), " ").into(),
2487                    builder.finish().into(),
2488                ],
2489            ));
2490            if let Some(parent) = self.0.parent() {
2491                parent.splice_children(self.0.index()..self.0.index() + 1, vec![new_root.into()]);
2492                self.0 = parent
2493                    .children_with_tokens()
2494                    .nth(self.0.index())
2495                    .unwrap()
2496                    .clone()
2497                    .into_node()
2498                    .unwrap();
2499            } else {
2500                self.0 = new_root;
2501            }
2502        }
2503    }
2504
2505    /// Build a new relation
2506    pub fn build(name: &str) -> RelationBuilder {
2507        RelationBuilder::new(name)
2508    }
2509}
2510
2511/// A builder for creating a `Relation`
2512///
2513/// # Example
2514/// ```
2515/// use debian_control::lossless::relations::{Relation};
2516/// use debian_control::relations::VersionConstraint;
2517/// let relation = Relation::build("samba")
2518///    .version_constraint(VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap())
2519///    .archqual("any")
2520///    .architectures(vec!["amd64".to_string(), "i386".to_string()])
2521///    .build();
2522/// assert_eq!(relation.to_string(), "samba:any (>= 2.0) [amd64 i386]");
2523/// ```
2524pub struct RelationBuilder {
2525    name: String,
2526    version_constraint: Option<(VersionConstraint, Version)>,
2527    archqual: Option<String>,
2528    architectures: Vec<String>,
2529    profiles: Vec<Vec<BuildProfile>>,
2530}
2531
2532impl RelationBuilder {
2533    /// Create a new `RelationBuilder` with the given package name
2534    fn new(name: &str) -> Self {
2535        Self {
2536            name: name.to_string(),
2537            version_constraint: None,
2538            archqual: None,
2539            architectures: vec![],
2540            profiles: vec![],
2541        }
2542    }
2543
2544    /// Set the version constraint for this relation
2545    pub fn version_constraint(mut self, vc: VersionConstraint, version: Version) -> Self {
2546        self.version_constraint = Some((vc, version));
2547        self
2548    }
2549
2550    /// Set the architecture qualifier for this relation
2551    pub fn archqual(mut self, archqual: &str) -> Self {
2552        self.archqual = Some(archqual.to_string());
2553        self
2554    }
2555
2556    /// Set the architectures for this relation
2557    pub fn architectures(mut self, architectures: Vec<String>) -> Self {
2558        self.architectures = architectures;
2559        self
2560    }
2561
2562    /// Set the build profiles for this relation
2563    pub fn profiles(mut self, profiles: Vec<Vec<BuildProfile>>) -> Self {
2564        self.profiles = profiles;
2565        self
2566    }
2567
2568    /// Add a build profile to this relation
2569    pub fn add_profile(mut self, profile: Vec<BuildProfile>) -> Self {
2570        self.profiles.push(profile);
2571        self
2572    }
2573
2574    /// Build the `Relation`
2575    pub fn build(self) -> Relation {
2576        let mut relation = Relation::new(&self.name, self.version_constraint);
2577        if let Some(archqual) = &self.archqual {
2578            relation.set_archqual(archqual);
2579        }
2580        relation.set_architectures(self.architectures.iter().map(|s| s.as_str()));
2581        for profile in &self.profiles {
2582            relation.add_profile(profile);
2583        }
2584        relation
2585    }
2586}
2587
2588impl PartialOrd for Relation {
2589    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
2590        // Compare by name first, then by version
2591        let name_cmp = self.name().cmp(&other.name());
2592        if name_cmp != std::cmp::Ordering::Equal {
2593            return Some(name_cmp);
2594        }
2595
2596        let self_version = self.version();
2597        let other_version = other.version();
2598
2599        match (self_version, other_version) {
2600            (Some((self_vc, self_version)), Some((other_vc, other_version))) => {
2601                let vc_cmp = self_vc.cmp(&other_vc);
2602                if vc_cmp != std::cmp::Ordering::Equal {
2603                    return Some(vc_cmp);
2604                }
2605
2606                Some(self_version.cmp(&other_version))
2607            }
2608            (Some(_), None) => Some(std::cmp::Ordering::Greater),
2609            (None, Some(_)) => Some(std::cmp::Ordering::Less),
2610            (None, None) => Some(std::cmp::Ordering::Equal),
2611        }
2612    }
2613}
2614
2615impl Eq for Relation {}
2616
2617impl Ord for Relation {
2618    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
2619        self.partial_cmp(other).unwrap()
2620    }
2621}
2622
2623impl std::str::FromStr for Relations {
2624    type Err = String;
2625
2626    fn from_str(s: &str) -> Result<Self, Self::Err> {
2627        let parse = parse(s, false);
2628        if parse.errors.is_empty() {
2629            Ok(parse.root_mut())
2630        } else {
2631            Err(parse.errors.join("\n"))
2632        }
2633    }
2634}
2635
2636impl std::str::FromStr for Entry {
2637    type Err = String;
2638
2639    fn from_str(s: &str) -> Result<Self, Self::Err> {
2640        let root: Relations = s.parse()?;
2641
2642        let mut entries = root.entries();
2643        let entry = if let Some(entry) = entries.next() {
2644            entry
2645        } else {
2646            return Err("No entry found".to_string());
2647        };
2648
2649        if entries.next().is_some() {
2650            return Err("Multiple entries found".to_string());
2651        }
2652
2653        Ok(entry)
2654    }
2655}
2656
2657impl std::str::FromStr for Relation {
2658    type Err = String;
2659
2660    fn from_str(s: &str) -> Result<Self, Self::Err> {
2661        let entry: Entry = s.parse()?;
2662
2663        let mut relations = entry.relations();
2664        let relation = if let Some(relation) = relations.next() {
2665            relation
2666        } else {
2667            return Err("No relation found".to_string());
2668        };
2669
2670        if relations.next().is_some() {
2671            return Err("Multiple relations found".to_string());
2672        }
2673
2674        Ok(relation)
2675    }
2676}
2677
2678impl From<crate::lossy::Relation> for Relation {
2679    fn from(relation: crate::lossy::Relation) -> Self {
2680        let mut builder = Relation::build(&relation.name);
2681
2682        if let Some((vc, version)) = relation.version {
2683            builder = builder.version_constraint(vc, version);
2684        }
2685
2686        if let Some(archqual) = relation.archqual {
2687            builder = builder.archqual(&archqual);
2688        }
2689
2690        if let Some(architectures) = relation.architectures {
2691            builder = builder.architectures(architectures);
2692        }
2693
2694        builder = builder.profiles(relation.profiles);
2695
2696        builder.build()
2697    }
2698}
2699
2700impl From<Relation> for crate::lossy::Relation {
2701    fn from(relation: Relation) -> Self {
2702        crate::lossy::Relation {
2703            name: relation.name(),
2704            version: relation.version(),
2705            archqual: relation.archqual(),
2706            architectures: relation.architectures().map(|a| a.collect()),
2707            profiles: relation.profiles().collect(),
2708        }
2709    }
2710}
2711
2712impl From<Entry> for Vec<crate::lossy::Relation> {
2713    fn from(entry: Entry) -> Self {
2714        entry.relations().map(|r| r.into()).collect()
2715    }
2716}
2717
2718impl From<Vec<crate::lossy::Relation>> for Entry {
2719    fn from(relations: Vec<crate::lossy::Relation>) -> Self {
2720        let relations: Vec<Relation> = relations.into_iter().map(|r| r.into()).collect();
2721        Entry::from(relations)
2722    }
2723}
2724
2725#[cfg(test)]
2726mod tests {
2727    use super::*;
2728
2729    #[test]
2730    fn test_parse() {
2731        let input = "python3-dulwich";
2732        let parsed: Relations = input.parse().unwrap();
2733        assert_eq!(parsed.to_string(), input);
2734        assert_eq!(parsed.entries().count(), 1);
2735        let entry = parsed.entries().next().unwrap();
2736        assert_eq!(entry.to_string(), "python3-dulwich");
2737        assert_eq!(entry.relations().count(), 1);
2738        let relation = entry.relations().next().unwrap();
2739        assert_eq!(relation.to_string(), "python3-dulwich");
2740        assert_eq!(relation.version(), None);
2741
2742        let input = "python3-dulwich (>= 0.20.21)";
2743        let parsed: Relations = input.parse().unwrap();
2744        assert_eq!(parsed.to_string(), input);
2745        assert_eq!(parsed.entries().count(), 1);
2746        let entry = parsed.entries().next().unwrap();
2747        assert_eq!(entry.to_string(), "python3-dulwich (>= 0.20.21)");
2748        assert_eq!(entry.relations().count(), 1);
2749        let relation = entry.relations().next().unwrap();
2750        assert_eq!(relation.to_string(), "python3-dulwich (>= 0.20.21)");
2751        assert_eq!(
2752            relation.version(),
2753            Some((
2754                VersionConstraint::GreaterThanEqual,
2755                "0.20.21".parse().unwrap()
2756            ))
2757        );
2758    }
2759
2760    #[test]
2761    fn test_multiple() {
2762        let input = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)";
2763        let parsed: Relations = input.parse().unwrap();
2764        assert_eq!(parsed.to_string(), input);
2765        assert_eq!(parsed.entries().count(), 2);
2766        let entry = parsed.entries().next().unwrap();
2767        assert_eq!(entry.to_string(), "python3-dulwich (>= 0.20.21)");
2768        assert_eq!(entry.relations().count(), 1);
2769        let relation = entry.relations().next().unwrap();
2770        assert_eq!(relation.to_string(), "python3-dulwich (>= 0.20.21)");
2771        assert_eq!(
2772            relation.version(),
2773            Some((
2774                VersionConstraint::GreaterThanEqual,
2775                "0.20.21".parse().unwrap()
2776            ))
2777        );
2778        let entry = parsed.entries().nth(1).unwrap();
2779        assert_eq!(entry.to_string(), "python3-dulwich (<< 0.21)");
2780        assert_eq!(entry.relations().count(), 1);
2781        let relation = entry.relations().next().unwrap();
2782        assert_eq!(relation.to_string(), "python3-dulwich (<< 0.21)");
2783        assert_eq!(
2784            relation.version(),
2785            Some((VersionConstraint::LessThan, "0.21".parse().unwrap()))
2786        );
2787    }
2788
2789    #[test]
2790    fn test_architectures() {
2791        let input = "python3-dulwich [amd64 arm64 armhf i386 mips mips64el mipsel ppc64el s390x]";
2792        let parsed: Relations = input.parse().unwrap();
2793        assert_eq!(parsed.to_string(), input);
2794        assert_eq!(parsed.entries().count(), 1);
2795        let entry = parsed.entries().next().unwrap();
2796        assert_eq!(
2797            entry.to_string(),
2798            "python3-dulwich [amd64 arm64 armhf i386 mips mips64el mipsel ppc64el s390x]"
2799        );
2800        assert_eq!(entry.relations().count(), 1);
2801        let relation = entry.relations().next().unwrap();
2802        assert_eq!(
2803            relation.to_string(),
2804            "python3-dulwich [amd64 arm64 armhf i386 mips mips64el mipsel ppc64el s390x]"
2805        );
2806        assert_eq!(relation.version(), None);
2807        assert_eq!(
2808            relation.architectures().unwrap().collect::<Vec<_>>(),
2809            vec![
2810                "amd64", "arm64", "armhf", "i386", "mips", "mips64el", "mipsel", "ppc64el", "s390x"
2811            ]
2812            .into_iter()
2813            .map(|s| s.to_string())
2814            .collect::<Vec<_>>()
2815        );
2816    }
2817
2818    #[test]
2819    fn test_profiles() {
2820        let input = "foo (>= 1.0) [i386 arm] <!nocheck> <!cross>, bar";
2821        let parsed: Relations = input.parse().unwrap();
2822        assert_eq!(parsed.to_string(), input);
2823        assert_eq!(parsed.entries().count(), 2);
2824        let entry = parsed.entries().next().unwrap();
2825        assert_eq!(
2826            entry.to_string(),
2827            "foo (>= 1.0) [i386 arm] <!nocheck> <!cross>"
2828        );
2829        assert_eq!(entry.relations().count(), 1);
2830        let relation = entry.relations().next().unwrap();
2831        assert_eq!(
2832            relation.to_string(),
2833            "foo (>= 1.0) [i386 arm] <!nocheck> <!cross>"
2834        );
2835        assert_eq!(
2836            relation.version(),
2837            Some((VersionConstraint::GreaterThanEqual, "1.0".parse().unwrap()))
2838        );
2839        assert_eq!(
2840            relation.architectures().unwrap().collect::<Vec<_>>(),
2841            vec!["i386", "arm"]
2842                .into_iter()
2843                .map(|s| s.to_string())
2844                .collect::<Vec<_>>()
2845        );
2846        assert_eq!(
2847            relation.profiles().collect::<Vec<_>>(),
2848            vec![
2849                vec![BuildProfile::Disabled("nocheck".to_string())],
2850                vec![BuildProfile::Disabled("cross".to_string())]
2851            ]
2852        );
2853    }
2854
2855    #[test]
2856    fn test_substvar() {
2857        let input = "${shlibs:Depends}";
2858
2859        let (parsed, errors) = Relations::parse_relaxed(input, true);
2860        assert_eq!(errors, Vec::<String>::new());
2861        assert_eq!(parsed.to_string(), input);
2862        assert_eq!(parsed.entries().count(), 0);
2863
2864        assert_eq!(
2865            parsed.substvars().collect::<Vec<_>>(),
2866            vec!["${shlibs:Depends}"]
2867        );
2868    }
2869
2870    #[test]
2871    fn test_new() {
2872        let r = Relation::new(
2873            "samba",
2874            Some((VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap())),
2875        );
2876
2877        assert_eq!(r.to_string(), "samba (>= 2.0)");
2878    }
2879
2880    #[test]
2881    fn test_drop_constraint() {
2882        let mut r = Relation::new(
2883            "samba",
2884            Some((VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap())),
2885        );
2886
2887        r.drop_constraint();
2888
2889        assert_eq!(r.to_string(), "samba");
2890    }
2891
2892    #[test]
2893    fn test_simple() {
2894        let r = Relation::simple("samba");
2895
2896        assert_eq!(r.to_string(), "samba");
2897    }
2898
2899    #[test]
2900    fn test_remove_first_entry() {
2901        let mut rels: Relations = r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"#
2902            .parse()
2903            .unwrap();
2904        let removed = rels.remove_entry(0);
2905        assert_eq!(removed.to_string(), "python3-dulwich (>= 0.20.21)");
2906        assert_eq!(rels.to_string(), "python3-dulwich (<< 0.21)");
2907    }
2908
2909    #[test]
2910    fn test_remove_last_entry() {
2911        let mut rels: Relations = r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"#
2912            .parse()
2913            .unwrap();
2914        rels.remove_entry(1);
2915        assert_eq!(rels.to_string(), "python3-dulwich (>= 0.20.21)");
2916    }
2917
2918    #[test]
2919    fn test_remove_middle() {
2920        let mut rels: Relations =
2921            r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21), python3-dulwich (<< 0.22)"#
2922                .parse()
2923                .unwrap();
2924        rels.remove_entry(1);
2925        assert_eq!(
2926            rels.to_string(),
2927            "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.22)"
2928        );
2929    }
2930
2931    #[test]
2932    fn test_remove_added() {
2933        let mut rels: Relations = r#"python3-dulwich (>= 0.20.21)"#.parse().unwrap();
2934        let entry = Entry::from(vec![Relation::simple("python3-dulwich")]);
2935        rels.push(entry);
2936        rels.remove_entry(1);
2937        assert_eq!(rels.to_string(), "python3-dulwich (>= 0.20.21)");
2938    }
2939
2940    #[test]
2941    fn test_push() {
2942        let mut rels: Relations = r#"python3-dulwich (>= 0.20.21)"#.parse().unwrap();
2943        let entry = Entry::from(vec![Relation::simple("python3-dulwich")]);
2944        rels.push(entry);
2945        assert_eq!(
2946            rels.to_string(),
2947            "python3-dulwich (>= 0.20.21), python3-dulwich"
2948        );
2949    }
2950
2951    #[test]
2952    fn test_insert_with_custom_separator() {
2953        let mut rels: Relations = "python3".parse().unwrap();
2954        let entry = Entry::from(vec![Relation::simple("debhelper")]);
2955        rels.insert_with_separator(1, entry, Some("\n "));
2956        assert_eq!(rels.to_string(), "python3,\n debhelper");
2957    }
2958
2959    #[test]
2960    fn test_insert_with_wrap_and_sort_separator() {
2961        let mut rels: Relations = "python3".parse().unwrap();
2962        let entry = Entry::from(vec![Relation::simple("rustc")]);
2963        // Simulate wrap-and-sort -a style with field name "Depends: " (9 chars)
2964        rels.insert_with_separator(1, entry, Some("\n         "));
2965        assert_eq!(rels.to_string(), "python3,\n         rustc");
2966    }
2967
2968    #[test]
2969    fn test_push_from_empty() {
2970        let mut rels: Relations = "".parse().unwrap();
2971        let entry = Entry::from(vec![Relation::simple("python3-dulwich")]);
2972        rels.push(entry);
2973        assert_eq!(rels.to_string(), "python3-dulwich");
2974    }
2975
2976    #[test]
2977    fn test_insert() {
2978        let mut rels: Relations = r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"#
2979            .parse()
2980            .unwrap();
2981        let entry = Entry::from(vec![Relation::simple("python3-dulwich")]);
2982        rels.insert(1, entry);
2983        assert_eq!(
2984            rels.to_string(),
2985            "python3-dulwich (>= 0.20.21), python3-dulwich, python3-dulwich (<< 0.21)"
2986        );
2987    }
2988
2989    #[test]
2990    fn test_insert_at_start() {
2991        let mut rels: Relations = r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"#
2992            .parse()
2993            .unwrap();
2994        let entry = Entry::from(vec![Relation::simple("python3-dulwich")]);
2995        rels.insert(0, entry);
2996        assert_eq!(
2997            rels.to_string(),
2998            "python3-dulwich, python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"
2999        );
3000    }
3001
3002    #[test]
3003    fn test_insert_after_error() {
3004        let (mut rels, errors) = Relations::parse_relaxed("@foo@, debhelper (>= 1.0)", false);
3005        assert_eq!(
3006            errors,
3007            vec![
3008                "expected $ or identifier but got ERROR",
3009                "expected comma or end of file but got Some(IDENT)",
3010                "expected $ or identifier but got ERROR"
3011            ]
3012        );
3013        let entry = Entry::from(vec![Relation::simple("bar")]);
3014        rels.push(entry);
3015        assert_eq!(rels.to_string(), "@foo@, debhelper (>= 1.0), bar");
3016    }
3017
3018    #[test]
3019    fn test_insert_before_error() {
3020        let (mut rels, errors) = Relations::parse_relaxed("debhelper (>= 1.0), @foo@, bla", false);
3021        assert_eq!(
3022            errors,
3023            vec![
3024                "expected $ or identifier but got ERROR",
3025                "expected comma or end of file but got Some(IDENT)",
3026                "expected $ or identifier but got ERROR"
3027            ]
3028        );
3029        let entry = Entry::from(vec![Relation::simple("bar")]);
3030        rels.insert(0, entry);
3031        assert_eq!(rels.to_string(), "bar, debhelper (>= 1.0), @foo@, bla");
3032    }
3033
3034    #[test]
3035    fn test_replace() {
3036        let mut rels: Relations = r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"#
3037            .parse()
3038            .unwrap();
3039        let entry = Entry::from(vec![Relation::simple("python3-dulwich")]);
3040        rels.replace(1, entry);
3041        assert_eq!(
3042            rels.to_string(),
3043            "python3-dulwich (>= 0.20.21), python3-dulwich"
3044        );
3045    }
3046
3047    #[test]
3048    fn test_relation_from_entries() {
3049        let entries = vec![
3050            Entry::from(vec![Relation::simple("python3-dulwich")]),
3051            Entry::from(vec![Relation::simple("python3-breezy")]),
3052        ];
3053        let rels: Relations = entries.into();
3054        assert_eq!(rels.entries().count(), 2);
3055        assert_eq!(rels.to_string(), "python3-dulwich, python3-breezy");
3056    }
3057
3058    #[test]
3059    fn test_entry_from_relations() {
3060        let relations = vec![
3061            Relation::simple("python3-dulwich"),
3062            Relation::simple("python3-breezy"),
3063        ];
3064        let entry: Entry = relations.into();
3065        assert_eq!(entry.relations().count(), 2);
3066        assert_eq!(entry.to_string(), "python3-dulwich | python3-breezy");
3067    }
3068
3069    #[test]
3070    fn test_parse_entry() {
3071        let parsed: Entry = "python3-dulwich (>= 0.20.21) | bar".parse().unwrap();
3072        assert_eq!(parsed.to_string(), "python3-dulwich (>= 0.20.21) | bar");
3073        assert_eq!(parsed.relations().count(), 2);
3074
3075        assert_eq!(
3076            "foo, bar".parse::<Entry>().unwrap_err(),
3077            "Multiple entries found"
3078        );
3079        assert_eq!("".parse::<Entry>().unwrap_err(), "No entry found");
3080    }
3081
3082    #[test]
3083    fn test_parse_relation() {
3084        let parsed: Relation = "python3-dulwich (>= 0.20.21)".parse().unwrap();
3085        assert_eq!(parsed.to_string(), "python3-dulwich (>= 0.20.21)");
3086        assert_eq!(
3087            parsed.version(),
3088            Some((
3089                VersionConstraint::GreaterThanEqual,
3090                "0.20.21".parse().unwrap()
3091            ))
3092        );
3093        assert_eq!(
3094            "foo | bar".parse::<Relation>().unwrap_err(),
3095            "Multiple relations found"
3096        );
3097        assert_eq!("".parse::<Relation>().unwrap_err(), "No entry found");
3098    }
3099
3100    #[test]
3101    fn test_special() {
3102        let parsed: Relation = "librust-breezyshim+dirty-tracker-dev:amd64 (>= 0.1.138-~~)"
3103            .parse()
3104            .unwrap();
3105        assert_eq!(
3106            parsed.to_string(),
3107            "librust-breezyshim+dirty-tracker-dev:amd64 (>= 0.1.138-~~)"
3108        );
3109        assert_eq!(
3110            parsed.version(),
3111            Some((
3112                VersionConstraint::GreaterThanEqual,
3113                "0.1.138-~~".parse().unwrap()
3114            ))
3115        );
3116        assert_eq!(parsed.archqual(), Some("amd64".to_string()));
3117        assert_eq!(parsed.name(), "librust-breezyshim+dirty-tracker-dev");
3118    }
3119
3120    #[test]
3121    fn test_relations_satisfied_by() {
3122        let rels: Relations = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"
3123            .parse()
3124            .unwrap();
3125        let satisfied = |name: &str| -> Option<debversion::Version> {
3126            match name {
3127                "python3-dulwich" => Some("0.20.21".parse().unwrap()),
3128                _ => None,
3129            }
3130        };
3131        assert!(rels.satisfied_by(satisfied));
3132
3133        let satisfied = |name: &str| match name {
3134            "python3-dulwich" => Some("0.21".parse().unwrap()),
3135            _ => None,
3136        };
3137        assert!(!rels.satisfied_by(satisfied));
3138
3139        let satisfied = |name: &str| match name {
3140            "python3-dulwich" => Some("0.20.20".parse().unwrap()),
3141            _ => None,
3142        };
3143        assert!(!rels.satisfied_by(satisfied));
3144    }
3145
3146    #[test]
3147    fn test_entry_satisfied_by() {
3148        let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
3149            .parse()
3150            .unwrap();
3151        let satisfied = |name: &str| -> Option<debversion::Version> {
3152            match name {
3153                "python3-dulwich" => Some("0.20.21".parse().unwrap()),
3154                _ => None,
3155            }
3156        };
3157        assert!(entry.satisfied_by(satisfied));
3158        let satisfied = |name: &str| -> Option<debversion::Version> {
3159            match name {
3160                "python3-dulwich" => Some("0.18".parse().unwrap()),
3161                _ => None,
3162            }
3163        };
3164        assert!(!entry.satisfied_by(satisfied));
3165    }
3166
3167    #[test]
3168    fn test_wrap_and_sort_relation() {
3169        let relation: Relation = "   python3-dulwich   (>= 11) [  amd64 ] <  lala>"
3170            .parse()
3171            .unwrap();
3172
3173        let wrapped = relation.wrap_and_sort();
3174
3175        assert_eq!(
3176            wrapped.to_string(),
3177            "python3-dulwich (>= 11) [amd64] <lala>"
3178        );
3179    }
3180
3181    #[test]
3182    fn test_wrap_and_sort_relations() {
3183        let entry: Relations =
3184            "python3-dulwich (>= 0.20.21)   | bar, \n\n\n\npython3-dulwich (<< 0.21)"
3185                .parse()
3186                .unwrap();
3187
3188        let wrapped = entry.wrap_and_sort();
3189
3190        assert_eq!(
3191            wrapped.to_string(),
3192            "bar | python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"
3193        );
3194    }
3195
3196    #[cfg(feature = "serde")]
3197    #[test]
3198    fn test_serialize_relations() {
3199        let relations: Relations = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"
3200            .parse()
3201            .unwrap();
3202        let serialized = serde_json::to_string(&relations).unwrap();
3203        assert_eq!(
3204            serialized,
3205            r#""python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)""#
3206        );
3207    }
3208
3209    #[cfg(feature = "serde")]
3210    #[test]
3211    fn test_deserialize_relations() {
3212        let relations: Relations = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"
3213            .parse()
3214            .unwrap();
3215        let serialized = serde_json::to_string(&relations).unwrap();
3216        let deserialized: Relations = serde_json::from_str(&serialized).unwrap();
3217        assert_eq!(deserialized.to_string(), relations.to_string());
3218    }
3219
3220    #[cfg(feature = "serde")]
3221    #[test]
3222    fn test_serialize_relation() {
3223        let relation: Relation = "python3-dulwich (>= 0.20.21)".parse().unwrap();
3224        let serialized = serde_json::to_string(&relation).unwrap();
3225        assert_eq!(serialized, r#""python3-dulwich (>= 0.20.21)""#);
3226    }
3227
3228    #[cfg(feature = "serde")]
3229    #[test]
3230    fn test_deserialize_relation() {
3231        let relation: Relation = "python3-dulwich (>= 0.20.21)".parse().unwrap();
3232        let serialized = serde_json::to_string(&relation).unwrap();
3233        let deserialized: Relation = serde_json::from_str(&serialized).unwrap();
3234        assert_eq!(deserialized.to_string(), relation.to_string());
3235    }
3236
3237    #[cfg(feature = "serde")]
3238    #[test]
3239    fn test_serialize_entry() {
3240        let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
3241            .parse()
3242            .unwrap();
3243        let serialized = serde_json::to_string(&entry).unwrap();
3244        assert_eq!(
3245            serialized,
3246            r#""python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)""#
3247        );
3248    }
3249
3250    #[cfg(feature = "serde")]
3251    #[test]
3252    fn test_deserialize_entry() {
3253        let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
3254            .parse()
3255            .unwrap();
3256        let serialized = serde_json::to_string(&entry).unwrap();
3257        let deserialized: Entry = serde_json::from_str(&serialized).unwrap();
3258        assert_eq!(deserialized.to_string(), entry.to_string());
3259    }
3260
3261    #[test]
3262    fn test_remove_first_relation() {
3263        let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
3264            .parse()
3265            .unwrap();
3266        let mut rel = entry.relations().next().unwrap();
3267        rel.remove();
3268        assert_eq!(entry.to_string(), "python3-dulwich (<< 0.18)");
3269    }
3270
3271    #[test]
3272    fn test_remove_last_relation() {
3273        let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
3274            .parse()
3275            .unwrap();
3276        let mut rel = entry.relations().nth(1).unwrap();
3277        rel.remove();
3278        assert_eq!(entry.to_string(), "python3-dulwich (>= 0.20.21)");
3279    }
3280
3281    #[test]
3282    fn test_remove_only_relation() {
3283        let entry: Entry = "python3-dulwich (>= 0.20.21)".parse().unwrap();
3284        let mut rel = entry.relations().next().unwrap();
3285        rel.remove();
3286        assert_eq!(entry.to_string(), "");
3287    }
3288
3289    #[test]
3290    fn test_relations_is_empty() {
3291        let entry: Relations = "python3-dulwich (>= 0.20.21)".parse().unwrap();
3292        assert!(!entry.is_empty());
3293        assert_eq!(1, entry.len());
3294        let mut rel = entry.entries().next().unwrap();
3295        rel.remove();
3296        assert!(entry.is_empty());
3297        assert_eq!(0, entry.len());
3298    }
3299
3300    #[test]
3301    fn test_entry_is_empty() {
3302        let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
3303            .parse()
3304            .unwrap();
3305        assert!(!entry.is_empty());
3306        assert_eq!(2, entry.len());
3307        let mut rel = entry.relations().next().unwrap();
3308        rel.remove();
3309        assert!(!entry.is_empty());
3310        assert_eq!(1, entry.len());
3311        let mut rel = entry.relations().next().unwrap();
3312        rel.remove();
3313        assert!(entry.is_empty());
3314        assert_eq!(0, entry.len());
3315    }
3316
3317    #[test]
3318    fn test_relation_set_version() {
3319        let mut rel: Relation = "samba".parse().unwrap();
3320        rel.set_version(None);
3321        assert_eq!("samba", rel.to_string());
3322        rel.set_version(Some((
3323            VersionConstraint::GreaterThanEqual,
3324            "2.0".parse().unwrap(),
3325        )));
3326        assert_eq!("samba (>= 2.0)", rel.to_string());
3327        rel.set_version(None);
3328        assert_eq!("samba", rel.to_string());
3329        rel.set_version(Some((
3330            VersionConstraint::GreaterThanEqual,
3331            "2.0".parse().unwrap(),
3332        )));
3333        rel.set_version(Some((
3334            VersionConstraint::GreaterThanEqual,
3335            "1.1".parse().unwrap(),
3336        )));
3337        assert_eq!("samba (>= 1.1)", rel.to_string());
3338    }
3339
3340    #[test]
3341    fn test_replace_relation() {
3342        let mut entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
3343            .parse()
3344            .unwrap();
3345        let new_rel = Relation::simple("python3-breezy");
3346        entry.replace(0, new_rel);
3347        assert_eq!(
3348            entry.to_string(),
3349            "python3-breezy | python3-dulwich (<< 0.18)"
3350        );
3351    }
3352
3353    #[test]
3354    fn test_entry_push_relation() {
3355        let relations: Relations = "python3-dulwich (>= 0.20.21)".parse().unwrap();
3356        let new_rel = Relation::simple("python3-breezy");
3357        let mut entry = relations.entries().next().unwrap();
3358        entry.push(new_rel);
3359        assert_eq!(
3360            entry.to_string(),
3361            "python3-dulwich (>= 0.20.21) | python3-breezy"
3362        );
3363        assert_eq!(
3364            relations.to_string(),
3365            "python3-dulwich (>= 0.20.21) | python3-breezy"
3366        );
3367    }
3368
3369    #[test]
3370    fn test_relations_remove_empty_entry() {
3371        let (mut relations, errors) = Relations::parse_relaxed("foo, , bar, ", false);
3372        assert_eq!(errors, Vec::<String>::new());
3373        assert_eq!(relations.to_string(), "foo, , bar, ");
3374        assert_eq!(relations.len(), 2);
3375        assert_eq!(
3376            relations.entries().next().unwrap().to_string(),
3377            "foo".to_string()
3378        );
3379        assert_eq!(
3380            relations.entries().nth(1).unwrap().to_string(),
3381            "bar".to_string()
3382        );
3383        relations.remove_entry(1);
3384        assert_eq!(relations.to_string(), "foo, , ");
3385    }
3386
3387    #[test]
3388    fn test_entry_remove_relation() {
3389        let entry: Entry = "python3-dulwich | samba".parse().unwrap();
3390        let removed = entry.remove_relation(0);
3391        assert_eq!(removed.to_string(), "python3-dulwich");
3392        assert_eq!(entry.to_string(), "samba");
3393    }
3394
3395    #[test]
3396    fn test_wrap_and_sort_removes_empty_entries() {
3397        let relations: Relations = "foo, , bar, ".parse().unwrap();
3398        let wrapped = relations.wrap_and_sort();
3399        assert_eq!(wrapped.to_string(), "bar, foo");
3400    }
3401
3402    #[test]
3403    fn test_set_archqual() {
3404        let entry: Entry = "python3-dulwich | samba".parse().unwrap();
3405        let mut rel = entry.relations().next().unwrap();
3406        rel.set_archqual("amd64");
3407        assert_eq!(rel.to_string(), "python3-dulwich:amd64");
3408        assert_eq!(rel.archqual(), Some("amd64".to_string()));
3409        assert_eq!(entry.to_string(), "python3-dulwich:amd64 | samba");
3410        rel.set_archqual("i386");
3411        assert_eq!(rel.to_string(), "python3-dulwich:i386");
3412        assert_eq!(rel.archqual(), Some("i386".to_string()));
3413        assert_eq!(entry.to_string(), "python3-dulwich:i386 | samba");
3414    }
3415
3416    #[test]
3417    fn test_set_architectures() {
3418        let mut relation = Relation::simple("samba");
3419        relation.set_architectures(vec!["amd64", "i386"].into_iter());
3420        assert_eq!(relation.to_string(), "samba [amd64 i386]");
3421    }
3422
3423    #[test]
3424    fn test_ensure_minimum_version_add_new() {
3425        let mut relations: Relations = "python3".parse().unwrap();
3426        relations.ensure_minimum_version("debhelper", &"12".parse().unwrap());
3427        assert_eq!(relations.to_string(), "python3, debhelper (>= 12)");
3428    }
3429
3430    #[test]
3431    fn test_ensure_minimum_version_update() {
3432        let mut relations: Relations = "debhelper (>= 9)".parse().unwrap();
3433        relations.ensure_minimum_version("debhelper", &"12".parse().unwrap());
3434        assert_eq!(relations.to_string(), "debhelper (>= 12)");
3435    }
3436
3437    #[test]
3438    fn test_ensure_minimum_version_no_change() {
3439        let mut relations: Relations = "debhelper (>= 13)".parse().unwrap();
3440        relations.ensure_minimum_version("debhelper", &"12".parse().unwrap());
3441        assert_eq!(relations.to_string(), "debhelper (>= 13)");
3442    }
3443
3444    #[test]
3445    fn test_ensure_minimum_version_no_version() {
3446        let mut relations: Relations = "debhelper".parse().unwrap();
3447        relations.ensure_minimum_version("debhelper", &"12".parse().unwrap());
3448        assert_eq!(relations.to_string(), "debhelper (>= 12)");
3449    }
3450
3451    #[test]
3452    fn test_ensure_exact_version_add_new() {
3453        let mut relations: Relations = "python3".parse().unwrap();
3454        relations.ensure_exact_version("debhelper", &"12".parse().unwrap());
3455        assert_eq!(relations.to_string(), "python3, debhelper (= 12)");
3456    }
3457
3458    #[test]
3459    fn test_ensure_exact_version_update() {
3460        let mut relations: Relations = "debhelper (>= 9)".parse().unwrap();
3461        relations.ensure_exact_version("debhelper", &"12".parse().unwrap());
3462        assert_eq!(relations.to_string(), "debhelper (= 12)");
3463    }
3464
3465    #[test]
3466    fn test_ensure_exact_version_no_change() {
3467        let mut relations: Relations = "debhelper (= 12)".parse().unwrap();
3468        relations.ensure_exact_version("debhelper", &"12".parse().unwrap());
3469        assert_eq!(relations.to_string(), "debhelper (= 12)");
3470    }
3471
3472    #[test]
3473    fn test_ensure_some_version_add_new() {
3474        let mut relations: Relations = "python3".parse().unwrap();
3475        relations.ensure_some_version("debhelper");
3476        assert_eq!(relations.to_string(), "python3, debhelper");
3477    }
3478
3479    #[test]
3480    fn test_ensure_some_version_exists_with_version() {
3481        let mut relations: Relations = "debhelper (>= 12)".parse().unwrap();
3482        relations.ensure_some_version("debhelper");
3483        assert_eq!(relations.to_string(), "debhelper (>= 12)");
3484    }
3485
3486    #[test]
3487    fn test_ensure_some_version_exists_no_version() {
3488        let mut relations: Relations = "debhelper".parse().unwrap();
3489        relations.ensure_some_version("debhelper");
3490        assert_eq!(relations.to_string(), "debhelper");
3491    }
3492
3493    #[test]
3494    fn test_filter_entries_basic() {
3495        let mut relations: Relations = "python3, debhelper, rustc".parse().unwrap();
3496        relations.filter_entries(|entry| entry.relations().any(|r| r.name().starts_with("python")));
3497        assert_eq!(relations.to_string(), "python3");
3498    }
3499
3500    #[test]
3501    fn test_filter_entries_keep_all() {
3502        let mut relations: Relations = "python3, debhelper".parse().unwrap();
3503        relations.filter_entries(|_| true);
3504        assert_eq!(relations.to_string(), "python3, debhelper");
3505    }
3506
3507    #[test]
3508    fn test_filter_entries_remove_all() {
3509        let mut relations: Relations = "python3, debhelper".parse().unwrap();
3510        relations.filter_entries(|_| false);
3511        assert_eq!(relations.to_string(), "");
3512    }
3513
3514    #[test]
3515    fn test_filter_entries_keep_middle() {
3516        let mut relations: Relations = "aaa, bbb, ccc".parse().unwrap();
3517        relations.filter_entries(|entry| entry.relations().any(|r| r.name() == "bbb"));
3518        assert_eq!(relations.to_string(), "bbb");
3519    }
3520
3521    // Tests for new convenience methods
3522
3523    #[test]
3524    fn test_is_sorted_wrap_and_sort_order() {
3525        // Sorted according to WrapAndSortOrder
3526        let relations: Relations = "debhelper, python3, rustc".parse().unwrap();
3527        assert!(relations.is_sorted(&WrapAndSortOrder));
3528
3529        // Not sorted
3530        let relations: Relations = "rustc, debhelper, python3".parse().unwrap();
3531        assert!(!relations.is_sorted(&WrapAndSortOrder));
3532
3533        // Build systems first (sorted alphabetically within their group)
3534        let (relations, _) =
3535            Relations::parse_relaxed("cdbs, debhelper-compat, python3, ${misc:Depends}", true);
3536        assert!(relations.is_sorted(&WrapAndSortOrder));
3537    }
3538
3539    #[test]
3540    fn test_is_sorted_default_order() {
3541        // Sorted alphabetically
3542        let relations: Relations = "aaa, bbb, ccc".parse().unwrap();
3543        assert!(relations.is_sorted(&DefaultSortingOrder));
3544
3545        // Not sorted
3546        let relations: Relations = "ccc, aaa, bbb".parse().unwrap();
3547        assert!(!relations.is_sorted(&DefaultSortingOrder));
3548
3549        // Special items at end
3550        let (relations, _) = Relations::parse_relaxed("aaa, bbb, ${misc:Depends}", true);
3551        assert!(relations.is_sorted(&DefaultSortingOrder));
3552    }
3553
3554    #[test]
3555    fn test_is_sorted_with_substvars() {
3556        // Substvars should be ignored by DefaultSortingOrder
3557        let (relations, _) = Relations::parse_relaxed("python3, ${misc:Depends}, rustc", true);
3558        // This is considered sorted because ${misc:Depends} is ignored
3559        assert!(relations.is_sorted(&DefaultSortingOrder));
3560    }
3561
3562    #[test]
3563    fn test_drop_dependency_exists() {
3564        let mut relations: Relations = "python3, debhelper, rustc".parse().unwrap();
3565        assert!(relations.drop_dependency("debhelper"));
3566        assert_eq!(relations.to_string(), "python3, rustc");
3567    }
3568
3569    #[test]
3570    fn test_drop_dependency_not_exists() {
3571        let mut relations: Relations = "python3, rustc".parse().unwrap();
3572        assert!(!relations.drop_dependency("nonexistent"));
3573        assert_eq!(relations.to_string(), "python3, rustc");
3574    }
3575
3576    #[test]
3577    fn test_drop_dependency_only_item() {
3578        let mut relations: Relations = "python3".parse().unwrap();
3579        assert!(relations.drop_dependency("python3"));
3580        assert_eq!(relations.to_string(), "");
3581    }
3582
3583    #[test]
3584    fn test_add_dependency_to_empty() {
3585        let mut relations: Relations = "".parse().unwrap();
3586        let entry = Entry::from(Relation::simple("python3"));
3587        relations.add_dependency(entry, None);
3588        assert_eq!(relations.to_string(), "python3");
3589    }
3590
3591    #[test]
3592    fn test_add_dependency_sorted_position() {
3593        let mut relations: Relations = "debhelper, rustc".parse().unwrap();
3594        let entry = Entry::from(Relation::simple("python3"));
3595        relations.add_dependency(entry, None);
3596        // Should be inserted in sorted position
3597        assert_eq!(relations.to_string(), "debhelper, python3, rustc");
3598    }
3599
3600    #[test]
3601    fn test_add_dependency_explicit_position() {
3602        let mut relations: Relations = "python3, rustc".parse().unwrap();
3603        let entry = Entry::from(Relation::simple("debhelper"));
3604        relations.add_dependency(entry, Some(0));
3605        assert_eq!(relations.to_string(), "debhelper, python3, rustc");
3606    }
3607
3608    #[test]
3609    fn test_add_dependency_build_system_first() {
3610        let mut relations: Relations = "python3, rustc".parse().unwrap();
3611        let entry = Entry::from(Relation::simple("debhelper-compat"));
3612        relations.add_dependency(entry, None);
3613        // debhelper-compat should be inserted first (build system)
3614        assert_eq!(relations.to_string(), "debhelper-compat, python3, rustc");
3615    }
3616
3617    #[test]
3618    fn test_add_dependency_at_end() {
3619        let mut relations: Relations = "debhelper, python3".parse().unwrap();
3620        let entry = Entry::from(Relation::simple("zzz-package"));
3621        relations.add_dependency(entry, None);
3622        // Should be added at the end (alphabetically after python3)
3623        assert_eq!(relations.to_string(), "debhelper, python3, zzz-package");
3624    }
3625
3626    #[test]
3627    fn test_get_relation_exists() {
3628        let relations: Relations = "python3, debhelper (>= 12), rustc".parse().unwrap();
3629        let result = relations.get_relation("debhelper");
3630        assert!(result.is_ok());
3631        let (idx, entry) = result.unwrap();
3632        assert_eq!(idx, 1);
3633        assert_eq!(entry.to_string(), "debhelper (>= 12)");
3634    }
3635
3636    #[test]
3637    fn test_get_relation_not_exists() {
3638        let relations: Relations = "python3, rustc".parse().unwrap();
3639        let result = relations.get_relation("nonexistent");
3640        assert_eq!(result, Err("Package nonexistent not found".to_string()));
3641    }
3642
3643    #[test]
3644    fn test_get_relation_complex_rule() {
3645        let relations: Relations = "python3 | python3-minimal, rustc".parse().unwrap();
3646        let result = relations.get_relation("python3");
3647        assert_eq!(
3648            result,
3649            Err("Complex rule for python3, aborting".to_string())
3650        );
3651    }
3652
3653    #[test]
3654    fn test_iter_relations_for_simple() {
3655        let relations: Relations = "python3, debhelper, python3-dev".parse().unwrap();
3656        let entries: Vec<_> = relations.iter_relations_for("python3").collect();
3657        assert_eq!(entries.len(), 1);
3658        assert_eq!(entries[0].0, 0);
3659        assert_eq!(entries[0].1.to_string(), "python3");
3660    }
3661
3662    #[test]
3663    fn test_iter_relations_for_alternatives() {
3664        let relations: Relations = "python3 | python3-minimal, python3-dev".parse().unwrap();
3665        let entries: Vec<_> = relations.iter_relations_for("python3").collect();
3666        // Should find both the alternative entry and python3-dev is not included
3667        assert_eq!(entries.len(), 1);
3668        assert_eq!(entries[0].0, 0);
3669    }
3670
3671    #[test]
3672    fn test_iter_relations_for_not_found() {
3673        let relations: Relations = "python3, rustc".parse().unwrap();
3674        let entries: Vec<_> = relations.iter_relations_for("debhelper").collect();
3675        assert_eq!(entries.len(), 0);
3676    }
3677
3678    #[test]
3679    fn test_has_relation_exists() {
3680        let relations: Relations = "python3, debhelper, rustc".parse().unwrap();
3681        assert!(relations.has_relation("debhelper"));
3682        assert!(relations.has_relation("python3"));
3683        assert!(relations.has_relation("rustc"));
3684    }
3685
3686    #[test]
3687    fn test_has_relation_not_exists() {
3688        let relations: Relations = "python3, rustc".parse().unwrap();
3689        assert!(!relations.has_relation("debhelper"));
3690    }
3691
3692    #[test]
3693    fn test_has_relation_in_alternative() {
3694        let relations: Relations = "python3 | python3-minimal".parse().unwrap();
3695        assert!(relations.has_relation("python3"));
3696        assert!(relations.has_relation("python3-minimal"));
3697    }
3698
3699    #[test]
3700    fn test_sorting_order_wrap_and_sort_build_systems() {
3701        let order = WrapAndSortOrder;
3702        // Build systems should come before regular packages
3703        assert!(order.lt("debhelper", "python3"));
3704        assert!(order.lt("debhelper-compat", "rustc"));
3705        assert!(order.lt("cdbs", "aaa"));
3706        assert!(order.lt("dh-python", "python3"));
3707    }
3708
3709    #[test]
3710    fn test_sorting_order_wrap_and_sort_regular_packages() {
3711        let order = WrapAndSortOrder;
3712        // Regular packages sorted alphabetically
3713        assert!(order.lt("aaa", "bbb"));
3714        assert!(order.lt("python3", "rustc"));
3715        assert!(!order.lt("rustc", "python3"));
3716    }
3717
3718    #[test]
3719    fn test_sorting_order_wrap_and_sort_substvars() {
3720        let order = WrapAndSortOrder;
3721        // Substvars should come after regular packages
3722        assert!(order.lt("python3", "${misc:Depends}"));
3723        assert!(!order.lt("${misc:Depends}", "python3"));
3724        // But wrap-and-sort doesn't ignore them
3725        assert!(!order.ignore("${misc:Depends}"));
3726    }
3727
3728    #[test]
3729    fn test_sorting_order_default_special_items() {
3730        let order = DefaultSortingOrder;
3731        // Special items should come after regular items
3732        assert!(order.lt("python3", "${misc:Depends}"));
3733        assert!(order.lt("aaa", "@cdbs@"));
3734        // And should be ignored
3735        assert!(order.ignore("${misc:Depends}"));
3736        assert!(order.ignore("@cdbs@"));
3737        assert!(!order.ignore("python3"));
3738    }
3739
3740    #[test]
3741    fn test_is_special_package_name() {
3742        assert!(is_special_package_name("${misc:Depends}"));
3743        assert!(is_special_package_name("${shlibs:Depends}"));
3744        assert!(is_special_package_name("@cdbs@"));
3745        assert!(!is_special_package_name("python3"));
3746        assert!(!is_special_package_name("debhelper"));
3747    }
3748
3749    #[test]
3750    fn test_add_dependency_with_explicit_position() {
3751        // Test that add_dependency works with explicit position and preserves whitespace
3752        let mut relations: Relations = "python3,  rustc".parse().unwrap();
3753        let entry = Entry::from(Relation::simple("debhelper"));
3754        relations.add_dependency(entry, Some(1));
3755        // Should preserve the 2-space pattern from the original
3756        assert_eq!(relations.to_string(), "python3,  debhelper,  rustc");
3757    }
3758
3759    #[test]
3760    fn test_whitespace_detection_single_space() {
3761        let mut relations: Relations = "python3, rustc".parse().unwrap();
3762        let entry = Entry::from(Relation::simple("debhelper"));
3763        relations.add_dependency(entry, Some(1));
3764        assert_eq!(relations.to_string(), "python3, debhelper, rustc");
3765    }
3766
3767    #[test]
3768    fn test_whitespace_detection_multiple_spaces() {
3769        let mut relations: Relations = "python3,  rustc,  gcc".parse().unwrap();
3770        let entry = Entry::from(Relation::simple("debhelper"));
3771        relations.add_dependency(entry, Some(1));
3772        // Should detect and use the 2-space pattern
3773        assert_eq!(relations.to_string(), "python3,  debhelper,  rustc,  gcc");
3774    }
3775
3776    #[test]
3777    fn test_whitespace_detection_mixed_patterns() {
3778        // When patterns differ, use the most common one
3779        let mut relations: Relations = "a, b, c,  d, e".parse().unwrap();
3780        let entry = Entry::from(Relation::simple("x"));
3781        relations.push(entry);
3782        // Three single-space (after a, b, d), one double-space (after c)
3783        // Should use single space as it's most common
3784        assert_eq!(relations.to_string(), "a, b, c,  d, e, x");
3785    }
3786
3787    #[test]
3788    fn test_whitespace_detection_newlines() {
3789        let mut relations: Relations = "python3,\n rustc".parse().unwrap();
3790        let entry = Entry::from(Relation::simple("debhelper"));
3791        relations.add_dependency(entry, Some(1));
3792        // Detects full pattern including newline
3793        assert_eq!(relations.to_string(), "python3,\n debhelper,\n rustc");
3794    }
3795
3796    #[test]
3797    fn test_append_with_newline_no_trailing() {
3798        let mut relations: Relations = "foo,\n bar".parse().unwrap();
3799        let entry = Entry::from(Relation::simple("blah"));
3800        relations.add_dependency(entry, None);
3801        assert_eq!(relations.to_string(), "foo,\n bar,\n blah");
3802    }
3803
3804    #[test]
3805    fn test_append_with_trailing_newline() {
3806        let mut relations: Relations = "foo,\n bar\n".parse().unwrap();
3807        let entry = Entry::from(Relation::simple("blah"));
3808        relations.add_dependency(entry, None);
3809        assert_eq!(relations.to_string(), "foo,\n bar,\n blah");
3810    }
3811
3812    #[test]
3813    fn test_append_with_4_space_indent() {
3814        let mut relations: Relations = "foo,\n    bar".parse().unwrap();
3815        let entry = Entry::from(Relation::simple("blah"));
3816        relations.add_dependency(entry, None);
3817        assert_eq!(relations.to_string(), "foo,\n    bar,\n    blah");
3818    }
3819
3820    #[test]
3821    fn test_append_with_4_space_and_trailing_newline() {
3822        let mut relations: Relations = "foo,\n    bar\n".parse().unwrap();
3823        let entry = Entry::from(Relation::simple("blah"));
3824        relations.add_dependency(entry, None);
3825        assert_eq!(relations.to_string(), "foo,\n    bar,\n    blah");
3826    }
3827
3828    #[test]
3829    fn test_odd_syntax_append_no_trailing() {
3830        let mut relations: Relations = "\n foo\n , bar".parse().unwrap();
3831        let entry = Entry::from(Relation::simple("blah"));
3832        relations.add_dependency(entry, None);
3833        assert_eq!(relations.to_string(), "\n foo\n , bar\n , blah");
3834    }
3835
3836    #[test]
3837    fn test_odd_syntax_append_with_trailing() {
3838        let mut relations: Relations = "\n foo\n , bar\n".parse().unwrap();
3839        let entry = Entry::from(Relation::simple("blah"));
3840        relations.add_dependency(entry, None);
3841        assert_eq!(relations.to_string(), "\n foo\n , bar\n , blah");
3842    }
3843
3844    #[test]
3845    fn test_insert_at_1_no_trailing() {
3846        let mut relations: Relations = "foo,\n bar".parse().unwrap();
3847        let entry = Entry::from(Relation::simple("blah"));
3848        relations.add_dependency(entry, Some(1));
3849        assert_eq!(relations.to_string(), "foo,\n blah,\n bar");
3850    }
3851
3852    #[test]
3853    fn test_insert_at_1_with_trailing() {
3854        let mut relations: Relations = "foo,\n bar\n".parse().unwrap();
3855        let entry = Entry::from(Relation::simple("blah"));
3856        relations.add_dependency(entry, Some(1));
3857        assert_eq!(relations.to_string(), "foo,\n blah,\n bar");
3858    }
3859
3860    #[test]
3861    fn test_odd_syntax_insert_at_1() {
3862        let mut relations: Relations = "\n foo\n , bar\n".parse().unwrap();
3863        let entry = Entry::from(Relation::simple("blah"));
3864        relations.add_dependency(entry, Some(1));
3865        assert_eq!(relations.to_string(), "\n foo\n , blah\n , bar");
3866    }
3867}