camel_cli/commands/
new.rs1use crate::template::embedded::EmbeddedTemplate;
2use crate::template::{ProfileLayout, TemplateContext, TemplateProvider};
3
4#[derive(clap::Args)]
5pub struct NewArgs {
6 pub name: String,
8
9 #[arg(long, default_value = "basic")]
11 pub template: String,
12
13 #[arg(long)]
15 pub force: bool,
16
17 #[arg(long, value_name = "LAYOUT", default_value = "env")]
19 pub profile_layout: ProfileLayout,
20}
21
22fn resolve_template(name: &str) -> Result<Box<dyn TemplateProvider>, Box<dyn std::error::Error>> {
23 match name {
24 "basic" => Ok(Box::new(EmbeddedTemplate::basic())),
25 other => Err(format!("Unknown template: '{other}'. Available templates: basic").into()),
26 }
27}
28
29pub fn run_new(args: NewArgs) {
30 let NewArgs {
31 name,
32 template,
33 force,
34 profile_layout,
35 } = args;
36
37 if name.contains("..") || name.contains('\\') {
38 eprintln!("Error: project name must not contain '..' or backslashes");
39 std::process::exit(1);
40 }
41
42 let ctx = TemplateContext {
43 project_name: name.clone(),
44 profile_layout,
45 };
46
47 let provider = resolve_template(&template).unwrap_or_else(|e| {
48 eprintln!("Error: {e}");
49 std::process::exit(1);
50 });
51
52 let files = provider.files(&ctx).unwrap_or_else(|e| {
53 eprintln!("Error generating project: {e}");
54 std::process::exit(1);
55 });
56
57 let target = std::path::Path::new(&name);
58 if target.exists() && !force {
59 let is_non_empty = target.read_dir().is_ok_and(|mut d| d.next().is_some());
60 if is_non_empty {
61 eprintln!(
62 "Directory '{}' already exists and is not empty. Use --force to overwrite.",
63 name
64 );
65 std::process::exit(1);
66 }
67 }
68
69 std::fs::create_dir_all(target).unwrap_or_else(|e| {
70 eprintln!("Failed to create directory '{}': {}", name, e);
71 std::process::exit(1);
72 });
73
74 for file in &files {
75 let file_path = target.join(&file.path);
76 if let Some(parent) = file_path.parent() {
77 std::fs::create_dir_all(parent).unwrap_or_else(|e| {
78 eprintln!("Failed to create directory '{}': {}", parent.display(), e);
79 std::process::exit(1);
80 });
81 }
82 std::fs::write(&file_path, &file.content).unwrap_or_else(|e| {
83 eprintln!("Failed to write '{}': {}", file_path.display(), e);
84 std::process::exit(1);
85 });
86 }
87
88 let display_name = std::path::Path::new(&name)
89 .file_name()
90 .and_then(|n| n.to_str())
91 .unwrap_or(&name);
92 println!("Created camel project: {}\n", display_name);
93 println!("Next steps:");
94 println!(" cd {}", name);
95 println!(" camel run");
96 println!(" camel run --watch");
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 #[test]
104 fn resolve_template_basic_returns_ok() {
105 let result = resolve_template("basic");
106 assert!(result.is_ok());
107 assert_eq!(result.unwrap().name(), "basic");
108 }
109
110 #[test]
111 fn resolve_template_unknown_returns_error() {
112 let result = resolve_template("nonexistent");
113 match result {
114 Ok(_) => panic!("expected error, got Ok"),
115 Err(err) => {
116 let msg = err.to_string();
117 assert!(msg.contains("Unknown template"), "got: {msg}");
118 assert!(msg.contains("nonexistent"), "got: {msg}");
119 }
120 }
121 }
122}