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