mirror of
https://github.com/nushell/nushell
synced 2024-12-28 14:03:09 +00:00
Slim down cli plugin logic.
This commit is contained in:
parent
303a9defd3
commit
4724b3c570
10 changed files with 308 additions and 241 deletions
|
@ -1,8 +1,5 @@
|
||||||
use crate::commands::classified::block::run_block;
|
use crate::commands::classified::block::run_block;
|
||||||
use crate::commands::classified::maybe_text_codec::{MaybeTextCodec, StringOrBinary};
|
use crate::commands::classified::maybe_text_codec::{MaybeTextCodec, StringOrBinary};
|
||||||
use crate::commands::plugin::JsonRpc;
|
|
||||||
use crate::commands::plugin::{PluginCommand, PluginSink};
|
|
||||||
use crate::commands::whole_stream_command;
|
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::git::current_branch;
|
use crate::git::current_branch;
|
||||||
use crate::path::canonicalize;
|
use crate::path::canonicalize;
|
||||||
|
@ -12,117 +9,31 @@ use crate::EnvironmentSyncer;
|
||||||
use futures_codec::FramedRead;
|
use futures_codec::FramedRead;
|
||||||
use nu_errors::{ProximateShellError, ShellDiagnostic, ShellError};
|
use nu_errors::{ProximateShellError, ShellDiagnostic, ShellError};
|
||||||
use nu_protocol::hir::{ClassifiedCommand, Expression, InternalCommand, Literal, NamedArguments};
|
use nu_protocol::hir::{ClassifiedCommand, Expression, InternalCommand, Literal, NamedArguments};
|
||||||
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
|
use nu_protocol::{Primitive, ReturnSuccess, UntaggedValue, Value};
|
||||||
#[allow(unused)]
|
|
||||||
use nu_source::Tagged;
|
|
||||||
|
|
||||||
use log::{debug, trace};
|
use log::{debug, trace};
|
||||||
use rustyline::config::{ColorMode, CompletionType, Config};
|
use rustyline::config::{ColorMode, CompletionType, Config};
|
||||||
use rustyline::error::ReadlineError;
|
use rustyline::error::ReadlineError;
|
||||||
use rustyline::{self, config::Configurer, At, Cmd, Editor, KeyPress, Movement, Word};
|
use rustyline::{self, config::Configurer, At, Cmd, Editor, KeyPress, Movement, Word};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::io::{BufRead, BufReader, Write};
|
|
||||||
use std::iter::Iterator;
|
use std::iter::Iterator;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::{Child, Command, Stdio};
|
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
use rayon::prelude::*;
|
fn register_plugins(context: &mut Context) -> Result<(), ShellError> {
|
||||||
|
if let Ok(plugins) = crate::plugin::scan() {
|
||||||
|
context.add_commands(
|
||||||
|
plugins
|
||||||
|
.into_iter()
|
||||||
|
.filter(|p| !context.is_command_registered(p.name()))
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn load_plugin(path: &std::path::Path, context: &mut Context) -> Result<(), ShellError> {
|
Ok(())
|
||||||
let ext = path.extension();
|
|
||||||
let ps1_file = match ext {
|
|
||||||
Some(ext) => ext == "ps1",
|
|
||||||
None => false,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut child: Child = if ps1_file {
|
|
||||||
Command::new("pwsh")
|
|
||||||
.stdin(Stdio::piped())
|
|
||||||
.stdout(Stdio::piped())
|
|
||||||
.args(&[
|
|
||||||
"-NoLogo",
|
|
||||||
"-NoProfile",
|
|
||||||
"-ExecutionPolicy",
|
|
||||||
"Bypass",
|
|
||||||
"-File",
|
|
||||||
&path.to_string_lossy(),
|
|
||||||
])
|
|
||||||
.spawn()
|
|
||||||
.expect("Failed to spawn PowerShell process")
|
|
||||||
} else {
|
|
||||||
Command::new(path)
|
|
||||||
.stdin(Stdio::piped())
|
|
||||||
.stdout(Stdio::piped())
|
|
||||||
.spawn()
|
|
||||||
.expect("Failed to spawn child process")
|
|
||||||
};
|
|
||||||
|
|
||||||
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
|
|
||||||
let stdout = child.stdout.as_mut().expect("Failed to open stdout");
|
|
||||||
|
|
||||||
let mut reader = BufReader::new(stdout);
|
|
||||||
|
|
||||||
let request = JsonRpc::new("config", Vec::<Value>::new());
|
|
||||||
let request_raw = serde_json::to_string(&request)?;
|
|
||||||
trace!(target: "nu::load", "plugin infrastructure config -> path {:#?}, request {:?}", &path, &request_raw);
|
|
||||||
stdin.write_all(format!("{}\n", request_raw).as_bytes())?;
|
|
||||||
let path = dunce::canonicalize(path)?;
|
|
||||||
|
|
||||||
let mut input = String::new();
|
|
||||||
let result = match reader.read_line(&mut input) {
|
|
||||||
Ok(count) => {
|
|
||||||
trace!(target: "nu::load", "plugin infrastructure -> config response for {:#?}", &path);
|
|
||||||
trace!(target: "nu::load", "plugin infrastructure -> processing response ({} bytes)", count);
|
|
||||||
trace!(target: "nu::load", "plugin infrastructure -> response: {}", input);
|
|
||||||
|
|
||||||
let response = serde_json::from_str::<JsonRpc<Result<Signature, ShellError>>>(&input);
|
|
||||||
match response {
|
|
||||||
Ok(jrpc) => match jrpc.params {
|
|
||||||
Ok(params) => {
|
|
||||||
let fname = path.to_string_lossy();
|
|
||||||
|
|
||||||
trace!(target: "nu::load", "plugin infrastructure -> processing {:?}", params);
|
|
||||||
|
|
||||||
let name = params.name.clone();
|
|
||||||
let fname = fname.to_string();
|
|
||||||
|
|
||||||
if context.get_command(&name).is_some() {
|
|
||||||
trace!(target: "nu::load", "plugin infrastructure -> {:?} already loaded.", &name);
|
|
||||||
} else if params.is_filter {
|
|
||||||
context.add_commands(vec![whole_stream_command(PluginCommand::new(
|
|
||||||
name, fname, params,
|
|
||||||
))]);
|
|
||||||
} else {
|
|
||||||
context.add_commands(vec![whole_stream_command(PluginSink::new(
|
|
||||||
name, fname, params,
|
|
||||||
))]);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Err(e) => Err(e),
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
trace!(target: "nu::load", "plugin infrastructure -> incompatible {:?}", input);
|
|
||||||
Err(ShellError::untagged_runtime_error(format!(
|
|
||||||
"Error: {:?}",
|
|
||||||
e
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => Err(ShellError::untagged_runtime_error(format!(
|
|
||||||
"Error: {:?}",
|
|
||||||
e
|
|
||||||
))),
|
|
||||||
};
|
|
||||||
|
|
||||||
let _ = child.wait();
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_paths() -> Vec<std::path::PathBuf> {
|
pub fn search_paths() -> Vec<std::path::PathBuf> {
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
let mut search_paths = Vec::new();
|
let mut search_paths = Vec::new();
|
||||||
|
@ -153,78 +64,6 @@ fn search_paths() -> Vec<std::path::PathBuf> {
|
||||||
search_paths
|
search_paths
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_plugins(context: &mut Context) -> Result<(), ShellError> {
|
|
||||||
let opts = glob::MatchOptions {
|
|
||||||
case_sensitive: false,
|
|
||||||
require_literal_separator: false,
|
|
||||||
require_literal_leading_dot: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
for path in search_paths() {
|
|
||||||
let mut pattern = path.to_path_buf();
|
|
||||||
|
|
||||||
pattern.push(std::path::Path::new("nu_plugin_[a-z0-9][a-z0-9]*"));
|
|
||||||
|
|
||||||
let plugs: Vec<_> = glob::glob_with(&pattern.to_string_lossy(), opts)?
|
|
||||||
.filter_map(|x| x.ok())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let _failures: Vec<_> = plugs
|
|
||||||
.par_iter()
|
|
||||||
.map(|path| {
|
|
||||||
let bin_name = {
|
|
||||||
if let Some(name) = path.file_name() {
|
|
||||||
name.to_str().unwrap_or("")
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// allow plugins with extensions on all platforms
|
|
||||||
let is_valid_name = {
|
|
||||||
bin_name
|
|
||||||
.chars()
|
|
||||||
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '.')
|
|
||||||
};
|
|
||||||
|
|
||||||
let is_executable = {
|
|
||||||
#[cfg(windows)]
|
|
||||||
{
|
|
||||||
bin_name.ends_with(".exe")
|
|
||||||
|| bin_name.ends_with(".bat")
|
|
||||||
|| bin_name.ends_with(".cmd")
|
|
||||||
|| bin_name.ends_with(".py")
|
|
||||||
|| bin_name.ends_with(".ps1")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
{
|
|
||||||
!bin_name.contains('.')
|
|
||||||
|| (bin_name.ends_with('.')
|
|
||||||
|| bin_name.ends_with(".py")
|
|
||||||
|| bin_name.ends_with(".rb")
|
|
||||||
|| bin_name.ends_with(".sh")
|
|
||||||
|| bin_name.ends_with(".bash")
|
|
||||||
|| bin_name.ends_with(".zsh")
|
|
||||||
|| bin_name.ends_with(".pl")
|
|
||||||
|| bin_name.ends_with(".awk")
|
|
||||||
|| bin_name.ends_with(".ps1"))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if is_valid_name && is_executable {
|
|
||||||
trace!(target: "nu::load", "plugin infrastructure -> Trying {:?}", path.display());
|
|
||||||
|
|
||||||
// we are ok if this plugin load fails
|
|
||||||
let _ = load_plugin(&path, &mut context.clone());
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct History;
|
pub struct History;
|
||||||
|
|
||||||
impl History {
|
impl History {
|
||||||
|
@ -489,7 +328,7 @@ pub async fn run_vec_of_pipelines(
|
||||||
let mut syncer = crate::EnvironmentSyncer::new();
|
let mut syncer = crate::EnvironmentSyncer::new();
|
||||||
let mut context = create_default_context(&mut syncer, false)?;
|
let mut context = create_default_context(&mut syncer, false)?;
|
||||||
|
|
||||||
let _ = crate::load_plugins(&mut context);
|
let _ = register_plugins(&mut context);
|
||||||
|
|
||||||
#[cfg(feature = "ctrlc")]
|
#[cfg(feature = "ctrlc")]
|
||||||
{
|
{
|
||||||
|
@ -755,7 +594,7 @@ pub async fn cli(
|
||||||
mut syncer: EnvironmentSyncer,
|
mut syncer: EnvironmentSyncer,
|
||||||
mut context: Context,
|
mut context: Context,
|
||||||
) -> Result<(), Box<dyn Error>> {
|
) -> Result<(), Box<dyn Error>> {
|
||||||
let _ = load_plugins(&mut context);
|
let _ = register_plugins(&mut context);
|
||||||
|
|
||||||
let (mut rl, config) = set_rustyline_configuration();
|
let (mut rl, config) = set_rustyline_configuration();
|
||||||
|
|
||||||
|
|
|
@ -79,7 +79,6 @@ pub(crate) mod open;
|
||||||
pub(crate) mod parse;
|
pub(crate) mod parse;
|
||||||
pub(crate) mod path;
|
pub(crate) mod path;
|
||||||
pub(crate) mod pivot;
|
pub(crate) mod pivot;
|
||||||
pub(crate) mod plugin;
|
|
||||||
pub(crate) mod prepend;
|
pub(crate) mod prepend;
|
||||||
pub(crate) mod prev;
|
pub(crate) mod prev;
|
||||||
pub(crate) mod pwd;
|
pub(crate) mod pwd;
|
||||||
|
|
|
@ -4,6 +4,7 @@ pub(crate) mod expr;
|
||||||
pub(crate) mod external;
|
pub(crate) mod external;
|
||||||
pub(crate) mod internal;
|
pub(crate) mod internal;
|
||||||
pub(crate) mod maybe_text_codec;
|
pub(crate) mod maybe_text_codec;
|
||||||
|
pub(crate) mod plugin;
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
pub(crate) use dynamic::Command as DynamicCommand;
|
pub(crate) use dynamic::Command as DynamicCommand;
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::command::{whole_stream_command, WholeStreamCommand};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use derive_new::new;
|
use derive_new::new;
|
||||||
use log::trace;
|
use log::trace;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
|
use nu_plugin::jsonrpc::JsonRpc;
|
||||||
use nu_protocol::{Primitive, ReturnValue, Signature, UntaggedValue, Value};
|
use nu_protocol::{Primitive, ReturnValue, Signature, UntaggedValue, Value};
|
||||||
use serde::{self, Deserialize, Serialize};
|
use serde::{self, Deserialize, Serialize};
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
|
@ -11,23 +12,6 @@ use std::io::Write;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process::{Child, Command, Stdio};
|
use std::process::{Child, Command, Stdio};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct JsonRpc<T> {
|
|
||||||
jsonrpc: String,
|
|
||||||
pub method: String,
|
|
||||||
pub params: T,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> JsonRpc<T> {
|
|
||||||
pub fn new<U: Into<String>>(method: U, params: T) -> Self {
|
|
||||||
JsonRpc {
|
|
||||||
jsonrpc: "2.0".into(),
|
|
||||||
method: method.into(),
|
|
||||||
params,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
#[serde(tag = "method")]
|
#[serde(tag = "method")]
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
|
@ -37,15 +21,77 @@ pub enum NuResult {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum PluginCommand {
|
||||||
|
Filter(PluginFilter),
|
||||||
|
Sink(PluginSink),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PluginCommand {
|
||||||
|
fn command(self) -> Result<crate::commands::Command, ShellError> {
|
||||||
|
match self {
|
||||||
|
PluginCommand::Filter(cmd) => Ok(whole_stream_command(cmd)),
|
||||||
|
PluginCommand::Sink(cmd) => Ok(whole_stream_command(cmd)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum PluginMode {
|
||||||
|
Filter,
|
||||||
|
Sink,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PluginCommandBuilder {
|
||||||
|
mode: PluginMode,
|
||||||
|
name: String,
|
||||||
|
path: String,
|
||||||
|
config: Signature,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PluginCommandBuilder {
|
||||||
|
pub fn new(
|
||||||
|
name: impl Into<String>,
|
||||||
|
path: impl Into<String>,
|
||||||
|
config: impl Into<Signature>,
|
||||||
|
) -> Self {
|
||||||
|
let config = config.into();
|
||||||
|
|
||||||
|
PluginCommandBuilder {
|
||||||
|
mode: if config.is_filter {
|
||||||
|
PluginMode::Filter
|
||||||
|
} else {
|
||||||
|
PluginMode::Sink
|
||||||
|
},
|
||||||
|
name: name.into(),
|
||||||
|
path: path.into(),
|
||||||
|
config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(&self) -> Result<crate::commands::Command, ShellError> {
|
||||||
|
let mode = &self.mode;
|
||||||
|
|
||||||
|
let name = self.name.clone();
|
||||||
|
let path = self.path.clone();
|
||||||
|
let config = self.config.clone();
|
||||||
|
|
||||||
|
let cmd = match mode {
|
||||||
|
PluginMode::Filter => PluginCommand::Filter(PluginFilter { name, path, config }),
|
||||||
|
PluginMode::Sink => PluginCommand::Sink(PluginSink { name, path, config }),
|
||||||
|
};
|
||||||
|
|
||||||
|
cmd.command()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(new)]
|
#[derive(new)]
|
||||||
pub struct PluginCommand {
|
pub struct PluginFilter {
|
||||||
name: String,
|
name: String,
|
||||||
path: String,
|
path: String,
|
||||||
config: Signature,
|
config: Signature,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl WholeStreamCommand for PluginCommand {
|
impl WholeStreamCommand for PluginFilter {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
&self.name
|
&self.name
|
||||||
}
|
}
|
||||||
|
@ -63,11 +109,11 @@ impl WholeStreamCommand for PluginCommand {
|
||||||
args: CommandArgs,
|
args: CommandArgs,
|
||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
filter_plugin(self.path.clone(), args, registry).await
|
run_filter(self.path.clone(), args, registry).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn filter_plugin(
|
async fn run_filter(
|
||||||
path: String,
|
path: String,
|
||||||
args: CommandArgs,
|
args: CommandArgs,
|
||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
|
@ -349,11 +395,11 @@ impl WholeStreamCommand for PluginSink {
|
||||||
args: CommandArgs,
|
args: CommandArgs,
|
||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
sink_plugin(self.path.clone(), args, registry).await
|
run_sink(self.path.clone(), args, registry).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn sink_plugin(
|
async fn run_sink(
|
||||||
path: String,
|
path: String,
|
||||||
args: CommandArgs,
|
args: CommandArgs,
|
||||||
registry: &CommandRegistry,
|
registry: &CommandRegistry,
|
|
@ -228,10 +228,15 @@ impl Context {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
pub(crate) fn get_command(&self, name: &str) -> Option<Command> {
|
pub(crate) fn get_command(&self, name: &str) -> Option<Command> {
|
||||||
self.registry.get_command(name)
|
self.registry.get_command(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_command_registered(&self, name: &str) -> bool {
|
||||||
|
self.registry.has(name)
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn expect_command(&self, name: &str) -> Result<Command, ShellError> {
|
pub(crate) fn expect_command(&self, name: &str) -> Result<Command, ShellError> {
|
||||||
self.registry.expect_command(name)
|
self.registry.expect_command(name)
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ mod futures;
|
||||||
mod git;
|
mod git;
|
||||||
mod keybinding;
|
mod keybinding;
|
||||||
mod path;
|
mod path;
|
||||||
|
mod plugin;
|
||||||
mod shell;
|
mod shell;
|
||||||
mod stream;
|
mod stream;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
@ -34,8 +35,8 @@ pub mod utils;
|
||||||
mod examples;
|
mod examples;
|
||||||
|
|
||||||
pub use crate::cli::{
|
pub use crate::cli::{
|
||||||
cli, create_default_context, load_plugins, parse_and_eval, process_line,
|
cli, create_default_context, parse_and_eval, process_line, run_pipeline_standalone,
|
||||||
run_pipeline_standalone, run_vec_of_pipelines, LineResult,
|
run_vec_of_pipelines, LineResult,
|
||||||
};
|
};
|
||||||
pub use crate::commands::command::{
|
pub use crate::commands::command::{
|
||||||
whole_stream_command, CommandArgs, EvaluatedWholeStreamCommandArgs, Example, WholeStreamCommand,
|
whole_stream_command, CommandArgs, EvaluatedWholeStreamCommandArgs, Example, WholeStreamCommand,
|
||||||
|
|
170
crates/nu-cli/src/plugin.rs
Normal file
170
crates/nu-cli/src/plugin.rs
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
use crate::commands::classified::plugin::PluginCommandBuilder;
|
||||||
|
use log::trace;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_plugin::jsonrpc::JsonRpc;
|
||||||
|
use nu_protocol::{Signature, Value};
|
||||||
|
use std::io::{BufRead, BufReader, Write};
|
||||||
|
use std::process::{Child, Command, Stdio};
|
||||||
|
|
||||||
|
use rayon::prelude::*;
|
||||||
|
|
||||||
|
pub fn build_plugin_command(
|
||||||
|
path: &std::path::Path,
|
||||||
|
) -> Result<Option<PluginCommandBuilder>, ShellError> {
|
||||||
|
let ext = path.extension();
|
||||||
|
let ps1_file = match ext {
|
||||||
|
Some(ext) => ext == "ps1",
|
||||||
|
None => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut child: Child = if ps1_file {
|
||||||
|
Command::new("pwsh")
|
||||||
|
.stdin(Stdio::piped())
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.args(&[
|
||||||
|
"-NoLogo",
|
||||||
|
"-NoProfile",
|
||||||
|
"-ExecutionPolicy",
|
||||||
|
"Bypass",
|
||||||
|
"-File",
|
||||||
|
&path.to_string_lossy(),
|
||||||
|
])
|
||||||
|
.spawn()
|
||||||
|
.expect("Failed to spawn PowerShell process")
|
||||||
|
} else {
|
||||||
|
Command::new(path)
|
||||||
|
.stdin(Stdio::piped())
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
.expect("Failed to spawn child process")
|
||||||
|
};
|
||||||
|
|
||||||
|
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
|
||||||
|
let stdout = child.stdout.as_mut().expect("Failed to open stdout");
|
||||||
|
|
||||||
|
let mut reader = BufReader::new(stdout);
|
||||||
|
|
||||||
|
let request = JsonRpc::new("config", Vec::<Value>::new());
|
||||||
|
let request_raw = serde_json::to_string(&request)?;
|
||||||
|
trace!(target: "nu::load", "plugin infrastructure config -> path {:#?}, request {:?}", &path, &request_raw);
|
||||||
|
stdin.write_all(format!("{}\n", request_raw).as_bytes())?;
|
||||||
|
let path = dunce::canonicalize(path)?;
|
||||||
|
|
||||||
|
let mut input = String::new();
|
||||||
|
let result = match reader.read_line(&mut input) {
|
||||||
|
Ok(count) => {
|
||||||
|
trace!(target: "nu::load", "plugin infrastructure -> config response for {:#?}", &path);
|
||||||
|
trace!(target: "nu::load", "plugin infrastructure -> processing response ({} bytes)", count);
|
||||||
|
trace!(target: "nu::load", "plugin infrastructure -> response: {}", input);
|
||||||
|
|
||||||
|
let response = serde_json::from_str::<JsonRpc<Result<Signature, ShellError>>>(&input);
|
||||||
|
match response {
|
||||||
|
Ok(jrpc) => match jrpc.params {
|
||||||
|
Ok(params) => {
|
||||||
|
let fname = path.to_string_lossy();
|
||||||
|
|
||||||
|
trace!(target: "nu::load", "plugin infrastructure -> processing {:?}", params);
|
||||||
|
|
||||||
|
let name = params.name.clone();
|
||||||
|
|
||||||
|
let fname = fname.to_string();
|
||||||
|
|
||||||
|
Ok(Some(PluginCommandBuilder::new(&name, &fname, params)))
|
||||||
|
}
|
||||||
|
Err(e) => Err(e),
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
trace!(target: "nu::load", "plugin infrastructure -> incompatible {:?}", input);
|
||||||
|
Err(ShellError::untagged_runtime_error(format!(
|
||||||
|
"Error: {:?}",
|
||||||
|
e
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => Err(ShellError::untagged_runtime_error(format!(
|
||||||
|
"Error: {:?}",
|
||||||
|
e
|
||||||
|
))),
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = child.wait();
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scan() -> Result<Vec<crate::commands::Command>, ShellError> {
|
||||||
|
let mut plugins = vec![];
|
||||||
|
|
||||||
|
let opts = glob::MatchOptions {
|
||||||
|
case_sensitive: false,
|
||||||
|
require_literal_separator: false,
|
||||||
|
require_literal_leading_dot: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
for path in crate::cli::search_paths() {
|
||||||
|
let mut pattern = path.to_path_buf();
|
||||||
|
|
||||||
|
pattern.push(std::path::Path::new("nu_plugin_[a-z0-9][a-z0-9]*"));
|
||||||
|
|
||||||
|
let plugs: Vec<_> = glob::glob_with(&pattern.to_string_lossy(), opts)?
|
||||||
|
.filter_map(|x| x.ok())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let plugs: Vec<_> = plugs
|
||||||
|
.par_iter()
|
||||||
|
.filter_map(|path| {
|
||||||
|
let bin_name = {
|
||||||
|
if let Some(name) = path.file_name() {
|
||||||
|
name.to_str().unwrap_or("")
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// allow plugins with extensions on all platforms
|
||||||
|
let is_valid_name = {
|
||||||
|
bin_name
|
||||||
|
.chars()
|
||||||
|
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '.')
|
||||||
|
};
|
||||||
|
|
||||||
|
let is_executable = {
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
bin_name.ends_with(".exe")
|
||||||
|
|| bin_name.ends_with(".bat")
|
||||||
|
|| bin_name.ends_with(".cmd")
|
||||||
|
|| bin_name.ends_with(".py")
|
||||||
|
|| bin_name.ends_with(".ps1")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
{
|
||||||
|
!bin_name.contains('.')
|
||||||
|
|| (bin_name.ends_with('.')
|
||||||
|
|| bin_name.ends_with(".py")
|
||||||
|
|| bin_name.ends_with(".rb")
|
||||||
|
|| bin_name.ends_with(".sh")
|
||||||
|
|| bin_name.ends_with(".bash")
|
||||||
|
|| bin_name.ends_with(".zsh")
|
||||||
|
|| bin_name.ends_with(".pl")
|
||||||
|
|| bin_name.ends_with(".awk")
|
||||||
|
|| bin_name.ends_with(".ps1"))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if is_valid_name && is_executable {
|
||||||
|
trace!(target: "nu::load", "plugin infrastructure -> Trying {:?}", path.display());
|
||||||
|
build_plugin_command(&path).unwrap_or_else(|_| None)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}).map(|p| p.build())
|
||||||
|
.filter_map(Result::ok)
|
||||||
|
.collect::<Vec<crate::commands::Command>>();
|
||||||
|
plugins.extend(plugs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(plugins)
|
||||||
|
}
|
41
crates/nu-plugin/src/jsonrpc.rs
Normal file
41
crates/nu-plugin/src/jsonrpc.rs
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
use nu_protocol::{outln, CallInfo, Value};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct JsonRpc<T> {
|
||||||
|
jsonrpc: String,
|
||||||
|
pub method: String,
|
||||||
|
pub params: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> JsonRpc<T> {
|
||||||
|
pub fn new<U: Into<String>>(method: U, params: T) -> Self {
|
||||||
|
JsonRpc {
|
||||||
|
jsonrpc: "2.0".into(),
|
||||||
|
method: method.into(),
|
||||||
|
params,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_response<T: Serialize>(result: T) {
|
||||||
|
let response = JsonRpc::new("response", result);
|
||||||
|
let response_raw = serde_json::to_string(&response);
|
||||||
|
|
||||||
|
match response_raw {
|
||||||
|
Ok(response) => outln!("{}", response),
|
||||||
|
Err(err) => outln!("{}", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(tag = "method")]
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
pub enum NuCommand {
|
||||||
|
config,
|
||||||
|
begin_filter { params: CallInfo },
|
||||||
|
filter { params: Value },
|
||||||
|
end_filter,
|
||||||
|
sink { params: (CallInfo, Vec<Value>) },
|
||||||
|
quit,
|
||||||
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
|
pub mod jsonrpc;
|
||||||
mod plugin;
|
mod plugin;
|
||||||
|
|
||||||
pub mod test_helpers;
|
pub mod test_helpers;
|
||||||
|
|
||||||
pub use crate::plugin::{serve_plugin, Plugin};
|
pub use crate::plugin::{serve_plugin, Plugin};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
use crate::jsonrpc::{send_response, NuCommand};
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{outln, CallInfo, ReturnValue, Signature, Value};
|
use nu_protocol::{CallInfo, ReturnValue, Signature, Value};
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
/// The `Plugin` trait defines the API which plugins may use to "hook" into nushell.
|
/// The `Plugin` trait defines the API which plugins may use to "hook" into nushell.
|
||||||
|
@ -134,40 +134,3 @@ pub fn serve_plugin(plugin: &mut dyn Plugin) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct JsonRpc<T> {
|
|
||||||
jsonrpc: String,
|
|
||||||
pub method: String,
|
|
||||||
pub params: T,
|
|
||||||
}
|
|
||||||
impl<T> JsonRpc<T> {
|
|
||||||
pub fn new<U: Into<String>>(method: U, params: T) -> Self {
|
|
||||||
JsonRpc {
|
|
||||||
jsonrpc: "2.0".into(),
|
|
||||||
method: method.into(),
|
|
||||||
params,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send_response<T: Serialize>(result: T) {
|
|
||||||
let response = JsonRpc::new("response", result);
|
|
||||||
let response_raw = serde_json::to_string(&response);
|
|
||||||
|
|
||||||
match response_raw {
|
|
||||||
Ok(response) => outln!("{}", response),
|
|
||||||
Err(err) => outln!("{}", err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
#[serde(tag = "method")]
|
|
||||||
#[allow(non_camel_case_types)]
|
|
||||||
pub enum NuCommand {
|
|
||||||
config,
|
|
||||||
begin_filter { params: CallInfo },
|
|
||||||
filter { params: Value },
|
|
||||||
end_filter,
|
|
||||||
sink { params: (CallInfo, Vec<Value>) },
|
|
||||||
quit,
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue