Accept filenames in other plugin management commands (#12639)

# Description

This allows the following commands to all accept a filename instead of a
plugin name:

- `plugin use`
- `plugin rm`
- `plugin stop`

Slightly complicated because of the need to also check against
`NU_PLUGIN_DIRS`, but I also fixed some issues with that at the same
time

Requested by @fdncred

# User-Facing Changes

The new commands are updated as described.

# Tests + Formatting

Tests for `NU_PLUGIN_DIRS` handling also made more robust.

- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`

# After Submitting

- [ ] Double check new docs to make sure they describe this capability
This commit is contained in:
Devyn Cairns
2024-04-24 04:28:45 -07:00
committed by GitHub
parent 1633004643
commit b576123b0a
11 changed files with 283 additions and 37 deletions

View File

@ -4,7 +4,7 @@ use nu_engine::{command_prelude::*, current_dir};
use nu_plugin::{GetPlugin, PersistentPlugin};
use nu_protocol::{PluginCacheItem, PluginGcConfig, PluginIdentity, RegisteredPlugin};
use crate::util::modify_plugin_file;
use crate::util::{get_plugin_dirs, modify_plugin_file};
#[derive(Clone)]
pub struct PluginAdd;
@ -85,24 +85,10 @@ apparent the next time `nu` is next launched with that plugin cache file.
let cwd = current_dir(engine_state, stack)?;
// Check the current directory, or fall back to NU_PLUGIN_DIRS
let filename_expanded = match nu_path::canonicalize_with(&filename.item, &cwd) {
Ok(path) => path,
Err(err) => {
// Try to find it in NU_PLUGIN_DIRS first, before giving up
let mut found = None;
if let Some(nu_plugin_dirs) = stack.get_env_var(engine_state, "NU_PLUGIN_DIRS") {
for dir in nu_plugin_dirs.into_list().unwrap_or(vec![]) {
if let Ok(path) = nu_path::canonicalize_with(dir.as_str()?, &cwd)
.and_then(|dir| nu_path::canonicalize_with(&filename.item, dir))
{
found = Some(path);
break;
}
}
}
found.ok_or(err.into_spanned(filename.span))?
}
};
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()

View File

@ -1,6 +1,6 @@
use nu_engine::command_prelude::*;
use crate::util::modify_plugin_file;
use crate::util::{canonicalize_possible_filename_arg, modify_plugin_file};
#[derive(Clone)]
pub struct PluginRm;
@ -28,7 +28,7 @@ impl Command for PluginRm {
.required(
"name",
SyntaxShape::String,
"The name of the plugin to remove (not the filename)",
"The name, or filename, of the plugin to remove",
)
.category(Category::Plugin)
}
@ -61,6 +61,11 @@ fixed with `plugin add`.
description: "Remove the installed signatures for the `inc` plugin.",
result: None,
},
Example {
example: "plugin rm ~/.cargo/bin/nu_plugin_inc",
description: "Remove the installed signatures for the plugin with the filename `~/.cargo/bin/nu_plugin_inc`.",
result: None,
},
Example {
example: "plugin rm --plugin-config polars.msgpackz polars",
description: "Remove the installed signatures for the `polars` plugin from the \"polars.msgpackz\" plugin cache file.",
@ -80,8 +85,19 @@ fixed with `plugin add`.
let custom_path = call.get_flag(engine_state, stack, "plugin-config")?;
let force = call.has_flag(engine_state, stack, "force")?;
let filename = canonicalize_possible_filename_arg(engine_state, stack, &name.item);
modify_plugin_file(engine_state, stack, call.head, custom_path, |contents| {
if !force && !contents.plugins.iter().any(|p| p.name == name.item) {
if let Some(index) = contents
.plugins
.iter()
.position(|p| p.name == name.item || p.filename == filename)
{
contents.plugins.remove(index);
Ok(())
} else if force {
Ok(())
} else {
Err(ShellError::GenericError {
error: format!("Failed to remove the `{}` plugin", name.item),
msg: "couldn't find a plugin with this name in the cache file".into(),
@ -89,9 +105,6 @@ fixed with `plugin add`.
help: None,
inner: vec![],
})
} else {
contents.remove_plugin(&name.item);
Ok(())
}
})?;

View File

@ -1,5 +1,7 @@
use nu_engine::command_prelude::*;
use crate::util::canonicalize_possible_filename_arg;
#[derive(Clone)]
pub struct PluginStop;
@ -14,7 +16,7 @@ impl Command for PluginStop {
.required(
"name",
SyntaxShape::String,
"The name of the plugin to stop.",
"The name, or filename, of the plugin to stop",
)
.category(Category::Plugin)
}
@ -30,6 +32,11 @@ impl Command for PluginStop {
description: "Stop the plugin named `inc`.",
result: None,
},
Example {
example: "plugin stop ~/.cargo/bin/nu_plugin_inc",
description: "Stop the plugin with the filename `~/.cargo/bin/nu_plugin_inc`.",
result: None,
},
Example {
example: "plugin list | each { |p| plugin stop $p.name }",
description: "Stop all plugins.",
@ -47,9 +54,12 @@ impl Command for PluginStop {
) -> Result<PipelineData, ShellError> {
let name: Spanned<String> = call.req(engine_state, stack, 0)?;
let filename = canonicalize_possible_filename_arg(engine_state, stack, &name.item);
let mut found = false;
for plugin in engine_state.plugins() {
if plugin.identity().name() == name.item {
let id = &plugin.identity();
if id.name() == name.item || id.filename() == filename {
plugin.stop()?;
found = true;
}

View File

@ -24,7 +24,7 @@ impl Command for PluginUse {
.required(
"name",
SyntaxShape::String,
"The name of the plugin to load (not the filename)",
"The name, or filename, of the plugin to load",
)
.category(Category::Plugin)
}
@ -41,6 +41,9 @@ preparing a plugin cache file and passing `--plugin-config`, or using the
If the plugin was already loaded, this will reload the latest definition from
the cache file into scope.
Note that even if the plugin filename is specified, it will only be loaded if
it was already previously registered with `plugin add`.
"#
.trim()
}
@ -70,6 +73,11 @@ the cache file into scope.
example: r#"plugin use query"#,
result: None,
},
Example {
description: "Load the commands for the plugin with the filename `~/.cargo/bin/nu_plugin_query` from $nu.plugin-path",
example: r#"plugin use ~/.cargo/bin/nu_plugin_query"#,
result: None,
},
Example {
description:
"Load the commands for the `query` plugin from a custom plugin cache file",