nu_plugin_engine/
context.rs

1use crate::util::MutableCow;
2use nu_engine::{ClosureEvalOnce, get_eval_block_with_early_return, get_full_help};
3use nu_plugin_protocol::EvaluatedCall;
4use nu_protocol::{
5    Config, DeclId, IntoSpanned, OutDest, PipelineData, PluginIdentity, ShellError, Signals, Span,
6    Spanned, Value,
7    engine::{Call, Closure, EngineState, Redirection, Stack},
8    ir,
9};
10use std::{
11    borrow::Cow,
12    collections::HashMap,
13    sync::{Arc, atomic::AtomicU32},
14};
15
16/// Object safe trait for abstracting operations required of the plugin context.
17pub trait PluginExecutionContext: Send + Sync {
18    /// A span pointing to the command being executed
19    fn span(&self) -> Span;
20    /// The [`Signals`] struct, if present
21    fn signals(&self) -> &Signals;
22    /// The pipeline externals state, for tracking the foreground process group, if present
23    fn pipeline_externals_state(&self) -> Option<&Arc<(AtomicU32, AtomicU32)>>;
24    /// Get engine configuration
25    fn get_config(&self) -> Result<Arc<Config>, ShellError>;
26    /// Get plugin configuration
27    fn get_plugin_config(&self) -> Result<Option<Value>, ShellError>;
28    /// Get an environment variable from `$env`
29    fn get_env_var(&self, name: &str) -> Result<Option<&Value>, ShellError>;
30    /// Get all environment variables
31    fn get_env_vars(&self) -> Result<HashMap<String, Value>, ShellError>;
32    /// Get current working directory
33    fn get_current_dir(&self) -> Result<Spanned<String>, ShellError>;
34    /// Set an environment variable
35    fn add_env_var(&mut self, name: String, value: Value) -> Result<(), ShellError>;
36    /// Get help for the current command
37    fn get_help(&self) -> Result<Spanned<String>, ShellError>;
38    /// Get the contents of a [`Span`]
39    fn get_span_contents(&self, span: Span) -> Result<Spanned<Vec<u8>>, ShellError>;
40    /// Evaluate a closure passed to the plugin
41    fn eval_closure(
42        &self,
43        closure: Spanned<Closure>,
44        positional: Vec<Value>,
45        input: PipelineData,
46        redirect_stdout: bool,
47        redirect_stderr: bool,
48    ) -> Result<PipelineData, ShellError>;
49    /// Find a declaration by name
50    fn find_decl(&self, name: &str) -> Result<Option<DeclId>, ShellError>;
51    /// Call a declaration with arguments and input
52    fn call_decl(
53        &mut self,
54        decl_id: DeclId,
55        call: EvaluatedCall,
56        input: PipelineData,
57        redirect_stdout: bool,
58        redirect_stderr: bool,
59    ) -> Result<PipelineData, ShellError>;
60    /// Create an owned version of the context with `'static` lifetime
61    fn boxed(&self) -> Box<dyn PluginExecutionContext>;
62}
63
64/// The execution context of a plugin command. Can be borrowed.
65pub struct PluginExecutionCommandContext<'a> {
66    identity: Arc<PluginIdentity>,
67    engine_state: Cow<'a, EngineState>,
68    stack: MutableCow<'a, Stack>,
69    call: Call<'a>,
70}
71
72impl<'a> PluginExecutionCommandContext<'a> {
73    pub fn new(
74        identity: Arc<PluginIdentity>,
75        engine_state: &'a EngineState,
76        stack: &'a mut Stack,
77        call: &'a Call<'a>,
78    ) -> PluginExecutionCommandContext<'a> {
79        PluginExecutionCommandContext {
80            identity,
81            engine_state: Cow::Borrowed(engine_state),
82            stack: MutableCow::Borrowed(stack),
83            call: call.clone(),
84        }
85    }
86}
87
88impl PluginExecutionContext for PluginExecutionCommandContext<'_> {
89    fn span(&self) -> Span {
90        self.call.head
91    }
92
93    fn signals(&self) -> &Signals {
94        self.engine_state.signals()
95    }
96
97    fn pipeline_externals_state(&self) -> Option<&Arc<(AtomicU32, AtomicU32)>> {
98        Some(&self.engine_state.pipeline_externals_state)
99    }
100
101    fn get_config(&self) -> Result<Arc<Config>, ShellError> {
102        Ok(self.stack.get_config(&self.engine_state))
103    }
104
105    fn get_plugin_config(&self) -> Result<Option<Value>, ShellError> {
106        // Fetch the configuration for a plugin
107        //
108        // The `plugin` must match the registered name of a plugin.  For `plugin add
109        // nu_plugin_example` the plugin config lookup uses `"example"`
110        Ok(self
111            .get_config()?
112            .plugins
113            .get(self.identity.name())
114            .cloned()
115            .map(|value| {
116                let span = value.span();
117                match value {
118                    Value::Closure { val, .. } => {
119                        ClosureEvalOnce::new(&self.engine_state, &self.stack, *val)
120                            .run_with_input(PipelineData::empty())
121                            .and_then(|data| data.into_value(span))
122                            .unwrap_or_else(|err| Value::error(err, self.call.head))
123                    }
124                    _ => value.clone(),
125                }
126            }))
127    }
128
129    fn get_env_var(&self, name: &str) -> Result<Option<&Value>, ShellError> {
130        Ok(self
131            .stack
132            .get_env_var_insensitive(&self.engine_state, name)
133            .map(|(_, value)| value))
134    }
135
136    fn get_env_vars(&self) -> Result<HashMap<String, Value>, ShellError> {
137        Ok(self.stack.get_env_vars(&self.engine_state))
138    }
139
140    fn get_current_dir(&self) -> Result<Spanned<String>, ShellError> {
141        #[allow(deprecated)]
142        let cwd = nu_engine::env::current_dir_str(&self.engine_state, &self.stack)?;
143        // The span is not really used, so just give it call.head
144        Ok(cwd.into_spanned(self.call.head))
145    }
146
147    fn add_env_var(&mut self, name: String, value: Value) -> Result<(), ShellError> {
148        self.stack.add_env_var(name, value);
149        Ok(())
150    }
151
152    fn get_help(&self) -> Result<Spanned<String>, ShellError> {
153        let decl = self.engine_state.get_decl(self.call.decl_id);
154
155        Ok(
156            get_full_help(decl, &self.engine_state, &mut self.stack.clone())
157                .into_spanned(self.call.head),
158        )
159    }
160
161    fn get_span_contents(&self, span: Span) -> Result<Spanned<Vec<u8>>, ShellError> {
162        Ok(self
163            .engine_state
164            .get_span_contents(span)
165            .to_vec()
166            .into_spanned(self.call.head))
167    }
168
169    fn eval_closure(
170        &self,
171        closure: Spanned<Closure>,
172        positional: Vec<Value>,
173        input: PipelineData,
174        redirect_stdout: bool,
175        redirect_stderr: bool,
176    ) -> Result<PipelineData, ShellError> {
177        let block = self
178            .engine_state
179            .try_get_block(closure.item.block_id)
180            .ok_or_else(|| ShellError::GenericError {
181                error: "Plugin misbehaving".into(),
182                msg: format!(
183                    "Tried to evaluate unknown block id: {}",
184                    closure.item.block_id.get()
185                ),
186                span: Some(closure.span),
187                help: None,
188                inner: vec![],
189            })?;
190
191        let mut stack = self
192            .stack
193            .captures_to_stack(closure.item.captures)
194            .reset_pipes();
195
196        let stack = &mut stack.push_redirection(
197            redirect_stdout.then_some(Redirection::Pipe(OutDest::PipeSeparate)),
198            redirect_stderr.then_some(Redirection::Pipe(OutDest::PipeSeparate)),
199        );
200
201        // Set up the positional arguments
202        for (idx, value) in positional.into_iter().enumerate() {
203            if let Some(arg) = block.signature.get_positional(idx) {
204                if let Some(var_id) = arg.var_id {
205                    stack.add_var(var_id, value);
206                } else {
207                    return Err(ShellError::NushellFailedSpanned {
208                        msg: "Error while evaluating closure from plugin".into(),
209                        label: "closure argument missing var_id".into(),
210                        span: closure.span,
211                    });
212                }
213            }
214        }
215
216        let eval_block_with_early_return = get_eval_block_with_early_return(&self.engine_state);
217
218        eval_block_with_early_return(&self.engine_state, stack, block, input)
219    }
220
221    fn find_decl(&self, name: &str) -> Result<Option<DeclId>, ShellError> {
222        Ok(self.engine_state.find_decl(name.as_bytes(), &[]))
223    }
224
225    fn call_decl(
226        &mut self,
227        decl_id: DeclId,
228        call: EvaluatedCall,
229        input: PipelineData,
230        redirect_stdout: bool,
231        redirect_stderr: bool,
232    ) -> Result<PipelineData, ShellError> {
233        if decl_id.get() >= self.engine_state.num_decls() {
234            return Err(ShellError::GenericError {
235                error: "Plugin misbehaving".into(),
236                msg: format!("Tried to call unknown decl id: {}", decl_id.get()),
237                span: Some(call.head),
238                help: None,
239                inner: vec![],
240            });
241        }
242
243        let decl = self.engine_state.get_decl(decl_id);
244
245        let stack = &mut self.stack.push_redirection(
246            redirect_stdout.then_some(Redirection::Pipe(OutDest::PipeSeparate)),
247            redirect_stderr.then_some(Redirection::Pipe(OutDest::PipeSeparate)),
248        );
249
250        let mut call_builder = ir::Call::build(decl_id, call.head);
251
252        for positional in call.positional {
253            call_builder.add_positional(stack, positional.span(), positional);
254        }
255
256        for (name, value) in call.named {
257            if let Some(value) = value {
258                call_builder.add_named(stack, &name.item, "", name.span, value);
259            } else {
260                call_builder.add_flag(stack, &name.item, "", name.span);
261            }
262        }
263
264        call_builder.with(stack, |stack, call| {
265            decl.run(&self.engine_state, stack, call, input)
266        })
267    }
268
269    fn boxed(&self) -> Box<dyn PluginExecutionContext + 'static> {
270        Box::new(PluginExecutionCommandContext {
271            identity: self.identity.clone(),
272            engine_state: Cow::Owned(self.engine_state.clone().into_owned()),
273            stack: self.stack.owned(),
274            call: self.call.to_owned(),
275        })
276    }
277}
278
279/// A bogus execution context for testing that doesn't really implement anything properly
280#[cfg(test)]
281pub(crate) struct PluginExecutionBogusContext;
282
283#[cfg(test)]
284impl PluginExecutionContext for PluginExecutionBogusContext {
285    fn span(&self) -> Span {
286        Span::test_data()
287    }
288
289    fn signals(&self) -> &Signals {
290        &Signals::EMPTY
291    }
292
293    fn pipeline_externals_state(&self) -> Option<&Arc<(AtomicU32, AtomicU32)>> {
294        None
295    }
296
297    fn get_config(&self) -> Result<Arc<Config>, ShellError> {
298        Err(ShellError::NushellFailed {
299            msg: "get_config not implemented on bogus".into(),
300        })
301    }
302
303    fn get_plugin_config(&self) -> Result<Option<Value>, ShellError> {
304        Ok(None)
305    }
306
307    fn get_env_var(&self, _name: &str) -> Result<Option<&Value>, ShellError> {
308        Err(ShellError::NushellFailed {
309            msg: "get_env_var not implemented on bogus".into(),
310        })
311    }
312
313    fn get_env_vars(&self) -> Result<HashMap<String, Value>, ShellError> {
314        Err(ShellError::NushellFailed {
315            msg: "get_env_vars not implemented on bogus".into(),
316        })
317    }
318
319    fn get_current_dir(&self) -> Result<Spanned<String>, ShellError> {
320        Err(ShellError::NushellFailed {
321            msg: "get_current_dir not implemented on bogus".into(),
322        })
323    }
324
325    fn add_env_var(&mut self, _name: String, _value: Value) -> Result<(), ShellError> {
326        Err(ShellError::NushellFailed {
327            msg: "add_env_var not implemented on bogus".into(),
328        })
329    }
330
331    fn get_help(&self) -> Result<Spanned<String>, ShellError> {
332        Err(ShellError::NushellFailed {
333            msg: "get_help not implemented on bogus".into(),
334        })
335    }
336
337    fn get_span_contents(&self, _span: Span) -> Result<Spanned<Vec<u8>>, ShellError> {
338        Err(ShellError::NushellFailed {
339            msg: "get_span_contents not implemented on bogus".into(),
340        })
341    }
342
343    fn eval_closure(
344        &self,
345        _closure: Spanned<Closure>,
346        _positional: Vec<Value>,
347        _input: PipelineData,
348        _redirect_stdout: bool,
349        _redirect_stderr: bool,
350    ) -> Result<PipelineData, ShellError> {
351        Err(ShellError::NushellFailed {
352            msg: "eval_closure not implemented on bogus".into(),
353        })
354    }
355
356    fn find_decl(&self, _name: &str) -> Result<Option<DeclId>, ShellError> {
357        Err(ShellError::NushellFailed {
358            msg: "find_decl not implemented on bogus".into(),
359        })
360    }
361
362    fn call_decl(
363        &mut self,
364        _decl_id: DeclId,
365        _call: EvaluatedCall,
366        _input: PipelineData,
367        _redirect_stdout: bool,
368        _redirect_stderr: bool,
369    ) -> Result<PipelineData, ShellError> {
370        Err(ShellError::NushellFailed {
371            msg: "call_decl not implemented on bogus".into(),
372        })
373    }
374
375    fn boxed(&self) -> Box<dyn PluginExecutionContext + 'static> {
376        Box::new(PluginExecutionBogusContext)
377    }
378}