Allow NU_LIBS_DIR and friends to be const (#8310)

# Description

Allow NU_LIBS_DIR and friends to be const they can be updated within the
same parse pass. This will allow us to remove having multiple config
files eventually.

Small implementation detail: I've changed `call.parser_info` to a
hashmap with string keys, so the information can have names rather than
indices, and we don't have to worry too much about the order in which we
put things into it.

Closes https://github.com/nushell/nushell/issues/8422

# User-Facing Changes

In a single file, users can now do stuff like
```
const NU_LIBS_DIR = ['/some/path/here']
source script.nu
```
and the source statement will use the value of NU_LIBS_DIR declared the
line before.

Currently, if there is no `NU_LIBS_DIR` const, then we fallback to using
the value of the `NU_LIBS_DIR` env-var, so there are no breaking changes
(unless someone named a const NU_LIBS_DIR for some reason).


![2023-03-04-014103_hyprshot](https://user-images.githubusercontent.com/13265529/222885263-135cdd0d-7884-438b-b2ed-c3979fa44463.png)

# Tests + Formatting

~~TODO: write tests~~ Done

# After Submitting

~~TODO: update docs~~ Will do when we update default_env.nu/merge
default_env.nu into default_config.nu.
This commit is contained in:
StevenDoesStuffs 2023-03-17 07:23:29 -05:00 committed by GitHub
parent 7095d8994e
commit 400a9d3b1e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 265 additions and 117 deletions

View File

@ -6,7 +6,7 @@ use nu_protocol::{
PipelineData, Span, Type, Value, PipelineData, Span, Type, Value,
}; };
use reedline::Suggestion; use reedline::Suggestion;
use std::sync::Arc; use std::{collections::HashMap, sync::Arc};
use super::completer::map_value_completions; use super::completer::map_value_completions;
@ -66,7 +66,7 @@ impl Completer for CustomCompletion {
], ],
redirect_stdout: true, redirect_stdout: true,
redirect_stderr: true, redirect_stderr: true,
parser_info: vec![], parser_info: HashMap::new(),
}, },
PipelineData::empty(), PipelineData::empty(),
); );

View File

@ -1,4 +1,4 @@
use nu_engine::{eval_block, find_in_dirs_env, redirect_env, CallExt}; use nu_engine::{eval_block, find_in_dirs_env, get_dirs_var_from_call, redirect_env, CallExt};
use nu_parser::trim_quotes_str; use nu_parser::trim_quotes_str;
use nu_protocol::ast::{Call, Expr}; use nu_protocol::ast::{Call, Expr};
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
@ -66,23 +66,24 @@ impl Command for OverlayUse {
let mut name_arg: Spanned<String> = call.req(engine_state, caller_stack, 0)?; let mut name_arg: Spanned<String> = call.req(engine_state, caller_stack, 0)?;
name_arg.item = trim_quotes_str(&name_arg.item).to_string(); name_arg.item = trim_quotes_str(&name_arg.item).to_string();
let maybe_origin_module_id = if let Some(overlay_expr) = call.parser_info_nth(0) { let maybe_origin_module_id =
if let Expr::Overlay(module_id) = overlay_expr.expr { if let Some(overlay_expr) = call.get_parser_info("overlay_expr") {
module_id if let Expr::Overlay(module_id) = overlay_expr.expr {
module_id
} else {
return Err(ShellError::NushellFailedSpanned {
msg: "Not an overlay".to_string(),
label: "requires an overlay (path or a string)".to_string(),
span: overlay_expr.span,
});
}
} else { } else {
return Err(ShellError::NushellFailedSpanned { return Err(ShellError::NushellFailedSpanned {
msg: "Not an overlay".to_string(), msg: "Missing positional".to_string(),
label: "requires an overlay (path or a string)".to_string(), label: "missing required overlay".to_string(),
span: overlay_expr.span, span: call.head,
}); });
} };
} else {
return Err(ShellError::NushellFailedSpanned {
msg: "Missing positional".to_string(),
label: "missing required overlay".to_string(),
span: call.head,
});
};
let overlay_name = if let Some(name) = call.opt(engine_state, caller_stack, 1)? { let overlay_name = if let Some(name) = call.opt(engine_state, caller_stack, 1)? {
name name
@ -113,7 +114,12 @@ impl Command for OverlayUse {
// Evaluate the export-env block (if any) and keep its environment // Evaluate the export-env block (if any) and keep its environment
if let Some(block_id) = module.env_block { if let Some(block_id) = module.env_block {
let maybe_path = find_in_dirs_env(&name_arg.item, engine_state, caller_stack)?; let maybe_path = find_in_dirs_env(
&name_arg.item,
engine_state,
caller_stack,
get_dirs_var_from_call(call),
)?;
let block = engine_state.get_block(block_id); let block = engine_state.get_block(block_id);
let mut callee_stack = caller_stack.gather_captures(&block.captures); let mut callee_stack = caller_stack.gather_captures(&block.captures);

View File

@ -1,4 +1,4 @@
use nu_engine::{eval_block, find_in_dirs_env, redirect_env}; use nu_engine::{eval_block, find_in_dirs_env, get_dirs_var_from_call, redirect_env};
use nu_protocol::ast::{Call, Expr, Expression}; use nu_protocol::ast::{Call, Expr, Expression};
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
@ -48,7 +48,7 @@ impl Command for Use {
let import_pattern = if let Some(Expression { let import_pattern = if let Some(Expression {
expr: Expr::ImportPattern(pat), expr: Expr::ImportPattern(pat),
.. ..
}) = call.parser_info_nth(0) }) = call.get_parser_info("import_pattern")
{ {
pat pat
} else { } else {
@ -72,9 +72,12 @@ impl Command for Use {
let module_arg_str = String::from_utf8_lossy( let module_arg_str = String::from_utf8_lossy(
engine_state.get_span_contents(&import_pattern.head.span), engine_state.get_span_contents(&import_pattern.head.span),
); );
let maybe_parent = if let Some(path) = let maybe_parent = if let Some(path) = find_in_dirs_env(
find_in_dirs_env(&module_arg_str, engine_state, caller_stack)? &module_arg_str,
{ engine_state,
caller_stack,
get_dirs_var_from_call(call),
)? {
path.parent().map(|p| p.to_path_buf()).or(None) path.parent().map(|p| p.to_path_buf()).or(None)
} else { } else {
None None

View File

@ -45,7 +45,7 @@ impl Command for Source {
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
// Note: this hidden positional is the block_id that corresponded to the 0th position // Note: this hidden positional is the block_id that corresponded to the 0th position
// it is put here by the parser // it is put here by the parser
let block_id: i64 = call.req_parser_info(engine_state, stack, 0)?; let block_id: i64 = call.req_parser_info(engine_state, stack, "block_id")?;
let block = engine_state.get_block(block_id as usize).clone(); let block = engine_state.get_block(block_id as usize).clone();
eval_block_with_early_return( eval_block_with_early_return(

View File

@ -1,6 +1,8 @@
use std::path::PathBuf; use std::path::PathBuf;
use nu_engine::{eval_block_with_early_return, find_in_dirs_env, redirect_env, CallExt}; use nu_engine::{
eval_block_with_early_return, find_in_dirs_env, get_dirs_var_from_call, 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::{ use nu_protocol::{
@ -42,12 +44,15 @@ impl Command for SourceEnv {
// Note: this hidden positional is the block_id that corresponded to the 0th position // Note: this hidden positional is the block_id that corresponded to the 0th position
// it is put here by the parser // it is put here by the parser
let block_id: i64 = call.req_parser_info(engine_state, caller_stack, 0)?; let block_id: i64 = call.req_parser_info(engine_state, caller_stack, "block_id")?;
// Set the currently evaluated directory (file-relative PWD) // Set the currently evaluated directory (file-relative PWD)
let mut parent = if let Some(path) = let mut parent = if let Some(path) = find_in_dirs_env(
find_in_dirs_env(&source_filename.item, engine_state, caller_stack)? &source_filename.item,
{ engine_state,
caller_stack,
get_dirs_var_from_call(call),
)? {
PathBuf::from(&path) PathBuf::from(&path)
} else { } else {
return Err(ShellError::FileNotFound(source_filename.span)); return Err(ShellError::FileNotFound(source_filename.span));

View File

@ -1,4 +1,4 @@
use nu_engine::{find_in_dirs_env, CallExt}; use nu_engine::{find_in_dirs_env, get_dirs_var_from_call, CallExt};
use nu_parser::{parse, parse_module_block, unescape_unquote_string}; use nu_parser::{parse, parse_module_block, unescape_unquote_string};
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, StateWorkingSet};
@ -106,7 +106,12 @@ impl Command for NuCheck {
_ => { _ => {
if let Some(path_str) = path { if let Some(path_str) = path {
// look up the path as relative to FILE_PWD or inside NU_LIB_DIRS (same process as source-env) // look up the path as relative to FILE_PWD or inside NU_LIB_DIRS (same process as source-env)
let path = match find_in_dirs_env(&path_str.item, engine_state, stack) { let path = match find_in_dirs_env(
&path_str.item,
engine_state,
stack,
get_dirs_var_from_call(call),
) {
Ok(path) => { Ok(path) => {
if let Some(path) = path { if let Some(path) = path {
path path

View File

@ -39,7 +39,7 @@ pub trait CallExt {
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, stack: &mut Stack,
pos: usize, name: &str,
) -> Result<T, ShellError>; ) -> Result<T, ShellError>;
} }
@ -111,9 +111,9 @@ impl CallExt for Call {
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, stack: &mut Stack,
pos: usize, name: &str,
) -> Result<T, ShellError> { ) -> Result<T, ShellError> {
if let Some(expr) = self.parser_info_nth(pos) { if let Some(expr) = self.get_parser_info(name) {
let result = eval_expression(engine_state, stack, expr)?; let result = eval_expression(engine_state, stack, expr)?;
FromValue::from_value(&result) FromValue::from_value(&result)
} else if self.parser_info.is_empty() { } else if self.parser_info.is_empty() {

View File

@ -1,9 +1,9 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use nu_protocol::ast::PathMember; use nu_protocol::ast::{Call, Expr, PathMember};
use nu_protocol::engine::{EngineState, Stack}; use nu_protocol::engine::{EngineState, Stack};
use nu_protocol::{PipelineData, ShellError, Span, Value}; use nu_protocol::{PipelineData, ShellError, Span, Value, VarId};
use nu_path::canonicalize_with; use nu_path::canonicalize_with;
@ -18,8 +18,6 @@ const ENV_PATH_NAME: &str = "PATH";
const ENV_CONVERSIONS: &str = "ENV_CONVERSIONS"; const ENV_CONVERSIONS: &str = "ENV_CONVERSIONS";
static LIB_DIRS_ENV: &str = "NU_LIB_DIRS";
enum ConversionResult { enum ConversionResult {
Ok(Value), Ok(Value),
ConversionError(ShellError), // Failure during the conversion itself ConversionError(ShellError), // Failure during the conversion itself
@ -224,6 +222,17 @@ pub fn path_str(
env_to_string(pathname, &pathval, engine_state, stack) env_to_string(pathname, &pathval, engine_state, stack)
} }
pub const DIR_VAR_PARSER_INFO: &str = "dirs_var";
pub fn get_dirs_var_from_call(call: &Call) -> Option<VarId> {
call.get_parser_info(DIR_VAR_PARSER_INFO).and_then(|x| {
if let Expr::Var(id) = x.expr {
Some(id)
} else {
None
}
})
}
/// This helper function is used to find files during eval /// This helper function is used to find files during eval
/// ///
/// First, the actual current working directory is selected as /// First, the actual current working directory is selected as
@ -239,6 +248,7 @@ pub fn find_in_dirs_env(
filename: &str, filename: &str,
engine_state: &EngineState, engine_state: &EngineState,
stack: &Stack, stack: &Stack,
dirs_var: Option<VarId>,
) -> Result<Option<PathBuf>, ShellError> { ) -> Result<Option<PathBuf>, ShellError> {
// Choose whether to use file-relative or PWD-relative path // Choose whether to use file-relative or PWD-relative path
let cwd = if let Some(pwd) = stack.get_env_var(engine_state, "FILE_PWD") { let cwd = if let Some(pwd) = stack.get_env_var(engine_state, "FILE_PWD") {
@ -262,36 +272,32 @@ pub fn find_in_dirs_env(
current_dir_str(engine_state, stack)? current_dir_str(engine_state, stack)?
}; };
if let Ok(p) = canonicalize_with(filename, &cwd) { Ok((|| -> Option<PathBuf> {
Ok(Some(p)) if let Ok(p) = canonicalize_with(filename, &cwd) {
} else { return Some(p);
let path = Path::new(filename);
if path.is_relative() {
if let Some(lib_dirs) = stack.get_env_var(engine_state, LIB_DIRS_ENV) {
if let Ok(dirs) = lib_dirs.as_list() {
for lib_dir in dirs {
if let Ok(dir) = lib_dir.as_path() {
// make sure the dir is absolute path
if let Ok(dir_abs) = canonicalize_with(dir, &cwd) {
if let Ok(path) = canonicalize_with(filename, dir_abs) {
return Ok(Some(path));
}
}
}
}
Ok(None)
} else {
Ok(None)
}
} else {
Ok(None)
}
} else {
Ok(None)
} }
} let path = Path::new(filename);
if !path.is_relative() {
return None;
}
let lib_dirs = dirs_var.and_then(|dirs_var| engine_state.find_constant(dirs_var, &[]));
// TODO: remove (see #8310)
let lib_dirs_fallback = stack.get_env_var(engine_state, "NU_LIB_DIRS");
let lib_dirs = lib_dirs.or(lib_dirs_fallback.as_ref())?;
lib_dirs
.as_list()
.ok()?
.iter()
.map(|lib_dir| -> Option<PathBuf> {
let dir = lib_dir.as_path().ok()?;
let dir_abs = canonicalize_with(dir, &cwd).ok()?;
canonicalize_with(filename, dir_abs).ok()
})
.find(Option::is_some)
.flatten()
})())
} }
fn get_converted_value( fn get_converted_value(

View File

@ -828,7 +828,7 @@ pub fn eval_element_with_input(
], ],
redirect_stdout: false, redirect_stdout: false,
redirect_stderr: false, redirect_stderr: false,
parser_info: vec![], parser_info: HashMap::new(),
}, },
input, input,
) )
@ -899,7 +899,7 @@ pub fn eval_element_with_input(
], ],
redirect_stdout: false, redirect_stdout: false,
redirect_stderr: false, redirect_stderr: false,
parser_info: vec![], parser_info: HashMap::new(),
}, },
input, input,
) )

View File

@ -1,4 +1,5 @@
use log::trace; use log::trace;
use nu_engine::get_dirs_var_from_call;
use nu_path::canonicalize_with; use nu_path::canonicalize_with;
use nu_protocol::{ use nu_protocol::{
ast::{ ast::{
@ -7,13 +8,14 @@ use nu_protocol::{
}, },
engine::{StateWorkingSet, DEFAULT_OVERLAY_NAME}, engine::{StateWorkingSet, DEFAULT_OVERLAY_NAME},
span, Alias, BlockId, Exportable, Module, PositionalArg, Span, Spanned, SyntaxShape, Type, span, Alias, BlockId, Exportable, Module, PositionalArg, Span, Spanned, SyntaxShape, Type,
VarId,
}; };
use std::collections::HashSet; use std::collections::{HashMap, HashSet};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
static LIB_DIRS_ENV: &str = "NU_LIB_DIRS"; pub const LIB_DIRS_VAR: &str = "NU_LIB_DIRS";
#[cfg(feature = "plugin")] #[cfg(feature = "plugin")]
static PLUGIN_DIRS_ENV: &str = "NU_PLUGIN_DIRS"; pub const PLUGIN_DIRS_VAR: &str = "NU_PLUGIN_DIRS";
use crate::{ use crate::{
eval::{eval_constant, value_as_string}, eval::{eval_constant, value_as_string},
@ -962,7 +964,7 @@ pub fn parse_old_alias(
decl_id, decl_id,
redirect_stdout: true, redirect_stdout: true,
redirect_stderr: false, redirect_stderr: false,
parser_info: vec![], parser_info: HashMap::new(),
})); }));
return ( return (
Pipeline::from_vec(vec![Expression { Pipeline::from_vec(vec![Expression {
@ -1263,7 +1265,7 @@ pub fn parse_export_in_module(
arguments: vec![], arguments: vec![],
redirect_stdout: true, redirect_stdout: true,
redirect_stderr: false, redirect_stderr: false,
parser_info: vec![], parser_info: HashMap::new(),
}); });
let exportables = if let Some(kw_span) = spans.get(1) { let exportables = if let Some(kw_span) = spans.get(1) {
@ -2087,7 +2089,7 @@ pub fn parse_module(
], ],
redirect_stdout: true, redirect_stdout: true,
redirect_stderr: false, redirect_stderr: false,
parser_info: vec![], parser_info: HashMap::new(),
}); });
( (
@ -2229,9 +2231,12 @@ pub fn parse_use(
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_with_id(
find_in_dirs(&module_filename, working_set, &cwd, LIB_DIRS_ENV) &module_filename,
{ working_set,
&cwd,
get_dirs_var_from_call(&call),
) {
if let Some(i) = working_set if let Some(i) = working_set
.parsed_module_files .parsed_module_files
.iter() .iter()
@ -2444,7 +2449,7 @@ pub fn parse_use(
}; };
let mut call = call; let mut call = call;
call.add_parser_info(import_pattern_expr); call.set_parser_info("import_pattern".to_string(), import_pattern_expr);
( (
Pipeline::from_vec(vec![Expression { Pipeline::from_vec(vec![Expression {
@ -2665,7 +2670,7 @@ pub fn parse_hide(
}; };
let mut call = call; let mut call = call;
call.add_parser_info(import_pattern_expr); call.set_parser_info("import_pattern".to_string(), import_pattern_expr);
( (
Pipeline::from_vec(vec![Expression { Pipeline::from_vec(vec![Expression {
@ -2889,9 +2894,12 @@ pub fn parse_overlay_use(
if let Ok(module_filename) = if let Ok(module_filename) =
String::from_utf8(trim_quotes(overlay_name.as_bytes()).to_vec()) String::from_utf8(trim_quotes(overlay_name.as_bytes()).to_vec())
{ {
if let Some(module_path) = if let Some(module_path) = find_in_dirs_with_id(
find_in_dirs(&module_filename, working_set, &cwd, LIB_DIRS_ENV) &module_filename,
{ working_set,
&cwd,
get_dirs_var_from_call(&call),
) {
let overlay_name = if let Some(stem) = module_path.file_stem() { let overlay_name = if let Some(stem) = module_path.file_stem() {
stem.to_string_lossy().to_string() stem.to_string_lossy().to_string()
} else { } else {
@ -2982,16 +2990,19 @@ pub fn parse_overlay_use(
// Change the call argument to include the Overlay expression with the module ID // Change the call argument to include the Overlay expression with the module ID
let mut call = call; let mut call = call;
call.add_parser_info(Expression { call.set_parser_info(
expr: Expr::Overlay(if is_module_updated { "overlay_expr".to_string(),
Some(origin_module_id) Expression {
} else { expr: Expr::Overlay(if is_module_updated {
None Some(origin_module_id)
}), } else {
span: overlay_name_span, None
ty: Type::Any, }),
custom_completion: None, span: overlay_name_span,
}); ty: Type::Any,
custom_completion: None,
},
);
let pipeline = Pipeline::from_vec(vec![Expression { let pipeline = Pipeline::from_vec(vec![Expression {
expr: Expr::Call(call), expr: Expr::Call(call),
@ -3160,7 +3171,7 @@ pub fn parse_let_or_const(
], ],
redirect_stdout: true, redirect_stdout: true,
redirect_stderr: false, redirect_stderr: false,
parser_info: vec![], parser_info: HashMap::new(),
}); });
return ( return (
@ -3282,7 +3293,7 @@ pub fn parse_mut(
], ],
redirect_stdout: true, redirect_stdout: true,
redirect_stderr: false, redirect_stderr: false,
parser_info: vec![], parser_info: HashMap::new(),
}); });
return ( return (
@ -3411,7 +3422,7 @@ pub fn parse_source(
} }
}; };
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_VAR) {
if let Ok(contents) = std::fs::read(&path) { if let Ok(contents) = std::fs::read(&path) {
// Change currently parsed directory // Change currently parsed directory
let prev_currently_parsed_cwd = if let Some(parent) = path.parent() { let prev_currently_parsed_cwd = if let Some(parent) = path.parent() {
@ -3457,12 +3468,15 @@ pub fn parse_source(
// FIXME: 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_parser_info(Expression { call_with_block.set_parser_info(
expr: Expr::Int(block_id as i64), "block_id".to_string(),
span: spans[1], Expression {
ty: Type::Any, expr: Expr::Int(block_id as i64),
custom_completion: None, span: spans[1],
}); ty: Type::Any,
custom_completion: None,
},
);
return ( return (
Pipeline::from_vec(vec![Expression { Pipeline::from_vec(vec![Expression {
@ -3672,7 +3686,7 @@ pub fn parse_register(
if let Some(err) = err { if let Some(err) = err {
Err(err) Err(err)
} else { } else {
let path = if let Some(p) = find_in_dirs(&name, working_set, &cwd, PLUGIN_DIRS_ENV) let path = if let Some(p) = find_in_dirs(&name, working_set, &cwd, PLUGIN_DIRS_VAR)
{ {
p p
} else { } else {
@ -3817,12 +3831,67 @@ pub fn parse_register(
/// a) the directory of a file currently being parsed /// a) the directory of a file currently being parsed
/// b) current working directory (PWD) /// b) current working directory (PWD)
/// ///
/// Then, if the file is not found in the actual cwd, NU_LIB_DIRS is checked. /// Then, if the file is not found in the actual cwd, dirs_var is checked.
/// If there is a relative path in NU_LIB_DIRS, it is assumed to be relative to the actual cwd /// If dirs_var is an Expr::Var, then we look for a const with that VarId,
/// and if dirs_var is an Expr::String, then we look for an environment with that name.
/// If there is a relative path in dirs_var, it is assumed to be relative to the actual cwd
/// determined in the first step. /// determined in the first step.
/// ///
/// Always returns an absolute path /// Always returns an absolute path
pub fn find_in_dirs_with_id(
filename: &str,
working_set: &StateWorkingSet,
cwd: &str,
dirs_var_id: Option<VarId>,
) -> Option<PathBuf> {
// Choose whether to use file-relative or PWD-relative path
let actual_cwd = if let Some(currently_parsed_cwd) = &working_set.currently_parsed_cwd {
currently_parsed_cwd.as_path()
} else {
Path::new(cwd)
};
if let Ok(p) = canonicalize_with(filename, actual_cwd) {
return Some(p);
}
let path = Path::new(filename);
if !path.is_relative() {
return None;
}
working_set
.find_constant(dirs_var_id?)?
.as_list()
.ok()?
.iter()
.map(|lib_dir| -> Option<PathBuf> {
let dir = lib_dir.as_path().ok()?;
let dir_abs = canonicalize_with(dir, actual_cwd).ok()?;
canonicalize_with(filename, dir_abs).ok()
})
.find(Option::is_some)
.flatten()
}
pub fn find_dirs_var(working_set: &StateWorkingSet, var_name: &str) -> Option<VarId> {
working_set
.find_variable(format!("${}", var_name).as_bytes())
.filter(|var_id| working_set.find_constant(*var_id).is_some())
}
pub fn find_in_dirs( pub fn find_in_dirs(
filename: &str,
working_set: &StateWorkingSet,
cwd: &str,
dirs_var_name: &str,
) -> Option<PathBuf> {
find_dirs_var(working_set, dirs_var_name)
.and_then(|var_id| find_in_dirs_with_id(filename, working_set, cwd, Some(var_id)))
.or_else(|| find_in_dirs_old(filename, working_set, cwd, dirs_var_name))
}
// TODO: remove (see #8310)
pub fn find_in_dirs_old(
filename: &str, filename: &str,
working_set: &StateWorkingSet, working_set: &StateWorkingSet,
cwd: &str, cwd: &str,

View File

@ -7,6 +7,7 @@ use crate::{
ParseError, Token, TokenContents, ParseError, Token, TokenContents,
}; };
use nu_engine::DIR_VAR_PARSER_INFO;
use nu_protocol::{ use nu_protocol::{
ast::{ ast::{
Argument, Assignment, Bits, Block, Boolean, Call, CellPath, Comparison, Expr, Expression, Argument, Assignment, Bits, Block, Boolean, Call, CellPath, Comparison, Expr, Expression,
@ -19,10 +20,10 @@ use nu_protocol::{
}; };
use crate::parse_keywords::{ use crate::parse_keywords::{
is_unaliasable_parser_keyword, parse_alias, parse_def, parse_def_predecl, find_dirs_var, is_unaliasable_parser_keyword, parse_alias, parse_def, parse_def_predecl,
parse_export_in_block, parse_extern, parse_for, parse_hide, parse_keyword, parse_let_or_const, parse_export_in_block, parse_extern, parse_for, parse_hide, parse_keyword, parse_let_or_const,
parse_module, parse_old_alias, parse_overlay_hide, parse_overlay_new, parse_overlay_use, parse_module, parse_old_alias, parse_overlay_hide, parse_overlay_new, parse_overlay_use,
parse_source, parse_use, parse_where, parse_where_expr, parse_source, parse_use, parse_where, parse_where_expr, LIB_DIRS_VAR,
}; };
use itertools::Itertools; use itertools::Itertools;
@ -781,6 +782,25 @@ pub struct ParsedInternalCall {
pub error: Option<ParseError>, pub error: Option<ParseError>,
} }
fn attach_parser_info_builtin(working_set: &StateWorkingSet, name: &str, call: &mut Call) {
match name {
"use" | "overlay use" | "source-env" | "nu-check" => {
if let Some(var_id) = find_dirs_var(working_set, LIB_DIRS_VAR) {
call.set_parser_info(
DIR_VAR_PARSER_INFO.to_owned(),
Expression {
expr: Expr::Var(var_id),
span: call.head,
ty: Type::Any,
custom_completion: None,
},
);
}
}
_ => {}
}
}
pub fn parse_internal_call( pub fn parse_internal_call(
working_set: &mut StateWorkingSet, working_set: &mut StateWorkingSet,
command_span: Span, command_span: Span,
@ -800,6 +820,10 @@ pub fn parse_internal_call(
let signature = decl.signature(); let signature = decl.signature();
let output = signature.output_type.clone(); let output = signature.output_type.clone();
if decl.is_builtin() {
attach_parser_info_builtin(working_set, decl.name(), &mut call);
}
// The index into the positional parameter in the definition // The index into the positional parameter in the definition
let mut positional_idx = 0; let mut positional_idx = 0;
@ -5312,7 +5336,7 @@ pub fn parse_expression(
arguments, arguments,
redirect_stdout: true, redirect_stdout: true,
redirect_stderr: false, redirect_stderr: false,
parser_info: vec![], parser_info: HashMap::new(),
})); }));
( (
@ -6158,7 +6182,7 @@ fn wrap_expr_with_collect(working_set: &mut StateWorkingSet, expr: &Expression)
decl_id, decl_id,
redirect_stdout: true, redirect_stdout: true,
redirect_stderr: false, redirect_stderr: false,
parser_info: vec![], parser_info: HashMap::new(),
})), })),
span, span,
ty: Type::String, ty: Type::String,

View File

@ -1,3 +1,5 @@
use std::collections::HashMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use super::Expression; use super::Expression;
@ -19,7 +21,7 @@ pub struct Call {
pub redirect_stdout: bool, pub redirect_stdout: bool,
pub redirect_stderr: bool, pub redirect_stderr: bool,
/// this field is used by the parser to pass additional command-specific information /// this field is used by the parser to pass additional command-specific information
pub parser_info: Vec<Expression>, pub parser_info: HashMap<String, Expression>,
} }
impl Call { impl Call {
@ -30,7 +32,7 @@ impl Call {
arguments: vec![], arguments: vec![],
redirect_stdout: true, redirect_stdout: true,
redirect_stderr: false, redirect_stderr: false,
parser_info: vec![], parser_info: HashMap::new(),
} }
} }
@ -70,10 +72,6 @@ impl Call {
self.arguments.push(Argument::Positional(positional)); self.arguments.push(Argument::Positional(positional));
} }
pub fn add_parser_info(&mut self, info: Expression) {
self.parser_info.push(info);
}
pub fn add_unknown(&mut self, unknown: Expression) { pub fn add_unknown(&mut self, unknown: Expression) {
self.arguments.push(Argument::Unknown(unknown)); self.arguments.push(Argument::Unknown(unknown));
} }
@ -106,8 +104,12 @@ impl Call {
self.positional_iter().count() self.positional_iter().count()
} }
pub fn parser_info_nth(&self, i: usize) -> Option<&Expression> { pub fn get_parser_info(&self, name: &str) -> Option<&Expression> {
self.parser_info.get(i) self.parser_info.get(name)
}
pub fn set_parser_info(&mut self, name: String, val: Expression) -> Option<Expression> {
self.parser_info.insert(name, val)
} }
pub fn has_flag(&self, flag_name: &str) -> bool { pub fn has_flag(&self, flag_name: &str) -> bool {

View File

@ -140,6 +140,34 @@ fn nu_lib_dirs_relative_repl() {
}) })
} }
// TODO: add absolute path tests after we expand const capabilities (see #8310)
#[test]
fn const_nu_lib_dirs_relative() {
Playground::setup("const_nu_lib_dirs_relative", |dirs, sandbox| {
sandbox
.mkdir("scripts")
.with_files(vec![FileWithContentToBeTrimmed(
"scripts/foo.nu",
r#"
let-env FOO = "foo"
"#,
)])
.with_files(vec![FileWithContentToBeTrimmed(
"main.nu",
r#"
const NU_LIB_DIRS = [ 'scripts' ]
source-env foo.nu
$env.FOO
"#,
)]);
let outcome = nu!(cwd: dirs.test(), "source main.nu");
assert!(outcome.err.is_empty());
assert_eq!(outcome.out, "foo");
})
}
#[test] #[test]
fn nu_lib_dirs_relative_script() { fn nu_lib_dirs_relative_script() {
Playground::setup("nu_lib_dirs_relative_script", |dirs, sandbox| { Playground::setup("nu_lib_dirs_relative_script", |dirs, sandbox| {