2020-04-27 04:04:54 +02:00
|
|
|
use crate::commands::WholeStreamCommand;
|
2019-12-04 20:52:31 +01:00
|
|
|
use crate::data::command_dict;
|
2020-01-17 23:46:18 +01:00
|
|
|
|
2019-08-30 00:52:32 +02:00
|
|
|
use crate::prelude::*;
|
Extract core stuff into own crates
This commit extracts five new crates:
- nu-source, which contains the core source-code handling logic in Nu,
including Text, Span, and also the pretty.rs-based debug logic
- nu-parser, which is the parser and expander logic
- nu-protocol, which is the bulk of the types and basic conveniences
used by plugins
- nu-errors, which contains ShellError, ParseError and error handling
conveniences
- nu-textview, which is the textview plugin extracted into a crate
One of the major consequences of this refactor is that it's no longer
possible to `impl X for Spanned<Y>` outside of the `nu-source` crate, so
a lot of types became more concrete (Value became a concrete type
instead of Spanned<Value>, for example).
This also turned a number of inherent methods in the main nu crate into
plain functions (impl Value {} became a bunch of functions in the
`value` namespace in `crate::data::value`).
2019-11-26 03:30:48 +01:00
|
|
|
use nu_errors::ShellError;
|
|
|
|
use nu_protocol::{
|
2020-04-27 04:04:54 +02:00
|
|
|
NamedType, PositionalType, ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder,
|
|
|
|
UntaggedValue,
|
Extract core stuff into own crates
This commit extracts five new crates:
- nu-source, which contains the core source-code handling logic in Nu,
including Text, Span, and also the pretty.rs-based debug logic
- nu-parser, which is the parser and expander logic
- nu-protocol, which is the bulk of the types and basic conveniences
used by plugins
- nu-errors, which contains ShellError, ParseError and error handling
conveniences
- nu-textview, which is the textview plugin extracted into a crate
One of the major consequences of this refactor is that it's no longer
possible to `impl X for Spanned<Y>` outside of the `nu-source` crate, so
a lot of types became more concrete (Value became a concrete type
instead of Spanned<Value>, for example).
This also turned a number of inherent methods in the main nu crate into
plain functions (impl Value {} became a bunch of functions in the
`value` namespace in `crate::data::value`).
2019-11-26 03:30:48 +01:00
|
|
|
};
|
2020-04-27 04:04:54 +02:00
|
|
|
use nu_source::{SpannedItem, Tagged};
|
2019-12-09 19:52:01 +01:00
|
|
|
use nu_value_ext::get_data_by_key;
|
2019-08-30 00:52:32 +02:00
|
|
|
|
|
|
|
pub struct Help;
|
|
|
|
|
2020-04-27 04:04:54 +02:00
|
|
|
#[derive(Deserialize)]
|
|
|
|
pub struct HelpArgs {
|
2020-05-04 10:44:33 +02:00
|
|
|
rest: Vec<Tagged<String>>,
|
2020-04-27 04:04:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
impl WholeStreamCommand for Help {
|
2019-08-30 00:52:32 +02:00
|
|
|
fn name(&self) -> &str {
|
|
|
|
"help"
|
|
|
|
}
|
|
|
|
|
Extract core stuff into own crates
This commit extracts five new crates:
- nu-source, which contains the core source-code handling logic in Nu,
including Text, Span, and also the pretty.rs-based debug logic
- nu-parser, which is the parser and expander logic
- nu-protocol, which is the bulk of the types and basic conveniences
used by plugins
- nu-errors, which contains ShellError, ParseError and error handling
conveniences
- nu-textview, which is the textview plugin extracted into a crate
One of the major consequences of this refactor is that it's no longer
possible to `impl X for Spanned<Y>` outside of the `nu-source` crate, so
a lot of types became more concrete (Value became a concrete type
instead of Spanned<Value>, for example).
This also turned a number of inherent methods in the main nu crate into
plain functions (impl Value {} became a bunch of functions in the
`value` namespace in `crate::data::value`).
2019-11-26 03:30:48 +01:00
|
|
|
fn signature(&self) -> Signature {
|
2020-05-04 10:44:33 +02:00
|
|
|
Signature::build("help").rest(SyntaxShape::String, "the name of command to get help on")
|
2019-08-30 00:52:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fn usage(&self) -> &str {
|
|
|
|
"Display help information about commands."
|
|
|
|
}
|
|
|
|
|
|
|
|
fn run(
|
|
|
|
&self,
|
2020-04-27 04:04:54 +02:00
|
|
|
args: CommandArgs,
|
2019-09-03 11:36:23 +02:00
|
|
|
registry: &CommandRegistry,
|
2019-08-30 00:52:32 +02:00
|
|
|
) -> Result<OutputStream, ShellError> {
|
2020-04-27 04:04:54 +02:00
|
|
|
args.process(registry, help)?.run()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn help(
|
2020-05-04 10:44:33 +02:00
|
|
|
HelpArgs { rest }: HelpArgs,
|
2020-04-27 04:04:54 +02:00
|
|
|
RunnableContext { registry, name, .. }: RunnableContext,
|
|
|
|
) -> Result<OutputStream, ShellError> {
|
2020-05-04 10:44:33 +02:00
|
|
|
if let Some(document) = rest.get(0) {
|
2020-04-27 04:04:54 +02:00
|
|
|
let mut help = VecDeque::new();
|
|
|
|
if document.item == "commands" {
|
|
|
|
let mut sorted_names = registry.names();
|
|
|
|
sorted_names.sort();
|
|
|
|
for cmd in sorted_names {
|
2020-05-04 10:44:33 +02:00
|
|
|
// If it's a subcommand, don't list it during the commands list
|
|
|
|
if cmd.contains(' ') {
|
|
|
|
continue;
|
|
|
|
}
|
2020-04-27 04:04:54 +02:00
|
|
|
let mut short_desc = TaggedDictBuilder::new(name.clone());
|
|
|
|
let document_tag = document.tag.clone();
|
|
|
|
let value = command_dict(
|
|
|
|
registry.get_command(&cmd).ok_or_else(|| {
|
|
|
|
ShellError::labeled_error(
|
|
|
|
format!("Could not load {}", cmd),
|
|
|
|
"could not load command",
|
|
|
|
document_tag,
|
|
|
|
)
|
|
|
|
})?,
|
|
|
|
name.clone(),
|
|
|
|
);
|
|
|
|
|
|
|
|
short_desc.insert_untagged("name", cmd);
|
|
|
|
short_desc.insert_untagged(
|
|
|
|
"description",
|
|
|
|
get_data_by_key(&value, "usage".spanned_unknown())
|
|
|
|
.ok_or_else(|| {
|
|
|
|
ShellError::labeled_error(
|
|
|
|
"Expected a usage key",
|
|
|
|
"expected a 'usage' key",
|
|
|
|
&value.tag,
|
|
|
|
)
|
|
|
|
})?
|
|
|
|
.as_string()?,
|
|
|
|
);
|
|
|
|
|
|
|
|
help.push_back(ReturnSuccess::value(short_desc.into_value()));
|
2019-09-12 20:29:16 +02:00
|
|
|
}
|
2020-05-04 10:44:33 +02:00
|
|
|
} else if rest.len() == 2 {
|
|
|
|
// Check for a subcommand
|
|
|
|
let command_name = format!("{} {}", rest[0].item, rest[1].item);
|
|
|
|
if let Some(command) = registry.get_command(&command_name) {
|
|
|
|
return Ok(get_help(
|
|
|
|
&command.name(),
|
|
|
|
&command.usage(),
|
|
|
|
command.signature(),
|
|
|
|
®istry,
|
|
|
|
)
|
|
|
|
.into());
|
|
|
|
}
|
2020-04-27 04:04:54 +02:00
|
|
|
} else if let Some(command) = registry.get_command(&document.item) {
|
2020-05-04 10:44:33 +02:00
|
|
|
return Ok(get_help(
|
|
|
|
&command.name(),
|
|
|
|
&command.usage(),
|
|
|
|
command.signature(),
|
|
|
|
®istry,
|
|
|
|
)
|
|
|
|
.into());
|
2020-04-27 04:04:54 +02:00
|
|
|
} else {
|
|
|
|
return Err(ShellError::labeled_error(
|
|
|
|
"Can't find command (use 'help commands' for full list)",
|
|
|
|
"can't find command",
|
2020-05-04 10:44:33 +02:00
|
|
|
document.tag.span,
|
2020-04-27 04:04:54 +02:00
|
|
|
));
|
|
|
|
}
|
|
|
|
let help = futures::stream::iter(help);
|
|
|
|
Ok(help.to_output_stream())
|
|
|
|
} else {
|
|
|
|
let msg = r#"Welcome to Nushell.
|
2019-09-12 20:33:52 +02:00
|
|
|
|
2019-09-12 20:29:16 +02:00
|
|
|
Here are some tips to help you get started.
|
2019-09-12 20:33:52 +02:00
|
|
|
* help commands - list all available commands
|
|
|
|
* help <command name> - display help about a particular command
|
|
|
|
|
2020-03-13 18:23:41 +01:00
|
|
|
Nushell works on the idea of a "pipeline". Pipelines are commands connected with the '|' character.
|
|
|
|
Each stage in the pipeline works together to load, parse, and display information to you.
|
2020-01-31 05:13:14 +01:00
|
|
|
|
|
|
|
[Examples]
|
|
|
|
|
|
|
|
List the files in the current directory, sorted by size:
|
|
|
|
ls | sort-by size
|
|
|
|
|
|
|
|
Get information about the current system:
|
|
|
|
sys | get host
|
|
|
|
|
|
|
|
Get the processes on your system actively using CPU:
|
|
|
|
ps | where cpu > 0
|
|
|
|
|
2019-12-15 13:56:26 +01:00
|
|
|
You can also learn more at https://www.nushell.sh/book/"#;
|
2019-09-12 20:29:16 +02:00
|
|
|
|
2020-04-27 04:04:54 +02:00
|
|
|
let output_stream = futures::stream::iter(vec![ReturnSuccess::value(
|
|
|
|
UntaggedValue::string(msg).into_value(name),
|
|
|
|
)]);
|
2019-09-12 20:29:16 +02:00
|
|
|
|
2020-04-27 04:04:54 +02:00
|
|
|
Ok(output_stream.to_output_stream())
|
2019-08-30 00:52:32 +02:00
|
|
|
}
|
|
|
|
}
|
2020-01-17 23:46:18 +01:00
|
|
|
|
|
|
|
pub(crate) fn get_help(
|
|
|
|
cmd_name: &str,
|
|
|
|
cmd_usage: &str,
|
|
|
|
cmd_sig: Signature,
|
2020-05-04 10:44:33 +02:00
|
|
|
registry: &CommandRegistry,
|
2020-01-17 23:46:18 +01:00
|
|
|
) -> impl Into<OutputStream> {
|
|
|
|
let mut help = VecDeque::new();
|
|
|
|
let mut long_desc = String::new();
|
|
|
|
|
|
|
|
long_desc.push_str(&cmd_usage);
|
|
|
|
long_desc.push_str("\n");
|
|
|
|
|
|
|
|
let signature = cmd_sig;
|
|
|
|
|
2020-05-04 10:44:33 +02:00
|
|
|
let mut subcommands = String::new();
|
|
|
|
for name in registry.names() {
|
|
|
|
if name.starts_with(&format!("{} ", cmd_name)) {
|
|
|
|
let subcommand = registry.get_command(&name).expect("This shouldn't happen");
|
|
|
|
|
|
|
|
subcommands.push_str(&format!(" {} - {}\n", name, subcommand.usage()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-17 23:46:18 +01:00
|
|
|
let mut one_liner = String::new();
|
|
|
|
one_liner.push_str(&signature.name);
|
|
|
|
one_liner.push_str(" ");
|
|
|
|
|
|
|
|
for positional in &signature.positional {
|
|
|
|
match &positional.0 {
|
|
|
|
PositionalType::Mandatory(name, _m) => {
|
|
|
|
one_liner.push_str(&format!("<{}> ", name));
|
|
|
|
}
|
|
|
|
PositionalType::Optional(name, _o) => {
|
|
|
|
one_liner.push_str(&format!("({}) ", name));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if signature.rest_positional.is_some() {
|
|
|
|
one_liner.push_str(" ...args");
|
|
|
|
}
|
|
|
|
|
2020-05-04 10:44:33 +02:00
|
|
|
if !subcommands.is_empty() {
|
|
|
|
one_liner.push_str("<subcommand> ");
|
|
|
|
}
|
|
|
|
|
2020-01-17 23:46:18 +01:00
|
|
|
if !signature.named.is_empty() {
|
|
|
|
one_liner.push_str("{flags} ");
|
|
|
|
}
|
|
|
|
|
|
|
|
long_desc.push_str(&format!("\nUsage:\n > {}\n", one_liner));
|
|
|
|
|
2020-05-04 10:44:33 +02:00
|
|
|
if !subcommands.is_empty() {
|
|
|
|
long_desc.push_str("\nSubcommands:\n");
|
|
|
|
long_desc.push_str(&subcommands);
|
|
|
|
}
|
|
|
|
|
2020-01-17 23:46:18 +01:00
|
|
|
if !signature.positional.is_empty() || signature.rest_positional.is_some() {
|
2020-05-04 10:44:33 +02:00
|
|
|
long_desc.push_str("\nParameters:\n");
|
2020-01-17 23:46:18 +01:00
|
|
|
for positional in signature.positional {
|
|
|
|
match positional.0 {
|
|
|
|
PositionalType::Mandatory(name, _m) => {
|
|
|
|
long_desc.push_str(&format!(" <{}> {}\n", name, positional.1));
|
|
|
|
}
|
|
|
|
PositionalType::Optional(name, _o) => {
|
|
|
|
long_desc.push_str(&format!(" ({}) {}\n", name, positional.1));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(rest_positional) = signature.rest_positional {
|
|
|
|
long_desc.push_str(&format!(" ...args: {}\n", rest_positional.1));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !signature.named.is_empty() {
|
2020-05-04 10:44:33 +02:00
|
|
|
long_desc.push_str("\nFlags:\n");
|
2020-01-17 23:46:18 +01:00
|
|
|
for (flag, ty) in signature.named {
|
2020-02-12 03:24:31 +01:00
|
|
|
let msg = match ty.0 {
|
|
|
|
NamedType::Switch(s) => {
|
|
|
|
if let Some(c) = s {
|
|
|
|
format!(
|
|
|
|
" -{}, --{}{} {}\n",
|
|
|
|
c,
|
|
|
|
flag,
|
|
|
|
if !ty.1.is_empty() { ":" } else { "" },
|
|
|
|
ty.1
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
format!(
|
|
|
|
" --{}{} {}\n",
|
|
|
|
flag,
|
|
|
|
if !ty.1.is_empty() { ":" } else { "" },
|
|
|
|
ty.1
|
|
|
|
)
|
|
|
|
}
|
2020-01-17 23:46:18 +01:00
|
|
|
}
|
2020-02-12 03:24:31 +01:00
|
|
|
NamedType::Mandatory(s, m) => {
|
|
|
|
if let Some(c) = s {
|
|
|
|
format!(
|
|
|
|
" -{}, --{} <{}> (required parameter){} {}\n",
|
|
|
|
c,
|
|
|
|
flag,
|
|
|
|
m.display(),
|
|
|
|
if !ty.1.is_empty() { ":" } else { "" },
|
|
|
|
ty.1
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
format!(
|
|
|
|
" --{} <{}> (required parameter){} {}\n",
|
|
|
|
flag,
|
|
|
|
m.display(),
|
|
|
|
if !ty.1.is_empty() { ":" } else { "" },
|
|
|
|
ty.1
|
|
|
|
)
|
|
|
|
}
|
2020-01-17 23:46:18 +01:00
|
|
|
}
|
2020-02-12 03:24:31 +01:00
|
|
|
NamedType::Optional(s, o) => {
|
|
|
|
if let Some(c) = s {
|
|
|
|
format!(
|
|
|
|
" -{}, --{} <{}>{} {}\n",
|
|
|
|
c,
|
|
|
|
flag,
|
|
|
|
o.display(),
|
|
|
|
if !ty.1.is_empty() { ":" } else { "" },
|
|
|
|
ty.1
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
format!(
|
|
|
|
" --{} <{}>{} {}\n",
|
|
|
|
flag,
|
|
|
|
o.display(),
|
|
|
|
if !ty.1.is_empty() { ":" } else { "" },
|
|
|
|
ty.1
|
|
|
|
)
|
|
|
|
}
|
2020-01-17 23:46:18 +01:00
|
|
|
}
|
2020-02-12 03:24:31 +01:00
|
|
|
};
|
|
|
|
long_desc.push_str(&msg);
|
2020-01-17 23:46:18 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
help.push_back(ReturnSuccess::value(
|
|
|
|
UntaggedValue::string(long_desc).into_value(Tag::from((0, cmd_name.len(), None))),
|
|
|
|
));
|
|
|
|
help
|
|
|
|
}
|