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