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:
Devyn Cairns 2024-03-27 03:59:57 -07:00 committed by GitHub
parent 03b5e9d853
commit 01d30a416b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
45 changed files with 962 additions and 674 deletions

View File

@ -1,6 +1,6 @@
use std::sync::Arc; use std::{ops::Deref, sync::Arc};
use nu_plugin::{Plugin, PluginDeclaration}; use nu_plugin::{create_plugin_signature, Plugin, PluginDeclaration};
use nu_protocol::{engine::StateWorkingSet, RegisteredPlugin, ShellError}; use nu_protocol::{engine::StateWorkingSet, RegisteredPlugin, ShellError};
use crate::{fake_persistent_plugin::FakePersistentPlugin, spawn_fake_plugin::spawn_fake_plugin}; use crate::{fake_persistent_plugin::FakePersistentPlugin, spawn_fake_plugin::spawn_fake_plugin};
@ -15,7 +15,7 @@ pub fn fake_register(
let reg_plugin_clone = reg_plugin.clone(); let reg_plugin_clone = reg_plugin.clone();
for command in plugin.commands() { for command in plugin.commands() {
let signature = command.signature(); let signature = create_plugin_signature(command.deref());
let decl = PluginDeclaration::new(reg_plugin.clone(), signature); let decl = PluginDeclaration::new(reg_plugin.clone(), signature);
working_set.add_decl(Box::new(decl)); working_set.add_decl(Box::new(decl));
} }

View File

@ -7,8 +7,10 @@
//! //!
//! use nu_plugin::*; //! use nu_plugin::*;
//! use nu_plugin_test_support::PluginTest; //! use nu_plugin_test_support::PluginTest;
//! use nu_protocol::{PluginSignature, PipelineData, Type, Span, Value, LabeledError}; //! use nu_protocol::{
//! use nu_protocol::IntoInterruptiblePipelineData; //! Example, IntoInterruptiblePipelineData, LabeledError, PipelineData, ShellError, Signature,
//! Span, Type, Value,
//! };
//! //!
//! struct LowercasePlugin; //! struct LowercasePlugin;
//! struct Lowercase; //! struct Lowercase;
@ -16,26 +18,50 @@
//! impl PluginCommand for Lowercase { //! impl PluginCommand for Lowercase {
//! type Plugin = LowercasePlugin; //! type Plugin = LowercasePlugin;
//! //!
//! fn signature(&self) -> PluginSignature { //! fn name(&self) -> &str {
//! PluginSignature::build("lowercase") //! "lowercase"
//! .usage("Convert each string in a stream to lowercase") //! }
//! .input_output_type(Type::List(Type::String.into()), Type::List(Type::String.into())) //!
//! fn usage(&self) -> &str {
//! "Convert each string in a stream to lowercase"
//! }
//!
//! fn signature(&self) -> Signature {
//! Signature::build(self.name()).input_output_type(
//! Type::List(Type::String.into()),
//! Type::List(Type::String.into()),
//! )
//! }
//!
//! fn examples(&self) -> Vec<Example> {
//! vec![Example {
//! example: r#"[Hello wORLD] | lowercase"#,
//! description: "Lowercase a list of strings",
//! result: Some(Value::test_list(vec![
//! Value::test_string("hello"),
//! Value::test_string("world"),
//! ])),
//! }]
//! } //! }
//! //!
//! fn run( //! fn run(
//! &self, //! &self,
//! plugin: &LowercasePlugin, //! _plugin: &LowercasePlugin,
//! engine: &EngineInterface, //! _engine: &EngineInterface,
//! call: &EvaluatedCall, //! call: &EvaluatedCall,
//! input: PipelineData, //! input: PipelineData,
//! ) -> Result<PipelineData, LabeledError> { //! ) -> Result<PipelineData, LabeledError> {
//! let span = call.head; //! let span = call.head;
//! Ok(input.map(move |value| { //! Ok(input.map(
//! value.as_str() //! move |value| {
//! .map(|string| Value::string(string.to_lowercase(), span)) //! value
//! // Errors in a stream should be returned as values. //! .as_str()
//! .unwrap_or_else(|err| Value::error(err, span)) //! .map(|string| Value::string(string.to_lowercase(), span))
//! }, None)?) //! // Errors in a stream should be returned as values.
//! .unwrap_or_else(|err| Value::error(err, span))
//! },
//! None,
//! )?)
//! } //! }
//! } //! }
//! //!
@ -45,7 +71,14 @@
//! } //! }
//! } //! }
//! //!
//! fn test_lowercase() -> Result<(), LabeledError> { //! // #[test]
//! fn test_examples() -> Result<(), ShellError> {
//! PluginTest::new("lowercase", LowercasePlugin.into())?
//! .test_command_examples(&Lowercase)
//! }
//!
//! // #[test]
//! fn test_lowercase() -> Result<(), ShellError> {
//! let input = vec![Value::test_string("FooBar")].into_pipeline_data(None); //! let input = vec![Value::test_string("FooBar")].into_pipeline_data(None);
//! let output = PluginTest::new("lowercase", LowercasePlugin.into())? //! let output = PluginTest::new("lowercase", LowercasePlugin.into())?
//! .eval_with("lowercase", input)? //! .eval_with("lowercase", input)?
@ -60,6 +93,7 @@
//! Ok(()) //! Ok(())
//! } //! }
//! # //! #
//! # test_examples().unwrap();
//! # test_lowercase().unwrap(); //! # test_lowercase().unwrap();
//! ``` //! ```

View File

@ -7,7 +7,7 @@ use nu_plugin::{Plugin, PluginCommand, PluginCustomValue, PluginSource};
use nu_protocol::{ use nu_protocol::{
debugger::WithoutDebug, debugger::WithoutDebug,
engine::{EngineState, Stack, StateWorkingSet}, engine::{EngineState, Stack, StateWorkingSet},
report_error_new, LabeledError, PipelineData, PluginExample, ShellError, Span, Value, report_error_new, Example, LabeledError, PipelineData, ShellError, Span, Value,
}; };
use crate::{diff::diff_by_line, fake_register::fake_register}; use crate::{diff::diff_by_line, fake_register::fake_register};
@ -186,20 +186,20 @@ impl PluginTest {
/// ///
/// ```rust,no_run /// ```rust,no_run
/// # use nu_plugin_test_support::PluginTest; /// # use nu_plugin_test_support::PluginTest;
/// # use nu_protocol::{ShellError, PluginExample, Value}; /// # use nu_protocol::{ShellError, Example, Value};
/// # use nu_plugin::*; /// # use nu_plugin::*;
/// # fn test(MyPlugin: impl Plugin + Send + 'static) -> Result<(), ShellError> { /// # fn test(MyPlugin: impl Plugin + Send + 'static) -> Result<(), ShellError> {
/// PluginTest::new("my_plugin", MyPlugin.into())? /// PluginTest::new("my_plugin", MyPlugin.into())?
/// .test_examples(&[ /// .test_examples(&[
/// PluginExample { /// Example {
/// example: "my-command".into(), /// example: "my-command",
/// description: "Run my-command".into(), /// description: "Run my-command",
/// result: Some(Value::test_string("my-command output")), /// result: Some(Value::test_string("my-command output")),
/// }, /// },
/// ]) /// ])
/// # } /// # }
/// ``` /// ```
pub fn test_examples(&mut self, examples: &[PluginExample]) -> Result<(), ShellError> { pub fn test_examples(&mut self, examples: &[Example]) -> Result<(), ShellError> {
let mut failed = false; let mut failed = false;
for example in examples { for example in examples {
@ -210,7 +210,7 @@ impl PluginTest {
eprintln!("{} {}", bold.paint("Description:"), example.description); eprintln!("{} {}", bold.paint("Description:"), example.description);
}; };
if let Some(expectation) = &example.result { if let Some(expectation) = &example.result {
match self.eval(&example.example) { match self.eval(example.example) {
Ok(data) => { Ok(data) => {
let mut value = data.into_value(Span::test_data()); let mut value = data.into_value(Span::test_data());
@ -270,6 +270,6 @@ impl PluginTest {
&mut self, &mut self,
command: &impl PluginCommand, command: &impl PluginCommand,
) -> Result<(), ShellError> { ) -> Result<(), ShellError> {
self.test_examples(&command.signature().examples) self.test_examples(&command.examples())
} }
} }

View File

@ -3,8 +3,7 @@ use std::cmp::Ordering;
use nu_plugin::{EngineInterface, EvaluatedCall, Plugin, SimplePluginCommand}; use nu_plugin::{EngineInterface, EvaluatedCall, Plugin, SimplePluginCommand};
use nu_plugin_test_support::PluginTest; use nu_plugin_test_support::PluginTest;
use nu_protocol::{ use nu_protocol::{
CustomValue, LabeledError, PipelineData, PluginExample, PluginSignature, ShellError, Span, CustomValue, Example, LabeledError, PipelineData, ShellError, Signature, Span, Type, Value,
Type, Value,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -58,14 +57,24 @@ impl Plugin for CustomU32Plugin {
impl SimplePluginCommand for IntoU32 { impl SimplePluginCommand for IntoU32 {
type Plugin = CustomU32Plugin; type Plugin = CustomU32Plugin;
fn signature(&self) -> PluginSignature { fn name(&self) -> &str {
PluginSignature::build("into u32") "into u32"
.input_output_type(Type::Int, Type::Custom("CustomU32".into())) }
.plugin_examples(vec![PluginExample {
example: "340 | into u32".into(), fn usage(&self) -> &str {
description: "Make a u32".into(), "Convert a number to a 32-bit unsigned integer"
result: Some(CustomU32(340).into_value(Span::test_data())), }
}])
fn signature(&self) -> Signature {
Signature::build(self.name()).input_output_type(Type::Int, Type::Custom("CustomU32".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
example: "340 | into u32",
description: "Make a u32",
result: Some(CustomU32(340).into_value(Span::test_data())),
}]
} }
fn run( fn run(
@ -87,9 +96,16 @@ impl SimplePluginCommand for IntoU32 {
impl SimplePluginCommand for IntoIntFromU32 { impl SimplePluginCommand for IntoIntFromU32 {
type Plugin = CustomU32Plugin; type Plugin = CustomU32Plugin;
fn signature(&self) -> PluginSignature { fn name(&self) -> &str {
PluginSignature::build("into int from u32") "into int from u32"
.input_output_type(Type::Custom("CustomU32".into()), Type::Int) }
fn usage(&self) -> &str {
"Turn a u32 back into a number"
}
fn signature(&self) -> Signature {
Signature::build(self.name()).input_output_type(Type::Custom("CustomU32".into()), Type::Int)
} }
fn run( fn run(

View File

@ -2,7 +2,7 @@
use nu_plugin::*; use nu_plugin::*;
use nu_plugin_test_support::PluginTest; use nu_plugin_test_support::PluginTest;
use nu_protocol::{LabeledError, PluginExample, PluginSignature, ShellError, Type, Value}; use nu_protocol::{Example, LabeledError, ShellError, Signature, Type, Value};
struct HelloPlugin; struct HelloPlugin;
struct Hello; struct Hello;
@ -16,14 +16,24 @@ impl Plugin for HelloPlugin {
impl SimplePluginCommand for Hello { impl SimplePluginCommand for Hello {
type Plugin = HelloPlugin; type Plugin = HelloPlugin;
fn signature(&self) -> PluginSignature { fn name(&self) -> &str {
PluginSignature::build("hello") "hello"
.input_output_type(Type::Nothing, Type::String) }
.plugin_examples(vec![PluginExample {
example: "hello".into(), fn usage(&self) -> &str {
description: "Print a friendly greeting".into(), "Print a friendly greeting"
result: Some(Value::test_string("Hello, World!")), }
}])
fn signature(&self) -> Signature {
Signature::build(PluginCommand::name(self)).input_output_type(Type::Nothing, Type::String)
}
fn examples(&self) -> Vec<Example> {
vec![Example {
example: "hello",
description: "Print a friendly greeting",
result: Some(Value::test_string("Hello, World!")),
}]
} }
fn run( fn run(
@ -44,9 +54,9 @@ fn test_specified_examples() -> Result<(), ShellError> {
#[test] #[test]
fn test_an_error_causing_example() -> Result<(), ShellError> { fn test_an_error_causing_example() -> Result<(), ShellError> {
let result = PluginTest::new("hello", HelloPlugin.into())?.test_examples(&[PluginExample { let result = PluginTest::new("hello", HelloPlugin.into())?.test_examples(&[Example {
example: "hello --unknown-flag".into(), example: "hello --unknown-flag",
description: "Run hello with an unknown flag".into(), description: "Run hello with an unknown flag",
result: Some(Value::test_string("Hello, World!")), result: Some(Value::test_string("Hello, World!")),
}]); }]);
assert!(result.is_err()); assert!(result.is_err());
@ -55,9 +65,9 @@ fn test_an_error_causing_example() -> Result<(), ShellError> {
#[test] #[test]
fn test_an_example_with_the_wrong_result() -> Result<(), ShellError> { fn test_an_example_with_the_wrong_result() -> Result<(), ShellError> {
let result = PluginTest::new("hello", HelloPlugin.into())?.test_examples(&[PluginExample { let result = PluginTest::new("hello", HelloPlugin.into())?.test_examples(&[Example {
example: "hello".into(), example: "hello",
description: "Run hello but the example result is wrong".into(), description: "Run hello but the example result is wrong",
result: Some(Value::test_string("Goodbye, World!")), result: Some(Value::test_string("Goodbye, World!")),
}]); }]);
assert!(result.is_err()); assert!(result.is_err());

View File

@ -1,8 +1,8 @@
use nu_plugin::*; use nu_plugin::*;
use nu_plugin_test_support::PluginTest; use nu_plugin_test_support::PluginTest;
use nu_protocol::{ use nu_protocol::{
IntoInterruptiblePipelineData, LabeledError, PipelineData, PluginExample, PluginSignature, Example, IntoInterruptiblePipelineData, LabeledError, PipelineData, ShellError, Signature,
ShellError, Span, Type, Value, Span, Type, Value,
}; };
struct LowercasePlugin; struct LowercasePlugin;
@ -11,21 +11,30 @@ struct Lowercase;
impl PluginCommand for Lowercase { impl PluginCommand for Lowercase {
type Plugin = LowercasePlugin; type Plugin = LowercasePlugin;
fn signature(&self) -> PluginSignature { fn name(&self) -> &str {
PluginSignature::build("lowercase") "lowercase"
.usage("Convert each string in a stream to lowercase") }
.input_output_type(
Type::List(Type::String.into()), fn usage(&self) -> &str {
Type::List(Type::String.into()), "Convert each string in a stream to lowercase"
) }
.plugin_examples(vec![PluginExample {
example: r#"[Hello wORLD] | lowercase"#.into(), fn signature(&self) -> Signature {
description: "Lowercase a list of strings".into(), Signature::build(self.name()).input_output_type(
result: Some(Value::test_list(vec![ Type::List(Type::String.into()),
Value::test_string("hello"), Type::List(Type::String.into()),
Value::test_string("world"), )
])), }
}])
fn examples(&self) -> Vec<Example> {
vec![Example {
example: r#"[Hello wORLD] | lowercase"#,
description: "Lowercase a list of strings",
result: Some(Value::test_list(vec![
Value::test_string("hello"),
Value::test_string("world"),
])),
}]
} }
fn run( fn run(

View File

@ -17,8 +17,8 @@
//! //!
//! ```rust,no_run //! ```rust,no_run
//! use nu_plugin::{EvaluatedCall, MsgPackSerializer, serve_plugin}; //! use nu_plugin::{EvaluatedCall, MsgPackSerializer, serve_plugin};
//! use nu_plugin::{Plugin, PluginCommand, SimplePluginCommand, EngineInterface}; //! use nu_plugin::{EngineInterface, Plugin, PluginCommand, SimplePluginCommand};
//! use nu_protocol::{PluginSignature, LabeledError, Value}; //! use nu_protocol::{LabeledError, Signature, Value};
//! //!
//! struct MyPlugin; //! struct MyPlugin;
//! struct MyCommand; //! struct MyCommand;
@ -32,7 +32,15 @@
//! impl SimplePluginCommand for MyCommand { //! impl SimplePluginCommand for MyCommand {
//! type Plugin = MyPlugin; //! type Plugin = MyPlugin;
//! //!
//! fn signature(&self) -> PluginSignature { //! fn name(&self) -> &str {
//! "my-command"
//! }
//!
//! fn usage(&self) -> &str {
//! todo!();
//! }
//!
//! fn signature(&self) -> Signature {
//! todo!(); //! todo!();
//! } //! }
//! //!
@ -71,9 +79,10 @@ pub use serializers::{json::JsonSerializer, msgpack::MsgPackSerializer};
// Used by other nu crates. // Used by other nu crates.
#[doc(hidden)] #[doc(hidden)]
pub use plugin::{ pub use plugin::{
get_signature, serve_plugin_io, EngineInterfaceManager, GetPlugin, Interface, InterfaceManager, create_plugin_signature, get_signature, serve_plugin_io, EngineInterfaceManager, GetPlugin,
PersistentPlugin, PluginDeclaration, PluginExecutionCommandContext, PluginExecutionContext, Interface, InterfaceManager, PersistentPlugin, PluginDeclaration,
PluginInterface, PluginInterfaceManager, PluginSource, ServePluginError, PluginExecutionCommandContext, PluginExecutionContext, PluginInterface, PluginInterfaceManager,
PluginSource, ServePluginError,
}; };
#[doc(hidden)] #[doc(hidden)]
pub use protocol::{PluginCustomValue, PluginInput, PluginOutput}; pub use protocol::{PluginCustomValue, PluginInput, PluginOutput};

View File

@ -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}; use crate::{EngineInterface, EvaluatedCall, Plugin};
@ -19,16 +21,23 @@ use crate::{EngineInterface, EvaluatedCall, Plugin};
/// Basic usage: /// Basic usage:
/// ``` /// ```
/// # use nu_plugin::*; /// # use nu_plugin::*;
/// # use nu_protocol::{PluginSignature, PipelineData, Type, Value, LabeledError}; /// # use nu_protocol::{Signature, PipelineData, Type, Value, LabeledError};
/// struct LowercasePlugin; /// struct LowercasePlugin;
/// struct Lowercase; /// struct Lowercase;
/// ///
/// impl PluginCommand for Lowercase { /// impl PluginCommand for Lowercase {
/// type Plugin = LowercasePlugin; /// type Plugin = LowercasePlugin;
/// ///
/// fn signature(&self) -> PluginSignature { /// fn name(&self) -> &str {
/// PluginSignature::build("lowercase") /// "lowercase"
/// .usage("Convert each string in a stream to 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())) /// .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 { 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 /// Since [`.run()`] takes a reference to the plugin, it is necessary to define the type of
/// plugin that the command expects here. /// plugin that the command expects here.
type Plugin: Plugin; 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`. /// In case this contains spaces, it will be treated as a subcommand.
fn signature(&self) -> PluginSignature; 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 /// 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 /// 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: /// Basic usage:
/// ``` /// ```
/// # use nu_plugin::*; /// # use nu_plugin::*;
/// # use nu_protocol::{PluginSignature, Type, Value, LabeledError}; /// # use nu_protocol::{LabeledError, Signature, Type, Value};
/// struct HelloPlugin; /// struct HelloPlugin;
/// struct Hello; /// struct Hello;
/// ///
/// impl SimplePluginCommand for Hello { /// impl SimplePluginCommand for Hello {
/// type Plugin = HelloPlugin; /// type Plugin = HelloPlugin;
/// ///
/// fn signature(&self) -> PluginSignature { /// fn name(&self) -> &str {
/// PluginSignature::build("hello") /// "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) /// .input_output_type(Type::Nothing, Type::String)
/// } /// }
/// ///
@ -143,18 +204,62 @@ pub trait PluginCommand: Sync {
/// # } /// # }
/// ``` /// ```
pub trait SimplePluginCommand: 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 /// Since [`.run()`] takes a reference to the plugin, it is necessary to define the type of
/// plugin that the command expects here. /// plugin that the command expects here.
type Plugin: Plugin; 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`. /// In case this contains spaces, it will be treated as a subcommand.
fn signature(&self) -> PluginSignature; 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 /// 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 /// 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; type Plugin = <Self as SimplePluginCommand>::Plugin;
fn signature(&self) -> PluginSignature { fn examples(&self) -> Vec<Example> {
<Self as SimplePluginCommand>::signature(self) <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( fn run(
@ -204,4 +317,45 @@ where
<Self as SimplePluginCommand>::run(self, plugin, engine, call, &input_value) <Self as SimplePluginCommand>::run(self, plugin, engine, call, &input_value)
.map(|value| PipelineData::Value(value, None)) .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(),
)
} }

View File

@ -11,7 +11,7 @@ use crate::{
}; };
use nu_protocol::{ use nu_protocol::{
engine::Closure, Config, CustomValue, IntoInterruptiblePipelineData, LabeledError, engine::Closure, Config, CustomValue, IntoInterruptiblePipelineData, LabeledError,
PipelineData, PluginExample, PluginSignature, ShellError, Span, Spanned, Value, PipelineData, PluginExample, PluginSignature, ShellError, Signature, Span, Spanned, Value,
}; };
use std::{ use std::{
collections::HashMap, collections::HashMap,
@ -786,15 +786,16 @@ fn interface_write_signature() -> Result<(), ShellError> {
fn interface_write_signature_custom_value() -> Result<(), ShellError> { fn interface_write_signature_custom_value() -> Result<(), ShellError> {
let test = TestCase::new(); let test = TestCase::new();
let interface = test.engine().interface_for_context(38); let interface = test.engine().interface_for_context(38);
let signatures = vec![PluginSignature::build("test command").plugin_examples(vec![ let signatures = vec![PluginSignature::new(
PluginExample { Signature::build("test command"),
vec![PluginExample {
example: "test command".into(), example: "test command".into(),
description: "a test".into(), description: "a test".into(),
result: Some(Value::test_custom_value(Box::new( result: Some(Value::test_custom_value(Box::new(
expected_test_custom_value(), expected_test_custom_value(),
))), ))),
}, }],
])]; )];
interface.write_signature(signatures.clone())?; interface.write_signature(signatures.clone())?;
let written = test.next_written().expect("nothing written"); let written = test.next_written().expect("nothing written");

View File

@ -3,15 +3,7 @@ use crate::{
protocol::{CallInfo, CustomValueOp, PluginCustomValue, PluginInput, PluginOutput}, protocol::{CallInfo, CustomValueOp, PluginCustomValue, PluginInput, PluginOutput},
EncodingType, 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::{ use std::{
cmp::Ordering, cmp::Ordering,
collections::HashMap, collections::HashMap,
@ -19,14 +11,28 @@ use std::{
ffi::OsStr, ffi::OsStr,
fmt::Write, fmt::Write,
io::{BufReader, Read, Write as WriteTrait}, io::{BufReader, Read, Write as WriteTrait},
ops::Deref,
path::Path, path::Path,
process::{Child, ChildStdout, Command as CommandSys, Stdio}, process::{Child, ChildStdout, Command as CommandSys, Stdio},
sync::mpsc::TrySendError, sync::{
sync::{mpsc, Arc, Mutex}, mpsc::{self, TrySendError},
Arc, Mutex,
},
thread, thread,
}; };
use nu_engine::documentation::get_flags_section;
use nu_protocol::{
ast::Operator, CustomValue, IntoSpanned, LabeledError, PipelineData, PluginSignature,
ShellError, Spanned, Value,
};
use thiserror::Error; use thiserror::Error;
#[cfg(unix)]
use std::os::unix::process::CommandExt;
#[cfg(windows)]
use std::os::windows::process::CommandExt;
use self::gc::PluginGc; use self::gc::PluginGc;
pub use self::interface::{PluginRead, PluginWrite}; pub use self::interface::{PluginRead, PluginWrite};
@ -38,7 +44,7 @@ mod interface;
mod persistent; mod persistent;
mod source; mod source;
pub use command::{PluginCommand, SimplePluginCommand}; pub use command::{create_plugin_signature, PluginCommand, SimplePluginCommand};
pub use declaration::PluginDeclaration; pub use declaration::PluginDeclaration;
pub use interface::{ pub use interface::{
EngineInterface, EngineInterfaceManager, Interface, InterfaceManager, PluginInterface, EngineInterface, EngineInterfaceManager, Interface, InterfaceManager, PluginInterface,
@ -229,7 +235,7 @@ where
/// Basic usage: /// Basic usage:
/// ``` /// ```
/// # use nu_plugin::*; /// # use nu_plugin::*;
/// # use nu_protocol::{PluginSignature, LabeledError, Type, Value}; /// # use nu_protocol::{LabeledError, Signature, Type, Value};
/// struct HelloPlugin; /// struct HelloPlugin;
/// struct Hello; /// struct Hello;
/// ///
@ -242,8 +248,16 @@ where
/// impl SimplePluginCommand for Hello { /// impl SimplePluginCommand for Hello {
/// type Plugin = HelloPlugin; /// type Plugin = HelloPlugin;
/// ///
/// fn signature(&self) -> PluginSignature { /// fn name(&self) -> &str {
/// PluginSignature::build("hello") /// "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) /// .input_output_type(Type::Nothing, Type::String)
/// } /// }
/// ///
@ -556,11 +570,11 @@ where
let mut commands: HashMap<String, _> = HashMap::new(); let mut commands: HashMap<String, _> = HashMap::new();
for command in plugin.commands() { 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!( eprintln!(
"Plugin `{plugin_name}` warning: command `{}` shadowed by another command with the \ "Plugin `{plugin_name}` warning: command `{}` shadowed by another command with the \
same name. Check your command signatures", same name. Check your commands' `name()` methods",
previous.signature().sig.name previous.name()
); );
} }
} }
@ -636,7 +650,7 @@ where
ReceivedPluginCall::Signature { engine } => { ReceivedPluginCall::Signature { engine } => {
let sigs = commands let sigs = commands
.values() .values()
.map(|command| command.signature()) .map(|command| create_plugin_signature(command.deref()))
.collect(); .collect();
engine.write_signature(sigs).try_to_report(&engine)?; 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| { plugin.commands().into_iter().for_each(|command| {
let signature = command.signature(); let signature = command.signature();
let res = write!(help, "\nCommand: {}", signature.sig.name) let res = write!(help, "\nCommand: {}", command.name())
.and_then(|_| writeln!(help, "\nUsage:\n > {}", signature.sig.usage)) .and_then(|_| writeln!(help, "\nUsage:\n > {}", command.usage()))
.and_then(|_| { .and_then(|_| {
if !signature.sig.extra_usage.is_empty() { if !command.extra_usage().is_empty() {
writeln!(help, "\nExtra usage:\n > {}", signature.sig.extra_usage) writeln!(help, "\nExtra usage:\n > {}", command.extra_usage())
} else { } else {
Ok(()) Ok(())
} }
}) })
.and_then(|_| { .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}") write!(help, "{flags}")
}) })
.and_then(|_| writeln!(help, "\nParameters:")) .and_then(|_| writeln!(help, "\nParameters:"))
.and_then(|_| { .and_then(|_| {
signature signature
.sig
.required_positional .required_positional
.iter() .iter()
.try_for_each(|positional| { .try_for_each(|positional| {
@ -781,7 +794,6 @@ fn print_help(plugin: &impl Plugin, encoder: impl PluginEncoder) {
}) })
.and_then(|_| { .and_then(|_| {
signature signature
.sig
.optional_positional .optional_positional
.iter() .iter()
.try_for_each(|positional| { .try_for_each(|positional| {
@ -793,7 +805,7 @@ fn print_help(plugin: &impl Plugin, encoder: impl PluginEncoder) {
}) })
}) })
.and_then(|_| { .and_then(|_| {
if let Some(rest_positional) = &signature.sig.rest_positional { if let Some(rest_positional) = &signature.rest_positional {
writeln!( writeln!(
help, help,
" ...{} <{}>: {}", " ...{} <{}>: {}",

View File

@ -5,7 +5,9 @@ macro_rules! generate_tests {
PluginCallResponse, PluginCustomValue, PluginInput, PluginOption, PluginOutput, PluginCallResponse, PluginCustomValue, PluginInput, PluginOption, PluginOutput,
StreamData, StreamMessage, StreamData, StreamMessage,
}; };
use nu_protocol::{LabeledError, PluginSignature, Span, Spanned, SyntaxShape, Value}; use nu_protocol::{
LabeledError, PluginSignature, Signature, Span, Spanned, SyntaxShape, Value,
};
#[test] #[test]
fn decode_eof() { fn decode_eof() {
@ -211,17 +213,20 @@ macro_rules! generate_tests {
#[test] #[test]
fn response_round_trip_signature() { fn response_round_trip_signature() {
let signature = PluginSignature::build("nu-plugin") let signature = PluginSignature::new(
.required("first", SyntaxShape::String, "first required") Signature::build("nu-plugin")
.required("second", SyntaxShape::Int, "second required") .required("first", SyntaxShape::String, "first required")
.required_named("first-named", SyntaxShape::String, "first named", Some('f')) .required("second", SyntaxShape::Int, "second required")
.required_named( .required_named("first-named", SyntaxShape::String, "first named", Some('f'))
"second-named", .required_named(
SyntaxShape::String, "second-named",
"second named", SyntaxShape::String,
Some('s'), "second named",
) Some('s'),
.rest("remaining", SyntaxShape::Int, "remaining"); )
.rest("remaining", SyntaxShape::Int, "remaining"),
vec![],
);
let response = PluginCallResponse::Signature(vec![signature.clone()]); let response = PluginCallResponse::Signature(vec![signature.clone()]);
let output = PluginOutput::CallResponse(3, response); let output = PluginOutput::CallResponse(3, response);

View File

@ -530,11 +530,7 @@ impl EngineState {
let examples = decl let examples = decl
.examples() .examples()
.into_iter() .into_iter()
.map(|eg| PluginExample { .map(PluginExample::from)
example: eg.example.into(),
description: eg.description.into(),
result: eg.result,
})
.collect(); .collect();
let sig_with_examples = PluginSignature::new(sig, examples); let sig_with_examples = PluginSignature::new(sig, examples);
serde_json::to_string_pretty(&sig_with_examples) serde_json::to_string_pretty(&sig_with_examples)

View File

@ -19,3 +19,13 @@ pub struct PluginExample {
pub description: String, pub description: String,
pub result: Option<Value>, pub result: Option<Value>,
} }
impl From<Example<'_>> for PluginExample {
fn from(value: Example) -> Self {
PluginExample {
example: value.example.into(),
description: value.description.into(),
result: value.result,
}
}
}

View File

@ -1,7 +1,4 @@
use crate::{ use crate::{PluginExample, Signature};
engine::Command, BlockId, Category, Flag, PluginExample, PositionalArg, Signature, SyntaxShape,
Type,
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// A simple wrapper for Signature that includes examples. /// A simple wrapper for Signature that includes examples.
@ -16,210 +13,9 @@ impl PluginSignature {
Self { sig, examples } Self { sig, examples }
} }
/// Add a default help option to a signature
pub fn add_help(mut self) -> PluginSignature {
self.sig = self.sig.add_help();
self
}
/// Build an internal signature with default help option /// Build an internal signature with default help option
pub fn build(name: impl Into<String>) -> PluginSignature { pub fn build(name: impl Into<String>) -> PluginSignature {
let sig = Signature::new(name.into()).add_help(); let sig = Signature::new(name.into()).add_help();
Self::new(sig, vec![]) Self::new(sig, vec![])
} }
/// Add a description to the signature
pub fn usage(mut self, msg: impl Into<String>) -> PluginSignature {
self.sig = self.sig.usage(msg);
self
}
/// Add an extra description to the signature
pub fn extra_usage(mut self, msg: impl Into<String>) -> PluginSignature {
self.sig = self.sig.extra_usage(msg);
self
}
/// Add search terms to the signature
pub fn search_terms(mut self, terms: Vec<String>) -> PluginSignature {
self.sig = self.sig.search_terms(terms);
self
}
/// Update signature's fields from a Command trait implementation
pub fn update_from_command(mut self, command: &dyn Command) -> PluginSignature {
self.sig = self.sig.update_from_command(command);
self
}
/// Allow unknown signature parameters
pub fn allows_unknown_args(mut self) -> PluginSignature {
self.sig = self.sig.allows_unknown_args();
self
}
/// Add a required positional argument to the signature
pub fn required(
mut self,
name: impl Into<String>,
shape: impl Into<SyntaxShape>,
desc: impl Into<String>,
) -> PluginSignature {
self.sig = self.sig.required(name, shape, desc);
self
}
/// Add an optional positional argument to the signature
pub fn optional(
mut self,
name: impl Into<String>,
shape: impl Into<SyntaxShape>,
desc: impl Into<String>,
) -> PluginSignature {
self.sig = self.sig.optional(name, shape, desc);
self
}
pub fn rest(
mut self,
name: &str,
shape: impl Into<SyntaxShape>,
desc: impl Into<String>,
) -> PluginSignature {
self.sig = self.sig.rest(name, shape, desc);
self
}
/// Is this command capable of operating on its input via cell paths?
pub fn operates_on_cell_paths(&self) -> bool {
self.sig.operates_on_cell_paths()
}
/// Add an optional named flag argument to the signature
pub fn named(
mut self,
name: impl Into<String>,
shape: impl Into<SyntaxShape>,
desc: impl Into<String>,
short: Option<char>,
) -> PluginSignature {
self.sig = self.sig.named(name, shape, desc, short);
self
}
/// Add a required named flag argument to the signature
pub fn required_named(
mut self,
name: impl Into<String>,
shape: impl Into<SyntaxShape>,
desc: impl Into<String>,
short: Option<char>,
) -> PluginSignature {
self.sig = self.sig.required_named(name, shape, desc, short);
self
}
/// Add a switch to the signature
pub fn switch(
mut self,
name: impl Into<String>,
desc: impl Into<String>,
short: Option<char>,
) -> PluginSignature {
self.sig = self.sig.switch(name, desc, short);
self
}
/// Changes the input type of the command signature
pub fn input_output_type(mut self, input_type: Type, output_type: Type) -> PluginSignature {
self.sig.input_output_types.push((input_type, output_type));
self
}
/// Set the input-output type signature variants of the command
pub fn input_output_types(mut self, input_output_types: Vec<(Type, Type)>) -> PluginSignature {
self.sig = self.sig.input_output_types(input_output_types);
self
}
/// Changes the signature category
pub fn category(mut self, category: Category) -> PluginSignature {
self.sig = self.sig.category(category);
self
}
/// Sets that signature will create a scope as it parses
pub fn creates_scope(mut self) -> PluginSignature {
self.sig = self.sig.creates_scope();
self
}
// Is it allowed for the type signature to feature a variant that has no corresponding example?
pub fn allow_variants_without_examples(mut self, allow: bool) -> PluginSignature {
self.sig = self.sig.allow_variants_without_examples(allow);
self
}
pub fn call_signature(&self) -> String {
self.sig.call_signature()
}
/// Get list of the short-hand flags
pub fn get_shorts(&self) -> Vec<char> {
self.sig.get_shorts()
}
/// Get list of the long-hand flags
pub fn get_names(&self) -> Vec<&str> {
self.sig.get_names()
}
pub fn get_positional(&self, position: usize) -> Option<PositionalArg> {
self.sig.get_positional(position)
}
pub fn num_positionals(&self) -> usize {
self.sig.num_positionals()
}
pub fn num_positionals_after(&self, idx: usize) -> usize {
self.sig.num_positionals_after(idx)
}
/// Find the matching long flag
pub fn get_long_flag(&self, name: &str) -> Option<Flag> {
self.sig.get_long_flag(name)
}
/// Find the matching long flag
pub fn get_short_flag(&self, short: char) -> Option<Flag> {
self.sig.get_short_flag(short)
}
/// Set the filter flag for the signature
pub fn filter(mut self) -> PluginSignature {
self.sig = self.sig.filter();
self
}
/// Create a placeholder implementation of Command as a way to predeclare a definition's
/// signature so other definitions can see it. This placeholder is later replaced with the
/// full definition in a second pass of the parser.
pub fn predeclare(self) -> Box<dyn Command> {
self.sig.predeclare()
}
/// Combines a signature and a block into a runnable block
pub fn into_block_command(self, block_id: BlockId) -> Box<dyn Command> {
self.sig.into_block_command(block_id)
}
pub fn formatted_flags(self) -> String {
self.sig.formatted_flags()
}
pub fn plugin_examples(mut self, examples: Vec<PluginExample>) -> PluginSignature {
self.examples = examples;
self
}
} }

View File

@ -1,8 +1,7 @@
use crate::CustomValuePlugin; use crate::CustomValuePlugin;
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand}; use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{ use nu_protocol::{
record, Category, CustomValue, LabeledError, PluginSignature, ShellError, Span, SyntaxShape, record, Category, CustomValue, LabeledError, ShellError, Signature, Span, SyntaxShape, Value,
Value,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -59,9 +58,16 @@ pub struct DropCheck;
impl SimplePluginCommand for DropCheck { impl SimplePluginCommand for DropCheck {
type Plugin = CustomValuePlugin; type Plugin = CustomValuePlugin;
fn signature(&self) -> nu_protocol::PluginSignature { fn name(&self) -> &str {
PluginSignature::build("custom-value drop-check") "custom-value drop-check"
.usage("Generates a custom value that prints a message when dropped") }
fn usage(&self) -> &str {
"Generates a custom value that prints a message when dropped"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required("msg", SyntaxShape::String, "the message to print on drop") .required("msg", SyntaxShape::String, "the message to print on drop")
.category(Category::Experimental) .category(Category::Experimental)
} }

View File

@ -1,21 +1,30 @@
use crate::{cool_custom_value::CoolCustomValue, CustomValuePlugin}; use crate::{cool_custom_value::CoolCustomValue, CustomValuePlugin};
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand}; use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{Category, LabeledError, PluginExample, PluginSignature, Span, Value}; use nu_protocol::{Category, Example, LabeledError, Signature, Span, Value};
pub struct Generate; pub struct Generate;
impl SimplePluginCommand for Generate { impl SimplePluginCommand for Generate {
type Plugin = CustomValuePlugin; type Plugin = CustomValuePlugin;
fn signature(&self) -> PluginSignature { fn name(&self) -> &str {
PluginSignature::build("custom-value generate") "custom-value generate"
.usage("PluginSignature for a plugin that generates a custom value") }
.category(Category::Experimental)
.plugin_examples(vec![PluginExample { fn usage(&self) -> &str {
example: "custom-value generate".into(), "PluginSignature for a plugin that generates a custom value"
description: "Generate a new CoolCustomValue".into(), }
result: Some(CoolCustomValue::new("abc").into_value(Span::test_data())),
}]) fn signature(&self) -> Signature {
Signature::build(self.name()).category(Category::Experimental)
}
fn examples(&self) -> Vec<Example> {
vec![Example {
example: "custom-value generate",
description: "Generate a new CoolCustomValue",
result: Some(CoolCustomValue::new("abc").into_value(Span::test_data())),
}]
} }
fn run( fn run(

View File

@ -1,35 +1,43 @@
use crate::{second_custom_value::SecondCustomValue, CustomValuePlugin}; use crate::{second_custom_value::SecondCustomValue, CustomValuePlugin};
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand}; use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{ use nu_protocol::{Category, Example, LabeledError, Signature, Span, SyntaxShape, Value};
Category, LabeledError, PluginExample, PluginSignature, Span, SyntaxShape, Value,
};
pub struct Generate2; pub struct Generate2;
impl SimplePluginCommand for Generate2 { impl SimplePluginCommand for Generate2 {
type Plugin = CustomValuePlugin; type Plugin = CustomValuePlugin;
fn signature(&self) -> PluginSignature { fn name(&self) -> &str {
PluginSignature::build("custom-value generate2") "custom-value generate2"
.usage("PluginSignature for a plugin that generates a different custom value") }
fn usage(&self) -> &str {
"PluginSignature for a plugin that generates a different custom value"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.optional( .optional(
"closure", "closure",
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])), SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
"An optional closure to pass the custom value to", "An optional closure to pass the custom value to",
) )
.category(Category::Experimental) .category(Category::Experimental)
.plugin_examples(vec![ }
PluginExample {
example: "custom-value generate2".into(), fn examples(&self) -> Vec<Example> {
description: "Generate a new SecondCustomValue".into(), vec![
result: Some(SecondCustomValue::new("xyz").into_value(Span::test_data())), Example {
}, example: "custom-value generate2",
PluginExample { description: "Generate a new SecondCustomValue",
example: "custom-value generate2 { print }".into(), result: Some(SecondCustomValue::new("xyz").into_value(Span::test_data())),
description: "Generate a new SecondCustomValue and pass it to a closure".into(), },
result: None, Example {
}, example: "custom-value generate2 { print }",
]) description: "Generate a new SecondCustomValue and pass it to a closure",
result: None,
},
]
} }
fn run( fn run(

View File

@ -2,31 +2,38 @@ use crate::{
cool_custom_value::CoolCustomValue, second_custom_value::SecondCustomValue, CustomValuePlugin, cool_custom_value::CoolCustomValue, second_custom_value::SecondCustomValue, CustomValuePlugin,
}; };
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand}; use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{ use nu_protocol::{Category, Example, LabeledError, ShellError, Signature, Span, Value};
Category, LabeledError, PluginExample, PluginSignature, ShellError, Span, Value,
};
pub struct Update; pub struct Update;
impl SimplePluginCommand for Update { impl SimplePluginCommand for Update {
type Plugin = CustomValuePlugin; type Plugin = CustomValuePlugin;
fn signature(&self) -> PluginSignature { fn name(&self) -> &str {
PluginSignature::build("custom-value update") "custom-value update"
.usage("PluginSignature for a plugin that updates a custom value") }
.category(Category::Experimental)
.plugin_examples(vec![ fn usage(&self) -> &str {
PluginExample { "PluginSignature for a plugin that updates a custom value"
example: "custom-value generate | custom-value update".into(), }
description: "Update a CoolCustomValue".into(),
result: Some(CoolCustomValue::new("abcxyz").into_value(Span::test_data())), fn signature(&self) -> Signature {
}, Signature::build(self.name()).category(Category::Experimental)
PluginExample { }
example: "custom-value generate2 | custom-value update".into(),
description: "Update a SecondCustomValue".into(), fn examples(&self) -> Vec<Example> {
result: Some(SecondCustomValue::new("xyzabc").into_value(Span::test_data())), vec![
}, Example {
]) example: "custom-value generate | custom-value update",
description: "Update a CoolCustomValue",
result: Some(CoolCustomValue::new("abcxyz").into_value(Span::test_data())),
},
Example {
example: "custom-value generate2 | custom-value update",
description: "Update a SecondCustomValue",
result: Some(SecondCustomValue::new("xyzabc").into_value(Span::test_data())),
},
]
} }
fn run( fn run(

View File

@ -1,15 +1,22 @@
use crate::{update::Update, CustomValuePlugin}; use crate::{update::Update, CustomValuePlugin};
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand}; use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{Category, LabeledError, PluginSignature, SyntaxShape, Value}; use nu_protocol::{Category, LabeledError, Signature, SyntaxShape, Value};
pub struct UpdateArg; pub struct UpdateArg;
impl SimplePluginCommand for UpdateArg { impl SimplePluginCommand for UpdateArg {
type Plugin = CustomValuePlugin; type Plugin = CustomValuePlugin;
fn signature(&self) -> PluginSignature { fn name(&self) -> &str {
PluginSignature::build("custom-value update-arg") "custom-value update-arg"
.usage("PluginSignature for a plugin that updates a custom value as an argument") }
fn usage(&self) -> &str {
"Updates a custom value as an argument"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required( .required(
"custom_value", "custom_value",
SyntaxShape::Any, SyntaxShape::Any,

View File

@ -1,35 +1,48 @@
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand}; use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand};
use nu_protocol::{ use nu_protocol::{
Category, LabeledError, PipelineData, PluginExample, PluginSignature, RawStream, Type, Value, Category, Example, LabeledError, PipelineData, RawStream, Signature, Type, Value,
}; };
use crate::Example; use crate::ExamplePlugin;
/// `<list<string>> | example collect-external` /// `<list<string>> | example collect-external`
pub struct CollectExternal; pub struct CollectExternal;
impl PluginCommand for CollectExternal { impl PluginCommand for CollectExternal {
type Plugin = Example; type Plugin = ExamplePlugin;
fn signature(&self) -> PluginSignature { fn name(&self) -> &str {
PluginSignature::build("example collect-external") "example collect-external"
.usage("Example transformer to raw external stream") }
.search_terms(vec!["example".into()])
fn usage(&self) -> &str {
"Example transformer to raw external stream"
}
fn search_terms(&self) -> Vec<&str> {
vec!["example"]
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.input_output_types(vec![ .input_output_types(vec![
(Type::List(Type::String.into()), Type::String), (Type::List(Type::String.into()), Type::String),
(Type::List(Type::Binary.into()), Type::Binary), (Type::List(Type::Binary.into()), Type::Binary),
]) ])
.plugin_examples(vec![PluginExample {
example: "[a b] | example collect-external".into(),
description: "collect strings into one stream".into(),
result: Some(Value::test_string("ab")),
}])
.category(Category::Experimental) .category(Category::Experimental)
} }
fn examples(&self) -> Vec<Example> {
vec![Example {
example: "[a b] | example collect-external",
description: "collect strings into one stream",
result: Some(Value::test_string("ab")),
}]
}
fn run( fn run(
&self, &self,
_plugin: &Example, _plugin: &ExamplePlugin,
_engine: &EngineInterface, _engine: &EngineInterface,
call: &EvaluatedCall, call: &EvaluatedCall,
input: PipelineData, input: PipelineData,
@ -55,5 +68,5 @@ impl PluginCommand for CollectExternal {
#[test] #[test]
fn test_examples() -> Result<(), nu_protocol::ShellError> { fn test_examples() -> Result<(), nu_protocol::ShellError> {
use nu_plugin_test_support::PluginTest; use nu_plugin_test_support::PluginTest;
PluginTest::new("example", Example.into())?.test_command_examples(&CollectExternal) PluginTest::new("example", ExamplePlugin.into())?.test_command_examples(&CollectExternal)
} }

View File

@ -1,25 +1,38 @@
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand}; use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{Category, LabeledError, PluginSignature, Type, Value}; use nu_protocol::{Category, LabeledError, Signature, Type, Value};
use crate::Example; use crate::ExamplePlugin;
pub struct Config; pub struct Config;
impl SimplePluginCommand for Config { impl SimplePluginCommand for Config {
type Plugin = Example; type Plugin = ExamplePlugin;
fn signature(&self) -> PluginSignature { fn name(&self) -> &str {
PluginSignature::build("example config") "example config"
.usage("Show plugin configuration") }
.extra_usage("The configuration is set under $env.config.plugins.example")
fn usage(&self) -> &str {
"Show plugin configuration"
}
fn extra_usage(&self) -> &str {
"The configuration is set under $env.config.plugins.example"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.category(Category::Experimental) .category(Category::Experimental)
.search_terms(vec!["example".into(), "configuration".into()])
.input_output_type(Type::Nothing, Type::Table(vec![])) .input_output_type(Type::Nothing, Type::Table(vec![]))
} }
fn search_terms(&self) -> Vec<&str> {
vec!["example", "configuration"]
}
fn run( fn run(
&self, &self,
_plugin: &Example, _plugin: &ExamplePlugin,
engine: &EngineInterface, engine: &EngineInterface,
call: &EvaluatedCall, call: &EvaluatedCall,
_input: &Value, _input: &Value,

View File

@ -1,18 +1,23 @@
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand}; use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{Category, LabeledError, PluginSignature, Value}; use nu_protocol::{Category, LabeledError, Signature, Value};
use crate::Example; use crate::ExamplePlugin;
pub struct DisableGc; pub struct DisableGc;
impl SimplePluginCommand for DisableGc { impl SimplePluginCommand for DisableGc {
type Plugin = Example; type Plugin = ExamplePlugin;
fn signature(&self) -> PluginSignature { fn name(&self) -> &str {
PluginSignature::build("example disable-gc") "example disable-gc"
.usage("Disable the plugin garbage collector for `example`") }
.extra_usage(
"\ fn usage(&self) -> &str {
"Disable the plugin garbage collector for `example`"
}
fn extra_usage(&self) -> &str {
"\
Plugins are garbage collected by default after a period of inactivity. This Plugins are garbage collected by default after a period of inactivity. This
behavior is configurable with `$env.config.plugin_gc.default`, or to change it behavior is configurable with `$env.config.plugin_gc.default`, or to change it
specifically for the example plugin, use specifically for the example plugin, use
@ -20,21 +25,22 @@ specifically for the example plugin, use
This command demonstrates how plugins can control this behavior and disable GC This command demonstrates how plugins can control this behavior and disable GC
temporarily if they need to. It is still possible to stop the plugin explicitly temporarily if they need to. It is still possible to stop the plugin explicitly
using `plugin stop example`.", using `plugin stop example`."
) }
.search_terms(vec![
"example".into(), fn signature(&self) -> Signature {
"gc".into(), Signature::build(self.name())
"plugin_gc".into(),
"garbage".into(),
])
.switch("reset", "Turn the garbage collector back on", None) .switch("reset", "Turn the garbage collector back on", None)
.category(Category::Experimental) .category(Category::Experimental)
} }
fn search_terms(&self) -> Vec<&str> {
vec!["example", "gc", "plugin_gc", "garbage"]
}
fn run( fn run(
&self, &self,
_plugin: &Example, _plugin: &ExamplePlugin,
engine: &EngineInterface, engine: &EngineInterface,
call: &EvaluatedCall, call: &EvaluatedCall,
_input: &Value, _input: &Value,

View File

@ -1,17 +1,27 @@
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand}; use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{Category, LabeledError, PluginSignature, SyntaxShape, Type, Value}; use nu_protocol::{Category, LabeledError, Signature, SyntaxShape, Type, Value};
use crate::Example; use crate::ExamplePlugin;
pub struct Env; pub struct Env;
impl SimplePluginCommand for Env { impl SimplePluginCommand for Env {
type Plugin = Example; type Plugin = ExamplePlugin;
fn signature(&self) -> PluginSignature { fn name(&self) -> &str {
PluginSignature::build("example env") "example env"
.usage("Get environment variable(s)") }
.extra_usage("Returns all environment variables if no name provided")
fn usage(&self) -> &str {
"Get environment variable(s)"
}
fn extra_usage(&self) -> &str {
"Returns all environment variables if no name provided"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.category(Category::Experimental) .category(Category::Experimental)
.optional( .optional(
"name", "name",
@ -25,13 +35,16 @@ impl SimplePluginCommand for Env {
"Set an environment variable to the value", "Set an environment variable to the value",
None, None,
) )
.search_terms(vec!["example".into(), "env".into()])
.input_output_type(Type::Nothing, Type::Any) .input_output_type(Type::Nothing, Type::Any)
} }
fn search_terms(&self) -> Vec<&str> {
vec!["example", "env"]
}
fn run( fn run(
&self, &self,
_plugin: &Example, _plugin: &ExamplePlugin,
engine: &EngineInterface, engine: &EngineInterface,
call: &EvaluatedCall, call: &EvaluatedCall,
_input: &Value, _input: &Value,

View File

@ -1,37 +1,48 @@
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand}; use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand};
use nu_protocol::{ use nu_protocol::{Category, Example, LabeledError, PipelineData, Signature, SyntaxShape, Type};
Category, LabeledError, PipelineData, PluginExample, PluginSignature, SyntaxShape, Type,
};
use crate::Example; use crate::ExamplePlugin;
/// `<list> | example for-each { |value| ... }` /// `<list> | example for-each { |value| ... }`
pub struct ForEach; pub struct ForEach;
impl PluginCommand for ForEach { impl PluginCommand for ForEach {
type Plugin = Example; type Plugin = ExamplePlugin;
fn signature(&self) -> PluginSignature { fn name(&self) -> &str {
PluginSignature::build("example for-each") "example for-each"
.usage("Example execution of a closure with a stream") }
.extra_usage("Prints each value the closure returns to stderr")
fn usage(&self) -> &str {
"Example execution of a closure with a stream"
}
fn extra_usage(&self) -> &str {
"Prints each value the closure returns to stderr"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.input_output_type(Type::ListStream, Type::Nothing) .input_output_type(Type::ListStream, Type::Nothing)
.required( .required(
"closure", "closure",
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])), SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
"The closure to run for each input value", "The closure to run for each input value",
) )
.plugin_examples(vec![PluginExample {
example: "ls | get name | example for-each { |f| ^file $f }".into(),
description: "example with an external command".into(),
result: None,
}])
.category(Category::Experimental) .category(Category::Experimental)
} }
fn examples(&self) -> Vec<Example> {
vec![Example {
example: "ls | get name | example for-each { |f| ^file $f }",
description: "example with an external command",
result: None,
}]
}
fn run( fn run(
&self, &self,
_plugin: &Example, _plugin: &ExamplePlugin,
engine: &EngineInterface, engine: &EngineInterface,
call: &EvaluatedCall, call: &EvaluatedCall,
input: PipelineData, input: PipelineData,
@ -49,5 +60,5 @@ impl PluginCommand for ForEach {
#[test] #[test]
fn test_examples() -> Result<(), nu_protocol::ShellError> { fn test_examples() -> Result<(), nu_protocol::ShellError> {
use nu_plugin_test_support::PluginTest; use nu_plugin_test_support::PluginTest;
PluginTest::new("example", Example.into())?.test_command_examples(&ForEach) PluginTest::new("example", ExamplePlugin.into())?.test_command_examples(&ForEach)
} }

View File

@ -1,21 +1,31 @@
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand}; use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand};
use nu_protocol::{ use nu_protocol::{
Category, IntoInterruptiblePipelineData, LabeledError, PipelineData, PluginExample, Category, Example, IntoInterruptiblePipelineData, LabeledError, PipelineData, Signature,
PluginSignature, SyntaxShape, Type, Value, SyntaxShape, Type, Value,
}; };
use crate::Example; use crate::ExamplePlugin;
/// `example generate <initial> { |previous| {out: ..., next: ...} }` /// `example generate <initial> { |previous| {out: ..., next: ...} }`
pub struct Generate; pub struct Generate;
impl PluginCommand for Generate { impl PluginCommand for Generate {
type Plugin = Example; type Plugin = ExamplePlugin;
fn signature(&self) -> PluginSignature { fn name(&self) -> &str {
PluginSignature::build("example generate") "example generate"
.usage("Example execution of a closure to produce a stream") }
.extra_usage("See the builtin `generate` command")
fn usage(&self) -> &str {
"Example execution of a closure to produce a stream"
}
fn extra_usage(&self) -> &str {
"See the builtin `generate` command"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.input_output_type(Type::Nothing, Type::ListStream) .input_output_type(Type::Nothing, Type::ListStream)
.required( .required(
"initial", "initial",
@ -27,23 +37,25 @@ impl PluginCommand for Generate {
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])), SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
"The closure to run to generate values", "The closure to run to generate values",
) )
.plugin_examples(vec![PluginExample {
example: "example generate 0 { |i| if $i <= 10 { {out: $i, next: ($i + 2)} } }"
.into(),
description: "Generate a sequence of numbers".into(),
result: Some(Value::test_list(
[0, 2, 4, 6, 8, 10]
.into_iter()
.map(Value::test_int)
.collect(),
)),
}])
.category(Category::Experimental) .category(Category::Experimental)
} }
fn examples(&self) -> Vec<Example> {
vec![Example {
example: "example generate 0 { |i| if $i <= 10 { {out: $i, next: ($i + 2)} } }",
description: "Generate a sequence of numbers",
result: Some(Value::test_list(
[0, 2, 4, 6, 8, 10]
.into_iter()
.map(Value::test_int)
.collect(),
)),
}]
}
fn run( fn run(
&self, &self,
_plugin: &Example, _plugin: &ExamplePlugin,
engine: &EngineInterface, engine: &EngineInterface,
call: &EvaluatedCall, call: &EvaluatedCall,
_input: PipelineData, _input: PipelineData,
@ -81,7 +93,7 @@ impl PluginCommand for Generate {
fn test_examples() -> Result<(), nu_protocol::ShellError> { fn test_examples() -> Result<(), nu_protocol::ShellError> {
use nu_cmd_lang::If; use nu_cmd_lang::If;
use nu_plugin_test_support::PluginTest; use nu_plugin_test_support::PluginTest;
PluginTest::new("example", Example.into())? PluginTest::new("example", ExamplePlugin.into())?
.add_decl(Box::new(If))? .add_decl(Box::new(If))?
.test_command_examples(&Generate) .test_command_examples(&Generate)
} }

View File

@ -1,28 +1,38 @@
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand}; use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{Category, LabeledError, PluginSignature, Value}; use nu_protocol::{Category, LabeledError, Signature, Value};
use crate::Example; use crate::ExamplePlugin;
pub struct Main; pub struct Main;
impl SimplePluginCommand for Main { impl SimplePluginCommand for Main {
type Plugin = Example; type Plugin = ExamplePlugin;
fn signature(&self) -> PluginSignature { fn name(&self) -> &str {
PluginSignature::build("example") "example"
.usage("Example commands for Nushell plugins") }
.extra_usage(
r#" fn usage(&self) -> &str {
"Example commands for Nushell plugins"
}
fn extra_usage(&self) -> &str {
r#"
The `example` plugin demonstrates usage of the Nushell plugin API. The `example` plugin demonstrates usage of the Nushell plugin API.
Several commands provided to test and demonstrate different capabilities of Several commands provided to test and demonstrate different capabilities of
plugins exposed through the API. None of these commands are intended to be plugins exposed through the API. None of these commands are intended to be
particularly useful. particularly useful.
"# "#
.trim(), .trim()
) }
.search_terms(vec!["example".into()])
.category(Category::Experimental) fn signature(&self) -> Signature {
Signature::build(self.name()).category(Category::Experimental)
}
fn search_terms(&self) -> Vec<&str> {
vec!["example"]
} }
fn run( fn run(

View File

@ -1,37 +1,53 @@
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand}; use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{Category, LabeledError, PluginExample, PluginSignature, SyntaxShape, Value}; use nu_protocol::{Category, Example, LabeledError, Signature, SyntaxShape, Value};
use crate::Example; use crate::ExamplePlugin;
pub struct One; pub struct One;
impl SimplePluginCommand for One { impl SimplePluginCommand for One {
type Plugin = Example; type Plugin = ExamplePlugin;
fn signature(&self) -> PluginSignature { fn name(&self) -> &str {
"example one"
}
fn usage(&self) -> &str {
"Plugin test example 1. Returns Value::Nothing"
}
fn extra_usage(&self) -> &str {
"Extra usage for example one"
}
fn signature(&self) -> Signature {
// The signature defines the usage of the command inside Nu, and also automatically // The signature defines the usage of the command inside Nu, and also automatically
// generates its help page. // generates its help page.
PluginSignature::build("example one") Signature::build(self.name())
.usage("PluginSignature test 1 for plugin. Returns Value::Nothing")
.extra_usage("Extra usage for example one")
.search_terms(vec!["example".into()])
.required("a", SyntaxShape::Int, "required integer value") .required("a", SyntaxShape::Int, "required integer value")
.required("b", SyntaxShape::String, "required string value") .required("b", SyntaxShape::String, "required string value")
.switch("flag", "a flag for the signature", Some('f')) .switch("flag", "a flag for the signature", Some('f'))
.optional("opt", SyntaxShape::Int, "Optional number") .optional("opt", SyntaxShape::Int, "Optional number")
.named("named", SyntaxShape::String, "named string", Some('n')) .named("named", SyntaxShape::String, "named string", Some('n'))
.rest("rest", SyntaxShape::String, "rest value string") .rest("rest", SyntaxShape::String, "rest value string")
.plugin_examples(vec![PluginExample {
example: "example one 3 bb".into(),
description: "running example with an int value and string value".into(),
result: None,
}])
.category(Category::Experimental) .category(Category::Experimental)
} }
fn search_terms(&self) -> Vec<&str> {
vec!["example"]
}
fn examples(&self) -> Vec<Example> {
vec![Example {
example: "example one 3 bb",
description: "running example with an int value and string value",
result: None,
}]
}
fn run( fn run(
&self, &self,
plugin: &Example, plugin: &ExamplePlugin,
_engine: &EngineInterface, _engine: &EngineInterface,
call: &EvaluatedCall, call: &EvaluatedCall,
input: &Value, input: &Value,
@ -45,5 +61,5 @@ impl SimplePluginCommand for One {
#[test] #[test]
fn test_examples() -> Result<(), nu_protocol::ShellError> { fn test_examples() -> Result<(), nu_protocol::ShellError> {
use nu_plugin_test_support::PluginTest; use nu_plugin_test_support::PluginTest;
PluginTest::new("example", Example.into())?.test_command_examples(&One) PluginTest::new("example", ExamplePlugin.into())?.test_command_examples(&One)
} }

View File

@ -1,39 +1,51 @@
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand}; use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand};
use nu_protocol::{ use nu_protocol::{
Category, LabeledError, ListStream, PipelineData, PluginExample, PluginSignature, SyntaxShape, Category, Example, LabeledError, ListStream, PipelineData, Signature, SyntaxShape, Type, Value,
Type, Value,
}; };
use crate::Example; use crate::ExamplePlugin;
/// `example seq <first> <last>` /// `example seq <first> <last>`
pub struct Seq; pub struct Seq;
impl PluginCommand for Seq { impl PluginCommand for Seq {
type Plugin = Example; type Plugin = ExamplePlugin;
fn signature(&self) -> PluginSignature { fn name(&self) -> &str {
PluginSignature::build("example seq") "example seq"
.usage("Example stream generator for a list of values") }
.search_terms(vec!["example".into()])
fn usage(&self) -> &str {
"Example stream generator for a list of values"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required("first", SyntaxShape::Int, "first number to generate") .required("first", SyntaxShape::Int, "first number to generate")
.required("last", SyntaxShape::Int, "last number to generate") .required("last", SyntaxShape::Int, "last number to generate")
.input_output_type(Type::Nothing, Type::List(Type::Int.into())) .input_output_type(Type::Nothing, Type::List(Type::Int.into()))
.plugin_examples(vec![PluginExample {
example: "example seq 1 3".into(),
description: "generate a sequence from 1 to 3".into(),
result: Some(Value::test_list(vec![
Value::test_int(1),
Value::test_int(2),
Value::test_int(3),
])),
}])
.category(Category::Experimental) .category(Category::Experimental)
} }
fn search_terms(&self) -> Vec<&str> {
vec!["example"]
}
fn examples(&self) -> Vec<Example> {
vec![Example {
example: "example seq 1 3",
description: "generate a sequence from 1 to 3",
result: Some(Value::test_list(vec![
Value::test_int(1),
Value::test_int(2),
Value::test_int(3),
])),
}]
}
fn run( fn run(
&self, &self,
_plugin: &Example, _plugin: &ExamplePlugin,
_engine: &EngineInterface, _engine: &EngineInterface,
call: &EvaluatedCall, call: &EvaluatedCall,
_input: PipelineData, _input: PipelineData,
@ -50,5 +62,5 @@ impl PluginCommand for Seq {
#[test] #[test]
fn test_examples() -> Result<(), nu_protocol::ShellError> { fn test_examples() -> Result<(), nu_protocol::ShellError> {
use nu_plugin_test_support::PluginTest; use nu_plugin_test_support::PluginTest;
PluginTest::new("example", Example.into())?.test_command_examples(&Seq) PluginTest::new("example", ExamplePlugin.into())?.test_command_examples(&Seq)
} }

View File

@ -1,35 +1,46 @@
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand}; use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand};
use nu_protocol::{ use nu_protocol::{Category, Example, LabeledError, PipelineData, Signature, Span, Type, Value};
Category, LabeledError, PipelineData, PluginExample, PluginSignature, Span, Type, Value,
};
use crate::Example; use crate::ExamplePlugin;
/// `<list> | example sum` /// `<list> | example sum`
pub struct Sum; pub struct Sum;
impl PluginCommand for Sum { impl PluginCommand for Sum {
type Plugin = Example; type Plugin = ExamplePlugin;
fn signature(&self) -> PluginSignature { fn name(&self) -> &str {
PluginSignature::build("example sum") "example sum"
.usage("Example stream consumer for a list of values") }
.search_terms(vec!["example".into()])
fn usage(&self) -> &str {
"Example stream consumer for a list of values"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.input_output_types(vec![ .input_output_types(vec![
(Type::List(Type::Int.into()), Type::Int), (Type::List(Type::Int.into()), Type::Int),
(Type::List(Type::Float.into()), Type::Float), (Type::List(Type::Float.into()), Type::Float),
]) ])
.plugin_examples(vec![PluginExample {
example: "example seq 1 5 | example sum".into(),
description: "sum values from 1 to 5".into(),
result: Some(Value::test_int(15)),
}])
.category(Category::Experimental) .category(Category::Experimental)
} }
fn search_terms(&self) -> Vec<&str> {
vec!["example"]
}
fn examples(&self) -> Vec<Example> {
vec![Example {
example: "example seq 1 5 | example sum",
description: "sum values from 1 to 5",
result: Some(Value::test_int(15)),
}]
}
fn run( fn run(
&self, &self,
_plugin: &Example, _plugin: &ExamplePlugin,
_engine: &EngineInterface, _engine: &EngineInterface,
call: &EvaluatedCall, call: &EvaluatedCall,
input: PipelineData, input: PipelineData,
@ -92,5 +103,5 @@ impl IntOrFloat {
#[test] #[test]
fn test_examples() -> Result<(), nu_protocol::ShellError> { fn test_examples() -> Result<(), nu_protocol::ShellError> {
use nu_plugin_test_support::PluginTest; use nu_plugin_test_support::PluginTest;
PluginTest::new("example", Example.into())?.test_command_examples(&Sum) PluginTest::new("example", ExamplePlugin.into())?.test_command_examples(&Sum)
} }

View File

@ -1,18 +1,25 @@
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand}; use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{Category, LabeledError, PluginSignature, SyntaxShape, Value}; use nu_protocol::{Category, LabeledError, Signature, SyntaxShape, Value};
use crate::Example; use crate::ExamplePlugin;
pub struct Three; pub struct Three;
impl SimplePluginCommand for Three { impl SimplePluginCommand for Three {
type Plugin = Example; type Plugin = ExamplePlugin;
fn signature(&self) -> PluginSignature { fn name(&self) -> &str {
"example three"
}
fn usage(&self) -> &str {
"Plugin test example 3. Returns labeled error"
}
fn signature(&self) -> Signature {
// The signature defines the usage of the command inside Nu, and also automatically // The signature defines the usage of the command inside Nu, and also automatically
// generates its help page. // generates its help page.
PluginSignature::build("example three") Signature::build(self.name())
.usage("PluginSignature test 3 for plugin. Returns labeled error")
.required("a", SyntaxShape::Int, "required integer value") .required("a", SyntaxShape::Int, "required integer value")
.required("b", SyntaxShape::String, "required string value") .required("b", SyntaxShape::String, "required string value")
.switch("flag", "a flag for the signature", Some('f')) .switch("flag", "a flag for the signature", Some('f'))
@ -24,7 +31,7 @@ impl SimplePluginCommand for Three {
fn run( fn run(
&self, &self,
plugin: &Example, plugin: &ExamplePlugin,
_engine: &EngineInterface, _engine: &EngineInterface,
call: &EvaluatedCall, call: &EvaluatedCall,
input: &Value, input: &Value,

View File

@ -1,18 +1,25 @@
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand}; use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{record, Category, LabeledError, PluginSignature, SyntaxShape, Value}; use nu_protocol::{record, Category, LabeledError, Signature, SyntaxShape, Value};
use crate::Example; use crate::ExamplePlugin;
pub struct Two; pub struct Two;
impl SimplePluginCommand for Two { impl SimplePluginCommand for Two {
type Plugin = Example; type Plugin = ExamplePlugin;
fn signature(&self) -> PluginSignature { fn name(&self) -> &str {
"example two"
}
fn usage(&self) -> &str {
"Plugin test example 2. Returns list of records"
}
fn signature(&self) -> Signature {
// The signature defines the usage of the command inside Nu, and also automatically // The signature defines the usage of the command inside Nu, and also automatically
// generates its help page. // generates its help page.
PluginSignature::build("example two") Signature::build(self.name())
.usage("PluginSignature test 2 for plugin. Returns list of records")
.required("a", SyntaxShape::Int, "required integer value") .required("a", SyntaxShape::Int, "required integer value")
.required("b", SyntaxShape::String, "required string value") .required("b", SyntaxShape::String, "required string value")
.switch("flag", "a flag for the signature", Some('f')) .switch("flag", "a flag for the signature", Some('f'))
@ -24,7 +31,7 @@ impl SimplePluginCommand for Two {
fn run( fn run(
&self, &self,
plugin: &Example, plugin: &ExamplePlugin,
_engine: &EngineInterface, _engine: &EngineInterface,
call: &EvaluatedCall, call: &EvaluatedCall,
input: &Value, input: &Value,

View File

@ -1,9 +1,9 @@
use nu_plugin::EvaluatedCall; use nu_plugin::EvaluatedCall;
use nu_protocol::{LabeledError, Value}; use nu_protocol::{LabeledError, Value};
pub struct Example; pub struct ExamplePlugin;
impl Example { impl ExamplePlugin {
pub fn print_values( pub fn print_values(
&self, &self,
index: u32, index: u32,

View File

@ -4,9 +4,9 @@ mod commands;
mod example; mod example;
pub use commands::*; pub use commands::*;
pub use example::Example; pub use example::ExamplePlugin;
impl Plugin for Example { impl Plugin for ExamplePlugin {
fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> { fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> {
// This is a list of all of the commands you would like Nu to register when your plugin is // This is a list of all of the commands you would like Nu to register when your plugin is
// loaded. // loaded.

View File

@ -1,12 +1,12 @@
use nu_plugin::{serve_plugin, MsgPackSerializer}; use nu_plugin::{serve_plugin, MsgPackSerializer};
use nu_plugin_example::Example; use nu_plugin_example::ExamplePlugin;
fn main() { fn main() {
// When defining your plugin, you can select the Serializer that could be // When defining your plugin, you can select the Serializer that could be
// used to encode and decode the messages. The available options are // used to encode and decode the messages. The available options are
// MsgPackSerializer and JsonSerializer. Both are defined in the serializer // MsgPackSerializer and JsonSerializer. Both are defined in the serializer
// folder in nu-plugin. // folder in nu-plugin.
serve_plugin(&Example {}, MsgPackSerializer {}) serve_plugin(&ExamplePlugin {}, MsgPackSerializer {})
// Note // Note
// When creating plugins in other languages one needs to consider how a plugin // When creating plugins in other languages one needs to consider how a plugin

View File

@ -4,20 +4,26 @@ use eml_parser::EmlParser;
use indexmap::IndexMap; use indexmap::IndexMap;
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand}; use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{ use nu_protocol::{
record, Category, LabeledError, PluginExample, PluginSignature, ShellError, Span, SyntaxShape, record, Category, Example, LabeledError, ShellError, Signature, Span, SyntaxShape, Type, Value,
Type, Value,
}; };
const DEFAULT_BODY_PREVIEW: usize = 50; const DEFAULT_BODY_PREVIEW: usize = 50;
pub const CMD_NAME: &str = "from eml";
pub struct FromEml; pub struct FromEml;
impl SimplePluginCommand for FromEml { impl SimplePluginCommand for FromEml {
type Plugin = FromCmds; type Plugin = FromCmds;
fn signature(&self) -> nu_protocol::PluginSignature { fn name(&self) -> &str {
PluginSignature::build(CMD_NAME) "from eml"
}
fn usage(&self) -> &str {
"Parse text as .eml and create record."
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.input_output_types(vec![(Type::String, Type::Record(vec![]))]) .input_output_types(vec![(Type::String, Type::Record(vec![]))])
.named( .named(
"preview-body", "preview-body",
@ -25,11 +31,13 @@ impl SimplePluginCommand for FromEml {
"How many bytes of the body to preview", "How many bytes of the body to preview",
Some('b'), Some('b'),
) )
.usage("Parse text as .eml and create record.")
.plugin_examples(examples())
.category(Category::Formats) .category(Category::Formats)
} }
fn examples(&self) -> Vec<Example> {
examples()
}
fn run( fn run(
&self, &self,
_plugin: &FromCmds, _plugin: &FromCmds,
@ -45,16 +53,15 @@ impl SimplePluginCommand for FromEml {
} }
} }
pub fn examples() -> Vec<PluginExample> { pub fn examples() -> Vec<Example<'static>> {
vec![ vec![
PluginExample { Example {
description: "Convert eml structured data into record".into(), description: "Convert eml structured data into record",
example: "'From: test@email.com example: "'From: test@email.com
Subject: Welcome Subject: Welcome
To: someone@somewhere.com To: someone@somewhere.com
Test' | from eml" Test' | from eml",
.into(),
result: Some(Value::test_record(record! { result: Some(Value::test_record(record! {
"Subject" => Value::test_string("Welcome"), "Subject" => Value::test_string("Welcome"),
"From" => Value::test_record(record! { "From" => Value::test_record(record! {
@ -68,14 +75,13 @@ Test' | from eml"
"Body" => Value::test_string("Test"), "Body" => Value::test_string("Test"),
})), })),
}, },
PluginExample { Example {
description: "Convert eml structured data into record".into(), description: "Convert eml structured data into record",
example: "'From: test@email.com example: "'From: test@email.com
Subject: Welcome Subject: Welcome
To: someone@somewhere.com To: someone@somewhere.com
Test' | from eml -b 1" Test' | from eml -b 1",
.into(),
result: Some(Value::test_record(record! { result: Some(Value::test_record(record! {
"Subject" => Value::test_string("Welcome"), "Subject" => Value::test_string("Welcome"),
"From" => Value::test_record(record! { "From" => Value::test_record(record! {

View File

@ -1,27 +1,36 @@
use crate::FromCmds; use crate::FromCmds;
use ical::{parser::ical::component::*, property::Property}; use ical::{parser::ical::component::*, property::Property};
use indexmap::IndexMap; use indexmap::IndexMap;
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand}; use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{ use nu_protocol::{
record, Category, LabeledError, PluginExample, PluginSignature, ShellError, Span, Type, Value, record, Category, Example, LabeledError, ShellError, Signature, Span, Type, Value,
}; };
use std::io::BufReader; use std::io::BufReader;
pub const CMD_NAME: &str = "from ics";
pub struct FromIcs; pub struct FromIcs;
impl SimplePluginCommand for FromIcs { impl SimplePluginCommand for FromIcs {
type Plugin = FromCmds; type Plugin = FromCmds;
fn signature(&self) -> nu_protocol::PluginSignature { fn name(&self) -> &str {
PluginSignature::build(CMD_NAME) "from ics"
}
fn usage(&self) -> &str {
"Parse text as .ics and create table."
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.input_output_types(vec![(Type::String, Type::Table(vec![]))]) .input_output_types(vec![(Type::String, Type::Table(vec![]))])
.usage("Parse text as .ics and create table.")
.plugin_examples(examples())
.category(Category::Formats) .category(Category::Formats)
} }
fn examples(&self) -> Vec<Example> {
examples()
}
fn run( fn run(
&self, &self,
_plugin: &FromCmds, _plugin: &FromCmds,
@ -71,13 +80,11 @@ impl SimplePluginCommand for FromIcs {
} }
} }
pub fn examples() -> Vec<PluginExample> { pub fn examples() -> Vec<Example<'static>> {
vec![PluginExample { vec![Example {
example: " example: "'BEGIN:VCALENDAR
'BEGIN:VCALENDAR END:VCALENDAR' | from ics",
END:VCALENDAR' | from ics" description: "Converts ics formatted string to table",
.into(),
description: "Converts ics formatted string to table".into(),
result: Some(Value::test_list(vec![Value::test_record(record! { result: Some(Value::test_list(vec![Value::test_record(record! {
"properties" => Value::test_list(vec![]), "properties" => Value::test_list(vec![]),
"events" => Value::test_list(vec![]), "events" => Value::test_list(vec![]),

View File

@ -1,24 +1,33 @@
use crate::FromCmds; use crate::FromCmds;
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand}; use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{ use nu_protocol::{
record, Category, LabeledError, PluginExample, PluginSignature, Record, ShellError, Type, Value, record, Category, Example, LabeledError, Record, ShellError, Signature, Type, Value,
}; };
pub const CMD_NAME: &str = "from ini";
pub struct FromIni; pub struct FromIni;
impl SimplePluginCommand for FromIni { impl SimplePluginCommand for FromIni {
type Plugin = FromCmds; type Plugin = FromCmds;
fn signature(&self) -> PluginSignature { fn name(&self) -> &str {
PluginSignature::build(CMD_NAME) "from ini"
}
fn usage(&self) -> &str {
"Parse text as .ini and create table."
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.input_output_types(vec![(Type::String, Type::Record(vec![]))]) .input_output_types(vec![(Type::String, Type::Record(vec![]))])
.usage("Parse text as .ini and create table.")
.plugin_examples(examples())
.category(Category::Formats) .category(Category::Formats)
} }
fn examples(&self) -> Vec<Example> {
examples()
}
fn run( fn run(
&self, &self,
_plugin: &FromCmds, _plugin: &FromCmds,
@ -73,13 +82,12 @@ impl SimplePluginCommand for FromIni {
} }
} }
pub fn examples() -> Vec<PluginExample> { pub fn examples() -> Vec<Example<'static>> {
vec![PluginExample { vec![Example {
example: "'[foo] example: "'[foo]
a=1 a=1
b=2' | from ini" b=2' | from ini",
.into(), description: "Converts ini formatted string to record",
description: "Converts ini formatted string to record".into(),
result: Some(Value::test_record(record! { result: Some(Value::test_record(record! {
"foo" => Value::test_record(record! { "foo" => Value::test_record(record! {
"a" => Value::test_string("1"), "a" => Value::test_string("1"),

View File

@ -1,26 +1,35 @@
use crate::FromCmds; use crate::FromCmds;
use ical::{parser::vcard::component::*, property::Property}; use ical::{parser::vcard::component::*, property::Property};
use indexmap::IndexMap; use indexmap::IndexMap;
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand}; use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{ use nu_protocol::{
record, Category, LabeledError, PluginExample, PluginSignature, ShellError, Span, Type, Value, record, Category, Example, LabeledError, ShellError, Signature, Span, Type, Value,
}; };
pub const CMD_NAME: &str = "from vcf";
pub struct FromVcf; pub struct FromVcf;
impl SimplePluginCommand for FromVcf { impl SimplePluginCommand for FromVcf {
type Plugin = FromCmds; type Plugin = FromCmds;
fn signature(&self) -> PluginSignature { fn name(&self) -> &str {
PluginSignature::build(CMD_NAME) "from vcf"
}
fn usage(&self) -> &str {
"Parse text as .vcf and create table."
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.input_output_types(vec![(Type::String, Type::Table(vec![]))]) .input_output_types(vec![(Type::String, Type::Table(vec![]))])
.usage("Parse text as .vcf and create table.")
.plugin_examples(examples())
.category(Category::Formats) .category(Category::Formats)
} }
fn examples(&self) -> Vec<Example> {
examples()
}
fn run( fn run(
&self, &self,
_plugin: &FromCmds, _plugin: &FromCmds,
@ -68,15 +77,14 @@ impl SimplePluginCommand for FromVcf {
} }
} }
pub fn examples() -> Vec<PluginExample> { pub fn examples() -> Vec<Example<'static>> {
vec![PluginExample { vec![Example {
example: "'BEGIN:VCARD example: "'BEGIN:VCARD
N:Foo N:Foo
FN:Bar FN:Bar
EMAIL:foo@bar.com EMAIL:foo@bar.com
END:VCARD' | from vcf" END:VCARD' | from vcf",
.into(), description: "Converts ics formatted string to table",
description: "Converts ics formatted string to table".into(),
result: Some(Value::test_list(vec![Value::test_record(record! { result: Some(Value::test_list(vec![Value::test_record(record! {
"properties" => Value::test_list( "properties" => Value::test_list(
vec![ vec![

View File

@ -1,6 +1,6 @@
use crate::GStat; use crate::GStat;
use nu_plugin::{EngineInterface, EvaluatedCall, Plugin, PluginCommand, SimplePluginCommand}; use nu_plugin::{EngineInterface, EvaluatedCall, Plugin, PluginCommand, SimplePluginCommand};
use nu_protocol::{Category, LabeledError, PluginSignature, Spanned, SyntaxShape, Value}; use nu_protocol::{Category, LabeledError, Signature, Spanned, SyntaxShape, Value};
pub struct GStatPlugin; pub struct GStatPlugin;
@ -13,9 +13,16 @@ impl Plugin for GStatPlugin {
impl SimplePluginCommand for GStat { impl SimplePluginCommand for GStat {
type Plugin = GStatPlugin; type Plugin = GStatPlugin;
fn signature(&self) -> PluginSignature { fn name(&self) -> &str {
PluginSignature::build("gstat") "gstat"
.usage("Get the git status of a repo") }
fn usage(&self) -> &str {
"Get the git status of a repo"
}
fn signature(&self) -> Signature {
Signature::build(PluginCommand::name(self))
.optional("path", SyntaxShape::Filepath, "path to repo") .optional("path", SyntaxShape::Filepath, "path to repo")
.category(Category::Custom("prompt".to_string())) .category(Category::Custom("prompt".to_string()))
} }

View File

@ -1,6 +1,6 @@
use crate::{inc::SemVerAction, Inc}; use crate::{inc::SemVerAction, Inc};
use nu_plugin::{EngineInterface, EvaluatedCall, Plugin, PluginCommand, SimplePluginCommand}; use nu_plugin::{EngineInterface, EvaluatedCall, Plugin, PluginCommand, SimplePluginCommand};
use nu_protocol::{ast::CellPath, LabeledError, PluginSignature, SyntaxShape, Value}; use nu_protocol::{ast::CellPath, LabeledError, Signature, SyntaxShape, Value};
pub struct IncPlugin; pub struct IncPlugin;
@ -13,9 +13,16 @@ impl Plugin for IncPlugin {
impl SimplePluginCommand for Inc { impl SimplePluginCommand for Inc {
type Plugin = IncPlugin; type Plugin = IncPlugin;
fn signature(&self) -> PluginSignature { fn name(&self) -> &str {
PluginSignature::build("inc") "inc"
.usage("Increment a value or version. Optionally use the column of a table.") }
fn usage(&self) -> &str {
"Increment a value or version. Optionally use the column of a table."
}
fn signature(&self) -> Signature {
Signature::build(PluginCommand::name(self))
.optional("cell_path", SyntaxShape::CellPath, "cell path to update") .optional("cell_path", SyntaxShape::CellPath, "cell path to update")
.switch( .switch(
"major", "major",

View File

@ -1,6 +1,6 @@
use crate::{query_json::QueryJson, query_web::QueryWeb, query_xml::QueryXml}; use crate::{query_json::QueryJson, query_web::QueryWeb, query_xml::QueryXml};
use nu_plugin::{EvaluatedCall, Plugin, PluginCommand, SimplePluginCommand}; use nu_plugin::{EvaluatedCall, Plugin, PluginCommand, SimplePluginCommand};
use nu_protocol::{Category, LabeledError, PluginSignature, Value}; use nu_protocol::{Category, LabeledError, Signature, Value};
#[derive(Default)] #[derive(Default)]
pub struct Query; pub struct Query;
@ -32,10 +32,16 @@ pub struct QueryCommand;
impl SimplePluginCommand for QueryCommand { impl SimplePluginCommand for QueryCommand {
type Plugin = Query; type Plugin = Query;
fn signature(&self) -> PluginSignature { fn name(&self) -> &str {
PluginSignature::build("query") "query"
.usage("Show all the query commands") }
.category(Category::Filters)
fn usage(&self) -> &str {
"Show all the query commands"
}
fn signature(&self) -> Signature {
Signature::build(PluginCommand::name(self)).category(Category::Filters)
} }
fn run( fn run(

View File

@ -1,20 +1,23 @@
use crate::Query; use crate::Query;
use gjson::Value as gjValue; use gjson::Value as gjValue;
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand}; use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{ use nu_protocol::{Category, LabeledError, Record, Signature, Span, Spanned, SyntaxShape, Value};
Category, LabeledError, PluginSignature, Record, Span, Spanned, SyntaxShape, Value,
};
pub struct QueryJson; pub struct QueryJson;
impl SimplePluginCommand for QueryJson { impl SimplePluginCommand for QueryJson {
type Plugin = Query; type Plugin = Query;
fn signature(&self) -> PluginSignature { fn name(&self) -> &str {
PluginSignature::build("query json") "query json"
.usage( }
"execute json query on json file (open --raw <file> | query json 'query string')",
) fn usage(&self) -> &str {
"execute json query on json file (open --raw <file> | query json 'query string')"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required("query", SyntaxShape::String, "json query") .required("query", SyntaxShape::String, "json query")
.category(Category::Filters) .category(Category::Filters)
} }

View File

@ -1,8 +1,7 @@
use crate::{web_tables::WebTable, Query}; use crate::{web_tables::WebTable, Query};
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand}; use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{ use nu_protocol::{
Category, LabeledError, PluginExample, PluginSignature, Record, Span, Spanned, SyntaxShape, Category, Example, LabeledError, Record, Signature, Span, Spanned, SyntaxShape, Value,
Value,
}; };
use scraper::{Html, Selector as ScraperSelector}; use scraper::{Html, Selector as ScraperSelector};
@ -11,12 +10,18 @@ pub struct QueryWeb;
impl SimplePluginCommand for QueryWeb { impl SimplePluginCommand for QueryWeb {
type Plugin = Query; type Plugin = Query;
fn signature(&self) -> PluginSignature { fn name(&self) -> &str {
PluginSignature::build("query web") "query web"
.usage("execute selector query on html/web") }
fn usage(&self) -> &str {
"execute selector query on html/web"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.named("query", SyntaxShape::String, "selector query", Some('q')) .named("query", SyntaxShape::String, "selector query", Some('q'))
.switch("as-html", "return the query output as html", Some('m')) .switch("as-html", "return the query output as html", Some('m'))
.plugin_examples(web_examples())
.named( .named(
"attribute", "attribute",
SyntaxShape::String, SyntaxShape::String,
@ -37,6 +42,10 @@ impl SimplePluginCommand for QueryWeb {
.category(Category::Network) .category(Category::Network)
} }
fn examples(&self) -> Vec<Example> {
web_examples()
}
fn run( fn run(
&self, &self,
_plugin: &Query, _plugin: &Query,
@ -48,27 +57,27 @@ impl SimplePluginCommand for QueryWeb {
} }
} }
pub fn web_examples() -> Vec<PluginExample> { pub fn web_examples() -> Vec<Example<'static>> {
vec![ vec![
PluginExample { Example {
example: "http get https://phoronix.com | query web --query 'header' | flatten".into(), example: "http get https://phoronix.com | query web --query 'header' | flatten",
description: "Retrieve all `<header>` elements from phoronix.com website".into(), description: "Retrieve all `<header>` elements from phoronix.com website",
result: None, result: None,
}, },
PluginExample { Example {
example: "http get https://en.wikipedia.org/wiki/List_of_cities_in_India_by_population | example: "http get https://en.wikipedia.org/wiki/List_of_cities_in_India_by_population |
query web --as-table [City 'Population(2011)[3]' 'Population(2001)[3][a]' 'State or unionterritory' 'Ref']".into(), query web --as-table [City 'Population(2011)[3]' 'Population(2001)[3][a]' 'State or unionterritory' 'Ref']",
description: "Retrieve a html table from Wikipedia and parse it into a nushell table using table headers as guides".into(), description: "Retrieve a html table from Wikipedia and parse it into a nushell table using table headers as guides",
result: None result: None
}, },
PluginExample { Example {
example: "http get https://www.nushell.sh | query web --query 'h2, h2 + p' | each {str join} | group 2 | each {rotate --ccw tagline description} | flatten".into(), example: "http get https://www.nushell.sh | query web --query 'h2, h2 + p' | each {str join} | group 2 | each {rotate --ccw tagline description} | flatten",
description: "Pass multiple css selectors to extract several elements within single query, group the query results together and rotate them to create a table".into(), description: "Pass multiple css selectors to extract several elements within single query, group the query results together and rotate them to create a table",
result: None, result: None,
}, },
PluginExample { Example {
example: "http get https://example.org | query web --query a --attribute href".into(), example: "http get https://example.org | query web --query a --attribute href",
description: "Retrieve a specific html attribute instead of the default text".into(), description: "Retrieve a specific html attribute instead of the default text",
result: None, result: None,
} }
] ]

View File

@ -1,7 +1,7 @@
use crate::Query; use crate::Query;
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand}; use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{ use nu_protocol::{
record, Category, LabeledError, PluginSignature, Record, Span, Spanned, SyntaxShape, Value, record, Category, LabeledError, Record, Signature, Span, Spanned, SyntaxShape, Value,
}; };
use sxd_document::parser; use sxd_document::parser;
use sxd_xpath::{Context, Factory}; use sxd_xpath::{Context, Factory};
@ -11,9 +11,16 @@ pub struct QueryXml;
impl SimplePluginCommand for QueryXml { impl SimplePluginCommand for QueryXml {
type Plugin = Query; type Plugin = Query;
fn signature(&self) -> PluginSignature { fn name(&self) -> &str {
PluginSignature::build("query xml") "query xml"
.usage("execute xpath query on xml") }
fn usage(&self) -> &str {
"execute xpath query on xml"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required("query", SyntaxShape::String, "xpath query") .required("query", SyntaxShape::String, "xpath query")
.category(Category::Filters) .category(Category::Filters)
} }

View File

@ -10,7 +10,7 @@ fn help() {
"example one --help" "example one --help"
); );
assert!(actual.out.contains("PluginSignature test 1")); assert!(actual.out.contains("test example 1"));
assert!(actual.out.contains("Extra usage for example one")); assert!(actual.out.contains("Extra usage for example one"));
}) })
} }