mirror of
https://github.com/nushell/nushell.git
synced 2025-08-19 15:02:25 +02:00
Plugin json (#474)
* json encoder * thread to pass messages * description for example
This commit is contained in:
163
crates/nu-plugin/src/protocol/evaluated_call.rs
Normal file
163
crates/nu-plugin/src/protocol/evaluated_call.rs
Normal file
@@ -0,0 +1,163 @@
|
||||
use nu_engine::eval_expression;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{EngineState, Stack},
|
||||
FromValue, ShellError, Span, Spanned, Value,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// The evaluated call is used with the Plugins because the plugin doesn't have
|
||||
// access to the Stack and the EngineState. For that reason, before encoding the
|
||||
// message to the plugin all the arguments to the original call (which are expressions)
|
||||
// are evaluated and passed to Values
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct EvaluatedCall {
|
||||
pub head: Span,
|
||||
pub positional: Vec<Value>,
|
||||
pub named: Vec<(Spanned<String>, Option<Value>)>,
|
||||
}
|
||||
|
||||
impl EvaluatedCall {
|
||||
pub fn try_from_call(
|
||||
call: &Call,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
) -> Result<Self, ShellError> {
|
||||
let positional = call
|
||||
.positional
|
||||
.iter()
|
||||
.map(|expr| eval_expression(engine_state, stack, expr))
|
||||
.collect::<Result<Vec<Value>, ShellError>>()?;
|
||||
|
||||
let mut named = Vec::with_capacity(call.named.len());
|
||||
for (string, expr) in call.named.iter() {
|
||||
let value = match expr {
|
||||
None => None,
|
||||
Some(expr) => Some(eval_expression(engine_state, stack, expr)?),
|
||||
};
|
||||
|
||||
named.push((string.clone(), value))
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
head: call.head,
|
||||
positional,
|
||||
named,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn has_flag(&self, flag_name: &str) -> bool {
|
||||
for name in &self.named {
|
||||
if flag_name == name.0.item {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn get_flag_value(&self, flag_name: &str) -> Option<Value> {
|
||||
for name in &self.named {
|
||||
if flag_name == name.0.item {
|
||||
return name.1.clone();
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn nth(&self, pos: usize) -> Option<Value> {
|
||||
self.positional.get(pos).cloned()
|
||||
}
|
||||
|
||||
pub fn get_flag<T: FromValue>(&self, name: &str) -> Result<Option<T>, ShellError> {
|
||||
if let Some(value) = self.get_flag_value(name) {
|
||||
FromValue::from_value(&value).map(Some)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rest<T: FromValue>(&self, starting_pos: usize) -> Result<Vec<T>, ShellError> {
|
||||
self.positional
|
||||
.iter()
|
||||
.skip(starting_pos)
|
||||
.map(|value| FromValue::from_value(value))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn opt<T: FromValue>(&self, pos: usize) -> Result<Option<T>, ShellError> {
|
||||
if let Some(value) = self.nth(pos) {
|
||||
FromValue::from_value(&value).map(Some)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn req<T: FromValue>(&self, pos: usize) -> Result<T, ShellError> {
|
||||
if let Some(value) = self.nth(pos) {
|
||||
FromValue::from_value(&value)
|
||||
} else {
|
||||
Err(ShellError::AccessBeyondEnd(
|
||||
self.positional.len(),
|
||||
self.head,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use nu_protocol::{Span, Spanned, Value};
|
||||
|
||||
#[test]
|
||||
fn call_to_value() {
|
||||
let call = EvaluatedCall {
|
||||
head: Span { start: 0, end: 10 },
|
||||
positional: vec![
|
||||
Value::Float {
|
||||
val: 1.0,
|
||||
span: Span { start: 0, end: 10 },
|
||||
},
|
||||
Value::String {
|
||||
val: "something".into(),
|
||||
span: Span { start: 0, end: 10 },
|
||||
},
|
||||
],
|
||||
named: vec![
|
||||
(
|
||||
Spanned {
|
||||
item: "name".to_string(),
|
||||
span: Span { start: 0, end: 10 },
|
||||
},
|
||||
Some(Value::Float {
|
||||
val: 1.0,
|
||||
span: Span { start: 0, end: 10 },
|
||||
}),
|
||||
),
|
||||
(
|
||||
Spanned {
|
||||
item: "flag".to_string(),
|
||||
span: Span { start: 0, end: 10 },
|
||||
},
|
||||
None,
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
let name: Option<f64> = call.get_flag("name").unwrap();
|
||||
assert_eq!(name, Some(1.0));
|
||||
|
||||
assert!(call.has_flag("flag"));
|
||||
|
||||
let required: f64 = call.req(0).unwrap();
|
||||
assert!((required - 1.0).abs() < f64::EPSILON);
|
||||
|
||||
let optional: Option<String> = call.opt(1).unwrap();
|
||||
assert_eq!(optional, Some("something".to_string()));
|
||||
|
||||
let rest: Vec<String> = call.rest(1).unwrap();
|
||||
assert_eq!(rest, vec!["something".to_string()]);
|
||||
}
|
||||
}
|
90
crates/nu-plugin/src/protocol/mod.rs
Normal file
90
crates/nu-plugin/src/protocol/mod.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
mod evaluated_call;
|
||||
|
||||
pub use evaluated_call::EvaluatedCall;
|
||||
use nu_protocol::{ShellError, Signature, Span, Value};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct CallInfo {
|
||||
pub name: String,
|
||||
pub call: EvaluatedCall,
|
||||
pub input: Value,
|
||||
}
|
||||
|
||||
// Information sent to the plugin
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub enum PluginCall {
|
||||
Signature,
|
||||
CallInfo(Box<CallInfo>),
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Serialize, Deserialize, Debug)]
|
||||
pub struct LabeledError {
|
||||
pub label: String,
|
||||
pub msg: String,
|
||||
pub span: Option<Span>,
|
||||
}
|
||||
|
||||
impl From<LabeledError> for ShellError {
|
||||
fn from(error: LabeledError) -> Self {
|
||||
match error.span {
|
||||
Some(span) => ShellError::SpannedLabeledError(error.label, error.msg, span),
|
||||
None => ShellError::LabeledError(error.label, error.msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ShellError> for LabeledError {
|
||||
fn from(error: ShellError) -> Self {
|
||||
match error {
|
||||
ShellError::SpannedLabeledError(label, msg, span) => LabeledError {
|
||||
label,
|
||||
msg,
|
||||
span: Some(span),
|
||||
},
|
||||
ShellError::LabeledError(label, msg) => LabeledError {
|
||||
label,
|
||||
msg,
|
||||
span: None,
|
||||
},
|
||||
ShellError::CantConvert(expected, input, span) => LabeledError {
|
||||
label: format!("Can't convert to {}", expected),
|
||||
msg: format!("can't convert {} to {}", expected, input),
|
||||
span: Some(span),
|
||||
},
|
||||
ShellError::DidYouMean(suggestion, span) => LabeledError {
|
||||
label: "Name not found".into(),
|
||||
msg: format!("did you mean '{}'", suggestion),
|
||||
span: Some(span),
|
||||
},
|
||||
ShellError::PluginFailedToLoad(msg) => LabeledError {
|
||||
label: "Plugin failed to load".into(),
|
||||
msg,
|
||||
span: None,
|
||||
},
|
||||
ShellError::PluginFailedToEncode(msg) => LabeledError {
|
||||
label: "Plugin failed to encode".into(),
|
||||
msg,
|
||||
span: None,
|
||||
},
|
||||
ShellError::PluginFailedToDecode(msg) => LabeledError {
|
||||
label: "Plugin failed to decode".into(),
|
||||
msg,
|
||||
span: None,
|
||||
},
|
||||
err => LabeledError {
|
||||
label: "Error - Add to LabeledError From<ShellError>".into(),
|
||||
msg: err.to_string(),
|
||||
span: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Information received from the plugin
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub enum PluginResponse {
|
||||
Error(LabeledError),
|
||||
Signature(Vec<Signature>),
|
||||
Value(Box<Value>),
|
||||
}
|
Reference in New Issue
Block a user