diff --git a/Cargo.lock b/Cargo.lock index 501c3f79fa..d38d6ea450 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -395,6 +395,7 @@ dependencies = [ "nu-json", "nu-parser", "nu-path", + "nu-plugin", "nu-protocol", "nu-table", "nu-term-grid", @@ -668,7 +669,6 @@ dependencies = [ "nu-json", "nu-parser", "nu-path", - "nu-plugin", "nu-protocol", "nu-table", "nu-term-grid", @@ -762,6 +762,16 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "nu_plugin_inc" +version = "0.1.0" +dependencies = [ + "nu-engine", + "nu-plugin", + "nu-protocol", + "semver", +] + [[package]] name = "num-integer" version = "0.1.44" @@ -861,6 +871,15 @@ dependencies = [ "regex", ] +[[package]] +name = "pest" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +dependencies = [ + "ucd-trie", +] + [[package]] name = "phf" version = "0.10.0" @@ -1125,6 +1144,24 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +dependencies = [ + "pest", +] + [[package]] name = "serde" version = "1.0.130" @@ -1393,6 +1430,12 @@ version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" +[[package]] +name = "ucd-trie" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" + [[package]] name = "uncased" version = "0.9.6" diff --git a/Cargo.toml b/Cargo.toml index 973fae44cc..c7ab76ab85 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ members = [ "crates/nu-command", "crates/nu-protocol", "crates/nu-plugin", + "crates/nu_plugin_inc", ] [dependencies] @@ -26,12 +27,17 @@ nu-json = { path="./crates/nu-json" } nu-parser = { path="./crates/nu-parser" } nu-path = { path="./crates/nu-path" } nu-protocol = { path = "./crates/nu-protocol" } +nu-plugin = { path = "./crates/nu-plugin", optional = true } nu-table = { path = "./crates/nu-table" } nu-term-grid = { path = "./crates/nu-term-grid" } miette = "3.0.0" ctrlc = "3.2.1" # mimalloc = { version = "*", default-features = false } +[features] +plugin = ["nu-plugin", "nu-parser/plugin", "nu-command/plugin"] +default = ["plugin"] + [dev-dependencies] tempfile = "3.2.0" assert_cmd = "1.0.7" diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index 99d13d33c3..6e03532062 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -13,7 +13,6 @@ 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" @@ -34,3 +33,4 @@ titlecase = "1.1.0" [features] trash-support = ["trash"] +plugin = ["nu-parser/plugin"] diff --git a/crates/nu-command/src/core_commands/mod.rs b/crates/nu-command/src/core_commands/mod.rs index 626ac16cda..86ff25b648 100644 --- a/crates/nu-command/src/core_commands/mod.rs +++ b/crates/nu-command/src/core_commands/mod.rs @@ -9,8 +9,6 @@ mod hide; mod if_; mod let_; mod module; -mod register; -mod run_plugin; mod source; mod use_; @@ -25,7 +23,11 @@ pub use hide::Hide; pub use if_::If; pub use let_::Let; pub use module::Module; -pub use register::Register; -pub use run_plugin::RunPlugin; pub use source::Source; pub use use_::Use; + +#[cfg(feature = "plugin")] +mod register; + +#[cfg(feature = "plugin")] +pub use register::Register; diff --git a/crates/nu-command/src/core_commands/run_plugin.rs b/crates/nu-command/src/core_commands/run_plugin.rs deleted file mode 100644 index c8b448fb02..0000000000 --- a/crates/nu-command/src/core_commands/run_plugin.rs +++ /dev/null @@ -1,30 +0,0 @@ -use nu_protocol::ast::Call; -use nu_protocol::engine::{Command, EngineState, Stack}; -use nu_protocol::{PipelineData, Signature}; - -#[derive(Clone)] -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: &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 e4c8fcda1e..02d2922092 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -79,10 +79,8 @@ pub fn create_default_context() -> EngineState { Mv, ParEach, Ps, - Register, Range, Rm, - RunPlugin, Select, Size, Split, @@ -101,6 +99,9 @@ pub fn create_default_context() -> EngineState { Zip ); + #[cfg(feature = "plugin")] + bind_command!(Register); + // This is a WIP proof of concept bind_command!(ListGitBranches, Git, GitCheckout, Source); diff --git a/crates/nu-parser/Cargo.toml b/crates/nu-parser/Cargo.toml index 2dc4d6dc27..2eb26d7f1f 100644 --- a/crates/nu-parser/Cargo.toml +++ b/crates/nu-parser/Cargo.toml @@ -7,4 +7,7 @@ edition = "2018" miette = "3.0.0" thiserror = "1.0.29" nu-protocol = { path = "../nu-protocol"} -nu-plugin = { path = "../nu-plugin"} +nu-plugin = { path = "../nu-plugin", optional=true} + +[features] +plugin = ["nu-plugin"] diff --git a/crates/nu-parser/src/lib.rs b/crates/nu-parser/src/lib.rs index dc5534c9e6..639c43d5a4 100644 --- a/crates/nu-parser/src/lib.rs +++ b/crates/nu-parser/src/lib.rs @@ -11,6 +11,9 @@ 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_plugin, parse_use, + parse_alias, parse_def, parse_def_predecl, parse_let, parse_module, parse_use, }; pub use parser::{find_captures_in_expr, parse, Import, VarDecl}; + +#[cfg(feature = "plugin")] +pub use parse_keywords::parse_plugin; diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index 200fb9d7c5..30c9092c78 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -1,4 +1,3 @@ -use nu_plugin::plugin::{get_signature, PluginDeclaration}; use nu_protocol::{ ast::{Block, Call, Expr, Expression, ImportPattern, ImportPatternMember, Pipeline, Statement}, engine::StateWorkingSet, @@ -6,6 +5,9 @@ use nu_protocol::{ }; use std::path::Path; +#[cfg(feature = "plugin")] +use nu_plugin::plugin::{get_signature, PluginDeclaration}; + use crate::{ lex, lite_parse, parser::{ @@ -925,6 +927,7 @@ pub fn parse_source( ) } +#[cfg(feature = "plugin")] pub fn parse_plugin( working_set: &mut StateWorkingSet, spans: &[Span], @@ -960,11 +963,14 @@ pub fn parse_plugin( // get signature from plugin match get_signature(source_file) { Err(err) => Some(ParseError::PluginError(format!("{}", err))), - Ok(signature) => { - // create plugin command declaration (need struct impl Command) - // store declaration in working set - let plugin_decl = PluginDeclaration::new(filename, signature); - working_set.add_decl(Box::new(plugin_decl)); + Ok(signatures) => { + for signature in signatures { + // create plugin command declaration (need struct impl Command) + // store declaration in working set + let plugin_decl = + PluginDeclaration::new(filename.clone(), signature); + working_set.add_decl(Box::new(plugin_decl)); + } None } diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 3d932b4c89..4b139782b9 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -15,10 +15,12 @@ use nu_protocol::{ }; use crate::parse_keywords::{ - parse_alias, parse_def, parse_def_predecl, parse_hide, parse_let, parse_module, parse_plugin, - parse_use, + parse_alias, parse_def, parse_def_predecl, parse_hide, parse_let, parse_module, parse_use, }; +#[cfg(feature = "plugin")] +use crate::parse_keywords::parse_plugin; + #[derive(Debug, Clone)] pub enum Import {} @@ -3148,6 +3150,7 @@ pub fn parse_statement( Some(ParseError::UnexpectedKeyword("export".into(), spans[0])), ), b"hide" => parse_hide(working_set, spans), + #[cfg(feature = "plugin")] b"register" => parse_plugin(working_set, spans), _ => { let (expr, err) = parse_expression(working_set, spans, true); diff --git a/crates/nu-plugin/schema/plugin.capnp b/crates/nu-plugin/schema/plugin.capnp index 8992bc0f99..ed47822602 100644 --- a/crates/nu-plugin/schema/plugin.capnp +++ b/crates/nu-plugin/schema/plugin.capnp @@ -111,8 +111,9 @@ struct Call { } struct CallInfo { - call @0: Call; - input @1: Value; + name @0: Text; + call @1: Call; + input @2: Value; } # Main communication structs with the plugin @@ -126,7 +127,7 @@ struct PluginCall { struct PluginResponse { union { error @0 :Text; - signature @1 :Signature; + signature @1 :List(Signature); value @2 :Value; } } diff --git a/crates/nu-plugin/src/plugin.rs b/crates/nu-plugin/src/plugin.rs index 672c648463..efa14094db 100644 --- a/crates/nu-plugin/src/plugin.rs +++ b/crates/nu-plugin/src/plugin.rs @@ -11,6 +11,7 @@ const OUTPUT_BUFFER_SIZE: usize = 8192; #[derive(Debug)] pub struct CallInfo { + pub name: String, pub call: Call, pub input: Value, } @@ -26,7 +27,7 @@ pub enum PluginCall { #[derive(Debug)] pub enum PluginResponse { Error(String), - Signature(Box), + Signature(Vec), Value(Box), } @@ -37,6 +38,7 @@ pub enum PluginError { UnableToSpawn(String), EncodingError(String), DecodingError(String), + RunTimeError(String), } impl Display for PluginError { @@ -53,11 +55,14 @@ impl Display for PluginError { PluginError::DecodingError(err) => { write!(f, "error while decoding: {}", err) } + PluginError::RunTimeError(err) => { + write!(f, "runtime error: {}", err) + } } } } -pub fn get_signature(path: &Path) -> Result, PluginError> { +pub fn get_signature(path: &Path) -> Result, PluginError> { let mut plugin_cmd = create_command(path); // Both stdout and stdin are piped so we can get the information from the plugin @@ -114,12 +119,12 @@ fn create_command(path: &Path) -> CommandSys { #[derive(Debug, Clone)] pub struct PluginDeclaration { name: String, - signature: Box, + signature: Signature, filename: String, } impl PluginDeclaration { - pub fn new(filename: String, signature: Box) -> Self { + pub fn new(filename: String, signature: Signature) -> Self { Self { name: signature.name.clone(), signature, @@ -134,11 +139,11 @@ impl Command for PluginDeclaration { } fn signature(&self) -> Signature { - self.signature.as_ref().clone() + self.signature.clone() } fn usage(&self) -> &str { - "plugin name plus arguments" + self.signature.usage.as_str() } fn run( @@ -175,6 +180,7 @@ impl Command for PluginDeclaration { // PluginCall information let plugin_call = PluginCall::CallInfo(Box::new(CallInfo { + name: self.name.clone(), call: call.clone(), input, })); @@ -221,8 +227,8 @@ impl Command for PluginDeclaration { /// The `Plugin` trait defines the API which plugins use to "hook" into nushell. pub trait Plugin { - fn signature(&self) -> Signature; - fn run(&self, call: &Call, input: &Value) -> Result; + fn signature(&self) -> Vec; + fn run(&mut self, name: &str, call: &Call, input: &Value) -> Result; } // Function used in the plugin definition for the communication protocol between @@ -242,12 +248,12 @@ pub fn serve_plugin(plugin: &mut impl Plugin) { match plugin_call { // Sending the signature back to nushell to create the declaration definition PluginCall::Signature => { - let response = PluginResponse::Signature(Box::new(plugin.signature())); + let response = PluginResponse::Signature(plugin.signature()); encode_response(&response, &mut std::io::stdout()) .expect("Error encoding response"); } PluginCall::CallInfo(call_info) => { - let value = plugin.run(&call_info.call, &call_info.input); + let value = plugin.run(&call_info.name, &call_info.call, &call_info.input); let response = match value { Ok(value) => PluginResponse::Value(Box::new(value)), diff --git a/crates/nu-plugin/src/plugin_call.rs b/crates/nu-plugin/src/plugin_call.rs index 40c6c295e1..19e0c73d69 100644 --- a/crates/nu-plugin/src/plugin_call.rs +++ b/crates/nu-plugin/src/plugin_call.rs @@ -1,7 +1,9 @@ use crate::plugin::{CallInfo, PluginCall, PluginError, PluginResponse}; use crate::plugin_capnp::{plugin_call, plugin_response}; +use crate::serializers::signature::deserialize_signature; use crate::serializers::{call, signature, value}; use capnp::serialize_packed; +use nu_protocol::Signature; pub fn encode_call( plugin_call: &PluginCall, @@ -16,6 +18,9 @@ pub fn encode_call( PluginCall::CallInfo(call_info) => { let mut call_info_builder = builder.reborrow().init_call_info(); + // Serializing name from the call + call_info_builder.set_name(call_info.name.as_str()); + // Serializing argument information from the call let call_builder = call_info_builder .reborrow() @@ -53,6 +58,10 @@ pub fn decode_call(reader: &mut impl std::io::BufRead) -> Result { let reader = reader.map_err(|e| PluginError::DecodingError(e.to_string()))?; + let name = reader + .get_name() + .map_err(|e| PluginError::DecodingError(e.to_string()))?; + let call_reader = reader .get_call() .map_err(|e| PluginError::DecodingError(e.to_string()))?; @@ -67,7 +76,11 @@ pub fn decode_call(reader: &mut impl std::io::BufRead) -> Result builder.reborrow().set_error(msg.as_str()), - PluginResponse::Signature(sign) => { - let signature_builder = builder.reborrow().init_signature(); - signature::serialize_signature(sign, signature_builder) + PluginResponse::Signature(signatures) => { + let mut signature_list_builder = + builder.reborrow().init_signature(signatures.len() as u32); + + for (index, signature) in signatures.iter().enumerate() { + let signature_builder = signature_list_builder.reborrow().get(index as u32); + signature::serialize_signature(signature, signature_builder) + } } PluginResponse::Value(val) => { let value_builder = builder.reborrow().init_value(); @@ -113,10 +131,13 @@ pub fn decode_response(reader: &mut impl std::io::BufRead) -> Result { 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))) + let signatures = reader + .iter() + .map(deserialize_signature) + .collect::, PluginError>>()?; + + Ok(PluginResponse::Signature(signatures)) } Ok(plugin_response::Value(reader)) => { let reader = reader.map_err(|e| PluginError::DecodingError(e.to_string()))?; @@ -163,6 +184,8 @@ mod tests { #[test] fn callinfo_round_trip_callinfo() { + let name = "test".to_string(); + let input = Value::Bool { val: false, span: Span { start: 1, end: 20 }, @@ -200,6 +223,7 @@ mod tests { }; let plugin_call = PluginCall::CallInfo(Box::new(CallInfo { + name: name.clone(), call: call.clone(), input: input.clone(), })); @@ -211,6 +235,7 @@ mod tests { match returned { PluginCall::Signature => panic!("returned wrong call type"), PluginCall::CallInfo(call_info) => { + assert_eq!(name, call_info.name); assert_eq!(input, call_info.input); assert_eq!(call.head, call_info.call.head); assert_eq!(call.positional.len(), call_info.call.positional.len()); @@ -251,7 +276,7 @@ mod tests { ) .rest("remaining", SyntaxShape::Int, "remaining"); - let response = PluginResponse::Signature(Box::new(signature.clone())); + let response = PluginResponse::Signature(vec![signature.clone()]); let mut buffer: Vec = Vec::new(); encode_response(&response, &mut buffer).expect("unable to serialize message"); @@ -262,32 +287,33 @@ mod tests { PluginResponse::Error(_) => panic!("returned wrong call type"), 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); + assert!(returned_signature.len() == 1); + assert_eq!(signature.name, returned_signature[0].name); + assert_eq!(signature.usage, returned_signature[0].usage); + assert_eq!(signature.extra_usage, returned_signature[0].extra_usage); + assert_eq!(signature.is_filter, returned_signature[0].is_filter); signature .required_positional .iter() - .zip(returned_signature.required_positional.iter()) + .zip(returned_signature[0].required_positional.iter()) .for_each(|(lhs, rhs)| assert_eq!(lhs, rhs)); signature .optional_positional .iter() - .zip(returned_signature.optional_positional.iter()) + .zip(returned_signature[0].optional_positional.iter()) .for_each(|(lhs, rhs)| assert_eq!(lhs, rhs)); signature .named .iter() - .zip(returned_signature.named.iter()) + .zip(returned_signature[0].named.iter()) .for_each(|(lhs, rhs)| assert_eq!(lhs, rhs)); assert_eq!( signature.rest_positional, - returned_signature.rest_positional, + returned_signature[0].rest_positional, ); } } diff --git a/crates/nu-plugin/src/plugin_capnp.rs b/crates/nu-plugin/src/plugin_capnp.rs index cd49bad6e0..4aca078fb7 100644 --- a/crates/nu-plugin/src/plugin_capnp.rs +++ b/crates/nu-plugin/src/plugin_capnp.rs @@ -3480,25 +3480,35 @@ pub mod call_info { self.reader.total_size() } #[inline] - pub fn get_call(self) -> ::capnp::Result> { + pub fn get_name(self) -> ::capnp::Result<::capnp::text::Reader<'a>> { ::capnp::traits::FromPointerReader::get_from_pointer( &self.reader.get_pointer_field(0), ::core::option::Option::None, ) } - pub fn has_call(&self) -> bool { + pub fn has_name(&self) -> bool { !self.reader.get_pointer_field(0).is_null() } #[inline] - pub fn get_input(self) -> ::capnp::Result> { + pub fn get_call(self) -> ::capnp::Result> { ::capnp::traits::FromPointerReader::get_from_pointer( &self.reader.get_pointer_field(1), ::core::option::Option::None, ) } - pub fn has_input(&self) -> bool { + pub fn has_call(&self) -> bool { !self.reader.get_pointer_field(1).is_null() } + #[inline] + pub fn get_input(self) -> ::capnp::Result> { + ::capnp::traits::FromPointerReader::get_from_pointer( + &self.reader.get_pointer_field(2), + ::core::option::Option::None, + ) + } + pub fn has_input(&self) -> bool { + !self.reader.get_pointer_field(2).is_null() + } } pub struct Builder<'a> { @@ -3571,34 +3581,52 @@ pub mod call_info { self.builder.into_reader().total_size() } #[inline] - pub fn get_call(self) -> ::capnp::Result> { + pub fn get_name(self) -> ::capnp::Result<::capnp::text::Builder<'a>> { ::capnp::traits::FromPointerBuilder::get_from_pointer( self.builder.get_pointer_field(0), ::core::option::Option::None, ) } #[inline] + pub fn set_name(&mut self, value: ::capnp::text::Reader<'_>) { + self.builder.get_pointer_field(0).set_text(value); + } + #[inline] + pub fn init_name(self, size: u32) -> ::capnp::text::Builder<'a> { + self.builder.get_pointer_field(0).init_text(size) + } + pub fn has_name(&self) -> bool { + !self.builder.get_pointer_field(0).is_null() + } + #[inline] + pub fn get_call(self) -> ::capnp::Result> { + ::capnp::traits::FromPointerBuilder::get_from_pointer( + self.builder.get_pointer_field(1), + ::core::option::Option::None, + ) + } + #[inline] pub fn set_call( &mut self, value: crate::plugin_capnp::call::Reader<'_>, ) -> ::capnp::Result<()> { ::capnp::traits::SetPointerBuilder::set_pointer_builder( - self.builder.get_pointer_field(0), + self.builder.get_pointer_field(1), value, false, ) } #[inline] pub fn init_call(self) -> crate::plugin_capnp::call::Builder<'a> { - ::capnp::traits::FromPointerBuilder::init_pointer(self.builder.get_pointer_field(0), 0) + ::capnp::traits::FromPointerBuilder::init_pointer(self.builder.get_pointer_field(1), 0) } pub fn has_call(&self) -> bool { - !self.builder.get_pointer_field(0).is_null() + !self.builder.get_pointer_field(1).is_null() } #[inline] pub fn get_input(self) -> ::capnp::Result> { ::capnp::traits::FromPointerBuilder::get_from_pointer( - self.builder.get_pointer_field(1), + self.builder.get_pointer_field(2), ::core::option::Option::None, ) } @@ -3608,17 +3636,17 @@ pub mod call_info { value: crate::plugin_capnp::value::Reader<'_>, ) -> ::capnp::Result<()> { ::capnp::traits::SetPointerBuilder::set_pointer_builder( - self.builder.get_pointer_field(1), + self.builder.get_pointer_field(2), value, false, ) } #[inline] pub fn init_input(self) -> crate::plugin_capnp::value::Builder<'a> { - ::capnp::traits::FromPointerBuilder::init_pointer(self.builder.get_pointer_field(1), 0) + ::capnp::traits::FromPointerBuilder::init_pointer(self.builder.get_pointer_field(2), 0) } pub fn has_input(&self) -> bool { - !self.builder.get_pointer_field(1).is_null() + !self.builder.get_pointer_field(2).is_null() } } @@ -3634,17 +3662,17 @@ pub mod call_info { } impl Pipeline { pub fn get_call(&self) -> crate::plugin_capnp::call::Pipeline { - ::capnp::capability::FromTypelessPipeline::new(self._typeless.get_pointer_field(0)) + ::capnp::capability::FromTypelessPipeline::new(self._typeless.get_pointer_field(1)) } pub fn get_input(&self) -> crate::plugin_capnp::value::Pipeline { - ::capnp::capability::FromTypelessPipeline::new(self._typeless.get_pointer_field(1)) + ::capnp::capability::FromTypelessPipeline::new(self._typeless.get_pointer_field(2)) } } mod _private { use capnp::private::layout; pub const STRUCT_SIZE: layout::StructSize = layout::StructSize { data: 0, - pointers: 2, + pointers: 3, }; pub const TYPE_ID: u64 = 0x8e03_127e_9170_7d6a; } @@ -4073,7 +4101,7 @@ pub mod plugin_response { #[inline] pub fn set_signature( &mut self, - value: crate::plugin_capnp::signature::Reader<'_>, + value: ::capnp::struct_list::Reader<'a, crate::plugin_capnp::signature::Owned>, ) -> ::capnp::Result<()> { self.builder.set_data_field::(0, 1); ::capnp::traits::SetPointerBuilder::set_pointer_builder( @@ -4083,9 +4111,15 @@ pub mod plugin_response { ) } #[inline] - pub fn init_signature(self) -> crate::plugin_capnp::signature::Builder<'a> { + pub fn init_signature( + self, + size: u32, + ) -> ::capnp::struct_list::Builder<'a, crate::plugin_capnp::signature::Owned> { self.builder.set_data_field::(0, 1); - ::capnp::traits::FromPointerBuilder::init_pointer(self.builder.get_pointer_field(0), 0) + ::capnp::traits::FromPointerBuilder::init_pointer( + self.builder.get_pointer_field(0), + size, + ) } pub fn has_signature(&self) -> bool { if self.builder.get_data_field::(0) != 1 { @@ -4168,12 +4202,12 @@ pub mod plugin_response { } pub type WhichReader<'a> = Which< ::capnp::Result<::capnp::text::Reader<'a>>, - ::capnp::Result>, + ::capnp::Result<::capnp::struct_list::Reader<'a, crate::plugin_capnp::signature::Owned>>, ::capnp::Result>, >; pub type WhichBuilder<'a> = Which< ::capnp::Result<::capnp::text::Builder<'a>>, - ::capnp::Result>, + ::capnp::Result<::capnp::struct_list::Builder<'a, crate::plugin_capnp::signature::Owned>>, ::capnp::Result>, >; } diff --git a/crates/nu_plugin_inc/Cargo.toml b/crates/nu_plugin_inc/Cargo.toml new file mode 100644 index 0000000000..b3f4702268 --- /dev/null +++ b/crates/nu_plugin_inc/Cargo.toml @@ -0,0 +1,17 @@ +[package] +authors = ["The Nu Project Contributors"] +description = "A version incrementer plugin for Nushell" +edition = "2018" +license = "MIT" +name = "nu_plugin_inc" +version = "0.1.0" + +[lib] +doctest = false + +[dependencies] +nu-plugin = { path="../nu-plugin", version = "0.1.0" } +nu-protocol = { path="../nu-protocol", version = "0.1.0" } +nu-engine = { path="../nu-engine", version = "0.1.0" } + +semver = "0.11.0" diff --git a/crates/nu_plugin_inc/src/inc.rs b/crates/nu_plugin_inc/src/inc.rs new file mode 100644 index 0000000000..c0327b39be --- /dev/null +++ b/crates/nu_plugin_inc/src/inc.rs @@ -0,0 +1,138 @@ +use nu_plugin::plugin::PluginError; +use nu_protocol::{Span, Value}; + +#[derive(Debug, Eq, PartialEq)] +pub enum Action { + SemVerAction(SemVerAction), + Default, +} + +#[derive(Debug, Eq, PartialEq)] +pub enum SemVerAction { + Major, + Minor, + Patch, +} + +#[derive(Default)] +pub struct Inc { + pub error: Option, + pub action: Option, +} + +impl Inc { + pub fn new() -> Self { + Default::default() + } + + fn apply(&self, input: &str) -> Value { + match &self.action { + Some(Action::SemVerAction(act_on)) => { + let mut ver = match semver::Version::parse(input) { + Ok(parsed_ver) => parsed_ver, + Err(_) => { + return Value::String { + val: input.to_string(), + span: Span::unknown(), + } + } + }; + + match act_on { + SemVerAction::Major => ver.increment_major(), + SemVerAction::Minor => ver.increment_minor(), + SemVerAction::Patch => ver.increment_patch(), + } + + Value::String { + val: ver.to_string(), + span: Span::unknown(), + } + } + Some(Action::Default) | None => match input.parse::() { + Ok(v) => Value::String { + val: (v + 1).to_string(), + span: Span::unknown(), + }, + Err(_) => Value::String { + val: input.to_string(), + span: Span::unknown(), + }, + }, + } + } + + pub fn for_semver(&mut self, part: SemVerAction) { + if self.permit() { + self.action = Some(Action::SemVerAction(part)); + } else { + self.log_error("can only apply one"); + } + } + + fn permit(&mut self) -> bool { + self.action.is_none() + } + + fn log_error(&mut self, message: &str) { + self.error = Some(message.to_string()); + } + + pub fn usage() -> &'static str { + "Usage: inc field [--major|--minor|--patch]" + } + + pub fn inc(&self, value: &Value) -> Result { + match value { + Value::Int { val, span } => Ok(Value::Int { + val: val + 1, + span: span.clone(), + }), + Value::String { val, .. } => Ok(self.apply(val)), + _ => Err(PluginError::RunTimeError("incrementable value".to_string())), + } + } +} + +#[cfg(test)] +mod tests { + mod semver { + use nu_protocol::{Span, Value}; + + use crate::inc::SemVerAction; + use crate::Inc; + + #[test] + fn major() { + let expected = Value::String { + val: "1.0.0".to_string(), + span: Span::unknown(), + }; + let mut inc = Inc::new(); + inc.for_semver(SemVerAction::Major); + assert_eq!(inc.apply("0.1.3"), expected) + } + + #[test] + fn minor() { + let expected = Value::String { + val: "0.2.0".to_string(), + span: Span::unknown(), + }; + let mut inc = Inc::new(); + inc.for_semver(SemVerAction::Minor); + assert_eq!(inc.apply("0.1.3"), expected) + } + + #[test] + fn patch() { + let expected = Value::String { + val: "0.1.4".to_string(), + span: Span::unknown(), + }; + let mut inc = Inc::new(); + inc.for_semver(SemVerAction::Patch); + assert_eq!(inc.apply("0.1.3"), expected) + } + } +} diff --git a/crates/nu_plugin_inc/src/lib.rs b/crates/nu_plugin_inc/src/lib.rs new file mode 100644 index 0000000000..e5428d8e6f --- /dev/null +++ b/crates/nu_plugin_inc/src/lib.rs @@ -0,0 +1,4 @@ +mod inc; +mod nu; + +pub use inc::Inc; diff --git a/crates/nu_plugin_inc/src/main.rs b/crates/nu_plugin_inc/src/main.rs new file mode 100644 index 0000000000..7245f1fbca --- /dev/null +++ b/crates/nu_plugin_inc/src/main.rs @@ -0,0 +1,6 @@ +use nu_plugin::serve_plugin; +use nu_plugin_inc::Inc; + +fn main() { + serve_plugin(&mut Inc::new()) +} diff --git a/crates/nu_plugin_inc/src/nu/mod.rs b/crates/nu_plugin_inc/src/nu/mod.rs new file mode 100644 index 0000000000..8eaf2fc9f0 --- /dev/null +++ b/crates/nu_plugin_inc/src/nu/mod.rs @@ -0,0 +1,47 @@ +use crate::inc::SemVerAction; +use crate::Inc; +use nu_plugin::{plugin::PluginError, Plugin}; +use nu_protocol::ast::Call; +use nu_protocol::{Signature, Span, Value}; + +impl Plugin for Inc { + fn signature(&self) -> Vec { + vec![Signature::build("inc") + .desc("Increment a value or version. Optionally use the column of a table.") + .switch( + "major", + "increment the major version (eg 1.2.1 -> 2.0.0)", + Some('M'), + ) + .switch( + "minor", + "increment the minor version (eg 1.2.1 -> 1.3.0)", + Some('m'), + ) + .switch( + "patch", + "increment the patch version (eg 1.2.1 -> 1.2.2)", + Some('p'), + )] + } + + fn run(&mut self, name: &str, call: &Call, input: &Value) -> Result { + if name != "inc" { + return Ok(Value::Nothing { + span: Span::unknown(), + }); + } + + if call.has_flag("major") { + self.for_semver(SemVerAction::Major); + } + if call.has_flag("minor") { + self.for_semver(SemVerAction::Minor); + } + if call.has_flag("patch") { + self.for_semver(SemVerAction::Patch); + } + + self.inc(input) + } +}