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}