use crate::util::{get_plugin_dirs, modify_plugin_file};
use nu_engine::command_prelude::*;
use nu_plugin_engine::{GetPlugin, PersistentPlugin};
use nu_protocol::{
PluginGcConfig, PluginIdentity, PluginRegistryItem, RegisteredPlugin,
shell_error::generic::GenericError, shell_error::io::IoError,
};
use std::{path::PathBuf, sync::Arc};
#[derive(Clone)]
pub struct PluginAdd;
impl Command for PluginAdd {
fn name(&self) -> &str {
"plugin add"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.input_output_type(Type::Nothing, Type::Nothing)
.named(
"plugin-config",
SyntaxShape::Filepath,
"Use a plugin registry file other than the one set in `$nu.plugin-path`.",
None,
)
.named(
"shell",
SyntaxShape::Filepath,
"Use an additional shell program (cmd, sh, python, etc.) to run the plugin.",
Some('s'),
)
.required(
"filename",
SyntaxShape::String,
"Path to the executable for the plugin.",
)
.category(Category::Plugin)
}
fn description(&self) -> &str {
"Add a plugin to the plugin registry file."
}
fn extra_description(&self) -> &str {
"
This does not load the plugin commands into the scope - see `plugin use` for
that.
Instead, it runs the plugin to get its command signatures, and then edits the
plugin registry file (by default, `$nu.plugin-path`). The changes will be
apparent the next time `nu` is next launched with that plugin registry file.
"
.trim()
}
fn search_terms(&self) -> Vec<&str> {
vec!["load", "register", "signature"]
}
fn examples(&self) -> Vec<Example<'_>> {
vec![
Example {
example: "plugin add nu_plugin_inc",
description: "Run the `nu_plugin_inc` plugin from the current directory or $env.NU_PLUGIN_DIRS and install its signatures.",
result: None,
},
Example {
example: "plugin add --plugin-config polars.msgpackz nu_plugin_polars",
description: "Run the `nu_plugin_polars` plugin from the current directory or $env.NU_PLUGIN_DIRS, and install its signatures to the \"polars.msgpackz\" plugin registry file.",
result: None,
},
]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let filename: Spanned<String> = call.req(engine_state, stack, 0)?;
let shell: Option<Spanned<String>> = call.get_flag(engine_state, stack, "shell")?;
let cwd = engine_state.cwd(Some(stack))?;
let filename_expanded = nu_path::locate_in_dirs(&filename.item, &cwd, || {
get_plugin_dirs(engine_state, stack)
})
.map_err(|err| {
IoError::new(
err.not_found_as(NotFound::File),
filename.span,
PathBuf::from(filename.item),
)
})?;
let shell_expanded = shell
.as_ref()
.map(|s| {
nu_path::absolute_with(&s.item, &cwd).map_err(|err| IoError::new(err, s.span, None))
})
.transpose()?;
let identity = PluginIdentity::new(filename_expanded, shell_expanded).map_err(|_| {
ShellError::Generic(GenericError::new(
"Plugin filename is invalid",
"plugin executable files must start with `nu_plugin_`",
filename.span,
))
})?;
let custom_path = call.get_flag(engine_state, stack, "plugin-config")?;
let plugin = Arc::new(PersistentPlugin::new(
identity,
PluginGcConfig {
enabled: true,
stop_after: 0,
},
));
let interface = plugin.clone().get_plugin(Some((engine_state, stack)))?;
let metadata = interface.get_metadata()?;
let commands = interface.get_signature()?;
modify_plugin_file(engine_state, stack, call.head, &custom_path, |contents| {
let item = PluginRegistryItem::new(plugin.identity(), metadata, commands);
contents.upsert_plugin(item);
Ok(())
})?;
Ok(Value::nothing(call.head).into_pipeline_data())
}
}