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
133 .stack
134 .get_env_var_insensitive(&self.engine_state, name)
135 .map(|(_, value)| value))
136 }
137
138 fn get_env_vars(&self) -> Result<HashMap<String, Value>, ShellError> {
139 Ok(self.stack.get_env_vars(&self.engine_state))
140 }
141
142 fn get_current_dir(&self) -> Result<Spanned<String>, ShellError> {
143 let cwd = self.engine_state.cwd_as_string(Some(&self.stack))?;
144 Ok(cwd.into_spanned(self.call.head))
146 }
147
148 fn add_env_var(&mut self, name: String, value: Value) -> Result<(), ShellError> {
149 self.stack.add_env_var(name, value);
150 Ok(())
151 }
152
153 fn get_help(&self) -> Result<Spanned<String>, ShellError> {
154 let decl = self.engine_state.get_decl(self.call.decl_id);
155
156 Ok(
157 get_full_help(decl, &self.engine_state, &mut self.stack.clone())
158 .into_spanned(self.call.head),
159 )
160 }
161
162 fn get_span_contents(&self, span: Span) -> Result<Spanned<Vec<u8>>, ShellError> {
163 Ok(self
164 .engine_state
165 .get_span_contents(span)
166 .to_vec()
167 .into_spanned(self.call.head))
168 }
169
170 fn eval_closure(
171 &self,
172 closure: Spanned<Closure>,
173 positional: Vec<Value>,
174 input: PipelineData,
175 redirect_stdout: bool,
176 redirect_stderr: bool,
177 ) -> Result<PipelineData, ShellError> {
178 let block = self
179 .engine_state
180 .try_get_block(closure.item.block_id)
181 .ok_or_else(|| ShellError::GenericError {
182 error: "Plugin misbehaving".into(),
183 msg: format!(
184 "Tried to evaluate unknown block id: {}",
185 closure.item.block_id.get()
186 ),
187 span: Some(closure.span),
188 help: None,
189 inner: vec![],
190 })?;
191
192 let mut stack = self
193 .stack
194 .captures_to_stack(closure.item.captures)
195 .reset_pipes();
196
197 let stack = &mut stack.push_redirection(
198 redirect_stdout.then_some(Redirection::Pipe(OutDest::PipeSeparate)),
199 redirect_stderr.then_some(Redirection::Pipe(OutDest::PipeSeparate)),
200 );
201
202 for (idx, value) in positional.into_iter().enumerate() {
204 if let Some(arg) = block.signature.get_positional(idx) {
205 if let Some(var_id) = arg.var_id {
206 stack.add_var(var_id, value);
207 } else {
208 return Err(ShellError::NushellFailedSpanned {
209 msg: "Error while evaluating closure from plugin".into(),
210 label: "closure argument missing var_id".into(),
211 span: closure.span,
212 });
213 }
214 }
215 }
216
217 let eval_block_with_early_return = get_eval_block_with_early_return(&self.engine_state);
218
219 eval_block_with_early_return(&self.engine_state, stack, block, input).map(|p| p.body)
220 }
221
222 fn find_decl(&self, name: &str) -> Result<Option<DeclId>, ShellError> {
223 Ok(self.engine_state.find_decl(name.as_bytes(), &[]))
224 }
225
226 fn get_block_ir(&self, block_id: BlockId) -> Result<IrBlock, ShellError> {
227 let block =
228 self.engine_state
229 .try_get_block(block_id)
230 .ok_or_else(|| ShellError::GenericError {
231 error: "Plugin misbehaving".into(),
232 msg: format!("Tried to get IR for unknown block id: {}", block_id.get()),
233 span: Some(self.call.head),
234 help: None,
235 inner: vec![],
236 })?;
237
238 block
239 .ir_block
240 .clone()
241 .ok_or_else(|| ShellError::GenericError {
242 error: "Block has no IR".into(),
243 msg: format!("Block {} was not compiled to IR", block_id.get()),
244 span: Some(self.call.head),
245 help: Some(
246 "This block may be a declaration or built-in that has no IR representation"
247 .into(),
248 ),
249 inner: vec![],
250 })
251 }
252
253 fn call_decl(
254 &mut self,
255 decl_id: DeclId,
256 call: EvaluatedCall,
257 input: PipelineData,
258 redirect_stdout: bool,
259 redirect_stderr: bool,
260 ) -> Result<PipelineData, ShellError> {
261 if decl_id.get() >= self.engine_state.num_decls() {
262 return Err(ShellError::GenericError {
263 error: "Plugin misbehaving".into(),
264 msg: format!("Tried to call unknown decl id: {}", decl_id.get()),
265 span: Some(call.head),
266 help: None,
267 inner: vec![],
268 });
269 }
270
271 let decl = self.engine_state.get_decl(decl_id);
272
273 let stack = &mut self.stack.push_redirection(
274 redirect_stdout.then_some(Redirection::Pipe(OutDest::PipeSeparate)),
275 redirect_stderr.then_some(Redirection::Pipe(OutDest::PipeSeparate)),
276 );
277
278 let mut call_builder = ir::Call::build(decl_id, call.head);
279
280 for positional in call.positional {
281 call_builder.add_positional(stack, positional.span(), positional);
282 }
283
284 for (name, value) in call.named {
285 if let Some(value) = value {
286 call_builder.add_named(stack, &name.item, "", name.span, value);
287 } else {
288 call_builder.add_flag(stack, &name.item, "", name.span);
289 }
290 }
291
292 call_builder.with(stack, |stack, call| {
293 decl.run(&self.engine_state, stack, call, input)
294 })
295 }
296
297 fn boxed(&self) -> Box<dyn PluginExecutionContext + 'static> {
298 Box::new(PluginExecutionCommandContext {
299 identity: self.identity.clone(),
300 engine_state: Cow::Owned(self.engine_state.clone().into_owned()),
301 stack: self.stack.owned(),
302 call: self.call.to_owned(),
303 })
304 }
305}
306
307#[cfg(test)]
309pub(crate) struct PluginExecutionBogusContext;
310
311#[cfg(test)]
312impl PluginExecutionContext for PluginExecutionBogusContext {
313 fn span(&self) -> Span {
314 Span::test_data()
315 }
316
317 fn signals(&self) -> &Signals {
318 &Signals::EMPTY
319 }
320
321 fn pipeline_externals_state(&self) -> Option<&Arc<(AtomicU32, AtomicU32)>> {
322 None
323 }
324
325 fn get_config(&self) -> Result<Arc<Config>, ShellError> {
326 Err(ShellError::NushellFailed {
327 msg: "get_config not implemented on bogus".into(),
328 })
329 }
330
331 fn get_plugin_config(&self) -> Result<Option<Value>, ShellError> {
332 Ok(None)
333 }
334
335 fn get_env_var(&self, _name: &str) -> Result<Option<&Value>, ShellError> {
336 Err(ShellError::NushellFailed {
337 msg: "get_env_var not implemented on bogus".into(),
338 })
339 }
340
341 fn get_env_vars(&self) -> Result<HashMap<String, Value>, ShellError> {
342 Err(ShellError::NushellFailed {
343 msg: "get_env_vars not implemented on bogus".into(),
344 })
345 }
346
347 fn get_current_dir(&self) -> Result<Spanned<String>, ShellError> {
348 Err(ShellError::NushellFailed {
349 msg: "get_current_dir not implemented on bogus".into(),
350 })
351 }
352
353 fn add_env_var(&mut self, _name: String, _value: Value) -> Result<(), ShellError> {
354 Err(ShellError::NushellFailed {
355 msg: "add_env_var not implemented on bogus".into(),
356 })
357 }
358
359 fn get_help(&self) -> Result<Spanned<String>, ShellError> {
360 Err(ShellError::NushellFailed {
361 msg: "get_help not implemented on bogus".into(),
362 })
363 }
364
365 fn get_span_contents(&self, _span: Span) -> Result<Spanned<Vec<u8>>, ShellError> {
366 Err(ShellError::NushellFailed {
367 msg: "get_span_contents not implemented on bogus".into(),
368 })
369 }
370
371 fn eval_closure(
372 &self,
373 _closure: Spanned<Closure>,
374 _positional: Vec<Value>,
375 _input: PipelineData,
376 _redirect_stdout: bool,
377 _redirect_stderr: bool,
378 ) -> Result<PipelineData, ShellError> {
379 Err(ShellError::NushellFailed {
380 msg: "eval_closure not implemented on bogus".into(),
381 })
382 }
383
384 fn find_decl(&self, _name: &str) -> Result<Option<DeclId>, ShellError> {
385 Err(ShellError::NushellFailed {
386 msg: "find_decl not implemented on bogus".into(),
387 })
388 }
389
390 fn get_block_ir(&self, _block_id: BlockId) -> Result<IrBlock, ShellError> {
391 Err(ShellError::NushellFailed {
392 msg: "get_block_ir not implemented on bogus".into(),
393 })
394 }
395
396 fn call_decl(
397 &mut self,
398 _decl_id: DeclId,
399 _call: EvaluatedCall,
400 _input: PipelineData,
401 _redirect_stdout: bool,
402 _redirect_stderr: bool,
403 ) -> Result<PipelineData, ShellError> {
404 Err(ShellError::NushellFailed {
405 msg: "call_decl not implemented on bogus".into(),
406 })
407 }
408
409 fn boxed(&self) -> Box<dyn PluginExecutionContext + 'static> {
410 Box::new(PluginExecutionBogusContext)
411 }
412}