argc/command/
mod.rs

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    // (key, value, position)
47    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(&param, 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(&param, 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        // auto alias if command name contains `_`
486        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        // update command_fn
503        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        // update derived_flag_option_params
546        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        // update require_tools
567        require_tools.extend(self.meta_require_tools());
568        self.require_tools = require_tools;
569
570        // update recursively
571        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(&param, 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}