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}