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, pub named: Vec<(Spanned, Option)>, } impl EvaluatedCall { pub fn try_from_call( call: &Call, engine_state: &EngineState, stack: &mut Stack, ) -> Result { let positional = call .positional_iter() .map(|expr| eval_expression(engine_state, stack, expr)) .collect::, 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 { for name in &self.named { if flag_name == name.0.item { return name.1.clone(); } } None } pub fn nth(&self, pos: usize) -> Option { self.positional.get(pos).cloned() } pub fn get_flag(&self, name: &str) -> Result, ShellError> { if let Some(value) = self.get_flag_value(name) { FromValue::from_value(&value).map(Some) } else { Ok(None) } } pub fn rest(&self, starting_pos: usize) -> Result, ShellError> { self.positional .iter() .skip(starting_pos) .map(|value| FromValue::from_value(value)) .collect() } pub fn opt(&self, pos: usize) -> Result, ShellError> { if let Some(value) = self.nth(pos) { FromValue::from_value(&value).map(Some) } else { Ok(None) } } pub fn req(&self, pos: usize) -> Result { 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 = 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 = call.opt(1).unwrap(); assert_eq!(optional, Some("something".to_string())); let rest: Vec = call.rest(1).unwrap(); assert_eq!(rest, vec!["something".to_string()]); } }