nu_cmd_plugin/commands/plugin/
add.rs1use crate::util::{get_plugin_dirs, modify_plugin_file};
2use nu_engine::command_prelude::*;
3use nu_plugin_engine::{GetPlugin, PersistentPlugin};
4use nu_protocol::{
5 PluginGcConfig, PluginIdentity, PluginRegistryItem, RegisteredPlugin,
6 shell_error::generic::GenericError, shell_error::io::IoError,
7};
8use std::{path::PathBuf, sync::Arc};
9
10#[derive(Clone)]
11pub struct PluginAdd;
12
13impl Command for PluginAdd {
14 fn name(&self) -> &str {
15 "plugin add"
16 }
17
18 fn signature(&self) -> Signature {
19 Signature::build(self.name())
20 .input_output_type(Type::Nothing, Type::Nothing)
21 .named(
23 "plugin-config",
24 SyntaxShape::Filepath,
25 "Use a plugin registry file other than the one set in `$nu.plugin-path`.",
26 None,
27 )
28 .named(
29 "shell",
30 SyntaxShape::Filepath,
31 "Use an additional shell program (cmd, sh, python, etc.) to run the plugin.",
32 Some('s'),
33 )
34 .required(
35 "filename",
36 SyntaxShape::String,
37 "Path to the executable for the plugin.",
38 )
39 .category(Category::Plugin)
40 }
41
42 fn description(&self) -> &str {
43 "Add a plugin to the plugin registry file."
44 }
45
46 fn extra_description(&self) -> &str {
47 "
48This does not load the plugin commands into the scope - see `plugin use` for
49that.
50
51Instead, it runs the plugin to get its command signatures, and then edits the
52plugin registry file (by default, `$nu.plugin-path`). The changes will be
53apparent the next time `nu` is next launched with that plugin registry file.
54"
55 .trim()
56 }
57
58 fn search_terms(&self) -> Vec<&str> {
59 vec!["load", "register", "signature"]
60 }
61
62 fn examples(&self) -> Vec<Example<'_>> {
63 vec![
64 Example {
65 example: "plugin add nu_plugin_inc",
66 description: "Run the `nu_plugin_inc` plugin from the current directory or $env.NU_PLUGIN_DIRS and install its signatures.",
67 result: None,
68 },
69 Example {
70 example: "plugin add --plugin-config polars.msgpackz nu_plugin_polars",
71 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.",
72 result: None,
73 },
74 ]
75 }
76
77 fn run(
78 &self,
79 engine_state: &EngineState,
80 stack: &mut Stack,
81 call: &Call,
82 _input: PipelineData,
83 ) -> Result<PipelineData, ShellError> {
84 let filename: Spanned<String> = call.req(engine_state, stack, 0)?;
85 let shell: Option<Spanned<String>> = call.get_flag(engine_state, stack, "shell")?;
86 let cwd = engine_state.cwd(Some(stack))?;
87
88 let filename_expanded = nu_path::locate_in_dirs(&filename.item, &cwd, || {
90 get_plugin_dirs(engine_state, stack)
91 })
92 .map_err(|err| {
93 IoError::new(
94 err.not_found_as(NotFound::File),
95 filename.span,
96 PathBuf::from(filename.item),
97 )
98 })?;
99
100 let shell_expanded = shell
101 .as_ref()
102 .map(|s| {
103 nu_path::absolute_with(&s.item, &cwd).map_err(|err| IoError::new(err, s.span, None))
104 })
105 .transpose()?;
106
107 let identity = PluginIdentity::new(filename_expanded, shell_expanded).map_err(|_| {
109 ShellError::Generic(GenericError::new(
110 "Plugin filename is invalid",
111 "plugin executable files must start with `nu_plugin_`",
112 filename.span,
113 ))
114 })?;
115
116 let custom_path = call.get_flag(engine_state, stack, "plugin-config")?;
117
118 let plugin = Arc::new(PersistentPlugin::new(
121 identity,
122 PluginGcConfig {
123 enabled: true,
124 stop_after: 0,
125 },
126 ));
127 let interface = plugin.clone().get_plugin(Some((engine_state, stack)))?;
128 let metadata = interface.get_metadata()?;
129 let commands = interface.get_signature()?;
130
131 modify_plugin_file(engine_state, stack, call.head, &custom_path, |contents| {
132 let item = PluginRegistryItem::new(plugin.identity(), metadata, commands);
134 contents.upsert_plugin(item);
135 Ok(())
136 })?;
137
138 Ok(Value::nothing(call.head).into_pipeline_data())
139 }
140}