Core nu plugin load capability. (#2544)

We introduce the `plugin` nu sub command (`nu plugin`) with basic plugin
loading support. We can choose to load plugins from a directory. Originally
introduced to make integration tests faster (by not loading any plugins on startup at all)
but `nu plugin --load some_path ; test_pipeline_that_uses_plugins_just_loaded` does not see it.

Therefore, a `nu_with_plugins!` macro for tests was introduced on top of nu`s `--skip-plugins`
switch executable which is set to true when running the integration tests that use the `nu!` macro now..
This commit is contained in:
Andrés N. Robalino
2020-09-14 09:07:02 -05:00
committed by GitHub
parent e05e6b42fe
commit 0178b53289
11 changed files with 260 additions and 18 deletions

View File

@ -20,8 +20,8 @@ use std::iter::Iterator;
use std::path::{Path, PathBuf};
use std::sync::atomic::Ordering;
fn register_plugins(context: &mut Context) -> Result<(), ShellError> {
if let Ok(plugins) = crate::plugin::scan() {
pub fn register_plugins(context: &mut Context) -> Result<(), ShellError> {
if let Ok(plugins) = crate::plugin::scan(search_paths()) {
context.add_commands(
plugins
.into_iter()
@ -78,6 +78,7 @@ pub fn create_default_context(
use crate::commands::*;
context.add_commands(vec![
whole_stream_command(NuPlugin),
// System/file operations
whole_stream_command(Exec),
whole_stream_command(Pwd),
@ -568,8 +569,6 @@ pub async fn cli(
let configuration = nu_data::config::NuConfig::new();
let history_path = crate::commands::history::history_path(&configuration);
let _ = register_plugins(&mut context);
let (mut rl, config) = create_rustyline_configuration();
// we are ok if history does not exist

View File

@ -76,6 +76,7 @@ pub(crate) mod mkdir;
pub(crate) mod move_;
pub(crate) mod next;
pub(crate) mod nth;
pub(crate) mod nu;
pub(crate) mod open;
pub(crate) mod parse;
pub(crate) mod path;
@ -160,6 +161,7 @@ pub(crate) use each::EachWindow;
pub(crate) use echo::Echo;
pub(crate) use if_::If;
pub(crate) use is_empty::IsEmpty;
pub(crate) use nu::NuPlugin;
pub(crate) use update::Update;
pub(crate) mod kill;
pub(crate) use kill::Kill;

View File

@ -200,6 +200,28 @@ pub(crate) async fn run_internal_command(
)]);
InputStream::from_stream(futures::stream::iter(vec![]))
}
CommandAction::AddPlugins(path) => {
match crate::plugin::scan(vec![std::path::PathBuf::from(path)]) {
Ok(plugins) => {
context.add_commands(
plugins
.into_iter()
.filter(|p| {
!context.is_command_registered(p.name())
})
.collect(),
);
InputStream::from_stream(futures::stream::iter(vec![]))
}
Err(reason) => {
context.error(reason.clone());
InputStream::one(
UntaggedValue::Error(reason).into_untagged_value(),
)
}
}
}
CommandAction::PreviousShell => {
context.shell_manager.prev();
InputStream::from_stream(futures::stream::iter(vec![]))

View File

@ -0,0 +1,3 @@
mod plugin;
pub use plugin::SubCommand as NuPlugin;

View File

@ -0,0 +1,124 @@
use std::path::PathBuf;
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::path::canonicalize;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{CommandAction, ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
use nu_source::Tagged;
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
pub struct SubCommand;
#[derive(Deserialize)]
pub struct Arguments {
#[serde(rename = "load")]
pub load_path: Option<Tagged<PathBuf>>,
}
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"nu plugin"
}
fn signature(&self) -> Signature {
Signature::build("nu plugin").named(
"load",
SyntaxShape::Path,
"a path to load the plugins from",
Some('l'),
)
}
fn usage(&self) -> &str {
"Nu Plugin"
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Load all plugins in the current directory",
example: "nu plugin --load .",
result: Some(vec![]),
}]
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let shell_manager = args.shell_manager.clone();
let (Arguments { load_path }, _) = args.process(&registry).await?;
if let Some(Tagged {
item: load_path,
tag,
}) = load_path
{
let path = canonicalize(shell_manager.path(), load_path).map_err(|_| {
ShellError::labeled_error(
"Cannot load plugins from directory",
"directory not found",
&tag,
)
})?;
if !path.is_dir() {
return Err(ShellError::labeled_error(
"Cannot load plugins from directory",
"is not a directory",
&tag,
));
}
#[cfg(unix)]
{
let has_exec = path
.metadata()
.map(|m| umask::Mode::from(m.permissions().mode()).has(umask::USER_READ))
.map_err(|e| {
ShellError::labeled_error(
"Cannot load plugins from directory",
format!("cannot stat ({})", e),
&tag,
)
})?;
if !has_exec {
return Err(ShellError::labeled_error(
"Cannot load plugins from directory",
"permission denied",
&tag,
));
}
}
return Ok(vec![ReturnSuccess::action(CommandAction::AddPlugins(
path.to_string_lossy().to_string(),
))]
.into());
}
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(crate::commands::help::get_help(&SubCommand, &registry))
.into_value(Tag::unknown()),
)))
}
}
#[cfg(test)]
mod tests {
use super::SubCommand;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
}

View File

@ -35,8 +35,8 @@ pub mod utils;
mod examples;
pub use crate::cli::{
cli, create_default_context, parse_and_eval, process_line, run_pipeline_standalone,
run_vec_of_pipelines, LineResult,
cli, create_default_context, parse_and_eval, process_line, register_plugins,
run_pipeline_standalone, run_vec_of_pipelines, LineResult,
};
pub use crate::commands::command::{
whole_stream_command, CommandArgs, EvaluatedWholeStreamCommandArgs, Example, WholeStreamCommand,

View File

@ -93,7 +93,7 @@ pub fn build_plugin_command(
result
}
pub fn scan() -> Result<Vec<crate::commands::Command>, ShellError> {
pub fn scan(paths: Vec<std::path::PathBuf>) -> Result<Vec<crate::commands::Command>, ShellError> {
let mut plugins = vec![];
let opts = glob::MatchOptions {
@ -102,7 +102,7 @@ pub fn scan() -> Result<Vec<crate::commands::Command>, ShellError> {
require_literal_leading_dot: false,
};
for path in crate::cli::search_paths() {
for path in paths {
let mut pattern = path.to_path_buf();
pattern.push(std::path::Path::new("nu_plugin_[a-z0-9][a-z0-9]*"));