use clap::Parser;
use std::path::PathBuf;
#[derive(Parser, Debug, Clone)]
#[command(name = "rs-guard")]
#[command(about = "AI-powered code review CLI for GitHub PRs")]
#[command(version = env!("CARGO_PKG_VERSION"))]
pub struct Args {
#[arg(
short,
long,
default_value = ".github/review-prompt.md",
help = "Path to system prompt markdown file"
)]
pub prompt_file: PathBuf,
#[arg(
short,
long,
help = "LLM model identifier (default: provider-specific)"
)]
pub model: Option<String>,
#[arg(
short,
long,
help = "Sampling temperature (0.0 - 2.0) [default: 0.1]",
value_parser = parse_temperature
)]
pub temperature: Option<f32>,
#[arg(
long,
env = "RS_GUARD_PROVIDER",
help = "LLM provider to use [default: deepseek]"
)]
pub provider: Option<String>,
#[arg(
short,
long,
default_value = ".reviewer.toml",
help = "Path to configuration TOML file"
)]
pub config: PathBuf,
#[arg(long, help = "Maximum tokens for LLM completions")]
pub max_tokens: Option<u32>,
#[arg(
long,
env = "RS_GUARD_DIFF_FILE",
help = "Path to a pre-existing diff file to review"
)]
pub diff_file: Option<String>,
#[arg(long, help = "Bypass response cache and force LLM API call")]
pub no_cache: bool,
#[arg(long, help = "Dry-run mode: review without submitting or blocking")]
pub dry_run: bool,
}
fn parse_temperature(s: &str) -> Result<f32, String> {
let v: f32 = s
.parse()
.map_err(|e| format!("Invalid temperature '{}': {}", s, e))?;
if !(0.0..=2.0).contains(&v) {
return Err(format!(
"Temperature must be between 0.0 and 2.0, got: {}",
v
));
}
Ok(v)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_temperature_valid() {
assert_eq!(parse_temperature("0.0").unwrap(), 0.0);
assert_eq!(parse_temperature("0.1").unwrap(), 0.1);
assert_eq!(parse_temperature("1.0").unwrap(), 1.0);
assert_eq!(parse_temperature("2.0").unwrap(), 2.0);
}
#[test]
fn test_parse_temperature_out_of_range() {
assert!(parse_temperature("-0.1").is_err());
assert!(parse_temperature("2.1").is_err());
assert!(parse_temperature("5.0").is_err());
}
#[test]
fn test_parse_temperature_invalid_string() {
assert!(parse_temperature("not-a-number").is_err());
assert!(parse_temperature("").is_err());
}
#[test]
fn test_dry_run_flag_parsing() {
let args = Args::parse_from(["rs-guard", "--dry-run"]);
assert!(args.dry_run);
}
#[test]
fn test_dry_run_flag_default_false() {
let args = Args::parse_from(["rs-guard"]);
assert!(!args.dry_run);
}
}