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    /// The parse results are stored as a "green tree".
383    /// We'll discuss working with the results later
384    struct Parse {
385        green_node: GreenNode,
386        #[allow(unused)]
387        errors: Vec<String>,
388    }
389
390    fn parse(text: &str) -> Parse {
391        struct Parser {
392            /// input tokens, including whitespace,
393            /// in *reverse* order.
394            tokens: Vec<(SyntaxKind, String)>,
395            /// the in-progress tree.
396            builder: GreenNodeBuilder<'static>,
397            /// the list of syntax errors we've accumulated
398            /// so far.
399            errors: Vec<String>,
400        }
401
402        impl Parser {
403            fn error(&mut self, error: String) {
404                self.errors.push(error);
405                self.builder.start_node(SyntaxKind::ERROR.into());
406                if self.current().is_some() {
407                    self.bump();
408                }
409                self.builder.finish_node();
410            }
411
412            fn parse_relation(&mut self) {
413                self.builder.start_node(SyntaxKind::RELATION.into());
414                if self.current() == Some(IDENT) {
415                    self.bump();
416                } else {
417                    self.error("Expected package name".to_string());
418                }
419                match self.peek_past_ws() {
420                    Some(COMMA) => {}
421                    None | Some(L_PARENS) => {
422                        self.skip_ws();
423                    }
424                    e => {
425                        self.skip_ws();
426                        self.error(format!(
427                            "Expected ':' or '|' or '[' or '<' or ',' but got {e:?}"
428                        ));
429                    }
430                }
431
432                if self.peek_past_ws() == Some(L_PARENS) {
433                    self.skip_ws();
434                    self.builder.start_node(VERSION.into());
435                    self.bump();
436                    self.skip_ws();
437
438                    self.builder.start_node(CONSTRAINT.into());
439
440                    while self.current() == Some(L_ANGLE)
441                        || self.current() == Some(R_ANGLE)
442                        || self.current() == Some(EQUAL)
443                    {
444                        self.bump();
445                    }
446
447                    self.builder.finish_node();
448
449                    self.skip_ws();
450
451                    if self.current() == Some(IDENT) {
452                        self.bump();
453                    } else {
454                        self.error("Expected version".to_string());
455                    }
456
457                    if self.current() == Some(R_PARENS) {
458                        self.bump();
459                    } else {
460                        self.error("Expected ')'".to_string());
461                    }
462
463                    self.builder.finish_node();
464                }
465
466                self.builder.finish_node();
467            }
468
469            fn parse(mut self) -> Parse {
470                self.builder.start_node(SyntaxKind::ROOT.into());
471
472                self.skip_ws();
473
474                while self.current().is_some() {
475                    match self.current() {
476                        Some(IDENT) => self.parse_relation(),
477                        Some(COMMA) => {
478                            // Empty relation, but that's okay - probably?
479                        }
480                        Some(c) => {
481                            self.error(format!("expected identifier or comma but got {c:?}"));
482                        }
483                        None => {
484                            self.error("expected identifier but got end of file".to_string());
485                        }
486                    }
487
488                    self.skip_ws();
489                    match self.current() {
490                        Some(COMMA) => {
491                            self.bump();
492                        }
493                        None => {
494                            break;
495                        }
496                        c => {
497                            self.error(format!("expected comma or end of file but got {c:?}"));
498                        }
499                    }
500                    self.skip_ws();
501                }
502
503                self.builder.finish_node();
504                // Turn the builder into a GreenNode
505                Parse {
506                    green_node: self.builder.finish(),
507                    errors: self.errors,
508                }
509            }
510            /// Advance one token, adding it to the current branch of the tree builder.
511            fn bump(&mut self) {
512                let (kind, text) = self.tokens.pop().unwrap();
513                self.builder.token(kind.into(), text.as_str());
514            }
515            /// Peek at the first unprocessed token
516            fn current(&self) -> Option<SyntaxKind> {
517                self.tokens.last().map(|(kind, _)| *kind)
518            }
519            fn skip_ws(&mut self) {
520                while self.current() == Some(WHITESPACE) || self.current() == Some(NEWLINE) {
521                    self.bump()
522                }
523            }
524
525            fn peek_past_ws(&self) -> Option<SyntaxKind> {
526                let mut i = self.tokens.len();
527                while i > 0 {
528                    i -= 1;
529                    match self.tokens[i].0 {
530                        WHITESPACE | NEWLINE => {}
531                        _ => return Some(self.tokens[i].0),
532                    }
533                }
534                None
535            }
536        }
537
538        let mut tokens = crate::relations::lex(text);
539        tokens.reverse();
540        Parser {
541            tokens,
542            builder: GreenNodeBuilder::new(),
543            errors: Vec::new(),
544        }
545        .parse()
546    }
547
548    /// To work with the parse results we need a view into the
549    /// green tree - the Syntax tree.
550    /// It is also immutable, like a GreenNode,
551    /// but it contains parent pointers, offsets, and
552    /// has identity semantics.
553    type SyntaxNode = rowan::SyntaxNode<Lang>;
554    #[allow(unused)]
555    type SyntaxToken = rowan::SyntaxToken<Lang>;
556    #[allow(unused)]
557    type SyntaxElement = rowan::NodeOrToken<SyntaxNode, SyntaxToken>;
558
559    impl Parse {
560        fn root_mut(&self) -> Relations {
561            Relations::cast(SyntaxNode::new_root_mut(self.green_node.clone())).unwrap()
562        }
563    }
564
565    macro_rules! ast_node {
566        ($ast:ident, $kind:ident) => {
567            /// A node in the syntax tree representing a $ast
568            #[repr(transparent)]
569            pub struct $ast(SyntaxNode);
570            impl $ast {
571                #[allow(unused)]
572                fn cast(node: SyntaxNode) -> Option<Self> {
573                    if node.kind() == $kind {
574                        Some(Self(node))
575                    } else {
576                        None
577                    }
578                }
579            }
580
581            impl std::fmt::Display for $ast {
582                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
583                    f.write_str(&self.0.text().to_string())
584                }
585            }
586        };
587    }
588
589    ast_node!(Relations, ROOT);
590    ast_node!(Relation, RELATION);
591
592    impl PartialEq for Relations {
593        fn eq(&self, other: &Self) -> bool {
594            self.relations().collect::<Vec<_>>() == other.relations().collect::<Vec<_>>()
595        }
596    }
597
598    impl PartialEq for Relation {
599        fn eq(&self, other: &Self) -> bool {
600            self.name() == other.name() && self.version() == other.version()
601        }
602    }
603
604    #[cfg(feature = "serde")]
605    impl serde::Serialize for Relations {
606        fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
607            let rep = self.to_string();
608            serializer.serialize_str(&rep)
609        }
610    }
611
612    #[cfg(feature = "serde")]
613    impl<'de> serde::Deserialize<'de> for Relations {
614        fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
615            let s = String::deserialize(deserializer)?;
616            let relations = s.parse().map_err(serde::de::Error::custom)?;
617            Ok(relations)
618        }
619    }
620
621    impl std::fmt::Debug for Relations {
622        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
623            let mut s = f.debug_struct("Relations");
624
625            for relation in self.relations() {
626                s.field("relation", &relation);
627            }
628
629            s.finish()
630        }
631    }
632
633    impl std::fmt::Debug for Relation {
634        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
635            let mut s = f.debug_struct("Relation");
636
637            s.field("name", &self.name());
638
639            if let Some((vc, version)) = self.version() {
640                s.field("version", &vc);
641                s.field("version", &version);
642            }
643
644            s.finish()
645        }
646    }
647
648    #[cfg(feature = "serde")]
649    impl serde::Serialize for Relation {
650        fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
651            let rep = self.to_string();
652            serializer.serialize_str(&rep)
653        }
654    }
655
656    #[cfg(feature = "serde")]
657    impl<'de> serde::Deserialize<'de> for Relation {
658        fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
659            let s = String::deserialize(deserializer)?;
660            let relation = s.parse().map_err(serde::de::Error::custom)?;
661            Ok(relation)
662        }
663    }
664
665    impl Default for Relations {
666        fn default() -> Self {
667            Self::new()
668        }
669    }
670
671    impl Relations {
672        /// Create a new relations field
673        pub fn new() -> Self {
674            Self::from(vec![])
675        }
676
677        /// Wrap and sort this relations field
678        #[must_use]
679        pub fn wrap_and_sort(self) -> Self {
680            let mut entries = self
681                .relations()
682                .map(|e| e.wrap_and_sort())
683                .collect::<Vec<_>>();
684            entries.sort();
685            // TODO: preserve comments
686            Self::from(entries)
687        }
688
689        /// Iterate over the entries in this relations field
690        pub fn relations(&self) -> impl Iterator<Item = Relation> + '_ {
691            self.0.children().filter_map(Relation::cast)
692        }
693
694        /// Iterate over the entries in this relations field
695        pub fn iter(&self) -> impl Iterator<Item = Relation> + '_ {
696            self.relations()
697        }
698
699        /// Remove the entry at the given index
700        pub fn get_relation(&self, idx: usize) -> Option<Relation> {
701            self.relations().nth(idx)
702        }
703
704        /// Remove the relation at the given index
705        pub fn remove_relation(&mut self, idx: usize) -> Relation {
706            let mut relation = self.get_relation(idx).unwrap();
707            relation.remove();
708            relation
709        }
710
711        /// Insert a new relation at the given index
712        pub fn insert(&mut self, idx: usize, relation: Relation) {
713            let is_empty = !self.0.children_with_tokens().any(|n| n.kind() == COMMA);
714            let (position, new_children) = if let Some(current_relation) = self.relations().nth(idx)
715            {
716                let to_insert: Vec<NodeOrToken<GreenNode, GreenToken>> = if idx == 0 && is_empty {
717                    vec![relation.0.green().into()]
718                } else {
719                    vec![
720                        relation.0.green().into(),
721                        NodeOrToken::Token(GreenToken::new(COMMA.into(), ",")),
722                        NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), " ")),
723                    ]
724                };
725
726                (current_relation.0.index(), to_insert)
727            } else {
728                let child_count = self.0.children_with_tokens().count();
729                (
730                    child_count,
731                    if idx == 0 {
732                        vec![relation.0.green().into()]
733                    } else {
734                        vec![
735                            NodeOrToken::Token(GreenToken::new(COMMA.into(), ",")),
736                            NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), " ")),
737                            relation.0.green().into(),
738                        ]
739                    },
740                )
741            };
742            // We can safely replace the root here since Relations is a root node
743            self.0 = SyntaxNode::new_root_mut(
744                self.0.replace_with(
745                    self.0
746                        .green()
747                        .splice_children(position..position, new_children),
748                ),
749            );
750        }
751
752        /// Replace the relation at the given index
753        pub fn replace(&mut self, idx: usize, relation: Relation) {
754            let current_relation = self.get_relation(idx).unwrap();
755            self.0.splice_children(
756                current_relation.0.index()..current_relation.0.index() + 1,
757                vec![relation.0.into()],
758            );
759        }
760
761        /// Push a new relation to the relations field
762        pub fn push(&mut self, relation: Relation) {
763            let pos = self.relations().count();
764            self.insert(pos, relation);
765        }
766
767        /// Parse a relations field from a string, allowing syntax errors
768        pub fn parse_relaxed(s: &str) -> (Relations, Vec<String>) {
769            let parse = parse(s);
770            (parse.root_mut(), parse.errors)
771        }
772
773        /// Check if this relations field is satisfied by the given package versions.
774        pub fn satisfied_by(
775            &self,
776            package_version: impl crate::relations::VersionLookup + Copy,
777        ) -> bool {
778            self.relations().all(|e| e.satisfied_by(package_version))
779        }
780
781        /// Check if this relations field is empty
782        pub fn is_empty(&self) -> bool {
783            self.relations().count() == 0
784        }
785
786        /// Get the number of entries in this relations field
787        pub fn len(&self) -> usize {
788            self.relations().count()
789        }
790    }
791
792    impl From<Vec<Relation>> for Relations {
793        fn from(entries: Vec<Relation>) -> Self {
794            let mut builder = GreenNodeBuilder::new();
795            builder.start_node(ROOT.into());
796            for (i, relation) in entries.into_iter().enumerate() {
797                if i > 0 {
798                    builder.token(COMMA.into(), ",");
799                    builder.token(WHITESPACE.into(), " ");
800                }
801                inject(&mut builder, relation.0);
802            }
803            builder.finish_node();
804            Relations(SyntaxNode::new_root_mut(builder.finish()))
805        }
806    }
807
808    impl From<Relation> for Relations {
809        fn from(relation: Relation) -> Self {
810            Self::from(vec![relation])
811        }
812    }
813
814    impl From<Relation> for crate::lossy::Relation {
815        fn from(relation: Relation) -> Self {
816            let mut rel = crate::lossy::Relation::new();
817            rel.name = relation.name();
818            rel.version = relation.version();
819            rel
820        }
821    }
822
823    impl From<Relations> for crate::lossy::Relations {
824        fn from(relations: Relations) -> Self {
825            let mut rels = crate::lossy::Relations::new();
826            for relation in relations.relations() {
827                rels.0.push(relation.into());
828            }
829            rels
830        }
831    }
832
833    impl From<crate::lossy::Relations> for Relations {
834        fn from(relations: crate::lossy::Relations) -> Self {
835            let mut entries = vec![];
836            for relation in relations.iter() {
837                entries.push(relation.clone().into());
838            }
839            Self::from(entries)
840        }
841    }
842
843    impl From<crate::lossy::Relation> for Relation {
844        fn from(relation: crate::lossy::Relation) -> Self {
845            Relation::new(&relation.name, relation.version)
846        }
847    }
848
849    fn inject(builder: &mut GreenNodeBuilder, node: SyntaxNode) {
850        builder.start_node(node.kind().into());
851        for child in node.children_with_tokens() {
852            match child {
853                rowan::NodeOrToken::Node(child) => {
854                    inject(builder, child);
855                }
856                rowan::NodeOrToken::Token(token) => {
857                    builder.token(token.kind().into(), token.text());
858                }
859            }
860        }
861        builder.finish_node();
862    }
863
864    impl Relation {
865        /// Create a new relation
866        ///
867        /// # Arguments
868        /// * `name` - The name of the package
869        /// * `version_constraint` - The version constraint and version to use
870        ///
871        /// # Example
872        /// ```
873        /// use r_description::lossless::{Relation};
874        /// use r_description::VersionConstraint;
875        /// let relation = Relation::new("vign", Some((VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap())));
876        /// assert_eq!(relation.to_string(), "vign (>= 2.0)");
877        /// ```
878        pub fn new(name: &str, version_constraint: Option<(VersionConstraint, Version)>) -> Self {
879            let mut builder = GreenNodeBuilder::new();
880            builder.start_node(SyntaxKind::RELATION.into());
881            builder.token(IDENT.into(), name);
882            if let Some((vc, version)) = version_constraint {
883                builder.token(WHITESPACE.into(), " ");
884                builder.start_node(SyntaxKind::VERSION.into());
885                builder.token(L_PARENS.into(), "(");
886                builder.start_node(SyntaxKind::CONSTRAINT.into());
887                for c in vc.to_string().chars() {
888                    builder.token(
889                        match c {
890                            '>' => R_ANGLE.into(),
891                            '<' => L_ANGLE.into(),
892                            '=' => EQUAL.into(),
893                            _ => unreachable!(),
894                        },
895                        c.to_string().as_str(),
896                    );
897                }
898                builder.finish_node();
899
900                builder.token(WHITESPACE.into(), " ");
901
902                builder.token(IDENT.into(), version.to_string().as_str());
903
904                builder.token(R_PARENS.into(), ")");
905
906                builder.finish_node();
907            }
908
909            builder.finish_node();
910            Relation(SyntaxNode::new_root_mut(builder.finish()))
911        }
912
913        /// Wrap and sort this relation
914        ///
915        /// # Example
916        /// ```
917        /// use r_description::lossless::Relation;
918        /// let relation = "  vign  (  >= 2.0) ".parse::<Relation>().unwrap();
919        /// assert_eq!(relation.wrap_and_sort().to_string(), "vign (>= 2.0)");
920        /// ```
921        #[must_use]
922        pub fn wrap_and_sort(&self) -> Self {
923            let mut builder = GreenNodeBuilder::new();
924            builder.start_node(SyntaxKind::RELATION.into());
925            builder.token(IDENT.into(), self.name().as_str());
926            if let Some((vc, version)) = self.version() {
927                builder.token(WHITESPACE.into(), " ");
928                builder.start_node(SyntaxKind::VERSION.into());
929                builder.token(L_PARENS.into(), "(");
930                builder.start_node(SyntaxKind::CONSTRAINT.into());
931                builder.token(
932                    match vc {
933                        VersionConstraint::GreaterThanEqual => R_ANGLE.into(),
934                        VersionConstraint::LessThanEqual => L_ANGLE.into(),
935                        VersionConstraint::Equal => EQUAL.into(),
936                        VersionConstraint::GreaterThan => R_ANGLE.into(),
937                        VersionConstraint::LessThan => L_ANGLE.into(),
938                    },
939                    vc.to_string().as_str(),
940                );
941                builder.finish_node();
942                builder.token(WHITESPACE.into(), " ");
943                builder.token(IDENT.into(), version.to_string().as_str());
944                builder.token(R_PARENS.into(), ")");
945                builder.finish_node();
946            }
947            builder.finish_node();
948            Relation(SyntaxNode::new_root_mut(builder.finish()))
949        }
950
951        /// Create a new simple relation, without any version constraints.
952        ///
953        /// # Example
954        /// ```
955        /// use r_description::lossless::Relation;
956        /// let relation = Relation::simple("vign");
957        /// assert_eq!(relation.to_string(), "vign");
958        /// ```
959        pub fn simple(name: &str) -> Self {
960            Self::new(name, None)
961        }
962
963        /// Remove the version constraint from the relation.
964        ///
965        /// # Example
966        /// ```
967        /// use r_description::lossless::{Relation};
968        /// use r_description::VersionConstraint;
969        /// let mut relation = Relation::new("vign", Some((VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap())));
970        /// relation.drop_constraint();
971        /// assert_eq!(relation.to_string(), "vign");
972        /// ```
973        pub fn drop_constraint(&mut self) -> bool {
974            let version_token = self.0.children().find(|n| n.kind() == VERSION);
975            if let Some(version_token) = version_token {
976                // Remove any whitespace before the version token
977                while let Some(prev) = version_token.prev_sibling_or_token() {
978                    if prev.kind() == WHITESPACE || prev.kind() == NEWLINE {
979                        prev.detach();
980                    } else {
981                        break;
982                    }
983                }
984                version_token.detach();
985                return true;
986            }
987
988            false
989        }
990
991        /// Return the name of the package in the relation.
992        ///
993        /// # Example
994        /// ```
995        /// use r_description::lossless::Relation;
996        /// let relation = Relation::simple("vign");
997        /// assert_eq!(relation.name(), "vign");
998        /// ```
999        pub fn name(&self) -> String {
1000            self.0
1001                .children_with_tokens()
1002                .find_map(|it| match it {
1003                    SyntaxElement::Token(token) if token.kind() == IDENT => Some(token),
1004                    _ => None,
1005                })
1006                .unwrap()
1007                .text()
1008                .to_string()
1009        }
1010
1011        /// Return the version constraint and the version it is constrained to.
1012        pub fn version(&self) -> Option<(VersionConstraint, Version)> {
1013            let vc = self.0.children().find(|n| n.kind() == VERSION);
1014            let vc = vc.as_ref()?;
1015            let constraint = vc.children().find(|n| n.kind() == CONSTRAINT);
1016
1017            let version = vc.children_with_tokens().find_map(|it| match it {
1018                SyntaxElement::Token(token) if token.kind() == IDENT => Some(token),
1019                _ => None,
1020            });
1021
1022            if let (Some(constraint), Some(version)) = (constraint, version) {
1023                let vc: VersionConstraint = constraint.to_string().parse().unwrap();
1024                Some((vc, (version.text().to_string()).parse().unwrap()))
1025            } else {
1026                None
1027            }
1028        }
1029
1030        /// Set the version constraint for this relation
1031        ///
1032        /// # Example
1033        /// ```
1034        /// use r_description::lossless::{Relation};
1035        /// use r_description::VersionConstraint;
1036        /// let mut relation = Relation::simple("vign");
1037        /// relation.set_version(Some((VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap())));
1038        /// assert_eq!(relation.to_string(), "vign (>= 2.0)");
1039        /// ```
1040        pub fn set_version(&mut self, version_constraint: Option<(VersionConstraint, Version)>) {
1041            let current_version = self.0.children().find(|n| n.kind() == VERSION);
1042            if let Some((vc, version)) = version_constraint {
1043                let mut builder = GreenNodeBuilder::new();
1044                builder.start_node(VERSION.into());
1045                builder.token(L_PARENS.into(), "(");
1046                builder.start_node(CONSTRAINT.into());
1047                match vc {
1048                    VersionConstraint::GreaterThanEqual => {
1049                        builder.token(R_ANGLE.into(), ">");
1050                        builder.token(EQUAL.into(), "=");
1051                    }
1052                    VersionConstraint::LessThanEqual => {
1053                        builder.token(L_ANGLE.into(), "<");
1054                        builder.token(EQUAL.into(), "=");
1055                    }
1056                    VersionConstraint::Equal => {
1057                        builder.token(EQUAL.into(), "=");
1058                    }
1059                    VersionConstraint::GreaterThan => {
1060                        builder.token(R_ANGLE.into(), ">");
1061                    }
1062                    VersionConstraint::LessThan => {
1063                        builder.token(L_ANGLE.into(), "<");
1064                    }
1065                }
1066                builder.finish_node(); // CONSTRAINT
1067                builder.token(WHITESPACE.into(), " ");
1068                builder.token(IDENT.into(), version.to_string().as_str());
1069                builder.token(R_PARENS.into(), ")");
1070                builder.finish_node(); // VERSION
1071
1072                if let Some(current_version) = current_version {
1073                    self.0.splice_children(
1074                        current_version.index()..current_version.index() + 1,
1075                        vec![SyntaxNode::new_root_mut(builder.finish()).into()],
1076                    );
1077                } else {
1078                    let name_node = self.0.children_with_tokens().find(|n| n.kind() == IDENT);
1079                    let idx = if let Some(name_node) = name_node {
1080                        name_node.index() + 1
1081                    } else {
1082                        0
1083                    };
1084                    let new_children = vec![
1085                        GreenToken::new(WHITESPACE.into(), " ").into(),
1086                        builder.finish().into(),
1087                    ];
1088                    let new_root = SyntaxNode::new_root_mut(
1089                        self.0.green().splice_children(idx..idx, new_children),
1090                    );
1091                    if let Some(parent) = self.0.parent() {
1092                        parent.splice_children(
1093                            self.0.index()..self.0.index() + 1,
1094                            vec![new_root.into()],
1095                        );
1096                        self.0 = parent
1097                            .children_with_tokens()
1098                            .nth(self.0.index())
1099                            .unwrap()
1100                            .clone()
1101                            .into_node()
1102                            .unwrap();
1103                    } else {
1104                        self.0 = new_root;
1105                    }
1106                }
1107            } else if let Some(current_version) = current_version {
1108                // Remove any whitespace before the version token
1109                while let Some(prev) = current_version.prev_sibling_or_token() {
1110                    if prev.kind() == WHITESPACE || prev.kind() == NEWLINE {
1111                        prev.detach();
1112                    } else {
1113                        break;
1114                    }
1115                }
1116                current_version.detach();
1117            }
1118        }
1119
1120        /// Remove this relation
1121        ///
1122        /// # Example
1123        /// ```
1124        /// use r_description::lossless::{Relation, Relations};
1125        /// let mut relations: Relations = r"cli (>= 0.19.0), blah (<< 1.26.0)".parse().unwrap();
1126        /// let mut relation = relations.get_relation(0).unwrap();
1127        /// assert_eq!(relation.to_string(), "cli (>= 0.19.0)");
1128        /// relation.remove();
1129        /// assert_eq!(relations.to_string(), "blah (<< 1.26.0)");
1130        /// ```
1131        pub fn remove(&mut self) {
1132            let is_first = !self
1133                .0
1134                .siblings(Direction::Prev)
1135                .skip(1)
1136                .any(|n| n.kind() == RELATION);
1137            if !is_first {
1138                // Not the first item in the list. Remove whitespace backwards to the previous
1139                // pipe, the pipe and any whitespace until the previous relation
1140                while let Some(n) = self.0.prev_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                        break;
1148                    }
1149                }
1150                while let Some(n) = self.0.prev_sibling_or_token() {
1151                    if n.kind() == WHITESPACE || n.kind() == NEWLINE {
1152                        n.detach();
1153                    } else {
1154                        break;
1155                    }
1156                }
1157            } else {
1158                // First item in the list. Remove whitespace up to the pipe, the pipe and anything
1159                // before the next relation
1160                while let Some(n) = self.0.next_sibling_or_token() {
1161                    if n.kind() == WHITESPACE || n.kind() == NEWLINE {
1162                        n.detach();
1163                    } else if n.kind() == COMMA {
1164                        n.detach();
1165                        break;
1166                    } else {
1167                        panic!("Unexpected node: {n:?}");
1168                    }
1169                }
1170
1171                while let Some(n) = self.0.next_sibling_or_token() {
1172                    if n.kind() == WHITESPACE || n.kind() == NEWLINE {
1173                        n.detach();
1174                    } else {
1175                        break;
1176                    }
1177                }
1178            }
1179            self.0.detach();
1180        }
1181
1182        /// Check if this relation is satisfied by the given package version.
1183        pub fn satisfied_by(
1184            &self,
1185            package_version: impl crate::relations::VersionLookup + Copy,
1186        ) -> bool {
1187            let name = self.name();
1188            let version = self.version();
1189            if let Some(version) = version {
1190                if let Some(package_version) = package_version.lookup_version(&name) {
1191                    match version.0 {
1192                        VersionConstraint::GreaterThanEqual => {
1193                            package_version.into_owned() >= version.1
1194                        }
1195                        VersionConstraint::LessThanEqual => {
1196                            package_version.into_owned() <= version.1
1197                        }
1198                        VersionConstraint::Equal => package_version.into_owned() == version.1,
1199                        VersionConstraint::GreaterThan => package_version.into_owned() > version.1,
1200                        VersionConstraint::LessThan => package_version.into_owned() < version.1,
1201                    }
1202                } else {
1203                    false
1204                }
1205            } else {
1206                true
1207            }
1208        }
1209    }
1210
1211    impl PartialOrd for Relation {
1212        fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1213            Some(self.cmp(other))
1214        }
1215    }
1216
1217    impl Eq for Relation {}
1218
1219    impl Ord for Relation {
1220        fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1221            // Compare by name first, then by version
1222            let name_cmp = self.name().cmp(&other.name());
1223            if name_cmp != std::cmp::Ordering::Equal {
1224                return name_cmp;
1225            }
1226
1227            let self_version = self.version();
1228            let other_version = other.version();
1229
1230            match (self_version, other_version) {
1231                (Some((self_vc, self_version)), Some((other_vc, other_version))) => {
1232                    let vc_cmp = self_vc.cmp(&other_vc);
1233                    if vc_cmp != std::cmp::Ordering::Equal {
1234                        return vc_cmp;
1235                    }
1236
1237                    self_version.cmp(&other_version)
1238                }
1239                (Some(_), None) => std::cmp::Ordering::Greater,
1240                (None, Some(_)) => std::cmp::Ordering::Less,
1241                (None, None) => std::cmp::Ordering::Equal,
1242            }
1243        }
1244    }
1245
1246    impl std::str::FromStr for Relations {
1247        type Err = String;
1248
1249        fn from_str(s: &str) -> Result<Self, Self::Err> {
1250            let parse = parse(s);
1251            if parse.errors.is_empty() {
1252                Ok(parse.root_mut())
1253            } else {
1254                Err(parse.errors.join("\n"))
1255            }
1256        }
1257    }
1258
1259    impl std::str::FromStr for Relation {
1260        type Err = String;
1261
1262        fn from_str(s: &str) -> Result<Self, Self::Err> {
1263            let rels = s.parse::<Relations>()?;
1264            let mut relations = rels.relations();
1265
1266            let relation = if let Some(relation) = relations.next() {
1267                relation
1268            } else {
1269                return Err("No relation found".to_string());
1270            };
1271
1272            if relations.next().is_some() {
1273                return Err("Multiple relations found".to_string());
1274            }
1275
1276            Ok(relation)
1277        }
1278    }
1279
1280    #[cfg(test)]
1281    mod tests {
1282        use super::*;
1283
1284        #[test]
1285        fn test_parse() {
1286            let input = "cli";
1287            let parsed: Relations = input.parse().unwrap();
1288            assert_eq!(parsed.to_string(), input);
1289            assert_eq!(parsed.relations().count(), 1);
1290            let relation = parsed.relations().next().unwrap();
1291            assert_eq!(relation.to_string(), "cli");
1292            assert_eq!(relation.version(), None);
1293
1294            let input = "cli (>= 0.20.21)";
1295            let parsed: Relations = input.parse().unwrap();
1296            assert_eq!(parsed.to_string(), input);
1297            assert_eq!(parsed.relations().count(), 1);
1298            let relation = parsed.relations().next().unwrap();
1299            assert_eq!(relation.to_string(), "cli (>= 0.20.21)");
1300            assert_eq!(
1301                relation.version(),
1302                Some((
1303                    VersionConstraint::GreaterThanEqual,
1304                    "0.20.21".parse().unwrap()
1305                ))
1306            );
1307        }
1308
1309        #[test]
1310        fn test_multiple() {
1311            let input = "cli (>= 0.20.21), cli (<< 0.21)";
1312            let parsed: Relations = input.parse().unwrap();
1313            assert_eq!(parsed.to_string(), input);
1314            assert_eq!(parsed.relations().count(), 2);
1315            let relation = parsed.relations().next().unwrap();
1316            assert_eq!(relation.to_string(), "cli (>= 0.20.21)");
1317            assert_eq!(
1318                relation.version(),
1319                Some((
1320                    VersionConstraint::GreaterThanEqual,
1321                    "0.20.21".parse().unwrap()
1322                ))
1323            );
1324            let relation = parsed.relations().nth(1).unwrap();
1325            assert_eq!(relation.to_string(), "cli (<< 0.21)");
1326            assert_eq!(
1327                relation.version(),
1328                Some((VersionConstraint::LessThan, "0.21".parse().unwrap()))
1329            );
1330        }
1331
1332        #[test]
1333        fn test_new() {
1334            let r = Relation::new(
1335                "cli",
1336                Some((VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap())),
1337            );
1338
1339            assert_eq!(r.to_string(), "cli (>= 2.0)");
1340        }
1341
1342        #[test]
1343        fn test_drop_constraint() {
1344            let mut r = Relation::new(
1345                "cli",
1346                Some((VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap())),
1347            );
1348
1349            r.drop_constraint();
1350
1351            assert_eq!(r.to_string(), "cli");
1352        }
1353
1354        #[test]
1355        fn test_simple() {
1356            let r = Relation::simple("cli");
1357
1358            assert_eq!(r.to_string(), "cli");
1359        }
1360
1361        #[test]
1362        fn test_remove_first_relation() {
1363            let mut rels: Relations = r#"cli (>= 0.20.21), cli (<< 0.21)"#.parse().unwrap();
1364            let removed = rels.remove_relation(0);
1365            assert_eq!(removed.to_string(), "cli (>= 0.20.21)");
1366            assert_eq!(rels.to_string(), "cli (<< 0.21)");
1367        }
1368
1369        #[test]
1370        fn test_remove_last_relation() {
1371            let mut rels: Relations = r#"cli (>= 0.20.21), cli (<< 0.21)"#.parse().unwrap();
1372            rels.remove_relation(1);
1373            assert_eq!(rels.to_string(), "cli (>= 0.20.21)");
1374        }
1375
1376        #[test]
1377        fn test_remove_middle() {
1378            let mut rels: Relations =
1379                r#"cli (>= 0.20.21), cli (<< 0.21), cli (<< 0.22)"#.parse().unwrap();
1380            rels.remove_relation(1);
1381            assert_eq!(rels.to_string(), "cli (>= 0.20.21), cli (<< 0.22)");
1382        }
1383
1384        #[test]
1385        fn test_remove_added() {
1386            let mut rels: Relations = r#"cli (>= 0.20.21)"#.parse().unwrap();
1387            let relation = Relation::simple("cli");
1388            rels.push(relation);
1389            rels.remove_relation(1);
1390            assert_eq!(rels.to_string(), "cli (>= 0.20.21)");
1391        }
1392
1393        #[test]
1394        fn test_push() {
1395            let mut rels: Relations = r#"cli (>= 0.20.21)"#.parse().unwrap();
1396            let relation = Relation::simple("cli");
1397            rels.push(relation);
1398            assert_eq!(rels.to_string(), "cli (>= 0.20.21), cli");
1399        }
1400
1401        #[test]
1402        fn test_push_from_empty() {
1403            let mut rels: Relations = "".parse().unwrap();
1404            let relation = Relation::simple("cli");
1405            rels.push(relation);
1406            assert_eq!(rels.to_string(), "cli");
1407        }
1408
1409        #[test]
1410        fn test_insert() {
1411            let mut rels: Relations = r#"cli (>= 0.20.21), cli (<< 0.21)"#.parse().unwrap();
1412            let relation = Relation::simple("cli");
1413            rels.insert(1, relation);
1414            assert_eq!(rels.to_string(), "cli (>= 0.20.21), cli, cli (<< 0.21)");
1415        }
1416
1417        #[test]
1418        fn test_insert_at_start() {
1419            let mut rels: Relations = r#"cli (>= 0.20.21), cli (<< 0.21)"#.parse().unwrap();
1420            let relation = Relation::simple("cli");
1421            rels.insert(0, relation);
1422            assert_eq!(rels.to_string(), "cli, cli (>= 0.20.21), cli (<< 0.21)");
1423        }
1424
1425        #[test]
1426        fn test_insert_after_error() {
1427            let (mut rels, errors) = Relations::parse_relaxed("@foo@, debhelper (>= 1.0)");
1428            assert_eq!(
1429                errors,
1430                vec![
1431                    "expected identifier or comma but got ERROR",
1432                    "expected comma or end of file but got Some(IDENT)",
1433                    "expected identifier or comma but got ERROR"
1434                ]
1435            );
1436            let relation = Relation::simple("bar");
1437            rels.push(relation);
1438            assert_eq!(rels.to_string(), "@foo@, debhelper (>= 1.0), bar");
1439        }
1440
1441        #[test]
1442        fn test_insert_before_error() {
1443            let (mut rels, errors) = Relations::parse_relaxed("debhelper (>= 1.0), @foo@, bla");
1444            assert_eq!(
1445                errors,
1446                vec![
1447                    "expected identifier or comma but got ERROR",
1448                    "expected comma or end of file but got Some(IDENT)",
1449                    "expected identifier or comma but got ERROR"
1450                ]
1451            );
1452            let relation = Relation::simple("bar");
1453            rels.insert(0, relation);
1454            assert_eq!(rels.to_string(), "bar, debhelper (>= 1.0), @foo@, bla");
1455        }
1456
1457        #[test]
1458        fn test_replace() {
1459            let mut rels: Relations = r#"cli (>= 0.20.21), cli (<< 0.21)"#.parse().unwrap();
1460            let relation = Relation::simple("cli");
1461            rels.replace(1, relation);
1462            assert_eq!(rels.to_string(), "cli (>= 0.20.21), cli");
1463        }
1464
1465        #[test]
1466        fn test_parse_relation() {
1467            let parsed: Relation = "cli (>= 0.20.21)".parse().unwrap();
1468            assert_eq!(parsed.to_string(), "cli (>= 0.20.21)");
1469            assert_eq!(
1470                parsed.version(),
1471                Some((
1472                    VersionConstraint::GreaterThanEqual,
1473                    "0.20.21".parse().unwrap()
1474                ))
1475            );
1476            assert_eq!(
1477                "foo, bar".parse::<Relation>().unwrap_err(),
1478                "Multiple relations found"
1479            );
1480            assert_eq!("".parse::<Relation>().unwrap_err(), "No relation found");
1481        }
1482
1483        #[test]
1484        fn test_relations_satisfied_by() {
1485            let rels: Relations = "cli (>= 0.20.21), cli (<< 0.21)".parse().unwrap();
1486            let satisfied = |name: &str| -> Option<Version> {
1487                match name {
1488                    "cli" => Some("0.20.21".parse().unwrap()),
1489                    _ => None,
1490                }
1491            };
1492            assert!(rels.satisfied_by(satisfied));
1493
1494            let satisfied = |name: &str| match name {
1495                "cli" => Some("0.21".parse().unwrap()),
1496                _ => None,
1497            };
1498            assert!(!rels.satisfied_by(satisfied));
1499
1500            let satisfied = |name: &str| match name {
1501                "cli" => Some("0.20.20".parse().unwrap()),
1502                _ => None,
1503            };
1504            assert!(!rels.satisfied_by(satisfied));
1505        }
1506
1507        #[test]
1508        fn test_wrap_and_sort_relation() {
1509            let relation: Relation = "   cli   (>=   11.0)".parse().unwrap();
1510
1511            let wrapped = relation.wrap_and_sort();
1512
1513            assert_eq!(wrapped.to_string(), "cli (>= 11.0)");
1514        }
1515
1516        #[test]
1517        fn test_wrap_and_sort_relations() {
1518            let relations: Relations = "cli (>= 0.20.21)  , \n\n\n\ncli (<< 0.21)".parse().unwrap();
1519
1520            let wrapped = relations.wrap_and_sort();
1521
1522            assert_eq!(wrapped.to_string(), "cli (<< 0.21), cli (>= 0.20.21)");
1523        }
1524
1525        #[cfg(feature = "serde")]
1526        #[test]
1527        fn test_serialize_relations() {
1528            let relations: Relations = "cli (>= 0.20.21), cli (<< 0.21)".parse().unwrap();
1529            let serialized = serde_json::to_string(&relations).unwrap();
1530            assert_eq!(serialized, r#""cli (>= 0.20.21), cli (<< 0.21)""#);
1531        }
1532
1533        #[cfg(feature = "serde")]
1534        #[test]
1535        fn test_deserialize_relations() {
1536            let relations: Relations = "cli (>= 0.20.21), cli (<< 0.21)".parse().unwrap();
1537            let serialized = serde_json::to_string(&relations).unwrap();
1538            let deserialized: Relations = serde_json::from_str(&serialized).unwrap();
1539            assert_eq!(deserialized.to_string(), relations.to_string());
1540        }
1541
1542        #[cfg(feature = "serde")]
1543        #[test]
1544        fn test_serialize_relation() {
1545            let relation: Relation = "cli (>= 0.20.21)".parse().unwrap();
1546            let serialized = serde_json::to_string(&relation).unwrap();
1547            assert_eq!(serialized, r#""cli (>= 0.20.21)""#);
1548        }
1549
1550        #[cfg(feature = "serde")]
1551        #[test]
1552        fn test_deserialize_relation() {
1553            let relation: Relation = "cli (>= 0.20.21)".parse().unwrap();
1554            let serialized = serde_json::to_string(&relation).unwrap();
1555            let deserialized: Relation = serde_json::from_str(&serialized).unwrap();
1556            assert_eq!(deserialized.to_string(), relation.to_string());
1557        }
1558
1559        #[test]
1560        fn test_relation_set_version() {
1561            let mut rel: Relation = "vign".parse().unwrap();
1562            rel.set_version(None);
1563            assert_eq!("vign", rel.to_string());
1564            rel.set_version(Some((
1565                VersionConstraint::GreaterThanEqual,
1566                "2.0".parse().unwrap(),
1567            )));
1568            assert_eq!("vign (>= 2.0)", rel.to_string());
1569            rel.set_version(None);
1570            assert_eq!("vign", rel.to_string());
1571            rel.set_version(Some((
1572                VersionConstraint::GreaterThanEqual,
1573                "2.0".parse().unwrap(),
1574            )));
1575            rel.set_version(Some((
1576                VersionConstraint::GreaterThanEqual,
1577                "1.1".parse().unwrap(),
1578            )));
1579            assert_eq!("vign (>= 1.1)", rel.to_string());
1580        }
1581
1582        #[test]
1583        fn test_wrap_and_sort_removes_empty_entries() {
1584            let relations: Relations = "foo, , bar, ".parse().unwrap();
1585            let wrapped = relations.wrap_and_sort();
1586            assert_eq!(wrapped.to_string(), "bar, foo");
1587        }
1588    }
1589}
1590
1591#[cfg(test)]
1592mod tests {
1593    use super::*;
1594
1595    #[test]
1596    fn test_parse() {
1597        let s = r###"Package: mypackage
1598Title: What the Package Does (One Line, Title Case)
1599Version: 0.0.0.9000
1600Authors@R: 
1601    person("First", "Last", , "first.last@example.com", role = c("aut", "cre"),
1602           comment = c(ORCID = "YOUR-ORCID-ID"))
1603Description: What the package does (one paragraph).
1604License: `use_mit_license()`, `use_gpl3_license()` or friends to pick a
1605    license
1606Encoding: UTF-8
1607Roxygen: list(markdown = TRUE)
1608RoxygenNote: 7.3.2
1609"###;
1610        let desc: RDescription = s.parse().unwrap();
1611
1612        assert_eq!(desc.package(), Some("mypackage".to_string()));
1613        assert_eq!(
1614            desc.title(),
1615            Some("What the Package Does (One Line, Title Case)".to_string())
1616        );
1617        assert_eq!(desc.version(), Some("0.0.0.9000".to_string()));
1618        assert_eq!(
1619            desc.authors(),
1620            Some(RCode(
1621                r#"person("First", "Last", , "first.last@example.com", role = c("aut", "cre"),
1622comment = c(ORCID = "YOUR-ORCID-ID"))"#
1623                    .to_string()
1624            ))
1625        );
1626        assert_eq!(
1627            desc.description(),
1628            Some("What the package does (one paragraph).".to_string())
1629        );
1630        assert_eq!(
1631            desc.license(),
1632            Some(
1633                "`use_mit_license()`, `use_gpl3_license()` or friends to pick a\nlicense"
1634                    .to_string()
1635            )
1636        );
1637        assert_eq!(desc.encoding(), Some("UTF-8".to_string()));
1638        assert_eq!(desc.roxygen(), Some("list(markdown = TRUE)".to_string()));
1639        assert_eq!(desc.roxygen_note(), Some("7.3.2".to_string()));
1640
1641        assert_eq!(desc.to_string(), s);
1642    }
1643
1644    #[test]
1645    fn test_parse_dplyr() {
1646        let s = include_str!("../testdata/dplyr.desc");
1647
1648        let desc: RDescription = s.parse().unwrap();
1649        assert_eq!("dplyr", desc.package().unwrap());
1650        assert_eq!(
1651            "https://dplyr.tidyverse.org, https://github.com/tidyverse/dplyr",
1652            desc.url().unwrap().as_str()
1653        );
1654    }
1655}