use crate::app::{self, CheckInput, OutputFormat};
use crate::cli::help;
use crate::cli::parse::{expect_value, parse_format, parse_mode};
use std::path::PathBuf;
pub(super) fn check(args: &[String]) -> Result<(), String> {
let mut input = CheckInput::default();
let mut i = 0usize;
while i < args.len() {
match args[i].as_str() {
"--root" => {
i += 1;
input.root = PathBuf::from(expect_value(args, i, "--root")?);
}
"--base" => {
i += 1;
input.base = Some(expect_value(args, i, "--base")?.to_string());
}
"--diff" => {
i += 1;
input.diff_file = Some(PathBuf::from(expect_value(args, i, "--diff")?));
}
"--mode" => {
i += 1;
input.mode = parse_mode(expect_value(args, i, "--mode")?)?;
}
"--json" => input.format = OutputFormat::Json,
"--format" => {
i += 1;
input.format = parse_format(expect_value(args, i, "--format")?)?;
}
"--no-unchanged-tests" => input.include_unchanged_tests = false,
"--help" | "-h" => {
help::print_check_help();
return Ok(());
}
other => return Err(format!("unknown check argument {other:?}")),
}
i += 1;
}
let format = input.format.clone();
let output = app::check_workspace(input)?;
print!("{}", app::render_check(&output, &format));
Ok(())
}
pub(super) fn explain(args: &[String]) -> Result<(), String> {
let mut input = CheckInput::default();
let mut selector: Option<String> = None;
let mut i = 0usize;
while i < args.len() {
match args[i].as_str() {
"--root" => {
i += 1;
input.root = PathBuf::from(expect_value(args, i, "--root")?);
}
"--base" => {
i += 1;
input.base = Some(expect_value(args, i, "--base")?.to_string());
}
"--diff" => {
i += 1;
input.diff_file = Some(PathBuf::from(expect_value(args, i, "--diff")?));
}
"--help" | "-h" => {
help::print_explain_help();
return Ok(());
}
value if selector.is_none() => selector = Some(value.to_string()),
other => return Err(format!("unexpected explain argument {other:?}")),
}
i += 1;
}
let selector = selector.ok_or_else(|| "missing finding selector".to_string())?;
println!("{}", app::explain_finding_with_input(input, &selector)?);
Ok(())
}
pub(super) fn context(args: &[String]) -> Result<(), String> {
let mut input = CheckInput {
format: OutputFormat::Json,
..CheckInput::default()
};
let mut selector: Option<String> = None;
let mut max_tests = 5usize;
let mut i = 0usize;
while i < args.len() {
match args[i].as_str() {
"--root" => {
i += 1;
input.root = PathBuf::from(expect_value(args, i, "--root")?);
}
"--base" => {
i += 1;
input.base = Some(expect_value(args, i, "--base")?.to_string());
}
"--diff" => {
i += 1;
input.diff_file = Some(PathBuf::from(expect_value(args, i, "--diff")?));
}
"--at" => {
i += 1;
selector = Some(expect_value(args, i, "--at")?.to_string());
}
"--finding" => {
i += 1;
selector = Some(expect_value(args, i, "--finding")?.to_string());
}
"--max-related-tests" => {
i += 1;
max_tests = expect_value(args, i, "--max-related-tests")?
.parse::<usize>()
.map_err(|err| format!("invalid --max-related-tests: {err}"))?;
}
"--json" => input.format = OutputFormat::Json,
"--help" | "-h" => {
help::print_context_help();
return Ok(());
}
other => return Err(format!("unexpected context argument {other:?}")),
}
i += 1;
}
let selector = selector.ok_or_else(|| "missing --at or --finding selector".to_string())?;
println!(
"{}",
app::collect_context_with_input(input, &selector, max_tests)?
);
Ok(())
}
pub(super) fn doctor(args: &[String]) -> Result<(), String> {
let root = match args {
[] => PathBuf::from("."),
[flag] if flag == "--help" || flag == "-h" => {
help::print_doctor_help();
return Ok(());
}
[flag] if flag == "--root" => return Err("missing value for --root".to_string()),
[flag, value] if flag == "--root" => PathBuf::from(value),
[other, ..] => return Err(format!("unknown doctor argument {other:?}")),
};
let mut ok = true;
println!("ripr doctor");
if root.join("Cargo.toml").exists() {
println!(
"✓ Cargo.toml found at {}",
root.join("Cargo.toml").display()
);
} else {
println!("! no Cargo.toml found at {}", root.display());
ok = false;
}
match std::process::Command::new("git").arg("--version").output() {
Ok(output) if output.status.success() => {
println!("✓ {}", String::from_utf8_lossy(&output.stdout).trim())
}
_ => {
println!("! git not available");
ok = false;
}
}
match std::process::Command::new("cargo")
.arg("--version")
.output()
{
Ok(output) if output.status.success() => {
println!("✓ {}", String::from_utf8_lossy(&output.stdout).trim())
}
_ => {
println!("! cargo not available");
ok = false;
}
}
if ok {
Ok(())
} else {
Err("doctor found issues".to_string())
}
}
pub(super) fn lsp(args: &[String]) -> Result<(), String> {
for arg in args {
match arg.as_str() {
"--stdio" => {}
"--version" | "-V" => {
println!("ripr-lsp {}", env!("CARGO_PKG_VERSION"));
return Ok(());
}
"--help" | "-h" => {
help::print_lsp_help();
return Ok(());
}
other => return Err(format!("unknown lsp argument {other:?}")),
}
}
crate::lsp::serve()
}
#[cfg(test)]
mod tests {
use super::*;
fn args(values: &[&str]) -> Vec<String> {
values.iter().map(|value| value.to_string()).collect()
}
#[test]
fn check_requires_values_for_value_flags() {
assert_eq!(
check(&args(&["--diff"])),
Err("missing value for --diff".to_string())
);
assert_eq!(
check(&args(&["--mode"])),
Err("missing value for --mode".to_string())
);
}
#[test]
fn command_help_branches_return_ok() {
assert_eq!(check(&args(&["--help"])), Ok(()));
assert_eq!(explain(&args(&["--help"])), Ok(()));
assert_eq!(context(&args(&["--help"])), Ok(()));
assert_eq!(doctor(&args(&["--help"])), Ok(()));
assert_eq!(lsp(&args(&["--help"])), Ok(()));
}
#[test]
fn context_rejects_invalid_max_related_tests() {
let result = context(&args(&[
"--at",
"probe:file.rs:1:predicate",
"--max-related-tests",
"many",
]));
assert!(
matches!(result, Err(message) if message.starts_with("invalid --max-related-tests:"))
);
}
#[test]
fn doctor_requires_root_value() {
assert_eq!(
doctor(&args(&["--root"])),
Err("missing value for --root".to_string())
);
}
#[test]
fn doctor_rejects_unknown_arguments() {
assert_eq!(
doctor(&args(&["--verbose"])),
Err("unknown doctor argument \"--verbose\"".to_string())
);
}
#[test]
fn doctor_accepts_default_root() {
assert_eq!(doctor(&args(&[])), Ok(()));
}
#[test]
fn lsp_version_returns_ok() {
assert_eq!(lsp(&args(&["--version"])), Ok(()));
}
#[test]
fn lsp_rejects_unknown_arguments() {
assert_eq!(
lsp(&args(&["--bad"])),
Err("unknown lsp argument \"--bad\"".to_string())
);
}
}