debian-copyright-0.1.2/Cargo.toml0000644000000016230000000000100122640ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "debian-copyright" version = "0.1.2" authors = ["Jelmer Vernooij "] description = "A parser for Debian copyright files" homepage = "https://github.com/jelmer/debian-control-rs" license = "Apache-2.0" repository = "https://github.com/jelmer/debian-control-rs" [dependencies.deb822-lossless] version = "0.1.2" [dependencies.debversion] version = "0.2.2" [dependencies.regex] version = "1.10.2" debian-copyright-0.1.2/Cargo.toml.orig000064400000000000000000000006431046102023000157460ustar 00000000000000[package] name = "debian-copyright" authors = ["Jelmer Vernooij "] version = "0.1.2" edition = "2021" license = "Apache-2.0" description = "A parser for Debian copyright files" repository = "https://github.com/jelmer/debian-control-rs" homepage = "https://github.com/jelmer/debian-control-rs" [dependencies] debversion = "0.2.2" deb822-lossless = { path = "..", version = "0.1.2" } regex = "1.10.2" debian-copyright-0.1.2/src/lib.rs000064400000000000000000000100541046102023000147570ustar 00000000000000use deb822_lossless::{Deb822, Paragraph}; use std::path::Path; pub const CURRENT_FORMAT: &str = "https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/"; pub const KNOWN_FORMATS: &[&str] = &[CURRENT_FORMAT]; pub struct Copyright(Deb822); impl Copyright { pub fn new() -> Self { Copyright(Deb822::new()) } pub fn header(&self) -> Option
{ self.0.paragraphs().next().map(Header) } pub fn iter_files(&self) -> impl Iterator { self.0 .paragraphs() .filter(|x| x.contains_key("Files")) .map(FilesParagraph) } pub fn iter_licenses(&self) -> impl Iterator { self.0 .paragraphs() .filter(|x| !x.contains_key("Files") && x.contains_key("License")) .map(LicenseParagraph) } /// Returns the Files paragraph for the given filename. /// /// Consistent with the specification, this returns the last paragraph /// that matches (which should be the most specific) pub fn find_files(&self, filename: &Path) -> Option { self.iter_files().filter(|p| p.matches(filename)).last() } } impl Default for Copyright { fn default() -> Self { Copyright(Deb822::new()) } } pub struct Header(Paragraph); impl Header { pub fn format_string(&self) -> Option { self.0 .get("Format") .or_else(|| self.0.get("Format-Specification")) } pub fn upstream_name(&self) -> Option { self.0.get("Upstream-Name") } pub fn upstream_contact(&self) -> Option { self.0.get("Upstream-Contact") } pub fn source(&self) -> Option { self.0.get("Source") } pub fn files_excluded(&self) -> Option> { self.0 .get("Files-Excluded") .map(|x| x.split('\n').map(|x| x.to_string()).collect::>()) } pub fn fix(&mut self) { if self.0.contains_key("Format-Specification") { self.0.rename("Format-Specification", "Format"); } if let Some(mut format) = self.0.get("Format") { if !format.ends_with('/') { format.push('/'); } if let Some(rest) = format.strip_prefix("http:") { format = format!("https:{}", rest); } if KNOWN_FORMATS.contains(&format.as_str()) { format = CURRENT_FORMAT.to_string(); } self.0.insert("Format", format.as_str()); } } } pub struct FilesParagraph(Paragraph); impl FilesParagraph { pub fn files(&self) -> Vec { self.0 .get("Files") .unwrap() .split_whitespace() .map(|v| v.to_string()) .collect::>() } pub fn matches(&self, filename: &std::path::Path) -> bool { self.files() .iter() .any(|f| glob_to_regex(f).is_match(filename.to_str().unwrap())) } pub fn copyright(&self) -> Vec { self.0 .get("Copyright") .unwrap_or_default() .split('\n') .map(|x| x.to_string()) .collect::>() } pub fn comment(&self) -> Option { self.0.get("Comment") } } pub struct LicenseParagraph(Paragraph); fn glob_to_regex(glob: &str) -> regex::Regex { let mut it = glob.chars(); let mut r = String::new(); while let Some(c) = it.next() { r.push_str( match c { '*' => ".*".to_string(), '?' => ".".to_string(), '\\' => match it.next().unwrap() { '?' | '*' | '\\' => regex::escape(c.to_string().as_str()), x => { panic!("invalid escape sequence: \\{}", x); } }, c => regex::escape(c.to_string().as_str()), } .as_str(), ) } regex::Regex::new(r.as_str()).unwrap() }