nushell/crates/nu-command/src/help/help_commands.rs
JT e77a0a48aa
Rename main to script name when running scripts (#9948)
# Description

This PR does three related changes:

* Keeps the originally declared name in help outputs.
* Updates the name of the commands called `main` in the user script to
the name of the script.
* Fixes the source of signature information in multiple places. This
allows scripts to have more complete help output.

Combined, the above allow the user to see the script name in the help
output of scripts, like so:


![image](https://github.com/nushell/nushell/assets/547158/741d192c-0a39-45a7-8f36-3a0dc8eeae2b)

NOTE: You still declare and call the definition `main`, so from inside
the script `main` is still the correct name. But multiple folks agreed
that seeing `main` in the script help was confusing, so this PR changes
that.

# User-Facing Changes

One potential minor breaking change is that module renames will be shown
as their originally defined name rather than the renamed name. I believe
this to be a better default.

# 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 -A clippy::result_large_err` to check that
you're using the standard code style
- `cargo test --workspace` to check that all tests pass
- `cargo run -- -c "use std testing; testing run-tests --path
crates/nu-std"` to run the tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->

# 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.
-->
2023-08-12 05:58:49 +12:00

303 lines
9.7 KiB
Rust

use crate::help::highlight_search_in_table;
use nu_color_config::StyleComputer;
use nu_engine::{get_full_help, CallExt};
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
span, Category, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError,
Signature, Span, Spanned, SyntaxShape, Type, Value,
};
use std::borrow::Borrow;
#[derive(Clone)]
pub struct HelpCommands;
impl Command for HelpCommands {
fn name(&self) -> &str {
"help commands"
}
fn usage(&self) -> &str {
"Show help on nushell commands."
}
fn signature(&self) -> Signature {
Signature::build("help commands")
.category(Category::Core)
.rest(
"rest",
SyntaxShape::String,
"the name of command to get help on",
)
.named(
"find",
SyntaxShape::String,
"string to find in command names, usage, and search terms",
Some('f'),
)
.input_output_types(vec![(Type::Nothing, Type::Table(vec![]))])
.allow_variants_without_examples(true)
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
help_commands(engine_state, stack, call)
}
}
pub fn help_commands(
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));
let highlight_style =
style_computer.compute("search_result", &Value::string("search result", head));
if let Some(f) = find {
let all_cmds_vec = build_help_commands(engine_state, head);
let found_cmds_vec = highlight_search_in_table(
all_cmds_vec,
&f.item,
&["name", "usage", "search_terms"],
&string_style,
&highlight_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_commands(engine_state, 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_commands(engine_state: &EngineState, span: Span) -> Vec<Value> {
let commands = engine_state.get_decls_sorted(false);
let mut found_cmds_vec = Vec::new();
for (_, decl_id) in commands {
let mut cols = vec![];
let mut vals = vec![];
let decl = engine_state.get_decl(decl_id);
let sig = decl.signature().update_from_command(decl.borrow());
let key = sig.name;
let usage = sig.usage;
let search_terms = sig.search_terms;
cols.push("name".into());
vals.push(Value::String { val: key, span });
cols.push("category".into());
vals.push(Value::string(sig.category.to_string(), span));
cols.push("command_type".into());
vals.push(Value::String {
val: format!("{:?}", decl.command_type()).to_lowercase(),
span,
});
cols.push("usage".into());
vals.push(Value::String { val: usage, span });
cols.push("params".into());
// Build table of parameters
let param_table = {
let mut vals = vec![];
for required_param in &sig.required_positional {
vals.push(Value::Record {
cols: vec![
"name".to_string(),
"type".to_string(),
"required".to_string(),
"description".to_string(),
],
vals: vec![
Value::string(&required_param.name, span),
Value::string(required_param.shape.to_string(), span),
Value::bool(true, span),
Value::string(&required_param.desc, span),
],
span,
});
}
for optional_param in &sig.optional_positional {
vals.push(Value::Record {
cols: vec![
"name".to_string(),
"type".to_string(),
"required".to_string(),
"description".to_string(),
],
vals: vec![
Value::string(&optional_param.name, span),
Value::string(optional_param.shape.to_string(), span),
Value::bool(false, span),
Value::string(&optional_param.desc, span),
],
span,
});
}
if let Some(rest_positional) = &sig.rest_positional {
vals.push(Value::Record {
cols: vec![
"name".to_string(),
"type".to_string(),
"required".to_string(),
"description".to_string(),
],
vals: vec![
Value::string(format!("...{}", rest_positional.name), span),
Value::string(rest_positional.shape.to_string(), span),
Value::bool(false, span),
Value::string(&rest_positional.desc, span),
],
span,
});
}
for named_param in &sig.named {
let name = if let Some(short) = named_param.short {
if named_param.long.is_empty() {
format!("-{}", short)
} else {
format!("--{}(-{})", named_param.long, short)
}
} else {
format!("--{}", named_param.long)
};
vals.push(Value::Record {
cols: vec![
"name".to_string(),
"type".to_string(),
"required".to_string(),
"description".to_string(),
],
vals: vec![
Value::string(name, span),
Value::string(
if let Some(arg) = &named_param.arg {
arg.to_string()
} else {
"switch".to_string()
},
span,
),
Value::bool(named_param.required, span),
Value::string(&named_param.desc, span),
],
span,
});
}
Value::List { vals, span }
};
vals.push(param_table);
cols.push("input_output".into());
// Build the signature input/output table
let input_output_table = {
let mut vals = vec![];
for (input_type, output_type) in sig.input_output_types {
vals.push(Value::Record {
cols: vec!["input".to_string(), "output".to_string()],
vals: vec![
Value::String {
val: input_type.to_string(),
span,
},
Value::String {
val: output_type.to_string(),
span,
},
],
span,
});
}
Value::List { vals, span }
};
vals.push(input_output_table);
cols.push("search_terms".into());
vals.push(Value::String {
val: search_terms.join(", "),
span,
});
found_cmds_vec.push(Value::Record { cols, vals, span });
}
found_cmds_vec
}
#[cfg(test)]
mod test {
#[test]
fn test_examples() {
use super::HelpCommands;
use crate::test_examples;
test_examples(HelpCommands {})
}
}