mirror of
https://github.com/nushell/nushell.git
synced 2024-11-25 01:43:47 +01:00
Allow plugins to report their own version and store it in the registry (#12883)
# Description This allows plugins to report their version (and potentially other metadata in the future). The version is shown in `plugin list` and in `version`. The metadata is stored in the registry file, and reflects whatever was retrieved on `plugin add`, not necessarily the running binary. This can help you to diagnose if there's some kind of mismatch with what you expect. We could potentially use this functionality to show a warning or error if a plugin being run does not have the same version as what was in the cache file, suggesting `plugin add` be run again, but I haven't done that at this point. It is optional, and it requires the plugin author to make some code changes if they want to provide it, since I can't automatically determine the version of the calling crate or anything tricky like that to do it. Example: ``` > plugin list | select name version is_running pid ╭───┬────────────────┬─────────┬────────────┬─────╮ │ # │ name │ version │ is_running │ pid │ ├───┼────────────────┼─────────┼────────────┼─────┤ │ 0 │ example │ 0.93.1 │ false │ │ │ 1 │ gstat │ 0.93.1 │ false │ │ │ 2 │ inc │ 0.93.1 │ false │ │ │ 3 │ python_example │ 0.1.0 │ false │ │ ╰───┴────────────────┴─────────┴────────────┴─────╯ ``` cc @maxim-uvarov (he asked for it) # User-Facing Changes - `plugin list` gets a `version` column - `version` shows plugin versions when available - plugin authors *should* add `fn metadata()` to their `impl Plugin`, but don't have to # Tests + Formatting Tested the low level stuff and also the `plugin list` column. # After Submitting - [ ] update plugin guide docs - [ ] update plugin protocol docs (`Metadata` call & response) - [ ] update plugin template (`fn metadata()` should be easy) - [ ] release notes
This commit is contained in:
parent
dd8f8861ed
commit
91d44f15c1
@ -344,7 +344,10 @@ pub fn migrate_old_plugin_file(engine_state: &EngineState, storage_path: &str) -
|
|||||||
name: identity.name().to_owned(),
|
name: identity.name().to_owned(),
|
||||||
filename: identity.filename().to_owned(),
|
filename: identity.filename().to_owned(),
|
||||||
shell: identity.shell().map(|p| p.to_owned()),
|
shell: identity.shell().map(|p| p.to_owned()),
|
||||||
data: PluginRegistryItemData::Valid { commands },
|
data: PluginRegistryItemData::Valid {
|
||||||
|
metadata: Default::default(),
|
||||||
|
commands,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,11 +116,18 @@ pub fn version(engine_state: &EngineState, span: Span) -> Result<PipelineData, S
|
|||||||
Value::string(features_enabled().join(", "), span),
|
Value::string(features_enabled().join(", "), span),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get a list of plugin names
|
// Get a list of plugin names and versions if present
|
||||||
let installed_plugins = engine_state
|
let installed_plugins = engine_state
|
||||||
.plugins()
|
.plugins()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| x.identity().name())
|
.map(|x| {
|
||||||
|
let name = x.identity().name();
|
||||||
|
if let Some(version) = x.metadata().and_then(|m| m.version) {
|
||||||
|
format!("{name} {version}")
|
||||||
|
} else {
|
||||||
|
name.into()
|
||||||
|
}
|
||||||
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
record.push(
|
record.push(
|
||||||
|
@ -118,11 +118,12 @@ apparent the next time `nu` is next launched with that plugin registry file.
|
|||||||
},
|
},
|
||||||
));
|
));
|
||||||
let interface = plugin.clone().get_plugin(Some((engine_state, stack)))?;
|
let interface = plugin.clone().get_plugin(Some((engine_state, stack)))?;
|
||||||
|
let metadata = interface.get_metadata()?;
|
||||||
let commands = interface.get_signature()?;
|
let commands = interface.get_signature()?;
|
||||||
|
|
||||||
modify_plugin_file(engine_state, stack, call.head, custom_path, |contents| {
|
modify_plugin_file(engine_state, stack, call.head, custom_path, |contents| {
|
||||||
// Update the file with the received signatures
|
// Update the file with the received metadata and signatures
|
||||||
let item = PluginRegistryItem::new(plugin.identity(), commands);
|
let item = PluginRegistryItem::new(plugin.identity(), metadata, commands);
|
||||||
contents.upsert_plugin(item);
|
contents.upsert_plugin(item);
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
|
@ -16,6 +16,7 @@ impl Command for PluginList {
|
|||||||
Type::Table(
|
Type::Table(
|
||||||
[
|
[
|
||||||
("name".into(), Type::String),
|
("name".into(), Type::String),
|
||||||
|
("version".into(), Type::String),
|
||||||
("is_running".into(), Type::Bool),
|
("is_running".into(), Type::Bool),
|
||||||
("pid".into(), Type::Int),
|
("pid".into(), Type::Int),
|
||||||
("filename".into(), Type::String),
|
("filename".into(), Type::String),
|
||||||
@ -43,6 +44,7 @@ impl Command for PluginList {
|
|||||||
description: "List installed plugins.",
|
description: "List installed plugins.",
|
||||||
result: Some(Value::test_list(vec![Value::test_record(record! {
|
result: Some(Value::test_list(vec![Value::test_record(record! {
|
||||||
"name" => Value::test_string("inc"),
|
"name" => Value::test_string("inc"),
|
||||||
|
"version" => Value::test_string(env!("CARGO_PKG_VERSION")),
|
||||||
"is_running" => Value::test_bool(true),
|
"is_running" => Value::test_bool(true),
|
||||||
"pid" => Value::test_int(106480),
|
"pid" => Value::test_int(106480),
|
||||||
"filename" => if cfg!(windows) {
|
"filename" => if cfg!(windows) {
|
||||||
@ -98,8 +100,15 @@ impl Command for PluginList {
|
|||||||
.map(|s| Value::string(s.to_string_lossy(), head))
|
.map(|s| Value::string(s.to_string_lossy(), head))
|
||||||
.unwrap_or(Value::nothing(head));
|
.unwrap_or(Value::nothing(head));
|
||||||
|
|
||||||
|
let metadata = plugin.metadata();
|
||||||
|
let version = metadata
|
||||||
|
.and_then(|m| m.version)
|
||||||
|
.map(|s| Value::string(s, head))
|
||||||
|
.unwrap_or(Value::nothing(head));
|
||||||
|
|
||||||
let record = record! {
|
let record = record! {
|
||||||
"name" => Value::string(plugin.identity().name(), head),
|
"name" => Value::string(plugin.identity().name(), head),
|
||||||
|
"version" => version,
|
||||||
"is_running" => Value::bool(plugin.is_running(), head),
|
"is_running" => Value::bool(plugin.is_running(), head),
|
||||||
"pid" => pid,
|
"pid" => pid,
|
||||||
"filename" => Value::string(plugin.identity().filename().to_string_lossy(), head),
|
"filename" => Value::string(plugin.identity().filename().to_string_lossy(), head),
|
||||||
|
@ -3740,28 +3740,37 @@ pub fn parse_register(working_set: &mut StateWorkingSet, lite_command: &LiteComm
|
|||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let signatures = plugin
|
let metadata_and_signatures = plugin
|
||||||
.clone()
|
.clone()
|
||||||
.get(get_envs)
|
.get(get_envs)
|
||||||
.and_then(|p| p.get_signature())
|
.and_then(|p| {
|
||||||
|
let meta = p.get_metadata()?;
|
||||||
|
let sigs = p.get_signature()?;
|
||||||
|
Ok((meta, sigs))
|
||||||
|
})
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
log::warn!("Error getting signatures: {err:?}");
|
log::warn!("Error getting metadata and signatures: {err:?}");
|
||||||
ParseError::LabeledError(
|
ParseError::LabeledError(
|
||||||
"Error getting signatures".into(),
|
"Error getting metadata and signatures".into(),
|
||||||
err.to_string(),
|
err.to_string(),
|
||||||
spans[0],
|
spans[0],
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Ok(ref signatures) = signatures {
|
match metadata_and_signatures {
|
||||||
// Add the loaded plugin to the delta
|
Ok((meta, sigs)) => {
|
||||||
working_set.update_plugin_registry(PluginRegistryItem::new(
|
// Set the metadata on the plugin
|
||||||
&identity,
|
plugin.set_metadata(Some(meta.clone()));
|
||||||
signatures.clone(),
|
// Add the loaded plugin to the delta
|
||||||
));
|
working_set.update_plugin_registry(PluginRegistryItem::new(
|
||||||
|
&identity,
|
||||||
|
meta,
|
||||||
|
sigs.clone(),
|
||||||
|
));
|
||||||
|
Ok(sigs)
|
||||||
|
}
|
||||||
|
Err(err) => Err(err),
|
||||||
}
|
}
|
||||||
|
|
||||||
signatures
|
|
||||||
},
|
},
|
||||||
|sig| sig.map(|sig| vec![sig]),
|
|sig| sig.map(|sig| vec![sig]),
|
||||||
)?;
|
)?;
|
||||||
|
@ -252,7 +252,7 @@ pub fn load_plugin_registry_item(
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
match &plugin.data {
|
match &plugin.data {
|
||||||
PluginRegistryItemData::Valid { commands } => {
|
PluginRegistryItemData::Valid { metadata, commands } => {
|
||||||
let plugin = add_plugin_to_working_set(working_set, &identity)?;
|
let plugin = add_plugin_to_working_set(working_set, &identity)?;
|
||||||
|
|
||||||
// Ensure that the plugin is reset. We're going to load new signatures, so we want to
|
// Ensure that the plugin is reset. We're going to load new signatures, so we want to
|
||||||
@ -260,6 +260,9 @@ pub fn load_plugin_registry_item(
|
|||||||
// doesn't.
|
// doesn't.
|
||||||
plugin.reset()?;
|
plugin.reset()?;
|
||||||
|
|
||||||
|
// Set the plugin metadata from the file
|
||||||
|
plugin.set_metadata(Some(metadata.clone()));
|
||||||
|
|
||||||
// Create the declarations from the commands
|
// Create the declarations from the commands
|
||||||
for signature in commands {
|
for signature in commands {
|
||||||
let decl = PluginDeclaration::new(plugin.clone(), signature.clone());
|
let decl = PluginDeclaration::new(plugin.clone(), signature.clone());
|
||||||
|
@ -11,8 +11,8 @@ use nu_plugin_protocol::{
|
|||||||
PluginOutput, ProtocolInfo, StreamId, StreamMessage,
|
PluginOutput, ProtocolInfo, StreamId, StreamMessage,
|
||||||
};
|
};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Operator, CustomValue, IntoSpanned, PipelineData, PluginSignature, ShellError, Span,
|
ast::Operator, CustomValue, IntoSpanned, PipelineData, PluginMetadata, PluginSignature,
|
||||||
Spanned, Value,
|
ShellError, Span, Spanned, Value,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
collections::{btree_map, BTreeMap},
|
collections::{btree_map, BTreeMap},
|
||||||
@ -716,6 +716,7 @@ impl PluginInterface {
|
|||||||
|
|
||||||
// Convert the call into one with a header and handle the stream, if necessary
|
// Convert the call into one with a header and handle the stream, if necessary
|
||||||
let (call, writer) = match call {
|
let (call, writer) = match call {
|
||||||
|
PluginCall::Metadata => (PluginCall::Metadata, Default::default()),
|
||||||
PluginCall::Signature => (PluginCall::Signature, Default::default()),
|
PluginCall::Signature => (PluginCall::Signature, Default::default()),
|
||||||
PluginCall::CustomValueOp(value, op) => {
|
PluginCall::CustomValueOp(value, op) => {
|
||||||
(PluginCall::CustomValueOp(value, op), Default::default())
|
(PluginCall::CustomValueOp(value, op), Default::default())
|
||||||
@ -913,6 +914,17 @@ impl PluginInterface {
|
|||||||
self.receive_plugin_call_response(result.receiver, context, result.state)
|
self.receive_plugin_call_response(result.receiver, context, result.state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the metadata from the plugin.
|
||||||
|
pub fn get_metadata(&self) -> Result<PluginMetadata, ShellError> {
|
||||||
|
match self.plugin_call(PluginCall::Metadata, None)? {
|
||||||
|
PluginCallResponse::Metadata(meta) => Ok(meta),
|
||||||
|
PluginCallResponse::Error(err) => Err(err.into()),
|
||||||
|
_ => Err(ShellError::PluginFailedToDecode {
|
||||||
|
msg: "Received unexpected response to plugin Metadata call".into(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the command signatures from the plugin.
|
/// Get the command signatures from the plugin.
|
||||||
pub fn get_signature(&self) -> Result<Vec<PluginSignature>, ShellError> {
|
pub fn get_signature(&self) -> Result<Vec<PluginSignature>, ShellError> {
|
||||||
match self.plugin_call(PluginCall::Signature, None)? {
|
match self.plugin_call(PluginCall::Signature, None)? {
|
||||||
@ -1206,6 +1218,7 @@ impl CurrentCallState {
|
|||||||
source: &PluginSource,
|
source: &PluginSource,
|
||||||
) -> Result<(), ShellError> {
|
) -> Result<(), ShellError> {
|
||||||
match call {
|
match call {
|
||||||
|
PluginCall::Metadata => Ok(()),
|
||||||
PluginCall::Signature => Ok(()),
|
PluginCall::Signature => Ok(()),
|
||||||
PluginCall::Run(CallInfo { call, .. }) => self.prepare_call_args(call, source),
|
PluginCall::Run(CallInfo { call, .. }) => self.prepare_call_args(call, source),
|
||||||
PluginCall::CustomValueOp(_, op) => {
|
PluginCall::CustomValueOp(_, op) => {
|
||||||
|
@ -18,7 +18,7 @@ use nu_protocol::{
|
|||||||
ast::{Math, Operator},
|
ast::{Math, Operator},
|
||||||
engine::Closure,
|
engine::Closure,
|
||||||
ByteStreamType, CustomValue, IntoInterruptiblePipelineData, IntoSpanned, PipelineData,
|
ByteStreamType, CustomValue, IntoInterruptiblePipelineData, IntoSpanned, PipelineData,
|
||||||
PluginSignature, ShellError, Span, Spanned, Value,
|
PluginMetadata, PluginSignature, ShellError, Span, Spanned, Value,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
@ -1019,6 +1019,25 @@ fn start_fake_plugin_call_responder(
|
|||||||
.expect("failed to spawn thread");
|
.expect("failed to spawn thread");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn interface_get_metadata() -> Result<(), ShellError> {
|
||||||
|
let test = TestCase::new();
|
||||||
|
let manager = test.plugin("test");
|
||||||
|
let interface = manager.get_interface();
|
||||||
|
|
||||||
|
start_fake_plugin_call_responder(manager, 1, |_| {
|
||||||
|
vec![ReceivedPluginCallMessage::Response(
|
||||||
|
PluginCallResponse::Metadata(PluginMetadata::new().with_version("test")),
|
||||||
|
)]
|
||||||
|
});
|
||||||
|
|
||||||
|
let metadata = interface.get_metadata()?;
|
||||||
|
|
||||||
|
assert_eq!(Some("test"), metadata.version.as_deref());
|
||||||
|
assert!(test.has_unconsumed_write());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn interface_get_signature() -> Result<(), ShellError> {
|
fn interface_get_signature() -> Result<(), ShellError> {
|
||||||
let test = TestCase::new();
|
let test = TestCase::new();
|
||||||
|
@ -7,7 +7,7 @@ use super::{PluginInterface, PluginSource};
|
|||||||
use nu_plugin_core::CommunicationMode;
|
use nu_plugin_core::CommunicationMode;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, Stack},
|
engine::{EngineState, Stack},
|
||||||
PluginGcConfig, PluginIdentity, RegisteredPlugin, ShellError,
|
PluginGcConfig, PluginIdentity, PluginMetadata, RegisteredPlugin, ShellError,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
@ -31,6 +31,8 @@ pub struct PersistentPlugin {
|
|||||||
struct MutableState {
|
struct MutableState {
|
||||||
/// Reference to the plugin if running
|
/// Reference to the plugin if running
|
||||||
running: Option<RunningPlugin>,
|
running: Option<RunningPlugin>,
|
||||||
|
/// Metadata for the plugin, e.g. version.
|
||||||
|
metadata: Option<PluginMetadata>,
|
||||||
/// Plugin's preferred communication mode (if known)
|
/// Plugin's preferred communication mode (if known)
|
||||||
preferred_mode: Option<PreferredCommunicationMode>,
|
preferred_mode: Option<PreferredCommunicationMode>,
|
||||||
/// Garbage collector config
|
/// Garbage collector config
|
||||||
@ -59,6 +61,7 @@ impl PersistentPlugin {
|
|||||||
identity,
|
identity,
|
||||||
mutable: Mutex::new(MutableState {
|
mutable: Mutex::new(MutableState {
|
||||||
running: None,
|
running: None,
|
||||||
|
metadata: None,
|
||||||
preferred_mode: None,
|
preferred_mode: None,
|
||||||
gc_config,
|
gc_config,
|
||||||
}),
|
}),
|
||||||
@ -268,6 +271,16 @@ impl RegisteredPlugin for PersistentPlugin {
|
|||||||
self.stop_internal(true)
|
self.stop_internal(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn metadata(&self) -> Option<PluginMetadata> {
|
||||||
|
self.mutable.lock().ok().and_then(|m| m.metadata.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_metadata(&self, metadata: Option<PluginMetadata>) {
|
||||||
|
if let Ok(mut mutable) = self.mutable.lock() {
|
||||||
|
mutable.metadata = metadata;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn set_gc_config(&self, gc_config: &PluginGcConfig) {
|
fn set_gc_config(&self, gc_config: &PluginGcConfig) {
|
||||||
if let Ok(mut mutable) = self.mutable.lock() {
|
if let Ok(mut mutable) = self.mutable.lock() {
|
||||||
// Save the new config for future calls
|
// Save the new config for future calls
|
||||||
|
@ -23,7 +23,7 @@ pub mod test_util;
|
|||||||
|
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Operator, engine::Closure, ByteStreamType, Config, LabeledError, PipelineData,
|
ast::Operator, engine::Closure, ByteStreamType, Config, LabeledError, PipelineData,
|
||||||
PluginSignature, ShellError, Span, Spanned, Value,
|
PluginMetadata, PluginSignature, ShellError, Span, Spanned, Value,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@ -119,6 +119,7 @@ pub struct ByteStreamInfo {
|
|||||||
/// Calls that a plugin can execute. The type parameter determines the input type.
|
/// Calls that a plugin can execute. The type parameter determines the input type.
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub enum PluginCall<D> {
|
pub enum PluginCall<D> {
|
||||||
|
Metadata,
|
||||||
Signature,
|
Signature,
|
||||||
Run(CallInfo<D>),
|
Run(CallInfo<D>),
|
||||||
CustomValueOp(Spanned<PluginCustomValue>, CustomValueOp),
|
CustomValueOp(Spanned<PluginCustomValue>, CustomValueOp),
|
||||||
@ -132,6 +133,7 @@ impl<D> PluginCall<D> {
|
|||||||
f: impl FnOnce(D) -> Result<T, ShellError>,
|
f: impl FnOnce(D) -> Result<T, ShellError>,
|
||||||
) -> Result<PluginCall<T>, ShellError> {
|
) -> Result<PluginCall<T>, ShellError> {
|
||||||
Ok(match self {
|
Ok(match self {
|
||||||
|
PluginCall::Metadata => PluginCall::Metadata,
|
||||||
PluginCall::Signature => PluginCall::Signature,
|
PluginCall::Signature => PluginCall::Signature,
|
||||||
PluginCall::Run(call) => PluginCall::Run(call.map_data(f)?),
|
PluginCall::Run(call) => PluginCall::Run(call.map_data(f)?),
|
||||||
PluginCall::CustomValueOp(custom_value, op) => {
|
PluginCall::CustomValueOp(custom_value, op) => {
|
||||||
@ -143,6 +145,7 @@ impl<D> PluginCall<D> {
|
|||||||
/// The span associated with the call.
|
/// The span associated with the call.
|
||||||
pub fn span(&self) -> Option<Span> {
|
pub fn span(&self) -> Option<Span> {
|
||||||
match self {
|
match self {
|
||||||
|
PluginCall::Metadata => None,
|
||||||
PluginCall::Signature => None,
|
PluginCall::Signature => None,
|
||||||
PluginCall::Run(CallInfo { call, .. }) => Some(call.head),
|
PluginCall::Run(CallInfo { call, .. }) => Some(call.head),
|
||||||
PluginCall::CustomValueOp(val, _) => Some(val.span),
|
PluginCall::CustomValueOp(val, _) => Some(val.span),
|
||||||
@ -309,6 +312,7 @@ pub enum StreamMessage {
|
|||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub enum PluginCallResponse<D> {
|
pub enum PluginCallResponse<D> {
|
||||||
Error(LabeledError),
|
Error(LabeledError),
|
||||||
|
Metadata(PluginMetadata),
|
||||||
Signature(Vec<PluginSignature>),
|
Signature(Vec<PluginSignature>),
|
||||||
Ordering(Option<Ordering>),
|
Ordering(Option<Ordering>),
|
||||||
PipelineData(D),
|
PipelineData(D),
|
||||||
@ -323,6 +327,7 @@ impl<D> PluginCallResponse<D> {
|
|||||||
) -> Result<PluginCallResponse<T>, ShellError> {
|
) -> Result<PluginCallResponse<T>, ShellError> {
|
||||||
Ok(match self {
|
Ok(match self {
|
||||||
PluginCallResponse::Error(err) => PluginCallResponse::Error(err),
|
PluginCallResponse::Error(err) => PluginCallResponse::Error(err),
|
||||||
|
PluginCallResponse::Metadata(meta) => PluginCallResponse::Metadata(meta),
|
||||||
PluginCallResponse::Signature(sigs) => PluginCallResponse::Signature(sigs),
|
PluginCallResponse::Signature(sigs) => PluginCallResponse::Signature(sigs),
|
||||||
PluginCallResponse::Ordering(ordering) => PluginCallResponse::Ordering(ordering),
|
PluginCallResponse::Ordering(ordering) => PluginCallResponse::Ordering(ordering),
|
||||||
PluginCallResponse::PipelineData(input) => PluginCallResponse::PipelineData(f(input)?),
|
PluginCallResponse::PipelineData(input) => PluginCallResponse::PipelineData(f(input)?),
|
||||||
|
@ -6,7 +6,7 @@ use std::{
|
|||||||
use nu_plugin_engine::{GetPlugin, PluginInterface};
|
use nu_plugin_engine::{GetPlugin, PluginInterface};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, Stack},
|
engine::{EngineState, Stack},
|
||||||
PluginGcConfig, PluginIdentity, RegisteredPlugin, ShellError,
|
PluginGcConfig, PluginIdentity, PluginMetadata, RegisteredPlugin, ShellError,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct FakePersistentPlugin {
|
pub struct FakePersistentPlugin {
|
||||||
@ -42,6 +42,12 @@ impl RegisteredPlugin for FakePersistentPlugin {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn metadata(&self) -> Option<PluginMetadata> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_metadata(&self, _metadata: Option<PluginMetadata>) {}
|
||||||
|
|
||||||
fn set_gc_config(&self, _gc_config: &PluginGcConfig) {
|
fn set_gc_config(&self, _gc_config: &PluginGcConfig) {
|
||||||
// We don't have a GC
|
// We don't have a GC
|
||||||
}
|
}
|
||||||
|
@ -66,6 +66,10 @@
|
|||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! impl Plugin for LowercasePlugin {
|
//! impl Plugin for LowercasePlugin {
|
||||||
|
//! fn version(&self) -> String {
|
||||||
|
//! env!("CARGO_PKG_VERSION").into()
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
//! fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin=Self>>> {
|
//! fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin=Self>>> {
|
||||||
//! vec![Box::new(Lowercase)]
|
//! vec![Box::new(Lowercase)]
|
||||||
//! }
|
//! }
|
||||||
|
@ -53,6 +53,10 @@ struct IntoU32;
|
|||||||
struct IntoIntFromU32;
|
struct IntoIntFromU32;
|
||||||
|
|
||||||
impl Plugin for CustomU32Plugin {
|
impl Plugin for CustomU32Plugin {
|
||||||
|
fn version(&self) -> String {
|
||||||
|
"0.0.0".into()
|
||||||
|
}
|
||||||
|
|
||||||
fn commands(&self) -> Vec<Box<dyn nu_plugin::PluginCommand<Plugin = Self>>> {
|
fn commands(&self) -> Vec<Box<dyn nu_plugin::PluginCommand<Plugin = Self>>> {
|
||||||
vec![Box::new(IntoU32), Box::new(IntoIntFromU32)]
|
vec![Box::new(IntoU32), Box::new(IntoIntFromU32)]
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,10 @@ struct HelloPlugin;
|
|||||||
struct Hello;
|
struct Hello;
|
||||||
|
|
||||||
impl Plugin for HelloPlugin {
|
impl Plugin for HelloPlugin {
|
||||||
|
fn version(&self) -> String {
|
||||||
|
"0.0.0".into()
|
||||||
|
}
|
||||||
|
|
||||||
fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> {
|
fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> {
|
||||||
vec![Box::new(Hello)]
|
vec![Box::new(Hello)]
|
||||||
}
|
}
|
||||||
|
@ -59,6 +59,10 @@ impl PluginCommand for Lowercase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Plugin for LowercasePlugin {
|
impl Plugin for LowercasePlugin {
|
||||||
|
fn version(&self) -> String {
|
||||||
|
"0.0.0".into()
|
||||||
|
}
|
||||||
|
|
||||||
fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> {
|
fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> {
|
||||||
vec![Box::new(Lowercase)]
|
vec![Box::new(Lowercase)]
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,10 @@
|
|||||||
//! struct MyCommand;
|
//! struct MyCommand;
|
||||||
//!
|
//!
|
||||||
//! impl Plugin for MyPlugin {
|
//! impl Plugin for MyPlugin {
|
||||||
|
//! fn version(&self) -> String {
|
||||||
|
//! env!("CARGO_PKG_VERSION").into()
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
//! fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> {
|
//! fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> {
|
||||||
//! vec![Box::new(MyCommand)]
|
//! vec![Box::new(MyCommand)]
|
||||||
//! }
|
//! }
|
||||||
|
@ -60,6 +60,9 @@ use crate::{EngineInterface, EvaluatedCall, Plugin};
|
|||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// # impl Plugin for LowercasePlugin {
|
/// # impl Plugin for LowercasePlugin {
|
||||||
|
/// # fn version(&self) -> String {
|
||||||
|
/// # "0.0.0".into()
|
||||||
|
/// # }
|
||||||
/// # fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin=Self>>> {
|
/// # fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin=Self>>> {
|
||||||
/// # vec![Box::new(Lowercase)]
|
/// # vec![Box::new(Lowercase)]
|
||||||
/// # }
|
/// # }
|
||||||
@ -195,6 +198,9 @@ pub trait PluginCommand: Sync {
|
|||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// # impl Plugin for HelloPlugin {
|
/// # impl Plugin for HelloPlugin {
|
||||||
|
/// # fn version(&self) -> String {
|
||||||
|
/// # "0.0.0".into()
|
||||||
|
/// # }
|
||||||
/// # fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin=Self>>> {
|
/// # fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin=Self>>> {
|
||||||
/// # vec![Box::new(Hello)]
|
/// # vec![Box::new(Hello)]
|
||||||
/// # }
|
/// # }
|
||||||
|
@ -11,8 +11,8 @@ use nu_plugin_protocol::{
|
|||||||
ProtocolInfo,
|
ProtocolInfo,
|
||||||
};
|
};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::Closure, Config, LabeledError, PipelineData, PluginSignature, ShellError, Span,
|
engine::Closure, Config, LabeledError, PipelineData, PluginMetadata, PluginSignature,
|
||||||
Spanned, Value,
|
ShellError, Span, Spanned, Value,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
collections::{btree_map, BTreeMap, HashMap},
|
collections::{btree_map, BTreeMap, HashMap},
|
||||||
@ -29,6 +29,9 @@ use std::{
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub enum ReceivedPluginCall {
|
pub enum ReceivedPluginCall {
|
||||||
|
Metadata {
|
||||||
|
engine: EngineInterface,
|
||||||
|
},
|
||||||
Signature {
|
Signature {
|
||||||
engine: EngineInterface,
|
engine: EngineInterface,
|
||||||
},
|
},
|
||||||
@ -280,8 +283,11 @@ impl InterfaceManager for EngineInterfaceManager {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
match call {
|
match call {
|
||||||
// We just let the receiver handle it rather than trying to store signature here
|
// Ask the plugin for metadata
|
||||||
// or something
|
PluginCall::Metadata => {
|
||||||
|
self.send_plugin_call(ReceivedPluginCall::Metadata { engine: interface })
|
||||||
|
}
|
||||||
|
// Ask the plugin for signatures
|
||||||
PluginCall::Signature => {
|
PluginCall::Signature => {
|
||||||
self.send_plugin_call(ReceivedPluginCall::Signature { engine: interface })
|
self.send_plugin_call(ReceivedPluginCall::Signature { engine: interface })
|
||||||
}
|
}
|
||||||
@ -416,6 +422,13 @@ impl EngineInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Write a call response of plugin metadata.
|
||||||
|
pub(crate) fn write_metadata(&self, metadata: PluginMetadata) -> Result<(), ShellError> {
|
||||||
|
let response = PluginCallResponse::Metadata(metadata);
|
||||||
|
self.write(PluginOutput::CallResponse(self.context()?, response))?;
|
||||||
|
self.flush()
|
||||||
|
}
|
||||||
|
|
||||||
/// Write a call response of plugin signatures.
|
/// Write a call response of plugin signatures.
|
||||||
///
|
///
|
||||||
/// Any custom values in the examples will be rendered using `to_base_value()`.
|
/// Any custom values in the examples will be rendered using `to_base_value()`.
|
||||||
|
@ -322,6 +322,26 @@ fn manager_consume_goodbye_closes_plugin_call_channel() -> Result<(), ShellError
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn manager_consume_call_metadata_forwards_to_receiver_with_context() -> Result<(), ShellError> {
|
||||||
|
let mut manager = TestCase::new().engine();
|
||||||
|
set_default_protocol_info(&mut manager)?;
|
||||||
|
|
||||||
|
let rx = manager
|
||||||
|
.take_plugin_call_receiver()
|
||||||
|
.expect("couldn't take receiver");
|
||||||
|
|
||||||
|
manager.consume(PluginInput::Call(0, PluginCall::Metadata))?;
|
||||||
|
|
||||||
|
match rx.try_recv().expect("call was not forwarded to receiver") {
|
||||||
|
ReceivedPluginCall::Metadata { engine } => {
|
||||||
|
assert_eq!(Some(0), engine.context);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
call => panic!("wrong call type: {call:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn manager_consume_call_signature_forwards_to_receiver_with_context() -> Result<(), ShellError> {
|
fn manager_consume_call_signature_forwards_to_receiver_with_context() -> Result<(), ShellError> {
|
||||||
let mut manager = TestCase::new().engine();
|
let mut manager = TestCase::new().engine();
|
||||||
|
@ -16,7 +16,8 @@ use nu_plugin_core::{
|
|||||||
};
|
};
|
||||||
use nu_plugin_protocol::{CallInfo, CustomValueOp, PluginCustomValue, PluginInput, PluginOutput};
|
use nu_plugin_protocol::{CallInfo, CustomValueOp, PluginCustomValue, PluginInput, PluginOutput};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Operator, CustomValue, IntoSpanned, LabeledError, PipelineData, ShellError, Spanned, Value,
|
ast::Operator, CustomValue, IntoSpanned, LabeledError, PipelineData, PluginMetadata,
|
||||||
|
ShellError, Spanned, Value,
|
||||||
};
|
};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
@ -52,6 +53,10 @@ pub(crate) const OUTPUT_BUFFER_SIZE: usize = 16384;
|
|||||||
/// struct Hello;
|
/// struct Hello;
|
||||||
///
|
///
|
||||||
/// impl Plugin for HelloPlugin {
|
/// impl Plugin for HelloPlugin {
|
||||||
|
/// fn version(&self) -> String {
|
||||||
|
/// env!("CARGO_PKG_VERSION").into()
|
||||||
|
/// }
|
||||||
|
///
|
||||||
/// fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin=Self>>> {
|
/// fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin=Self>>> {
|
||||||
/// vec![Box::new(Hello)]
|
/// vec![Box::new(Hello)]
|
||||||
/// }
|
/// }
|
||||||
@ -89,6 +94,23 @@ pub(crate) const OUTPUT_BUFFER_SIZE: usize = 16384;
|
|||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
pub trait Plugin: Sync {
|
pub trait Plugin: Sync {
|
||||||
|
/// The version of the plugin.
|
||||||
|
///
|
||||||
|
/// The recommended implementation, which will use the version from your crate's `Cargo.toml`
|
||||||
|
/// file:
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// # use nu_plugin::{Plugin, PluginCommand};
|
||||||
|
/// # struct MyPlugin;
|
||||||
|
/// # impl Plugin for MyPlugin {
|
||||||
|
/// fn version(&self) -> String {
|
||||||
|
/// env!("CARGO_PKG_VERSION").into()
|
||||||
|
/// }
|
||||||
|
/// # fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> { vec![] }
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
fn version(&self) -> String;
|
||||||
|
|
||||||
/// The commands supported by the plugin
|
/// The commands supported by the plugin
|
||||||
///
|
///
|
||||||
/// Each [`PluginCommand`] contains both the signature of the command and the functionality it
|
/// Each [`PluginCommand`] contains both the signature of the command and the functionality it
|
||||||
@ -216,6 +238,7 @@ pub trait Plugin: Sync {
|
|||||||
/// # struct MyPlugin;
|
/// # struct MyPlugin;
|
||||||
/// # impl MyPlugin { fn new() -> Self { Self }}
|
/// # impl MyPlugin { fn new() -> Self { Self }}
|
||||||
/// # impl Plugin for MyPlugin {
|
/// # impl Plugin for MyPlugin {
|
||||||
|
/// # fn version(&self) -> String { "0.0.0".into() }
|
||||||
/// # fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin=Self>>> {todo!();}
|
/// # fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin=Self>>> {todo!();}
|
||||||
/// # }
|
/// # }
|
||||||
/// fn main() {
|
/// fn main() {
|
||||||
@ -504,6 +527,12 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
match plugin_call {
|
match plugin_call {
|
||||||
|
// Send metadata back to nushell so it can be stored with the plugin signatures
|
||||||
|
ReceivedPluginCall::Metadata { engine } => {
|
||||||
|
engine
|
||||||
|
.write_metadata(PluginMetadata::new().with_version(plugin.version()))
|
||||||
|
.try_to_report(&engine)?;
|
||||||
|
}
|
||||||
// Sending the signature back to nushell to create the declaration definition
|
// Sending the signature back to nushell to create the declaration definition
|
||||||
ReceivedPluginCall::Signature { engine } => {
|
ReceivedPluginCall::Signature { engine } => {
|
||||||
let sigs = commands
|
let sigs = commands
|
||||||
|
38
crates/nu-protocol/src/plugin/metadata.rs
Normal file
38
crates/nu-protocol/src/plugin/metadata.rs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// Metadata about the installed plugin. This is cached in the registry file along with the
|
||||||
|
/// signatures. None of the metadata fields are required, and more may be added in the future.
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct PluginMetadata {
|
||||||
|
/// The version of the plugin itself, as self-reported.
|
||||||
|
pub version: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PluginMetadata {
|
||||||
|
/// Create empty metadata.
|
||||||
|
pub const fn new() -> PluginMetadata {
|
||||||
|
PluginMetadata { version: None }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the version of the plugin on the metadata. A suggested way to construct this is:
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// # use nu_protocol::PluginMetadata;
|
||||||
|
/// # fn example() -> PluginMetadata {
|
||||||
|
/// PluginMetadata::new().with_version(env!("CARGO_PKG_VERSION"))
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// which will use the version of your plugin's crate from its `Cargo.toml` file.
|
||||||
|
pub fn with_version(mut self, version: impl Into<String>) -> Self {
|
||||||
|
self.version = Some(version.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PluginMetadata {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,11 @@
|
|||||||
mod identity;
|
mod identity;
|
||||||
|
mod metadata;
|
||||||
mod registered;
|
mod registered;
|
||||||
mod registry_file;
|
mod registry_file;
|
||||||
mod signature;
|
mod signature;
|
||||||
|
|
||||||
pub use identity::*;
|
pub use identity::*;
|
||||||
|
pub use metadata::*;
|
||||||
pub use registered::*;
|
pub use registered::*;
|
||||||
pub use registry_file::*;
|
pub use registry_file::*;
|
||||||
pub use signature::*;
|
pub use signature::*;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use std::{any::Any, sync::Arc};
|
use std::{any::Any, sync::Arc};
|
||||||
|
|
||||||
use crate::{PluginGcConfig, PluginIdentity, ShellError};
|
use crate::{PluginGcConfig, PluginIdentity, PluginMetadata, ShellError};
|
||||||
|
|
||||||
/// Trait for plugins registered in the [`EngineState`](crate::engine::EngineState).
|
/// Trait for plugins registered in the [`EngineState`](crate::engine::EngineState).
|
||||||
pub trait RegisteredPlugin: Send + Sync {
|
pub trait RegisteredPlugin: Send + Sync {
|
||||||
@ -13,6 +13,12 @@ pub trait RegisteredPlugin: Send + Sync {
|
|||||||
/// Process ID of the plugin executable, if running.
|
/// Process ID of the plugin executable, if running.
|
||||||
fn pid(&self) -> Option<u32>;
|
fn pid(&self) -> Option<u32>;
|
||||||
|
|
||||||
|
/// Get metadata for the plugin, if set.
|
||||||
|
fn metadata(&self) -> Option<PluginMetadata>;
|
||||||
|
|
||||||
|
/// Set metadata for the plugin.
|
||||||
|
fn set_metadata(&self, metadata: Option<PluginMetadata>);
|
||||||
|
|
||||||
/// Set garbage collection config for the plugin.
|
/// Set garbage collection config for the plugin.
|
||||||
fn set_gc_config(&self, gc_config: &PluginGcConfig);
|
fn set_gc_config(&self, gc_config: &PluginGcConfig);
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ use std::{
|
|||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{PluginIdentity, PluginSignature, ShellError, Span};
|
use crate::{PluginIdentity, PluginMetadata, PluginSignature, ShellError, Span};
|
||||||
|
|
||||||
// This has a big impact on performance
|
// This has a big impact on performance
|
||||||
const BUFFER_SIZE: usize = 65536;
|
const BUFFER_SIZE: usize = 65536;
|
||||||
@ -121,9 +121,10 @@ pub struct PluginRegistryItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl PluginRegistryItem {
|
impl PluginRegistryItem {
|
||||||
/// Create a [`PluginRegistryItem`] from an identity and signatures.
|
/// Create a [`PluginRegistryItem`] from an identity, metadata, and signatures.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
identity: &PluginIdentity,
|
identity: &PluginIdentity,
|
||||||
|
metadata: PluginMetadata,
|
||||||
mut commands: Vec<PluginSignature>,
|
mut commands: Vec<PluginSignature>,
|
||||||
) -> PluginRegistryItem {
|
) -> PluginRegistryItem {
|
||||||
// Sort the commands for consistency
|
// Sort the commands for consistency
|
||||||
@ -133,7 +134,7 @@ impl PluginRegistryItem {
|
|||||||
name: identity.name().to_owned(),
|
name: identity.name().to_owned(),
|
||||||
filename: identity.filename().to_owned(),
|
filename: identity.filename().to_owned(),
|
||||||
shell: identity.shell().map(|p| p.to_owned()),
|
shell: identity.shell().map(|p| p.to_owned()),
|
||||||
data: PluginRegistryItemData::Valid { commands },
|
data: PluginRegistryItemData::Valid { metadata, commands },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -144,6 +145,9 @@ impl PluginRegistryItem {
|
|||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum PluginRegistryItemData {
|
pub enum PluginRegistryItemData {
|
||||||
Valid {
|
Valid {
|
||||||
|
/// Metadata for the plugin, including its version.
|
||||||
|
#[serde(default)]
|
||||||
|
metadata: PluginMetadata,
|
||||||
/// Signatures and examples for each command provided by the plugin.
|
/// Signatures and examples for each command provided by the plugin.
|
||||||
commands: Vec<PluginSignature>,
|
commands: Vec<PluginSignature>,
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use super::{PluginRegistryFile, PluginRegistryItem, PluginRegistryItemData};
|
use super::{PluginRegistryFile, PluginRegistryItem, PluginRegistryItemData};
|
||||||
use crate::{
|
use crate::{
|
||||||
Category, PluginExample, PluginSignature, ShellError, Signature, SyntaxShape, Type, Value,
|
Category, PluginExample, PluginMetadata, PluginSignature, ShellError, Signature, SyntaxShape,
|
||||||
|
Type, Value,
|
||||||
};
|
};
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
@ -11,6 +12,9 @@ fn foo_plugin() -> PluginRegistryItem {
|
|||||||
filename: "/path/to/nu_plugin_foo".into(),
|
filename: "/path/to/nu_plugin_foo".into(),
|
||||||
shell: None,
|
shell: None,
|
||||||
data: PluginRegistryItemData::Valid {
|
data: PluginRegistryItemData::Valid {
|
||||||
|
metadata: PluginMetadata {
|
||||||
|
version: Some("0.1.0".into()),
|
||||||
|
},
|
||||||
commands: vec![PluginSignature {
|
commands: vec![PluginSignature {
|
||||||
sig: Signature::new("foo")
|
sig: Signature::new("foo")
|
||||||
.input_output_type(Type::Int, Type::List(Box::new(Type::Int)))
|
.input_output_type(Type::Int, Type::List(Box::new(Type::Int)))
|
||||||
@ -36,6 +40,9 @@ fn bar_plugin() -> PluginRegistryItem {
|
|||||||
filename: "/path/to/nu_plugin_bar".into(),
|
filename: "/path/to/nu_plugin_bar".into(),
|
||||||
shell: None,
|
shell: None,
|
||||||
data: PluginRegistryItemData::Valid {
|
data: PluginRegistryItemData::Valid {
|
||||||
|
metadata: PluginMetadata {
|
||||||
|
version: Some("0.2.0".into()),
|
||||||
|
},
|
||||||
commands: vec![PluginSignature {
|
commands: vec![PluginSignature {
|
||||||
sig: Signature::new("bar")
|
sig: Signature::new("bar")
|
||||||
.usage("overwrites files with random data")
|
.usage("overwrites files with random data")
|
||||||
|
@ -42,6 +42,10 @@ impl CustomValuePlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Plugin for CustomValuePlugin {
|
impl Plugin for CustomValuePlugin {
|
||||||
|
fn version(&self) -> String {
|
||||||
|
env!("CARGO_PKG_VERSION").into()
|
||||||
|
}
|
||||||
|
|
||||||
fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> {
|
fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> {
|
||||||
vec![
|
vec![
|
||||||
Box::new(Generate),
|
Box::new(Generate),
|
||||||
|
@ -7,6 +7,10 @@ pub use commands::*;
|
|||||||
pub use example::ExamplePlugin;
|
pub use example::ExamplePlugin;
|
||||||
|
|
||||||
impl Plugin for ExamplePlugin {
|
impl Plugin for ExamplePlugin {
|
||||||
|
fn version(&self) -> String {
|
||||||
|
env!("CARGO_PKG_VERSION").into()
|
||||||
|
}
|
||||||
|
|
||||||
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.
|
||||||
|
@ -10,6 +10,10 @@ pub use from::vcf::FromVcf;
|
|||||||
pub struct FromCmds;
|
pub struct FromCmds;
|
||||||
|
|
||||||
impl Plugin for FromCmds {
|
impl Plugin for FromCmds {
|
||||||
|
fn version(&self) -> String {
|
||||||
|
env!("CARGO_PKG_VERSION").into()
|
||||||
|
}
|
||||||
|
|
||||||
fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> {
|
fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> {
|
||||||
vec![
|
vec![
|
||||||
Box::new(FromEml),
|
Box::new(FromEml),
|
||||||
|
@ -5,6 +5,10 @@ use nu_protocol::{Category, LabeledError, Signature, Spanned, SyntaxShape, Value
|
|||||||
pub struct GStatPlugin;
|
pub struct GStatPlugin;
|
||||||
|
|
||||||
impl Plugin for GStatPlugin {
|
impl Plugin for GStatPlugin {
|
||||||
|
fn version(&self) -> String {
|
||||||
|
env!("CARGO_PKG_VERSION").into()
|
||||||
|
}
|
||||||
|
|
||||||
fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> {
|
fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> {
|
||||||
vec![Box::new(GStat)]
|
vec![Box::new(GStat)]
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,10 @@ use nu_protocol::{ast::CellPath, LabeledError, Signature, SyntaxShape, Value};
|
|||||||
pub struct IncPlugin;
|
pub struct IncPlugin;
|
||||||
|
|
||||||
impl Plugin for IncPlugin {
|
impl Plugin for IncPlugin {
|
||||||
|
fn version(&self) -> String {
|
||||||
|
env!("CARGO_PKG_VERSION").into()
|
||||||
|
}
|
||||||
|
|
||||||
fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> {
|
fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> {
|
||||||
vec![Box::new(Inc::new())]
|
vec![Box::new(Inc::new())]
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
# language without adding any extra dependencies to our tests.
|
# language without adding any extra dependencies to our tests.
|
||||||
|
|
||||||
const NUSHELL_VERSION = "0.94.3"
|
const NUSHELL_VERSION = "0.94.3"
|
||||||
|
const PLUGIN_VERSION = "0.1.0" # bump if you change commands!
|
||||||
|
|
||||||
def main [--stdio] {
|
def main [--stdio] {
|
||||||
if ($stdio) {
|
if ($stdio) {
|
||||||
@ -229,6 +230,13 @@ def handle_input []: any -> nothing {
|
|||||||
}
|
}
|
||||||
{ Call: [$id, $plugin_call] } => {
|
{ Call: [$id, $plugin_call] } => {
|
||||||
match $plugin_call {
|
match $plugin_call {
|
||||||
|
"Metadata" => {
|
||||||
|
write_response $id {
|
||||||
|
Metadata: {
|
||||||
|
version: $PLUGIN_VERSION
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
"Signature" => {
|
"Signature" => {
|
||||||
write_response $id { Signature: $SIGNATURES }
|
write_response $id { Signature: $SIGNATURES }
|
||||||
}
|
}
|
||||||
|
@ -99,6 +99,10 @@ pub struct PolarsPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Plugin for PolarsPlugin {
|
impl Plugin for PolarsPlugin {
|
||||||
|
fn version(&self) -> String {
|
||||||
|
env!("CARGO_PKG_VERSION").into()
|
||||||
|
}
|
||||||
|
|
||||||
fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> {
|
fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> {
|
||||||
let mut commands: Vec<Box<dyn PluginCommand<Plugin = Self>>> = vec![Box::new(PolarsCmd)];
|
let mut commands: Vec<Box<dyn PluginCommand<Plugin = Self>>> = vec![Box::new(PolarsCmd)];
|
||||||
commands.append(&mut eager_commands());
|
commands.append(&mut eager_commands());
|
||||||
|
@ -28,6 +28,7 @@ import json
|
|||||||
|
|
||||||
|
|
||||||
NUSHELL_VERSION = "0.94.3"
|
NUSHELL_VERSION = "0.94.3"
|
||||||
|
PLUGIN_VERSION = "0.1.0" # bump if you change commands!
|
||||||
|
|
||||||
|
|
||||||
def signatures():
|
def signatures():
|
||||||
@ -228,7 +229,13 @@ def handle_input(input):
|
|||||||
exit(0)
|
exit(0)
|
||||||
elif "Call" in input:
|
elif "Call" in input:
|
||||||
[id, plugin_call] = input["Call"]
|
[id, plugin_call] = input["Call"]
|
||||||
if plugin_call == "Signature":
|
if plugin_call == "Metadata":
|
||||||
|
write_response(id, {
|
||||||
|
"Metadata": {
|
||||||
|
"version": PLUGIN_VERSION,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
elif plugin_call == "Signature":
|
||||||
write_response(id, signatures())
|
write_response(id, signatures())
|
||||||
elif "Run" in plugin_call:
|
elif "Run" in plugin_call:
|
||||||
process_call(id, plugin_call["Run"])
|
process_call(id, plugin_call["Run"])
|
||||||
|
@ -16,6 +16,10 @@ impl Query {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Plugin for Query {
|
impl Plugin for Query {
|
||||||
|
fn version(&self) -> String {
|
||||||
|
env!("CARGO_PKG_VERSION").into()
|
||||||
|
}
|
||||||
|
|
||||||
fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> {
|
fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> {
|
||||||
vec![
|
vec![
|
||||||
Box::new(QueryCommand),
|
Box::new(QueryCommand),
|
||||||
|
@ -136,7 +136,21 @@ fn handle_message(
|
|||||||
) -> Result<(), Box<dyn Error>> {
|
) -> Result<(), Box<dyn Error>> {
|
||||||
if let Some(plugin_call) = message.get("Call") {
|
if let Some(plugin_call) = message.get("Call") {
|
||||||
let (id, plugin_call) = (&plugin_call[0], &plugin_call[1]);
|
let (id, plugin_call) = (&plugin_call[0], &plugin_call[1]);
|
||||||
if plugin_call.as_str() == Some("Signature") {
|
if plugin_call.as_str() == Some("Metadata") {
|
||||||
|
write(
|
||||||
|
output,
|
||||||
|
&json!({
|
||||||
|
"CallResponse": [
|
||||||
|
id,
|
||||||
|
{
|
||||||
|
"Metadata": {
|
||||||
|
"version": env!("CARGO_PKG_VERSION"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
} else if plugin_call.as_str() == Some("Signature") {
|
||||||
write(
|
write(
|
||||||
output,
|
output,
|
||||||
&json!({
|
&json!({
|
||||||
|
12
src/main.rs
12
src/main.rs
@ -400,7 +400,7 @@ fn main() -> Result<()> {
|
|||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
if let Some(plugins) = &parsed_nu_cli_args.plugins {
|
if let Some(plugins) = &parsed_nu_cli_args.plugins {
|
||||||
use nu_plugin_engine::{GetPlugin, PluginDeclaration};
|
use nu_plugin_engine::{GetPlugin, PluginDeclaration};
|
||||||
use nu_protocol::{engine::StateWorkingSet, ErrSpan, PluginIdentity};
|
use nu_protocol::{engine::StateWorkingSet, ErrSpan, PluginIdentity, RegisteredPlugin};
|
||||||
|
|
||||||
// Load any plugins specified with --plugins
|
// Load any plugins specified with --plugins
|
||||||
start_time = std::time::Instant::now();
|
start_time = std::time::Instant::now();
|
||||||
@ -419,8 +419,14 @@ fn main() -> Result<()> {
|
|||||||
// Create the plugin and add it to the working set
|
// Create the plugin and add it to the working set
|
||||||
let plugin = nu_plugin_engine::add_plugin_to_working_set(&mut working_set, &identity)?;
|
let plugin = nu_plugin_engine::add_plugin_to_working_set(&mut working_set, &identity)?;
|
||||||
|
|
||||||
// Spawn the plugin to get its signatures, and then add the commands to the working set
|
// Spawn the plugin to get the metadata and signatures
|
||||||
for signature in plugin.clone().get_plugin(None)?.get_signature()? {
|
let interface = plugin.clone().get_plugin(None)?;
|
||||||
|
|
||||||
|
// Set its metadata
|
||||||
|
plugin.set_metadata(Some(interface.get_metadata()?));
|
||||||
|
|
||||||
|
// Add the commands from the signature to the working set
|
||||||
|
for signature in interface.get_signature()? {
|
||||||
let decl = PluginDeclaration::new(plugin.clone(), signature);
|
let decl = PluginDeclaration::new(plugin.clone(), signature);
|
||||||
working_set.add_decl(Box::new(decl));
|
working_set.add_decl(Box::new(decl));
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,17 @@ fn plugin_list_shows_installed_plugins() {
|
|||||||
assert!(out.status.success());
|
assert!(out.status.success());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn plugin_list_shows_installed_plugin_version() {
|
||||||
|
let out = nu_with_plugins!(
|
||||||
|
cwd: ".",
|
||||||
|
plugin: ("nu_plugin_inc"),
|
||||||
|
r#"(plugin list).version.0"#
|
||||||
|
);
|
||||||
|
assert_eq!(env!("CARGO_PKG_VERSION"), out.out);
|
||||||
|
assert!(out.status.success());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn plugin_keeps_running_after_calling_it() {
|
fn plugin_keeps_running_after_calling_it() {
|
||||||
let out = nu_with_plugins!(
|
let out = nu_with_plugins!(
|
||||||
|
@ -18,6 +18,13 @@ fn example_plugin_path() -> PathBuf {
|
|||||||
.expect("nu_plugin_example not found")
|
.expect("nu_plugin_example not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn valid_plugin_item_data() -> PluginRegistryItemData {
|
||||||
|
PluginRegistryItemData::Valid {
|
||||||
|
metadata: Default::default(),
|
||||||
|
commands: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn plugin_add_then_restart_nu() {
|
fn plugin_add_then_restart_nu() {
|
||||||
let result = nu_with_plugins!(
|
let result = nu_with_plugins!(
|
||||||
@ -149,7 +156,7 @@ fn plugin_rm_then_restart_nu() {
|
|||||||
name: "example".into(),
|
name: "example".into(),
|
||||||
filename: example_plugin_path,
|
filename: example_plugin_path,
|
||||||
shell: None,
|
shell: None,
|
||||||
data: PluginRegistryItemData::Valid { commands: vec![] },
|
data: valid_plugin_item_data(),
|
||||||
});
|
});
|
||||||
|
|
||||||
contents.upsert_plugin(PluginRegistryItem {
|
contents.upsert_plugin(PluginRegistryItem {
|
||||||
@ -157,7 +164,7 @@ fn plugin_rm_then_restart_nu() {
|
|||||||
// this doesn't exist, but it should be ok
|
// this doesn't exist, but it should be ok
|
||||||
filename: dirs.test().join("nu_plugin_foo"),
|
filename: dirs.test().join("nu_plugin_foo"),
|
||||||
shell: None,
|
shell: None,
|
||||||
data: PluginRegistryItemData::Valid { commands: vec![] },
|
data: valid_plugin_item_data(),
|
||||||
});
|
});
|
||||||
|
|
||||||
contents
|
contents
|
||||||
@ -225,7 +232,7 @@ fn plugin_rm_from_custom_path() {
|
|||||||
name: "example".into(),
|
name: "example".into(),
|
||||||
filename: example_plugin_path,
|
filename: example_plugin_path,
|
||||||
shell: None,
|
shell: None,
|
||||||
data: PluginRegistryItemData::Valid { commands: vec![] },
|
data: valid_plugin_item_data(),
|
||||||
});
|
});
|
||||||
|
|
||||||
contents.upsert_plugin(PluginRegistryItem {
|
contents.upsert_plugin(PluginRegistryItem {
|
||||||
@ -233,7 +240,7 @@ fn plugin_rm_from_custom_path() {
|
|||||||
// this doesn't exist, but it should be ok
|
// this doesn't exist, but it should be ok
|
||||||
filename: dirs.test().join("nu_plugin_foo"),
|
filename: dirs.test().join("nu_plugin_foo"),
|
||||||
shell: None,
|
shell: None,
|
||||||
data: PluginRegistryItemData::Valid { commands: vec![] },
|
data: valid_plugin_item_data(),
|
||||||
});
|
});
|
||||||
|
|
||||||
contents
|
contents
|
||||||
@ -273,7 +280,7 @@ fn plugin_rm_using_filename() {
|
|||||||
name: "example".into(),
|
name: "example".into(),
|
||||||
filename: example_plugin_path.clone(),
|
filename: example_plugin_path.clone(),
|
||||||
shell: None,
|
shell: None,
|
||||||
data: PluginRegistryItemData::Valid { commands: vec![] },
|
data: valid_plugin_item_data(),
|
||||||
});
|
});
|
||||||
|
|
||||||
contents.upsert_plugin(PluginRegistryItem {
|
contents.upsert_plugin(PluginRegistryItem {
|
||||||
@ -281,7 +288,7 @@ fn plugin_rm_using_filename() {
|
|||||||
// this doesn't exist, but it should be ok
|
// this doesn't exist, but it should be ok
|
||||||
filename: dirs.test().join("nu_plugin_foo"),
|
filename: dirs.test().join("nu_plugin_foo"),
|
||||||
shell: None,
|
shell: None,
|
||||||
data: PluginRegistryItemData::Valid { commands: vec![] },
|
data: valid_plugin_item_data(),
|
||||||
});
|
});
|
||||||
|
|
||||||
contents
|
contents
|
||||||
@ -331,7 +338,7 @@ fn warning_on_invalid_plugin_item() {
|
|||||||
name: "example".into(),
|
name: "example".into(),
|
||||||
filename: example_plugin_path,
|
filename: example_plugin_path,
|
||||||
shell: None,
|
shell: None,
|
||||||
data: PluginRegistryItemData::Valid { commands: vec![] },
|
data: valid_plugin_item_data(),
|
||||||
});
|
});
|
||||||
|
|
||||||
contents.upsert_plugin(PluginRegistryItem {
|
contents.upsert_plugin(PluginRegistryItem {
|
||||||
|
Loading…
Reference in New Issue
Block a user