Devyn Cairns f3843a6176
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
2024-07-19 13:54:21 +08:00

79 lines
2.2 KiB
Rust

use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand};
use nu_protocol::{
IntoSpanned, LabeledError, PipelineData, Record, Signature, Spanned, SyntaxShape, Value,
};
use crate::ExamplePlugin;
pub struct CallDecl;
impl PluginCommand for CallDecl {
type Plugin = ExamplePlugin;
fn name(&self) -> &str {
"example call-decl"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required(
"name",
SyntaxShape::String,
"the name of the command to call",
)
.optional(
"named_args",
SyntaxShape::Record(vec![]),
"named arguments to pass to the command",
)
.rest(
"positional_args",
SyntaxShape::Any,
"positional arguments to pass to the command",
)
}
fn usage(&self) -> &str {
"Demonstrates calling other commands from plugins using `call_decl()`."
}
fn extra_usage(&self) -> &str {
"
The arguments will not be typechecked at parse time. This command is for
demonstration only, and should not be used for anything real.
"
.trim()
}
fn run(
&self,
_plugin: &ExamplePlugin,
engine: &EngineInterface,
call: &EvaluatedCall,
input: PipelineData,
) -> Result<PipelineData, LabeledError> {
let name: Spanned<String> = call.req(0)?;
let named_args: Option<Record> = call.opt(1)?;
let positional_args: Vec<Value> = call.rest(2)?;
let decl_id = engine.find_decl(&name.item)?.ok_or_else(|| {
LabeledError::new(format!("Can't find `{}`", name.item))
.with_label("not in scope", name.span)
})?;
let mut new_call = EvaluatedCall::new(call.head);
for (key, val) in named_args.into_iter().flatten() {
new_call.add_named(key.into_spanned(val.span()), val);
}
for val in positional_args {
new_call.add_positional(val);
}
let result = engine.call_decl(decl_id, new_call, input, true, false)?;
Ok(result)
}
}