mirror of
https://github.com/nushell/nushell.git
synced 2024-11-25 01:43:47 +01:00
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:
parent
e05e6b42fe
commit
0178b53289
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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![]))
|
||||
|
3
crates/nu-cli/src/commands/nu/mod.rs
Normal file
3
crates/nu-cli/src/commands/nu/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
mod plugin;
|
||||
|
||||
pub use plugin::SubCommand as NuPlugin;
|
124
crates/nu-cli/src/commands/nu/plugin.rs
Normal file
124
crates/nu-cli/src/commands/nu/plugin.rs
Normal 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(®istry).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, ®istry))
|
||||
.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 {})
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -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]*"));
|
||||
|
@ -22,8 +22,10 @@ pub enum CommandAction {
|
||||
EnterValueShell(Value),
|
||||
/// Enter the help shell, which allows exploring the help system
|
||||
EnterHelpShell(Value),
|
||||
/// Enter the help shell, which allows exploring the help system
|
||||
/// Add an alias command
|
||||
AddAlias(String, Vec<(String, SyntaxShape)>, Block),
|
||||
/// Add plugins from path given
|
||||
AddPlugins(String),
|
||||
/// Go to the previous shell in the shell ring buffer
|
||||
PreviousShell,
|
||||
/// Go to the next shell in the shell ring buffer
|
||||
@ -46,6 +48,7 @@ impl PrettyDebug for CommandAction {
|
||||
CommandAction::EnterValueShell(v) => b::typed("enter value shell", v.pretty()),
|
||||
CommandAction::EnterHelpShell(v) => b::typed("enter help shell", v.pretty()),
|
||||
CommandAction::AddAlias(..) => b::description("add alias"),
|
||||
CommandAction::AddPlugins(..) => b::description("add plugins"),
|
||||
CommandAction::PreviousShell => b::description("previous shell"),
|
||||
CommandAction::NextShell => b::description("next shell"),
|
||||
CommandAction::LeaveShell => b::description("leave shell"),
|
||||
|
@ -14,6 +14,83 @@ macro_rules! nu {
|
||||
nu!($cwd, $path)
|
||||
}};
|
||||
|
||||
($cwd:expr, $path:expr) => {{
|
||||
pub use std::error::Error;
|
||||
pub use std::io::prelude::*;
|
||||
pub use std::process::{Command, Stdio};
|
||||
|
||||
let commands = &*format!(
|
||||
"
|
||||
cd \"{}\"
|
||||
{}
|
||||
exit",
|
||||
$crate::fs::in_directory($cwd),
|
||||
$crate::fs::DisplayPath::display_path(&$path)
|
||||
);
|
||||
|
||||
let test_bins = $crate::fs::binaries();
|
||||
let test_bins = dunce::canonicalize(&test_bins).unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"Couldn't canonicalize dummy binaries path {}: {:?}",
|
||||
test_bins.display(),
|
||||
e
|
||||
)
|
||||
});
|
||||
|
||||
let mut paths = $crate::shell_os_paths();
|
||||
paths.insert(0, test_bins);
|
||||
|
||||
let paths_joined = match std::env::join_paths(paths.iter()) {
|
||||
Ok(all) => all,
|
||||
Err(_) => panic!("Couldn't join paths for PATH var."),
|
||||
};
|
||||
|
||||
let mut process = match Command::new($crate::fs::executable_path())
|
||||
.env("PATH", paths_joined)
|
||||
.arg("--skip-plugins")
|
||||
.stdout(Stdio::piped())
|
||||
.stdin(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
{
|
||||
Ok(child) => child,
|
||||
Err(why) => panic!("Can't run test {}", why.to_string()),
|
||||
};
|
||||
|
||||
let stdin = process.stdin.as_mut().expect("couldn't open stdin");
|
||||
stdin
|
||||
.write_all(commands.as_bytes())
|
||||
.expect("couldn't write to stdin");
|
||||
|
||||
let output = process
|
||||
.wait_with_output()
|
||||
.expect("couldn't read from stdout/stderr");
|
||||
|
||||
let out = $crate::macros::read_std(&output.stdout);
|
||||
let err = String::from_utf8_lossy(&output.stderr);
|
||||
|
||||
println!("=== stderr\n{}", err);
|
||||
|
||||
$crate::macros::Outcome::new(out,err.into_owned())
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! nu_with_plugins {
|
||||
(cwd: $cwd:expr, $path:expr, $($part:expr),*) => {{
|
||||
use $crate::fs::DisplayPath;
|
||||
|
||||
let path = format!($path, $(
|
||||
$part.display_path()
|
||||
),*);
|
||||
|
||||
nu_with_plugins!($cwd, &path)
|
||||
}};
|
||||
|
||||
(cwd: $cwd:expr, $path:expr) => {{
|
||||
nu_with_plugins!($cwd, $path)
|
||||
}};
|
||||
|
||||
($cwd:expr, $path:expr) => {{
|
||||
pub use std::error::Error;
|
||||
pub use std::io::prelude::*;
|
||||
|
14
src/main.rs
14
src/main.rs
@ -17,6 +17,13 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
.possible_values(&["error", "warn", "info", "debug", "trace"])
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("skip-plugins")
|
||||
.hidden(true)
|
||||
.long("skip-plugins")
|
||||
.multiple(false)
|
||||
.takes_value(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("testbin")
|
||||
.hidden(true)
|
||||
@ -154,7 +161,12 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
|
||||
None => {
|
||||
let mut syncer = EnvironmentSyncer::new();
|
||||
let context = create_default_context(&mut syncer, true)?;
|
||||
let mut context = create_default_context(&mut syncer, true)?;
|
||||
|
||||
if !matches.is_present("skip-plugins") {
|
||||
let _ = nu_cli::register_plugins(&mut context);
|
||||
}
|
||||
|
||||
futures::executor::block_on(nu_cli::cli(syncer, context))?;
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
use nu_test_support::fs::Stub::FileWithContent;
|
||||
use nu_test_support::nu;
|
||||
use nu_test_support::nu_with_plugins;
|
||||
use nu_test_support::playground::Playground;
|
||||
|
||||
#[test]
|
||||
fn can_only_apply_one() {
|
||||
let actual = nu!(
|
||||
let actual = nu_with_plugins!(
|
||||
cwd: "tests/fixtures/formats",
|
||||
"open cargo_sample.toml | first 1 | inc package.version --major --minor"
|
||||
);
|
||||
@ -25,7 +25,7 @@ fn by_one_with_field_passed() {
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let actual = nu!(
|
||||
let actual = nu_with_plugins!(
|
||||
cwd: dirs.test(),
|
||||
"open sample.toml | inc package.edition | get package.edition | echo $it"
|
||||
);
|
||||
@ -45,7 +45,7 @@ fn by_one_with_no_field_passed() {
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let actual = nu!(
|
||||
let actual = nu_with_plugins!(
|
||||
cwd: dirs.test(),
|
||||
"open sample.toml | get package.contributors | inc | echo $it"
|
||||
);
|
||||
@ -65,7 +65,7 @@ fn semversion_major_inc() {
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let actual = nu!(
|
||||
let actual = nu_with_plugins!(
|
||||
cwd: dirs.test(),
|
||||
"open sample.toml | inc package.version -M | get package.version | echo $it"
|
||||
);
|
||||
@ -85,7 +85,7 @@ fn semversion_minor_inc() {
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let actual = nu!(
|
||||
let actual = nu_with_plugins!(
|
||||
cwd: dirs.test(),
|
||||
"open sample.toml | inc package.version --minor | get package.version | echo $it"
|
||||
);
|
||||
@ -105,7 +105,7 @@ fn semversion_patch_inc() {
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let actual = nu!(
|
||||
let actual = nu_with_plugins!(
|
||||
cwd: dirs.test(),
|
||||
"open sample.toml | inc package.version --patch | get package.version | echo $it"
|
||||
);
|
||||
@ -125,7 +125,7 @@ fn semversion_without_passing_field() {
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let actual = nu!(
|
||||
let actual = nu_with_plugins!(
|
||||
cwd: dirs.test(),
|
||||
"open sample.toml | get package.version | inc --patch | echo $it"
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user