Skip to main content

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    BlockId, Config, DeclId, IntoSpanned, OutDest, PipelineData, PluginIdentity, ShellError,
6    Signals, Span, Spanned, Value,
7    engine::{Call, Closure, EngineState, Redirection, Stack},
8    ir::{self, IrBlock},
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    /// Get the compiled IR for a block
52    fn get_block_ir(&self, block_id: BlockId) -> Result<IrBlock, ShellError>;
53    /// Call a declaration with arguments and input
54    fn call_decl(
55        &mut self,
56        decl_id: DeclId,
57        call: EvaluatedCall,
58        input: PipelineData,
59        redirect_stdout: bool,
60        redirect_stderr: bool,
61    ) -> Result<PipelineData, ShellError>;
62    /// Create an owned version of the context with `'static` lifetime
63    fn boxed(&self) -> Box<dyn PluginExecutionContext>;
64}
65
66/// The execution context of a plugin command. Can be borrowed.
67pub struct PluginExecutionCommandContext<'a> {
68    identity: Arc<PluginIdentity>,
69    engine_state: Cow<'a, EngineState>,
70    stack: MutableCow<'a, Stack>,
71    call: Call<'a>,
72}
73
74impl<'a> PluginExecutionCommandContext<'a> {
75    pub fn new(
76        identity: Arc<PluginIdentity>,
77        engine_state: &'a EngineState,
78        stack: &'a mut Stack,
79        call: &'a Call<'a>,
80    ) -> PluginExecutionCommandContext<'a> {
81        PluginExecutionCommandContext {
82            identity,
83            engine_state: Cow::Borrowed(engine_state),
84            stack: MutableCow::Borrowed(stack),
85            call: call.clone(),
86        }
87    }
88}
89
90impl PluginExecutionContext for PluginExecutionCommandContext<'_> {
91    fn span(&self) -> Span {
92        self.call.head
93    }
94
95    fn signals(&self) -> &Signals {
96        self.engine_state.signals()
97    }
98
99    fn pipeline_externals_state(&self) -> Option<&Arc<(AtomicU32, AtomicU32)>> {
100        Some(&self.engine_state.pipeline_externals_state)
101    }
102
103    fn get_config(&self) -> Result<Arc<Config>, ShellError> {
104        Ok(self.stack.get_config(&self.engine_state))
105    }
106
107    fn get_plugin_config(&self) -> Result<Option<Value>, ShellError> {
108        // Fetch the configuration for a plugin
109        //
110        // The `plugin` must match the registered name of a plugin.  For `plugin add
111        // nu_plugin_example` the plugin config lookup uses `"example"`
112        Ok(self
113            .get_config()?
114            .plugins
115            .get(self.identity.name())
116            .cloned()
117            .map(|value| {
118                let span = value.span();
119                match value {
120                    Value::Closure { val, .. } => {
121                        ClosureEvalOnce::new(&self.engine_state, &self.stack, *val)
122                            .run_with_input(PipelineData::empty())
123                            .and_then(|data| data.into_value(span))
124                            .unwrap_or_else(|err| Value::error(err, self.call.head))
125                    }
126                    _ => value.clone(),
127                }
128            }))
129    }
130
131    fn get_env_var(&self, name: &str) -> Result<Option<&Value>, ShellError> {
132        Ok(self.stack.get_env_var(&self.engine_state, name))
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        let cwd = self.engine_state.cwd_as_string(Some(&self.stack))?;
141        // The span is not really used, so just give it call.head
142        Ok(cwd.into_spanned(self.call.head))
143    }
144
145    fn add_env_var(&mut self, name: String, value: Value) -> Result<(), ShellError> {
146        self.stack.add_env_var(name, value);
147        Ok(())
148    }
149
150    fn get_help(&self) -> Result<Spanned<String>, ShellError> {
151        let decl = self.engine_state.get_decl(self.call.decl_id);
152
153        Ok(
154            get_full_help(decl, &self.engine_state, &mut self.stack.clone())
155                .into_spanned(self.call.head),
156        )
157    }
158
159    fn get_span_contents(&self, span: Span) -> Result<Spanned<Vec<u8>>, ShellError> {
160        Ok(self
161            .engine_state
162            .get_span_contents(span)
163            .to_vec()
164            .into_spanned(self.call.head))
165    }
166
167    fn eval_closure(
168        &self,
169        closure: Spanned<Closure>,
170        positional: Vec<Value>,
171        input: PipelineData,
172        redirect_stdout: bool,
173        redirect_stderr: bool,
174    ) -> Result<PipelineData, ShellError> {
175        let block = self
176            .engine_state
177            .try_get_block(closure.item.block_id)
178            .ok_or_else(|| ShellError::GenericError {
179                error: "Plugin misbehaving".into(),
180                msg: format!(
181                    "Tried to evaluate unknown block id: {}",
182                    closure.item.block_id.get()
183                ),
184                span: Some(closure.span),
185                help: None,
186                inner: vec![],
187            })?;
188
189        let mut stack = self
190            .stack
191            .captures_to_stack(closure.item.captures)
192            .reset_pipes();
193
194        let stack = &mut stack.push_redirection(
195            redirect_stdout.then_some(Redirection::Pipe(OutDest::PipeSeparate)),
196            redirect_stderr.then_some(Redirection::Pipe(OutDest::PipeSeparate)),
197        );
198
199        // Set up the positional arguments
200        for (idx, value) in positional.into_iter().enumerate() {
201            if let Some(arg) = block.signature.get_positional(idx) {
202                if let Some(var_id) = arg.var_id {
203                    stack.add_var(var_id, value);
204                } else {
205                    return Err(ShellError::NushellFailedSpanned {
206                        msg: "Error while evaluating closure from plugin".into(),
207                        label: "closure argument missing var_id".into(),
208                        span: closure.span,
209                    });
210                }
211            }
212        }
213
214        let eval_block_with_early_return = get_eval_block_with_early_return(&self.engine_state);
215
216        eval_block_with_early_return(&self.engine_state, stack, block, input).map(|p| p.body)
217    }
218
219    fn find_decl(&self, name: &str) -> Result<Option<DeclId>, ShellError> {
220        Ok(self.engine_state.find_decl(name.as_bytes(), &[]))
221    }
222
223    fn get_block_ir(&self, block_id: BlockId) -> Result<IrBlock, ShellError> {
224        let block =
225            self.engine_state
226                .try_get_block(block_id)
227                .ok_or_else(|| ShellError::GenericError {
228                    error: "Plugin misbehaving".into(),
229                    msg: format!("Tried to get IR for unknown block id: {}", block_id.get()),
230                    span: Some(self.call.head),
231                    help: None,
232                    inner: vec![],
233                })?;
234
235        block
236            .ir_block
237            .clone()
238            .ok_or_else(|| ShellError::GenericError {
239                error: "Block has no IR".into(),
240                msg: format!("Block {} was not compiled to IR", block_id.get()),
241                span: Some(self.call.head),
242                help: Some(
243                    "This block may be a declaration or built-in that has no IR representation"
244                        .into(),
245                ),
246                inner: vec![],
247            })
248    }
249
250    fn call_decl(
251        &mut self,
252        decl_id: DeclId,
253        call: EvaluatedCall,
254        input: PipelineData,
255        redirect_stdout: bool,
256        redirect_stderr: bool,
257    ) -> Result<PipelineData, ShellError> {
258        if decl_id.get() >= self.engine_state.num_decls() {
259            return Err(ShellError::GenericError {
260                error: "Plugin misbehaving".into(),
261                msg: format!("Tried to call unknown decl id: {}", decl_id.get()),
262                span: Some(call.head),
263                help: None,
264                inner: vec![],
265            });
266        }
267
268        let decl = self.engine_state.get_decl(decl_id);
269
270        let stack = &mut self.stack.push_redirection(
271            redirect_stdout.then_some(Redirection::Pipe(OutDest::PipeSeparate)),
272            redirect_stderr.then_some(Redirection::Pipe(OutDest::PipeSeparate)),
273        );
274
275        let mut call_builder = ir::Call::build(decl_id, call.head);
276
277        for positional in call.positional {
278            call_builder.add_positional(stack, positional.span(), positional);
279        }
280
281        for (name, value) in call.named {
282            if let Some(value) = value {
283                call_builder.add_named(stack, &name.item, "", name.span, value);
284            } else {
285                call_builder.add_flag(stack, &name.item, "", name.span);
286            }
287        }
288
289        call_builder.with(stack, |stack, call| {
290            decl.run(&self.engine_state, stack, call, input)
291        })
292    }
293
294    fn boxed(&self) -> Box<dyn PluginExecutionContext + 'static> {
295        Box::new(PluginExecutionCommandContext {
296            identity: self.identity.clone(),
297            engine_state: Cow::Owned(self.engine_state.clone().into_owned()),
298            stack: self.stack.owned(),
299            call: self.call.to_owned(),
300        })
301    }
302}
303
304/// A bogus execution context for testing that doesn't really implement anything properly
305#[cfg(test)]
306pub(crate) struct PluginExecutionBogusContext;
307
308#[cfg(test)]
309impl PluginExecutionContext for PluginExecutionBogusContext {
310    fn span(&self) -> Span {
311        Span::test_data()
312    }
313
314    fn signals(&self) -> &Signals {
315        &Signals::EMPTY
316    }
317
318    fn pipeline_externals_state(&self) -> Option<&Arc<(AtomicU32, AtomicU32)>> {
319        None
320    }
321
322    fn get_config(&self) -> Result<Arc<Config>, ShellError> {
323        Err(ShellError::NushellFailed {
324            msg: "get_config not implemented on bogus".into(),
325        })
326    }
327
328    fn get_plugin_config(&self) -> Result<Option<Value>, ShellError> {
329        Ok(None)
330    }
331
332    fn get_env_var(&self, _name: &str) -> Result<Option<&Value>, ShellError> {
333        Err(ShellError::NushellFailed {
334            msg: "get_env_var not implemented on bogus".into(),
335        })
336    }
337
338    fn get_env_vars(&self) -> Result<HashMap<String, Value>, ShellError> {
339        Err(ShellError::NushellFailed {
340            msg: "get_env_vars not implemented on bogus".into(),
341        })
342    }
343
344    fn get_current_dir(&self) -> Result<Spanned<String>, ShellError> {
345        Err(ShellError::NushellFailed {
346            msg: "get_current_dir not implemented on bogus".into(),
347        })
348    }
349
350    fn add_env_var(&mut self, _name: String, _value: Value) -> Result<(), ShellError> {
351        Err(ShellError::NushellFailed {
352            msg: "add_env_var not implemented on bogus".into(),
353        })
354    }
355
356    fn get_help(&self) -> Result<Spanned<String>, ShellError> {
357        Err(ShellError::NushellFailed {
358            msg: "get_help not implemented on bogus".into(),
359        })
360    }
361
362    fn get_span_contents(&self, _span: Span) -> Result<Spanned<Vec<u8>>, ShellError> {
363        Err(ShellError::NushellFailed {
364            msg: "get_span_contents not implemented on bogus".into(),
365        })
366    }
367
368    fn eval_closure(
369        &self,
370        _closure: Spanned<Closure>,
371        _positional: Vec<Value>,
372        _input: PipelineData,
373        _redirect_stdout: bool,
374        _redirect_stderr: bool,
375    ) -> Result<PipelineData, ShellError> {
376        Err(ShellError::NushellFailed {
377            msg: "eval_closure not implemented on bogus".into(),
378        })
379    }
380
381    fn find_decl(&self, _name: &str) -> Result<Option<DeclId>, ShellError> {
382        Err(ShellError::NushellFailed {
383            msg: "find_decl not implemented on bogus".into(),
384        })
385    }
386
387    fn get_block_ir(&self, _block_id: BlockId) -> Result<IrBlock, ShellError> {
388        Err(ShellError::NushellFailed {
389            msg: "get_block_ir not implemented on bogus".into(),
390        })
391    }
392
393    fn call_decl(
394        &mut self,
395        _decl_id: DeclId,
396        _call: EvaluatedCall,
397        _input: PipelineData,
398        _redirect_stdout: bool,
399        _redirect_stderr: bool,
400    ) -> Result<PipelineData, ShellError> {
401        Err(ShellError::NushellFailed {
402            msg: "call_decl not implemented on bogus".into(),
403        })
404    }
405
406    fn boxed(&self) -> Box<dyn PluginExecutionContext + 'static> {
407        Box::new(PluginExecutionBogusContext)
408    }
409}