Bring module's environment when activating overlay (#6425)

This commit is contained in:
Jakub Žádník 2022-08-27 01:32:19 +03:00 committed by GitHub
parent 3f1824111d
commit 34d7c17e78
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 107 additions and 18 deletions

View File

@ -1,4 +1,4 @@
use nu_engine::{eval_block, CallExt}; use nu_engine::{eval_block, redirect_env, CallExt};
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape}; use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape};
@ -49,11 +49,11 @@ impl Command for OverlayUse {
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, caller_stack: &mut Stack,
call: &Call, call: &Call,
_input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let name_arg: Spanned<String> = call.req(engine_state, stack, 0)?; let name_arg: Spanned<String> = call.req(engine_state, caller_stack, 0)?;
let (overlay_name, overlay_name_span) = if let Some(kw_expression) = call.positional_nth(1) let (overlay_name, overlay_name_span) = if let Some(kw_expression) = call.positional_nth(1)
{ {
@ -96,10 +96,10 @@ impl Command for OverlayUse {
if let Some(overlay_id) = engine_state.find_overlay(overlay_name.as_bytes()) { if let Some(overlay_id) = engine_state.find_overlay(overlay_name.as_bytes()) {
let old_module_id = engine_state.get_overlay(overlay_id).origin; let old_module_id = engine_state.get_overlay(overlay_id).origin;
stack.add_overlay(overlay_name.clone()); caller_stack.add_overlay(overlay_name.clone());
if let Some(new_module_id) = engine_state.find_module(overlay_name.as_bytes(), &[]) { if let Some(new_module_id) = engine_state.find_module(overlay_name.as_bytes(), &[]) {
if !stack.has_env_overlay(&overlay_name, engine_state) if !caller_stack.has_env_overlay(&overlay_name, engine_state)
|| (old_module_id != new_module_id) || (old_module_id != new_module_id)
{ {
// Add environment variables only if: // Add environment variables only if:
@ -118,7 +118,7 @@ impl Command for OverlayUse {
let val = eval_block( let val = eval_block(
engine_state, engine_state,
stack, caller_stack,
block, block,
PipelineData::new(call.head), PipelineData::new(call.head),
false, false,
@ -126,7 +126,24 @@ impl Command for OverlayUse {
)? )?
.into_value(call.head); .into_value(call.head);
stack.add_env_var(name, val); caller_stack.add_env_var(name, val);
}
// Evaluate the export-env block (if any) and keep its environment
if let Some(block_id) = module.env_block {
let block = engine_state.get_block(block_id);
let mut callee_stack = caller_stack.gather_captures(&block.captures);
let _ = eval_block(
engine_state,
&mut callee_stack,
block,
input,
call.redirect_stdout,
call.redirect_stderr,
);
redirect_env(engine_state, caller_stack, &callee_stack);
} }
} }
} }

View File

@ -5,7 +5,7 @@ use nu_protocol::{
ImportPatternMember, Pipeline, ImportPatternMember, Pipeline,
}, },
engine::{StateWorkingSet, DEFAULT_OVERLAY_NAME}, engine::{StateWorkingSet, DEFAULT_OVERLAY_NAME},
span, Exportable, Module, PositionalArg, Span, Spanned, SyntaxShape, Type, span, BlockId, Exportable, Module, PositionalArg, Span, Spanned, SyntaxShape, Type,
}; };
use std::collections::HashSet; use std::collections::HashSet;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@ -1215,12 +1215,11 @@ pub fn parse_export_env(
working_set: &mut StateWorkingSet, working_set: &mut StateWorkingSet,
spans: &[Span], spans: &[Span],
expand_aliases_denylist: &[usize], expand_aliases_denylist: &[usize],
) -> (Pipeline, Option<ParseError>) { ) -> (Pipeline, Option<BlockId>, Option<ParseError>) {
// Just used to be allowed inside modules, otherwise does nothing in the parser.
if !spans.is_empty() && working_set.get_span_contents(spans[0]) != b"export-env" { if !spans.is_empty() && working_set.get_span_contents(spans[0]) != b"export-env" {
return ( return (
garbage_pipeline(spans), garbage_pipeline(spans),
None,
Some(ParseError::UnknownState( Some(ParseError::UnknownState(
"internal error: Wrong call name for 'export-env' command".into(), "internal error: Wrong call name for 'export-env' command".into(),
span(spans), span(spans),
@ -1231,6 +1230,7 @@ pub fn parse_export_env(
if spans.len() < 2 { if spans.len() < 2 {
return ( return (
garbage_pipeline(spans), garbage_pipeline(spans),
None,
Some(ParseError::MissingPositional( Some(ParseError::MissingPositional(
"block".into(), "block".into(),
span(spans), span(spans),
@ -1265,6 +1265,7 @@ pub fn parse_export_env(
ty: output, ty: output,
custom_completion: None, custom_completion: None,
}]), }]),
None,
err, err,
); );
} }
@ -1274,6 +1275,7 @@ pub fn parse_export_env(
None => { None => {
return ( return (
garbage_pipeline(spans), garbage_pipeline(spans),
None,
Some(ParseError::UnknownState( Some(ParseError::UnknownState(
"internal error: 'export-env' declaration not found".into(), "internal error: 'export-env' declaration not found".into(),
span(spans), span(spans),
@ -1282,6 +1284,30 @@ pub fn parse_export_env(
} }
}; };
let block_id = if let Some(block) = call.positional_nth(0) {
if let Some(block_id) = block.as_block() {
block_id
} else {
return (
garbage_pipeline(spans),
None,
Some(ParseError::UnknownState(
"internal error: 'export-env' block is not a block".into(),
block.span,
)),
);
}
} else {
return (
garbage_pipeline(spans),
None,
Some(ParseError::UnknownState(
"internal error: 'export-env' block is missing".into(),
span(spans),
)),
);
};
let pipeline = Pipeline::from_vec(vec![Expression { let pipeline = Pipeline::from_vec(vec![Expression {
expr: Expr::Call(call), expr: Expr::Call(call),
span: span(spans), span: span(spans),
@ -1289,7 +1315,7 @@ pub fn parse_export_env(
custom_completion: None, custom_completion: None,
}]); }]);
(pipeline, None) (pipeline, Some(block_id), None)
} }
pub fn parse_module_block( pub fn parse_module_block(
@ -1388,11 +1414,19 @@ pub fn parse_module_block(
(pipe, err) (pipe, err)
} }
b"export-env" => parse_export_env( b"export-env" => {
working_set, let (pipe, maybe_env_block, err) = parse_export_env(
&pipeline.commands[0].parts, working_set,
expand_aliases_denylist, &pipeline.commands[0].parts,
), expand_aliases_denylist,
);
if let Some(block_id) = maybe_env_block {
module.add_env_block(block_id);
}
(pipe, err)
}
_ => ( _ => (
garbage_pipeline(&pipeline.commands[0].parts), garbage_pipeline(&pipeline.commands[0].parts),
Some(ParseError::ExpectedKeyword( Some(ParseError::ExpectedKeyword(

View File

@ -11,6 +11,7 @@ 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 env_block: Option<BlockId>,
pub span: Option<Span>, pub span: Option<Span>,
} }
@ -20,6 +21,7 @@ impl Module {
decls: IndexMap::new(), decls: IndexMap::new(),
aliases: IndexMap::new(), aliases: IndexMap::new(),
env_vars: IndexMap::new(), env_vars: IndexMap::new(),
env_block: None,
span: None, span: None,
} }
} }
@ -29,6 +31,7 @@ impl Module {
decls: IndexMap::new(), decls: IndexMap::new(),
aliases: IndexMap::new(), aliases: IndexMap::new(),
env_vars: IndexMap::new(), env_vars: IndexMap::new(),
env_block: None,
span: Some(span), span: Some(span),
} }
} }
@ -45,6 +48,10 @@ impl Module {
self.env_vars.insert(name, block_id) self.env_vars.insert(name, block_id)
} }
pub fn add_env_block(&mut self, block_id: BlockId) {
self.env_block = Some(block_id);
}
pub fn extend(&mut self, other: &Module) { 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());

View File

@ -736,3 +736,34 @@ fn overlay_remove_and_add_renamed_overlay() {
assert_eq!(actual.out, "foo"); assert_eq!(actual.out, "foo");
assert_eq!(actual_repl.out, "foo"); assert_eq!(actual_repl.out, "foo");
} }
#[test]
fn overlay_use_export_env() {
let inp = &[
r#"module spam { export-env { let-env FOO = 'foo' } }"#,
r#"overlay use spam"#,
r#"$env.FOO"#,
];
let actual = nu!(cwd: "tests/overlays", pipeline(&inp.join("; ")));
let actual_repl = nu!(cwd: "tests/overlays", nu_repl_code(inp));
assert_eq!(actual.out, "foo");
assert_eq!(actual_repl.out, "foo");
}
#[test]
fn overlay_use_export_env_hide() {
let inp = &[
r#"let-env FOO = 'foo'"#,
r#"module spam { export-env { hide-env FOO } }"#,
r#"overlay use spam"#,
r#"$env.FOO"#,
];
let actual = nu!(cwd: "tests/overlays", pipeline(&inp.join("; ")));
let actual_repl = nu!(cwd: "tests/overlays", nu_repl_code(inp));
assert!(actual.err.contains("did you mean"));
assert!(actual_repl.err.contains("did you mean"));
}