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}