mirror of
https://github.com/nushell/nushell
synced 2024-12-27 05:23:11 +00:00
Deprecate register
and add plugin use
(#12607)
# Description Adds a new keyword, `plugin use`. Unlike `register`, this merely loads the signatures from the plugin cache file. The file is configurable with the `--plugin-config` option either to `nu` or to `plugin use` itself, just like the other `plugin` family of commands. At the REPL, one might do this to replace `register`: ```nushell > plugin add ~/.cargo/bin/nu_plugin_foo > plugin use foo ``` This will not work in a script, because `plugin use` is a keyword and `plugin add` does not evaluate at parse time (intentionally). This means we no longer run random binaries during parse. The `--plugins` option has been added to allow running `nu` with certain plugins in one step. This is used especially for the `nu_with_plugins!` test macro, but I'd imagine is generally useful. The only weird quirk is that it has to be a list, and we don't really do this for any of our other CLI args at the moment. `register` now prints a deprecation parse warning. This should fix #11923, as we now have a complete alternative to `register`. # User-Facing Changes - Add `plugin use` command - Deprecate `register` - Add `--plugins` option to `nu` to replace a common use of `register` # Tests + Formatting I think I've tested it thoroughly enough and every existing test passes. Testing nu CLI options and alternate config files is a little hairy and I wish there were some more generic helpers for this, so this will go on my TODO list for refactoring. - 🟢 `toolkit fmt` - 🟢 `toolkit clippy` - 🟢 `toolkit test` - 🟢 `toolkit test stdlib` # After Submitting - [ ] Update plugins sections of book - [ ] Release notes
This commit is contained in:
parent
5c7f7883c8
commit
1f4131532d
27 changed files with 759 additions and 172 deletions
|
@ -54,7 +54,7 @@ apparent the next time `nu` is next launched with that plugin cache file.
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
vec!["plugin", "add", "register", "load", "signature"]
|
vec!["load", "register", "signature"]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
|
|
@ -29,6 +29,10 @@ impl Command for PluginList {
|
||||||
"List installed plugins."
|
"List installed plugins."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["scope"]
|
||||||
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<nu_protocol::Example> {
|
fn examples(&self) -> Vec<nu_protocol::Example> {
|
||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
|
|
|
@ -4,11 +4,13 @@ mod add;
|
||||||
mod list;
|
mod list;
|
||||||
mod rm;
|
mod rm;
|
||||||
mod stop;
|
mod stop;
|
||||||
|
mod use_;
|
||||||
|
|
||||||
pub use add::PluginAdd;
|
pub use add::PluginAdd;
|
||||||
pub use list::PluginList;
|
pub use list::PluginList;
|
||||||
pub use rm::PluginRm;
|
pub use rm::PluginRm;
|
||||||
pub use stop::PluginStop;
|
pub use stop::PluginStop;
|
||||||
|
pub use use_::PluginUse;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct PluginCommand;
|
pub struct PluginCommand;
|
||||||
|
@ -28,10 +30,6 @@ impl Command for PluginCommand {
|
||||||
"Commands for managing plugins."
|
"Commands for managing plugins."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
|
||||||
"To load a plugin, see `register`."
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
|
@ -54,6 +52,20 @@ impl Command for PluginCommand {
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![
|
||||||
|
Example {
|
||||||
|
example: "plugin add nu_plugin_inc",
|
||||||
|
description: "Run the `nu_plugin_inc` plugin from the current directory and install its signatures.",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
example: "plugin use inc",
|
||||||
|
description: "
|
||||||
|
Load (or reload) the `inc` plugin from the plugin cache file and put its commands in scope.
|
||||||
|
The plugin must already be in the cache file at parse time.
|
||||||
|
"
|
||||||
|
.trim(),
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
Example {
|
Example {
|
||||||
example: "plugin list",
|
example: "plugin list",
|
||||||
description: "List installed plugins",
|
description: "List installed plugins",
|
||||||
|
@ -64,11 +76,6 @@ impl Command for PluginCommand {
|
||||||
description: "Stop the plugin named `inc`.",
|
description: "Stop the plugin named `inc`.",
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
|
||||||
example: "plugin add nu_plugin_inc",
|
|
||||||
description: "Run the `nu_plugin_inc` plugin from the current directory and install its signatures.",
|
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
Example {
|
Example {
|
||||||
example: "plugin rm inc",
|
example: "plugin rm inc",
|
||||||
description: "Remove the installed signatures for the `inc` plugin.",
|
description: "Remove the installed signatures for the `inc` plugin.",
|
||||||
|
|
|
@ -51,7 +51,7 @@ fixed with `plugin add`.
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
vec!["plugin", "rm", "remove", "delete", "signature"]
|
vec!["remove", "delete", "signature"]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
|
81
crates/nu-cmd-plugin/src/commands/plugin/use_.rs
Normal file
81
crates/nu-cmd-plugin/src/commands/plugin/use_.rs
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PluginUse;
|
||||||
|
|
||||||
|
impl Command for PluginUse {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"plugin use"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Load a plugin from the plugin cache file into scope."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
|
Signature::build(self.name())
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||||
|
.named(
|
||||||
|
"plugin-config",
|
||||||
|
SyntaxShape::Filepath,
|
||||||
|
"Use a plugin cache file other than the one set in `$nu.plugin-path`",
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.required(
|
||||||
|
"name",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"The name of the plugin to load (not the filename)",
|
||||||
|
)
|
||||||
|
.category(Category::Plugin)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
r#"
|
||||||
|
This command is a parser keyword. For details, check:
|
||||||
|
https://www.nushell.sh/book/thinking_in_nu.html
|
||||||
|
|
||||||
|
The plugin definition must be available in the plugin cache file at parse time.
|
||||||
|
Run `plugin add` first in the REPL to do this, or from a script consider
|
||||||
|
preparing a plugin cache file and passing `--plugin-config`, or using the
|
||||||
|
`--plugin` option to `nu` instead.
|
||||||
|
|
||||||
|
If the plugin was already loaded, this will reload the latest definition from
|
||||||
|
the cache file into scope.
|
||||||
|
"#
|
||||||
|
.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["add", "register", "scope"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_parser_keyword(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
_engine_state: &EngineState,
|
||||||
|
_stack: &mut Stack,
|
||||||
|
_call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
Ok(PipelineData::empty())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Load the commands for the `query` plugin from $nu.plugin-path",
|
||||||
|
example: r#"plugin use query"#,
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description:
|
||||||
|
"Load the commands for the `query` plugin from a custom plugin cache file",
|
||||||
|
example: r#"plugin use --plugin-config local-plugins.msgpackz query"#,
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -35,12 +35,17 @@ impl Command for Register {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
fn extra_usage(&self) -> &str {
|
||||||
r#"This command is a parser keyword. For details, check:
|
r#"
|
||||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
Deprecated in favor of `plugin add` and `plugin use`.
|
||||||
|
|
||||||
|
This command is a parser keyword. For details, check:
|
||||||
|
https://www.nushell.sh/book/thinking_in_nu.html
|
||||||
|
"#
|
||||||
|
.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
vec!["plugin", "add", "register"]
|
vec!["add"]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_parser_keyword(&self) -> bool {
|
fn is_parser_keyword(&self) -> bool {
|
||||||
|
|
|
@ -12,11 +12,12 @@ pub fn add_plugin_command_context(mut engine_state: EngineState) -> EngineState
|
||||||
}
|
}
|
||||||
|
|
||||||
bind_command!(
|
bind_command!(
|
||||||
PluginCommand,
|
|
||||||
PluginAdd,
|
PluginAdd,
|
||||||
|
PluginCommand,
|
||||||
PluginList,
|
PluginList,
|
||||||
PluginRm,
|
PluginRm,
|
||||||
PluginStop,
|
PluginStop,
|
||||||
|
PluginUse,
|
||||||
Register,
|
Register,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -479,5 +479,5 @@ fn read_code_should_fail_rather_than_panic() {
|
||||||
let actual = nu!(cwd: "tests/fixtures/formats", pipeline(
|
let actual = nu!(cwd: "tests/fixtures/formats", pipeline(
|
||||||
r#"open code.nu | from nuon"#
|
r#"open code.nu | from nuon"#
|
||||||
));
|
));
|
||||||
assert!(actual.err.contains("error when parsing"))
|
assert!(actual.err.contains("Error when loading"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,6 +71,7 @@ pub const UNALIASABLE_PARSER_KEYWORDS: &[&[u8]] = &[
|
||||||
b"source",
|
b"source",
|
||||||
b"where",
|
b"where",
|
||||||
b"register",
|
b"register",
|
||||||
|
b"plugin use",
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Check whether spans start with a parser keyword that can be aliased
|
/// Check whether spans start with a parser keyword that can be aliased
|
||||||
|
@ -93,11 +94,14 @@ pub fn is_unaliasable_parser_keyword(working_set: &StateWorkingSet, spans: &[Spa
|
||||||
/// This is a new more compact method of calling parse_xxx() functions without repeating the
|
/// This is a new more compact method of calling parse_xxx() functions without repeating the
|
||||||
/// parse_call() in each function. Remaining keywords can be moved here.
|
/// parse_call() in each function. Remaining keywords can be moved here.
|
||||||
pub fn parse_keyword(working_set: &mut StateWorkingSet, lite_command: &LiteCommand) -> Pipeline {
|
pub fn parse_keyword(working_set: &mut StateWorkingSet, lite_command: &LiteCommand) -> Pipeline {
|
||||||
|
let orig_parse_errors_len = working_set.parse_errors.len();
|
||||||
|
|
||||||
let call_expr = parse_call(working_set, &lite_command.parts, lite_command.parts[0]);
|
let call_expr = parse_call(working_set, &lite_command.parts, lite_command.parts[0]);
|
||||||
|
|
||||||
// if err.is_some() {
|
// If an error occurred, don't invoke the keyword-specific functionality
|
||||||
// return (Pipeline::from_vec(vec![call_expr]), err);
|
if working_set.parse_errors.len() > orig_parse_errors_len {
|
||||||
// }
|
return Pipeline::from_vec(vec![call_expr]);
|
||||||
|
}
|
||||||
|
|
||||||
if let Expression {
|
if let Expression {
|
||||||
expr: Expr::Call(call),
|
expr: Expr::Call(call),
|
||||||
|
@ -121,6 +125,8 @@ pub fn parse_keyword(working_set: &mut StateWorkingSet, lite_command: &LiteComma
|
||||||
"overlay hide" => parse_overlay_hide(working_set, call),
|
"overlay hide" => parse_overlay_hide(working_set, call),
|
||||||
"overlay new" => parse_overlay_new(working_set, call),
|
"overlay new" => parse_overlay_new(working_set, call),
|
||||||
"overlay use" => parse_overlay_use(working_set, call),
|
"overlay use" => parse_overlay_use(working_set, call),
|
||||||
|
#[cfg(feature = "plugin")]
|
||||||
|
"plugin use" => parse_plugin_use(working_set, call),
|
||||||
_ => Pipeline::from_vec(vec![call_expr]),
|
_ => Pipeline::from_vec(vec![call_expr]),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -1946,7 +1952,7 @@ pub fn parse_module_file_or_dir(
|
||||||
let cwd = working_set.get_cwd();
|
let cwd = working_set.get_cwd();
|
||||||
|
|
||||||
let module_path =
|
let module_path =
|
||||||
if let Some(path) = find_in_dirs(&module_path_str, working_set, &cwd, LIB_DIRS_VAR) {
|
if let Some(path) = find_in_dirs(&module_path_str, working_set, &cwd, Some(LIB_DIRS_VAR)) {
|
||||||
path
|
path
|
||||||
} else {
|
} else {
|
||||||
working_set.error(ParseError::ModuleNotFound(path_span, module_path_str));
|
working_set.error(ParseError::ModuleNotFound(path_span, module_path_str));
|
||||||
|
@ -3402,7 +3408,7 @@ pub fn parse_source(working_set: &mut StateWorkingSet, lite_command: &LiteComman
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(path) = find_in_dirs(&filename, working_set, &cwd, LIB_DIRS_VAR) {
|
if let Some(path) = find_in_dirs(&filename, working_set, &cwd, Some(LIB_DIRS_VAR)) {
|
||||||
if let Some(contents) = path.read(working_set) {
|
if let Some(contents) = path.read(working_set) {
|
||||||
// Add the file to the stack of files being processed.
|
// Add the file to the stack of files being processed.
|
||||||
if let Err(e) = working_set.files.push(path.clone().path_buf(), spans[1]) {
|
if let Err(e) = working_set.files.push(path.clone().path_buf(), spans[1]) {
|
||||||
|
@ -3546,11 +3552,13 @@ pub fn parse_where(working_set: &mut StateWorkingSet, lite_command: &LiteCommand
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `register` is deprecated and will be removed in 0.94. Use `plugin add` and `plugin use` instead.
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
pub fn parse_register(working_set: &mut StateWorkingSet, lite_command: &LiteCommand) -> Pipeline {
|
pub fn parse_register(working_set: &mut StateWorkingSet, lite_command: &LiteCommand) -> Pipeline {
|
||||||
use nu_plugin::{get_signature, PersistentPlugin, PluginDeclaration};
|
use nu_plugin::{get_signature, PluginDeclaration};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::Stack, ErrSpan, PluginCacheItem, PluginIdentity, PluginSignature, RegisteredPlugin,
|
engine::Stack, ErrSpan, ParseWarning, PluginCacheItem, PluginIdentity, PluginSignature,
|
||||||
|
RegisteredPlugin,
|
||||||
};
|
};
|
||||||
|
|
||||||
let spans = &lite_command.parts;
|
let spans = &lite_command.parts;
|
||||||
|
@ -3561,7 +3569,7 @@ pub fn parse_register(working_set: &mut StateWorkingSet, lite_command: &LiteComm
|
||||||
// Maybe this is not necessary but it is a sanity check
|
// Maybe this is not necessary but it is a sanity check
|
||||||
if working_set.get_span_contents(spans[0]) != b"register" {
|
if working_set.get_span_contents(spans[0]) != b"register" {
|
||||||
working_set.error(ParseError::UnknownState(
|
working_set.error(ParseError::UnknownState(
|
||||||
"internal error: Wrong call name for parse plugin function".into(),
|
"internal error: Wrong call name for 'register' function".into(),
|
||||||
span(spans),
|
span(spans),
|
||||||
));
|
));
|
||||||
return garbage_pipeline(spans);
|
return garbage_pipeline(spans);
|
||||||
|
@ -3609,6 +3617,16 @@ pub fn parse_register(working_set: &mut StateWorkingSet, lite_command: &LiteComm
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Now that the call is parsed, add the deprecation warning
|
||||||
|
working_set
|
||||||
|
.parse_warnings
|
||||||
|
.push(ParseWarning::DeprecatedWarning {
|
||||||
|
old_command: "register".into(),
|
||||||
|
new_suggestion: "use `plugin add` and `plugin use`".into(),
|
||||||
|
span: call.head,
|
||||||
|
url: "https://www.nushell.sh/book/plugins.html".into(),
|
||||||
|
});
|
||||||
|
|
||||||
// Extracting the required arguments from the call and keeping them together in a tuple
|
// Extracting the required arguments from the call and keeping them together in a tuple
|
||||||
let arguments = call
|
let arguments = call
|
||||||
.positional_nth(0)
|
.positional_nth(0)
|
||||||
|
@ -3619,7 +3637,8 @@ pub fn parse_register(working_set: &mut StateWorkingSet, lite_command: &LiteComm
|
||||||
.coerce_into_string()
|
.coerce_into_string()
|
||||||
.map_err(|err| err.wrap(working_set, call.head))?;
|
.map_err(|err| err.wrap(working_set, call.head))?;
|
||||||
|
|
||||||
let Some(path) = find_in_dirs(&filename, working_set, &cwd, PLUGIN_DIRS_VAR) else {
|
let Some(path) = find_in_dirs(&filename, working_set, &cwd, Some(PLUGIN_DIRS_VAR))
|
||||||
|
else {
|
||||||
return Err(ParseError::RegisteredFileNotFound(filename, expr.span));
|
return Err(ParseError::RegisteredFileNotFound(filename, expr.span));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3695,27 +3714,8 @@ pub fn parse_register(working_set: &mut StateWorkingSet, lite_command: &LiteComm
|
||||||
// Create the plugin identity. This validates that the plugin name starts with `nu_plugin_`
|
// Create the plugin identity. This validates that the plugin name starts with `nu_plugin_`
|
||||||
let identity = PluginIdentity::new(path, shell).err_span(path_span)?;
|
let identity = PluginIdentity::new(path, shell).err_span(path_span)?;
|
||||||
|
|
||||||
// Find garbage collection config
|
let plugin = nu_plugin::add_plugin_to_working_set(working_set, &identity)
|
||||||
let gc_config = working_set
|
.map_err(|err| err.wrap(working_set, call.head))?;
|
||||||
.get_config()
|
|
||||||
.plugin_gc
|
|
||||||
.get(identity.name())
|
|
||||||
.clone();
|
|
||||||
|
|
||||||
// Add it to the working set
|
|
||||||
let plugin = working_set.find_or_create_plugin(&identity, || {
|
|
||||||
Arc::new(PersistentPlugin::new(identity.clone(), gc_config.clone()))
|
|
||||||
});
|
|
||||||
|
|
||||||
// Downcast the plugin to `PersistentPlugin` - we generally expect this to succeed. The
|
|
||||||
// trait object only exists so that nu-protocol can contain plugins without knowing anything
|
|
||||||
// about their implementation, but we only use `PersistentPlugin` in practice.
|
|
||||||
let plugin: Arc<PersistentPlugin> = plugin.as_any().downcast().map_err(|_| {
|
|
||||||
ParseError::InternalError(
|
|
||||||
"encountered unexpected RegisteredPlugin type".into(),
|
|
||||||
spans[0],
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let signatures = signature.map_or_else(
|
let signatures = signature.map_or_else(
|
||||||
|| {
|
|| {
|
||||||
|
@ -3731,8 +3731,6 @@ pub fn parse_register(working_set: &mut StateWorkingSet, lite_command: &LiteComm
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
plugin.set_gc_config(&gc_config);
|
|
||||||
|
|
||||||
let signatures = get_signature(plugin.clone(), get_envs).map_err(|err| {
|
let signatures = get_signature(plugin.clone(), get_envs).map_err(|err| {
|
||||||
log::warn!("Error getting signatures: {err:?}");
|
log::warn!("Error getting signatures: {err:?}");
|
||||||
ParseError::LabeledError(
|
ParseError::LabeledError(
|
||||||
|
@ -3776,6 +3774,100 @@ pub fn parse_register(working_set: &mut StateWorkingSet, lite_command: &LiteComm
|
||||||
}])
|
}])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "plugin")]
|
||||||
|
pub fn parse_plugin_use(working_set: &mut StateWorkingSet, call: Box<Call>) -> Pipeline {
|
||||||
|
use nu_protocol::{FromValue, PluginCacheFile};
|
||||||
|
|
||||||
|
let cwd = working_set.get_cwd();
|
||||||
|
|
||||||
|
if let Err(err) = (|| {
|
||||||
|
let name = call
|
||||||
|
.positional_nth(0)
|
||||||
|
.map(|expr| {
|
||||||
|
eval_constant(working_set, expr)
|
||||||
|
.and_then(Spanned::<String>::from_value)
|
||||||
|
.map_err(|err| err.wrap(working_set, call.head))
|
||||||
|
})
|
||||||
|
.expect("required positional should have been checked")?;
|
||||||
|
|
||||||
|
let plugin_config = call
|
||||||
|
.named_iter()
|
||||||
|
.find(|(arg_name, _, _)| arg_name.item == "plugin-config")
|
||||||
|
.map(|(_, _, expr)| {
|
||||||
|
let expr = expr
|
||||||
|
.as_ref()
|
||||||
|
.expect("--plugin-config arg should have been checked already");
|
||||||
|
eval_constant(working_set, expr)
|
||||||
|
.and_then(Spanned::<String>::from_value)
|
||||||
|
.map_err(|err| err.wrap(working_set, call.head))
|
||||||
|
})
|
||||||
|
.transpose()?;
|
||||||
|
|
||||||
|
// Find the actual plugin config path location. We don't have a const/env variable for this,
|
||||||
|
// it either lives in the current working directory or in the script's directory
|
||||||
|
let plugin_config_path = if let Some(custom_path) = &plugin_config {
|
||||||
|
find_in_dirs(&custom_path.item, working_set, &cwd, None).ok_or_else(|| {
|
||||||
|
ParseError::FileNotFound(custom_path.item.clone(), custom_path.span)
|
||||||
|
})?
|
||||||
|
} else {
|
||||||
|
ParserPath::RealPath(
|
||||||
|
working_set
|
||||||
|
.permanent_state
|
||||||
|
.plugin_path
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| ParseError::LabeledErrorWithHelp {
|
||||||
|
error: "Plugin cache file not set".into(),
|
||||||
|
label: "can't load plugin without cache file".into(),
|
||||||
|
span: call.head,
|
||||||
|
help:
|
||||||
|
"pass --plugin-config to `plugin use` when $nu.plugin-path is not set"
|
||||||
|
.into(),
|
||||||
|
})?
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let file = plugin_config_path.open(working_set).map_err(|err| {
|
||||||
|
ParseError::LabeledError(
|
||||||
|
"Plugin cache file can't be opened".into(),
|
||||||
|
err.to_string(),
|
||||||
|
plugin_config.as_ref().map(|p| p.span).unwrap_or(call.head),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// The file is now open, so we just have to parse the contents and find the plugin
|
||||||
|
let contents = PluginCacheFile::read_from(file, Some(call.head))
|
||||||
|
.map_err(|err| err.wrap(working_set, call.head))?;
|
||||||
|
|
||||||
|
let plugin_item = contents
|
||||||
|
.plugins
|
||||||
|
.iter()
|
||||||
|
.find(|plugin| plugin.name == name.item)
|
||||||
|
.ok_or_else(|| ParseError::PluginNotFound {
|
||||||
|
name: name.item.clone(),
|
||||||
|
name_span: name.span,
|
||||||
|
plugin_config_span: plugin_config.as_ref().map(|p| p.span),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Now add the signatures to the working set
|
||||||
|
nu_plugin::load_plugin_cache_item(working_set, plugin_item, Some(call.head))
|
||||||
|
.map_err(|err| err.wrap(working_set, call.head))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})() {
|
||||||
|
working_set.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
let call_span = call.span();
|
||||||
|
|
||||||
|
Pipeline::from_vec(vec![Expression {
|
||||||
|
expr: Expr::Call(call),
|
||||||
|
span: call_span,
|
||||||
|
ty: Type::Nothing,
|
||||||
|
custom_completion: None,
|
||||||
|
}])
|
||||||
|
}
|
||||||
|
|
||||||
pub fn find_dirs_var(working_set: &StateWorkingSet, var_name: &str) -> Option<VarId> {
|
pub fn find_dirs_var(working_set: &StateWorkingSet, var_name: &str) -> Option<VarId> {
|
||||||
working_set
|
working_set
|
||||||
.find_variable(format!("${}", var_name).as_bytes())
|
.find_variable(format!("${}", var_name).as_bytes())
|
||||||
|
@ -3799,13 +3891,13 @@ pub fn find_in_dirs(
|
||||||
filename: &str,
|
filename: &str,
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
cwd: &str,
|
cwd: &str,
|
||||||
dirs_var_name: &str,
|
dirs_var_name: Option<&str>,
|
||||||
) -> Option<ParserPath> {
|
) -> Option<ParserPath> {
|
||||||
pub fn find_in_dirs_with_id(
|
pub fn find_in_dirs_with_id(
|
||||||
filename: &str,
|
filename: &str,
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
cwd: &str,
|
cwd: &str,
|
||||||
dirs_var_name: &str,
|
dirs_var_name: Option<&str>,
|
||||||
) -> Option<ParserPath> {
|
) -> Option<ParserPath> {
|
||||||
// Choose whether to use file-relative or PWD-relative path
|
// Choose whether to use file-relative or PWD-relative path
|
||||||
let actual_cwd = working_set
|
let actual_cwd = working_set
|
||||||
|
@ -3845,8 +3937,10 @@ pub fn find_in_dirs(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look up relative path from NU_LIB_DIRS
|
// Look up relative path from NU_LIB_DIRS
|
||||||
working_set
|
dirs_var_name
|
||||||
.get_variable(find_dirs_var(working_set, dirs_var_name)?)
|
.as_ref()
|
||||||
|
.and_then(|dirs_var_name| find_dirs_var(working_set, dirs_var_name))
|
||||||
|
.map(|var_id| working_set.get_variable(var_id))?
|
||||||
.const_val
|
.const_val
|
||||||
.as_ref()?
|
.as_ref()?
|
||||||
.as_list()
|
.as_list()
|
||||||
|
@ -3868,7 +3962,7 @@ pub fn find_in_dirs(
|
||||||
filename: &str,
|
filename: &str,
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
cwd: &str,
|
cwd: &str,
|
||||||
dirs_env: &str,
|
dirs_env: Option<&str>,
|
||||||
) -> Option<PathBuf> {
|
) -> Option<PathBuf> {
|
||||||
// Choose whether to use file-relative or PWD-relative path
|
// Choose whether to use file-relative or PWD-relative path
|
||||||
let actual_cwd = working_set
|
let actual_cwd = working_set
|
||||||
|
@ -3882,7 +3976,9 @@ pub fn find_in_dirs(
|
||||||
let path = Path::new(filename);
|
let path = Path::new(filename);
|
||||||
|
|
||||||
if path.is_relative() {
|
if path.is_relative() {
|
||||||
if let Some(lib_dirs) = working_set.get_env_var(dirs_env) {
|
if let Some(lib_dirs) =
|
||||||
|
dirs_env.and_then(|dirs_env| working_set.get_env_var(dirs_env))
|
||||||
|
{
|
||||||
if let Ok(dirs) = lib_dirs.as_list() {
|
if let Ok(dirs) = lib_dirs.as_list() {
|
||||||
for lib_dir in dirs {
|
for lib_dir in dirs {
|
||||||
if let Ok(dir) = lib_dir.to_path() {
|
if let Ok(dir) = lib_dir.to_path() {
|
||||||
|
|
|
@ -5149,12 +5149,24 @@ pub fn parse_expression(working_set: &mut StateWorkingSet, spans: &[Span]) -> Ex
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
b"register" => {
|
b"register" => {
|
||||||
working_set.error(ParseError::BuiltinCommandInPipeline(
|
working_set.error(ParseError::BuiltinCommandInPipeline(
|
||||||
"plugin".into(),
|
"register".into(),
|
||||||
spans[0],
|
spans[0],
|
||||||
));
|
));
|
||||||
|
|
||||||
parse_call(working_set, &spans[pos..], spans[0])
|
parse_call(working_set, &spans[pos..], spans[0])
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "plugin")]
|
||||||
|
b"plugin" => {
|
||||||
|
if spans.len() > 1 && working_set.get_span_contents(spans[1]) == b"use" {
|
||||||
|
// only 'plugin use' is banned
|
||||||
|
working_set.error(ParseError::BuiltinCommandInPipeline(
|
||||||
|
"plugin use".into(),
|
||||||
|
spans[0],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_call(working_set, &spans[pos..], spans[0])
|
||||||
|
}
|
||||||
|
|
||||||
_ => parse_call(working_set, &spans[pos..], spans[0]),
|
_ => parse_call(working_set, &spans[pos..], spans[0]),
|
||||||
}
|
}
|
||||||
|
@ -5286,6 +5298,20 @@ pub fn parse_builtin_commands(
|
||||||
b"where" => parse_where(working_set, lite_command),
|
b"where" => parse_where(working_set, lite_command),
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
b"register" => parse_register(working_set, lite_command),
|
b"register" => parse_register(working_set, lite_command),
|
||||||
|
// Only "plugin use" is a keyword
|
||||||
|
#[cfg(feature = "plugin")]
|
||||||
|
b"plugin"
|
||||||
|
if lite_command
|
||||||
|
.parts
|
||||||
|
.get(1)
|
||||||
|
.is_some_and(|span| working_set.get_span_contents(*span) == b"use") =>
|
||||||
|
{
|
||||||
|
if let Some(redirection) = lite_command.redirection.as_ref() {
|
||||||
|
working_set.error(redirecting_builtin_error("plugin use", redirection));
|
||||||
|
return garbage_pipeline(&lite_command.parts);
|
||||||
|
}
|
||||||
|
parse_keyword(working_set, lite_command)
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let element = parse_pipeline_element(working_set, lite_command);
|
let element = parse_pipeline_element(working_set, lite_command);
|
||||||
|
|
||||||
|
|
|
@ -103,17 +103,33 @@ impl ParserPath {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read<'a>(&'a self, working_set: &'a StateWorkingSet) -> Option<Vec<u8>> {
|
pub fn open<'a>(
|
||||||
|
&'a self,
|
||||||
|
working_set: &'a StateWorkingSet,
|
||||||
|
) -> std::io::Result<Box<dyn std::io::Read + 'a>> {
|
||||||
match self {
|
match self {
|
||||||
ParserPath::RealPath(p) => std::fs::read(p).ok(),
|
ParserPath::RealPath(p) => {
|
||||||
|
std::fs::File::open(p).map(|f| Box::new(f) as Box<dyn std::io::Read>)
|
||||||
|
}
|
||||||
ParserPath::VirtualFile(_, file_id) => working_set
|
ParserPath::VirtualFile(_, file_id) => working_set
|
||||||
.get_contents_of_file(*file_id)
|
.get_contents_of_file(*file_id)
|
||||||
.map(|bytes| bytes.to_vec()),
|
.map(|bytes| Box::new(bytes) as Box<dyn std::io::Read>)
|
||||||
|
.ok_or(std::io::ErrorKind::NotFound.into()),
|
||||||
|
|
||||||
ParserPath::VirtualDir(..) => None,
|
ParserPath::VirtualDir(..) => Err(std::io::ErrorKind::NotFound.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn read<'a>(&'a self, working_set: &'a StateWorkingSet) -> Option<Vec<u8>> {
|
||||||
|
self.open(working_set)
|
||||||
|
.and_then(|mut reader| {
|
||||||
|
let mut vec = vec![];
|
||||||
|
reader.read_to_end(&mut vec)?;
|
||||||
|
Ok(vec)
|
||||||
|
})
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn from_virtual_path(
|
pub fn from_virtual_path(
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
name: &str,
|
name: &str,
|
||||||
|
|
|
@ -78,10 +78,11 @@ pub use serializers::{json::JsonSerializer, msgpack::MsgPackSerializer};
|
||||||
// Used by other nu crates.
|
// Used by other nu crates.
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use plugin::{
|
pub use plugin::{
|
||||||
create_plugin_signature, get_signature, load_plugin_cache_item, load_plugin_file,
|
add_plugin_to_working_set, create_plugin_signature, get_signature, load_plugin_cache_item,
|
||||||
serve_plugin_io, EngineInterfaceManager, GetPlugin, Interface, InterfaceManager,
|
load_plugin_file, serve_plugin_io, EngineInterfaceManager, GetPlugin, Interface,
|
||||||
PersistentPlugin, PluginDeclaration, PluginExecutionCommandContext, PluginExecutionContext,
|
InterfaceManager, PersistentPlugin, PluginDeclaration, PluginExecutionCommandContext,
|
||||||
PluginInterface, PluginInterfaceManager, PluginSource, ServePluginError,
|
PluginExecutionContext, PluginInterface, PluginInterfaceManager, PluginSource,
|
||||||
|
ServePluginError,
|
||||||
};
|
};
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use protocol::{PluginCustomValue, PluginInput, PluginOutput};
|
pub use protocol::{PluginCustomValue, PluginInput, PluginOutput};
|
||||||
|
|
|
@ -748,9 +748,9 @@ impl PluginInterface {
|
||||||
PluginCall::CustomValueOp(val, _) => Some(val.span),
|
PluginCall::CustomValueOp(val, _) => Some(val.span),
|
||||||
},
|
},
|
||||||
help: Some(format!(
|
help: Some(format!(
|
||||||
"the plugin may have experienced an error. Try registering the plugin again \
|
"the plugin may have experienced an error. Try loading the plugin again \
|
||||||
with `{}`",
|
with `{}`",
|
||||||
self.state.source.identity.register_command(),
|
self.state.source.identity.use_command(),
|
||||||
)),
|
)),
|
||||||
inner: vec![],
|
inner: vec![],
|
||||||
})?;
|
})?;
|
||||||
|
|
|
@ -25,7 +25,7 @@ use nu_engine::documentation::get_flags_section;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Operator, engine::StateWorkingSet, report_error_new, CustomValue, IntoSpanned,
|
ast::Operator, engine::StateWorkingSet, report_error_new, CustomValue, IntoSpanned,
|
||||||
LabeledError, PipelineData, PluginCacheFile, PluginCacheItem, PluginCacheItemData,
|
LabeledError, PipelineData, PluginCacheFile, PluginCacheItem, PluginCacheItemData,
|
||||||
PluginIdentity, PluginSignature, ShellError, Span, Spanned, Value,
|
PluginIdentity, PluginSignature, RegisteredPlugin, ShellError, Span, Spanned, Value,
|
||||||
};
|
};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
@ -942,7 +942,7 @@ pub fn load_plugin_cache_item(
|
||||||
working_set: &mut StateWorkingSet,
|
working_set: &mut StateWorkingSet,
|
||||||
plugin: &PluginCacheItem,
|
plugin: &PluginCacheItem,
|
||||||
span: Option<Span>,
|
span: Option<Span>,
|
||||||
) -> Result<(), ShellError> {
|
) -> Result<Arc<PersistentPlugin>, ShellError> {
|
||||||
let identity =
|
let identity =
|
||||||
PluginIdentity::new(plugin.filename.clone(), plugin.shell.clone()).map_err(|_| {
|
PluginIdentity::new(plugin.filename.clone(), plugin.shell.clone()).map_err(|_| {
|
||||||
ShellError::GenericError {
|
ShellError::GenericError {
|
||||||
|
@ -960,39 +960,54 @@ pub fn load_plugin_cache_item(
|
||||||
|
|
||||||
match &plugin.data {
|
match &plugin.data {
|
||||||
PluginCacheItemData::Valid { commands } => {
|
PluginCacheItemData::Valid { commands } => {
|
||||||
// Find garbage collection config for the plugin
|
let plugin = add_plugin_to_working_set(working_set, &identity)?;
|
||||||
let gc_config = working_set
|
|
||||||
.get_config()
|
|
||||||
.plugin_gc
|
|
||||||
.get(identity.name())
|
|
||||||
.clone();
|
|
||||||
|
|
||||||
// Add it to / get it from the working set
|
// Ensure that the plugin is reset. We're going to load new signatures, so we want to
|
||||||
let plugin = working_set.find_or_create_plugin(&identity, || {
|
// make sure the running plugin reflects those new signatures, and it's possible that it
|
||||||
Arc::new(PersistentPlugin::new(identity.clone(), gc_config.clone()))
|
// doesn't.
|
||||||
});
|
plugin.reset()?;
|
||||||
|
|
||||||
// Downcast the plugin to `PersistentPlugin` - we generally expect this to succeed.
|
|
||||||
// The trait object only exists so that nu-protocol can contain plugins without knowing
|
|
||||||
// anything about their implementation, but we only use `PersistentPlugin` in practice.
|
|
||||||
let plugin: Arc<PersistentPlugin> =
|
|
||||||
plugin
|
|
||||||
.as_any()
|
|
||||||
.downcast()
|
|
||||||
.map_err(|_| ShellError::NushellFailed {
|
|
||||||
msg: "encountered unexpected RegisteredPlugin type".into(),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// Create the declarations from the commands
|
// Create the declarations from the commands
|
||||||
for signature in commands {
|
for signature in commands {
|
||||||
let decl = PluginDeclaration::new(plugin.clone(), signature.clone());
|
let decl = PluginDeclaration::new(plugin.clone(), signature.clone());
|
||||||
working_set.add_decl(Box::new(decl));
|
working_set.add_decl(Box::new(decl));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(plugin)
|
||||||
}
|
}
|
||||||
PluginCacheItemData::Invalid => Err(ShellError::PluginCacheDataInvalid {
|
PluginCacheItemData::Invalid => Err(ShellError::PluginCacheDataInvalid {
|
||||||
plugin_name: identity.name().to_owned(),
|
plugin_name: identity.name().to_owned(),
|
||||||
register_command: identity.register_command(),
|
span,
|
||||||
|
add_command: identity.add_command(),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn add_plugin_to_working_set(
|
||||||
|
working_set: &mut StateWorkingSet,
|
||||||
|
identity: &PluginIdentity,
|
||||||
|
) -> Result<Arc<PersistentPlugin>, ShellError> {
|
||||||
|
// Find garbage collection config for the plugin
|
||||||
|
let gc_config = working_set
|
||||||
|
.get_config()
|
||||||
|
.plugin_gc
|
||||||
|
.get(identity.name())
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
// Add it to / get it from the working set
|
||||||
|
let plugin = working_set.find_or_create_plugin(identity, || {
|
||||||
|
Arc::new(PersistentPlugin::new(identity.clone(), gc_config.clone()))
|
||||||
|
});
|
||||||
|
|
||||||
|
plugin.set_gc_config(&gc_config);
|
||||||
|
|
||||||
|
// Downcast the plugin to `PersistentPlugin` - we generally expect this to succeed.
|
||||||
|
// The trait object only exists so that nu-protocol can contain plugins without knowing
|
||||||
|
// anything about their implementation, but we only use `PersistentPlugin` in practice.
|
||||||
|
plugin
|
||||||
|
.as_any()
|
||||||
|
.downcast()
|
||||||
|
.map_err(|_| ShellError::NushellFailed {
|
||||||
|
msg: "encountered unexpected RegisteredPlugin type".into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -108,6 +108,13 @@ impl Expression {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn as_filepath(&self) -> Option<(String, bool)> {
|
||||||
|
match &self.expr {
|
||||||
|
Expr::Filepath(string, quoted) => Some((string.clone(), *quoted)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn as_import_pattern(&self) -> Option<ImportPattern> {
|
pub fn as_import_pattern(&self) -> Option<ImportPattern> {
|
||||||
match &self.expr {
|
match &self.expr {
|
||||||
Expr::ImportPattern(pattern) => Some(*pattern.clone()),
|
Expr::ImportPattern(pattern) => Some(*pattern.clone()),
|
||||||
|
|
|
@ -439,6 +439,19 @@ pub enum ParseError {
|
||||||
#[diagnostic(code(nu::parser::file_not_found))]
|
#[diagnostic(code(nu::parser::file_not_found))]
|
||||||
FileNotFound(String, #[label("File not found: {0}")] Span),
|
FileNotFound(String, #[label("File not found: {0}")] Span),
|
||||||
|
|
||||||
|
#[error("Plugin not found")]
|
||||||
|
#[diagnostic(
|
||||||
|
code(nu::parser::plugin_not_found),
|
||||||
|
help("plugins need to be added to the plugin cache file before your script is run (see `plugin add`)"),
|
||||||
|
)]
|
||||||
|
PluginNotFound {
|
||||||
|
name: String,
|
||||||
|
#[label("Plugin not found: {name}")]
|
||||||
|
name_span: Span,
|
||||||
|
#[label("in this cache file")]
|
||||||
|
plugin_config_span: Option<Span>,
|
||||||
|
},
|
||||||
|
|
||||||
#[error("Invalid literal")] // <problem> in <entity>.
|
#[error("Invalid literal")] // <problem> in <entity>.
|
||||||
#[diagnostic()]
|
#[diagnostic()]
|
||||||
InvalidLiteral(String, String, #[label("{0} in {1}")] Span),
|
InvalidLiteral(String, String, #[label("{0} in {1}")] Span),
|
||||||
|
@ -544,6 +557,7 @@ impl ParseError {
|
||||||
ParseError::SourcedFileNotFound(_, s) => *s,
|
ParseError::SourcedFileNotFound(_, s) => *s,
|
||||||
ParseError::RegisteredFileNotFound(_, s) => *s,
|
ParseError::RegisteredFileNotFound(_, s) => *s,
|
||||||
ParseError::FileNotFound(_, s) => *s,
|
ParseError::FileNotFound(_, s) => *s,
|
||||||
|
ParseError::PluginNotFound { name_span, .. } => *name_span,
|
||||||
ParseError::LabeledError(_, _, s) => *s,
|
ParseError::LabeledError(_, _, s) => *s,
|
||||||
ParseError::ShellAndAnd(s) => *s,
|
ParseError::ShellAndAnd(s) => *s,
|
||||||
ParseError::ShellOrOr(s) => *s,
|
ParseError::ShellOrOr(s) => *s,
|
||||||
|
|
|
@ -5,19 +5,21 @@ use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Error, Diagnostic, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Error, Diagnostic, Serialize, Deserialize)]
|
||||||
pub enum ParseWarning {
|
pub enum ParseWarning {
|
||||||
#[error("Deprecated: {0}")]
|
#[error("Deprecated: {old_command}")]
|
||||||
DeprecatedWarning(
|
#[diagnostic(help("for more info: {url}"))]
|
||||||
String,
|
DeprecatedWarning {
|
||||||
String,
|
old_command: String,
|
||||||
#[label = "`{0}` is deprecated and will be removed in 0.90. Please use `{1}` instead, more info: https://www.nushell.sh/book/custom_commands.html"]
|
new_suggestion: String,
|
||||||
Span,
|
#[label("`{old_command}` is deprecated and will be removed in 0.94. Please {new_suggestion} instead")]
|
||||||
),
|
span: Span,
|
||||||
|
url: String,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParseWarning {
|
impl ParseWarning {
|
||||||
pub fn span(&self) -> Span {
|
pub fn span(&self) -> Span {
|
||||||
match self {
|
match self {
|
||||||
ParseWarning::DeprecatedWarning(_, _, s) => *s,
|
ParseWarning::DeprecatedWarning { span, .. } => *span,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -750,17 +750,19 @@ pub enum ShellError {
|
||||||
span: Span,
|
span: Span,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// The cached plugin data (in `$nu.plugin-path`) for a plugin is invalid.
|
/// The cached plugin data for a plugin is invalid.
|
||||||
///
|
///
|
||||||
/// ## Resolution
|
/// ## Resolution
|
||||||
///
|
///
|
||||||
/// `register` the plugin again to update the data, or remove it.
|
/// `plugin add` the plugin again to update the data, or remove it with `plugin rm`.
|
||||||
#[error("The cached plugin data for `{plugin_name}` is invalid")]
|
#[error("The cached plugin data for `{plugin_name}` is invalid")]
|
||||||
#[diagnostic(code(nu::shell::plugin_cache_data_invalid))]
|
#[diagnostic(code(nu::shell::plugin_cache_data_invalid))]
|
||||||
PluginCacheDataInvalid {
|
PluginCacheDataInvalid {
|
||||||
plugin_name: String,
|
plugin_name: String,
|
||||||
#[help("try registering the plugin again with `{}`")]
|
#[label("plugin `{plugin_name}` loaded here")]
|
||||||
register_command: String,
|
span: Option<Span>,
|
||||||
|
#[help("the format in the plugin cache file is not compatible with this version of Nushell.\n\nTry adding the plugin again with `{}`")]
|
||||||
|
add_command: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// A plugin failed to load.
|
/// A plugin failed to load.
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use crate::{ParseError, Spanned};
|
use crate::{ParseError, ShellError, Spanned};
|
||||||
|
|
||||||
/// Error when an invalid plugin filename was encountered. This can be converted to [`ParseError`]
|
/// Error when an invalid plugin filename was encountered.
|
||||||
/// if a span is added.
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct InvalidPluginFilename;
|
pub struct InvalidPluginFilename(PathBuf);
|
||||||
|
|
||||||
impl std::fmt::Display for InvalidPluginFilename {
|
impl std::fmt::Display for InvalidPluginFilename {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
@ -23,6 +22,18 @@ impl From<Spanned<InvalidPluginFilename>> for ParseError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Spanned<InvalidPluginFilename>> for ShellError {
|
||||||
|
fn from(error: Spanned<InvalidPluginFilename>) -> ShellError {
|
||||||
|
ShellError::GenericError {
|
||||||
|
error: format!("Invalid plugin filename: {}", error.item.0.display()),
|
||||||
|
msg: "not a valid plugin filename".into(),
|
||||||
|
span: Some(error.span),
|
||||||
|
help: Some("valid Nushell plugin filenames must start with `nu_plugin_`".into()),
|
||||||
|
inner: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct PluginIdentity {
|
pub struct PluginIdentity {
|
||||||
/// The filename used to start the plugin
|
/// The filename used to start the plugin
|
||||||
|
@ -35,17 +46,25 @@ pub struct PluginIdentity {
|
||||||
|
|
||||||
impl PluginIdentity {
|
impl PluginIdentity {
|
||||||
/// Create a new plugin identity from a path to plugin executable and shell option.
|
/// Create a new plugin identity from a path to plugin executable and shell option.
|
||||||
|
///
|
||||||
|
/// The `filename` must be an absolute path. Canonicalize before trying to construct the
|
||||||
|
/// [`PluginIdentity`].
|
||||||
pub fn new(
|
pub fn new(
|
||||||
filename: impl Into<PathBuf>,
|
filename: impl Into<PathBuf>,
|
||||||
shell: Option<PathBuf>,
|
shell: Option<PathBuf>,
|
||||||
) -> Result<PluginIdentity, InvalidPluginFilename> {
|
) -> Result<PluginIdentity, InvalidPluginFilename> {
|
||||||
let filename = filename.into();
|
let filename: PathBuf = filename.into();
|
||||||
|
|
||||||
|
// Must pass absolute path.
|
||||||
|
if filename.is_relative() {
|
||||||
|
return Err(InvalidPluginFilename(filename));
|
||||||
|
}
|
||||||
|
|
||||||
let name = filename
|
let name = filename
|
||||||
.file_stem()
|
.file_stem()
|
||||||
.map(|stem| stem.to_string_lossy().into_owned())
|
.map(|stem| stem.to_string_lossy().into_owned())
|
||||||
.and_then(|stem| stem.strip_prefix("nu_plugin_").map(|s| s.to_owned()))
|
.and_then(|stem| stem.strip_prefix("nu_plugin_").map(|s| s.to_owned()))
|
||||||
.ok_or(InvalidPluginFilename)?;
|
.ok_or_else(|| InvalidPluginFilename(filename.clone()))?;
|
||||||
|
|
||||||
Ok(PluginIdentity {
|
Ok(PluginIdentity {
|
||||||
filename,
|
filename,
|
||||||
|
@ -89,30 +108,42 @@ impl PluginIdentity {
|
||||||
.expect("fake plugin identity path is invalid")
|
.expect("fake plugin identity path is invalid")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A command that could be used to register the plugin, for suggesting in errors.
|
/// A command that could be used to add the plugin, for suggesting in errors.
|
||||||
pub fn register_command(&self) -> String {
|
pub fn add_command(&self) -> String {
|
||||||
if let Some(shell) = self.shell() {
|
if let Some(shell) = self.shell() {
|
||||||
format!(
|
format!(
|
||||||
"register --shell '{}' '{}'",
|
"plugin add --shell '{}' '{}'",
|
||||||
shell.display(),
|
shell.display(),
|
||||||
self.filename().display(),
|
self.filename().display(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
format!("register '{}'", self.filename().display())
|
format!("plugin add '{}'", self.filename().display())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A command that could be used to reload the plugin, for suggesting in errors.
|
||||||
|
pub fn use_command(&self) -> String {
|
||||||
|
format!("plugin use '{}'", self.name())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parses_name_from_path() {
|
fn parses_name_from_path() {
|
||||||
assert_eq!("test", PluginIdentity::new_fake("test").name());
|
assert_eq!("test", PluginIdentity::new_fake("test").name());
|
||||||
assert_eq!("test_2", PluginIdentity::new_fake("test_2").name());
|
assert_eq!("test_2", PluginIdentity::new_fake("test_2").name());
|
||||||
|
let absolute_path = if cfg!(windows) {
|
||||||
|
r"C:\path\to\nu_plugin_foo.sh"
|
||||||
|
} else {
|
||||||
|
"/path/to/nu_plugin_foo.sh"
|
||||||
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"foo",
|
"foo",
|
||||||
PluginIdentity::new("nu_plugin_foo.sh", Some("sh".into()))
|
PluginIdentity::new(absolute_path, Some("sh".into()))
|
||||||
.expect("should be valid")
|
.expect("should be valid")
|
||||||
.name()
|
.name()
|
||||||
);
|
);
|
||||||
|
// Relative paths should be invalid
|
||||||
|
PluginIdentity::new("nu_plugin_foo.sh", Some("sh".into())).expect_err("should be invalid");
|
||||||
PluginIdentity::new("other", None).expect_err("should be invalid");
|
PluginIdentity::new("other", None).expect_err("should be invalid");
|
||||||
PluginIdentity::new("", None).expect_err("should be invalid");
|
PluginIdentity::new("", None).expect_err("should be invalid");
|
||||||
}
|
}
|
||||||
|
|
|
@ -235,7 +235,6 @@ macro_rules! nu_with_plugins {
|
||||||
|
|
||||||
use crate::{Outcome, NATIVE_PATH_ENV_VAR};
|
use crate::{Outcome, NATIVE_PATH_ENV_VAR};
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::fmt::Write;
|
|
||||||
use std::{
|
use std::{
|
||||||
path::Path,
|
path::Path,
|
||||||
process::{Command, Stdio},
|
process::{Command, Stdio},
|
||||||
|
@ -340,17 +339,17 @@ where
|
||||||
|
|
||||||
crate::commands::ensure_plugins_built();
|
crate::commands::ensure_plugins_built();
|
||||||
|
|
||||||
let registrations: String = plugins
|
let plugin_paths_quoted: Vec<String> = plugins
|
||||||
.iter()
|
.iter()
|
||||||
.fold(String::new(), |mut output, plugin_name| {
|
.map(|plugin_name| {
|
||||||
let plugin = with_exe(plugin_name);
|
let plugin = with_exe(plugin_name);
|
||||||
let plugin_path = nu_path::canonicalize_with(&plugin, &test_bins)
|
let plugin_path = nu_path::canonicalize_with(&plugin, &test_bins)
|
||||||
.unwrap_or_else(|_| panic!("failed to canonicalize plugin {} path", &plugin));
|
.unwrap_or_else(|_| panic!("failed to canonicalize plugin {} path", &plugin));
|
||||||
let plugin_path = plugin_path.to_string_lossy();
|
let plugin_path = plugin_path.to_string_lossy();
|
||||||
let _ = write!(output, "register {plugin_path};");
|
escape_quote_string(plugin_path.into_owned())
|
||||||
output
|
})
|
||||||
});
|
.collect();
|
||||||
let commands = format!("{registrations}{command}");
|
let plugins_arg = format!("[{}]", plugin_paths_quoted.join(","));
|
||||||
|
|
||||||
let target_cwd = crate::fs::in_directory(&cwd);
|
let target_cwd = crate::fs::in_directory(&cwd);
|
||||||
// In plugin testing, we need to use installed nushell to drive
|
// In plugin testing, we need to use installed nushell to drive
|
||||||
|
@ -362,13 +361,15 @@ where
|
||||||
let process = match setup_command(&executable_path, &target_cwd)
|
let process = match setup_command(&executable_path, &target_cwd)
|
||||||
.envs(envs)
|
.envs(envs)
|
||||||
.arg("--commands")
|
.arg("--commands")
|
||||||
.arg(commands)
|
.arg(command)
|
||||||
.arg("--config")
|
.arg("--config")
|
||||||
.arg(temp_config_file)
|
.arg(temp_config_file)
|
||||||
.arg("--env-config")
|
.arg("--env-config")
|
||||||
.arg(temp_env_config_file)
|
.arg(temp_env_config_file)
|
||||||
.arg("--plugin-config")
|
.arg("--plugin-config")
|
||||||
.arg(temp_plugin_file)
|
.arg(temp_plugin_file)
|
||||||
|
.arg("--plugins")
|
||||||
|
.arg(plugins_arg)
|
||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
.stderr(Stdio::piped())
|
.stderr(Stdio::piped())
|
||||||
.spawn()
|
.spawn()
|
||||||
|
|
|
@ -36,6 +36,8 @@ pub(crate) fn gather_commandline_args() -> (Vec<String>, String, Vec<String>) {
|
||||||
"--log-level" | "--log-target" | "--testbin" | "--threads" | "-t"
|
"--log-level" | "--log-target" | "--testbin" | "--threads" | "-t"
|
||||||
| "--include-path" | "--lsp" | "--ide-goto-def" | "--ide-hover" | "--ide-complete"
|
| "--include-path" | "--lsp" | "--ide-goto-def" | "--ide-hover" | "--ide-complete"
|
||||||
| "--ide-check" => args.next(),
|
| "--ide-check" => args.next(),
|
||||||
|
#[cfg(feature = "plugin")]
|
||||||
|
"--plugins" => args.next(),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -87,6 +89,8 @@ pub(crate) fn parse_commandline_args(
|
||||||
let testbin = call.get_flag_expr("testbin");
|
let testbin = call.get_flag_expr("testbin");
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
let plugin_file = call.get_flag_expr("plugin-config");
|
let plugin_file = call.get_flag_expr("plugin-config");
|
||||||
|
#[cfg(feature = "plugin")]
|
||||||
|
let plugins = call.get_flag_expr("plugins");
|
||||||
let no_config_file = call.get_named_arg("no-config-file");
|
let no_config_file = call.get_named_arg("no-config-file");
|
||||||
let no_history = call.get_named_arg("no-history");
|
let no_history = call.get_named_arg("no-history");
|
||||||
let no_std_lib = call.get_named_arg("no-std-lib");
|
let no_std_lib = call.get_named_arg("no-std-lib");
|
||||||
|
@ -131,17 +135,60 @@ pub(crate) fn parse_commandline_args(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extract_path(
|
||||||
|
expression: Option<&Expression>,
|
||||||
|
) -> Result<Option<Spanned<String>>, ShellError> {
|
||||||
|
if let Some(expr) = expression {
|
||||||
|
let tuple = expr.as_filepath();
|
||||||
|
if let Some((str, _)) = tuple {
|
||||||
|
Ok(Some(Spanned {
|
||||||
|
item: str,
|
||||||
|
span: expr.span,
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
Err(ShellError::TypeMismatch {
|
||||||
|
err_message: "path".into(),
|
||||||
|
span: expr.span,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let commands = extract_contents(commands)?;
|
let commands = extract_contents(commands)?;
|
||||||
let testbin = extract_contents(testbin)?;
|
let testbin = extract_contents(testbin)?;
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
let plugin_file = extract_contents(plugin_file)?;
|
let plugin_file = extract_path(plugin_file)?;
|
||||||
let config_file = extract_contents(config_file)?;
|
let config_file = extract_path(config_file)?;
|
||||||
let env_file = extract_contents(env_file)?;
|
let env_file = extract_path(env_file)?;
|
||||||
let log_level = extract_contents(log_level)?;
|
let log_level = extract_contents(log_level)?;
|
||||||
let log_target = extract_contents(log_target)?;
|
let log_target = extract_contents(log_target)?;
|
||||||
let execute = extract_contents(execute)?;
|
let execute = extract_contents(execute)?;
|
||||||
let include_path = extract_contents(include_path)?;
|
let include_path = extract_contents(include_path)?;
|
||||||
|
|
||||||
|
#[cfg(feature = "plugin")]
|
||||||
|
let plugins = plugins
|
||||||
|
.map(|expr| match &expr.expr {
|
||||||
|
Expr::List(list) => list
|
||||||
|
.iter()
|
||||||
|
.map(|item| {
|
||||||
|
item.expr()
|
||||||
|
.as_filepath()
|
||||||
|
.map(|(s, _)| s.into_spanned(item.expr().span))
|
||||||
|
.ok_or_else(|| ShellError::TypeMismatch {
|
||||||
|
err_message: "path".into(),
|
||||||
|
span: item.expr().span,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<Spanned<String>>, _>>(),
|
||||||
|
_ => Err(ShellError::TypeMismatch {
|
||||||
|
err_message: "list<path>".into(),
|
||||||
|
span: expr.span,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.transpose()?;
|
||||||
|
|
||||||
let help = call.has_flag(engine_state, &mut stack, "help")?;
|
let help = call.has_flag(engine_state, &mut stack, "help")?;
|
||||||
|
|
||||||
if help {
|
if help {
|
||||||
|
@ -175,6 +222,8 @@ pub(crate) fn parse_commandline_args(
|
||||||
testbin,
|
testbin,
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
plugin_file,
|
plugin_file,
|
||||||
|
#[cfg(feature = "plugin")]
|
||||||
|
plugins,
|
||||||
no_config_file,
|
no_config_file,
|
||||||
no_history,
|
no_history,
|
||||||
no_std_lib,
|
no_std_lib,
|
||||||
|
@ -217,6 +266,8 @@ pub(crate) struct NushellCliArgs {
|
||||||
pub(crate) testbin: Option<Spanned<String>>,
|
pub(crate) testbin: Option<Spanned<String>>,
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
pub(crate) plugin_file: Option<Spanned<String>>,
|
pub(crate) plugin_file: Option<Spanned<String>>,
|
||||||
|
#[cfg(feature = "plugin")]
|
||||||
|
pub(crate) plugins: Option<Vec<Spanned<String>>>,
|
||||||
pub(crate) no_config_file: Option<Spanned<String>>,
|
pub(crate) no_config_file: Option<Spanned<String>>,
|
||||||
pub(crate) no_history: Option<Spanned<String>>,
|
pub(crate) no_history: Option<Spanned<String>>,
|
||||||
pub(crate) no_std_lib: Option<Spanned<String>>,
|
pub(crate) no_std_lib: Option<Spanned<String>>,
|
||||||
|
@ -294,13 +345,13 @@ impl Command for Nu {
|
||||||
.switch("version", "print the version", Some('v'))
|
.switch("version", "print the version", Some('v'))
|
||||||
.named(
|
.named(
|
||||||
"config",
|
"config",
|
||||||
SyntaxShape::String,
|
SyntaxShape::Filepath,
|
||||||
"start with an alternate config file",
|
"start with an alternate config file",
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.named(
|
.named(
|
||||||
"env-config",
|
"env-config",
|
||||||
SyntaxShape::String,
|
SyntaxShape::Filepath,
|
||||||
"start with an alternate environment config file",
|
"start with an alternate environment config file",
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
@ -337,12 +388,19 @@ impl Command for Nu {
|
||||||
|
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
{
|
{
|
||||||
signature = signature.named(
|
signature = signature
|
||||||
"plugin-config",
|
.named(
|
||||||
SyntaxShape::String,
|
"plugin-config",
|
||||||
"start with an alternate plugin cache file",
|
SyntaxShape::Filepath,
|
||||||
None,
|
"start with an alternate plugin cache file",
|
||||||
);
|
None,
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"plugins",
|
||||||
|
SyntaxShape::List(Box::new(SyntaxShape::Filepath)),
|
||||||
|
"list of plugin executable files to load, separately from the cache file",
|
||||||
|
None,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
signature = signature
|
signature = signature
|
||||||
|
|
40
src/main.rs
40
src/main.rs
|
@ -389,6 +389,46 @@ fn main() -> Result<()> {
|
||||||
use_color,
|
use_color,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#[cfg(feature = "plugin")]
|
||||||
|
if let Some(plugins) = &parsed_nu_cli_args.plugins {
|
||||||
|
use nu_plugin::{GetPlugin, PluginDeclaration};
|
||||||
|
use nu_protocol::{engine::StateWorkingSet, ErrSpan, PluginIdentity};
|
||||||
|
|
||||||
|
// Load any plugins specified with --plugins
|
||||||
|
start_time = std::time::Instant::now();
|
||||||
|
|
||||||
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||||
|
for plugin_filename in plugins {
|
||||||
|
// Make sure the plugin filenames are canonicalized
|
||||||
|
let filename = canonicalize_with(&plugin_filename.item, &init_cwd)
|
||||||
|
.err_span(plugin_filename.span)
|
||||||
|
.map_err(ShellError::from)?;
|
||||||
|
|
||||||
|
let identity = PluginIdentity::new(&filename, None)
|
||||||
|
.err_span(plugin_filename.span)
|
||||||
|
.map_err(ShellError::from)?;
|
||||||
|
|
||||||
|
// Create the plugin and add it to the working set
|
||||||
|
let plugin = nu_plugin::add_plugin_to_working_set(&mut working_set, &identity)?;
|
||||||
|
|
||||||
|
// Spawn the plugin to get its signatures, and then add the commands to the working set
|
||||||
|
for signature in plugin.clone().get_plugin(None)?.get_signature()? {
|
||||||
|
let decl = PluginDeclaration::new(plugin.clone(), signature);
|
||||||
|
working_set.add_decl(Box::new(decl));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
engine_state.merge_delta(working_set.render())?;
|
||||||
|
|
||||||
|
perf(
|
||||||
|
"load plugins specified in --plugins",
|
||||||
|
start_time,
|
||||||
|
file!(),
|
||||||
|
line!(),
|
||||||
|
column!(),
|
||||||
|
use_color,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
start_time = std::time::Instant::now();
|
start_time = std::time::Instant::now();
|
||||||
if parsed_nu_cli_args.lsp {
|
if parsed_nu_cli_args.lsp {
|
||||||
perf(
|
perf(
|
||||||
|
|
|
@ -39,6 +39,7 @@ pub fn run_test_with_env(input: &str, expected: &str, env: &HashMap<&str, &str>)
|
||||||
let name = file.path();
|
let name = file.path();
|
||||||
|
|
||||||
let mut cmd = Command::cargo_bin("nu")?;
|
let mut cmd = Command::cargo_bin("nu")?;
|
||||||
|
cmd.arg("--no-config-file");
|
||||||
cmd.arg(name).envs(env);
|
cmd.arg(name).envs(env);
|
||||||
|
|
||||||
writeln!(file, "{input}")?;
|
writeln!(file, "{input}")?;
|
||||||
|
@ -53,6 +54,7 @@ pub fn run_test(input: &str, expected: &str) -> TestResult {
|
||||||
|
|
||||||
let mut cmd = Command::cargo_bin("nu")?;
|
let mut cmd = Command::cargo_bin("nu")?;
|
||||||
cmd.arg("--no-std-lib");
|
cmd.arg("--no-std-lib");
|
||||||
|
cmd.arg("--no-config-file");
|
||||||
cmd.arg(name);
|
cmd.arg(name);
|
||||||
cmd.env(
|
cmd.env(
|
||||||
"PWD",
|
"PWD",
|
||||||
|
@ -70,6 +72,7 @@ pub fn run_test_std(input: &str, expected: &str) -> TestResult {
|
||||||
let name = file.path();
|
let name = file.path();
|
||||||
|
|
||||||
let mut cmd = Command::cargo_bin("nu")?;
|
let mut cmd = Command::cargo_bin("nu")?;
|
||||||
|
cmd.arg("--no-config-file");
|
||||||
cmd.arg(name);
|
cmd.arg(name);
|
||||||
cmd.env(
|
cmd.env(
|
||||||
"PWD",
|
"PWD",
|
||||||
|
@ -105,6 +108,7 @@ pub fn run_test_contains(input: &str, expected: &str) -> TestResult {
|
||||||
|
|
||||||
let mut cmd = Command::cargo_bin("nu")?;
|
let mut cmd = Command::cargo_bin("nu")?;
|
||||||
cmd.arg("--no-std-lib");
|
cmd.arg("--no-std-lib");
|
||||||
|
cmd.arg("--no-config-file");
|
||||||
cmd.arg(name);
|
cmd.arg(name);
|
||||||
|
|
||||||
writeln!(file, "{input}")?;
|
writeln!(file, "{input}")?;
|
||||||
|
@ -132,6 +136,7 @@ pub fn test_ide_contains(input: &str, ide_commands: &[&str], expected: &str) ->
|
||||||
|
|
||||||
let mut cmd = Command::cargo_bin("nu")?;
|
let mut cmd = Command::cargo_bin("nu")?;
|
||||||
cmd.arg("--no-std-lib");
|
cmd.arg("--no-std-lib");
|
||||||
|
cmd.arg("--no-config-file");
|
||||||
for ide_command in ide_commands {
|
for ide_command in ide_commands {
|
||||||
cmd.arg(ide_command);
|
cmd.arg(ide_command);
|
||||||
}
|
}
|
||||||
|
@ -162,6 +167,7 @@ pub fn fail_test(input: &str, expected: &str) -> TestResult {
|
||||||
|
|
||||||
let mut cmd = Command::cargo_bin("nu")?;
|
let mut cmd = Command::cargo_bin("nu")?;
|
||||||
cmd.arg("--no-std-lib");
|
cmd.arg("--no-std-lib");
|
||||||
|
cmd.arg("--no-config-file");
|
||||||
cmd.arg(name);
|
cmd.arg(name);
|
||||||
cmd.env(
|
cmd.env(
|
||||||
"PWD",
|
"PWD",
|
||||||
|
|
|
@ -594,6 +594,42 @@ register $file
|
||||||
fail_test(input, "expected string, found int")
|
fail_test(input, "expected string, found int")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn plugin_use_with_string_literal() -> TestResult {
|
||||||
|
fail_test(
|
||||||
|
r#"plugin use 'nu-plugin-math'"#,
|
||||||
|
"Plugin cache file not set",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn plugin_use_with_string_constant() -> TestResult {
|
||||||
|
let input = "\
|
||||||
|
const file = 'nu-plugin-math'
|
||||||
|
plugin use $file
|
||||||
|
";
|
||||||
|
// should not fail with `not a constant`
|
||||||
|
fail_test(input, "Plugin cache file not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn plugin_use_with_string_variable() -> TestResult {
|
||||||
|
let input = "\
|
||||||
|
let file = 'nu-plugin-math'
|
||||||
|
plugin use $file
|
||||||
|
";
|
||||||
|
fail_test(input, "Value is not a parse-time constant")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn plugin_use_with_non_string_constant() -> TestResult {
|
||||||
|
let input = "\
|
||||||
|
const file = 6
|
||||||
|
plugin use $file
|
||||||
|
";
|
||||||
|
fail_test(input, "expected string, found int")
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn extern_errors_with_no_space_between_params_and_name_1() -> TestResult {
|
fn extern_errors_with_no_space_between_params_and_name_1() -> TestResult {
|
||||||
fail_test("extern cmd[]", "expected space")
|
fail_test("extern cmd[]", "expected space")
|
||||||
|
|
2
tests/fixtures/formats/code.nu
vendored
2
tests/fixtures/formats/code.nu
vendored
|
@ -1 +1 @@
|
||||||
register
|
plugin use
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
use std::{
|
use std::{fs::File, path::PathBuf};
|
||||||
fs::File,
|
|
||||||
path::PathBuf,
|
|
||||||
process::{Command, Stdio},
|
|
||||||
};
|
|
||||||
|
|
||||||
use nu_protocol::{PluginCacheFile, PluginCacheItem, PluginCacheItemData};
|
use nu_protocol::{PluginCacheFile, PluginCacheItem, PluginCacheItemData};
|
||||||
use nu_test_support::{fs::Stub, nu, nu_with_plugins, playground::Playground};
|
use nu_test_support::{fs::Stub, nu, nu_with_plugins, playground::Playground};
|
||||||
|
@ -69,16 +65,70 @@ fn plugin_add_to_custom_path() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn plugin_rm_then_restart_nu() {
|
fn plugin_rm_then_restart_nu() {
|
||||||
let result = nu_with_plugins!(
|
let example_plugin_path = example_plugin_path();
|
||||||
cwd: ".",
|
Playground::setup("plugin rm from custom path", |dirs, playground| {
|
||||||
plugin: ("nu_plugin_example"),
|
playground.with_files(vec![
|
||||||
r#"
|
Stub::FileWithContent("config.nu", ""),
|
||||||
plugin rm example
|
Stub::FileWithContent("env.nu", ""),
|
||||||
^$nu.current-exe --config $nu.config-path --env-config $nu.env-path --plugin-config $nu.plugin-path --commands 'plugin list | get name | to json --raw'
|
]);
|
||||||
"#
|
|
||||||
);
|
let file = File::create(dirs.test().join("test-plugin-file.msgpackz"))
|
||||||
assert!(result.status.success());
|
.expect("failed to create file");
|
||||||
assert_eq!(r#"[]"#, result.out);
|
let mut contents = PluginCacheFile::new();
|
||||||
|
|
||||||
|
contents.upsert_plugin(PluginCacheItem {
|
||||||
|
name: "example".into(),
|
||||||
|
filename: example_plugin_path,
|
||||||
|
shell: None,
|
||||||
|
data: PluginCacheItemData::Valid { commands: vec![] },
|
||||||
|
});
|
||||||
|
|
||||||
|
contents.upsert_plugin(PluginCacheItem {
|
||||||
|
name: "foo".into(),
|
||||||
|
// this doesn't exist, but it should be ok
|
||||||
|
filename: dirs.test().join("nu_plugin_foo"),
|
||||||
|
shell: None,
|
||||||
|
data: PluginCacheItemData::Valid { commands: vec![] },
|
||||||
|
});
|
||||||
|
|
||||||
|
contents
|
||||||
|
.write_to(file, None)
|
||||||
|
.expect("failed to write plugin file");
|
||||||
|
|
||||||
|
assert_cmd::Command::new(nu_test_support::fs::executable_path())
|
||||||
|
.current_dir(dirs.test())
|
||||||
|
.args([
|
||||||
|
"--no-std-lib",
|
||||||
|
"--config",
|
||||||
|
"config.nu",
|
||||||
|
"--env-config",
|
||||||
|
"env.nu",
|
||||||
|
"--plugin-config",
|
||||||
|
"test-plugin-file.msgpackz",
|
||||||
|
"--commands",
|
||||||
|
"plugin rm example",
|
||||||
|
])
|
||||||
|
.assert()
|
||||||
|
.success()
|
||||||
|
.stderr("");
|
||||||
|
|
||||||
|
assert_cmd::Command::new(nu_test_support::fs::executable_path())
|
||||||
|
.current_dir(dirs.test())
|
||||||
|
.args([
|
||||||
|
"--no-std-lib",
|
||||||
|
"--config",
|
||||||
|
"config.nu",
|
||||||
|
"--env-config",
|
||||||
|
"env.nu",
|
||||||
|
"--plugin-config",
|
||||||
|
"test-plugin-file.msgpackz",
|
||||||
|
"--commands",
|
||||||
|
"plugin list | get name | to json --raw",
|
||||||
|
])
|
||||||
|
.assert()
|
||||||
|
.success()
|
||||||
|
.stdout("[\"foo\"]\n");
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -176,7 +226,7 @@ fn warning_on_invalid_plugin_item() {
|
||||||
.write_to(file, None)
|
.write_to(file, None)
|
||||||
.expect("failed to write plugin file");
|
.expect("failed to write plugin file");
|
||||||
|
|
||||||
let result = Command::new(nu_test_support::fs::executable_path())
|
let result = assert_cmd::Command::new(nu_test_support::fs::executable_path())
|
||||||
.current_dir(dirs.test())
|
.current_dir(dirs.test())
|
||||||
.args([
|
.args([
|
||||||
"--no-std-lib",
|
"--no-std-lib",
|
||||||
|
@ -189,9 +239,6 @@ fn warning_on_invalid_plugin_item() {
|
||||||
"--commands",
|
"--commands",
|
||||||
"plugin list | get name | to json --raw",
|
"plugin list | get name | to json --raw",
|
||||||
])
|
])
|
||||||
.stdin(Stdio::null())
|
|
||||||
.stdout(Stdio::piped())
|
|
||||||
.stderr(Stdio::piped())
|
|
||||||
.output()
|
.output()
|
||||||
.expect("failed to run nu");
|
.expect("failed to run nu");
|
||||||
|
|
||||||
|
@ -209,3 +256,78 @@ fn warning_on_invalid_plugin_item() {
|
||||||
assert!(err.contains("badtest"));
|
assert!(err.contains("badtest"));
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn plugin_use_error_not_found() {
|
||||||
|
Playground::setup("plugin use error not found", |dirs, playground| {
|
||||||
|
playground.with_files(vec![
|
||||||
|
Stub::FileWithContent("config.nu", ""),
|
||||||
|
Stub::FileWithContent("env.nu", ""),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Make an empty msgpackz
|
||||||
|
let file = File::create(dirs.test().join("plugin.msgpackz"))
|
||||||
|
.expect("failed to open plugin.msgpackz");
|
||||||
|
PluginCacheFile::default()
|
||||||
|
.write_to(file, None)
|
||||||
|
.expect("failed to write empty cache file");
|
||||||
|
|
||||||
|
let output = assert_cmd::Command::new(nu_test_support::fs::executable_path())
|
||||||
|
.current_dir(dirs.test())
|
||||||
|
.args(["--config", "config.nu"])
|
||||||
|
.args(["--env-config", "env.nu"])
|
||||||
|
.args(["--plugin-config", "plugin.msgpackz"])
|
||||||
|
.args(["--commands", "plugin use custom_values"])
|
||||||
|
.output()
|
||||||
|
.expect("failed to run nu");
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
assert!(stderr.contains("Plugin not found"));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn plugin_add_and_then_use() {
|
||||||
|
let example_plugin_path = example_plugin_path();
|
||||||
|
let result = nu_with_plugins!(
|
||||||
|
cwd: ".",
|
||||||
|
plugins: [],
|
||||||
|
&format!(r#"
|
||||||
|
plugin add '{}'
|
||||||
|
(
|
||||||
|
^$nu.current-exe
|
||||||
|
--config $nu.config-path
|
||||||
|
--env-config $nu.env-path
|
||||||
|
--plugin-config $nu.plugin-path
|
||||||
|
--commands 'plugin use example; plugin list | get name | to json --raw'
|
||||||
|
)
|
||||||
|
"#, example_plugin_path.display())
|
||||||
|
);
|
||||||
|
assert!(result.status.success());
|
||||||
|
assert_eq!(r#"["example"]"#, result.out);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn plugin_add_then_use_with_custom_path() {
|
||||||
|
let example_plugin_path = example_plugin_path();
|
||||||
|
Playground::setup("plugin add to custom path", |dirs, _playground| {
|
||||||
|
let result_add = nu!(
|
||||||
|
cwd: dirs.test(),
|
||||||
|
&format!("
|
||||||
|
plugin add --plugin-config test-plugin-file.msgpackz '{}'
|
||||||
|
", example_plugin_path.display())
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(result_add.status.success());
|
||||||
|
|
||||||
|
let result_use = nu!(
|
||||||
|
cwd: dirs.test(),
|
||||||
|
r#"
|
||||||
|
plugin use --plugin-config test-plugin-file.msgpackz example
|
||||||
|
plugin list | get name | to json --raw
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(result_use.status.success());
|
||||||
|
assert_eq!(r#"["example"]"#, result_use.out);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -1,26 +1,42 @@
|
||||||
use nu_test_support::nu;
|
use assert_cmd::Command;
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn register() {
|
|
||||||
let out = nu!("register crates/nu_plugin_nu_example/nu_plugin_nu_example.nu");
|
|
||||||
assert!(out.status.success());
|
|
||||||
assert!(out.out.trim().is_empty());
|
|
||||||
assert!(out.err.trim().is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn call() {
|
fn call() {
|
||||||
let out = nu!(r#"
|
// Add the `nu` binaries to the path env
|
||||||
register crates/nu_plugin_nu_example/nu_plugin_nu_example.nu
|
let path_env = std::env::join_paths(
|
||||||
nu_plugin_nu_example 4242 teststring
|
std::iter::once(nu_test_support::fs::binaries()).chain(
|
||||||
"#);
|
std::env::var_os(nu_test_support::NATIVE_PATH_ENV_VAR)
|
||||||
assert!(out.status.success());
|
.as_deref()
|
||||||
|
.map(std::env::split_paths)
|
||||||
|
.into_iter()
|
||||||
|
.flatten(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.expect("failed to make path var");
|
||||||
|
|
||||||
assert!(out.err.contains("name: nu_plugin_nu_example"));
|
let assert = Command::new(nu_test_support::fs::executable_path())
|
||||||
assert!(out.err.contains("4242"));
|
.env(nu_test_support::NATIVE_PATH_ENV_VAR, path_env)
|
||||||
assert!(out.err.contains("teststring"));
|
.args([
|
||||||
|
"--no-config-file",
|
||||||
|
"--no-std-lib",
|
||||||
|
"--plugins",
|
||||||
|
&format!(
|
||||||
|
"[crates{0}nu_plugin_nu_example{0}nu_plugin_nu_example.nu]",
|
||||||
|
std::path::MAIN_SEPARATOR
|
||||||
|
),
|
||||||
|
"--commands",
|
||||||
|
"nu_plugin_nu_example 4242 teststring",
|
||||||
|
])
|
||||||
|
.assert()
|
||||||
|
.success();
|
||||||
|
|
||||||
assert!(out.out.contains("one"));
|
let output = assert.get_output();
|
||||||
assert!(out.out.contains("two"));
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
assert!(out.out.contains("three"));
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
assert!(stdout.contains("one"));
|
||||||
|
assert!(stdout.contains("two"));
|
||||||
|
assert!(stdout.contains("three"));
|
||||||
|
assert!(stderr.contains("name: nu_plugin_nu_example"));
|
||||||
|
assert!(stderr.contains("4242"));
|
||||||
|
assert!(stderr.contains("teststring"));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue