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