nu_plugin/plugin/
command.rs

1use nu_protocol::{
2    DynamicSuggestion, Example, IntoSpanned, LabeledError, PipelineData, PluginExample,
3    PluginSignature, ShellError, Signature, Value, engine::ArgType,
4};
5
6use crate::{DynamicCompletionCall, 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    #[allow(unused_variables)]
156    /// Get completion items for `arg_type`.
157    ///
158    /// It's useful when you want to get auto completion items of a flag or positional argument
159    /// dynamically.
160    ///
161    /// The implementation can returns 3 types of return values:
162    /// - None: I couldn't find any suggestions, please fall back to default completions
163    /// - Some(vec![]): there are no suggestions
164    /// - Some(vec![item1, item2]): item1 and item2 are available
165    #[expect(deprecated, reason = "forwarding experimental status")]
166    fn get_dynamic_completion(
167        &self,
168        plugin: &Self::Plugin,
169        engine: &EngineInterface,
170        call: DynamicCompletionCall,
171        arg_type: ArgType,
172        _experimental: nu_protocol::engine::ExperimentalMarker,
173    ) -> Option<Vec<DynamicSuggestion>> {
174        None
175    }
176}
177
178/// The API for a simple Nushell plugin command
179///
180/// This trait is an alternative to [`PluginCommand`], and operates on values instead of streams.
181/// Note that this may make handling large lists more difficult.
182///
183/// The plugin command must be able to be safely shared between threads, so that multiple
184/// invocations can be run in parallel. If interior mutability is desired, consider synchronization
185/// primitives such as [mutexes](std::sync::Mutex) and [channels](std::sync::mpsc).
186///
187/// # Examples
188/// Basic usage:
189/// ```
190/// # use nu_plugin::*;
191/// # use nu_protocol::{LabeledError, Signature, Type, Value};
192/// struct HelloPlugin;
193/// struct Hello;
194///
195/// impl SimplePluginCommand for Hello {
196///     type Plugin = HelloPlugin;
197///
198///     fn name(&self) -> &str {
199///         "hello"
200///     }
201///
202///     fn description(&self) -> &str {
203///         "Every programmer's favorite greeting"
204///     }
205///
206///     fn signature(&self) -> Signature {
207///         Signature::build(PluginCommand::name(self))
208///             .input_output_type(Type::Nothing, Type::String)
209///     }
210///
211///     fn run(
212///         &self,
213///         plugin: &HelloPlugin,
214///         engine: &EngineInterface,
215///         call: &EvaluatedCall,
216///         input: &Value,
217///     ) -> Result<Value, LabeledError> {
218///         Ok(Value::string("Hello, World!".to_owned(), call.head))
219///     }
220/// }
221///
222/// # impl Plugin for HelloPlugin {
223/// #     fn version(&self) -> String {
224/// #         "0.0.0".into()
225/// #     }
226/// #     fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin=Self>>> {
227/// #         vec![Box::new(Hello)]
228/// #     }
229/// # }
230/// #
231/// # fn main() {
232/// #     serve_plugin(&HelloPlugin{}, MsgPackSerializer)
233/// # }
234/// ```
235pub trait SimplePluginCommand: Sync {
236    /// The type of plugin this command runs on.
237    ///
238    /// Since [`.run()`] takes a reference to the plugin, it is necessary to define the type of
239    /// plugin that the command expects here.
240    type Plugin: Plugin;
241
242    /// The name of the command from within Nu.
243    ///
244    /// In case this contains spaces, it will be treated as a subcommand.
245    fn name(&self) -> &str;
246
247    /// The signature of the command.
248    ///
249    /// This defines the arguments and input/output types of the command.
250    fn signature(&self) -> Signature;
251
252    /// A brief description of usage for the command.
253    ///
254    /// This should be short enough to fit in completion menus.
255    fn description(&self) -> &str;
256
257    /// Additional documentation for usage of the command.
258    ///
259    /// This is optional - any arguments documented by [`.signature()`] will be shown in the help
260    /// page automatically. However, this can be useful for explaining things that would be too
261    /// brief to include in [`.description()`](Self::description) and may span multiple lines.
262    fn extra_description(&self) -> &str {
263        ""
264    }
265
266    /// Search terms to help users find the command.
267    ///
268    /// A search query matching any of these search keywords, e.g. on `help --find`, will also
269    /// show this command as a result. This may be used to suggest this command as a replacement
270    /// for common system commands, or based alternate names for the functionality this command
271    /// provides.
272    ///
273    /// For example, a `fold` command might mention `reduce` in its search terms.
274    fn search_terms(&self) -> Vec<&str> {
275        vec![]
276    }
277
278    /// Examples, in Nu, of how the command might be used.
279    ///
280    /// The examples are not restricted to only including this command, and may demonstrate
281    /// pipelines using the command. A `result` may optionally be provided to show users what the
282    /// command would return.
283    ///
284    /// `PluginTest::test_command_examples()` from the
285    /// [`nu-plugin-test-support`](https://docs.rs/nu-plugin-test-support) crate can be used in
286    /// plugin tests to automatically test that examples produce the `result`s as specified.
287    fn examples(&self) -> Vec<Example<'_>> {
288        vec![]
289    }
290
291    /// Perform the actual behavior of the plugin command.
292    ///
293    /// The behavior of the plugin is defined by the implementation of this method. When Nushell
294    /// invoked the plugin [`serve_plugin`](crate::serve_plugin) will call this method and print the
295    /// serialized returned value or error to stdout, which Nushell will interpret.
296    ///
297    /// `engine` provides an interface back to the Nushell engine. See [`EngineInterface`] docs for
298    /// details on what methods are available.
299    ///
300    /// The `call` contains metadata describing how the plugin command was invoked, including
301    /// arguments, and `input` contains the structured data piped into the command.
302    ///
303    /// This variant does not support streaming. Consider implementing [`PluginCommand`] directly
304    /// if streaming is desired.
305    fn run(
306        &self,
307        plugin: &Self::Plugin,
308        engine: &EngineInterface,
309        call: &EvaluatedCall,
310        input: &Value,
311    ) -> Result<Value, LabeledError>;
312
313    /// Get completion items for `arg_type`.
314    ///
315    /// It's useful when you want to get auto completion items of a flag or positional argument
316    /// dynamically.
317    ///
318    /// The implementation can returns 3 types of return values:
319    /// - None: I couldn't find any suggestions, please fall back to default completions
320    /// - Some(vec![]): there are no suggestions
321    /// - Some(vec![item1, item2]): item1 and item2 are available
322    #[allow(unused_variables)]
323    #[expect(deprecated, reason = "forwarding experimental status")]
324    fn get_dynamic_completion(
325        &self,
326        plugin: &Self::Plugin,
327        engine: &EngineInterface,
328        call: DynamicCompletionCall,
329        arg_type: ArgType,
330        _experimental: nu_protocol::engine::ExperimentalMarker,
331    ) -> Option<Vec<DynamicSuggestion>> {
332        None
333    }
334}
335
336/// All [`SimplePluginCommand`]s can be used as [`PluginCommand`]s, but input streams will be fully
337/// consumed before the plugin command runs.
338impl<T> PluginCommand for T
339where
340    T: SimplePluginCommand,
341{
342    type Plugin = <Self as SimplePluginCommand>::Plugin;
343
344    fn examples(&self) -> Vec<Example<'_>> {
345        <Self as SimplePluginCommand>::examples(self)
346    }
347
348    fn extra_description(&self) -> &str {
349        <Self as SimplePluginCommand>::extra_description(self)
350    }
351
352    fn name(&self) -> &str {
353        <Self as SimplePluginCommand>::name(self)
354    }
355
356    fn run(
357        &self,
358        plugin: &Self::Plugin,
359        engine: &EngineInterface,
360        call: &EvaluatedCall,
361        input: PipelineData,
362    ) -> Result<PipelineData, LabeledError> {
363        // Unwrap the PipelineData from input, consuming the potential stream, and pass it to the
364        // simpler signature in Plugin
365        let span = input.span().unwrap_or(call.head);
366        let input_value = input.into_value(span)?;
367        // Wrap the output in PipelineData::value
368        <Self as SimplePluginCommand>::run(self, plugin, engine, call, &input_value)
369            .map(|value| PipelineData::value(value, None))
370    }
371
372    fn search_terms(&self) -> Vec<&str> {
373        <Self as SimplePluginCommand>::search_terms(self)
374    }
375
376    fn signature(&self) -> Signature {
377        <Self as SimplePluginCommand>::signature(self)
378    }
379
380    fn description(&self) -> &str {
381        <Self as SimplePluginCommand>::description(self)
382    }
383
384    #[allow(unused_variables)]
385    #[allow(deprecated, reason = "internal usage")]
386    fn get_dynamic_completion(
387        &self,
388        plugin: &Self::Plugin,
389        engine: &EngineInterface,
390        call: DynamicCompletionCall,
391        arg_type: ArgType,
392        experimental: nu_protocol::engine::ExperimentalMarker,
393    ) -> Option<Vec<DynamicSuggestion>> {
394        <Self as SimplePluginCommand>::get_dynamic_completion(
395            self,
396            plugin,
397            engine,
398            call,
399            arg_type,
400            experimental,
401        )
402    }
403}
404
405/// Build a [`PluginSignature`] from the signature-related methods on [`PluginCommand`].
406///
407/// This is sent to the engine on `plugin add`.
408///
409/// This is not a public API.
410#[doc(hidden)]
411pub fn create_plugin_signature(command: &(impl PluginCommand + ?Sized)) -> PluginSignature {
412    PluginSignature::new(
413        // Add results of trait methods to signature
414        command
415            .signature()
416            .description(command.description())
417            .extra_description(command.extra_description())
418            .search_terms(
419                command
420                    .search_terms()
421                    .into_iter()
422                    .map(String::from)
423                    .collect(),
424            ),
425        // Convert `Example`s to `PluginExample`s
426        command
427            .examples()
428            .into_iter()
429            .map(PluginExample::from)
430            .collect(),
431    )
432}
433
434/// Render examples to their base value so they can be sent in the response to `Signature`.
435pub(crate) fn render_examples(
436    plugin: &impl Plugin,
437    engine: &EngineInterface,
438    examples: &mut [PluginExample],
439) -> Result<(), ShellError> {
440    for example in examples {
441        if let Some(ref mut value) = example.result {
442            value.recurse_mut(&mut |value| {
443                let span = value.span();
444                match value {
445                    Value::Custom { .. } => {
446                        let value_taken = std::mem::replace(value, Value::nothing(span));
447                        let Value::Custom { val, .. } = value_taken else {
448                            unreachable!()
449                        };
450                        *value =
451                            plugin.custom_value_to_base_value(engine, val.into_spanned(span))?;
452                        Ok::<_, ShellError>(())
453                    }
454                    _ => Ok(()),
455                }
456            })?;
457        }
458    }
459    Ok(())
460}