nu_plugin_engine/
context.rs

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