nu_plugin_protocol/
evaluated_call.rs

1use nu_protocol::{
2    ast::{self, Expression},
3    engine::{Call, CallImpl, EngineState, Stack},
4    ir, FromValue, ShellError, Span, Spanned, Value,
5};
6use serde::{Deserialize, Serialize};
7
8/// A representation of the plugin's invocation command including command line args
9///
10/// The `EvaluatedCall` contains information about the way a `Plugin` was invoked representing the
11/// [`Span`] corresponding to the invocation as well as the arguments it was invoked with. It is
12/// one of the items passed to `PluginCommand::run()`, along with the plugin reference, the engine
13/// interface, and a [`Value`] that represents the input.
14///
15/// The evaluated call is used with the Plugins because the plugin doesn't have
16/// access to the Stack and the EngineState the way a built in command might. For that
17/// reason, before encoding the message to the plugin all the arguments to the original
18/// call (which are expressions) are evaluated and passed to Values
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct EvaluatedCall {
21    /// Span of the command invocation
22    pub head: Span,
23    /// Values of positional arguments
24    pub positional: Vec<Value>,
25    /// Names and values of named arguments
26    pub named: Vec<(Spanned<String>, Option<Value>)>,
27}
28
29impl EvaluatedCall {
30    /// Create a new [`EvaluatedCall`] with the given head span.
31    pub fn new(head: Span) -> EvaluatedCall {
32        EvaluatedCall {
33            head,
34            positional: vec![],
35            named: vec![],
36        }
37    }
38
39    /// Add a positional argument to an [`EvaluatedCall`].
40    ///
41    /// # Example
42    ///
43    /// ```rust
44    /// # use nu_protocol::{Value, Span, IntoSpanned};
45    /// # use nu_plugin_protocol::EvaluatedCall;
46    /// # let head = Span::test_data();
47    /// let mut call = EvaluatedCall::new(head);
48    /// call.add_positional(Value::test_int(1337));
49    /// ```
50    pub fn add_positional(&mut self, value: Value) -> &mut Self {
51        self.positional.push(value);
52        self
53    }
54
55    /// Add a named argument to an [`EvaluatedCall`].
56    ///
57    /// # Example
58    ///
59    /// ```rust
60    /// # use nu_protocol::{Value, Span, IntoSpanned};
61    /// # use nu_plugin_protocol::EvaluatedCall;
62    /// # let head = Span::test_data();
63    /// let mut call = EvaluatedCall::new(head);
64    /// call.add_named("foo".into_spanned(head), Value::test_string("bar"));
65    /// ```
66    pub fn add_named(&mut self, name: Spanned<impl Into<String>>, value: Value) -> &mut Self {
67        self.named.push((name.map(Into::into), Some(value)));
68        self
69    }
70
71    /// Add a flag argument to an [`EvaluatedCall`]. A flag argument is a named argument with no
72    /// value.
73    ///
74    /// # Example
75    ///
76    /// ```rust
77    /// # use nu_protocol::{Value, Span, IntoSpanned};
78    /// # use nu_plugin_protocol::EvaluatedCall;
79    /// # let head = Span::test_data();
80    /// let mut call = EvaluatedCall::new(head);
81    /// call.add_flag("pretty".into_spanned(head));
82    /// ```
83    pub fn add_flag(&mut self, name: Spanned<impl Into<String>>) -> &mut Self {
84        self.named.push((name.map(Into::into), None));
85        self
86    }
87
88    /// Builder variant of [`.add_positional()`](Self::add_positional).
89    pub fn with_positional(mut self, value: Value) -> Self {
90        self.add_positional(value);
91        self
92    }
93
94    /// Builder variant of [`.add_named()`](Self::add_named).
95    pub fn with_named(mut self, name: Spanned<impl Into<String>>, value: Value) -> Self {
96        self.add_named(name, value);
97        self
98    }
99
100    /// Builder variant of [`.add_flag()`](Self::add_flag).
101    pub fn with_flag(mut self, name: Spanned<impl Into<String>>) -> Self {
102        self.add_flag(name);
103        self
104    }
105
106    /// Try to create an [`EvaluatedCall`] from a command `Call`.
107    pub fn try_from_call(
108        call: &Call,
109        engine_state: &EngineState,
110        stack: &mut Stack,
111        eval_expression_fn: fn(&EngineState, &mut Stack, &Expression) -> Result<Value, ShellError>,
112    ) -> Result<Self, ShellError> {
113        match &call.inner {
114            CallImpl::AstRef(call) => {
115                Self::try_from_ast_call(call, engine_state, stack, eval_expression_fn)
116            }
117            CallImpl::AstBox(call) => {
118                Self::try_from_ast_call(call, engine_state, stack, eval_expression_fn)
119            }
120            CallImpl::IrRef(call) => Self::try_from_ir_call(call, stack),
121            CallImpl::IrBox(call) => Self::try_from_ir_call(call, stack),
122        }
123    }
124
125    fn try_from_ast_call(
126        call: &ast::Call,
127        engine_state: &EngineState,
128        stack: &mut Stack,
129        eval_expression_fn: fn(&EngineState, &mut Stack, &Expression) -> Result<Value, ShellError>,
130    ) -> Result<Self, ShellError> {
131        let positional =
132            call.rest_iter_flattened(0, |expr| eval_expression_fn(engine_state, stack, expr))?;
133
134        let mut named = Vec::with_capacity(call.named_len());
135        for (string, _, expr) in call.named_iter() {
136            let value = match expr {
137                None => None,
138                Some(expr) => Some(eval_expression_fn(engine_state, stack, expr)?),
139            };
140
141            named.push((string.clone(), value))
142        }
143
144        Ok(Self {
145            head: call.head,
146            positional,
147            named,
148        })
149    }
150
151    fn try_from_ir_call(call: &ir::Call, stack: &Stack) -> Result<Self, ShellError> {
152        let positional = call.rest_iter_flattened(stack, 0)?;
153
154        let mut named = Vec::with_capacity(call.named_len(stack));
155        named.extend(
156            call.named_iter(stack)
157                .map(|(name, value)| (name.map(|s| s.to_owned()), value.cloned())),
158        );
159
160        Ok(Self {
161            head: call.head,
162            positional,
163            named,
164        })
165    }
166
167    /// Check if a flag (named parameter that does not take a value) is set
168    /// Returns Ok(true) if flag is set or passed true value
169    /// Returns Ok(false) if flag is not set or passed false value
170    /// Returns Err if passed value is not a boolean
171    ///
172    /// # Examples
173    /// Invoked as `my_command --foo`:
174    /// ```
175    /// # use nu_protocol::{Spanned, Span, Value};
176    /// # use nu_plugin_protocol::EvaluatedCall;
177    /// # let null_span = Span::new(0, 0);
178    /// # let call = EvaluatedCall {
179    /// #     head: null_span,
180    /// #     positional: Vec::new(),
181    /// #     named: vec![(
182    /// #         Spanned { item: "foo".to_owned(), span: null_span},
183    /// #         None
184    /// #     )],
185    /// # };
186    /// assert!(call.has_flag("foo").unwrap());
187    /// ```
188    ///
189    /// Invoked as `my_command --bar`:
190    /// ```
191    /// # use nu_protocol::{Spanned, Span, Value};
192    /// # use nu_plugin_protocol::EvaluatedCall;
193    /// # let null_span = Span::new(0, 0);
194    /// # let call = EvaluatedCall {
195    /// #     head: null_span,
196    /// #     positional: Vec::new(),
197    /// #     named: vec![(
198    /// #         Spanned { item: "bar".to_owned(), span: null_span},
199    /// #         None
200    /// #     )],
201    /// # };
202    /// assert!(!call.has_flag("foo").unwrap());
203    /// ```
204    ///
205    /// Invoked as `my_command --foo=true`:
206    /// ```
207    /// # use nu_protocol::{Spanned, Span, Value};
208    /// # use nu_plugin_protocol::EvaluatedCall;
209    /// # let null_span = Span::new(0, 0);
210    /// # let call = EvaluatedCall {
211    /// #     head: null_span,
212    /// #     positional: Vec::new(),
213    /// #     named: vec![(
214    /// #         Spanned { item: "foo".to_owned(), span: null_span},
215    /// #         Some(Value::bool(true, Span::unknown()))
216    /// #     )],
217    /// # };
218    /// assert!(call.has_flag("foo").unwrap());
219    /// ```
220    ///
221    /// Invoked as `my_command --foo=false`:
222    /// ```
223    /// # use nu_protocol::{Spanned, Span, Value};
224    /// # use nu_plugin_protocol::EvaluatedCall;
225    /// # let null_span = Span::new(0, 0);
226    /// # let call = EvaluatedCall {
227    /// #     head: null_span,
228    /// #     positional: Vec::new(),
229    /// #     named: vec![(
230    /// #         Spanned { item: "foo".to_owned(), span: null_span},
231    /// #         Some(Value::bool(false, Span::unknown()))
232    /// #     )],
233    /// # };
234    /// assert!(!call.has_flag("foo").unwrap());
235    /// ```
236    ///
237    /// Invoked with wrong type as `my_command --foo=1`:
238    /// ```
239    /// # use nu_protocol::{Spanned, Span, Value};
240    /// # use nu_plugin_protocol::EvaluatedCall;
241    /// # let null_span = Span::new(0, 0);
242    /// # let call = EvaluatedCall {
243    /// #     head: null_span,
244    /// #     positional: Vec::new(),
245    /// #     named: vec![(
246    /// #         Spanned { item: "foo".to_owned(), span: null_span},
247    /// #         Some(Value::int(1, Span::unknown()))
248    /// #     )],
249    /// # };
250    /// assert!(call.has_flag("foo").is_err());
251    /// ```
252    pub fn has_flag(&self, flag_name: &str) -> Result<bool, ShellError> {
253        for name in &self.named {
254            if flag_name == name.0.item {
255                return match &name.1 {
256                    Some(Value::Bool { val, .. }) => Ok(*val),
257                    None => Ok(true),
258                    Some(result) => Err(ShellError::CantConvert {
259                        to_type: "bool".into(),
260                        from_type: result.get_type().to_string(),
261                        span: result.span(),
262                        help: Some("".into()),
263                    }),
264                };
265            }
266        }
267
268        Ok(false)
269    }
270
271    /// Returns the [`Span`] of the name of an optional named argument.
272    ///
273    /// This can be used in errors for named arguments that don't take values.
274    pub fn get_flag_span(&self, flag_name: &str) -> Option<Span> {
275        self.named
276            .iter()
277            .find(|(name, _)| name.item == flag_name)
278            .map(|(name, _)| name.span)
279    }
280
281    /// Returns the [`Value`] of an optional named argument
282    ///
283    /// # Examples
284    /// Invoked as `my_command --foo 123`:
285    /// ```
286    /// # use nu_protocol::{Spanned, Span, Value};
287    /// # use nu_plugin_protocol::EvaluatedCall;
288    /// # let null_span = Span::new(0, 0);
289    /// # let call = EvaluatedCall {
290    /// #     head: null_span,
291    /// #     positional: Vec::new(),
292    /// #     named: vec![(
293    /// #         Spanned { item: "foo".to_owned(), span: null_span},
294    /// #         Some(Value::int(123, null_span))
295    /// #     )],
296    /// # };
297    /// let opt_foo = match call.get_flag_value("foo") {
298    ///     Some(Value::Int { val, .. }) => Some(val),
299    ///     None => None,
300    ///     _ => panic!(),
301    /// };
302    /// assert_eq!(opt_foo, Some(123));
303    /// ```
304    ///
305    /// Invoked as `my_command`:
306    /// ```
307    /// # use nu_protocol::{Spanned, Span, Value};
308    /// # use nu_plugin_protocol::EvaluatedCall;
309    /// # let null_span = Span::new(0, 0);
310    /// # let call = EvaluatedCall {
311    /// #     head: null_span,
312    /// #     positional: Vec::new(),
313    /// #     named: vec![],
314    /// # };
315    /// let opt_foo = match call.get_flag_value("foo") {
316    ///     Some(Value::Int { val, .. }) => Some(val),
317    ///     None => None,
318    ///     _ => panic!(),
319    /// };
320    /// assert_eq!(opt_foo, None);
321    /// ```
322    pub fn get_flag_value(&self, flag_name: &str) -> Option<Value> {
323        for name in &self.named {
324            if flag_name == name.0.item {
325                return name.1.clone();
326            }
327        }
328
329        None
330    }
331
332    /// Returns the [`Value`] of a given (zero indexed) positional argument, if present
333    ///
334    /// Examples:
335    /// Invoked as `my_command a b c`:
336    /// ```
337    /// # use nu_protocol::{Spanned, Span, Value};
338    /// # use nu_plugin_protocol::EvaluatedCall;
339    /// # let null_span = Span::new(0, 0);
340    /// # let call = EvaluatedCall {
341    /// #     head: null_span,
342    /// #     positional: vec![
343    /// #         Value::string("a".to_owned(), null_span),
344    /// #         Value::string("b".to_owned(), null_span),
345    /// #         Value::string("c".to_owned(), null_span),
346    /// #     ],
347    /// #     named: vec![],
348    /// # };
349    /// let arg = match call.nth(1) {
350    ///     Some(Value::String { val, .. }) => val,
351    ///     _ => panic!(),
352    /// };
353    /// assert_eq!(arg, "b".to_owned());
354    ///
355    /// let arg = call.nth(7);
356    /// assert!(arg.is_none());
357    /// ```
358    pub fn nth(&self, pos: usize) -> Option<Value> {
359        self.positional.get(pos).cloned()
360    }
361
362    /// Returns the value of a named argument interpreted as type `T`
363    ///
364    /// # Examples
365    /// Invoked as `my_command --foo 123`:
366    /// ```
367    /// # use nu_protocol::{Spanned, Span, Value};
368    /// # use nu_plugin_protocol::EvaluatedCall;
369    /// # let null_span = Span::new(0, 0);
370    /// # let call = EvaluatedCall {
371    /// #     head: null_span,
372    /// #     positional: Vec::new(),
373    /// #     named: vec![(
374    /// #         Spanned { item: "foo".to_owned(), span: null_span},
375    /// #         Some(Value::int(123, null_span))
376    /// #     )],
377    /// # };
378    /// let foo = call.get_flag::<i64>("foo");
379    /// assert_eq!(foo.unwrap(), Some(123));
380    /// ```
381    ///
382    /// Invoked as `my_command --bar 123`:
383    /// ```
384    /// # use nu_protocol::{Spanned, Span, Value};
385    /// # use nu_plugin_protocol::EvaluatedCall;
386    /// # let null_span = Span::new(0, 0);
387    /// # let call = EvaluatedCall {
388    /// #     head: null_span,
389    /// #     positional: Vec::new(),
390    /// #     named: vec![(
391    /// #         Spanned { item: "bar".to_owned(), span: null_span},
392    /// #         Some(Value::int(123, null_span))
393    /// #     )],
394    /// # };
395    /// let foo = call.get_flag::<i64>("foo");
396    /// assert_eq!(foo.unwrap(), None);
397    /// ```
398    ///
399    /// Invoked as `my_command --foo abc`:
400    /// ```
401    /// # use nu_protocol::{Spanned, Span, Value};
402    /// # use nu_plugin_protocol::EvaluatedCall;
403    /// # let null_span = Span::new(0, 0);
404    /// # let call = EvaluatedCall {
405    /// #     head: null_span,
406    /// #     positional: Vec::new(),
407    /// #     named: vec![(
408    /// #         Spanned { item: "foo".to_owned(), span: null_span},
409    /// #         Some(Value::string("abc".to_owned(), null_span))
410    /// #     )],
411    /// # };
412    /// let foo = call.get_flag::<i64>("foo");
413    /// assert!(foo.is_err());
414    /// ```
415    pub fn get_flag<T: FromValue>(&self, name: &str) -> Result<Option<T>, ShellError> {
416        if let Some(value) = self.get_flag_value(name) {
417            FromValue::from_value(value).map(Some)
418        } else {
419            Ok(None)
420        }
421    }
422
423    /// Retrieve the Nth and all following positional arguments as type `T`
424    ///
425    /// # Example
426    /// Invoked as `my_command zero one two three`:
427    /// ```
428    /// # use nu_protocol::{Spanned, Span, Value};
429    /// # use nu_plugin_protocol::EvaluatedCall;
430    /// # let null_span = Span::new(0, 0);
431    /// # let call = EvaluatedCall {
432    /// #     head: null_span,
433    /// #     positional: vec![
434    /// #         Value::string("zero".to_owned(), null_span),
435    /// #         Value::string("one".to_owned(), null_span),
436    /// #         Value::string("two".to_owned(), null_span),
437    /// #         Value::string("three".to_owned(), null_span),
438    /// #     ],
439    /// #     named: Vec::new(),
440    /// # };
441    /// let args = call.rest::<String>(0);
442    /// assert_eq!(args.unwrap(), vec!["zero", "one", "two", "three"]);
443    ///
444    /// let args = call.rest::<String>(2);
445    /// assert_eq!(args.unwrap(), vec!["two", "three"]);
446    /// ```
447    pub fn rest<T: FromValue>(&self, starting_pos: usize) -> Result<Vec<T>, ShellError> {
448        self.positional
449            .iter()
450            .skip(starting_pos)
451            .map(|value| FromValue::from_value(value.clone()))
452            .collect()
453    }
454
455    /// Retrieve the value of an optional positional argument interpreted as type `T`
456    ///
457    /// Returns the value of a (zero indexed) positional argument of type `T`.
458    /// Alternatively returns [`None`] if the positional argument does not exist
459    /// or an error that can be passed back to the shell on error.
460    pub fn opt<T: FromValue>(&self, pos: usize) -> Result<Option<T>, ShellError> {
461        if let Some(value) = self.nth(pos) {
462            FromValue::from_value(value).map(Some)
463        } else {
464            Ok(None)
465        }
466    }
467
468    /// Retrieve the value of a mandatory positional argument as type `T`
469    ///
470    /// Expect a positional argument of type `T` and return its value or, if the
471    /// argument does not exist or is of the wrong type, return an error that can
472    /// be passed back to the shell.
473    pub fn req<T: FromValue>(&self, pos: usize) -> Result<T, ShellError> {
474        if let Some(value) = self.nth(pos) {
475            FromValue::from_value(value)
476        } else if self.positional.is_empty() {
477            Err(ShellError::AccessEmptyContent { span: self.head })
478        } else {
479            Err(ShellError::AccessBeyondEnd {
480                max_idx: self.positional.len() - 1,
481                span: self.head,
482            })
483        }
484    }
485}
486
487#[cfg(test)]
488mod test {
489    use super::*;
490    use nu_protocol::{Span, Spanned, Value};
491
492    #[test]
493    fn call_to_value() {
494        let call = EvaluatedCall {
495            head: Span::new(0, 10),
496            positional: vec![
497                Value::float(1.0, Span::new(0, 10)),
498                Value::string("something", Span::new(0, 10)),
499            ],
500            named: vec![
501                (
502                    Spanned {
503                        item: "name".to_string(),
504                        span: Span::new(0, 10),
505                    },
506                    Some(Value::float(1.0, Span::new(0, 10))),
507                ),
508                (
509                    Spanned {
510                        item: "flag".to_string(),
511                        span: Span::new(0, 10),
512                    },
513                    None,
514                ),
515            ],
516        };
517
518        let name: Option<f64> = call.get_flag("name").unwrap();
519        assert_eq!(name, Some(1.0));
520
521        assert!(call.has_flag("flag").unwrap());
522
523        let required: f64 = call.req(0).unwrap();
524        assert!((required - 1.0).abs() < f64::EPSILON);
525
526        let optional: Option<String> = call.opt(1).unwrap();
527        assert_eq!(optional, Some("something".to_string()));
528
529        let rest: Vec<String> = call.rest(1).unwrap();
530        assert_eq!(rest, vec!["something".to_string()]);
531    }
532}