diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index 352d7daf45..506cca3ac1 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -13,6 +13,7 @@ nu-protocol = { path = "../nu-protocol" } nu-table = { path = "../nu-table" } nu-term-grid = { path = "../nu-term-grid" } nu-parser = { path = "../nu-parser" } +nu-plugin = { path = "../nu-plugin" } trash = { version = "1.3.0", optional = true } unicode-segmentation = "1.8.0" diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 1fe5d90ec3..38d769f7fe 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -56,6 +56,7 @@ pub fn create_default_context() -> Rc> { Mv, Ps, Rm, + RunPlugin, Select, Size, Split, diff --git a/crates/nu-command/src/experimental/mod.rs b/crates/nu-command/src/experimental/mod.rs index b90a5fd0dd..7442ffab02 100644 --- a/crates/nu-command/src/experimental/mod.rs +++ b/crates/nu-command/src/experimental/mod.rs @@ -1,7 +1,9 @@ mod git; mod git_checkout; mod list_git_branches; +mod run_plugin; pub use git::Git; pub use git_checkout::GitCheckout; pub use list_git_branches::ListGitBranches; +pub use run_plugin::RunPlugin; diff --git a/crates/nu-command/src/experimental/run_plugin.rs b/crates/nu-command/src/experimental/run_plugin.rs new file mode 100644 index 0000000000..d54c3c94b8 --- /dev/null +++ b/crates/nu-command/src/experimental/run_plugin.rs @@ -0,0 +1,28 @@ +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{ShellError, Signature, Value}; + +pub struct RunPlugin; + +impl Command for RunPlugin { + fn name(&self) -> &str { + "run_plugin" + } + + fn usage(&self) -> &str { + "test for plugin encoding" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("run_plugin") + } + + fn run( + &self, + _context: &EvaluationContext, + _call: &Call, + _input: Value, + ) -> Result { + Err(ShellError::InternalError("plugin".into())) + } +} diff --git a/crates/nu-plugin/Cargo.toml b/crates/nu-plugin/Cargo.toml new file mode 100644 index 0000000000..cb5346eab7 --- /dev/null +++ b/crates/nu-plugin/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "nu-plugin" +version = "0.1.0" +edition = "2018" + +[dependencies] +capnp = "0.14.3" +nu-protocol = { path = "../nu-protocol" } + +[build-dependencies] +capnpc = "0.14.3" + + diff --git a/crates/nu-plugin/build.rs b/crates/nu-plugin/build.rs new file mode 100644 index 0000000000..40757c1621 --- /dev/null +++ b/crates/nu-plugin/build.rs @@ -0,0 +1,7 @@ +fn main() { + capnpc::CompilerCommand::new() + .src_prefix("schema") + .file("schema/value.capnp") + .run() + .expect("compiling schema"); +} diff --git a/crates/nu-plugin/schema/value.capnp b/crates/nu-plugin/schema/value.capnp new file mode 100644 index 0000000000..0a3cefb77b --- /dev/null +++ b/crates/nu-plugin/schema/value.capnp @@ -0,0 +1,19 @@ +@0xb299d30dc02d72bc; + +struct Value { + span @0: Span; + + struct Span { + start @0 :UInt64; + end @1 :UInt64; + } + + union { + void @1 :Void; + bool @2 :Bool; + int @3 :Int64; + float @4 :Float64; + string @5 :Text; + list @6 :List(Value); + } +} diff --git a/crates/nu-plugin/src/lib.rs b/crates/nu-plugin/src/lib.rs new file mode 100644 index 0000000000..da6d868b8d --- /dev/null +++ b/crates/nu-plugin/src/lib.rs @@ -0,0 +1,168 @@ +pub mod value_capnp { + include!(concat!(env!("OUT_DIR"), "/value_capnp.rs")); +} + +pub mod plugin_value { + + use crate::value_capnp::value; + use capnp::serialize_packed; + use nu_protocol::{Span, Value}; + + pub fn serialize_value(value: &Value, writer: &mut impl std::io::Write) -> capnp::Result<()> { + let mut message = ::capnp::message::Builder::new_default(); + + { + let mut serialized_value = message.init_root::(); + + let value_span = match value { + Value::Nothing { span } => { + serialized_value.set_void(()); + *span + } + Value::Bool { val, span } => { + serialized_value.set_bool(*val); + *span + } + Value::Int { val, span } => { + serialized_value.set_int(*val); + *span + } + Value::Float { val, span } => { + serialized_value.set_float(*val); + *span + } + Value::String { val, span } => { + serialized_value.set_string(&val); + *span + } + Value::List { vals, span } => { + { + serialize_list(vals, &mut serialized_value); + } + *span + } + _ => Span::unknown(), + }; + + { + let mut span = serialized_value.reborrow().init_span(); + span.set_start(value_span.start as u64); + span.set_end(value_span.end as u64); + } + } + + serialize_packed::write_message(writer, &message) + } + + fn serialize_list(vals: &[Value], builder: &mut value::Builder) { + let mut list_builder = builder.reborrow().init_list(vals.len() as u32); + + for (index, val) in vals.iter().enumerate() { + match val { + Value::Nothing { .. } => { + list_builder.reborrow().get(index as u32).set_void(()); + } + Value::Bool { val, .. } => { + list_builder.reborrow().get(index as u32).set_bool(*val); + } + Value::Int { val, .. } => { + list_builder.reborrow().get(index as u32).set_int(*val); + } + Value::Float { val, .. } => { + list_builder.reborrow().get(index as u32).set_float(*val); + } + Value::String { val, .. } => { + list_builder.reborrow().get(index as u32).set_string(val); + } + Value::List { vals, .. } => { + let test = builder.reborrow(); + serialize_list(vals, test) + } + _ => {} + } + } + } + + pub fn deserialize_value(reader: &mut impl std::io::BufRead) -> Value { + let message_reader = + serialize_packed::read_message(reader, ::capnp::message::ReaderOptions::new()).unwrap(); + + let plugin_value = message_reader.get_root::().unwrap(); + + let span_reader = plugin_value.get_span().unwrap(); + let span = Span { + start: span_reader.get_start() as usize, + end: span_reader.get_end() as usize, + }; + + match plugin_value.which() { + Ok(value::Void(())) => Value::Nothing { span }, + Ok(value::Bool(val)) => Value::Bool { val, span }, + Ok(value::Int(val)) => Value::Int { val, span }, + Ok(value::Float(val)) => Value::Float { val, span }, + Ok(value::String(val)) => Value::String { + val: val.unwrap().to_string(), + span, + }, + Ok(value::List(_)) => Value::Nothing { + span: Span::unknown(), + }, + Err(capnp::NotInSchema(_)) => Value::Nothing { + span: Span::unknown(), + }, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use nu_protocol::{Span, Value}; + + #[test] + fn value_round_trip() { + let values = [ + Value::Bool { + val: false, + span: Span { start: 1, end: 20 }, + }, + Value::Int { + val: 10, + span: Span { start: 2, end: 30 }, + }, + Value::Float { + val: 10.0, + span: Span { start: 3, end: 40 }, + }, + Value::String { + val: "a string".into(), + span: Span { start: 4, end: 50 }, + }, + ]; + + for value in values { + let mut buffer: Vec = Vec::new(); + plugin_value::serialize_value(&value, &mut buffer).expect("unable to write message"); + let returned_value = plugin_value::deserialize_value(&mut buffer.as_slice()); + + assert_eq!(value, returned_value) + } + + { + // Since nothing doesn't implement PartialOrd, we only compare that the + // encoded and decoded spans are correct + let value = Value::Nothing { + span: Span { start: 0, end: 10 }, + }; + + let mut buffer: Vec = Vec::new(); + plugin_value::serialize_value(&value, &mut buffer).expect("unable to write message"); + let returned_value = plugin_value::deserialize_value(&mut buffer.as_slice()); + + assert_eq!( + value.span().expect("span"), + returned_value.span().expect("span") + ) + } + } +}