use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::path::Path;
#[cfg(feature = "scan")]
use crate::license_info::LicenseInfo;
#[cfg(feature = "scan")]
use crate::settings;
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "snake_case")]
pub struct License {
pub license: String,
pub text: String,
}
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "snake_case")]
pub struct ThirdPartyLibrary {
pub package_name: String,
pub package_version: String,
pub license: String,
pub licenses: Vec<License>,
}
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "snake_case")]
pub struct ThirdPartyLicenses {
pub root_name: String,
pub third_party_libraries: Vec<ThirdPartyLibrary>,
}
impl ThirdPartyLicenses {
pub fn load(file: &Path) -> Result<Self> {
let str = std::fs::read_to_string(file).with_context(|| {
format!(
"Cannot read third party licenses from file {}",
file.to_str().unwrap_or("empty")
)
})?;
serde_json::from_str::<Self>(str.as_str()).with_context(|| {
format!(
"Cannot parse third party licenses from file {}",
file.to_str().unwrap_or("empty")
)
})
}
#[cfg(feature = "scan")]
pub fn save(&self, file_path: &Path) -> Result<()> {
let parent_dir = file_path.parent().unwrap_or_else(|| {
panic!(
"Cannot get parent directory of third party file {}",
file_path.to_string_lossy().into_owned()
)
});
std::fs::create_dir_all(parent_dir)?;
let str = serde_json::to_string_pretty(self).with_context(|| "Cannot serialize third party licenses")?;
std::fs::write(file_path, str).with_context(|| {
format!(
"Cannot serialize third party licenses to file {}",
file_path.to_str().unwrap_or("empty")
)
})
}
#[cfg(feature = "scan")]
pub fn apply_overrides(&mut self, overrides: &[settings::Override]) -> Result<()> {
for package in &mut self.third_party_libraries {
if let Some(license_override) = settings::Override::find_override(&package.package_name, overrides)
&& let Some(license_id) = &license_override.license_id
{
package.license = license_id.clone();
if license_override.overwrite_all_license_ids {
for package_license in &mut package.licenses {
package_license.license = license_id.clone();
}
}
}
}
Ok(())
}
#[cfg(feature = "scan")]
pub fn export(&self, result_dir: &Path) -> Result<()> {
let base_path = result_dir.join(self.root_name.clone());
for package in &self.third_party_libraries {
let pkg_dir = base_path.join(package.package_name.clone());
std::fs::create_dir_all(pkg_dir.as_path())?;
let pkg_desc_file = pkg_dir.join("README.txt");
let pkg_desc = format!(
"Package: {}\nVersion: {}\nLicense: {}\n",
package.package_name, package.package_version, package.license
);
std::fs::write(pkg_desc_file.as_path(), pkg_desc).with_context(|| {
format!(
"Cannot write package description to file {}",
pkg_desc_file.to_string_lossy()
)
})?;
for license_text in &package.licenses {
let file_name = license_text.license.replace(std::path::is_separator, "-");
let file_name = file_name.replace('$', "-");
let license_text_file = pkg_dir.join(format!("{file_name}.txt"));
std::fs::write(license_text_file.as_path(), license_text.text.clone()).with_context(|| {
format!(
"Cannot write license text to file {}",
license_text_file.to_string_lossy()
)
})?;
}
}
Ok(())
}
#[cfg(feature = "scan")]
pub fn new(root_name: &str, license_infos: &[LicenseInfo]) -> Self {
let mut third_party_libraries = vec![];
for info in license_infos {
let mut licenses = vec![];
for license_text in &info.license_texts {
licenses.push(License {
license: license_text.id.clone(),
text: license_text.text.clone(),
});
}
third_party_libraries.push(ThirdPartyLibrary {
package_name: info.package_name.clone(),
package_version: info.version.clone().unwrap_or_default(),
license: info.license.clone(),
licenses,
});
}
ThirdPartyLicenses {
root_name: root_name.to_owned(),
third_party_libraries,
}
}
#[cfg(feature = "debug")]
pub fn print(&self) {
let mut root_tree = termtree::Tree::new(self.root_name.clone());
for lib in &self.third_party_libraries {
let mut pkg_tree = termtree::Tree::new(format!("Package: {}", lib.package_name.clone()));
pkg_tree.push(termtree::Tree::new(format!("Version: {}", lib.package_version)));
pkg_tree.push(termtree::Tree::new(format!("License ID: {}", lib.license)));
let mut license_tree = termtree::Tree::new("Licenses:".to_owned());
for license in &lib.licenses {
license_tree.push(termtree::Tree::new(format!("ID: {}", license.license)));
}
pkg_tree.push(license_tree);
root_tree.push(pkg_tree);
}
for line in root_tree.to_string().lines() {
log::debug!("{line}")
}
}
}