1use deb822_fast::{FromDeb822Paragraph, ToDeb822Paragraph};
10use deb822_derive::{FromDeb822, ToDeb822};
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::GreaterThan => actual.as_ref() > version,
225 VersionConstraint::LessThan => actual.as_ref() < version,
226 }
227 } else {
228 false
229 }
230 } else {
231 actual.is_some()
232 }
233 }
234}
235
236impl std::fmt::Display for Relation {
237 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
238 write!(f, "{}", self.name)?;
239 if let Some((constraint, version)) = &self.version {
240 write!(f, " ({constraint} {version})")?;
241 }
242 Ok(())
243 }
244}
245
246#[cfg(feature = "serde")]
247impl<'de> serde::Deserialize<'de> for Relation {
248 fn deserialize<D>(deserializer: D) -> Result<Relation, D::Error>
249 where
250 D: serde::Deserializer<'de>,
251 {
252 let s = String::deserialize(deserializer)?;
253 s.parse().map_err(serde::de::Error::custom)
254 }
255}
256
257#[cfg(feature = "serde")]
258impl serde::Serialize for Relation {
259 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
260 where
261 S: serde::Serializer,
262 {
263 self.to_string().serialize(serializer)
264 }
265}
266
267#[derive(Debug, Clone, PartialEq, Eq, Hash)]
269pub struct Relations(pub Vec<Relation>);
270
271impl std::ops::Index<usize> for Relations {
272 type Output = Relation;
273
274 fn index(&self, index: usize) -> &Self::Output {
275 &self.0[index]
276 }
277}
278
279impl std::ops::IndexMut<usize> for Relations {
280 fn index_mut(&mut self, index: usize) -> &mut Self::Output {
281 &mut self.0[index]
282 }
283}
284
285impl FromIterator<Relation> for Relations {
286 fn from_iter<I: IntoIterator<Item = Relation>>(iter: I) -> Self {
287 Self(iter.into_iter().collect())
288 }
289}
290
291impl Default for Relations {
292 fn default() -> Self {
293 Self::new()
294 }
295}
296
297impl Relations {
298 pub fn new() -> Self {
300 Self(Vec::new())
301 }
302
303 pub fn remove(&mut self, index: usize) {
305 self.0.remove(index);
306 }
307
308 pub fn iter(&self) -> impl Iterator<Item = &Relation> {
310 self.0.iter()
311 }
312
313 pub fn len(&self) -> usize {
315 self.0.len()
316 }
317
318 pub fn is_empty(&self) -> bool {
320 self.0.is_empty()
321 }
322
323 pub fn satisfied_by(
325 &self,
326 package_version: impl crate::relations::VersionLookup + Copy,
327 ) -> bool {
328 self.0.iter().all(|r| r.satisfied_by(package_version))
329 }
330}
331
332impl std::fmt::Display for Relations {
333 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
334 for (i, relation) in self.0.iter().enumerate() {
335 if i > 0 {
336 f.write_str(", ")?;
337 }
338 write!(f, "{relation}")?;
339 }
340 Ok(())
341 }
342}
343
344impl std::str::FromStr for Relation {
345 type Err = String;
346
347 fn from_str(s: &str) -> Result<Self, Self::Err> {
348 let tokens = lex(s);
349 let mut tokens = tokens.into_iter().peekable();
350
351 fn eat_whitespace(tokens: &mut Peekable<impl Iterator<Item = (SyntaxKind, String)>>) {
352 while let Some((k, _)) = tokens.peek() {
353 match k {
354 WHITESPACE | NEWLINE => {
355 tokens.next();
356 }
357 _ => break,
358 }
359 }
360 }
361
362 let name = match tokens.next() {
363 Some((IDENT, name)) => name,
364 _ => return Err("Expected package name".to_string()),
365 };
366
367 eat_whitespace(&mut tokens);
368
369 let version = if let Some((L_PARENS, _)) = tokens.peek() {
370 tokens.next();
371 eat_whitespace(&mut tokens);
372 let mut constraint = String::new();
373 while let Some((kind, t)) = tokens.peek() {
374 match kind {
375 EQUAL | L_ANGLE | R_ANGLE => {
376 constraint.push_str(t);
377 tokens.next();
378 }
379 _ => break,
380 }
381 }
382 let constraint = constraint.parse()?;
383 eat_whitespace(&mut tokens);
384 let version_string = match tokens.next() {
386 Some((IDENT, s)) => s,
387 _ => return Err("Expected version string".to_string()),
388 };
389 let version: Version = version_string.parse().map_err(|e: String| e.to_string())?;
390 eat_whitespace(&mut tokens);
391 if let Some((R_PARENS, _)) = tokens.next() {
392 } else {
393 return Err(format!("Expected ')', found {:?}", tokens.next()));
394 }
395 Some((constraint, version))
396 } else {
397 None
398 };
399
400 eat_whitespace(&mut tokens);
401
402 if let Some((kind, _)) = tokens.next() {
403 return Err(format!("Unexpected token: {kind:?}"));
404 }
405
406 Ok(Relation { name, version })
407 }
408}
409
410impl std::str::FromStr for Relations {
411 type Err = String;
412
413 fn from_str(s: &str) -> Result<Self, Self::Err> {
414 let mut relations = Vec::new();
415 if s.is_empty() {
416 return Ok(Relations(relations));
417 }
418 for relation in s.split(',') {
419 let relation = relation.trim();
420 if relation.is_empty() {
421 continue;
423 }
424 relations.push(relation.parse()?);
425 }
426 Ok(Relations(relations))
427 }
428}
429
430#[cfg(feature = "serde")]
431impl<'de> serde::Deserialize<'de> for Relations {
432 fn deserialize<D>(deserializer: D) -> Result<Relations, D::Error>
433 where
434 D: serde::Deserializer<'de>,
435 {
436 let s = String::deserialize(deserializer)?;
437 s.parse().map_err(serde::de::Error::custom)
438 }
439}
440
441#[cfg(feature = "serde")]
442impl serde::Serialize for Relations {
443 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
444 where
445 S: serde::Serializer,
446 {
447 self.to_string().serialize(serializer)
448 }
449}
450
451impl std::str::FromStr for RDescription {
452 type Err = String;
453
454 fn from_str(s: &str) -> Result<Self, Self::Err> {
455 let para: deb822_lossless::Paragraph = s
456 .parse()
457 .map_err(|e: deb822_lossless::ParseError| e.to_string())?;
458 Self::from_paragraph(¶)
459 }
460}
461
462impl std::fmt::Display for RDescription {
463 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
464 let para: deb822_lossless::Paragraph = self.to_paragraph();
465 f.write_str(¶.to_string())?;
466 Ok(())
467 }
468}
469
470#[cfg(test)]
471mod tests {
472 use super::*;
473
474 #[test]
475 fn test_parse() {
476 let s = r###"Package: mypackage
477Title: What the Package Does (One Line, Title Case)
478Version: 0.0.0.9000
479Authors@R:
480 person("First", "Last", , "first.last@example.com", role = c("aut", "cre"),
481 comment = c(ORCID = "YOUR-ORCID-ID"))
482Description: What the package does (one paragraph).
483License: `use_mit_license()`, `use_gpl3_license()` or friends to pick a
484 license
485Encoding: UTF-8
486Roxygen: list(markdown = TRUE)
487RoxygenNote: 7.3.2
488"###;
489 let desc: RDescription = s.parse().unwrap();
490
491 assert_eq!(desc.name, "mypackage".to_string());
492 assert_eq!(
493 desc.title,
494 "What the Package Does (One Line, Title Case)".to_string()
495 );
496 assert_eq!(desc.version, "0.0.0.9000".parse().unwrap());
497 assert_eq!(
498 desc.authors,
499 Some(RCode(
500 r#"person("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: person("First", "Last", , "first.last@example.com", role = c("aut", "cre"),
521 comment = c(ORCID = "YOUR-ORCID-ID"))
522Version: 0.0.0.9000
523Encoding: UTF-8
524License: `use_mit_license()`, `use_gpl3_license()` or friends to pick a
525 license
526"###
527 );
528 }
529
530 #[test]
531 fn test_parse_dplyr() {
532 let s = include_str!("../testdata/dplyr.desc");
533 let desc: RDescription = s.parse().unwrap();
534
535 assert_eq!(desc.name, "dplyr".to_string());
536 }
537
538 #[test]
539 fn test_parse_relations() {
540 let input = "cli";
541 let parsed: Relations = input.parse().unwrap();
542 assert_eq!(parsed.to_string(), input);
543 assert_eq!(parsed.len(), 1);
544 let relation = &parsed[0];
545 assert_eq!(relation.to_string(), "cli");
546 assert_eq!(relation.version, None);
547
548 let input = "cli (>= 0.20.21)";
549 let parsed: Relations = input.parse().unwrap();
550 assert_eq!(parsed.to_string(), input);
551 assert_eq!(parsed.len(), 1);
552 let relation = &parsed[0];
553 assert_eq!(relation.to_string(), "cli (>= 0.20.21)");
554 assert_eq!(
555 relation.version,
556 Some((
557 VersionConstraint::GreaterThanEqual,
558 "0.20.21".parse().unwrap()
559 ))
560 );
561 }
562
563 #[test]
564 fn test_multiple() {
565 let input = "cli (>= 0.20.21), cli (<< 0.21)";
566 let parsed: Relations = input.parse().unwrap();
567 assert_eq!(parsed.to_string(), input);
568 assert_eq!(parsed.len(), 2);
569 let relation = &parsed[0];
570 assert_eq!(relation.to_string(), "cli (>= 0.20.21)");
571 assert_eq!(
572 relation.version,
573 Some((
574 VersionConstraint::GreaterThanEqual,
575 "0.20.21".parse().unwrap()
576 ))
577 );
578 let relation = &parsed[1];
579 assert_eq!(relation.to_string(), "cli (<< 0.21)");
580 assert_eq!(
581 relation.version,
582 Some((VersionConstraint::LessThan, "0.21".parse().unwrap()))
583 );
584 }
585
586 #[cfg(feature = "serde")]
587 #[test]
588 fn test_serde_relations() {
589 let input = "cli (>= 0.20.21), cli (<< 0.21)";
590 let parsed: Relations = input.parse().unwrap();
591 let serialized = serde_json::to_string(&parsed).unwrap();
592 assert_eq!(serialized, r#""cli (>= 0.20.21), cli (<< 0.21)""#);
593 let deserialized: Relations = serde_json::from_str(&serialized).unwrap();
594 assert_eq!(deserialized, parsed);
595 }
596
597 #[cfg(feature = "serde")]
598 #[test]
599 fn test_serde_relation() {
600 let input = "cli (>= 0.20.21)";
601 let parsed: Relation = input.parse().unwrap();
602 let serialized = serde_json::to_string(&parsed).unwrap();
603 assert_eq!(serialized, r#""cli (>= 0.20.21)""#);
604 let deserialized: Relation = serde_json::from_str(&serialized).unwrap();
605 assert_eq!(deserialized, parsed);
606 }
607
608 #[test]
609 fn test_relations_is_empty() {
610 let input = "cli (>= 0.20.21)";
611 let parsed: Relations = input.parse().unwrap();
612 assert!(!parsed.is_empty());
613 let input = "";
614 let parsed: Relations = input.parse().unwrap();
615 assert!(parsed.is_empty());
616 }
617
618 #[test]
619 fn test_relations_len() {
620 let input = "cli (>= 0.20.21), cli (<< 0.21)";
621 let parsed: Relations = input.parse().unwrap();
622 assert_eq!(parsed.len(), 2);
623 }
624
625 #[test]
626 fn test_relations_remove() {
627 let input = "cli (>= 0.20.21), cli (<< 0.21)";
628 let mut parsed: Relations = input.parse().unwrap();
629 parsed.remove(1);
630 assert_eq!(parsed.len(), 1);
631 assert_eq!(parsed.to_string(), "cli (>= 0.20.21)");
632 }
633
634 #[test]
635 fn test_relations_satisfied_by() {
636 let input = "cli (>= 0.20.21), cli (<< 0.21)";
637 let parsed: Relations = input.parse().unwrap();
638 assert!(parsed.satisfied_by(|name: &str| -> Option<Version> {
639 match name {
640 "cli" => Some("0.20.21".parse().unwrap()),
641 _ => None,
642 }
643 }));
644 assert!(!parsed.satisfied_by(|name: &str| -> Option<Version> {
645 match name {
646 "cli" => Some("0.21".parse().unwrap()),
647 _ => None,
648 }
649 }));
650 }
651
652 #[test]
653 fn test_relation_satisfied_by() {
654 let input = "cli (>= 0.20.21)";
655 let parsed: Relation = input.parse().unwrap();
656 assert!(parsed.satisfied_by(|name: &str| -> Option<Version> {
657 match name {
658 "cli" => Some("0.20.21".parse().unwrap()),
659 _ => None,
660 }
661 }));
662 assert!(!parsed.satisfied_by(|name: &str| -> Option<Version> {
663 match name {
664 "cli" => Some("0.20.20".parse().unwrap()),
665 _ => None,
666 }
667 }));
668 }
669
670 #[test]
671 fn test_parse_url_entry() {
672 let input = "https://example.com/";
673 let parsed: UrlEntry = input.parse().unwrap();
674 assert_eq!(parsed.url.as_str(), input);
675 assert_eq!(parsed.label, None);
676
677 let input = "https://example.com (Example)";
678 let parsed: UrlEntry = input.parse().unwrap();
679 assert_eq!(parsed.url.as_str(), "https://example.com/");
680 assert_eq!(parsed.label, Some("Example".to_string()));
681 }
682
683 #[test]
684 fn test_deserialize_url_list() {
685 let input = "https://example.com/, https://example.org (Example)";
686 let parsed = deserialize_url_list(input).unwrap();
687 assert_eq!(parsed.len(), 2);
688 assert_eq!(parsed[0].url.as_str(), "https://example.com/");
689 assert_eq!(parsed[0].label, None);
690 assert_eq!(parsed[1].url.as_str(), "https://example.org/");
691 assert_eq!(parsed[1].label, Some("Example".to_string()));
692 }
693
694 #[test]
695 fn test_deserialize_url_list2() {
696 let input = "https://example.com/\n https://example.org (Example)\n https://example.net";
697 let parsed = deserialize_url_list(input).unwrap();
698 assert_eq!(parsed.len(), 3);
699 assert_eq!(parsed[0].url.as_str(), "https://example.com/");
700 assert_eq!(parsed[0].label, None);
701 assert_eq!(parsed[1].url.as_str(), "https://example.org/");
702 assert_eq!(parsed[1].label, Some("Example".to_string()));
703 assert_eq!(parsed[2].url.as_str(), "https://example.net/");
704 assert_eq!(parsed[2].label, None);
705 }
706}