mirror of
https://github.com/nushell/nushell.git
synced 2025-08-09 01:24:58 +02:00
Change PluginCommand API to be more like Command (#12279)
# Description
This is something that was discussed in the core team meeting last
Wednesday. @ayax79 is building `nu-plugin-polars` with all of the
dataframe commands into a plugin, and there are a lot of them, so it
would help to make the API more similar. At the same time, I think the
`Command` API is just better anyway. I don't think the difference is
justified, and the types for core commands have the benefit of requiring
less `.into()` because they often don't own their data
- Broke `signature()` up into `name()`, `usage()`, `extra_usage()`,
`search_terms()`, `examples()`
- `signature()` returns `nu_protocol::Signature`
- `examples()` returns `Vec<nu_protocol::Example>`
- `PluginSignature` and `PluginExample` no longer need to be used by
plugin developers
# User-Facing Changes
Breaking API for plugins yet again 😄
This commit is contained in:
@ -17,8 +17,8 @@
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use nu_plugin::{EvaluatedCall, MsgPackSerializer, serve_plugin};
|
||||
//! use nu_plugin::{Plugin, PluginCommand, SimplePluginCommand, EngineInterface};
|
||||
//! use nu_protocol::{PluginSignature, LabeledError, Value};
|
||||
//! use nu_plugin::{EngineInterface, Plugin, PluginCommand, SimplePluginCommand};
|
||||
//! use nu_protocol::{LabeledError, Signature, Value};
|
||||
//!
|
||||
//! struct MyPlugin;
|
||||
//! struct MyCommand;
|
||||
@ -32,7 +32,15 @@
|
||||
//! impl SimplePluginCommand for MyCommand {
|
||||
//! type Plugin = MyPlugin;
|
||||
//!
|
||||
//! fn signature(&self) -> PluginSignature {
|
||||
//! fn name(&self) -> &str {
|
||||
//! "my-command"
|
||||
//! }
|
||||
//!
|
||||
//! fn usage(&self) -> &str {
|
||||
//! todo!();
|
||||
//! }
|
||||
//!
|
||||
//! fn signature(&self) -> Signature {
|
||||
//! todo!();
|
||||
//! }
|
||||
//!
|
||||
@ -71,9 +79,10 @@ pub use serializers::{json::JsonSerializer, msgpack::MsgPackSerializer};
|
||||
// Used by other nu crates.
|
||||
#[doc(hidden)]
|
||||
pub use plugin::{
|
||||
get_signature, serve_plugin_io, EngineInterfaceManager, GetPlugin, Interface, InterfaceManager,
|
||||
PersistentPlugin, PluginDeclaration, PluginExecutionCommandContext, PluginExecutionContext,
|
||||
PluginInterface, PluginInterfaceManager, PluginSource, ServePluginError,
|
||||
create_plugin_signature, get_signature, serve_plugin_io, EngineInterfaceManager, GetPlugin,
|
||||
Interface, InterfaceManager, PersistentPlugin, PluginDeclaration,
|
||||
PluginExecutionCommandContext, PluginExecutionContext, PluginInterface, PluginInterfaceManager,
|
||||
PluginSource, ServePluginError,
|
||||
};
|
||||
#[doc(hidden)]
|
||||
pub use protocol::{PluginCustomValue, PluginInput, PluginOutput};
|
||||
|
@ -1,4 +1,6 @@
|
||||
use nu_protocol::{LabeledError, PipelineData, PluginSignature, Value};
|
||||
use nu_protocol::{
|
||||
Example, LabeledError, PipelineData, PluginExample, PluginSignature, Signature, Value,
|
||||
};
|
||||
|
||||
use crate::{EngineInterface, EvaluatedCall, Plugin};
|
||||
|
||||
@ -19,16 +21,23 @@ use crate::{EngineInterface, EvaluatedCall, Plugin};
|
||||
/// Basic usage:
|
||||
/// ```
|
||||
/// # use nu_plugin::*;
|
||||
/// # use nu_protocol::{PluginSignature, PipelineData, Type, Value, LabeledError};
|
||||
/// # use nu_protocol::{Signature, PipelineData, Type, Value, LabeledError};
|
||||
/// struct LowercasePlugin;
|
||||
/// struct Lowercase;
|
||||
///
|
||||
/// impl PluginCommand for Lowercase {
|
||||
/// type Plugin = LowercasePlugin;
|
||||
///
|
||||
/// fn signature(&self) -> PluginSignature {
|
||||
/// PluginSignature::build("lowercase")
|
||||
/// .usage("Convert each string in a stream to lowercase")
|
||||
/// fn name(&self) -> &str {
|
||||
/// "lowercase"
|
||||
/// }
|
||||
///
|
||||
/// fn usage(&self) -> &str {
|
||||
/// "Convert each string in a stream to lowercase"
|
||||
/// }
|
||||
///
|
||||
/// fn signature(&self) -> Signature {
|
||||
/// Signature::build(PluginCommand::name(self))
|
||||
/// .input_output_type(Type::List(Type::String.into()), Type::List(Type::String.into()))
|
||||
/// }
|
||||
///
|
||||
@ -60,18 +69,62 @@ use crate::{EngineInterface, EvaluatedCall, Plugin};
|
||||
/// # }
|
||||
/// ```
|
||||
pub trait PluginCommand: Sync {
|
||||
/// The type of plugin this command runs on
|
||||
/// The type of plugin this command runs on.
|
||||
///
|
||||
/// Since [`.run()`] takes a reference to the plugin, it is necessary to define the type of
|
||||
/// plugin that the command expects here.
|
||||
type Plugin: Plugin;
|
||||
|
||||
/// The signature of the plugin command
|
||||
/// The name of the command from within Nu.
|
||||
///
|
||||
/// These are aggregated from the [`Plugin`] and sent to the engine on `register`.
|
||||
fn signature(&self) -> PluginSignature;
|
||||
/// In case this contains spaces, it will be treated as a subcommand.
|
||||
fn name(&self) -> &str;
|
||||
|
||||
/// Perform the actual behavior of the plugin command
|
||||
/// The signature of the command.
|
||||
///
|
||||
/// This defines the arguments and input/output types of the command.
|
||||
fn signature(&self) -> Signature;
|
||||
|
||||
/// A brief description of usage for the command.
|
||||
///
|
||||
/// This should be short enough to fit in completion menus.
|
||||
fn usage(&self) -> &str;
|
||||
|
||||
/// Additional documentation for usage of the command.
|
||||
///
|
||||
/// This is optional - any arguments documented by [`.signature()`] will be shown in the help
|
||||
/// page automatically. However, this can be useful for explaining things that would be too
|
||||
/// brief to include in [`.usage()`] and may span multiple lines.
|
||||
fn extra_usage(&self) -> &str {
|
||||
""
|
||||
}
|
||||
|
||||
/// Search terms to help users find the command.
|
||||
///
|
||||
/// A search query matching any of these search keywords, e.g. on `help --find`, will also
|
||||
/// show this command as a result. This may be used to suggest this command as a replacement
|
||||
/// for common system commands, or based alternate names for the functionality this command
|
||||
/// provides.
|
||||
///
|
||||
/// For example, a `fold` command might mention `reduce` in its search terms.
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
/// Examples, in Nu, of how the command might be used.
|
||||
///
|
||||
/// The examples are not restricted to only including this command, and may demonstrate
|
||||
/// pipelines using the command. A `result` may optionally be provided to show users what the
|
||||
/// command would return.
|
||||
///
|
||||
/// `PluginTest::test_command_examples()` from the
|
||||
/// [`nu-plugin-test-support`](https://docs.rs/nu-plugin-test-support) crate can be used in
|
||||
/// plugin tests to automatically test that examples produce the `result`s as specified.
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
/// Perform the actual behavior of the plugin command.
|
||||
///
|
||||
/// The behavior of the plugin is defined by the implementation of this method. When Nushell
|
||||
/// invoked the plugin [`serve_plugin`](crate::serve_plugin) will call this method and print the
|
||||
@ -109,15 +162,23 @@ pub trait PluginCommand: Sync {
|
||||
/// Basic usage:
|
||||
/// ```
|
||||
/// # use nu_plugin::*;
|
||||
/// # use nu_protocol::{PluginSignature, Type, Value, LabeledError};
|
||||
/// # use nu_protocol::{LabeledError, Signature, Type, Value};
|
||||
/// struct HelloPlugin;
|
||||
/// struct Hello;
|
||||
///
|
||||
/// impl SimplePluginCommand for Hello {
|
||||
/// type Plugin = HelloPlugin;
|
||||
///
|
||||
/// fn signature(&self) -> PluginSignature {
|
||||
/// PluginSignature::build("hello")
|
||||
/// fn name(&self) -> &str {
|
||||
/// "hello"
|
||||
/// }
|
||||
///
|
||||
/// fn usage(&self) -> &str {
|
||||
/// "Every programmer's favorite greeting"
|
||||
/// }
|
||||
///
|
||||
/// fn signature(&self) -> Signature {
|
||||
/// Signature::build(PluginCommand::name(self))
|
||||
/// .input_output_type(Type::Nothing, Type::String)
|
||||
/// }
|
||||
///
|
||||
@ -143,18 +204,62 @@ pub trait PluginCommand: Sync {
|
||||
/// # }
|
||||
/// ```
|
||||
pub trait SimplePluginCommand: Sync {
|
||||
/// The type of plugin this command runs on
|
||||
/// The type of plugin this command runs on.
|
||||
///
|
||||
/// Since [`.run()`] takes a reference to the plugin, it is necessary to define the type of
|
||||
/// plugin that the command expects here.
|
||||
type Plugin: Plugin;
|
||||
|
||||
/// The signature of the plugin command
|
||||
/// The name of the command from within Nu.
|
||||
///
|
||||
/// These are aggregated from the [`Plugin`] and sent to the engine on `register`.
|
||||
fn signature(&self) -> PluginSignature;
|
||||
/// In case this contains spaces, it will be treated as a subcommand.
|
||||
fn name(&self) -> &str;
|
||||
|
||||
/// Perform the actual behavior of the plugin command
|
||||
/// The signature of the command.
|
||||
///
|
||||
/// This defines the arguments and input/output types of the command.
|
||||
fn signature(&self) -> Signature;
|
||||
|
||||
/// A brief description of usage for the command.
|
||||
///
|
||||
/// This should be short enough to fit in completion menus.
|
||||
fn usage(&self) -> &str;
|
||||
|
||||
/// Additional documentation for usage of the command.
|
||||
///
|
||||
/// This is optional - any arguments documented by [`.signature()`] will be shown in the help
|
||||
/// page automatically. However, this can be useful for explaining things that would be too
|
||||
/// brief to include in [`.usage()`] and may span multiple lines.
|
||||
fn extra_usage(&self) -> &str {
|
||||
""
|
||||
}
|
||||
|
||||
/// Search terms to help users find the command.
|
||||
///
|
||||
/// A search query matching any of these search keywords, e.g. on `help --find`, will also
|
||||
/// show this command as a result. This may be used to suggest this command as a replacement
|
||||
/// for common system commands, or based alternate names for the functionality this command
|
||||
/// provides.
|
||||
///
|
||||
/// For example, a `fold` command might mention `reduce` in its search terms.
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
/// Examples, in Nu, of how the command might be used.
|
||||
///
|
||||
/// The examples are not restricted to only including this command, and may demonstrate
|
||||
/// pipelines using the command. A `result` may optionally be provided to show users what the
|
||||
/// command would return.
|
||||
///
|
||||
/// `PluginTest::test_command_examples()` from the
|
||||
/// [`nu-plugin-test-support`](https://docs.rs/nu-plugin-test-support) crate can be used in
|
||||
/// plugin tests to automatically test that examples produce the `result`s as specified.
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
/// Perform the actual behavior of the plugin command.
|
||||
///
|
||||
/// The behavior of the plugin is defined by the implementation of this method. When Nushell
|
||||
/// invoked the plugin [`serve_plugin`](crate::serve_plugin) will call this method and print the
|
||||
@ -185,8 +290,16 @@ where
|
||||
{
|
||||
type Plugin = <Self as SimplePluginCommand>::Plugin;
|
||||
|
||||
fn signature(&self) -> PluginSignature {
|
||||
<Self as SimplePluginCommand>::signature(self)
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
<Self as SimplePluginCommand>::examples(self)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
<Self as SimplePluginCommand>::extra_usage(self)
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
<Self as SimplePluginCommand>::name(self)
|
||||
}
|
||||
|
||||
fn run(
|
||||
@ -204,4 +317,45 @@ where
|
||||
<Self as SimplePluginCommand>::run(self, plugin, engine, call, &input_value)
|
||||
.map(|value| PipelineData::Value(value, None))
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
<Self as SimplePluginCommand>::search_terms(self)
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
<Self as SimplePluginCommand>::signature(self)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
<Self as SimplePluginCommand>::usage(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a [`PluginSignature`] from the signature-related methods on [`PluginCommand`].
|
||||
///
|
||||
/// This is sent to the engine on `register`.
|
||||
///
|
||||
/// This is not a public API.
|
||||
#[doc(hidden)]
|
||||
pub fn create_plugin_signature(command: &(impl PluginCommand + ?Sized)) -> PluginSignature {
|
||||
PluginSignature::new(
|
||||
// Add results of trait methods to signature
|
||||
command
|
||||
.signature()
|
||||
.usage(command.usage())
|
||||
.extra_usage(command.extra_usage())
|
||||
.search_terms(
|
||||
command
|
||||
.search_terms()
|
||||
.into_iter()
|
||||
.map(String::from)
|
||||
.collect(),
|
||||
),
|
||||
// Convert `Example`s to `PluginExample`s
|
||||
command
|
||||
.examples()
|
||||
.into_iter()
|
||||
.map(PluginExample::from)
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ use crate::{
|
||||
};
|
||||
use nu_protocol::{
|
||||
engine::Closure, Config, CustomValue, IntoInterruptiblePipelineData, LabeledError,
|
||||
PipelineData, PluginExample, PluginSignature, ShellError, Span, Spanned, Value,
|
||||
PipelineData, PluginExample, PluginSignature, ShellError, Signature, Span, Spanned, Value,
|
||||
};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
@ -786,15 +786,16 @@ fn interface_write_signature() -> Result<(), ShellError> {
|
||||
fn interface_write_signature_custom_value() -> Result<(), ShellError> {
|
||||
let test = TestCase::new();
|
||||
let interface = test.engine().interface_for_context(38);
|
||||
let signatures = vec![PluginSignature::build("test command").plugin_examples(vec![
|
||||
PluginExample {
|
||||
let signatures = vec![PluginSignature::new(
|
||||
Signature::build("test command"),
|
||||
vec![PluginExample {
|
||||
example: "test command".into(),
|
||||
description: "a test".into(),
|
||||
result: Some(Value::test_custom_value(Box::new(
|
||||
expected_test_custom_value(),
|
||||
))),
|
||||
},
|
||||
])];
|
||||
}],
|
||||
)];
|
||||
interface.write_signature(signatures.clone())?;
|
||||
|
||||
let written = test.next_written().expect("nothing written");
|
||||
|
@ -3,15 +3,7 @@ use crate::{
|
||||
protocol::{CallInfo, CustomValueOp, PluginCustomValue, PluginInput, PluginOutput},
|
||||
EncodingType,
|
||||
};
|
||||
use nu_engine::documentation::get_flags_section;
|
||||
use nu_protocol::{
|
||||
ast::Operator, CustomValue, IntoSpanned, LabeledError, PipelineData, PluginSignature,
|
||||
ShellError, Spanned, Value,
|
||||
};
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::process::CommandExt;
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::process::CommandExt;
|
||||
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
collections::HashMap,
|
||||
@ -19,14 +11,28 @@ use std::{
|
||||
ffi::OsStr,
|
||||
fmt::Write,
|
||||
io::{BufReader, Read, Write as WriteTrait},
|
||||
ops::Deref,
|
||||
path::Path,
|
||||
process::{Child, ChildStdout, Command as CommandSys, Stdio},
|
||||
sync::mpsc::TrySendError,
|
||||
sync::{mpsc, Arc, Mutex},
|
||||
sync::{
|
||||
mpsc::{self, TrySendError},
|
||||
Arc, Mutex,
|
||||
},
|
||||
thread,
|
||||
};
|
||||
|
||||
use nu_engine::documentation::get_flags_section;
|
||||
use nu_protocol::{
|
||||
ast::Operator, CustomValue, IntoSpanned, LabeledError, PipelineData, PluginSignature,
|
||||
ShellError, Spanned, Value,
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::process::CommandExt;
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::process::CommandExt;
|
||||
|
||||
use self::gc::PluginGc;
|
||||
pub use self::interface::{PluginRead, PluginWrite};
|
||||
|
||||
@ -38,7 +44,7 @@ mod interface;
|
||||
mod persistent;
|
||||
mod source;
|
||||
|
||||
pub use command::{PluginCommand, SimplePluginCommand};
|
||||
pub use command::{create_plugin_signature, PluginCommand, SimplePluginCommand};
|
||||
pub use declaration::PluginDeclaration;
|
||||
pub use interface::{
|
||||
EngineInterface, EngineInterfaceManager, Interface, InterfaceManager, PluginInterface,
|
||||
@ -229,7 +235,7 @@ where
|
||||
/// Basic usage:
|
||||
/// ```
|
||||
/// # use nu_plugin::*;
|
||||
/// # use nu_protocol::{PluginSignature, LabeledError, Type, Value};
|
||||
/// # use nu_protocol::{LabeledError, Signature, Type, Value};
|
||||
/// struct HelloPlugin;
|
||||
/// struct Hello;
|
||||
///
|
||||
@ -242,8 +248,16 @@ where
|
||||
/// impl SimplePluginCommand for Hello {
|
||||
/// type Plugin = HelloPlugin;
|
||||
///
|
||||
/// fn signature(&self) -> PluginSignature {
|
||||
/// PluginSignature::build("hello")
|
||||
/// fn name(&self) -> &str {
|
||||
/// "hello"
|
||||
/// }
|
||||
///
|
||||
/// fn usage(&self) -> &str {
|
||||
/// "Every programmer's favorite greeting"
|
||||
/// }
|
||||
///
|
||||
/// fn signature(&self) -> Signature {
|
||||
/// Signature::build(PluginCommand::name(self))
|
||||
/// .input_output_type(Type::Nothing, Type::String)
|
||||
/// }
|
||||
///
|
||||
@ -556,11 +570,11 @@ where
|
||||
let mut commands: HashMap<String, _> = HashMap::new();
|
||||
|
||||
for command in plugin.commands() {
|
||||
if let Some(previous) = commands.insert(command.signature().sig.name.clone(), command) {
|
||||
if let Some(previous) = commands.insert(command.name().into(), command) {
|
||||
eprintln!(
|
||||
"Plugin `{plugin_name}` warning: command `{}` shadowed by another command with the \
|
||||
same name. Check your command signatures",
|
||||
previous.signature().sig.name
|
||||
same name. Check your commands' `name()` methods",
|
||||
previous.name()
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -636,7 +650,7 @@ where
|
||||
ReceivedPluginCall::Signature { engine } => {
|
||||
let sigs = commands
|
||||
.values()
|
||||
.map(|command| command.signature())
|
||||
.map(|command| create_plugin_signature(command.deref()))
|
||||
.collect();
|
||||
engine.write_signature(sigs).try_to_report(&engine)?;
|
||||
}
|
||||
@ -752,23 +766,22 @@ fn print_help(plugin: &impl Plugin, encoder: impl PluginEncoder) {
|
||||
|
||||
plugin.commands().into_iter().for_each(|command| {
|
||||
let signature = command.signature();
|
||||
let res = write!(help, "\nCommand: {}", signature.sig.name)
|
||||
.and_then(|_| writeln!(help, "\nUsage:\n > {}", signature.sig.usage))
|
||||
let res = write!(help, "\nCommand: {}", command.name())
|
||||
.and_then(|_| writeln!(help, "\nUsage:\n > {}", command.usage()))
|
||||
.and_then(|_| {
|
||||
if !signature.sig.extra_usage.is_empty() {
|
||||
writeln!(help, "\nExtra usage:\n > {}", signature.sig.extra_usage)
|
||||
if !command.extra_usage().is_empty() {
|
||||
writeln!(help, "\nExtra usage:\n > {}", command.extra_usage())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
.and_then(|_| {
|
||||
let flags = get_flags_section(None, &signature.sig, |v| format!("{:#?}", v));
|
||||
let flags = get_flags_section(None, &signature, |v| format!("{:#?}", v));
|
||||
write!(help, "{flags}")
|
||||
})
|
||||
.and_then(|_| writeln!(help, "\nParameters:"))
|
||||
.and_then(|_| {
|
||||
signature
|
||||
.sig
|
||||
.required_positional
|
||||
.iter()
|
||||
.try_for_each(|positional| {
|
||||
@ -781,7 +794,6 @@ fn print_help(plugin: &impl Plugin, encoder: impl PluginEncoder) {
|
||||
})
|
||||
.and_then(|_| {
|
||||
signature
|
||||
.sig
|
||||
.optional_positional
|
||||
.iter()
|
||||
.try_for_each(|positional| {
|
||||
@ -793,7 +805,7 @@ fn print_help(plugin: &impl Plugin, encoder: impl PluginEncoder) {
|
||||
})
|
||||
})
|
||||
.and_then(|_| {
|
||||
if let Some(rest_positional) = &signature.sig.rest_positional {
|
||||
if let Some(rest_positional) = &signature.rest_positional {
|
||||
writeln!(
|
||||
help,
|
||||
" ...{} <{}>: {}",
|
||||
|
@ -5,7 +5,9 @@ macro_rules! generate_tests {
|
||||
PluginCallResponse, PluginCustomValue, PluginInput, PluginOption, PluginOutput,
|
||||
StreamData, StreamMessage,
|
||||
};
|
||||
use nu_protocol::{LabeledError, PluginSignature, Span, Spanned, SyntaxShape, Value};
|
||||
use nu_protocol::{
|
||||
LabeledError, PluginSignature, Signature, Span, Spanned, SyntaxShape, Value,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn decode_eof() {
|
||||
@ -211,17 +213,20 @@ macro_rules! generate_tests {
|
||||
|
||||
#[test]
|
||||
fn response_round_trip_signature() {
|
||||
let signature = PluginSignature::build("nu-plugin")
|
||||
.required("first", SyntaxShape::String, "first required")
|
||||
.required("second", SyntaxShape::Int, "second required")
|
||||
.required_named("first-named", SyntaxShape::String, "first named", Some('f'))
|
||||
.required_named(
|
||||
"second-named",
|
||||
SyntaxShape::String,
|
||||
"second named",
|
||||
Some('s'),
|
||||
)
|
||||
.rest("remaining", SyntaxShape::Int, "remaining");
|
||||
let signature = PluginSignature::new(
|
||||
Signature::build("nu-plugin")
|
||||
.required("first", SyntaxShape::String, "first required")
|
||||
.required("second", SyntaxShape::Int, "second required")
|
||||
.required_named("first-named", SyntaxShape::String, "first named", Some('f'))
|
||||
.required_named(
|
||||
"second-named",
|
||||
SyntaxShape::String,
|
||||
"second named",
|
||||
Some('s'),
|
||||
)
|
||||
.rest("remaining", SyntaxShape::Int, "remaining"),
|
||||
vec![],
|
||||
);
|
||||
|
||||
let response = PluginCallResponse::Signature(vec![signature.clone()]);
|
||||
let output = PluginOutput::CallResponse(3, response);
|
||||
|
Reference in New Issue
Block a user