Allow plugins to receive configuration from the nushell configuration (#10955)

# Description

When nushell calls a plugin it now sends a configuration `Value` from
the nushell config under `$env.config.plugins.PLUGIN_SHORT_NAME`. This
allows plugin authors to read configuration provided by plugin users.

The `PLUGIN_SHORT_NAME` must match the registered filename after
`nu_plugin_`. If you register `target/debug/nu_plugin_config` the
`PLUGIN_NAME` will be `config` and the nushell config will loook like:

        $env.config = {
          # ...
          plugins: {
            config: [
              some
              values
            ]
          }
        }

Configuration may also use a closure which allows passing values from
`$env` to a plugin:

        $env.config = {
          # ...
          plugins: {
            config: {||
              $env.some_value
            }
          }
        }

This is a breaking change for the plugin API as the `Plugin::run()`
function now accepts a new configuration argument which is an
`&Option<Value>`. If no configuration was supplied the value is `None`.

Plugins compiled after this change should work with older nushell, and
will behave as if the configuration was not set.

Initially discussed in #10867

# User-Facing Changes

* Plugins can read configuration data stored in `$env.config.plugins`
* The plugin `CallInfo` now includes a `config` entry, existing plugins
will require updates

# Tests + Formatting

- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`

# After Submitting

- [ ] Update [Creating a plugin (in
Rust)](https://www.nushell.sh/contributor-book/plugins.html#creating-a-plugin-in-rust)
[source](https://github.com/nushell/nushell.github.io/blob/main/contributor-book/plugins.md)
- [ ] Add "Configuration" section to [Plugins
documentation](https://www.nushell.sh/contributor-book/plugins.html)
This commit is contained in:
Eric Hodel
2024-01-15 00:59:47 -08:00
committed by GitHub
parent e72a4116ec
commit 7071617f18
21 changed files with 256 additions and 4 deletions

View File

@ -28,6 +28,7 @@
//! fn run(
//! &mut self,
//! name: &str,
//! config: &Option<Value>,
//! call: &EvaluatedCall,
//! input: &Value
//! ) -> Result<Value, LabeledError> {

View File

@ -6,6 +6,7 @@ use crate::protocol::{
};
use std::path::{Path, PathBuf};
use nu_engine::eval_block;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ast::Call, PluginSignature, Signature};
use nu_protocol::{Example, PipelineData, ShellError, Value};
@ -126,10 +127,48 @@ impl Command for PluginDeclaration {
value => CallInput::Value(value),
};
// Fetch the configuration for a plugin
//
// The `plugin` must match the registered name of a plugin. For
// `register nu_plugin_example` the plugin config lookup uses `"example"`
let config = self
.filename
.file_stem()
.and_then(|file| {
file.to_string_lossy()
.clone()
.strip_prefix("nu_plugin_")
.map(|name| {
nu_engine::get_config(engine_state, stack)
.plugins
.get(name)
.cloned()
})
})
.flatten()
.map(|value| {
let span = value.span();
match value {
Value::Closure { val, .. } => {
let input = PipelineData::Empty;
let block = engine_state.get_block(val.block_id).clone();
let mut stack = stack.captures_to_stack(val.captures);
match eval_block(engine_state, &mut stack, &block, input, false, false) {
Ok(v) => v.into_value(span),
Err(e) => Value::error(e, call.head),
}
}
_ => value.clone(),
}
});
let plugin_call = PluginCall::CallInfo(CallInfo {
name: self.name.clone(),
call: EvaluatedCall::try_from_call(call, engine_state, stack)?,
input,
config,
});
let encoding = {

View File

@ -217,6 +217,7 @@ pub fn get_signature(
/// fn run(
/// &mut self,
/// name: &str,
/// config: &Option<Value>,
/// call: &EvaluatedCall,
/// input: &Value,
/// ) -> Result<Value, LabeledError> {
@ -246,6 +247,7 @@ pub trait Plugin {
fn run(
&mut self,
name: &str,
config: &Option<Value>,
call: &EvaluatedCall,
input: &Value,
) -> Result<Value, LabeledError>;
@ -264,7 +266,7 @@ pub trait Plugin {
/// # impl MyPlugin { fn new() -> Self { Self }}
/// # impl Plugin for MyPlugin {
/// # fn signature(&self) -> Vec<PluginSignature> {todo!();}
/// # fn run(&mut self, name: &str, call: &EvaluatedCall, input: &Value)
/// # fn run(&mut self, name: &str, config: &Option<Value>, call: &EvaluatedCall, input: &Value)
/// # -> Result<Value, LabeledError> {todo!();}
/// # }
/// fn main() {
@ -333,7 +335,9 @@ pub fn serve_plugin(plugin: &mut impl Plugin, encoder: impl PluginEncoder) {
};
let value = match input {
Ok(input) => plugin.run(&call_info.name, &call_info.call, &input),
Ok(input) => {
plugin.run(&call_info.name, &call_info.config, &call_info.call, &input)
}
Err(err) => Err(err.into()),
};

View File

@ -13,6 +13,7 @@ pub struct CallInfo {
pub name: String,
pub call: EvaluatedCall,
pub input: CallInput,
pub config: Option<Value>,
}
#[derive(Serialize, Deserialize, Debug, PartialEq)]

View File

@ -106,6 +106,7 @@ mod tests {
name: name.clone(),
call: call.clone(),
input: CallInput::Value(input.clone()),
config: None,
});
let encoder = JsonSerializer {};

View File

@ -107,6 +107,7 @@ mod tests {
name: name.clone(),
call: call.clone(),
input: CallInput::Value(input.clone()),
config: None,
});
let encoder = MsgPackSerializer {};