mirror of
https://github.com/nushell/nushell
synced 2024-12-28 14:03:09 +00:00
Added help externs
command (#8403)
# Description `help externs` - command, which list external commands Closes https://github.com/nushell/nushell/issues/8301 # User-Facing Changes ```nu $ help externs ╭───┬──────────────┬─────────────┬───────────────────────────────────────────────────╮ │ # │ name │ module_name │ usage │ ├───┼──────────────┼─────────────┼───────────────────────────────────────────────────┤ │ 0 │ git push │ completions │ Push changes │ │ │ │ │ │ │ 1 │ git fetch │ completions │ Download objects and refs from another repository │ │ │ │ │ │ │ 2 │ git checkout │ completions │ Check out git branches and files │ │ │ │ │ │ ╰───┴──────────────┴─────────────┴───────────────────────────────────────────────────╯ ``` # Tests + Formatting Don't forget to add tests that cover your changes. Make sure you've run and fixed any issues with these commands: - `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style - `cargo test --workspace` to check that all tests pass # After Submitting If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date.
This commit is contained in:
parent
f34ac9be62
commit
4de0347fdc
4 changed files with 201 additions and 0 deletions
152
crates/nu-cmd-lang/src/core_commands/help_externs.rs
Normal file
152
crates/nu-cmd-lang/src/core_commands/help_externs.rs
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
use crate::help::highlight_search_in_table;
|
||||||
|
use nu_color_config::StyleComputer;
|
||||||
|
use nu_engine::{get_full_help, scope::ScopeData, CallExt};
|
||||||
|
use nu_protocol::{
|
||||||
|
ast::Call,
|
||||||
|
engine::{Command, EngineState, Stack},
|
||||||
|
span, Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
|
||||||
|
ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct HelpExterns;
|
||||||
|
|
||||||
|
impl Command for HelpExterns {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"help externs"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Show help on nushell externs."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("help externs")
|
||||||
|
.category(Category::Core)
|
||||||
|
.rest(
|
||||||
|
"rest",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"the name of extern to get help on",
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"find",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"string to find in extern names and usage",
|
||||||
|
Some('f'),
|
||||||
|
)
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Table(vec![]))])
|
||||||
|
.allow_variants_without_examples(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "show all externs",
|
||||||
|
example: "help externs",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "show help for single extern",
|
||||||
|
example: "help externs smth",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "search for string in extern names and usages",
|
||||||
|
example: "help externs --find smth",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
help_externs(engine_state, stack, call)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn help_externs(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
|
||||||
|
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
|
||||||
|
|
||||||
|
// 🚩The following two-lines are copied from filters/find.rs:
|
||||||
|
let style_computer = StyleComputer::from_config(engine_state, stack);
|
||||||
|
// Currently, search results all use the same style.
|
||||||
|
// Also note that this sample string is passed into user-written code (the closure that may or may not be
|
||||||
|
// defined for "string").
|
||||||
|
let string_style = style_computer.compute("string", &Value::string("search result", head));
|
||||||
|
|
||||||
|
if let Some(f) = find {
|
||||||
|
let all_cmds_vec = build_help_externs(engine_state, stack, head);
|
||||||
|
let found_cmds_vec =
|
||||||
|
highlight_search_in_table(all_cmds_vec, &f.item, &["name", "usage"], &string_style)?;
|
||||||
|
|
||||||
|
return Ok(found_cmds_vec
|
||||||
|
.into_iter()
|
||||||
|
.into_pipeline_data(engine_state.ctrlc.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if rest.is_empty() {
|
||||||
|
let found_cmds_vec = build_help_externs(engine_state, stack, head);
|
||||||
|
|
||||||
|
Ok(found_cmds_vec
|
||||||
|
.into_iter()
|
||||||
|
.into_pipeline_data(engine_state.ctrlc.clone()))
|
||||||
|
} else {
|
||||||
|
let mut name = String::new();
|
||||||
|
|
||||||
|
for r in &rest {
|
||||||
|
if !name.is_empty() {
|
||||||
|
name.push(' ');
|
||||||
|
}
|
||||||
|
name.push_str(&r.item);
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = engine_state
|
||||||
|
.get_signatures_with_examples(false)
|
||||||
|
.iter()
|
||||||
|
.filter(|(signature, _, _, _, _)| signature.name == name)
|
||||||
|
.map(|(signature, examples, _, _, is_parser_keyword)| {
|
||||||
|
get_full_help(signature, examples, engine_state, stack, *is_parser_keyword)
|
||||||
|
})
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
|
if !output.is_empty() {
|
||||||
|
Ok(Value::String {
|
||||||
|
val: output.join("======================\n\n"),
|
||||||
|
span: call.head,
|
||||||
|
}
|
||||||
|
.into_pipeline_data())
|
||||||
|
} else {
|
||||||
|
Err(ShellError::CommandNotFound(span(&[
|
||||||
|
rest[0].span,
|
||||||
|
rest[rest.len() - 1].span,
|
||||||
|
])))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_help_externs(engine_state: &EngineState, stack: &Stack, span: Span) -> Vec<Value> {
|
||||||
|
let mut scope = ScopeData::new(engine_state, stack);
|
||||||
|
scope.populate_all();
|
||||||
|
scope.collect_externs(span)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use super::HelpExterns;
|
||||||
|
use crate::test_examples;
|
||||||
|
test_examples(HelpExterns {})
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ mod for_;
|
||||||
pub mod help;
|
pub mod help;
|
||||||
pub mod help_aliases;
|
pub mod help_aliases;
|
||||||
pub mod help_commands;
|
pub mod help_commands;
|
||||||
|
pub mod help_externs;
|
||||||
pub mod help_modules;
|
pub mod help_modules;
|
||||||
mod help_operators;
|
mod help_operators;
|
||||||
mod hide;
|
mod hide;
|
||||||
|
@ -59,6 +60,7 @@ pub use for_::For;
|
||||||
pub use help::Help;
|
pub use help::Help;
|
||||||
pub use help_aliases::HelpAliases;
|
pub use help_aliases::HelpAliases;
|
||||||
pub use help_commands::HelpCommands;
|
pub use help_commands::HelpCommands;
|
||||||
|
pub use help_externs::HelpExterns;
|
||||||
pub use help_modules::HelpModules;
|
pub use help_modules::HelpModules;
|
||||||
pub use help_operators::HelpOperators;
|
pub use help_operators::HelpOperators;
|
||||||
pub use hide::Hide;
|
pub use hide::Hide;
|
||||||
|
|
|
@ -39,6 +39,7 @@ pub fn create_default_context() -> EngineState {
|
||||||
HelpAliases,
|
HelpAliases,
|
||||||
HelpCommands,
|
HelpCommands,
|
||||||
HelpModules,
|
HelpModules,
|
||||||
|
HelpExterns,
|
||||||
HelpOperators,
|
HelpOperators,
|
||||||
Hide,
|
Hide,
|
||||||
HideEnv,
|
HideEnv,
|
||||||
|
|
|
@ -463,6 +463,52 @@ impl<'e, 's> ScopeData<'e, 's> {
|
||||||
sig_records
|
sig_records
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn collect_externs(&self, span: Span) -> Vec<Value> {
|
||||||
|
let mut externals = vec![];
|
||||||
|
for ((command_name, _), decl_id) in &self.commands_map {
|
||||||
|
let decl = self.engine_state.get_decl(**decl_id);
|
||||||
|
|
||||||
|
if decl.is_known_external() {
|
||||||
|
let mut cols = vec![];
|
||||||
|
let mut vals = vec![];
|
||||||
|
|
||||||
|
let mut module_commands = vec![];
|
||||||
|
for module in &self.modules_map {
|
||||||
|
let module_name = String::from_utf8_lossy(module.0).to_string();
|
||||||
|
let module_id = self.engine_state.find_module(module.0, &[]);
|
||||||
|
if let Some(module_id) = module_id {
|
||||||
|
let module = self.engine_state.get_module(module_id);
|
||||||
|
if module.has_decl(command_name) {
|
||||||
|
module_commands.push(module_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cols.push("name".into());
|
||||||
|
vals.push(Value::String {
|
||||||
|
val: String::from_utf8_lossy(command_name).to_string(),
|
||||||
|
span,
|
||||||
|
});
|
||||||
|
|
||||||
|
cols.push("module_name".into());
|
||||||
|
vals.push(Value::String {
|
||||||
|
val: module_commands.join(", "),
|
||||||
|
span,
|
||||||
|
});
|
||||||
|
|
||||||
|
cols.push("usage".to_string());
|
||||||
|
vals.push(Value::String {
|
||||||
|
val: decl.usage().into(),
|
||||||
|
span,
|
||||||
|
});
|
||||||
|
|
||||||
|
externals.push(Value::Record { cols, vals, span })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
externals
|
||||||
|
}
|
||||||
|
|
||||||
pub fn collect_aliases(&self, span: Span) -> Vec<Value> {
|
pub fn collect_aliases(&self, span: Span) -> Vec<Value> {
|
||||||
let mut aliases = vec![];
|
let mut aliases = vec![];
|
||||||
for (alias_name, alias_id) in &self.aliases_map {
|
for (alias_name, alias_id) in &self.aliases_map {
|
||||||
|
|
Loading…
Reference in a new issue