nu_command/help/
help_modules.rs1use crate::help::highlight_search_in_table;
2use nu_color_config::StyleComputer;
3use nu_engine::{command_prelude::*, scope::ScopeData};
4use nu_protocol::DeclId;
5
6#[derive(Clone)]
7pub struct HelpModules;
8
9impl Command for HelpModules {
10 fn name(&self) -> &str {
11 "help modules"
12 }
13
14 fn description(&self) -> &str {
15 "Show help on nushell modules."
16 }
17
18 fn extra_description(&self) -> &str {
19 r#"When requesting help for a single module, its commands and aliases will be highlighted if they
20are also available in the current scope. Commands/aliases that were imported under a different name
21(such as with a prefix after `use some-module`) will be highlighted in parentheses."#
22 }
23
24 fn signature(&self) -> Signature {
25 Signature::build("help modules")
26 .category(Category::Core)
27 .rest(
28 "rest",
29 SyntaxShape::String,
30 "The name of module to get help on.",
31 )
32 .named(
33 "find",
34 SyntaxShape::String,
35 "string to find in module names and descriptions",
36 Some('f'),
37 )
38 .input_output_types(vec![(Type::Nothing, Type::table())])
39 .allow_variants_without_examples(true)
40 }
41
42 fn examples(&self) -> Vec<Example> {
43 vec![
44 Example {
45 description: "show all modules",
46 example: "help modules",
47 result: None,
48 },
49 Example {
50 description: "show help for single module",
51 example: "help modules my-module",
52 result: None,
53 },
54 Example {
55 description: "search for string in module names and descriptions",
56 example: "help modules --find my-module",
57 result: None,
58 },
59 ]
60 }
61
62 fn run(
63 &self,
64 engine_state: &EngineState,
65 stack: &mut Stack,
66 call: &Call,
67 _input: PipelineData,
68 ) -> Result<PipelineData, ShellError> {
69 help_modules(engine_state, stack, call)
70 }
71}
72
73pub fn help_modules(
74 engine_state: &EngineState,
75 stack: &mut Stack,
76 call: &Call,
77) -> Result<PipelineData, ShellError> {
78 let head = call.head;
79 let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
80 let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
81
82 let style_computer = StyleComputer::from_config(engine_state, stack);
84 let string_style = style_computer.compute("string", &Value::string("search result", head));
88 let highlight_style =
89 style_computer.compute("search_result", &Value::string("search result", head));
90
91 if let Some(f) = find {
92 let all_cmds_vec = build_help_modules(engine_state, stack, head);
93 let found_cmds_vec = highlight_search_in_table(
94 all_cmds_vec,
95 &f.item,
96 &["name", "description"],
97 &string_style,
98 &highlight_style,
99 )?;
100
101 return Ok(Value::list(found_cmds_vec, head).into_pipeline_data());
102 }
103
104 if rest.is_empty() {
105 let found_cmds_vec = build_help_modules(engine_state, stack, head);
106 Ok(Value::list(found_cmds_vec, head).into_pipeline_data())
107 } else {
108 let mut name = String::new();
109
110 for r in &rest {
111 if !name.is_empty() {
112 name.push(' ');
113 }
114 name.push_str(&r.item);
115 }
116
117 let Some(module_id) = engine_state.find_module(name.as_bytes(), &[]) else {
118 return Err(ShellError::ModuleNotFoundAtRuntime {
119 mod_name: name,
120 span: Span::merge_many(rest.iter().map(|s| s.span)),
121 });
122 };
123
124 let module = engine_state.get_module(module_id);
125
126 let module_desc = engine_state.build_module_desc(module_id);
127
128 const G: &str = "\x1b[32m"; const C: &str = "\x1b[36m"; const CB: &str = "\x1b[1;36m"; const RESET: &str = "\x1b[0m"; let mut long_desc = String::new();
135
136 if let Some((desc, extra_desc)) = module_desc {
137 long_desc.push_str(&desc);
138 long_desc.push_str("\n\n");
139
140 if !extra_desc.is_empty() {
141 long_desc.push_str(&extra_desc);
142 long_desc.push_str("\n\n");
143 }
144 }
145
146 long_desc.push_str(&format!("{G}Module{RESET}: {C}{name}{RESET}"));
147 long_desc.push_str("\n\n");
148
149 if !module.decls.is_empty() || module.main.is_some() {
150 let commands: Vec<(Vec<u8>, DeclId)> = engine_state
151 .get_decls_sorted(false)
152 .into_iter()
153 .filter(|(_, id)| !engine_state.get_decl(*id).is_alias())
154 .collect();
155
156 let mut module_commands: Vec<(Vec<u8>, DeclId)> = module
157 .decls()
158 .into_iter()
159 .filter(|(_, id)| !engine_state.get_decl(*id).is_alias())
160 .collect();
161 module_commands.sort_by(|a, b| a.0.cmp(&b.0));
162
163 let commands_str = module_commands
164 .iter()
165 .map(|(name_bytes, id)| {
166 let name = String::from_utf8_lossy(name_bytes);
167 if let Some((used_name_bytes, _)) =
168 commands.iter().find(|(_, decl_id)| id == decl_id)
169 {
170 if engine_state.find_decl(name.as_bytes(), &[]).is_some() {
171 format!("{CB}{name}{RESET}")
172 } else {
173 let command_name = String::from_utf8_lossy(used_name_bytes);
174 format!("{name} ({CB}{command_name}{RESET})")
175 }
176 } else {
177 format!("{name}")
178 }
179 })
180 .collect::<Vec<String>>()
181 .join(", ");
182
183 long_desc.push_str(&format!("{G}Exported commands{RESET}:\n {commands_str}"));
184 long_desc.push_str("\n\n");
185 }
186
187 if !module.decls.is_empty() {
188 let aliases: Vec<(Vec<u8>, DeclId)> = engine_state
189 .get_decls_sorted(false)
190 .into_iter()
191 .filter(|(_, id)| engine_state.get_decl(*id).is_alias())
192 .collect();
193
194 let mut module_aliases: Vec<(Vec<u8>, DeclId)> = module
195 .decls()
196 .into_iter()
197 .filter(|(_, id)| engine_state.get_decl(*id).is_alias())
198 .collect();
199 module_aliases.sort_by(|a, b| a.0.cmp(&b.0));
200
201 let aliases_str = module_aliases
202 .iter()
203 .map(|(name_bytes, id)| {
204 let name = String::from_utf8_lossy(name_bytes);
205 if let Some((used_name_bytes, _)) =
206 aliases.iter().find(|(_, alias_id)| id == alias_id)
207 {
208 if engine_state.find_decl(name.as_bytes(), &[]).is_some() {
209 format!("{CB}{name}{RESET}")
210 } else {
211 let alias_name = String::from_utf8_lossy(used_name_bytes);
212 format!("{name} ({CB}{alias_name}{RESET})")
213 }
214 } else {
215 format!("{name}")
216 }
217 })
218 .collect::<Vec<String>>()
219 .join(", ");
220
221 long_desc.push_str(&format!("{G}Exported aliases{RESET}:\n {aliases_str}"));
222 long_desc.push_str("\n\n");
223 }
224
225 if module.env_block.is_some() {
226 long_desc.push_str(&format!("This module {C}exports{RESET} environment."));
227 } else {
228 long_desc.push_str(&format!(
229 "This module {C}does not export{RESET} environment."
230 ));
231 }
232
233 let config = stack.get_config(engine_state);
234 if !config.use_ansi_coloring.get(engine_state) {
235 long_desc = nu_utils::strip_ansi_string_likely(long_desc);
236 }
237
238 Ok(Value::string(long_desc, call.head).into_pipeline_data())
239 }
240}
241
242fn build_help_modules(engine_state: &EngineState, stack: &Stack, span: Span) -> Vec<Value> {
243 let mut scope_data = ScopeData::new(engine_state, stack);
244 scope_data.populate_modules();
245
246 scope_data.collect_modules(span)
247}
248
249#[cfg(test)]
250mod test {
251 #[test]
252 fn test_examples() {
253 use super::HelpModules;
254 use crate::test_examples;
255 test_examples(HelpModules {})
256 }
257}