1use crate::RCode;
13use deb822_lossless::Paragraph;
14pub use relations::{Relation, Relations, Version};
15
16pub 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)]
32pub enum Error {
34 Io(std::io::Error),
36
37 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 pub fn new() -> Self {
75 Self(Paragraph::new())
76 }
77
78 pub fn package(&self) -> Option<String> {
80 self.0.get("Package")
81 }
82
83 pub fn set_package(&mut self, package: &str) {
85 self.0.insert("Package", package);
86 }
87
88 pub fn title(&self) -> Option<String> {
93 self.0.get("Title")
94 }
95
96 pub fn maintainer(&self) -> Option<String> {
98 self.0.get("Maintainer")
99 }
100
101 pub fn set_maintainer(&mut self, maintainer: &str) {
103 self.0.insert("Maintainer", maintainer);
104 }
105
106 pub fn authors(&self) -> Option<RCode> {
108 self.0.get("Authors@R").map(|s| s.parse().unwrap())
109 }
110
111 pub fn set_authors(&mut self, authors: &RCode) {
113 self.0.insert("Authors@R", &authors.to_string());
114 }
115
116 pub fn set_title(&mut self, title: &str) {
118 self.0.insert("Title", title);
119 }
120
121 pub fn description(&self) -> Option<String> {
123 self.0.get("Description")
124 }
125
126 pub fn set_description(&mut self, description: &str) {
128 self.0.insert("Description", description);
129 }
130
131 pub fn version(&self) -> Option<String> {
133 self.0.get("Version")
134 }
135
136 pub fn set_version(&mut self, version: &str) {
138 self.0.insert("Version", version);
139 }
140
141 pub fn encoding(&self) -> Option<String> {
143 self.0.get("Encoding")
144 }
145
146 pub fn set_encoding(&mut self, encoding: &str) {
148 self.0.insert("Encoding", encoding);
149 }
150
151 pub fn license(&self) -> Option<String> {
153 self.0.get("License")
154 }
155
156 pub fn set_license(&mut self, license: &str) {
158 self.0.insert("License", license);
159 }
160
161 pub fn roxygen_note(&self) -> Option<String> {
163 self.0.get("RoxygenNote")
164 }
165
166 pub fn set_roxygen_note(&mut self, roxygen_note: &str) {
168 self.0.insert("RoxygenNote", roxygen_note);
169 }
170
171 pub fn roxygen(&self) -> Option<String> {
173 self.0.get("Roxygen")
174 }
175
176 pub fn set_roxygen(&mut self, roxygen: &str) {
178 self.0.insert("Roxygen", roxygen);
179 }
180
181 pub fn url(&self) -> Option<String> {
183 self.0.get("URL")
185 }
186
187 pub fn set_url(&mut self, url: &str) {
189 self.0.insert("URL", url);
191 }
192
193 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 pub fn set_bug_reports(&mut self, bug_reports: &url::Url) {
202 self.0.insert("BugReports", bug_reports.as_str());
203 }
204
205 pub fn imports(&self) -> Option<Relations> {
207 self.0.get("Imports").map(|s| s.parse().unwrap())
208 }
209
210 pub fn set_imports(&mut self, imports: Relations) {
212 self.0.insert("Imports", &imports.to_string());
213 }
214
215 pub fn suggests(&self) -> Option<Relations> {
217 self.0.get("Suggests").map(|s| s.parse().unwrap())
218 }
219
220 pub fn set_suggests(&mut self, suggests: Relations) {
222 self.0.insert("Suggests", &suggests.to_string());
223 }
224
225 pub fn depends(&self) -> Option<Relations> {
227 self.0.get("Depends").map(|s| s.parse().unwrap())
228 }
229
230 pub fn set_depends(&mut self, depends: Relations) {
232 self.0.insert("Depends", &depends.to_string());
233 }
234
235 pub fn linking_to(&self) -> Option<Relations> {
237 self.0.get("LinkingTo").map(|s| s.parse().unwrap())
238 }
239
240 pub fn set_linking_to(&mut self, linking_to: Relations) {
242 self.0.insert("LinkingTo", &linking_to.to_string());
243 }
244
245 pub fn enhances(&self) -> Option<Relations> {
247 self.0.get("Enhances").map(|s| s.parse().unwrap())
248 }
249
250 pub fn set_enhances(&mut self, enhances: Relations) {
252 self.0.insert("Enhances", &enhances.to_string());
253 }
254
255 pub fn lazy_data(&self) -> Option<bool> {
257 self.0.get("LazyData").map(|s| s == "true")
258 }
259
260 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 pub fn collate(&self) -> Option<String> {
268 self.0.get("Collate")
269 }
270
271 pub fn set_collate(&mut self, collate: &str) {
273 self.0.insert("Collate", collate);
274 }
275
276 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 pub fn set_vignette_builder(&mut self, vignette_builder: &[&str]) {
285 self.0
286 .insert("VignetteBuilder", &vignette_builder.join(", "));
287 }
288
289 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 pub fn set_system_requirements(&mut self, system_requirements: &[&str]) {
298 self.0
299 .insert("SystemRequirements", &system_requirements.join(", "));
300 }
301
302 pub fn date(&self) -> Option<String> {
304 self.0.get("Date")
305 }
306
307 pub fn set_date(&mut self, date: &str) {
309 self.0.insert("Date", date);
310 }
311
312 pub fn repository(&self) -> Option<String> {
316 self.0.get("Repository")
317 }
318
319 pub fn set_repository(&mut self, repository: &str) {
321 self.0.insert("Repository", repository);
322 }
323}
324
325pub mod relations {
326 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 pub struct Version {
357 pub components: Vec<u32>,
359 original: String,
360 }
361
362 impl Version {
363 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 #[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 #[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 use rowan::{GreenNode, GreenToken};
485
486 use rowan::GreenNodeBuilder;
490
491 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 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 tokens: Vec<(SyntaxKind, String)>,
520 builder: GreenNodeBuilder<'static>,
522 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 }
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 Parse {
632 green_node: self.builder.finish(),
633 errors: self.errors,
634 }
635 }
636 fn bump(&mut self) {
638 let (kind, text) = self.tokens.pop().unwrap();
639 self.builder.token(kind.into(), text.as_str());
640 }
641 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 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 #[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 pub fn new() -> Self {
800 Self::from(vec![])
801 }
802
803 #[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 Self::from(entries)
813 }
814
815 pub fn relations(&self) -> impl Iterator<Item = Relation> + '_ {
817 self.0.children().filter_map(Relation::cast)
818 }
819
820 pub fn iter(&self) -> impl Iterator<Item = Relation> + '_ {
822 self.relations()
823 }
824
825 pub fn get_relation(&self, idx: usize) -> Option<Relation> {
827 self.relations().nth(idx)
828 }
829
830 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 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 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 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 pub fn push(&mut self, relation: Relation) {
889 let pos = self.relations().count();
890 self.insert(pos, relation);
891 }
892
893 pub fn parse_relaxed(s: &str) -> (Relations, Vec<String>) {
895 let parse = parse(s);
896 (parse.root_mut(), parse.errors)
897 }
898
899 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 pub fn is_empty(&self) -> bool {
909 self.relations().count() == 0
910 }
911
912 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 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 #[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 pub fn simple(name: &str) -> Self {
1074 Self::new(name, None)
1075 }
1076
1077 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 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 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 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 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(); builder.token(WHITESPACE.into(), " ");
1164 builder.token(IDENT.into(), version.to_string().as_str());
1165 builder.token(R_PARENS.into(), ")");
1166 builder.finish_node(); 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 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 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 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 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 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 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}