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 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 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}