nu_command/help/
help_modules.rs

1use 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    // 🚩The following two-lines are copied from filters/find.rs:
83    let style_computer = StyleComputer::from_config(engine_state, stack);
84    // Currently, search results all use the same style.
85    // Also note that this sample string is passed into user-written code (the closure that may or may not be
86    // defined for "string").
87    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        // TODO: merge this into documentation.rs at some point
129        const G: &str = "\x1b[32m"; // green
130        const C: &str = "\x1b[36m"; // cyan
131        const CB: &str = "\x1b[1;36m"; // cyan bold
132        const RESET: &str = "\x1b[0m"; // reset
133
134        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}