diff --git a/Cargo.lock b/Cargo.lock index 24566d5c3e..a613f28c03 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -685,6 +685,7 @@ name = "nu-parser" version = "0.1.0" dependencies = [ "miette", + "nu-plugin", "nu-protocol", "thiserror", ] diff --git a/crates/nu-command/src/core_commands/mod.rs b/crates/nu-command/src/core_commands/mod.rs index a124ffcf18..3b871ea0a8 100644 --- a/crates/nu-command/src/core_commands/mod.rs +++ b/crates/nu-command/src/core_commands/mod.rs @@ -8,6 +8,7 @@ mod hide; mod if_; mod let_; mod module; +mod register; mod source; mod use_; @@ -21,5 +22,6 @@ pub use hide::Hide; pub use if_::If; pub use let_::Let; pub use module::Module; +pub use register::Register; pub use source::Source; pub use use_::Use; diff --git a/crates/nu-command/src/core_commands/register.rs b/crates/nu-command/src/core_commands/register.rs new file mode 100644 index 0000000000..9578e717e0 --- /dev/null +++ b/crates/nu-command/src/core_commands/register.rs @@ -0,0 +1,34 @@ +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{PipelineData, Signature, SyntaxShape}; + +#[derive(Clone)] +pub struct Register; + +impl Command for Register { + fn name(&self) -> &str { + "register" + } + + fn usage(&self) -> &str { + "Register a plugin" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("register").required( + "plugin", + SyntaxShape::Filepath, + "location of bin for plugin", + ) + } + + fn run( + &self, + _engine_state: &EngineState, + _stack: &mut Stack, + _call: &Call, + _input: PipelineData, + ) -> Result { + Ok(PipelineData::new()) + } +} diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 8195f0fa48..59f18457f5 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -58,6 +58,7 @@ pub fn create_default_context() -> EngineState { Mv, ParEach, Ps, + Register, Rm, RunPlugin, Select, diff --git a/crates/nu-parser/Cargo.toml b/crates/nu-parser/Cargo.toml index 96dd9444a5..2dc4d6dc27 100644 --- a/crates/nu-parser/Cargo.toml +++ b/crates/nu-parser/Cargo.toml @@ -7,3 +7,4 @@ edition = "2018" miette = "3.0.0" thiserror = "1.0.29" nu-protocol = { path = "../nu-protocol"} +nu-plugin = { path = "../nu-plugin"} diff --git a/crates/nu-parser/src/errors.rs b/crates/nu-parser/src/errors.rs index 36412e9a7b..936af1e413 100644 --- a/crates/nu-parser/src/errors.rs +++ b/crates/nu-parser/src/errors.rs @@ -174,4 +174,12 @@ pub enum ParseError { #[error("Module export not found.")] #[diagnostic(code(nu::parser::export_not_found), url(docsrs))] ExportNotFound(#[label = "could not find imports"] Span), + + #[error("File not found")] + #[diagnostic(code(nu::parser::export_not_found), url(docsrs))] + FileNotFound(String), + + #[error("Plugin error")] + #[diagnostic(code(nu::parser::export_not_found), url(docsrs))] + PluginError(String), } diff --git a/crates/nu-parser/src/lib.rs b/crates/nu-parser/src/lib.rs index 8fc5e559c8..dc5534c9e6 100644 --- a/crates/nu-parser/src/lib.rs +++ b/crates/nu-parser/src/lib.rs @@ -11,6 +11,6 @@ pub use flatten::{flatten_block, FlatShape}; pub use lex::{lex, Token, TokenContents}; pub use lite_parse::{lite_parse, LiteBlock}; pub use parse_keywords::{ - parse_alias, parse_def, parse_def_predecl, parse_let, parse_module, parse_use, + parse_alias, parse_def, parse_def_predecl, parse_let, parse_module, parse_plugin, parse_use, }; pub use parser::{find_captures_in_expr, parse, Import, VarDecl}; diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index b086ed223f..955d63999b 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -1,3 +1,4 @@ +use nu_plugin::plugin::get_signature; use nu_protocol::{ ast::{Block, Call, Expr, Expression, ImportPatternMember, Pipeline, Statement}, engine::StateWorkingSet, @@ -854,3 +855,78 @@ pub fn parse_source( )), ) } + +pub fn parse_plugin( + working_set: &mut StateWorkingSet, + spans: &[Span], +) -> (Statement, Option) { + let name = working_set.get_span_contents(spans[0]); + + if name != b"register" { + return ( + garbage_statement(spans), + Some(ParseError::UnknownState( + "internal error: Wrong call name for parse plugin function".into(), + span(spans), + )), + ); + } + + if let Some(decl_id) = working_set.find_decl(b"register") { + let (call, call_span, mut err) = + parse_internal_call(working_set, spans[0], &spans[1..], decl_id); + + let error = { + match spans.len() { + 1 => Some(ParseError::MissingPositional( + "plugin location".into(), + spans[0], + )), + 2 => { + let name_expr = working_set.get_span_contents(spans[1]); + if let Ok(filename) = String::from_utf8(name_expr.to_vec()) { + let source_file = Path::new(&filename); + + // get signature from plugin + // create plugin command declaration (need struct impl Command) + // store declaration in working set + match get_signature(source_file) { + Err(err) => Some(ParseError::PluginError(format!("{}", err))), + Ok(_signature) => None, + } + } else { + Some(ParseError::NonUtf8(spans[1])) + } + } + _ => { + let span = spans[2..].iter().fold(spans[2], |acc, next| Span { + start: acc.start, + end: next.end, + }); + + Some(ParseError::ExtraPositional(span)) + } + } + }; + + err = error.or(err); + + ( + Statement::Pipeline(Pipeline::from_vec(vec![Expression { + expr: Expr::Call(call), + span: call_span, + ty: Type::Unknown, + custom_completion: None, + }])), + err, + ) + } else { + ( + garbage_statement(spans), + Some(ParseError::UnknownState( + "internal error: Register declaration not found".into(), + span(spans), + )), + ) + } +} diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 14a437d8e0..409b2f3db1 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -15,7 +15,8 @@ use nu_protocol::{ }; use crate::parse_keywords::{ - parse_alias, parse_def, parse_def_predecl, parse_hide, parse_let, parse_module, parse_use, + parse_alias, parse_def, parse_def_predecl, parse_hide, parse_let, parse_module, parse_plugin, + parse_use, }; #[derive(Debug, Clone)] @@ -2987,6 +2988,7 @@ pub fn parse_statement( Some(ParseError::UnexpectedKeyword("export".into(), spans[0])), ), b"hide" => parse_hide(working_set, spans), + b"register" => parse_plugin(working_set, spans), _ => { let (expr, err) = parse_expression(working_set, spans, true); (Statement::Pipeline(Pipeline::from_vec(vec![expr])), err) diff --git a/crates/nu-plugin/schema/plugin.capnp b/crates/nu-plugin/schema/plugin.capnp index db29f5208c..bf878e2954 100644 --- a/crates/nu-plugin/schema/plugin.capnp +++ b/crates/nu-plugin/schema/plugin.capnp @@ -6,10 +6,17 @@ # required to comunicate with the plugins created for nushell # Generic structs used as helpers for the encoding -struct Option(Value) { +struct Option(T) { union { none @0 :Void; - some @1 :Value; + some @1 :T; + } +} + +struct Err(T) { + union { + err @0 :Text; + ok @1 :T; } } @@ -27,6 +34,7 @@ struct Span { end @1 :UInt64; } +# Resulting value from plugin struct Value { span @0: Span; @@ -40,6 +48,7 @@ struct Value { } } +# Structs required to define the plugin signature struct Signature { name @0 :Text; usage @1 :Text; @@ -76,6 +85,7 @@ enum Shape { boolean @5; } +# The next structs define the call information sent to th plugin struct Expression { union { garbage @0 :Void; @@ -100,3 +110,18 @@ struct CallInfo { call @0: Call; input @1: Value; } + +# Main communication structs with the plugin +struct PluginCall { + union { + signature @0 :Void; + callInfo @1 :CallInfo; + } +} + +struct PluginResponse { + union { + signature @0 :Signature; + value @1 :Value; + } +} diff --git a/crates/nu-plugin/src/lib.rs b/crates/nu-plugin/src/lib.rs index c4d1a54347..e5c4a78c4e 100644 --- a/crates/nu-plugin/src/lib.rs +++ b/crates/nu-plugin/src/lib.rs @@ -1,4 +1,5 @@ pub mod plugin; +pub mod plugin_call; pub mod serializers; pub mod plugin_capnp { diff --git a/crates/nu-plugin/src/plugin.rs b/crates/nu-plugin/src/plugin.rs index 2026ed938b..e3bbadee47 100644 --- a/crates/nu-plugin/src/plugin.rs +++ b/crates/nu-plugin/src/plugin.rs @@ -1,4 +1,91 @@ +use std::process::{Command, Stdio}; +use std::{fmt::Display, path::Path}; + +use nu_protocol::{ast::Call, Signature, Value}; + //use nu_protocol::{ShellError, Value}; +#[derive(Debug)] +pub struct CallInfo { + pub call: Call, + pub input: Value, +} + +// Information sent to the plugin +#[derive(Debug)] +pub enum PluginCall { + Signature, + CallInfo(Box), +} + +// Information received from the plugin +#[derive(Debug)] +pub enum PluginResponse { + Signature(Box), + Value(Box), +} + /// The `Plugin` trait defines the API which plugins may use to "hook" into nushell. pub trait Plugin {} + +#[derive(Debug)] +pub enum PluginError { + MissingSignature, + UnableToSpawn(String), + EncodingError(String), + DecodingError(String), +} + +impl Display for PluginError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + PluginError::MissingSignature => write!(f, "missing signature in plugin"), + PluginError::UnableToSpawn(err) => { + write!(f, "error in spawned child process: {}", err) + } + PluginError::EncodingError(err) => { + write!(f, "error while encoding: {}", err) + } + PluginError::DecodingError(err) => { + write!(f, "error while decoding: {}", err) + } + } + } +} + +pub fn get_signature(path: &Path) -> Result { + let mut plugin_cmd = create_command(path); + + // Both stdout and stdin are piped so we can get the information from the plugin + plugin_cmd.stdout(Stdio::piped()); + plugin_cmd.stdin(Stdio::piped()); + + match plugin_cmd.spawn() { + Err(err) => Err(PluginError::UnableToSpawn(format!("{}", err))), + Ok(mut child) => { + // create message to plugin to indicate signature + // send message to plugin + // deserialize message with signature + match child.wait() { + Err(err) => Err(PluginError::UnableToSpawn(format!("{}", err))), + Ok(_) => Ok(Signature::build("testing")), + } + } + } +} + +fn create_command(path: &Path) -> Command { + //TODO. The selection of shell could be modifiable from the config file. + if cfg!(windows) { + let mut process = Command::new("cmd"); + process.arg("/c"); + process.arg(path); + + process + } else { + let mut process = Command::new("sh"); + process.arg("-c").arg(path); + + process + } +} diff --git a/crates/nu-plugin/src/plugin_call.rs b/crates/nu-plugin/src/plugin_call.rs new file mode 100644 index 0000000000..b02c493c29 --- /dev/null +++ b/crates/nu-plugin/src/plugin_call.rs @@ -0,0 +1,310 @@ +use crate::plugin::{CallInfo, PluginCall, PluginError, PluginResponse}; +use crate::plugin_capnp::{plugin_call, plugin_response}; +use crate::serializers::{call, signature, value}; +use capnp::serialize_packed; + +pub fn encode_call( + plugin_call: &PluginCall, + writer: &mut impl std::io::Write, +) -> Result<(), PluginError> { + let mut message = ::capnp::message::Builder::new_default(); + + let mut builder = message.init_root::(); + + match &plugin_call { + PluginCall::Signature => builder.set_signature(()), + PluginCall::CallInfo(call_info) => { + let mut call_info_builder = builder.reborrow().init_call_info(); + + // Serializing argument information from the call + let call_builder = call_info_builder + .reborrow() + .get_call() + .map_err(|e| PluginError::EncodingError(e.to_string()))?; + + call::serialize_call(&call_info.call, call_builder) + .map_err(|e| PluginError::EncodingError(e.to_string()))?; + + // Serializing the input value from the call info + let value_builder = call_info_builder + .reborrow() + .get_input() + .map_err(|e| PluginError::EncodingError(e.to_string()))?; + + value::serialize_value(&call_info.input, value_builder); + } + }; + + serialize_packed::write_message(writer, &message) + .map_err(|e| PluginError::EncodingError(e.to_string())) +} + +pub fn decode_call(reader: &mut impl std::io::BufRead) -> Result { + let message_reader = + serialize_packed::read_message(reader, ::capnp::message::ReaderOptions::new()).unwrap(); + + let reader = message_reader + .get_root::() + .map_err(|e| PluginError::DecodingError(e.to_string()))?; + + match reader.which() { + Err(capnp::NotInSchema(_)) => Err(PluginError::DecodingError("value not in schema".into())), + Ok(plugin_call::Signature(())) => Ok(PluginCall::Signature), + Ok(plugin_call::CallInfo(reader)) => { + let reader = reader.map_err(|e| PluginError::DecodingError(e.to_string()))?; + + let call_reader = reader + .get_call() + .map_err(|e| PluginError::DecodingError(e.to_string()))?; + + let call = call::deserialize_call(call_reader) + .map_err(|e| PluginError::DecodingError(e.to_string()))?; + + let input_reader = reader + .get_input() + .map_err(|e| PluginError::DecodingError(e.to_string()))?; + + let input = value::deserialize_value(input_reader) + .map_err(|e| PluginError::DecodingError(e.to_string()))?; + + Ok(PluginCall::CallInfo(Box::new(CallInfo { call, input }))) + } + } +} + +pub fn encode_response( + plugin_response: &PluginResponse, + writer: &mut impl std::io::Write, +) -> Result<(), PluginError> { + let mut message = ::capnp::message::Builder::new_default(); + + let mut builder = message.init_root::(); + + match &plugin_response { + PluginResponse::Signature(sign) => { + let signature_builder = builder.reborrow().init_signature(); + signature::serialize_signature(sign, signature_builder) + } + PluginResponse::Value(val) => { + let value_builder = builder.reborrow().init_value(); + value::serialize_value(val, value_builder); + } + }; + + serialize_packed::write_message(writer, &message) + .map_err(|e| PluginError::EncodingError(e.to_string())) +} + +pub fn decode_response(reader: &mut impl std::io::BufRead) -> Result { + let message_reader = + serialize_packed::read_message(reader, ::capnp::message::ReaderOptions::new()).unwrap(); + + let reader = message_reader + .get_root::() + .map_err(|e| PluginError::DecodingError(e.to_string()))?; + + match reader.which() { + Err(capnp::NotInSchema(_)) => Err(PluginError::DecodingError("value not in schema".into())), + Ok(plugin_response::Signature(reader)) => { + let reader = reader.map_err(|e| PluginError::DecodingError(e.to_string()))?; + let sign = signature::deserialize_signature(reader) + .map_err(|e| PluginError::DecodingError(e.to_string()))?; + + Ok(PluginResponse::Signature(Box::new(sign))) + } + Ok(plugin_response::Value(reader)) => { + let reader = reader.map_err(|e| PluginError::DecodingError(e.to_string()))?; + let val = value::deserialize_value(reader) + .map_err(|e| PluginError::DecodingError(e.to_string()))?; + + Ok(PluginResponse::Value(Box::new(val))) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::plugin::{PluginCall, PluginResponse}; + use nu_protocol::{ + ast::{Call, Expr, Expression}, + Signature, Span, Spanned, SyntaxShape, Value, + }; + + fn compare_expressions(lhs: &Expression, rhs: &Expression) { + match (&lhs.expr, &rhs.expr) { + (Expr::Bool(a), Expr::Bool(b)) => assert_eq!(a, b), + (Expr::Int(a), Expr::Int(b)) => assert_eq!(a, b), + (Expr::Float(a), Expr::Float(b)) => assert_eq!(a, b), + (Expr::String(a), Expr::String(b)) => assert_eq!(a, b), + _ => panic!("not matching values"), + } + } + + #[test] + fn callinfo_round_trip_signature() { + let plugin_call = PluginCall::Signature; + + let mut buffer: Vec = Vec::new(); + encode_call(&plugin_call, &mut buffer).expect("unable to serialize message"); + let returned = decode_call(&mut buffer.as_slice()).expect("unable to deserialize message"); + + match returned { + PluginCall::Signature => {} + PluginCall::CallInfo(_) => panic!("decoded into wrong value"), + } + } + + #[test] + fn callinfo_round_trip_callinfo() { + let input = Value::Bool { + val: false, + span: Span { start: 1, end: 20 }, + }; + + let call = Call { + decl_id: 1, + head: Span { start: 0, end: 10 }, + positional: vec![ + Expression { + expr: Expr::Float(1.0), + span: Span { start: 0, end: 10 }, + ty: nu_protocol::Type::Float, + custom_completion: None, + }, + Expression { + expr: Expr::String("something".into()), + span: Span { start: 0, end: 10 }, + ty: nu_protocol::Type::Float, + custom_completion: None, + }, + ], + named: vec![( + Spanned { + item: "name".to_string(), + span: Span { start: 0, end: 10 }, + }, + Some(Expression { + expr: Expr::Float(1.0), + span: Span { start: 0, end: 10 }, + ty: nu_protocol::Type::Float, + custom_completion: None, + }), + )], + }; + + let plugin_call = PluginCall::CallInfo(Box::new(CallInfo { + call: call.clone(), + input: input.clone(), + })); + + let mut buffer: Vec = Vec::new(); + encode_call(&plugin_call, &mut buffer).expect("unable to serialize message"); + let returned = decode_call(&mut buffer.as_slice()).expect("unable to deserialize message"); + + match returned { + PluginCall::Signature => panic!("returned wrong call type"), + PluginCall::CallInfo(call_info) => { + assert_eq!(input, call_info.input); + assert_eq!(call.head, call_info.call.head); + assert_eq!(call.positional.len(), call_info.call.positional.len()); + + call.positional + .iter() + .zip(call_info.call.positional.iter()) + .for_each(|(lhs, rhs)| compare_expressions(lhs, rhs)); + + call.named + .iter() + .zip(call_info.call.named.iter()) + .for_each(|(lhs, rhs)| { + // Comparing the keys + assert_eq!(lhs.0.item, rhs.0.item); + + match (&lhs.1, &rhs.1) { + (None, None) => {} + (Some(a), Some(b)) => compare_expressions(a, b), + _ => panic!("not matching values"), + } + }); + } + } + } + + #[test] + fn response_round_trip_signature() { + let signature = Signature::build("nu-plugin") + .required("first", SyntaxShape::String, "first required") + .required("second", SyntaxShape::Int, "second required") + .required_named("first_named", SyntaxShape::String, "first named", Some('f')) + .required_named( + "second_named", + SyntaxShape::String, + "second named", + Some('s'), + ) + .rest("remaining", SyntaxShape::Int, "remaining"); + + let response = PluginResponse::Signature(Box::new(signature.clone())); + + let mut buffer: Vec = Vec::new(); + encode_response(&response, &mut buffer).expect("unable to serialize message"); + let returned = + decode_response(&mut buffer.as_slice()).expect("unable to deserialize message"); + + match returned { + PluginResponse::Value(_) => panic!("returned wrong call type"), + PluginResponse::Signature(returned_signature) => { + assert_eq!(signature.name, returned_signature.name); + assert_eq!(signature.usage, returned_signature.usage); + assert_eq!(signature.extra_usage, returned_signature.extra_usage); + assert_eq!(signature.is_filter, returned_signature.is_filter); + + signature + .required_positional + .iter() + .zip(returned_signature.required_positional.iter()) + .for_each(|(lhs, rhs)| assert_eq!(lhs, rhs)); + + signature + .optional_positional + .iter() + .zip(returned_signature.optional_positional.iter()) + .for_each(|(lhs, rhs)| assert_eq!(lhs, rhs)); + + signature + .named + .iter() + .zip(returned_signature.named.iter()) + .for_each(|(lhs, rhs)| assert_eq!(lhs, rhs)); + + assert_eq!( + signature.rest_positional, + returned_signature.rest_positional, + ); + } + } + } + + #[test] + fn response_round_trip_value() { + let value = Value::Int { + val: 10, + span: Span { start: 2, end: 30 }, + }; + + let response = PluginResponse::Value(Box::new(value.clone())); + + let mut buffer: Vec = Vec::new(); + encode_response(&response, &mut buffer).expect("unable to serialize message"); + let returned = + decode_response(&mut buffer.as_slice()).expect("unable to deserialize message"); + + match returned { + PluginResponse::Signature(_) => panic!("returned wrong call type"), + PluginResponse::Value(returned_value) => { + assert_eq!(&value, returned_value.as_ref()) + } + } + } +} diff --git a/crates/nu-plugin/src/serializers/call.rs b/crates/nu-plugin/src/serializers/call.rs index 36afc70cd1..305b29e850 100644 --- a/crates/nu-plugin/src/serializers/call.rs +++ b/crates/nu-plugin/src/serializers/call.rs @@ -1,21 +1,11 @@ +use crate::plugin::PluginError; use crate::plugin_capnp::{call, expression, option}; -use capnp::serialize_packed; use nu_protocol::{ ast::{Call, Expr, Expression}, - ShellError, Span, Spanned, Type, + Span, Spanned, Type, }; -pub fn write_buffer(call: &Call, writer: &mut impl std::io::Write) -> Result<(), ShellError> { - let mut message = ::capnp::message::Builder::new_default(); - - let builder = message.init_root::(); - serialize_call(call, builder)?; - - serialize_packed::write_message(writer, &message) - .map_err(|e| ShellError::EncodingError(e.to_string())) -} - -pub(crate) fn serialize_call(call: &Call, mut builder: call::Builder) -> Result<(), ShellError> { +pub(crate) fn serialize_call(call: &Call, mut builder: call::Builder) -> Result<(), PluginError> { let mut head = builder.reborrow().init_head(); head.set_start(call.head.start as u64); head.set_end(call.head.end as u64); @@ -37,7 +27,7 @@ fn serialize_positional(positional: &[Expression], mut builder: call::Builder) { fn serialize_named( named: &[(Spanned, Option)], mut builder: call::Builder, -) -> Result<(), ShellError> { +) -> Result<(), PluginError> { let mut named_builder = builder .reborrow() .init_named() @@ -48,7 +38,7 @@ fn serialize_named( entry_builder .reborrow() .set_key(key.item.as_str()) - .map_err(|e| ShellError::EncodingError(e.to_string()))?; + .map_err(|e| PluginError::EncodingError(e.to_string()))?; let mut value_builder = entry_builder.init_value(); match expression { @@ -84,21 +74,10 @@ fn serialize_expression(expression: &Expression, mut builder: expression::Builde } } -pub fn read_buffer(reader: &mut impl std::io::BufRead) -> Result { - let message_reader = - serialize_packed::read_message(reader, ::capnp::message::ReaderOptions::new()).unwrap(); - - let reader = message_reader - .get_root::() - .map_err(|e| ShellError::DecodingError(e.to_string()))?; - - deserialize_call(reader) -} - -pub(crate) fn deserialize_call(reader: call::Reader) -> Result { +pub(crate) fn deserialize_call(reader: call::Reader) -> Result { let head_reader = reader .get_head() - .map_err(|e| ShellError::DecodingError(e.to_string()))?; + .map_err(|e| PluginError::DecodingError(e.to_string()))?; let head = Span { start: head_reader.get_start() as usize, @@ -119,10 +98,10 @@ pub(crate) fn deserialize_call(reader: call::Reader) -> Result fn deserialize_positionals( span: Span, reader: call::Reader, -) -> Result, ShellError> { +) -> Result, PluginError> { let positional_reader = reader .get_positional() - .map_err(|e| ShellError::DecodingError(e.to_string()))?; + .map_err(|e| PluginError::DecodingError(e.to_string()))?; positional_reader .iter() @@ -132,14 +111,14 @@ fn deserialize_positionals( type NamedList = Vec<(Spanned, Option)>; -fn deserialize_named(span: Span, reader: call::Reader) -> Result { +fn deserialize_named(span: Span, reader: call::Reader) -> Result { let named_reader = reader .get_named() - .map_err(|e| ShellError::DecodingError(e.to_string()))?; + .map_err(|e| PluginError::DecodingError(e.to_string()))?; let entries_list = named_reader .get_entries() - .map_err(|e| ShellError::DecodingError(e.to_string()))?; + .map_err(|e| PluginError::DecodingError(e.to_string()))?; let mut entries: Vec<(Spanned, Option)> = Vec::with_capacity(entries_list.len() as usize); @@ -147,21 +126,21 @@ fn deserialize_named(span: Span, reader: call::Reader) -> Result None, Ok(option::Some(expression_reader)) => { let expression_reader = - expression_reader.map_err(|e| ShellError::DecodingError(e.to_string()))?; + expression_reader.map_err(|e| PluginError::DecodingError(e.to_string()))?; let expression = deserialize_expression(span, expression_reader) - .map_err(|e| ShellError::DecodingError(e.to_string()))?; + .map_err(|e| PluginError::DecodingError(e.to_string()))?; Some(expression) } @@ -179,7 +158,7 @@ fn deserialize_named(span: Span, reader: call::Reader) -> Result Result { +) -> Result { let expr = match reader.which() { Ok(expression::Garbage(())) => Expr::Garbage, Ok(expression::Bool(val)) => Expr::Bool(val), @@ -187,18 +166,18 @@ fn deserialize_expression( Ok(expression::Float(val)) => Expr::Float(val), Ok(expression::String(val)) => { let string = val - .map_err(|e| ShellError::DecodingError(e.to_string()))? + .map_err(|e| PluginError::DecodingError(e.to_string()))? .to_string(); Expr::String(string) } Ok(expression::List(values)) => { - let values = values.map_err(|e| ShellError::DecodingError(e.to_string()))?; + let values = values.map_err(|e| PluginError::DecodingError(e.to_string()))?; let values_list = values .iter() .map(|inner_reader| deserialize_expression(span, inner_reader)) - .collect::, ShellError>>()?; + .collect::, PluginError>>()?; Expr::List(values_list) } @@ -215,6 +194,7 @@ fn deserialize_expression( #[cfg(test)] mod tests { + use capnp::serialize_packed; use core::panic; use super::*; @@ -223,6 +203,27 @@ mod tests { Span, Spanned, }; + fn write_buffer(call: &Call, writer: &mut impl std::io::Write) -> Result<(), PluginError> { + let mut message = ::capnp::message::Builder::new_default(); + + let builder = message.init_root::(); + serialize_call(call, builder)?; + + serialize_packed::write_message(writer, &message) + .map_err(|e| PluginError::EncodingError(e.to_string())) + } + + fn read_buffer(reader: &mut impl std::io::BufRead) -> Result { + let message_reader = + serialize_packed::read_message(reader, ::capnp::message::ReaderOptions::new()).unwrap(); + + let reader = message_reader + .get_root::() + .map_err(|e| PluginError::DecodingError(e.to_string()))?; + + deserialize_call(reader) + } + fn compare_expressions(lhs: &Expression, rhs: &Expression) { match (&lhs.expr, &rhs.expr) { (Expr::Bool(a), Expr::Bool(b)) => assert_eq!(a, b), diff --git a/crates/nu-plugin/src/serializers/callinfo.rs b/crates/nu-plugin/src/serializers/callinfo.rs deleted file mode 100644 index 5023d666a4..0000000000 --- a/crates/nu-plugin/src/serializers/callinfo.rs +++ /dev/null @@ -1,147 +0,0 @@ -use super::{call, value}; -use crate::plugin_capnp::call_info; -use capnp::serialize_packed; -use nu_protocol::{ast::Call, ShellError, Value}; - -#[derive(Debug)] -pub struct CallInfo { - pub call: Call, - pub input: Value, -} - -pub fn write_buffer( - call: &Call, - input: &Value, - writer: &mut impl std::io::Write, -) -> Result<(), ShellError> { - let mut message = ::capnp::message::Builder::new_default(); - - let mut builder = message.init_root::(); - let value_builder = builder - .reborrow() - .get_input() - .map_err(|e| ShellError::DecodingError(e.to_string()))?; - - value::serialize_value(input, value_builder); - - let call_builder = builder - .reborrow() - .get_call() - .map_err(|e| ShellError::DecodingError(e.to_string()))?; - - call::serialize_call(call, call_builder) - .map_err(|e| ShellError::DecodingError(e.to_string()))?; - - serialize_packed::write_message(writer, &message) - .map_err(|e| ShellError::EncodingError(e.to_string())) -} - -pub fn read_buffer(reader: &mut impl std::io::BufRead) -> Result { - let message_reader = - serialize_packed::read_message(reader, ::capnp::message::ReaderOptions::new()).unwrap(); - - let reader = message_reader - .get_root::() - .map_err(|e| ShellError::DecodingError(e.to_string()))?; - - let call_reader = reader - .get_call() - .map_err(|e| ShellError::DecodingError(e.to_string()))?; - - let call = call::deserialize_call(call_reader) - .map_err(|e| ShellError::DecodingError(e.to_string()))?; - - let value_reader = reader - .get_input() - .map_err(|e| ShellError::DecodingError(e.to_string()))?; - - let input = value::deserialize_value(value_reader) - .map_err(|e| ShellError::DecodingError(e.to_string()))?; - - Ok(CallInfo { call, input }) -} - -#[cfg(test)] -mod tests { - use super::*; - use nu_protocol::{ - ast::{Call, Expr, Expression}, - Span, Spanned, Value, - }; - - fn compare_expressions(lhs: &Expression, rhs: &Expression) { - match (&lhs.expr, &rhs.expr) { - (Expr::Bool(a), Expr::Bool(b)) => assert_eq!(a, b), - (Expr::Int(a), Expr::Int(b)) => assert_eq!(a, b), - (Expr::Float(a), Expr::Float(b)) => assert_eq!(a, b), - (Expr::String(a), Expr::String(b)) => assert_eq!(a, b), - _ => panic!("not matching values"), - } - } - - #[test] - fn callinfo_round_trip() { - let input = Value::Bool { - val: false, - span: Span { start: 1, end: 20 }, - }; - - let call = Call { - decl_id: 1, - head: Span { start: 0, end: 10 }, - positional: vec![ - Expression { - expr: Expr::Float(1.0), - span: Span { start: 0, end: 10 }, - ty: nu_protocol::Type::Float, - custom_completion: None, - }, - Expression { - expr: Expr::String("something".into()), - span: Span { start: 0, end: 10 }, - ty: nu_protocol::Type::Float, - custom_completion: None, - }, - ], - named: vec![( - Spanned { - item: "name".to_string(), - span: Span { start: 0, end: 10 }, - }, - Some(Expression { - expr: Expr::Float(1.0), - span: Span { start: 0, end: 10 }, - ty: nu_protocol::Type::Float, - custom_completion: None, - }), - )], - }; - - let mut buffer: Vec = Vec::new(); - write_buffer(&call, &input, &mut buffer).expect("unable to serialize message"); - let call_info = read_buffer(&mut buffer.as_slice()).expect("unable to read message"); - - assert_eq!(input, call_info.input); - assert_eq!(call.head, call_info.call.head); - assert_eq!(call.positional.len(), call_info.call.positional.len()); - - call.positional - .iter() - .zip(call_info.call.positional.iter()) - .for_each(|(lhs, rhs)| compare_expressions(lhs, rhs)); - - call.named - .iter() - .zip(call_info.call.named.iter()) - .for_each(|(lhs, rhs)| { - // Comparing the keys - assert_eq!(lhs.0.item, rhs.0.item); - - match (&lhs.1, &rhs.1) { - (None, None) => {} - (Some(a), Some(b)) => compare_expressions(a, b), - _ => panic!("not matching values"), - } - }); - } -} diff --git a/crates/nu-plugin/src/serializers/mod.rs b/crates/nu-plugin/src/serializers/mod.rs index 4bd1691b69..9f96435fc9 100644 --- a/crates/nu-plugin/src/serializers/mod.rs +++ b/crates/nu-plugin/src/serializers/mod.rs @@ -1,4 +1,3 @@ pub mod call; -pub mod callinfo; pub mod signature; pub mod value; diff --git a/crates/nu-plugin/src/serializers/signature.rs b/crates/nu-plugin/src/serializers/signature.rs index cbff82580e..7b98281a0d 100644 --- a/crates/nu-plugin/src/serializers/signature.rs +++ b/crates/nu-plugin/src/serializers/signature.rs @@ -1,20 +1,6 @@ +use crate::plugin::PluginError; use crate::plugin_capnp::{argument, flag, option, signature, Shape}; -use capnp::serialize_packed; -use nu_protocol::{Flag, PositionalArg, ShellError, Signature, SyntaxShape}; - -pub fn write_buffer( - signature: &Signature, - writer: &mut impl std::io::Write, -) -> Result<(), ShellError> { - let mut message = ::capnp::message::Builder::new_default(); - - let builder = message.init_root::(); - - serialize_signature(signature, builder); - - serialize_packed::write_message(writer, &message) - .map_err(|e| ShellError::EncodingError(e.to_string())) -} +use nu_protocol::{Flag, PositionalArg, Signature, SyntaxShape}; pub(crate) fn serialize_signature(signature: &Signature, mut builder: signature::Builder) { builder.set_name(signature.name.as_str()); @@ -100,59 +86,48 @@ fn serialize_flag(arg: &Flag, mut builder: flag::Builder) { } } -pub fn read_buffer(reader: &mut impl std::io::BufRead) -> Result { - let message_reader = - serialize_packed::read_message(reader, ::capnp::message::ReaderOptions::new()).unwrap(); - - let reader = message_reader - .get_root::() - .map_err(|e| ShellError::DecodingError(e.to_string()))?; - - deserialize_signature(reader) -} - -pub(crate) fn deserialize_signature(reader: signature::Reader) -> Result { +pub(crate) fn deserialize_signature(reader: signature::Reader) -> Result { let name = reader .get_name() - .map_err(|e| ShellError::EncodingError(e.to_string()))?; + .map_err(|e| PluginError::EncodingError(e.to_string()))?; let usage = reader .get_usage() - .map_err(|e| ShellError::EncodingError(e.to_string()))?; + .map_err(|e| PluginError::EncodingError(e.to_string()))?; let extra_usage = reader .get_extra_usage() - .map_err(|e| ShellError::EncodingError(e.to_string()))?; + .map_err(|e| PluginError::EncodingError(e.to_string()))?; let is_filter = reader.get_is_filter(); // Deserializing required arguments let required_list = reader .get_required_positional() - .map_err(|e| ShellError::EncodingError(e.to_string()))?; + .map_err(|e| PluginError::EncodingError(e.to_string()))?; let required_positional = required_list .iter() .map(deserialize_argument) - .collect::, ShellError>>()?; + .collect::, PluginError>>()?; // Deserializing optional arguments let optional_list = reader .get_optional_positional() - .map_err(|e| ShellError::EncodingError(e.to_string()))?; + .map_err(|e| PluginError::EncodingError(e.to_string()))?; let optional_positional = optional_list .iter() .map(deserialize_argument) - .collect::, ShellError>>()?; + .collect::, PluginError>>()?; // Deserializing rest arguments let rest_option = reader .get_rest() - .map_err(|e| ShellError::EncodingError(e.to_string()))?; + .map_err(|e| PluginError::EncodingError(e.to_string()))?; let rest_positional = match rest_option.which() { Err(capnp::NotInSchema(_)) => None, Ok(option::None(())) => None, Ok(option::Some(rest_reader)) => { - let rest_reader = rest_reader.map_err(|e| ShellError::EncodingError(e.to_string()))?; + let rest_reader = rest_reader.map_err(|e| PluginError::EncodingError(e.to_string()))?; Some(deserialize_argument(rest_reader)?) } }; @@ -160,12 +135,12 @@ pub(crate) fn deserialize_signature(reader: signature::Reader) -> Result, ShellError>>()?; + .collect::, PluginError>>()?; Ok(Signature { name: name.to_string(), @@ -180,18 +155,18 @@ pub(crate) fn deserialize_signature(reader: signature::Reader) -> Result Result { +fn deserialize_argument(reader: argument::Reader) -> Result { let name = reader .get_name() - .map_err(|e| ShellError::EncodingError(e.to_string()))?; + .map_err(|e| PluginError::EncodingError(e.to_string()))?; let desc = reader .get_desc() - .map_err(|e| ShellError::EncodingError(e.to_string()))?; + .map_err(|e| PluginError::EncodingError(e.to_string()))?; let shape = reader .get_shape() - .map_err(|e| ShellError::EncodingError(e.to_string()))?; + .map_err(|e| PluginError::EncodingError(e.to_string()))?; let shape = match shape { Shape::String => SyntaxShape::String, @@ -210,33 +185,33 @@ fn deserialize_argument(reader: argument::Reader) -> Result Result { +fn deserialize_flag(reader: flag::Reader) -> Result { let long = reader .get_long() - .map_err(|e| ShellError::EncodingError(e.to_string()))?; + .map_err(|e| PluginError::EncodingError(e.to_string()))?; let desc = reader .get_desc() - .map_err(|e| ShellError::EncodingError(e.to_string()))?; + .map_err(|e| PluginError::EncodingError(e.to_string()))?; let required = reader.get_required(); let short = reader .get_short() - .map_err(|e| ShellError::EncodingError(e.to_string()))?; + .map_err(|e| PluginError::EncodingError(e.to_string()))?; let short = match short.which() { Err(capnp::NotInSchema(_)) => None, Ok(option::None(())) => None, Ok(option::Some(reader)) => { - let reader = reader.map_err(|e| ShellError::EncodingError(e.to_string()))?; + let reader = reader.map_err(|e| PluginError::EncodingError(e.to_string()))?; reader.chars().next() } }; let arg = reader .get_arg() - .map_err(|e| ShellError::EncodingError(e.to_string()))?; + .map_err(|e| PluginError::EncodingError(e.to_string()))?; let arg = match arg { Shape::None => None, @@ -260,8 +235,34 @@ fn deserialize_flag(reader: flag::Reader) -> Result { #[cfg(test)] mod tests { use super::*; + use capnp::serialize_packed; use nu_protocol::{Signature, SyntaxShape}; + pub fn write_buffer( + signature: &Signature, + writer: &mut impl std::io::Write, + ) -> Result<(), PluginError> { + let mut message = ::capnp::message::Builder::new_default(); + + let builder = message.init_root::(); + + serialize_signature(signature, builder); + + serialize_packed::write_message(writer, &message) + .map_err(|e| PluginError::EncodingError(e.to_string())) + } + + pub fn read_buffer(reader: &mut impl std::io::BufRead) -> Result { + let message_reader = + serialize_packed::read_message(reader, ::capnp::message::ReaderOptions::new()).unwrap(); + + let reader = message_reader + .get_root::() + .map_err(|e| PluginError::DecodingError(e.to_string()))?; + + deserialize_signature(reader) + } + #[test] fn value_round_trip() { let signature = Signature::build("nu-plugin") diff --git a/crates/nu-plugin/src/serializers/value.rs b/crates/nu-plugin/src/serializers/value.rs index c424a930dc..d2a641ddb2 100644 --- a/crates/nu-plugin/src/serializers/value.rs +++ b/crates/nu-plugin/src/serializers/value.rs @@ -1,20 +1,6 @@ +use crate::plugin::PluginError; use crate::plugin_capnp::value; -use capnp::serialize_packed; -use nu_protocol::{ShellError, Span, Value}; - -pub fn write_buffer(value: &Value, writer: &mut impl std::io::Write) -> Result<(), ShellError> { - let mut message = ::capnp::message::Builder::new_default(); - - let mut builder = message.init_root::(); - - let value_span = serialize_value(value, builder.reborrow()); - let mut span = builder.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) - .map_err(|e| ShellError::EncodingError(e.to_string())) -} +use nu_protocol::{Span, Value}; pub(crate) fn serialize_value(value: &Value, mut builder: value::Builder) -> Span { match value { @@ -55,21 +41,10 @@ pub(crate) fn serialize_value(value: &Value, mut builder: value::Builder) -> Spa } } -pub fn read_buffer(reader: &mut impl std::io::BufRead) -> Result { - let message_reader = - serialize_packed::read_message(reader, ::capnp::message::ReaderOptions::new()).unwrap(); - - let reader = message_reader - .get_root::() - .map_err(|e| ShellError::DecodingError(e.to_string()))?; - - deserialize_value(reader.reborrow()) -} - -pub(crate) fn deserialize_value(reader: value::Reader) -> Result { +pub(crate) fn deserialize_value(reader: value::Reader) -> Result { let span_reader = reader .get_span() - .map_err(|e| ShellError::DecodingError(e.to_string()))?; + .map_err(|e| PluginError::DecodingError(e.to_string()))?; let span = Span { start: span_reader.get_start() as usize, @@ -83,17 +58,17 @@ pub(crate) fn deserialize_value(reader: value::Reader) -> Result Ok(Value::Float { val, span }), Ok(value::String(val)) => { let string = val - .map_err(|e| ShellError::DecodingError(e.to_string()))? + .map_err(|e| PluginError::DecodingError(e.to_string()))? .to_string(); Ok(Value::String { val: string, span }) } Ok(value::List(vals)) => { - let values = vals.map_err(|e| ShellError::DecodingError(e.to_string()))?; + let values = vals.map_err(|e| PluginError::DecodingError(e.to_string()))?; let values_list = values .iter() .map(deserialize_value) - .collect::, ShellError>>()?; + .collect::, PluginError>>()?; Ok(Value::List { vals: values_list, @@ -109,8 +84,37 @@ pub(crate) fn deserialize_value(reader: value::Reader) -> Result Result<(), PluginError> { + let mut message = ::capnp::message::Builder::new_default(); + + let mut builder = message.init_root::(); + + let value_span = serialize_value(value, builder.reborrow()); + let mut span = builder.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) + .map_err(|e| PluginError::EncodingError(e.to_string())) + } + + pub fn read_buffer(reader: &mut impl std::io::BufRead) -> Result { + let message_reader = + serialize_packed::read_message(reader, ::capnp::message::ReaderOptions::new()).unwrap(); + + let reader = message_reader + .get_root::() + .map_err(|e| PluginError::DecodingError(e.to_string()))?; + + deserialize_value(reader.reborrow()) + } + #[test] fn value_round_trip() { let values = [