* WIP: Start laying overlays

* Rename Overlay->Module; Start adding overlay

* Revamp adding overlay

* Add overlay add tests; Disable debug print

* Fix overlay add; Add overlay remove

* Add overlay remove tests

* Add missing overlay remove file

* Add overlay list command

* (WIP?) Enable overlays for env vars

* Move OverlayFrames to ScopeFrames

* (WIP) Move everything to overlays only

ScopeFrame contains nothing but overlays now

* Fix predecls

* Fix wrong overlay id translation and aliases

* Fix broken env lookup logic

* Remove TODOs

* Add overlay add + remove for environment

* Add a few overlay tests; Fix overlay add name

* Some cleanup; Fix overlay add/remove names

* Clippy

* Fmt

* Remove walls of comments

* List overlays from stack; Add debugging flag

Currently, the engine state ordering is somehow broken.

* Fix (?) overlay list test

* Fix tests on Windows

* Fix activated overlay ordering

* Check for active overlays equality in overlay list

This removes the -p flag: Either both parser and engine will have the
same overlays, or the command will fail.

* Add merging on overlay remove

* Change help message and comment

* Add some remove-merge/discard tests

* (WIP) Track removed overlays properly

* Clippy; Fmt

* Fix getting last overlay; Fix predecls in overlays

* Remove merging; Fix re-add overwriting stuff

Also some error message tweaks.

* Fix overlay error in the engine

* Update variable_completions.rs

* Adds flags and optional arguments to view-source (#5446)

* added flags and optional arguments to view-source

* removed redundant code

* removed redundant code

* fmt

* fix bug in shell_integration (#5450)

* fix bug in shell_integration

* add some comments

* enable cd to work with directory abbreviations (#5452)

* enable cd to work with abbreviations

* add abbreviation example

* fix tests

* make it configurable

* make cd recornize symblic link (#5454)

* implement seq char command to generate single character sequence (#5453)

* add tmp code

* add seq char command

* Add split number flag in `split row` (#5434)

Signed-off-by: Yuheng Su <gipsyh.icu@gmail.com>

* Add two more overlay tests

* Add ModuleId to OverlayFrame

* Fix env conversion accidentally activating overlay

It activated overlay from permanent state prematurely which would
cause `overlay add` to misbehave.

* Remove unused parameter; Add overlay list test

* Remove added traces

* Add overlay commands examples

* Modify TODO

* Fix $nu.scope iteration

* Disallow removing default overlay

* Refactor some parser errors

* Remove last overlay if no argument

* Diversify overlay examples

* Make it possible to update overlay's module

In case the origin module updates, the overlay add loads the new module,
makes it overlay's origin and applies the changes. Before, it was
impossible to update the overlay if the module changed.

Co-authored-by: JT <547158+jntrnr@users.noreply.github.com>
Co-authored-by: pwygab <88221256+merelymyself@users.noreply.github.com>
Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
Co-authored-by: WindSoilder <WindSoilder@outlook.com>
Co-authored-by: Yuheng Su <gipsyh.icu@gmail.com>
This commit is contained in:
Jakub Žádník 2022-05-07 22:39:22 +03:00 committed by GitHub
parent 1cb449b2d1
commit 9b99b2f6ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 2638 additions and 607 deletions

View File

@ -42,7 +42,9 @@ pub fn evaluate_commands(
// Merge the delta in case env vars changed in the config // Merge the delta in case env vars changed in the config
match nu_engine::env::current_dir(engine_state, stack) { match nu_engine::env::current_dir(engine_state, stack) {
Ok(cwd) => { Ok(cwd) => {
if let Err(e) = engine_state.merge_delta(StateDelta::new(), Some(stack), cwd) { if let Err(e) =
engine_state.merge_delta(StateDelta::new(engine_state), Some(stack), cwd)
{
let working_set = StateWorkingSet::new(engine_state); let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &e); report_error(&working_set, &e);
std::process::exit(1); std::process::exit(1);

View File

@ -39,7 +39,7 @@ impl CommandCompletion {
) -> Vec<String> { ) -> Vec<String> {
let mut executables = vec![]; let mut executables = vec![];
let paths = self.engine_state.env_vars.get("PATH"); let paths = self.engine_state.get_env_var("PATH");
if let Some(paths) = paths { if let Some(paths) = paths {
if let Ok(paths) = paths.as_list() { if let Ok(paths) = paths.as_list() {
@ -214,7 +214,7 @@ impl Completer for CommandCompletion {
vec![] vec![]
}; };
let cwd = if let Some(d) = self.engine_state.env_vars.get("PWD") { let cwd = if let Some(d) = self.engine_state.get_env_var("PWD") {
match d.as_string() { match d.as_string() {
Ok(s) => s, Ok(s) => s,
Err(_) => "".to_string(), Err(_) => "".to_string(),

View File

@ -33,7 +33,7 @@ impl Completer for DirectoryCompletion {
_: usize, _: usize,
options: &CompletionOptions, options: &CompletionOptions,
) -> Vec<Suggestion> { ) -> Vec<Suggestion> {
let cwd = if let Some(d) = self.engine_state.env_vars.get("PWD") { let cwd = if let Some(d) = self.engine_state.get_env_var("PWD") {
match d.as_string() { match d.as_string() {
Ok(s) => s, Ok(s) => s,
Err(_) => "".to_string(), Err(_) => "".to_string(),

View File

@ -37,7 +37,7 @@ impl Completer for DotNuCompletion {
// Fetch the lib dirs // Fetch the lib dirs
let lib_dirs: Vec<String> = let lib_dirs: Vec<String> =
if let Some(lib_dirs) = self.engine_state.env_vars.get("NU_LIB_DIRS") { if let Some(lib_dirs) = self.engine_state.get_env_var("NU_LIB_DIRS") {
lib_dirs lib_dirs
.as_list() .as_list()
.into_iter() .into_iter()
@ -70,7 +70,7 @@ impl Completer for DotNuCompletion {
partial = base_dir_partial; partial = base_dir_partial;
} else { } else {
// Fetch the current folder // Fetch the current folder
let current_folder = if let Some(d) = self.engine_state.env_vars.get("PWD") { let current_folder = if let Some(d) = self.engine_state.get_env_var("PWD") {
match d.as_string() { match d.as_string() {
Ok(s) => s, Ok(s) => s,
Err(_) => "".to_string(), Err(_) => "".to_string(),

View File

@ -30,7 +30,7 @@ impl Completer for FileCompletion {
_: usize, _: usize,
options: &CompletionOptions, options: &CompletionOptions,
) -> Vec<Suggestion> { ) -> Vec<Suggestion> {
let cwd = if let Some(d) = self.engine_state.env_vars.get("PWD") { let cwd = if let Some(d) = self.engine_state.get_env_var("PWD") {
match d.as_string() { match d.as_string() {
Ok(s) => s, Ok(s) => s,
Err(_) => "".to_string(), Err(_) => "".to_string(),

View File

@ -11,7 +11,7 @@ use std::sync::Arc;
#[derive(Clone)] #[derive(Clone)]
pub struct VariableCompletion { pub struct VariableCompletion {
engine_state: Arc<EngineState>, engine_state: Arc<EngineState>, // TODO: Is engine state necessary? It's already a part of working set in fetch()
stack: Stack, stack: Stack,
var_context: (Vec<u8>, Vec<Vec<u8>>), // tuple with $var and the sublevels (.b.c.d) var_context: (Vec<u8>, Vec<Vec<u8>>), // tuple with $var and the sublevels (.b.c.d)
} }
@ -143,9 +143,17 @@ impl Completer for VariableCompletion {
} }
} }
// TODO: The following can be refactored (see find_commands_by_predicate() used in
// command_completions).
let mut removed_overlays = vec![];
// Working set scope vars // Working set scope vars
for scope in &working_set.delta.scope { for scope_frame in working_set.delta.scope.iter().rev() {
for v in &scope.vars { for overlay_frame in scope_frame
.active_overlays(&mut removed_overlays)
.iter()
.rev()
{
for v in &overlay_frame.vars {
if options.match_algorithm.matches_u8(v.0, &prefix) { if options.match_algorithm.matches_u8(v.0, &prefix) {
output.push(Suggestion { output.push(Suggestion {
value: String::from_utf8_lossy(v.0).to_string(), value: String::from_utf8_lossy(v.0).to_string(),
@ -157,10 +165,17 @@ impl Completer for VariableCompletion {
} }
} }
} }
}
// Permanent state vars // Permanent state vars
for scope in &self.engine_state.scope { // for scope in &self.engine_state.scope {
for v in &scope.vars { for overlay_frame in self
.engine_state
.active_overlays(&removed_overlays)
.iter()
.rev()
{
for v in &overlay_frame.vars {
if options.match_algorithm.matches_u8(v.0, &prefix) { if options.match_algorithm.matches_u8(v.0, &prefix) {
output.push(Suggestion { output.push(Suggestion {
value: String::from_utf8_lossy(v.0).to_string(), value: String::from_utf8_lossy(v.0).to_string(),
@ -173,7 +188,7 @@ impl Completer for VariableCompletion {
} }
} }
output.dedup(); output.dedup(); // TODO: Removes only consecutive duplicates, is it intended?
output output
} }

View File

@ -69,7 +69,9 @@ pub fn eval_config_contents(
// Merge the delta in case env vars changed in the config // Merge the delta in case env vars changed in the config
match nu_engine::env::current_dir(engine_state, stack) { match nu_engine::env::current_dir(engine_state, stack) {
Ok(cwd) => { Ok(cwd) => {
if let Err(e) = engine_state.merge_delta(StateDelta::new(), Some(stack), cwd) { if let Err(e) =
engine_state.merge_delta(StateDelta::new(engine_state), Some(stack), cwd)
{
let working_set = StateWorkingSet::new(engine_state); let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &e); report_error(&working_set, &e);
} }

View File

@ -71,7 +71,7 @@ pub fn print_table_or_error(
_ => None, _ => None,
}; };
match engine_state.find_decl("table".as_bytes()) { match engine_state.find_decl("table".as_bytes(), &[]) {
Some(decl_id) => { Some(decl_id) => {
let table = engine_state.get_decl(decl_id).run( let table = engine_state.get_decl(decl_id).run(
engine_state, engine_state,

View File

@ -301,7 +301,7 @@ pub fn evaluate_repl(
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") { if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
let path = cwd.as_string()?; let path = cwd.as_string()?;
let _ = std::env::set_current_dir(path); let _ = std::env::set_current_dir(path);
engine_state.env_vars.insert("PWD".into(), cwd); engine_state.add_env_var("PWD".into(), cwd);
} }
if use_shell_integration { if use_shell_integration {

View File

@ -179,7 +179,7 @@ fn gather_env_vars(vars: impl Iterator<Item = (String, String)>, engine_state: &
}; };
// stack.add_env_var(name, value); // stack.add_env_var(name, value);
engine_state.env_vars.insert(name, value); engine_state.add_env_var(name, value);
} }
} }
} }
@ -319,12 +319,18 @@ mod test {
&mut engine_state, &mut engine_state,
); );
let env = engine_state.env_vars; let env = engine_state.render_env_vars();
assert!(matches!(env.get("FOO"), Some(Value::String { val, .. }) if val == "foo")); assert!(
assert!(matches!(env.get("SYMBOLS"), Some(Value::String { val, .. }) if val == symbols)); matches!(env.get(&"FOO".to_string()), Some(&Value::String { val, .. }) if val == "foo")
assert!(matches!(env.get(symbols), Some(Value::String { val, .. }) if val == "symbols")); );
assert!(env.get("PWD").is_some()); assert!(
matches!(env.get(&"SYMBOLS".to_string()), Some(&Value::String { val, .. }) if val == symbols)
);
assert!(
matches!(env.get(&symbols.to_string()), Some(&Value::String { val, .. }) if val == "symbols")
);
assert!(env.get(&"PWD".to_string()).is_some());
assert_eq!(env.len(), 4); assert_eq!(env.len(), 4);
} }
} }

View File

@ -63,23 +63,23 @@ https://www.nushell.sh/book/thinking_in_nushell.html#parsing-and-evaluation-are-
return Err(ShellError::NonUtf8(import_pattern.head.span)); return Err(ShellError::NonUtf8(import_pattern.head.span));
}; };
if let Some(overlay_id) = engine_state.find_overlay(&import_pattern.head.name) { if let Some(module_id) = engine_state.find_module(&import_pattern.head.name, &[]) {
// The first word is a module // The first word is a module
let overlay = engine_state.get_overlay(overlay_id); let module = engine_state.get_module(module_id);
let env_vars_to_hide = if import_pattern.members.is_empty() { let env_vars_to_hide = if import_pattern.members.is_empty() {
overlay.env_vars_with_head(&import_pattern.head.name) module.env_vars_with_head(&import_pattern.head.name)
} else { } else {
match &import_pattern.members[0] { match &import_pattern.members[0] {
ImportPatternMember::Glob { .. } => overlay.env_vars(), ImportPatternMember::Glob { .. } => module.env_vars(),
ImportPatternMember::Name { name, span } => { ImportPatternMember::Name { name, span } => {
let mut output = vec![]; let mut output = vec![];
if let Some((name, id)) = if let Some((name, id)) =
overlay.env_var_with_head(name, &import_pattern.head.name) module.env_var_with_head(name, &import_pattern.head.name)
{ {
output.push((name, id)); output.push((name, id));
} else if !(overlay.has_alias(name) || overlay.has_decl(name)) { } else if !(module.has_alias(name) || module.has_decl(name)) {
return Err(ShellError::EnvVarNotFoundAtRuntime( return Err(ShellError::EnvVarNotFoundAtRuntime(
String::from_utf8_lossy(name).into(), String::from_utf8_lossy(name).into(),
*span, *span,
@ -93,10 +93,10 @@ https://www.nushell.sh/book/thinking_in_nushell.html#parsing-and-evaluation-are-
for (name, span) in names { for (name, span) in names {
if let Some((name, id)) = if let Some((name, id)) =
overlay.env_var_with_head(name, &import_pattern.head.name) module.env_var_with_head(name, &import_pattern.head.name)
{ {
output.push((name, id)); output.push((name, id));
} else if !(overlay.has_alias(name) || overlay.has_decl(name)) { } else if !(module.has_alias(name) || module.has_decl(name)) {
return Err(ShellError::EnvVarNotFoundAtRuntime( return Err(ShellError::EnvVarNotFoundAtRuntime(
String::from_utf8_lossy(name).into(), String::from_utf8_lossy(name).into(),
*span, *span,

View File

@ -22,6 +22,7 @@ mod ignore;
mod let_; mod let_;
mod metadata; mod metadata;
mod module; mod module;
pub(crate) mod overlay;
mod source; mod source;
mod tutor; mod tutor;
mod use_; mod use_;
@ -51,6 +52,7 @@ pub use ignore::Ignore;
pub use let_::Let; pub use let_::Let;
pub use metadata::Metadata; pub use metadata::Metadata;
pub use module::Module; pub use module::Module;
pub use overlay::*;
pub use source::Source; pub use source::Source;
pub use tutor::Tutor; pub use tutor::Tutor;
pub use use_::Use; pub use use_::Use;

View File

@ -0,0 +1,139 @@
use nu_engine::{eval_block, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape};
use std::path::Path;
#[derive(Clone)]
pub struct OverlayAdd;
impl Command for OverlayAdd {
fn name(&self) -> &str {
"overlay add"
}
fn usage(&self) -> &str {
"Add definitions from a module as an overlay"
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("overlay add")
.required(
"name",
SyntaxShape::String,
"Module name to create overlay for",
)
// TODO:
// .switch(
// "prefix",
// "Prepend module name to the imported symbols",
// Some('p'),
// )
.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_nushell.html#parsing-and-evaluation-are-different-stages"#
}
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)?;
// TODO: This logic is duplicated in the parser.
if stack.has_env_overlay(&name_arg.item, engine_state) {
stack.add_overlay(name_arg.item);
} else {
let (overlay_name, module) =
if let Some(module_id) = engine_state.find_module(name_arg.item.as_bytes(), &[]) {
(name_arg.item, engine_state.get_module(module_id))
} else if let Some(os_str) = Path::new(&name_arg.item).file_stem() {
let name = if let Some(s) = os_str.to_str() {
s.to_string()
} else {
return Err(ShellError::NonUtf8(name_arg.span));
};
if let Some(module_id) = engine_state.find_module(name.as_bytes(), &[]) {
(name, engine_state.get_module(module_id))
} else {
return Err(ShellError::ModuleOrOverlayNotFoundAtRuntime(
name_arg.item,
name_arg.span,
));
}
} else {
return Err(ShellError::ModuleOrOverlayNotFoundAtRuntime(
name_arg.item,
name_arg.span,
));
};
stack.add_overlay(overlay_name);
for (name, block_id) in module.env_vars() {
let name = if let Ok(s) = String::from_utf8(name.clone()) {
s
} else {
return Err(ShellError::NonUtf8(name_arg.span));
};
let block = engine_state.get_block(block_id);
let val = eval_block(
engine_state,
stack,
block,
PipelineData::new(call.head),
false,
true,
)?
.into_value(call.head);
stack.add_env_var(name, val);
}
}
Ok(PipelineData::new(call.head))
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Create an overlay from a module",
example: r#"module spam { export def foo [] { "foo" } }
overlay add spam"#,
result: None,
},
Example {
description: "Create an overlay from a file",
example: r#"echo 'export env FOO { "foo" }' | save spam.nu
overlay add spam.nu"#,
result: None,
},
]
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(OverlayAdd {})
}
}

View File

@ -0,0 +1,58 @@
use nu_engine::get_full_help;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, IntoPipelineData, PipelineData, Signature, 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)
}
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_nushell.html#parsing-and-evaluation-are-different-stages"#
}
fn is_parser_keyword(&self) -> bool {
true
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
Ok(Value::String {
val: get_full_help(&Overlay.signature(), &[], engine_state, stack),
span: call.head,
}
.into_pipeline_data())
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(Overlay {})
}
}

View File

@ -0,0 +1,85 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Value,
};
use log::trace;
#[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)
}
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_parser: Vec<Value> = engine_state
.active_overlay_names(&[])
.iter()
.map(|s| Value::string(String::from_utf8_lossy(s), call.head))
.collect();
let active_overlays_engine: Vec<Value> = stack
.active_overlays
.iter()
.map(|s| Value::string(s, call.head))
.collect();
// Check if the overlays in the engine match the overlays in the parser
if (active_overlays_parser.len() != active_overlays_engine.len())
|| active_overlays_parser
.iter()
.zip(active_overlays_engine.iter())
.any(|(op, oe)| op != oe)
{
trace!("parser overlays: {:?}", active_overlays_parser);
trace!("engine overlays: {:?}", active_overlays_engine);
return Err(ShellError::NushellFailedSpannedHelp(
"Overlay mismatch".into(),
"Active overlays do not match between the engine and the parser.".into(),
call.head,
"Run Nushell with --log-level=trace to see what went wrong.".into(),
));
}
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 add spam
overlay list | last"#,
result: Some(Value::String {
val: "spam".to_string(),
span: Span::test_data(),
}),
}]
}
}

View File

@ -0,0 +1,9 @@
mod add;
mod command;
mod list;
mod remove;
pub use add::OverlayAdd;
pub use command::Overlay;
pub use list::OverlayList;
pub use remove::OverlayRemove;

View File

@ -0,0 +1,82 @@
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, Example, PipelineData, Signature, Spanned, SyntaxShape};
#[derive(Clone)]
pub struct OverlayRemove;
impl Command for OverlayRemove {
fn name(&self) -> &str {
"overlay remove"
}
fn usage(&self) -> &str {
"Remove an active overlay"
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("overlay remove")
.optional("name", SyntaxShape::String, "Overlay to remove")
.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_nushell.html#parsing-and-evaluation-are-different-stages"#
}
fn is_parser_keyword(&self) -> bool {
true
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
// let module_name: Spanned<String> = call.req(engine_state, stack, 0)?;
let module_name: Spanned<String> = if let Some(name) = call.opt(engine_state, stack, 0)? {
name
} else {
Spanned {
item: stack.last_overlay_name()?,
span: call.head,
}
};
// TODO: Add env merging
stack.remove_overlay(&module_name.item, &module_name.span)?;
Ok(PipelineData::new(call.head))
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Remove an overlay created from a module",
example: r#"module spam { export def foo [] { "foo" } }
overlay add spam
overlay remove spam"#,
result: None,
},
Example {
description: "Remove an overlay created from a file",
example: r#"echo 'export alias f = "foo"' | save spam.nu
overlay add spam.nu
overlay remove spam"#,
result: None,
},
Example {
description: "Remove the last activated overlay",
example: r#"module spam { export env FOO { "foo" } }
overlay add spam
overlay remove"#,
result: None,
},
]
}
}

View File

@ -424,7 +424,7 @@ fn display(help: &str, engine_state: &EngineState, stack: &mut Stack, span: Span
code_mode = false; code_mode = false;
//TODO: support no-color mode //TODO: support no-color mode
if let Some(highlighter) = engine_state.find_decl(b"nu-highlight") { if let Some(highlighter) = engine_state.find_decl(b"nu-highlight", &[]) {
let decl = engine_state.get_decl(highlighter); let decl = engine_state.get_decl(highlighter);
if let Ok(output) = decl.run( if let Ok(output) = decl.run(

View File

@ -55,20 +55,20 @@ https://www.nushell.sh/book/thinking_in_nushell.html#parsing-and-evaluation-are-
)); ));
}; };
if let Some(overlay_id) = import_pattern.head.id { if let Some(module_id) = import_pattern.head.id {
let overlay = engine_state.get_overlay(overlay_id); let module = engine_state.get_module(module_id);
let env_vars_to_use = if import_pattern.members.is_empty() { let env_vars_to_use = if import_pattern.members.is_empty() {
overlay.env_vars_with_head(&import_pattern.head.name) module.env_vars_with_head(&import_pattern.head.name)
} else { } else {
match &import_pattern.members[0] { match &import_pattern.members[0] {
ImportPatternMember::Glob { .. } => overlay.env_vars(), ImportPatternMember::Glob { .. } => module.env_vars(),
ImportPatternMember::Name { name, span } => { ImportPatternMember::Name { name, span } => {
let mut output = vec![]; let mut output = vec![];
if let Some(id) = overlay.get_env_var_id(name) { if let Some(id) = module.get_env_var_id(name) {
output.push((name.clone(), id)); output.push((name.clone(), id));
} else if !overlay.has_decl(name) && !overlay.has_alias(name) { } else if !module.has_decl(name) && !module.has_alias(name) {
return Err(ShellError::EnvVarNotFoundAtRuntime( return Err(ShellError::EnvVarNotFoundAtRuntime(
String::from_utf8_lossy(name).into(), String::from_utf8_lossy(name).into(),
*span, *span,
@ -81,9 +81,9 @@ https://www.nushell.sh/book/thinking_in_nushell.html#parsing-and-evaluation-are-
let mut output = vec![]; let mut output = vec![];
for (name, span) in names { for (name, span) in names {
if let Some(id) = overlay.get_env_var_id(name) { if let Some(id) = module.get_env_var_id(name) {
output.push((name.clone(), id)); output.push((name.clone(), id));
} else if !overlay.has_decl(name) && !overlay.has_alias(name) { } else if !module.has_decl(name) && !module.has_alias(name) {
return Err(ShellError::EnvVarNotFoundAtRuntime( return Err(ShellError::EnvVarNotFoundAtRuntime(
String::from_utf8_lossy(name).into(), String::from_utf8_lossy(name).into(),
*span, *span,
@ -105,8 +105,6 @@ https://www.nushell.sh/book/thinking_in_nushell.html#parsing-and-evaluation-are-
let block = engine_state.get_block(block_id); let block = engine_state.get_block(block_id);
// TODO: Add string conversions (e.g. int to string)
// TODO: Later expand env to take all Values
let val = eval_block( let val = eval_block(
engine_state, engine_state,
stack, stack,

View File

@ -38,25 +38,19 @@ impl Command for ListDF {
call: &Call, call: &Call,
_input: PipelineData, _input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let vals = engine_state let mut vals: Vec<(String, Value)> = vec![];
.scope
.iter() for overlay_frame in engine_state.active_overlays(&[]) {
.flat_map(|frame| { for var in &overlay_frame.vars {
frame if let Ok(value) = stack.get_var(*var.1, call.head) {
.vars
.iter()
.filter_map(|var| {
let value = stack.get_var(*var.1, call.head);
match value {
Ok(value) => {
let name = String::from_utf8_lossy(var.0).to_string(); let name = String::from_utf8_lossy(var.0).to_string();
Some((name, value)) vals.push((name, value));
} }
Err(_) => None,
} }
}) }
.collect::<Vec<(String, Value)>>()
}) let vals = vals
.into_iter()
.filter_map(|(name, value)| match NuDataFrame::try_from_value(value) { .filter_map(|(name, value)| match NuDataFrame::try_from_value(value) {
Ok(df) => Some((name, df)), Ok(df) => Some((name, df)),
Err(_) => None, Err(_) => None,

View File

@ -52,6 +52,10 @@ pub fn create_default_context(cwd: impl AsRef<Path>) -> EngineState {
History, History,
If, If,
Ignore, Ignore,
Overlay,
OverlayAdd,
OverlayList,
OverlayRemove,
Let, Let,
Metadata, Metadata,
Module, Module,

View File

@ -47,7 +47,7 @@ impl Command for ViewSource {
} }
} }
Value::String { val, .. } => { Value::String { val, .. } => {
if let Some(decl_id) = engine_state.find_decl(val.as_bytes()) { if let Some(decl_id) = engine_state.find_decl(val.as_bytes(), &[]) {
// arg is a command // arg is a command
let decl = engine_state.get_decl(decl_id); let decl = engine_state.get_decl(decl_id);
let sig = decl.signature(); let sig = decl.signature();
@ -115,11 +115,11 @@ impl Command for ViewSource {
Vec::new(), Vec::new(),
)) ))
} }
} else if let Some(overlay_id) = engine_state.find_overlay(val.as_bytes()) { } else if let Some(module_id) = engine_state.find_module(val.as_bytes(), &[]) {
// arg is a module // arg is a module
let overlay = engine_state.get_overlay(overlay_id); let module = engine_state.get_module(module_id);
if let Some(overlay_span) = overlay.span { if let Some(module_span) = module.span {
let contents = engine_state.get_span_contents(&overlay_span); let contents = engine_state.get_span_contents(&module_span);
Ok(Value::string(String::from_utf8_lossy(contents), call.head) Ok(Value::string(String::from_utf8_lossy(contents), call.head)
.into_pipeline_data()) .into_pipeline_data())
} else { } else {

View File

@ -151,7 +151,7 @@ impl Command for Open {
}; };
if let Some(ext) = ext { if let Some(ext) = ext {
match engine_state.find_decl(format!("from {}", ext).as_bytes()) { match engine_state.find_decl(format!("from {}", ext).as_bytes(), &[]) {
Some(converter_id) => { Some(converter_id) => {
let decl = engine_state.get_decl(converter_id); let decl = engine_state.get_decl(converter_id);
if let Some(block_id) = decl.get_block_id() { if let Some(block_id) = decl.get_block_id() {

View File

@ -82,7 +82,7 @@ impl Command for Save {
}; };
if let Some(ext) = ext { if let Some(ext) = ext {
let output = match engine_state.find_decl(format!("to {}", ext).as_bytes()) { let output = match engine_state.find_decl(format!("to {}", ext).as_bytes(), &[]) {
Some(converter_id) => { Some(converter_id) => {
let output = engine_state.get_decl(converter_id).run( let output = engine_state.get_decl(converter_id).run(
engine_state, engine_state,

View File

@ -294,7 +294,7 @@ fn helper(
} }
if let Some(ext) = ext { if let Some(ext) = ext {
match engine_state.find_decl(format!("from {}", ext).as_bytes()) { match engine_state.find_decl(format!("from {}", ext).as_bytes(), &[]) {
Some(converter_id) => engine_state.get_decl(converter_id).run( Some(converter_id) => engine_state.get_decl(converter_id).run(
engine_state, engine_state,
stack, stack,

View File

@ -351,7 +351,7 @@ fn helper(
return Ok(output); return Ok(output);
} }
if let Some(ext) = ext { if let Some(ext) = ext {
match engine_state.find_decl(format!("from {}", ext).as_bytes()) { match engine_state.find_decl(format!("from {}", ext).as_bytes(), &[]) {
Some(converter_id) => engine_state.get_decl(converter_id).run( Some(converter_id) => engine_state.get_decl(converter_id).run(
engine_state, engine_state,
stack, stack,

View File

@ -69,7 +69,7 @@ fn entry(arg: impl Into<String>, path: impl Into<String>, builtin: bool, span: S
} }
fn get_entry_in_aliases(engine_state: &EngineState, name: &str, span: Span) -> Option<Value> { fn get_entry_in_aliases(engine_state: &EngineState, name: &str, span: Span) -> Option<Value> {
if let Some(alias_id) = engine_state.find_alias(name.as_bytes()) { if let Some(alias_id) = engine_state.find_alias(name.as_bytes(), &[]) {
let alias = engine_state.get_alias(alias_id); let alias = engine_state.get_alias(alias_id);
let alias_str = alias let alias_str = alias
.iter() .iter()
@ -90,7 +90,7 @@ fn get_entry_in_aliases(engine_state: &EngineState, name: &str, span: Span) -> O
} }
fn get_entry_in_commands(engine_state: &EngineState, name: &str, span: Span) -> Option<Value> { fn get_entry_in_commands(engine_state: &EngineState, name: &str, span: Span) -> Option<Value> {
if let Some(decl_id) = engine_state.find_decl(name.as_bytes()) { if let Some(decl_id) = engine_state.find_decl(name.as_bytes(), &[]) {
let (msg, is_builtin) = if engine_state.get_decl(decl_id).get_block_id().is_some() { let (msg, is_builtin) = if engine_state.get_decl(decl_id).get_block_id().is_some() {
("Nushell custom command", false) ("Nushell custom command", false)
} else { } else {

View File

@ -115,7 +115,7 @@ fn get_documentation(
if config.no_color { if config.no_color {
long_desc.push_str(&format!("\n > {}\n", example.example)); long_desc.push_str(&format!("\n > {}\n", example.example));
} else if let Some(highlighter) = engine_state.find_decl(b"nu-highlight") { } else if let Some(highlighter) = engine_state.find_decl(b"nu-highlight", &[]) {
let decl = engine_state.get_decl(highlighter); let decl = engine_state.get_decl(highlighter);
match decl.run( match decl.run(

View File

@ -34,7 +34,9 @@ pub fn convert_env_values(engine_state: &mut EngineState, stack: &Stack) -> Opti
let mut new_scope = HashMap::new(); let mut new_scope = HashMap::new();
for (name, val) in &engine_state.env_vars { let env_vars = engine_state.render_env_vars();
for (name, val) in env_vars {
match get_converted_value(engine_state, stack, name, val, "from_string") { match get_converted_value(engine_state, stack, name, val, "from_string") {
ConversionResult::Ok(v) => { ConversionResult::Ok(v) => {
let _ = new_scope.insert(name.to_string(), v); let _ = new_scope.insert(name.to_string(), v);
@ -64,8 +66,26 @@ pub fn convert_env_values(engine_state: &mut EngineState, stack: &Stack) -> Opti
} }
} }
if let Ok(last_overlay_name) = &stack.last_overlay_name() {
if let Some(env_vars) = engine_state.env_vars.get_mut(last_overlay_name) {
for (k, v) in new_scope { for (k, v) in new_scope {
engine_state.env_vars.insert(k, v); env_vars.insert(k, v);
}
} else {
error = error.or_else(|| {
Some(ShellError::NushellFailedHelp(
"Last active overlay not found in permanent state.".into(),
"This error happened during the conversion of environment variables from strings to Nushell values.".into(),
))
});
}
} else {
error = error.or_else(|| {
Some(ShellError::NushellFailedHelp(
"Last active overlay not found in stack.".into(),
"This error happened during the conversion of environment variables from strings to Nushell values.".into(),
))
});
} }
error error

View File

@ -169,12 +169,10 @@ pub fn eval_call(
} }
// add new env vars from callee to caller // add new env vars from callee to caller
for env_vars in callee_stack.env_vars { for (var, value) in callee_stack.get_stack_env_vars() {
for (var, value) in env_vars {
caller_stack.add_env_var(var, value); caller_stack.add_env_var(var, value);
} }
} }
}
result result
} else { } else {
@ -195,7 +193,7 @@ fn eval_external(
redirect_stderr: bool, redirect_stderr: bool,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let decl_id = engine_state let decl_id = engine_state
.find_decl("run-external".as_bytes()) .find_decl("run-external".as_bytes(), &[])
.ok_or(ShellError::ExternalNotSupported(head.span))?; .ok_or(ShellError::ExternalNotSupported(head.span))?;
let command = engine_state.get_decl(decl_id); let command = engine_state.get_decl(decl_id);
@ -660,7 +658,7 @@ pub fn eval_block(
// Drain the input to the screen via tabular output // Drain the input to the screen via tabular output
let config = engine_state.get_config(); let config = engine_state.get_config();
match engine_state.find_decl("table".as_bytes()) { match engine_state.find_decl("table".as_bytes(), &[]) {
Some(decl_id) => { Some(decl_id) => {
let table = engine_state.get_decl(decl_id).run( let table = engine_state.get_decl(decl_id).run(
engine_state, engine_state,
@ -716,7 +714,7 @@ pub fn eval_block(
// Drain the input to the screen via tabular output // Drain the input to the screen via tabular output
let config = engine_state.get_config(); let config = engine_state.get_config();
match engine_state.find_decl("table".as_bytes()) { match engine_state.find_decl("table".as_bytes(), &[]) {
Some(decl_id) => { Some(decl_id) => {
let table = engine_state.get_decl(decl_id).run( let table = engine_state.get_decl(decl_id).run(
engine_state, engine_state,
@ -806,21 +804,21 @@ pub fn create_scope(
let mut vars = vec![]; let mut vars = vec![];
let mut commands = vec![]; let mut commands = vec![];
let mut aliases = vec![]; let mut aliases = vec![];
let mut overlays = vec![]; let mut modules = vec![];
let mut vars_map = HashMap::new(); let mut vars_map = HashMap::new();
let mut commands_map = HashMap::new(); let mut commands_map = HashMap::new();
let mut aliases_map = HashMap::new(); let mut aliases_map = HashMap::new();
let mut overlays_map = HashMap::new(); let mut modules_map = HashMap::new();
let mut visibility = Visibility::new(); let mut visibility = Visibility::new();
for frame in &engine_state.scope { for overlay_frame in engine_state.active_overlays(&[]) {
vars_map.extend(&frame.vars); vars_map.extend(&overlay_frame.vars);
commands_map.extend(&frame.decls); commands_map.extend(&overlay_frame.decls);
aliases_map.extend(&frame.aliases); aliases_map.extend(&overlay_frame.aliases);
overlays_map.extend(&frame.overlays); modules_map.extend(&overlay_frame.modules);
visibility.merge_with(frame.visibility.clone()); visibility.merge_with(overlay_frame.visibility.clone());
} }
for var in vars_map { for var in vars_map {
@ -846,14 +844,14 @@ pub fn create_scope(
let mut cols = vec![]; let mut cols = vec![];
let mut vals = vec![]; let mut vals = vec![];
let mut overlay_commands = vec![]; let mut module_commands = vec![];
for overlay in &overlays_map { for module in &modules_map {
let overlay_name = String::from_utf8_lossy(*overlay.0).to_string(); let module_name = String::from_utf8_lossy(module.0).to_string();
let overlay_id = engine_state.find_overlay(*overlay.0); let module_id = engine_state.find_module(module.0, &[]);
if let Some(overlay_id) = overlay_id { if let Some(module_id) = module_id {
let overlay = engine_state.get_overlay(overlay_id); let module = engine_state.get_module(module_id);
if overlay.has_decl(command_name) { if module.has_decl(command_name) {
overlay_commands.push(overlay_name); module_commands.push(module_name);
} }
} }
} }
@ -865,7 +863,7 @@ pub fn create_scope(
}); });
cols.push("module_name".into()); cols.push("module_name".into());
vals.push(Value::string(overlay_commands.join(", "), span)); vals.push(Value::string(module_commands.join(", "), span));
let decl = engine_state.get_decl(*decl_id); let decl = engine_state.get_decl(*decl_id);
let signature = decl.signature(); let signature = decl.signature();
@ -1132,9 +1130,9 @@ pub fn create_scope(
} }
} }
for overlay in overlays_map { for module in modules_map {
overlays.push(Value::String { modules.push(Value::String {
val: String::from_utf8_lossy(overlay.0).to_string(), val: String::from_utf8_lossy(module.0).to_string(),
span, span,
}); });
} }
@ -1179,10 +1177,10 @@ pub fn create_scope(
span, span,
}); });
overlays.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal)); modules.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
output_cols.push("overlays".to_string()); output_cols.push("modules".to_string());
output_vals.push(Value::List { output_vals.push(Value::List {
vals: overlays, vals: modules,
span, span,
}); });

View File

@ -102,6 +102,34 @@ pub enum ParseError {
)] )]
ModuleNotFound(#[label = "module not found"] Span), ModuleNotFound(#[label = "module not found"] Span),
#[error("Active overlay not found.")]
#[diagnostic(code(nu::parser::active_overlay_not_found), url(docsrs))]
ActiveOverlayNotFound(#[label = "not an active overlay"] Span),
#[error("Module or overlay not found.")]
#[diagnostic(
code(nu::parser::module_or_overlay_not_found),
url(docsrs),
help("Requires either an existing overlay, a module, or an import pattern defining a module.")
)]
ModuleOrOverlayNotFound(#[label = "not a module or an overlay"] Span),
#[error("Cannot remove the last overlay.")]
#[diagnostic(
code(nu::parser::cant_remove_last_overlay),
url(docsrs),
help("At least one overlay must always be active.")
)]
CantRemoveLastOverlay(#[label = "this is the last overlay, can't remove it"] Span),
#[error("Cannot remove default overlay.")]
#[diagnostic(
code(nu::parser::cant_remove_default_overlay),
url(docsrs),
help("'{0}' is a default overlay. Default overlays cannot be removed.")
)]
CantRemoveDefaultOverlay(String, #[label = "can't remove overlay"] Span),
#[error("Not found.")] #[error("Not found.")]
#[diagnostic(code(nu::parser::not_found), url(docsrs))] #[diagnostic(code(nu::parser::not_found), url(docsrs))]
NotFound(#[label = "did not find anything under this name"] Span), NotFound(#[label = "did not find anything under this name"] Span),
@ -245,6 +273,15 @@ pub enum ParseError {
#[diagnostic(code(nu::parser::file_not_found), url(docsrs))] #[diagnostic(code(nu::parser::file_not_found), url(docsrs))]
FileNotFound(String, #[label("File not found: {0}")] Span), FileNotFound(String, #[label("File not found: {0}")] Span),
/// Error while trying to read a file
///
/// ## Resolution
///
/// The error will show the result from a file operation
#[error("Error trying to read file")]
#[diagnostic(code(nu::shell::error_reading_file), url(docsrs))]
ReadingFile(String, #[label("{0}")] Span),
#[error("{0}")] #[error("{0}")]
#[diagnostic()] #[diagnostic()]
LabeledError(String, String, #[label("{1}")] Span), LabeledError(String, String, #[label("{1}")] Span),
@ -268,6 +305,10 @@ impl ParseError {
ParseError::VariableNotFound(s) => *s, ParseError::VariableNotFound(s) => *s,
ParseError::VariableNotValid(s) => *s, ParseError::VariableNotValid(s) => *s,
ParseError::ModuleNotFound(s) => *s, ParseError::ModuleNotFound(s) => *s,
ParseError::ModuleOrOverlayNotFound(s) => *s,
ParseError::ActiveOverlayNotFound(s) => *s,
ParseError::CantRemoveLastOverlay(s) => *s,
ParseError::CantRemoveDefaultOverlay(_, s) => *s,
ParseError::NotFound(s) => *s, ParseError::NotFound(s) => *s,
ParseError::DuplicateCommandDef(s) => *s, ParseError::DuplicateCommandDef(s) => *s,
ParseError::UnknownCommand(s) => *s, ParseError::UnknownCommand(s) => *s,
@ -297,6 +338,7 @@ impl ParseError {
ParseError::SourcedFileNotFound(_, s) => *s, ParseError::SourcedFileNotFound(_, s) => *s,
ParseError::RegisteredFileNotFound(_, s) => *s, ParseError::RegisteredFileNotFound(_, s) => *s,
ParseError::FileNotFound(_, s) => *s, ParseError::FileNotFound(_, s) => *s,
ParseError::ReadingFile(_, s) => *s,
ParseError::LabeledError(_, _, s) => *s, ParseError::LabeledError(_, _, s) => *s,
} }
} }

View File

@ -40,7 +40,7 @@ impl Command for KnownExternal {
let call_span = call.span(); let call_span = call.span();
let head_span = call.head; let head_span = call.head;
let decl_id = engine_state let decl_id = engine_state
.find_decl("run-external".as_bytes()) .find_decl("run-external".as_bytes(), &[])
.ok_or(ShellError::ExternalNotSupported(head_span))?; .ok_or(ShellError::ExternalNotSupported(head_span))?;
let command = engine_state.get_decl(decl_id); let command = engine_state.get_decl(decl_id);

View File

@ -4,8 +4,8 @@ use nu_protocol::{
Argument, Block, Call, Expr, Expression, ImportPattern, ImportPatternHead, Argument, Block, Call, Expr, Expression, ImportPattern, ImportPatternHead,
ImportPatternMember, Pipeline, ImportPatternMember, Pipeline,
}, },
engine::StateWorkingSet, engine::{StateWorkingSet, DEFAULT_OVERLAY_NAME},
span, Exportable, Overlay, PositionalArg, Span, SyntaxShape, Type, span, Exportable, Module, PositionalArg, Span, SyntaxShape, Type,
}; };
use std::collections::HashSet; use std::collections::HashSet;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@ -1013,7 +1013,7 @@ pub fn parse_module_block(
working_set: &mut StateWorkingSet, working_set: &mut StateWorkingSet,
span: Span, span: Span,
expand_aliases_denylist: &[usize], expand_aliases_denylist: &[usize],
) -> (Block, Overlay, Option<ParseError>) { ) -> (Block, Module, Option<ParseError>) {
let mut error = None; let mut error = None;
working_set.enter_scope(); working_set.enter_scope();
@ -1037,7 +1037,7 @@ pub fn parse_module_block(
} }
} }
let mut overlay = Overlay::from_span(span); let mut module = Module::from_span(span);
let block: Block = output let block: Block = output
.block .block
@ -1093,13 +1093,13 @@ pub fn parse_module_block(
match exportable { match exportable {
Some(Exportable::Decl(decl_id)) => { Some(Exportable::Decl(decl_id)) => {
overlay.add_decl(name, decl_id); module.add_decl(name, decl_id);
} }
Some(Exportable::EnvVar(block_id)) => { Some(Exportable::EnvVar(block_id)) => {
overlay.add_env_var(name, block_id); module.add_env_var(name, block_id);
} }
Some(Exportable::Alias(alias_id)) => { Some(Exportable::Alias(alias_id)) => {
overlay.add_alias(name, alias_id); module.add_alias(name, alias_id);
} }
None => {} // None should always come with error from parse_export() None => {} // None should always come with error from parse_export()
} }
@ -1130,7 +1130,7 @@ pub fn parse_module_block(
working_set.exit_scope(); working_set.exit_scope();
(block, overlay, error) (block, module, error)
} }
pub fn parse_module( pub fn parse_module(
@ -1175,12 +1175,12 @@ pub fn parse_module(
let block_span = Span { start, end }; let block_span = Span { start, end };
let (block, overlay, err) = let (block, module, err) =
parse_module_block(working_set, block_span, expand_aliases_denylist); parse_module_block(working_set, block_span, expand_aliases_denylist);
error = error.or(err); error = error.or(err);
let block_id = working_set.add_block(block); let block_id = working_set.add_block(block);
let _ = working_set.add_overlay(&module_name, overlay); let _ = working_set.add_module(&module_name, module);
let block_expr = Expression { let block_expr = Expression {
expr: Expr::Block(block_id), expr: Expr::Block(block_id),
@ -1286,7 +1286,7 @@ pub fn parse_use(
garbage_pipeline(spans), garbage_pipeline(spans),
Some(ParseError::UnknownState( Some(ParseError::UnknownState(
"internal error: Import pattern positional is not import pattern".into(), "internal error: Import pattern positional is not import pattern".into(),
call_span, expr.span,
)), )),
); );
} }
@ -1306,11 +1306,11 @@ pub fn parse_use(
// TODO: Add checking for importing too long import patterns, e.g.: // TODO: Add checking for importing too long import patterns, e.g.:
// > use spam foo non existent names here do not throw error // > use spam foo non existent names here do not throw error
let (import_pattern, overlay) = let (import_pattern, module) =
if let Some(overlay_id) = working_set.find_overlay(&import_pattern.head.name) { if let Some(module_id) = working_set.find_module(&import_pattern.head.name) {
(import_pattern, working_set.get_overlay(overlay_id).clone()) (import_pattern, working_set.get_module(module_id).clone())
} else { } else {
// TODO: Do not close over when loading module from file // TODO: Do not close over when loading module from file?
// It could be a file // It could be a file
let (module_filename, err) = let (module_filename, err) =
@ -1338,7 +1338,7 @@ pub fn parse_use(
working_set.add_file(module_filename, &contents); working_set.add_file(module_filename, &contents);
let span_end = working_set.next_span_start(); let span_end = working_set.next_span_start();
let (block, overlay, err) = parse_module_block( let (block, module, err) = parse_module_block(
working_set, working_set,
Span::new(span_start, span_end), Span::new(span_start, span_end),
expand_aliases_denylist, expand_aliases_denylist,
@ -1346,19 +1346,19 @@ pub fn parse_use(
error = error.or(err); error = error.or(err);
let _ = working_set.add_block(block); let _ = working_set.add_block(block);
let overlay_id = working_set.add_overlay(&module_name, overlay.clone()); let module_id = working_set.add_module(&module_name, module.clone());
( (
ImportPattern { ImportPattern {
head: ImportPatternHead { head: ImportPatternHead {
name: module_name.into(), name: module_name.into(),
id: Some(overlay_id), id: Some(module_id),
span: spans[1], span: spans[1],
}, },
members: import_pattern.members, members: import_pattern.members,
hidden: HashSet::new(), hidden: HashSet::new(),
}, },
overlay, module,
) )
} else { } else {
return ( return (
@ -1377,7 +1377,7 @@ pub fn parse_use(
let mut import_pattern = ImportPattern::new(); let mut import_pattern = ImportPattern::new();
import_pattern.head.span = spans[1]; import_pattern.head.span = spans[1];
(import_pattern, Overlay::new()) (import_pattern, Module::new())
} }
} else { } else {
return (garbage_pipeline(spans), Some(ParseError::NonUtf8(spans[1]))); return (garbage_pipeline(spans), Some(ParseError::NonUtf8(spans[1])));
@ -1386,21 +1386,21 @@ pub fn parse_use(
let (decls_to_use, aliases_to_use) = if import_pattern.members.is_empty() { let (decls_to_use, aliases_to_use) = if import_pattern.members.is_empty() {
( (
overlay.decls_with_head(&import_pattern.head.name), module.decls_with_head(&import_pattern.head.name),
overlay.aliases_with_head(&import_pattern.head.name), module.aliases_with_head(&import_pattern.head.name),
) )
} else { } else {
match &import_pattern.members[0] { match &import_pattern.members[0] {
ImportPatternMember::Glob { .. } => (overlay.decls(), overlay.aliases()), ImportPatternMember::Glob { .. } => (module.decls(), module.aliases()),
ImportPatternMember::Name { name, span } => { ImportPatternMember::Name { name, span } => {
let mut decl_output = vec![]; let mut decl_output = vec![];
let mut alias_output = vec![]; let mut alias_output = vec![];
if let Some(id) = overlay.get_decl_id(name) { if let Some(id) = module.get_decl_id(name) {
decl_output.push((name.clone(), id)); decl_output.push((name.clone(), id));
} else if let Some(id) = overlay.get_alias_id(name) { } else if let Some(id) = module.get_alias_id(name) {
alias_output.push((name.clone(), id)); alias_output.push((name.clone(), id));
} else if !overlay.has_env_var(name) { } else if !module.has_env_var(name) {
error = error.or(Some(ParseError::ExportNotFound(*span))) error = error.or(Some(ParseError::ExportNotFound(*span)))
} }
@ -1411,11 +1411,11 @@ pub fn parse_use(
let mut alias_output = vec![]; let mut alias_output = vec![];
for (name, span) in names { for (name, span) in names {
if let Some(id) = overlay.get_decl_id(name) { if let Some(id) = module.get_decl_id(name) {
decl_output.push((name.clone(), id)); decl_output.push((name.clone(), id));
} else if let Some(id) = overlay.get_alias_id(name) { } else if let Some(id) = module.get_alias_id(name) {
alias_output.push((name.clone(), id)); alias_output.push((name.clone(), id));
} else if !overlay.has_env_var(name) { } else if !module.has_env_var(name) {
error = error.or(Some(ParseError::ExportNotFound(*span))); error = error.or(Some(ParseError::ExportNotFound(*span)));
break; break;
} }
@ -1426,7 +1426,7 @@ pub fn parse_use(
} }
}; };
// Extend the current scope with the module's overlay // Extend the current scope with the module's exportables
working_set.use_decls(decls_to_use); working_set.use_decls(decls_to_use);
working_set.use_aliases(aliases_to_use); working_set.use_aliases(aliases_to_use);
@ -1542,26 +1542,26 @@ pub fn parse_hide(
error = error.or(err); error = error.or(err);
} }
let (is_module, overlay) = let (is_module, module) =
if let Some(overlay_id) = working_set.find_overlay(&import_pattern.head.name) { if let Some(module_id) = working_set.find_module(&import_pattern.head.name) {
(true, working_set.get_overlay(overlay_id).clone()) (true, working_set.get_module(module_id).clone())
} else if import_pattern.members.is_empty() { } else if import_pattern.members.is_empty() {
// The pattern head can be: // The pattern head can be:
if let Some(id) = working_set.find_alias(&import_pattern.head.name) { if let Some(id) = working_set.find_alias(&import_pattern.head.name) {
// an alias, // an alias,
let mut overlay = Overlay::new(); let mut module = Module::new();
overlay.add_alias(&import_pattern.head.name, id); module.add_alias(&import_pattern.head.name, id);
(false, overlay) (false, module)
} else if let Some(id) = working_set.find_decl(&import_pattern.head.name) { } else if let Some(id) = working_set.find_decl(&import_pattern.head.name) {
// a custom command, // a custom command,
let mut overlay = Overlay::new(); let mut module = Module::new();
overlay.add_decl(&import_pattern.head.name, id); module.add_decl(&import_pattern.head.name, id);
(false, overlay) (false, module)
} else { } else {
// , or it could be an env var (handled by the engine) // , or it could be an env var (handled by the engine)
(false, Overlay::new()) (false, Module::new())
} }
} else { } else {
return ( return (
@ -1574,28 +1574,27 @@ pub fn parse_hide(
let (aliases_to_hide, decls_to_hide) = if import_pattern.members.is_empty() { let (aliases_to_hide, decls_to_hide) = if import_pattern.members.is_empty() {
if is_module { if is_module {
( (
overlay.alias_names_with_head(&import_pattern.head.name), module.alias_names_with_head(&import_pattern.head.name),
overlay.decl_names_with_head(&import_pattern.head.name), module.decl_names_with_head(&import_pattern.head.name),
) )
} else { } else {
(overlay.alias_names(), overlay.decl_names()) (module.alias_names(), module.decl_names())
} }
} else { } else {
match &import_pattern.members[0] { match &import_pattern.members[0] {
ImportPatternMember::Glob { .. } => (overlay.alias_names(), overlay.decl_names()), ImportPatternMember::Glob { .. } => (module.alias_names(), module.decl_names()),
ImportPatternMember::Name { name, span } => { ImportPatternMember::Name { name, span } => {
let mut aliases = vec![]; let mut aliases = vec![];
let mut decls = vec![]; let mut decls = vec![];
if let Some(item) = if let Some(item) = module.alias_name_with_head(name, &import_pattern.head.name)
overlay.alias_name_with_head(name, &import_pattern.head.name)
{ {
aliases.push(item); aliases.push(item);
} else if let Some(item) = } else if let Some(item) =
overlay.decl_name_with_head(name, &import_pattern.head.name) module.decl_name_with_head(name, &import_pattern.head.name)
{ {
decls.push(item); decls.push(item);
} else if !overlay.has_env_var(name) { } else if !module.has_env_var(name) {
error = error.or(Some(ParseError::ExportNotFound(*span))); error = error.or(Some(ParseError::ExportNotFound(*span)));
} }
@ -1607,14 +1606,14 @@ pub fn parse_hide(
for (name, span) in names { for (name, span) in names {
if let Some(item) = if let Some(item) =
overlay.alias_name_with_head(name, &import_pattern.head.name) module.alias_name_with_head(name, &import_pattern.head.name)
{ {
aliases.push(item); aliases.push(item);
} else if let Some(item) = } else if let Some(item) =
overlay.decl_name_with_head(name, &import_pattern.head.name) module.decl_name_with_head(name, &import_pattern.head.name)
{ {
decls.push(item); decls.push(item);
} else if !overlay.has_env_var(name) { } else if !module.has_env_var(name) {
error = error.or(Some(ParseError::ExportNotFound(*span))); error = error.or(Some(ParseError::ExportNotFound(*span)));
break; break;
} }
@ -1673,6 +1672,445 @@ pub fn parse_hide(
} }
} }
pub fn parse_overlay(
working_set: &mut StateWorkingSet,
spans: &[Span],
expand_aliases_denylist: &[usize],
) -> (Pipeline, Option<ParseError>) {
if working_set.get_span_contents(spans[0]) != b"overlay" {
return (
garbage_pipeline(spans),
Some(ParseError::UnknownState(
"internal error: Wrong call name for 'overlay' command".into(),
span(spans),
)),
);
}
if spans.len() > 1 {
let subcommand = working_set.get_span_contents(spans[1]);
match subcommand {
b"add" => {
return parse_overlay_add(working_set, spans, expand_aliases_denylist);
}
b"list" => {
// TODO: Abstract this code blob, it's repeated all over the place:
let call = match working_set.find_decl(b"overlay list") {
Some(decl_id) => {
let (call, mut err) = parse_internal_call(
working_set,
span(&spans[..2]),
if spans.len() > 2 { &spans[2..] } else { &[] },
decl_id,
expand_aliases_denylist,
);
let decl = working_set.get_decl(decl_id);
let call_span = span(spans);
err = check_call(call_span, &decl.signature(), &call).or(err);
if err.is_some() || call.has_flag("help") {
return (
Pipeline::from_vec(vec![Expression {
expr: Expr::Call(call),
span: call_span,
ty: Type::Any,
custom_completion: None,
}]),
err,
);
}
call
}
None => {
return (
garbage_pipeline(spans),
Some(ParseError::UnknownState(
"internal error: 'overlay' declaration not found".into(),
span(spans),
)),
)
}
};
return (
Pipeline::from_vec(vec![Expression {
expr: Expr::Call(call),
span: span(spans),
ty: Type::Any,
custom_completion: None,
}]),
None,
);
}
b"remove" => {
return parse_overlay_remove(working_set, spans, expand_aliases_denylist);
}
_ => { /* continue parsing overlay */ }
}
}
let call = match working_set.find_decl(b"overlay") {
Some(decl_id) => {
let (call, mut err) = parse_internal_call(
working_set,
spans[0],
&spans[1..],
decl_id,
expand_aliases_denylist,
);
let decl = working_set.get_decl(decl_id);
let call_span = span(spans);
err = check_call(call_span, &decl.signature(), &call).or(err);
if err.is_some() || call.has_flag("help") {
return (
Pipeline::from_vec(vec![Expression {
expr: Expr::Call(call),
span: call_span,
ty: Type::Any,
custom_completion: None,
}]),
err,
);
}
call
}
None => {
return (
garbage_pipeline(spans),
Some(ParseError::UnknownState(
"internal error: 'overlay' declaration not found".into(),
span(spans),
)),
)
}
};
(
Pipeline::from_vec(vec![Expression {
expr: Expr::Call(call),
span: span(spans),
ty: Type::Any,
custom_completion: None,
}]),
None,
)
}
pub fn parse_overlay_add(
working_set: &mut StateWorkingSet,
spans: &[Span],
expand_aliases_denylist: &[usize],
) -> (Pipeline, Option<ParseError>) {
if spans.len() > 1 && working_set.get_span_contents(span(&spans[0..2])) != b"overlay add" {
return (
garbage_pipeline(spans),
Some(ParseError::UnknownState(
"internal error: Wrong call name for 'overlay add' command".into(),
span(spans),
)),
);
}
// TODO: Allow full import pattern as argument (requires custom naming of module/overlay)
let (call, call_span) = match working_set.find_decl(b"overlay add") {
Some(decl_id) => {
let (call, mut err) = parse_internal_call(
working_set,
span(&spans[0..2]),
&spans[2..],
decl_id,
expand_aliases_denylist,
);
let decl = working_set.get_decl(decl_id);
let call_span = span(spans);
err = check_call(call_span, &decl.signature(), &call).or(err);
if err.is_some() || call.has_flag("help") {
return (
Pipeline::from_vec(vec![Expression {
expr: Expr::Call(call),
span: call_span,
ty: Type::Any,
custom_completion: None,
}]),
err,
);
}
(call, call_span)
}
None => {
return (
garbage_pipeline(spans),
Some(ParseError::UnknownState(
"internal error: 'overlay add' declaration not found".into(),
span(spans),
)),
)
}
};
let (overlay_name, overlay_name_span) = if let Some(expr) = call.positional_nth(0) {
if let Some(s) = expr.as_string() {
(s, expr.span)
} else {
return (
garbage_pipeline(spans),
Some(ParseError::UnknownState(
"internal error: Module name not a string".into(),
expr.span,
)),
);
}
} else {
return (
garbage_pipeline(spans),
Some(ParseError::UnknownState(
"internal error: Missing required positional after call parsing".into(),
call_span,
)),
);
};
let pipeline = Pipeline::from_vec(vec![Expression {
expr: Expr::Call(call),
span: span(spans),
ty: Type::Any,
custom_completion: None,
}]);
// TODO: Add support for it -- needs to play well with overlay remove
let has_prefix = false; //call.has_flag("prefix");
let cwd = working_set.get_cwd();
let mut error = None;
let result = if let Some(module_id) = working_set.find_overlay_origin(overlay_name.as_bytes()) {
// Activate existing overlay
if let Some(new_module_id) = working_set.find_module(overlay_name.as_bytes()) {
if module_id == new_module_id {
Some((overlay_name, Module::new(), module_id))
} else {
// The origin module of an overlay changed => update it
Some((
overlay_name,
working_set.get_module(new_module_id).clone(),
new_module_id,
))
}
} else {
Some((overlay_name, Module::new(), module_id))
}
} else {
// Create a new overlay from a module
if let Some(module_id) =
// the name is a module
working_set.find_module(overlay_name.as_bytes())
{
Some((
overlay_name,
working_set.get_module(module_id).clone(),
module_id,
))
} else {
// try if the name is a file
if let Ok(module_filename) =
String::from_utf8(trim_quotes(overlay_name.as_bytes()).to_vec())
{
if let Some(module_path) =
find_in_dirs(&module_filename, working_set, &cwd, LIB_DIRS_ENV)
{
let overlay_name = if let Some(stem) = module_path.file_stem() {
stem.to_string_lossy().to_string()
} else {
return (
pipeline,
Some(ParseError::ModuleOrOverlayNotFound(spans[1])),
);
};
if let Ok(contents) = std::fs::read(module_path) {
let span_start = working_set.next_span_start();
working_set.add_file(module_filename, &contents);
let span_end = working_set.next_span_start();
let (block, module, err) = parse_module_block(
working_set,
Span::new(span_start, span_end),
expand_aliases_denylist,
);
error = error.or(err);
let _ = working_set.add_block(block);
let module_id = working_set.add_module(&overlay_name, module.clone());
Some((overlay_name, module, module_id))
} else {
return (
pipeline,
Some(ParseError::ModuleOrOverlayNotFound(spans[1])),
);
}
} else {
error = error.or(Some(ParseError::ModuleOrOverlayNotFound(overlay_name_span)));
None
}
} else {
return (garbage_pipeline(spans), Some(ParseError::NonUtf8(spans[1])));
}
}
};
if let Some((name, module, module_id)) = result {
let (decls_to_lay, aliases_to_lay) = if has_prefix {
(
module.decls_with_head(name.as_bytes()),
module.aliases_with_head(name.as_bytes()),
)
} else {
(module.decls(), module.aliases())
};
working_set.add_overlay(
name.as_bytes().to_vec(),
module_id,
decls_to_lay,
aliases_to_lay,
);
}
(pipeline, error)
}
pub fn parse_overlay_remove(
working_set: &mut StateWorkingSet,
spans: &[Span],
expand_aliases_denylist: &[usize],
) -> (Pipeline, Option<ParseError>) {
if spans.len() > 1 && working_set.get_span_contents(span(&spans[0..2])) != b"overlay remove" {
return (
garbage_pipeline(spans),
Some(ParseError::UnknownState(
"internal error: Wrong call name for 'overlay remove' command".into(),
span(spans),
)),
);
}
let call = match working_set.find_decl(b"overlay remove") {
Some(decl_id) => {
let (call, mut err) = parse_internal_call(
working_set,
span(&spans[0..2]),
&spans[2..],
decl_id,
expand_aliases_denylist,
);
let decl = working_set.get_decl(decl_id);
let call_span = span(spans);
err = check_call(call_span, &decl.signature(), &call).or(err);
if err.is_some() || call.has_flag("help") {
return (
Pipeline::from_vec(vec![Expression {
expr: Expr::Call(call),
span: call_span,
ty: Type::Any,
custom_completion: None,
}]),
err,
);
}
call
}
None => {
return (
garbage_pipeline(spans),
Some(ParseError::UnknownState(
"internal error: 'overlay remove' declaration not found".into(),
span(spans),
)),
)
}
};
let (overlay_name, overlay_name_span) = if let Some(expr) = call.positional_nth(0) {
if let Some(s) = expr.as_string() {
(s, expr.span)
} else {
return (
garbage_pipeline(spans),
Some(ParseError::UnknownState(
"internal error: Module name not a string".into(),
expr.span,
)),
);
}
} else {
(
String::from_utf8_lossy(working_set.last_overlay_name()).to_string(),
call.head,
)
};
let pipeline = Pipeline::from_vec(vec![Expression {
expr: Expr::Call(call),
span: span(spans),
ty: Type::Any,
custom_completion: None,
}]);
if overlay_name == DEFAULT_OVERLAY_NAME {
return (
pipeline,
Some(ParseError::CantRemoveDefaultOverlay(
overlay_name,
overlay_name_span,
)),
);
}
if !working_set
.unique_overlay_names()
.contains(&overlay_name.as_bytes().to_vec())
{
return (
pipeline,
Some(ParseError::ActiveOverlayNotFound(overlay_name_span)),
);
}
if working_set.num_overlays() < 2 {
return (
pipeline,
Some(ParseError::CantRemoveLastOverlay(overlay_name_span)),
);
}
// let original_module = if call.has_flag("discard") {
// None
// } else if let Some(module_id) = working_set.find_module(overlay_name.as_bytes()) {
// // TODO: Remove clone
// Some(working_set.get_module(module_id).clone())
// } else {
// Some(Module::new())
// };
working_set.remove_overlay(overlay_name.as_bytes());
(pipeline, None)
}
pub fn parse_let( pub fn parse_let(
working_set: &mut StateWorkingSet, working_set: &mut StateWorkingSet,
spans: &[Span], spans: &[Span],
@ -2108,7 +2546,7 @@ pub fn parse_register(
) )
} }
fn find_in_dirs( pub fn find_in_dirs(
filename: &str, filename: &str,
working_set: &StateWorkingSet, working_set: &StateWorkingSet,
cwd: &str, cwd: &str,
@ -2120,7 +2558,7 @@ fn find_in_dirs(
let path = Path::new(filename); let path = Path::new(filename);
if path.is_relative() { if path.is_relative() {
if let Some(lib_dirs) = working_set.get_env(dirs_env) { if let Some(lib_dirs) = working_set.get_env_var(dirs_env) {
if let Ok(dirs) = lib_dirs.as_list() { if let Ok(dirs) = lib_dirs.as_list() {
for lib_dir in dirs { for lib_dir in dirs {
if let Ok(dir) = lib_dir.as_path() { if let Ok(dir) = lib_dir.as_path() {

View File

@ -18,7 +18,8 @@ use nu_protocol::{
}; };
use crate::parse_keywords::{ use crate::parse_keywords::{
parse_alias, parse_def, parse_def_predecl, parse_hide, parse_let, parse_module, parse_use, parse_alias, parse_def, parse_def_predecl, parse_hide, parse_let, parse_module, parse_overlay,
parse_use,
}; };
use log::trace; use log::trace;
@ -2700,7 +2701,7 @@ pub fn parse_import_pattern(
); );
}; };
let maybe_overlay_id = working_set.find_overlay(&head); let maybe_module_id = working_set.find_module(&head);
let (import_pattern, err) = if let Some(tail_span) = spans.get(1) { let (import_pattern, err) = if let Some(tail_span) = spans.get(1) {
// FIXME: expand this to handle deeper imports once we support module imports // FIXME: expand this to handle deeper imports once we support module imports
@ -2710,7 +2711,7 @@ pub fn parse_import_pattern(
ImportPattern { ImportPattern {
head: ImportPatternHead { head: ImportPatternHead {
name: head, name: head,
id: maybe_overlay_id, id: maybe_module_id,
span: *head_span, span: *head_span,
}, },
members: vec![ImportPatternMember::Glob { span: *tail_span }], members: vec![ImportPatternMember::Glob { span: *tail_span }],
@ -2743,7 +2744,7 @@ pub fn parse_import_pattern(
ImportPattern { ImportPattern {
head: ImportPatternHead { head: ImportPatternHead {
name: head, name: head,
id: maybe_overlay_id, id: maybe_module_id,
span: *head_span, span: *head_span,
}, },
members: vec![ImportPatternMember::List { names: output }], members: vec![ImportPatternMember::List { names: output }],
@ -2756,7 +2757,7 @@ pub fn parse_import_pattern(
ImportPattern { ImportPattern {
head: ImportPatternHead { head: ImportPatternHead {
name: head, name: head,
id: maybe_overlay_id, id: maybe_module_id,
span: *head_span, span: *head_span,
}, },
members: vec![], members: vec![],
@ -2771,7 +2772,7 @@ pub fn parse_import_pattern(
ImportPattern { ImportPattern {
head: ImportPatternHead { head: ImportPatternHead {
name: head, name: head,
id: maybe_overlay_id, id: maybe_module_id,
span: *head_span, span: *head_span,
}, },
members: vec![ImportPatternMember::Name { members: vec![ImportPatternMember::Name {
@ -2788,7 +2789,7 @@ pub fn parse_import_pattern(
ImportPattern { ImportPattern {
head: ImportPatternHead { head: ImportPatternHead {
name: head, name: head,
id: maybe_overlay_id, id: maybe_module_id,
span: *head_span, span: *head_span,
}, },
members: vec![], members: vec![],
@ -4404,6 +4405,31 @@ pub fn parse_expression(
.0, .0,
Some(ParseError::BuiltinCommandInPipeline("use".into(), spans[0])), Some(ParseError::BuiltinCommandInPipeline("use".into(), spans[0])),
), ),
b"overlay" => {
if spans.len() > 1 && working_set.get_span_contents(spans[1]) == b"list" {
// whitelist 'overlay list'
parse_call(
working_set,
&spans[pos..],
spans[0],
expand_aliases_denylist,
)
} else {
(
parse_call(
working_set,
&spans[pos..],
spans[0],
expand_aliases_denylist,
)
.0,
Some(ParseError::BuiltinCommandInPipeline(
"overlay".into(),
spans[0],
)),
)
}
}
b"source" => ( b"source" => (
parse_call( parse_call(
working_set, working_set,
@ -4558,6 +4584,7 @@ pub fn parse_builtin_commands(
b"alias" => parse_alias(working_set, &lite_command.parts, expand_aliases_denylist), b"alias" => parse_alias(working_set, &lite_command.parts, expand_aliases_denylist),
b"module" => parse_module(working_set, &lite_command.parts, expand_aliases_denylist), b"module" => parse_module(working_set, &lite_command.parts, expand_aliases_denylist),
b"use" => parse_use(working_set, &lite_command.parts, expand_aliases_denylist), b"use" => parse_use(working_set, &lite_command.parts, expand_aliases_denylist),
b"overlay" => parse_overlay(working_set, &lite_command.parts, expand_aliases_denylist),
b"source" => parse_source(working_set, &lite_command.parts, expand_aliases_denylist), b"source" => parse_source(working_set, &lite_command.parts, expand_aliases_denylist),
b"export" => { b"export" => {
if let Some(decl_id) = working_set.find_decl(b"alias") { if let Some(decl_id) = working_set.find_decl(b"alias") {

View File

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{span, OverlayId, Span}; use crate::{span, ModuleId, Span};
use std::collections::HashSet; use std::collections::HashSet;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
@ -13,7 +13,7 @@ pub enum ImportPatternMember {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ImportPatternHead { pub struct ImportPatternHead {
pub name: Vec<u8>, pub name: Vec<u8>,
pub id: Option<OverlayId>, pub id: Option<ModuleId>,
pub span: Span, pub span: Span,
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,11 @@
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use crate::engine::EngineState; use crate::engine::{EngineState, DEFAULT_OVERLAY_NAME};
use crate::{ShellError, Span, Value, VarId}; use crate::{ShellError, Span, Value, VarId};
/// Environment variables per overlay
pub type EnvVars = HashMap<String, HashMap<String, Value>>;
/// A runtime value stack used during evaluation /// A runtime value stack used during evaluation
/// ///
/// A note on implementation: /// A note on implementation:
@ -25,16 +28,11 @@ pub struct Stack {
/// Variables /// Variables
pub vars: HashMap<VarId, Value>, pub vars: HashMap<VarId, Value>,
/// Environment variables arranged as a stack to be able to recover values from parent scopes /// Environment variables arranged as a stack to be able to recover values from parent scopes
pub env_vars: Vec<HashMap<String, Value>>, pub env_vars: Vec<EnvVars>,
/// Tells which environment variables from engine state are hidden. We don't need to track the /// Tells which environment variables from engine state are hidden, per overlay.
/// env vars in the stack since we can just delete them. pub env_hidden: HashMap<String, HashSet<String>>,
pub env_hidden: HashSet<String>, /// List of active overlays
} pub active_overlays: Vec<String>,
impl Default for Stack {
fn default() -> Self {
Self::new()
}
} }
impl Stack { impl Stack {
@ -42,18 +40,23 @@ impl Stack {
Stack { Stack {
vars: HashMap::new(), vars: HashMap::new(),
env_vars: vec![], env_vars: vec![],
env_hidden: HashSet::new(), env_hidden: HashMap::new(),
active_overlays: vec![DEFAULT_OVERLAY_NAME.to_string()],
} }
} }
pub fn with_env(&mut self, env_vars: &[HashMap<String, Value>], env_hidden: &HashSet<String>) { pub fn with_env(
&mut self,
env_vars: &[EnvVars],
env_hidden: &HashMap<String, HashSet<String>>,
) {
// Do not clone the environment if it hasn't changed // Do not clone the environment if it hasn't changed
if self.env_vars.iter().any(|scope| !scope.is_empty()) { if self.env_vars.iter().any(|scope| !scope.is_empty()) {
self.env_vars = env_vars.to_owned(); self.env_vars = env_vars.to_owned();
} }
if !self.env_hidden.is_empty() { if !self.env_hidden.is_empty() {
self.env_hidden = env_hidden.clone(); self.env_hidden = env_hidden.to_owned();
} }
} }
@ -78,30 +81,52 @@ impl Stack {
} }
pub fn add_env_var(&mut self, var: String, value: Value) { pub fn add_env_var(&mut self, var: String, value: Value) {
if let Some(last_overlay) = self.active_overlays.last() {
if let Some(env_hidden) = self.env_hidden.get_mut(last_overlay) {
// if the env var was hidden, let's activate it again // if the env var was hidden, let's activate it again
self.env_hidden.remove(&var); env_hidden.remove(&var);
}
if let Some(scope) = self.env_vars.last_mut() { if let Some(scope) = self.env_vars.last_mut() {
scope.insert(var, value); if let Some(env_vars) = scope.get_mut(last_overlay) {
env_vars.insert(var, value);
} else { } else {
self.env_vars.push(HashMap::from([(var, value)])); scope.insert(last_overlay.into(), HashMap::from([(var, value)]));
} }
} else {
self.env_vars.push(HashMap::from([(
last_overlay.into(),
HashMap::from([(var, value)]),
)]));
}
} else {
// TODO: Remove panic
panic!("internal error: no active overlay");
}
}
pub fn last_overlay_name(&self) -> Result<String, ShellError> {
self.active_overlays
.last()
.cloned()
.ok_or_else(|| ShellError::NushellFailed("No active overlay".into()))
} }
pub fn captures_to_stack(&self, captures: &HashMap<VarId, Value>) -> Stack { pub fn captures_to_stack(&self, captures: &HashMap<VarId, Value>) -> Stack {
let mut output = Stack::new();
output.vars = captures.clone();
// FIXME: this is probably slow // FIXME: this is probably slow
output.env_vars = self.env_vars.clone(); let mut env_vars = self.env_vars.clone();
output.env_vars.push(HashMap::new()); env_vars.push(HashMap::new());
output Stack {
vars: captures.clone(),
env_vars,
env_hidden: HashMap::new(),
active_overlays: self.active_overlays.clone(),
}
} }
pub fn gather_captures(&self, captures: &[VarId]) -> Stack { pub fn gather_captures(&self, captures: &[VarId]) -> Stack {
let mut output = Stack::new(); let mut vars = HashMap::new();
let fake_span = Span::new(0, 0); let fake_span = Span::new(0, 0);
@ -109,30 +134,59 @@ impl Stack {
// Note: this assumes we have calculated captures correctly and that commands // Note: this assumes we have calculated captures correctly and that commands
// that take in a var decl will manually set this into scope when running the blocks // that take in a var decl will manually set this into scope when running the blocks
if let Ok(value) = self.get_var(*capture, fake_span) { if let Ok(value) = self.get_var(*capture, fake_span) {
output.vars.insert(*capture, value); vars.insert(*capture, value);
} }
} }
// FIXME: this is probably slow let mut env_vars = self.env_vars.clone();
output.env_vars = self.env_vars.clone(); env_vars.push(HashMap::new());
output.env_vars.push(HashMap::new());
output Stack {
vars,
env_vars,
env_hidden: HashMap::new(),
active_overlays: self.active_overlays.clone(),
}
} }
/// Flatten the env var scope frames into one frame /// Flatten the env var scope frames into one frame
pub fn get_env_vars(&self, engine_state: &EngineState) -> HashMap<String, Value> { pub fn get_env_vars(&self, engine_state: &EngineState) -> HashMap<String, Value> {
// TODO: We're collecting im::HashMap to HashMap here. It might make sense to make these let mut result = HashMap::new();
// the same data structure.
let mut result: HashMap<String, Value> = engine_state for active_overlay in self.active_overlays.iter() {
.env_vars if let Some(env_vars) = engine_state.env_vars.get(active_overlay) {
result.extend(
env_vars
.iter() .iter()
.filter(|(k, _)| !self.env_hidden.contains(*k)) .filter(|(k, _)| {
if let Some(env_hidden) = self.env_hidden.get(active_overlay) {
!env_hidden.contains(*k)
} else {
// nothing has been hidden in this overlay
true
}
})
.map(|(k, v)| (k.clone(), v.clone())) .map(|(k, v)| (k.clone(), v.clone()))
.collect(); .collect::<HashMap<String, Value>>(),
);
}
}
result.extend(self.get_stack_env_vars());
result
}
/// Get flattened environment variables only from the stack
pub fn get_stack_env_vars(&self) -> HashMap<String, Value> {
let mut result = HashMap::new();
for scope in &self.env_vars { for scope in &self.env_vars {
result.extend(scope.clone()); for active_overlay in self.active_overlays.iter() {
if let Some(env_vars) = scope.get(active_overlay) {
result.extend(env_vars.clone());
}
}
} }
result result
@ -140,16 +194,33 @@ impl Stack {
/// Same as get_env_vars, but returns only the names as a HashSet /// Same as get_env_vars, but returns only the names as a HashSet
pub fn get_env_var_names(&self, engine_state: &EngineState) -> HashSet<String> { pub fn get_env_var_names(&self, engine_state: &EngineState) -> HashSet<String> {
let mut result: HashSet<String> = engine_state let mut result = HashSet::new();
.env_vars
for active_overlay in self.active_overlays.iter() {
if let Some(env_vars) = engine_state.env_vars.get(active_overlay) {
result.extend(
env_vars
.keys() .keys()
.filter(|k| !self.env_hidden.contains(*k)) .filter(|k| {
if let Some(env_hidden) = self.env_hidden.get(active_overlay) {
!env_hidden.contains(*k)
} else {
// nothing has been hidden in this overlay
true
}
})
.cloned() .cloned()
.collect(); .collect::<HashSet<String>>(),
);
}
}
for scope in &self.env_vars { for scope in &self.env_vars {
let scope_keys: HashSet<String> = scope.keys().cloned().collect(); for active_overlay in self.active_overlays.iter() {
result.extend(scope_keys); if let Some(env_vars) = scope.get(active_overlay) {
result.extend(env_vars.keys().cloned().collect::<HashSet<String>>());
}
}
} }
result result
@ -157,83 +228,123 @@ impl Stack {
pub fn get_env_var(&self, engine_state: &EngineState, name: &str) -> Option<Value> { pub fn get_env_var(&self, engine_state: &EngineState, name: &str) -> Option<Value> {
for scope in self.env_vars.iter().rev() { for scope in self.env_vars.iter().rev() {
if let Some(v) = scope.get(name) { for active_overlay in self.active_overlays.iter().rev() {
if let Some(env_vars) = scope.get(active_overlay) {
if let Some(v) = env_vars.get(name) {
return Some(v.clone()); return Some(v.clone());
} }
} }
if self.env_hidden.contains(name) {
None
} else {
engine_state.env_vars.get(name).cloned()
} }
} }
for active_overlay in self.active_overlays.iter().rev() {
let is_hidden = if let Some(env_hidden) = self.env_hidden.get(active_overlay) {
env_hidden.contains(name)
} else {
false
};
if !is_hidden {
if let Some(env_vars) = engine_state.env_vars.get(active_overlay) {
if let Some(v) = env_vars.get(name) {
return Some(v.clone());
}
}
}
}
None
}
pub fn has_env_var(&self, engine_state: &EngineState, name: &str) -> bool { pub fn has_env_var(&self, engine_state: &EngineState, name: &str) -> bool {
for scope in self.env_vars.iter().rev() {
for active_overlay in self.active_overlays.iter().rev() {
if let Some(env_vars) = scope.get(active_overlay) {
if env_vars.contains_key(name) {
return true;
}
}
}
}
for active_overlay in self.active_overlays.iter().rev() {
let is_hidden = if let Some(env_hidden) = self.env_hidden.get(active_overlay) {
env_hidden.contains(name)
} else {
false
};
if !is_hidden {
if let Some(env_vars) = engine_state.env_vars.get(active_overlay) {
if env_vars.contains_key(name) {
return true;
}
}
}
}
false
}
pub fn remove_env_var(&mut self, engine_state: &EngineState, name: &str) -> Option<Value> {
for scope in self.env_vars.iter_mut().rev() {
for active_overlay in self.active_overlays.iter().rev() {
if let Some(env_vars) = scope.get_mut(active_overlay) {
if let Some(v) = env_vars.remove(name) {
return Some(v);
}
}
}
}
for active_overlay in self.active_overlays.iter().rev() {
if let Some(env_vars) = engine_state.env_vars.get(active_overlay) {
if let Some(val) = env_vars.get(name) {
if let Some(env_hidden) = self.env_hidden.get_mut(active_overlay) {
env_hidden.insert(name.into());
} else {
self.env_hidden
.insert(active_overlay.into(), HashSet::from([name.into()]));
}
return Some(val.clone());
}
}
}
None
}
pub fn has_env_overlay(&self, name: &str, engine_state: &EngineState) -> bool {
for scope in self.env_vars.iter().rev() { for scope in self.env_vars.iter().rev() {
if scope.contains_key(name) { if scope.contains_key(name) {
return true; return true;
} }
} }
if self.env_hidden.contains(name) {
false
} else {
engine_state.env_vars.contains_key(name) engine_state.env_vars.contains_key(name)
} }
pub fn add_overlay(&mut self, name: String) {
self.env_hidden.remove(&name);
self.active_overlays.retain(|o| o != &name);
self.active_overlays.push(name);
} }
pub fn remove_env_var(&mut self, engine_state: &EngineState, name: &str) -> Option<Value> { pub fn remove_overlay(&mut self, name: &String, span: &Span) -> Result<(), ShellError> {
for scope in self.env_vars.iter_mut().rev() { if !self.active_overlays.contains(name) {
if let Some(v) = scope.remove(name) { return Err(ShellError::OverlayNotFoundAtRuntime(name.into(), *span));
return Some(v); }
self.active_overlays.retain(|o| o != name);
Ok(())
} }
} }
if self.env_hidden.contains(name) { impl Default for Stack {
// the environment variable is already hidden fn default() -> Self {
None Self::new()
} else if let Some(val) = engine_state.env_vars.get(name) {
// the environment variable was found in the engine state => mark it as hidden
self.env_hidden.insert(name.to_string());
Some(val.clone())
} else {
None
}
}
// pub fn get_config(&self) -> Result<Config, ShellError> {
// let config = self.get_var(CONFIG_VARIABLE_ID, Span::new(0, 0));
// match config {
// Ok(config) => config.into_config(),
// Err(e) => Err(e),
// }
// }
// pub fn update_config(&mut self, name: &str, value: Value) {
// if let Some(Value::Record { cols, vals, .. }) = self.vars.get_mut(&CONFIG_VARIABLE_ID) {
// for col_val in cols.iter().zip(vals.iter_mut()) {
// if col_val.0 == name {
// *col_val.1 = value;
// return;
// }
// }
// cols.push(name.to_string());
// vals.push(value);
// }
// }
pub fn print_stack(&self) {
println!("vars:");
for (var, val) in &self.vars {
println!(" {}: {:?}", var, val);
}
for (i, scope) in self.env_vars.iter().rev().enumerate() {
println!("env vars, scope {} (from the last);", i);
for (var, val) in scope {
println!(" {}: {:?}", var, val.clone().debug_value());
}
}
} }
} }

View File

@ -2,4 +2,5 @@ pub type VarId = usize;
pub type DeclId = usize; pub type DeclId = usize;
pub type AliasId = usize; pub type AliasId = usize;
pub type BlockId = usize; pub type BlockId = usize;
pub type ModuleId = usize;
pub type OverlayId = usize; pub type OverlayId = usize;

View File

@ -5,7 +5,7 @@ pub mod engine;
mod example; mod example;
mod exportable; mod exportable;
mod id; mod id;
mod overlay; mod module;
mod pipeline_data; mod pipeline_data;
mod shell_error; mod shell_error;
mod signature; mod signature;
@ -21,7 +21,7 @@ pub use engine::{ENV_VARIABLE_ID, IN_VARIABLE_ID, NU_VARIABLE_ID};
pub use example::*; pub use example::*;
pub use exportable::*; pub use exportable::*;
pub use id::*; pub use id::*;
pub use overlay::*; pub use module::*;
pub use pipeline_data::*; pub use pipeline_data::*;
pub use shell_error::*; pub use shell_error::*;
pub use signature::*; pub use signature::*;

View File

@ -7,16 +7,16 @@ use indexmap::IndexMap;
/// Collection of definitions that can be exported from a module /// Collection of definitions that can be exported from a module
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Overlay { pub struct Module {
pub decls: IndexMap<Vec<u8>, DeclId>, pub decls: IndexMap<Vec<u8>, DeclId>,
pub aliases: IndexMap<Vec<u8>, AliasId>, pub aliases: IndexMap<Vec<u8>, AliasId>,
pub env_vars: IndexMap<Vec<u8>, BlockId>, pub env_vars: IndexMap<Vec<u8>, BlockId>,
pub span: Option<Span>, pub span: Option<Span>,
} }
impl Overlay { impl Module {
pub fn new() -> Self { pub fn new() -> Self {
Overlay { Module {
decls: IndexMap::new(), decls: IndexMap::new(),
aliases: IndexMap::new(), aliases: IndexMap::new(),
env_vars: IndexMap::new(), env_vars: IndexMap::new(),
@ -25,7 +25,7 @@ impl Overlay {
} }
pub fn from_span(span: Span) -> Self { pub fn from_span(span: Span) -> Self {
Overlay { Module {
decls: IndexMap::new(), decls: IndexMap::new(),
aliases: IndexMap::new(), aliases: IndexMap::new(),
env_vars: IndexMap::new(), env_vars: IndexMap::new(),
@ -45,7 +45,7 @@ impl Overlay {
self.env_vars.insert(name.to_vec(), block_id) self.env_vars.insert(name.to_vec(), block_id)
} }
pub fn extend(&mut self, other: &Overlay) { pub fn extend(&mut self, other: &Module) {
self.decls.extend(other.decls.clone()); self.decls.extend(other.decls.clone());
self.env_vars.extend(other.env_vars.clone()); self.env_vars.extend(other.env_vars.clone());
} }
@ -201,7 +201,7 @@ impl Overlay {
} }
} }
impl Default for Overlay { impl Default for Module {
fn default() -> Self { fn default() -> Self {
Self::new() Self::new()
} }

View File

@ -448,7 +448,7 @@ impl PipelineData {
return Ok(()); return Ok(());
} }
match engine_state.find_decl("table".as_bytes()) { match engine_state.find_decl("table".as_bytes(), &[]) {
Some(decl_id) => { Some(decl_id) => {
let table = engine_state.get_decl(decl_id).run( let table = engine_state.get_decl(decl_id).run(
engine_state, engine_state,

View File

@ -168,7 +168,7 @@ pub enum ShellError {
/// It is very likely that this is a bug. Please file an issue at https://github.com/nushell/nushell/issues with relevant information. /// It is very likely that this is a bug. Please file an issue at https://github.com/nushell/nushell/issues with relevant information.
#[error("Nushell failed: {0}.")] #[error("Nushell failed: {0}.")]
#[diagnostic(code(nu::shell::nushell_failed), url(docsrs))] #[diagnostic(code(nu::shell::nushell_failed), url(docsrs))]
// Only use this one if we Nushell completely falls over and hits a state that isn't possible or isn't recoverable // Only use this one if Nushell completely falls over and hits a state that isn't possible or isn't recoverable
NushellFailed(String), NushellFailed(String),
/// Catastrophic nushell failure. This reflects a completely unexpected or unrecoverable error. /// Catastrophic nushell failure. This reflects a completely unexpected or unrecoverable error.
@ -177,10 +177,30 @@ pub enum ShellError {
/// ///
/// It is very likely that this is a bug. Please file an issue at https://github.com/nushell/nushell/issues with relevant information. /// It is very likely that this is a bug. Please file an issue at https://github.com/nushell/nushell/issues with relevant information.
#[error("Nushell failed: {0}.")] #[error("Nushell failed: {0}.")]
#[diagnostic(code(nu::shell::nushell_failed), url(docsrs))] #[diagnostic(code(nu::shell::nushell_failed_spanned), url(docsrs))]
// Only use this one if we Nushell completely falls over and hits a state that isn't possible or isn't recoverable // Only use this one if Nushell completely falls over and hits a state that isn't possible or isn't recoverable
NushellFailedSpanned(String, String, #[label = "{1}"] Span), NushellFailedSpanned(String, String, #[label = "{1}"] Span),
/// Catastrophic nushell failure. This reflects a completely unexpected or unrecoverable error.
///
/// ## Resolution
///
/// It is very likely that this is a bug. Please file an issue at https://github.com/nushell/nushell/issues with relevant information.
#[error("Nushell failed: {0}.")]
#[diagnostic(code(nu::shell::nushell_failed_help), url(docsrs))]
// Only use this one if Nushell completely falls over and hits a state that isn't possible or isn't recoverable
NushellFailedHelp(String, #[help] String),
/// Catastrophic nushell failure. This reflects a completely unexpected or unrecoverable error.
///
/// ## Resolution
///
/// It is very likely that this is a bug. Please file an issue at https://github.com/nushell/nushell/issues with relevant information.
#[error("Nushell failed: {0}.")]
#[diagnostic(code(nu::shell::nushell_failed_spanned_help), url(docsrs))]
// Only use this one if Nushell completely falls over and hits a state that isn't possible or isn't recoverable
NushellFailedSpannedHelp(String, String, #[label = "{1}"] Span, #[help] String),
/// A referenced variable was not found at runtime. /// A referenced variable was not found at runtime.
/// ///
/// ## Resolution /// ## Resolution
@ -199,6 +219,33 @@ pub enum ShellError {
#[diagnostic(code(nu::shell::env_variable_not_found), url(docsrs))] #[diagnostic(code(nu::shell::env_variable_not_found), url(docsrs))]
EnvVarNotFoundAtRuntime(String, #[label = "environment variable not found"] Span), EnvVarNotFoundAtRuntime(String, #[label = "environment variable not found"] Span),
/// A referenced module was not found at runtime.
///
/// ## Resolution
///
/// Check the module name. Did you typo it? Did you forget to declare it? Is the casing right?
#[error("Module '{0}' not found")]
#[diagnostic(code(nu::shell::module_not_found), url(docsrs))]
ModuleNotFoundAtRuntime(String, #[label = "module not found"] Span),
/// A referenced module or overlay was not found at runtime.
///
/// ## Resolution
///
/// Check the module name. Did you typo it? Did you forget to declare it? Is the casing right?
#[error("Module or overlay'{0}' not found")]
#[diagnostic(code(nu::shell::module_not_found), url(docsrs))]
ModuleOrOverlayNotFoundAtRuntime(String, #[label = "not a module or overlay"] Span),
/// A referenced overlay was not found at runtime.
///
/// ## Resolution
///
/// Check the overlay name. Did you typo it? Did you forget to declare it? Is the casing right?
#[error("Overlay '{0}' not found")]
#[diagnostic(code(nu::shell::overlay_not_found), url(docsrs))]
OverlayNotFoundAtRuntime(String, #[label = "overlay not found"] Span),
/// The given item was not found. This is a fairly generic error that depends on context. /// The given item was not found. This is a fairly generic error that depends on context.
/// ///
/// ## Resolution /// ## Resolution

View File

@ -1,5 +1,6 @@
extern crate nu_test_support; extern crate nu_test_support;
mod overlays;
mod parsing; mod parsing;
mod path; mod path;
mod plugins; mod plugins;

310
tests/overlays/mod.rs Normal file
View File

@ -0,0 +1,310 @@
use nu_test_support::{nu, pipeline};
#[test]
fn add_overlay() {
let actual = nu!(
cwd: "tests/overlays", pipeline(
r#"
module spam { export def foo [] { "foo" } };
overlay add spam;
foo
"#
));
assert_eq!(actual.out, "foo");
}
#[test]
fn add_overlay_env() {
let actual = nu!(
cwd: "tests/overlays", pipeline(
r#"
module spam { export env FOO { "foo" } };
overlay add spam;
$env.FOO
"#
));
assert_eq!(actual.out, "foo");
}
#[test]
fn add_overlay_from_file_decl() {
let actual = nu!(
cwd: "tests/overlays", pipeline(
r#"
overlay add samples/spam.nu;
foo
"#
));
assert_eq!(actual.out, "foo");
}
#[test]
fn add_overlay_from_file_alias() {
let actual = nu!(
cwd: "tests/overlays", pipeline(
r#"
overlay add samples/spam.nu;
bar
"#
));
assert_eq!(actual.out, "bar");
}
#[test]
fn add_overlay_from_file_env() {
let actual = nu!(
cwd: "tests/overlays", pipeline(
r#"
overlay add samples/spam.nu;
$env.BAZ
"#
));
assert_eq!(actual.out, "baz");
}
#[test]
fn add_overlay_scoped() {
let actual = nu!(
cwd: "tests/overlays", pipeline(
r#"
module spam { export def foo [] { "foo" } };
do { overlay add spam };
foo
"#
));
assert!(!actual.err.is_empty())
}
#[test]
fn update_overlay_from_module() {
let actual = nu!(
cwd: "tests/overlays", pipeline(
r#"
module spam { export def foo [] { "foo" } };
overlay add spam;
module spam { export def foo [] { "bar" } };
overlay add spam;
foo
"#
));
assert_eq!(actual.out, "bar");
}
#[test]
fn update_overlay_from_module_env() {
let actual = nu!(
cwd: "tests/overlays", pipeline(
r#"
module spam { export env FOO { "foo" } };
overlay add spam;
module spam { export env FOO { "bar" } };
overlay add spam;
$env.FOO
"#
));
assert_eq!(actual.out, "bar");
}
#[test]
fn remove_overlay() {
let actual = nu!(
cwd: "tests/overlays", pipeline(
r#"
module spam { export def foo [] { "foo" } };
overlay add spam;
overlay remove spam;
foo
"#
));
assert!(!actual.err.is_empty());
}
#[test]
fn remove_last_overlay() {
let actual = nu!(
cwd: "tests/overlays", pipeline(
r#"
module spam { export def foo [] { "foo" } };
overlay add spam;
overlay remove;
foo
"#
));
assert!(!actual.err.is_empty());
}
#[test]
fn remove_overlay_scoped() {
let actual = nu!(
cwd: "tests/overlays", pipeline(
r#"
module spam { export def foo [] { "foo" } };
overlay add spam;
do {
overlay remove spam
};
foo
"#
));
assert_eq!(actual.out, "foo");
}
#[test]
fn remove_overlay_env() {
let actual = nu!(
cwd: "tests/overlays", pipeline(
r#"
module spam { export env FOO { "foo" } };
overlay add spam;
overlay remove spam;
$env.FOO
"#
));
assert!(actual.err.contains("did you mean"));
}
#[test]
fn remove_overlay_scoped_env() {
let actual = nu!(
cwd: "tests/overlays", pipeline(
r#"
module spam { export env FOO { "foo" } };
overlay add spam;
do {
overlay remove spam
};
$env.FOO
"#
));
assert_eq!(actual.out, "foo");
}
#[test]
fn list_default_overlay() {
let actual = nu!(
cwd: "tests/overlays", pipeline(
r#"
overlay list | last
"#,
));
assert_eq!(actual.out, "zero");
}
#[test]
fn list_last_overlay() {
let actual = nu!(
cwd: "tests/overlays", pipeline(
r#"
module spam { export def foo [] { "foo" } };
overlay add spam;
overlay list | last
"#,
));
assert_eq!(actual.out, "spam");
}
#[test]
fn list_overlay_scoped() {
let actual = nu!(
cwd: "tests/overlays", pipeline(
r#"
module spam { export def foo [] { "foo" } };
overlay add spam;
do { overlay list | last }
"#
));
assert_eq!(actual.out, "spam");
}
#[test]
fn remove_overlay_discard_decl() {
let actual = nu!(
cwd: "tests/overlays", pipeline(
r#"
overlay add samples/spam.nu;
def bagr [] { "bagr" };
overlay remove spam;
bagr
"#
));
assert!(!actual.err.is_empty());
}
#[test]
fn remove_overlay_discard_alias() {
let actual = nu!(
cwd: "tests/overlays", pipeline(
r#"
overlay add samples/spam.nu;
alias bagr = "bagr";
overlay remove spam;
bagr
"#
));
assert!(!actual.err.is_empty());
}
#[test]
fn remove_overlay_discard_env() {
let actual = nu!(
cwd: "tests/overlays", pipeline(
r#"
overlay add samples/spam.nu;
let-env BAGR = "bagr";
overlay remove spam;
$env.bagr
"#
));
assert!(actual.err.contains("did you mean"));
}
#[test]
fn preserve_overrides() {
let actual = nu!(
cwd: "tests/overlays", pipeline(
r#"
overlay add samples/spam.nu;
def foo [] { "new-foo" };
overlay remove spam;
overlay add spam;
foo
"#
));
assert_eq!(actual.out, "new-foo");
}
#[test]
fn reset_overrides() {
let actual = nu!(
cwd: "tests/overlays", pipeline(
r#"
overlay add samples/spam.nu;
def foo [] { "new-foo" };
overlay remove spam;
overlay add samples/spam.nu;
foo
"#
));
assert_eq!(actual.out, "foo");
}

View File

@ -0,0 +1,5 @@
export def foo [] { "foo" }
export alias bar = "bar"
export env BAZ { "baz" }