mirror of
https://github.com/nushell/nushell.git
synced 2025-06-30 14:40:06 +02:00
Support for all custom value operations on plugin custom values (#12088)
# Description Adds support for the following operations on plugin custom values, in addition to `to_base_value` which was already present: - `follow_path_int()` - `follow_path_string()` - `partial_cmp()` - `operation()` - `Drop` (notification, if opted into with `CustomValue::notify_plugin_on_drop`) There are additionally customizable methods within the `Plugin` and `StreamingPlugin` traits for implementing these functions in a way that requires access to the plugin state, as a registered handle model such as might be used in a dataframes plugin would. `Value::append` was also changed to handle custom values correctly. # User-Facing Changes - Signature of `CustomValue::follow_path_string` and `CustomValue::follow_path_int` changed to give access to the span of the custom value itself, useful for some errors. - Plugins using custom values have to be recompiled because the engine will try to do custom value operations that aren't supported - Plugins can do more things 🎉 # Tests + Formatting Tests were added for all of the new custom values functionality. - 🟢 `toolkit fmt` - 🟢 `toolkit clippy` - 🟢 `toolkit test` - 🟢 `toolkit test stdlib` # After Submitting - [ ] Document protocol reference `CustomValueOp` variants: - [ ] `FollowPathInt` - [ ] `FollowPathString` - [ ] `PartialCmp` - [ ] `Operation` - [ ] `Dropped` - [ ] Document `notify_on_drop` optional field in `PluginCustomValue`
This commit is contained in:
@ -1,7 +1,9 @@
|
||||
use nu_protocol::{CustomValue, ShellError, Span, Value};
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use nu_protocol::{ast, CustomValue, ShellError, Span, Value};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub struct CoolCustomValue {
|
||||
pub(crate) cool: String,
|
||||
}
|
||||
@ -44,7 +46,7 @@ impl CoolCustomValue {
|
||||
|
||||
#[typetag::serde]
|
||||
impl CustomValue for CoolCustomValue {
|
||||
fn clone_value(&self, span: nu_protocol::Span) -> Value {
|
||||
fn clone_value(&self, span: Span) -> Value {
|
||||
Value::custom_value(Box::new(self.clone()), span)
|
||||
}
|
||||
|
||||
@ -52,13 +54,94 @@ impl CustomValue for CoolCustomValue {
|
||||
self.typetag_name().to_string()
|
||||
}
|
||||
|
||||
fn to_base_value(&self, span: nu_protocol::Span) -> Result<Value, ShellError> {
|
||||
fn to_base_value(&self, span: Span) -> Result<Value, ShellError> {
|
||||
Ok(Value::string(
|
||||
format!("I used to be a custom value! My data was ({})", self.cool),
|
||||
span,
|
||||
))
|
||||
}
|
||||
|
||||
fn follow_path_int(
|
||||
&self,
|
||||
_self_span: Span,
|
||||
index: usize,
|
||||
path_span: Span,
|
||||
) -> Result<Value, ShellError> {
|
||||
if index == 0 {
|
||||
Ok(Value::string(&self.cool, path_span))
|
||||
} else {
|
||||
Err(ShellError::AccessBeyondEnd {
|
||||
max_idx: 0,
|
||||
span: path_span,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn follow_path_string(
|
||||
&self,
|
||||
self_span: Span,
|
||||
column_name: String,
|
||||
path_span: Span,
|
||||
) -> Result<Value, ShellError> {
|
||||
if column_name == "cool" {
|
||||
Ok(Value::string(&self.cool, path_span))
|
||||
} else {
|
||||
Err(ShellError::CantFindColumn {
|
||||
col_name: column_name,
|
||||
span: path_span,
|
||||
src_span: self_span,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn partial_cmp(&self, other: &Value) -> Option<Ordering> {
|
||||
if let Value::CustomValue { val, .. } = other {
|
||||
val.as_any()
|
||||
.downcast_ref()
|
||||
.and_then(|other: &CoolCustomValue| PartialOrd::partial_cmp(self, other))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn operation(
|
||||
&self,
|
||||
lhs_span: Span,
|
||||
operator: ast::Operator,
|
||||
op_span: Span,
|
||||
right: &Value,
|
||||
) -> Result<Value, ShellError> {
|
||||
match operator {
|
||||
// Append the string inside `cool`
|
||||
ast::Operator::Math(ast::Math::Append) => {
|
||||
if let Some(right) = right
|
||||
.as_custom_value()
|
||||
.ok()
|
||||
.and_then(|c| c.as_any().downcast_ref::<CoolCustomValue>())
|
||||
{
|
||||
Ok(Value::custom_value(
|
||||
Box::new(CoolCustomValue {
|
||||
cool: format!("{}{}", self.cool, right.cool),
|
||||
}),
|
||||
op_span,
|
||||
))
|
||||
} else {
|
||||
Err(ShellError::OperatorMismatch {
|
||||
op_span,
|
||||
lhs_ty: self.typetag_name().into(),
|
||||
lhs_span,
|
||||
rhs_ty: right.get_type().to_string(),
|
||||
rhs_span: right.span(),
|
||||
})
|
||||
}
|
||||
}
|
||||
_ => Err(ShellError::UnsupportedOperator {
|
||||
operator,
|
||||
span: op_span,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
50
crates/nu_plugin_custom_values/src/drop_check.rs
Normal file
50
crates/nu_plugin_custom_values/src/drop_check.rs
Normal file
@ -0,0 +1,50 @@
|
||||
use nu_protocol::{record, CustomValue, ShellError, Span, Value};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct DropCheck {
|
||||
pub(crate) msg: String,
|
||||
}
|
||||
|
||||
impl DropCheck {
|
||||
pub(crate) fn new(msg: String) -> DropCheck {
|
||||
DropCheck { msg }
|
||||
}
|
||||
|
||||
pub(crate) fn into_value(self, span: Span) -> Value {
|
||||
Value::custom_value(Box::new(self), span)
|
||||
}
|
||||
|
||||
pub(crate) fn notify(&self) {
|
||||
eprintln!("DropCheck was dropped: {}", self.msg);
|
||||
}
|
||||
}
|
||||
|
||||
#[typetag::serde]
|
||||
impl CustomValue for DropCheck {
|
||||
fn clone_value(&self, span: Span) -> Value {
|
||||
self.clone().into_value(span)
|
||||
}
|
||||
|
||||
fn value_string(&self) -> String {
|
||||
"DropCheck".into()
|
||||
}
|
||||
|
||||
fn to_base_value(&self, span: Span) -> Result<Value, ShellError> {
|
||||
Ok(Value::record(
|
||||
record! {
|
||||
"msg" => Value::string(&self.msg, span)
|
||||
},
|
||||
span,
|
||||
))
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn notify_plugin_on_drop(&self) -> bool {
|
||||
// This is what causes Nushell to let us know when the value is dropped
|
||||
true
|
||||
}
|
||||
}
|
@ -1,11 +1,14 @@
|
||||
mod cool_custom_value;
|
||||
mod drop_check;
|
||||
mod second_custom_value;
|
||||
|
||||
use cool_custom_value::CoolCustomValue;
|
||||
use drop_check::DropCheck;
|
||||
use second_custom_value::SecondCustomValue;
|
||||
|
||||
use nu_plugin::{serve_plugin, EngineInterface, MsgPackSerializer, Plugin};
|
||||
use nu_plugin::{EvaluatedCall, LabeledError};
|
||||
use nu_protocol::{Category, PluginSignature, ShellError, SyntaxShape, Value};
|
||||
use second_custom_value::SecondCustomValue;
|
||||
use nu_protocol::{Category, CustomValue, PluginSignature, ShellError, SyntaxShape, Value};
|
||||
|
||||
struct CustomValuePlugin;
|
||||
|
||||
@ -34,6 +37,10 @@ impl Plugin for CustomValuePlugin {
|
||||
"the custom value to update",
|
||||
)
|
||||
.category(Category::Experimental),
|
||||
PluginSignature::build("custom-value drop-check")
|
||||
.usage("Generates a custom value that prints a message when dropped")
|
||||
.required("msg", SyntaxShape::String, "the message to print on drop")
|
||||
.category(Category::Experimental),
|
||||
]
|
||||
}
|
||||
|
||||
@ -49,6 +56,7 @@ impl Plugin for CustomValuePlugin {
|
||||
"custom-value generate2" => self.generate2(engine, call),
|
||||
"custom-value update" => self.update(call, input),
|
||||
"custom-value update-arg" => self.update(call, &call.req(0)?),
|
||||
"custom-value drop-check" => self.drop_check(call),
|
||||
_ => Err(LabeledError {
|
||||
label: "Plugin call with wrong name signature".into(),
|
||||
msg: "the signature used to call the plugin does not match any name in the plugin signature vector".into(),
|
||||
@ -56,6 +64,18 @@ impl Plugin for CustomValuePlugin {
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn custom_value_dropped(
|
||||
&self,
|
||||
_engine: &EngineInterface,
|
||||
custom_value: Box<dyn CustomValue>,
|
||||
) -> Result<(), LabeledError> {
|
||||
// This is how we implement our drop behavior for DropCheck.
|
||||
if let Some(drop_check) = custom_value.as_any().downcast_ref::<DropCheck>() {
|
||||
drop_check.notify();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl CustomValuePlugin {
|
||||
@ -101,6 +121,10 @@ impl CustomValuePlugin {
|
||||
}
|
||||
.into())
|
||||
}
|
||||
|
||||
fn drop_check(&self, call: &EvaluatedCall) -> Result<Value, LabeledError> {
|
||||
Ok(DropCheck::new(call.req(0)?).into_value(call.head))
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
Reference in New Issue
Block a user