1use crate::_generated::python::PYTHONS;
2use crate::commands::_prelude::*;
3use crate::python::get_current_user_and_email;
4use origen_metal::tera::{Context, Tera};
5use std::env;
6use std::fs::{create_dir, File};
7use std::path::{Path, PathBuf};
8use std::process::exit;
9
10pub const BASE_CMD: &'static str = "new";
11pub const WS_CMD: &'static str = "workspace";
12pub const APP_CMD: &'static str = "application";
13pub const PL_CMD: &'static str = "plugin";
14
15lazy_static! {
16 static ref APP_NS_DIR: &'static str = "app_namespace_dir/";
17}
18
19include!(concat!(env!("OUT_DIR"), "/new_app_templates.rs"));
27
28macro_rules! common_new_args {
29 ( $cmd: expr, $new_type: expr ) => {{
30 $cmd.arg(req_sv_arg!("name", "NAME", concat!($new_type, " name")))
31 .arg(
32 sv_opt!("desc", "DESC", concat!("Description of the ", $new_type))
33 .visible_alias("description"),
34 )
35 .arg(sv_opt!("path", "PATH", "Path to build the new workspace").short('p'))
36 }};
37}
38
39gen_core_cmd_funcs__no_exts__no_app_opts!(
40 BASE_CMD,
41 "Create a new origen environment (e.g., app, workspace)",
42 { |cmd: App| { cmd.arg_required_else_help(true) } },
43 core_subcmd__no_exts__no_app_opts!(WS_CMD, "Create a new workspace", {
44 |cmd: App| {
45 cmd.visible_alias("ws")
46 .arg(req_sv_arg!("name", "NAME", "Workspace name"))
47 .arg(
48 sv_opt!("desc", "DESC", "Description of the workspace")
49 .visible_alias("description"),
50 )
51 .arg(sv_opt!("path", "PATH", "Path to build the new workspace").short('p'))
52 }
53 }),
54 core_subcmd__no_exts__no_app_opts!(PL_CMD, "Create a new workspace", {
55 |cmd: App| common_new_args!(cmd, "Plugin").visible_alias("pl")
56 }),
57 core_subcmd__no_exts__no_app_opts!(APP_CMD, "Create a new application", {
59 |cmd: App| {
60 common_new_args!(cmd, "Application").visible_alias("app")
61 }
64 })
65);
66
67pub fn current_user_to_author() -> Result<String> {
68 let info = get_current_user_and_email()?;
69 Ok(format!("{} {} <{}>", info.0, info.1, info.2))
70}
71
72pub fn run(invocation: &clap::ArgMatches) -> origen::Result<()> {
73 if let Some((n, subcmd)) = invocation.subcommand() {
74 let mut context = Context::new();
75 let name = subcmd.get_one::<String>("name").unwrap();
76
77 let mut out_dir;
78 if let Some(path) = subcmd.get_one::<String>("path") {
79 let p = PathBuf::from(path);
80 if p.is_relative() {
81 out_dir = env::current_dir()?;
82 out_dir.push(&p);
83 } else {
84 out_dir = p;
85 }
86 } else {
87 out_dir = env::current_dir()?;
88 out_dir.push(&name);
89 }
90
91 if out_dir.exists() {
93 if !out_dir.read_dir()?.next().is_none() {
95 log_error!("Target directory {} is not empty!", &out_dir.display());
96 exit(1);
97 }
98 }
99
100 context.insert("name", name);
101 context.insert(
102 "desc",
103 subcmd.get_one::<String>("desc").unwrap_or(&"".to_string()),
104 );
105
106 let mut author = "".to_string();
108 if origen_fe_available!() {
109 match current_user_to_author() {
110 Ok(n) => author = n,
111 Err(e) => {
112 log_warning!(
113 "Errors occurred getting the current username and email from origen: {}",
114 e
115 );
116 }
117 }
118 } else {
119 if let Err(e) = origen_metal::try_lookup_and_set_current_user() {
120 log_warning!("Errors occurred populating current user: {}", e);
121 } else {
122 let users = origen_metal::users();
123 match users.current_user() {
124 Ok(u) => match u.username() {
125 Ok(username) => match u.get_email() {
126 Ok(e) => {
127 if let Some(email) = e {
128 author += &format!("{} <{}>", &username, &email);
129 } else {
130 log_warning!("Could not retrieve user email. Only including username in 'author'");
131 author += &username;
132 }
133 }
134 Err(e) => {
135 log_warning!("Cannot retrieve current user's email: {}", e.msg);
136 }
137 },
138 Err(e) => {
139 log_warning!("Cannot retrieve current user: {}", e.msg);
140 }
141 },
142 Err(e) => {
143 log_warning!("Errors occurred populating current user: {}", e);
144 }
145 }
146 }
147 }
148 context.insert("author", &author);
149
150 let origen_version = origen::STATUS
152 .origen_version
153 .to_string()
154 .replace("-dev.", ".dev")
155 .replace("-alpha.", ".a")
156 .replace("-beta.", ".b");
157 context.insert("origen_version", &origen_version);
158 context.insert(
159 "python_version",
160 &format!(
161 ">={},<{}",
162 PYTHONS[2].strip_prefix("python").unwrap(),
163 "3.13"
164 ),
165 );
166 log_trace!("'origen new' context: {:?}", context);
167
168 let mut tera = Tera::default();
169 for (n, contents) in SHARED.entries() {
170 tera.add_raw_template(&format!("shared/{}", n), contents)?;
171 }
172
173 let (app_gen, pl_gen, ws_gen, path_base): (bool, bool, bool, &str);
174 match n {
175 WS_CMD => {
176 app_gen = false;
177 pl_gen = false;
178 ws_gen = true;
179 path_base = "workspace";
180 for (n, contents) in WORKSPACE.entries() {
181 tera.add_raw_template(&format!("{}/{}", path_base, n), contents)?;
182 }
183 }
184 PL_CMD => {
185 app_gen = false;
186 pl_gen = true;
187 ws_gen = false;
188 path_base = "plugin";
189
190 for (n, contents) in PY_APP.entries() {
191 tera.add_raw_template(&format!("{}/{}", path_base, n), contents)?;
192 }
193 }
194 APP_CMD => {
195 app_gen = true;
196 pl_gen = false;
197 ws_gen = false;
198 path_base = "application";
199 for (name, contents) in PY_APP.entries() {
200 tera.add_raw_template(&format!("{}/{}", path_base, name), contents)?;
201 }
202 }
203 _ => unreachable_invalid_subc!(n),
204 }
205 context.insert("app_gen", &app_gen);
206 context.insert("pl_gen", &pl_gen);
207 context.insert("ws_gen", &ws_gen);
208
209 if !out_dir.exists() {
210 create_dir(&out_dir)?;
211 }
212
213 let base_prefix = format!("{}/", path_base);
214 let mut errored = false;
215 for t in tera.get_template_names() {
216 if let Some(p) = t.strip_prefix(&base_prefix) {
217 let path;
218 if let Ok(p2) = Path::new(p).strip_prefix(*APP_NS_DIR) {
219 log_debug!(
220 "Moving template from '{}' space to '{}' space",
221 *APP_NS_DIR,
222 name
223 );
224 path = out_dir.join(name).join(p2);
225 } else {
226 path = out_dir.join(p);
227 }
228 displayln!("Rendering template...");
229 displayln!(" {}", t);
230 displayln!("=> {}", path.display());
231
232 std::fs::create_dir_all(path.parent().unwrap_or_else(|| Path::new("")))?;
233 let f = File::create(path)?;
234
235 match tera.render_to(t, &context, f) {
236 Ok(()) => {
237 display_greenln!(" Success!");
238 }
239 Err(e) => {
240 errored = true;
241 display_redln!(" {}", origen_metal::Error::from(e));
242 }
243 }
244 }
245 }
246
247 if errored {
248 display_redln!(
249 "Failed to create new {}. Please review output logs for errors message.",
250 path_base
251 );
252 bail!("Failed to create new {}", path_base)
253 } else {
254 display_greenln!("Created new {}:", path_base);
255 display_greenln!(" {}", &out_dir.display());
256 Ok(())
257 }
258 } else {
259 unreachable!()
260 }
261}