From 247550ac4e140932aa28544c0b3035ff6535e038 Mon Sep 17 00:00:00 2001 From: trivernis Date: Mon, 3 Oct 2022 18:00:31 +0200 Subject: [PATCH] Rename builder to context builder and add context to eval scripts --- src/{state_builder => context}/bindings.rs | 0 .../mod.rs => context/builder.rs} | 73 ++++++++++++++----- .../command_group_config.rs | 6 +- src/context/mod.rs | 68 +++++++++++++++++ src/error.rs | 4 + src/into_value.rs | 2 +- src/lib.rs | 3 +- src/utils.rs | 34 ++++++++- tests/test_eval.rs | 22 ++++++ 9 files changed, 185 insertions(+), 27 deletions(-) rename src/{state_builder => context}/bindings.rs (100%) rename src/{state_builder/mod.rs => context/builder.rs} (52%) rename src/{state_builder => context}/command_group_config.rs (92%) create mode 100644 src/context/mod.rs create mode 100644 tests/test_eval.rs diff --git a/src/state_builder/bindings.rs b/src/context/bindings.rs similarity index 100% rename from src/state_builder/bindings.rs rename to src/context/bindings.rs diff --git a/src/state_builder/mod.rs b/src/context/builder.rs similarity index 52% rename from src/state_builder/mod.rs rename to src/context/builder.rs index 91d4994..b8098cd 100644 --- a/src/state_builder/mod.rs +++ b/src/context/builder.rs @@ -1,40 +1,44 @@ +use crate::{ + error::CrateResult, + into_value::IntoValue, + utils::{parse_nu_script, NewEmpty}, +}; use std::env; use nu_protocol::{ + ast::Block, engine::{EngineState, Stack, StateWorkingSet}, - Span, + PipelineData, Span, }; -mod bindings; -mod command_group_config; -pub use command_group_config::CommandGroupConfig; - -use crate::{error::CrateResult, into_value::IntoValue, utils::SpanEmpty}; +use super::{CommandGroupConfig, Context}; /// Builder to create a new nu engine state -pub struct StateBuilder { +pub struct ContextBuilder { engine_state: EngineState, stack: Stack, + blocks: Vec, } -impl Default for StateBuilder { +impl Default for ContextBuilder { fn default() -> Self { Self { engine_state: EngineState::new(), stack: Stack::new(), + blocks: Vec::new(), } } } -impl StateBuilder { +impl ContextBuilder { /// Enables certain command groups specified in the Config on the state - pub fn with_command_groups(&mut self, group_config: CommandGroupConfig) -> &mut Self { + pub fn with_command_groups(mut self, group_config: CommandGroupConfig) -> Self { macro_rules! toggle_command_groups { ($($group:ident),*) => { paste::item!( $( if group_config.$group { - bindings::[](&mut self.engine_state); + super::bindings::[](&mut self.engine_state); } )* ) @@ -70,11 +74,7 @@ impl StateBuilder { } /// Adds a variable to the state - pub fn add_var( - &mut self, - name: S, - value: V, - ) -> CrateResult<&mut Self> { + pub fn add_var(mut self, name: S, value: V) -> CrateResult { let mut working_set = StateWorkingSet::new(&self.engine_state); let var_id = working_set.add_variable( @@ -90,7 +90,7 @@ impl StateBuilder { } /// Adds an environment variable to the state - pub fn add_env_var(&mut self, name: S, value: V) -> &mut Self { + pub fn add_env_var(mut self, name: S, value: V) -> Self { self.engine_state .add_env_var(name.to_string(), value.into_value()); @@ -99,11 +99,46 @@ impl StateBuilder { /// Adds the environment variables of the parent process to the /// states env variables - pub fn add_parent_env_vars(&mut self) -> &mut Self { + pub fn add_parent_env_vars(self) -> Self { + let mut builder = self; + for (name, val) in env::vars() { - self.add_env_var(name, val); + builder = builder.add_env_var(name, val); } + builder + } + + /// Adds a block to the builder + /// This block is evaluated when building to put + /// the blocks contents in scope. + /// Note: Code not contained in declarations is being executed when building + /// the context + pub fn add_block(mut self, block: Block) -> Self { + self.blocks.push(block); + self } + + /// Adds a script to the context. + /// This script is being parsed so this operation can fail + pub fn add_script(mut self, contents: String) -> CrateResult { + let block = parse_nu_script(&mut self.engine_state, contents)?; + self.blocks.push(block); + + Ok(self) + } + + /// builds the context + pub fn build(self) -> CrateResult { + let mut ctx = Context { + engine_state: self.engine_state, + stack: self.stack, + }; + for block in self.blocks { + ctx.eval_block(&block, PipelineData::empty())?; + } + + Ok(ctx) + } } diff --git a/src/state_builder/command_group_config.rs b/src/context/command_group_config.rs similarity index 92% rename from src/state_builder/command_group_config.rs rename to src/context/command_group_config.rs index 8126acf..7eb8acc 100644 --- a/src/state_builder/command_group_config.rs +++ b/src/context/command_group_config.rs @@ -3,14 +3,14 @@ macro_rules! command_group_config { ($(#[doc=$doc:literal] $group:ident),*) => { /// Enables or disables certain command groups - #[derive(Clone, Debug)] + #[derive(Clone, Debug, Default)] pub struct CommandGroupConfig { $(pub(crate) $group: bool,)* } impl CommandGroupConfig { /// Enables all commands - pub fn all_groups(&mut self, enabled: bool) -> &mut Self { + pub fn all_groups(mut self, enabled: bool) -> Self { $( self.$group = enabled; )* @@ -22,7 +22,7 @@ macro_rules! command_group_config { paste::item! { #[doc=$doc] #[inline] - pub fn [< $group _group>](&mut self, enabled: bool) -> &mut Self { + pub fn [< $group _group>](mut self, enabled: bool) -> Self { self.$group = enabled; self diff --git a/src/context/mod.rs b/src/context/mod.rs new file mode 100644 index 0000000..b8746c0 --- /dev/null +++ b/src/context/mod.rs @@ -0,0 +1,68 @@ +mod bindings; +mod builder; +mod command_group_config; +pub use builder::*; +pub use command_group_config::CommandGroupConfig; +use nu_protocol::{ + ast::Block, + engine::{EngineState, Stack}, + PipelineData, +}; + +use crate::{ + error::{CrateError, CrateResult}, + utils::parse_nu_script, +}; + +/// Represents the evaluation context of nu scripts and commands +/// This context is the state of the engine itself plus the stack +/// It stores variables on +#[derive(Clone)] +pub struct Context { + engine_state: EngineState, + stack: Stack, +} + +impl Context { + pub fn builder() -> ContextBuilder { + ContextBuilder::default() + } + + /// Evaluates the given block with the current engine context (stack plus engine state) + pub fn eval_block(&mut self, block: &Block, input: PipelineData) -> CrateResult { + nu_engine::eval_block( + &self.engine_state, + &mut self.stack, + block, + input, + false, + false, + ) + .map_err(CrateError::from) + } + + /// Evals nu script as string with the current engine context + pub fn eval_raw( + &mut self, + contents: S, + input: PipelineData, + ) -> CrateResult { + let block = parse_nu_script(&mut self.engine_state, contents.to_string())?; + + self.eval_block(&block, input) + } + + /// Prints the data of the given pipeline to stdout + pub fn print_pipeline(&mut self, pipeline: PipelineData) -> CrateResult<()> { + pipeline.print(&self.engine_state, &mut self.stack, false, false)?; + + Ok(()) + } + + /// Prints the data of the given pipeline to stderr + pub fn print_pipeline_stderr(&mut self, pipeline: PipelineData) -> CrateResult<()> { + pipeline.print(&self.engine_state, &mut self.stack, false, true)?; + + Ok(()) + } +} diff --git a/src/error.rs b/src/error.rs index 3b4080f..b725767 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,3 +1,4 @@ +use nu_parser::ParseError; use nu_protocol::ShellError; use thiserror::Error; @@ -7,4 +8,7 @@ pub type CrateResult = std::result::Result; pub enum CrateError { #[error("Shell Error {0}")] NuShellError(#[from] ShellError), + + #[error("Parse Error {0}")] + NuParseError(#[from] ParseError), } diff --git a/src/into_value.rs b/src/into_value.rs index 61f2e9e..dd6d94d 100644 --- a/src/into_value.rs +++ b/src/into_value.rs @@ -1,7 +1,7 @@ use nu_protocol::{Span, Value}; use rusty_value::{Fields, HashableValue, RustyValue}; -use crate::utils::SpanEmpty; +use crate::utils::NewEmpty; pub struct RawValue(Value); diff --git a/src/lib.rs b/src/lib.rs index 45e5d5d..a5c02f2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,10 @@ +pub mod context; pub(crate) mod error; pub(crate) mod into_value; -pub mod state_builder; pub(crate) mod utils; pub use into_value::*; pub use rusty_value; +pub use utils::NewEmpty; pub type Error = error::CrateError; diff --git a/src/utils.rs b/src/utils.rs index 364866e..a7c295a 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,11 +1,39 @@ -use nu_protocol::Span; +use nu_protocol::{ + ast::Block, + engine::{EngineState, StateWorkingSet}, + PipelineData, Span, +}; -pub trait SpanEmpty { +use crate::error::{CrateError, CrateResult}; + +pub trait NewEmpty { fn empty() -> Self; } -impl SpanEmpty for Span { +impl NewEmpty for Span { + #[inline] fn empty() -> Self { Span::new(0, 0) } } + +impl NewEmpty for PipelineData { + #[inline] + fn empty() -> Self { + Self::new(Span::empty()) + } +} + +pub fn parse_nu_script(engine_state: &mut EngineState, contents: String) -> CrateResult { + let mut working_set = StateWorkingSet::new(&engine_state); + let (block, err) = nu_parser::parse(&mut working_set, None, &contents.into_bytes(), false, &[]); + + if let Some(err) = err { + Err(CrateError::from(err)) + } else { + let delta = working_set.render(); + engine_state.merge_delta(delta)?; + + Ok(block) + } +} diff --git a/tests/test_eval.rs b/tests/test_eval.rs new file mode 100644 index 0000000..e608bd0 --- /dev/null +++ b/tests/test_eval.rs @@ -0,0 +1,22 @@ +use embed_nu::{ + context::{CommandGroupConfig, Context}, + NewEmpty, +}; +use nu_protocol::PipelineData; + +#[test] +fn it_evals_strings() { + let mut ctx = get_context(); + let pipeline = ctx + .eval_raw(r#"echo "Hello World""#, PipelineData::empty()) + .unwrap(); + ctx.print_pipeline(pipeline).unwrap() +} + +fn get_context() -> Context { + Context::builder() + .with_command_groups(CommandGroupConfig::default().all_groups(true)) + .add_parent_env_vars() + .build() + .unwrap() +}