diff --git a/README.md b/README.md index c86923a..cf71d9f 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ use embed_nu::{rusty_value::*, CommandGroupConfig, Context, NewEmpty, PipelineDa fn main() { let mut ctx = Context::builder() .with_command_groups(CommandGroupConfig::default().all_groups(true)) + .unwrap() .add_parent_env_vars() .build() .unwrap(); @@ -73,7 +74,7 @@ fn main() { bar: 12 }; // convert this struct into a nu value - // this is also done implicitely when passing the value to the nu context + // this is also done implicitly when passing the value to the nu context // as function arguments or variables let value = instance.into_value(); dbg!(value); diff --git a/src/context/bindings.rs b/src/context/bindings.rs index 4f342b1..ac70067 100644 --- a/src/context/bindings.rs +++ b/src/context/bindings.rs @@ -1,15 +1,17 @@ use nu_command::*; use nu_protocol::engine::{EngineState, StateWorkingSet}; +use crate::error::CrateResult; + macro_rules! bind_commands { ($engine_state:expr, $( $command:expr),* $(,)? ) => { bind($engine_state, |working_set| { $( working_set.add_decl(Box::new($command)); )* - }); + }) }; } -pub fn bind_core_commands(engine_state: &mut EngineState) { +pub fn bind_core_commands(engine_state: &mut EngineState) -> CrateResult<()> { bind_commands!( engine_state, Alias, @@ -45,14 +47,14 @@ pub fn bind_core_commands(engine_state: &mut EngineState) { Module, Use, Version, - ); + ) } -pub fn bind_chart_commands(engine_state: &mut EngineState) { - bind_commands!(engine_state, Histogram); +pub fn bind_chart_commands(engine_state: &mut EngineState) -> CrateResult<()> { + bind_commands!(engine_state, Histogram) } -pub fn bind_filter_commands(engine_state: &mut EngineState) { +pub fn bind_filter_commands(engine_state: &mut EngineState) -> CrateResult<()> { bind_commands! { engine_state, All, @@ -116,14 +118,14 @@ pub fn bind_filter_commands(engine_state: &mut EngineState) { Window, Wrap, Zip, - }; + } } -pub fn bind_misc_commands(engine_state: &mut EngineState) { - bind_commands!(engine_state, History, Tutor, HistorySession); +pub fn bind_misc_commands(engine_state: &mut EngineState) -> CrateResult<()> { + bind_commands!(engine_state, History, Tutor, HistorySession) } -pub fn bind_path_commands(engine_state: &mut EngineState) { +pub fn bind_path_commands(engine_state: &mut EngineState) -> CrateResult<()> { bind_commands! { engine_state, Path, @@ -136,10 +138,10 @@ pub fn bind_path_commands(engine_state: &mut EngineState) { PathRelativeTo, PathSplit, PathType, - }; + } } -pub fn bind_system_commands(engine_state: &mut EngineState) { +pub fn bind_system_commands(engine_state: &mut EngineState) -> CrateResult<()> { bind_commands! { engine_state, Benchmark, @@ -149,10 +151,10 @@ pub fn bind_system_commands(engine_state: &mut EngineState) { NuCheck, Sys, Ps, - }; + } } -pub fn bind_string_commands(engine_state: &mut EngineState) { +pub fn bind_string_commands(engine_state: &mut EngineState) -> CrateResult<()> { bind_commands! { engine_state, BuildString, @@ -195,10 +197,10 @@ pub fn bind_string_commands(engine_state: &mut EngineState) { StrTrim, StrTitleCase, StrUpcase - }; + } } -pub fn bind_bit_commands(engine_state: &mut EngineState) { +pub fn bind_bit_commands(engine_state: &mut EngineState) -> CrateResult<()> { bind_commands! { engine_state, Bits, @@ -213,7 +215,7 @@ pub fn bind_bit_commands(engine_state: &mut EngineState) { } } -pub fn bind_byte_commands(engine_state: &mut EngineState) { +pub fn bind_byte_commands(engine_state: &mut EngineState) -> CrateResult<()> { bind_commands! { engine_state, Bytes, @@ -231,7 +233,7 @@ pub fn bind_byte_commands(engine_state: &mut EngineState) { } } -pub fn bind_file_system_commands(engine_state: &mut EngineState) { +pub fn bind_file_system_commands(engine_state: &mut EngineState) -> CrateResult<()> { bind_commands! { engine_state, Cd, @@ -245,10 +247,10 @@ pub fn bind_file_system_commands(engine_state: &mut EngineState) { Touch, Glob, Watch, - }; + } } -pub fn bind_platform_commands(engine_state: &mut EngineState) { +pub fn bind_platform_commands(engine_state: &mut EngineState) -> CrateResult<()> { bind_commands! { engine_state, Ansi, @@ -264,10 +266,10 @@ pub fn bind_platform_commands(engine_state: &mut EngineState) { KeybindingsList, Sleep, TermSize, - }; + } } -pub fn bind_date_commands(engine_state: &mut EngineState) { +pub fn bind_date_commands(engine_state: &mut EngineState) -> CrateResult<()> { bind_commands! { engine_state, Date, @@ -278,10 +280,10 @@ pub fn bind_date_commands(engine_state: &mut EngineState) { DateToRecord, DateToTable, DateToTimezone, - }; + } } -pub fn bind_shell_commands(engine_state: &mut EngineState) { +pub fn bind_shell_commands(engine_state: &mut EngineState) -> CrateResult<()> { bind_commands! { engine_state, Enter, @@ -290,10 +292,10 @@ pub fn bind_shell_commands(engine_state: &mut EngineState) { NextShell, PrevShell, Shells, - }; + } } -pub fn bind_format_commands(engine_state: &mut EngineState) { +pub fn bind_format_commands(engine_state: &mut EngineState) -> CrateResult<()> { bind_commands! { engine_state, From, @@ -330,18 +332,18 @@ pub fn bind_format_commands(engine_state: &mut EngineState) { ToUrl, ToXml, ToYaml, - }; + } } -pub fn bind_viewer_commands(engine_state: &mut EngineState) { +pub fn bind_viewer_commands(engine_state: &mut EngineState) -> CrateResult<()> { bind_commands! { engine_state, Griddle, Table, - }; + } } -pub fn bind_conversion_commands(engine_state: &mut EngineState) { +pub fn bind_conversion_commands(engine_state: &mut EngineState) -> CrateResult<()> { bind_commands! { engine_state, Fmt, @@ -354,10 +356,10 @@ pub fn bind_conversion_commands(engine_state: &mut EngineState) { IntoFilesize, IntoInt, IntoString, - }; + } } -pub fn bind_environment_commands(engine_state: &mut EngineState) { +pub fn bind_environment_commands(engine_state: &mut EngineState) -> CrateResult<()> { bind_commands! { engine_state, Env, @@ -368,10 +370,10 @@ pub fn bind_environment_commands(engine_state: &mut EngineState) { WithEnv, // nu config commands have been removed as editing isn't possible // in this environment - }; + } } -pub fn bind_math_commands(engine_state: &mut EngineState) { +pub fn bind_math_commands(engine_state: &mut EngineState) -> CrateResult<()> { bind_commands! { engine_state, Math, @@ -390,10 +392,10 @@ pub fn bind_math_commands(engine_state: &mut EngineState) { MathStddev, MathSum, MathVariance, - }; + } } -pub fn bind_network_commands(engine_state: &mut EngineState) { +pub fn bind_network_commands(engine_state: &mut EngineState) -> CrateResult<()> { bind_commands! { engine_state, Fetch, @@ -407,7 +409,7 @@ pub fn bind_network_commands(engine_state: &mut EngineState) { } } -pub fn bind_random_commands(engine_state: &mut EngineState) { +pub fn bind_random_commands(engine_state: &mut EngineState) -> CrateResult<()> { bind_commands! { engine_state, Random, @@ -417,42 +419,44 @@ pub fn bind_random_commands(engine_state: &mut EngineState) { RandomDice, RandomInteger, RandomUuid, - }; + } } -pub fn bind_generator_commands(engine_state: &mut EngineState) { +pub fn bind_generator_commands(engine_state: &mut EngineState) -> CrateResult<()> { bind_commands! { engine_state, Cal, Seq, SeqDate, SeqChar, - }; + } } -pub fn bind_hash_commands(engine_state: &mut EngineState) { +pub fn bind_hash_commands(engine_state: &mut EngineState) -> CrateResult<()> { bind_commands! { engine_state, Hash, HashMd5::default(), HashSha256::default(), - }; + } } -pub fn bind_experimental_commands(engine_state: &mut EngineState) { +pub fn bind_experimental_commands(engine_state: &mut EngineState) -> CrateResult<()> { bind_commands! { engine_state, ViewSource, IsAdmin, - }; + } } #[inline] -fn bind(engine_state: &mut EngineState, bind_fn: F) { +fn bind( + engine_state: &mut EngineState, + bind_fn: F, +) -> CrateResult<()> { let mut working_set = StateWorkingSet::new(engine_state); bind_fn(&mut working_set); let delta = working_set.render(); - engine_state - .merge_delta(delta) - .expect("Failed to bind commands"); + engine_state.merge_delta(delta)?; + Ok(()) } diff --git a/src/context/builder.rs b/src/context/builder.rs index b8098cd..27222ca 100644 --- a/src/context/builder.rs +++ b/src/context/builder.rs @@ -7,7 +7,7 @@ use std::env; use nu_protocol::{ ast::Block, - engine::{EngineState, Stack, StateWorkingSet}, + engine::{Command, EngineState, Stack, StateWorkingSet}, PipelineData, Span, }; @@ -32,13 +32,13 @@ impl Default for ContextBuilder { impl ContextBuilder { /// Enables certain command groups specified in the Config on the state - pub fn with_command_groups(mut self, group_config: CommandGroupConfig) -> Self { + pub fn with_command_groups(mut self, group_config: CommandGroupConfig) -> CrateResult { macro_rules! toggle_command_groups { ($($group:ident),*) => { paste::item!( $( if group_config.$group { - super::bindings::[](&mut self.engine_state); + super::bindings::[](&mut self.engine_state)?; } )* ) @@ -70,7 +70,19 @@ impl ContextBuilder { hash, experimental ); - self + Ok(self) + } + + /// Adds a custom command to the engine that can be used inside nu expressions + /// This method can also be used to pass nu builtin commands to enable single commands + /// Instead of whole command groups + pub fn add_command(mut self, command: C) -> CrateResult { + let mut working_set = StateWorkingSet::new(&self.engine_state); + working_set.add_decl(Box::new(command)); + let delta = working_set.render(); + self.engine_state.merge_delta(delta)?; + + Ok(self) } /// Adds a variable to the state diff --git a/src/lib.rs b/src/lib.rs index 973eb26..75cb40e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,7 +10,7 @@ pub use argument::{Argument, IntoArgument}; pub use context::{CommandGroupConfig, Context, ContextBuilder}; pub use into_expression::*; pub use into_value::*; -pub use nu_protocol::{PipelineData, Value}; +pub use nu_protocol::{self, PipelineData, Value}; pub use rusty_value; pub use utils::NewEmpty; diff --git a/tests/test_eval.rs b/tests/test_eval.rs index 910867c..a3faa2a 100644 --- a/tests/test_eval.rs +++ b/tests/test_eval.rs @@ -1,5 +1,7 @@ -use embed_nu::rusty_value::*; +use embed_nu::{rusty_value::*, IntoValue}; use embed_nu::{CommandGroupConfig, Context, NewEmpty, PipelineData}; +use nu_protocol::engine::Command; +use nu_protocol::{Config, Signature, SyntaxShape}; #[test] fn it_evals_strings() { @@ -45,10 +47,61 @@ fn it_executes_functions() { ctx.print_pipeline(pipeline).unwrap(); } +#[test] +fn it_executes_custom_commands() { + let mut ctx = get_context(); + let pipeline = ctx + .eval_raw(r#"custom_upper "Hello world""#, PipelineData::empty()) + .unwrap(); + let string_output = pipeline.collect_string(" ", &Config::default()).unwrap(); + assert_eq!(string_output, String::from("HELLO WORLD")) +} + fn get_context() -> Context { Context::builder() .with_command_groups(CommandGroupConfig::default().all_groups(true)) + .unwrap() + .add_command(CustomCommand) + .unwrap() .add_parent_env_vars() .build() .unwrap() } + +#[derive(Clone)] +struct CustomCommand; + +impl Command for CustomCommand { + fn name(&self) -> &str { + "custom_upper" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::new("custom_upper") + .required( + "text", + SyntaxShape::String, + "Text to print in full uppercase.", + ) + .category(nu_protocol::Category::Experimental) + } + + fn usage(&self) -> &str { + "custom_upper " + } + + fn run( + &self, + _engine_state: &nu_protocol::engine::EngineState, + _stack: &mut nu_protocol::engine::Stack, + call: &nu_protocol::ast::Call, + _input: PipelineData, + ) -> Result { + let string_input = call.positional_nth(0).unwrap(); + let string_input = string_input.as_string().unwrap(); + let upper = string_input.to_uppercase(); + println!("{upper}"); + + Ok(PipelineData::Value(upper.into_value(), None)) + } +}