nu_command/help/
help_commands.rs

1use crate::filters::find_internal;
2use nu_engine::{command_prelude::*, get_full_help};
3
4#[derive(Clone)]
5pub struct HelpCommands;
6
7impl Command for HelpCommands {
8    fn name(&self) -> &str {
9        "help commands"
10    }
11
12    fn description(&self) -> &str {
13        "Show help on nushell commands."
14    }
15
16    fn signature(&self) -> Signature {
17        Signature::build("help commands")
18            .category(Category::Core)
19            .rest(
20                "rest",
21                SyntaxShape::String,
22                "The name of command to get help on.",
23            )
24            .named(
25                "find",
26                SyntaxShape::String,
27                "string to find in command names, descriptions, and search terms",
28                Some('f'),
29            )
30            .input_output_types(vec![(Type::Nothing, Type::table())])
31            .allow_variants_without_examples(true)
32    }
33
34    fn run(
35        &self,
36        engine_state: &EngineState,
37        stack: &mut Stack,
38        call: &Call,
39        _input: PipelineData,
40    ) -> Result<PipelineData, ShellError> {
41        help_commands(engine_state, stack, call)
42    }
43}
44
45pub fn help_commands(
46    engine_state: &EngineState,
47    stack: &mut Stack,
48    call: &Call,
49) -> Result<PipelineData, ShellError> {
50    let head = call.head;
51    let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
52    let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
53
54    if let Some(f) = find {
55        let all_cmds_vec = build_help_commands(engine_state, head);
56        return find_internal(
57            all_cmds_vec,
58            engine_state,
59            stack,
60            &f.item,
61            &["name", "description", "search_terms"],
62            true,
63        );
64    }
65
66    if rest.is_empty() {
67        Ok(build_help_commands(engine_state, head))
68    } else {
69        let mut name = String::new();
70
71        for r in &rest {
72            if !name.is_empty() {
73                name.push(' ');
74            }
75            name.push_str(&r.item);
76        }
77
78        if let Some(decl) = engine_state.find_decl(name.as_bytes(), &[]) {
79            let cmd = engine_state.get_decl(decl);
80            let help_text = get_full_help(cmd, engine_state, stack);
81            Ok(Value::string(help_text, call.head).into_pipeline_data())
82        } else {
83            Err(ShellError::CommandNotFound {
84                span: Span::merge_many(rest.iter().map(|s| s.span)),
85            })
86        }
87    }
88}
89
90fn build_help_commands(engine_state: &EngineState, span: Span) -> PipelineData {
91    let commands = engine_state.get_decls_sorted(false);
92    let mut found_cmds_vec = Vec::new();
93
94    for (_, decl_id) in commands {
95        let decl = engine_state.get_decl(decl_id);
96        let sig = decl.signature().update_from_command(decl);
97
98        let key = sig.name;
99        let description = sig.description;
100        let search_terms = sig.search_terms;
101
102        let command_type = decl.command_type().to_string();
103
104        // Build table of parameters
105        let param_table = {
106            let mut vals = vec![];
107
108            for required_param in &sig.required_positional {
109                vals.push(Value::record(
110                    record! {
111                        "name" => Value::string(&required_param.name, span),
112                        "type" => Value::string(required_param.shape.to_string(), span),
113                        "required" => Value::bool(true, span),
114                        "description" => Value::string(&required_param.desc, span),
115                    },
116                    span,
117                ));
118            }
119
120            for optional_param in &sig.optional_positional {
121                vals.push(Value::record(
122                    record! {
123                        "name" => Value::string(&optional_param.name, span),
124                        "type" => Value::string(optional_param.shape.to_string(), span),
125                        "required" => Value::bool(false, span),
126                        "description" => Value::string(&optional_param.desc, span),
127                    },
128                    span,
129                ));
130            }
131
132            if let Some(rest_positional) = &sig.rest_positional {
133                vals.push(Value::record(
134                    record! {
135                        "name" => Value::string(format!("...{}", rest_positional.name), span),
136                        "type" => Value::string(rest_positional.shape.to_string(), span),
137                        "required" => Value::bool(false, span),
138                        "description" => Value::string(&rest_positional.desc, span),
139                    },
140                    span,
141                ));
142            }
143
144            for named_param in &sig.named {
145                let name = if let Some(short) = named_param.short {
146                    if named_param.long.is_empty() {
147                        format!("-{short}")
148                    } else {
149                        format!("--{}(-{})", named_param.long, short)
150                    }
151                } else {
152                    format!("--{}", named_param.long)
153                };
154
155                let typ = if let Some(arg) = &named_param.arg {
156                    arg.to_string()
157                } else {
158                    "switch".to_string()
159                };
160
161                vals.push(Value::record(
162                    record! {
163                        "name" => Value::string(name, span),
164                        "type" => Value::string(typ, span),
165                        "required" => Value::bool(named_param.required, span),
166                        "description" => Value::string(&named_param.desc, span),
167                    },
168                    span,
169                ));
170            }
171
172            Value::list(vals, span)
173        };
174
175        // Build the signature input/output table
176        let input_output_table = {
177            let mut vals = vec![];
178
179            for (input_type, output_type) in sig.input_output_types {
180                vals.push(Value::record(
181                    record! {
182                        "input" => Value::string(input_type.to_string(), span),
183                        "output" => Value::string(output_type.to_string(), span),
184                    },
185                    span,
186                ));
187            }
188
189            Value::list(vals, span)
190        };
191
192        let record = record! {
193            "name" => Value::string(key, span),
194            "category" => Value::string(sig.category.to_string(), span),
195            "command_type" => Value::string(command_type, span),
196            "description" => Value::string(description, span),
197            "params" => param_table,
198            "input_output" => input_output_table,
199            "search_terms" => Value::string(search_terms.join(", "), span),
200            "is_const" => Value::bool(decl.is_const(), span),
201        };
202
203        found_cmds_vec.push(Value::record(record, span));
204    }
205
206    Value::list(found_cmds_vec, span).into_pipeline_data()
207}
208
209#[cfg(test)]
210mod test {
211    #[test]
212    fn test_examples() {
213        use super::HelpCommands;
214        use crate::test_examples;
215        test_examples(HelpCommands {})
216    }
217}