Require static path for source-env (#6526)

This commit is contained in:
Jakub Žádník 2022-09-08 23:41:49 +03:00 committed by GitHub
parent 1adebefc3e
commit e76b3d61de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 60 additions and 118 deletions

View File

@ -1,11 +1,10 @@
use std::path::PathBuf; use std::path::PathBuf;
use nu_engine::{eval_block, find_in_dirs_env, redirect_env, CallExt}; use nu_engine::{eval_block, find_in_dirs_env, redirect_env, CallExt};
use nu_parser::parse;
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack, StateWorkingSet}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, CliError, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Value, Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Value,
}; };
/// Source a file for environment variables. /// Source a file for environment variables.
@ -40,96 +39,47 @@ impl Command for SourceEnv {
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let source_filename: Spanned<String> = call.req(engine_state, caller_stack, 0)?; let source_filename: Spanned<String> = call.req(engine_state, caller_stack, 0)?;
if let Some(path) = find_in_dirs_env(&source_filename.item, engine_state, caller_stack)? { // Note: this hidden positional is the block_id that corresponded to the 0th position
if let Ok(content) = std::fs::read_to_string(&path) { // it is put here by the parser
let mut parent = PathBuf::from(&path); let block_id: i64 = call.req(engine_state, caller_stack, 1)?;
parent.pop();
let mut new_engine_state = engine_state.clone(); // Set the currently evaluated directory (file-relative PWD)
let mut parent = if let Some(path) =
let (block, delta) = { find_in_dirs_env(&source_filename.item, engine_state, caller_stack)?
let mut working_set = StateWorkingSet::new(&new_engine_state); {
PathBuf::from(&path)
// Set the currently parsed directory
working_set.currently_parsed_cwd = Some(parent.clone());
let (block, err) = parse(&mut working_set, None, content.as_bytes(), true, &[]);
if let Some(err) = err {
// Because the error span points at new_engine_state, we must create the error message now
let msg = format!(
r#"Found this parser error: {:?}"#,
CliError(&err, &working_set)
);
return Err(ShellError::GenericError(
"Failed to parse content".to_string(),
"cannot parse this file".to_string(),
Some(source_filename.span),
Some(msg),
vec![],
));
} else {
(block, working_set.render())
}
};
// Merge parser changes to a temporary engine state
new_engine_state.merge_delta(delta)?;
// Set the currently evaluated directory
let file_pwd = Value::String {
val: parent.to_string_lossy().to_string(),
span: call.head,
};
caller_stack.add_env_var("FILE_PWD".to_string(), file_pwd);
// Evaluate the parsed file's block
let mut callee_stack = caller_stack.gather_captures(&block.captures);
let result = eval_block(
&new_engine_state,
&mut callee_stack,
&block,
input,
true,
true,
);
let result = if let Err(err) = result {
// Because the error span points at new_engine_state, we must create the error message now
let working_set = StateWorkingSet::new(&new_engine_state);
let msg = format!(
r#"Found this shell error: {:?}"#,
CliError(&err, &working_set)
);
Err(ShellError::GenericError(
"Failed to evaluate content".to_string(),
"cannot evaluate this file".to_string(),
Some(source_filename.span),
Some(msg),
vec![],
))
} else {
result
};
// Merge the block's environment to the current stack
redirect_env(engine_state, caller_stack, &callee_stack);
// Remove the file-relative PWD
caller_stack.remove_env_var(engine_state, "FILE_PWD");
result
} else {
Err(ShellError::FileNotFound(source_filename.span))
}
} else { } else {
Err(ShellError::FileNotFound(source_filename.span)) return Err(ShellError::FileNotFound(source_filename.span));
} };
parent.pop();
let file_pwd = Value::String {
val: parent.to_string_lossy().to_string(),
span: call.head,
};
caller_stack.add_env_var("FILE_PWD".to_string(), file_pwd);
// Evaluate the block
let block = engine_state.get_block(block_id as usize).clone();
let mut callee_stack = caller_stack.gather_captures(&block.captures);
let result = eval_block(
engine_state,
&mut callee_stack,
&block,
input,
call.redirect_stdout,
call.redirect_stderr,
);
// Merge the block's environment to the current stack
redirect_env(engine_state, caller_stack, &callee_stack);
// Remove the file-relative PWD
caller_stack.remove_env_var(engine_state, "FILE_PWD");
result
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {

View File

@ -143,6 +143,7 @@ fn sources_unicode_file_in_non_utf8_dir() {
// How do I create non-UTF-8 path??? // How do I create non-UTF-8 path???
} }
#[ignore]
#[test] #[test]
fn can_source_dynamic_path() { fn can_source_dynamic_path() {
Playground::setup("can_source_dynamic_path", |dirs, sandbox| { Playground::setup("can_source_dynamic_path", |dirs, sandbox| {
@ -269,39 +270,26 @@ fn source_env_dont_cd_overlay() {
} }
#[test] #[test]
fn source_env_nice_parse_error() { fn source_env_is_scoped() {
Playground::setup("source_env_nice_parse_error", |dirs, sandbox| { Playground::setup("source_env_is_scoped", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContentToBeTrimmed( sandbox.with_files(vec![FileWithContentToBeTrimmed(
"spam.nu", "spam.nu",
r#" r#"
let x def foo [] { 'foo' }
"#, alias bar = 'bar'
"#,
)]); )]);
let inp = &[r#"source-env spam.nu"#]; let inp = &[r#"source-env spam.nu"#, r#"foo"#];
let actual = nu!(cwd: dirs.test(), pipeline(&inp.join("; "))); let actual = nu!(cwd: dirs.test(), pipeline(&inp.join("; ")));
assert!(actual.err.contains("cannot parse this file")); assert!(actual.err.contains("did you mean"));
assert!(actual.err.contains("───"));
})
}
#[test] let inp = &[r#"source-env spam.nu"#, r#"bar"#];
fn source_env_nice_shell_error() {
Playground::setup("source_env_nice_shell_error", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContentToBeTrimmed(
"spam.nu",
r#"
let-env FILE_PWD = 'foo'
"#,
)]);
let inp = &[r#"source-env spam.nu"#];
let actual = nu!(cwd: dirs.test(), pipeline(&inp.join("; "))); let actual = nu!(cwd: dirs.test(), pipeline(&inp.join("; ")));
assert!(actual.err.contains("cannot evaluate this file")); assert!(actual.err.contains("did you mean"));
assert!(actual.err.contains("───"));
}) })
} }

View File

@ -2902,8 +2902,10 @@ pub fn parse_source(
let mut error = None; let mut error = None;
let name = working_set.get_span_contents(spans[0]); let name = working_set.get_span_contents(spans[0]);
if name == b"source" { if name == b"source" || name == b"source-env" {
if let Some(decl_id) = working_set.find_decl(b"source", &Type::Any) { let scoped = name == b"source-env";
if let Some(decl_id) = working_set.find_decl(name, &Type::Any) {
let cwd = working_set.get_cwd(); let cwd = working_set.get_cwd();
// Is this the right call to be using here? // Is this the right call to be using here?
@ -2958,7 +2960,7 @@ pub fn parse_source(
working_set, working_set,
path.file_name().and_then(|x| x.to_str()), path.file_name().and_then(|x| x.to_str()),
&contents, &contents,
false, scoped,
expand_aliases_denylist, expand_aliases_denylist,
); );
@ -2983,7 +2985,7 @@ pub fn parse_source(
let mut call_with_block = call; let mut call_with_block = call;
// Adding this expression to the positional creates a syntax highlighting error // FIXME: Adding this expression to the positional creates a syntax highlighting error
// after writing `source example.nu` // after writing `source example.nu`
call_with_block.add_positional(Expression { call_with_block.add_positional(Expression {
expr: Expr::Int(block_id as i64), expr: Expr::Int(block_id as i64),

View File

@ -4815,7 +4815,9 @@ pub fn parse_builtin_commands(
(pipeline, err) (pipeline, err)
} }
b"overlay" => parse_overlay(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" | b"source-env" => {
parse_source(working_set, &lite_command.parts, expand_aliases_denylist)
}
b"export" => parse_export_in_block(working_set, lite_command, expand_aliases_denylist), b"export" => parse_export_in_block(working_set, lite_command, expand_aliases_denylist),
b"hide" => parse_hide(working_set, &lite_command.parts, expand_aliases_denylist), b"hide" => parse_hide(working_set, &lite_command.parts, expand_aliases_denylist),
#[cfg(feature = "plugin")] #[cfg(feature = "plugin")]