Skip to main content

rippy_cli/
debug_cmd.rs

1//! CLI handler for `rippy debug` — trace the decision path for a command.
2
3use std::path::PathBuf;
4use std::process::ExitCode;
5
6use crate::cli::DebugArgs;
7use crate::config::{self, ConfigSourceInfo};
8use crate::error::RippyError;
9use crate::inspect;
10
11/// Run the `rippy debug` subcommand.
12///
13/// # Errors
14///
15/// Returns `RippyError` if parsing or tracing fails.
16pub fn run(args: &DebugArgs) -> Result<ExitCode, RippyError> {
17    let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
18    let trace = inspect::collect_trace_data(&args.command, &cwd, args.config.as_deref())?;
19    let sources = config::enumerate_config_sources(&cwd, args.config.as_deref());
20
21    if args.json {
22        let mut json_output = serde_json::json!({
23            "command": trace.command,
24            "sources": sources,
25            "decision": trace.decision,
26            "reason": trace.reason,
27            "steps": trace.steps.iter().map(|s| serde_json::json!({
28                "stage": s.stage,
29                "matched": s.matched,
30                "detail": s.detail,
31            })).collect::<Vec<_>>(),
32        });
33        if let Some(resolved) = &trace.resolved {
34            json_output["resolved"] = serde_json::Value::String(resolved.clone());
35        }
36        let json = serde_json::to_string_pretty(&json_output)
37            .map_err(|e| RippyError::Setup(format!("JSON serialization failed: {e}")))?;
38        println!("{json}");
39    } else {
40        print_debug_text(&trace, &sources);
41    }
42
43    Ok(ExitCode::SUCCESS)
44}
45
46fn print_debug_text(trace: &inspect::TraceOutput, sources: &[ConfigSourceInfo]) {
47    println!("Command: {}", trace.command);
48    if let Some(resolved) = &trace.resolved {
49        println!("Resolved: {resolved}");
50    }
51    println!();
52
53    println!("Config sources:");
54    for (i, source) in sources.iter().enumerate() {
55        let path_info = source
56            .path
57            .as_ref()
58            .map_or(String::new(), |p| format!(" ({})", p.display()));
59        println!("  {}. {}{path_info}", i + 1, source.tier);
60    }
61
62    println!("\nDecision trace:");
63    for (i, step) in trace.steps.iter().enumerate() {
64        let status = if step.matched { "+" } else { "-" };
65        println!("  {}. {:<16} [{status}] {}", i + 1, step.stage, step.detail);
66    }
67
68    println!("\nVerdict: {}", trace.decision.to_uppercase());
69    println!("  Reason: {}", trace.reason);
70}