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, shell_error::io::IoError,
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| {
92 IoError::new(
93 err.not_found_as(NotFound::File),
94 filename.span,
95 PathBuf::from(filename.item),
96 )
97 })?;
98
99 let shell_expanded = shell
100 .as_ref()
101 .map(|s| {
102 nu_path::canonicalize_with(&s.item, &cwd)
103 .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::GenericError {
110 error: "Plugin filename is invalid".into(),
111 msg: "plugin executable files must start with `nu_plugin_`".into(),
112 span: Some(filename.span),
113 help: None,
114 inner: vec![],
115 }
116 })?;
117
118 let custom_path = call.get_flag(engine_state, stack, "plugin-config")?;
119
120 let plugin = Arc::new(PersistentPlugin::new(
123 identity,
124 PluginGcConfig {
125 enabled: true,
126 stop_after: 0,
127 },
128 ));
129 let interface = plugin.clone().get_plugin(Some((engine_state, stack)))?;
130 let metadata = interface.get_metadata()?;
131 let commands = interface.get_signature()?;
132
133 modify_plugin_file(engine_state, stack, call.head, &custom_path, |contents| {
134 let item = PluginRegistryItem::new(plugin.identity(), metadata, commands);
136 contents.upsert_plugin(item);
137 Ok(())
138 })?;
139
140 Ok(Value::nothing(call.head).into_pipeline_data())
141 }
142}