mirror of
https://github.com/nushell/nushell.git
synced 2025-06-30 22:50:14 +02:00
Break up interdependencies of command crates (#9429)
# Description Make sure that our different crates that contain commands can be compiled in parallel. This can under certain circumstances accelerate the compilation with sufficient multithreading available. ## Details - Move `help` commands from `nu-cmd-lang` back to `nu-command` - This also makes sense as the commands are implemented in an ANSI-terminal specific way - Make `nu-cmd-lang` only a dev dependency for `nu-command` - Change context creation helpers for `nu-cmd-extra` and `nu-cmd-dataframe` to have a consistent api used in `src/main.rs`:`get_engine_state()` - `nu-command` now indepedent from `nu-cmd-extra` and `nu-cmd-dataframe` that are now dependencies of `nu` directly. (change to internal features) - Fix tests that previously used `nu-command::create_default_context()` with replacement functions ## From scratch compilation times: just debug (dev) build and default features ``` cargo clean --profile dev && cargo build --timings ``` ### before  ### after  # User-Facing Changes None direct, only change to compilation on multithreaded jobs expected. # Tests + Formatting Tests that previously chose to use `nu-command` for their scope will still use `nu-cmd-lang` + `nu-command` (command list in the granularity at the time)
This commit is contained in:
committed by
GitHub
parent
b14bdd865f
commit
46eebc644c
@ -13,9 +13,6 @@ version = "0.81.1"
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.81.1" }
|
||||
nu-cmd-dataframe = { path = "../nu-cmd-dataframe", version = "0.81.1", optional = true }
|
||||
nu-cmd-extra = { path = "../nu-cmd-extra", version = "0.81.1", optional = true }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.81.1" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.81.1" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.81.1" }
|
||||
@ -117,17 +114,15 @@ features = ["Win32_Foundation", "Win32_Storage_FileSystem", "Win32_System_System
|
||||
version = "0.48"
|
||||
|
||||
[features]
|
||||
dataframe = ["dep:nu-cmd-dataframe"]
|
||||
extra = ["dep:nu-cmd-extra"]
|
||||
plugin = ["nu-parser/plugin"]
|
||||
sqlite = [
|
||||
"rusqlite",
|
||||
] # TODO: given that rusqlite is included in reedline, should we just always include it?
|
||||
sqlite = ["rusqlite"]
|
||||
trash-support = ["trash"]
|
||||
which-support = ["which"]
|
||||
|
||||
[dev-dependencies]
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.81.1" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.81.1" }
|
||||
|
||||
dirs-next = "2.0"
|
||||
mockito = "1.0"
|
||||
quickcheck = "1.0"
|
||||
|
@ -1,15 +1,10 @@
|
||||
use nu_protocol::engine::{EngineState, StateWorkingSet};
|
||||
|
||||
use crate::*;
|
||||
#[cfg(feature = "dataframe")]
|
||||
use nu_cmd_dataframe::*;
|
||||
|
||||
#[cfg(feature = "extra")]
|
||||
use nu_cmd_extra::*;
|
||||
|
||||
pub fn create_default_context() -> EngineState {
|
||||
let mut engine_state = nu_cmd_lang::create_default_context();
|
||||
|
||||
use crate::{
|
||||
help::{HelpAliases, HelpCommands, HelpExterns, HelpModules, HelpOperators},
|
||||
*,
|
||||
};
|
||||
pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
||||
let delta = {
|
||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||
|
||||
@ -24,12 +19,6 @@ pub fn create_default_context() -> EngineState {
|
||||
// them only accessible if the correct input value category is used with the
|
||||
// declaration
|
||||
|
||||
#[cfg(feature = "extra")]
|
||||
add_extra_decls(&mut working_set);
|
||||
|
||||
#[cfg(feature = "dataframe")]
|
||||
add_dataframe_decls(&mut working_set);
|
||||
|
||||
// Database-related
|
||||
// Adds all related commands to query databases
|
||||
#[cfg(feature = "sqlite")]
|
||||
@ -138,6 +127,16 @@ pub fn create_default_context() -> EngineState {
|
||||
Sys,
|
||||
};
|
||||
|
||||
// Help
|
||||
bind_command! {
|
||||
Help,
|
||||
HelpAliases,
|
||||
HelpExterns,
|
||||
HelpCommands,
|
||||
HelpModules,
|
||||
HelpOperators,
|
||||
};
|
||||
|
||||
// Debug
|
||||
bind_command! {
|
||||
Ast,
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::help::highlight_search_string;
|
||||
use itertools::Itertools;
|
||||
use nu_cmd_lang::help::highlight_search_string;
|
||||
|
||||
use fancy_regex::Regex;
|
||||
use nu_ansi_term::Style;
|
||||
|
267
crates/nu-command/src/help/help_.rs
Normal file
267
crates/nu-command/src/help/help_.rs
Normal file
@ -0,0 +1,267 @@
|
||||
use crate::help::help_aliases;
|
||||
use crate::help::help_commands;
|
||||
use crate::help::help_modules;
|
||||
use fancy_regex::Regex;
|
||||
use nu_ansi_term::Style;
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
span, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned,
|
||||
SyntaxShape, Type, Value,
|
||||
};
|
||||
#[derive(Clone)]
|
||||
pub struct Help;
|
||||
|
||||
impl Command for Help {
|
||||
fn name(&self) -> &str {
|
||||
"help"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("help")
|
||||
.input_output_types(vec![(Type::Nothing, Type::String)])
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::String,
|
||||
"the name of command, alias or module to get help on",
|
||||
)
|
||||
.named(
|
||||
"find",
|
||||
SyntaxShape::String,
|
||||
"string to find in command names, usage, and search terms",
|
||||
Some('f'),
|
||||
)
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Display help information about different parts of Nushell."
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"`help word` searches for "word" in commands, aliases and modules, in that order."#
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> 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)?;
|
||||
|
||||
if rest.is_empty() && find.is_none() {
|
||||
let msg = r#"Welcome to Nushell.
|
||||
|
||||
Here are some tips to help you get started.
|
||||
* help -h or help help - show available `help` subcommands and examples
|
||||
* help commands - list all available commands
|
||||
* help <name> - display help about a particular command, alias, or module
|
||||
* help --find <text to search> - search through all help commands table
|
||||
|
||||
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.
|
||||
|
||||
[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
|
||||
|
||||
You can also learn more at https://www.nushell.sh/book/"#;
|
||||
|
||||
Ok(Value::string(msg, head).into_pipeline_data())
|
||||
} else if find.is_some() {
|
||||
help_commands(engine_state, stack, call)
|
||||
} else {
|
||||
let result = help_aliases(engine_state, stack, call);
|
||||
|
||||
let result = if let Err(ShellError::AliasNotFound(_)) = result {
|
||||
help_commands(engine_state, stack, call)
|
||||
} else {
|
||||
result
|
||||
};
|
||||
|
||||
let result = if let Err(ShellError::CommandNotFound(_)) = result {
|
||||
help_modules(engine_state, stack, call)
|
||||
} else {
|
||||
result
|
||||
};
|
||||
|
||||
if let Err(ShellError::ModuleNotFoundAtRuntime {
|
||||
mod_name: _,
|
||||
span: _,
|
||||
}) = result
|
||||
{
|
||||
let rest_spans: Vec<Span> = rest.iter().map(|arg| arg.span).collect();
|
||||
Err(ShellError::NotFound {
|
||||
span: span(&rest_spans),
|
||||
})
|
||||
} else {
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "show help for single command, alias, or module",
|
||||
example: "help match",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "show help for single sub-command, alias, or module",
|
||||
example: "help str lpad",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "search for string in command names, usage and search terms",
|
||||
example: "help --find char",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn highlight_search_in_table(
|
||||
table: Vec<Value>, // list of records
|
||||
search_string: &str,
|
||||
searched_cols: &[&str],
|
||||
string_style: &Style,
|
||||
highlight_style: &Style,
|
||||
) -> Result<Vec<Value>, ShellError> {
|
||||
let orig_search_string = search_string;
|
||||
let search_string = search_string.to_lowercase();
|
||||
let mut matches = vec![];
|
||||
|
||||
for record in table {
|
||||
let (cols, mut vals, record_span) = if let Value::Record { cols, vals, span } = record {
|
||||
(cols, vals, span)
|
||||
} else {
|
||||
return Err(ShellError::NushellFailedSpanned {
|
||||
msg: "Expected record".to_string(),
|
||||
label: format!("got {}", record.get_type()),
|
||||
span: record.span()?,
|
||||
});
|
||||
};
|
||||
|
||||
let has_match = cols.iter().zip(vals.iter_mut()).fold(
|
||||
Ok(false),
|
||||
|acc: Result<bool, ShellError>, (col, val)| {
|
||||
if searched_cols.contains(&col.as_str()) {
|
||||
if let Value::String { val: s, span } = val {
|
||||
if s.to_lowercase().contains(&search_string) {
|
||||
*val = Value::String {
|
||||
val: highlight_search_string(
|
||||
s,
|
||||
orig_search_string,
|
||||
string_style,
|
||||
highlight_style,
|
||||
)?,
|
||||
span: *span,
|
||||
};
|
||||
Ok(true)
|
||||
} else {
|
||||
// column does not contain the searched string
|
||||
acc
|
||||
}
|
||||
} else {
|
||||
// ignore non-string values
|
||||
acc
|
||||
}
|
||||
} else {
|
||||
// don't search this column
|
||||
acc
|
||||
}
|
||||
},
|
||||
)?;
|
||||
|
||||
if has_match {
|
||||
matches.push(Value::Record {
|
||||
cols,
|
||||
vals,
|
||||
span: record_span,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(matches)
|
||||
}
|
||||
|
||||
// Highlight the search string using ANSI escape sequences and regular expressions.
|
||||
pub fn highlight_search_string(
|
||||
haystack: &str,
|
||||
needle: &str,
|
||||
string_style: &Style,
|
||||
highlight_style: &Style,
|
||||
) -> Result<String, ShellError> {
|
||||
let regex_string = format!("(?i){needle}");
|
||||
let regex = match Regex::new(®ex_string) {
|
||||
Ok(regex) => regex,
|
||||
Err(err) => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Could not compile regex".into(),
|
||||
err.to_string(),
|
||||
Some(Span::test_data()),
|
||||
None,
|
||||
Vec::new(),
|
||||
));
|
||||
}
|
||||
};
|
||||
// strip haystack to remove existing ansi style
|
||||
let stripped_haystack = nu_utils::strip_ansi_likely(haystack);
|
||||
let mut last_match_end = 0;
|
||||
let mut highlighted = String::new();
|
||||
|
||||
for cap in regex.captures_iter(stripped_haystack.as_ref()) {
|
||||
match cap {
|
||||
Ok(capture) => {
|
||||
let start = match capture.get(0) {
|
||||
Some(acap) => acap.start(),
|
||||
None => 0,
|
||||
};
|
||||
let end = match capture.get(0) {
|
||||
Some(acap) => acap.end(),
|
||||
None => 0,
|
||||
};
|
||||
highlighted.push_str(
|
||||
&string_style
|
||||
.paint(&stripped_haystack[last_match_end..start])
|
||||
.to_string(),
|
||||
);
|
||||
highlighted.push_str(
|
||||
&highlight_style
|
||||
.paint(&stripped_haystack[start..end])
|
||||
.to_string(),
|
||||
);
|
||||
last_match_end = end;
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Error with regular expression capture".into(),
|
||||
e.to_string(),
|
||||
None,
|
||||
None,
|
||||
Vec::new(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
highlighted.push_str(
|
||||
&string_style
|
||||
.paint(&stripped_haystack[last_match_end..])
|
||||
.to_string(),
|
||||
);
|
||||
Ok(highlighted)
|
||||
}
|
185
crates/nu-command/src/help/help_aliases.rs
Normal file
185
crates/nu-command/src/help/help_aliases.rs
Normal file
@ -0,0 +1,185 @@
|
||||
use crate::help::highlight_search_in_table;
|
||||
use nu_color_config::StyleComputer;
|
||||
use nu_engine::{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 HelpAliases;
|
||||
|
||||
impl Command for HelpAliases {
|
||||
fn name(&self) -> &str {
|
||||
"help aliases"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Show help on nushell aliases."
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("help aliases")
|
||||
.category(Category::Core)
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::String,
|
||||
"the name of alias to get help on",
|
||||
)
|
||||
.named(
|
||||
"find",
|
||||
SyntaxShape::String,
|
||||
"string to find in alias 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 aliases",
|
||||
example: "help aliases",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "show help for single alias",
|
||||
example: "help aliases my-alias",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "search for string in alias names and usages",
|
||||
example: "help aliases --find my-alias",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
help_aliases(engine_state, stack, call)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn help_aliases(
|
||||
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_aliases(engine_state, stack, head);
|
||||
let found_cmds_vec = highlight_search_in_table(
|
||||
all_cmds_vec,
|
||||
&f.item,
|
||||
&["name", "usage"],
|
||||
&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_aliases(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 Some(alias) = engine_state.find_decl(name.as_bytes(), &[]) else {
|
||||
return Err(ShellError::AliasNotFound(span(
|
||||
&rest.iter().map(|r| r.span).collect::<Vec<Span>>(),
|
||||
)));
|
||||
};
|
||||
|
||||
let Some(alias) = engine_state.get_decl(alias).as_alias() else {
|
||||
return Err(ShellError::AliasNotFound(span(
|
||||
&rest.iter().map(|r| r.span).collect::<Vec<Span>>(),
|
||||
)));
|
||||
};
|
||||
|
||||
let alias_expansion =
|
||||
String::from_utf8_lossy(engine_state.get_span_contents(&alias.wrapped_call.span));
|
||||
let usage = alias.usage();
|
||||
let extra_usage = alias.extra_usage();
|
||||
|
||||
// TODO: merge this into documentation.rs at some point
|
||||
const G: &str = "\x1b[32m"; // green
|
||||
const C: &str = "\x1b[36m"; // cyan
|
||||
const RESET: &str = "\x1b[0m"; // reset
|
||||
|
||||
let mut long_desc = String::new();
|
||||
|
||||
long_desc.push_str(usage);
|
||||
long_desc.push_str("\n\n");
|
||||
|
||||
if !extra_usage.is_empty() {
|
||||
long_desc.push_str(extra_usage);
|
||||
long_desc.push_str("\n\n");
|
||||
}
|
||||
|
||||
long_desc.push_str(&format!("{G}Alias{RESET}: {C}{name}{RESET}"));
|
||||
long_desc.push_str("\n\n");
|
||||
long_desc.push_str(&format!("{G}Expansion{RESET}:\n {alias_expansion}"));
|
||||
|
||||
let config = engine_state.get_config();
|
||||
if !config.use_ansi_coloring {
|
||||
long_desc = nu_utils::strip_ansi_string_likely(long_desc);
|
||||
}
|
||||
|
||||
Ok(Value::String {
|
||||
val: long_desc,
|
||||
span: call.head,
|
||||
}
|
||||
.into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
fn build_help_aliases(engine_state: &EngineState, stack: &Stack, span: Span) -> Vec<Value> {
|
||||
let mut scope_data = ScopeData::new(engine_state, stack);
|
||||
scope_data.populate_all();
|
||||
|
||||
scope_data.collect_aliases(span)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use super::HelpAliases;
|
||||
use crate::test_examples;
|
||||
test_examples(HelpAliases {})
|
||||
}
|
||||
}
|
188
crates/nu-command/src/help/help_commands.rs
Normal file
188
crates/nu-command/src/help/help_commands.rs
Normal file
@ -0,0 +1,188 @@
|
||||
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 (name_bytes, decl_id) in commands {
|
||||
let mut cols = vec![];
|
||||
let mut vals = vec![];
|
||||
|
||||
let name = String::from_utf8_lossy(&name_bytes).to_string();
|
||||
let decl = engine_state.get_decl(decl_id);
|
||||
let sig = decl.signature().update_from_command(name, decl.borrow());
|
||||
|
||||
let signatures = sig.to_string().trim_start().replace("\n ", "\n");
|
||||
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("signatures".into());
|
||||
vals.push(Value::String {
|
||||
val: if decl.is_parser_keyword() {
|
||||
"".to_string()
|
||||
} else {
|
||||
signatures
|
||||
},
|
||||
span,
|
||||
});
|
||||
|
||||
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 {})
|
||||
}
|
||||
}
|
159
crates/nu-command/src/help/help_externs.rs
Normal file
159
crates/nu-command/src/help/help_externs.rs
Normal file
@ -0,0 +1,159 @@
|
||||
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));
|
||||
let highlight_style =
|
||||
style_computer.compute("search_result", &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,
|
||||
&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_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 {})
|
||||
}
|
||||
}
|
269
crates/nu-command/src/help/help_modules.rs
Normal file
269
crates/nu-command/src/help/help_modules.rs
Normal file
@ -0,0 +1,269 @@
|
||||
use crate::help::highlight_search_in_table;
|
||||
use nu_color_config::StyleComputer;
|
||||
use nu_engine::{scope::ScopeData, CallExt};
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
span, Category, DeclId, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
|
||||
ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct HelpModules;
|
||||
|
||||
impl Command for HelpModules {
|
||||
fn name(&self) -> &str {
|
||||
"help modules"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Show help on nushell modules."
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"When requesting help for a single module, its commands and aliases will be highlighted if they
|
||||
are also available in the current scope. Commands/aliases that were imported under a different name
|
||||
(such as with a prefix after `use some-module`) will be highlighted in parentheses."#
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("help modules")
|
||||
.category(Category::Core)
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::String,
|
||||
"the name of module to get help on",
|
||||
)
|
||||
.named(
|
||||
"find",
|
||||
SyntaxShape::String,
|
||||
"string to find in module 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 modules",
|
||||
example: "help modules",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "show help for single module",
|
||||
example: "help modules my-module",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "search for string in module names and usages",
|
||||
example: "help modules --find my-module",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
help_modules(engine_state, stack, call)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn help_modules(
|
||||
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_modules(engine_state, stack, head);
|
||||
let found_cmds_vec = highlight_search_in_table(
|
||||
all_cmds_vec,
|
||||
&f.item,
|
||||
&["name", "usage"],
|
||||
&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_modules(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 Some(module_id) = engine_state.find_module(name.as_bytes(), &[]) else {
|
||||
return Err(ShellError::ModuleNotFoundAtRuntime {
|
||||
mod_name: name,
|
||||
span: span(&rest.iter().map(|r| r.span).collect::<Vec<Span>>()),
|
||||
});
|
||||
};
|
||||
|
||||
let module = engine_state.get_module(module_id);
|
||||
|
||||
let module_usage = engine_state.build_module_usage(module_id);
|
||||
|
||||
// TODO: merge this into documentation.rs at some point
|
||||
const G: &str = "\x1b[32m"; // green
|
||||
const C: &str = "\x1b[36m"; // cyan
|
||||
const CB: &str = "\x1b[1;36m"; // cyan bold
|
||||
const RESET: &str = "\x1b[0m"; // reset
|
||||
|
||||
let mut long_desc = String::new();
|
||||
|
||||
if let Some((usage, extra_usage)) = module_usage {
|
||||
long_desc.push_str(&usage);
|
||||
long_desc.push_str("\n\n");
|
||||
|
||||
if !extra_usage.is_empty() {
|
||||
long_desc.push_str(&extra_usage);
|
||||
long_desc.push_str("\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
long_desc.push_str(&format!("{G}Module{RESET}: {C}{name}{RESET}"));
|
||||
long_desc.push_str("\n\n");
|
||||
|
||||
if !module.decls.is_empty() || module.main.is_some() {
|
||||
let commands: Vec<(Vec<u8>, DeclId)> = engine_state
|
||||
.get_decls_sorted(false)
|
||||
.filter(|(_, id)| !engine_state.get_decl(*id).is_alias())
|
||||
.collect();
|
||||
|
||||
let mut module_commands: Vec<(Vec<u8>, DeclId)> = module
|
||||
.decls()
|
||||
.into_iter()
|
||||
.filter(|(_, id)| !engine_state.get_decl(*id).is_alias())
|
||||
.collect();
|
||||
module_commands.sort_by(|a, b| a.0.cmp(&b.0));
|
||||
|
||||
let commands_str = module_commands
|
||||
.iter()
|
||||
.map(|(name_bytes, id)| {
|
||||
let name = String::from_utf8_lossy(name_bytes);
|
||||
if let Some((used_name_bytes, _)) =
|
||||
commands.iter().find(|(_, decl_id)| id == decl_id)
|
||||
{
|
||||
if engine_state.find_decl(name.as_bytes(), &[]).is_some() {
|
||||
format!("{CB}{name}{RESET}")
|
||||
} else {
|
||||
let command_name = String::from_utf8_lossy(used_name_bytes);
|
||||
format!("{name} ({CB}{command_name}{RESET})")
|
||||
}
|
||||
} else {
|
||||
format!("{name}")
|
||||
}
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ");
|
||||
|
||||
long_desc.push_str(&format!("{G}Exported commands{RESET}:\n {commands_str}"));
|
||||
long_desc.push_str("\n\n");
|
||||
}
|
||||
|
||||
if !module.decls.is_empty() {
|
||||
let aliases: Vec<(Vec<u8>, DeclId)> = engine_state
|
||||
.get_decls_sorted(false)
|
||||
.filter(|(_, id)| engine_state.get_decl(*id).is_alias())
|
||||
.collect();
|
||||
|
||||
let mut module_aliases: Vec<(Vec<u8>, DeclId)> = module
|
||||
.decls()
|
||||
.into_iter()
|
||||
.filter(|(_, id)| engine_state.get_decl(*id).is_alias())
|
||||
.collect();
|
||||
module_aliases.sort_by(|a, b| a.0.cmp(&b.0));
|
||||
|
||||
let aliases_str = module_aliases
|
||||
.iter()
|
||||
.map(|(name_bytes, id)| {
|
||||
let name = String::from_utf8_lossy(name_bytes);
|
||||
if let Some((used_name_bytes, _)) =
|
||||
aliases.iter().find(|(_, alias_id)| id == alias_id)
|
||||
{
|
||||
if engine_state.find_decl(name.as_bytes(), &[]).is_some() {
|
||||
format!("{CB}{name}{RESET}")
|
||||
} else {
|
||||
let alias_name = String::from_utf8_lossy(used_name_bytes);
|
||||
format!("{name} ({CB}{alias_name}{RESET})")
|
||||
}
|
||||
} else {
|
||||
format!("{name}")
|
||||
}
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ");
|
||||
|
||||
long_desc.push_str(&format!("{G}Exported aliases{RESET}:\n {aliases_str}"));
|
||||
long_desc.push_str("\n\n");
|
||||
}
|
||||
|
||||
if module.env_block.is_some() {
|
||||
long_desc.push_str(&format!("This module {C}exports{RESET} environment."));
|
||||
} else {
|
||||
long_desc.push_str(&format!(
|
||||
"This module {C}does not export{RESET} environment."
|
||||
));
|
||||
}
|
||||
|
||||
let config = engine_state.get_config();
|
||||
if !config.use_ansi_coloring {
|
||||
long_desc = nu_utils::strip_ansi_string_likely(long_desc);
|
||||
}
|
||||
|
||||
Ok(Value::String {
|
||||
val: long_desc,
|
||||
span: call.head,
|
||||
}
|
||||
.into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
fn build_help_modules(engine_state: &EngineState, stack: &Stack, span: Span) -> Vec<Value> {
|
||||
let mut scope_data = ScopeData::new(engine_state, stack);
|
||||
scope_data.populate_modules();
|
||||
|
||||
scope_data.collect_modules(span)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use super::HelpModules;
|
||||
use crate::test_examples;
|
||||
test_examples(HelpModules {})
|
||||
}
|
||||
}
|
329
crates/nu-command/src/help/help_operators.rs
Normal file
329
crates/nu-command/src/help/help_operators.rs
Normal file
@ -0,0 +1,329 @@
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct HelpOperators;
|
||||
|
||||
impl Command for HelpOperators {
|
||||
fn name(&self) -> &str {
|
||||
"help operators"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Show help on nushell operators."
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("help operators")
|
||||
.category(Category::Core)
|
||||
.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> {
|
||||
let head = call.head;
|
||||
let op_info = generate_operator_info();
|
||||
let mut recs = vec![];
|
||||
|
||||
for op in op_info {
|
||||
let mut cols = vec![];
|
||||
let mut vals = vec![];
|
||||
cols.push("type".into());
|
||||
vals.push(Value::string(op.op_type, head));
|
||||
cols.push("operator".into());
|
||||
vals.push(Value::string(op.operator, head));
|
||||
cols.push("name".into());
|
||||
vals.push(Value::string(op.name, head));
|
||||
cols.push("description".into());
|
||||
vals.push(Value::string(op.description, head));
|
||||
cols.push("precedence".into());
|
||||
vals.push(Value::int(op.precedence, head));
|
||||
recs.push(Value::Record {
|
||||
cols,
|
||||
vals,
|
||||
span: head,
|
||||
})
|
||||
}
|
||||
|
||||
Ok(recs
|
||||
.into_iter()
|
||||
.into_pipeline_data(engine_state.ctrlc.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
struct OperatorInfo {
|
||||
op_type: String,
|
||||
operator: String,
|
||||
name: String,
|
||||
description: String,
|
||||
precedence: i64,
|
||||
}
|
||||
|
||||
fn generate_operator_info() -> Vec<OperatorInfo> {
|
||||
vec![
|
||||
OperatorInfo {
|
||||
op_type: "Assignment".into(),
|
||||
operator: "=".into(),
|
||||
name: "Assign".into(),
|
||||
description: "Assigns a value to a variable.".into(),
|
||||
precedence: 10,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Assignment".into(),
|
||||
operator: "+=".into(),
|
||||
name: "PlusAssign".into(),
|
||||
description: "Adds a value to a variable.".into(),
|
||||
precedence: 10,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Assignment".into(),
|
||||
operator: "++=".into(),
|
||||
name: "AppendAssign".into(),
|
||||
description: "Appends a list or a value to a variable.".into(),
|
||||
precedence: 10,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Assignment".into(),
|
||||
operator: "-=".into(),
|
||||
name: "MinusAssign".into(),
|
||||
description: "Subtracts a value from a variable.".into(),
|
||||
precedence: 10,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Assignment".into(),
|
||||
operator: "*=".into(),
|
||||
name: "MultiplyAssign".into(),
|
||||
description: "Multiplies a variable by a value.".into(),
|
||||
precedence: 10,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Assignment".into(),
|
||||
operator: "/=".into(),
|
||||
name: "DivideAssign".into(),
|
||||
description: "Divides a variable by a value.".into(),
|
||||
precedence: 10,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Comparison".into(),
|
||||
operator: "==".into(),
|
||||
name: "Equal".into(),
|
||||
description: "Checks if two values are equal.".into(),
|
||||
precedence: 80,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Comparison".into(),
|
||||
operator: "!=".into(),
|
||||
name: "NotEqual".into(),
|
||||
description: "Checks if two values are not equal.".into(),
|
||||
precedence: 80,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Comparison".into(),
|
||||
operator: "<".into(),
|
||||
name: "LessThan".into(),
|
||||
description: "Checks if a value is less than another.".into(),
|
||||
precedence: 80,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Comparison".into(),
|
||||
operator: "<=".into(),
|
||||
name: "LessThanOrEqual".into(),
|
||||
description: "Checks if a value is less than or equal to another.".into(),
|
||||
precedence: 80,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Comparison".into(),
|
||||
operator: ">".into(),
|
||||
name: "GreaterThan".into(),
|
||||
description: "Checks if a value is greater than another.".into(),
|
||||
precedence: 80,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Comparison".into(),
|
||||
operator: ">=".into(),
|
||||
name: "GreaterThanOrEqual".into(),
|
||||
description: "Checks if a value is greater than or equal to another.".into(),
|
||||
precedence: 80,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Comparison".into(),
|
||||
operator: "=~".into(),
|
||||
name: "RegexMatch".into(),
|
||||
description: "Checks if a value matches a regular expression.".into(),
|
||||
precedence: 80,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Comparison".into(),
|
||||
operator: "!~".into(),
|
||||
name: "NotRegexMatch".into(),
|
||||
description: "Checks if a value does not match a regular expression.".into(),
|
||||
precedence: 80,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Comparison".into(),
|
||||
operator: "in".into(),
|
||||
name: "In".into(),
|
||||
description: "Checks if a value is in a list or string.".into(),
|
||||
precedence: 80,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Comparison".into(),
|
||||
operator: "not-in".into(),
|
||||
name: "NotIn".into(),
|
||||
description: "Checks if a value is not in a list or string.".into(),
|
||||
precedence: 80,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Comparison".into(),
|
||||
operator: "starts-with".into(),
|
||||
name: "StartsWith".into(),
|
||||
description: "Checks if a string starts with another.".into(),
|
||||
precedence: 80,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Comparison".into(),
|
||||
operator: "ends-with".into(),
|
||||
name: "EndsWith".into(),
|
||||
description: "Checks if a string ends with another.".into(),
|
||||
precedence: 80,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Comparison".into(),
|
||||
operator: "not".into(),
|
||||
name: "UnaryNot".into(),
|
||||
description: "Negates a value or expression.".into(),
|
||||
precedence: 0,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Math".into(),
|
||||
operator: "+".into(),
|
||||
name: "Plus".into(),
|
||||
description: "Adds two values.".into(),
|
||||
precedence: 90,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Math".into(),
|
||||
operator: "++".into(),
|
||||
name: "Append".into(),
|
||||
description: "Appends two lists or a list and a value.".into(),
|
||||
precedence: 80,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Math".into(),
|
||||
operator: "-".into(),
|
||||
name: "Minus".into(),
|
||||
description: "Subtracts two values.".into(),
|
||||
precedence: 90,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Math".into(),
|
||||
operator: "*".into(),
|
||||
name: "Multiply".into(),
|
||||
description: "Multiplies two values.".into(),
|
||||
precedence: 95,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Math".into(),
|
||||
operator: "/".into(),
|
||||
name: "Divide".into(),
|
||||
description: "Divides two values.".into(),
|
||||
precedence: 95,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Math".into(),
|
||||
operator: "//".into(),
|
||||
name: "FloorDivision".into(),
|
||||
description: "Divides two values and floors the result.".into(),
|
||||
precedence: 95,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Math".into(),
|
||||
operator: "mod".into(),
|
||||
name: "Modulo".into(),
|
||||
description: "Divides two values and returns the remainder.".into(),
|
||||
precedence: 95,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Math".into(),
|
||||
operator: "**".into(),
|
||||
name: "Pow ".into(),
|
||||
description: "Raises one value to the power of another.".into(),
|
||||
precedence: 100,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Bitwise".into(),
|
||||
operator: "bit-or".into(),
|
||||
name: "BitOr".into(),
|
||||
description: "Performs a bitwise OR on two values.".into(),
|
||||
precedence: 60,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Bitwise".into(),
|
||||
operator: "bit-xor".into(),
|
||||
name: "BitXor".into(),
|
||||
description: "Performs a bitwise XOR on two values.".into(),
|
||||
precedence: 70,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Bitwise".into(),
|
||||
operator: "bit-and".into(),
|
||||
name: "BitAnd".into(),
|
||||
description: "Performs a bitwise AND on two values.".into(),
|
||||
precedence: 75,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Bitwise".into(),
|
||||
operator: "bit-shl".into(),
|
||||
name: "ShiftLeft".into(),
|
||||
description: "Shifts a value left by another.".into(),
|
||||
precedence: 85,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Bitwise".into(),
|
||||
operator: "bit-shr".into(),
|
||||
name: "ShiftRight".into(),
|
||||
description: "Shifts a value right by another.".into(),
|
||||
precedence: 85,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Boolean".into(),
|
||||
operator: "and".into(),
|
||||
name: "And".into(),
|
||||
description: "Checks if two values are true.".into(),
|
||||
precedence: 50,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Boolean".into(),
|
||||
operator: "or".into(),
|
||||
name: "Or".into(),
|
||||
description: "Checks if either value is true.".into(),
|
||||
precedence: 40,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Boolean".into(),
|
||||
operator: "xor".into(),
|
||||
name: "Xor".into(),
|
||||
description: "Checks if one value is true and the other is false.".into(),
|
||||
precedence: 45,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use super::HelpOperators;
|
||||
use crate::test_examples;
|
||||
test_examples(HelpOperators {})
|
||||
}
|
||||
}
|
18
crates/nu-command/src/help/mod.rs
Normal file
18
crates/nu-command/src/help/mod.rs
Normal file
@ -0,0 +1,18 @@
|
||||
mod help_;
|
||||
mod help_aliases;
|
||||
mod help_commands;
|
||||
mod help_externs;
|
||||
mod help_modules;
|
||||
mod help_operators;
|
||||
|
||||
pub use help_::Help;
|
||||
pub use help_aliases::HelpAliases;
|
||||
pub use help_commands::HelpCommands;
|
||||
pub use help_externs::HelpExterns;
|
||||
pub use help_modules::HelpModules;
|
||||
pub use help_operators::HelpOperators;
|
||||
|
||||
pub(crate) use help_::{highlight_search_in_table, highlight_search_string};
|
||||
pub(crate) use help_aliases::help_aliases;
|
||||
pub(crate) use help_commands::help_commands;
|
||||
pub(crate) use help_modules::help_modules;
|
@ -13,6 +13,7 @@ mod filters;
|
||||
mod formats;
|
||||
mod generators;
|
||||
mod hash;
|
||||
mod help;
|
||||
pub mod hook;
|
||||
mod input_handler;
|
||||
mod math;
|
||||
@ -45,6 +46,7 @@ pub use filters::*;
|
||||
pub use formats::*;
|
||||
pub use generators::*;
|
||||
pub use hash::*;
|
||||
pub use help::*;
|
||||
pub use hook::*;
|
||||
pub use math::*;
|
||||
pub use misc::*;
|
||||
|
@ -1,10 +1,16 @@
|
||||
use nu_command::create_default_context;
|
||||
use nu_protocol::{engine::StateWorkingSet, Category, Span};
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, StateWorkingSet},
|
||||
Category, Span,
|
||||
};
|
||||
use quickcheck_macros::quickcheck;
|
||||
|
||||
mod commands;
|
||||
mod format_conversions;
|
||||
|
||||
fn create_default_context() -> EngineState {
|
||||
nu_command::add_shell_command_context(nu_cmd_lang::create_default_context())
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn quickcheck_parse(data: String) -> bool {
|
||||
let (tokens, err) = nu_parser::lex(data.as_bytes(), 0, b"", b"", true);
|
||||
@ -24,7 +30,7 @@ fn quickcheck_parse(data: String) -> bool {
|
||||
|
||||
#[test]
|
||||
fn signature_name_matches_command_name() {
|
||||
let ctx = crate::create_default_context();
|
||||
let ctx = create_default_context();
|
||||
let decls = ctx.get_decls_sorted(true);
|
||||
let mut failures = Vec::new();
|
||||
|
||||
@ -50,7 +56,7 @@ fn signature_name_matches_command_name() {
|
||||
|
||||
#[test]
|
||||
fn commands_declare_input_output_types() {
|
||||
let ctx = crate::create_default_context();
|
||||
let ctx = create_default_context();
|
||||
let decls = ctx.get_decls_sorted(true);
|
||||
let mut failures = Vec::new();
|
||||
|
||||
|
Reference in New Issue
Block a user