Skip to main content

nu_command/help/
help_modules.rs

1use crate::filters::find_internal;
2use nu_engine::{command_prelude::*, scope::ScopeData};
3use nu_protocol::DeclId;
4use std::fmt::Write;
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        "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    if let Some(f) = find {
83        let all_cmds_vec = build_help_modules(engine_state, stack, head);
84        return find_internal(
85            all_cmds_vec,
86            engine_state,
87            stack,
88            &f.item,
89            &["name", "description"],
90            true,
91            head,
92        );
93    }
94
95    if rest.is_empty() {
96        Ok(build_help_modules(engine_state, stack, head))
97    } else {
98        let mut name = String::new();
99
100        for r in &rest {
101            if !name.is_empty() {
102                name.push(' ');
103            }
104            name.push_str(&r.item);
105        }
106
107        let Some(module_id) = engine_state.find_module(name.as_bytes(), &[]) else {
108            return Err(ShellError::ModuleNotFoundAtRuntime {
109                mod_name: name,
110                span: Span::merge_many(rest.iter().map(|s| s.span)),
111            });
112        };
113
114        let module = engine_state.get_module(module_id);
115
116        let module_desc = engine_state.build_module_desc(module_id);
117
118        // TODO: merge this into documentation.rs at some point
119        const G: &str = "\x1b[32m"; // green
120        const C: &str = "\x1b[36m"; // cyan
121        const CB: &str = "\x1b[1;36m"; // cyan bold
122        const RESET: &str = "\x1b[0m"; // reset
123
124        let mut long_desc = String::new();
125
126        if let Some((desc, extra_desc)) = module_desc {
127            long_desc.push_str(&desc);
128            long_desc.push_str("\n\n");
129
130            if !extra_desc.is_empty() {
131                long_desc.push_str(&extra_desc);
132                long_desc.push_str("\n\n");
133            }
134        }
135
136        write!(long_desc, "{G}Module{RESET}: {C}{name}{RESET}")
137            .expect("writing to a String is infallible");
138        long_desc.push_str("\n\n");
139
140        if !module.decls.is_empty() || module.main.is_some() {
141            let commands: Vec<(Vec<u8>, DeclId)> = engine_state
142                .get_decls_sorted(false)
143                .into_iter()
144                .filter(|(_, id)| !engine_state.get_decl(*id).is_alias())
145                .collect();
146
147            let mut module_commands: Vec<(Vec<u8>, DeclId)> = module
148                .decls()
149                .into_iter()
150                .filter(|(_, id)| !engine_state.get_decl(*id).is_alias())
151                .collect();
152            module_commands.sort_by(|a, b| a.0.cmp(&b.0));
153
154            let commands_str = module_commands
155                .iter()
156                .map(|(name_bytes, id)| {
157                    let name = String::from_utf8_lossy(name_bytes);
158                    if let Some((used_name_bytes, _)) =
159                        commands.iter().find(|(_, decl_id)| id == decl_id)
160                    {
161                        if engine_state.find_decl(name.as_bytes(), &[]).is_some() {
162                            format!("{CB}{name}{RESET}")
163                        } else {
164                            let command_name = String::from_utf8_lossy(used_name_bytes);
165                            format!("{name} ({CB}{command_name}{RESET})")
166                        }
167                    } else {
168                        format!("{name}")
169                    }
170                })
171                .collect::<Vec<String>>()
172                .join(", ");
173
174            write!(long_desc, "{G}Exported commands{RESET}:\n  {commands_str}")
175                .expect("writing to a String is infallible");
176            long_desc.push_str("\n\n");
177        }
178
179        if !module.decls.is_empty() {
180            let aliases: Vec<(Vec<u8>, DeclId)> = engine_state
181                .get_decls_sorted(false)
182                .into_iter()
183                .filter(|(_, id)| engine_state.get_decl(*id).is_alias())
184                .collect();
185
186            let mut module_aliases: Vec<(Vec<u8>, DeclId)> = module
187                .decls()
188                .into_iter()
189                .filter(|(_, id)| engine_state.get_decl(*id).is_alias())
190                .collect();
191            module_aliases.sort_by(|a, b| a.0.cmp(&b.0));
192
193            let aliases_str = module_aliases
194                .iter()
195                .map(|(name_bytes, id)| {
196                    let name = String::from_utf8_lossy(name_bytes);
197                    if let Some((used_name_bytes, _)) =
198                        aliases.iter().find(|(_, alias_id)| id == alias_id)
199                    {
200                        if engine_state.find_decl(name.as_bytes(), &[]).is_some() {
201                            format!("{CB}{name}{RESET}")
202                        } else {
203                            let alias_name = String::from_utf8_lossy(used_name_bytes);
204                            format!("{name} ({CB}{alias_name}{RESET})")
205                        }
206                    } else {
207                        format!("{name}")
208                    }
209                })
210                .collect::<Vec<String>>()
211                .join(", ");
212
213            write!(long_desc, "{G}Exported aliases{RESET}:\n  {aliases_str}")
214                .expect("writing to a String is infallible");
215            long_desc.push_str("\n\n");
216        }
217
218        if module.env_block.is_some() {
219            write!(long_desc, "This module {C}exports{RESET} environment.")
220                .expect("writing to a String is infallible");
221        } else {
222            write!(
223                long_desc,
224                "This module {C}does not export{RESET} environment."
225            )
226            .expect("writing to a String is infallible");
227        }
228
229        let config = stack.get_config(engine_state);
230        if !config.use_ansi_coloring.get(engine_state) {
231            long_desc = nu_utils::strip_ansi_string_likely(long_desc);
232        }
233
234        Ok(Value::string(long_desc, call.head).into_pipeline_data())
235    }
236}
237
238fn build_help_modules(engine_state: &EngineState, stack: &Stack, span: Span) -> PipelineData {
239    let mut scope_data = ScopeData::new(engine_state, stack);
240    scope_data.populate_modules();
241
242    Value::list(scope_data.collect_modules(span), span).into_pipeline_data()
243}
244
245#[cfg(test)]
246mod test {
247    #[test]
248    fn test_examples() -> nu_test_support::Result {
249        use super::HelpModules;
250        nu_test_support::test().examples(HelpModules)
251    }
252}