From 400a9d3b1eb4ec0e2383604257a71df807d36824 Mon Sep 17 00:00:00 2001 From: StevenDoesStuffs Date: Fri, 17 Mar 2023 07:23:29 -0500 Subject: [PATCH] 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. --- .../src/completions/custom_completions.rs | 4 +- .../src/core_commands/overlay/use_.rs | 38 +++-- crates/nu-cmd-lang/src/core_commands/use_.rs | 13 +- crates/nu-command/src/deprecated/source.rs | 2 +- crates/nu-command/src/env/source_env.rs | 15 +- crates/nu-command/src/system/nu_check.rs | 9 +- crates/nu-engine/src/call_ext.rs | 6 +- crates/nu-engine/src/env.rs | 72 +++++---- crates/nu-engine/src/eval.rs | 4 +- crates/nu-parser/src/parse_keywords.rs | 141 +++++++++++++----- crates/nu-parser/src/parser.rs | 32 +++- crates/nu-protocol/src/ast/call.rs | 18 ++- tests/shell/mod.rs | 28 ++++ 13 files changed, 265 insertions(+), 117 deletions(-) diff --git a/crates/nu-cli/src/completions/custom_completions.rs b/crates/nu-cli/src/completions/custom_completions.rs index 950685d45..6c51a6bef 100644 --- a/crates/nu-cli/src/completions/custom_completions.rs +++ b/crates/nu-cli/src/completions/custom_completions.rs @@ -6,7 +6,7 @@ use nu_protocol::{ PipelineData, Span, Type, Value, }; use reedline::Suggestion; -use std::sync::Arc; +use std::{collections::HashMap, sync::Arc}; use super::completer::map_value_completions; @@ -66,7 +66,7 @@ impl Completer for CustomCompletion { ], redirect_stdout: true, redirect_stderr: true, - parser_info: vec![], + parser_info: HashMap::new(), }, PipelineData::empty(), ); diff --git a/crates/nu-cmd-lang/src/core_commands/overlay/use_.rs b/crates/nu-cmd-lang/src/core_commands/overlay/use_.rs index 4d546abec..e64acbae8 100644 --- a/crates/nu-cmd-lang/src/core_commands/overlay/use_.rs +++ b/crates/nu-cmd-lang/src/core_commands/overlay/use_.rs @@ -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_protocol::ast::{Call, Expr}; use nu_protocol::engine::{Command, EngineState, Stack}; @@ -66,23 +66,24 @@ impl Command for OverlayUse { let mut name_arg: Spanned = call.req(engine_state, caller_stack, 0)?; 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) { - if let Expr::Overlay(module_id) = overlay_expr.expr { - module_id + let maybe_origin_module_id = + if let Some(overlay_expr) = call.get_parser_info("overlay_expr") { + 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 { return Err(ShellError::NushellFailedSpanned { - msg: "Not an overlay".to_string(), - label: "requires an overlay (path or a string)".to_string(), - span: overlay_expr.span, + msg: "Missing positional".to_string(), + label: "missing required overlay".to_string(), + 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)? { name @@ -113,7 +114,12 @@ impl Command for OverlayUse { // Evaluate the export-env block (if any) and keep its environment 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 mut callee_stack = caller_stack.gather_captures(&block.captures); diff --git a/crates/nu-cmd-lang/src/core_commands/use_.rs b/crates/nu-cmd-lang/src/core_commands/use_.rs index 7b4afbeee..5bfb0b920 100644 --- a/crates/nu-cmd-lang/src/core_commands/use_.rs +++ b/crates/nu-cmd-lang/src/core_commands/use_.rs @@ -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::engine::{Command, EngineState, Stack}; use nu_protocol::{ @@ -48,7 +48,7 @@ impl Command for Use { let import_pattern = if let Some(Expression { expr: Expr::ImportPattern(pat), .. - }) = call.parser_info_nth(0) + }) = call.get_parser_info("import_pattern") { pat } else { @@ -72,9 +72,12 @@ impl Command for Use { let module_arg_str = String::from_utf8_lossy( engine_state.get_span_contents(&import_pattern.head.span), ); - let maybe_parent = if let Some(path) = - find_in_dirs_env(&module_arg_str, engine_state, caller_stack)? - { + let maybe_parent = if let Some(path) = find_in_dirs_env( + &module_arg_str, + engine_state, + caller_stack, + get_dirs_var_from_call(call), + )? { path.parent().map(|p| p.to_path_buf()).or(None) } else { None diff --git a/crates/nu-command/src/deprecated/source.rs b/crates/nu-command/src/deprecated/source.rs index 5a29828dd..904b00039 100644 --- a/crates/nu-command/src/deprecated/source.rs +++ b/crates/nu-command/src/deprecated/source.rs @@ -45,7 +45,7 @@ impl Command for Source { ) -> Result { // Note: this hidden positional is the block_id that corresponded to the 0th position // 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(); eval_block_with_early_return( diff --git a/crates/nu-command/src/env/source_env.rs b/crates/nu-command/src/env/source_env.rs index 3edabac17..c77008b69 100644 --- a/crates/nu-command/src/env/source_env.rs +++ b/crates/nu-command/src/env/source_env.rs @@ -1,6 +1,8 @@ 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::engine::{Command, EngineState, Stack}; 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 // 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) - let mut parent = if let Some(path) = - find_in_dirs_env(&source_filename.item, engine_state, caller_stack)? - { + let mut parent = if let Some(path) = find_in_dirs_env( + &source_filename.item, + engine_state, + caller_stack, + get_dirs_var_from_call(call), + )? { PathBuf::from(&path) } else { return Err(ShellError::FileNotFound(source_filename.span)); diff --git a/crates/nu-command/src/system/nu_check.rs b/crates/nu-command/src/system/nu_check.rs index 2566d6c5d..f9ea8e220 100644 --- a/crates/nu-command/src/system/nu_check.rs +++ b/crates/nu-command/src/system/nu_check.rs @@ -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_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack, StateWorkingSet}; @@ -106,7 +106,12 @@ impl Command for NuCheck { _ => { 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) - 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) => { if let Some(path) = path { path diff --git a/crates/nu-engine/src/call_ext.rs b/crates/nu-engine/src/call_ext.rs index 7bdab98d8..d3c543aab 100644 --- a/crates/nu-engine/src/call_ext.rs +++ b/crates/nu-engine/src/call_ext.rs @@ -39,7 +39,7 @@ pub trait CallExt { &self, engine_state: &EngineState, stack: &mut Stack, - pos: usize, + name: &str, ) -> Result; } @@ -111,9 +111,9 @@ impl CallExt for Call { &self, engine_state: &EngineState, stack: &mut Stack, - pos: usize, + name: &str, ) -> Result { - 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)?; FromValue::from_value(&result) } else if self.parser_info.is_empty() { diff --git a/crates/nu-engine/src/env.rs b/crates/nu-engine/src/env.rs index 3a0436639..8615ed3cc 100644 --- a/crates/nu-engine/src/env.rs +++ b/crates/nu-engine/src/env.rs @@ -1,9 +1,9 @@ use std::collections::HashMap; 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::{PipelineData, ShellError, Span, Value}; +use nu_protocol::{PipelineData, ShellError, Span, Value, VarId}; use nu_path::canonicalize_with; @@ -18,8 +18,6 @@ const ENV_PATH_NAME: &str = "PATH"; const ENV_CONVERSIONS: &str = "ENV_CONVERSIONS"; -static LIB_DIRS_ENV: &str = "NU_LIB_DIRS"; - enum ConversionResult { Ok(Value), ConversionError(ShellError), // Failure during the conversion itself @@ -224,6 +222,17 @@ pub fn path_str( 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 { + 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 /// /// First, the actual current working directory is selected as @@ -239,6 +248,7 @@ pub fn find_in_dirs_env( filename: &str, engine_state: &EngineState, stack: &Stack, + dirs_var: Option, ) -> Result, ShellError> { // Choose whether to use file-relative or PWD-relative path 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)? }; - if let Ok(p) = canonicalize_with(filename, &cwd) { - Ok(Some(p)) - } else { - 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) + Ok((|| -> Option { + if let Ok(p) = canonicalize_with(filename, &cwd) { + return Some(p); } - } + 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 { + 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( diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 57a705148..e8b30b92f 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -828,7 +828,7 @@ pub fn eval_element_with_input( ], redirect_stdout: false, redirect_stderr: false, - parser_info: vec![], + parser_info: HashMap::new(), }, input, ) @@ -899,7 +899,7 @@ pub fn eval_element_with_input( ], redirect_stdout: false, redirect_stderr: false, - parser_info: vec![], + parser_info: HashMap::new(), }, input, ) diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index ddf00bd2f..65f83e1e1 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -1,4 +1,5 @@ use log::trace; +use nu_engine::get_dirs_var_from_call; use nu_path::canonicalize_with; use nu_protocol::{ ast::{ @@ -7,13 +8,14 @@ use nu_protocol::{ }, engine::{StateWorkingSet, DEFAULT_OVERLAY_NAME}, 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}; -static LIB_DIRS_ENV: &str = "NU_LIB_DIRS"; +pub const LIB_DIRS_VAR: &str = "NU_LIB_DIRS"; #[cfg(feature = "plugin")] -static PLUGIN_DIRS_ENV: &str = "NU_PLUGIN_DIRS"; +pub const PLUGIN_DIRS_VAR: &str = "NU_PLUGIN_DIRS"; use crate::{ eval::{eval_constant, value_as_string}, @@ -962,7 +964,7 @@ pub fn parse_old_alias( decl_id, redirect_stdout: true, redirect_stderr: false, - parser_info: vec![], + parser_info: HashMap::new(), })); return ( Pipeline::from_vec(vec![Expression { @@ -1263,7 +1265,7 @@ pub fn parse_export_in_module( arguments: vec![], redirect_stdout: true, redirect_stderr: false, - parser_info: vec![], + parser_info: HashMap::new(), }); let exportables = if let Some(kw_span) = spans.get(1) { @@ -2087,7 +2089,7 @@ pub fn parse_module( ], redirect_stdout: true, 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); if err.is_none() { - if let Some(module_path) = - find_in_dirs(&module_filename, working_set, &cwd, LIB_DIRS_ENV) - { + if let Some(module_path) = find_in_dirs_with_id( + &module_filename, + working_set, + &cwd, + get_dirs_var_from_call(&call), + ) { if let Some(i) = working_set .parsed_module_files .iter() @@ -2444,7 +2449,7 @@ pub fn parse_use( }; 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 { @@ -2665,7 +2670,7 @@ pub fn parse_hide( }; 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 { @@ -2889,9 +2894,12 @@ pub fn parse_overlay_use( 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) - { + if let Some(module_path) = find_in_dirs_with_id( + &module_filename, + working_set, + &cwd, + get_dirs_var_from_call(&call), + ) { let overlay_name = if let Some(stem) = module_path.file_stem() { stem.to_string_lossy().to_string() } else { @@ -2982,16 +2990,19 @@ pub fn parse_overlay_use( // Change the call argument to include the Overlay expression with the module ID let mut call = call; - call.add_parser_info(Expression { - expr: Expr::Overlay(if is_module_updated { - Some(origin_module_id) - } else { - None - }), - span: overlay_name_span, - ty: Type::Any, - custom_completion: None, - }); + call.set_parser_info( + "overlay_expr".to_string(), + Expression { + expr: Expr::Overlay(if is_module_updated { + Some(origin_module_id) + } else { + None + }), + span: overlay_name_span, + ty: Type::Any, + custom_completion: None, + }, + ); let pipeline = Pipeline::from_vec(vec![Expression { expr: Expr::Call(call), @@ -3160,7 +3171,7 @@ pub fn parse_let_or_const( ], redirect_stdout: true, redirect_stderr: false, - parser_info: vec![], + parser_info: HashMap::new(), }); return ( @@ -3282,7 +3293,7 @@ pub fn parse_mut( ], redirect_stdout: true, redirect_stderr: false, - parser_info: vec![], + parser_info: HashMap::new(), }); 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) { // Change currently parsed directory 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 // after writing `source example.nu` - call_with_block.add_parser_info(Expression { - expr: Expr::Int(block_id as i64), - span: spans[1], - ty: Type::Any, - custom_completion: None, - }); + call_with_block.set_parser_info( + "block_id".to_string(), + Expression { + expr: Expr::Int(block_id as i64), + span: spans[1], + ty: Type::Any, + custom_completion: None, + }, + ); return ( Pipeline::from_vec(vec![Expression { @@ -3672,7 +3686,7 @@ pub fn parse_register( if let Some(err) = err { Err(err) } 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 } else { @@ -3817,12 +3831,67 @@ pub fn parse_register( /// a) the directory of a file currently being parsed /// b) current working directory (PWD) /// -/// Then, if the file is not found in the actual cwd, NU_LIB_DIRS is checked. -/// If there is a relative path in NU_LIB_DIRS, it is assumed to be relative to the actual cwd +/// Then, if the file is not found in the actual cwd, dirs_var is checked. +/// 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. /// /// Always returns an absolute path +pub fn find_in_dirs_with_id( + filename: &str, + working_set: &StateWorkingSet, + cwd: &str, + dirs_var_id: Option, +) -> Option { + // 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 { + 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 { + 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( + filename: &str, + working_set: &StateWorkingSet, + cwd: &str, + dirs_var_name: &str, +) -> Option { + 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, working_set: &StateWorkingSet, cwd: &str, diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index beb6777a6..f7844be96 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -7,6 +7,7 @@ use crate::{ ParseError, Token, TokenContents, }; +use nu_engine::DIR_VAR_PARSER_INFO; use nu_protocol::{ ast::{ Argument, Assignment, Bits, Block, Boolean, Call, CellPath, Comparison, Expr, Expression, @@ -19,10 +20,10 @@ use nu_protocol::{ }; 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_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; @@ -781,6 +782,25 @@ pub struct ParsedInternalCall { pub error: Option, } +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( working_set: &mut StateWorkingSet, command_span: Span, @@ -800,6 +820,10 @@ pub fn parse_internal_call( let signature = decl.signature(); 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 let mut positional_idx = 0; @@ -5312,7 +5336,7 @@ pub fn parse_expression( arguments, redirect_stdout: true, 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, redirect_stdout: true, redirect_stderr: false, - parser_info: vec![], + parser_info: HashMap::new(), })), span, ty: Type::String, diff --git a/crates/nu-protocol/src/ast/call.rs b/crates/nu-protocol/src/ast/call.rs index 2ae2e9beb..de51617ba 100644 --- a/crates/nu-protocol/src/ast/call.rs +++ b/crates/nu-protocol/src/ast/call.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use serde::{Deserialize, Serialize}; use super::Expression; @@ -19,7 +21,7 @@ pub struct Call { pub redirect_stdout: bool, pub redirect_stderr: bool, /// this field is used by the parser to pass additional command-specific information - pub parser_info: Vec, + pub parser_info: HashMap, } impl Call { @@ -30,7 +32,7 @@ impl Call { arguments: vec![], redirect_stdout: true, redirect_stderr: false, - parser_info: vec![], + parser_info: HashMap::new(), } } @@ -70,10 +72,6 @@ impl Call { 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) { self.arguments.push(Argument::Unknown(unknown)); } @@ -106,8 +104,12 @@ impl Call { self.positional_iter().count() } - pub fn parser_info_nth(&self, i: usize) -> Option<&Expression> { - self.parser_info.get(i) + pub fn get_parser_info(&self, name: &str) -> Option<&Expression> { + self.parser_info.get(name) + } + + pub fn set_parser_info(&mut self, name: String, val: Expression) -> Option { + self.parser_info.insert(name, val) } pub fn has_flag(&self, flag_name: &str) -> bool { diff --git a/tests/shell/mod.rs b/tests/shell/mod.rs index 91406a82d..d629b4bb4 100644 --- a/tests/shell/mod.rs +++ b/tests/shell/mod.rs @@ -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] fn nu_lib_dirs_relative_script() { Playground::setup("nu_lib_dirs_relative_script", |dirs, sandbox| {