mirror of
https://github.com/nushell/nushell
synced 2024-12-28 14:03:09 +00:00
Use relative paths as file-relative when parsing a file (#6150)
* Make function local (not used anywhere else) * Use path relative to the parsed file * Do not use real cwd at all
This commit is contained in:
parent
8bd6b5b913
commit
c92211c016
7 changed files with 277 additions and 42 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -2731,6 +2731,7 @@ dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"miette 5.1.1",
|
"miette 5.1.1",
|
||||||
"nu-json",
|
"nu-json",
|
||||||
|
"nu-path",
|
||||||
"nu-utils",
|
"nu-utils",
|
||||||
"num-format",
|
"num-format",
|
||||||
"regex",
|
"regex",
|
||||||
|
|
|
@ -6,6 +6,8 @@ use nu_protocol::{
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct NuCheck;
|
pub struct NuCheck;
|
||||||
|
|
||||||
|
@ -117,13 +119,30 @@ impl Command for NuCheck {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_all {
|
// Change currently parsed directory
|
||||||
|
let prev_currently_parsed_cwd = if let Some(parent) = Path::new(&path).parent()
|
||||||
|
{
|
||||||
|
let prev = working_set.currently_parsed_cwd.clone();
|
||||||
|
|
||||||
|
working_set.currently_parsed_cwd = Some(parent.into());
|
||||||
|
|
||||||
|
prev
|
||||||
|
} else {
|
||||||
|
working_set.currently_parsed_cwd.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = if is_all {
|
||||||
heuristic_parse_file(path, &mut working_set, call, is_debug)
|
heuristic_parse_file(path, &mut working_set, call, is_debug)
|
||||||
} else if is_module {
|
} else if is_module {
|
||||||
parse_file_module(path, &mut working_set, call, is_debug)
|
parse_file_module(path, &mut working_set, call, is_debug)
|
||||||
} else {
|
} else {
|
||||||
parse_file_script(path, &mut working_set, call, is_debug)
|
parse_file_script(path, &mut working_set, call, is_debug)
|
||||||
}
|
};
|
||||||
|
|
||||||
|
// Restore the currently parsed directory back
|
||||||
|
working_set.currently_parsed_cwd = prev_currently_parsed_cwd;
|
||||||
|
|
||||||
|
result
|
||||||
} else {
|
} else {
|
||||||
Err(ShellError::GenericError(
|
Err(ShellError::GenericError(
|
||||||
"Failed to execute command".to_string(),
|
"Failed to execute command".to_string(),
|
||||||
|
|
|
@ -724,3 +724,40 @@ fn parse_failure_due_conflicted_flags() {
|
||||||
.contains("You cannot have both `--all` and `--as-module` on the same command line"));
|
.contains("You cannot have both `--all` and `--as-module` on the same command line"));
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_script_with_nested_scripts_success() {
|
||||||
|
Playground::setup("nu_check_test_24", |dirs, sandbox| {
|
||||||
|
sandbox
|
||||||
|
.mkdir("lol")
|
||||||
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"lol/lol.nu",
|
||||||
|
r#"
|
||||||
|
source ../foo.nu
|
||||||
|
use lol_shell.nu
|
||||||
|
overlay add ../lol/lol_shell.nu
|
||||||
|
"#,
|
||||||
|
)])
|
||||||
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"lol/lol_shell.nu",
|
||||||
|
r#"
|
||||||
|
export def ls [] { "lol" }
|
||||||
|
"#,
|
||||||
|
)])
|
||||||
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"foo.nu",
|
||||||
|
r#"
|
||||||
|
let-env FOO = 'foo'
|
||||||
|
"#,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: dirs.test(), pipeline(
|
||||||
|
r#"
|
||||||
|
nu-check lol/lol.nu
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "true");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -1333,6 +1333,7 @@ pub fn parse_use(
|
||||||
|
|
||||||
let (module_filename, err) =
|
let (module_filename, err) =
|
||||||
unescape_unquote_string(&import_pattern.head.name, import_pattern.head.span);
|
unescape_unquote_string(&import_pattern.head.name, import_pattern.head.span);
|
||||||
|
|
||||||
if err.is_none() {
|
if err.is_none() {
|
||||||
if let Some(module_path) =
|
if let Some(module_path) =
|
||||||
find_in_dirs(&module_filename, working_set, &cwd, LIB_DIRS_ENV)
|
find_in_dirs(&module_filename, working_set, &cwd, LIB_DIRS_ENV)
|
||||||
|
@ -1351,11 +1352,22 @@ pub fn parse_use(
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Ok(contents) = std::fs::read(module_path) {
|
if let Ok(contents) = std::fs::read(&module_path) {
|
||||||
let span_start = working_set.next_span_start();
|
let span_start = working_set.next_span_start();
|
||||||
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();
|
||||||
|
|
||||||
|
// Change currently parsed directory
|
||||||
|
let prev_currently_parsed_cwd = if let Some(parent) = module_path.parent() {
|
||||||
|
let prev = working_set.currently_parsed_cwd.clone();
|
||||||
|
|
||||||
|
working_set.currently_parsed_cwd = Some(parent.into());
|
||||||
|
|
||||||
|
prev
|
||||||
|
} else {
|
||||||
|
working_set.currently_parsed_cwd.clone()
|
||||||
|
};
|
||||||
|
|
||||||
let (block, module, 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),
|
||||||
|
@ -1363,6 +1375,9 @@ pub fn parse_use(
|
||||||
);
|
);
|
||||||
error = error.or(err);
|
error = error.or(err);
|
||||||
|
|
||||||
|
// Restore the currently parsed directory back
|
||||||
|
working_set.currently_parsed_cwd = prev_currently_parsed_cwd;
|
||||||
|
|
||||||
let _ = working_set.add_block(block);
|
let _ = working_set.add_block(block);
|
||||||
let module_id = working_set.add_module(&module_name, module.clone());
|
let module_id = working_set.add_module(&module_name, module.clone());
|
||||||
|
|
||||||
|
@ -2069,11 +2084,22 @@ pub fn parse_overlay_add(
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Ok(contents) = std::fs::read(module_path) {
|
if let Ok(contents) = std::fs::read(&module_path) {
|
||||||
let span_start = working_set.next_span_start();
|
let span_start = working_set.next_span_start();
|
||||||
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();
|
||||||
|
|
||||||
|
// Change currently parsed directory
|
||||||
|
let prev_currently_parsed_cwd = if let Some(parent) = module_path.parent() {
|
||||||
|
let prev = working_set.currently_parsed_cwd.clone();
|
||||||
|
|
||||||
|
working_set.currently_parsed_cwd = Some(parent.into());
|
||||||
|
|
||||||
|
prev
|
||||||
|
} else {
|
||||||
|
working_set.currently_parsed_cwd.clone()
|
||||||
|
};
|
||||||
|
|
||||||
let (block, module, 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),
|
||||||
|
@ -2081,6 +2107,9 @@ pub fn parse_overlay_add(
|
||||||
);
|
);
|
||||||
error = error.or(err);
|
error = error.or(err);
|
||||||
|
|
||||||
|
// Restore the currently parsed directory back
|
||||||
|
working_set.currently_parsed_cwd = prev_currently_parsed_cwd;
|
||||||
|
|
||||||
let _ = working_set.add_block(block);
|
let _ = working_set.add_block(block);
|
||||||
let module_id = working_set.add_module(&overlay_name, module.clone());
|
let module_id = working_set.add_module(&overlay_name, module.clone());
|
||||||
|
|
||||||
|
@ -2370,6 +2399,7 @@ pub fn parse_source(
|
||||||
if name == b"source" {
|
if name == b"source" {
|
||||||
if let Some(decl_id) = working_set.find_decl(b"source", &Type::Any) {
|
if let Some(decl_id) = working_set.find_decl(b"source", &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?
|
||||||
// Some of the others (`parse_let`) use it, some of them (`parse_hide`) don't.
|
// Some of the others (`parse_let`) use it, some of them (`parse_hide`) don't.
|
||||||
let ParsedInternalCall {
|
let ParsedInternalCall {
|
||||||
|
@ -2401,9 +2431,21 @@ pub fn parse_source(
|
||||||
if spans.len() >= 2 {
|
if spans.len() >= 2 {
|
||||||
let name_expr = working_set.get_span_contents(spans[1]);
|
let name_expr = working_set.get_span_contents(spans[1]);
|
||||||
let (filename, err) = unescape_unquote_string(name_expr, spans[1]);
|
let (filename, err) = unescape_unquote_string(name_expr, spans[1]);
|
||||||
|
|
||||||
if err.is_none() {
|
if err.is_none() {
|
||||||
if let Some(path) = find_in_dirs(&filename, working_set, &cwd, LIB_DIRS_ENV) {
|
if let Some(path) = find_in_dirs(&filename, working_set, &cwd, LIB_DIRS_ENV) {
|
||||||
if let Ok(contents) = std::fs::read(&path) {
|
if let Ok(contents) = std::fs::read(&path) {
|
||||||
|
// Change currently parsed directory
|
||||||
|
let prev_currently_parsed_cwd = if let Some(parent) = path.parent() {
|
||||||
|
let prev = working_set.currently_parsed_cwd.clone();
|
||||||
|
|
||||||
|
working_set.currently_parsed_cwd = Some(parent.into());
|
||||||
|
|
||||||
|
prev
|
||||||
|
} else {
|
||||||
|
working_set.currently_parsed_cwd.clone()
|
||||||
|
};
|
||||||
|
|
||||||
// This will load the defs from the file into the
|
// This will load the defs from the file into the
|
||||||
// working set, if it was a successful parse.
|
// working set, if it was a successful parse.
|
||||||
let (block, err) = parse(
|
let (block, err) = parse(
|
||||||
|
@ -2414,6 +2456,9 @@ pub fn parse_source(
|
||||||
expand_aliases_denylist,
|
expand_aliases_denylist,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Restore the currently parsed directory back
|
||||||
|
working_set.currently_parsed_cwd = prev_currently_parsed_cwd;
|
||||||
|
|
||||||
if err.is_some() {
|
if err.is_some() {
|
||||||
// Unsuccessful parse of file
|
// Unsuccessful parse of file
|
||||||
return (
|
return (
|
||||||
|
@ -2695,13 +2740,27 @@ pub fn parse_register(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_in_dirs(
|
/// This helper function is used to find files during parsing
|
||||||
|
///
|
||||||
|
/// Checks whether the file is:
|
||||||
|
/// 1. relative to the directory of the file currently being parsed
|
||||||
|
/// 2. relative to the current working directory
|
||||||
|
/// 3. within one of the NU_LIB_DIRS entries
|
||||||
|
///
|
||||||
|
/// Always returns absolute path
|
||||||
|
fn find_in_dirs(
|
||||||
filename: &str,
|
filename: &str,
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
cwd: &str,
|
cwd: &str,
|
||||||
dirs_env: &str,
|
dirs_env: &str,
|
||||||
) -> Option<PathBuf> {
|
) -> Option<PathBuf> {
|
||||||
if let Ok(p) = canonicalize_with(filename, cwd) {
|
if let Some(currently_parsed_cwd) = &working_set.currently_parsed_cwd {
|
||||||
|
if let Ok(p) = canonicalize_with(filename, currently_parsed_cwd) {
|
||||||
|
Some(p)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else if let Ok(p) = canonicalize_with(filename, cwd) {
|
||||||
Some(p)
|
Some(p)
|
||||||
} else {
|
} else {
|
||||||
let path = Path::new(filename);
|
let path = Path::new(filename);
|
||||||
|
|
|
@ -10,6 +10,7 @@ version = "0.66.1"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-utils = { path = "../nu-utils", version = "0.66.1" }
|
nu-utils = { path = "../nu-utils", version = "0.66.1" }
|
||||||
|
nu-path = { path = "../nu-path", version = "0.66.1" }
|
||||||
thiserror = "1.0.31"
|
thiserror = "1.0.31"
|
||||||
miette = { version = "5.1.0", features = ["fancy"] }
|
miette = { version = "5.1.0", features = ["fancy"] }
|
||||||
serde = {version = "1.0.130", features = ["derive"]}
|
serde = {version = "1.0.130", features = ["derive"]}
|
||||||
|
|
|
@ -729,6 +729,14 @@ impl EngineState {
|
||||||
self.num_files() - 1
|
self.num_files() - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_cwd(&self) -> Option<String> {
|
||||||
|
if let Some(pwd_value) = self.get_env_var(PWD_ENV) {
|
||||||
|
pwd_value.as_string().ok()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
pub fn get_sig_quit(&self) -> &Option<Arc<AtomicBool>> {
|
pub fn get_sig_quit(&self) -> &Option<Arc<AtomicBool>> {
|
||||||
&self.sig_quit
|
&self.sig_quit
|
||||||
|
@ -755,6 +763,8 @@ pub struct StateWorkingSet<'a> {
|
||||||
pub delta: StateDelta,
|
pub delta: StateDelta,
|
||||||
pub external_commands: Vec<Vec<u8>>,
|
pub external_commands: Vec<Vec<u8>>,
|
||||||
pub type_scope: TypeScope,
|
pub type_scope: TypeScope,
|
||||||
|
/// Current working directory relative to the file being parsed right now
|
||||||
|
pub currently_parsed_cwd: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A temporary placeholder for expression types. It is used to keep track of the input types
|
/// A temporary placeholder for expression types. It is used to keep track of the input types
|
||||||
|
@ -927,6 +937,7 @@ impl<'a> StateWorkingSet<'a> {
|
||||||
permanent_state,
|
permanent_state,
|
||||||
external_commands: vec![],
|
external_commands: vec![],
|
||||||
type_scope: TypeScope::default(),
|
type_scope: TypeScope::default(),
|
||||||
|
currently_parsed_cwd: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
use nu_test_support::nu;
|
use nu_test_support::fs::Stub::FileWithContentToBeTrimmed;
|
||||||
|
use nu_test_support::playground::Playground;
|
||||||
|
use nu_test_support::{nu, pipeline};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn run_nu_script_single_line() {
|
fn run_nu_script_single_line() {
|
||||||
|
@ -44,3 +46,108 @@ fn run_nu_script_multiline_end_pipe_win() {
|
||||||
|
|
||||||
assert_eq!(actual.out, "3");
|
assert_eq!(actual.out, "3");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_file_relative_to_parsed_file() {
|
||||||
|
Playground::setup("relative_files", |dirs, sandbox| {
|
||||||
|
sandbox
|
||||||
|
.mkdir("lol")
|
||||||
|
.mkdir("lol/lol")
|
||||||
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"lol/lol/lol.nu",
|
||||||
|
r#"
|
||||||
|
source ../../foo.nu
|
||||||
|
use ../lol_shell.nu
|
||||||
|
overlay add ../../lol/lol_shell.nu
|
||||||
|
|
||||||
|
$'($env.FOO) (lol_shell ls) (ls)'
|
||||||
|
"#,
|
||||||
|
)])
|
||||||
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"lol/lol_shell.nu",
|
||||||
|
r#"
|
||||||
|
export def ls [] { "lol" }
|
||||||
|
"#,
|
||||||
|
)])
|
||||||
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"foo.nu",
|
||||||
|
r#"
|
||||||
|
let-env FOO = 'foo'
|
||||||
|
"#,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: dirs.test(), pipeline(
|
||||||
|
r#"
|
||||||
|
source lol/lol/lol.nu
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "foo lol lol");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_file_relative_to_parsed_file_dont_use_cwd_1() {
|
||||||
|
Playground::setup("relative_files", |dirs, sandbox| {
|
||||||
|
sandbox
|
||||||
|
.mkdir("lol")
|
||||||
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"lol/lol.nu",
|
||||||
|
r#"
|
||||||
|
source foo.nu
|
||||||
|
"#,
|
||||||
|
)])
|
||||||
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"lol/foo.nu",
|
||||||
|
r#"
|
||||||
|
let-env FOO = 'good'
|
||||||
|
"#,
|
||||||
|
)])
|
||||||
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"foo.nu",
|
||||||
|
r#"
|
||||||
|
let-env FOO = 'bad'
|
||||||
|
"#,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: dirs.test(), pipeline(
|
||||||
|
r#"
|
||||||
|
source lol/lol.nu;
|
||||||
|
$env.FOO
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "good");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_file_relative_to_parsed_file_dont_use_cwd_2() {
|
||||||
|
Playground::setup("relative_files", |dirs, sandbox| {
|
||||||
|
sandbox
|
||||||
|
.mkdir("lol")
|
||||||
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"lol/lol.nu",
|
||||||
|
r#"
|
||||||
|
source foo.nu
|
||||||
|
"#,
|
||||||
|
)])
|
||||||
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"foo.nu",
|
||||||
|
r#"
|
||||||
|
let-env FOO = 'bad'
|
||||||
|
"#,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: dirs.test(), pipeline(
|
||||||
|
r#"
|
||||||
|
source lol/lol.nu
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert!(actual.err.contains("File not found"));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue