From a3a9571dacc7fea25260028e88fdf4da954f07e4 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sat, 21 May 2022 09:49:42 +1200 Subject: [PATCH] Add environment change hook (#5600) * add environment change hook * clippy --- crates/nu-cli/src/repl.rs | 70 ++++- crates/nu-protocol/src/config.rs | 5 +- crates/nu-protocol/src/engine/engine_state.rs | 264 +----------------- crates/nu-protocol/src/engine/mod.rs | 2 + crates/nu-protocol/src/engine/overlay.rs | 264 ++++++++++++++++++ crates/nu-protocol/src/engine/stack.rs | 3 +- docs/sample_config/default_config.nu | 5 + 7 files changed, 344 insertions(+), 269 deletions(-) create mode 100644 crates/nu-protocol/src/engine/overlay.rs diff --git a/crates/nu-cli/src/repl.rs b/crates/nu-cli/src/repl.rs index 50cbc3b2f..b3b91f0d5 100644 --- a/crates/nu-cli/src/repl.rs +++ b/crates/nu-cli/src/repl.rs @@ -12,7 +12,7 @@ use nu_engine::{convert_env_values, eval_block}; use nu_parser::lex; use nu_protocol::{ engine::{EngineState, Stack, StateWorkingSet}, - BlockId, PipelineData, ShellError, Span, Value, + BlockId, PipelineData, PositionalArg, ShellError, Span, Value, }; use reedline::{DefaultHinter, Emacs, Vi}; use std::io::{self, Write}; @@ -201,12 +201,58 @@ pub fn evaluate_repl( // Right before we start our prompt and take input from the user, // fire the "pre_prompt" hook if let Some(hook) = &config.hooks.pre_prompt { - if let Err(err) = run_hook(engine_state, stack, hook) { + if let Err(err) = run_hook(engine_state, stack, vec![], hook) { let working_set = StateWorkingSet::new(engine_state); report_error(&working_set, &err); } } + // Next, check all the environment variables they ask for + // fire the "env_change" hook + if let Some(hook) = config.hooks.env_change.clone() { + match hook { + Value::Record { + cols, vals: blocks, .. + } => { + for (idx, env_var) in cols.iter().enumerate() { + let before = engine_state + .previous_env_vars + .get(env_var) + .cloned() + .unwrap_or_default(); + let after = stack.get_env_var(engine_state, env_var).unwrap_or_default(); + if before != after { + if let Err(err) = run_hook( + engine_state, + stack, + vec![before, after.clone()], + &blocks[idx], + ) { + let working_set = StateWorkingSet::new(engine_state); + report_error(&working_set, &err); + } + + engine_state + .previous_env_vars + .insert(env_var.to_string(), after); + } + } + } + x => { + let working_set = StateWorkingSet::new(engine_state); + report_error( + &working_set, + &ShellError::TypeMismatch( + "record for 'env_change' hook".to_string(), + x.span().unwrap_or_else(|_| Span::new(0, 0)), + ), + ) + } + } + } + + config = engine_state.get_config(); + if config.shell_integration { run_ansi_sequence(PRE_EXECUTE_MARKER)?; } @@ -232,7 +278,7 @@ pub fn evaluate_repl( // Right before we start running the code the user gave us, // fire the "pre_execution" hook if let Some(hook) = &config.hooks.pre_execution { - if let Err(err) = run_hook(engine_state, stack, hook) { + if let Err(err) = run_hook(engine_state, stack, vec![], hook) { let working_set = StateWorkingSet::new(engine_state); report_error(&working_set, &err); } @@ -394,12 +440,13 @@ fn run_ansi_sequence(seq: &str) -> Result<(), ShellError> { pub fn run_hook( engine_state: &EngineState, stack: &mut Stack, + arguments: Vec, value: &Value, ) -> Result<(), ShellError> { match value { Value::List { vals, .. } => { for val in vals { - run_hook(engine_state, stack, val)? + run_hook(engine_state, stack, arguments.clone(), val)? } Ok(()) } @@ -407,7 +454,7 @@ pub fn run_hook( val: block_id, span, .. - } => run_hook_block(engine_state, stack, *block_id, *span), + } => run_hook_block(engine_state, stack, *block_id, arguments, *span), x => match x.span() { Ok(span) => Err(ShellError::MissingConfigValue( "block for hook in config".into(), @@ -425,12 +472,23 @@ pub fn run_hook_block( engine_state: &EngineState, stack: &mut Stack, block_id: BlockId, + arguments: Vec, span: Span, ) -> Result<(), ShellError> { let block = engine_state.get_block(block_id); let input = PipelineData::new(span); - match eval_block(engine_state, stack, block, input, false, false) { + let mut callee_stack = stack.gather_captures(&block.captures); + + for (idx, PositionalArg { var_id, .. }) in + block.signature.required_positional.iter().enumerate() + { + if let Some(var_id) = var_id { + callee_stack.add_var(*var_id, arguments[idx].clone()) + } + } + + match eval_block(engine_state, &mut callee_stack, block, input, false, false) { Ok(pipeline_data) => match pipeline_data.into_value(span) { Value::Error { error } => Err(error), _ => Ok(()), diff --git a/crates/nu-protocol/src/config.rs b/crates/nu-protocol/src/config.rs index 3a4eaf0f5..25792ad29 100644 --- a/crates/nu-protocol/src/config.rs +++ b/crates/nu-protocol/src/config.rs @@ -29,6 +29,7 @@ pub struct ParsedMenu { pub struct Hooks { pub pre_prompt: Option, pub pre_execution: Option, + pub env_change: Option, } impl Hooks { @@ -36,6 +37,7 @@ impl Hooks { Self { pre_prompt: None, pre_execution: None, + env_change: None, } } } @@ -384,9 +386,10 @@ fn create_hooks(value: &Value) -> Result { match cols[idx].as_str() { "pre_prompt" => hooks.pre_prompt = Some(vals[idx].clone()), "pre_execution" => hooks.pre_execution = Some(vals[idx].clone()), + "env_change" => hooks.env_change = Some(vals[idx].clone()), x => { return Err(ShellError::UnsupportedConfigValue( - "'pre_prompt' or 'pre_execution'".to_string(), + "'pre_prompt', 'pre_execution', or 'env_change'".to_string(), x.to_string(), *span, )); diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index 4dc68752a..d4aa735d6 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -1,4 +1,4 @@ -use super::{Command, EnvVars, Stack}; +use super::{Command, EnvVars, OverlayFrame, ScopeFrame, Stack, Visibility, DEFAULT_OVERLAY_NAME}; use crate::Value; use crate::{ ast::Block, AliasId, BlockId, Config, DeclId, Example, Module, ModuleId, OverlayId, ShellError, @@ -16,266 +16,6 @@ use std::{ }; static PWD_ENV: &str = "PWD"; -pub static DEFAULT_OVERLAY_NAME: &str = "zero"; - -/// Tells whether a decl or alias is visible or not -#[derive(Debug, Clone)] -pub struct Visibility { - decl_ids: HashMap, - alias_ids: HashMap, -} - -impl Visibility { - pub fn new() -> Self { - Visibility { - decl_ids: HashMap::new(), - alias_ids: HashMap::new(), - } - } - - pub fn is_decl_id_visible(&self, decl_id: &DeclId) -> bool { - *self.decl_ids.get(decl_id).unwrap_or(&true) // by default it's visible - } - - pub fn is_alias_id_visible(&self, alias_id: &AliasId) -> bool { - *self.alias_ids.get(alias_id).unwrap_or(&true) // by default it's visible - } - - fn hide_decl_id(&mut self, decl_id: &DeclId) { - self.decl_ids.insert(*decl_id, false); - } - - fn hide_alias_id(&mut self, alias_id: &AliasId) { - self.alias_ids.insert(*alias_id, false); - } - - fn use_decl_id(&mut self, decl_id: &DeclId) { - self.decl_ids.insert(*decl_id, true); - } - - fn use_alias_id(&mut self, alias_id: &AliasId) { - self.alias_ids.insert(*alias_id, true); - } - - pub fn merge_with(&mut self, other: Visibility) { - // overwrite own values with the other - self.decl_ids.extend(other.decl_ids); - self.alias_ids.extend(other.alias_ids); - } - - fn append(&mut self, other: &Visibility) { - // take new values from the other but keep own values - for (decl_id, visible) in other.decl_ids.iter() { - if !self.decl_ids.contains_key(decl_id) { - self.decl_ids.insert(*decl_id, *visible); - } - } - - for (alias_id, visible) in other.alias_ids.iter() { - if !self.alias_ids.contains_key(alias_id) { - self.alias_ids.insert(*alias_id, *visible); - } - } - } -} - -#[derive(Debug, Clone)] -pub struct ScopeFrame { - /// List of both active and incactive overlays in this ScopeFrame. - /// - /// The order does not have any menaning. Indexed locally (within this ScopeFrame) by - /// OverlayIds in active_overlays. - overlays: Vec<(Vec, OverlayFrame)>, - - /// List of currently active overlays. - /// - /// Order is significant: The last item points at the last activated overlay. - pub active_overlays: Vec, - - /// Deactivated overlays from permanent state. - /// ! Stores OverlayIds from the permanent state, not from this frame. ! - // removed_overlays: Vec, - - /// Removed overlays from previous scope frames / permanent state - removed_overlays: Vec>, - - /// temporary storage for predeclarations - predecls: HashMap, DeclId>, -} - -impl ScopeFrame { - pub fn new() -> Self { - Self { - overlays: vec![], - active_overlays: vec![], - removed_overlays: vec![], - predecls: HashMap::new(), - } - } - - pub fn with_empty_overlay(name: Vec, origin: ModuleId) -> Self { - Self { - overlays: vec![(name, OverlayFrame::from(origin))], - active_overlays: vec![0], - removed_overlays: vec![], - predecls: HashMap::new(), - } - } - - pub fn get_var(&self, var_name: &[u8]) -> Option<&VarId> { - for overlay_id in self.active_overlays.iter().rev() { - if let Some(var_id) = self - .overlays - .get(*overlay_id) - .expect("internal error: missing overlay") - .1 - .vars - .get(var_name) - { - return Some(var_id); - } - } - - None - } - - pub fn active_overlay_ids(&self, removed_overlays: &mut Vec>) -> Vec { - for name in &self.removed_overlays { - if !removed_overlays.contains(name) { - removed_overlays.push(name.clone()); - } - } - - self.active_overlays - .iter() - .filter(|id| !removed_overlays.contains(self.get_overlay_name(**id))) - .copied() - .collect() - } - - pub fn active_overlays(&self, removed_overlays: &mut Vec>) -> Vec<&OverlayFrame> { - self.active_overlay_ids(removed_overlays) - .iter() - .map(|id| self.get_overlay(*id)) - .collect() - } - - pub fn active_overlay_names(&self, removed_overlays: &mut Vec>) -> Vec<&Vec> { - self.active_overlay_ids(removed_overlays) - .iter() - .map(|id| self.get_overlay_name(*id)) - .collect() - } - - pub fn get_overlay_name(&self, overlay_id: OverlayId) -> &Vec { - &self - .overlays - .get(overlay_id) - .expect("internal error: missing overlay") - .0 - } - - pub fn get_overlay(&self, overlay_id: OverlayId) -> &OverlayFrame { - &self - .overlays - .get(overlay_id) - .expect("internal error: missing overlay") - .1 - } - - pub fn get_overlay_mut(&mut self, overlay_id: OverlayId) -> &mut OverlayFrame { - &mut self - .overlays - .get_mut(overlay_id) - .expect("internal error: missing overlay") - .1 - } - - pub fn find_overlay(&self, name: &[u8]) -> Option { - self.overlays.iter().position(|(n, _)| n == name) - } - - pub fn find_active_overlay(&self, name: &[u8]) -> Option { - self.overlays - .iter() - .position(|(n, _)| n == name) - .and_then(|id| { - if self.active_overlays.contains(&id) { - Some(id) - } else { - None - } - }) - } -} - -// type OverlayDiff = (Vec<(Vec, DeclId)>, Vec<(Vec, AliasId)>); - -#[derive(Debug, Clone)] -pub struct OverlayFrame { - pub vars: HashMap, VarId>, - predecls: HashMap, DeclId>, // temporary storage for predeclarations - pub decls: HashMap, DeclId>, - pub aliases: HashMap, AliasId>, - pub modules: HashMap, ModuleId>, - pub visibility: Visibility, - pub origin: ModuleId, // The original module the overlay was created from -} - -impl OverlayFrame { - pub fn from(origin: ModuleId) -> Self { - Self { - vars: HashMap::new(), - predecls: HashMap::new(), - decls: HashMap::new(), - aliases: HashMap::new(), - modules: HashMap::new(), - visibility: Visibility::new(), - origin, - } - } - - // Find out which definitions are custom compared to the origin module - // pub fn diff(&self, engine_state: &EngineState) -> OverlayDiff { - // let module = engine_state.get_module(self.origin); - - // let decls = self - // .decls - // .iter() - // .filter(|(name, decl_id)| { - // if self.visibility.is_decl_id_visible(decl_id) { - // if let Some(original_id) = module.get_decl_id(name) { - // &original_id != *decl_id - // } else { - // true - // } - // } else { - // false - // } - // }) - // .map(|(name, decl_id)| (name.to_owned(), *decl_id)) - // .collect(); - - // let aliases = self - // .aliases - // .iter() - // .filter(|(name, alias_id)| { - // if self.visibility.is_alias_id_visible(alias_id) { - // if let Some(original_id) = module.get_alias_id(name) { - // &original_id != *alias_id - // } else { - // true - // } - // } else { - // false - // } - // }) - // .map(|(name, alias_id)| (name.to_owned(), *alias_id)) - // .collect(); - - // (decls, aliases) - // } -} /// The core global engine state. This includes all global definitions as well as any global state that /// will persist for the whole session. @@ -331,6 +71,7 @@ pub struct EngineState { pub scope: ScopeFrame, pub ctrlc: Option>, pub env_vars: EnvVars, + pub previous_env_vars: HashMap, pub config: Config, #[cfg(feature = "plugin")] pub plugin_signatures: Option, @@ -361,6 +102,7 @@ impl EngineState { scope: ScopeFrame::with_empty_overlay(DEFAULT_OVERLAY_NAME.as_bytes().to_vec(), 0), ctrlc: None, env_vars: EnvVars::from([(DEFAULT_OVERLAY_NAME.to_string(), HashMap::new())]), + previous_env_vars: HashMap::new(), config: Config::default(), #[cfg(feature = "plugin")] plugin_signatures: None, diff --git a/crates/nu-protocol/src/engine/mod.rs b/crates/nu-protocol/src/engine/mod.rs index 296578b41..5eab7c84a 100644 --- a/crates/nu-protocol/src/engine/mod.rs +++ b/crates/nu-protocol/src/engine/mod.rs @@ -2,10 +2,12 @@ mod call_info; mod capture_block; mod command; mod engine_state; +mod overlay; mod stack; pub use call_info::*; pub use capture_block::*; pub use command::*; pub use engine_state::*; +pub use overlay::*; pub use stack::*; diff --git a/crates/nu-protocol/src/engine/overlay.rs b/crates/nu-protocol/src/engine/overlay.rs new file mode 100644 index 000000000..3c3e0655e --- /dev/null +++ b/crates/nu-protocol/src/engine/overlay.rs @@ -0,0 +1,264 @@ +use std::collections::HashMap; + +use crate::{AliasId, DeclId, ModuleId, OverlayId, VarId}; + +pub static DEFAULT_OVERLAY_NAME: &str = "zero"; + +/// Tells whether a decl or alias is visible or not +#[derive(Debug, Clone)] +pub struct Visibility { + decl_ids: HashMap, + alias_ids: HashMap, +} + +impl Visibility { + pub fn new() -> Self { + Visibility { + decl_ids: HashMap::new(), + alias_ids: HashMap::new(), + } + } + + pub fn is_decl_id_visible(&self, decl_id: &DeclId) -> bool { + *self.decl_ids.get(decl_id).unwrap_or(&true) // by default it's visible + } + + pub fn is_alias_id_visible(&self, alias_id: &AliasId) -> bool { + *self.alias_ids.get(alias_id).unwrap_or(&true) // by default it's visible + } + + pub fn hide_decl_id(&mut self, decl_id: &DeclId) { + self.decl_ids.insert(*decl_id, false); + } + + pub fn hide_alias_id(&mut self, alias_id: &AliasId) { + self.alias_ids.insert(*alias_id, false); + } + + pub fn use_decl_id(&mut self, decl_id: &DeclId) { + self.decl_ids.insert(*decl_id, true); + } + + pub fn use_alias_id(&mut self, alias_id: &AliasId) { + self.alias_ids.insert(*alias_id, true); + } + + pub fn merge_with(&mut self, other: Visibility) { + // overwrite own values with the other + self.decl_ids.extend(other.decl_ids); + self.alias_ids.extend(other.alias_ids); + } + + pub fn append(&mut self, other: &Visibility) { + // take new values from the other but keep own values + for (decl_id, visible) in other.decl_ids.iter() { + if !self.decl_ids.contains_key(decl_id) { + self.decl_ids.insert(*decl_id, *visible); + } + } + + for (alias_id, visible) in other.alias_ids.iter() { + if !self.alias_ids.contains_key(alias_id) { + self.alias_ids.insert(*alias_id, *visible); + } + } + } +} + +#[derive(Debug, Clone)] +pub struct ScopeFrame { + /// List of both active and incactive overlays in this ScopeFrame. + /// + /// The order does not have any menaning. Indexed locally (within this ScopeFrame) by + /// OverlayIds in active_overlays. + pub overlays: Vec<(Vec, OverlayFrame)>, + + /// List of currently active overlays. + /// + /// Order is significant: The last item points at the last activated overlay. + pub active_overlays: Vec, + + /// Deactivated overlays from permanent state. + /// ! Stores OverlayIds from the permanent state, not from this frame. ! + // removed_overlays: Vec, + + /// Removed overlays from previous scope frames / permanent state + pub removed_overlays: Vec>, + + /// temporary storage for predeclarations + pub predecls: HashMap, DeclId>, +} + +impl ScopeFrame { + pub fn new() -> Self { + Self { + overlays: vec![], + active_overlays: vec![], + removed_overlays: vec![], + predecls: HashMap::new(), + } + } + + pub fn with_empty_overlay(name: Vec, origin: ModuleId) -> Self { + Self { + overlays: vec![(name, OverlayFrame::from(origin))], + active_overlays: vec![0], + removed_overlays: vec![], + predecls: HashMap::new(), + } + } + + pub fn get_var(&self, var_name: &[u8]) -> Option<&VarId> { + for overlay_id in self.active_overlays.iter().rev() { + if let Some(var_id) = self + .overlays + .get(*overlay_id) + .expect("internal error: missing overlay") + .1 + .vars + .get(var_name) + { + return Some(var_id); + } + } + + None + } + + pub fn active_overlay_ids(&self, removed_overlays: &mut Vec>) -> Vec { + for name in &self.removed_overlays { + if !removed_overlays.contains(name) { + removed_overlays.push(name.clone()); + } + } + + self.active_overlays + .iter() + .filter(|id| !removed_overlays.contains(self.get_overlay_name(**id))) + .copied() + .collect() + } + + pub fn active_overlays(&self, removed_overlays: &mut Vec>) -> Vec<&OverlayFrame> { + self.active_overlay_ids(removed_overlays) + .iter() + .map(|id| self.get_overlay(*id)) + .collect() + } + + pub fn active_overlay_names(&self, removed_overlays: &mut Vec>) -> Vec<&Vec> { + self.active_overlay_ids(removed_overlays) + .iter() + .map(|id| self.get_overlay_name(*id)) + .collect() + } + + pub fn get_overlay_name(&self, overlay_id: OverlayId) -> &Vec { + &self + .overlays + .get(overlay_id) + .expect("internal error: missing overlay") + .0 + } + + pub fn get_overlay(&self, overlay_id: OverlayId) -> &OverlayFrame { + &self + .overlays + .get(overlay_id) + .expect("internal error: missing overlay") + .1 + } + + pub fn get_overlay_mut(&mut self, overlay_id: OverlayId) -> &mut OverlayFrame { + &mut self + .overlays + .get_mut(overlay_id) + .expect("internal error: missing overlay") + .1 + } + + pub fn find_overlay(&self, name: &[u8]) -> Option { + self.overlays.iter().position(|(n, _)| n == name) + } + + pub fn find_active_overlay(&self, name: &[u8]) -> Option { + self.overlays + .iter() + .position(|(n, _)| n == name) + .and_then(|id| { + if self.active_overlays.contains(&id) { + Some(id) + } else { + None + } + }) + } +} + +// type OverlayDiff = (Vec<(Vec, DeclId)>, Vec<(Vec, AliasId)>); + +#[derive(Debug, Clone)] +pub struct OverlayFrame { + pub vars: HashMap, VarId>, + pub predecls: HashMap, DeclId>, // temporary storage for predeclarations + pub decls: HashMap, DeclId>, + pub aliases: HashMap, AliasId>, + pub modules: HashMap, ModuleId>, + pub visibility: Visibility, + pub origin: ModuleId, // The original module the overlay was created from +} + +impl OverlayFrame { + pub fn from(origin: ModuleId) -> Self { + Self { + vars: HashMap::new(), + predecls: HashMap::new(), + decls: HashMap::new(), + aliases: HashMap::new(), + modules: HashMap::new(), + visibility: Visibility::new(), + origin, + } + } + + // Find out which definitions are custom compared to the origin module + // pub fn diff(&self, engine_state: &EngineState) -> OverlayDiff { + // let module = engine_state.get_module(self.origin); + + // let decls = self + // .decls + // .iter() + // .filter(|(name, decl_id)| { + // if self.visibility.is_decl_id_visible(decl_id) { + // if let Some(original_id) = module.get_decl_id(name) { + // &original_id != *decl_id + // } else { + // true + // } + // } else { + // false + // } + // }) + // .map(|(name, decl_id)| (name.to_owned(), *decl_id)) + // .collect(); + + // let aliases = self + // .aliases + // .iter() + // .filter(|(name, alias_id)| { + // if self.visibility.is_alias_id_visible(alias_id) { + // if let Some(original_id) = module.get_alias_id(name) { + // &original_id != *alias_id + // } else { + // true + // } + // } else { + // false + // } + // }) + // .map(|(name, alias_id)| (name.to_owned(), *alias_id)) + // .collect(); + + // (decls, aliases) + // } +} diff --git a/crates/nu-protocol/src/engine/stack.rs b/crates/nu-protocol/src/engine/stack.rs index c1ff76c91..8b7a35c4b 100644 --- a/crates/nu-protocol/src/engine/stack.rs +++ b/crates/nu-protocol/src/engine/stack.rs @@ -1,6 +1,7 @@ use std::collections::{HashMap, HashSet}; -use crate::engine::{EngineState, DEFAULT_OVERLAY_NAME}; +use crate::engine::EngineState; +use crate::engine::DEFAULT_OVERLAY_NAME; use crate::{ShellError, Span, Value, VarId}; /// Environment variables per overlay diff --git a/docs/sample_config/default_config.nu b/docs/sample_config/default_config.nu index a00e1c50a..f81bf33fc 100644 --- a/docs/sample_config/default_config.nu +++ b/docs/sample_config/default_config.nu @@ -206,6 +206,11 @@ let-env config = { pre_execution: [{ $nothing # replace with source code to run before the repl input is run }] + env_change: { + PWD: [{|before, after| + $nothing # replace with source code to run if the PWD environment is different since the last repl input + }] + } } menus: [ # Configuration for default nushell menus