Cratification: Break out nu_cmd_lang into a separate crate (#8181)

# Description

This breaks out the core_commands into a separate crate called
nu_cmd_lang

_(Thank you for improving Nushell. Please, check our [contributing
guide](../CONTRIBUTING.md) and talk to the core team before making major
changes.)_

_(Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.)_

# User-Facing Changes

_(List of all changes that impact the user experience here. This helps
us keep track of breaking changes.)_

# 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:
Michael Angerman
2023-02-24 07:54:42 -08:00
committed by GitHub
parent d0aefa99eb
commit 585e104608
59 changed files with 531 additions and 60 deletions

View File

@ -0,0 +1,56 @@
use nu_engine::get_full_help;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value,
};
#[derive(Clone)]
pub struct Overlay;
impl Command for Overlay {
fn name(&self) -> &str {
"overlay"
}
fn signature(&self) -> Signature {
Signature::build("overlay")
.category(Category::Core)
.input_output_types(vec![(Type::Nothing, Type::String)])
}
fn usage(&self) -> &str {
"Commands for manipulating overlays."
}
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nu.html
You must use one of the following subcommands. Using this command as-is will only produce this help message."#
}
fn is_parser_keyword(&self) -> bool {
true
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
Ok(Value::String {
val: get_full_help(
&Overlay.signature(),
&[],
engine_state,
stack,
self.is_parser_keyword(),
),
span: call.head,
}
.into_pipeline_data())
}
}

View File

@ -0,0 +1,132 @@
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type,
};
#[derive(Clone)]
pub struct OverlayHide;
impl Command for OverlayHide {
fn name(&self) -> &str {
"overlay hide"
}
fn usage(&self) -> &str {
"Hide an active overlay"
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("overlay hide")
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
.optional("name", SyntaxShape::String, "Overlay to hide")
.switch(
"keep-custom",
"Keep all newly added commands and aliases in the next activated overlay",
Some('k'),
)
.named(
"keep-env",
SyntaxShape::List(Box::new(SyntaxShape::String)),
"List of environment variables to keep in the next activated overlay",
Some('e'),
)
.category(Category::Core)
}
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nu.html"#
}
fn is_parser_keyword(&self) -> bool {
true
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let overlay_name: Spanned<String> = if let Some(name) = call.opt(engine_state, stack, 0)? {
name
} else {
Spanned {
item: stack.last_overlay_name()?,
span: call.head,
}
};
if !stack.is_overlay_active(&overlay_name.item) {
return Err(ShellError::OverlayNotFoundAtRuntime(
overlay_name.item,
overlay_name.span,
));
}
let keep_env: Option<Vec<Spanned<String>>> =
call.get_flag(engine_state, stack, "keep-env")?;
let env_vars_to_keep = if let Some(env_var_names_to_keep) = keep_env {
let mut env_vars_to_keep = vec![];
for name in env_var_names_to_keep.into_iter() {
match stack.get_env_var(engine_state, &name.item) {
Some(val) => env_vars_to_keep.push((name.item, val.clone())),
None => return Err(ShellError::EnvVarNotFoundAtRuntime(name.item, name.span)),
}
}
env_vars_to_keep
} else {
vec![]
};
stack.remove_overlay(&overlay_name.item);
for (name, val) in env_vars_to_keep {
stack.add_env_var(name, val);
}
Ok(PipelineData::empty())
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Keep a custom command after hiding the overlay",
example: r#"module spam { export def foo [] { "foo" } }
overlay use spam
def bar [] { "bar" }
overlay hide spam --keep-custom
bar
"#,
result: None,
},
Example {
description: "Hide an overlay created from a file",
example: r#"'export alias f = "foo"' | save spam.nu
overlay use spam.nu
overlay hide spam"#,
result: None,
},
Example {
description: "Hide the last activated overlay",
example: r#"module spam { export-env { let-env FOO = "foo" } }
overlay use spam
overlay hide"#,
result: None,
},
Example {
description: "Keep the current working directory when removing an overlay",
example: r#"overlay new spam
cd some-dir
overlay hide --keep-env [ PWD ] spam"#,
result: None,
},
]
}
}

View File

@ -0,0 +1,58 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value,
};
#[derive(Clone)]
pub struct OverlayList;
impl Command for OverlayList {
fn name(&self) -> &str {
"overlay list"
}
fn usage(&self) -> &str {
"List all active overlays"
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("overlay list")
.category(Category::Core)
.input_output_types(vec![(Type::Nothing, Type::List(Box::new(Type::String)))])
}
fn extra_usage(&self) -> &str {
"The overlays are listed in the order they were activated."
}
fn run(
&self,
_engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let active_overlays_engine: Vec<Value> = stack
.active_overlays
.iter()
.map(|s| Value::string(s, call.head))
.collect();
Ok(Value::List {
vals: active_overlays_engine,
span: call.head,
}
.into_pipeline_data())
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Get the last activated overlay",
example: r#"module spam { export def foo [] { "foo" } }
overlay use spam
overlay list | last"#,
result: Some(Value::test_string("spam")),
}]
}
}

View File

@ -0,0 +1,11 @@
mod command;
mod hide;
mod list;
mod new;
mod use_;
pub use command::Overlay;
pub use hide::OverlayHide;
pub use list::OverlayList;
pub use new::OverlayNew;
pub use use_::OverlayUse;

View File

@ -0,0 +1,78 @@
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type,
};
#[derive(Clone)]
pub struct OverlayNew;
impl Command for OverlayNew {
fn name(&self) -> &str {
"overlay new"
}
fn usage(&self) -> &str {
"Create an empty overlay"
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("overlay new")
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
.allow_variants_without_examples(true)
.required("name", SyntaxShape::String, "Name of the overlay")
// TODO:
// .switch(
// "prefix",
// "Prepend module name to the imported symbols",
// Some('p'),
// )
.category(Category::Core)
}
fn extra_usage(&self) -> &str {
r#"The command will first create an empty module, then add it as an overlay.
This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nu.html"#
}
fn is_parser_keyword(&self) -> bool {
true
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let name_arg: Spanned<String> = call.req(engine_state, stack, 0)?;
stack.add_overlay(name_arg.item);
Ok(PipelineData::empty())
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Create an empty overlay",
example: r#"overlay new spam"#,
result: None,
}]
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(OverlayNew {})
}
}

View File

@ -0,0 +1,199 @@
use nu_engine::{eval_block, find_in_dirs_env, redirect_env, CallExt};
use nu_parser::trim_quotes_str;
use nu_protocol::ast::{Call, Expr};
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Value,
};
use std::path::Path;
#[derive(Clone)]
pub struct OverlayUse;
impl Command for OverlayUse {
fn name(&self) -> &str {
"overlay use"
}
fn usage(&self) -> &str {
"Use definitions from a module as an overlay"
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("overlay use")
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
.allow_variants_without_examples(true)
.required(
"name",
SyntaxShape::String,
"Module name to use overlay for",
)
.optional(
"as",
SyntaxShape::Keyword(b"as".to_vec(), Box::new(SyntaxShape::String)),
"as keyword followed by a new name",
)
.switch(
"prefix",
"Prepend module name to the imported commands and aliases",
Some('p'),
)
.switch(
"reload",
"If the overlay already exists, reload its definitions and environment.",
Some('r'),
)
.category(Category::Core)
}
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nu.html"#
}
fn is_parser_keyword(&self) -> bool {
true
}
fn run(
&self,
engine_state: &EngineState,
caller_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let mut name_arg: Spanned<String> = call.req(engine_state, caller_stack, 0)?;
name_arg.item = trim_quotes_str(&name_arg.item).to_string();
let maybe_origin_module_id = if let Some(overlay_expr) = call.parser_info_nth(0) {
if let Expr::Overlay(module_id) = overlay_expr.expr {
module_id
} else {
return Err(ShellError::NushellFailedSpanned(
"Not an overlay".to_string(),
"requires an overlay (path or a string)".to_string(),
overlay_expr.span,
));
}
} else {
return Err(ShellError::NushellFailedSpanned(
"Missing positional".to_string(),
"missing required overlay".to_string(),
call.head,
));
};
let overlay_name = if let Some(name) = call.opt(engine_state, caller_stack, 1)? {
name
} else if engine_state
.find_overlay(name_arg.item.as_bytes())
.is_some()
{
name_arg.item.clone()
} else if let Some(os_str) = Path::new(&name_arg.item).file_stem() {
if let Some(name) = os_str.to_str() {
name.to_string()
} else {
return Err(ShellError::NonUtf8(name_arg.span));
}
} else {
return Err(ShellError::OverlayNotFoundAtRuntime(
name_arg.item,
name_arg.span,
));
};
if let Some(module_id) = maybe_origin_module_id {
// Add environment variables only if:
// a) adding a new overlay
// b) refreshing an active overlay (the origin module changed)
let module = engine_state.get_module(module_id);
// Evaluate the export-env block (if any) and keep its environment
if let Some(block_id) = module.env_block {
let maybe_path = find_in_dirs_env(&name_arg.item, engine_state, caller_stack)?;
let block = engine_state.get_block(block_id);
let mut callee_stack = caller_stack.gather_captures(&block.captures);
if let Some(path) = &maybe_path {
// Set the currently evaluated directory, if the argument is a valid path
let mut parent = path.clone();
parent.pop();
let file_pwd = Value::string(parent.to_string_lossy(), call.head);
callee_stack.add_env_var("FILE_PWD".to_string(), file_pwd);
}
let _ = eval_block(
engine_state,
&mut callee_stack,
block,
input,
call.redirect_stdout,
call.redirect_stderr,
);
// The export-env block should see the env vars *before* activating this overlay
caller_stack.add_overlay(overlay_name);
// Merge the block's environment to the current stack
redirect_env(engine_state, caller_stack, &callee_stack);
} else {
caller_stack.add_overlay(overlay_name);
}
} else {
caller_stack.add_overlay(overlay_name);
}
Ok(PipelineData::empty())
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Create an overlay from a module",
example: r#"module spam { export def foo [] { "foo" } }
overlay use spam
foo"#,
result: None,
},
Example {
description: "Create an overlay from a module and rename it",
example: r#"module spam { export def foo [] { "foo" } }
overlay use spam as spam_new
foo"#,
result: None,
},
Example {
description: "Create an overlay with a prefix",
example: r#"'export def foo { "foo" }'
overlay use --prefix spam
spam foo"#,
result: None,
},
Example {
description: "Create an overlay from a file",
example: r#"'export-env { let-env FOO = "foo" }' | save spam.nu
overlay use spam.nu
$env.FOO"#,
result: None,
},
]
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(OverlayUse {})
}
}