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 pub template: String,
14
15 #[arg(short, long, default_value = ".")]
17 pub out: PathBuf,
18
19 #[arg(short = 'v', long = "var", value_parser = parse_key_val::<String, String>)]
21 pub vars: Vec<(String, String)>,
22
23 #[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 let cache_manager = CacheManager::new()?;
46 let project_dir = std::env::current_dir()?;
47 let lockfile_manager = LockfileManager::new(&project_dir);
48
49 let resolver = TemplateResolver::new(cache_manager, lockfile_manager);
51
52 let template_source = resolver.resolve(&args.template)?;
54
55 let mut pipeline = PipelineBuilder::new().build()?;
57
58 let vars: BTreeMap<String, String> = args.vars.iter().cloned().collect();
60
61 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}