ggen_cli_lib/cmds/project/
diff.rs

1use clap::Args;
2use ggen_utils::error::Result;
3use std::process::Command;
4
5#[derive(Args, Debug)]
6pub struct DiffArgs {
7    /// Template reference (e.g., "gpack-id:path/to.tmpl" or "local/path.tmpl")
8    pub template_ref: String,
9
10    /// Variables to pass to the template (key=value format)
11    #[arg(short = 'v', long = "var")]
12    pub vars: Vec<String>,
13
14    /// Show unified diff with N lines of context
15    #[arg(short = 'U', long = "unified", default_value = "3")]
16    pub context: usize,
17
18    /// Colorize the diff output
19    #[arg(long, default_value = "true")]
20    pub color: bool,
21}
22
23/// Main entry point for `ggen project diff`
24pub async fn run(args: &DiffArgs) -> Result<()> {
25    println!("Generating unified diff");
26
27    // Validate template reference
28    if args.template_ref.is_empty() {
29        return Err(ggen_utils::error::Error::new(
30            "Template reference cannot be empty",
31        ));
32    }
33
34    // Parse variables
35    let mut variables = std::collections::HashMap::new();
36    for var in &args.vars {
37        if let Some((key, value)) = var.split_once('=') {
38            variables.insert(key.to_string(), value.to_string());
39        } else {
40            return Err(ggen_utils::error::Error::new_fmt(format_args!(
41                "Invalid variable format: {}. Expected key=value",
42                var
43            )));
44        }
45    }
46
47    // Generate diff using cargo make
48    let mut cmd = Command::new("cargo");
49    cmd.args(["make", "project-diff"]);
50    cmd.arg("--template").arg(&args.template_ref);
51    cmd.arg("--context").arg(args.context.to_string());
52
53    if args.color {
54        cmd.arg("--color");
55    }
56
57    for (key, value) in &variables {
58        cmd.arg("--var").arg(format!("{}={}", key, value));
59    }
60
61    let output = cmd.output().map_err(ggen_utils::error::Error::from)?;
62
63    if !output.status.success() {
64        let stderr = String::from_utf8_lossy(&output.stderr);
65        return Err(ggen_utils::error::Error::new_fmt(format_args!(
66            "Diff generation failed: {}",
67            stderr
68        )));
69    }
70
71    let stdout = String::from_utf8_lossy(&output.stdout);
72
73    if stdout.trim().is_empty() {
74        println!("✅ No differences found");
75        println!("No differences found between template output and current project state.");
76    } else {
77        println!("✅ Diff generated successfully");
78        println!("{}", stdout);
79    }
80
81    Ok(())
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    #[tokio::test]
89    async fn test_diff_args_parsing() {
90        let args = DiffArgs {
91            template_ref: "test.tmpl".to_string(),
92            vars: vec!["name=Test".to_string()],
93            context: 5,
94            color: true,
95        };
96
97        assert_eq!(args.template_ref, "test.tmpl");
98        assert_eq!(args.context, 5);
99    }
100}