Change PluginCommand API to be more like Command (#12279)

# Description

This is something that was discussed in the core team meeting last
Wednesday. @ayax79 is building `nu-plugin-polars` with all of the
dataframe commands into a plugin, and there are a lot of them, so it
would help to make the API more similar. At the same time, I think the
`Command` API is just better anyway. I don't think the difference is
justified, and the types for core commands have the benefit of requiring
less `.into()` because they often don't own their data

- Broke `signature()` up into `name()`, `usage()`, `extra_usage()`,
`search_terms()`, `examples()`
- `signature()` returns `nu_protocol::Signature`
- `examples()` returns `Vec<nu_protocol::Example>`
- `PluginSignature` and `PluginExample` no longer need to be used by
plugin developers

# User-Facing Changes
Breaking API for plugins yet again 😄
This commit is contained in:
Devyn Cairns
2024-03-27 03:59:57 -07:00
committed by GitHub
parent 03b5e9d853
commit 01d30a416b
45 changed files with 962 additions and 674 deletions

View File

@ -1,35 +1,48 @@
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand};
use nu_protocol::{
Category, LabeledError, PipelineData, PluginExample, PluginSignature, RawStream, Type, Value,
Category, Example, LabeledError, PipelineData, RawStream, Signature, Type, Value,
};
use crate::Example;
use crate::ExamplePlugin;
/// `<list<string>> | example collect-external`
pub struct CollectExternal;
impl PluginCommand for CollectExternal {
type Plugin = Example;
type Plugin = ExamplePlugin;
fn signature(&self) -> PluginSignature {
PluginSignature::build("example collect-external")
.usage("Example transformer to raw external stream")
.search_terms(vec!["example".into()])
fn name(&self) -> &str {
"example collect-external"
}
fn usage(&self) -> &str {
"Example transformer to raw external stream"
}
fn search_terms(&self) -> Vec<&str> {
vec!["example"]
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.input_output_types(vec![
(Type::List(Type::String.into()), Type::String),
(Type::List(Type::Binary.into()), Type::Binary),
])
.plugin_examples(vec![PluginExample {
example: "[a b] | example collect-external".into(),
description: "collect strings into one stream".into(),
result: Some(Value::test_string("ab")),
}])
.category(Category::Experimental)
}
fn examples(&self) -> Vec<Example> {
vec![Example {
example: "[a b] | example collect-external",
description: "collect strings into one stream",
result: Some(Value::test_string("ab")),
}]
}
fn run(
&self,
_plugin: &Example,
_plugin: &ExamplePlugin,
_engine: &EngineInterface,
call: &EvaluatedCall,
input: PipelineData,
@ -55,5 +68,5 @@ impl PluginCommand for CollectExternal {
#[test]
fn test_examples() -> Result<(), nu_protocol::ShellError> {
use nu_plugin_test_support::PluginTest;
PluginTest::new("example", Example.into())?.test_command_examples(&CollectExternal)
PluginTest::new("example", ExamplePlugin.into())?.test_command_examples(&CollectExternal)
}

View File

@ -1,25 +1,38 @@
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{Category, LabeledError, PluginSignature, Type, Value};
use nu_protocol::{Category, LabeledError, Signature, Type, Value};
use crate::Example;
use crate::ExamplePlugin;
pub struct Config;
impl SimplePluginCommand for Config {
type Plugin = Example;
type Plugin = ExamplePlugin;
fn signature(&self) -> PluginSignature {
PluginSignature::build("example config")
.usage("Show plugin configuration")
.extra_usage("The configuration is set under $env.config.plugins.example")
fn name(&self) -> &str {
"example config"
}
fn usage(&self) -> &str {
"Show plugin configuration"
}
fn extra_usage(&self) -> &str {
"The configuration is set under $env.config.plugins.example"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.category(Category::Experimental)
.search_terms(vec!["example".into(), "configuration".into()])
.input_output_type(Type::Nothing, Type::Table(vec![]))
}
fn search_terms(&self) -> Vec<&str> {
vec!["example", "configuration"]
}
fn run(
&self,
_plugin: &Example,
_plugin: &ExamplePlugin,
engine: &EngineInterface,
call: &EvaluatedCall,
_input: &Value,

View File

@ -1,18 +1,23 @@
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{Category, LabeledError, PluginSignature, Value};
use nu_protocol::{Category, LabeledError, Signature, Value};
use crate::Example;
use crate::ExamplePlugin;
pub struct DisableGc;
impl SimplePluginCommand for DisableGc {
type Plugin = Example;
type Plugin = ExamplePlugin;
fn signature(&self) -> PluginSignature {
PluginSignature::build("example disable-gc")
.usage("Disable the plugin garbage collector for `example`")
.extra_usage(
"\
fn name(&self) -> &str {
"example disable-gc"
}
fn usage(&self) -> &str {
"Disable the plugin garbage collector for `example`"
}
fn extra_usage(&self) -> &str {
"\
Plugins are garbage collected by default after a period of inactivity. This
behavior is configurable with `$env.config.plugin_gc.default`, or to change it
specifically for the example plugin, use
@ -20,21 +25,22 @@ specifically for the example plugin, use
This command demonstrates how plugins can control this behavior and disable GC
temporarily if they need to. It is still possible to stop the plugin explicitly
using `plugin stop example`.",
)
.search_terms(vec![
"example".into(),
"gc".into(),
"plugin_gc".into(),
"garbage".into(),
])
using `plugin stop example`."
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.switch("reset", "Turn the garbage collector back on", None)
.category(Category::Experimental)
}
fn search_terms(&self) -> Vec<&str> {
vec!["example", "gc", "plugin_gc", "garbage"]
}
fn run(
&self,
_plugin: &Example,
_plugin: &ExamplePlugin,
engine: &EngineInterface,
call: &EvaluatedCall,
_input: &Value,

View File

@ -1,17 +1,27 @@
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{Category, LabeledError, PluginSignature, SyntaxShape, Type, Value};
use nu_protocol::{Category, LabeledError, Signature, SyntaxShape, Type, Value};
use crate::Example;
use crate::ExamplePlugin;
pub struct Env;
impl SimplePluginCommand for Env {
type Plugin = Example;
type Plugin = ExamplePlugin;
fn signature(&self) -> PluginSignature {
PluginSignature::build("example env")
.usage("Get environment variable(s)")
.extra_usage("Returns all environment variables if no name provided")
fn name(&self) -> &str {
"example env"
}
fn usage(&self) -> &str {
"Get environment variable(s)"
}
fn extra_usage(&self) -> &str {
"Returns all environment variables if no name provided"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.category(Category::Experimental)
.optional(
"name",
@ -25,13 +35,16 @@ impl SimplePluginCommand for Env {
"Set an environment variable to the value",
None,
)
.search_terms(vec!["example".into(), "env".into()])
.input_output_type(Type::Nothing, Type::Any)
}
fn search_terms(&self) -> Vec<&str> {
vec!["example", "env"]
}
fn run(
&self,
_plugin: &Example,
_plugin: &ExamplePlugin,
engine: &EngineInterface,
call: &EvaluatedCall,
_input: &Value,

View File

@ -1,37 +1,48 @@
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand};
use nu_protocol::{
Category, LabeledError, PipelineData, PluginExample, PluginSignature, SyntaxShape, Type,
};
use nu_protocol::{Category, Example, LabeledError, PipelineData, Signature, SyntaxShape, Type};
use crate::Example;
use crate::ExamplePlugin;
/// `<list> | example for-each { |value| ... }`
pub struct ForEach;
impl PluginCommand for ForEach {
type Plugin = Example;
type Plugin = ExamplePlugin;
fn signature(&self) -> PluginSignature {
PluginSignature::build("example for-each")
.usage("Example execution of a closure with a stream")
.extra_usage("Prints each value the closure returns to stderr")
fn name(&self) -> &str {
"example for-each"
}
fn usage(&self) -> &str {
"Example execution of a closure with a stream"
}
fn extra_usage(&self) -> &str {
"Prints each value the closure returns to stderr"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.input_output_type(Type::ListStream, Type::Nothing)
.required(
"closure",
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
"The closure to run for each input value",
)
.plugin_examples(vec![PluginExample {
example: "ls | get name | example for-each { |f| ^file $f }".into(),
description: "example with an external command".into(),
result: None,
}])
.category(Category::Experimental)
}
fn examples(&self) -> Vec<Example> {
vec![Example {
example: "ls | get name | example for-each { |f| ^file $f }",
description: "example with an external command",
result: None,
}]
}
fn run(
&self,
_plugin: &Example,
_plugin: &ExamplePlugin,
engine: &EngineInterface,
call: &EvaluatedCall,
input: PipelineData,
@ -49,5 +60,5 @@ impl PluginCommand for ForEach {
#[test]
fn test_examples() -> Result<(), nu_protocol::ShellError> {
use nu_plugin_test_support::PluginTest;
PluginTest::new("example", Example.into())?.test_command_examples(&ForEach)
PluginTest::new("example", ExamplePlugin.into())?.test_command_examples(&ForEach)
}

View File

@ -1,21 +1,31 @@
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand};
use nu_protocol::{
Category, IntoInterruptiblePipelineData, LabeledError, PipelineData, PluginExample,
PluginSignature, SyntaxShape, Type, Value,
Category, Example, IntoInterruptiblePipelineData, LabeledError, PipelineData, Signature,
SyntaxShape, Type, Value,
};
use crate::Example;
use crate::ExamplePlugin;
/// `example generate <initial> { |previous| {out: ..., next: ...} }`
pub struct Generate;
impl PluginCommand for Generate {
type Plugin = Example;
type Plugin = ExamplePlugin;
fn signature(&self) -> PluginSignature {
PluginSignature::build("example generate")
.usage("Example execution of a closure to produce a stream")
.extra_usage("See the builtin `generate` command")
fn name(&self) -> &str {
"example generate"
}
fn usage(&self) -> &str {
"Example execution of a closure to produce a stream"
}
fn extra_usage(&self) -> &str {
"See the builtin `generate` command"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.input_output_type(Type::Nothing, Type::ListStream)
.required(
"initial",
@ -27,23 +37,25 @@ impl PluginCommand for Generate {
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
"The closure to run to generate values",
)
.plugin_examples(vec![PluginExample {
example: "example generate 0 { |i| if $i <= 10 { {out: $i, next: ($i + 2)} } }"
.into(),
description: "Generate a sequence of numbers".into(),
result: Some(Value::test_list(
[0, 2, 4, 6, 8, 10]
.into_iter()
.map(Value::test_int)
.collect(),
)),
}])
.category(Category::Experimental)
}
fn examples(&self) -> Vec<Example> {
vec![Example {
example: "example generate 0 { |i| if $i <= 10 { {out: $i, next: ($i + 2)} } }",
description: "Generate a sequence of numbers",
result: Some(Value::test_list(
[0, 2, 4, 6, 8, 10]
.into_iter()
.map(Value::test_int)
.collect(),
)),
}]
}
fn run(
&self,
_plugin: &Example,
_plugin: &ExamplePlugin,
engine: &EngineInterface,
call: &EvaluatedCall,
_input: PipelineData,
@ -81,7 +93,7 @@ impl PluginCommand for Generate {
fn test_examples() -> Result<(), nu_protocol::ShellError> {
use nu_cmd_lang::If;
use nu_plugin_test_support::PluginTest;
PluginTest::new("example", Example.into())?
PluginTest::new("example", ExamplePlugin.into())?
.add_decl(Box::new(If))?
.test_command_examples(&Generate)
}

View File

@ -1,28 +1,38 @@
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{Category, LabeledError, PluginSignature, Value};
use nu_protocol::{Category, LabeledError, Signature, Value};
use crate::Example;
use crate::ExamplePlugin;
pub struct Main;
impl SimplePluginCommand for Main {
type Plugin = Example;
type Plugin = ExamplePlugin;
fn signature(&self) -> PluginSignature {
PluginSignature::build("example")
.usage("Example commands for Nushell plugins")
.extra_usage(
r#"
fn name(&self) -> &str {
"example"
}
fn usage(&self) -> &str {
"Example commands for Nushell plugins"
}
fn extra_usage(&self) -> &str {
r#"
The `example` 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(),
)
.search_terms(vec!["example".into()])
.category(Category::Experimental)
.trim()
}
fn signature(&self) -> Signature {
Signature::build(self.name()).category(Category::Experimental)
}
fn search_terms(&self) -> Vec<&str> {
vec!["example"]
}
fn run(

View File

@ -1,37 +1,53 @@
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{Category, LabeledError, PluginExample, PluginSignature, SyntaxShape, Value};
use nu_protocol::{Category, Example, LabeledError, Signature, SyntaxShape, Value};
use crate::Example;
use crate::ExamplePlugin;
pub struct One;
impl SimplePluginCommand for One {
type Plugin = Example;
type Plugin = ExamplePlugin;
fn signature(&self) -> PluginSignature {
fn name(&self) -> &str {
"example one"
}
fn usage(&self) -> &str {
"Plugin test example 1. Returns Value::Nothing"
}
fn extra_usage(&self) -> &str {
"Extra usage for example one"
}
fn signature(&self) -> Signature {
// The signature defines the usage of the command inside Nu, and also automatically
// generates its help page.
PluginSignature::build("example one")
.usage("PluginSignature test 1 for plugin. Returns Value::Nothing")
.extra_usage("Extra usage for example one")
.search_terms(vec!["example".into()])
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")
.plugin_examples(vec![PluginExample {
example: "example one 3 bb".into(),
description: "running example with an int value and string value".into(),
result: None,
}])
.category(Category::Experimental)
}
fn search_terms(&self) -> Vec<&str> {
vec!["example"]
}
fn examples(&self) -> Vec<Example> {
vec![Example {
example: "example one 3 bb",
description: "running example with an int value and string value",
result: None,
}]
}
fn run(
&self,
plugin: &Example,
plugin: &ExamplePlugin,
_engine: &EngineInterface,
call: &EvaluatedCall,
input: &Value,
@ -45,5 +61,5 @@ impl SimplePluginCommand for One {
#[test]
fn test_examples() -> Result<(), nu_protocol::ShellError> {
use nu_plugin_test_support::PluginTest;
PluginTest::new("example", Example.into())?.test_command_examples(&One)
PluginTest::new("example", ExamplePlugin.into())?.test_command_examples(&One)
}

View File

@ -1,39 +1,51 @@
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand};
use nu_protocol::{
Category, LabeledError, ListStream, PipelineData, PluginExample, PluginSignature, SyntaxShape,
Type, Value,
Category, Example, LabeledError, ListStream, PipelineData, Signature, SyntaxShape, Type, Value,
};
use crate::Example;
use crate::ExamplePlugin;
/// `example seq <first> <last>`
pub struct Seq;
impl PluginCommand for Seq {
type Plugin = Example;
type Plugin = ExamplePlugin;
fn signature(&self) -> PluginSignature {
PluginSignature::build("example seq")
.usage("Example stream generator for a list of values")
.search_terms(vec!["example".into()])
fn name(&self) -> &str {
"example seq"
}
fn usage(&self) -> &str {
"Example stream generator for a list of values"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required("first", SyntaxShape::Int, "first number to generate")
.required("last", SyntaxShape::Int, "last number to generate")
.input_output_type(Type::Nothing, Type::List(Type::Int.into()))
.plugin_examples(vec![PluginExample {
example: "example seq 1 3".into(),
description: "generate a sequence from 1 to 3".into(),
result: Some(Value::test_list(vec![
Value::test_int(1),
Value::test_int(2),
Value::test_int(3),
])),
}])
.category(Category::Experimental)
}
fn search_terms(&self) -> Vec<&str> {
vec!["example"]
}
fn examples(&self) -> Vec<Example> {
vec![Example {
example: "example seq 1 3",
description: "generate a sequence from 1 to 3",
result: Some(Value::test_list(vec![
Value::test_int(1),
Value::test_int(2),
Value::test_int(3),
])),
}]
}
fn run(
&self,
_plugin: &Example,
_plugin: &ExamplePlugin,
_engine: &EngineInterface,
call: &EvaluatedCall,
_input: PipelineData,
@ -50,5 +62,5 @@ impl PluginCommand for Seq {
#[test]
fn test_examples() -> Result<(), nu_protocol::ShellError> {
use nu_plugin_test_support::PluginTest;
PluginTest::new("example", Example.into())?.test_command_examples(&Seq)
PluginTest::new("example", ExamplePlugin.into())?.test_command_examples(&Seq)
}

View File

@ -1,35 +1,46 @@
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand};
use nu_protocol::{
Category, LabeledError, PipelineData, PluginExample, PluginSignature, Span, Type, Value,
};
use nu_protocol::{Category, Example, LabeledError, PipelineData, Signature, Span, Type, Value};
use crate::Example;
use crate::ExamplePlugin;
/// `<list> | example sum`
pub struct Sum;
impl PluginCommand for Sum {
type Plugin = Example;
type Plugin = ExamplePlugin;
fn signature(&self) -> PluginSignature {
PluginSignature::build("example sum")
.usage("Example stream consumer for a list of values")
.search_terms(vec!["example".into()])
fn name(&self) -> &str {
"example sum"
}
fn usage(&self) -> &str {
"Example stream consumer for a list of values"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.input_output_types(vec![
(Type::List(Type::Int.into()), Type::Int),
(Type::List(Type::Float.into()), Type::Float),
])
.plugin_examples(vec![PluginExample {
example: "example seq 1 5 | example sum".into(),
description: "sum values from 1 to 5".into(),
result: Some(Value::test_int(15)),
}])
.category(Category::Experimental)
}
fn search_terms(&self) -> Vec<&str> {
vec!["example"]
}
fn examples(&self) -> Vec<Example> {
vec![Example {
example: "example seq 1 5 | example sum",
description: "sum values from 1 to 5",
result: Some(Value::test_int(15)),
}]
}
fn run(
&self,
_plugin: &Example,
_plugin: &ExamplePlugin,
_engine: &EngineInterface,
call: &EvaluatedCall,
input: PipelineData,
@ -92,5 +103,5 @@ impl IntOrFloat {
#[test]
fn test_examples() -> Result<(), nu_protocol::ShellError> {
use nu_plugin_test_support::PluginTest;
PluginTest::new("example", Example.into())?.test_command_examples(&Sum)
PluginTest::new("example", ExamplePlugin.into())?.test_command_examples(&Sum)
}

View File

@ -1,18 +1,25 @@
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{Category, LabeledError, PluginSignature, SyntaxShape, Value};
use nu_protocol::{Category, LabeledError, Signature, SyntaxShape, Value};
use crate::Example;
use crate::ExamplePlugin;
pub struct Three;
impl SimplePluginCommand for Three {
type Plugin = Example;
type Plugin = ExamplePlugin;
fn signature(&self) -> PluginSignature {
fn name(&self) -> &str {
"example three"
}
fn usage(&self) -> &str {
"Plugin test example 3. Returns labeled error"
}
fn signature(&self) -> Signature {
// The signature defines the usage of the command inside Nu, and also automatically
// generates its help page.
PluginSignature::build("example three")
.usage("PluginSignature test 3 for plugin. Returns labeled error")
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'))
@ -24,7 +31,7 @@ impl SimplePluginCommand for Three {
fn run(
&self,
plugin: &Example,
plugin: &ExamplePlugin,
_engine: &EngineInterface,
call: &EvaluatedCall,
input: &Value,

View File

@ -1,18 +1,25 @@
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{record, Category, LabeledError, PluginSignature, SyntaxShape, Value};
use nu_protocol::{record, Category, LabeledError, Signature, SyntaxShape, Value};
use crate::Example;
use crate::ExamplePlugin;
pub struct Two;
impl SimplePluginCommand for Two {
type Plugin = Example;
type Plugin = ExamplePlugin;
fn signature(&self) -> PluginSignature {
fn name(&self) -> &str {
"example two"
}
fn usage(&self) -> &str {
"Plugin test example 2. Returns list of records"
}
fn signature(&self) -> Signature {
// The signature defines the usage of the command inside Nu, and also automatically
// generates its help page.
PluginSignature::build("example two")
.usage("PluginSignature test 2 for plugin. Returns list of records")
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'))
@ -24,7 +31,7 @@ impl SimplePluginCommand for Two {
fn run(
&self,
plugin: &Example,
plugin: &ExamplePlugin,
_engine: &EngineInterface,
call: &EvaluatedCall,
input: &Value,

View File

@ -1,9 +1,9 @@
use nu_plugin::EvaluatedCall;
use nu_protocol::{LabeledError, Value};
pub struct Example;
pub struct ExamplePlugin;
impl Example {
impl ExamplePlugin {
pub fn print_values(
&self,
index: u32,

View File

@ -4,9 +4,9 @@ mod commands;
mod example;
pub use commands::*;
pub use example::Example;
pub use example::ExamplePlugin;
impl Plugin for Example {
impl Plugin for ExamplePlugin {
fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> {
// This is a list of all of the commands you would like Nu to register when your plugin is
// loaded.

View File

@ -1,12 +1,12 @@
use nu_plugin::{serve_plugin, MsgPackSerializer};
use nu_plugin_example::Example;
use nu_plugin_example::ExamplePlugin;
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(&Example {}, MsgPackSerializer {})
serve_plugin(&ExamplePlugin {}, MsgPackSerializer {})
// Note
// When creating plugins in other languages one needs to consider how a plugin