mirror of
https://github.com/nushell/nushell.git
synced 2025-06-30 06:30:08 +02: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:
@ -24,6 +24,10 @@
|
||||
//! struct MyCommand;
|
||||
//!
|
||||
//! impl Plugin for MyPlugin {
|
||||
//! fn version(&self) -> String {
|
||||
//! env!("CARGO_PKG_VERSION").into()
|
||||
//! }
|
||||
//!
|
||||
//! fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> {
|
||||
//! vec![Box::new(MyCommand)]
|
||||
//! }
|
||||
|
@ -60,6 +60,9 @@ use crate::{EngineInterface, EvaluatedCall, Plugin};
|
||||
/// }
|
||||
///
|
||||
/// # impl Plugin for LowercasePlugin {
|
||||
/// # fn version(&self) -> String {
|
||||
/// # "0.0.0".into()
|
||||
/// # }
|
||||
/// # fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin=Self>>> {
|
||||
/// # vec![Box::new(Lowercase)]
|
||||
/// # }
|
||||
@ -195,6 +198,9 @@ pub trait PluginCommand: Sync {
|
||||
/// }
|
||||
///
|
||||
/// # impl Plugin for HelloPlugin {
|
||||
/// # fn version(&self) -> String {
|
||||
/// # "0.0.0".into()
|
||||
/// # }
|
||||
/// # fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin=Self>>> {
|
||||
/// # vec![Box::new(Hello)]
|
||||
/// # }
|
||||
|
@ -11,8 +11,8 @@ use nu_plugin_protocol::{
|
||||
ProtocolInfo,
|
||||
};
|
||||
use nu_protocol::{
|
||||
engine::Closure, Config, LabeledError, PipelineData, PluginSignature, ShellError, Span,
|
||||
Spanned, Value,
|
||||
engine::Closure, Config, LabeledError, PipelineData, PluginMetadata, PluginSignature,
|
||||
ShellError, Span, Spanned, Value,
|
||||
};
|
||||
use std::{
|
||||
collections::{btree_map, BTreeMap, HashMap},
|
||||
@ -29,6 +29,9 @@ use std::{
|
||||
#[derive(Debug)]
|
||||
#[doc(hidden)]
|
||||
pub enum ReceivedPluginCall {
|
||||
Metadata {
|
||||
engine: EngineInterface,
|
||||
},
|
||||
Signature {
|
||||
engine: EngineInterface,
|
||||
},
|
||||
@ -280,8 +283,11 @@ impl InterfaceManager for EngineInterfaceManager {
|
||||
}
|
||||
};
|
||||
match call {
|
||||
// We just let the receiver handle it rather than trying to store signature here
|
||||
// or something
|
||||
// Ask the plugin for metadata
|
||||
PluginCall::Metadata => {
|
||||
self.send_plugin_call(ReceivedPluginCall::Metadata { engine: interface })
|
||||
}
|
||||
// Ask the plugin for signatures
|
||||
PluginCall::Signature => {
|
||||
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.
|
||||
///
|
||||
/// 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(())
|
||||
}
|
||||
|
||||
#[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]
|
||||
fn manager_consume_call_signature_forwards_to_receiver_with_context() -> Result<(), ShellError> {
|
||||
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_protocol::{
|
||||
ast::Operator, CustomValue, IntoSpanned, LabeledError, PipelineData, ShellError, Spanned, Value,
|
||||
ast::Operator, CustomValue, IntoSpanned, LabeledError, PipelineData, PluginMetadata,
|
||||
ShellError, Spanned, Value,
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
@ -52,6 +53,10 @@ pub(crate) const OUTPUT_BUFFER_SIZE: usize = 16384;
|
||||
/// struct Hello;
|
||||
///
|
||||
/// impl Plugin for HelloPlugin {
|
||||
/// fn version(&self) -> String {
|
||||
/// env!("CARGO_PKG_VERSION").into()
|
||||
/// }
|
||||
///
|
||||
/// fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin=Self>>> {
|
||||
/// vec![Box::new(Hello)]
|
||||
/// }
|
||||
@ -89,6 +94,23 @@ pub(crate) const OUTPUT_BUFFER_SIZE: usize = 16384;
|
||||
/// # }
|
||||
/// ```
|
||||
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
|
||||
///
|
||||
/// Each [`PluginCommand`] contains both the signature of the command and the functionality it
|
||||
@ -216,6 +238,7 @@ pub trait Plugin: Sync {
|
||||
/// # struct MyPlugin;
|
||||
/// # impl MyPlugin { fn new() -> Self { Self }}
|
||||
/// # impl Plugin for MyPlugin {
|
||||
/// # fn version(&self) -> String { "0.0.0".into() }
|
||||
/// # fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin=Self>>> {todo!();}
|
||||
/// # }
|
||||
/// fn main() {
|
||||
@ -504,6 +527,12 @@ where
|
||||
}
|
||||
|
||||
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
|
||||
ReceivedPluginCall::Signature { engine } => {
|
||||
let sigs = commands
|
||||
|
Reference in New Issue
Block a user