mirror of
https://github.com/nushell/nushell.git
synced 2025-08-10 12:58:11 +02:00
Make plugins able to find and call other commands (#13407)
# Description Adds functionality to the plugin interface to support calling internal commands from plugins. For example, using `view ir --json`: ```rust let closure: Value = call.req(0)?; let Some(decl_id) = engine.find_decl("view ir")? else { return Err(LabeledError::new("`view ir` not found")); }; let ir_json = engine.call_decl( decl_id, EvaluatedCall::new(call.head) .with_named("json".into_spanned(call.head), Value::bool(true, call.head)) .with_positional(closure), PipelineData::Empty, true, false, )?.into_value()?.into_string()?; let ir = serde_json::from_value(&ir_json); // ... ``` # User-Facing Changes Plugin developers can now use `EngineInterface::find_decl()` and `call_decl()` to call internal commands, which could be handy for formatters like `to csv` or `to nuon`, or for reflection commands that help gain insight into the engine. # Tests + Formatting - 🟢 `toolkit fmt` - 🟢 `toolkit clippy` - 🟢 `toolkit test` - 🟢 `toolkit test stdlib` # After Submitting - [ ] release notes - [ ] update plugin protocol documentation: `FindDecl`, `CallDecl` engine calls; `Identifier` engine call response
This commit is contained in:
@ -1,9 +1,10 @@
|
||||
use crate::util::MutableCow;
|
||||
use nu_engine::{get_eval_block_with_early_return, get_full_help, ClosureEvalOnce};
|
||||
use nu_plugin_protocol::EvaluatedCall;
|
||||
use nu_protocol::{
|
||||
engine::{Call, Closure, EngineState, Redirection, Stack},
|
||||
Config, IntoSpanned, OutDest, PipelineData, PluginIdentity, ShellError, Signals, Span, Spanned,
|
||||
Value,
|
||||
ir, Config, DeclId, IntoSpanned, OutDest, PipelineData, PluginIdentity, ShellError, Signals,
|
||||
Span, Spanned, Value,
|
||||
};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
@ -44,6 +45,17 @@ pub trait PluginExecutionContext: Send + Sync {
|
||||
redirect_stdout: bool,
|
||||
redirect_stderr: bool,
|
||||
) -> Result<PipelineData, ShellError>;
|
||||
/// Find a declaration by name
|
||||
fn find_decl(&self, name: &str) -> Result<Option<DeclId>, ShellError>;
|
||||
/// Call a declaration with arguments and input
|
||||
fn call_decl(
|
||||
&mut self,
|
||||
decl_id: DeclId,
|
||||
call: EvaluatedCall,
|
||||
input: PipelineData,
|
||||
redirect_stdout: bool,
|
||||
redirect_stderr: bool,
|
||||
) -> Result<PipelineData, ShellError>;
|
||||
/// Create an owned version of the context with `'static` lifetime
|
||||
fn boxed(&self) -> Box<dyn PluginExecutionContext>;
|
||||
}
|
||||
@ -177,19 +189,10 @@ impl<'a> PluginExecutionContext for PluginExecutionCommandContext<'a> {
|
||||
.captures_to_stack(closure.item.captures)
|
||||
.reset_pipes();
|
||||
|
||||
let stdout = if redirect_stdout {
|
||||
Some(Redirection::Pipe(OutDest::Capture))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let stderr = if redirect_stderr {
|
||||
Some(Redirection::Pipe(OutDest::Capture))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let stack = &mut stack.push_redirection(stdout, stderr);
|
||||
let stack = &mut stack.push_redirection(
|
||||
redirect_stdout.then_some(Redirection::Pipe(OutDest::Capture)),
|
||||
redirect_stderr.then_some(Redirection::Pipe(OutDest::Capture)),
|
||||
);
|
||||
|
||||
// Set up the positional arguments
|
||||
for (idx, value) in positional.into_iter().enumerate() {
|
||||
@ -211,6 +214,57 @@ impl<'a> PluginExecutionContext for PluginExecutionCommandContext<'a> {
|
||||
eval_block_with_early_return(&self.engine_state, stack, block, input)
|
||||
}
|
||||
|
||||
fn find_decl(&self, name: &str) -> Result<Option<DeclId>, ShellError> {
|
||||
Ok(self.engine_state.find_decl(name.as_bytes(), &[]))
|
||||
}
|
||||
|
||||
fn call_decl(
|
||||
&mut self,
|
||||
decl_id: DeclId,
|
||||
call: EvaluatedCall,
|
||||
input: PipelineData,
|
||||
redirect_stdout: bool,
|
||||
redirect_stderr: bool,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
if decl_id >= self.engine_state.num_decls() {
|
||||
return Err(ShellError::GenericError {
|
||||
error: "Plugin misbehaving".into(),
|
||||
msg: format!("Tried to call unknown decl id: {}", decl_id),
|
||||
span: Some(call.head),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
});
|
||||
}
|
||||
|
||||
let decl = self.engine_state.get_decl(decl_id);
|
||||
|
||||
let stack = &mut self.stack.push_redirection(
|
||||
redirect_stdout.then_some(Redirection::Pipe(OutDest::Capture)),
|
||||
redirect_stderr.then_some(Redirection::Pipe(OutDest::Capture)),
|
||||
);
|
||||
|
||||
let mut call_builder = ir::Call::build(decl_id, call.head);
|
||||
|
||||
for positional in call.positional {
|
||||
call_builder.add_positional(stack, positional.span(), positional);
|
||||
}
|
||||
|
||||
for (name, value) in call.named {
|
||||
if let Some(value) = value {
|
||||
call_builder.add_named(stack, &name.item, "", name.span, value);
|
||||
} else {
|
||||
call_builder.add_flag(stack, &name.item, "", name.span);
|
||||
}
|
||||
}
|
||||
|
||||
decl.run(
|
||||
&self.engine_state,
|
||||
stack,
|
||||
&(&call_builder.finish()).into(),
|
||||
input,
|
||||
)
|
||||
}
|
||||
|
||||
fn boxed(&self) -> Box<dyn PluginExecutionContext + 'static> {
|
||||
Box::new(PluginExecutionCommandContext {
|
||||
identity: self.identity.clone(),
|
||||
@ -298,6 +352,25 @@ impl PluginExecutionContext for PluginExecutionBogusContext {
|
||||
})
|
||||
}
|
||||
|
||||
fn find_decl(&self, _name: &str) -> Result<Option<DeclId>, ShellError> {
|
||||
Err(ShellError::NushellFailed {
|
||||
msg: "find_decl not implemented on bogus".into(),
|
||||
})
|
||||
}
|
||||
|
||||
fn call_decl(
|
||||
&mut self,
|
||||
_decl_id: DeclId,
|
||||
_call: EvaluatedCall,
|
||||
_input: PipelineData,
|
||||
_redirect_stdout: bool,
|
||||
_redirect_stderr: bool,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Err(ShellError::NushellFailed {
|
||||
msg: "call_decl not implemented on bogus".into(),
|
||||
})
|
||||
}
|
||||
|
||||
fn boxed(&self) -> Box<dyn PluginExecutionContext + 'static> {
|
||||
Box::new(PluginExecutionBogusContext)
|
||||
}
|
||||
|
Reference in New Issue
Block a user