Skip to content

Commit b8405c7

Browse files
authored
Plugin command api doc updates (#1305)
1 parent 57ce87b commit b8405c7

File tree

1 file changed

+108
-65
lines changed

1 file changed

+108
-65
lines changed

contributor-book/plugins.md

+108-65
Original file line numberDiff line numberDiff line change
@@ -54,40 +54,45 @@ version = "0.1.0"
5454
edition = "2021"
5555

5656
[dependencies]
57-
nu-plugin = "0.85.0" # These version numbers may differ
58-
nu-protocol = "0.85.0"
57+
nu-plugin = "0.92.0" # These version numbers may differ
58+
nu-protocol = "0.92.0"
5959
```
6060

6161
With this, we can open up `src/main.rs` and create our plugin.
6262

6363
```rust
64-
use nu_plugin::{serve_plugin, LabeledError, Plugin, JsonSerializer, EvaluatedCall, EngineInterface};
64+
use nu_plugin::{serve_plugin, LabeledError, JsonSerializer, EvaluatedCall};
65+
use nu_plugin::{Plugin, PluginCommand, SimplePluginCommand, EngineInterface};
6566
use nu_protocol::{Value, PluginSignature, Type};
6667

67-
struct Len;
68+
struct LenPlugin;
6869

69-
impl Len {
70-
fn new() -> Self {
71-
Self
70+
impl Plugin for LenPlugin {
71+
fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> {
72+
vec![
73+
Box::new(Len),
74+
]
7275
}
7376
}
7477

75-
impl Plugin for Len {
76-
fn signature(&self) -> Vec<PluginSignature> {
77-
vec![PluginSignature::build("len")
78+
struct Len;
79+
80+
impl SimplePluginCommand for Len {
81+
type Plugin = LenPlugin;
82+
83+
fn signature(&self) -> PluginSignature {
84+
PluginSignature::build("len")
7885
.usage("calculates the length of its input")
7986
.input_output_type(Type::String, Type::Int)
80-
]
8187
}
8288

8389
fn run(
8490
&self,
85-
name: &str,
91+
_plugin: &LenPlugin,
8692
_engine: &EngineInterface,
8793
call: &EvaluatedCall,
8894
input: &Value,
8995
) -> Result<Value, LabeledError> {
90-
assert_eq!(name, "len");
9196
let span = input.span();
9297
match input {
9398
Value::String { val, .. } => Ok(
@@ -103,7 +108,7 @@ impl Plugin for Len {
103108
}
104109

105110
fn main() {
106-
serve_plugin(&Len::new(), JsonSerializer)
111+
serve_plugin(&LenPlugin, JsonSerializer)
107112
}
108113
```
109114

@@ -113,46 +118,56 @@ First off, let's look at main:
113118

114119
```rust
115120
fn main() {
116-
serve_plugin(&Len::new(), JsonSerializer)
121+
serve_plugin(&LenPlugin, JsonSerializer)
117122
}
118123
```
119124

120-
In main, we just call a single function `serve_plugin`. This will do the work of calling into our plugin, handling the JSON serialization/deserialization, and sending values and errors back to Nu for us. To start it up, we pass it something that implements the `Plugin` trait and something that implements the `PluginEncoder` trait. We're given a choice of serialization formats that Nu supports. Ordinarily plugins written in Rust should use `MsgPackSerializer`, but here we select JSON to demonstrate how the communication protocol works further on in this tutorial.
125+
In `main()`, we just call a single function `serve_plugin`. This will do the work of calling into our plugin, handling the JSON serialization/deserialization, and sending values and errors back to Nu for us. To start it up, we pass it something that implements the `Plugin` trait and something that implements the `PluginEncoder` trait. We're given a choice of serialization formats that Nu supports. Ordinarily plugins written in Rust should use `MsgPackSerializer` as it is considerably faster, but here we select JSON to demonstrate how the communication protocol works further on in this tutorial.
121126

122-
Next, above main, is this implementation of the `Plugin` trait for our particular plugin. Here, we'll implement the Plugin trait for our type, Len, which we'll see more of soon. Let's take a look at how we implement this trait:
127+
Above `main()` is the implementation of the `SimplePluginCommand` trait for the `len` command that our plugin will expose, which is represented by the `Len` type. We use `SimplePluginCommand` rather than `PluginCommand` in order to simplify our implementation and avoid [handling streams](#using-streams-in-plugins). Let's take a look at how we implement this trait:
123128

124129
```rust
125-
impl Plugin for Len {
126-
fn signature(&self) -> Vec<PluginSignature> {
127-
vec![PluginSignature::build("len")
130+
impl SimplePluginCommand for Len {
131+
type Plugin = LenPlugin;
132+
133+
// ...
134+
}
135+
```
136+
137+
We first specify the plugin type our command expects. This allows us to receive a reference to it in `run()`, which we can use for shared state between commands.
138+
139+
```rust
140+
impl SimplePluginCommand for Len {
141+
// ...
142+
143+
fn signature(&self) -> PluginSignature {
144+
PluginSignature::build("len")
128145
.usage("calculates the length of its input")
129146
.input_type(Type::String)
130147
.output_type(Type::Int)
131-
]
132148
}
133149

134150
// ...
135151
}
136152
```
137153

138-
There are two methods required for this implementation. The first is the `signature` part, which is run by Nu when it first starts up. This tells Nu the basic information about the plugin: its name, the parameters it takes, the description, what kind of plugin it is, and defines the input and output types.
154+
There are two methods required for this implementation. The first is the `signature` part, which is run by Nu when the plugin is registered. This tells Nu the basic information about the command: its name, the parameters it takes, the description, what kind of plugin it is, and defines the input and output types.
155+
139156
Here, we tell Nu that the name is "len", give it a basic description for `help` to display and declare that we expect to be passed a string and will return an integer.
140157

141-
Next, in the `run` implementation, we describe how to do work as values flow into this plugin. Here, we receive a `Value` type that we expect to be a string.
142-
We also return either `Value` or an error.
158+
Next, in the `run` implementation, we describe how to do work as values flow into this plugin. Here, we receive a `Value` type that we expect to be a string. We also return either `Value` or an error.
143159

144160
```rust
145-
impl Plugin for Len {
161+
impl SimplePluginCommand for Len {
146162
// ...
147163

148164
fn run(
149165
&self,
150-
name: &str,
166+
_plugin: &LenPlugin,
151167
_engine: &EngineInterface,
152168
call: &EvaluatedCall,
153169
input: &Value,
154170
) -> Result<Value, LabeledError> {
155-
assert_eq!(name, "len");
156171
let span = input.span();
157172
match input {
158173
Value::String { val, .. } => Ok(
@@ -172,26 +187,35 @@ We use Rust's pattern matching to check the type of the `Value` coming in, and t
172187

173188
Our `Len` command doesn't require any parameters, but if it did we'd get them from the `EvaluatedCall`.
174189

175-
Next, let's look at `Len` itself to see what it's doing:
176-
177190
```rust
178191
struct Len;
192+
```
193+
194+
`Len` is defined as a unit struct, with no fields, and this is the most common type definition for a command in a plugin. However, you may choose to keep state here if you want to - every call of `len` shares the same reference.
179195

180-
impl Len {
181-
fn new() -> Self {
182-
Self
196+
Above that, let's have a look at the definition of `LenPlugin`, which implements the `Plugin` trait:
197+
198+
```rust
199+
struct LenPlugin;
200+
201+
impl Plugin for LenPlugin {
202+
fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> {
203+
vec![
204+
Box::new(Len),
205+
]
183206
}
184207
}
185208
```
186209

187-
We create a very simple `Len`, in fact, it has no structure at all. Instead, it's just a placeholder that will let us implement the plugin.
210+
Again, we use a unit struct for `LenPlugin`, but this is the recommended place to put plugin state if needed. All commands also get a reference to the plugin type. This is what we eventually pass to `serve_plugin()` in `main()`.
188211

189-
The `new` method is optional, it's just a convenient way to create a new value of the `Len` type to pass into `serve_plugin` later.
212+
The only required method in `Plugin` is `commands()`, which initializes the plugin's commands. A boxed `dyn` reference is used so that we can keep all of the different command types in the single list. Dispatch by command name is automatically handled in `serve_plugin()` by looking at the name defined in the signature - in our case, that's `len`. A plugin can contain many commands, so if you end up adding more, just add them to the list returned by `commands()`.
190213

191214
Lastly, let's look at the top of the file:
192215

193216
```rust
194-
use nu_plugin::{serve_plugin, LabeledError, Plugin, JsonSerializer, EvaluatedCall};
217+
use nu_plugin::{serve_plugin, LabeledError, JsonSerializer, EvaluatedCall};
218+
use nu_plugin::{Plugin, PluginCommand, SimplePluginCommand, EngineInterface};
195219
use nu_protocol::{Value, PluginSignature, Type};
196220
```
197221

@@ -226,36 +250,35 @@ Signatures:
226250

227251
## Using streams in plugins
228252

229-
The default `Plugin` trait that we just implemented for our plugin does not support streaming input or output. If we want to extend our plugin to support determining the lengths of lists, it would be helpful to not have to consume an entire list that is a stream. We can do this by implementing `StreamingPlugin` instead.
253+
The `SimplePluginCommand` trait that we just implemented for our plugin does not support streaming input or output. If we want to extend our plugin to support determining the lengths of lists, it would be helpful to not have to consume an entire list that is a stream. We can do this by implementing `PluginCommand` instead.
230254

231255
```rust
232256
// add these imports:
233-
use nu_plugin::StreamingPlugin;
234257
use nu_protocol::{IntoPipelineData, PipelineData};
235258
// ...
236259

237-
// change Plugin to StreamingPlugin:
238-
impl StreamingPlugin for Len {
239-
fn signature(&self) -> Vec<PluginSignature> {
260+
// change SimplePluginCommand to PluginCommand:
261+
impl PluginCommand for Len {
262+
type Plugin = LenPlugin;
263+
264+
fn signature(&self) -> PluginSignature {
240265
// ... add the list type to the signature
241-
vec![PluginSignature::build("len")
266+
PluginSignature::build("len")
242267
.usage("calculates the length of its input")
243268
.input_output_types(vec![
244269
(Type::String, Type::Int),
245270
(Type::List(Type::Any.into()), Type::Int),
246271
])
247-
]
248272
}
249273

250274
// ... and change input and output types to PipelineData
251275
fn run(
252276
&self,
253-
name: &str,
277+
_plugin: &LenPlugin,
254278
_engine: &EngineInterface,
255279
call: &EvaluatedCall,
256280
input: PipelineData,
257281
) -> Result<PipelineData, LabeledError> {
258-
assert_eq!(name, "len");
259282
// Check if the input is a stream or list
260283
match input {
261284
PipelineData::ListStream(..) |
@@ -341,20 +364,30 @@ The plugin configuration can be retrieved with [`EngineInterface::get_plugin_con
341364
use nu_plugin::*;
342365
use nu_protocol::{PluginSignature, Value, Type};
343366

344-
struct Motd;
367+
struct MotdPlugin;
345368

346-
impl Plugin for Motd {
347-
fn signature(&self) -> Vec<PluginSignature> {
369+
impl Plugin for MotdPlugin {
370+
fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> {
348371
vec![
349-
PluginSignature::build("motd")
350-
.usage("Message of the day")
351-
.input_output_type(Type::Nothing, Type::String)
372+
Box::new(Motd),
352373
]
353374
}
375+
}
376+
377+
struct Motd;
378+
379+
impl SimplePluginCommand for Motd {
380+
type Plugin = MotdPlugin;
381+
382+
fn signature(&self) -> PluginSignature {
383+
PluginSignature::build("motd")
384+
.usage("Message of the day")
385+
.input_output_type(Type::Nothing, Type::String)
386+
}
354387

355388
fn run(
356389
&self,
357-
_name: &str,
390+
_plugin: &MotdPlugin,
358391
engine: &EngineInterface,
359392
call: &EvaluatedCall,
360393
_input: &Value,
@@ -378,7 +411,7 @@ impl Plugin for Motd {
378411
}
379412

380413
fn main() {
381-
serve_plugin(&Motd, MsgPackSerializer)
414+
serve_plugin(&MotdPlugin, MsgPackSerializer)
382415
}
383416
```
384417

@@ -398,25 +431,35 @@ Plugins can accept and evaluate closures using [`EngineInterface::eval_closure`]
398431
use nu_plugin::*;
399432
use nu_protocol::{PluginSignature, Value, Type, SyntaxShape, PipelineData};
400433

401-
struct MyEach;
434+
struct MyEachPlugin;
402435

403-
impl StreamingPlugin for MyEach {
404-
fn signature(&self) -> Vec<PluginSignature> {
436+
impl Plugin for MyEachPlugin {
437+
fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> {
405438
vec![
406-
PluginSignature::build("my-each")
407-
.usage("Run closure on each element of a list")
408-
.required(
409-
"closure",
410-
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
411-
"The closure to evaluate",
412-
)
413-
.input_output_type(Type::ListStream, Type::ListStream)
439+
Box::new(MyEach),
414440
]
415441
}
442+
}
443+
444+
struct MyEach;
445+
446+
impl PluginCommand for MyEach {
447+
type Plugin = MyEachPlugin;
448+
449+
fn signature(&self) -> PluginSignature {
450+
PluginSignature::build("my-each")
451+
.usage("Run closure on each element of a list")
452+
.required(
453+
"closure",
454+
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
455+
"The closure to evaluate",
456+
)
457+
.input_output_type(Type::ListStream, Type::ListStream)
458+
}
416459

417460
fn run(
418461
&self,
419-
_name: &str,
462+
_plugin: &MyEachPlugin,
420463
engine: &EngineInterface,
421464
call: &EvaluatedCall,
422465
input: PipelineData,
@@ -432,7 +475,7 @@ impl StreamingPlugin for MyEach {
432475
}
433476

434477
fn main() {
435-
serve_plugin(&MyEach, MsgPackSerializer)
478+
serve_plugin(&MyEachPlugin, MsgPackSerializer)
436479
}
437480
```
438481

0 commit comments

Comments
 (0)