1use clap::Args;
2use derive_more::Display;
3use include_dir::{Dir, include_dir};
4use std::{fs, path::Path};
5use walkdir::WalkDir;
6
7use crate::commons::ErrorCode;
8
9#[derive(clap::ValueEnum, Clone, Default, Display)]
10pub enum Template {
11 #[display("fullstack")]
12 Fullstack,
13 #[display("frontend")]
14 #[default]
15 Frontend,
16}
17
18impl Template {
19 pub fn get_dir(&self) -> Dir<'_> {
20 match self {
21 Template::Fullstack => include_dir!("$CARGO_MANIFEST_DIR/src/new/fs_template"),
22 Template::Frontend => include_dir!("$CARGO_MANIFEST_DIR/src/new/fe_template"),
23 }
24 }
25}
26
27#[derive(Args)]
28pub struct NewOpts {
29 pub project_name: String,
30 #[arg(short, long, default_value_t = {Template::default()})]
31 pub template: Template,
32 #[arg(long, default_value_t = {"./".to_string()})]
33 pub dest_dir: String,
34}
35
36pub fn run(opts: NewOpts) -> Result<(), ErrorCode> {
37 log::info!("Creating {}", opts.project_name);
38
39 let target_path = Path::new(&opts.dest_dir).join(&opts.project_name);
40
41 if let Ok(mut dir) = target_path.read_dir()
43 && dir.next().is_some()
44 {
45 log::error!(
46 "Destination dir ({}) is not empty",
47 target_path.to_string_lossy()
48 );
49 return Err(ErrorCode::NewProjectDirNotEmpty);
50 }
51
52 if let Err(err) = fs::create_dir_all(&target_path) {
54 log::error!(
55 "Can't create directory {}: {}",
56 target_path.to_string_lossy(),
57 err
58 );
59 return Err(ErrorCode::NewProjectCantCreateDir);
60 };
61
62 if let Err(err) = opts
64 .template
65 .get_dir()
66 .extract(Path::new(&opts.dest_dir).join(&opts.project_name))
67 {
68 log::error!(
69 "Can't unpack vertigo stub to {}: {}",
70 target_path.to_string_lossy(),
71 err
72 );
73 return Err(ErrorCode::NewProjectCantUnpackStub);
74 };
75
76 process_cargo_toml_files(&target_path, &opts.project_name)?;
79
80 Ok(())
81}
82
83fn process_cargo_toml_files(dir: &Path, package_name: &str) -> Result<(), ErrorCode> {
85 for entry in WalkDir::new(dir).into_iter().filter_map(|e| e.ok()) {
86 if entry.file_name() == "Cargo.toml_" {
87 let path = entry.path();
88 let content = match fs::read_to_string(path) {
90 Ok(content) => content,
91 Err(err) => {
92 log::error!("Can't read {}: {}", path.display(), err);
93 return Err(ErrorCode::NewProjectCantCreateCargoToml);
94 }
95 };
96
97 let new_content = content.replace("my_app", package_name);
99
100 if let Some(parent) = path.parent() {
102 let cargo_toml_path = parent.join("Cargo.toml");
103 if let Err(err) = fs::write(&cargo_toml_path, new_content) {
104 log::error!("Can't write to {}: {}", cargo_toml_path.display(), err);
105 return Err(ErrorCode::NewProjectCanWriteToCargoToml);
106 }
107 }
108
109 if let Err(err) = fs::remove_file(path) {
111 log::error!("Can't remove {}: {}", path.display(), err);
112 return Err(ErrorCode::NewProjectCantCreateCargoToml);
113 }
114 }
115 }
116
117 Ok(())
118}