1use crate::{License, CURRENT_FORMAT, KNOWN_FORMATS};
39use deb822_lossless::IndentPattern;
40use deb822_lossless::{Deb822, Paragraph, TextRange};
41use std::path::Path;
42
43fn decode_field_text(text: &str) -> String {
58 text.lines()
59 .map(|line| {
60 if line == "." {
61 ""
63 } else {
64 line
65 }
66 })
67 .collect::<Vec<_>>()
68 .join("\n")
69}
70
71fn encode_field_text(text: &str) -> String {
85 text.lines()
86 .map(|line| {
87 if line.is_empty() {
88 "."
90 } else {
91 line
92 }
93 })
94 .collect::<Vec<_>>()
95 .join("\n")
96}
97
98const HEADER_FIELD_ORDER: &[&str] = &[
100 "Format",
101 "Upstream-Name",
102 "Upstream-Contact",
103 "Source",
104 "Disclaimer",
105 "Comment",
106 "License",
107 "Copyright",
108];
109
110const FILES_FIELD_ORDER: &[&str] = &["Files", "Copyright", "License", "Comment"];
112
113const LICENSE_FIELD_ORDER: &[&str] = &["License", "Comment"];
115
116const FILES_SEPARATOR: &str = " ";
118
119#[derive(Debug, Clone, PartialEq)]
121pub struct Copyright(Deb822);
122
123impl Copyright {
124 pub fn new() -> Self {
126 let mut deb822 = Deb822::new();
127 let mut header = deb822.add_paragraph();
128 header.set("Format", CURRENT_FORMAT);
129 Copyright(deb822)
130 }
131
132 pub fn empty() -> Self {
136 Self(Deb822::new())
137 }
138
139 pub fn header(&self) -> Option<Header> {
141 self.0.paragraphs().next().map(Header)
142 }
143
144 pub fn iter_files(&self) -> impl Iterator<Item = FilesParagraph> {
146 self.0
147 .paragraphs()
148 .filter(|x| x.contains_key("Files"))
149 .map(FilesParagraph)
150 }
151
152 pub fn iter_licenses(&self) -> impl Iterator<Item = LicenseParagraph> {
154 self.0
155 .paragraphs()
156 .filter(|x| {
157 !x.contains_key("Files") && !x.contains_key("Format") && x.contains_key("License")
158 })
159 .map(LicenseParagraph)
160 }
161
162 pub fn header_in_range(&self, range: TextRange) -> Option<Header> {
170 self.header().filter(|h| {
171 let para_range = h.as_deb822().text_range();
172 para_range.start() < range.end() && para_range.end() > range.start()
173 })
174 }
175
176 pub fn iter_files_in_range(
184 &self,
185 range: TextRange,
186 ) -> impl Iterator<Item = FilesParagraph> + '_ {
187 self.iter_files().filter(move |f| {
188 let para_range = f.as_deb822().text_range();
189 para_range.start() < range.end() && para_range.end() > range.start()
190 })
191 }
192
193 pub fn iter_licenses_in_range(
201 &self,
202 range: TextRange,
203 ) -> impl Iterator<Item = LicenseParagraph> + '_ {
204 self.iter_licenses().filter(move |l| {
205 let para_range = l.as_deb822().text_range();
206 para_range.start() < range.end() && para_range.end() > range.start()
207 })
208 }
209
210 pub fn find_files(&self, filename: &Path) -> Option<FilesParagraph> {
215 self.iter_files().filter(|p| p.matches(filename)).last()
216 }
217
218 pub fn find_license_by_name(&self, name: &str) -> Option<License> {
222 self.iter_licenses()
223 .find(|p| p.name().as_deref() == Some(name))
224 .map(|x| x.into())
225 }
226
227 pub fn find_license_for_file(&self, filename: &Path) -> Option<License> {
229 let files = self.find_files(filename)?;
230 let license = files.license()?;
231 if license.text().is_some() {
232 return Some(license);
233 }
234 self.find_license_by_name(license.name()?)
235 }
236
237 pub fn from_str_relaxed(s: &str) -> Result<(Self, Vec<String>), Error> {
239 if !s.starts_with("Format:") {
240 return Err(Error::NotMachineReadable);
241 }
242
243 let (deb822, errors) = Deb822::from_str_relaxed(s);
244 Ok((Self(deb822), errors))
245 }
246
247 pub fn from_file_relaxed<P: AsRef<Path>>(path: P) -> Result<(Self, Vec<String>), Error> {
249 let text = std::fs::read_to_string(path)?;
250 Self::from_str_relaxed(&text)
251 }
252
253 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
255 let text = std::fs::read_to_string(path)?;
256 use std::str::FromStr;
257 Self::from_str(&text)
258 }
259
260 pub fn add_files(
264 &mut self,
265 files: &[&str],
266 copyright: &[&str],
267 license: &License,
268 ) -> FilesParagraph {
269 let mut para = self.0.add_paragraph();
270 para.set_with_field_order("Files", &files.join(FILES_SEPARATOR), FILES_FIELD_ORDER);
271 para.set_with_field_order("Copyright", ©right.join("\n"), FILES_FIELD_ORDER);
272 let license_text = match license {
273 License::Name(name) => name.to_string(),
274 License::Named(name, text) => format!("{}\n{}", name, text),
275 License::Text(text) => text.to_string(),
276 };
277 para.set_with_forced_indent(
278 "License",
279 &license_text,
280 &IndentPattern::Fixed(1),
281 Some(FILES_FIELD_ORDER),
282 );
283 FilesParagraph(para)
284 }
285
286 pub fn add_license(&mut self, license: &License) -> LicenseParagraph {
290 let mut para = self.0.add_paragraph();
291 let license_text = match license {
292 License::Name(name) => name.to_string(),
293 License::Named(name, text) => format!("{}\n{}", name, encode_field_text(text)),
294 License::Text(text) => encode_field_text(text),
295 };
296 para.set_with_indent_pattern(
298 "License",
299 &license_text,
300 Some(&IndentPattern::Fixed(1)),
301 Some(LICENSE_FIELD_ORDER),
302 );
303 LicenseParagraph(para)
304 }
305
306 pub fn remove_license_by_name(&mut self, name: &str) -> bool {
311 let mut index = None;
313 for (i, para) in self.0.paragraphs().enumerate() {
314 if !para.contains_key("Files")
315 && !para.contains_key("Format")
316 && para.contains_key("License")
317 {
318 let license_para = LicenseParagraph(para);
319 if license_para.name().as_deref() == Some(name) {
320 index = Some(i);
321 break;
322 }
323 }
324 }
325
326 if let Some(i) = index {
327 self.0.remove_paragraph(i);
328 true
329 } else {
330 false
331 }
332 }
333
334 pub fn remove_files_by_pattern(&mut self, pattern: &str) -> bool {
339 let mut index = None;
341 for (i, para) in self.0.paragraphs().enumerate() {
342 if para.contains_key("Files") {
343 let files_para = FilesParagraph(para);
344 if files_para.files().iter().any(|f| f == pattern) {
345 index = Some(i);
346 break;
347 }
348 }
349 }
350
351 if let Some(i) = index {
352 self.0.remove_paragraph(i);
353 true
354 } else {
355 false
356 }
357 }
358
359 pub fn wrap_and_sort(
372 &mut self,
373 indentation: deb822_lossless::Indentation,
374 immediate_empty_line: bool,
375 max_line_length_one_liner: Option<usize>,
376 ) {
377 let sort_paragraphs = |a: &Paragraph, b: &Paragraph| -> std::cmp::Ordering {
379 let a_is_header = a.contains_key("Format");
380 let b_is_header = b.contains_key("Format");
381 let a_is_files = a.contains_key("Files");
382 let b_is_files = b.contains_key("Files");
383
384 if a_is_header && !b_is_header {
386 return std::cmp::Ordering::Less;
387 }
388 if !a_is_header && b_is_header {
389 return std::cmp::Ordering::Greater;
390 }
391
392 if a_is_files && !b_is_files && !b_is_header {
394 return std::cmp::Ordering::Less;
395 }
396 if !a_is_files && b_is_files && !a_is_header {
397 return std::cmp::Ordering::Greater;
398 }
399
400 if a_is_files && b_is_files {
402 let a_files = a.get("Files").unwrap_or_default();
403 let b_files = b.get("Files").unwrap_or_default();
404
405 let a_first = a_files.split_whitespace().next().unwrap_or("");
406 let b_first = b_files.split_whitespace().next().unwrap_or("");
407
408 let a_depth = crate::pattern_depth(a_first);
409 let b_depth = crate::pattern_depth(b_first);
410
411 let a_key = crate::pattern_sort_key(a_first, a_depth);
412 let b_key = crate::pattern_sort_key(b_first, b_depth);
413
414 return a_key.cmp(&b_key);
415 }
416
417 std::cmp::Ordering::Equal
418 };
419
420 let wrap_and_sort_para = |para: &Paragraph| -> Paragraph {
422 let is_header = para.contains_key("Format");
423 let is_files = para.contains_key("Files");
424
425 if is_header {
426 let mut header = Header(para.clone());
427 header.wrap_and_sort(indentation, immediate_empty_line, max_line_length_one_liner);
428 header.0
429 } else if is_files {
430 let mut files = FilesParagraph(para.clone());
431 files.wrap_and_sort(indentation, immediate_empty_line, max_line_length_one_liner);
432 files.0
433 } else {
434 let mut license = LicenseParagraph(para.clone());
435 license.wrap_and_sort(indentation, immediate_empty_line, max_line_length_one_liner);
436 license.0
437 }
438 };
439
440 self.0 = self
441 .0
442 .wrap_and_sort(Some(&sort_paragraphs), Some(&wrap_and_sort_para));
443 }
444}
445
446#[derive(Debug)]
448pub enum Error {
449 ParseError(deb822_lossless::ParseError),
451
452 IoError(std::io::Error),
454
455 InvalidValue(String),
457
458 NotMachineReadable,
460}
461
462impl From<deb822_lossless::Error> for Error {
463 fn from(e: deb822_lossless::Error) -> Self {
464 match e {
465 deb822_lossless::Error::ParseError(e) => Error::ParseError(e),
466 deb822_lossless::Error::IoError(e) => Error::IoError(e),
467 deb822_lossless::Error::InvalidValue(msg) => Error::InvalidValue(msg),
468 }
469 }
470}
471
472impl From<std::io::Error> for Error {
473 fn from(e: std::io::Error) -> Self {
474 Error::IoError(e)
475 }
476}
477
478impl From<deb822_lossless::ParseError> for Error {
479 fn from(e: deb822_lossless::ParseError) -> Self {
480 Error::ParseError(e)
481 }
482}
483
484impl std::fmt::Display for Error {
485 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
486 match &self {
487 Error::ParseError(e) => write!(f, "parse error: {}", e),
488 Error::NotMachineReadable => write!(f, "not machine readable"),
489 Error::IoError(e) => write!(f, "io error: {}", e),
490 Error::InvalidValue(msg) => write!(f, "invalid value: {}", msg),
491 }
492 }
493}
494
495impl std::error::Error for Error {}
496
497impl Default for Copyright {
498 fn default() -> Self {
499 Copyright(Deb822::new())
500 }
501}
502
503impl std::str::FromStr for Copyright {
504 type Err = Error;
505
506 fn from_str(s: &str) -> Result<Self, Self::Err> {
507 if !s.starts_with("Format:") {
508 return Err(Error::NotMachineReadable);
509 }
510 Ok(Self(Deb822::from_str(s)?))
511 }
512}
513
514impl std::fmt::Display for Copyright {
515 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
516 f.write_str(&self.0.to_string())
517 }
518}
519
520pub struct Header(Paragraph);
522
523impl Header {
524 pub fn format_string(&self) -> Option<String> {
526 self.0
527 .get("Format")
528 .or_else(|| self.0.get("Format-Specification"))
529 }
530
531 pub fn as_deb822(&self) -> &Paragraph {
533 &self.0
534 }
535
536 #[deprecated = "Use as_deb822 instead"]
538 pub fn as_mut_deb822(&mut self) -> &mut Paragraph {
539 &mut self.0
540 }
541
542 pub fn upstream_name(&self) -> Option<String> {
544 self.0.get("Upstream-Name")
545 }
546
547 pub fn set_upstream_name(&mut self, name: &str) {
549 self.0
550 .set_with_field_order("Upstream-Name", name, HEADER_FIELD_ORDER);
551 }
552
553 pub fn upstream_contact(&self) -> Option<String> {
555 self.0.get("Upstream-Contact")
556 }
557
558 pub fn set_upstream_contact(&mut self, contact: &str) {
560 self.0
561 .set_with_field_order("Upstream-Contact", contact, HEADER_FIELD_ORDER);
562 }
563
564 pub fn source(&self) -> Option<String> {
566 self.0.get("Source")
567 }
568
569 pub fn set_source(&mut self, source: &str) {
571 self.0
572 .set_with_field_order("Source", source, HEADER_FIELD_ORDER);
573 }
574
575 pub fn files_excluded(&self) -> Option<Vec<String>> {
577 self.0
578 .get("Files-Excluded")
579 .map(|x| x.split('\n').map(|x| x.to_string()).collect::<Vec<_>>())
580 }
581
582 pub fn set_files_excluded(&mut self, files: &[&str]) {
584 self.0
585 .set_with_field_order("Files-Excluded", &files.join("\n"), HEADER_FIELD_ORDER);
586 }
587
588 pub fn fix(&mut self) {
593 if self.0.contains_key("Format-Specification") {
594 self.0.rename("Format-Specification", "Format");
595 }
596
597 if let Some(mut format) = self.0.get("Format") {
598 if !format.ends_with('/') {
599 format.push('/');
600 }
601
602 if let Some(rest) = format.strip_prefix("http:") {
603 format = format!("https:{}", rest);
604 }
605
606 if KNOWN_FORMATS.contains(&format.as_str()) {
607 format = CURRENT_FORMAT.to_string();
608 }
609
610 self.0.set("Format", format.as_str());
611 }
612 }
613
614 pub fn wrap_and_sort(
621 &mut self,
622 indentation: deb822_lossless::Indentation,
623 immediate_empty_line: bool,
624 max_line_length_one_liner: Option<usize>,
625 ) {
626 let sort_entries =
627 |a: &deb822_lossless::Entry, b: &deb822_lossless::Entry| -> std::cmp::Ordering {
628 let a_key = a.key().unwrap_or_default();
629 let b_key = b.key().unwrap_or_default();
630 let a_pos = HEADER_FIELD_ORDER.iter().position(|&k| k == a_key);
631 let b_pos = HEADER_FIELD_ORDER.iter().position(|&k| k == b_key);
632 match (a_pos, b_pos) {
633 (Some(a_idx), Some(b_idx)) => a_idx.cmp(&b_idx),
634 (Some(_), None) => std::cmp::Ordering::Less,
635 (None, Some(_)) => std::cmp::Ordering::Greater,
636 (None, None) => std::cmp::Ordering::Equal,
637 }
638 };
639 self.0 = self.0.wrap_and_sort(
640 indentation,
641 immediate_empty_line,
642 max_line_length_one_liner,
643 Some(&sort_entries),
644 None,
645 );
646 }
647}
648
649pub struct FilesParagraph(Paragraph);
651
652impl FilesParagraph {
653 pub fn as_deb822(&self) -> &Paragraph {
655 &self.0
656 }
657
658 pub fn files(&self) -> Vec<String> {
660 self.0
661 .get("Files")
662 .unwrap()
663 .split_whitespace()
664 .map(|v| v.to_string())
665 .collect::<Vec<_>>()
666 }
667
668 pub fn set_files(&mut self, files: &[&str]) {
670 self.0
671 .set_with_field_order("Files", &files.join(FILES_SEPARATOR), FILES_FIELD_ORDER);
672 }
673
674 pub fn add_file(&mut self, pattern: &str) {
678 let mut files = self.files();
679 if !files.contains(&pattern.to_string()) {
680 files.push(pattern.to_string());
681 self.0
682 .set_with_field_order("Files", &files.join(FILES_SEPARATOR), FILES_FIELD_ORDER);
683 }
684 }
685
686 pub fn remove_file(&mut self, pattern: &str) -> bool {
690 let mut files = self.files();
691 if let Some(pos) = files.iter().position(|f| f == pattern) {
692 files.remove(pos);
693 self.0
694 .set_with_field_order("Files", &files.join(FILES_SEPARATOR), FILES_FIELD_ORDER);
695 true
696 } else {
697 false
698 }
699 }
700
701 pub fn matches(&self, filename: &std::path::Path) -> bool {
703 self.files()
704 .iter()
705 .any(|f| crate::glob::glob_to_regex(f).is_match(filename.to_str().unwrap()))
706 }
707
708 pub fn copyright(&self) -> Vec<String> {
710 self.0
711 .get("Copyright")
712 .unwrap_or_default()
713 .split('\n')
714 .map(|x| x.to_string())
715 .collect::<Vec<_>>()
716 }
717
718 pub fn set_copyright(&mut self, authors: &[&str]) {
720 self.0
721 .set_with_field_order("Copyright", &authors.join("\n"), FILES_FIELD_ORDER);
722 }
723
724 pub fn comment(&self) -> Option<String> {
726 self.0.get("Comment")
727 }
728
729 pub fn set_comment(&mut self, comment: &str) {
731 self.0
732 .set_with_field_order("Comment", comment, FILES_FIELD_ORDER);
733 }
734
735 pub fn license(&self) -> Option<License> {
737 self.0.get_multiline("License").map(|x| {
738 x.split_once('\n').map_or_else(
739 || License::Name(x.to_string()),
740 |(name, text)| {
741 if name.is_empty() {
742 License::Text(text.to_string())
743 } else {
744 License::Named(name.to_string(), text.to_string())
745 }
746 },
747 )
748 })
749 }
750
751 pub fn set_license(&mut self, license: &License) {
753 let text = match license {
754 License::Name(name) => name.to_string(),
755 License::Named(name, text) => format!("{}\n{}", name, encode_field_text(text)),
756 License::Text(text) => encode_field_text(text),
757 };
758 let indent_pattern = deb822_lossless::IndentPattern::Fixed(1);
760 self.0
761 .set_with_forced_indent("License", &text, &indent_pattern, Some(FILES_FIELD_ORDER));
762 }
763
764 pub fn wrap_and_sort(
771 &mut self,
772 indentation: deb822_lossless::Indentation,
773 immediate_empty_line: bool,
774 max_line_length_one_liner: Option<usize>,
775 ) {
776 let sort_entries =
777 |a: &deb822_lossless::Entry, b: &deb822_lossless::Entry| -> std::cmp::Ordering {
778 let a_key = a.key().unwrap_or_default();
779 let b_key = b.key().unwrap_or_default();
780 let a_pos = FILES_FIELD_ORDER.iter().position(|&k| k == a_key);
781 let b_pos = FILES_FIELD_ORDER.iter().position(|&k| k == b_key);
782 match (a_pos, b_pos) {
783 (Some(a_idx), Some(b_idx)) => a_idx.cmp(&b_idx),
784 (Some(_), None) => std::cmp::Ordering::Less,
785 (None, Some(_)) => std::cmp::Ordering::Greater,
786 (None, None) => std::cmp::Ordering::Equal,
787 }
788 };
789
790 let format_value = |key: &str, value: &str| -> String {
791 if key == "Files" {
792 let mut patterns: Vec<_> = value.split_whitespace().collect();
793 patterns.sort_by_key(|p| {
794 let depth = crate::pattern_depth(p);
795 crate::pattern_sort_key(p, depth)
796 });
797 patterns.join(FILES_SEPARATOR)
798 } else {
799 value.to_string()
800 }
801 };
802
803 self.0 = self.0.wrap_and_sort(
804 indentation,
805 immediate_empty_line,
806 max_line_length_one_liner,
807 Some(&sort_entries),
808 Some(&format_value),
809 );
810 }
811}
812
813pub struct LicenseParagraph(Paragraph);
815
816impl From<LicenseParagraph> for License {
817 fn from(p: LicenseParagraph) -> Self {
818 let x = p.0.get_multiline("License").unwrap();
819 x.split_once('\n').map_or_else(
820 || License::Name(x.to_string()),
821 |(name, text)| {
822 if name.is_empty() {
823 License::Text(text.to_string())
824 } else {
825 License::Named(name.to_string(), text.to_string())
826 }
827 },
828 )
829 }
830}
831
832impl LicenseParagraph {
833 pub fn as_deb822(&self) -> &Paragraph {
835 &self.0
836 }
837
838 pub fn comment(&self) -> Option<String> {
840 self.0.get("Comment")
841 }
842
843 pub fn set_comment(&mut self, comment: &str) {
845 self.0
846 .set_with_field_order("Comment", comment, LICENSE_FIELD_ORDER);
847 }
848
849 pub fn name(&self) -> Option<String> {
851 self.0
852 .get_multiline("License")
853 .and_then(|x| x.split_once('\n').map(|(name, _)| name.to_string()))
854 }
855
856 pub fn text(&self) -> Option<String> {
858 self.0
859 .get_multiline("License")
860 .and_then(|x| x.split_once('\n').map(|(_, text)| decode_field_text(text)))
861 }
862
863 pub fn license(&self) -> License {
865 let x = self.0.get_multiline("License").unwrap();
866 x.split_once('\n').map_or_else(
867 || License::Name(x.to_string()),
868 |(name, text)| {
869 let decoded_text = decode_field_text(text);
870 if name.is_empty() {
871 License::Text(decoded_text)
872 } else {
873 License::Named(name.to_string(), decoded_text)
874 }
875 },
876 )
877 }
878
879 pub fn set_license(&mut self, license: &License) {
881 let text = match license {
882 License::Name(name) => name.to_string(),
883 License::Named(name, text) => format!("{}\n{}", name, encode_field_text(text)),
884 License::Text(text) => encode_field_text(text),
885 };
886 let indent_pattern = deb822_lossless::IndentPattern::Fixed(1);
888 self.0
889 .set_with_forced_indent("License", &text, &indent_pattern, Some(LICENSE_FIELD_ORDER));
890 }
891
892 pub fn set_name(&mut self, name: &str) {
897 let current = self.license();
898 let new_license = match current {
899 License::Named(_, text) | License::Text(text) => License::Named(name.to_string(), text),
900 License::Name(_) => License::Name(name.to_string()),
901 };
902 self.set_license(&new_license);
903 }
904
905 pub fn set_text(&mut self, text: Option<&str>) {
911 let current = self.license();
912 let new_license = match (current, text) {
913 (License::Named(name, _), Some(new_text)) | (License::Name(name), Some(new_text)) => {
914 License::Named(name, new_text.to_string())
915 }
916 (License::Named(name, _), None) | (License::Name(name), None) => License::Name(name),
917 (License::Text(_), Some(new_text)) => License::Text(new_text.to_string()),
918 (License::Text(_), None) => {
919 License::Name(String::new())
921 }
922 };
923 self.set_license(&new_license);
924 }
925
926 pub fn wrap_and_sort(
933 &mut self,
934 indentation: deb822_lossless::Indentation,
935 immediate_empty_line: bool,
936 max_line_length_one_liner: Option<usize>,
937 ) {
938 let sort_entries =
939 |a: &deb822_lossless::Entry, b: &deb822_lossless::Entry| -> std::cmp::Ordering {
940 let a_key = a.key().unwrap_or_default();
941 let b_key = b.key().unwrap_or_default();
942 let a_pos = LICENSE_FIELD_ORDER.iter().position(|&k| k == a_key);
943 let b_pos = LICENSE_FIELD_ORDER.iter().position(|&k| k == b_key);
944 match (a_pos, b_pos) {
945 (Some(a_idx), Some(b_idx)) => a_idx.cmp(&b_idx),
946 (Some(_), None) => std::cmp::Ordering::Less,
947 (None, Some(_)) => std::cmp::Ordering::Greater,
948 (None, None) => std::cmp::Ordering::Equal,
949 }
950 };
951 self.0 = self.0.wrap_and_sort(
952 indentation,
953 immediate_empty_line,
954 max_line_length_one_liner,
955 Some(&sort_entries),
956 None,
957 );
958 }
959}
960
961#[cfg(test)]
962mod tests {
963 use deb822_lossless::{TextRange, TextSize};
964
965 #[test]
966 fn test_not_machine_readable() {
967 let s = r#"
968This copyright file is not machine readable.
969"#;
970 let ret = s.parse::<super::Copyright>();
971 assert!(ret.is_err());
972 assert!(matches!(ret.unwrap_err(), super::Error::NotMachineReadable));
973 }
974
975 #[test]
976 fn test_new() {
977 let n = super::Copyright::new();
978 assert_eq!(
979 n.to_string().as_str(),
980 "Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\n"
981 );
982 }
983
984 #[test]
985 fn test_parse() {
986 let s = r#"Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
987Upstream-Name: foo
988Upstream-Contact: Joe Bloggs <joe@example.com>
989Source: https://example.com/foo
990
991Files: *
992Copyright:
993 2020 Joe Bloggs <joe@example.com>
994License: GPL-3+
995
996Files: debian/*
997Comment: Debian packaging is licensed under the GPL-3+.
998Copyright: 2023 Jelmer Vernooij
999License: GPL-3+
1000
1001License: GPL-3+
1002 This program is free software: you can redistribute it and/or modify
1003 it under the terms of the GNU General Public License as published by
1004 the Free Software Foundation, either version 3 of the License, or
1005 (at your option) any later version.
1006"#;
1007 let copyright = s.parse::<super::Copyright>().expect("failed to parse");
1008
1009 assert_eq!(
1010 "https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/",
1011 copyright.header().unwrap().format_string().unwrap()
1012 );
1013 assert_eq!("foo", copyright.header().unwrap().upstream_name().unwrap());
1014 assert_eq!(
1015 "Joe Bloggs <joe@example.com>",
1016 copyright.header().unwrap().upstream_contact().unwrap()
1017 );
1018 assert_eq!(
1019 "https://example.com/foo",
1020 copyright.header().unwrap().source().unwrap()
1021 );
1022
1023 let files = copyright.iter_files().collect::<Vec<_>>();
1024 assert_eq!(2, files.len());
1025 assert_eq!("*", files[0].files().join(" "));
1026 assert_eq!("debian/*", files[1].files().join(" "));
1027 assert_eq!(
1028 "Debian packaging is licensed under the GPL-3+.",
1029 files[1].comment().unwrap()
1030 );
1031 assert_eq!(
1032 vec!["2023 Jelmer Vernooij".to_string()],
1033 files[1].copyright()
1034 );
1035 assert_eq!("GPL-3+", files[1].license().unwrap().name().unwrap());
1036 assert_eq!(files[1].license().unwrap().text(), None);
1037
1038 let licenses = copyright.iter_licenses().collect::<Vec<_>>();
1039 assert_eq!(1, licenses.len());
1040 assert_eq!("GPL-3+", licenses[0].name().unwrap());
1041 assert_eq!(
1042 "This program is free software: you can redistribute it and/or modify
1043it under the terms of the GNU General Public License as published by
1044the Free Software Foundation, either version 3 of the License, or
1045(at your option) any later version.",
1046 licenses[0].text().unwrap()
1047 );
1048
1049 let upstream_files = copyright.find_files(std::path::Path::new("foo.c")).unwrap();
1050 assert_eq!(vec!["*"], upstream_files.files());
1051
1052 let debian_files = copyright
1053 .find_files(std::path::Path::new("debian/foo.c"))
1054 .unwrap();
1055 assert_eq!(vec!["debian/*"], debian_files.files());
1056
1057 let gpl = copyright.find_license_by_name("GPL-3+");
1058 assert!(gpl.is_some());
1059
1060 let gpl = copyright.find_license_for_file(std::path::Path::new("debian/foo.c"));
1061 assert_eq!(gpl.unwrap().name().unwrap(), "GPL-3+");
1062 }
1063
1064 #[test]
1065 fn test_from_str_relaxed() {
1066 let s = r#"Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
1067Upstream-Name: foo
1068Source: https://example.com/foo
1069
1070Files: *
1071Copyright: 2020 Joe Bloggs <joe@example.com>
1072License: GPL-3+
1073"#;
1074 let (copyright, errors) = super::Copyright::from_str_relaxed(s).unwrap();
1075 assert!(errors.is_empty());
1076 assert_eq!("foo", copyright.header().unwrap().upstream_name().unwrap());
1077 }
1078
1079 #[test]
1080 fn test_from_file_relaxed() {
1081 let tmpfile = std::env::temp_dir().join("test_copyright.txt");
1082 std::fs::write(
1083 &tmpfile,
1084 r#"Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
1085Upstream-Name: foo
1086Source: https://example.com/foo
1087
1088Files: *
1089Copyright: 2020 Joe Bloggs <joe@example.com>
1090License: GPL-3+
1091"#,
1092 )
1093 .unwrap();
1094 let (copyright, errors) = super::Copyright::from_file_relaxed(&tmpfile).unwrap();
1095 assert!(errors.is_empty());
1096 assert_eq!("foo", copyright.header().unwrap().upstream_name().unwrap());
1097 std::fs::remove_file(&tmpfile).unwrap();
1098 }
1099
1100 #[test]
1101 fn test_header_set_upstream_contact() {
1102 let copyright = super::Copyright::new();
1103 let mut header = copyright.header().unwrap();
1104 header.set_upstream_contact("Test Person <test@example.com>");
1105 assert_eq!(
1106 header.upstream_contact().unwrap(),
1107 "Test Person <test@example.com>"
1108 );
1109 }
1110
1111 #[test]
1112 fn test_header_set_source() {
1113 let copyright = super::Copyright::new();
1114 let mut header = copyright.header().unwrap();
1115 header.set_source("https://example.com/source");
1116 assert_eq!(header.source().unwrap(), "https://example.com/source");
1117 }
1118
1119 #[test]
1120 fn test_license_paragraph_set_comment() {
1121 let s = r#"Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
1122
1123License: GPL-3+
1124 This is the license text.
1125"#;
1126 let copyright = s.parse::<super::Copyright>().unwrap();
1127 let mut license = copyright.iter_licenses().next().unwrap();
1128 license.set_comment("This is a test comment");
1129 assert_eq!(license.comment().unwrap(), "This is a test comment");
1130 }
1131
1132 #[test]
1133 fn test_license_paragraph_set_license() {
1134 let s = r#"Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
1135
1136License: GPL-3+
1137 Old license text.
1138"#;
1139 let copyright = s.parse::<super::Copyright>().unwrap();
1140 let mut license = copyright.iter_licenses().next().unwrap();
1141
1142 let new_license = crate::License::Named(
1143 "MIT".to_string(),
1144 "Permission is hereby granted...".to_string(),
1145 );
1146 license.set_license(&new_license);
1147
1148 assert_eq!(license.name().unwrap(), "MIT");
1149 assert_eq!(license.text().unwrap(), "Permission is hereby granted...");
1150 }
1151
1152 #[test]
1153 fn test_iter_licenses_excludes_header() {
1154 let s = r#"Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
1156Upstream-Name: foo
1157License: GPL-3+
1158
1159Files: *
1160Copyright: 2020 Joe Bloggs
1161License: MIT
1162
1163License: GPL-3+
1164 This is the GPL-3+ license text.
1165"#;
1166 let copyright = s.parse::<super::Copyright>().unwrap();
1167 let licenses: Vec<_> = copyright.iter_licenses().collect();
1168
1169 assert_eq!(1, licenses.len());
1171 assert_eq!("GPL-3+", licenses[0].name().unwrap());
1172 assert_eq!(
1173 "This is the GPL-3+ license text.",
1174 licenses[0].text().unwrap()
1175 );
1176 }
1177
1178 #[test]
1179 fn test_add_files() {
1180 let mut copyright = super::Copyright::new();
1181 let license = crate::License::Name("GPL-3+".to_string());
1182 copyright.add_files(
1183 &["src/*", "*.rs"],
1184 &["2024 John Doe", "2024 Jane Doe"],
1185 &license,
1186 );
1187
1188 let files: Vec<_> = copyright.iter_files().collect();
1189 assert_eq!(1, files.len());
1190 assert_eq!(vec!["src/*", "*.rs"], files[0].files());
1191 assert_eq!(vec!["2024 John Doe", "2024 Jane Doe"], files[0].copyright());
1192 assert_eq!("GPL-3+", files[0].license().unwrap().name().unwrap());
1193
1194 assert_eq!(
1196 copyright.to_string(),
1197 "Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\n\n\
1198 Files: src/* *.rs\n\
1199 Copyright: 2024 John Doe\n 2024 Jane Doe\n\
1200 License: GPL-3+\n"
1201 );
1202 }
1203
1204 #[test]
1205 fn test_add_files_with_license_text() {
1206 let mut copyright = super::Copyright::new();
1207 let license = crate::License::Named(
1208 "MIT".to_string(),
1209 "Permission is hereby granted...".to_string(),
1210 );
1211 copyright.add_files(&["*"], &["2024 Test Author"], &license);
1212
1213 let files: Vec<_> = copyright.iter_files().collect();
1214 assert_eq!(1, files.len());
1215 assert_eq!("MIT", files[0].license().unwrap().name().unwrap());
1216 assert_eq!(
1217 "Permission is hereby granted...",
1218 files[0].license().unwrap().text().unwrap()
1219 );
1220
1221 assert_eq!(
1223 copyright.to_string(),
1224 "Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\n\n\
1225 Files: *\n\
1226 Copyright: 2024 Test Author\n\
1227 License: MIT\n Permission is hereby granted...\n"
1228 );
1229 }
1230
1231 #[test]
1232 fn test_add_license() {
1233 let mut copyright = super::Copyright::new();
1234 let license = crate::License::Named(
1235 "GPL-3+".to_string(),
1236 "This is the GPL-3+ license text.".to_string(),
1237 );
1238 copyright.add_license(&license);
1239
1240 let licenses: Vec<_> = copyright.iter_licenses().collect();
1241 assert_eq!(1, licenses.len());
1242 assert_eq!("GPL-3+", licenses[0].name().unwrap());
1243 assert_eq!(
1244 "This is the GPL-3+ license text.",
1245 licenses[0].text().unwrap()
1246 );
1247
1248 assert_eq!(
1250 copyright.to_string(),
1251 "Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\n\n\
1252 License: GPL-3+\n This is the GPL-3+ license text.\n"
1253 );
1254 }
1255
1256 #[test]
1257 fn test_add_multiple_paragraphs() {
1258 let mut copyright = super::Copyright::new();
1259
1260 let license1 = crate::License::Name("MIT".to_string());
1262 copyright.add_files(&["src/*"], &["2024 Author One"], &license1);
1263
1264 let license2 = crate::License::Name("GPL-3+".to_string());
1266 copyright.add_files(&["debian/*"], &["2024 Author Two"], &license2);
1267
1268 let license3 =
1270 crate::License::Named("GPL-3+".to_string(), "Full GPL-3+ text here.".to_string());
1271 copyright.add_license(&license3);
1272
1273 assert_eq!(2, copyright.iter_files().count());
1275 assert_eq!(1, copyright.iter_licenses().count());
1276
1277 let files: Vec<_> = copyright.iter_files().collect();
1278 assert_eq!(vec!["src/*"], files[0].files());
1279 assert_eq!(vec!["debian/*"], files[1].files());
1280
1281 let licenses: Vec<_> = copyright.iter_licenses().collect();
1282 assert_eq!("GPL-3+", licenses[0].name().unwrap());
1283 assert_eq!("Full GPL-3+ text here.", licenses[0].text().unwrap());
1284
1285 assert_eq!(
1287 copyright.to_string(),
1288 "Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\n\n\
1289 Files: src/*\n\
1290 Copyright: 2024 Author One\n\
1291 License: MIT\n\n\
1292 Files: debian/*\n\
1293 Copyright: 2024 Author Two\n\
1294 License: GPL-3+\n\n\
1295 License: GPL-3+\n Full GPL-3+ text here.\n"
1296 );
1297 }
1298
1299 #[test]
1300 fn test_remove_license_by_name() {
1301 let mut copyright = super::Copyright::new();
1302
1303 let license1 = crate::License::Named("MIT".to_string(), "MIT license text.".to_string());
1305 copyright.add_license(&license1);
1306
1307 let license2 =
1308 crate::License::Named("GPL-3+".to_string(), "GPL-3+ license text.".to_string());
1309 copyright.add_license(&license2);
1310
1311 let license3 =
1312 crate::License::Named("Apache-2.0".to_string(), "Apache license text.".to_string());
1313 copyright.add_license(&license3);
1314
1315 assert_eq!(3, copyright.iter_licenses().count());
1317
1318 let removed = copyright.remove_license_by_name("GPL-3+");
1320 assert!(removed);
1321
1322 assert_eq!(2, copyright.iter_licenses().count());
1324
1325 let licenses: Vec<_> = copyright.iter_licenses().collect();
1327 assert_eq!("MIT", licenses[0].name().unwrap());
1328 assert_eq!("Apache-2.0", licenses[1].name().unwrap());
1329
1330 let removed = copyright.remove_license_by_name("BSD-3-Clause");
1332 assert!(!removed);
1333 assert_eq!(2, copyright.iter_licenses().count());
1334 }
1335
1336 #[test]
1337 fn test_remove_files_by_pattern() {
1338 let mut copyright = super::Copyright::new();
1339
1340 let license1 = crate::License::Name("MIT".to_string());
1342 copyright.add_files(&["src/*"], &["2024 Author One"], &license1);
1343
1344 let license2 = crate::License::Name("GPL-3+".to_string());
1345 copyright.add_files(&["debian/*"], &["2024 Author Two"], &license2);
1346
1347 let license3 = crate::License::Name("Apache-2.0".to_string());
1348 copyright.add_files(&["docs/*"], &["2024 Author Three"], &license3);
1349
1350 assert_eq!(3, copyright.iter_files().count());
1352
1353 let removed = copyright.remove_files_by_pattern("debian/*");
1355 assert!(removed);
1356
1357 assert_eq!(2, copyright.iter_files().count());
1359
1360 let files: Vec<_> = copyright.iter_files().collect();
1362 assert_eq!(vec!["src/*"], files[0].files());
1363 assert_eq!(vec!["docs/*"], files[1].files());
1364
1365 let removed = copyright.remove_files_by_pattern("tests/*");
1367 assert!(!removed);
1368 assert_eq!(2, copyright.iter_files().count());
1369 }
1370
1371 #[test]
1372 fn test_remove_files_by_pattern_with_multiple_patterns() {
1373 let mut copyright = super::Copyright::new();
1374
1375 let license = crate::License::Name("MIT".to_string());
1377 copyright.add_files(&["src/*", "*.rs"], &["2024 Author"], &license);
1378
1379 assert_eq!(1, copyright.iter_files().count());
1381
1382 let removed = copyright.remove_files_by_pattern("*.rs");
1384 assert!(removed);
1385
1386 assert_eq!(0, copyright.iter_files().count());
1388 }
1389
1390 #[test]
1391 fn test_license_paragraph_set_name() {
1392 let s = r#"Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
1393
1394License: GPL-3+
1395 This is the GPL-3+ license text.
1396"#;
1397 let copyright = s.parse::<super::Copyright>().unwrap();
1398 let mut license = copyright.iter_licenses().next().unwrap();
1399
1400 license.set_name("Apache-2.0");
1402
1403 assert_eq!(license.name().unwrap(), "Apache-2.0");
1404 assert_eq!(license.text().unwrap(), "This is the GPL-3+ license text.");
1405 }
1406
1407 #[test]
1408 fn test_license_paragraph_set_name_no_text() {
1409 let s = r#"Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
1410
1411License: GPL-3+
1412"#;
1413 let copyright = s.parse::<super::Copyright>().unwrap();
1414 let mut license = copyright.iter_licenses().next().unwrap();
1415
1416 license.set_name("MIT");
1418
1419 assert_eq!(license.license(), crate::License::Name("MIT".to_string()));
1420 assert_eq!(license.text(), None);
1421 }
1422
1423 #[test]
1424 fn test_license_paragraph_set_text() {
1425 let s = r#"Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
1426
1427License: GPL-3+
1428 Old license text.
1429"#;
1430 let copyright = s.parse::<super::Copyright>().unwrap();
1431 let mut license = copyright.iter_licenses().next().unwrap();
1432
1433 license.set_text(Some("New license text."));
1435
1436 assert_eq!(license.name().unwrap(), "GPL-3+");
1437 assert_eq!(license.text().unwrap(), "New license text.");
1438 }
1439
1440 #[test]
1441 fn test_license_paragraph_set_text_remove() {
1442 let s = r#"Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
1443
1444License: GPL-3+
1445 Old license text.
1446"#;
1447 let copyright = s.parse::<super::Copyright>().unwrap();
1448 let mut license = copyright.iter_licenses().next().unwrap();
1449
1450 license.set_text(None);
1452
1453 assert_eq!(
1454 license.license(),
1455 crate::License::Name("GPL-3+".to_string())
1456 );
1457 assert_eq!(license.text(), None);
1458 }
1459
1460 #[test]
1461 fn test_license_paragraph_set_text_add() {
1462 let s = r#"Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
1463
1464License: GPL-3+
1465"#;
1466 let copyright = s.parse::<super::Copyright>().unwrap();
1467 let mut license = copyright.iter_licenses().next().unwrap();
1468
1469 license.set_text(Some("This is the full GPL-3+ license text."));
1471
1472 assert_eq!(license.name().unwrap(), "GPL-3+");
1473 assert_eq!(
1474 license.text().unwrap(),
1475 "This is the full GPL-3+ license text."
1476 );
1477 }
1478
1479 #[test]
1480 fn test_files_paragraph_set_files() {
1481 let s = r#"Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
1482
1483Files: *
1484Copyright: 2024 Test Author
1485License: MIT
1486"#;
1487 let copyright = s.parse::<super::Copyright>().unwrap();
1488 let mut files = copyright.iter_files().next().unwrap();
1489
1490 files.set_files(&["src/*", "*.rs", "tests/*"]);
1492
1493 assert_eq!(vec!["src/*", "*.rs", "tests/*"], files.files());
1495 }
1496
1497 #[test]
1498 fn test_files_paragraph_add_file() {
1499 let s = r#"Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
1500
1501Files: src/*
1502Copyright: 2024 Test Author
1503License: MIT
1504"#;
1505 let copyright = s.parse::<super::Copyright>().unwrap();
1506 let mut files = copyright.iter_files().next().unwrap();
1507
1508 files.add_file("*.rs");
1510 assert_eq!(vec!["src/*", "*.rs"], files.files());
1511
1512 files.add_file("tests/*");
1514 assert_eq!(vec!["src/*", "*.rs", "tests/*"], files.files());
1515
1516 files.add_file("*.rs");
1518 assert_eq!(vec!["src/*", "*.rs", "tests/*"], files.files());
1519 }
1520
1521 #[test]
1522 fn test_files_paragraph_remove_file() {
1523 let s = r#"Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
1524
1525Files: src/* *.rs tests/*
1526Copyright: 2024 Test Author
1527License: MIT
1528"#;
1529 let copyright = s.parse::<super::Copyright>().unwrap();
1530 let mut files = copyright.iter_files().next().unwrap();
1531
1532 let removed = files.remove_file("*.rs");
1534 assert!(removed);
1535 assert_eq!(vec!["src/*", "tests/*"], files.files());
1536
1537 let removed = files.remove_file("tests/*");
1539 assert!(removed);
1540 assert_eq!(vec!["src/*"], files.files());
1541
1542 let removed = files.remove_file("debian/*");
1544 assert!(!removed);
1545 assert_eq!(vec!["src/*"], files.files());
1546 }
1547
1548 #[test]
1549 fn test_field_order_with_comment() {
1550 let mut copyright = super::Copyright::new();
1552
1553 let files = vec!["*"];
1554 let copyrights = vec!["Unknown"];
1555 let license = crate::License::Name("GPL-2+".to_string());
1556
1557 let mut para = copyright.add_files(&files, ©rights, &license);
1558 para.set_comment("Test comment");
1559
1560 let output = copyright.to_string();
1561
1562 let expected =
1564 "Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\n\n\
1565 Files: *\n\
1566 Copyright: Unknown\n\
1567 License: GPL-2+\n\
1568 Comment: Test comment\n";
1569
1570 assert_eq!(
1571 output, expected,
1572 "Fields should be in DEP-5 order (Files, Copyright, License, Comment), but got:\n{}",
1573 output
1574 );
1575 }
1576
1577 #[test]
1578 fn test_license_text_decoding_paragraph_markers() {
1579 let s = r#"Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
1581
1582License: MIT
1583 Permission is hereby granted, free of charge, to any person obtaining a copy
1584 of this software and associated documentation files.
1585 .
1586 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
1587"#;
1588 let copyright = s.parse::<super::Copyright>().expect("failed to parse");
1589 let license_para = copyright
1590 .iter_licenses()
1591 .next()
1592 .expect("no license paragraph");
1593 let text = license_para.text().expect("no license text");
1594
1595 assert!(
1597 text.contains("\n\n"),
1598 "Expected blank line in decoded text, got: {:?}",
1599 text
1600 );
1601 assert!(
1602 !text.contains("\n.\n"),
1603 "Period marker should be decoded, not present in output"
1604 );
1605
1606 let expected = "Permission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND.";
1608 assert_eq!(text, expected);
1609 }
1610
1611 #[test]
1612 fn test_license_enum_decoding() {
1613 let s = r#"Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
1615
1616License: GPL-3+
1617 This program is free software.
1618 .
1619 You can redistribute it.
1620"#;
1621 let copyright = s.parse::<super::Copyright>().expect("failed to parse");
1622 let license_para = copyright
1623 .iter_licenses()
1624 .next()
1625 .expect("no license paragraph");
1626 let license = license_para.license();
1627
1628 match license {
1629 crate::License::Named(name, text) => {
1630 assert_eq!(name, "GPL-3+");
1631 assert!(text.contains("\n\n"), "Expected blank line in decoded text");
1632 assert!(!text.contains("\n.\n"), "Period marker should be decoded");
1633 assert_eq!(
1634 text,
1635 "This program is free software.\n\nYou can redistribute it."
1636 );
1637 }
1638 _ => panic!("Expected Named license"),
1639 }
1640 }
1641
1642 #[test]
1643 fn test_encode_field_text() {
1644 let input = "line 1\n\nline 3";
1646 let output = super::encode_field_text(input);
1647 assert_eq!(output, "line 1\n.\nline 3");
1648 }
1649
1650 #[test]
1651 fn test_encode_decode_round_trip() {
1652 let original = "First paragraph\n\nSecond paragraph\n\nThird paragraph";
1654 let encoded = super::encode_field_text(original);
1655 let decoded = super::decode_field_text(&encoded);
1656 assert_eq!(
1657 decoded, original,
1658 "Round-trip encoding/decoding should preserve text"
1659 );
1660 }
1661
1662 #[test]
1663 fn test_set_license_with_blank_lines() {
1664 let s = r#"Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
1666
1667License: GPL-3+
1668 Original text
1669"#;
1670 let copyright = s.parse::<super::Copyright>().expect("failed to parse");
1671 let mut license_para = copyright
1672 .iter_licenses()
1673 .next()
1674 .expect("no license paragraph");
1675
1676 let new_license = crate::License::Named(
1678 "GPL-3+".to_string(),
1679 "First paragraph.\n\nSecond paragraph.".to_string(),
1680 );
1681 license_para.set_license(&new_license);
1682
1683 let raw_text = copyright.to_string();
1685 let expected_output = "Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\n\nLicense: GPL-3+\n First paragraph.\n .\n Second paragraph.\n";
1686 assert_eq!(raw_text, expected_output);
1687
1688 let retrieved = license_para.text().expect("no text");
1690 assert_eq!(retrieved, "First paragraph.\n\nSecond paragraph.");
1691 }
1692
1693 #[test]
1694 fn test_set_text_with_blank_lines() {
1695 let s = r#"Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
1697
1698License: MIT
1699 Original text
1700"#;
1701 let copyright = s.parse::<super::Copyright>().expect("failed to parse");
1702 let mut license_para = copyright
1703 .iter_licenses()
1704 .next()
1705 .expect("no license paragraph");
1706
1707 license_para.set_text(Some("Line 1\n\nLine 2"));
1709
1710 let raw_text = copyright.to_string();
1712 let expected_output = "Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\n\nLicense: MIT\n Line 1\n .\n Line 2\n";
1713 assert_eq!(raw_text, expected_output);
1714
1715 let retrieved = license_para.text().expect("no text");
1717 assert_eq!(retrieved, "Line 1\n\nLine 2");
1718 }
1719
1720 #[test]
1721 fn test_set_license_uses_single_space_indent_for_new_multiline() {
1722 let s = r#"Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
1725
1726License: Apache-2.0
1727"#;
1728 let copyright = s.parse::<super::Copyright>().expect("failed to parse");
1729 let mut license_para = copyright
1730 .iter_licenses()
1731 .next()
1732 .expect("no license paragraph");
1733
1734 let new_license = crate::License::Named(
1736 "Apache-2.0".to_string(),
1737 "Licensed under the Apache License, Version 2.0".to_string(),
1738 );
1739 license_para.set_license(&new_license);
1740
1741 let result = copyright.to_string();
1743 let expected = "Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\n\nLicense: Apache-2.0\n Licensed under the Apache License, Version 2.0\n";
1744 assert_eq!(result, expected);
1745 }
1746
1747 #[test]
1748 fn test_header_as_deb822() {
1749 let s = r#"Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
1750Upstream-Name: foo
1751"#;
1752 let copyright = s.parse::<super::Copyright>().unwrap();
1753 let header = copyright.header().unwrap();
1754 let para = header.as_deb822();
1755 assert_eq!(para.get("Upstream-Name"), Some("foo".to_string()));
1756 }
1757
1758 #[test]
1759 fn test_files_paragraph_as_deb822() {
1760 let s = r#"Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
1761
1762Files: *
1763Copyright: 2024 Test
1764License: MIT
1765"#;
1766 let copyright = s.parse::<super::Copyright>().unwrap();
1767 let files = copyright.iter_files().next().unwrap();
1768 let para = files.as_deb822();
1769 assert_eq!(para.get("Files"), Some("*".to_string()));
1770 }
1771
1772 #[test]
1773 fn test_license_paragraph_as_deb822() {
1774 let s = r#"Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
1775
1776License: GPL-3+
1777 License text
1778"#;
1779 let copyright = s.parse::<super::Copyright>().unwrap();
1780 let license = copyright.iter_licenses().next().unwrap();
1781 let para = license.as_deb822();
1782 assert!(para.get("License").unwrap().starts_with("GPL-3+"));
1783 }
1784
1785 #[test]
1786 fn test_header_in_range() {
1787 let s = r#"Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
1788Upstream-Name: example
1789
1790Files: *
1791Copyright: 2024 Author
1792License: MIT
1793"#;
1794 let copyright = s.parse::<super::Copyright>().expect("failed to parse");
1795
1796 let header = copyright.header().unwrap();
1798 let header_range = header.as_deb822().text_range();
1799
1800 let result = copyright.header_in_range(header_range);
1802 assert!(result.is_some());
1803 assert_eq!(
1804 "https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/",
1805 result.unwrap().format_string().unwrap()
1806 );
1807
1808 let overlapping_range =
1810 TextRange::new(TextSize::from(0), header_range.end() - TextSize::from(10));
1811 let result = copyright.header_in_range(overlapping_range);
1812 assert!(result.is_some());
1813
1814 let files = copyright.iter_files().next().unwrap();
1816 let files_range = files.as_deb822().text_range();
1817 let result = copyright.header_in_range(files_range);
1818 assert!(result.is_none());
1819 }
1820
1821 #[test]
1822 fn test_iter_files_in_range() {
1823 let s = r#"Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
1824
1825Files: *
1826Copyright: 2024 Main Author
1827License: GPL-3+
1828
1829Files: src/*
1830Copyright: 2024 Author
1831License: MIT
1832
1833Files: debian/*
1834Copyright: 2024 Debian Maintainer
1835License: GPL-3+
1836"#;
1837 let copyright = s.parse::<super::Copyright>().expect("failed to parse");
1838
1839 let all_files: Vec<_> = copyright.iter_files().collect();
1841 assert_eq!(3, all_files.len());
1842
1843 let second_range = all_files[1].as_deb822().text_range();
1845 let result: Vec<_> = copyright.iter_files_in_range(second_range).collect();
1846 assert_eq!(1, result.len());
1847 assert_eq!(vec!["src/*"], result[0].files());
1848
1849 let span_range = TextRange::new(
1851 all_files[0].as_deb822().text_range().start(),
1852 all_files[1].as_deb822().text_range().end(),
1853 );
1854 let result: Vec<_> = copyright.iter_files_in_range(span_range).collect();
1855 assert_eq!(2, result.len());
1856 assert_eq!(vec!["*"], result[0].files());
1857 assert_eq!(vec!["src/*"], result[1].files());
1858
1859 let header_range = copyright.header().unwrap().as_deb822().text_range();
1861 let result: Vec<_> = copyright.iter_files_in_range(header_range).collect();
1862 assert_eq!(0, result.len());
1863 }
1864
1865 #[test]
1866 fn test_iter_licenses_in_range() {
1867 let s = r#"Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
1868
1869Files: *
1870Copyright: 2024 Author
1871License: MIT
1872
1873License: MIT
1874 MIT license text here.
1875
1876License: GPL-3+
1877 GPL license text here.
1878"#;
1879 let copyright = s.parse::<super::Copyright>().expect("failed to parse");
1880
1881 let all_licenses: Vec<_> = copyright.iter_licenses().collect();
1883 assert_eq!(2, all_licenses.len());
1884
1885 let first_range = all_licenses[0].as_deb822().text_range();
1887 let result: Vec<_> = copyright.iter_licenses_in_range(first_range).collect();
1888 assert_eq!(1, result.len());
1889 assert_eq!(Some("MIT".to_string()), result[0].name());
1890
1891 let span_range = TextRange::new(
1893 all_licenses[0].as_deb822().text_range().start(),
1894 all_licenses[1].as_deb822().text_range().end(),
1895 );
1896 let result: Vec<_> = copyright.iter_licenses_in_range(span_range).collect();
1897 assert_eq!(2, result.len());
1898 assert_eq!(Some("MIT".to_string()), result[0].name());
1899 assert_eq!(Some("GPL-3+".to_string()), result[1].name());
1900
1901 let files = copyright.iter_files().next().unwrap();
1903 let files_range = files.as_deb822().text_range();
1904 let result: Vec<_> = copyright.iter_licenses_in_range(files_range).collect();
1905 assert_eq!(0, result.len());
1906 }
1907
1908 #[test]
1909 fn test_header_wrap_and_sort() {
1910 let s = r#"Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
1912Comment: Some comment
1913Source: https://example.com
1914Upstream-Contact: John Doe
1915Upstream-Name: example
1916"#;
1917 let copyright = s.parse::<super::Copyright>().expect("failed to parse");
1918 let mut header = copyright.header().unwrap();
1919
1920 header.wrap_and_sort(deb822_lossless::Indentation::Spaces(1), false, None);
1921
1922 let expected = "Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\nUpstream-Name: example\nUpstream-Contact: John Doe\nSource: https://example.com\nComment: Some comment\n";
1924 assert_eq!(expected, header.0.to_string());
1925 }
1926
1927 #[test]
1928 fn test_files_paragraph_wrap_and_sort_field_order() {
1929 let s = r#"Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
1931
1932Comment: Some comment
1933License: MIT
1934Copyright: 2024 Author
1935Files: *
1936"#;
1937 let copyright = s.parse::<super::Copyright>().expect("failed to parse");
1938 let mut files = copyright.iter_files().next().unwrap();
1939
1940 files.wrap_and_sort(deb822_lossless::Indentation::Spaces(1), false, None);
1941
1942 let expected = "Files: *\nCopyright: 2024 Author\nLicense: MIT\nComment: Some comment\n";
1944 assert_eq!(expected, files.0.to_string());
1945 }
1946
1947 #[test]
1948 fn test_files_paragraph_wrap_and_sort_patterns() {
1949 let s = r#"Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
1951
1952Files: debian/* src/foo/* * src/*
1953Copyright: 2024 Author
1954License: MIT
1955"#;
1956 let copyright = s.parse::<super::Copyright>().expect("failed to parse");
1957 let mut files = copyright.iter_files().next().unwrap();
1958
1959 files.wrap_and_sort(deb822_lossless::Indentation::Spaces(1), false, None);
1960
1961 assert_eq!(vec!["*", "src/*", "src/foo/*", "debian/*"], files.files());
1963
1964 let expected = "Files: * src/* src/foo/* debian/*\nCopyright: 2024 Author\nLicense: MIT\n";
1966 assert_eq!(expected, files.0.to_string());
1967 }
1968
1969 #[test]
1970 fn test_license_paragraph_wrap_and_sort() {
1971 let s = r#"Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
1973
1974Comment: This is a comment
1975License: GPL-3+
1976 GPL license text here.
1977"#;
1978 let copyright = s.parse::<super::Copyright>().expect("failed to parse");
1979 let mut license = copyright.iter_licenses().next().unwrap();
1980
1981 license.wrap_and_sort(deb822_lossless::Indentation::Spaces(1), false, None);
1982
1983 let expected = "License: GPL-3+\n GPL license text here.\nComment: This is a comment\n";
1985 assert_eq!(expected, license.0.to_string());
1986 }
1987
1988 #[test]
1989 fn test_copyright_wrap_and_sort() {
1990 let s = r#"Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
1992Upstream-Name: example
1993
1994Files: debian/*
1995Copyright: 2024 Debian Maintainer
1996License: GPL-3+
1997
1998License: GPL-3+
1999 GPL license text here.
2000
2001Files: src/foo/* src/*
2002Copyright: 2024 Author
2003License: MIT
2004
2005Files: *
2006Copyright: 2024 Main Author
2007License: GPL-3+
2008
2009License: MIT
2010 MIT license text here.
2011"#;
2012 let mut copyright = s.parse::<super::Copyright>().expect("failed to parse");
2013
2014 copyright.wrap_and_sort(deb822_lossless::Indentation::Spaces(1), false, None);
2016
2017 let expected = r#"Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
2019Upstream-Name: example
2020
2021Files: *
2022Copyright: 2024 Main Author
2023License: GPL-3+
2024
2025Files: src/* src/foo/*
2026Copyright: 2024 Author
2027License: MIT
2028
2029Files: debian/*
2030Copyright: 2024 Debian Maintainer
2031License: GPL-3+
2032
2033License: GPL-3+
2034 GPL license text here.
2035
2036License: MIT
2037 MIT license text here.
2038"#;
2039 assert_eq!(expected, copyright.to_string());
2040
2041 let files: Vec<_> = copyright.iter_files().collect();
2043 assert_eq!(3, files.len());
2044 assert_eq!(vec!["*"], files[0].files());
2045 assert_eq!(vec!["src/*", "src/foo/*"], files[1].files());
2046 assert_eq!(vec!["debian/*"], files[2].files());
2047
2048 let licenses: Vec<_> = copyright.iter_licenses().collect();
2049 assert_eq!(2, licenses.len());
2050 }
2051
2052 #[test]
2053 fn test_copyright_wrap_and_sort_file_patterns_within_paragraph() {
2054 let s = r#"Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
2056
2057Files: debian/* src/foo/* * src/*
2058Copyright: 2024 Author
2059License: MIT
2060"#;
2061 let mut copyright = s.parse::<super::Copyright>().expect("failed to parse");
2062
2063 copyright.wrap_and_sort(deb822_lossless::Indentation::Spaces(1), false, None);
2064
2065 let expected = r#"Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
2067
2068Files: * src/* src/foo/* debian/*
2069Copyright: 2024 Author
2070License: MIT
2071"#;
2072 assert_eq!(expected, copyright.to_string());
2073
2074 let files: Vec<_> = copyright.iter_files().collect();
2076 assert_eq!(1, files.len());
2077 assert_eq!(
2078 vec!["*", "src/*", "src/foo/*", "debian/*"],
2079 files[0].files()
2080 );
2081 }
2082
2083 #[test]
2084 fn test_set_license_normalizes_unusual_indentation() {
2085 let s = r#"Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
2088
2089License: Apache-2.0
2090 Apache License
2091 Version 2.0, January 2004
2092 http://www.apache.org/licenses/
2093 .
2094 TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
2095"#;
2096 let copyright = s.parse::<super::Copyright>().expect("failed to parse");
2097 let mut license_para = copyright
2098 .iter_licenses()
2099 .next()
2100 .expect("no license paragraph");
2101
2102 let new_text = "Licensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\nhttp://www.apache.org/licenses/LICENSE-2.0";
2104 let new_license = crate::License::Named("Apache-2.0".to_string(), new_text.to_string());
2105 license_para.set_license(&new_license);
2106
2107 let result = copyright.to_string();
2109
2110 let expected = "Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\n\nLicense: Apache-2.0\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n .\n http://www.apache.org/licenses/LICENSE-2.0\n";
2112
2113 assert_eq!(result, expected);
2114 }
2115}
2116
2117#[derive(Debug, Clone, PartialEq, Eq)]
2121pub struct Parse(deb822_lossless::Parse<Deb822>);
2122
2123impl Parse {
2124 pub fn parse(text: &str) -> Self {
2126 Parse(Deb822::parse(text))
2127 }
2128
2129 pub fn parse_relaxed(text: &str) -> Self {
2131 let deb822_parse = Deb822::parse(text);
2132 Parse(deb822_parse)
2133 }
2134
2135 pub fn errors(&self) -> &[String] {
2137 self.0.errors()
2138 }
2139
2140 pub fn ok(&self) -> bool {
2142 self.0.ok()
2143 }
2144
2145 pub fn to_copyright(&self) -> Copyright {
2147 if let Ok(deb822) = self.0.clone().to_result() {
2148 Copyright(deb822)
2149 } else {
2150 Copyright(Deb822::new())
2152 }
2153 }
2154
2155 pub fn to_result(self) -> Result<Copyright, Error> {
2157 self.0.to_result().map(Copyright).map_err(Error::ParseError)
2158 }
2159}
2160
2161unsafe impl Send for Parse {}
2163unsafe impl Sync for Parse {}