mirror of
https://github.com/nushell/nushell.git
synced 2025-07-08 18:37:07 +02:00
# Description [Context on Discord](https://discord.com/channels/601130461678272522/855947301380947968/1292279795035668583) **This is a breaking change, due to the removal of `is_running`.** Some users find the `plugin list` command confusing, because it doesn't show anything different after running `plugin add` or `plugin rm`. This modifies the `plugin list` command to also look at the plugin registry file to give some idea of how the plugins in engine state differ from those in the plugin registry file. The following values of `status` are now produced instead of `is_running`: - `added`: The plugin is present in the plugin registry file, but not in the engine. - `loaded`: The plugin is present both in the plugin registry file and in the engine, but is not running. - `running`: The plugin is currently running, and the `pid` column should contain its process ID. - `modified`: The plugin state present in the plugin registry file is different from the state in the engine. - `removed`: The plugin is still loaded in the engine, but is not present in the plugin registry file. - `invalid`: The data in the plugin registry file couldn't be deserialized, and the plugin most likely needs to be added again. Example (`commands` omitted): ``` ╭──────┬─────────────────────┬────────────┬───────────┬──────────┬─────────────────────────────────────────────────────┬─────────╮ │ # │ name │ version │ status │ pid │ filename │ shell │ ├──────┼─────────────────────┼────────────┼───────────┼──────────┼─────────────────────────────────────────────────────┼─────────┤ │ 0 │ custom_values │ 0.1.0 │ loaded │ │ /home/devyn/.cargo/bin/nu_plugin_custom_values │ │ │ 1 │ dbus │ 0.11.0 │ loaded │ │ /home/devyn/.cargo/bin/nu_plugin_dbus │ │ │ 2 │ example │ 0.98.1 │ loaded │ │ /home/devyn/.cargo/bin/nu_plugin_example │ │ │ 3 │ explore_ir │ 0.3.0 │ loaded │ │ /home/devyn/.cargo/bin/nu_plugin_explore_ir │ │ │ 4 │ formats │ 0.98.1 │ loaded │ │ /home/devyn/.cargo/bin/nu_plugin_formats │ │ │ 5 │ gstat │ 0.98.1 │ running │ 236662 │ /home/devyn/.cargo/bin/nu_plugin_gstat │ │ │ 6 │ inc │ 0.98.1 │ loaded │ │ /home/devyn/.cargo/bin/nu_plugin_inc │ │ │ 7 │ polars │ 0.98.1 │ added │ │ /home/devyn/.cargo/bin/nu_plugin_polars │ │ │ 8 │ query │ 0.98.1 │ removed │ │ /home/devyn/.cargo/bin/nu_plugin_query │ │ │ 9 │ stress_internals │ 0.98.1 │ loaded │ │ /home/devyn/.cargo/bin/nu_plugin_stress_internals │ │ ╰──────┴─────────────────────┴────────────┴───────────┴──────────┴─────────────────────────────────────────────────────┴─────────╯ ``` # User-Facing Changes To `plugin list`: * **Breaking:** The `is_running` column is removed and replaced with `status`. Use `status == running` to filter equivalently. * The `--plugin-config` from other plugin management commands is now supported. * Added an `--engine` flag which behaves more or less like before, and doesn't load the plugin registry file at all. * Added a `--registry` flag which only checks the plugin registry file. All plugins appear as `added` since there is no state to compare with. Because the default is to check both, the `plugin list` command might be a little bit slower. If you don't need to check the plugin registry file, the `--engine` flag does not load the plugin registry file at all, so it should be just as fast as before. # Tests + Formatting Added tests for `added` and `removed` statuses. `modified` and `invalid` are a bit more tricky so I didn't try. # After Submitting - [ ] update documentation that references the `plugin list` command - [ ] release notes
132 lines
4.7 KiB
Rust
132 lines
4.7 KiB
Rust
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};
|
|
use std::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)
|
|
// This matches the option to `nu`
|
|
.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 {
|
|
r#"
|
|
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))?;
|
|
|
|
// Check the current directory, or fall back to NU_PLUGIN_DIRS
|
|
let filename_expanded = nu_path::locate_in_dirs(&filename.item, &cwd, || {
|
|
get_plugin_dirs(engine_state, stack)
|
|
})
|
|
.err_span(filename.span)?;
|
|
|
|
let shell_expanded = shell
|
|
.as_ref()
|
|
.map(|s| nu_path::canonicalize_with(&s.item, &cwd).err_span(s.span))
|
|
.transpose()?;
|
|
|
|
// Parse the plugin filename so it can be used to spawn the plugin
|
|
let identity = PluginIdentity::new(filename_expanded, shell_expanded).map_err(|_| {
|
|
ShellError::GenericError {
|
|
error: "Plugin filename is invalid".into(),
|
|
msg: "plugin executable files must start with `nu_plugin_`".into(),
|
|
span: Some(filename.span),
|
|
help: None,
|
|
inner: vec![],
|
|
}
|
|
})?;
|
|
|
|
let custom_path = call.get_flag(engine_state, stack, "plugin-config")?;
|
|
|
|
// Start the plugin manually, to get the freshest signatures and to not affect engine
|
|
// state. Provide a GC config that will stop it ASAP
|
|
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| {
|
|
// Update the file with the received metadata and signatures
|
|
let item = PluginRegistryItem::new(plugin.identity(), metadata, commands);
|
|
contents.upsert_plugin(item);
|
|
Ok(())
|
|
})?;
|
|
|
|
Ok(Value::nothing(call.head).into_pipeline_data())
|
|
}
|
|
}
|