diff --git a/Cargo.lock b/Cargo.lock index f4495d6bfa..5ca6d3985f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3503,6 +3503,16 @@ dependencies = [ "semver", ] +[[package]] +name = "nu_plugin_once" +version = "0.1.0" +dependencies = [ + "nu-cmd-lang", + "nu-plugin", + "nu-plugin-test-support", + "nu-protocol", +] + [[package]] name = "nu_plugin_polars" version = "0.96.2" diff --git a/Cargo.toml b/Cargo.toml index e60acd2548..c9281927c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,7 @@ members = [ "crates/nu_plugin_query", "crates/nu_plugin_custom_values", "crates/nu_plugin_formats", + "crates/nu_plugin_once", "crates/nu_plugin_polars", "crates/nu_plugin_stress_internals", "crates/nu-std", diff --git a/Cross.toml b/Cross.toml index 804afebdba..f21fd3b6c3 100644 --- a/Cross.toml +++ b/Cross.toml @@ -1,18 +1,10 @@ # Configuration for cross-rs: https://github.com/cross-rs/cross # Run cross-rs like this: -# cross build --target aarch64-unknown-linux-gnu --release -# or -# cross build --target aarch64-unknown-linux-musl --release --features=static-link-openssl - -[target.aarch64-unknown-linux-gnu] -pre-build = [ - "dpkg --add-architecture $CROSS_DEB_ARCH", - "apt-get update && apt-get install --assume-yes libssl-dev:$CROSS_DEB_ARCH clang" -] +# cross build --target i686-unknown-linux-musl --release --features=static-link-openssl # NOTE: for musl you will need to build with --features=static-link-openssl -[target.aarch64-unknown-linux-musl] +[target.i686-unknown-linux-musl] pre-build = [ - "dpkg --add-architecture $CROSS_DEB_ARCH", - "apt-get update && apt-get install --assume-yes clang" -] + "dpkg --add-architecture i386", + "apt-get update && apt-get install --assume-yes libssl-dev:i386 clang" +] \ No newline at end of file diff --git a/crates/nu_plugin_once/Cargo.toml b/crates/nu_plugin_once/Cargo.toml new file mode 100644 index 0000000000..b3e9e866dc --- /dev/null +++ b/crates/nu_plugin_once/Cargo.toml @@ -0,0 +1,23 @@ +[package] +authors = ["Chris Daßler"] +description = "A object oriented shell plugin for Nushell" +repository = "https://github.com/nushell/nushell/tree/main/crates/nu_plugin_example" +edition = "2021" +license = "MIT" +name = "nu_plugin_once" +version = "0.1.0" + +[[bin]] +name = "nu_plugin_once" +bench = false + +[lib] +bench = false + +[dependencies] +nu-plugin = { path = "../nu-plugin", version = "0.96.1" } +nu-protocol = { path = "../nu-protocol", version = "0.96.1", features = ["plugin"] } + +[dev-dependencies] +nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.96.1" } +nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.1" } \ No newline at end of file diff --git a/crates/nu_plugin_once/README.md b/crates/nu_plugin_once/README.md new file mode 100644 index 0000000000..e1d839a0b0 --- /dev/null +++ b/crates/nu_plugin_once/README.md @@ -0,0 +1,36 @@ +# Plugin Once + +Crate with a simple example of the Plugin trait that needs to be implemented +in order to create a binary that can be registered into nushell declaration list + +## `once config` + +This subcommand demonstrates sending configuration from the nushell `$env.config` to a plugin. + +To make use of the plugin after building `nushell` run: + +```nushell +plugin add target/debug/nu_plugin_once +# or then either restart your current nushell session or run: +plugin use target/debug/nu_plugin_once +``` + +The configuration for the plugin lives in `$env.config.plugins.once`: + +```nushell +$env.config = { + plugins: { + once: [ + some + values + ] + } +} +``` + +To list plugin values run: + +```nushell +once config +``` + diff --git a/crates/nu_plugin_once/src/commands/main.rs b/crates/nu_plugin_once/src/commands/main.rs new file mode 100644 index 0000000000..f09be52652 --- /dev/null +++ b/crates/nu_plugin_once/src/commands/main.rs @@ -0,0 +1,47 @@ +use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand}; +use nu_protocol::{Category, LabeledError, Signature, Value}; + +use crate::OncePlugin; + +pub struct Main; + +impl SimplePluginCommand for Main { + type Plugin = OncePlugin; + + fn name(&self) -> &str { + "once" + } + + fn usage(&self) -> &str { + "once commands for Nushell plugins" + } + + fn extra_usage(&self) -> &str { + r#" +The `once` plugin demonstrates usage of the Nushell plugin API. + +Several commands provided to test and demonstrate different capabilities of +plugins exposed through the API. None of these commands are intended to be +particularly useful. +"# + .trim() + } + + fn signature(&self) -> Signature { + Signature::build(self.name()).category(Category::Experimental) + } + + fn search_terms(&self) -> Vec<&str> { + vec!["once"] + } + + fn run( + &self, + _plugin: &Self::Plugin, + engine: &EngineInterface, + call: &EvaluatedCall, + _input: &Value, + ) -> Result { + Ok(Value::string(engine.get_help()?, call.head)) + } +} diff --git a/crates/nu_plugin_once/src/commands/mod.rs b/crates/nu_plugin_once/src/commands/mod.rs new file mode 100644 index 0000000000..de33edeaca --- /dev/null +++ b/crates/nu_plugin_once/src/commands/mod.rs @@ -0,0 +1,10 @@ +// `once` command - just suggests to call --help +mod main; + +pub use main::Main; + +mod scenario; +pub use scenario::Scenario; + +mod scenario_deploy; +pub use scenario_deploy::ScenarioDeploy; \ No newline at end of file diff --git a/crates/nu_plugin_once/src/commands/scenario.rs b/crates/nu_plugin_once/src/commands/scenario.rs new file mode 100644 index 0000000000..f90b4fdd00 --- /dev/null +++ b/crates/nu_plugin_once/src/commands/scenario.rs @@ -0,0 +1,76 @@ +use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand}; +use nu_protocol::{record, Category, Example, LabeledError, Signature, Type, Value}; + +use crate::OncePlugin; + +pub struct Scenario; + +impl SimplePluginCommand for Scenario { + type Plugin = OncePlugin; + + fn name(&self) -> &str { + "once scenario" + } + + fn usage(&self) -> &str { + "Manage once scenarios on local or remote servers." + } + + fn extra_usage(&self) -> &str { + "Extra usage for once scenario" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .category(Category::Experimental) + .input_output_type(Type::Nothing, Type::table()) + } + + fn search_terms(&self) -> Vec<&str> { + vec!["once", "scenario"] + } + + fn examples(&self) -> Vec { + vec![Example { + example: "once scenario", + description: "List all scenarios", + result: Some(Value::test_list(vec![Value::test_record(record! { + "name" => Value::test_string("test"), + "filename" => Value::test_string("test.scenario.env") + })])), + }] + } + + fn run( + &self, + _plugin: &OncePlugin, + _engine: &EngineInterface, + call: &EvaluatedCall, + _input: &Value, + ) -> Result { + let head = call.head; + let list = vec![ + Value::record( + record! { + "name" => Value::test_string("dev"), + "filename" => Value::test_string("dev.scenario.env") + }, + head, + ), + Value::record( + record! { + "name" => Value::test_string("certbot"), + "filename" => Value::test_string("certbot.scenario.env") + }, + head, + ), + ]; + Ok(Value::list(list, head)) + } +} + +#[test] +fn test_examples() -> Result<(), nu_protocol::ShellError> { + use nu_plugin_test_support::PluginTest; + PluginTest::new("once", OncePlugin.into())?.test_command_examples(&Scenario) +} diff --git a/crates/nu_plugin_once/src/commands/scenario_deploy.rs b/crates/nu_plugin_once/src/commands/scenario_deploy.rs new file mode 100644 index 0000000000..6361931cae --- /dev/null +++ b/crates/nu_plugin_once/src/commands/scenario_deploy.rs @@ -0,0 +1,65 @@ +use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand}; +use nu_protocol::{Category, Example, LabeledError, Signature, SyntaxShape, Value}; + +use crate::OncePlugin; + +pub struct ScenarioDeploy; + +impl SimplePluginCommand for ScenarioDeploy { + type Plugin = OncePlugin; + + fn name(&self) -> &str { + "once scenario.deploy" + } + + fn usage(&self) -> &str { + "Deploy once scenarios on local or remote servers." + } + + fn extra_usage(&self) -> &str { + "Extra usage for once scenario.deploy" + } + + fn signature(&self) -> Signature { + // The signature defines the usage of the command inside Nu, and also automatically + // generates its help page. + Signature::build(self.name()) + .required("a", SyntaxShape::Int, "required integer value") + .required("b", SyntaxShape::String, "required string value") + .switch("flag", "a flag for the signature", Some('f')) + .optional("opt", SyntaxShape::Int, "Optional number") + .named("named", SyntaxShape::String, "named string", Some('n')) + .rest("rest", SyntaxShape::String, "rest value string") + .category(Category::Experimental) + } + + fn search_terms(&self) -> Vec<&str> { + vec!["once", "scenario", "deploy"] + } + + fn examples(&self) -> Vec { + vec![Example { + example: "once scenario.deploy 3 bb", + description: "running scenario.deploy with an int value and string value", + result: None, + }] + } + + fn run( + &self, + plugin: &OncePlugin, + _engine: &EngineInterface, + call: &EvaluatedCall, + input: &Value, + ) -> Result { + plugin.print_values(1, call, input)?; + + Ok(Value::nothing(call.head)) + } +} + +#[test] +fn test_examples() -> Result<(), nu_protocol::ShellError> { + use nu_plugin_test_support::PluginTest; + PluginTest::new("once", OncePlugin.into())?.test_command_examples(&ScenarioDeploy) +} diff --git a/crates/nu_plugin_once/src/lib.rs b/crates/nu_plugin_once/src/lib.rs new file mode 100644 index 0000000000..74b43b5b3f --- /dev/null +++ b/crates/nu_plugin_once/src/lib.rs @@ -0,0 +1,25 @@ +use nu_plugin::{Plugin, PluginCommand}; + +mod commands; +mod once; + +pub use commands::*; +pub use once::OncePlugin; + +impl Plugin for OncePlugin { + fn version(&self) -> String { + env!("CARGO_PKG_VERSION").into() + } + + fn commands(&self) -> Vec>> { + // This is a list of all of the commands you would like Nu to register when your plugin is + // loaded. + // + // If it doesn't appear on this list, it won't be added. + vec![ + Box::new(Main), + Box::new(Scenario), + Box::new(ScenarioDeploy) + ] + } +} diff --git a/crates/nu_plugin_once/src/main.rs b/crates/nu_plugin_once/src/main.rs new file mode 100644 index 0000000000..71e5a21764 --- /dev/null +++ b/crates/nu_plugin_once/src/main.rs @@ -0,0 +1,30 @@ +use nu_plugin::{serve_plugin, MsgPackSerializer}; +use nu_plugin_once::OncePlugin; + +fn main() { + // When defining your plugin, you can select the Serializer that could be + // used to encode and decode the messages. The available options are + // MsgPackSerializer and JsonSerializer. Both are defined in the serializer + // folder in nu-plugin. + serve_plugin(&OncePlugin {}, MsgPackSerializer {}) + + // Note + // When creating plugins in other languages one needs to consider how a plugin + // is added and used in nushell. + // The steps are: + // - The plugin is register. In this stage nushell calls the binary file of + // the plugin sending information using the encoded PluginCall::PluginSignature object. + // Use this encoded data in your plugin to design the logic that will return + // the encoded signatures. + // Nushell is expecting and encoded PluginResponse::PluginSignature with all the + // plugin signatures + // - When calling the plugin, nushell sends to the binary file the encoded + // PluginCall::CallInfo which has all the call information, such as the + // values of the arguments, the name of the signature called and the input + // from the pipeline. + // Use this data to design your plugin login and to create the value that + // will be sent to nushell + // Nushell expects an encoded PluginResponse::Value from the plugin + // - If an error needs to be sent back to nushell, one can encode PluginResponse::Error. + // This is a labeled error that nushell can format for pretty printing +} diff --git a/crates/nu_plugin_once/src/once.rs b/crates/nu_plugin_once/src/once.rs new file mode 100644 index 0000000000..a69e0ac4af --- /dev/null +++ b/crates/nu_plugin_once/src/once.rs @@ -0,0 +1,53 @@ +use nu_plugin::EvaluatedCall; +use nu_protocol::{LabeledError, Value}; + +pub struct OncePlugin; + +impl OncePlugin { + pub fn print_values( + &self, + index: u32, + call: &EvaluatedCall, + input: &Value, + ) -> Result<(), LabeledError> { + // Note. When debugging your plugin, you may want to print something to the console + // Use the eprintln macro to print your messages. Trying to print to stdout will + // cause a decoding error for your message + eprintln!("Calling test {index} signature"); + eprintln!("value received {input:?}"); + + // To extract the arguments from the Call object you can use the functions req, has_flag, + // opt, rest, and get_flag + // + // Note that plugin calls only accept simple arguments, this means that you can + // pass to the plug in Int and String. This should be improved when the plugin has + // the ability to call back to NuShell to extract more information + // Keep this in mind when designing your plugin signatures + let a: i64 = call.req(0)?; + let b: String = call.req(1)?; + let flag = call.has_flag("flag")?; + let opt: Option = call.opt(2)?; + let named: Option = call.get_flag("named")?; + let rest: Vec = call.rest(3)?; + + eprintln!("Required values"); + eprintln!("a: {a:}"); + eprintln!("b: {b:}"); + eprintln!("flag: {flag:}"); + eprintln!("rest: {rest:?}"); + + if let Some(v) = opt { + eprintln!("Found optional value opt: {v:}") + } else { + eprintln!("No optional value found") + } + + if let Some(v) = named { + eprintln!("Named value: {v:?}") + } else { + eprintln!("No named value found") + } + + Ok(()) + } +}