Skip to content

Commit

Permalink
Refactor a bit (#23)
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelzw authored Dec 13, 2024
1 parent 33bf78a commit 2460124
Show file tree
Hide file tree
Showing 18 changed files with 1,215 additions and 1,434 deletions.
779 changes: 272 additions & 507 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,4 @@ log = "0.4.22"
assert_cmd = "2.0.14"
predicates = "3.1.0"
serial_test = "3.2.0"
rstest = "0.23.0"
79 changes: 79 additions & 0 deletions src/check.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use crate::{fetch_license_infos, license_info::LicenseInfo, CheckOutput, CondaDenyCheckConfig};
use anyhow::{Context, Result};
use colored::Colorize;
use log::debug;
use std::io::Write;

fn check_license_infos(config: &CondaDenyCheckConfig) -> Result<CheckOutput> {
let license_infos = fetch_license_infos(config.lockfile_or_prefix.clone())
.with_context(|| "Fetching license information failed.")?;

if config.osi {
debug!("Checking licenses for OSI compliance");
Ok(license_infos.osi_check())
} else {
debug!("Checking licenses against specified whitelist");
license_infos.check(config)
}
}

pub fn check<W: Write>(check_config: CondaDenyCheckConfig, mut out: W) -> Result<()> {
let (safe_dependencies, unsafe_dependencies) = check_license_infos(&check_config)?;

writeln!(
out,
"{}",
format_check_output(
safe_dependencies,
unsafe_dependencies.clone(),
)
)?;

if !unsafe_dependencies.is_empty() {
Err(anyhow::anyhow!("Unsafe licenses found"))
} else {
Ok(())
}
}

pub fn format_check_output(
safe_dependencies: Vec<LicenseInfo>,
unsafe_dependencies: Vec<LicenseInfo>,
) -> String {
let mut output = String::new();

if !unsafe_dependencies.is_empty() {
output.push_str(
format!(
"\n❌ {}:\n\n",
"The following dependencies are unsafe".red()
)
.as_str(),
);
for license_info in &unsafe_dependencies {
output.push_str(&license_info.pretty_print())
}
}

if unsafe_dependencies.is_empty() {
output.push_str(&format!(
"\n{}",
"✅ No unsafe licenses found! ✅".to_string().green()
));
} else {
output.push_str(&format!(
"\n{}",
"❌ Unsafe licenses found! ❌".to_string().red()
));
}

output.push_str(&format!(
"\nThere were {} safe licenses and {} unsafe licenses.\n",
safe_dependencies.len().to_string().green(),
unsafe_dependencies.len().to_string().red()
));

output.push('\n');

output
}
139 changes: 89 additions & 50 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use std::path::PathBuf;

use clap::ArgAction;
use clap_verbosity_flag::{ErrorLevel, Verbosity};

use clap::Parser;
use rattler_conda_types::Platform;

#[derive(Parser, Debug)]
#[command(name = "conda-deny", about = "Check and list licenses of pixi and conda environments", version = env!("CARGO_PKG_VERSION"))]
Expand All @@ -10,94 +13,130 @@ pub struct Cli {
pub verbose: Verbosity<ErrorLevel>,

#[command(subcommand)]
pub command: Commands,
pub command: CondaDenyCliConfig,

/// Path to the conda-deny config file
#[arg(short, long, global = true)]
pub config: Option<String>,

#[arg(long, global = true)]
pub prefix: Option<Vec<String>>,

#[arg(short, long)]
pub lockfile: Option<Vec<String>>,

#[arg(short, long)]
pub platform: Option<Vec<String>>,

#[arg(short, long)]
pub environment: Option<Vec<String>>,
pub config: Option<PathBuf>,
}

#[derive(clap::Subcommand, Debug)]
pub enum Commands {
pub enum CondaDenyCliConfig {
Check {
#[arg(short, long, action = ArgAction::SetTrue)]
include_safe: bool,
/// Path to the pixi lockfile(s)
#[arg(short, long)]
lockfile: Option<Vec<PathBuf>>,

/// Path to the conda prefix(es)
#[arg(long, global = true)]
prefix: Option<Vec<PathBuf>>,

/// Platform(s) to check
#[arg(short, long)]
platform: Option<Vec<Platform>>,

/// Pixi environment(s) to check
#[arg(short, long)]
environment: Option<Vec<String>>,

/// Check against OSI licenses instead of custom license whitelists.
#[arg(short, long, action = ArgAction::SetTrue)]
osi: bool,
osi: Option<bool>,

/// Ignore when encountering pypi packages instead of failing.
#[arg(long, action = ArgAction::SetTrue)]
ignore_pypi: bool,
ignore_pypi: Option<bool>,
},
List {
/// Path to the pixi lockfile(s)
#[arg(short, long)]
lockfile: Option<Vec<PathBuf>>,

/// Path to the conda prefix(es)
#[arg(long, global = true)]
prefix: Option<Vec<PathBuf>>,

/// Platform(s) to list
#[arg(short, long)]
platform: Option<Vec<Platform>>,

/// Pixi environment(s) to list
#[arg(short, long)]
environment: Option<Vec<String>>,

/// Ignore when encountering pypi packages instead of failing.
#[arg(long)]
ignore_pypi: Option<bool>,
},
List {},
}

#[cfg(test)]
mod tests {
use super::*;
impl CondaDenyCliConfig {
pub fn lockfile(&self) -> Option<Vec<PathBuf>> {
match self {
CondaDenyCliConfig::Check { lockfile, .. } => lockfile.clone(),
CondaDenyCliConfig::List { lockfile, .. } => lockfile.clone(),
}
}

#[test]
fn test_cli_check_include_safe() {
let cli = Cli::try_parse_from(vec!["conda-deny", "check", "--include-safe"]).unwrap();
match cli.command {
Commands::Check { include_safe, .. } => {
assert!(include_safe);
}
_ => panic!("Expected check subcommand with --include-safe"),
pub fn prefix(&self) -> Option<Vec<PathBuf>> {
match self {
CondaDenyCliConfig::Check { prefix, .. } => prefix.clone(),
CondaDenyCliConfig::List { prefix, .. } => prefix.clone(),
}
}

pub fn platform(&self) -> Option<Vec<Platform>> {
match self {
CondaDenyCliConfig::Check { platform, .. } => platform.clone(),
CondaDenyCliConfig::List { platform, .. } => platform.clone(),
}
}

pub fn environment(&self) -> Option<Vec<String>> {
match self {
CondaDenyCliConfig::Check { environment, .. } => environment.clone(),
CondaDenyCliConfig::List { environment, .. } => environment.clone(),
}
}

pub fn ignore_pypi(&self) -> Option<bool> {
match self {
CondaDenyCliConfig::Check { ignore_pypi, .. } => *ignore_pypi,
CondaDenyCliConfig::List { ignore_pypi, .. } => *ignore_pypi,
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_cli_with_config() {
let cli =
Cli::try_parse_from(vec!["conda-deny", "list", "--config", "custom.toml"]).unwrap();
assert_eq!(cli.config.as_deref(), Some("custom.toml"));
assert_eq!(cli.config, Some("custom.toml".into()));
}

#[test]
fn test_cli_with_config_new_order() {
let cli =
Cli::try_parse_from(vec!["conda-deny", "check", "--config", "custom.toml"]).unwrap();
assert_eq!(cli.config.as_deref(), Some("custom.toml"));
assert_eq!(cli.config, Some("custom.toml".into()));
match cli.command {
Commands::Check { include_safe, .. } => {
assert!(!include_safe);
}
CondaDenyCliConfig::Check { .. } => {}
_ => panic!("Expected check subcommand with --config"),
}
}

#[test]
fn test_cli_with_check_arguments() {
let cli = Cli::try_parse_from(vec!["conda-deny", "check", "--include-safe"]).unwrap();
match cli.command {
Commands::Check { include_safe, .. } => {
assert!(include_safe);
}
_ => panic!("Expected check subcommand with --include-safe"),
}
}

#[test]
fn test_cli_with_check_osi() {
let cli = Cli::try_parse_from(vec!["conda-deny", "check", "--osi"]).unwrap();
match cli.command {
Commands::Check { osi, .. } => {
assert!(osi);
CondaDenyCliConfig::Check { osi, .. } => {
assert_eq!(osi, Some(true));
}
_ => panic!("Expected check subcommand with --osi"),
_ => panic!("Expected check subcommand with --include-safe"),
}
}
}
Loading

0 comments on commit 2460124

Please sign in to comment.