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