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}