origen/
bin.rs

1#[macro_use]
2extern crate lazy_static;
3#[macro_use]
4extern crate serde;
5#[macro_use]
6extern crate origen_metal;
7
8mod _generated;
9mod commands;
10mod framework;
11mod python;
12
13use clap::error::ErrorKind as ClapErrorKind;
14use clap::Command;
15use framework::{
16    add_verbosity_opts, VERBOSITY_KEYWORDS_OPT_NAME, VERBOSITY_OPT_NAME, VOV_OPT_NAME,
17};
18use framework::{AppCmds, AuxCmds, CmdHelps, Extensions, Plugins};
19use indexmap::map::IndexMap;
20use origen::{Result, STATUS};
21use std::iter::FromIterator;
22use std::process::exit;
23use std::sync::OnceLock;
24
25use VERBOSITY_KEYWORDS_OPT_NAME as VKS_OPT_NAME;
26use VERBOSITY_OPT_NAME as V_OPT_NAME;
27
28// #[derive(Clone)]
29// pub struct CommandHelp {
30//     name: String,
31//     help: String,
32//     shortcut: Option<String>,
33// }
34
35pub mod built_info {
36    // The file has been placed there by the build script.
37    include!(concat!(env!("OUT_DIR"), "/built.rs"));
38}
39
40static ORIGEN_FE_AVAILABLE: OnceLock<bool> = OnceLock::new();
41
42#[macro_export]
43macro_rules! origen_fe_available {
44    () => {{
45        *crate::ORIGEN_FE_AVAILABLE.get().unwrap_or(&false) == true
46    }};
47}
48
49// This is the entry point for the Origen CLI tool
50fn main() -> Result<()> {
51    // The pre-parser deliberately tolerates unknown application/plugin
52    // arguments, which means Clap cannot reliably retain global verbosity
53    // options that occur after an unknown token. Scan these two orthogonal
54    // globals directly so help and parse errors still honor their logging
55    // settings before the complete command tree exists.
56    let raw_args: Vec<std::ffi::OsString> = std::env::args_os().skip(1).collect();
57    let mut scanned_verbosity = 0u8;
58    let mut scanned_vks = Vec::new();
59    let mut missing_vk_value = false;
60    let mut version_only_invocation = true;
61    let mut saw_short_v = false;
62    let raw: Vec<&str> = raw_args.iter().filter_map(|a| a.to_str()).collect();
63    let mut i = 0;
64    while i < raw.len() {
65        let arg = raw[i];
66        if arg == "--" || arg == commands::exec::BASE_CMD {
67            version_only_invocation = false;
68            break;
69        }
70        if arg == "--verbose" || arg == "--verbosity" {
71            scanned_verbosity = scanned_verbosity.saturating_add(1);
72        } else if arg.starts_with('-') && arg.len() > 1 && arg[1..].chars().all(|c| c == 'v') {
73            scanned_verbosity = scanned_verbosity.saturating_add((arg.len() - 1) as u8);
74            saw_short_v = true;
75        } else if arg == "--verbosity_keywords" || arg == "--vk" {
76            i += 1;
77            if let Some(value) = raw.get(i).filter(|v| !v.starts_with('-')) {
78                scanned_vks.extend(value.split(',').map(str::to_string));
79            } else {
80                missing_vk_value = true;
81            }
82        } else if let Some(value) = arg
83            .strip_prefix("--verbosity_keywords=")
84            .or_else(|| arg.strip_prefix("--vk="))
85        {
86            scanned_vks.extend(value.split(',').map(str::to_string));
87        } else {
88            version_only_invocation = false;
89        }
90        i += 1;
91    }
92    if missing_vk_value {
93        eprintln!(
94            "error: a value is required for '--verbosity_keywords <verbosity_keywords>' but none was supplied"
95        );
96        std::process::exit(2);
97    }
98    if version_only_invocation && saw_short_v {
99        scanned_verbosity = scanned_verbosity.saturating_sub(1);
100    }
101    // Create a mini-app to handle verbose and verbosity keyword arguments and run any commands that should be run
102    // earlier in the flow (e.g.: exec). Call this the "pre-phase" app.
103    // Exits if a pre-phase command was handled, otherwise, set verbosity settings and continue with the main flow
104    // Note: pre-phase runs before plugins or extensions are available. By definition, commands executing during pre-phase are not extendable.
105    macro_rules! pre_phase_app {
106        () => {{
107            add_verbosity_opts(
108                Command::new("")
109                    .disable_version_flag(true)
110                    .allow_external_subcommands(true)
111                    .disable_help_flag(true)
112                    .ignore_errors(true),
113                true,
114            )
115        }};
116    }
117
118    let mut pre_phase_app = pre_phase_app!();
119    pre_phase_app = commands::exec::add_prephase_cmds(pre_phase_app);
120    pre_phase_app = commands::env::add_prephase_cmds(pre_phase_app);
121
122    let mut print_help = false;
123    let mut verbosity;
124    let mut vks;
125    let exe = match std::env::current_exe() {
126        Ok(p) => Some(format!("{}", p.display())),
127        Err(e) => {
128            log_error!("{}", e);
129            None
130        }
131    };
132    macro_rules! origen_init {
133        () => {{
134            origen::initialize(
135                Some(scanned_verbosity),
136                scanned_vks.clone(),
137                exe,
138                Some(built_info::PKG_VERSION.to_string()),
139                None,
140                None,
141            )
142        }};
143    }
144
145    match pre_phase_app.try_get_matches() {
146        Ok(m) => {
147            verbosity = *m.get_one::<u8>(V_OPT_NAME).unwrap_or(&0);
148            verbosity += m.get_one::<u8>(VOV_OPT_NAME).unwrap_or(&0);
149            vks = match m.get_many::<String>(VKS_OPT_NAME) {
150                Some(vks) => vks.map(|vk| vk.to_owned()).collect::<Vec<String>>(),
151                None => vec![],
152            };
153
154            macro_rules! run_pre_phase_cmd {
155                ($cmd_mod:ident, $subc:expr) => {{
156                    origen_init!();
157                    exit(commands::$cmd_mod::run_pre_phase(&$subc)?);
158                }};
159            }
160
161            match m.subcommand() {
162                Some((commands::exec::BASE_CMD, subc)) => {
163                    // `ignore_errors` lets the lightweight pre-parser collect
164                    // verbosity from otherwise invalid invocations. Only run
165                    // exec here when its required command was actually parsed;
166                    // the full parser must report missing-argument errors.
167                    if subc.get_one::<String>("cmd").is_some() {
168                        run_pre_phase_cmd!(exec, subc);
169                    }
170                }
171                Some((commands::env::BASE_CMD, subc))
172                    if subc.subcommand_matches("migrate").is_some() =>
173                {
174                    let strict = add_verbosity_opts(
175                        Command::new("")
176                            .disable_version_flag(true)
177                            .disable_help_flag(true),
178                        true,
179                    );
180                    let strict = commands::env::add_prephase_cmds(strict);
181                    if let Ok(strict_matches) = strict.try_get_matches_from(std::env::args_os()) {
182                        let strict_env = strict_matches
183                            .subcommand_matches(commands::env::BASE_CMD)
184                            .unwrap();
185                        run_pre_phase_cmd!(env, strict_env);
186                    }
187                }
188                // "External subcommand" received, which in this case is either a non-pre-prephase or invalid command.
189                // Either way, let the main flow handle it.
190                Some((_ext, ext_args)) => {
191                    // Args under "" are external subcommand args
192                    match ext_args.get_many::<std::ffi::OsString>("") {
193                        Some(args) => {
194                            // Need to repeatedly parse the args to overcome handling of corner cases.
195                            // Use a dummy app that just accepts verbosity and keywords. Parse this until empty.
196                            let mut dummy = pre_phase_app!().no_binary_name(true);
197                            let mut reduced = args
198                                .map(|a| a.to_owned())
199                                .collect::<Vec<std::ffi::OsString>>();
200                            loop {
201                                match dummy.try_get_matches_from_mut(reduced.clone()) {
202                                    Ok(dm) => {
203                                        verbosity += dm.get_one::<u8>(V_OPT_NAME).unwrap_or(&0);
204                                        verbosity += dm.get_one::<u8>(VOV_OPT_NAME).unwrap_or(&0);
205                                        match dm.get_many::<String>(VKS_OPT_NAME) {
206                                            Some(vkws) => vks.append(
207                                                &mut vkws
208                                                    .map(|vk| vk.to_owned())
209                                                    .collect::<Vec<String>>(),
210                                            ),
211                                            None => {}
212                                        };
213
214                                        match dm.subcommand() {
215                                            Some((_ext, dm_args)) => {
216                                                match dm_args.get_many::<std::ffi::OsString>("") {
217                                                    Some(dm_args) => {
218                                                        reduced = dm_args
219                                                            .map(|a| a.to_owned())
220                                                            .collect::<Vec<std::ffi::OsString>>();
221                                                    }
222                                                    None => break,
223                                                }
224                                            }
225                                            None => break,
226                                        }
227                                    }
228                                    Err(e) => {
229                                        e.exit();
230                                    }
231                                }
232                            }
233                        }
234                        None => {}
235                    }
236                }
237                _ => {
238                    // No subcommand, or options only. Set the verbosity based on previous handling of options.
239                    // The only options here will be verbosity and vks. Knock one of the -v flags off and set
240                    // verbosity according to that. The version printout will be handled later on.
241                    verbosity = *m.get_one::<u8>(V_OPT_NAME).unwrap_or(&0);
242                    verbosity += m.get_one::<u8>(VOV_OPT_NAME).unwrap();
243
244                    // version_or_verbosity will default to 0, even if not present on the invocation
245                    match m.value_source(VOV_OPT_NAME) {
246                        Some(clap::parser::ValueSource::DefaultValue) => {
247                            // This would actually fal into a 'origen -v' invocation, but actually want to display help here
248                            // E.g.: origen --verbose --vk blah
249                            //  should display help, not version
250                            print_help = true;
251                        }
252                        _ => {
253                            // A supplied short -v is also the legacy version
254                            // trigger. The direct argv scan above preserves the
255                            // requested logging level for the main parser.
256                        }
257                    }
258                }
259            }
260            origen_init!();
261        }
262        Err(_e) => {
263            // Any mis-use of pre-phase commands or unknown args/subcommands will be handled by the full app.
264            // Fallback to manually discerning the verbosity/keywords for the main phase
265            let mut dummy = pre_phase_app!().no_binary_name(true);
266            let mut reduced: Vec<std::ffi::OsString> = std::env::args_os().skip(1).collect();
267            verbosity = 0;
268            vks = vec![];
269            loop {
270                match dummy.try_get_matches_from_mut(reduced.clone()) {
271                    Ok(dm) => {
272                        verbosity += dm.get_one::<u8>(V_OPT_NAME).unwrap_or(&0);
273                        verbosity += dm.get_one::<u8>(VOV_OPT_NAME).unwrap_or(&0);
274                        match dm.get_many::<String>(VKS_OPT_NAME) {
275                            Some(vkws) => vks
276                                .append(&mut vkws.map(|vk| vk.to_owned()).collect::<Vec<String>>()),
277                            None => {}
278                        };
279
280                        match dm.subcommand() {
281                            Some((_ext, dm_args)) => {
282                                match dm_args.get_many::<std::ffi::OsString>("") {
283                                    Some(dm_args) => {
284                                        reduced = dm_args
285                                            .map(|a| a.to_owned())
286                                            .collect::<Vec<std::ffi::OsString>>();
287                                    }
288                                    None => break,
289                                }
290                            }
291                            None => break,
292                        }
293                    }
294                    Err(e) => {
295                        e.exit();
296                    }
297                }
298            }
299            origen_init!();
300        }
301    }
302
303    let version = match STATUS.is_app_present {
304        true => format!("Origen CLI: {}", STATUS.origen_version.to_string()),
305        false => format!("Origen: {}", STATUS.origen_version.to_string()),
306    };
307
308    // The main help message is going to be automatically generated to allow us to handle and clearly
309    // separate commands added by the app and plugins.
310    // When a command is added below it must also be added to these vectors.
311    // let mut origen_commands: Vec<CommandHelp> = vec![];
312    let mut helps = CmdHelps::new();
313    let app_cmds: Option<AppCmds>;
314    let mut extensions = Extensions::new();
315    let plugins = match Plugins::new(&mut extensions) {
316        Ok(pl) => {
317            if ORIGEN_FE_AVAILABLE.set(true).is_err() {
318                bail!("Could not set ORIGEN_BACKEND_AVAILABLE");
319            }
320            pl
321        }
322        Err(e) => {
323            if ORIGEN_FE_AVAILABLE.set(false).is_err() {
324                bail!("Could not set ORIGEN_BACKEND_AVAILABLE");
325            }
326            if python::is_backend_origen_mod_missing_err(&e) {
327                // _origen is available but plugins failed to load
328                log_error!("Failed to collect plugins. Encountered error: {}", e);
329                None
330            } else {
331                // _origen isn't available. This could be an error in retrieving plugins.
332                // Print a warning instead of error, while logging the error
333                log_trace!("Failed to collect plugins. Encountered error: {}", e);
334                log_warning!("Failed to collect plugins: _origen module missing");
335                None
336            }
337        }
338    };
339    let aux_cmds = AuxCmds::new(&mut extensions)?;
340
341    if let Some(app) = &STATUS.app.as_ref() {
342        app_cmds = Some(AppCmds::new(app, &mut extensions)?);
343    } else {
344        app_cmds = None;
345    }
346
347    // Structures to hold command aliases and replacements
348    // Clap does not want to own the values and, in the case of replacements
349    // cannot be checked (easily) due to borrowing from one command to update another
350    // Easier to just store things here and have clap reference them.
351    let mut top_app_replacements: Vec<[&str; 3]> = vec![];
352    let mut top_app_cmd_aliases: IndexMap<String, Vec<String>> = IndexMap::new();
353    let mut top_pl_replacements: Vec<[&str; 3]> = vec![];
354    let mut top_pl_cmd_aliases: IndexMap<String, IndexMap<String, Vec<String>>> = IndexMap::new();
355    let mut top_aux_replacements: Vec<[&str; 3]> = vec![];
356    let mut top_aux_cmd_aliases: IndexMap<String, IndexMap<String, Vec<String>>> = IndexMap::new();
357    let mut after_help_str = "".to_string();
358
359    let mut app = Command::new("")
360        .arg_required_else_help(true)
361        .disable_version_flag(true)
362        .before_help(format!(
363            "Origen, The Semiconductor Developer's Kit\n\n{}",
364            version
365        ))
366        .version(version.clone());
367    app = add_verbosity_opts(app, false);
368
369    /************************************************************************************/
370    /******************** Global only commands ******************************************/
371    /************************************************************************************/
372    // if !STATUS.is_app_present {
373    //************************************************************************************/
374    // let proj_help = "Manage multi-repository project areas and workspaces";
375    // origen_commands.push(CommandHelp {
376    //     name: "proj".to_string(),
377    //     help: proj_help.to_string(),
378    //     shortcut: None,
379    // });
380
381    // app = app
382    //     .subcommand(
383    //         Command::new("proj")
384    //             .display_order(1)
385    //             .about(proj_help)
386    //             .arg_required_else_help(true)
387    //             .subcommand(Command::new("init")
388    //                 .display_order(5)
389    //                 .about("Initialize a new project directory (create an initial project BOM)")
390    //                 .arg(Arg::new("dir")
391    //                     .action(SetArg)
392    //                     .help("The path to the project directory to initialize (PWD will be used by default if not given)")
393    //                     .value_name("DIR")
394    //                 )
395    //             )
396    //             .subcommand(Command::new("packages")
397    //                 .display_order(7)
398    //                 .about("Displays the IDs of all packages and package groups defined by the BOM")
399    //             )
400    //             .subcommand(Command::new("create")
401    //                 .display_order(10)
402    //                 .about("Create a new project workspace from the project BOM")
403    //                 .arg(Arg::new("path")
404    //                     .help("The path to the new workspace directory")
405    //                     .action(SetArg)
406    //                     .value_name("PATH")
407    //                     .required(true)
408    //                 )
409    //             )
410    //             .subcommand(Command::new("update")
411    //                 .display_order(15)
412    //                 .about("Update an existing project workspace per its current BOM")
413    //                 .arg(Arg::new("force")
414    //                     .short('f')
415    //                     .long("force")
416    //                     .required(false)
417    //                     .action(SetArgTrue)
418    //                     .help("Force the update and potentially lose any local modifications")
419    //                 )
420    //                 .arg(Arg::new("links")
421    //                     .short('l')
422    //                     .long("links")
423    //                     .required(false)
424    //                     .action(SetArgTrue)
425    //                     .help("Update the workspace links")
426    //                 )
427    //                 .arg(Arg::new("packages")
428    //                     .value_name("PACKAGES")
429    //                     .action(AppendArgs)
430    //                     .multiple(true)
431    //                     .help("Packages and/or groups to be updated, run 'origen proj packages' to see a list of possible package IDs")
432    //                     .required_unless("links")
433    //                     .required(true)
434    //                 )
435    //             )
436    //             .subcommand(Command::new("mods")
437    //                 .display_order(20)
438    //                 .about("Display a list of modified files within the given package(s)")
439    //                 .arg(Arg::new("packages")
440    //                     .help("Package(s) to look for modifications in, use 'all' to see the modification to all packages")
441    //                     .action(AppendArgs)
442    //                     .multiple(true)
443    //                     .value_name("PACKAGES")
444    //                     .required(true)
445    //                 )
446    //             )
447    //             .subcommand(Command::new("clean")
448    //                 .display_order(20)
449    //                 .about("Revert all local modifications within the given package(s)")
450    //                 .arg(Arg::new("packages")
451    //                     .help("Package(s) to revert local modifications in, use 'all' to clean all packages")
452    //                     .action(AppendArgs)
453    //                     .multiple(true)
454    //                     .value_name("PACKAGES")
455    //                     .required(true)
456    //                 )
457    //             )
458    //             .subcommand(Command::new("tag")
459    //                 .display_order(20)
460    //                 .about("Apply the given tag to the current view of the given package(s)")
461    //                 .arg(Arg::new("name")
462    //                     .help("Name of the tag to be applied")
463    //                     .action(SetArg)
464    //                     .value_name("NAME")
465    //                     .required(true)
466    //                 )
467    //                 .arg(Arg::new("packages")
468    //                     .help("Package(s) to be tagged, use 'all' to tag all packages")
469    //                     .multiple(true)
470    //                     .action(AppendArgs)
471    //                     .value_name("PACKAGES")
472    //                     .required(true)
473    //                 )
474    //                 .arg(Arg::new("force")
475    //                     .short('f')
476    //                     .long("force")
477    //                     .required(false)
478    //                     .action(SetArgTrue)
479    //                     .help("Force the application of the tag even if there are local modifications")
480    //                 )
481    //                 .arg(Arg::new("message")
482    //                     .short('m')
483    //                     .long("message")
484    //                     .required(false)
485    //                     .action(SetArg)
486    //                     .help("A message to be applied with the tag")
487    //                 )
488    //             )
489    //             .subcommand(Command::new("bom")
490    //                 .display_order(25)
491    //                 .about("View the active BOM in the current or given directory")
492    //                 .arg(Arg::new("dir")
493    //                     .action(SetArg)
494    //                     .help("The path to a directory (PWD will be used by default if not given)")
495    //                     .value_name("DIR")
496    //                 )
497    //             )
498    //     );
499
500    // //************************************************************************************/
501    // let new_help = "Create a new Origen application";
502    // origen_commands.push(CommandHelp {
503    //     name: "new".to_string(),
504    //     help: new_help.to_string(),
505    //     shortcut: None,
506    // });
507    // app = app.subcommand(
508    //     Command::new("new").about(new_help).arg(
509    //         Arg::new("name")
510    //             .help("The lowercased and underscored name of the new application")
511    //             .action(SetArg)
512    //             .required(true)
513    //             .number_of_values(1)
514    //             .value_name("NAME"),
515    //     )
516    //     .arg(Arg::new("setup")
517    //         .help("Don't create the new app's virtual environment after building (need to manually run 'origen env setup' within the new app workspace before using it in that case)")
518    //         .long("no-setup")
519    //         .required(false)
520    //         .action(SetArgTrue)
521    //     ),
522    // );
523    // }
524
525    commands::plugin::add_helps(&mut helps, plugins.as_ref());
526    commands::plugins::add_helps(&mut helps);
527    commands::aux_cmds::add_helps(&mut helps, &aux_cmds);
528    commands::eval::add_helps(&mut helps);
529    commands::exec::add_helps(&mut helps);
530    commands::credentials::add_helps(&mut helps);
531    commands::interactive::add_helps(&mut helps);
532
533    commands::env::add_helps(&mut helps);
534    if STATUS.is_app_present {
535        commands::app::add_helps(&mut helps, app_cmds.as_ref().unwrap());
536        commands::generate::add_helps(&mut helps);
537        commands::target::add_helps(&mut helps);
538        commands::save_ref::add_helps(&mut helps);
539        commands::web::add_helps(&mut helps);
540    } else {
541        commands::new::add_helps(&mut helps);
542    }
543
544    if STATUS.is_origen_present {
545        commands::develop_origen::add_helps(&mut helps);
546        commands::rc::add_helps(&mut helps);
547    }
548
549    helps.apply_exts(&extensions);
550
551    /************************************************************************************/
552    /******************** Global and app commands ***************************************/
553    /************************************************************************************/
554
555    // app = mailer::add_commands(app, &mut origen_commands)?;
556    app = commands::credentials::add_commands(app, &helps, &extensions)?;
557    app = commands::eval::add_commands(app, &helps, &extensions)?;
558    app = commands::exec::add_commands(app, &helps, &extensions)?;
559    app = commands::interactive::add_commands(app, &helps, &extensions)?;
560    app = commands::plugin::add_commands(app, &helps, plugins.as_ref(), &extensions)?;
561    app = commands::plugins::add_commands(app, &helps, &extensions)?;
562    app = commands::aux_cmds::add_commands(app, &helps, &aux_cmds, &extensions)?;
563
564    app = commands::env::add_commands(app, &helps, &extensions)?;
565    /************************************************************************************/
566    /******************** Origen dev commands *******************************************/
567    /************************************************************************************/
568    if STATUS.is_origen_present {
569        app = commands::develop_origen::add_commands(app, &helps, &extensions)?;
570        app = commands::rc::add_commands(app, &helps, &extensions)?;
571    }
572
573    /************************************************************************************/
574    /******************** In application commands ***************************************/
575    /************************************************************************************/
576    if STATUS.is_app_present {
577        app = commands::app::add_commands(app, &helps, app_cmds.as_ref().unwrap(), &extensions)?;
578        app = commands::generate::add_commands(app, &helps, &extensions)?;
579        app = commands::save_ref::add_commands(app, &helps, &extensions)?;
580        app = commands::web::add_commands(app, &helps, &extensions)?;
581
582        //         /************************************************************************************/
583        //         let new_help = "Generate a new block, flow, pattern, etc. for your application";
584        //         origen_commands.push(CommandHelp {
585        //             name: "new".to_string(),
586        //             help: new_help.to_string(),
587        //             shortcut: None,
588        //         });
589        //         app = app.subcommand(
590        //             Command::new("new")
591        //             .about(new_help)
592        //             .arg_required_else_help(true)
593        //             .subcommand(Command::new("dut")
594        //                 .display_order(5)
595        //                 .about("Create a new top-level (DUT) block, see 'origen new dut -h' for more info")
596        //                 .long_about(
597        // "This generator creates a top-level (DUT) block and all of the associated resources for it, e.g. a
598        // reg file, controller, target, timesets, pins, etc.
599
600        // The NAME of the DUT should be given in lower case, optionally prefixed by parent DUT name(s) separated
601        // by a forward slash.
602
603        // Any parent DUT(s) will be created if they don't exist, but they will not be modified if they do.
604
605        // Examples:
606        //   origen new dut                # Creates <app_name>/blocks/dut/...
607        //   origen new dut falcon         # Creates <app_name>/blocks/dut/derivatives/falcon/...
608        //   origen new dut dsp/falcon     # Creates <app_name>/blocks/dut/derivatives/dsp/derivatives/falcon/...")
609        //                 .arg(Arg::new("name")
610        //                     .action(SetArg)
611        //                     .required(false)
612        //                     .help("The name of the new DUT")
613        //                     .value_name("NAME")
614        //                 )
615        //             )
616        //             .subcommand(Command::new("block")
617        //                 .display_order(5)
618        //                 .about("Create a new block, see 'origen new block -h' for more info")
619        //                 .long_about(
620        // "This generator creates a block (e.g. to represent RAM, ATD, Flash, DAC, etc.) and all of the associated
621        // resources for it, e.g. a reg file, controller, timesets, etc.
622
623        // The NAME should be given in lower case (e.g. flash/flash2kb, adc/adc16), optionally with
624        // additional parent sub-block names after the initial type.
625
626        // Alternatively, a reference to an existing BLOCK can be added, in which case a nested block will be created
627        // within that block's 'blocks/' directory, rather than a primary top-level block.
628
629        // Any parent block(s) will be created if they don't exist, but they will not be modified if they do.
630
631        // Examples:
632        //   origen new block dac                  # Creates <app_name>/blocks/dac/...
633        //   origen new block adc/adc8bit          # Creates <app_name>/blocks/adc/derivatives/adc8bit/...
634        //   origen new block adc/adc16bit         # Creates <app_name>/blocks/adc/derivatives/adc16bit/...
635        //   origen new block nvm/flash/flash2kb   # Creates <app_name>/blocks/nvm/derivatives/flash/derivatives/flash2kb/...
636
637        //   # Example of creating a nested sub-block
638        //   origen new block bist --parent nvm/flash   # Creates <app_name>/blocks/nvm/derivatives/flash/blocks/bist/...")
639        //                 .arg(Arg::new("name")
640        //                     .action(SetArg)
641        //                     .required(true)
642        //                     .help("The name of the new block, including its parents if applicable")
643        //                     .value_name("NAME")
644        //                 )
645        //                 .arg(
646        //                     Arg::new("parent")
647        //                         .short('p')
648        //                         .long("parent")
649        //                         .help("Create the new block nested within this existing block")
650        //                         .action(SetArg)
651        //                         .required(false)
652        //                         .value_name("PARENT")
653        //                 )
654        //             )
655        //         );
656
657        //         /************************************************************************************/
658        //         let c_help = "Compile templates";
659        //         origen_commands.push(CommandHelp {
660        //             name: "compile".to_string(),
661        //             help: c_help.to_string(),
662        //             shortcut: Some("c".to_string()),
663        //         });
664        //         app = app.subcommand(
665        //             Command::new("compile")
666        //                 .about(c_help)
667        //                 .visible_alias("c")
668        //                 .arg(
669        //                     Arg::new("files")
670        //                         .help("The name of the file(s) to be generated")
671        //                         .action(AppendArgs)
672        //                         .value_name("FILES")
673        //                         .multiple(true)
674        //                         .required(true),
675        //                 )
676        //                 .arg(
677        //                     Arg::new("target")
678        //                         .short('t')
679        //                         .long("target")
680        //                         .help("Override the default target currently set by the workspace")
681        //                         .action(AppendArgs)
682        //                         .use_delimiter(true)
683        //                         .multiple(true)
684        //                         .number_of_values(1)
685        //                         .value_name("TARGET"),
686        //                 )
687        //                 .arg(
688        //                     Arg::new("mode")
689        //                         .short('m')
690        //                         .long("mode")
691        //                         .help("Override the default execution mode currently set by the workspace")
692        //                         .action(SetArg)
693        //                         .value_name("MODE"),
694        //                 ),
695        //         );
696
697        app = commands::target::add_commands(app, &helps, &extensions)?;
698
699    //         /************************************************************************************/
700    //         let t_help = "Create, Build, and View Web Documentation";
701    //         origen_commands.push(CommandHelp {
702    //             name: "web".to_string(),
703    //             help: t_help.to_string(),
704    //             shortcut: Some("w".to_string()),
705    //         });
706    //         app = app.subcommand(
707    //             Command::new("web")
708    //                 .about(t_help)
709    //                 .arg_required_else_help(true)
710    //                 .visible_alias("w")
711    //                 .subcommand(
712    //                     Command::new("build") // What I think this command should be called
713    //                         .about("Builds the web documentation")
714    //                         .visible_alias("b")
715    //                         .visible_alias("compile") // If coming from O1
716    //                         .visible_alias("html") // If coming from Sphinx and using quickstart's Makefile
717    //                         .arg(
718    //                             Arg::new("view")
719    //                                 .long("view")
720    //                                 .help("Launch your web browser after the build")
721    //                                 .action(SetArgTrue),
722    //                         )
723    //                         .arg(
724    //                             Arg::new("clean")
725    //                                 .long("clean")
726    //                                 .help(
727    //                                     "Clean up directories from previous builds and force a rebuild",
728    //                                 )
729    //                                 .action(SetArgTrue),
730    //                         )
731    //                         .arg(
732    //                             Arg::new("release")
733    //                                 .long("release")
734    //                                 .short('r')
735    //                                 .help("Release (deploy) the resulting web pages")
736    //                                 .action(SetArgTrue),
737    //                         )
738    //                         .arg(
739    //                             Arg::new("archive")
740    //                                 .long("archive")
741    //                                 .short('a')
742    //                                 .help("Archive the resulting web pages after building")
743    //                                 .action(SetArg)
744    //                                 .multiple(false)
745    //                                 .min_values(0),
746    //                         )
747    //                         .arg(
748    //                             Arg::new("as-release")
749    //                                 .long("as-release")
750    //                                 .help("Build webpages with release checks")
751    //                                 .action(SetArgTrue),
752    //                         )
753    //                         .arg(
754    //                             Arg::new("release-with-warnings")
755    //                                 .long("release-with-warnings")
756    //                                 .help("Release webpages even if warnings persists")
757    //                                 .action(SetArgTrue),
758    //                         )
759    //                         .arg(
760    //                             Arg::new("no-api")
761    //                                 .long("no-api")
762    //                                 .help("Skip building the API")
763    //                                 .action(SetArgTrue),
764    //                         )
765    //                         .arg(
766    //                             Arg::new("sphinx-args")
767    //                                 .long("sphinx-args")
768    //                                 .help(
769    //                                     "Additional arguments to pass to the 'sphinx-build' command
770    //   Argument will passed as a single string and appended to the build command
771    //   E.g.: 'origen web build --sphinx-args \"-q -D my_config_define=1\"'
772    //      -> 'sphinx-build <source_dir> <output_dir> -q -D my_config_define=1'",
773    //                                 )
774    //                                 .action(SetArg)
775    //                                 .multiple(false)
776    //                                 .allow_hyphen_values(true),
777    //                         ), // .arg(Arg::new("pdf")
778    //                            //     .long("pdf")
779    //                            //     .help("Create a PDF of resulting web pages")
780    //                            //     .action(SetArgTrue)
781    //                            // )
782    //                 )
783    //                 .subcommand(
784    //                     Command::new("view")
785    //                         .about("Launches your web browser to view previously built documentation")
786    //                         .visible_alias("v"),
787    //                 )
788    //                 .subcommand(
789    //                     Command::new("clean")
790    //                         .about("Cleans the output directory and all cached files"),
791    //                 ),
792    //         );
793
794    //         /************************************************************************************/
795    //         let mailer_help =
796    //             "Command-line-interface to Origen's mailer for quick emailing or shell-scripting";
797    //         origen_commands.push(CommandHelp {
798    //             name: "mailer".to_string(),
799    //             help: mailer_help.to_string(),
800    //             shortcut: None,
801    //         });
802    //         app = app.subcommand(
803    //             Command::new("mailer")
804    //                 .about(mailer_help)
805    //                 .arg_required_else_help(true)
806    //                 .subcommand(
807    //                     Command::new("send")
808    //                         .about("Quickly send an email")
809    //                         .arg(
810    //                             Arg::new("body")
811    //                                 .help("Email message body")
812    //                                 .long("body")
813    //                                 .action(SetArg)
814    //                                 .required(true)
815    //                                 .value_name("BODY")
816    //                                 .index(1),
817    //                         )
818    //                         .arg(
819    //                             Arg::new("subject")
820    //                                 .help("Email subject line")
821    //                                 .long("subject")
822    //                                 .short('s')
823    //                                 .action(SetArg)
824    //                                 .value_name("SUBJECT"),
825    //                         )
826    //                         .arg(
827    //                             Arg::new("to")
828    //                                 .help("Recipient list")
829    //                                 .long("to")
830    //                                 .short('t')
831    //                                 .action(AppendArgs)
832    //                                 .required(true)
833    //                                 .multiple(true)
834    //                                 .value_name("TO"),
835    //                         ),
836    //                 )
837    //                 .subcommand(
838    //                     Command::new("test")
839    //                         .about("Send a test email")
840    //                         .arg(
841    //                             Arg::new("to")
842    //                                 .help(
843    //                                     "Recipient list. If omitted, will be sent to the current user",
844    //                                 )
845    //                                 .long("to")
846    //                                 .short('t')
847    //                                 .action(AppendArgs)
848    //                                 .required(false)
849    //                                 .multiple(true)
850    //                                 .value_name("TO"),
851    //                         ),
852    //                 ),
853    //         );
854
855    //         /************************************************************************************/
856    //         let mode_help = "Set/view the default execution mode";
857    //         origen_commands.push(CommandHelp {
858    //             name: "mode".to_string(),
859    //             help: mode_help.to_string(),
860    //             shortcut: Some("m".to_string()),
861    //         });
862    //         app = app.subcommand(
863    //             Command::new("mode")
864    //                 .about(mode_help)
865    //                 .visible_alias("m")
866    //                 .arg(
867    //                     Arg::new("mode")
868    //                         .help("The name of the mode to be set as the default mode")
869    //                         .action(SetArg)
870    //                         .value_name("MODE"),
871    //                 ),
872    //         );
873    } else {
874        app = commands::new::add_commands(app, &helps, &extensions)?;
875    }
876
877    let mut all_cmds_and_aliases = vec![];
878    for subc in app.get_subcommands() {
879        all_cmds_and_aliases.push(subc.get_name().to_string());
880        for a in subc.get_all_aliases() {
881            all_cmds_and_aliases.push(a.to_string());
882        }
883    }
884
885    if let Some(a_cmds) = app_cmds.as_ref() {
886        for top_cmd in a_cmds.top_commands.iter() {
887            // TODO test that aliases vs. command names at the same level are safe (clap should fail earlier for this)
888            match app.try_get_matches_from_mut(["origen", top_cmd]) {
889                Ok(_) => {
890                    top_app_cmd_aliases.insert(top_cmd.to_string(), vec![top_cmd.to_string()]);
891                    top_app_replacements.push(["app", "commands", top_cmd]);
892                }
893                Err(e) => match e.kind() {
894                    ClapErrorKind::DisplayHelp
895                    | ClapErrorKind::DisplayHelpOnMissingArgumentOrSubcommand
896                    | ClapErrorKind::DisplayVersion
897                    | ClapErrorKind::InvalidSubcommand
898                    | ClapErrorKind::UnknownArgument => {
899                        top_app_cmd_aliases.insert(top_cmd.to_string(), vec![top_cmd.to_string()]);
900                        top_app_replacements.push(["app", "commands", top_cmd]);
901                    }
902                    _ => {}
903                },
904            }
905            let current_top_cmd_aliases = app
906                .find_subcommand("app")
907                .unwrap()
908                .find_subcommand("commands")
909                .unwrap()
910                .find_subcommand(top_cmd)
911                .unwrap()
912                .get_all_aliases()
913                .map(|a| a.to_string())
914                .collect::<Vec<String>>();
915            for a in current_top_cmd_aliases.iter() {
916                match app.try_get_matches_from_mut(["origen", a]) {
917                    Ok(_) => {
918                        if let Some(aliases) = top_app_cmd_aliases.get_mut(top_cmd) {
919                            aliases.push(a.to_string());
920                        } else {
921                            top_app_cmd_aliases.insert(top_cmd.to_string(), vec![a.to_string()]);
922                        }
923                    }
924                    Err(e) => match e.kind() {
925                        ClapErrorKind::DisplayHelp
926                        | ClapErrorKind::DisplayHelpOnMissingArgumentOrSubcommand
927                        | ClapErrorKind::DisplayVersion
928                        | ClapErrorKind::InvalidSubcommand
929                        | ClapErrorKind::UnknownArgument => {
930                            if let Some(aliases) = top_app_cmd_aliases.get_mut(top_cmd) {
931                                aliases.push(a.to_string());
932                            } else {
933                                top_app_cmd_aliases
934                                    .insert(top_cmd.to_string(), vec![a.to_string()]);
935                            }
936                        }
937                        _ => {}
938                    },
939                }
940            }
941        }
942
943        let mut strs = vec![];
944        if !top_app_cmd_aliases.is_empty() {
945            let mut len = 0;
946            for (n, aliases) in top_app_cmd_aliases.iter() {
947                for a in aliases.iter() {
948                    top_app_replacements.push(["app", "commands", a]);
949                }
950
951                let s = aliases.join(", ");
952                let l = s.len();
953                if l > len {
954                    len = l;
955                }
956                strs.push((s, l, n))
957            }
958            after_help_str += "APP COMMAND SHORTCUTS:\nThe following shortcuts to application commands are available:\n";
959            for s in strs.iter() {
960                after_help_str += &format!(
961                    "    {s}{:<w$} => {c}\n",
962                    "",
963                    w = (len - s.1),
964                    s = s.0,
965                    c = s.2
966                );
967            }
968            after_help_str += "\n";
969        }
970    }
971
972    if let Some(pls) = plugins.as_ref() {
973        for (n, pl) in pls.plugins.iter() {
974            for top_cmd in pl.top_commands.iter() {
975                if !all_cmds_and_aliases.contains(top_cmd) {
976                    if let Some(cmd_aliases) = top_pl_cmd_aliases.get_mut(n) {
977                        cmd_aliases.insert(top_cmd.to_string(), vec![top_cmd.to_string()]);
978                    } else {
979                        let mut pl_aliases = IndexMap::new();
980                        pl_aliases.insert(top_cmd.to_string(), vec![top_cmd.to_string()]);
981                        top_pl_cmd_aliases.insert(n.to_string(), pl_aliases);
982                        all_cmds_and_aliases.push(top_cmd.to_string());
983                    }
984                }
985
986                let current_top_cmd_aliases = app
987                    .find_subcommand("plugin")
988                    .unwrap()
989                    .find_subcommand(n)
990                    .unwrap()
991                    .find_subcommand(top_cmd)
992                    .unwrap()
993                    .get_all_aliases()
994                    .map(|a| a.to_string())
995                    .collect::<Vec<String>>();
996                for a in current_top_cmd_aliases.iter() {
997                    if !all_cmds_and_aliases.contains(a) {
998                        if let Some(pl_aliases) = top_pl_cmd_aliases.get_mut(n) {
999                            if let Some(cmd_aliases) = pl_aliases.get_mut(top_cmd) {
1000                                cmd_aliases.push(a.to_string());
1001                            } else {
1002                                pl_aliases.insert(top_cmd.to_string(), vec![a.to_string()]);
1003                            }
1004                        } else {
1005                            let mut pl_aliases = IndexMap::new();
1006                            pl_aliases.insert(top_cmd.to_string(), vec![a.to_string()]);
1007                            top_pl_cmd_aliases.insert(n.to_string(), pl_aliases);
1008                        }
1009                        all_cmds_and_aliases.push(a.to_string());
1010                    }
1011                }
1012            }
1013        }
1014
1015        let mut strs = vec![];
1016        if !top_pl_cmd_aliases.is_empty() {
1017            let mut len = 0;
1018            for (pln, pl_aliases) in top_pl_cmd_aliases.iter() {
1019                for (cmdn, cmda) in pl_aliases {
1020                    top_pl_replacements.push(["plugin", pln, cmdn]);
1021
1022                    let s = cmda.join(", ");
1023                    let l = s.len();
1024                    if l > len {
1025                        len = l;
1026                    }
1027                    strs.push((s, l, format!("{} {}", pln, cmdn)))
1028                }
1029            }
1030
1031            after_help_str += "PLUGIN COMMAND SHORTCUTS:\nThe following shortcuts to plugin commands are available:\n";
1032            for s in strs.iter() {
1033                after_help_str += &format!(
1034                    "    {s}{:<w$} => {c}\n",
1035                    "",
1036                    w = (len - s.1),
1037                    s = s.0,
1038                    c = s.2
1039                );
1040            }
1041            after_help_str += "\n";
1042        }
1043    }
1044
1045    for (n, ns) in aux_cmds.namespaces.iter() {
1046        for top_cmd in ns.top_commands.iter() {
1047            if !all_cmds_and_aliases.contains(top_cmd) {
1048                if let Some(cmd_aliases) = top_aux_cmd_aliases.get_mut(n) {
1049                    cmd_aliases.insert(top_cmd.to_string(), vec![top_cmd.to_string()]);
1050                } else {
1051                    let mut ns_aliases = IndexMap::new();
1052                    ns_aliases.insert(top_cmd.to_string(), vec![top_cmd.to_string()]);
1053                    top_aux_cmd_aliases.insert(n.to_string(), ns_aliases);
1054                }
1055                all_cmds_and_aliases.push(top_cmd.to_string());
1056            }
1057
1058            let current_top_cmd_aliases = app
1059                .find_subcommand("auxillary_commands")
1060                .unwrap()
1061                .find_subcommand(n)
1062                .unwrap()
1063                .find_subcommand(top_cmd)
1064                .unwrap()
1065                .get_all_aliases()
1066                .map(|a| a.to_string())
1067                .collect::<Vec<String>>();
1068            for a in current_top_cmd_aliases.iter() {
1069                if !all_cmds_and_aliases.contains(a) {
1070                    if let Some(ns_aliases) = top_aux_cmd_aliases.get_mut(n) {
1071                        if let Some(cmd_aliases) = ns_aliases.get_mut(top_cmd) {
1072                            cmd_aliases.push(a.to_string());
1073                        } else {
1074                            ns_aliases.insert(top_cmd.to_string(), vec![a.to_string()]);
1075                        }
1076                    } else {
1077                        let mut ns_aliases = IndexMap::new();
1078                        ns_aliases.insert(top_cmd.to_string(), vec![a.to_string()]);
1079                        top_aux_cmd_aliases.insert(n.to_string(), ns_aliases);
1080                    }
1081                    all_cmds_and_aliases.push(a.to_string());
1082                }
1083            }
1084        }
1085    }
1086    if !top_aux_cmd_aliases.is_empty() {
1087        let mut strs = vec![];
1088        let mut len = 0;
1089        for (auxn, aux_aliases) in top_aux_cmd_aliases.iter() {
1090            for (cmdn, cmda) in aux_aliases {
1091                top_aux_replacements.push(["auxillary_commands", auxn, cmdn]);
1092
1093                let s = cmda.join(", ");
1094                let l = s.len();
1095                if l > len {
1096                    len = l;
1097                }
1098                strs.push((s, l, format!("{} {}", auxn, cmdn)))
1099            }
1100        }
1101
1102        after_help_str += "AUX COMMAND SHORTCUTS:\nThe following shortcuts to auxillary commands are available:\n";
1103        for s in strs.iter() {
1104            after_help_str += &format!(
1105                "    {s}{:<w$} => {c}\n",
1106                "",
1107                w = (len - s.1),
1108                s = s.0,
1109                c = s.2
1110            );
1111        }
1112        after_help_str += "\n";
1113    }
1114
1115    after_help_str += "See 'origen <command> -h' for more information on a specific command.";
1116    app = app.after_help(after_help_str.clone());
1117
1118    // Clap 3's experimental `Command::replace` supported shortcuts which
1119    // expanded a top-level token into a nested command path. Clap 4 removed
1120    // that API, so normalize argv explicitly before parsing while preserving
1121    // the same externally visible shortcut behavior.
1122    let mut invocation_args: Vec<std::ffi::OsString> = std::env::args_os().collect();
1123    // The command may be preceded by value-less global flags ('origen -v <cmd>'
1124    // parses fine for a full command path, so a shortcut has to accept it too).
1125    // Only flags that are known to take no value are skipped; anything else
1126    // stops the scan so an option's value can never be mistaken for a command.
1127    fn is_valueless_global_flag(arg: &str) -> bool {
1128        matches!(arg, "--verbose" | "--no_targets" | "--no_target")
1129            || (arg.len() > 1
1130                && arg.starts_with('-')
1131                && !arg.starts_with("--")
1132                && arg[1..].chars().all(|c| c == 'v'))
1133    }
1134
1135    let command_position = {
1136        let mut position = None;
1137        for (index, arg) in invocation_args.iter().enumerate().skip(1) {
1138            match arg.to_str() {
1139                Some(text) if is_valueless_global_flag(text) => continue,
1140                Some(_) => {
1141                    position = Some(index);
1142                    break;
1143                }
1144                None => break,
1145            }
1146        }
1147        position
1148    };
1149    if let Some(requested) = command_position
1150        .and_then(|index| invocation_args.get(index))
1151        .and_then(|arg| arg.to_str())
1152    {
1153        if let Some(replacement) = top_app_replacements
1154            .iter()
1155            .chain(top_pl_replacements.iter())
1156            .chain(top_aux_replacements.iter())
1157            .find(|replacement| replacement[2] == requested)
1158        {
1159            let index = command_position.unwrap();
1160            invocation_args.splice(
1161                index..index + 1,
1162                replacement
1163                    .iter()
1164                    .map(|part| std::ffi::OsString::from(part)),
1165            );
1166        }
1167    }
1168    let matches = app.clone().get_matches_from(invocation_args);
1169
1170    macro_rules! run_cmd_match_case {
1171        ($cmd:ident, $cmd_name:ident) => {
1172            commands::$cmd::run(
1173                matches
1174                    .subcommand_matches(commands::$cmd::$cmd_name)
1175                    .unwrap(),
1176                &app,
1177                &extensions,
1178                plugins.as_ref(),
1179            )?
1180        };
1181        ($cmd:ident) => {
1182            commands::$cmd::run(
1183                matches
1184                    .subcommand_matches(commands::$cmd::BASE_CMD)
1185                    .unwrap(),
1186                &app,
1187                &extensions,
1188                plugins.as_ref(),
1189            )?
1190        };
1191    }
1192
1193    macro_rules! run_non_ext_cmd_match_case {
1194        ($cmd:ident, $cmd_name:ident) => {
1195            commands::$cmd::run(
1196                matches
1197                    .subcommand_matches(commands::$cmd::$cmd_name)
1198                    .unwrap(),
1199            )?
1200        };
1201        ($cmd:ident) => {
1202            commands::$cmd::run(
1203                matches
1204                    .subcommand_matches(commands::$cmd::BASE_CMD)
1205                    .unwrap(),
1206            )?
1207        };
1208    }
1209
1210    match matches.subcommand_name() {
1211        Some(commands::app::BASE_CMD) => commands::app::run(
1212            matches.subcommand_matches(commands::app::BASE_CMD).unwrap(),
1213            &app,
1214            &extensions,
1215            plugins.as_ref(),
1216            &app_cmds.as_ref().unwrap(),
1217        )?,
1218        Some(commands::new::BASE_CMD) => run_non_ext_cmd_match_case!(new),
1219        // Some("proj") => commands::proj::run(matches.subcommand_matches("proj").unwrap()),
1220        Some(commands::env::BASE_CMD) => run_non_ext_cmd_match_case!(env),
1221        Some(commands::eval::BASE_CMD) => run_cmd_match_case!(eval),
1222        Some(commands::develop_origen::BASE_CMD) => run_non_ext_cmd_match_case!(develop_origen),
1223        Some(commands::rc::BASE_CMD) => run_non_ext_cmd_match_case!(rc),
1224        Some(commands::interactive::BASE_CMD) => run_cmd_match_case!(interactive),
1225        Some(commands::aux_cmds::BASE_CMD) => commands::aux_cmds::run(
1226            matches
1227                .subcommand_matches(commands::aux_cmds::BASE_CMD)
1228                .unwrap(),
1229            &app,
1230            &extensions,
1231            plugins.as_ref(),
1232            &aux_cmds,
1233        )?,
1234        Some(commands::generate::BASE_CMD) => run_cmd_match_case!(generate),
1235        // Some("compile") => {
1236        //     let m = matches.subcommand_matches("compile").unwrap();
1237        //     commands::launch(
1238        //         "compile",
1239        //         if let Some(targets) = m.get_many::<String>("target") {
1240        //             Some(targets.map(|t| t.as_str()).collect())
1241        //         } else {
1242        //             Option::None
1243        //         },
1244        //         &m.get_one::<&str>("mode").map(|s| *s),
1245        //         Some(m.get_many::<String>("files").unwrap().map(|t| t.as_str()).collect()),
1246        //         m.get_one::<&str>("output_dir").map(|s| *s),
1247        //         m.get_one::<&str>("reference_dir").map(|s| *s),
1248        //         false,
1249        //         None,
1250        //     );
1251        // }
1252        Some(commands::target::BASE_CMD) => run_non_ext_cmd_match_case!(target),
1253        Some(commands::web::BASE_CMD) => run_cmd_match_case!(web),
1254        // Some("web") => {
1255        //     let cmd = matches.subcommand_matches("web").unwrap();
1256        //     let subcmd = cmd.subcommand().unwrap();
1257        //     let sub = subcmd.1;
1258        //     match subcmd.0 {
1259        //         "build" => {
1260        //             let mut args = IndexMap::new();
1261        //             if sub.contains_id("view") {
1262        //                 args.insert("view", "True".to_string());
1263        //             }
1264        //             if sub.contains_id("clean") {
1265        //                 args.insert("clean", "True".to_string());
1266        //             }
1267        //             if sub.contains_id("no-api") {
1268        //                 args.insert("no-api", "True".to_string());
1269        //             }
1270        //             if sub.contains_id("as-release") {
1271        //                 args.insert("as-release", "True".to_string());
1272        //             }
1273        //             if sub.contains_id("release-with-warnings") {
1274        //                 args.insert("release-with-warnings", "True".to_string());
1275        //             }
1276        //             if sub.contains_id("release") {
1277        //                 args.insert("release", "True".to_string());
1278        //             }
1279        //             if sub.contains_id("archive") {
1280        //                 if let Some(archive) = sub.get_one::<&str>("archive") {
1281        //                     args.insert("archive", format!("'{}'", archive));
1282        //                 } else {
1283        //                     args.insert("archive", "True".to_string());
1284        //                 }
1285        //             }
1286        //             if let Some(s_args) = sub.get_one::<&str>("sphinx-args") {
1287        //                 // Recall that this comes in as a single argument, potentially quoted to mimic multiple,
1288        //                 // but a single argument from the perspective here nonetheless
1289        //                 args.insert("sphinx-args", format!("'{}'", s_args));
1290        //             }
1291        //             commands::launch(
1292        //                 "web:build",
1293        //                 if let Some(targets) = cmd.get_many::<String>("target") {
1294        //                     Some(targets.map(|t| t.as_str()).collect())
1295        //                 } else {
1296        //                     Option::None
1297        //                 },
1298        //                 &None,
1299        //                 None,
1300        //                 None,
1301        //                 None,
1302        //                 false,
1303        //                 Some(args),
1304        //             )
1305        //         }
1306        //         "view" => commands::launch("web:view", None, &None, None, None, None, false, None),
1307        //         "clean" => {
1308        //             commands::launch("web:clean", None, &None, None, None, None, false, None)
1309        //         }
1310        //         _ => {}
1311        //     }
1312        // }
1313        // Some("mailer") => {
1314        //     let cmd = matches.subcommand_matches("mailer").unwrap();
1315        //     let subcmd = cmd.subcommand().unwrap();
1316        //     let sub = subcmd.1;
1317        //     match subcmd.0 {
1318        //         "send" => {
1319        //             let mut args = IndexMap::new();
1320        //             if let Some(t) = sub.get_many::<String>("to") {
1321        //                 let r = t.map(|x| format!("\"{}\"", x)).collect::<Vec<String>>();
1322        //                 args.insert("to", format!("[{}]", r.join(",")));
1323        //             }
1324        //             if let Some(s) = sub.get_one::<&str>("subject") {
1325        //                 args.insert("subject", format!("\"{}\"", s));
1326        //             }
1327        //             if let Some(b) = sub.get_one::<&str>("body") {
1328        //                 args.insert("body", format!("\"{}\"", b));
1329        //             }
1330
1331        //             commands::launch(
1332        //                 "mailer:send",
1333        //                 if let Some(targets) = cmd.get_many::<String>("target") {
1334        //                     Some(targets.map(|t| t.as_str()).collect())
1335        //                 } else {
1336        //                     Option::None
1337        //                 },
1338        //                 &None,
1339        //                 None,
1340        //                 None,
1341        //                 None,
1342        //                 false,
1343        //                 Some(args),
1344        //             )
1345        //         }
1346        //         "test" => {
1347        //             let mut args = IndexMap::new();
1348        //             if let Some(t) = sub.get_many::<String>("to") {
1349        //                 let r = t.map(|x| format!("\"{}\"", x)).collect::<Vec<String>>();
1350        //                 args.insert("to", format!("[{}]", r.join(",")));
1351        //             }
1352        //             commands::launch(
1353        //                 "mailer:test",
1354        //                 if let Some(targets) = cmd.get_many::<String>("target") {
1355        //                     Some(targets.map(|t| t.as_str()).collect())
1356        //                 } else {
1357        //                     Option::None
1358        //                 },
1359        //                 &None,
1360        //                 None,
1361        //                 None,
1362        //                 None,
1363        //                 false,
1364        //                 Some(args),
1365        //             )
1366        //         }
1367        //         _ => {}
1368        //     }
1369        // }
1370        Some(commands::credentials::BASE_CMD) => run_cmd_match_case!(credentials),
1371        // Some("mode") => {
1372        //     let matches = matches.subcommand_matches("mode").unwrap();
1373        //     commands::mode::run(matches.get_one::<&str>("mode").map(|s| *s));
1374        // }
1375        Some(commands::save_ref::BASE_CMD) => {
1376            let matches = matches
1377                .subcommand_matches(commands::save_ref::BASE_CMD)
1378                .unwrap();
1379            commands::save_ref::run(matches)?;
1380        }
1381        Some(commands::plugin::BASE_CMD) => run_cmd_match_case!(plugin),
1382        Some(commands::plugins::BASE_CMD) => commands::plugins::run(
1383            matches
1384                .subcommand_matches(commands::plugins::BASE_CMD)
1385                .unwrap(),
1386            plugins.as_ref(),
1387        )?,
1388        Some(invalid_cmd) => {
1389            // This case shouldn't happen as clap should've previously kicked out on any invalid command
1390            unreachable!("Uncaught invalid command encountered: '{}'", invalid_cmd);
1391        }
1392        None => {
1393            if print_help {
1394                // No subcommands or "-v" used, but verbose and/or vks used.
1395                // This will register as no subcommand, but actually want to display help, not version
1396                app.print_help()?;
1397                return Ok(());
1398            }
1399            // To get here means the user has typed "origen -v", which officially means
1400            // verbosity level 1 with no command, but really want version with verbosity level 0
1401            let mut max_len = 6; //'Origen' by default
1402            let mut versions: IndexMap<String, (bool, bool, String)> = IndexMap::new();
1403
1404            let cmd = "from origen.boot import run_cmd; run_cmd('_version_');";
1405            let mut output_lines = "".to_string();
1406            let mut error_lines = "".to_string();
1407
1408            let res = python::run_with_callbacks(
1409                cmd,
1410                Some(&mut |line| {
1411                    output_lines += &format!("{}\n", line);
1412                }),
1413                Some(&mut |line| {
1414                    error_lines += &format!("{}\n", line);
1415                }),
1416            );
1417            output_lines.pop();
1418            match res {
1419                Ok(_) => {
1420                    let lines = output_lines.split("\n").collect::<Vec<&str>>();
1421                    if lines.len() == 0 || lines.len() == 1 {
1422                        log_error!(
1423                            "Unable to parse in-application version output. Expected newlines:"
1424                        );
1425                        log_error!("{}", output_lines);
1426                    } else {
1427                        let mut phase = 0;
1428                        let mut current = "".to_string();
1429                        let mut is_private = false;
1430                        let mut is_okay = false;
1431                        let mut ver_or_message = "".to_string();
1432                        for l in lines {
1433                            if phase == 0 {
1434                                let ver = parse_version_token(l);
1435                                current = ver.0;
1436                                is_private = ver.1;
1437                                if !is_private && current.len() > max_len {
1438                                    max_len = current.len();
1439                                }
1440                                phase += 1;
1441                            } else if phase == 1 {
1442                                match origen::utility::status_to_bool(l) {
1443                                    Ok(stat) => is_okay = stat,
1444                                    Err(e) => {
1445                                        log_error!("{}", e.msg);
1446                                        log_error!("Unable to parse version information");
1447                                        break;
1448                                    }
1449                                }
1450                                phase += 1;
1451                            } else if phase == 2 {
1452                                match l.chars().next() {
1453                                    Some(t) => {
1454                                        if t == '\t' {
1455                                            ver_or_message += &l[1..];
1456                                        } else {
1457                                            versions.insert(
1458                                                current.to_string(),
1459                                                (is_okay, is_private, ver_or_message.to_string()),
1460                                            );
1461                                            let ver = parse_version_token(l);
1462                                            current = ver.0;
1463                                            is_private = ver.1;
1464                                            if !is_private && current.len() > max_len {
1465                                                max_len = current.len();
1466                                            }
1467                                            ver_or_message = "".to_string();
1468                                            phase = 1;
1469                                        }
1470                                    }
1471                                    None => {
1472                                        log_error!("Unable to parse in-application version output - unexpected empty line:");
1473                                        log_error!("{}", output_lines);
1474                                    }
1475                                }
1476                            } else {
1477                                log_error!("Unable to parse in-application version output:");
1478                                log_error!("{}", output_lines);
1479                            }
1480                        }
1481
1482                        if phase == 2 {
1483                            versions.insert(
1484                                current.clone(),
1485                                (is_okay, is_private, ver_or_message.clone()),
1486                            );
1487                        } else {
1488                            log_error!("Unable to parse in-application version output - unexpected format:");
1489                            log_error!("{}", output_lines);
1490                        }
1491                    }
1492                    versions.insert(
1493                        "CLI".to_string(),
1494                        (
1495                            true,
1496                            STATUS.is_app_present,
1497                            STATUS.cli_version().unwrap().to_pep440()?.to_string(),
1498                        ),
1499                    );
1500                }
1501                Err(_e) => {
1502                    if error_lines.contains(*python::NO_ORIGEN_BOOT_MODULE_ERROR) {
1503                        // Module not found error
1504                        if STATUS.is_app_present {
1505                            // Inside an app. This is problematic - origen should be available here
1506                            for err in error_lines.lines() {
1507                                log_error!("{}", err);
1508                            }
1509
1510                            versions.insert(
1511                                "Origen".to_string(),
1512                                (true, false, "No Origen Module Available".to_string()),
1513                            );
1514                            versions.insert(
1515                                "App".to_string(),
1516                                (
1517                                    true,
1518                                    false,
1519                                    "Unable To Parse Version Information".to_string(),
1520                                ),
1521                            );
1522                        } else {
1523                            // Outside of an app
1524                            // If the CLI only is used, this will be expected.
1525                            // In this case, treat it as info, not an error.
1526                            // Log the error for verbose output though.
1527                            for err in error_lines.lines() {
1528                                log_debug!("{}", err);
1529                            }
1530
1531                            versions.insert(
1532                                "Origen".to_string(),
1533                                (true, false, "No Origen Module Available".to_string()),
1534                            );
1535                        }
1536                    } else {
1537                        // Unrecognized error
1538                        for err in error_lines.lines() {
1539                            log_error!("{}", err);
1540                        }
1541                        versions.insert(
1542                            "Origen".to_string(),
1543                            (
1544                                true,
1545                                false,
1546                                "Errors Encountered Retrieving Origen Version Info".to_string(),
1547                            ),
1548                        );
1549                        if STATUS.is_app_present {
1550                            versions.insert(
1551                                "App".to_string(),
1552                                (
1553                                    true,
1554                                    false,
1555                                    "Unable To Parse Version Information".to_string(),
1556                                ),
1557                            );
1558                        }
1559                    }
1560                    versions.insert(
1561                        "CLI".to_string(),
1562                        (
1563                            true,
1564                            STATUS.is_app_present,
1565                            STATUS.cli_version().unwrap().to_pep440()?.to_string(),
1566                        ),
1567                    );
1568                }
1569            }
1570
1571            for (n, v) in versions.iter() {
1572                if v.0 == true {
1573                    if v.1 == true {
1574                        log_debug!("{}: {}", n, v.2);
1575                    } else {
1576                        println!("{}: {}{}", n, " ".repeat(max_len - n.len()), v.2);
1577                    }
1578                } else {
1579                    log_error!("Errors encountered retrieving version info for '{}':", n);
1580                    log_error!("{}", v.2);
1581                }
1582            }
1583        }
1584    }
1585    Ok(())
1586}
1587
1588fn parse_version_token(input: &str) -> (String, bool) {
1589    let chars = input.chars().collect::<Vec<char>>();
1590    if chars.len() > 2 {
1591        if chars[0] == '_' && chars[1] == ' ' {
1592            (String::from_iter(chars[2..].iter()), true)
1593        } else {
1594            (input.to_string(), false)
1595        }
1596    } else {
1597        (input.to_string(), false)
1598    }
1599}