use crate::{Config, Global, SolarError, ToolTrait, sorted};
use clap::Parser;
use derive_getters::Getters;
use reqwest::blocking::Client;
use serde::{Deserialize, Serialize};
use std::{
fs::{self, File},
io::Write,
path::{Path, PathBuf},
};
pub static LICENSES_DIR: &str = "LICENSES";
#[derive(Parser, Clone, Default, PartialEq, Debug, Serialize, Deserialize, Getters)]
pub struct Licenses {
#[arg(short, long, default_value = ".")]
#[serde(skip)]
destination: PathBuf,
#[arg(short, long, num_args = 0..)]
include_licenses: Option<Vec<String>>,
#[arg(short, long, num_args = 0..)]
licensed_under: Option<Vec<String>>,
}
impl Licenses {
pub fn new(
destination: PathBuf,
include_licenses: Option<Vec<String>>,
licensed_under: Option<Vec<String>>,
) -> Self {
Self {
destination,
include_licenses,
licensed_under,
}
}
fn check_vec_eq_unord<T>(vec_one: &[T], vec_two: &[T]) -> bool
where
T: Ord + Clone,
{
sorted(vec_one.to_owned()) == sorted(vec_two.to_owned())
}
fn check_opt_vec_eq_unord<T>(opt_one: &Option<Vec<T>>, opt_two: &Option<Vec<T>>) -> bool
where
T: Ord + Clone,
{
if let Some(vec_one) = opt_one
&& let Some(vec_two) = opt_two
{
return Self::check_vec_eq_unord(vec_one, vec_two);
}
opt_one == opt_two
}
fn update_config_after_uninstall(&self, current: &Self) -> Option<Self> {
let mut new_includes: Vec<String> = Vec::new();
if let Some(current_includes) = current.include_licenses()
&& let Some(removed_includes) = &self.include_licenses
{
for include in current_includes {
if !removed_includes.contains(include) {
new_includes.push(include.clone())
}
}
}
let mut new_under: Vec<String> = Vec::new();
if let Some(current_under) = current.licensed_under()
&& let Some(removed_under) = &self.licensed_under
{
for under in current_under {
if !removed_under.contains(under) {
new_under.push(under.clone())
}
}
}
if new_includes.is_empty() && new_under.is_empty() {
return None;
}
Some(Self::new(
self.destination.clone(),
Some(new_includes),
Some(new_under),
))
}
fn get_license(client: &Client, spdx: &str) -> Result<String, SolarError> {
let response = client.get(Global::licenses_url(spdx)?).send()?;
Ok(response.text()?)
}
}
impl ToolTrait for Licenses {
fn set_dest(&mut self, dest: &Path) {
self.destination = dest.to_path_buf();
}
fn install(&mut self) -> Result<(), SolarError> {
if self.include_licenses.is_none() && self.licensed_under.is_none() {
return Err(SolarError::from(
"No spdx identifiers supplied as arguments.",
));
}
let client = Client::new();
let licenses_dir = self.destination.join(PathBuf::from(LICENSES_DIR));
let config = Config::load_or_default(&self.destination);
fs::create_dir_all(&licenses_dir)?;
if let Some(cmd_include_licenses) = &mut self.include_licenses {
if let Some(current_config) = config.licenses()
&& let Some(cfg_include_licenses) = current_config.include_licenses()
{
cmd_include_licenses.extend(cfg_include_licenses.clone());
}
for spdx in cmd_include_licenses.iter() {
let license_path = licenses_dir.join(PathBuf::from(format!("LICENSE-{}", spdx)));
if !fs::exists(&license_path)? {
let mut license_file = File::create(license_path)?;
let license_text = Self::get_license(&client, spdx)?;
license_file.write_all(license_text.as_bytes())?;
}
}
}
if let Some(cmd_licensed_under) = &mut self.licensed_under {
if let Some(current_config) = config.licenses()
&& let Some(cfg_licensed_under) = current_config.licensed_under()
{
cmd_licensed_under.extend(cfg_licensed_under.clone());
}
for spdx in cmd_licensed_under.iter() {
let license_path = self
.destination
.join(PathBuf::from(format!("LICENSE-{}", spdx)));
if !fs::exists(&license_path)? {
let mut license_file = File::create(license_path)?;
let license_text = Self::get_license(&client, spdx)?;
license_file.write_all(license_text.as_bytes())?;
}
}
}
config.set_licenses(Some(self.clone())).save()?;
Ok(())
}
fn uninstall(&mut self) -> Result<(), SolarError> {
let config = Config::load_from(&self.destination)?;
let current_config = config
.licenses()
.as_ref()
.ok_or("No licenses configuration inside config file.")?;
if self.include_licenses.is_none() && self.licensed_under.is_none() {
self.include_licenses = current_config.include_licenses().clone();
self.licensed_under = current_config.licensed_under().clone();
}
let licenses_dir = self.destination.join(PathBuf::from(LICENSES_DIR));
if fs::exists(&licenses_dir)? {
if Self::check_opt_vec_eq_unord(
current_config.include_licenses(),
&self.include_licenses,
) {
fs::remove_dir_all(licenses_dir)?;
} else {
if let Some(includes) = &self.include_licenses {
for spdx in includes.iter() {
let file_path = licenses_dir.join(format!("LICENSE-{}", spdx));
if fs::exists(&file_path)? {
fs::remove_file(file_path)?;
}
}
}
}
}
if let Some(proj_licenses) = &self.licensed_under {
for spdx in proj_licenses.iter() {
let file_path = self.destination.join(format!("LICENSE-{}", spdx));
if fs::exists(&file_path)? {
fs::remove_file(file_path)?;
}
}
}
let new_licenses_config = self.update_config_after_uninstall(current_config);
let config = config.set_licenses(new_licenses_config);
match config.is_empty() {
true => fs::remove_file(config.path())?,
false => config.save()?,
}
Ok(())
}
}