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
78#[derive(FromDeb822, ToDeb822, Debug, PartialEq, Eq)]
79pub struct RDescription {
81 #[deb822(field = "Package")]
83 pub name: String,
84
85 #[deb822(field = "Description")]
87 pub description: String,
88
89 #[deb822(field = "Title")]
90 pub title: String,
92
93 #[deb822(field = "Maintainer")]
94 pub maintainer: Option<String>,
96
97 #[deb822(field = "Author")]
98 pub author: Option<String>,
100
101 #[deb822(field = "Authors@R")]
104 pub authors: Option<RCode>,
105
106 #[deb822(field = "Version")]
107 pub version: Version,
109
110 #[deb822(field = "Encoding")]
113 pub encoding: Option<String>,
114
115 #[deb822(field = "License")]
116 pub license: String,
118
119 #[deb822(field = "URL", serialize_with = serialize_url_list, deserialize_with = deserialize_url_list)]
120 pub url: Option<Vec<UrlEntry>>,
123
124 #[deb822(field = "BugReports")]
125 pub bug_reports: Option<String>,
127
128 #[deb822(field = "Imports")]
129 pub imports: Option<Relations>,
131
132 #[deb822(field = "Suggests")]
133 pub suggests: Option<Relations>,
135
136 #[deb822(field = "Depends")]
137 pub depends: Option<Relations>,
139
140 #[deb822(field = "LinkingTo")]
141 pub linking_to: Option<Relations>,
143
144 #[deb822(field = "LazyData")]
145 pub lazy_data: Option<String>,
147
148 #[deb822(field = "Collate")]
149 pub collate: Option<String>,
151
152 #[deb822(field = "VignetteBuilder")]
153 pub vignette_builder: Option<String>,
155
156 #[deb822(field = "SystemRequirements")]
157 pub system_requirements: Option<String>,
159
160 #[deb822(field = "Date")]
161 pub date: Option<String>,
164
165 #[deb822(field = "Language")]
166 pub language: Option<String>,
170
171 #[deb822(field = "Repository")]
172 pub repository: Option<String>,
174}
175
176#[derive(Debug, Clone, PartialEq, Eq, Hash)]
178pub struct Relation {
179 pub name: String,
181 pub version: Option<(VersionConstraint, Version)>,
183}
184
185impl Default for Relation {
186 fn default() -> Self {
187 Self::new()
188 }
189}
190
191impl Relation {
192 pub fn new() -> Self {
194 Self {
195 name: String::new(),
196 version: None,
197 }
198 }
199
200 pub fn satisfied_by(&self, package_version: impl crate::relations::VersionLookup) -> bool {
217 let actual = package_version.lookup_version(self.name.as_str());
218 if let Some((vc, version)) = &self.version {
219 if let Some(actual) = actual {
220 match vc {
221 VersionConstraint::GreaterThanEqual => actual.as_ref() >= version,
222 VersionConstraint::LessThanEqual => actual.as_ref() <= version,
223 VersionConstraint::Equal => actual.as_ref() == version,
224 VersionConstraint::NotEqual => actual.as_ref() != version,
225 VersionConstraint::GreaterThan => actual.as_ref() > version,
226 VersionConstraint::LessThan => actual.as_ref() < version,
227 }
228 } else {
229 false
230 }
231 } else {
232 actual.is_some()
233 }
234 }
235}
236
237impl std::fmt::Display for Relation {
238 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
239 write!(f, "{}", self.name)?;
240 if let Some((constraint, version)) = &self.version {
241 write!(f, " ({constraint} {version})")?;
242 }
243 Ok(())
244 }
245}
246
247#[cfg(feature = "serde")]
248impl<'de> serde::Deserialize<'de> for Relation {
249 fn deserialize<D>(deserializer: D) -> Result<Relation, D::Error>
250 where
251 D: serde::Deserializer<'de>,
252 {
253 let s = String::deserialize(deserializer)?;
254 s.parse().map_err(serde::de::Error::custom)
255 }
256}
257
258#[cfg(feature = "serde")]
259impl serde::Serialize for Relation {
260 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
261 where
262 S: serde::Serializer,
263 {
264 self.to_string().serialize(serializer)
265 }
266}
267
268#[derive(Debug, Clone, PartialEq, Eq, Hash)]
270pub struct Relations(pub Vec<Relation>);
271
272impl std::ops::Index<usize> for Relations {
273 type Output = Relation;
274
275 fn index(&self, index: usize) -> &Self::Output {
276 &self.0[index]
277 }
278}
279
280impl std::ops::IndexMut<usize> for Relations {
281 fn index_mut(&mut self, index: usize) -> &mut Self::Output {
282 &mut self.0[index]
283 }
284}
285
286impl FromIterator<Relation> for Relations {
287 fn from_iter<I: IntoIterator<Item = Relation>>(iter: I) -> Self {
288 Self(iter.into_iter().collect())
289 }
290}
291
292impl Default for Relations {
293 fn default() -> Self {
294 Self::new()
295 }
296}
297
298impl Relations {
299 pub fn new() -> Self {
301 Self(Vec::new())
302 }
303
304 pub fn remove(&mut self, index: usize) {
306 self.0.remove(index);
307 }
308
309 pub fn iter(&self) -> impl Iterator<Item = &Relation> {
311 self.0.iter()
312 }
313
314 pub fn len(&self) -> usize {
316 self.0.len()
317 }
318
319 pub fn is_empty(&self) -> bool {
321 self.0.is_empty()
322 }
323
324 pub fn satisfied_by(
326 &self,
327 package_version: impl crate::relations::VersionLookup + Copy,
328 ) -> bool {
329 self.0.iter().all(|r| r.satisfied_by(package_version))
330 }
331}
332
333impl std::fmt::Display for Relations {
334 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
335 for (i, relation) in self.0.iter().enumerate() {
336 if i > 0 {
337 f.write_str(", ")?;
338 }
339 write!(f, "{relation}")?;
340 }
341 Ok(())
342 }
343}
344
345impl std::str::FromStr for Relation {
346 type Err = String;
347
348 fn from_str(s: &str) -> Result<Self, Self::Err> {
349 let tokens = lex(s);
350 let mut tokens = tokens.into_iter().peekable();
351
352 fn eat_whitespace(tokens: &mut Peekable<impl Iterator<Item = (SyntaxKind, String)>>) {
353 while let Some((k, _)) = tokens.peek() {
354 match k {
355 WHITESPACE | NEWLINE => {
356 tokens.next();
357 }
358 _ => break,
359 }
360 }
361 }
362
363 let name = match tokens.next() {
364 Some((IDENT, name)) => name,
365 _ => return Err("Expected package name".to_string()),
366 };
367
368 eat_whitespace(&mut tokens);
369
370 let version = if let Some((L_PARENS, _)) = tokens.peek() {
371 tokens.next();
372 eat_whitespace(&mut tokens);
373 let mut constraint = String::new();
374 while let Some((kind, t)) = tokens.peek() {
375 match kind {
376 EQUAL | L_ANGLE | R_ANGLE | NOT => {
377 constraint.push_str(t);
378 tokens.next();
379 }
380 _ => break,
381 }
382 }
383 let constraint = constraint.parse()?;
384 eat_whitespace(&mut tokens);
385 let version_string = match tokens.next() {
387 Some((IDENT, s)) => s,
388 _ => return Err("Expected version string".to_string()),
389 };
390 let version: Version = version_string.parse().map_err(|e: String| e.to_string())?;
391 eat_whitespace(&mut tokens);
392 if let Some((R_PARENS, _)) = tokens.next() {
393 } else {
394 return Err(format!("Expected ')', found {:?}", tokens.next()));
395 }
396 Some((constraint, version))
397 } else {
398 None
399 };
400
401 eat_whitespace(&mut tokens);
402
403 if let Some((kind, _)) = tokens.next() {
404 return Err(format!("Unexpected token: {kind:?}"));
405 }
406
407 Ok(Relation { name, version })
408 }
409}
410
411impl std::str::FromStr for Relations {
412 type Err = String;
413
414 fn from_str(s: &str) -> Result<Self, Self::Err> {
415 let mut relations = Vec::new();
416 if s.is_empty() {
417 return Ok(Relations(relations));
418 }
419 for relation in s.split(',') {
420 let relation = relation.trim();
421 if relation.is_empty() {
422 continue;
424 }
425 relations.push(relation.parse()?);
426 }
427 Ok(Relations(relations))
428 }
429}
430
431#[cfg(feature = "serde")]
432impl<'de> serde::Deserialize<'de> for Relations {
433 fn deserialize<D>(deserializer: D) -> Result<Relations, D::Error>
434 where
435 D: serde::Deserializer<'de>,
436 {
437 let s = String::deserialize(deserializer)?;
438 s.parse().map_err(serde::de::Error::custom)
439 }
440}
441
442#[cfg(feature = "serde")]
443impl serde::Serialize for Relations {
444 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
445 where
446 S: serde::Serializer,
447 {
448 self.to_string().serialize(serializer)
449 }
450}
451
452impl std::str::FromStr for RDescription {
453 type Err = String;
454
455 fn from_str(s: &str) -> Result<Self, Self::Err> {
456 let para = deb822_fast::Paragraph::from_str(s).map_err(|e| e.to_string())?;
457 Self::from_paragraph(¶)
458 }
459}
460
461impl std::fmt::Display for RDescription {
462 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
463 let para: deb822_fast::Paragraph = self.to_paragraph();
464 f.write_str(¶.to_string())?;
465 Ok(())
466 }
467}
468
469#[cfg(test)]
470mod tests {
471 use super::*;
472
473 #[test]
474 fn test_parse() {
475 let s = r###"Package: mypackage
476Title: What the Package Does (One Line, Title Case)
477Version: 0.0.0.9000
478Authors@R:
479 person("First", "Last", , "first.last@example.com", role = c("aut", "cre"),
480 comment = c(ORCID = "YOUR-ORCID-ID"))
481Description: What the package does (one paragraph).
482License: `use_mit_license()`, `use_gpl3_license()` or friends to pick a
483 license
484Encoding: UTF-8
485Roxygen: list(markdown = TRUE)
486RoxygenNote: 7.3.2
487"###;
488 let desc: RDescription = s.parse().unwrap();
489
490 assert_eq!(desc.name, "mypackage".to_string());
491 assert_eq!(
492 desc.title,
493 "What the Package Does (One Line, Title Case)".to_string()
494 );
495 assert_eq!(desc.version, "0.0.0.9000".parse().unwrap());
496 assert_eq!(
497 desc.authors,
498 Some(RCode(
499 r#"
500person("First", "Last", , "first.last@example.com", role = c("aut", "cre"),
501comment = c(ORCID = "YOUR-ORCID-ID"))"#
502 .to_string()
503 ))
504 );
505 assert_eq!(
506 desc.description,
507 "What the package does (one paragraph).".to_string()
508 );
509 assert_eq!(
510 desc.license,
511 "`use_mit_license()`, `use_gpl3_license()` or friends to pick a\nlicense".to_string()
512 );
513 assert_eq!(desc.encoding, Some("UTF-8".to_string()));
514
515 assert_eq!(
516 desc.to_string(),
517 r###"Package: mypackage
518Description: What the package does (one paragraph).
519Title: What the Package Does (One Line, Title Case)
520Authors@R:
521 person("First", "Last", , "first.last@example.com", role = c("aut", "cre"),
522 comment = c(ORCID = "YOUR-ORCID-ID"))
523Version: 0.0.0.9000
524Encoding: UTF-8
525License: `use_mit_license()`, `use_gpl3_license()` or friends to pick a
526 license
527"###
528 );
529 }
530
531 #[test]
532 fn test_parse_dplyr() {
533 let s = include_str!("../testdata/dplyr.desc");
534 let desc: RDescription = s.parse().unwrap();
535
536 assert_eq!(desc.name, "dplyr".to_string());
537 }
538
539 #[test]
540 fn test_parse_relations() {
541 let input = "cli";
542 let parsed: Relations = input.parse().unwrap();
543 assert_eq!(parsed.to_string(), input);
544 assert_eq!(parsed.len(), 1);
545 let relation = &parsed[0];
546 assert_eq!(relation.to_string(), "cli");
547 assert_eq!(relation.version, None);
548
549 let input = "cli (>= 0.20.21)";
550 let parsed: Relations = input.parse().unwrap();
551 assert_eq!(parsed.to_string(), input);
552 assert_eq!(parsed.len(), 1);
553 let relation = &parsed[0];
554 assert_eq!(relation.to_string(), "cli (>= 0.20.21)");
555 assert_eq!(
556 relation.version,
557 Some((
558 VersionConstraint::GreaterThanEqual,
559 "0.20.21".parse().unwrap()
560 ))
561 );
562
563 let parsed: Relations = "xml2 (> 1.0.0)".parse().unwrap();
564 assert_eq!(parsed.len(), 1);
565 assert_eq!(parsed[0].name, "xml2");
566 assert_eq!(
567 parsed[0].version,
568 Some((VersionConstraint::GreaterThan, "1.0.0".parse().unwrap()))
569 );
570
571 let parsed: Relations = "xml2 (< 2.0.0)".parse().unwrap();
572 assert_eq!(parsed.len(), 1);
573 assert_eq!(parsed[0].name, "xml2");
574 assert_eq!(
575 parsed[0].version,
576 Some((VersionConstraint::LessThan, "2.0.0".parse().unwrap()))
577 );
578
579 let parsed: Relations = "xml2 (== 2.0.0)".parse().unwrap();
580 assert_eq!(parsed.len(), 1);
581 assert_eq!(
582 parsed[0].version,
583 Some((VersionConstraint::Equal, "2.0.0".parse().unwrap()))
584 );
585 assert_eq!(parsed.to_string(), "xml2 (== 2.0.0)");
586
587 let parsed: Relations = "xml2 (!= 2.0.0)".parse().unwrap();
588 assert_eq!(parsed.len(), 1);
589 assert_eq!(
590 parsed[0].version,
591 Some((VersionConstraint::NotEqual, "2.0.0".parse().unwrap()))
592 );
593 assert_eq!(parsed.to_string(), "xml2 (!= 2.0.0)");
594 }
595
596 #[test]
597 fn test_multiple() {
598 let input = "cli (>= 0.20.21), cli (< 0.21)";
599 let parsed: Relations = input.parse().unwrap();
600 assert_eq!(parsed.to_string(), input);
601 assert_eq!(parsed.len(), 2);
602 let relation = &parsed[0];
603 assert_eq!(relation.to_string(), "cli (>= 0.20.21)");
604 assert_eq!(
605 relation.version,
606 Some((
607 VersionConstraint::GreaterThanEqual,
608 "0.20.21".parse().unwrap()
609 ))
610 );
611 let relation = &parsed[1];
612 assert_eq!(relation.to_string(), "cli (< 0.21)");
613 assert_eq!(
614 relation.version,
615 Some((VersionConstraint::LessThan, "0.21".parse().unwrap()))
616 );
617 }
618
619 #[cfg(feature = "serde")]
620 #[test]
621 fn test_serde_relations() {
622 let input = "cli (>= 0.20.21), cli (< 0.21)";
623 let parsed: Relations = input.parse().unwrap();
624 let serialized = serde_json::to_string(&parsed).unwrap();
625 assert_eq!(serialized, r#""cli (>= 0.20.21), cli (< 0.21)""#);
626 let deserialized: Relations = serde_json::from_str(&serialized).unwrap();
627 assert_eq!(deserialized, parsed);
628 }
629
630 #[cfg(feature = "serde")]
631 #[test]
632 fn test_serde_relation() {
633 let input = "cli (>= 0.20.21)";
634 let parsed: Relation = input.parse().unwrap();
635 let serialized = serde_json::to_string(&parsed).unwrap();
636 assert_eq!(serialized, r#""cli (>= 0.20.21)""#);
637 let deserialized: Relation = serde_json::from_str(&serialized).unwrap();
638 assert_eq!(deserialized, parsed);
639 }
640
641 #[test]
642 fn test_relations_is_empty() {
643 let input = "cli (>= 0.20.21)";
644 let parsed: Relations = input.parse().unwrap();
645 assert!(!parsed.is_empty());
646 let input = "";
647 let parsed: Relations = input.parse().unwrap();
648 assert!(parsed.is_empty());
649 }
650
651 #[test]
652 fn test_relations_len() {
653 let input = "cli (>= 0.20.21), cli (< 0.21)";
654 let parsed: Relations = input.parse().unwrap();
655 assert_eq!(parsed.len(), 2);
656 }
657
658 #[test]
659 fn test_relations_remove() {
660 let input = "cli (>= 0.20.21), cli (< 0.21)";
661 let mut parsed: Relations = input.parse().unwrap();
662 parsed.remove(1);
663 assert_eq!(parsed.len(), 1);
664 assert_eq!(parsed.to_string(), "cli (>= 0.20.21)");
665 }
666
667 #[test]
668 fn test_relations_satisfied_by() {
669 let input = "cli (>= 0.20.21), cli (< 0.21)";
670 let parsed: Relations = input.parse().unwrap();
671 assert!(parsed.satisfied_by(|name: &str| -> Option<Version> {
672 match name {
673 "cli" => Some("0.20.21".parse().unwrap()),
674 _ => None,
675 }
676 }));
677 assert!(!parsed.satisfied_by(|name: &str| -> Option<Version> {
678 match name {
679 "cli" => Some("0.21".parse().unwrap()),
680 _ => None,
681 }
682 }));
683 }
684
685 #[test]
686 fn test_relation_satisfied_by() {
687 let input = "cli (>= 0.20.21)";
688 let parsed: Relation = input.parse().unwrap();
689 assert!(parsed.satisfied_by(|name: &str| -> Option<Version> {
690 match name {
691 "cli" => Some("0.20.21".parse().unwrap()),
692 _ => None,
693 }
694 }));
695 assert!(!parsed.satisfied_by(|name: &str| -> Option<Version> {
696 match name {
697 "cli" => Some("0.20.20".parse().unwrap()),
698 _ => None,
699 }
700 }));
701 }
702
703 #[test]
704 fn test_parse_url_entry() {
705 let input = "https://example.com/";
706 let parsed: UrlEntry = input.parse().unwrap();
707 assert_eq!(parsed.url.as_str(), input);
708 assert_eq!(parsed.label, None);
709
710 let input = "https://example.com (Example)";
711 let parsed: UrlEntry = input.parse().unwrap();
712 assert_eq!(parsed.url.as_str(), "https://example.com/");
713 assert_eq!(parsed.label, Some("Example".to_string()));
714 }
715
716 #[test]
717 fn test_deserialize_url_list() {
718 let input = "https://example.com/, https://example.org (Example)";
719 let parsed = deserialize_url_list(input).unwrap();
720 assert_eq!(parsed.len(), 2);
721 assert_eq!(parsed[0].url.as_str(), "https://example.com/");
722 assert_eq!(parsed[0].label, None);
723 assert_eq!(parsed[1].url.as_str(), "https://example.org/");
724 assert_eq!(parsed[1].label, Some("Example".to_string()));
725 }
726
727 #[test]
728 fn test_deserialize_url_list2() {
729 let input = "https://example.com/\n https://example.org (Example)\n https://example.net";
730 let parsed = deserialize_url_list(input).unwrap();
731 assert_eq!(parsed.len(), 3);
732 assert_eq!(parsed[0].url.as_str(), "https://example.com/");
733 assert_eq!(parsed[0].label, None);
734 assert_eq!(parsed[1].url.as_str(), "https://example.org/");
735 assert_eq!(parsed[1].label, Some("Example".to_string()));
736 assert_eq!(parsed[2].url.as_str(), "https://example.net/");
737 assert_eq!(parsed[2].label, None);
738 }
739}