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
17pub trait PluginExecutionContext: Send + Sync {
19 fn span(&self) -> Span;
21 fn signals(&self) -> &Signals;
23 fn pipeline_externals_state(&self) -> Option<&Arc<(AtomicU32, AtomicU32)>>;
25 fn get_config(&self) -> Result<Arc<Config>, ShellError>;
27 fn get_plugin_config(&self) -> Result<Option<Value>, ShellError>;
29 fn get_env_var(&self, name: &str) -> Result<Option<&Value>, ShellError>;
31 fn get_env_vars(&self) -> Result<HashMap<String, Value>, ShellError>;
33 fn get_current_dir(&self) -> Result<Spanned<String>, ShellError>;
35 fn add_env_var(&mut self, name: String, value: Value) -> Result<(), ShellError>;
37 fn get_help(&self) -> Result<Spanned<String>, ShellError>;
39 fn get_span_contents(&self, span: Span) -> Result<Spanned<Vec<u8>>, ShellError>;
41 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 fn find_decl(&self, name: &str) -> Result<Option<DeclId>, ShellError>;
52 fn get_block_ir(&self, block_id: BlockId) -> Result<IrBlock, ShellError>;
54 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 fn boxed(&self) -> Box<dyn PluginExecutionContext>;
65}
66
67pub 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 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 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 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#[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}