origen/commands/new/
mod.rs

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
19// This includes a map of all template files, it is built by cli/build.rs at compile time.
20// All files in each sub-directory of commands/new/templates are accessible via a map named after the
21// uppercased sub_directory, e.g.
22//      PYTHON_APP = { "pyproject.toml" => "[project]...", "config/application.toml" => "..." }
23//
24// Doing it this way means that we can just drop new files into the templates dirs and they will
25// automatically be picked up and included in the new app.
26include!(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    // TODO origen new - support new app
58    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            // .arg(sv_opt!("dut", "DUT NAME", "Use a different DUT name (default: dut). Cannot be used with --no_dut"))
62            // .arg(sf_opt!("no_dut", "Do not create a DUT. Cannot be used with --dut <DUT NAME>"))
63        }
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        //  Check output dir but hold off until other checks have passed
92        if out_dir.exists() {
93            // Check directory is empty
94            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        // Add author to context
107        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        // Add Python, Origen, and other library versions
151        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}