1mod names_checker;
2mod share_data;
3
4use self::names_checker::NamesChecker;
5use self::share_data::ShareData;
6
7use crate::argc_value::ArgcValue;
8#[cfg(feature = "eval")]
9use crate::matcher::Matcher;
10use crate::param::{EnvParam, FlagOptionParam, Param, PositionalParam};
11#[cfg(feature = "export")]
12use crate::param::{EnvValue, FlagOptionValue, PositionalValue};
13use crate::parser::{parse, parse_symbol, Event, EventData, EventScope, Position};
14use crate::runtime::Runtime;
15use crate::utils::{
16 AFTER_HOOK, BEFORE_HOOK, MAIN_NAME, META_COMBINE_SHORTS, META_DEFAULT_SUBCOMMAND, META_DOTENV,
17 META_INHERIT_FLAG_OPTIONS, META_REQUIRE_TOOLS, META_SYMBOL, META_VERSION, ROOT_NAME,
18};
19use crate::Result;
20
21use anyhow::{anyhow, bail};
22use indexmap::{IndexMap, IndexSet};
23use serde::Serialize;
24use std::cell::RefCell;
25use std::collections::HashMap;
26use std::sync::Arc;
27
28#[derive(Debug, Default)]
29pub(crate) struct Command {
30 pub(crate) name: Option<String>,
31 pub(crate) match_fn: Option<String>,
32 pub(crate) command_fn: Option<String>,
33 pub(crate) paths: Vec<String>,
34 pub(crate) describe: String,
35 pub(crate) flag_option_params: Vec<FlagOptionParam>,
36 pub(crate) derived_flag_option_params: Vec<FlagOptionParam>,
37 pub(crate) positional_params: Vec<PositionalParam>,
38 pub(crate) env_params: Vec<EnvParam>,
39 pub(crate) subcommands: Vec<Command>,
40 pub(crate) subcommand_fns: HashMap<String, Position>,
41 pub(crate) default_subcommand: Option<(usize, Position)>,
42 pub(crate) aliases: Option<(Vec<String>, Position)>,
43 pub(crate) version: Option<String>,
44 pub(crate) names_checker: NamesChecker,
45 pub(crate) share: Arc<RefCell<ShareData>>,
46 pub(crate) metadata: Vec<(String, String, Position)>,
48 pub(crate) symbols: IndexMap<char, SymbolParam>,
49 pub(crate) require_tools: IndexSet<String>,
50 pub(crate) help_flags: Vec<&'static str>,
51 pub(crate) version_flags: Vec<&'static str>,
52}
53
54impl Command {
55 pub(crate) fn new(source: &str, root_name: &str) -> Result<Self> {
56 let events = parse(source)?;
57 let mut root = Command::new_from_events(&events)?;
58 root.share.borrow_mut().name = Some(root_name.to_string());
59 root.update_recursively(vec![], IndexSet::new());
60 if root.has_metadata(META_INHERIT_FLAG_OPTIONS) {
61 root.inherit_flag_options();
62 }
63 root.inherit_envs();
64 Ok(root)
65 }
66
67 #[cfg(feature = "eval")]
68 pub(crate) fn eval<T: Runtime>(
69 &mut self,
70 runtime: T,
71 args: &[String],
72 script_path: Option<&str>,
73 wrap_width: Option<usize>,
74 ) -> Result<Vec<ArgcValue>> {
75 if args.is_empty() {
76 bail!("Invalid args");
77 }
78 if args.len() >= 3 && args[1] == T::INTERNAL_SYMBOL {
79 let fallback_args = vec![ROOT_NAME.to_string()];
80 let new_args = if args.len() == 3 {
81 &fallback_args
82 } else {
83 &args[3..]
84 };
85 let matcher = Matcher::new(runtime, self, new_args, false);
86 let mut arg_values = matcher.to_arg_values_for_param_fn();
87 arg_values.push(ArgcValue::ParamFn(args[2].clone()));
88 return Ok(arg_values);
89 }
90 let mut matcher = Matcher::new(runtime, self, args, false);
91 if let Some(script_path) = script_path {
92 matcher.set_script_path(script_path)
93 }
94 if let Some(wrap_width) = wrap_width {
95 matcher.set_wrap_width(wrap_width)
96 }
97 Ok(matcher.to_arg_values())
98 }
99
100 #[cfg(feature = "export")]
101 pub(crate) fn export(&self) -> CommandValue {
102 let mut extra: IndexMap<String, serde_json::Value> = IndexMap::new();
103 let require_tools = self.meta_require_tools();
104 if !require_tools.is_empty() {
105 extra.insert("require_tools".into(), require_tools.into());
106 }
107 if self.is_root() {
108 if self.get_metadata(META_COMBINE_SHORTS).is_some() {
109 extra.insert("combine_shorts".into(), true.into());
110 }
111 if let Some(dotenv) = self.dotenv() {
112 extra.insert("dotenv".into(), dotenv.into());
113 }
114 let (before_hook, after_hook) = self.exist_hooks();
115 if before_hook {
116 extra.insert("before_hook".into(), BEFORE_HOOK.into());
117 }
118 if after_hook {
119 extra.insert("after_hook".into(), AFTER_HOOK.into());
120 }
121 } else if let Some((idx, _)) = &self.default_subcommand {
122 extra.insert("default_subcommand".into(), (*idx).into());
123 }
124 if !self.metadata.is_empty() {
125 extra.insert(
126 "metadata".into(),
127 serde_json::json!(self
128 .metadata
129 .iter()
130 .map(|(k, v, _)| (
131 k.to_string(),
132 if v.is_empty() {
133 None
134 } else {
135 Some(v.to_string())
136 }
137 ))
138 .collect::<IndexMap<String, Option<String>>>()),
139 );
140 }
141 extra.insert("command_fn".into(), self.command_fn.clone().into());
142 let flag_options = self.all_flag_options().iter().map(|v| v.export()).collect();
143 CommandValue {
144 name: self.cmd_name(),
145 describe: self.describe.clone(),
146 version: self.version.clone(),
147 aliases: self.list_alias_names().clone(),
148 flag_options,
149 positionals: self.positional_params.iter().map(|v| v.export()).collect(),
150 envs: self.env_params.iter().map(|v| v.export()).collect(),
151 subcommands: self.subcommands.iter().map(|v| v.export()).collect(),
152 extra,
153 }
154 }
155
156 pub(crate) fn new_from_events(events: &[Event]) -> Result<Self> {
157 let mut root_cmd = Command::default();
158 let share_data = root_cmd.share.clone();
159 for event in events {
160 let Event { data, position } = event.clone();
161 match data {
162 EventData::Describe(value) => {
163 let cmd = Self::get_cmd(&mut root_cmd, "@describe", position)?;
164 cmd.describe = value;
165 }
166 EventData::Version(value) => {
167 let cmd = Self::get_cmd(&mut root_cmd, "@version", position)?;
168 cmd.version = Some(value);
169 }
170 EventData::Meta(key, value) => {
171 let cmd = Self::get_cmd(&mut root_cmd, "@meta", position)?;
172 match key.as_str() {
173 META_SYMBOL => {
174 let (ch, name, choice_fn) = parse_symbol(&value).ok_or_else(|| {
175 anyhow!("@meta(line {}) invalid symbol value", position)
176 })?;
177 cmd.symbols
178 .insert(ch, (name.to_string(), choice_fn.map(|v| v.to_string())));
179 }
180 META_VERSION => {
181 if value.is_empty() {
182 bail!("@meta(line {}) invalid version value", position)
183 } else {
184 cmd.version = Some(value.clone());
185 }
186 }
187 _ => {}
188 }
189 cmd.metadata.push((key, value, position));
190 }
191 EventData::Cmd(value) => {
192 if share_data.borrow().scope == EventScope::CmdStart {
193 bail!(
194 "@cmd(line {}) missing function?",
195 share_data.borrow().cmd_pos
196 )
197 }
198 share_data.borrow_mut().cmd_pos = position;
199 share_data.borrow_mut().scope = EventScope::CmdStart;
200 let subcmd = root_cmd.create_cmd();
201 if !value.is_empty() {
202 subcmd.describe.clone_from(&value);
203 }
204 }
205 EventData::Aliases(values) => {
206 let cmd = Self::get_cmd(&mut root_cmd, "@alias", position)?;
207 cmd.aliases = Some((values.to_vec(), position));
208 }
209 EventData::FlagOption(param) => {
210 param.guard().map_err(|err| {
211 anyhow!("{}(line {}) is invalid, {err}", param.tag_name(), position)
212 })?;
213 let cmd = Self::get_cmd(&mut root_cmd, param.tag_name(), position)?;
214 if param.is_option() {
215 share_data.borrow_mut().add_param_fn(
216 position,
217 param.default_fn(),
218 param.choice_fn(),
219 );
220 }
221 cmd.names_checker.check_flag_option(¶m, position)?;
222 cmd.flag_option_params.push(param);
223 }
224 EventData::Env(param) => {
225 param.guard().map_err(|err| {
226 anyhow!("{}(line {}) is invalid, {err}", param.tag_name(), position)
227 })?;
228 let cmd = Self::get_cmd(&mut root_cmd, param.tag_name(), position)?;
229 share_data.borrow_mut().add_param_fn(
230 position,
231 param.default_fn(),
232 param.choice_fn(),
233 );
234 cmd.names_checker.check_env(¶m, position)?;
235 cmd.env_params.push(param);
236 }
237 EventData::Positional(param) => {
238 param.guard().map_err(|err| {
239 anyhow!("{}(line {}) is invalid, {err}", param.tag_name(), position)
240 })?;
241 let cmd = Self::get_cmd(&mut root_cmd, param.tag_name(), position)?;
242 share_data.borrow_mut().add_param_fn(
243 position,
244 param.default_fn(),
245 param.choice_fn(),
246 );
247 cmd.add_positional_param(param, position)?;
248 }
249 EventData::Func(name) => {
250 if let Some(pos) = share_data.borrow_mut().cmd_fns.get(&name) {
251 bail!(
252 "{}(line {}) conflicts with cmd or alias at line {}",
253 name,
254 position,
255 pos
256 )
257 }
258 share_data.borrow_mut().fns.insert(name.clone(), position);
259 if share_data.borrow().scope == EventScope::CmdStart {
260 share_data
261 .borrow_mut()
262 .cmd_fns
263 .insert(name.clone(), position);
264
265 let parts: Vec<&str> = name.split("::").collect();
266 let parts_len = parts.len();
267 if parts_len == 0 {
268 bail!("{}(line {}) invalid command name", name, position);
269 }
270 if parts_len == 1 {
271 let cmd = root_cmd.subcommands.last_mut().unwrap();
272 cmd.name = Some(sanitize_cmd_name(&name));
273 cmd.match_fn = Some(name.to_string());
274 if let Some((aliases, aliases_pos)) = &cmd.aliases {
275 for name in aliases {
276 if let Some(pos) = share_data.borrow().cmd_fns.get(name) {
277 bail!(
278 "@alias(line {}) conflicts with cmd or alias at line {}",
279 aliases_pos,
280 pos
281 );
282 }
283 share_data
284 .borrow_mut()
285 .cmd_fns
286 .insert(name.clone(), *aliases_pos);
287 }
288 }
289 update_parent_cmd(&mut root_cmd)?;
290 } else {
291 let mut cmd = root_cmd.subcommands.pop().unwrap();
292 let (child, parents) = parts.split_last().unwrap();
293 let parents: Vec<String> =
294 parents.iter().map(|v| sanitize_cmd_name(v)).collect();
295 cmd.name = Some(sanitize_cmd_name(child));
296 cmd.match_fn = Some(name.to_string());
297 match retrieve_cmd(&mut root_cmd, &parents) {
298 Some(parent_cmd) => {
299 parent_cmd
300 .subcommand_fns
301 .insert(child.to_string(), position);
302 if let Some((aliases, aliases_pos)) = &cmd.aliases {
303 for name in aliases {
304 if let Some(pos) = parent_cmd.subcommand_fns.get(name) {
305 bail!(
306 "@alias(line {}) conflicts with cmd or alias at line {}",
307 aliases_pos,
308 pos
309 );
310 }
311 parent_cmd
312 .subcommand_fns
313 .insert(name.clone(), *aliases_pos);
314 }
315 }
316 parent_cmd.subcommands.push(cmd);
317 update_parent_cmd(parent_cmd)?;
318 }
319 None => {
320 bail!("{}(line {}) lack of parent command", name, position);
321 }
322 }
323 }
324 }
325 share_data.borrow_mut().scope = EventScope::FnEnd;
326 }
327 EventData::Unknown(name) => {
328 bail!("@{}(line {}) is unknown tag", name, position);
329 }
330 }
331 }
332 root_cmd.share.borrow().check_param_fn()?;
333 Ok(root_cmd)
334 }
335
336 pub(crate) fn has_metadata(&self, key: &str) -> bool {
337 self.metadata.iter().any(|(k, _, _)| k == key)
338 }
339
340 pub(crate) fn get_metadata(&self, key: &str) -> Option<&str> {
341 self.metadata
342 .iter()
343 .find(|(k, _, _)| k == key)
344 .map(|(_, v, _)| v.as_str())
345 }
346
347 pub(crate) fn meta_require_tools(&self) -> Vec<String> {
348 let raw_require_tools = self.get_metadata(META_REQUIRE_TOOLS).unwrap_or_default();
349 if raw_require_tools.is_empty() {
350 vec![]
351 } else {
352 raw_require_tools
353 .split(',')
354 .map(|v| v.to_string())
355 .collect()
356 }
357 }
358
359 pub(crate) fn flag_option_signs(&self) -> IndexSet<char> {
360 let mut signs: IndexSet<char> = ['-'].into();
361 for param in &self.flag_option_params {
362 if let Some(short) = param.short() {
363 signs.extend(short.chars().take(1))
364 }
365 signs.extend(param.long_prefix().chars().take(1))
366 }
367 signs
368 }
369
370 pub(crate) fn cmd_name(&self) -> String {
371 self.name
372 .clone()
373 .unwrap_or_else(|| self.share.borrow().name())
374 }
375
376 pub(crate) fn is_root(&self) -> bool {
377 self.paths.is_empty()
378 }
379
380 pub(crate) fn cmd_paths(&self) -> Vec<String> {
381 let root_name = self.share.borrow().name();
382 let mut paths = self.paths.clone();
383 paths.insert(0, root_name);
384 paths
385 }
386
387 pub(crate) fn full_name(&self) -> String {
388 self.cmd_paths().join("-")
389 }
390
391 pub(crate) fn render_version(&self) -> String {
392 format!(
393 "{} {}",
394 self.full_name(),
395 self.version.clone().unwrap_or_else(|| "0.0.0".to_string())
396 )
397 }
398
399 pub(crate) fn describe_oneline(&self) -> &str {
400 match self.describe.split_once('\n') {
401 Some((v, _)) => v,
402 None => self.describe.as_str(),
403 }
404 }
405
406 pub(crate) fn list_names(&self) -> Vec<String> {
407 let mut output: Vec<String> = match self.name.clone() {
408 Some(v) => vec![v],
409 None => vec![],
410 };
411 output.extend(self.list_alias_names());
412 output
413 }
414
415 pub(crate) fn list_alias_names(&self) -> Vec<String> {
416 match &self.aliases {
417 Some((v, _)) => v.clone(),
418 None => vec![],
419 }
420 }
421
422 pub(crate) fn list_subcommand_names(&self) -> Vec<String> {
423 self.subcommands
424 .iter()
425 .flat_map(|v| v.list_names())
426 .collect()
427 }
428
429 pub(crate) fn find_subcommand(&self, name: &str) -> Option<&Self> {
430 self.subcommands
431 .iter()
432 .find(|subcmd| subcmd.list_names().iter().any(|v| v == name))
433 }
434
435 pub(crate) fn find_default_subcommand(&self) -> Option<&Self> {
436 let (idx, _) = self.default_subcommand.as_ref()?;
437 Some(&self.subcommands[*idx])
438 }
439
440 pub(crate) fn find_flag_option(&self, name: &str) -> Option<&FlagOptionParam> {
441 self.flag_option_params.iter().find(|v| v.is_match(name))
442 }
443
444 pub(crate) fn find_env(&self, name: &str) -> Option<&EnvParam> {
445 self.env_params.iter().find(|v| v.id() == name)
446 }
447
448 pub(crate) fn all_flag_options(&self) -> Vec<&FlagOptionParam> {
449 let mut list: Vec<&FlagOptionParam> = self.flag_option_params.iter().collect();
450 list.extend(self.derived_flag_option_params.iter());
451 list
452 }
453
454 pub(crate) fn is_empty_flags_options_subcommands(&self) -> bool {
455 self.flag_option_params.is_empty() && self.subcommands.is_empty()
456 }
457
458 pub(crate) fn exist_hooks(&self) -> (bool, bool) {
459 let fns = &self.share.borrow().fns;
460 let before_hook = fns.contains_key(BEFORE_HOOK);
461 let after_hook = fns.contains_key(AFTER_HOOK);
462 (before_hook, after_hook)
463 }
464
465 pub(crate) fn exist_version(&self) -> bool {
466 self.version.is_some() || self.is_root()
467 }
468
469 pub(crate) fn delegated(&self) -> bool {
470 self.subcommands.is_empty()
471 && self.flag_option_params.is_empty()
472 && self.positional_params.len() == 1
473 && self.positional_params[0].terminated()
474 }
475
476 pub(crate) fn dotenv(&self) -> Option<&str> {
477 let dotenv = self.get_metadata(META_DOTENV)?;
478 let dotenv = if dotenv.is_empty() { ".env" } else { dotenv };
479 Some(dotenv)
480 }
481
482 fn update_recursively(&mut self, paths: Vec<String>, mut require_tools: IndexSet<String>) {
483 self.paths.clone_from(&paths);
484
485 if let Some(name) = self.name.clone() {
487 let compatible_name = if !name.starts_with('_') {
488 name.replace("_", "-")
489 } else {
490 name.clone()
491 };
492 if compatible_name != name {
493 match self.aliases.as_mut() {
494 Some((aliaes, _)) => aliaes.insert(0, compatible_name),
495 None => {
496 self.aliases = Some((vec![compatible_name], Position::default()));
497 }
498 }
499 }
500 }
501
502 if paths.is_empty() {
504 if self.share.borrow().fns.contains_key(MAIN_NAME) {
505 self.command_fn = Some(MAIN_NAME.to_string())
506 }
507 } else if self.subcommands.is_empty() {
508 self.command_fn.clone_from(&self.match_fn);
509 } else {
510 let command_fn = [paths.as_slice(), [MAIN_NAME.to_string()].as_slice()]
511 .concat()
512 .join("::");
513 if self.share.borrow().fns.contains_key(&command_fn) {
514 self.command_fn = Some(command_fn)
515 }
516 }
517
518 self.help_flags = {
519 let mut flags = vec!["--help", "-help"];
520 let short = match self.find_flag_option("-h") {
521 Some(param) => param.id() == "help",
522 None => true,
523 };
524 if short {
525 flags.push("-h");
526 }
527 flags
528 };
529 self.version_flags = {
530 let mut flags = vec![];
531 if self.exist_version() {
532 flags.push("--version");
533 flags.push("-version");
534 let short = match self.find_flag_option("-V") {
535 Some(param) => param.id() == "version",
536 None => true,
537 };
538 if short {
539 flags.push("-V");
540 }
541 }
542 flags
543 };
544
545 let mut describe = false;
547 let mut single = false;
548 for param in self.flag_option_params.iter() {
549 if param.long_prefix().len() == 1 {
550 single = true;
551 }
552 if !param.describe().is_empty() {
553 describe = true;
554 }
555 }
556 let long_prefix = if single { "-" } else { "--" };
557 self.derived_flag_option_params.extend(
558 [
559 self.create_help_flag(describe, long_prefix),
560 self.create_version_flag(describe, long_prefix),
561 ]
562 .into_iter()
563 .flatten(),
564 );
565
566 require_tools.extend(self.meta_require_tools());
568 self.require_tools = require_tools;
569
570 for subcmd in self.subcommands.iter_mut() {
572 let mut parents = paths.clone();
573 parents.push(subcmd.name.clone().unwrap_or_default());
574 subcmd.update_recursively(parents, self.require_tools.clone());
575 }
576 }
577
578 fn inherit_flag_options(&mut self) {
579 for subcmd in self.subcommands.iter_mut() {
580 let mut inherited_flag_options = vec![];
581 for flag_option in &self.flag_option_params {
582 if subcmd.find_flag_option(flag_option.id()).is_none() {
583 let mut flag_option = flag_option.clone();
584 flag_option.set_inherit();
585 inherited_flag_options.push(flag_option);
586 }
587 }
588 subcmd
589 .flag_option_params
590 .splice(..0, inherited_flag_options);
591 }
592 for subcmd in self.subcommands.iter_mut() {
593 subcmd.inherit_flag_options();
594 }
595 }
596
597 fn inherit_envs(&mut self) {
598 for subcmd in self.subcommands.iter_mut() {
599 let mut inherited_envs = vec![];
600 for env_param in &self.env_params {
601 if subcmd.find_env(env_param.id()).is_none() {
602 let mut env_param = env_param.clone();
603 env_param.set_inherit();
604 inherited_envs.push(env_param);
605 }
606 }
607 subcmd.env_params.splice(..0, inherited_envs);
608 }
609 for subcmd in self.subcommands.iter_mut() {
610 subcmd.inherit_envs();
611 }
612 }
613
614 fn add_positional_param(&mut self, param: PositionalParam, pos: Position) -> Result<()> {
615 self.names_checker.check_positional(¶m, pos)?;
616 self.positional_params.push(param);
617 Ok(())
618 }
619
620 fn get_cmd<'a>(cmd: &'a mut Self, tag_name: &str, position: usize) -> Result<&'a mut Self> {
621 if cmd.share.borrow().scope == EventScope::FnEnd {
622 bail!(
623 "{}(line {}) shouldn't be here, @cmd is missing?",
624 tag_name,
625 position
626 )
627 }
628 if cmd.subcommands.last().is_some() {
629 Ok(cmd.subcommands.last_mut().unwrap())
630 } else {
631 Ok(cmd)
632 }
633 }
634
635 fn create_cmd(&mut self) -> &mut Self {
636 let cmd = Command {
637 share: self.share.clone(),
638 names_checker: Default::default(),
639 ..Default::default()
640 };
641 self.subcommands.push(cmd);
642 self.subcommands.last_mut().unwrap()
643 }
644
645 fn create_help_flag(&self, describe: bool, long_prefix: &str) -> Option<FlagOptionParam> {
646 if self.find_flag_option("help").is_some() {
647 return None;
648 }
649 let describe = if describe { "Print help" } else { "" };
650 let short = if self.find_flag_option("-h").is_none() {
651 Some("-h")
652 } else {
653 None
654 };
655 Some(FlagOptionParam::create_help_flag(
656 short,
657 long_prefix,
658 describe,
659 ))
660 }
661
662 fn create_version_flag(&self, describe: bool, long_prefix: &str) -> Option<FlagOptionParam> {
663 if !self.exist_version() {
664 return None;
665 }
666 if self.find_flag_option("version").is_some() {
667 return None;
668 }
669 let describe = if describe { "Print version" } else { "" };
670 let short = if self.find_flag_option("-V").is_none() {
671 Some("-V")
672 } else {
673 None
674 };
675 Some(FlagOptionParam::create_version_flag(
676 short,
677 long_prefix,
678 describe,
679 ))
680 }
681}
682
683#[cfg(any(feature = "build", feature = "eval"))]
684impl Command {
685 pub(crate) fn render_help(&self, wrap_width: Option<usize>) -> String {
686 let mut output = vec![];
687 if !&self.describe.is_empty() {
688 output.push(render_block("", &self.describe, wrap_width));
689 }
690 if !output.is_empty() {
691 output.push(String::new());
692 }
693 output.push(self.render_usage());
694 output.push(String::new());
695 output.extend(self.render_positionals(wrap_width));
696 output.extend(self.render_flag_options(wrap_width));
697 output.extend(self.render_subcommands(wrap_width));
698 output.extend(self.render_envs(wrap_width));
699 if output.is_empty() {
700 return "\n".to_string();
701 }
702 output.join("\n")
703 }
704
705 fn render_usage(&self) -> String {
706 let mut output = vec!["USAGE:".to_string()];
707 output.extend(self.cmd_paths());
708 let required_options: Vec<String> = self
709 .flag_option_params
710 .iter()
711 .filter(|v| v.required())
712 .map(|v| v.render_name_notations())
713 .collect();
714 if self.flag_option_params.len() != required_options.len() {
715 output.push("[OPTIONS]".to_string());
716 }
717 output.extend(required_options);
718 if !self.subcommands.is_empty() {
719 output.push("<COMMAND>".to_string());
720 } else {
721 output.extend(self.positional_params.iter().map(|v| v.render_notation()));
722 }
723 output.join(" ")
724 }
725
726 fn render_flag_options(&self, wrap_width: Option<usize>) -> Vec<String> {
727 let mut output = vec![];
728 let default_subcmd = self.find_default_subcommand();
729 if self.flag_option_params.is_empty()
730 && default_subcmd
731 .map(|subcmd| subcmd.flag_option_params.is_empty())
732 .unwrap_or(true)
733 {
734 return output;
735 }
736
737 let params = match default_subcmd {
738 Some(subcmd) => [self.all_flag_options(), subcmd.all_flag_options()].concat(),
739 None => self.all_flag_options(),
740 };
741
742 let mut value_size = 0;
743 let list: IndexMap<String, String> = params
744 .into_iter()
745 .map(|param| {
746 let value = param.render_body();
747 let describe = param.render_describe();
748 value_size = value_size.max(value.len());
749 (value, describe)
750 })
751 .collect();
752 value_size += 2;
753 output.push("OPTIONS:".to_string());
754 render_list(
755 &mut output,
756 list.into_iter().collect(),
757 value_size,
758 wrap_width,
759 );
760 output
761 }
762
763 fn render_positionals(&self, wrap_width: Option<usize>) -> Vec<String> {
764 let mut output = vec![];
765 let params = match self.find_default_subcommand() {
766 Some(subcmd) => &subcmd.positional_params,
767 None => &self.positional_params,
768 };
769 if params.is_empty() {
770 return output;
771 }
772 let mut value_size = 0;
773 let list: Vec<_> = params
774 .iter()
775 .map(|param| {
776 let value = param.render_notation();
777 value_size = value_size.max(value.len());
778 (value, param.render_describe())
779 })
780 .collect();
781 value_size += 2;
782 output.push("ARGS:".to_string());
783 render_list(&mut output, list, value_size, wrap_width);
784 output
785 }
786
787 fn render_envs(&self, wrap_width: Option<usize>) -> Vec<String> {
788 let mut output = vec![];
789 let params = match self.find_default_subcommand() {
790 Some(subcmd) => &subcmd.env_params,
791 None => &self.env_params,
792 };
793 if params.is_empty() {
794 return output;
795 }
796 let mut value_size = 0;
797 let list: Vec<_> = params
798 .iter()
799 .map(|param| {
800 let value = param.render_body();
801 value_size = value_size.max(value.len());
802 (value, param.render_describe())
803 })
804 .collect();
805 value_size += 2;
806 output.push("ENVIRONMENTS:".to_string());
807 render_list(&mut output, list, value_size, wrap_width);
808 output
809 }
810
811 fn render_subcommands(&self, wrap_width: Option<usize>) -> Vec<String> {
812 let mut output = vec![];
813 if self.subcommands.is_empty() {
814 return output;
815 }
816 let mut value_size = 0;
817 let list: Vec<_> = self
818 .subcommands
819 .iter()
820 .map(|subcmd| {
821 let value = subcmd.cmd_name();
822 value_size = value_size.max(value.len());
823 (value, subcmd.render_subcommand_describe())
824 })
825 .collect();
826 value_size += 2;
827 output.push("COMMANDS:".to_string());
828 render_list(&mut output, list, value_size, wrap_width);
829 output
830 }
831
832 fn render_subcommand_describe(&self) -> String {
833 let mut output = self.describe_oneline().to_string();
834 if let Some((aliases, _)) = &self.aliases {
835 if !output.is_empty() {
836 output.push(' ')
837 }
838 output.push_str(&format!("[aliases: {}]", aliases.join(", ")));
839 }
840 if self.has_metadata(META_DEFAULT_SUBCOMMAND) {
841 if !output.is_empty() {
842 output.push(' ')
843 }
844 output.push_str("[default]");
845 }
846 output
847 }
848}
849
850#[cfg(feature = "export")]
851#[derive(Debug, Serialize)]
852pub struct CommandValue {
853 pub name: String,
854 pub describe: String,
855 pub version: Option<String>,
856 pub aliases: Vec<String>,
857 pub flag_options: Vec<FlagOptionValue>,
858 pub positionals: Vec<PositionalValue>,
859 pub envs: Vec<EnvValue>,
860 pub subcommands: Vec<CommandValue>,
861 #[serde(flatten)]
862 pub extra: IndexMap<String, serde_json::Value>,
863}
864
865pub(crate) type SymbolParam = (String, Option<String>);
866
867fn retrieve_cmd<'a>(cmd: &'a mut Command, paths: &[String]) -> Option<&'a mut Command> {
868 if paths.is_empty() {
869 return Some(cmd);
870 }
871 let child = cmd
872 .subcommands
873 .iter_mut()
874 .find(|v| v.name.as_deref() == Some(paths[0].as_str()))?;
875 retrieve_cmd(child, &paths[1..])
876}
877
878fn update_parent_cmd(parent: &mut Command) -> Result<()> {
879 let index = parent.subcommands.len() - 1;
880 let subcmd = &parent.subcommands[index];
881 if let Some((_, _, meta_pos)) = subcmd
882 .metadata
883 .iter()
884 .find(|(k, _, _)| k == META_DEFAULT_SUBCOMMAND)
885 {
886 if !parent.positional_params.is_empty() {
887 bail!(
888 "@meta(line {}) can't be added since the parent command has positional parameters",
889 meta_pos
890 )
891 }
892 if let Some((_, exist_pos)) = &parent.default_subcommand {
893 bail!("@meta(line {}) conflicts with {}", meta_pos, exist_pos)
894 } else {
895 parent.default_subcommand = Some((index, *meta_pos))
896 }
897 }
898 Ok(())
899}
900
901fn sanitize_cmd_name(name: &str) -> String {
902 name.trim_end_matches('_').to_string()
903}
904
905fn render_list(
906 output: &mut Vec<String>,
907 list: Vec<(String, String)>,
908 value_size: usize,
909 wrap_width: Option<usize>,
910) {
911 let mut mapped_list = vec![];
912 let multiline = list.iter().any(|(_, describe)| describe.contains('\n'));
913 for (value, describe) in list {
914 let item = if describe.is_empty() {
915 let maybe_newline = if multiline { "\n" } else { "" };
916 format!(" {value}{maybe_newline}")
917 } else if multiline {
918 format!(
919 " {value}\n{}\n",
920 render_block(&" ".repeat(10), &describe, wrap_width)
921 )
922 } else {
923 let spaces = " ".repeat(value_size - value.len());
924 render_block(&format!(" {value}{spaces}"), &describe, wrap_width)
925 };
926 mapped_list.push(item);
927 }
928 for item in mapped_list {
929 output.push(item);
930 }
931 if !multiline {
932 output.push("".to_string());
933 }
934}
935
936fn render_block(name: &str, describe: &str, wrap_width: Option<usize>) -> String {
937 let size = wrap_width.unwrap_or(999) - name.len();
938 let empty = " ".repeat(name.len());
939 describe
940 .split('\n')
941 .flat_map(|v| {
942 #[cfg(feature = "wrap-help")]
943 {
944 textwrap::wrap(v, size)
945 }
946 #[cfg(not(feature = "wrap-help"))]
947 {
948 vec![v]
949 }
950 })
951 .enumerate()
952 .map(|(i, v)| {
953 if i == 0 {
954 format!("{name}{v}")
955 } else {
956 format!("{empty}{v}")
957 }
958 })
959 .collect::<Vec<String>>()
960 .join("\n")
961}