Skip to content

Commit 7cdb143

Browse files
authored
--prefix list (#11)
* Add --osi to error message * Improve readme badges and fix list command for --prefix * Fix --prefix for list command behavior * Fix pre-commits * Bump version
1 parent b0154b1 commit 7cdb143

File tree

11 files changed

+7573
-97
lines changed

11 files changed

+7573
-97
lines changed

Cargo.lock

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "conda-deny"
33
description = "A CLI tool to check your project's dependencies for license compliance."
4-
version = "0.3.0"
4+
version = "0.3.1"
55
edition = "2021"
66

77
[features]

README.md

+12-4
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,24 @@
88

99
[![License][license-badge]](LICENSE)
1010
[![CI Status][ci-badge]][ci]
11-
![Binary Build](https://github.com/quantco/conda-deny/actions/workflows/build.yml/badge.svg)
11+
[![Binary Build][binary-build-badge]][binary-build]
1212
[![Conda Platform][conda-badge]][conda-url]
13-
[![codecov](https://codecov.io/gh/Quantco/conda-deny/graph/badge.svg)](https://codecov.io/gh/Quantco/conda-deny)
13+
[![Codecov][codecov]][codecov-url]
1414

1515
[license-badge]: https://img.shields.io/github/license/quantco/conda-deny?style=flat-square
16-
[ci-badge]: https://img.shields.io/github/actions/workflow/status/quantco/conda-deny/ci.yml?style=flat-square&branch=main
17-
[ci]: https://github.com/quantco/conda-deny/actions/
16+
17+
[ci-badge]: https://img.shields.io/github/actions/workflow/status/quantco/conda-deny/ci.yml?branch=main&style=flat-square&label=CI
18+
[ci]: https://github.com/quantco/conda-deny/actions/workflows/ci.yml
19+
20+
[binary-build-badge]: https://img.shields.io/github/actions/workflow/status/quantco/conda-deny/build.yml?branch=main&style=flat-square&label=Binary%20Build
21+
[binary-build]: https://github.com/quantco/conda-deny/actions/workflows/build.yml
22+
1823
[conda-badge]: https://img.shields.io/conda/vn/conda-forge/conda-deny?style=flat-square
1924
[conda-url]: https://prefix.dev/channels/conda-forge/packages/conda-deny
2025

26+
[codecov]: https://img.shields.io/codecov/c/github/quantco/conda-deny/main?style=flat-square
27+
[codecov-url]: https://codecov.io/gh/Quantco/conda-deny
28+
2129
</div>
2230

2331
## 🗂 Table of Contents

src/cli.rs

+9-9
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,15 @@ pub struct Cli {
1717

1818
#[arg(long, global = true)]
1919
pub prefix: Option<Vec<String>>,
20+
21+
#[arg(short, long)]
22+
pub lockfile: Option<Vec<String>>,
23+
24+
#[arg(short, long)]
25+
pub platform: Option<Vec<String>>,
26+
27+
#[arg(short, long)]
28+
pub environment: Option<Vec<String>>,
2029
}
2130

2231
#[derive(clap::Subcommand, Debug)]
@@ -27,15 +36,6 @@ pub enum Commands {
2736

2837
#[arg(short, long, action = ArgAction::SetTrue)]
2938
osi: bool,
30-
31-
#[arg(short, long)]
32-
lockfile: Option<Vec<String>>,
33-
34-
#[arg(short, long)]
35-
platform: Option<Vec<String>>,
36-
37-
#[arg(short, long)]
38-
environment: Option<Vec<String>>,
3939
},
4040
List {},
4141
}

src/lib.rs

+34-45
Original file line numberDiff line numberDiff line change
@@ -19,66 +19,55 @@ use log::debug;
1919
use crate::conda_deny_config::CondaDenyConfig;
2020
use crate::license_info::LicenseInfos;
2121

22-
pub type CheckInput<'a> = (
22+
pub type CliInput<'a> = (
2323
&'a CondaDenyConfig,
24-
Vec<String>,
25-
Vec<String>,
26-
Vec<String>,
27-
Vec<String>,
24+
&'a Vec<String>,
25+
&'a Vec<String>,
26+
&'a Vec<String>,
27+
&'a Vec<String>,
2828
bool,
2929
);
3030
pub type CheckOutput = (Vec<LicenseInfo>, Vec<LicenseInfo>);
3131

32-
pub fn list(conda_deny_config: &CondaDenyConfig) -> Result<()> {
33-
let mut license_infos =
34-
LicenseInfos::get_license_infos_from_config(conda_deny_config, vec![], vec![], vec![])
35-
.with_context(|| "Getting license information from config file failed.")?;
36-
37-
license_infos.sort();
38-
license_infos.dedup();
39-
40-
license_infos.list();
41-
Ok(())
42-
}
43-
44-
pub fn check_license_infos(check_input: CheckInput) -> Result<CheckOutput> {
45-
let (conda_deny_config, cli_lockfiles, cli_platforms, cli_environment, conda_prefixes, osi) =
46-
check_input;
32+
pub fn fetch_license_infos(cli_input: CliInput) -> Result<LicenseInfos> {
33+
let (conda_deny_config, cli_lockfiles, cli_platforms, cli_environments, conda_prefixes, _) =
34+
cli_input;
4735

4836
if conda_prefixes.is_empty() {
49-
let mut license_infos = LicenseInfos::get_license_infos_from_config(
37+
LicenseInfos::get_license_infos_from_config(
5038
conda_deny_config,
5139
cli_lockfiles,
5240
cli_platforms,
53-
cli_environment,
41+
cli_environments,
5442
)
55-
.with_context(|| "Getting license information from config file failed.")?;
43+
.with_context(|| "Getting license information from config file failed.")
44+
} else {
45+
LicenseInfos::from_conda_prefixes(conda_prefixes)
46+
.with_context(|| "Getting license information from conda prefixes failed.")
47+
}
48+
}
5649

57-
license_infos.sort();
58-
license_infos.dedup();
50+
pub fn list(cli_input: CliInput) -> Result<()> {
51+
let license_infos =
52+
fetch_license_infos(cli_input).with_context(|| "Fetching license information failed.")?;
53+
license_infos.list();
54+
Ok(())
55+
}
5956

60-
if osi {
61-
debug!("Checking licenses for OSI compliance");
62-
Ok(license_infos.osi_check())
63-
} else {
64-
let license_whitelist = build_license_whitelist(conda_deny_config)
65-
.with_context(|| "Building the license whitelist failed.")?;
66-
debug!("Checking licenses against specified whitelist");
67-
Ok(license_infos.check(&license_whitelist))
68-
}
57+
pub fn check_license_infos(cli_input: CliInput) -> Result<CheckOutput> {
58+
let (conda_deny_config, _, _, _, _, osi) = cli_input;
59+
60+
let license_infos =
61+
fetch_license_infos(cli_input).with_context(|| "Fetching license information failed.")?;
62+
63+
if osi {
64+
debug!("Checking licenses for OSI compliance");
65+
Ok(license_infos.osi_check())
6966
} else {
70-
let mut conda_prefixes_license_infos = LicenseInfos::from_conda_prefixes(&conda_prefixes)?;
71-
conda_prefixes_license_infos.sort();
72-
conda_prefixes_license_infos.dedup();
73-
if osi {
74-
debug!("Checking for OSI licenses");
75-
Ok(conda_prefixes_license_infos.osi_check())
76-
} else {
77-
let license_whitelist = build_license_whitelist(conda_deny_config)
67+
let license_whitelist = build_license_whitelist(conda_deny_config)
7868
.with_context(|| "Building the license whitelist failed.")?;
79-
debug!("Checking licenses against specified whitelist");
80-
Ok(conda_prefixes_license_infos.check(&license_whitelist))
81-
}
69+
debug!("Checking licenses against specified whitelist");
70+
Ok(license_infos.check(&license_whitelist))
8271
}
8372
}
8473

src/license_info.rs

+15-10
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ impl LicenseInfos {
153153
environment_specs.clone(),
154154
platforms.clone(),
155155
)
156-
.with_context(|| "Failed to ge package records for pixi.lock")?;
156+
.with_context(|| "Failed to get package records for pixi.lock")?;
157157

158158
package_records.extend(package_records_for_lockfile);
159159
} else {
@@ -176,6 +176,9 @@ impl LicenseInfos {
176176
license_infos.push(license_info);
177177
}
178178

179+
license_infos.sort();
180+
license_infos.dedup();
181+
179182
Ok(LicenseInfos { license_infos })
180183
}
181184

@@ -197,6 +200,9 @@ impl LicenseInfos {
197200
license_infos.extend(license_infos_for_meta.license_infos);
198201
}
199202

203+
license_infos.sort();
204+
license_infos.dedup();
205+
200206
Ok(LicenseInfos { license_infos })
201207
}
202208

@@ -211,17 +217,17 @@ impl LicenseInfos {
211217

212218
pub fn get_license_infos_from_config(
213219
config: &CondaDenyConfig,
214-
cli_lockfiles: Vec<String>,
215-
cli_platforms: Vec<String>,
216-
cli_environments: Vec<String>,
220+
cli_lockfiles: &[String],
221+
cli_platforms: &[String],
222+
cli_environments: &[String],
217223
) -> Result<LicenseInfos> {
218224
let mut platforms = config.get_platform_spec().map_or(vec![], |p| p);
219225
let mut lockfiles = config.get_lockfile_spec();
220226
let mut environment_specs = config.get_environment_spec().map_or(vec![], |e| e);
221227

222-
platforms.extend(cli_platforms);
223-
lockfiles.extend(cli_lockfiles);
224-
environment_specs.extend(cli_environments);
228+
platforms.extend(cli_platforms.to_owned());
229+
lockfiles.extend(cli_lockfiles.to_owned());
230+
environment_specs.extend(cli_environments.to_owned());
225231

226232
LicenseInfos::from_pixi_lockfiles(lockfiles, platforms, environment_specs)
227233
}
@@ -463,8 +469,7 @@ mod tests {
463469
"tests/test_pyproject_toml_files/"
464470
);
465471
let config = CondaDenyConfig::from_path(&test_file_path).expect("Failed to read config");
466-
let license_infos =
467-
LicenseInfos::get_license_infos_from_config(&config, vec![], vec![], vec![]);
468-
assert_eq!(license_infos.unwrap().license_infos.len(), 534);
472+
let license_infos = LicenseInfos::get_license_infos_from_config(&config, &[], &[], &[]);
473+
assert_eq!(license_infos.unwrap().license_infos.len(), 396);
469474
}
470475
}

src/license_whitelist.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ pub fn build_license_whitelist(
247247
}
248248

249249
if final_license_whitelist.safe_licenses.is_empty() {
250-
anyhow::bail!("Your license whitelist is empty.");
250+
anyhow::bail!("Your license whitelist is empty.\nIf you want to use the OSI license whitelist, use the --osi flag.");
251251
} else {
252252
debug!("License whitelist built successfully.");
253253
}

src/main.rs

+24-26
Original file line numberDiff line numberDiff line change
@@ -48,42 +48,39 @@ fn main() -> Result<()> {
4848
}
4949

5050
let conda_prefixes = cli.prefix.unwrap_or_default();
51+
let cli_lockfiles = cli.lockfile.unwrap_or_default();
52+
let cli_platforms = cli.platform.unwrap_or_default();
53+
let cli_environments = cli.environment.unwrap_or_default();
54+
55+
let cli_input = (
56+
&config,
57+
&cli_lockfiles,
58+
&cli_platforms,
59+
&cli_environments,
60+
&conda_prefixes,
61+
osi,
62+
);
63+
64+
debug!("CLI input for platforms: {:?}", cli_platforms);
65+
debug!("CLI input for environments: {:?}", cli_environments);
66+
debug!("CLI input for conda prefixes: {:?}", conda_prefixes);
67+
let mut locks_to_check = cli_lockfiles.clone();
68+
locks_to_check.push("pixi.lock".to_string());
69+
debug!("CLI input for pixi lockfiles: {:?}", locks_to_check);
70+
debug!("CLI input for OSI compliance: {}", osi);
5171

5272
match cli.command {
5373
Commands::Check {
5474
include_safe,
55-
osi,
56-
lockfile,
57-
platform,
58-
environment,
75+
osi: _,
5976
} => {
60-
let cli_lockfiles = lockfile.unwrap_or_default();
61-
let cli_platforms = platform.unwrap_or_default();
62-
let cli_environment = environment.unwrap_or_default();
63-
6477
debug!("Check command called.");
65-
debug!("Checking platforms: {:?}", cli_platforms);
66-
debug!("Checking environments: {:?}", cli_environment);
67-
debug!("Checking conda prefixes: {:?}", conda_prefixes);
68-
let mut locks_to_check = cli_lockfiles.clone();
69-
locks_to_check.push("pixi.lock".to_string());
70-
debug!("Checking pixi lockfiles: {:?}", locks_to_check);
71-
debug!("Checking for OSI compliance: {}", osi);
7278

7379
if include_safe {
7480
debug!("Including safe dependencies in output");
7581
}
7682

77-
let check_input = (
78-
&config,
79-
cli_lockfiles,
80-
cli_platforms,
81-
cli_environment,
82-
conda_prefixes,
83-
osi,
84-
);
85-
86-
let check_output = check_license_infos(check_input)?;
83+
let check_output = check_license_infos(cli_input)?;
8784

8885
let (safe_dependencies, unsafe_dependencies) = check_output;
8986

@@ -99,7 +96,8 @@ fn main() -> Result<()> {
9996
}
10097
Commands::List {} => {
10198
debug!("List command called");
102-
list(&config)
99+
list(cli_input)?;
100+
Ok(())
103101
}
104102
}
105103
}

tests/integration_tests.rs

+35
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#[cfg(test)]
22
mod tests {
33
use assert_cmd::prelude::*;
4+
use core::str;
45
use std::path::Path;
56
use std::process::Command;
67

@@ -93,4 +94,38 @@ mod tests {
9394
command.arg("check --osi").current_dir(test_dir);
9495
command.assert().failure();
9596
}
97+
98+
#[test]
99+
fn test_prefix_list() {
100+
// When --prefix is specified, only the license information for the conda-meta directory in the specified prefix should be listed
101+
// License information from pixi.lock should not be listed
102+
103+
let test_dir = Path::new("tests/test_end_to_end/test_prefix_list");
104+
105+
let output = Command::cargo_bin("conda-deny")
106+
.unwrap()
107+
.arg("list")
108+
.arg("--prefix")
109+
.arg("../../../tests/test_conda_prefixes/test-env")
110+
.current_dir(test_dir)
111+
.output()
112+
.expect("Failed to execute command");
113+
114+
assert!(
115+
output.status.success(),
116+
"Command did not execute successfully"
117+
);
118+
119+
let stdout = str::from_utf8(&output.stdout).expect("Failed to convert output to string");
120+
121+
let line_count = stdout.lines().count();
122+
123+
let expected_line_count = 50;
124+
assert_eq!(
125+
line_count, expected_line_count,
126+
"Unexpected number of output lines"
127+
);
128+
129+
println!("Output has {} lines", line_count);
130+
}
96131
}

0 commit comments

Comments
 (0)