rgen_cli_lib/cmds/
gen.rs

1use clap::Args;
2use std::collections::BTreeMap;
3use std::path::PathBuf;
4use rgen_utils::error::Result;
5
6use rgen_core::pipeline::PipelineBuilder;
7use rgen_core::resolver::TemplateResolver;
8use rgen_core::{CacheManager, LockfileManager};
9
10#[derive(Args, Debug)]
11pub struct GenArgs {
12    /// Template reference in format "pack_id:template_path"
13    pub template: String,
14
15    /// Output directory root
16    #[arg(short, long, default_value = ".")]
17    pub out: PathBuf,
18
19    /// Variables (key=value pairs)
20    #[arg(short = 'v', long = "var", value_parser = parse_key_val::<String, String>)]
21    pub vars: Vec<(String, String)>,
22
23    /// Dry run (no write)
24    #[arg(long)]
25    pub dry: bool,
26}
27
28fn parse_key_val<K, V>(s: &str) -> std::result::Result<(K, V), String>
29where
30    K: std::str::FromStr,
31    K::Err: ToString,
32    V: std::str::FromStr,
33    V::Err: ToString,
34{
35    let pos = s
36        .find('=')
37        .ok_or_else(|| format!("invalid KEY=value: no `=` found in `{s}`"))?;
38    let key = s[..pos].parse().map_err(|e: K::Err| e.to_string())?;
39    let val = s[pos + 1..].parse().map_err(|e: V::Err| e.to_string())?;
40    Ok((key, val))
41}
42
43pub fn run(args: &GenArgs) -> Result<()> {
44    // Initialize cache and lockfile managers
45    let cache_manager = CacheManager::new()?;
46    let project_dir = std::env::current_dir()?;
47    let lockfile_manager = LockfileManager::new(&project_dir);
48
49    // Create resolver
50    let resolver = TemplateResolver::new(cache_manager, lockfile_manager);
51
52    // Resolve template
53    let template_source = resolver.resolve(&args.template)?;
54
55    // Build pipeline (use default prefixes for now)
56    let mut pipeline = PipelineBuilder::new().build()?;
57
58    // Convert vars to BTreeMap
59    let vars: BTreeMap<String, String> = args.vars.iter().cloned().collect();
60
61    // Render template
62    let plan = pipeline.render_file(&template_source.template_path, &vars, args.dry)?;
63
64    if args.dry {
65        println!("DRY RUN - Would generate:");
66        println!("  Template: {}", template_source.template_path.display());
67        println!("  Vars:     {} variables", vars.len());
68    } else {
69        plan.apply()?;
70        println!("Generated successfully");
71    }
72
73    Ok(())
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn test_parse_key_val_valid() {
82        let result = parse_key_val::<String, String>("name=value");
83        assert!(result.is_ok());
84        let (key, val) = result.unwrap();
85        assert_eq!(key, "name");
86        assert_eq!(val, "value");
87    }
88
89    #[test]
90    fn test_parse_key_val_with_spaces() {
91        let result = parse_key_val::<String, String>("name=hello world");
92        assert!(result.is_ok());
93        let (key, val) = result.unwrap();
94        assert_eq!(key, "name");
95        assert_eq!(val, "hello world");
96    }
97
98    #[test]
99    fn test_parse_key_val_integer() {
100        let result = parse_key_val::<String, i32>("count=42");
101        assert!(result.is_ok());
102        let (key, val) = result.unwrap();
103        assert_eq!(key, "count");
104        assert_eq!(val, 42);
105    }
106
107    #[test]
108    fn test_parse_key_val_no_equals() {
109        let result = parse_key_val::<String, String>("invalid");
110        assert!(result.is_err());
111        assert!(result.unwrap_err().contains("no `=` found"));
112    }
113
114    #[test]
115    fn test_parse_key_val_empty_key() {
116        let result = parse_key_val::<String, String>("=value");
117        assert!(result.is_ok());
118        let (key, val) = result.unwrap();
119        assert_eq!(key, "");
120        assert_eq!(val, "value");
121    }
122
123    #[test]
124    fn test_parse_key_val_empty_value() {
125        let result = parse_key_val::<String, String>("key=");
126        assert!(result.is_ok());
127        let (key, val) = result.unwrap();
128        assert_eq!(key, "key");
129        assert_eq!(val, "");
130    }
131
132    #[test]
133    fn test_parse_key_val_multiple_equals() {
134        let result = parse_key_val::<String, String>("key=value=extra");
135        assert!(result.is_ok());
136        let (key, val) = result.unwrap();
137        assert_eq!(key, "key");
138        assert_eq!(val, "value=extra");
139    }
140}