Skip to main content

r_description/
lossless.rs

1//! A library for parsing and manipulating R DESCRIPTION files.
2//!
3//! This module allows losslessly parsing R DESCRIPTION files into a structured representation.
4//! This allows modification of individual fields while preserving the
5//! original formatting of the file.
6//!
7//! This parser also allows for syntax errors in the input, and will attempt to parse as much as
8//! possible.
9//!
10//! See https://r-pkgs.org/description.html for more information.
11
12use crate::RCode;
13use deb822_lossless::Paragraph;
14pub use relations::{Relation, Relations};
15
16/// R DESCRIPTION file
17pub struct RDescription(Paragraph);
18
19impl std::fmt::Display for RDescription {
20    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
21        write!(f, "{}", self.0)
22    }
23}
24
25impl Default for RDescription {
26    fn default() -> Self {
27        Self(Paragraph::new())
28    }
29}
30
31#[derive(Debug)]
32/// Error type for parsing DESCRIPTION files
33pub enum Error {
34    /// I/O error
35    Io(std::io::Error),
36
37    /// Parse error
38    Parse(deb822_lossless::ParseError),
39}
40
41impl std::fmt::Display for Error {
42    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
43        match self {
44            Self::Io(e) => write!(f, "IO error: {e}"),
45            Self::Parse(e) => write!(f, "Parse error: {e}"),
46        }
47    }
48}
49
50impl std::error::Error for Error {}
51
52impl From<deb822_lossless::ParseError> for Error {
53    fn from(e: deb822_lossless::ParseError) -> Self {
54        Self::Parse(e)
55    }
56}
57
58impl From<std::io::Error> for Error {
59    fn from(e: std::io::Error) -> Self {
60        Self::Io(e)
61    }
62}
63
64impl std::str::FromStr for RDescription {
65    type Err = Error;
66
67    fn from_str(s: &str) -> Result<Self, Self::Err> {
68        Ok(Self(Paragraph::from_str(s)?))
69    }
70}
71
72impl RDescription {
73    /// Create a new empty R DESCRIPTION file
74    pub fn new() -> Self {
75        Self(Paragraph::new())
76    }
77
78    /// Return the package name
79    pub fn package(&self) -> Option<String> {
80        self.0.get("Package")
81    }
82
83    /// Set the package name
84    pub fn set_package(&mut self, package: &str) {
85        self.0.insert("Package", package);
86    }
87
88    /// One line description of the package, and is often shown in a package listing
89    ///
90    /// It should be plain text (no markup), capitalised like a title, and NOT end in a period.
91    /// Keep it short: listings will often truncate the title to 65 characters.
92    pub fn title(&self) -> Option<String> {
93        self.0.get("Title")
94    }
95
96    /// Return the maintainer of the package
97    pub fn maintainer(&self) -> Option<String> {
98        self.0.get("Maintainer")
99    }
100
101    /// Set the maintainer of the package
102    pub fn set_maintainer(&mut self, maintainer: &str) {
103        self.0.insert("Maintainer", maintainer);
104    }
105
106    /// Return the authors of the package
107    pub fn authors(&self) -> Option<RCode> {
108        self.0.get("Authors@R").map(|s| s.parse().unwrap())
109    }
110
111    /// Set the authors of the package
112    pub fn set_authors(&mut self, authors: &RCode) {
113        self.0.insert("Authors@R", &authors.to_string());
114    }
115
116    /// Set the title of the package
117    pub fn set_title(&mut self, title: &str) {
118        self.0.insert("Title", title);
119    }
120
121    /// Return the description of the package
122    pub fn description(&self) -> Option<String> {
123        self.0.get("Description")
124    }
125
126    /// Set the description of the package
127    pub fn set_description(&mut self, description: &str) {
128        self.0.insert("Description", description);
129    }
130
131    /// Return the version of the package
132    pub fn version(&self) -> Option<String> {
133        self.0.get("Version")
134    }
135
136    /// Set the version of the package
137    pub fn set_version(&mut self, version: &str) {
138        self.0.insert("Version", version);
139    }
140
141    /// Return the encoding of the description file
142    pub fn encoding(&self) -> Option<String> {
143        self.0.get("Encoding")
144    }
145
146    /// Set the encoding of the description file
147    pub fn set_encoding(&mut self, encoding: &str) {
148        self.0.insert("Encoding", encoding);
149    }
150
151    /// Return the license of the package
152    pub fn license(&self) -> Option<String> {
153        self.0.get("License")
154    }
155
156    /// Set the license of the package
157    pub fn set_license(&mut self, license: &str) {
158        self.0.insert("License", license);
159    }
160
161    /// Return the roxygen note
162    pub fn roxygen_note(&self) -> Option<String> {
163        self.0.get("RoxygenNote")
164    }
165
166    /// Set the roxygen note
167    pub fn set_roxygen_note(&mut self, roxygen_note: &str) {
168        self.0.insert("RoxygenNote", roxygen_note);
169    }
170
171    /// Return the roxygen version
172    pub fn roxygen(&self) -> Option<String> {
173        self.0.get("Roxygen")
174    }
175
176    /// Set the roxygen version
177    pub fn set_roxygen(&mut self, roxygen: &str) {
178        self.0.insert("Roxygen", roxygen);
179    }
180
181    /// Return the URL field
182    pub fn url(&self) -> Option<String> {
183        // TODO: parse list of URLs, separated by commas
184        self.0.get("URL")
185    }
186
187    /// Set the URL field
188    pub fn set_url(&mut self, url: &str) {
189        // TODO: parse list of URLs, separated by commas
190        self.0.insert("URL", url);
191    }
192
193    /// Return the bug reports URL
194    pub fn bug_reports(&self) -> Option<url::Url> {
195        self.0
196            .get("BugReports")
197            .map(|s| url::Url::parse(s.as_str()).unwrap())
198    }
199
200    /// Set the bug reports URL
201    pub fn set_bug_reports(&mut self, bug_reports: &url::Url) {
202        self.0.insert("BugReports", bug_reports.as_str());
203    }
204
205    /// Return the imports field
206    pub fn imports(&self) -> Option<Vec<String>> {
207        self.0
208            .get("Imports")
209            .map(|s| s.split(',').map(|s| s.trim().to_string()).collect())
210    }
211
212    /// Set the imports field
213    pub fn set_imports(&mut self, imports: &[&str]) {
214        self.0.insert("Imports", &imports.join(", "));
215    }
216
217    /// Return the suggests field
218    pub fn suggests(&self) -> Option<Relations> {
219        self.0.get("Suggests").map(|s| s.parse().unwrap())
220    }
221
222    /// Set the suggests field
223    pub fn set_suggests(&mut self, suggests: Relations) {
224        self.0.insert("Suggests", &suggests.to_string());
225    }
226
227    /// Return the depends field
228    pub fn depends(&self) -> Option<Relations> {
229        self.0.get("Depends").map(|s| s.parse().unwrap())
230    }
231
232    /// Set the depends field
233    pub fn set_depends(&mut self, depends: Relations) {
234        self.0.insert("Depends", &depends.to_string());
235    }
236
237    /// Return the linking-to field
238    pub fn linking_to(&self) -> Option<Vec<String>> {
239        self.0
240            .get("LinkingTo")
241            .map(|s| s.split(',').map(|s| s.trim().to_string()).collect())
242    }
243
244    /// Set the linking-to field
245    pub fn set_linking_to(&mut self, linking_to: &[&str]) {
246        self.0.insert("LinkingTo", &linking_to.join(", "));
247    }
248
249    /// Return the lazy data field
250    pub fn lazy_data(&self) -> Option<bool> {
251        self.0.get("LazyData").map(|s| s == "true")
252    }
253
254    /// Set the lazy data field
255    pub fn set_lazy_data(&mut self, lazy_data: bool) {
256        self.0
257            .insert("LazyData", if lazy_data { "true" } else { "false" });
258    }
259
260    /// Return the collate field
261    pub fn collate(&self) -> Option<String> {
262        self.0.get("Collate")
263    }
264
265    /// Set the collate field
266    pub fn set_collate(&mut self, collate: &str) {
267        self.0.insert("Collate", collate);
268    }
269
270    /// Return the vignette builder field
271    pub fn vignette_builder(&self) -> Option<Vec<String>> {
272        self.0
273            .get("VignetteBuilder")
274            .map(|s| s.split(',').map(|s| s.trim().to_string()).collect())
275    }
276
277    /// Set the vignette builder field
278    pub fn set_vignette_builder(&mut self, vignette_builder: &[&str]) {
279        self.0
280            .insert("VignetteBuilder", &vignette_builder.join(", "));
281    }
282
283    /// Return the system requirements field
284    pub fn system_requirements(&self) -> Option<Vec<String>> {
285        self.0
286            .get("SystemRequirements")
287            .map(|s| s.split(',').map(|s| s.trim().to_string()).collect())
288    }
289
290    /// Set the system requirements field
291    pub fn set_system_requirements(&mut self, system_requirements: &[&str]) {
292        self.0
293            .insert("SystemRequirements", &system_requirements.join(", "));
294    }
295
296    /// Return the date field
297    pub fn date(&self) -> Option<String> {
298        self.0.get("Date")
299    }
300
301    /// Set the date field
302    pub fn set_date(&mut self, date: &str) {
303        self.0.insert("Date", date);
304    }
305
306    /// The R Repository to use for this package.
307    ///
308    /// E.g. "CRAN" or "Bioconductor"
309    pub fn repository(&self) -> Option<String> {
310        self.0.get("Repository")
311    }
312
313    /// Set the R Repository to use for this package.
314    pub fn set_repository(&mut self, repository: &str) {
315        self.0.insert("Repository", repository);
316    }
317}
318
319pub mod relations {
320    //! Parser for relationship fields like `Depends`, `Recommends`, etc.
321    //!
322    //! # Example
323    //! ```
324    //! use r_description::lossless::{Relations, Relation};
325    //! use r_description::VersionConstraint;
326    //!
327    //! let mut relations: Relations = r"cli (>= 0.19.0), R".parse().unwrap();
328    //! assert_eq!(relations.to_string(), "cli (>= 0.19.0), R");
329    //! assert!(relations.satisfied_by(|name: &str| -> Option<r_description::Version> {
330    //!    match name {
331    //!    "cli" => Some("0.19.0".parse().unwrap()),
332    //!    "R" => Some("2.25.1".parse().unwrap()),
333    //!    _ => None
334    //!    }}));
335    //! relations.remove_relation(1);
336    //! assert_eq!(relations.to_string(), "cli (>= 0.19.0)");
337    //! ```
338    use crate::relations::SyntaxKind::{self, *};
339    use crate::relations::VersionConstraint;
340    use crate::version::Version;
341    use rowan::{Direction, NodeOrToken};
342
343    /// Error type for parsing relations fields
344    #[derive(Debug, Clone, PartialEq, Eq, Hash)]
345    pub struct ParseError(Vec<String>);
346
347    impl std::fmt::Display for ParseError {
348        fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
349            for err in &self.0 {
350                writeln!(f, "{err}")?;
351            }
352            Ok(())
353        }
354    }
355
356    impl std::error::Error for ParseError {}
357
358    /// Second, implementing the `Language` trait teaches rowan to convert between
359    /// these two SyntaxKind types, allowing for a nicer SyntaxNode API where
360    /// "kinds" are values from our `enum SyntaxKind`, instead of plain u16 values.
361    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
362    enum Lang {}
363    impl rowan::Language for Lang {
364        type Kind = SyntaxKind;
365        fn kind_from_raw(raw: rowan::SyntaxKind) -> Self::Kind {
366            unsafe { std::mem::transmute::<u16, SyntaxKind>(raw.0) }
367        }
368        fn kind_to_raw(kind: Self::Kind) -> rowan::SyntaxKind {
369            kind.into()
370        }
371    }
372
373    /// GreenNode is an immutable tree, which is cheap to change,
374    /// but doesn't contain offsets and parent pointers.
375    use rowan::{GreenNode, GreenToken};
376
377    /// You can construct GreenNodes by hand, but a builder
378    /// is helpful for top-down parsers: it maintains a stack
379    /// of currently in-progress nodes
380    use rowan::GreenNodeBuilder;
381
382    /// Emit one token per character of a version constraint into an in-progress
383    /// CONSTRAINT node, matching what the lexer would have produced for the same
384    /// operator.
385    fn push_constraint_tokens(builder: &mut GreenNodeBuilder, vc: &VersionConstraint) {
386        for c in vc.to_string().chars() {
387            let kind = match c {
388                '>' => R_ANGLE,
389                '<' => L_ANGLE,
390                '=' => EQUAL,
391                '!' => NOT,
392                _ => unreachable!("unexpected constraint character {c:?}"),
393            };
394            builder.token(kind.into(), c.to_string().as_str());
395        }
396    }
397
398    /// The parse results are stored as a "green tree".
399    /// We'll discuss working with the results later
400    struct Parse {
401        green_node: GreenNode,
402        #[allow(unused)]
403        errors: Vec<String>,
404    }
405
406    fn parse(text: &str) -> Parse {
407        struct Parser {
408            /// input tokens, including whitespace,
409            /// in *reverse* order.
410            tokens: Vec<(SyntaxKind, String)>,
411            /// the in-progress tree.
412            builder: GreenNodeBuilder<'static>,
413            /// the list of syntax errors we've accumulated
414            /// so far.
415            errors: Vec<String>,
416        }
417
418        impl Parser {
419            fn error(&mut self, error: String) {
420                self.errors.push(error);
421                self.builder.start_node(SyntaxKind::ERROR.into());
422                if self.current().is_some() {
423                    self.bump();
424                }
425                self.builder.finish_node();
426            }
427
428            fn parse_relation(&mut self) {
429                self.builder.start_node(SyntaxKind::RELATION.into());
430                if self.current() == Some(IDENT) {
431                    self.bump();
432                } else {
433                    self.error("Expected package name".to_string());
434                }
435                match self.peek_past_ws() {
436                    Some(COMMA) => {}
437                    None | Some(L_PARENS) => {
438                        self.skip_ws();
439                    }
440                    e => {
441                        self.skip_ws();
442                        self.error(format!(
443                            "Expected ':' or '|' or '[' or '<' or ',' but got {e:?}"
444                        ));
445                    }
446                }
447
448                if self.peek_past_ws() == Some(L_PARENS) {
449                    self.skip_ws();
450                    self.builder.start_node(VERSION.into());
451                    self.bump();
452                    self.skip_ws();
453
454                    self.builder.start_node(CONSTRAINT.into());
455
456                    while self.current() == Some(L_ANGLE)
457                        || self.current() == Some(R_ANGLE)
458                        || self.current() == Some(EQUAL)
459                        || self.current() == Some(NOT)
460                    {
461                        self.bump();
462                    }
463
464                    self.builder.finish_node();
465
466                    self.skip_ws();
467
468                    if self.current() == Some(IDENT) {
469                        self.bump();
470                    } else {
471                        self.error("Expected version".to_string());
472                    }
473
474                    if self.current() == Some(R_PARENS) {
475                        self.bump();
476                    } else {
477                        self.error("Expected ')'".to_string());
478                    }
479
480                    self.builder.finish_node();
481                }
482
483                self.builder.finish_node();
484            }
485
486            fn parse(mut self) -> Parse {
487                self.builder.start_node(SyntaxKind::ROOT.into());
488
489                self.skip_ws();
490
491                while self.current().is_some() {
492                    match self.current() {
493                        Some(IDENT) => self.parse_relation(),
494                        Some(COMMA) => {
495                            // Empty relation, but that's okay - probably?
496                        }
497                        Some(c) => {
498                            self.error(format!("expected identifier or comma but got {c:?}"));
499                        }
500                        None => {
501                            self.error("expected identifier but got end of file".to_string());
502                        }
503                    }
504
505                    self.skip_ws();
506                    match self.current() {
507                        Some(COMMA) => {
508                            self.bump();
509                        }
510                        None => {
511                            break;
512                        }
513                        c => {
514                            self.error(format!("expected comma or end of file but got {c:?}"));
515                        }
516                    }
517                    self.skip_ws();
518                }
519
520                self.builder.finish_node();
521                // Turn the builder into a GreenNode
522                Parse {
523                    green_node: self.builder.finish(),
524                    errors: self.errors,
525                }
526            }
527            /// Advance one token, adding it to the current branch of the tree builder.
528            fn bump(&mut self) {
529                let (kind, text) = self.tokens.pop().unwrap();
530                self.builder.token(kind.into(), text.as_str());
531            }
532            /// Peek at the first unprocessed token
533            fn current(&self) -> Option<SyntaxKind> {
534                self.tokens.last().map(|(kind, _)| *kind)
535            }
536            fn skip_ws(&mut self) {
537                while self.current() == Some(WHITESPACE) || self.current() == Some(NEWLINE) {
538                    self.bump()
539                }
540            }
541
542            fn peek_past_ws(&self) -> Option<SyntaxKind> {
543                let mut i = self.tokens.len();
544                while i > 0 {
545                    i -= 1;
546                    match self.tokens[i].0 {
547                        WHITESPACE | NEWLINE => {}
548                        _ => return Some(self.tokens[i].0),
549                    }
550                }
551                None
552            }
553        }
554
555        let mut tokens = crate::relations::lex(text);
556        tokens.reverse();
557        Parser {
558            tokens,
559            builder: GreenNodeBuilder::new(),
560            errors: Vec::new(),
561        }
562        .parse()
563    }
564
565    /// To work with the parse results we need a view into the
566    /// green tree - the Syntax tree.
567    /// It is also immutable, like a GreenNode,
568    /// but it contains parent pointers, offsets, and
569    /// has identity semantics.
570    type SyntaxNode = rowan::SyntaxNode<Lang>;
571    #[allow(unused)]
572    type SyntaxToken = rowan::SyntaxToken<Lang>;
573    #[allow(unused)]
574    type SyntaxElement = rowan::NodeOrToken<SyntaxNode, SyntaxToken>;
575
576    impl Parse {
577        fn root_mut(&self) -> Relations {
578            Relations::cast(SyntaxNode::new_root_mut(self.green_node.clone())).unwrap()
579        }
580    }
581
582    macro_rules! ast_node {
583        ($ast:ident, $kind:ident) => {
584            /// A node in the syntax tree representing a $ast
585            #[repr(transparent)]
586            pub struct $ast(SyntaxNode);
587            impl $ast {
588                #[allow(unused)]
589                fn cast(node: SyntaxNode) -> Option<Self> {
590                    if node.kind() == $kind {
591                        Some(Self(node))
592                    } else {
593                        None
594                    }
595                }
596            }
597
598            impl std::fmt::Display for $ast {
599                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
600                    f.write_str(&self.0.text().to_string())
601                }
602            }
603        };
604    }
605
606    ast_node!(Relations, ROOT);
607    ast_node!(Relation, RELATION);
608
609    impl PartialEq for Relations {
610        fn eq(&self, other: &Self) -> bool {
611            self.relations().collect::<Vec<_>>() == other.relations().collect::<Vec<_>>()
612        }
613    }
614
615    impl PartialEq for Relation {
616        fn eq(&self, other: &Self) -> bool {
617            self.name() == other.name() && self.version() == other.version()
618        }
619    }
620
621    #[cfg(feature = "serde")]
622    impl serde::Serialize for Relations {
623        fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
624            let rep = self.to_string();
625            serializer.serialize_str(&rep)
626        }
627    }
628
629    #[cfg(feature = "serde")]
630    impl<'de> serde::Deserialize<'de> for Relations {
631        fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
632            let s = String::deserialize(deserializer)?;
633            let relations = s.parse().map_err(serde::de::Error::custom)?;
634            Ok(relations)
635        }
636    }
637
638    impl std::fmt::Debug for Relations {
639        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
640            let mut s = f.debug_struct("Relations");
641
642            for relation in self.relations() {
643                s.field("relation", &relation);
644            }
645
646            s.finish()
647        }
648    }
649
650    impl std::fmt::Debug for Relation {
651        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
652            let mut s = f.debug_struct("Relation");
653
654            s.field("name", &self.name());
655
656            if let Some((vc, version)) = self.version() {
657                s.field("version", &vc);
658                s.field("version", &version);
659            }
660
661            s.finish()
662        }
663    }
664
665    #[cfg(feature = "serde")]
666    impl serde::Serialize for Relation {
667        fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
668            let rep = self.to_string();
669            serializer.serialize_str(&rep)
670        }
671    }
672
673    #[cfg(feature = "serde")]
674    impl<'de> serde::Deserialize<'de> for Relation {
675        fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
676            let s = String::deserialize(deserializer)?;
677            let relation = s.parse().map_err(serde::de::Error::custom)?;
678            Ok(relation)
679        }
680    }
681
682    impl Default for Relations {
683        fn default() -> Self {
684            Self::new()
685        }
686    }
687
688    impl Relations {
689        /// Create a new relations field
690        pub fn new() -> Self {
691            Self::from(vec![])
692        }
693
694        /// Wrap and sort this relations field
695        #[must_use]
696        pub fn wrap_and_sort(self) -> Self {
697            let mut entries = self
698                .relations()
699                .map(|e| e.wrap_and_sort())
700                .collect::<Vec<_>>();
701            entries.sort();
702            // TODO: preserve comments
703            Self::from(entries)
704        }
705
706        /// Iterate over the entries in this relations field
707        pub fn relations(&self) -> impl Iterator<Item = Relation> + '_ {
708            self.0.children().filter_map(Relation::cast)
709        }
710
711        /// Iterate over the entries in this relations field
712        pub fn iter(&self) -> impl Iterator<Item = Relation> + '_ {
713            self.relations()
714        }
715
716        /// Remove the entry at the given index
717        pub fn get_relation(&self, idx: usize) -> Option<Relation> {
718            self.relations().nth(idx)
719        }
720
721        /// Remove the relation at the given index
722        pub fn remove_relation(&mut self, idx: usize) -> Relation {
723            let mut relation = self.get_relation(idx).unwrap();
724            relation.remove();
725            relation
726        }
727
728        /// Insert a new relation at the given index
729        pub fn insert(&mut self, idx: usize, relation: Relation) {
730            let is_empty = !self.0.children_with_tokens().any(|n| n.kind() == COMMA);
731            let (position, new_children) = if let Some(current_relation) = self.relations().nth(idx)
732            {
733                let to_insert: Vec<NodeOrToken<GreenNode, GreenToken>> = if idx == 0 && is_empty {
734                    vec![relation.0.green().into()]
735                } else {
736                    vec![
737                        relation.0.green().into(),
738                        NodeOrToken::Token(GreenToken::new(COMMA.into(), ",")),
739                        NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), " ")),
740                    ]
741                };
742
743                (current_relation.0.index(), to_insert)
744            } else {
745                let child_count = self.0.children_with_tokens().count();
746                (
747                    child_count,
748                    if idx == 0 {
749                        vec![relation.0.green().into()]
750                    } else {
751                        vec![
752                            NodeOrToken::Token(GreenToken::new(COMMA.into(), ",")),
753                            NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), " ")),
754                            relation.0.green().into(),
755                        ]
756                    },
757                )
758            };
759            // We can safely replace the root here since Relations is a root node
760            self.0 = SyntaxNode::new_root_mut(
761                self.0.replace_with(
762                    self.0
763                        .green()
764                        .splice_children(position..position, new_children),
765                ),
766            );
767        }
768
769        /// Replace the relation at the given index
770        pub fn replace(&mut self, idx: usize, relation: Relation) {
771            let current_relation = self.get_relation(idx).unwrap();
772            self.0.splice_children(
773                current_relation.0.index()..current_relation.0.index() + 1,
774                vec![relation.0.into()],
775            );
776        }
777
778        /// Push a new relation to the relations field
779        pub fn push(&mut self, relation: Relation) {
780            let pos = self.relations().count();
781            self.insert(pos, relation);
782        }
783
784        /// Parse a relations field from a string, allowing syntax errors
785        pub fn parse_relaxed(s: &str) -> (Relations, Vec<String>) {
786            let parse = parse(s);
787            (parse.root_mut(), parse.errors)
788        }
789
790        /// Check if this relations field is satisfied by the given package versions.
791        pub fn satisfied_by(
792            &self,
793            package_version: impl crate::relations::VersionLookup + Copy,
794        ) -> bool {
795            self.relations().all(|e| e.satisfied_by(package_version))
796        }
797
798        /// Check if this relations field is empty
799        pub fn is_empty(&self) -> bool {
800            self.relations().count() == 0
801        }
802
803        /// Get the number of entries in this relations field
804        pub fn len(&self) -> usize {
805            self.relations().count()
806        }
807    }
808
809    impl From<Vec<Relation>> for Relations {
810        fn from(entries: Vec<Relation>) -> Self {
811            let mut builder = GreenNodeBuilder::new();
812            builder.start_node(ROOT.into());
813            for (i, relation) in entries.into_iter().enumerate() {
814                if i > 0 {
815                    builder.token(COMMA.into(), ",");
816                    builder.token(WHITESPACE.into(), " ");
817                }
818                inject(&mut builder, relation.0);
819            }
820            builder.finish_node();
821            Relations(SyntaxNode::new_root_mut(builder.finish()))
822        }
823    }
824
825    impl From<Relation> for Relations {
826        fn from(relation: Relation) -> Self {
827            Self::from(vec![relation])
828        }
829    }
830
831    impl From<Relation> for crate::lossy::Relation {
832        fn from(relation: Relation) -> Self {
833            let mut rel = crate::lossy::Relation::new();
834            rel.name = relation.name();
835            rel.version = relation.version();
836            rel
837        }
838    }
839
840    impl From<Relations> for crate::lossy::Relations {
841        fn from(relations: Relations) -> Self {
842            let mut rels = crate::lossy::Relations::new();
843            for relation in relations.relations() {
844                rels.0.push(relation.into());
845            }
846            rels
847        }
848    }
849
850    impl From<crate::lossy::Relations> for Relations {
851        fn from(relations: crate::lossy::Relations) -> Self {
852            let mut entries = vec![];
853            for relation in relations.iter() {
854                entries.push(relation.clone().into());
855            }
856            Self::from(entries)
857        }
858    }
859
860    impl From<crate::lossy::Relation> for Relation {
861        fn from(relation: crate::lossy::Relation) -> Self {
862            Relation::new(&relation.name, relation.version)
863        }
864    }
865
866    fn inject(builder: &mut GreenNodeBuilder, node: SyntaxNode) {
867        builder.start_node(node.kind().into());
868        for child in node.children_with_tokens() {
869            match child {
870                rowan::NodeOrToken::Node(child) => {
871                    inject(builder, child);
872                }
873                rowan::NodeOrToken::Token(token) => {
874                    builder.token(token.kind().into(), token.text());
875                }
876            }
877        }
878        builder.finish_node();
879    }
880
881    impl Relation {
882        /// Create a new relation
883        ///
884        /// # Arguments
885        /// * `name` - The name of the package
886        /// * `version_constraint` - The version constraint and version to use
887        ///
888        /// # Example
889        /// ```
890        /// use r_description::lossless::{Relation};
891        /// use r_description::VersionConstraint;
892        /// let relation = Relation::new("vign", Some((VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap())));
893        /// assert_eq!(relation.to_string(), "vign (>= 2.0)");
894        /// ```
895        pub fn new(name: &str, version_constraint: Option<(VersionConstraint, Version)>) -> Self {
896            let mut builder = GreenNodeBuilder::new();
897            builder.start_node(SyntaxKind::RELATION.into());
898            builder.token(IDENT.into(), name);
899            if let Some((vc, version)) = version_constraint {
900                builder.token(WHITESPACE.into(), " ");
901                builder.start_node(SyntaxKind::VERSION.into());
902                builder.token(L_PARENS.into(), "(");
903                builder.start_node(SyntaxKind::CONSTRAINT.into());
904                push_constraint_tokens(&mut builder, &vc);
905                builder.finish_node();
906
907                builder.token(WHITESPACE.into(), " ");
908
909                builder.token(IDENT.into(), version.to_string().as_str());
910
911                builder.token(R_PARENS.into(), ")");
912
913                builder.finish_node();
914            }
915
916            builder.finish_node();
917            Relation(SyntaxNode::new_root_mut(builder.finish()))
918        }
919
920        /// Wrap and sort this relation
921        ///
922        /// # Example
923        /// ```
924        /// use r_description::lossless::Relation;
925        /// let relation = "  vign  (  >= 2.0) ".parse::<Relation>().unwrap();
926        /// assert_eq!(relation.wrap_and_sort().to_string(), "vign (>= 2.0)");
927        /// ```
928        #[must_use]
929        pub fn wrap_and_sort(&self) -> Self {
930            let mut builder = GreenNodeBuilder::new();
931            builder.start_node(SyntaxKind::RELATION.into());
932            builder.token(IDENT.into(), self.name().as_str());
933            if let Some((vc, version)) = self.version() {
934                builder.token(WHITESPACE.into(), " ");
935                builder.start_node(SyntaxKind::VERSION.into());
936                builder.token(L_PARENS.into(), "(");
937                builder.start_node(SyntaxKind::CONSTRAINT.into());
938                push_constraint_tokens(&mut builder, &vc);
939                builder.finish_node();
940                builder.token(WHITESPACE.into(), " ");
941                builder.token(IDENT.into(), version.to_string().as_str());
942                builder.token(R_PARENS.into(), ")");
943                builder.finish_node();
944            }
945            builder.finish_node();
946            Relation(SyntaxNode::new_root_mut(builder.finish()))
947        }
948
949        /// Create a new simple relation, without any version constraints.
950        ///
951        /// # Example
952        /// ```
953        /// use r_description::lossless::Relation;
954        /// let relation = Relation::simple("vign");
955        /// assert_eq!(relation.to_string(), "vign");
956        /// ```
957        pub fn simple(name: &str) -> Self {
958            Self::new(name, None)
959        }
960
961        /// Remove the version constraint from the relation.
962        ///
963        /// # Example
964        /// ```
965        /// use r_description::lossless::{Relation};
966        /// use r_description::VersionConstraint;
967        /// let mut relation = Relation::new("vign", Some((VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap())));
968        /// relation.drop_constraint();
969        /// assert_eq!(relation.to_string(), "vign");
970        /// ```
971        pub fn drop_constraint(&mut self) -> bool {
972            let version_token = self.0.children().find(|n| n.kind() == VERSION);
973            if let Some(version_token) = version_token {
974                // Remove any whitespace before the version token
975                while let Some(prev) = version_token.prev_sibling_or_token() {
976                    if prev.kind() == WHITESPACE || prev.kind() == NEWLINE {
977                        prev.detach();
978                    } else {
979                        break;
980                    }
981                }
982                version_token.detach();
983                return true;
984            }
985
986            false
987        }
988
989        /// Return the name of the package in the relation.
990        ///
991        /// # Example
992        /// ```
993        /// use r_description::lossless::Relation;
994        /// let relation = Relation::simple("vign");
995        /// assert_eq!(relation.name(), "vign");
996        /// ```
997        pub fn name(&self) -> String {
998            self.0
999                .children_with_tokens()
1000                .find_map(|it| match it {
1001                    SyntaxElement::Token(token) if token.kind() == IDENT => Some(token),
1002                    _ => None,
1003                })
1004                .unwrap()
1005                .text()
1006                .to_string()
1007        }
1008
1009        /// Return the version constraint and the version it is constrained to.
1010        pub fn version(&self) -> Option<(VersionConstraint, Version)> {
1011            let vc = self.0.children().find(|n| n.kind() == VERSION);
1012            let vc = vc.as_ref()?;
1013            let constraint = vc.children().find(|n| n.kind() == CONSTRAINT);
1014
1015            let version = vc.children_with_tokens().find_map(|it| match it {
1016                SyntaxElement::Token(token) if token.kind() == IDENT => Some(token),
1017                _ => None,
1018            });
1019
1020            if let (Some(constraint), Some(version)) = (constraint, version) {
1021                let vc: VersionConstraint = constraint.to_string().parse().unwrap();
1022                Some((vc, (version.text().to_string()).parse().unwrap()))
1023            } else {
1024                None
1025            }
1026        }
1027
1028        /// Set the version constraint for this relation
1029        ///
1030        /// # Example
1031        /// ```
1032        /// use r_description::lossless::{Relation};
1033        /// use r_description::VersionConstraint;
1034        /// let mut relation = Relation::simple("vign");
1035        /// relation.set_version(Some((VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap())));
1036        /// assert_eq!(relation.to_string(), "vign (>= 2.0)");
1037        /// ```
1038        pub fn set_version(&mut self, version_constraint: Option<(VersionConstraint, Version)>) {
1039            let current_version = self.0.children().find(|n| n.kind() == VERSION);
1040            if let Some((vc, version)) = version_constraint {
1041                let mut builder = GreenNodeBuilder::new();
1042                builder.start_node(VERSION.into());
1043                builder.token(L_PARENS.into(), "(");
1044                builder.start_node(CONSTRAINT.into());
1045                push_constraint_tokens(&mut builder, &vc);
1046                builder.finish_node(); // CONSTRAINT
1047                builder.token(WHITESPACE.into(), " ");
1048                builder.token(IDENT.into(), version.to_string().as_str());
1049                builder.token(R_PARENS.into(), ")");
1050                builder.finish_node(); // VERSION
1051
1052                if let Some(current_version) = current_version {
1053                    self.0.splice_children(
1054                        current_version.index()..current_version.index() + 1,
1055                        vec![SyntaxNode::new_root_mut(builder.finish()).into()],
1056                    );
1057                } else {
1058                    let name_node = self.0.children_with_tokens().find(|n| n.kind() == IDENT);
1059                    let idx = if let Some(name_node) = name_node {
1060                        name_node.index() + 1
1061                    } else {
1062                        0
1063                    };
1064                    let new_children = vec![
1065                        GreenToken::new(WHITESPACE.into(), " ").into(),
1066                        builder.finish().into(),
1067                    ];
1068                    let new_root = SyntaxNode::new_root_mut(
1069                        self.0.green().splice_children(idx..idx, new_children),
1070                    );
1071                    if let Some(parent) = self.0.parent() {
1072                        parent.splice_children(
1073                            self.0.index()..self.0.index() + 1,
1074                            vec![new_root.into()],
1075                        );
1076                        self.0 = parent
1077                            .children_with_tokens()
1078                            .nth(self.0.index())
1079                            .unwrap()
1080                            .clone()
1081                            .into_node()
1082                            .unwrap();
1083                    } else {
1084                        self.0 = new_root;
1085                    }
1086                }
1087            } else if let Some(current_version) = current_version {
1088                // Remove any whitespace before the version token
1089                while let Some(prev) = current_version.prev_sibling_or_token() {
1090                    if prev.kind() == WHITESPACE || prev.kind() == NEWLINE {
1091                        prev.detach();
1092                    } else {
1093                        break;
1094                    }
1095                }
1096                current_version.detach();
1097            }
1098        }
1099
1100        /// Remove this relation
1101        ///
1102        /// # Example
1103        /// ```
1104        /// use r_description::lossless::{Relation, Relations};
1105        /// let mut relations: Relations = r"cli (>= 0.19.0), blah (< 1.26.0)".parse().unwrap();
1106        /// let mut relation = relations.get_relation(0).unwrap();
1107        /// assert_eq!(relation.to_string(), "cli (>= 0.19.0)");
1108        /// relation.remove();
1109        /// assert_eq!(relations.to_string(), "blah (< 1.26.0)");
1110        /// ```
1111        pub fn remove(&mut self) {
1112            let is_first = !self
1113                .0
1114                .siblings(Direction::Prev)
1115                .skip(1)
1116                .any(|n| n.kind() == RELATION);
1117            if !is_first {
1118                // Not the first item in the list. Remove whitespace backwards to the previous
1119                // pipe, the pipe and any whitespace until the previous relation
1120                while let Some(n) = self.0.prev_sibling_or_token() {
1121                    if n.kind() == WHITESPACE || n.kind() == NEWLINE {
1122                        n.detach();
1123                    } else if n.kind() == COMMA {
1124                        n.detach();
1125                        break;
1126                    } else {
1127                        break;
1128                    }
1129                }
1130                while let Some(n) = self.0.prev_sibling_or_token() {
1131                    if n.kind() == WHITESPACE || n.kind() == NEWLINE {
1132                        n.detach();
1133                    } else {
1134                        break;
1135                    }
1136                }
1137            } else {
1138                // First item in the list. Remove whitespace up to the pipe, the pipe and anything
1139                // before the next relation
1140                while let Some(n) = self.0.next_sibling_or_token() {
1141                    if n.kind() == WHITESPACE || n.kind() == NEWLINE {
1142                        n.detach();
1143                    } else if n.kind() == COMMA {
1144                        n.detach();
1145                        break;
1146                    } else {
1147                        panic!("Unexpected node: {n:?}");
1148                    }
1149                }
1150
1151                while let Some(n) = self.0.next_sibling_or_token() {
1152                    if n.kind() == WHITESPACE || n.kind() == NEWLINE {
1153                        n.detach();
1154                    } else {
1155                        break;
1156                    }
1157                }
1158            }
1159            self.0.detach();
1160        }
1161
1162        /// Check if this relation is satisfied by the given package version.
1163        pub fn satisfied_by(
1164            &self,
1165            package_version: impl crate::relations::VersionLookup + Copy,
1166        ) -> bool {
1167            let name = self.name();
1168            let version = self.version();
1169            if let Some(version) = version {
1170                if let Some(package_version) = package_version.lookup_version(&name) {
1171                    match version.0 {
1172                        VersionConstraint::GreaterThanEqual => {
1173                            package_version.into_owned() >= version.1
1174                        }
1175                        VersionConstraint::LessThanEqual => {
1176                            package_version.into_owned() <= version.1
1177                        }
1178                        VersionConstraint::Equal => package_version.into_owned() == version.1,
1179                        VersionConstraint::NotEqual => package_version.into_owned() != version.1,
1180                        VersionConstraint::GreaterThan => package_version.into_owned() > version.1,
1181                        VersionConstraint::LessThan => package_version.into_owned() < version.1,
1182                    }
1183                } else {
1184                    false
1185                }
1186            } else {
1187                true
1188            }
1189        }
1190    }
1191
1192    impl PartialOrd for Relation {
1193        fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1194            Some(self.cmp(other))
1195        }
1196    }
1197
1198    impl Eq for Relation {}
1199
1200    impl Ord for Relation {
1201        fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1202            // Compare by name first, then by version
1203            let name_cmp = self.name().cmp(&other.name());
1204            if name_cmp != std::cmp::Ordering::Equal {
1205                return name_cmp;
1206            }
1207
1208            let self_version = self.version();
1209            let other_version = other.version();
1210
1211            match (self_version, other_version) {
1212                (Some((self_vc, self_version)), Some((other_vc, other_version))) => {
1213                    let vc_cmp = self_vc.cmp(&other_vc);
1214                    if vc_cmp != std::cmp::Ordering::Equal {
1215                        return vc_cmp;
1216                    }
1217
1218                    self_version.cmp(&other_version)
1219                }
1220                (Some(_), None) => std::cmp::Ordering::Greater,
1221                (None, Some(_)) => std::cmp::Ordering::Less,
1222                (None, None) => std::cmp::Ordering::Equal,
1223            }
1224        }
1225    }
1226
1227    impl std::str::FromStr for Relations {
1228        type Err = String;
1229
1230        fn from_str(s: &str) -> Result<Self, Self::Err> {
1231            let parse = parse(s);
1232            if parse.errors.is_empty() {
1233                Ok(parse.root_mut())
1234            } else {
1235                Err(parse.errors.join("\n"))
1236            }
1237        }
1238    }
1239
1240    impl std::str::FromStr for Relation {
1241        type Err = String;
1242
1243        fn from_str(s: &str) -> Result<Self, Self::Err> {
1244            let rels = s.parse::<Relations>()?;
1245            let mut relations = rels.relations();
1246
1247            let relation = if let Some(relation) = relations.next() {
1248                relation
1249            } else {
1250                return Err("No relation found".to_string());
1251            };
1252
1253            if relations.next().is_some() {
1254                return Err("Multiple relations found".to_string());
1255            }
1256
1257            Ok(relation)
1258        }
1259    }
1260
1261    #[cfg(test)]
1262    mod tests {
1263        use super::*;
1264
1265        #[test]
1266        fn test_parse() {
1267            let input = "cli";
1268            let parsed: Relations = input.parse().unwrap();
1269            assert_eq!(parsed.to_string(), input);
1270            assert_eq!(parsed.relations().count(), 1);
1271            let relation = parsed.relations().next().unwrap();
1272            assert_eq!(relation.to_string(), "cli");
1273            assert_eq!(relation.version(), None);
1274
1275            let input = "cli (>= 0.20.21)";
1276            let parsed: Relations = input.parse().unwrap();
1277            assert_eq!(parsed.to_string(), input);
1278            assert_eq!(parsed.relations().count(), 1);
1279            let relation = parsed.relations().next().unwrap();
1280            assert_eq!(relation.to_string(), "cli (>= 0.20.21)");
1281            assert_eq!(
1282                relation.version(),
1283                Some((
1284                    VersionConstraint::GreaterThanEqual,
1285                    "0.20.21".parse().unwrap()
1286                ))
1287            );
1288
1289            let input = "xml2 (> 1.0.0)";
1290            let parsed: Relations = input.parse().unwrap();
1291            assert_eq!(parsed.to_string(), input);
1292            let relation = parsed.relations().next().unwrap();
1293            assert_eq!(
1294                relation.version(),
1295                Some((VersionConstraint::GreaterThan, "1.0.0".parse().unwrap()))
1296            );
1297
1298            let input = "xml2 (< 2.0.0)";
1299            let parsed: Relations = input.parse().unwrap();
1300            assert_eq!(parsed.to_string(), input);
1301            let relation = parsed.relations().next().unwrap();
1302            assert_eq!(
1303                relation.version(),
1304                Some((VersionConstraint::LessThan, "2.0.0".parse().unwrap()))
1305            );
1306        }
1307
1308        #[test]
1309        fn test_r_equality_operators() {
1310            let input = "xml2 (== 1.0.0)";
1311            let parsed: Relations = input.parse().unwrap();
1312            assert_eq!(parsed.to_string(), input);
1313            let relation = parsed.relations().next().unwrap();
1314            assert_eq!(
1315                relation.version(),
1316                Some((VersionConstraint::Equal, "1.0.0".parse().unwrap()))
1317            );
1318
1319            let input = "xml2 (!= 1.0.0)";
1320            let parsed: Relations = input.parse().unwrap();
1321            assert_eq!(parsed.to_string(), input);
1322            let relation = parsed.relations().next().unwrap();
1323            assert_eq!(
1324                relation.version(),
1325                Some((VersionConstraint::NotEqual, "1.0.0".parse().unwrap()))
1326            );
1327        }
1328
1329        #[test]
1330        fn test_constructed_relations_use_r_operators() {
1331            let cases = [
1332                (VersionConstraint::LessThan, "xml2 (< 1.0)"),
1333                (VersionConstraint::LessThanEqual, "xml2 (<= 1.0)"),
1334                (VersionConstraint::Equal, "xml2 (== 1.0)"),
1335                (VersionConstraint::NotEqual, "xml2 (!= 1.0)"),
1336                (VersionConstraint::GreaterThan, "xml2 (> 1.0)"),
1337                (VersionConstraint::GreaterThanEqual, "xml2 (>= 1.0)"),
1338            ];
1339            for (vc, expected) in cases {
1340                let relation = Relation::new("xml2", Some((vc, "1.0".parse().unwrap())));
1341                assert_eq!(relation.to_string(), expected);
1342            }
1343        }
1344
1345        #[test]
1346        fn test_multiple() {
1347            let input = "cli (>= 0.20.21), cli (< 0.21)";
1348            let parsed: Relations = input.parse().unwrap();
1349            assert_eq!(parsed.to_string(), input);
1350            assert_eq!(parsed.relations().count(), 2);
1351            let relation = parsed.relations().next().unwrap();
1352            assert_eq!(relation.to_string(), "cli (>= 0.20.21)");
1353            assert_eq!(
1354                relation.version(),
1355                Some((
1356                    VersionConstraint::GreaterThanEqual,
1357                    "0.20.21".parse().unwrap()
1358                ))
1359            );
1360            let relation = parsed.relations().nth(1).unwrap();
1361            assert_eq!(relation.to_string(), "cli (< 0.21)");
1362            assert_eq!(
1363                relation.version(),
1364                Some((VersionConstraint::LessThan, "0.21".parse().unwrap()))
1365            );
1366        }
1367
1368        #[test]
1369        fn test_new() {
1370            let r = Relation::new(
1371                "cli",
1372                Some((VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap())),
1373            );
1374
1375            assert_eq!(r.to_string(), "cli (>= 2.0)");
1376        }
1377
1378        #[test]
1379        fn test_drop_constraint() {
1380            let mut r = Relation::new(
1381                "cli",
1382                Some((VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap())),
1383            );
1384
1385            r.drop_constraint();
1386
1387            assert_eq!(r.to_string(), "cli");
1388        }
1389
1390        #[test]
1391        fn test_simple() {
1392            let r = Relation::simple("cli");
1393
1394            assert_eq!(r.to_string(), "cli");
1395        }
1396
1397        #[test]
1398        fn test_remove_first_relation() {
1399            let mut rels: Relations = r#"cli (>= 0.20.21), cli (< 0.21)"#.parse().unwrap();
1400            let removed = rels.remove_relation(0);
1401            assert_eq!(removed.to_string(), "cli (>= 0.20.21)");
1402            assert_eq!(rels.to_string(), "cli (< 0.21)");
1403        }
1404
1405        #[test]
1406        fn test_remove_last_relation() {
1407            let mut rels: Relations = r#"cli (>= 0.20.21), cli (< 0.21)"#.parse().unwrap();
1408            rels.remove_relation(1);
1409            assert_eq!(rels.to_string(), "cli (>= 0.20.21)");
1410        }
1411
1412        #[test]
1413        fn test_remove_middle() {
1414            let mut rels: Relations =
1415                r#"cli (>= 0.20.21), cli (< 0.21), cli (< 0.22)"#.parse().unwrap();
1416            rels.remove_relation(1);
1417            assert_eq!(rels.to_string(), "cli (>= 0.20.21), cli (< 0.22)");
1418        }
1419
1420        #[test]
1421        fn test_remove_added() {
1422            let mut rels: Relations = r#"cli (>= 0.20.21)"#.parse().unwrap();
1423            let relation = Relation::simple("cli");
1424            rels.push(relation);
1425            rels.remove_relation(1);
1426            assert_eq!(rels.to_string(), "cli (>= 0.20.21)");
1427        }
1428
1429        #[test]
1430        fn test_push() {
1431            let mut rels: Relations = r#"cli (>= 0.20.21)"#.parse().unwrap();
1432            let relation = Relation::simple("cli");
1433            rels.push(relation);
1434            assert_eq!(rels.to_string(), "cli (>= 0.20.21), cli");
1435        }
1436
1437        #[test]
1438        fn test_push_from_empty() {
1439            let mut rels: Relations = "".parse().unwrap();
1440            let relation = Relation::simple("cli");
1441            rels.push(relation);
1442            assert_eq!(rels.to_string(), "cli");
1443        }
1444
1445        #[test]
1446        fn test_insert() {
1447            let mut rels: Relations = r#"cli (>= 0.20.21), cli (< 0.21)"#.parse().unwrap();
1448            let relation = Relation::simple("cli");
1449            rels.insert(1, relation);
1450            assert_eq!(rels.to_string(), "cli (>= 0.20.21), cli, cli (< 0.21)");
1451        }
1452
1453        #[test]
1454        fn test_insert_at_start() {
1455            let mut rels: Relations = r#"cli (>= 0.20.21), cli (< 0.21)"#.parse().unwrap();
1456            let relation = Relation::simple("cli");
1457            rels.insert(0, relation);
1458            assert_eq!(rels.to_string(), "cli, cli (>= 0.20.21), cli (< 0.21)");
1459        }
1460
1461        #[test]
1462        fn test_insert_after_error() {
1463            let (mut rels, errors) = Relations::parse_relaxed("@foo@, debhelper (>= 1.0)");
1464            assert_eq!(
1465                errors,
1466                vec![
1467                    "expected identifier or comma but got ERROR",
1468                    "expected comma or end of file but got Some(IDENT)",
1469                    "expected identifier or comma but got ERROR"
1470                ]
1471            );
1472            let relation = Relation::simple("bar");
1473            rels.push(relation);
1474            assert_eq!(rels.to_string(), "@foo@, debhelper (>= 1.0), bar");
1475        }
1476
1477        #[test]
1478        fn test_insert_before_error() {
1479            let (mut rels, errors) = Relations::parse_relaxed("debhelper (>= 1.0), @foo@, bla");
1480            assert_eq!(
1481                errors,
1482                vec![
1483                    "expected identifier or comma but got ERROR",
1484                    "expected comma or end of file but got Some(IDENT)",
1485                    "expected identifier or comma but got ERROR"
1486                ]
1487            );
1488            let relation = Relation::simple("bar");
1489            rels.insert(0, relation);
1490            assert_eq!(rels.to_string(), "bar, debhelper (>= 1.0), @foo@, bla");
1491        }
1492
1493        #[test]
1494        fn test_replace() {
1495            let mut rels: Relations = r#"cli (>= 0.20.21), cli (< 0.21)"#.parse().unwrap();
1496            let relation = Relation::simple("cli");
1497            rels.replace(1, relation);
1498            assert_eq!(rels.to_string(), "cli (>= 0.20.21), cli");
1499        }
1500
1501        #[test]
1502        fn test_parse_relation() {
1503            let parsed: Relation = "cli (>= 0.20.21)".parse().unwrap();
1504            assert_eq!(parsed.to_string(), "cli (>= 0.20.21)");
1505            assert_eq!(
1506                parsed.version(),
1507                Some((
1508                    VersionConstraint::GreaterThanEqual,
1509                    "0.20.21".parse().unwrap()
1510                ))
1511            );
1512            assert_eq!(
1513                "foo, bar".parse::<Relation>().unwrap_err(),
1514                "Multiple relations found"
1515            );
1516            assert_eq!("".parse::<Relation>().unwrap_err(), "No relation found");
1517        }
1518
1519        #[test]
1520        fn test_relations_satisfied_by() {
1521            let rels: Relations = "cli (>= 0.20.21), cli (< 0.21)".parse().unwrap();
1522            let satisfied = |name: &str| -> Option<Version> {
1523                match name {
1524                    "cli" => Some("0.20.21".parse().unwrap()),
1525                    _ => None,
1526                }
1527            };
1528            assert!(rels.satisfied_by(satisfied));
1529
1530            let satisfied = |name: &str| match name {
1531                "cli" => Some("0.21".parse().unwrap()),
1532                _ => None,
1533            };
1534            assert!(!rels.satisfied_by(satisfied));
1535
1536            let satisfied = |name: &str| match name {
1537                "cli" => Some("0.20.20".parse().unwrap()),
1538                _ => None,
1539            };
1540            assert!(!rels.satisfied_by(satisfied));
1541        }
1542
1543        #[test]
1544        fn test_wrap_and_sort_relation() {
1545            let relation: Relation = "   cli   (>=   11.0)".parse().unwrap();
1546
1547            let wrapped = relation.wrap_and_sort();
1548
1549            assert_eq!(wrapped.to_string(), "cli (>= 11.0)");
1550        }
1551
1552        #[test]
1553        fn test_wrap_and_sort_relations() {
1554            let relations: Relations = "cli (>= 0.20.21)  , \n\n\n\ncli (< 0.21)".parse().unwrap();
1555
1556            let wrapped = relations.wrap_and_sort();
1557
1558            assert_eq!(wrapped.to_string(), "cli (< 0.21), cli (>= 0.20.21)");
1559        }
1560
1561        #[cfg(feature = "serde")]
1562        #[test]
1563        fn test_serialize_relations() {
1564            let relations: Relations = "cli (>= 0.20.21), cli (< 0.21)".parse().unwrap();
1565            let serialized = serde_json::to_string(&relations).unwrap();
1566            assert_eq!(serialized, r#""cli (>= 0.20.21), cli (< 0.21)""#);
1567        }
1568
1569        #[cfg(feature = "serde")]
1570        #[test]
1571        fn test_deserialize_relations() {
1572            let relations: Relations = "cli (>= 0.20.21), cli (< 0.21)".parse().unwrap();
1573            let serialized = serde_json::to_string(&relations).unwrap();
1574            let deserialized: Relations = serde_json::from_str(&serialized).unwrap();
1575            assert_eq!(deserialized.to_string(), relations.to_string());
1576        }
1577
1578        #[cfg(feature = "serde")]
1579        #[test]
1580        fn test_serialize_relation() {
1581            let relation: Relation = "cli (>= 0.20.21)".parse().unwrap();
1582            let serialized = serde_json::to_string(&relation).unwrap();
1583            assert_eq!(serialized, r#""cli (>= 0.20.21)""#);
1584        }
1585
1586        #[cfg(feature = "serde")]
1587        #[test]
1588        fn test_deserialize_relation() {
1589            let relation: Relation = "cli (>= 0.20.21)".parse().unwrap();
1590            let serialized = serde_json::to_string(&relation).unwrap();
1591            let deserialized: Relation = serde_json::from_str(&serialized).unwrap();
1592            assert_eq!(deserialized.to_string(), relation.to_string());
1593        }
1594
1595        #[test]
1596        fn test_relation_set_version() {
1597            let mut rel: Relation = "vign".parse().unwrap();
1598            rel.set_version(None);
1599            assert_eq!("vign", rel.to_string());
1600            rel.set_version(Some((
1601                VersionConstraint::GreaterThanEqual,
1602                "2.0".parse().unwrap(),
1603            )));
1604            assert_eq!("vign (>= 2.0)", rel.to_string());
1605            rel.set_version(None);
1606            assert_eq!("vign", rel.to_string());
1607            rel.set_version(Some((
1608                VersionConstraint::GreaterThanEqual,
1609                "2.0".parse().unwrap(),
1610            )));
1611            rel.set_version(Some((
1612                VersionConstraint::GreaterThanEqual,
1613                "1.1".parse().unwrap(),
1614            )));
1615            assert_eq!("vign (>= 1.1)", rel.to_string());
1616        }
1617
1618        #[test]
1619        fn test_wrap_and_sort_removes_empty_entries() {
1620            let relations: Relations = "foo, , bar, ".parse().unwrap();
1621            let wrapped = relations.wrap_and_sort();
1622            assert_eq!(wrapped.to_string(), "bar, foo");
1623        }
1624    }
1625}
1626
1627#[cfg(test)]
1628mod tests {
1629    use super::*;
1630
1631    #[test]
1632    fn test_parse() {
1633        let s = r###"Package: mypackage
1634Title: What the Package Does (One Line, Title Case)
1635Version: 0.0.0.9000
1636Authors@R: 
1637    person("First", "Last", , "first.last@example.com", role = c("aut", "cre"),
1638           comment = c(ORCID = "YOUR-ORCID-ID"))
1639Description: What the package does (one paragraph).
1640License: `use_mit_license()`, `use_gpl3_license()` or friends to pick a
1641    license
1642Encoding: UTF-8
1643Roxygen: list(markdown = TRUE)
1644RoxygenNote: 7.3.2
1645"###;
1646        let desc: RDescription = s.parse().unwrap();
1647
1648        assert_eq!(desc.package(), Some("mypackage".to_string()));
1649        assert_eq!(
1650            desc.title(),
1651            Some("What the Package Does (One Line, Title Case)".to_string())
1652        );
1653        assert_eq!(desc.version(), Some("0.0.0.9000".to_string()));
1654        assert_eq!(
1655            desc.authors(),
1656            Some(RCode(
1657                r#"person("First", "Last", , "first.last@example.com", role = c("aut", "cre"),
1658comment = c(ORCID = "YOUR-ORCID-ID"))"#
1659                    .to_string()
1660            ))
1661        );
1662        assert_eq!(
1663            desc.description(),
1664            Some("What the package does (one paragraph).".to_string())
1665        );
1666        assert_eq!(
1667            desc.license(),
1668            Some(
1669                "`use_mit_license()`, `use_gpl3_license()` or friends to pick a\nlicense"
1670                    .to_string()
1671            )
1672        );
1673        assert_eq!(desc.encoding(), Some("UTF-8".to_string()));
1674        assert_eq!(desc.roxygen(), Some("list(markdown = TRUE)".to_string()));
1675        assert_eq!(desc.roxygen_note(), Some("7.3.2".to_string()));
1676
1677        assert_eq!(desc.to_string(), s);
1678    }
1679
1680    #[test]
1681    fn test_parse_dplyr() {
1682        let s = include_str!("../testdata/dplyr.desc");
1683
1684        let desc: RDescription = s.parse().unwrap();
1685        assert_eq!("dplyr", desc.package().unwrap());
1686        assert_eq!(
1687            "https://dplyr.tidyverse.org, https://github.com/tidyverse/dplyr",
1688            desc.url().unwrap().as_str()
1689        );
1690    }
1691}