1use deb822_derive::{FromDeb822, ToDeb822};
10use deb822_fast::{FromDeb822Paragraph, ToDeb822Paragraph};
11
12use crate::RCode;
13use std::iter::Peekable;
14
15use crate::relations::SyntaxKind::*;
16use crate::relations::{lex, SyntaxKind, VersionConstraint};
17use crate::version::Version;
18
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct UrlEntry {
22 pub url: url::Url,
24
25 pub label: Option<String>,
27}
28
29impl std::fmt::Display for UrlEntry {
30 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
31 write!(f, "{}", self.url.as_str())?;
32 if let Some(label) = &self.label {
33 write!(f, " ({label})")?;
34 }
35 Ok(())
36 }
37}
38
39impl std::str::FromStr for UrlEntry {
40 type Err = String;
41
42 fn from_str(s: &str) -> Result<Self, Self::Err> {
43 if let Some(pos) = s.find('(') {
44 let url = s[..pos].trim();
45 let label = s[pos + 1..s.len() - 1].trim();
46 Ok(UrlEntry {
47 url: url::Url::parse(url).map_err(|e| e.to_string())?,
48 label: Some(label.to_string()),
49 })
50 } else {
51 Ok(UrlEntry {
52 url: url::Url::parse(s).map_err(|e| e.to_string())?,
53 label: None,
54 })
55 }
56 }
57}
58
59fn serialize_url_list(urls: &[UrlEntry]) -> String {
60 let mut s = String::new();
61 for (i, url) in urls.iter().enumerate() {
62 if i > 0 {
63 s.push_str(", ");
64 }
65 s.push_str(url.to_string().as_str());
66 }
67 s
68}
69
70fn deserialize_url_list(s: &str) -> Result<Vec<UrlEntry>, String> {
71 s.split([',', '\n'].as_ref())
72 .filter(|s| !s.trim().is_empty())
73 .map(|s| s.trim().parse())
74 .collect::<Result<Vec<_>, String>>()
75 .map_err(|e| e.to_string())
76}
77
78fn serialize_repository_list(repositories: &[url::Url]) -> String {
79 let mut s = String::new();
80 for (i, repository) in repositories.iter().enumerate() {
81 if i > 0 {
82 s.push_str(", ");
83 }
84 s.push_str(repository.as_str());
85 }
86 s
87}
88
89fn deserialize_repository_list(s: &str) -> Result<Vec<url::Url>, String> {
90 s.split([',', '\n'].as_ref())
91 .filter(|s| !s.trim().is_empty())
92 .map(|s| url::Url::parse(s.trim()).map_err(|e| e.to_string()))
93 .collect()
94}
95
96#[derive(FromDeb822, ToDeb822, Debug, PartialEq, Eq)]
97pub struct RDescription {
99 #[deb822(field = "Package")]
101 pub name: String,
102
103 #[deb822(field = "Description")]
105 pub description: String,
106
107 #[deb822(field = "Title")]
108 pub title: String,
110
111 #[deb822(field = "Maintainer")]
112 pub maintainer: Option<String>,
114
115 #[deb822(field = "Author")]
116 pub author: Option<String>,
118
119 #[deb822(field = "Authors@R")]
122 pub authors: Option<RCode>,
123
124 #[deb822(field = "Version")]
125 pub version: Version,
127
128 #[deb822(field = "Encoding")]
131 pub encoding: Option<String>,
132
133 #[deb822(field = "License")]
134 pub license: String,
136
137 #[deb822(field = "URL", serialize_with = serialize_url_list, deserialize_with = deserialize_url_list)]
138 pub url: Option<Vec<UrlEntry>>,
141
142 #[deb822(field = "BugReports")]
143 pub bug_reports: Option<String>,
145
146 #[deb822(field = "Imports")]
147 pub imports: Option<Relations>,
149
150 #[deb822(field = "Suggests")]
151 pub suggests: Option<Relations>,
153
154 #[deb822(field = "Depends")]
155 pub depends: Option<Relations>,
157
158 #[deb822(field = "LinkingTo")]
159 pub linking_to: Option<Relations>,
161
162 #[deb822(field = "LazyData")]
163 pub lazy_data: Option<String>,
165
166 #[deb822(field = "Collate")]
167 pub collate: Option<String>,
169
170 #[deb822(field = "VignetteBuilder")]
171 pub vignette_builder: Option<String>,
173
174 #[deb822(field = "SystemRequirements")]
175 pub system_requirements: Option<String>,
177
178 #[deb822(field = "Date")]
179 pub date: Option<String>,
182
183 #[deb822(field = "Language")]
184 pub language: Option<String>,
188
189 #[deb822(field = "Repository")]
190 pub repository: Option<String>,
192
193 #[deb822(field = "Additional_repositories", serialize_with = serialize_repository_list, deserialize_with = deserialize_repository_list)]
194 pub additional_repositories: Option<Vec<url::Url>>,
196}
197
198#[derive(Debug, Clone, PartialEq, Eq, Hash)]
200pub struct Relation {
201 pub name: String,
203 pub version: Option<(VersionConstraint, Version)>,
205}
206
207impl Default for Relation {
208 fn default() -> Self {
209 Self::new()
210 }
211}
212
213impl Relation {
214 pub fn new() -> Self {
216 Self {
217 name: String::new(),
218 version: None,
219 }
220 }
221
222 pub fn satisfied_by(&self, package_version: impl crate::relations::VersionLookup) -> bool {
239 let actual = package_version.lookup_version(self.name.as_str());
240 if let Some((vc, version)) = &self.version {
241 if let Some(actual) = actual {
242 match vc {
243 VersionConstraint::GreaterThanEqual => actual.as_ref() >= version,
244 VersionConstraint::LessThanEqual => actual.as_ref() <= version,
245 VersionConstraint::Equal => actual.as_ref() == version,
246 VersionConstraint::NotEqual => actual.as_ref() != version,
247 VersionConstraint::GreaterThan => actual.as_ref() > version,
248 VersionConstraint::LessThan => actual.as_ref() < version,
249 }
250 } else {
251 false
252 }
253 } else {
254 actual.is_some()
255 }
256 }
257}
258
259impl std::fmt::Display for Relation {
260 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
261 write!(f, "{}", self.name)?;
262 if let Some((constraint, version)) = &self.version {
263 write!(f, " ({constraint} {version})")?;
264 }
265 Ok(())
266 }
267}
268
269#[cfg(feature = "serde")]
270impl<'de> serde::Deserialize<'de> for Relation {
271 fn deserialize<D>(deserializer: D) -> Result<Relation, D::Error>
272 where
273 D: serde::Deserializer<'de>,
274 {
275 let s = String::deserialize(deserializer)?;
276 s.parse().map_err(serde::de::Error::custom)
277 }
278}
279
280#[cfg(feature = "serde")]
281impl serde::Serialize for Relation {
282 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
283 where
284 S: serde::Serializer,
285 {
286 self.to_string().serialize(serializer)
287 }
288}
289
290#[derive(Debug, Clone, PartialEq, Eq, Hash)]
292pub struct Relations(pub Vec<Relation>);
293
294impl std::ops::Index<usize> for Relations {
295 type Output = Relation;
296
297 fn index(&self, index: usize) -> &Self::Output {
298 &self.0[index]
299 }
300}
301
302impl std::ops::IndexMut<usize> for Relations {
303 fn index_mut(&mut self, index: usize) -> &mut Self::Output {
304 &mut self.0[index]
305 }
306}
307
308impl FromIterator<Relation> for Relations {
309 fn from_iter<I: IntoIterator<Item = Relation>>(iter: I) -> Self {
310 Self(iter.into_iter().collect())
311 }
312}
313
314impl Default for Relations {
315 fn default() -> Self {
316 Self::new()
317 }
318}
319
320impl Relations {
321 pub fn new() -> Self {
323 Self(Vec::new())
324 }
325
326 pub fn remove(&mut self, index: usize) {
328 self.0.remove(index);
329 }
330
331 pub fn iter(&self) -> impl Iterator<Item = &Relation> {
333 self.0.iter()
334 }
335
336 pub fn len(&self) -> usize {
338 self.0.len()
339 }
340
341 pub fn is_empty(&self) -> bool {
343 self.0.is_empty()
344 }
345
346 pub fn satisfied_by(
348 &self,
349 package_version: impl crate::relations::VersionLookup + Copy,
350 ) -> bool {
351 self.0.iter().all(|r| r.satisfied_by(package_version))
352 }
353}
354
355impl std::fmt::Display for Relations {
356 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
357 for (i, relation) in self.0.iter().enumerate() {
358 if i > 0 {
359 f.write_str(", ")?;
360 }
361 write!(f, "{relation}")?;
362 }
363 Ok(())
364 }
365}
366
367impl std::str::FromStr for Relation {
368 type Err = String;
369
370 fn from_str(s: &str) -> Result<Self, Self::Err> {
371 let tokens = lex(s);
372 let mut tokens = tokens.into_iter().peekable();
373
374 fn eat_whitespace(tokens: &mut Peekable<impl Iterator<Item = (SyntaxKind, String)>>) {
375 while let Some((k, _)) = tokens.peek() {
376 match k {
377 WHITESPACE | NEWLINE => {
378 tokens.next();
379 }
380 _ => break,
381 }
382 }
383 }
384
385 let name = match tokens.next() {
386 Some((IDENT, name)) => name,
387 _ => return Err("Expected package name".to_string()),
388 };
389
390 eat_whitespace(&mut tokens);
391
392 let version = if let Some((L_PARENS, _)) = tokens.peek() {
393 tokens.next();
394 eat_whitespace(&mut tokens);
395 let mut constraint = String::new();
396 while let Some((kind, t)) = tokens.peek() {
397 match kind {
398 EQUAL | L_ANGLE | R_ANGLE | NOT => {
399 constraint.push_str(t);
400 tokens.next();
401 }
402 _ => break,
403 }
404 }
405 let constraint = constraint.parse()?;
406 eat_whitespace(&mut tokens);
407 let version_string = match tokens.next() {
409 Some((IDENT, s)) => s,
410 _ => return Err("Expected version string".to_string()),
411 };
412 let version: Version = version_string.parse().map_err(|e: String| e.to_string())?;
413 eat_whitespace(&mut tokens);
414 if let Some((R_PARENS, _)) = tokens.next() {
415 } else {
416 return Err(format!("Expected ')', found {:?}", tokens.next()));
417 }
418 Some((constraint, version))
419 } else {
420 None
421 };
422
423 eat_whitespace(&mut tokens);
424
425 if let Some((kind, _)) = tokens.next() {
426 return Err(format!("Unexpected token: {kind:?}"));
427 }
428
429 Ok(Relation { name, version })
430 }
431}
432
433impl std::str::FromStr for Relations {
434 type Err = String;
435
436 fn from_str(s: &str) -> Result<Self, Self::Err> {
437 let mut relations = Vec::new();
438 if s.is_empty() {
439 return Ok(Relations(relations));
440 }
441 for relation in s.split(',') {
442 let relation = relation.trim();
443 if relation.is_empty() {
444 continue;
446 }
447 relations.push(relation.parse()?);
448 }
449 Ok(Relations(relations))
450 }
451}
452
453#[cfg(feature = "serde")]
454impl<'de> serde::Deserialize<'de> for Relations {
455 fn deserialize<D>(deserializer: D) -> Result<Relations, D::Error>
456 where
457 D: serde::Deserializer<'de>,
458 {
459 let s = String::deserialize(deserializer)?;
460 s.parse().map_err(serde::de::Error::custom)
461 }
462}
463
464#[cfg(feature = "serde")]
465impl serde::Serialize for Relations {
466 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
467 where
468 S: serde::Serializer,
469 {
470 self.to_string().serialize(serializer)
471 }
472}
473
474impl std::str::FromStr for RDescription {
475 type Err = String;
476
477 fn from_str(s: &str) -> Result<Self, Self::Err> {
478 let para = deb822_fast::Paragraph::from_str(s).map_err(|e| e.to_string())?;
479 Self::from_paragraph(¶)
480 }
481}
482
483impl std::fmt::Display for RDescription {
484 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
485 let para: deb822_fast::Paragraph = self.to_paragraph();
486 f.write_str(¶.to_string())?;
487 Ok(())
488 }
489}
490
491#[cfg(test)]
492mod tests {
493 use super::*;
494
495 #[test]
496 fn test_parse() {
497 let s = r###"Package: mypackage
498Title: What the Package Does (One Line, Title Case)
499Version: 0.0.0.9000
500Authors@R:
501 person("First", "Last", , "first.last@example.com", role = c("aut", "cre"),
502 comment = c(ORCID = "YOUR-ORCID-ID"))
503Description: What the package does (one paragraph).
504License: `use_mit_license()`, `use_gpl3_license()` or friends to pick a
505 license
506Encoding: UTF-8
507Roxygen: list(markdown = TRUE)
508RoxygenNote: 7.3.2
509"###;
510 let desc: RDescription = s.parse().unwrap();
511
512 assert_eq!(desc.name, "mypackage".to_string());
513 assert_eq!(
514 desc.title,
515 "What the Package Does (One Line, Title Case)".to_string()
516 );
517 assert_eq!(desc.version, "0.0.0.9000".parse().unwrap());
518 assert_eq!(
519 desc.authors,
520 Some(RCode(
521 r#"
522person("First", "Last", , "first.last@example.com", role = c("aut", "cre"),
523comment = c(ORCID = "YOUR-ORCID-ID"))"#
524 .to_string()
525 ))
526 );
527 assert_eq!(
528 desc.description,
529 "What the package does (one paragraph).".to_string()
530 );
531 assert_eq!(
532 desc.license,
533 "`use_mit_license()`, `use_gpl3_license()` or friends to pick a\nlicense".to_string()
534 );
535 assert_eq!(desc.encoding, Some("UTF-8".to_string()));
536
537 assert_eq!(
538 desc.to_string(),
539 r###"Package: mypackage
540Description: What the package does (one paragraph).
541Title: What the Package Does (One Line, Title Case)
542Authors@R:
543 person("First", "Last", , "first.last@example.com", role = c("aut", "cre"),
544 comment = c(ORCID = "YOUR-ORCID-ID"))
545Version: 0.0.0.9000
546Encoding: UTF-8
547License: `use_mit_license()`, `use_gpl3_license()` or friends to pick a
548 license
549"###
550 );
551 }
552
553 #[test]
554 fn test_parse_dplyr() {
555 let s = include_str!("../testdata/dplyr.desc");
556 let desc: RDescription = s.parse().unwrap();
557
558 assert_eq!(desc.name, "dplyr".to_string());
559 }
560
561 #[test]
562 fn test_parse_relations() {
563 let input = "cli";
564 let parsed: Relations = input.parse().unwrap();
565 assert_eq!(parsed.to_string(), input);
566 assert_eq!(parsed.len(), 1);
567 let relation = &parsed[0];
568 assert_eq!(relation.to_string(), "cli");
569 assert_eq!(relation.version, None);
570
571 let input = "cli (>= 0.20.21)";
572 let parsed: Relations = input.parse().unwrap();
573 assert_eq!(parsed.to_string(), input);
574 assert_eq!(parsed.len(), 1);
575 let relation = &parsed[0];
576 assert_eq!(relation.to_string(), "cli (>= 0.20.21)");
577 assert_eq!(
578 relation.version,
579 Some((
580 VersionConstraint::GreaterThanEqual,
581 "0.20.21".parse().unwrap()
582 ))
583 );
584
585 let parsed: Relations = "xml2 (> 1.0.0)".parse().unwrap();
586 assert_eq!(parsed.len(), 1);
587 assert_eq!(parsed[0].name, "xml2");
588 assert_eq!(
589 parsed[0].version,
590 Some((VersionConstraint::GreaterThan, "1.0.0".parse().unwrap()))
591 );
592
593 let parsed: Relations = "xml2 (< 2.0.0)".parse().unwrap();
594 assert_eq!(parsed.len(), 1);
595 assert_eq!(parsed[0].name, "xml2");
596 assert_eq!(
597 parsed[0].version,
598 Some((VersionConstraint::LessThan, "2.0.0".parse().unwrap()))
599 );
600
601 let parsed: Relations = "xml2 (== 2.0.0)".parse().unwrap();
602 assert_eq!(parsed.len(), 1);
603 assert_eq!(
604 parsed[0].version,
605 Some((VersionConstraint::Equal, "2.0.0".parse().unwrap()))
606 );
607 assert_eq!(parsed.to_string(), "xml2 (== 2.0.0)");
608
609 let parsed: Relations = "xml2 (!= 2.0.0)".parse().unwrap();
610 assert_eq!(parsed.len(), 1);
611 assert_eq!(
612 parsed[0].version,
613 Some((VersionConstraint::NotEqual, "2.0.0".parse().unwrap()))
614 );
615 assert_eq!(parsed.to_string(), "xml2 (!= 2.0.0)");
616 }
617
618 #[test]
619 fn test_multiple() {
620 let input = "cli (>= 0.20.21), cli (< 0.21)";
621 let parsed: Relations = input.parse().unwrap();
622 assert_eq!(parsed.to_string(), input);
623 assert_eq!(parsed.len(), 2);
624 let relation = &parsed[0];
625 assert_eq!(relation.to_string(), "cli (>= 0.20.21)");
626 assert_eq!(
627 relation.version,
628 Some((
629 VersionConstraint::GreaterThanEqual,
630 "0.20.21".parse().unwrap()
631 ))
632 );
633 let relation = &parsed[1];
634 assert_eq!(relation.to_string(), "cli (< 0.21)");
635 assert_eq!(
636 relation.version,
637 Some((VersionConstraint::LessThan, "0.21".parse().unwrap()))
638 );
639 }
640
641 #[cfg(feature = "serde")]
642 #[test]
643 fn test_serde_relations() {
644 let input = "cli (>= 0.20.21), cli (< 0.21)";
645 let parsed: Relations = input.parse().unwrap();
646 let serialized = serde_json::to_string(&parsed).unwrap();
647 assert_eq!(serialized, r#""cli (>= 0.20.21), cli (< 0.21)""#);
648 let deserialized: Relations = serde_json::from_str(&serialized).unwrap();
649 assert_eq!(deserialized, parsed);
650 }
651
652 #[cfg(feature = "serde")]
653 #[test]
654 fn test_serde_relation() {
655 let input = "cli (>= 0.20.21)";
656 let parsed: Relation = input.parse().unwrap();
657 let serialized = serde_json::to_string(&parsed).unwrap();
658 assert_eq!(serialized, r#""cli (>= 0.20.21)""#);
659 let deserialized: Relation = serde_json::from_str(&serialized).unwrap();
660 assert_eq!(deserialized, parsed);
661 }
662
663 #[test]
664 fn test_relations_is_empty() {
665 let input = "cli (>= 0.20.21)";
666 let parsed: Relations = input.parse().unwrap();
667 assert!(!parsed.is_empty());
668 let input = "";
669 let parsed: Relations = input.parse().unwrap();
670 assert!(parsed.is_empty());
671 }
672
673 #[test]
674 fn test_relations_len() {
675 let input = "cli (>= 0.20.21), cli (< 0.21)";
676 let parsed: Relations = input.parse().unwrap();
677 assert_eq!(parsed.len(), 2);
678 }
679
680 #[test]
681 fn test_relations_remove() {
682 let input = "cli (>= 0.20.21), cli (< 0.21)";
683 let mut parsed: Relations = input.parse().unwrap();
684 parsed.remove(1);
685 assert_eq!(parsed.len(), 1);
686 assert_eq!(parsed.to_string(), "cli (>= 0.20.21)");
687 }
688
689 #[test]
690 fn test_relations_satisfied_by() {
691 let input = "cli (>= 0.20.21), cli (< 0.21)";
692 let parsed: Relations = input.parse().unwrap();
693 assert!(parsed.satisfied_by(|name: &str| -> Option<Version> {
694 match name {
695 "cli" => Some("0.20.21".parse().unwrap()),
696 _ => None,
697 }
698 }));
699 assert!(!parsed.satisfied_by(|name: &str| -> Option<Version> {
700 match name {
701 "cli" => Some("0.21".parse().unwrap()),
702 _ => None,
703 }
704 }));
705 }
706
707 #[test]
708 fn test_relation_satisfied_by() {
709 let input = "cli (>= 0.20.21)";
710 let parsed: Relation = input.parse().unwrap();
711 assert!(parsed.satisfied_by(|name: &str| -> Option<Version> {
712 match name {
713 "cli" => Some("0.20.21".parse().unwrap()),
714 _ => None,
715 }
716 }));
717 assert!(!parsed.satisfied_by(|name: &str| -> Option<Version> {
718 match name {
719 "cli" => Some("0.20.20".parse().unwrap()),
720 _ => None,
721 }
722 }));
723 }
724
725 #[test]
726 fn test_parse_url_entry() {
727 let input = "https://example.com/";
728 let parsed: UrlEntry = input.parse().unwrap();
729 assert_eq!(parsed.url.as_str(), input);
730 assert_eq!(parsed.label, None);
731
732 let input = "https://example.com (Example)";
733 let parsed: UrlEntry = input.parse().unwrap();
734 assert_eq!(parsed.url.as_str(), "https://example.com/");
735 assert_eq!(parsed.label, Some("Example".to_string()));
736 }
737
738 #[test]
739 fn test_deserialize_url_list() {
740 let input = "https://example.com/, https://example.org (Example)";
741 let parsed = deserialize_url_list(input).unwrap();
742 assert_eq!(parsed.len(), 2);
743 assert_eq!(parsed[0].url.as_str(), "https://example.com/");
744 assert_eq!(parsed[0].label, None);
745 assert_eq!(parsed[1].url.as_str(), "https://example.org/");
746 assert_eq!(parsed[1].label, Some("Example".to_string()));
747 }
748
749 #[test]
750 fn test_deserialize_url_list2() {
751 let input = "https://example.com/\n https://example.org (Example)\n https://example.net";
752 let parsed = deserialize_url_list(input).unwrap();
753 assert_eq!(parsed.len(), 3);
754 assert_eq!(parsed[0].url.as_str(), "https://example.com/");
755 assert_eq!(parsed[0].label, None);
756 assert_eq!(parsed[1].url.as_str(), "https://example.org/");
757 assert_eq!(parsed[1].label, Some("Example".to_string()));
758 assert_eq!(parsed[2].url.as_str(), "https://example.net/");
759 assert_eq!(parsed[2].label, None);
760 }
761
762 #[test]
763 fn test_deserialize_repository_list() {
764 let input = "https://example.com/src/contrib,\n https://example.org/src/contrib";
765 let parsed = deserialize_repository_list(input).unwrap();
766 assert_eq!(parsed.len(), 2);
767 assert_eq!(parsed[0].as_str(), "https://example.com/src/contrib");
768 assert_eq!(parsed[1].as_str(), "https://example.org/src/contrib");
769 }
770
771 #[test]
772 fn test_parse_additional_repositories() {
773 let s = r###"Package: mypackage
774Title: What the Package Does
775Version: 1.0.0
776Description: What the package does.
777License: MIT
778Additional_repositories: https://example.com/src/contrib,
779 https://example.org/src/contrib
780"###;
781 let desc: RDescription = s.parse().unwrap();
782
783 let repositories = desc.additional_repositories.as_ref().unwrap();
784 assert_eq!(repositories.len(), 2);
785 assert_eq!(repositories[0].as_str(), "https://example.com/src/contrib");
786 assert_eq!(repositories[1].as_str(), "https://example.org/src/contrib");
787
788 assert_eq!(
789 desc.to_string(),
790 "Package: mypackage\nDescription: What the package does.\nTitle: What the Package Does\nVersion: 1.0.0\nLicense: MIT\nAdditional_repositories: https://example.com/src/contrib, https://example.org/src/contrib\n"
791 );
792 }
793}