Support for getting help text from a plugin command (#12243)

# Description
There wasn't really a good way to implement a command group style (e.g.
`from`, `query`, etc.) command in the past that just returns the help
text even if `--help` is not passed. This adds a new engine call that
just does that.

This is actually something I ran into before when developing the dbus
plugin, so it's nice to fix it.

# User-Facing Changes


# Tests + Formatting
- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`

# After Submitting
- [ ] Document `GetHelp` engine call in proto
This commit is contained in:
Devyn Cairns
2024-03-23 16:30:38 -07:00
committed by GitHub
parent c79c43d2f8
commit 78be67f0c6
9 changed files with 79 additions and 36 deletions

View File

@ -4,7 +4,7 @@ use std::{
sync::{atomic::AtomicBool, Arc},
};
use nu_engine::get_eval_block_with_early_return;
use nu_engine::{get_eval_block_with_early_return, get_full_help};
use nu_protocol::{
ast::Call,
engine::{Closure, EngineState, Redirection, Stack},
@ -32,6 +32,8 @@ pub trait PluginExecutionContext: Send + Sync {
fn get_current_dir(&self) -> Result<Spanned<String>, ShellError>;
/// Set an environment variable
fn add_env_var(&mut self, name: String, value: Value) -> Result<(), ShellError>;
/// Get help for the current command
fn get_help(&self) -> Result<Spanned<String>, ShellError>;
/// Evaluate a closure passed to the plugin
fn eval_closure(
&self,
@ -137,6 +139,19 @@ impl<'a> PluginExecutionContext for PluginExecutionCommandContext<'a> {
Ok(())
}
fn get_help(&self) -> Result<Spanned<String>, ShellError> {
let decl = self.engine_state.get_decl(self.call.decl_id);
Ok(get_full_help(
&decl.signature(),
&decl.examples(),
&self.engine_state,
&mut self.stack.clone(),
false,
)
.into_spanned(self.call.head))
}
fn eval_closure(
&self,
closure: Spanned<Closure>,
@ -252,6 +267,12 @@ impl PluginExecutionContext for PluginExecutionBogusContext {
})
}
fn get_help(&self) -> Result<Spanned<String>, ShellError> {
Err(ShellError::NushellFailed {
msg: "get_help not implemented on bogus".into(),
})
}
fn eval_closure(
&self,
_closure: Spanned<Closure>,

View File

@ -636,6 +636,31 @@ impl EngineInterface {
}
}
/// Get the help string for the current command.
///
/// This returns the same string as passing `--help` would, and can be used for the top-level
/// command in a command group that doesn't do anything on its own (e.g. `query`).
///
/// # Example
/// ```rust,no_run
/// # use nu_protocol::{Value, ShellError};
/// # use nu_plugin::EngineInterface;
/// # fn example(engine: &EngineInterface) -> Result<(), ShellError> {
/// eprintln!("{}", engine.get_help()?);
/// # Ok(())
/// # }
/// ```
pub fn get_help(&self) -> Result<String, ShellError> {
match self.engine_call(EngineCall::GetHelp)? {
EngineCallResponse::PipelineData(PipelineData::Value(Value::String { val, .. }, _)) => {
Ok(val)
}
_ => Err(ShellError::PluginFailedToDecode {
msg: "Received unexpected response type for EngineCall::GetHelp".into(),
}),
}
}
/// Ask the engine to evaluate a closure. Input to the closure is passed as a stream, and the
/// output is available as a stream.
///

View File

@ -1005,6 +1005,24 @@ fn interface_add_env_var() -> Result<(), ShellError> {
Ok(())
}
#[test]
fn interface_get_help() -> Result<(), ShellError> {
let test = TestCase::new();
let manager = test.engine();
let interface = manager.interface_for_context(0);
start_fake_plugin_call_responder(manager, 1, move |_| {
EngineCallResponse::value(Value::test_string("help string"))
});
let help = interface.get_help()?;
assert_eq!("help string", help);
assert!(test.has_unconsumed_write());
Ok(())
}
#[test]
fn interface_eval_closure_with_stream() -> Result<(), ShellError> {
let test = TestCase::new();

View File

@ -1018,6 +1018,12 @@ pub(crate) fn handle_engine_call(
context.add_env_var(name, value)?;
Ok(EngineCallResponse::empty())
}
EngineCall::GetHelp => {
let help = context.get_help()?;
Ok(EngineCallResponse::value(Value::string(
help.item, help.span,
)))
}
EngineCall::EvalClosure {
closure,
positional,

View File

@ -447,6 +447,8 @@ pub enum EngineCall<D> {
GetCurrentDir,
/// Set an environment variable in the caller's scope
AddEnvVar(String, Value),
/// Get help for the current command
GetHelp,
/// Evaluate a closure with stream input/output
EvalClosure {
/// The closure to call.
@ -474,6 +476,7 @@ impl<D> EngineCall<D> {
EngineCall::GetEnvVars => "GetEnvs",
EngineCall::GetCurrentDir => "GetCurrentDir",
EngineCall::AddEnvVar(..) => "AddEnvVar",
EngineCall::GetHelp => "GetHelp",
EngineCall::EvalClosure { .. } => "EvalClosure",
}
}
@ -491,6 +494,7 @@ impl<D> EngineCall<D> {
EngineCall::GetEnvVars => EngineCall::GetEnvVars,
EngineCall::GetCurrentDir => EngineCall::GetCurrentDir,
EngineCall::AddEnvVar(name, value) => EngineCall::AddEnvVar(name, value),
EngineCall::GetHelp => EngineCall::GetHelp,
EngineCall::EvalClosure {
closure,
positional,