mirror of
https://github.com/nushell/nushell
synced 2024-12-27 05:23:11 +00:00
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:
parent
c79c43d2f8
commit
78be67f0c6
9 changed files with 79 additions and 36 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -3254,7 +3254,6 @@ name = "nu_plugin_query"
|
|||
version = "0.91.1"
|
||||
dependencies = [
|
||||
"gjson",
|
||||
"nu-engine",
|
||||
"nu-plugin",
|
||||
"nu-protocol",
|
||||
"scraper",
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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.
|
||||
///
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -28,11 +28,10 @@ particularly useful.
|
|||
fn run(
|
||||
&self,
|
||||
_plugin: &Self::Plugin,
|
||||
_engine: &EngineInterface,
|
||||
engine: &EngineInterface,
|
||||
call: &EvaluatedCall,
|
||||
_input: &Value,
|
||||
) -> Result<Value, LabeledError> {
|
||||
Err(LabeledError::new("No subcommand provided")
|
||||
.with_label("add --help to see a list of subcommands", call.head))
|
||||
Ok(Value::string(engine.get_help()?, call.head))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ bench = false
|
|||
[dependencies]
|
||||
nu-plugin = { path = "../nu-plugin", version = "0.91.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.91.1" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.91.1" }
|
||||
|
||||
gjson = "0.8"
|
||||
scraper = { default-features = false, version = "0.19" }
|
||||
|
|
|
@ -2,10 +2,8 @@ use crate::query_json::QueryJson;
|
|||
use crate::query_web::QueryWeb;
|
||||
use crate::query_xml::QueryXml;
|
||||
|
||||
use nu_engine::documentation::get_flags_section;
|
||||
use nu_plugin::{EvaluatedCall, Plugin, PluginCommand, SimplePluginCommand};
|
||||
use nu_protocol::{Category, LabeledError, PluginSignature, Value};
|
||||
use std::fmt::Write;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Query;
|
||||
|
@ -46,36 +44,10 @@ impl SimplePluginCommand for QueryCommand {
|
|||
fn run(
|
||||
&self,
|
||||
_plugin: &Query,
|
||||
_engine: &nu_plugin::EngineInterface,
|
||||
engine: &nu_plugin::EngineInterface,
|
||||
call: &EvaluatedCall,
|
||||
_input: &Value,
|
||||
) -> Result<Value, LabeledError> {
|
||||
let help = get_brief_subcommand_help();
|
||||
Ok(Value::string(help, call.head))
|
||||
Ok(Value::string(engine.get_help()?, call.head))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_brief_subcommand_help() -> String {
|
||||
let sigs: Vec<_> = Query
|
||||
.commands()
|
||||
.into_iter()
|
||||
.map(|cmd| cmd.signature())
|
||||
.collect();
|
||||
|
||||
let mut help = String::new();
|
||||
let _ = write!(help, "{}\n\n", sigs[0].sig.usage);
|
||||
let _ = write!(help, "Usage:\n > {}\n\n", sigs[0].sig.name);
|
||||
help.push_str("Subcommands:\n");
|
||||
|
||||
for x in sigs.iter().enumerate() {
|
||||
if x.0 == 0 {
|
||||
continue;
|
||||
}
|
||||
let _ = writeln!(help, " {} - {}", x.1.sig.name, x.1.sig.usage);
|
||||
}
|
||||
|
||||
help.push_str(&get_flags_section(None, &sigs[0].sig, |v| {
|
||||
format!("{:#?}", v)
|
||||
}));
|
||||
help
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue