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