nu_plugin/plugin/command.rs
1use nu_protocol::{
2 Example, IntoSpanned, LabeledError, PipelineData, PluginExample, PluginSignature, ShellError,
3 Signature, Value,
4};
5
6use crate::{EngineInterface, EvaluatedCall, Plugin};
7
8/// The API for a Nushell plugin command
9///
10/// This is the trait that Nushell plugin commands must implement. The methods defined on
11/// `PluginCommand` are invoked by [`serve_plugin`](crate::serve_plugin) during plugin registration
12/// and execution.
13///
14/// The plugin command must be able to be safely shared between threads, so that multiple
15/// invocations can be run in parallel. If interior mutability is desired, consider synchronization
16/// primitives such as [mutexes](std::sync::Mutex) and [channels](std::sync::mpsc).
17///
18/// This version of the trait expects stream input and output. If you have a simple plugin that just
19/// operates on plain values, consider using [`SimplePluginCommand`] instead.
20///
21/// # Examples
22/// Basic usage:
23/// ```
24/// # use nu_plugin::*;
25/// # use nu_protocol::{LabeledError, PipelineData, Signals, Signature, Type, Value};
26/// struct LowercasePlugin;
27/// struct Lowercase;
28///
29/// impl PluginCommand for Lowercase {
30/// type Plugin = LowercasePlugin;
31///
32/// fn name(&self) -> &str {
33/// "lowercase"
34/// }
35///
36/// fn description(&self) -> &str {
37/// "Convert each string in a stream to lowercase"
38/// }
39///
40/// fn signature(&self) -> Signature {
41/// Signature::build(PluginCommand::name(self))
42/// .input_output_type(Type::List(Type::String.into()), Type::List(Type::String.into()))
43/// }
44///
45/// fn run(
46/// &self,
47/// plugin: &LowercasePlugin,
48/// engine: &EngineInterface,
49/// call: &EvaluatedCall,
50/// input: PipelineData,
51/// ) -> Result<PipelineData, LabeledError> {
52/// let span = call.head;
53/// Ok(input.map(move |value| {
54/// value.as_str()
55/// .map(|string| Value::string(string.to_lowercase(), span))
56/// // Errors in a stream should be returned as values.
57/// .unwrap_or_else(|err| Value::error(err, span))
58/// }, &Signals::empty())?)
59/// }
60/// }
61///
62/// # impl Plugin for LowercasePlugin {
63/// # fn version(&self) -> String {
64/// # "0.0.0".into()
65/// # }
66/// # fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin=Self>>> {
67/// # vec![Box::new(Lowercase)]
68/// # }
69/// # }
70/// #
71/// # fn main() {
72/// # serve_plugin(&LowercasePlugin{}, MsgPackSerializer)
73/// # }
74/// ```
75pub trait PluginCommand: Sync {
76 /// The type of plugin this command runs on.
77 ///
78 /// Since [`.run()`](Self::run) takes a reference to the plugin, it is necessary to define the
79 /// type of plugin that the command expects here.
80 type Plugin: Plugin;
81
82 /// The name of the command from within Nu.
83 ///
84 /// In case this contains spaces, it will be treated as a subcommand.
85 fn name(&self) -> &str;
86
87 /// The signature of the command.
88 ///
89 /// This defines the arguments and input/output types of the command.
90 fn signature(&self) -> Signature;
91
92 /// A brief description of usage for the command.
93 ///
94 /// This should be short enough to fit in completion menus.
95 fn description(&self) -> &str;
96
97 /// Additional documentation for usage of the command.
98 ///
99 /// This is optional - any arguments documented by [`.signature()`](Self::signature) will be
100 /// shown in the help page automatically. However, this can be useful for explaining things that
101 /// would be too brief to include in [`.description()`](Self::description) and may span multiple lines.
102 fn extra_description(&self) -> &str {
103 ""
104 }
105
106 /// Search terms to help users find the command.
107 ///
108 /// A search query matching any of these search keywords, e.g. on `help --find`, will also
109 /// show this command as a result. This may be used to suggest this command as a replacement
110 /// for common system commands, or based alternate names for the functionality this command
111 /// provides.
112 ///
113 /// For example, a `fold` command might mention `reduce` in its search terms.
114 fn search_terms(&self) -> Vec<&str> {
115 vec![]
116 }
117
118 /// Examples, in Nu, of how the command might be used.
119 ///
120 /// The examples are not restricted to only including this command, and may demonstrate
121 /// pipelines using the command. A `result` may optionally be provided to show users what the
122 /// command would return.
123 ///
124 /// `PluginTest::test_command_examples()` from the
125 /// [`nu-plugin-test-support`](https://docs.rs/nu-plugin-test-support) crate can be used in
126 /// plugin tests to automatically test that examples produce the `result`s as specified.
127 fn examples(&self) -> Vec<Example> {
128 vec![]
129 }
130
131 /// Perform the actual behavior of the plugin command.
132 ///
133 /// The behavior of the plugin is defined by the implementation of this method. When Nushell
134 /// invoked the plugin [`serve_plugin`](crate::serve_plugin) will call this method and print the
135 /// serialized returned value or error to stdout, which Nushell will interpret.
136 ///
137 /// `engine` provides an interface back to the Nushell engine. See [`EngineInterface`] docs for
138 /// details on what methods are available.
139 ///
140 /// The `call` contains metadata describing how the plugin command was invoked, including
141 /// arguments, and `input` contains the structured data piped into the command.
142 ///
143 /// This variant expects to receive and produce [`PipelineData`], which allows for stream-based
144 /// handling of I/O. This is recommended if the plugin is expected to transform large
145 /// lists or potentially large quantities of bytes. The API is more complex however, and
146 /// [`SimplePluginCommand`] is recommended instead if this is not a concern.
147 fn run(
148 &self,
149 plugin: &Self::Plugin,
150 engine: &EngineInterface,
151 call: &EvaluatedCall,
152 input: PipelineData,
153 ) -> Result<PipelineData, LabeledError>;
154}
155
156/// The API for a simple Nushell plugin command
157///
158/// This trait is an alternative to [`PluginCommand`], and operates on values instead of streams.
159/// Note that this may make handling large lists more difficult.
160///
161/// The plugin command must be able to be safely shared between threads, so that multiple
162/// invocations can be run in parallel. If interior mutability is desired, consider synchronization
163/// primitives such as [mutexes](std::sync::Mutex) and [channels](std::sync::mpsc).
164///
165/// # Examples
166/// Basic usage:
167/// ```
168/// # use nu_plugin::*;
169/// # use nu_protocol::{LabeledError, Signature, Type, Value};
170/// struct HelloPlugin;
171/// struct Hello;
172///
173/// impl SimplePluginCommand for Hello {
174/// type Plugin = HelloPlugin;
175///
176/// fn name(&self) -> &str {
177/// "hello"
178/// }
179///
180/// fn description(&self) -> &str {
181/// "Every programmer's favorite greeting"
182/// }
183///
184/// fn signature(&self) -> Signature {
185/// Signature::build(PluginCommand::name(self))
186/// .input_output_type(Type::Nothing, Type::String)
187/// }
188///
189/// fn run(
190/// &self,
191/// plugin: &HelloPlugin,
192/// engine: &EngineInterface,
193/// call: &EvaluatedCall,
194/// input: &Value,
195/// ) -> Result<Value, LabeledError> {
196/// Ok(Value::string("Hello, World!".to_owned(), call.head))
197/// }
198/// }
199///
200/// # impl Plugin for HelloPlugin {
201/// # fn version(&self) -> String {
202/// # "0.0.0".into()
203/// # }
204/// # fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin=Self>>> {
205/// # vec![Box::new(Hello)]
206/// # }
207/// # }
208/// #
209/// # fn main() {
210/// # serve_plugin(&HelloPlugin{}, MsgPackSerializer)
211/// # }
212/// ```
213pub trait SimplePluginCommand: Sync {
214 /// The type of plugin this command runs on.
215 ///
216 /// Since [`.run()`] takes a reference to the plugin, it is necessary to define the type of
217 /// plugin that the command expects here.
218 type Plugin: Plugin;
219
220 /// The name of the command from within Nu.
221 ///
222 /// In case this contains spaces, it will be treated as a subcommand.
223 fn name(&self) -> &str;
224
225 /// The signature of the command.
226 ///
227 /// This defines the arguments and input/output types of the command.
228 fn signature(&self) -> Signature;
229
230 /// A brief description of usage for the command.
231 ///
232 /// This should be short enough to fit in completion menus.
233 fn description(&self) -> &str;
234
235 /// Additional documentation for usage of the command.
236 ///
237 /// This is optional - any arguments documented by [`.signature()`] will be shown in the help
238 /// page automatically. However, this can be useful for explaining things that would be too
239 /// brief to include in [`.description()`](Self::description) and may span multiple lines.
240 fn extra_description(&self) -> &str {
241 ""
242 }
243
244 /// Search terms to help users find the command.
245 ///
246 /// A search query matching any of these search keywords, e.g. on `help --find`, will also
247 /// show this command as a result. This may be used to suggest this command as a replacement
248 /// for common system commands, or based alternate names for the functionality this command
249 /// provides.
250 ///
251 /// For example, a `fold` command might mention `reduce` in its search terms.
252 fn search_terms(&self) -> Vec<&str> {
253 vec![]
254 }
255
256 /// Examples, in Nu, of how the command might be used.
257 ///
258 /// The examples are not restricted to only including this command, and may demonstrate
259 /// pipelines using the command. A `result` may optionally be provided to show users what the
260 /// command would return.
261 ///
262 /// `PluginTest::test_command_examples()` from the
263 /// [`nu-plugin-test-support`](https://docs.rs/nu-plugin-test-support) crate can be used in
264 /// plugin tests to automatically test that examples produce the `result`s as specified.
265 fn examples(&self) -> Vec<Example> {
266 vec![]
267 }
268
269 /// Perform the actual behavior of the plugin command.
270 ///
271 /// The behavior of the plugin is defined by the implementation of this method. When Nushell
272 /// invoked the plugin [`serve_plugin`](crate::serve_plugin) will call this method and print the
273 /// serialized returned value or error to stdout, which Nushell will interpret.
274 ///
275 /// `engine` provides an interface back to the Nushell engine. See [`EngineInterface`] docs for
276 /// details on what methods are available.
277 ///
278 /// The `call` contains metadata describing how the plugin command was invoked, including
279 /// arguments, and `input` contains the structured data piped into the command.
280 ///
281 /// This variant does not support streaming. Consider implementing [`PluginCommand`] directly
282 /// if streaming is desired.
283 fn run(
284 &self,
285 plugin: &Self::Plugin,
286 engine: &EngineInterface,
287 call: &EvaluatedCall,
288 input: &Value,
289 ) -> Result<Value, LabeledError>;
290}
291
292/// All [`SimplePluginCommand`]s can be used as [`PluginCommand`]s, but input streams will be fully
293/// consumed before the plugin command runs.
294impl<T> PluginCommand for T
295where
296 T: SimplePluginCommand,
297{
298 type Plugin = <Self as SimplePluginCommand>::Plugin;
299
300 fn examples(&self) -> Vec<Example> {
301 <Self as SimplePluginCommand>::examples(self)
302 }
303
304 fn extra_description(&self) -> &str {
305 <Self as SimplePluginCommand>::extra_description(self)
306 }
307
308 fn name(&self) -> &str {
309 <Self as SimplePluginCommand>::name(self)
310 }
311
312 fn run(
313 &self,
314 plugin: &Self::Plugin,
315 engine: &EngineInterface,
316 call: &EvaluatedCall,
317 input: PipelineData,
318 ) -> Result<PipelineData, LabeledError> {
319 // Unwrap the PipelineData from input, consuming the potential stream, and pass it to the
320 // simpler signature in Plugin
321 let span = input.span().unwrap_or(call.head);
322 let input_value = input.into_value(span)?;
323 // Wrap the output in PipelineData::Value
324 <Self as SimplePluginCommand>::run(self, plugin, engine, call, &input_value)
325 .map(|value| PipelineData::Value(value, None))
326 }
327
328 fn search_terms(&self) -> Vec<&str> {
329 <Self as SimplePluginCommand>::search_terms(self)
330 }
331
332 fn signature(&self) -> Signature {
333 <Self as SimplePluginCommand>::signature(self)
334 }
335
336 fn description(&self) -> &str {
337 <Self as SimplePluginCommand>::description(self)
338 }
339}
340
341/// Build a [`PluginSignature`] from the signature-related methods on [`PluginCommand`].
342///
343/// This is sent to the engine on `plugin add`.
344///
345/// This is not a public API.
346#[doc(hidden)]
347pub fn create_plugin_signature(command: &(impl PluginCommand + ?Sized)) -> PluginSignature {
348 PluginSignature::new(
349 // Add results of trait methods to signature
350 command
351 .signature()
352 .description(command.description())
353 .extra_description(command.extra_description())
354 .search_terms(
355 command
356 .search_terms()
357 .into_iter()
358 .map(String::from)
359 .collect(),
360 ),
361 // Convert `Example`s to `PluginExample`s
362 command
363 .examples()
364 .into_iter()
365 .map(PluginExample::from)
366 .collect(),
367 )
368}
369
370/// Render examples to their base value so they can be sent in the response to `Signature`.
371pub(crate) fn render_examples(
372 plugin: &impl Plugin,
373 engine: &EngineInterface,
374 examples: &mut [PluginExample],
375) -> Result<(), ShellError> {
376 for example in examples {
377 if let Some(ref mut value) = example.result {
378 value.recurse_mut(&mut |value| {
379 let span = value.span();
380 match value {
381 Value::Custom { .. } => {
382 let value_taken = std::mem::replace(value, Value::nothing(span));
383 let Value::Custom { val, .. } = value_taken else {
384 unreachable!()
385 };
386 *value =
387 plugin.custom_value_to_base_value(engine, val.into_spanned(span))?;
388 Ok::<_, ShellError>(())
389 }
390 _ => Ok(()),
391 }
392 })?;
393 }
394 }
395 Ok(())
396}