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