forked from extern/nushell
Split the plugin crate (#12563)
# Description This breaks `nu-plugin` up into four crates: - `nu-plugin-protocol`: just the type definitions for the protocol, no I/O. If someone wanted to wire up something more bare metal, maybe for async I/O, they could use this. - `nu-plugin-core`: the shared stuff between engine/plugin. Less stable interface. - `nu-plugin-engine`: everything required for the engine to talk to plugins. Less stable interface. - `nu-plugin`: everything required for the plugin to talk to the engine, what plugin developers use. Should be the most stable interface. No changes are made to the interface exposed by `nu-plugin` - it should all still be there. Re-exports from `nu-plugin-protocol` or `nu-plugin-core` are used as required. Plugins shouldn't ever have to use those crates directly. This should be somewhat faster to compile as `nu-plugin-engine` and `nu-plugin` can compile in parallel, and the engine doesn't need `nu-plugin` and plugins don't need `nu-plugin-engine` (except for test support), so that should reduce what needs to be compiled too. The only significant change here other than splitting stuff up was to break the `source` out of `PluginCustomValue` and create a new `PluginCustomValueWithSource` type that contains that instead. One bonus of that is we get rid of the option and it's now more type-safe, but it also means that the logic for that stuff (actually running the plugin for custom value ops) can live entirely within the `nu-plugin-engine` crate. # User-Facing Changes - New crates. - Added `local-socket` feature for `nu` to try to make it possible to compile without that support if needed. # Tests + Formatting - 🟢 `toolkit fmt` - 🟢 `toolkit clippy` - 🟢 `toolkit test` - 🟢 `toolkit test stdlib`
This commit is contained in:
106
crates/nu-plugin-protocol/src/protocol_info.rs
Normal file
106
crates/nu-plugin-protocol/src/protocol_info.rs
Normal file
@@ -0,0 +1,106 @@
|
||||
use nu_protocol::ShellError;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Protocol information, sent as a `Hello` message on initialization. This determines the
|
||||
/// compatibility of the plugin and engine. They are considered to be compatible if the lower
|
||||
/// version is semver compatible with the higher one.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct ProtocolInfo {
|
||||
/// The name of the protocol being implemented. Only one protocol is supported. This field
|
||||
/// can be safely ignored, because not matching is a deserialization error
|
||||
pub protocol: Protocol,
|
||||
/// The semantic version of the protocol. This should be the version of the `nu-plugin-protocol`
|
||||
/// crate
|
||||
pub version: String,
|
||||
/// Supported optional features. This helps to maintain semver compatibility when adding new
|
||||
/// features
|
||||
pub features: Vec<Feature>,
|
||||
}
|
||||
|
||||
impl Default for ProtocolInfo {
|
||||
fn default() -> ProtocolInfo {
|
||||
ProtocolInfo {
|
||||
protocol: Protocol::NuPlugin,
|
||||
version: env!("CARGO_PKG_VERSION").into(),
|
||||
features: default_features(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolInfo {
|
||||
/// True if the version specified in `self` is compatible with the version specified in `other`.
|
||||
pub fn is_compatible_with(&self, other: &ProtocolInfo) -> Result<bool, ShellError> {
|
||||
fn parse_failed(error: semver::Error) -> ShellError {
|
||||
ShellError::PluginFailedToLoad {
|
||||
msg: format!("Failed to parse protocol version: {error}"),
|
||||
}
|
||||
}
|
||||
let mut versions = [
|
||||
semver::Version::parse(&self.version).map_err(parse_failed)?,
|
||||
semver::Version::parse(&other.version).map_err(parse_failed)?,
|
||||
];
|
||||
|
||||
versions.sort();
|
||||
|
||||
// For example, if the lower version is 1.1.0, and the higher version is 1.2.3, the
|
||||
// requirement is that 1.2.3 matches ^1.1.0 (which it does)
|
||||
Ok(semver::Comparator {
|
||||
op: semver::Op::Caret,
|
||||
major: versions[0].major,
|
||||
minor: Some(versions[0].minor),
|
||||
patch: Some(versions[0].patch),
|
||||
pre: versions[0].pre.clone(),
|
||||
}
|
||||
.matches(&versions[1]))
|
||||
}
|
||||
|
||||
/// True if the protocol info contains a feature compatible with the given feature.
|
||||
pub fn supports_feature(&self, feature: &Feature) -> bool {
|
||||
self.features.iter().any(|f| feature.is_compatible_with(f))
|
||||
}
|
||||
}
|
||||
|
||||
/// Indicates the protocol in use. Only one protocol is supported.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
pub enum Protocol {
|
||||
/// Serializes to the value `"nu-plugin"`
|
||||
#[serde(rename = "nu-plugin")]
|
||||
#[default]
|
||||
NuPlugin,
|
||||
}
|
||||
|
||||
/// Indicates optional protocol features. This can help to make non-breaking-change additions to
|
||||
/// the protocol. Features are not restricted to plain strings and can contain additional
|
||||
/// configuration data.
|
||||
///
|
||||
/// Optional features should not be used by the protocol if they are not present in the
|
||||
/// [`ProtocolInfo`] sent by the other side.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(tag = "name")]
|
||||
pub enum Feature {
|
||||
/// The plugin supports running with a local socket passed via `--local-socket` instead of
|
||||
/// stdio.
|
||||
LocalSocket,
|
||||
|
||||
/// A feature that was not recognized on deserialization. Attempting to serialize this feature
|
||||
/// is an error. Matching against it may only be used if necessary to determine whether
|
||||
/// unsupported features are present.
|
||||
#[serde(other, skip_serializing)]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl Feature {
|
||||
/// True if the feature is considered to be compatible with another feature.
|
||||
pub fn is_compatible_with(&self, other: &Feature) -> bool {
|
||||
matches!((self, other), (Feature::LocalSocket, Feature::LocalSocket))
|
||||
}
|
||||
}
|
||||
|
||||
/// Protocol features compiled into this version of `nu-plugin`.
|
||||
pub fn default_features() -> Vec<Feature> {
|
||||
vec![
|
||||
// Only available if compiled with the `local-socket` feature flag (enabled by default).
|
||||
#[cfg(feature = "local-socket")]
|
||||
Feature::LocalSocket,
|
||||
]
|
||||
}
|
Reference in New Issue
Block a user