From c73d8d5f954c8a88c3f968cce4ff639025ad27b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Sat, 12 Mar 2022 22:12:15 +0200 Subject: [PATCH] Add LIB_DIRS and PLUGIN_DIRS (#4829) * Add LIB_DIRS and PLUGIN_DIRS * Put plugin dirs behind plugin feature --- crates/nu-parser/src/parse_keywords.rs | 59 +++++++++++++++++-- crates/nu-protocol/src/engine/engine_state.rs | 8 ++- docs/sample_config/default_config.nu | 16 ++++- 3 files changed, 75 insertions(+), 8 deletions(-) diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index d1ca01d9e..b3516780f 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -8,6 +8,11 @@ use nu_protocol::{ span, Exportable, Overlay, PositionalArg, Span, SyntaxShape, Type, CONFIG_VARIABLE_ID, }; use std::collections::HashSet; +use std::path::{Path, PathBuf}; + +static LIB_DIRS_ENV: &str = "NU_LIB_DIRS"; +#[cfg(feature = "plugin")] +static PLUGIN_DIRS_ENV: &str = "NU_PLUGIN_DIRS"; use crate::{ known_external::KnownExternal, @@ -1097,7 +1102,9 @@ pub fn parse_use( if let Ok(module_filename) = String::from_utf8(trim_quotes(&import_pattern.head.name).to_vec()) { - if let Ok(module_path) = canonicalize_with(&module_filename, cwd) { + if let Some(module_path) = + find_in_dirs(&module_filename, working_set, &cwd, LIB_DIRS_ENV) + { let module_name = if let Some(stem) = module_path.file_stem() { stem.to_string_lossy().to_string() } else { @@ -1561,7 +1568,7 @@ pub fn parse_source( let name_expr = working_set.get_span_contents(spans[1]); let name_expr = trim_quotes(name_expr); if let Ok(filename) = String::from_utf8(name_expr.to_vec()) { - if let Ok(path) = canonicalize_with(&filename, cwd) { + if let Some(path) = find_in_dirs(&filename, working_set, &cwd, LIB_DIRS_ENV) { if let Ok(contents) = std::fs::read(&path) { // This will load the defs from the file into the // working set, if it was a successful parse. @@ -1697,7 +1704,6 @@ pub fn parse_register( // Extracting the required arguments from the call and keeping them together in a tuple // The ? operator is not used because the error has to be kept to be printed in the shell // For that reason the values are kept in a result that will be passed at the end of this call - let cwd_clone = cwd.clone(); let arguments = call .positional .get(0) @@ -1707,9 +1713,12 @@ pub fn parse_register( let name_expr = trim_quotes(name_expr); String::from_utf8(name_expr.to_vec()) .map_err(|_| ParseError::NonUtf8(expr.span)) - .and_then(move |name| { - canonicalize_with(&name, cwd_clone) - .map_err(|_| ParseError::FileNotFound(name, expr.span)) + .and_then(|name| { + if let Some(p) = find_in_dirs(&name, working_set, &cwd, PLUGIN_DIRS_ENV) { + Ok(p) + } else { + Err(ParseError::FileNotFound(name, expr.span)) + } }) .and_then(|path| { if path.exists() & path.is_file() { @@ -1832,3 +1841,41 @@ pub fn parse_register( error, ) } + +fn find_in_dirs( + filename: &str, + working_set: &StateWorkingSet, + cwd: &str, + dirs_env: &str, +) -> Option { + if let Ok(p) = canonicalize_with(filename, cwd) { + Some(p) + } else { + let path = Path::new(filename); + + if path.is_relative() { + if let Some(lib_dirs) = working_set.get_env(dirs_env) { + if let Ok(dirs) = lib_dirs.as_list() { + for lib_dir in dirs { + if let Ok(dir) = lib_dir.as_path() { + if let Ok(dir_abs) = canonicalize_with(&dir, cwd) { + // make sure the dir is absolute path + if let Ok(path) = canonicalize_with(filename, dir_abs) { + return Some(path); + } + } + } + } + + None + } else { + None + } + } else { + None + } + } else { + None + } + } +} diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index 85e57f1a2..f370e22a5 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -16,6 +16,8 @@ use std::path::Path; #[cfg(feature = "plugin")] use std::path::PathBuf; +static PWD_ENV: &str = "PWD"; + // Tells whether a decl etc. is visible or not #[derive(Debug, Clone)] struct Visibility { @@ -1175,11 +1177,15 @@ impl<'a> StateWorkingSet<'a> { let pwd = self .permanent_state .env_vars - .get("PWD") + .get(PWD_ENV) .expect("internal error: can't find PWD"); pwd.as_string().expect("internal error: PWD not a string") } + pub fn get_env(&self, name: &str) -> Option<&Value> { + self.permanent_state.env_vars.get(name) + } + pub fn set_variable_type(&mut self, var_id: VarId, ty: Type) { let num_permanent_vars = self.permanent_state.num_vars(); if var_id < num_permanent_vars { diff --git a/docs/sample_config/default_config.nu b/docs/sample_config/default_config.nu index 9eb561489..a4f592433 100644 --- a/docs/sample_config/default_config.nu +++ b/docs/sample_config/default_config.nu @@ -40,6 +40,20 @@ let-env ENV_CONVERSIONS = { } } +# Directories to search for scripts when calling source or use +# +# By default, /scripts is added +let-env NU_LIB_DIRS = [ + ($nu.config-path | path dirname | path join 'scripts') +] + +# Directories to search for plugin binaries when calling register +# +# By default, /plugins is added +let-env NU_PLUGIN_DIRS = [ + ($nu.config-path | path dirname | path join 'plugins') +] + # Custom completions for external commands (those outside of Nushell) # Each completions has two parts: the form of the external command, including its flags and parameters # and a helper command that knows how to complete values for those flags and parameters @@ -229,7 +243,7 @@ let $config = { name: history_previous modifier: control keycode: char_z - mode: emacs + mode: emacs event: { until: [ { send: menupageprevious }