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
16pub trait PluginExecutionContext: Send + Sync {
18 fn span(&self) -> Span;
20 fn signals(&self) -> &Signals;
22 fn pipeline_externals_state(&self) -> Option<&Arc<(AtomicU32, AtomicU32)>>;
24 fn get_config(&self) -> Result<Arc<Config>, ShellError>;
26 fn get_plugin_config(&self) -> Result<Option<Value>, ShellError>;
28 fn get_env_var(&self, name: &str) -> Result<Option<&Value>, ShellError>;
30 fn get_env_vars(&self) -> Result<HashMap<String, Value>, ShellError>;
32 fn get_current_dir(&self) -> Result<Spanned<String>, ShellError>;
34 fn add_env_var(&mut self, name: String, value: Value) -> Result<(), ShellError>;
36 fn get_help(&self) -> Result<Spanned<String>, ShellError>;
38 fn get_span_contents(&self, span: Span) -> Result<Spanned<Vec<u8>>, ShellError>;
40 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 fn find_decl(&self, name: &str) -> Result<Option<DeclId>, ShellError>;
51 fn get_block_ir(&self, block_id: BlockId) -> Result<IrBlock, ShellError>;
53 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 fn boxed(&self) -> Box<dyn PluginExecutionContext>;
64}
65
66pub 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 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 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 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#[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}