1pub mod app_cmds;
2pub mod aux_cmds;
3pub mod cmd_gen_helpers;
4pub mod core_cmds;
5pub mod extensions;
6pub mod helps;
7pub mod plugins;
8
9use origen_metal::indexmap::IndexMap;
10use std::collections::HashMap;
11
12pub use app_cmds::AppCmds;
13pub use aux_cmds::AuxCmds;
14pub use extensions::{Extension, ExtensionTOML, Extensions};
15pub use helps::{CmdHelps, CmdSrc};
16pub use plugins::Plugins;
17use std::env;
18
19use crate::commands::_prelude::clap_arg_actions::*;
20use clap::Arg as ClapArg;
21use clap::Command as App;
22use clap::Command as ClapCommand;
23use origen::{in_app_invocation, Result};
24
25#[macro_export]
26macro_rules! uses_reserved_prefix {
27 ($q:expr) => {{
28 $q.starts_with(crate::framework::extensions::EXT_BASE_PREFIX)
29 }};
30}
31
32#[macro_export]
33macro_rules! err_processing_cmd_preface {
34 ($func:ident, $cmd_path:expr, $msg:expr, $($arg:expr),* $(,)?) => {{
35 $func!(concat!("When processing command '{}': ", $msg), $cmd_path, $($arg),*)
36 }}
37}
38
39#[macro_export]
40macro_rules! log_err_processing_cmd {
41 ($cmd_path:expr, $msg:expr, $($arg:expr),* $(,)?) => {{
42 crate::err_processing_cmd_preface!(log_error, $cmd_path, $msg, $($arg),*)
43 }};
44}
45
46#[macro_export]
47macro_rules! from_toml_args {
48 ($toml_args: expr, $cmd_path: expr) => {{
49 let mut current_names: Vec<Option<&str>> = vec!();
50 let mut variadic_arg: Option<&str> = None;
54 $toml_args.as_ref()
55 .map(|args| args.iter()
56 .filter_map( |a| {
57 if let Some(i) = current_names.iter().position( |n| *n == Some(&a.name)) {
58 crate::log_err_processing_cmd!(
59 $cmd_path,
60 "Argument '{}' is already present. Subsequent occurrences will be skipped (first occurrence at index {})",
61 &a.name,
62 i
63 );
64 current_names.push(None);
65 None
66 } else if crate::uses_reserved_prefix!(a.name) { crate::log_err_processing_cmd!(
68 $cmd_path,
69 "Argument '{}' uses reserved prefix '{}'. This option will not be available.",
70 &a.name,
71 crate::framework::extensions::EXT_BASE_NAME
72 );
73 current_names.push(None);
74 None
75 } else if variadic_arg.is_some() && a.required != Some(true) {
76 crate::log_err_processing_cmd!(
77 $cmd_path,
78 "Argument '{}' follows multi-value argument '{}' and is not required, so it could never receive a value. This argument will not be available; mark it as required, or declare it before '{}'.",
79 &a.name,
80 variadic_arg.unwrap(),
81 variadic_arg.unwrap()
82 );
83 current_names.push(None);
84 None
85 } else {
86 current_names.push(Some(&a.name));
87 if a.multiple == Some(true) || a.use_delimiter == Some(true) {
88 variadic_arg = Some(&a.name);
89 }
90 Some(crate::framework::Arg::from_toml(a))
91 }
92 })
93 .collect::<Vec<crate::framework::Arg>>())
94 .map(|mut args| {
95 crate::framework::promote_leading_positionals(&mut args, $cmd_path);
96 args
97 })
98 }}
99}
100
101pub(crate) fn promote_leading_positionals(args: &mut Vec<Arg>, cmd_path: &str) {
109 let last_required = args.iter().rposition(|a| a.required == Some(true));
110 if let Some(last_required) = last_required {
111 for arg in args[..last_required].iter_mut() {
112 if arg.required != Some(true) {
113 crate::log_err_processing_cmd!(
114 cmd_path,
115 "Argument '{}' is optional but precedes a required argument, which cannot be parsed unambiguously. It will be treated as required; declare it after the required arguments to keep it optional.",
116 &arg.name
117 );
118 arg.required = Some(true);
119 }
120 }
121 }
122}
123
124#[macro_export]
125macro_rules! from_toml_opts {
126 ($toml_opts: expr, $cmd_path: expr) => {
127 crate::from_toml_opts!($toml_opts, $cmd_path, None::<&str>)
128 };
129 ($toml_opts: expr, $cmd_path: expr, $ext_from: expr) => {{
130 let mut current_names: Vec<Option<&str>> = vec!();
131 $toml_opts.as_ref()
132 .map(|opts| opts.iter()
133 .filter_map( |o| {
134 if let Some(i) = current_names.iter().position( |n| *n == Some(&o.name)) {
135 if let Some(ext) = $ext_from {
136 crate::log_err_processing_cmd!(
137 $cmd_path,
138 "Option '{}' extended from {} is already present. Subsequent occurrences will be skipped (first occurrence at index {})",
139 &o.name,
140 ext,
141 i
142 );
143 } else {
144 crate::log_err_processing_cmd!(
145 $cmd_path,
146 "Option '{}' is already present. Subsequent occurrences will be skipped (first occurrence at index {})",
147 &o.name,
148 i
149 );
150 }
151 current_names.push(None);
152 None
153 } else if crate::uses_reserved_prefix!(o.name) {
154 if let Some(ext) = $ext_from {
155 crate::log_err_processing_cmd!(
156 $cmd_path,
157 "Option '{}' extended from {} uses reserved prefix '{}'. This option will not be available",
158 &o.name,
159 ext,
160 crate::framework::extensions::EXT_BASE_NAME
161 );
162 } else {
163 crate::log_err_processing_cmd!(
164 $cmd_path,
165 "Option '{}' uses reserved prefix '{}'. This option will not be available",
166 &o.name,
167 crate::framework::extensions::EXT_BASE_NAME
168 );
169 }
170 current_names.push(None);
171 None
172 } else {
173 current_names.push(Some(&o.name));
174 Some(crate::framework::Opt::from_toml(o, $cmd_path, $ext_from))
175 }
176 })
177 .collect::<Vec<crate::framework::Opt>>())
178 }}
179}
180
181pub trait Applies {
182 fn in_app_context(&self) -> Option<bool>;
183 fn in_global_context(&self) -> Option<bool>;
184 fn on_env(&self) -> Option<&Vec<String>>;
185 fn on_env_error_msg(&self, e: &String) -> String;
186
187 fn applies(&self) -> Result<bool> {
188 Ok(self.applies_with_env()? && self.applies_in_context()?)
189 }
190
191 fn applies_in_app_context(&self) -> Result<bool> {
192 Ok(self.in_app_context().unwrap_or(true))
193 }
194
195 fn applies_in_global_context(&self) -> Result<bool> {
196 Ok(self.in_global_context().unwrap_or(true))
197 }
198
199 fn applies_in_context(&self) -> Result<bool> {
200 if in_app_invocation() {
201 self.applies_in_app_context()
202 } else {
203 self.applies_in_global_context()
204 }
205 }
206
207 fn applies_with_env(&self) -> Result<bool> {
208 if let Some(envs) = self.on_env() {
209 for e in envs {
210 let mut s = e.splitn(1, '=');
211 let e_name = s.next().ok_or_else(|| self.on_env_error_msg(e))?.trim();
212 let e_val = s.next();
213 match env::var(e_name) {
214 Ok(val) => {
215 if let Some(v) = e_val {
216 if v == val {
217 return Ok(true);
218 }
219 } else {
220 return Ok(true);
221 }
222 }
223 Err(err) => match err {
224 env::VarError::NotPresent => {}
225 _ => {
226 return Err(err.into());
227 }
228 },
229 }
230 }
231 Ok(false)
232 } else {
233 Ok(true)
234 }
235 }
236}
237
238#[derive(Debug, Deserialize)]
239pub(crate) struct CommandsToml {
240 pub command: Option<Vec<CommandTOML>>,
241 pub extension: Option<Vec<ExtensionTOML>>,
242}
243
244#[derive(Debug, Deserialize, Clone)]
245pub struct CommandTOML {
246 pub name: String,
247 pub help: String,
248 pub alias: Option<String>,
249 pub arg: Option<Vec<ArgTOML>>,
250 pub opt: Option<Vec<OptTOML>>,
251 pub subcommand: Option<Vec<CommandTOML>>,
252 pub add_target_opt: Option<bool>,
253 pub add_mode_opt: Option<bool>,
254 pub in_global_context: Option<bool>,
255 pub in_app_context: Option<bool>,
256 pub on_env: Option<Vec<String>>,
257}
258
259#[derive(Debug)]
260pub struct Command {
261 pub name: String,
262 pub help: String,
263 pub alias: Option<String>,
264 pub args: Option<Vec<Arg>>,
265 pub opts: Option<Vec<Opt>>,
266 pub subcommands: Option<Vec<String>>,
267 pub add_mode_opt: Option<bool>,
268 pub add_target_opt: Option<bool>,
269 pub cmd_path: CmdSrc,
270 pub in_global_context: Option<bool>,
271 pub in_app_context: Option<bool>,
272 pub on_env: Option<Vec<String>>,
273}
274
275impl Command {
276 pub fn from_toml_cmd(
277 cmd: &CommandTOML,
278 cmd_path: CmdSrc,
279 parent_cmd: Option<&Self>,
280 ) -> Result<Option<Self>> {
281 let mut slf = Self {
282 name: cmd.name.to_owned(),
283 help: cmd.help.to_owned(),
284 alias: cmd.alias.to_owned(),
285 args: None,
286 opts: None,
287 subcommands: None,
288 add_mode_opt: cmd.add_mode_opt.or_else(|| {
289 if let Some(p) = parent_cmd {
290 p.add_mode_opt.to_owned()
291 } else {
292 None
293 }
294 }),
295 add_target_opt: cmd.add_target_opt.or_else(|| {
296 if let Some(p) = parent_cmd {
297 p.add_target_opt.to_owned()
298 } else {
299 None
300 }
301 }),
302 cmd_path: cmd_path,
303 in_global_context: cmd.in_global_context,
304 in_app_context: cmd.in_app_context,
305 on_env: cmd.on_env.to_owned(),
306 };
307 if !slf.applies()? {
308 return Ok(None);
309 }
310 let fp = slf.cmd_path.to_string();
311 slf.args = from_toml_args!(cmd.arg, &fp);
312 slf.opts = from_toml_opts!(cmd.opt, &fp);
313 if let Some(args) = slf.args.as_ref() {
314 if let Some(opts) = slf.opts.as_mut() {
315 opts.retain(|o| {
316 if let Some(idx) = args.iter().position(|a| a.name == o.name) {
317 crate::log_err_processing_cmd!(
318 &fp,
319 "Option '{}' conflicts with Arg of the same name (Arg #{})",
320 o.name,
321 idx,
322 );
323 false
324 } else {
325 true
326 }
327 });
328 }
329 }
330 slf.subcommands = cmd.subcommand.as_ref().map(|sub_cmds| {
331 sub_cmds
332 .iter()
333 .map(|c| format!("{}.{}", &slf.offset_path(), &c.name.to_string()))
334 .collect::<Vec<String>>()
335 });
336 Ok(Some(slf))
337 }
338
339 pub fn add_mode_opt(&self) -> bool {
340 self.add_mode_opt.unwrap_or(true)
341 }
342
343 pub fn add_target_opt(&self) -> bool {
344 self.add_target_opt.unwrap_or(true)
345 }
346
347 pub fn offset_path(&self) -> &str {
348 self.cmd_path.offset_path()
349 }
350}
351
352impl Applies for Command {
353 fn in_global_context(&self) -> Option<bool> {
354 self.in_global_context
355 }
356
357 fn in_app_context(&self) -> Option<bool> {
358 self.in_app_context
359 }
360
361 fn on_env(&self) -> Option<&Vec<String>> {
362 self.on_env.as_ref()
363 }
364
365 fn on_env_error_msg(&self, e: &String) -> String {
366 format!(
367 "Failed to parse 'on_env' '{}', for command {}",
368 e,
369 self.offset_path()
370 )
371 }
372}
373
374#[derive(Debug)]
375pub struct CmdOptCache {
376 opt_names: Vec<String>,
377 lns: HashMap<String, (bool, usize)>,
378 ilns: HashMap<String, (bool, usize)>,
379 ln_aliases: HashMap<String, (bool, usize)>,
380 sns: HashMap<char, (bool, usize)>,
381 sn_aliases: HashMap<char, (bool, usize)>,
382 ext_opt_names: IndexMap<String, usize>,
383 exts: Vec<String>,
384 cmd_path: String,
385 last_needs_visible_full_name: bool,
386 current: String,
387}
388
389macro_rules! processing_exts {
390 ($slf:expr) => {{
391 $slf.exts.len() > 0
392 }};
393}
394
395macro_rules! conflict_err_msg {
396 ($self:expr, $conflict:expr, $name:expr, $conflict_type:expr, $with_type:expr) => {{
397 if processing_exts!($self) {
398 if $conflict.0 {
399 log_err_processing_cmd!(
401 $self.cmd_path,
402 concat!(
403 $conflict_type,
404 " '{}' for extension option '{}', from {}, conflicts with ",
405 $with_type,
406 " from command option '{}'"
407 ),
408 $name,
409 $self.current,
410 $self.exts.last().unwrap(),
411 $self.opt_names[$conflict.1]
412 );
413 } else {
414 let e = $self.ext_opt_names.get_index($conflict.1).unwrap();
416 log_err_processing_cmd!(
417 $self.cmd_path,
418 concat!(
419 $conflict_type,
420 " '{}' for extension option '{}', from {}, conflicts with ",
421 $with_type,
422 " for extension '{}' provided by {}"
423 ),
424 $name,
425 $self.current,
426 $self.exts.last().unwrap(),
427 e.0,
428 $self.exts[*e.1]
429 );
430 }
431 } else {
432 log_err_processing_cmd!(
433 $self.cmd_path,
434 concat!(
435 $conflict_type,
436 " '{}' for command option '{}' conflicts with ",
437 $with_type,
438 " from option '{}'"
439 ),
440 $name,
441 $self.current,
442 $self.opt_names[$conflict.1]
443 );
444 }
445 }};
446}
447
448macro_rules! cache {
449 ($slf:expr, $cache:ident, $to_cache:expr) => {{
450 $slf.$cache.insert($to_cache, {
451 if processing_exts!($slf) {
452 (false, $slf.ext_opt_names.len() - 1)
453 } else {
454 (true, $slf.opt_names.len() - 1)
455 }
456 });
457 }};
458}
459
460impl CmdOptCache {
461 pub fn new(cmd_path: String) -> Self {
462 let slf = Self {
463 opt_names: Vec::new(),
464 lns: HashMap::new(),
465 ilns: HashMap::new(),
466 ln_aliases: HashMap::new(),
467 sns: HashMap::new(),
468 sn_aliases: HashMap::new(),
469 ext_opt_names: IndexMap::new(),
470 exts: Vec::new(),
471 cmd_path: cmd_path,
472 last_needs_visible_full_name: true,
473 current: "".to_string(),
474 };
475 slf
476 }
477
478 pub fn unchecked_populated(cmd: &App, cmd_path: String) -> Self {
479 let mut slf = Self::new(cmd_path);
480 for (i, arg) in cmd.get_arguments().enumerate() {
481 let mut push_arg = false;
482 if let Some(ln) = arg.get_long() {
483 slf.lns.insert(ln.to_string(), (true, i));
484 push_arg = true;
485 }
486 if let Some(lns) = arg.get_all_aliases() {
487 slf.ln_aliases.extend(
488 lns.iter()
489 .map(|ln| (ln.to_string(), (true, i)))
490 .collect::<Vec<(String, (bool, usize))>>(),
491 );
492 push_arg = true;
493 }
494 if let Some(sn) = arg.get_short() {
495 slf.sns.insert(sn, (true, i));
496 push_arg = true;
497 }
498 if let Some(sns) = arg.get_all_short_aliases() {
499 slf.sn_aliases.extend(
500 sns.iter()
501 .map(|sn| (*sn, (true, i)))
502 .collect::<Vec<(char, (bool, usize))>>(),
503 );
504 push_arg = true;
505 }
506
507 if push_arg {
508 slf.opt_names.push(arg.get_id().to_string());
509 }
510 }
511 slf
512 }
513
514 pub fn register(&mut self, name: &String, ext: Option<&Extension>) -> bool {
515 self.current = name.to_string();
517 if let Some(e) = ext {
518 self.exts.push(e.source.to_string());
519 if !self.ext_opt_names.contains_key(name) {
520 self.ext_opt_names
521 .insert(name.to_string(), self.exts.len() - 1);
522 }
523 } else {
524 self.opt_names.push(name.to_string());
525 }
526 true
527 }
528
529 pub fn iln_conflicts(&mut self, iln: &String) -> bool {
530 if let Some(conflict) = self.ln_aliases.get(iln) {
531 conflict_err_msg!(self, conflict, iln, "Inferred long name", "long name alias");
532 true
533 } else if let Some(conflict) = self.lns.get(iln) {
534 conflict_err_msg!(self, conflict, iln, "Inferred long name", "long name");
535 true
536 } else if let Some(conflict) = self.ilns.get(iln) {
537 conflict_err_msg!(
538 self,
539 conflict,
540 iln,
541 "Inferred long name",
542 "inferred long name"
543 );
544 true
545 } else {
546 cache!(self, ilns, iln.to_owned());
547 self.last_needs_visible_full_name = false;
548 false
549 }
550 }
551
552 pub fn ln_conflicts(&mut self, ln: &String) -> bool {
553 if let Some(conflict) = self.ln_aliases.get(ln) {
554 conflict_err_msg!(self, conflict, ln, "Long name", "long name alias");
555 true
556 } else if let Some(conflict) = self.lns.get(ln) {
557 conflict_err_msg!(self, conflict, ln, "Long name", "long name");
558 true
559 } else if let Some(conflict) = self.ilns.get(ln) {
560 conflict_err_msg!(self, conflict, ln, "Long name", "inferred long name");
561 true
562 } else {
563 cache!(self, lns, ln.to_owned());
564 self.last_needs_visible_full_name = false;
565 false
566 }
567 }
568
569 pub fn sn_conflicts(&mut self, sn: char) -> bool {
570 if let Some(conflict) = self.sn_aliases.get(&sn) {
571 conflict_err_msg!(self, conflict, sn, "Short name", "short name alias");
572 true
573 } else if let Some(conflict) = self.sns.get(&sn) {
574 conflict_err_msg!(self, conflict, sn, "Short name", "short name");
575 true
576 } else {
577 cache!(self, sns, sn);
578 self.last_needs_visible_full_name = false;
579 false
580 }
581 }
582
583 pub fn non_conflicting_snas(&mut self, snas: &Vec<char>) -> Vec<char> {
584 snas.iter()
585 .filter_map(|sna| {
586 if let Some(conflict) = self.sn_aliases.get(sna) {
587 conflict_err_msg!(self, conflict, sna, "Short name alias", "short name alias");
588 None
589 } else if let Some(conflict) = self.sns.get(sna) {
590 conflict_err_msg!(self, conflict, sna, "Short name alias", "short name");
591 None
592 } else {
593 cache!(self, sn_aliases, *sna);
594 Some(*sna)
595 }
596 })
597 .collect::<Vec<char>>()
598 }
599
600 pub fn non_conflicting_lnas(&mut self, lnas: &Vec<String>) -> Vec<String> {
601 lnas.iter()
602 .filter_map(|lna| {
603 if let Some(conflict) = self.ln_aliases.get(lna) {
604 conflict_err_msg!(self, conflict, lna, "Long name alias", "long name alias");
605 None
606 } else if let Some(conflict) = self.lns.get(lna) {
607 conflict_err_msg!(self, conflict, lna, "Long name alias", "long name");
608 None
609 } else if let Some(conflict) = self.ilns.get(lna) {
610 conflict_err_msg!(self, conflict, lna, "Long name alias", "inferred long name");
611 None
612 } else {
613 cache!(self, ln_aliases, lna.to_owned());
614 Some(lna.clone())
615 }
616 })
617 .collect::<Vec<String>>()
618 }
619
620 pub fn needs_visible_full_name(&mut self) -> bool {
621 let retn = self.last_needs_visible_full_name;
622 self.last_needs_visible_full_name = true;
623 retn
624 }
625}
626
627#[derive(Debug, Deserialize, Clone)]
628pub struct ArgTOML {
629 pub name: String,
630 pub help: String,
631 pub multiple: Option<bool>,
632 pub required: Option<bool>,
633 pub value_name: Option<String>,
634 pub use_delimiter: Option<bool>,
635}
636
637#[derive(Debug)]
638pub struct Arg {
639 pub name: String,
640 pub help: String,
641 pub multiple: Option<bool>,
642 pub required: Option<bool>,
643 pub value_name: Option<String>,
644 pub use_delimiter: Option<bool>,
645 pub upcased_name: Option<String>,
646}
647
648impl Arg {
649 fn from_toml(arg: &ArgTOML) -> Self {
650 Self {
651 name: arg.name.to_owned(),
652 help: arg.help.to_owned(),
653 multiple: arg.multiple,
654 required: arg.required,
655 value_name: arg.value_name.to_owned(),
656 use_delimiter: arg.use_delimiter,
657 upcased_name: {
658 if arg.value_name.is_some() {
659 None
660 } else {
661 Some(arg.name.to_uppercase())
662 }
663 },
664 }
665 }
666}
667
668#[derive(Debug, Deserialize, Clone)]
669pub struct OptTOML {
670 pub name: String,
671 pub help: String,
672 pub short: Option<char>,
673 pub long: Option<String>,
674 pub takes_value: Option<bool>,
675 pub multiple: Option<bool>,
676 pub required: Option<bool>,
677 pub value_name: Option<String>,
678 pub use_delimiter: Option<bool>,
679 pub short_aliases: Option<Vec<char>>,
680 pub long_aliases: Option<Vec<String>>,
681 pub hidden: Option<bool>,
682}
683
684#[derive(Debug)]
685pub struct Opt {
686 pub name: String,
687 pub help: String,
688 pub short: Option<char>,
689 pub long: Option<String>,
690 pub takes_value: Option<bool>,
691 pub multiple: Option<bool>,
692 pub required: Option<bool>,
693 pub value_name: Option<String>,
694 pub use_delimiter: Option<bool>,
695 pub short_aliases: Option<Vec<char>>,
696 pub long_aliases: Option<Vec<String>>,
697 pub hidden: Option<bool>,
698 pub upcased_name: Option<String>,
699 pub full_name: Option<String>,
700}
701
702impl Opt {
703 fn from_toml(opt: &OptTOML, cmd_path: &str, ext_from: Option<&str>) -> Self {
704 macro_rules! gen_err {
705 ($msg:tt $(,)? $($arg:expr),*) => {{
706 if let Some(ext) = ext_from {
707 log_err_processing_cmd!(cmd_path, concat!("Option '{}' extended from {} ", $msg), opt.name, ext, $($arg),*);
708 } else {
709 log_err_processing_cmd!(cmd_path, concat!("Option '{}' ", $msg), opt.name, $($arg),*);
710 }
711 }}
712 }
713
714 macro_rules! res_opt_ln_msg {
715 ($conflict:expr, $name:expr) => {
716 gen_err!(
717 "tried to use reserved option {} '{}' and will not be available as '--{}'",
718 $conflict,
719 $name,
720 $name
721 );
722 };
723 }
724 macro_rules! res_opt_sn_msg {
725 ($conflict:expr, $name:expr) => {
726 gen_err!(
727 "tried to use reserved option {} '{}' and will not be available as '-{}'",
728 $conflict,
729 $name,
730 $name
731 )
732 };
733 }
734 macro_rules! res_prefix_msg {
735 ($conflict:expr, $name:expr) => {
736 gen_err!(
737 "uses reserved prefix '{}' in {} '{}' and will not be available as '--{}'",
738 crate::framework::extensions::EXT_BASE_NAME,
739 $conflict,
740 $name,
741 $name
742 )
743 };
744 }
745
746 let ln = opt.long.as_ref().and_then(|ln| {
747 if RESERVED_OPT_NAMES.contains(&ln.as_str()) {
748 res_opt_ln_msg!("long name", ln);
749 None
750 } else if uses_reserved_prefix!(ln) {
751 res_prefix_msg!("long name", ln);
752 None
753 } else {
754 Some(ln.to_owned())
755 }
756 });
757
758 let sn = opt.short.as_ref().and_then(|sn| {
759 if RESERVED_OPT_SHORT_NAMES.contains(sn) {
760 res_opt_sn_msg!("short name", sn);
761 None
762 } else {
763 Some(*sn)
764 }
765 });
766
767 Self {
768 name: opt.name.to_owned(),
769 help: opt.help.to_owned(),
770 takes_value: opt.takes_value,
771 multiple: opt.multiple,
772 required: opt.required,
773 value_name: opt.value_name.to_owned(),
774 use_delimiter: opt.use_delimiter,
775 short_aliases: {
776 let mut snas: HashMap<&char, usize> = HashMap::new();
777 opt.short_aliases.as_ref().map( |sns| sns.iter().enumerate().filter_map( |(i, sna)| {
778 if RESERVED_OPT_SHORT_NAMES.contains(sna) {
779 res_opt_sn_msg!("short name alias", sna);
780 return None;
781 } else if sn.is_some() && (sn.as_ref().unwrap() == sna) {
782 gen_err!("specifies short name alias '{}' but it conflicts with the option's short name", sna);
783 return None;
784 } else if let Some(idx) = snas.get(sna) {
785 gen_err!("repeats short name alias '{}' (first occurrence at index {})", sna, idx);
786 return None;
787 }
788 snas.insert(sna, i);
789 Some(*sna)
790 }).collect())
791 },
792 short: sn,
793 long_aliases: {
794 let mut lnas: HashMap<&String, usize> = HashMap::new();
795 opt.long_aliases.as_ref().map( |lns| lns.iter().enumerate().filter_map( |(i, lna)| {
796 if RESERVED_OPT_NAMES.contains(&lna.as_str()) {
797 res_opt_ln_msg!("long name alias", lna);
798 return None
799 } else if uses_reserved_prefix!(lna) {
800 res_prefix_msg!("long name alias", lna);
801 return None
802 } else if ln.is_some() && (ln.as_ref().unwrap() == lna) {
803 gen_err!("specifies long name alias '{}' but it conflicts with the option's long name", lna);
804 return None
805 } else if (&opt.name == lna) && ln.is_none() {
806 gen_err!("specifies long name alias '{}' but it conflicts with the option's inferred long name. If this is intentional, please set this as the option's long name", lna);
807 return None
808 } else if let Some(idx) = lnas.get(lna) {
809 gen_err!("repeats long name alias '{}' (first occurrence at index {})", lna, idx);
810 return None
811 }
812 lnas.insert(lna, i);
813 Some(lna.to_owned())
814 }).collect())
815 },
816 long: ln,
817 hidden: opt.hidden,
818 upcased_name: {
819 if opt.value_name.is_some() {
820 None
821 } else {
822 Some(opt.name.to_uppercase())
823 }
824 },
825 full_name: None,
826 }
827 }
828
829 pub fn id(&self) -> &str {
830 if let Some(fname) = self.full_name.as_ref() {
831 fname.as_str()
832 } else {
833 self.name.as_str()
834 }
835 }
836}
837
838pub(crate) fn build_commands<'a, F, G, H>(
839 cmd_def: &'a Command,
840 exts: &G,
841 cmd_container: &F,
842 apply_helps: &H,
843) -> App
844where
845 F: Fn(&str) -> &'a Command,
846 G: Fn(&str, App, &mut CmdOptCache) -> App,
847 H: Fn(&str, App) -> App,
848{
849 let mut cmd = ClapCommand::new(cmd_def.name.clone());
850
851 cmd = add_app_opts(cmd, cmd_def.add_mode_opt(), cmd_def.add_target_opt());
852
853 if cmd_def.alias.is_some() {
855 cmd = cmd.visible_alias(cmd_def.alias.as_ref().unwrap().clone());
856 }
857
858 if let Some(args) = cmd_def.args.as_ref() {
859 cmd = apply_args(args, cmd);
860 }
861
862 let mut cache = CmdOptCache::new(cmd_def.cmd_path.to_string());
863 if let Some(opts) = cmd_def.opts.as_ref() {
864 cmd = apply_opts(opts, cmd, &mut cache, None);
865 }
866
867 if let Some(subcommands) = &cmd_def.subcommands {
868 for c in subcommands {
869 let subcmd = build_commands(cmd_container(c), exts, cmd_container, apply_helps);
870 cmd = cmd.subcommand(subcmd);
871 }
872 cmd = cmd.subcommand_negates_reqs(true);
873 }
874 cmd = exts(&cmd_def.offset_path(), cmd, &mut cache);
875 cmd = apply_helps(&cmd_def.offset_path(), cmd);
876
877 cmd
878}
879
880pub(crate) fn apply_args<'a>(args: &'a Vec<Arg>, mut cmd: App) -> App {
881 for arg_def in args {
882 let mut arg = clap::Arg::new(arg_def.name.clone())
883 .action(SetArg)
884 .help(arg_def.help.clone());
885
886 if let Some(vn) = arg_def.value_name.as_ref() {
887 arg = arg.value_name(vn.clone());
888 } else {
889 arg = arg.value_name(arg_def.upcased_name.as_ref().unwrap().clone());
890 }
891
892 if let Some(d) = arg_def.use_delimiter {
893 arg = arg.use_value_delimiter(d);
894 arg = arg.num_args(1..).action(AppendArgs);
895 }
896 if let Some(m) = arg_def.multiple {
897 arg = if m {
898 arg.num_args(1..).action(AppendArgs)
899 } else {
900 arg.num_args(1).action(SetArg)
901 };
902 }
903
904 if let Some(r) = arg_def.required {
905 arg = arg.required(r);
906 }
907 cmd = cmd.arg(arg);
908 }
909 cmd
910}
911
912pub(crate) fn apply_opts<'a>(
913 opts: &'a Vec<Opt>,
914 mut cmd: App,
915 cache: &mut CmdOptCache,
916 from_ext: Option<&Extension>,
917) -> App {
918 for opt_def in opts {
919 cache.register(&opt_def.name, from_ext);
920 let mut opt = clap::Arg::new(opt_def.id().to_string())
921 .action(CountArgs)
922 .help(opt_def.help.clone());
923
924 if let Some(val_name) = opt_def.value_name.as_ref() {
925 opt = opt.value_name(val_name.clone()).action(SetArg);
926 }
927 if let Some(tv) = opt_def.takes_value {
928 if tv {
929 opt = opt.action(SetArg);
930 }
931 }
932 if let Some(ud) = opt_def.use_delimiter {
933 if ud {
934 opt = opt.use_value_delimiter(ud);
935 }
936 opt = opt.num_args(1..);
937 opt = opt.action(AppendArgs);
938 }
939 if let Some(m) = opt_def.multiple {
940 if m {
941 opt = opt.num_args(1..).action(AppendArgs);
942 } else {
943 opt = opt.num_args(1).action(SetArg);
944 }
945 }
946
947 if let Some(ln) = opt_def.long.as_ref() {
948 if cache.ln_conflicts(ln) {
949 if !cache.iln_conflicts(&opt_def.name) {
951 opt = opt.long(opt_def.name.clone());
952 }
953 } else {
954 opt = opt.long(ln.clone());
955 }
956 } else {
957 if !opt_def.short.is_some() {
958 if !cache.iln_conflicts(&opt_def.name) {
959 opt = opt.long(opt_def.name.clone());
960 }
961 }
962 }
963 if let Some(sn) = opt_def.short {
964 if !cache.sn_conflicts(sn) {
965 opt = opt.short(sn);
966 } else {
967 if !opt.get_long().is_some() {
968 if !cache.iln_conflicts(&opt_def.name) {
969 opt = opt.long(opt_def.name.clone());
970 }
971 }
972 }
973 }
974
975 if let Some(r) = opt_def.required {
976 opt = opt.required(r);
977 }
978
979 if let Some(h) = opt_def.hidden {
980 opt = opt.hide(h);
981 }
982
983 if opt.get_action().takes_values() && opt_def.value_name.is_none() {
984 opt = opt.value_name(opt_def.upcased_name.as_ref().unwrap().clone());
985 }
986
987 if let Some(lns) = opt_def.long_aliases.as_ref() {
988 let v;
989 v = cache.non_conflicting_lnas(lns);
990 opt = opt.visible_aliases(v);
991 }
992
993 if let Some(sns) = opt_def.short_aliases.as_ref() {
994 let to_add;
995 to_add = cache.non_conflicting_snas(sns);
996 opt = opt.visible_short_aliases(to_add);
997 }
998
999 if from_ext.is_some() {
1000 let full_name = opt_def.full_name.as_ref().unwrap();
1001 if cache.needs_visible_full_name() {
1002 opt = opt.long(full_name.clone());
1003 } else {
1004 opt = opt.alias(full_name.clone());
1005 }
1006 } else {
1007 if cache.needs_visible_full_name() {
1008 log_err_processing_cmd!(
1009 cache.cmd_path,
1010 "Unable to place unique long name, short name, or inferred long name for command option '{}'. Please resolve any previous conflicts regarding this option or add/update this option's name, long name, or short name",
1011 opt_def.name
1012 );
1013 continue;
1014 }
1015 }
1016
1017 cmd = cmd.arg(opt);
1018 }
1019 cmd
1020}
1021
1022pub fn build_path<'a>(mut matches: &'a clap::ArgMatches) -> Result<String> {
1023 let mut path_pieces = vec![];
1024 while matches.subcommand_name().is_some() {
1025 let n = matches.subcommand_name().unwrap();
1026 matches = matches.subcommand_matches(&n).unwrap();
1027 path_pieces.push(n);
1028 }
1029 Ok(path_pieces.join("."))
1030}
1031
1032pub const HELP_OPT_NAME: &str = "help";
1033pub const HELP_OPT_SHORT_NAME: char = 'h';
1034pub const VERBOSITY_KEYWORDS_OPT_NAME: &str = "verbosity_keywords";
1035pub const VERBOSITY_KEYWORDS_OPT_LONG_NAME: &str = "vk";
1036pub const VERBOSITY_OPT_NAME: &str = "verbose";
1037pub const VERBOSITY_OPT_SHORT_NAME: char = 'v';
1038pub const VERBOSITY_OPT_LNA: &str = "verbosity";
1039pub const TARGET_OPT_NAME: &str = "targets";
1040pub const TARGET_OPT_ALIAS: &str = "target";
1041pub const TARGET_OPT_SN: char = 't';
1042pub const NO_TARGET_OPT_NAME: &str = "no_targets";
1043pub const NO_TARGET_OPT_ALIAS: &str = "no_target";
1044pub const MODE_OPT_NAME: &str = "mode";
1045
1046pub const RESERVED_OPT_NAMES: &[&str] = &[
1047 HELP_OPT_NAME,
1048 VERBOSITY_KEYWORDS_OPT_NAME,
1049 VERBOSITY_KEYWORDS_OPT_LONG_NAME,
1050 TARGET_OPT_NAME,
1051 TARGET_OPT_ALIAS,
1052 NO_TARGET_OPT_NAME,
1053 NO_TARGET_OPT_ALIAS,
1054 MODE_OPT_NAME,
1055 VERBOSITY_OPT_NAME,
1056 VERBOSITY_OPT_LNA,
1057];
1058
1059pub const RESERVED_OPT_SHORT_NAMES: &[char] =
1060 &[HELP_OPT_SHORT_NAME, TARGET_OPT_SN, VERBOSITY_OPT_SHORT_NAME];
1061
1062static VERBOSITY_HELP_STR: &str = "Terminal verbosity level e.g. -v, -vv, -vvv";
1063static VERBOSITY_KEYWORD_HELP_STR: &str = "Keywords for verbose listeners";
1064
1065pub const VOV_OPT_NAME: &str = "version_or_verbosity";
1066
1067pub fn add_verbosity_opts<'a>(cmd: ClapCommand, split_v: bool) -> ClapCommand {
1068 if split_v {
1069 cmd.arg(
1070 ClapArg::new(VERBOSITY_OPT_NAME)
1071 .long(VERBOSITY_OPT_NAME)
1072 .visible_alias(VERBOSITY_OPT_LNA)
1073 .action(CountArgs),
1074 )
1075 .arg(
1076 ClapArg::new(VOV_OPT_NAME)
1077 .short(VERBOSITY_OPT_SHORT_NAME)
1078 .action(CountArgs),
1079 )
1080 } else {
1081 cmd.arg(
1082 ClapArg::new(VERBOSITY_OPT_NAME)
1083 .long(VERBOSITY_OPT_NAME)
1084 .visible_alias(VERBOSITY_OPT_LNA)
1085 .short(VERBOSITY_OPT_SHORT_NAME)
1086 .action(CountArgs)
1087 .global(true)
1088 .help(VERBOSITY_HELP_STR),
1089 )
1090 }
1091 .arg(
1092 ClapArg::new(VERBOSITY_KEYWORDS_OPT_NAME)
1093 .long(VERBOSITY_KEYWORDS_OPT_NAME)
1094 .visible_alias(VERBOSITY_KEYWORDS_OPT_LONG_NAME)
1095 .action(AppendArgs)
1096 .num_args(1)
1097 .global(true)
1098 .help(VERBOSITY_KEYWORD_HELP_STR)
1099 .use_value_delimiter(true),
1100 )
1101}
1102
1103macro_rules! add_mode_opt {
1104 ($cmd:expr) => {
1105 $cmd.arg(
1106 clap::Arg::new(crate::framework::MODE_OPT_NAME)
1107 .long("mode")
1108 .value_name("MODE")
1109 .help("Override the default mode currently set by the workspace for this command")
1110 .action(crate::framework::SetArg),
1111 )
1112 };
1113}
1114
1115macro_rules! add_target_opt {
1116 ($cmd:expr) => {
1117 $cmd.arg(
1118 clap::Arg::new(crate::framework::TARGET_OPT_NAME)
1119 .short('t')
1120 .long(crate::framework::TARGET_OPT_NAME)
1121 .visible_alias(TARGET_OPT_ALIAS)
1122 .help("Override the targets currently set by the workspace for this command")
1123 .action(crate::commands::_prelude::AppendArgs)
1124 .use_value_delimiter(true)
1125 .num_args(1..)
1126 .value_name("TARGETS")
1127 .conflicts_with(crate::framework::NO_TARGET_OPT_NAME),
1128 )
1129 .arg(
1130 clap::Arg::new(crate::framework::NO_TARGET_OPT_NAME)
1131 .long(crate::framework::NO_TARGET_OPT_NAME)
1132 .visible_alias(NO_TARGET_OPT_ALIAS)
1133 .help("Clear any targets currently set by the workspace for this command")
1134 .action(crate::commands::_prelude::SetArgTrue),
1135 )
1136 };
1137}
1138
1139pub fn add_app_opts(mut cmd: ClapCommand, add_mode_opt: bool, add_target_opt: bool) -> ClapCommand {
1140 if in_app_invocation() {
1141 if add_target_opt {
1142 cmd = add_target_opt!(cmd);
1143 }
1144 if add_mode_opt {
1145 cmd = add_mode_opt!(cmd);
1146 }
1147 }
1148 cmd
1149}
1150
1151pub fn add_all_app_opts(cmd: ClapCommand) -> ClapCommand {
1152 if in_app_invocation() {
1153 add_mode_opt!(add_target_opt!(cmd))
1154 } else {
1155 cmd
1156 }
1157}
1158
1159#[macro_export]
1160macro_rules! output_dir_opt {
1161 () => {{
1162 Arg::new("output_dir")
1163 .short('o')
1164 .long("output-dir")
1165 .visible_alias("output_dir")
1166 .help("Override the default output directory (<APP ROOT>/output)")
1167 .action(SetArg)
1168 .value_name("OUTPUT_DIR")
1169 }};
1170}
1171
1172pub const REF_DIR_OPT_LNAS: &[&str] = &["reference_dir", "ref_dir", "reference-dir"];
1173
1174#[macro_export]
1175macro_rules! ref_dir_opt {
1176 () => {{
1177 Arg::new("reference_dir")
1178 .short('r')
1179 .long("ref-dir")
1180 .visible_aliases(crate::framework::REF_DIR_OPT_LNAS.iter().copied())
1181 .help("Override the default reference directory (<APP ROOT>/.ref)")
1182 .action(SetArg)
1183 .value_name("REFERENCE_DIR")
1184 }};
1185}