2021-12-12 12:50:35 +01:00
|
|
|
mod evaluated_call;
|
2022-07-25 18:32:56 +02:00
|
|
|
mod plugin_custom_value;
|
2024-02-25 23:32:50 +01:00
|
|
|
mod protocol_info;
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests;
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
pub(crate) mod test_util;
|
2021-12-12 12:50:35 +01:00
|
|
|
|
Add support for engine calls from plugins (#12029)
# Description
This allows plugins to make calls back to the engine to get config,
evaluate closures, and do other things that must be done within the
engine process.
Engine calls can both produce and consume streams as necessary. Closures
passed to plugins can both accept stream input and produce stream output
sent back to the plugin.
Engine calls referring to a plugin call's context can be processed as
long either the response hasn't been received, or the response created
streams that haven't ended yet.
This is a breaking API change for plugins. There are some pretty major
changes to the interface that plugins must implement, including:
1. Plugins now run with `&self` and must be `Sync`. Executing multiple
plugin calls in parallel is supported, and there's a chance that a
closure passed to a plugin could invoke the same plugin. Supporting
state across plugin invocations is left up to the plugin author to do in
whichever way they feel best, but the plugin object itself is still
shared. Even though the engine doesn't run multiple plugin calls through
the same process yet, I still considered it important to break the API
in this way at this stage. We might want to consider an optional
threadpool feature for performance.
2. Plugins take a reference to `EngineInterface`, which can be cloned.
This interface allows plugins to make calls back to the engine,
including for getting config and running closures.
3. Plugins no longer take the `config` parameter. This can be accessed
from the interface via the `.get_plugin_config()` engine call.
# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
Not only does this have plugin protocol changes, it will require plugins
to make some code changes before they will work again. But on the plus
side, the engine call feature is extensible, and we can add more things
to it as needed.
Plugin maintainers will have to change the trait signature at the very
least. If they were using `config`, they will have to call
`engine.get_plugin_config()` instead.
If they were using the mutable reference to the plugin, they will have
to come up with some strategy to work around it (for example, for `Inc`
I just cloned it). This shouldn't be such a big deal at the moment as
it's not like plugins have ever run as daemons with persistent state in
the past, and they don't in this PR either. But I thought it was
important to make the change before we support plugins as daemons, as an
exclusive mutable reference is not compatible with parallel plugin
calls.
I suggest this gets merged sometime *after* the current pending release,
so that we have some time to adjust to the previous plugin protocol
changes that don't require code changes before making ones that do.
# Tests + Formatting
- :green_circle: `toolkit fmt`
- :green_circle: `toolkit clippy`
- :green_circle: `toolkit test`
- :green_circle: `toolkit test stdlib`
# After Submitting
I will document the additional protocol features (`EngineCall`,
`EngineCallResponse`), and constraints on plugin call processing if
engine calls are used - basically, to be aware that an engine call could
result in a nested plugin call, so the plugin should be able to handle
that.
2024-03-09 18:26:30 +01:00
|
|
|
use nu_protocol::{
|
2024-03-21 12:27:21 +01:00
|
|
|
ast::Operator, engine::Closure, Config, LabeledError, PipelineData, PluginSignature, RawStream,
|
|
|
|
ShellError, Span, Spanned, Value,
|
Add support for engine calls from plugins (#12029)
# Description
This allows plugins to make calls back to the engine to get config,
evaluate closures, and do other things that must be done within the
engine process.
Engine calls can both produce and consume streams as necessary. Closures
passed to plugins can both accept stream input and produce stream output
sent back to the plugin.
Engine calls referring to a plugin call's context can be processed as
long either the response hasn't been received, or the response created
streams that haven't ended yet.
This is a breaking API change for plugins. There are some pretty major
changes to the interface that plugins must implement, including:
1. Plugins now run with `&self` and must be `Sync`. Executing multiple
plugin calls in parallel is supported, and there's a chance that a
closure passed to a plugin could invoke the same plugin. Supporting
state across plugin invocations is left up to the plugin author to do in
whichever way they feel best, but the plugin object itself is still
shared. Even though the engine doesn't run multiple plugin calls through
the same process yet, I still considered it important to break the API
in this way at this stage. We might want to consider an optional
threadpool feature for performance.
2. Plugins take a reference to `EngineInterface`, which can be cloned.
This interface allows plugins to make calls back to the engine,
including for getting config and running closures.
3. Plugins no longer take the `config` parameter. This can be accessed
from the interface via the `.get_plugin_config()` engine call.
# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
Not only does this have plugin protocol changes, it will require plugins
to make some code changes before they will work again. But on the plus
side, the engine call feature is extensible, and we can add more things
to it as needed.
Plugin maintainers will have to change the trait signature at the very
least. If they were using `config`, they will have to call
`engine.get_plugin_config()` instead.
If they were using the mutable reference to the plugin, they will have
to come up with some strategy to work around it (for example, for `Inc`
I just cloned it). This shouldn't be such a big deal at the moment as
it's not like plugins have ever run as daemons with persistent state in
the past, and they don't in this PR either. But I thought it was
important to make the change before we support plugins as daemons, as an
exclusive mutable reference is not compatible with parallel plugin
calls.
I suggest this gets merged sometime *after* the current pending release,
so that we have some time to adjust to the previous plugin protocol
changes that don't require code changes before making ones that do.
# Tests + Formatting
- :green_circle: `toolkit fmt`
- :green_circle: `toolkit clippy`
- :green_circle: `toolkit test`
- :green_circle: `toolkit test stdlib`
# After Submitting
I will document the additional protocol features (`EngineCall`,
`EngineCallResponse`), and constraints on plugin call processing if
engine calls are used - basically, to be aware that an engine call could
result in a nested plugin call, so the plugin should be able to handle
that.
2024-03-09 18:26:30 +01:00
|
|
|
};
|
Add `command_prelude` module (#12291)
# Description
When implementing a `Command`, one must also import all the types
present in the function signatures for `Command`. This makes it so that
we often import the same set of types in each command implementation
file. E.g., something like this:
```rust
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
record, Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
ShellError, Signature, Span, Type, Value,
};
```
This PR adds the `nu_engine::command_prelude` module which contains the
necessary and commonly used types to implement a `Command`:
```rust
// command_prelude.rs
pub use crate::CallExt;
pub use nu_protocol::{
ast::{Call, CellPath},
engine::{Command, EngineState, Stack},
record, Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, IntoSpanned,
PipelineData, Record, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
};
```
This should reduce the boilerplate needed to implement a command and
also gives us a place to track the breadth of the `Command` API. I tried
to be conservative with what went into the prelude modules, since it
might be hard/annoying to remove items from the prelude in the future.
Let me know if something should be included or excluded.
2024-03-26 22:17:30 +01:00
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use std::collections::HashMap;
|
|
|
|
|
|
|
|
pub use evaluated_call::EvaluatedCall;
|
2022-07-25 18:32:56 +02:00
|
|
|
pub use plugin_custom_value::PluginCustomValue;
|
2024-02-25 23:32:50 +01:00
|
|
|
#[cfg(test)]
|
2024-03-14 15:34:00 +01:00
|
|
|
pub use protocol_info::Protocol;
|
|
|
|
pub use protocol_info::ProtocolInfo;
|
2024-02-25 23:32:50 +01:00
|
|
|
|
|
|
|
/// A sequential identifier for a stream
|
|
|
|
pub type StreamId = usize;
|
|
|
|
|
|
|
|
/// A sequential identifier for a [`PluginCall`]
|
|
|
|
pub type PluginCallId = usize;
|
|
|
|
|
Add support for engine calls from plugins (#12029)
# Description
This allows plugins to make calls back to the engine to get config,
evaluate closures, and do other things that must be done within the
engine process.
Engine calls can both produce and consume streams as necessary. Closures
passed to plugins can both accept stream input and produce stream output
sent back to the plugin.
Engine calls referring to a plugin call's context can be processed as
long either the response hasn't been received, or the response created
streams that haven't ended yet.
This is a breaking API change for plugins. There are some pretty major
changes to the interface that plugins must implement, including:
1. Plugins now run with `&self` and must be `Sync`. Executing multiple
plugin calls in parallel is supported, and there's a chance that a
closure passed to a plugin could invoke the same plugin. Supporting
state across plugin invocations is left up to the plugin author to do in
whichever way they feel best, but the plugin object itself is still
shared. Even though the engine doesn't run multiple plugin calls through
the same process yet, I still considered it important to break the API
in this way at this stage. We might want to consider an optional
threadpool feature for performance.
2. Plugins take a reference to `EngineInterface`, which can be cloned.
This interface allows plugins to make calls back to the engine,
including for getting config and running closures.
3. Plugins no longer take the `config` parameter. This can be accessed
from the interface via the `.get_plugin_config()` engine call.
# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
Not only does this have plugin protocol changes, it will require plugins
to make some code changes before they will work again. But on the plus
side, the engine call feature is extensible, and we can add more things
to it as needed.
Plugin maintainers will have to change the trait signature at the very
least. If they were using `config`, they will have to call
`engine.get_plugin_config()` instead.
If they were using the mutable reference to the plugin, they will have
to come up with some strategy to work around it (for example, for `Inc`
I just cloned it). This shouldn't be such a big deal at the moment as
it's not like plugins have ever run as daemons with persistent state in
the past, and they don't in this PR either. But I thought it was
important to make the change before we support plugins as daemons, as an
exclusive mutable reference is not compatible with parallel plugin
calls.
I suggest this gets merged sometime *after* the current pending release,
so that we have some time to adjust to the previous plugin protocol
changes that don't require code changes before making ones that do.
# Tests + Formatting
- :green_circle: `toolkit fmt`
- :green_circle: `toolkit clippy`
- :green_circle: `toolkit test`
- :green_circle: `toolkit test stdlib`
# After Submitting
I will document the additional protocol features (`EngineCall`,
`EngineCallResponse`), and constraints on plugin call processing if
engine calls are used - basically, to be aware that an engine call could
result in a nested plugin call, so the plugin should be able to handle
that.
2024-03-09 18:26:30 +01:00
|
|
|
/// A sequential identifier for an [`EngineCall`]
|
|
|
|
pub type EngineCallId = usize;
|
|
|
|
|
2024-02-25 23:32:50 +01:00
|
|
|
/// Information about a plugin command invocation. This includes an [`EvaluatedCall`] as a
|
|
|
|
/// serializable representation of [`nu_protocol::ast::Call`]. The type parameter determines
|
|
|
|
/// the input type.
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
|
|
pub struct CallInfo<D> {
|
|
|
|
/// The name of the command to be run
|
2021-12-12 12:50:35 +01:00
|
|
|
pub name: String,
|
2024-02-25 23:32:50 +01:00
|
|
|
/// Information about the invocation, including arguments
|
2021-12-12 12:50:35 +01:00
|
|
|
pub call: EvaluatedCall,
|
2024-02-25 23:32:50 +01:00
|
|
|
/// Pipeline input. This is usually [`nu_protocol::PipelineData`] or [`PipelineDataHeader`]
|
|
|
|
pub input: D,
|
2022-07-25 18:32:56 +02:00
|
|
|
}
|
|
|
|
|
2024-03-20 09:57:22 +01:00
|
|
|
impl<D> CallInfo<D> {
|
|
|
|
/// Convert the type of `input` from `D` to `T`.
|
|
|
|
pub(crate) fn map_data<T>(
|
|
|
|
self,
|
|
|
|
f: impl FnOnce(D) -> Result<T, ShellError>,
|
|
|
|
) -> Result<CallInfo<T>, ShellError> {
|
|
|
|
Ok(CallInfo {
|
|
|
|
name: self.name,
|
|
|
|
call: self.call,
|
|
|
|
input: f(self.input)?,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-25 23:32:50 +01:00
|
|
|
/// The initial (and perhaps only) part of any [`nu_protocol::PipelineData`] sent over the wire.
|
|
|
|
///
|
|
|
|
/// This may contain a single value, or may initiate a stream with a [`StreamId`].
|
|
|
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
|
|
|
pub enum PipelineDataHeader {
|
|
|
|
/// No input
|
|
|
|
Empty,
|
|
|
|
/// A single value
|
2022-07-25 18:32:56 +02:00
|
|
|
Value(Value),
|
2024-02-25 23:32:50 +01:00
|
|
|
/// Initiate [`nu_protocol::PipelineData::ListStream`].
|
|
|
|
///
|
|
|
|
/// Items are sent via [`StreamData`]
|
|
|
|
ListStream(ListStreamInfo),
|
|
|
|
/// Initiate [`nu_protocol::PipelineData::ExternalStream`].
|
|
|
|
///
|
|
|
|
/// Items are sent via [`StreamData`]
|
|
|
|
ExternalStream(ExternalStreamInfo),
|
|
|
|
}
|
|
|
|
|
Add support for engine calls from plugins (#12029)
# Description
This allows plugins to make calls back to the engine to get config,
evaluate closures, and do other things that must be done within the
engine process.
Engine calls can both produce and consume streams as necessary. Closures
passed to plugins can both accept stream input and produce stream output
sent back to the plugin.
Engine calls referring to a plugin call's context can be processed as
long either the response hasn't been received, or the response created
streams that haven't ended yet.
This is a breaking API change for plugins. There are some pretty major
changes to the interface that plugins must implement, including:
1. Plugins now run with `&self` and must be `Sync`. Executing multiple
plugin calls in parallel is supported, and there's a chance that a
closure passed to a plugin could invoke the same plugin. Supporting
state across plugin invocations is left up to the plugin author to do in
whichever way they feel best, but the plugin object itself is still
shared. Even though the engine doesn't run multiple plugin calls through
the same process yet, I still considered it important to break the API
in this way at this stage. We might want to consider an optional
threadpool feature for performance.
2. Plugins take a reference to `EngineInterface`, which can be cloned.
This interface allows plugins to make calls back to the engine,
including for getting config and running closures.
3. Plugins no longer take the `config` parameter. This can be accessed
from the interface via the `.get_plugin_config()` engine call.
# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
Not only does this have plugin protocol changes, it will require plugins
to make some code changes before they will work again. But on the plus
side, the engine call feature is extensible, and we can add more things
to it as needed.
Plugin maintainers will have to change the trait signature at the very
least. If they were using `config`, they will have to call
`engine.get_plugin_config()` instead.
If they were using the mutable reference to the plugin, they will have
to come up with some strategy to work around it (for example, for `Inc`
I just cloned it). This shouldn't be such a big deal at the moment as
it's not like plugins have ever run as daemons with persistent state in
the past, and they don't in this PR either. But I thought it was
important to make the change before we support plugins as daemons, as an
exclusive mutable reference is not compatible with parallel plugin
calls.
I suggest this gets merged sometime *after* the current pending release,
so that we have some time to adjust to the previous plugin protocol
changes that don't require code changes before making ones that do.
# Tests + Formatting
- :green_circle: `toolkit fmt`
- :green_circle: `toolkit clippy`
- :green_circle: `toolkit test`
- :green_circle: `toolkit test stdlib`
# After Submitting
I will document the additional protocol features (`EngineCall`,
`EngineCallResponse`), and constraints on plugin call processing if
engine calls are used - basically, to be aware that an engine call could
result in a nested plugin call, so the plugin should be able to handle
that.
2024-03-09 18:26:30 +01:00
|
|
|
impl PipelineDataHeader {
|
|
|
|
/// Return a list of stream IDs embedded in the header
|
|
|
|
pub(crate) fn stream_ids(&self) -> Vec<StreamId> {
|
|
|
|
match self {
|
|
|
|
PipelineDataHeader::Empty => vec![],
|
|
|
|
PipelineDataHeader::Value(_) => vec![],
|
|
|
|
PipelineDataHeader::ListStream(info) => vec![info.id],
|
|
|
|
PipelineDataHeader::ExternalStream(info) => {
|
|
|
|
let mut out = vec![];
|
|
|
|
if let Some(stdout) = &info.stdout {
|
|
|
|
out.push(stdout.id);
|
|
|
|
}
|
|
|
|
if let Some(stderr) = &info.stderr {
|
|
|
|
out.push(stderr.id);
|
|
|
|
}
|
|
|
|
if let Some(exit_code) = &info.exit_code {
|
|
|
|
out.push(exit_code.id);
|
|
|
|
}
|
|
|
|
out
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-25 23:32:50 +01:00
|
|
|
/// Additional information about list (value) streams
|
|
|
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
|
|
|
pub struct ListStreamInfo {
|
|
|
|
pub id: StreamId,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Additional information about external streams
|
|
|
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
|
|
|
pub struct ExternalStreamInfo {
|
|
|
|
pub span: Span,
|
|
|
|
pub stdout: Option<RawStreamInfo>,
|
|
|
|
pub stderr: Option<RawStreamInfo>,
|
|
|
|
pub exit_code: Option<ListStreamInfo>,
|
|
|
|
pub trim_end_newline: bool,
|
2021-12-12 12:50:35 +01:00
|
|
|
}
|
|
|
|
|
2024-02-25 23:32:50 +01:00
|
|
|
/// Additional information about raw (byte) streams
|
|
|
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
|
|
|
pub struct RawStreamInfo {
|
|
|
|
pub id: StreamId,
|
|
|
|
pub is_binary: bool,
|
|
|
|
pub known_size: Option<u64>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl RawStreamInfo {
|
|
|
|
pub(crate) fn new(id: StreamId, stream: &RawStream) -> Self {
|
|
|
|
RawStreamInfo {
|
|
|
|
id,
|
|
|
|
is_binary: stream.is_binary,
|
|
|
|
known_size: stream.known_size,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Calls that a plugin can execute. The type parameter determines the input type.
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
|
|
pub enum PluginCall<D> {
|
2021-12-12 12:50:35 +01:00
|
|
|
Signature,
|
2024-02-25 23:32:50 +01:00
|
|
|
Run(CallInfo<D>),
|
|
|
|
CustomValueOp(Spanned<PluginCustomValue>, CustomValueOp),
|
|
|
|
}
|
|
|
|
|
2024-03-20 09:57:22 +01:00
|
|
|
impl<D> PluginCall<D> {
|
|
|
|
/// Convert the data type from `D` to `T`. The function will not be called if the variant does
|
|
|
|
/// not contain data.
|
|
|
|
pub(crate) fn map_data<T>(
|
|
|
|
self,
|
|
|
|
f: impl FnOnce(D) -> Result<T, ShellError>,
|
|
|
|
) -> Result<PluginCall<T>, ShellError> {
|
|
|
|
Ok(match self {
|
|
|
|
PluginCall::Signature => PluginCall::Signature,
|
|
|
|
PluginCall::Run(call) => PluginCall::Run(call.map_data(f)?),
|
|
|
|
PluginCall::CustomValueOp(custom_value, op) => {
|
|
|
|
PluginCall::CustomValueOp(custom_value, op)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-25 23:32:50 +01:00
|
|
|
/// Operations supported for custom values.
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
|
|
pub enum CustomValueOp {
|
|
|
|
/// [`to_base_value()`](nu_protocol::CustomValue::to_base_value)
|
|
|
|
ToBaseValue,
|
2024-03-12 10:37:08 +01:00
|
|
|
/// [`follow_path_int()`](nu_protocol::CustomValue::follow_path_int)
|
|
|
|
FollowPathInt(Spanned<usize>),
|
|
|
|
/// [`follow_path_string()`](nu_protocol::CustomValue::follow_path_string)
|
|
|
|
FollowPathString(Spanned<String>),
|
|
|
|
/// [`partial_cmp()`](nu_protocol::CustomValue::partial_cmp)
|
|
|
|
PartialCmp(Value),
|
|
|
|
/// [`operation()`](nu_protocol::CustomValue::operation)
|
|
|
|
Operation(Spanned<Operator>, Value),
|
|
|
|
/// Notify that the custom value has been dropped, if
|
|
|
|
/// [`notify_plugin_on_drop()`](nu_protocol::CustomValue::notify_plugin_on_drop) is true
|
|
|
|
Dropped,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl CustomValueOp {
|
|
|
|
/// Get the name of the op, for error messages.
|
|
|
|
pub(crate) fn name(&self) -> &'static str {
|
|
|
|
match self {
|
|
|
|
CustomValueOp::ToBaseValue => "to_base_value",
|
|
|
|
CustomValueOp::FollowPathInt(_) => "follow_path_int",
|
|
|
|
CustomValueOp::FollowPathString(_) => "follow_path_string",
|
|
|
|
CustomValueOp::PartialCmp(_) => "partial_cmp",
|
|
|
|
CustomValueOp::Operation(_, _) => "operation",
|
|
|
|
CustomValueOp::Dropped => "dropped",
|
|
|
|
}
|
|
|
|
}
|
2024-02-25 23:32:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Any data sent to the plugin
|
2024-03-23 19:29:54 +01:00
|
|
|
///
|
|
|
|
/// Note: exported for internal use, not public.
|
2024-02-25 23:32:50 +01:00
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
2024-03-23 19:29:54 +01:00
|
|
|
#[doc(hidden)]
|
2024-02-25 23:32:50 +01:00
|
|
|
pub enum PluginInput {
|
|
|
|
/// This must be the first message. Indicates supported protocol
|
|
|
|
Hello(ProtocolInfo),
|
|
|
|
/// Execute a [`PluginCall`], such as `Run` or `Signature`. The ID should not have been used
|
|
|
|
/// before.
|
|
|
|
Call(PluginCallId, PluginCall<PipelineDataHeader>),
|
2024-02-29 03:41:22 +01:00
|
|
|
/// Don't expect any more plugin calls. Exit after all currently executing plugin calls are
|
|
|
|
/// finished.
|
|
|
|
Goodbye,
|
Add support for engine calls from plugins (#12029)
# Description
This allows plugins to make calls back to the engine to get config,
evaluate closures, and do other things that must be done within the
engine process.
Engine calls can both produce and consume streams as necessary. Closures
passed to plugins can both accept stream input and produce stream output
sent back to the plugin.
Engine calls referring to a plugin call's context can be processed as
long either the response hasn't been received, or the response created
streams that haven't ended yet.
This is a breaking API change for plugins. There are some pretty major
changes to the interface that plugins must implement, including:
1. Plugins now run with `&self` and must be `Sync`. Executing multiple
plugin calls in parallel is supported, and there's a chance that a
closure passed to a plugin could invoke the same plugin. Supporting
state across plugin invocations is left up to the plugin author to do in
whichever way they feel best, but the plugin object itself is still
shared. Even though the engine doesn't run multiple plugin calls through
the same process yet, I still considered it important to break the API
in this way at this stage. We might want to consider an optional
threadpool feature for performance.
2. Plugins take a reference to `EngineInterface`, which can be cloned.
This interface allows plugins to make calls back to the engine,
including for getting config and running closures.
3. Plugins no longer take the `config` parameter. This can be accessed
from the interface via the `.get_plugin_config()` engine call.
# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
Not only does this have plugin protocol changes, it will require plugins
to make some code changes before they will work again. But on the plus
side, the engine call feature is extensible, and we can add more things
to it as needed.
Plugin maintainers will have to change the trait signature at the very
least. If they were using `config`, they will have to call
`engine.get_plugin_config()` instead.
If they were using the mutable reference to the plugin, they will have
to come up with some strategy to work around it (for example, for `Inc`
I just cloned it). This shouldn't be such a big deal at the moment as
it's not like plugins have ever run as daemons with persistent state in
the past, and they don't in this PR either. But I thought it was
important to make the change before we support plugins as daemons, as an
exclusive mutable reference is not compatible with parallel plugin
calls.
I suggest this gets merged sometime *after* the current pending release,
so that we have some time to adjust to the previous plugin protocol
changes that don't require code changes before making ones that do.
# Tests + Formatting
- :green_circle: `toolkit fmt`
- :green_circle: `toolkit clippy`
- :green_circle: `toolkit test`
- :green_circle: `toolkit test stdlib`
# After Submitting
I will document the additional protocol features (`EngineCall`,
`EngineCallResponse`), and constraints on plugin call processing if
engine calls are used - basically, to be aware that an engine call could
result in a nested plugin call, so the plugin should be able to handle
that.
2024-03-09 18:26:30 +01:00
|
|
|
/// Response to an [`EngineCall`]. The ID should be the same one sent with the engine call this
|
|
|
|
/// is responding to
|
|
|
|
EngineCallResponse(EngineCallId, EngineCallResponse<PipelineDataHeader>),
|
2024-02-25 23:32:50 +01:00
|
|
|
/// Stream control or data message. Untagged to keep them as small as possible.
|
|
|
|
///
|
|
|
|
/// For example, `Stream(Ack(0))` is encoded as `{"Ack": 0}`
|
|
|
|
#[serde(untagged)]
|
|
|
|
Stream(StreamMessage),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TryFrom<PluginInput> for StreamMessage {
|
|
|
|
type Error = PluginInput;
|
|
|
|
|
|
|
|
fn try_from(msg: PluginInput) -> Result<StreamMessage, PluginInput> {
|
|
|
|
match msg {
|
|
|
|
PluginInput::Stream(stream_msg) => Ok(stream_msg),
|
|
|
|
_ => Err(msg),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<StreamMessage> for PluginInput {
|
|
|
|
fn from(stream_msg: StreamMessage) -> PluginInput {
|
|
|
|
PluginInput::Stream(stream_msg)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A single item of stream data for a stream.
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
|
|
pub enum StreamData {
|
|
|
|
List(Value),
|
|
|
|
Raw(Result<Vec<u8>, ShellError>),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<Value> for StreamData {
|
|
|
|
fn from(value: Value) -> Self {
|
|
|
|
StreamData::List(value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<Result<Vec<u8>, ShellError>> for StreamData {
|
|
|
|
fn from(value: Result<Vec<u8>, ShellError>) -> Self {
|
|
|
|
StreamData::Raw(value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TryFrom<StreamData> for Value {
|
|
|
|
type Error = ShellError;
|
|
|
|
|
|
|
|
fn try_from(data: StreamData) -> Result<Value, ShellError> {
|
|
|
|
match data {
|
|
|
|
StreamData::List(value) => Ok(value),
|
|
|
|
StreamData::Raw(_) => Err(ShellError::PluginFailedToDecode {
|
|
|
|
msg: "expected list stream data, found raw data".into(),
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TryFrom<StreamData> for Result<Vec<u8>, ShellError> {
|
|
|
|
type Error = ShellError;
|
|
|
|
|
|
|
|
fn try_from(data: StreamData) -> Result<Result<Vec<u8>, ShellError>, ShellError> {
|
|
|
|
match data {
|
|
|
|
StreamData::Raw(value) => Ok(value),
|
|
|
|
StreamData::List(_) => Err(ShellError::PluginFailedToDecode {
|
|
|
|
msg: "expected raw stream data, found list data".into(),
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A stream control or data message.
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
|
|
pub enum StreamMessage {
|
|
|
|
/// Append data to the stream. Sent by the stream producer.
|
|
|
|
Data(StreamId, StreamData),
|
|
|
|
/// End of stream. Sent by the stream producer.
|
|
|
|
End(StreamId),
|
|
|
|
/// Notify that the read end of the stream has closed, and further messages should not be
|
|
|
|
/// sent. Sent by the stream consumer.
|
|
|
|
Drop(StreamId),
|
|
|
|
/// Acknowledge that a message has been consumed. This is used to implement flow control by
|
|
|
|
/// the stream producer. Sent by the stream consumer.
|
|
|
|
Ack(StreamId),
|
2021-12-12 12:50:35 +01:00
|
|
|
}
|
|
|
|
|
2024-02-25 23:32:50 +01:00
|
|
|
/// Response to a [`PluginCall`]. The type parameter determines the output type for pipeline data.
|
|
|
|
///
|
|
|
|
/// Note: exported for internal use, not public.
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
2023-06-16 16:25:40 +02:00
|
|
|
#[doc(hidden)]
|
2024-02-25 23:32:50 +01:00
|
|
|
pub enum PluginCallResponse<D> {
|
2021-12-12 12:50:35 +01:00
|
|
|
Error(LabeledError),
|
Make plugin commands support examples. (#7984)
# Description
As title, we can't provide examples for plugin commands, this pr would
make it possible
# User-Facing Changes
Take plugin `nu-example-1` as example:
```
❯ nu-example-1 -h
PluginSignature test 1 for plugin. Returns Value::Nothing
Usage:
> nu-example-1 {flags} <a> <b> (opt) ...(rest)
Flags:
-h, --help - Display the help message for this command
-f, --flag - a flag for the signature
-n, --named <String> - named string
Parameters:
a <int>: required integer value
b <string>: required string value
(optional) opt <int>: Optional number
...rest <string>: rest value string
Examples:
running example with an int value and string value
> nu-example-1 3 bb
```
The examples session is newly added.
## Basic idea behind these changes
when nushell query plugin signatures, plugin just returns it's signature
without any examples, so nushell have no idea about the examples of
plugin commands.
To adding the feature, we just making plugin returns it's signature with
examples.
Before:
```
1. get signature
---------------->
Nushell ------------------ Plugin
<-----------------
2. returns Vec<Signature>
```
After:
```
1. get signature
---------------->
Nushell ------------------ Plugin
<-----------------
2. returns Vec<PluginSignature>
```
When writing plugin signature to $nu.plugin-path:
Serialize `<PluginSignature>` rather than `<Signature>`, which would
enable us to serialize examples to `$nu.plugin-path`
## Shortcoming
It's a breaking changes because `Plugin::signature` is changed, and it
requires plugin authors to change their code for new signatures.
Fortunally it should be easy to change, for rust based plugin, we just
need to make a global replace from word `Signature` to word
`PluginSignature` in their plugin project.
Our content of plugin-path is really large, if one plugin have many
examples, it'd results to larger body of $nu.plugin-path, which is not
really scale. A solution would be save register information in other
binary formats rather than `json`. But I think it'd be another story.
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2023-02-08 23:14:18 +01:00
|
|
|
Signature(Vec<PluginSignature>),
|
2024-03-12 10:37:08 +01:00
|
|
|
Ordering(Option<Ordering>),
|
2024-02-25 23:32:50 +01:00
|
|
|
PipelineData(D),
|
|
|
|
}
|
|
|
|
|
2024-03-20 09:57:22 +01:00
|
|
|
impl<D> PluginCallResponse<D> {
|
|
|
|
/// Convert the data type from `D` to `T`. The function will not be called if the variant does
|
|
|
|
/// not contain data.
|
|
|
|
pub(crate) fn map_data<T>(
|
|
|
|
self,
|
|
|
|
f: impl FnOnce(D) -> Result<T, ShellError>,
|
|
|
|
) -> Result<PluginCallResponse<T>, ShellError> {
|
|
|
|
Ok(match self {
|
|
|
|
PluginCallResponse::Error(err) => PluginCallResponse::Error(err),
|
|
|
|
PluginCallResponse::Signature(sigs) => PluginCallResponse::Signature(sigs),
|
|
|
|
PluginCallResponse::Ordering(ordering) => PluginCallResponse::Ordering(ordering),
|
|
|
|
PluginCallResponse::PipelineData(input) => PluginCallResponse::PipelineData(f(input)?),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-25 23:32:50 +01:00
|
|
|
impl PluginCallResponse<PipelineDataHeader> {
|
|
|
|
/// Construct a plugin call response with a single value
|
|
|
|
pub fn value(value: Value) -> PluginCallResponse<PipelineDataHeader> {
|
|
|
|
if value.is_nothing() {
|
|
|
|
PluginCallResponse::PipelineData(PipelineDataHeader::Empty)
|
|
|
|
} else {
|
|
|
|
PluginCallResponse::PipelineData(PipelineDataHeader::Value(value))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-15 12:45:45 +01:00
|
|
|
impl PluginCallResponse<PipelineData> {
|
|
|
|
/// Does this response have a stream?
|
|
|
|
pub(crate) fn has_stream(&self) -> bool {
|
|
|
|
match self {
|
|
|
|
PluginCallResponse::PipelineData(data) => match data {
|
|
|
|
PipelineData::Empty => false,
|
|
|
|
PipelineData::Value(..) => false,
|
|
|
|
PipelineData::ListStream(..) => true,
|
|
|
|
PipelineData::ExternalStream { .. } => true,
|
|
|
|
},
|
|
|
|
_ => false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Keep plugins persistently running in the background (#12064)
# Description
This PR uses the new plugin protocol to intelligently keep plugin
processes running in the background for further plugin calls.
Running plugins can be seen by running the new `plugin list` command,
and stopped by running the new `plugin stop` command.
This is an enhancement for the performance of plugins, as starting new
plugin processes has overhead, especially for plugins in languages that
take a significant amount of time on startup. It also enables plugins
that have persistent state between commands, making the migration of
features like dataframes and `stor` to plugins possible.
Plugins are automatically stopped by the new plugin garbage collector,
configurable with `$env.config.plugin_gc`:
```nushell
$env.config.plugin_gc = {
# Configuration for plugin garbage collection
default: {
enabled: true # true to enable stopping of inactive plugins
stop_after: 10sec # how long to wait after a plugin is inactive to stop it
}
plugins: {
# alternate configuration for specific plugins, by name, for example:
#
# gstat: {
# enabled: false
# }
}
}
```
If garbage collection is enabled, plugins will be stopped after
`stop_after` passes after they were last active. Plugins are counted as
inactive if they have no running plugin calls. Reading the stream from
the response of a plugin call is still considered to be activity, but if
a plugin holds on to a stream but the call ends without an active
streaming response, it is not counted as active even if it is reading
it. Plugins can explicitly disable the GC as appropriate with
`engine.set_gc_disabled(true)`.
The `version` command now lists plugin names rather than plugin
commands. The list of plugin commands is accessible via `plugin list`.
Recommend doing this together with #12029, because it will likely force
plugin developers to do the right thing with mutability and lead to less
unexpected behavior when running plugins nested / in parallel.
# User-Facing Changes
- new command: `plugin list`
- new command: `plugin stop`
- changed command: `version` (now lists plugin names, rather than
commands)
- new config: `$env.config.plugin_gc`
- Plugins will keep running and be reused, at least for the configured
GC period
- Plugins that used mutable state in weird ways like `inc` did might
misbehave until fixed
- Plugins can disable GC if they need to
- Had to change plugin signature to accept `&EngineInterface` so that
the GC disable feature works. #12029 does this anyway, and I'm expecting
(resolvable) conflicts with that
# Tests + Formatting
- :green_circle: `toolkit fmt`
- :green_circle: `toolkit clippy`
- :green_circle: `toolkit test`
- :green_circle: `toolkit test stdlib`
Because there is some specific OS behavior required for plugins to not
respond to Ctrl-C directly, I've developed against and tested on both
Linux and Windows to ensure that works properly.
# After Submitting
I think this probably needs to be in the book somewhere
2024-03-10 00:10:22 +01:00
|
|
|
/// Options that can be changed to affect how the engine treats the plugin
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
|
|
pub enum PluginOption {
|
|
|
|
/// Send `GcDisabled(true)` to stop the plugin from being automatically garbage collected, or
|
|
|
|
/// `GcDisabled(false)` to enable it again.
|
|
|
|
///
|
|
|
|
/// See [`EngineInterface::set_gc_disabled`] for more information.
|
|
|
|
GcDisabled(bool),
|
|
|
|
}
|
|
|
|
|
2024-03-23 13:26:08 +01:00
|
|
|
/// This is just a serializable version of [`std::cmp::Ordering`], and can be converted 1:1
|
2024-03-12 10:37:08 +01:00
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
|
|
|
pub enum Ordering {
|
|
|
|
Less,
|
|
|
|
Equal,
|
|
|
|
Greater,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<std::cmp::Ordering> for Ordering {
|
|
|
|
fn from(value: std::cmp::Ordering) -> Self {
|
|
|
|
match value {
|
|
|
|
std::cmp::Ordering::Less => Ordering::Less,
|
|
|
|
std::cmp::Ordering::Equal => Ordering::Equal,
|
|
|
|
std::cmp::Ordering::Greater => Ordering::Greater,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<Ordering> for std::cmp::Ordering {
|
|
|
|
fn from(value: Ordering) -> Self {
|
|
|
|
match value {
|
|
|
|
Ordering::Less => std::cmp::Ordering::Less,
|
|
|
|
Ordering::Equal => std::cmp::Ordering::Equal,
|
|
|
|
Ordering::Greater => std::cmp::Ordering::Greater,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-25 23:32:50 +01:00
|
|
|
/// Information received from the plugin
|
|
|
|
///
|
|
|
|
/// Note: exported for internal use, not public.
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
|
|
#[doc(hidden)]
|
|
|
|
pub enum PluginOutput {
|
|
|
|
/// This must be the first message. Indicates supported protocol
|
|
|
|
Hello(ProtocolInfo),
|
Keep plugins persistently running in the background (#12064)
# Description
This PR uses the new plugin protocol to intelligently keep plugin
processes running in the background for further plugin calls.
Running plugins can be seen by running the new `plugin list` command,
and stopped by running the new `plugin stop` command.
This is an enhancement for the performance of plugins, as starting new
plugin processes has overhead, especially for plugins in languages that
take a significant amount of time on startup. It also enables plugins
that have persistent state between commands, making the migration of
features like dataframes and `stor` to plugins possible.
Plugins are automatically stopped by the new plugin garbage collector,
configurable with `$env.config.plugin_gc`:
```nushell
$env.config.plugin_gc = {
# Configuration for plugin garbage collection
default: {
enabled: true # true to enable stopping of inactive plugins
stop_after: 10sec # how long to wait after a plugin is inactive to stop it
}
plugins: {
# alternate configuration for specific plugins, by name, for example:
#
# gstat: {
# enabled: false
# }
}
}
```
If garbage collection is enabled, plugins will be stopped after
`stop_after` passes after they were last active. Plugins are counted as
inactive if they have no running plugin calls. Reading the stream from
the response of a plugin call is still considered to be activity, but if
a plugin holds on to a stream but the call ends without an active
streaming response, it is not counted as active even if it is reading
it. Plugins can explicitly disable the GC as appropriate with
`engine.set_gc_disabled(true)`.
The `version` command now lists plugin names rather than plugin
commands. The list of plugin commands is accessible via `plugin list`.
Recommend doing this together with #12029, because it will likely force
plugin developers to do the right thing with mutability and lead to less
unexpected behavior when running plugins nested / in parallel.
# User-Facing Changes
- new command: `plugin list`
- new command: `plugin stop`
- changed command: `version` (now lists plugin names, rather than
commands)
- new config: `$env.config.plugin_gc`
- Plugins will keep running and be reused, at least for the configured
GC period
- Plugins that used mutable state in weird ways like `inc` did might
misbehave until fixed
- Plugins can disable GC if they need to
- Had to change plugin signature to accept `&EngineInterface` so that
the GC disable feature works. #12029 does this anyway, and I'm expecting
(resolvable) conflicts with that
# Tests + Formatting
- :green_circle: `toolkit fmt`
- :green_circle: `toolkit clippy`
- :green_circle: `toolkit test`
- :green_circle: `toolkit test stdlib`
Because there is some specific OS behavior required for plugins to not
respond to Ctrl-C directly, I've developed against and tested on both
Linux and Windows to ensure that works properly.
# After Submitting
I think this probably needs to be in the book somewhere
2024-03-10 00:10:22 +01:00
|
|
|
/// Set option. No response expected
|
|
|
|
Option(PluginOption),
|
2024-02-25 23:32:50 +01:00
|
|
|
/// A response to a [`PluginCall`]. The ID should be the same sent with the plugin call this
|
|
|
|
/// is a response to
|
|
|
|
CallResponse(PluginCallId, PluginCallResponse<PipelineDataHeader>),
|
Add support for engine calls from plugins (#12029)
# Description
This allows plugins to make calls back to the engine to get config,
evaluate closures, and do other things that must be done within the
engine process.
Engine calls can both produce and consume streams as necessary. Closures
passed to plugins can both accept stream input and produce stream output
sent back to the plugin.
Engine calls referring to a plugin call's context can be processed as
long either the response hasn't been received, or the response created
streams that haven't ended yet.
This is a breaking API change for plugins. There are some pretty major
changes to the interface that plugins must implement, including:
1. Plugins now run with `&self` and must be `Sync`. Executing multiple
plugin calls in parallel is supported, and there's a chance that a
closure passed to a plugin could invoke the same plugin. Supporting
state across plugin invocations is left up to the plugin author to do in
whichever way they feel best, but the plugin object itself is still
shared. Even though the engine doesn't run multiple plugin calls through
the same process yet, I still considered it important to break the API
in this way at this stage. We might want to consider an optional
threadpool feature for performance.
2. Plugins take a reference to `EngineInterface`, which can be cloned.
This interface allows plugins to make calls back to the engine,
including for getting config and running closures.
3. Plugins no longer take the `config` parameter. This can be accessed
from the interface via the `.get_plugin_config()` engine call.
# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
Not only does this have plugin protocol changes, it will require plugins
to make some code changes before they will work again. But on the plus
side, the engine call feature is extensible, and we can add more things
to it as needed.
Plugin maintainers will have to change the trait signature at the very
least. If they were using `config`, they will have to call
`engine.get_plugin_config()` instead.
If they were using the mutable reference to the plugin, they will have
to come up with some strategy to work around it (for example, for `Inc`
I just cloned it). This shouldn't be such a big deal at the moment as
it's not like plugins have ever run as daemons with persistent state in
the past, and they don't in this PR either. But I thought it was
important to make the change before we support plugins as daemons, as an
exclusive mutable reference is not compatible with parallel plugin
calls.
I suggest this gets merged sometime *after* the current pending release,
so that we have some time to adjust to the previous plugin protocol
changes that don't require code changes before making ones that do.
# Tests + Formatting
- :green_circle: `toolkit fmt`
- :green_circle: `toolkit clippy`
- :green_circle: `toolkit test`
- :green_circle: `toolkit test stdlib`
# After Submitting
I will document the additional protocol features (`EngineCall`,
`EngineCallResponse`), and constraints on plugin call processing if
engine calls are used - basically, to be aware that an engine call could
result in a nested plugin call, so the plugin should be able to handle
that.
2024-03-09 18:26:30 +01:00
|
|
|
/// Execute an [`EngineCall`]. Engine calls must be executed within the `context` of a plugin
|
|
|
|
/// call, and the `id` should not have been used before
|
|
|
|
EngineCall {
|
|
|
|
/// The plugin call (by ID) to execute in the context of
|
|
|
|
context: PluginCallId,
|
|
|
|
/// A new identifier for this engine call. The response will reference this ID
|
|
|
|
id: EngineCallId,
|
|
|
|
call: EngineCall<PipelineDataHeader>,
|
|
|
|
},
|
2024-02-25 23:32:50 +01:00
|
|
|
/// Stream control or data message. Untagged to keep them as small as possible.
|
|
|
|
///
|
|
|
|
/// For example, `Stream(Ack(0))` is encoded as `{"Ack": 0}`
|
|
|
|
#[serde(untagged)]
|
|
|
|
Stream(StreamMessage),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TryFrom<PluginOutput> for StreamMessage {
|
|
|
|
type Error = PluginOutput;
|
|
|
|
|
|
|
|
fn try_from(msg: PluginOutput) -> Result<StreamMessage, PluginOutput> {
|
|
|
|
match msg {
|
|
|
|
PluginOutput::Stream(stream_msg) => Ok(stream_msg),
|
|
|
|
_ => Err(msg),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<StreamMessage> for PluginOutput {
|
|
|
|
fn from(stream_msg: StreamMessage) -> PluginOutput {
|
|
|
|
PluginOutput::Stream(stream_msg)
|
|
|
|
}
|
2021-12-12 12:50:35 +01:00
|
|
|
}
|
Add support for engine calls from plugins (#12029)
# Description
This allows plugins to make calls back to the engine to get config,
evaluate closures, and do other things that must be done within the
engine process.
Engine calls can both produce and consume streams as necessary. Closures
passed to plugins can both accept stream input and produce stream output
sent back to the plugin.
Engine calls referring to a plugin call's context can be processed as
long either the response hasn't been received, or the response created
streams that haven't ended yet.
This is a breaking API change for plugins. There are some pretty major
changes to the interface that plugins must implement, including:
1. Plugins now run with `&self` and must be `Sync`. Executing multiple
plugin calls in parallel is supported, and there's a chance that a
closure passed to a plugin could invoke the same plugin. Supporting
state across plugin invocations is left up to the plugin author to do in
whichever way they feel best, but the plugin object itself is still
shared. Even though the engine doesn't run multiple plugin calls through
the same process yet, I still considered it important to break the API
in this way at this stage. We might want to consider an optional
threadpool feature for performance.
2. Plugins take a reference to `EngineInterface`, which can be cloned.
This interface allows plugins to make calls back to the engine,
including for getting config and running closures.
3. Plugins no longer take the `config` parameter. This can be accessed
from the interface via the `.get_plugin_config()` engine call.
# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
Not only does this have plugin protocol changes, it will require plugins
to make some code changes before they will work again. But on the plus
side, the engine call feature is extensible, and we can add more things
to it as needed.
Plugin maintainers will have to change the trait signature at the very
least. If they were using `config`, they will have to call
`engine.get_plugin_config()` instead.
If they were using the mutable reference to the plugin, they will have
to come up with some strategy to work around it (for example, for `Inc`
I just cloned it). This shouldn't be such a big deal at the moment as
it's not like plugins have ever run as daemons with persistent state in
the past, and they don't in this PR either. But I thought it was
important to make the change before we support plugins as daemons, as an
exclusive mutable reference is not compatible with parallel plugin
calls.
I suggest this gets merged sometime *after* the current pending release,
so that we have some time to adjust to the previous plugin protocol
changes that don't require code changes before making ones that do.
# Tests + Formatting
- :green_circle: `toolkit fmt`
- :green_circle: `toolkit clippy`
- :green_circle: `toolkit test`
- :green_circle: `toolkit test stdlib`
# After Submitting
I will document the additional protocol features (`EngineCall`,
`EngineCallResponse`), and constraints on plugin call processing if
engine calls are used - basically, to be aware that an engine call could
result in a nested plugin call, so the plugin should be able to handle
that.
2024-03-09 18:26:30 +01:00
|
|
|
|
|
|
|
/// A remote call back to the engine during the plugin's execution.
|
|
|
|
///
|
|
|
|
/// The type parameter determines the input type, for calls that take pipeline data.
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
|
|
pub enum EngineCall<D> {
|
|
|
|
/// Get the full engine configuration
|
|
|
|
GetConfig,
|
|
|
|
/// Get the plugin-specific configuration (`$env.config.plugins.NAME`)
|
|
|
|
GetPluginConfig,
|
2024-03-12 12:34:32 +01:00
|
|
|
/// Get an environment variable
|
|
|
|
GetEnvVar(String),
|
|
|
|
/// Get all environment variables
|
|
|
|
GetEnvVars,
|
|
|
|
/// Get current working directory
|
|
|
|
GetCurrentDir,
|
2024-03-15 12:45:45 +01:00
|
|
|
/// Set an environment variable in the caller's scope
|
|
|
|
AddEnvVar(String, Value),
|
2024-03-24 00:30:38 +01:00
|
|
|
/// Get help for the current command
|
|
|
|
GetHelp,
|
Add support for engine calls from plugins (#12029)
# Description
This allows plugins to make calls back to the engine to get config,
evaluate closures, and do other things that must be done within the
engine process.
Engine calls can both produce and consume streams as necessary. Closures
passed to plugins can both accept stream input and produce stream output
sent back to the plugin.
Engine calls referring to a plugin call's context can be processed as
long either the response hasn't been received, or the response created
streams that haven't ended yet.
This is a breaking API change for plugins. There are some pretty major
changes to the interface that plugins must implement, including:
1. Plugins now run with `&self` and must be `Sync`. Executing multiple
plugin calls in parallel is supported, and there's a chance that a
closure passed to a plugin could invoke the same plugin. Supporting
state across plugin invocations is left up to the plugin author to do in
whichever way they feel best, but the plugin object itself is still
shared. Even though the engine doesn't run multiple plugin calls through
the same process yet, I still considered it important to break the API
in this way at this stage. We might want to consider an optional
threadpool feature for performance.
2. Plugins take a reference to `EngineInterface`, which can be cloned.
This interface allows plugins to make calls back to the engine,
including for getting config and running closures.
3. Plugins no longer take the `config` parameter. This can be accessed
from the interface via the `.get_plugin_config()` engine call.
# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
Not only does this have plugin protocol changes, it will require plugins
to make some code changes before they will work again. But on the plus
side, the engine call feature is extensible, and we can add more things
to it as needed.
Plugin maintainers will have to change the trait signature at the very
least. If they were using `config`, they will have to call
`engine.get_plugin_config()` instead.
If they were using the mutable reference to the plugin, they will have
to come up with some strategy to work around it (for example, for `Inc`
I just cloned it). This shouldn't be such a big deal at the moment as
it's not like plugins have ever run as daemons with persistent state in
the past, and they don't in this PR either. But I thought it was
important to make the change before we support plugins as daemons, as an
exclusive mutable reference is not compatible with parallel plugin
calls.
I suggest this gets merged sometime *after* the current pending release,
so that we have some time to adjust to the previous plugin protocol
changes that don't require code changes before making ones that do.
# Tests + Formatting
- :green_circle: `toolkit fmt`
- :green_circle: `toolkit clippy`
- :green_circle: `toolkit test`
- :green_circle: `toolkit test stdlib`
# After Submitting
I will document the additional protocol features (`EngineCall`,
`EngineCallResponse`), and constraints on plugin call processing if
engine calls are used - basically, to be aware that an engine call could
result in a nested plugin call, so the plugin should be able to handle
that.
2024-03-09 18:26:30 +01:00
|
|
|
/// Evaluate a closure with stream input/output
|
|
|
|
EvalClosure {
|
|
|
|
/// The closure to call.
|
|
|
|
///
|
|
|
|
/// This may come from a [`Value::Closure`] passed in as an argument to the plugin.
|
|
|
|
closure: Spanned<Closure>,
|
|
|
|
/// Positional arguments to add to the closure call
|
|
|
|
positional: Vec<Value>,
|
|
|
|
/// Input to the closure
|
|
|
|
input: D,
|
|
|
|
/// Whether to redirect stdout from external commands
|
|
|
|
redirect_stdout: bool,
|
|
|
|
/// Whether to redirect stderr from external commands
|
|
|
|
redirect_stderr: bool,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<D> EngineCall<D> {
|
|
|
|
/// Get the name of the engine call so it can be embedded in things like error messages
|
|
|
|
pub fn name(&self) -> &'static str {
|
|
|
|
match self {
|
|
|
|
EngineCall::GetConfig => "GetConfig",
|
|
|
|
EngineCall::GetPluginConfig => "GetPluginConfig",
|
2024-03-12 12:34:32 +01:00
|
|
|
EngineCall::GetEnvVar(_) => "GetEnv",
|
|
|
|
EngineCall::GetEnvVars => "GetEnvs",
|
|
|
|
EngineCall::GetCurrentDir => "GetCurrentDir",
|
2024-03-15 12:45:45 +01:00
|
|
|
EngineCall::AddEnvVar(..) => "AddEnvVar",
|
2024-03-24 00:30:38 +01:00
|
|
|
EngineCall::GetHelp => "GetHelp",
|
Add support for engine calls from plugins (#12029)
# Description
This allows plugins to make calls back to the engine to get config,
evaluate closures, and do other things that must be done within the
engine process.
Engine calls can both produce and consume streams as necessary. Closures
passed to plugins can both accept stream input and produce stream output
sent back to the plugin.
Engine calls referring to a plugin call's context can be processed as
long either the response hasn't been received, or the response created
streams that haven't ended yet.
This is a breaking API change for plugins. There are some pretty major
changes to the interface that plugins must implement, including:
1. Plugins now run with `&self` and must be `Sync`. Executing multiple
plugin calls in parallel is supported, and there's a chance that a
closure passed to a plugin could invoke the same plugin. Supporting
state across plugin invocations is left up to the plugin author to do in
whichever way they feel best, but the plugin object itself is still
shared. Even though the engine doesn't run multiple plugin calls through
the same process yet, I still considered it important to break the API
in this way at this stage. We might want to consider an optional
threadpool feature for performance.
2. Plugins take a reference to `EngineInterface`, which can be cloned.
This interface allows plugins to make calls back to the engine,
including for getting config and running closures.
3. Plugins no longer take the `config` parameter. This can be accessed
from the interface via the `.get_plugin_config()` engine call.
# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
Not only does this have plugin protocol changes, it will require plugins
to make some code changes before they will work again. But on the plus
side, the engine call feature is extensible, and we can add more things
to it as needed.
Plugin maintainers will have to change the trait signature at the very
least. If they were using `config`, they will have to call
`engine.get_plugin_config()` instead.
If they were using the mutable reference to the plugin, they will have
to come up with some strategy to work around it (for example, for `Inc`
I just cloned it). This shouldn't be such a big deal at the moment as
it's not like plugins have ever run as daemons with persistent state in
the past, and they don't in this PR either. But I thought it was
important to make the change before we support plugins as daemons, as an
exclusive mutable reference is not compatible with parallel plugin
calls.
I suggest this gets merged sometime *after* the current pending release,
so that we have some time to adjust to the previous plugin protocol
changes that don't require code changes before making ones that do.
# Tests + Formatting
- :green_circle: `toolkit fmt`
- :green_circle: `toolkit clippy`
- :green_circle: `toolkit test`
- :green_circle: `toolkit test stdlib`
# After Submitting
I will document the additional protocol features (`EngineCall`,
`EngineCallResponse`), and constraints on plugin call processing if
engine calls are used - basically, to be aware that an engine call could
result in a nested plugin call, so the plugin should be able to handle
that.
2024-03-09 18:26:30 +01:00
|
|
|
EngineCall::EvalClosure { .. } => "EvalClosure",
|
|
|
|
}
|
|
|
|
}
|
2024-03-20 09:57:22 +01:00
|
|
|
|
|
|
|
/// Convert the data type from `D` to `T`. The function will not be called if the variant does
|
|
|
|
/// not contain data.
|
|
|
|
pub(crate) fn map_data<T>(
|
|
|
|
self,
|
|
|
|
f: impl FnOnce(D) -> Result<T, ShellError>,
|
|
|
|
) -> Result<EngineCall<T>, ShellError> {
|
|
|
|
Ok(match self {
|
|
|
|
EngineCall::GetConfig => EngineCall::GetConfig,
|
|
|
|
EngineCall::GetPluginConfig => EngineCall::GetPluginConfig,
|
|
|
|
EngineCall::GetEnvVar(name) => EngineCall::GetEnvVar(name),
|
|
|
|
EngineCall::GetEnvVars => EngineCall::GetEnvVars,
|
|
|
|
EngineCall::GetCurrentDir => EngineCall::GetCurrentDir,
|
|
|
|
EngineCall::AddEnvVar(name, value) => EngineCall::AddEnvVar(name, value),
|
2024-03-24 00:30:38 +01:00
|
|
|
EngineCall::GetHelp => EngineCall::GetHelp,
|
2024-03-20 09:57:22 +01:00
|
|
|
EngineCall::EvalClosure {
|
|
|
|
closure,
|
|
|
|
positional,
|
|
|
|
input,
|
|
|
|
redirect_stdout,
|
|
|
|
redirect_stderr,
|
|
|
|
} => EngineCall::EvalClosure {
|
|
|
|
closure,
|
|
|
|
positional,
|
|
|
|
input: f(input)?,
|
|
|
|
redirect_stdout,
|
|
|
|
redirect_stderr,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
Add support for engine calls from plugins (#12029)
# Description
This allows plugins to make calls back to the engine to get config,
evaluate closures, and do other things that must be done within the
engine process.
Engine calls can both produce and consume streams as necessary. Closures
passed to plugins can both accept stream input and produce stream output
sent back to the plugin.
Engine calls referring to a plugin call's context can be processed as
long either the response hasn't been received, or the response created
streams that haven't ended yet.
This is a breaking API change for plugins. There are some pretty major
changes to the interface that plugins must implement, including:
1. Plugins now run with `&self` and must be `Sync`. Executing multiple
plugin calls in parallel is supported, and there's a chance that a
closure passed to a plugin could invoke the same plugin. Supporting
state across plugin invocations is left up to the plugin author to do in
whichever way they feel best, but the plugin object itself is still
shared. Even though the engine doesn't run multiple plugin calls through
the same process yet, I still considered it important to break the API
in this way at this stage. We might want to consider an optional
threadpool feature for performance.
2. Plugins take a reference to `EngineInterface`, which can be cloned.
This interface allows plugins to make calls back to the engine,
including for getting config and running closures.
3. Plugins no longer take the `config` parameter. This can be accessed
from the interface via the `.get_plugin_config()` engine call.
# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
Not only does this have plugin protocol changes, it will require plugins
to make some code changes before they will work again. But on the plus
side, the engine call feature is extensible, and we can add more things
to it as needed.
Plugin maintainers will have to change the trait signature at the very
least. If they were using `config`, they will have to call
`engine.get_plugin_config()` instead.
If they were using the mutable reference to the plugin, they will have
to come up with some strategy to work around it (for example, for `Inc`
I just cloned it). This shouldn't be such a big deal at the moment as
it's not like plugins have ever run as daemons with persistent state in
the past, and they don't in this PR either. But I thought it was
important to make the change before we support plugins as daemons, as an
exclusive mutable reference is not compatible with parallel plugin
calls.
I suggest this gets merged sometime *after* the current pending release,
so that we have some time to adjust to the previous plugin protocol
changes that don't require code changes before making ones that do.
# Tests + Formatting
- :green_circle: `toolkit fmt`
- :green_circle: `toolkit clippy`
- :green_circle: `toolkit test`
- :green_circle: `toolkit test stdlib`
# After Submitting
I will document the additional protocol features (`EngineCall`,
`EngineCallResponse`), and constraints on plugin call processing if
engine calls are used - basically, to be aware that an engine call could
result in a nested plugin call, so the plugin should be able to handle
that.
2024-03-09 18:26:30 +01:00
|
|
|
}
|
|
|
|
|
2024-03-23 13:26:08 +01:00
|
|
|
/// The response to an [`EngineCall`]. The type parameter determines the output type for pipeline
|
Add support for engine calls from plugins (#12029)
# Description
This allows plugins to make calls back to the engine to get config,
evaluate closures, and do other things that must be done within the
engine process.
Engine calls can both produce and consume streams as necessary. Closures
passed to plugins can both accept stream input and produce stream output
sent back to the plugin.
Engine calls referring to a plugin call's context can be processed as
long either the response hasn't been received, or the response created
streams that haven't ended yet.
This is a breaking API change for plugins. There are some pretty major
changes to the interface that plugins must implement, including:
1. Plugins now run with `&self` and must be `Sync`. Executing multiple
plugin calls in parallel is supported, and there's a chance that a
closure passed to a plugin could invoke the same plugin. Supporting
state across plugin invocations is left up to the plugin author to do in
whichever way they feel best, but the plugin object itself is still
shared. Even though the engine doesn't run multiple plugin calls through
the same process yet, I still considered it important to break the API
in this way at this stage. We might want to consider an optional
threadpool feature for performance.
2. Plugins take a reference to `EngineInterface`, which can be cloned.
This interface allows plugins to make calls back to the engine,
including for getting config and running closures.
3. Plugins no longer take the `config` parameter. This can be accessed
from the interface via the `.get_plugin_config()` engine call.
# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
Not only does this have plugin protocol changes, it will require plugins
to make some code changes before they will work again. But on the plus
side, the engine call feature is extensible, and we can add more things
to it as needed.
Plugin maintainers will have to change the trait signature at the very
least. If they were using `config`, they will have to call
`engine.get_plugin_config()` instead.
If they were using the mutable reference to the plugin, they will have
to come up with some strategy to work around it (for example, for `Inc`
I just cloned it). This shouldn't be such a big deal at the moment as
it's not like plugins have ever run as daemons with persistent state in
the past, and they don't in this PR either. But I thought it was
important to make the change before we support plugins as daemons, as an
exclusive mutable reference is not compatible with parallel plugin
calls.
I suggest this gets merged sometime *after* the current pending release,
so that we have some time to adjust to the previous plugin protocol
changes that don't require code changes before making ones that do.
# Tests + Formatting
- :green_circle: `toolkit fmt`
- :green_circle: `toolkit clippy`
- :green_circle: `toolkit test`
- :green_circle: `toolkit test stdlib`
# After Submitting
I will document the additional protocol features (`EngineCall`,
`EngineCallResponse`), and constraints on plugin call processing if
engine calls are used - basically, to be aware that an engine call could
result in a nested plugin call, so the plugin should be able to handle
that.
2024-03-09 18:26:30 +01:00
|
|
|
/// data.
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
|
|
pub enum EngineCallResponse<D> {
|
|
|
|
Error(ShellError),
|
|
|
|
PipelineData(D),
|
|
|
|
Config(Box<Config>),
|
2024-03-12 12:34:32 +01:00
|
|
|
ValueMap(HashMap<String, Value>),
|
Add support for engine calls from plugins (#12029)
# Description
This allows plugins to make calls back to the engine to get config,
evaluate closures, and do other things that must be done within the
engine process.
Engine calls can both produce and consume streams as necessary. Closures
passed to plugins can both accept stream input and produce stream output
sent back to the plugin.
Engine calls referring to a plugin call's context can be processed as
long either the response hasn't been received, or the response created
streams that haven't ended yet.
This is a breaking API change for plugins. There are some pretty major
changes to the interface that plugins must implement, including:
1. Plugins now run with `&self` and must be `Sync`. Executing multiple
plugin calls in parallel is supported, and there's a chance that a
closure passed to a plugin could invoke the same plugin. Supporting
state across plugin invocations is left up to the plugin author to do in
whichever way they feel best, but the plugin object itself is still
shared. Even though the engine doesn't run multiple plugin calls through
the same process yet, I still considered it important to break the API
in this way at this stage. We might want to consider an optional
threadpool feature for performance.
2. Plugins take a reference to `EngineInterface`, which can be cloned.
This interface allows plugins to make calls back to the engine,
including for getting config and running closures.
3. Plugins no longer take the `config` parameter. This can be accessed
from the interface via the `.get_plugin_config()` engine call.
# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
Not only does this have plugin protocol changes, it will require plugins
to make some code changes before they will work again. But on the plus
side, the engine call feature is extensible, and we can add more things
to it as needed.
Plugin maintainers will have to change the trait signature at the very
least. If they were using `config`, they will have to call
`engine.get_plugin_config()` instead.
If they were using the mutable reference to the plugin, they will have
to come up with some strategy to work around it (for example, for `Inc`
I just cloned it). This shouldn't be such a big deal at the moment as
it's not like plugins have ever run as daemons with persistent state in
the past, and they don't in this PR either. But I thought it was
important to make the change before we support plugins as daemons, as an
exclusive mutable reference is not compatible with parallel plugin
calls.
I suggest this gets merged sometime *after* the current pending release,
so that we have some time to adjust to the previous plugin protocol
changes that don't require code changes before making ones that do.
# Tests + Formatting
- :green_circle: `toolkit fmt`
- :green_circle: `toolkit clippy`
- :green_circle: `toolkit test`
- :green_circle: `toolkit test stdlib`
# After Submitting
I will document the additional protocol features (`EngineCall`,
`EngineCallResponse`), and constraints on plugin call processing if
engine calls are used - basically, to be aware that an engine call could
result in a nested plugin call, so the plugin should be able to handle
that.
2024-03-09 18:26:30 +01:00
|
|
|
}
|
|
|
|
|
2024-03-20 09:57:22 +01:00
|
|
|
impl<D> EngineCallResponse<D> {
|
|
|
|
/// Convert the data type from `D` to `T`. The function will not be called if the variant does
|
|
|
|
/// not contain data.
|
|
|
|
pub(crate) fn map_data<T>(
|
|
|
|
self,
|
|
|
|
f: impl FnOnce(D) -> Result<T, ShellError>,
|
|
|
|
) -> Result<EngineCallResponse<T>, ShellError> {
|
|
|
|
Ok(match self {
|
|
|
|
EngineCallResponse::Error(err) => EngineCallResponse::Error(err),
|
|
|
|
EngineCallResponse::PipelineData(data) => EngineCallResponse::PipelineData(f(data)?),
|
|
|
|
EngineCallResponse::Config(config) => EngineCallResponse::Config(config),
|
|
|
|
EngineCallResponse::ValueMap(map) => EngineCallResponse::ValueMap(map),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Add support for engine calls from plugins (#12029)
# Description
This allows plugins to make calls back to the engine to get config,
evaluate closures, and do other things that must be done within the
engine process.
Engine calls can both produce and consume streams as necessary. Closures
passed to plugins can both accept stream input and produce stream output
sent back to the plugin.
Engine calls referring to a plugin call's context can be processed as
long either the response hasn't been received, or the response created
streams that haven't ended yet.
This is a breaking API change for plugins. There are some pretty major
changes to the interface that plugins must implement, including:
1. Plugins now run with `&self` and must be `Sync`. Executing multiple
plugin calls in parallel is supported, and there's a chance that a
closure passed to a plugin could invoke the same plugin. Supporting
state across plugin invocations is left up to the plugin author to do in
whichever way they feel best, but the plugin object itself is still
shared. Even though the engine doesn't run multiple plugin calls through
the same process yet, I still considered it important to break the API
in this way at this stage. We might want to consider an optional
threadpool feature for performance.
2. Plugins take a reference to `EngineInterface`, which can be cloned.
This interface allows plugins to make calls back to the engine,
including for getting config and running closures.
3. Plugins no longer take the `config` parameter. This can be accessed
from the interface via the `.get_plugin_config()` engine call.
# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
Not only does this have plugin protocol changes, it will require plugins
to make some code changes before they will work again. But on the plus
side, the engine call feature is extensible, and we can add more things
to it as needed.
Plugin maintainers will have to change the trait signature at the very
least. If they were using `config`, they will have to call
`engine.get_plugin_config()` instead.
If they were using the mutable reference to the plugin, they will have
to come up with some strategy to work around it (for example, for `Inc`
I just cloned it). This shouldn't be such a big deal at the moment as
it's not like plugins have ever run as daemons with persistent state in
the past, and they don't in this PR either. But I thought it was
important to make the change before we support plugins as daemons, as an
exclusive mutable reference is not compatible with parallel plugin
calls.
I suggest this gets merged sometime *after* the current pending release,
so that we have some time to adjust to the previous plugin protocol
changes that don't require code changes before making ones that do.
# Tests + Formatting
- :green_circle: `toolkit fmt`
- :green_circle: `toolkit clippy`
- :green_circle: `toolkit test`
- :green_circle: `toolkit test stdlib`
# After Submitting
I will document the additional protocol features (`EngineCall`,
`EngineCallResponse`), and constraints on plugin call processing if
engine calls are used - basically, to be aware that an engine call could
result in a nested plugin call, so the plugin should be able to handle
that.
2024-03-09 18:26:30 +01:00
|
|
|
impl EngineCallResponse<PipelineData> {
|
|
|
|
/// Build an [`EngineCallResponse::PipelineData`] from a [`Value`]
|
|
|
|
pub(crate) fn value(value: Value) -> EngineCallResponse<PipelineData> {
|
|
|
|
EngineCallResponse::PipelineData(PipelineData::Value(value, None))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// An [`EngineCallResponse::PipelineData`] with [`PipelineData::Empty`]
|
|
|
|
pub(crate) const fn empty() -> EngineCallResponse<PipelineData> {
|
|
|
|
EngineCallResponse::PipelineData(PipelineData::Empty)
|
|
|
|
}
|
|
|
|
}
|