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