use super::env_vars::EnvVarNames;
use super::run_utils::get_version_info;
use clap::Parser;
use std::path::PathBuf;
use std::{fs, io};
#[derive(Parser, Default)]
#[command(
version,
about,
long_about = None,
long_version = get_version_info()
)]
pub struct CliArgs {
#[arg(value_name = "FILE_OR_FOLDER_TO_PROCESS", env= EnvVarNames::WorkingPath.as_str())]
pub input: Option<PathBuf>,
#[arg(short='m', long="show-novulns-msg", env= EnvVarNames::NoVulnsMsg.as_str())]
pub show_novulns_msg: Option<bool>,
#[arg(short='t', long="report-title", value_name = "REPORT_TITLE", env= EnvVarNames::ReportTitle.as_str())]
pub report_title: Option<String>,
#[arg(short='n', long="pdf-meta-name", value_name = "PDF_META_TITLE", env=EnvVarNames::PdfName.as_str())]
pub meta_name: Option<String>,
#[arg(short='b', long="bom-novulns", env=EnvVarNames::PureBomNoVulns.as_str())]
pub pure_bom_novulns: Option<bool>,
#[arg(short='c', long, env=EnvVarNames::ShowComponentList.as_str())]
pub show_components: Option<bool>,
#[arg(short='d', long="output-dir", env=EnvVarNames::OutputDir.as_str())]
pub output_dir: Option<PathBuf>,
#[cfg(feature = "concurrency")]
#[arg(short='j', long, env=EnvVarNames::MaxJobs.as_str())]
pub max_jobs: Option<u8>,
#[arg(short = 'L', long)]
pub license: bool,
}
impl CliArgs {
pub fn validate(&self) -> Result<(), io::Error> {
if let Some(path) = self.output_dir.as_ref() {
if !path.is_dir() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("Expected a directory got {}", path.display()),
));
} else {
let tmp_file = path.join("vex2pdf_perm_test_file");
let res_io = fs::File::create(&tmp_file);
if res_io.is_err() {
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
"Could not create a test file. possible permissions issue",
));
} else if res_io.is_ok() && fs::remove_file(tmp_file).is_err() {
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
"unable to delete permissions test file",
));
}
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_validate_no_output_dir() {
let args = CliArgs::default();
assert!(args.validate().is_ok());
}
#[test]
fn test_validate_valid_directory() {
let temp_dir = TempDir::new().unwrap();
let mut args = CliArgs::default();
args.output_dir = Some(temp_dir.path().to_path_buf());
assert!(args.validate().is_ok());
}
#[test]
fn test_validate_path_is_file_not_dir() {
let temp_dir = TempDir::new().unwrap();
let file = temp_dir.path().join("test.json");
fs::write(&file, r#"{"test": "data"}"#).unwrap();
let mut args = CliArgs::default();
args.output_dir = Some(file);
let err = args.validate().unwrap_err();
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
}
#[test]
fn test_validate_nonexistent_directory() {
let args = CliArgs {
output_dir: Some(PathBuf::from("/nonexistent/path/that/does/not/exist")),
..CliArgs::default()
};
let err = args.validate().unwrap_err();
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
}
#[test]
fn test_validate_readonly_directory() {
let temp_dir = TempDir::new().unwrap();
let readonly_dir = temp_dir.path().join("readonly");
fs::create_dir(&readonly_dir).unwrap();
let mut perms = fs::metadata(&readonly_dir).unwrap().permissions();
perms.set_readonly(true);
fs::set_permissions(&readonly_dir, perms).unwrap();
let args = CliArgs {
output_dir: Some(readonly_dir.clone()),
..CliArgs::default()
};
let err = args.validate().unwrap_err();
assert_eq!(err.kind(), io::ErrorKind::PermissionDenied);
let mut perms = fs::metadata(&readonly_dir).unwrap().permissions();
perms.set_readonly(false);
fs::set_permissions(&readonly_dir, perms).unwrap();
}
#[test]
fn test_validate_can_create_and_delete_test_file() {
let temp_dir = TempDir::new().unwrap();
let args = CliArgs {
output_dir: Some(temp_dir.path().to_path_buf()),
..CliArgs::default()
};
assert!(args.validate().is_ok());
assert!(!temp_dir.path().join("vex2pdf_perm_test_file").exists());
}
}