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