Plugin option for shell (#517)

* calling plugin without shell

* spelling error

* option on register to select a shell
This commit is contained in:
Fernando Herrera 2021-12-18 18:13:56 +00:00 committed by GitHub
parent b3b328d19d
commit 8933dde324
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 108 additions and 22 deletions

View File

@ -32,6 +32,12 @@ impl Command for Register {
SyntaxShape::Any, SyntaxShape::Any,
"Block with signature description as json object", "Block with signature description as json object",
) )
.named(
"shell",
SyntaxShape::Filepath,
"path of shell used to run plugin (cmd, sh, python, etc)",
Some('s'),
)
.category(Category::Core) .category(Category::Core)
} }

View File

@ -1195,7 +1195,7 @@ pub fn parse_register(
.map(|expr| { .map(|expr| {
let name_expr = working_set.get_span_contents(expr.span); let name_expr = working_set.get_span_contents(expr.span);
String::from_utf8(name_expr.to_vec()) String::from_utf8(name_expr.to_vec())
.map_err(|_| ParseError::NonUtf8(spans[1])) .map_err(|_| ParseError::NonUtf8(expr.span))
.and_then(|name| { .and_then(|name| {
canonicalize(&name).map_err(|_| ParseError::FileNotFound(name, expr.span)) canonicalize(&name).map_err(|_| ParseError::FileNotFound(name, expr.span))
}) })
@ -1224,7 +1224,7 @@ pub fn parse_register(
.map(|encoding| (path, encoding)) .map(|encoding| (path, encoding))
}); });
// Signature is the only optional value from the call and will be used to decide if // Signature is an optional value from the call and will be used to decide if
// the plugin is called to get the signatures or to use the given signature // the plugin is called to get the signatures or to use the given signature
let signature = call.positional.get(1).map(|expr| { let signature = call.positional.get(1).map(|expr| {
let signature = working_set.get_span_contents(expr.span); let signature = working_set.get_span_contents(expr.span);
@ -1237,16 +1237,52 @@ pub fn parse_register(
}) })
}); });
// Shell is another optional value used as base to call shell to plugins
let shell = call.get_flag_expr("shell").map(|expr| {
let shell_expr = working_set.get_span_contents(expr.span);
String::from_utf8(shell_expr.to_vec())
.map_err(|_| ParseError::NonUtf8(expr.span))
.and_then(|name| {
canonicalize(&name).map_err(|_| ParseError::FileNotFound(name, expr.span))
})
.and_then(|path| {
if path.exists() & path.is_file() {
Ok(path)
} else {
Err(ParseError::FileNotFound(format!("{:?}", path), expr.span))
}
})
});
let shell = match shell {
None => None,
Some(path) => match path {
Ok(path) => Some(path),
Err(err) => {
return (
Statement::Pipeline(Pipeline::from_vec(vec![Expression {
expr: Expr::Call(call),
span: call_span,
ty: Type::Unknown,
custom_completion: None,
}])),
Some(err),
);
}
},
};
let error = match signature { let error = match signature {
Some(signature) => arguments.and_then(|(path, encoding)| { Some(signature) => arguments.and_then(|(path, encoding)| {
signature.map(|signature| { signature.map(|signature| {
let plugin_decl = PluginDeclaration::new(path, signature, encoding); let plugin_decl = PluginDeclaration::new(path, signature, encoding, shell);
working_set.add_decl(Box::new(plugin_decl)); working_set.add_decl(Box::new(plugin_decl));
working_set.mark_plugins_file_dirty(); working_set.mark_plugins_file_dirty();
}) })
}), }),
None => arguments.and_then(|(path, encoding)| { None => arguments.and_then(|(path, encoding)| {
get_signature(path.as_path(), &encoding) get_signature(path.as_path(), &encoding, &shell)
.map_err(|err| { .map_err(|err| {
ParseError::LabeledError( ParseError::LabeledError(
"Error getting signatures".into(), "Error getting signatures".into(),
@ -1258,8 +1294,12 @@ pub fn parse_register(
for signature in signatures { for signature in signatures {
// create plugin command declaration (need struct impl Command) // create plugin command declaration (need struct impl Command)
// store declaration in working set // store declaration in working set
let plugin_decl = let plugin_decl = PluginDeclaration::new(
PluginDeclaration::new(path.clone(), signature, encoding.clone()); path.clone(),
signature,
encoding.clone(),
shell.clone(),
);
working_set.add_decl(Box::new(plugin_decl)); working_set.add_decl(Box::new(plugin_decl));
} }

View File

@ -14,16 +14,23 @@ pub struct PluginDeclaration {
name: String, name: String,
signature: Signature, signature: Signature,
filename: PathBuf, filename: PathBuf,
shell: Option<PathBuf>,
encoding: EncodingType, encoding: EncodingType,
} }
impl PluginDeclaration { impl PluginDeclaration {
pub fn new(filename: PathBuf, signature: Signature, encoding: EncodingType) -> Self { pub fn new(
filename: PathBuf,
signature: Signature,
encoding: EncodingType,
shell: Option<PathBuf>,
) -> Self {
Self { Self {
name: signature.name.clone(), name: signature.name.clone(),
signature, signature,
filename, filename,
encoding, encoding,
shell,
} }
} }
} }
@ -52,7 +59,7 @@ impl Command for PluginDeclaration {
// Decode information from plugin // Decode information from plugin
// Create PipelineData // Create PipelineData
let source_file = Path::new(&self.filename); let source_file = Path::new(&self.filename);
let mut plugin_cmd = create_command(source_file); let mut plugin_cmd = create_command(source_file, &self.shell);
let mut child = plugin_cmd.spawn().map_err(|err| { let mut child = plugin_cmd.spawn().map_err(|err| {
let decl = engine_state.get_decl(call.decl_id); let decl = engine_state.get_decl(call.decl_id);
@ -131,7 +138,7 @@ impl Command for PluginDeclaration {
Ok(pipeline_data) Ok(pipeline_data)
} }
fn is_plugin(&self) -> Option<(&PathBuf, &str)> { fn is_plugin(&self) -> Option<(&PathBuf, &str, &Option<PathBuf>)> {
Some((&self.filename, self.encoding.to_str())) Some((&self.filename, self.encoding.to_str(), &self.shell))
} }
} }

View File

View File

@ -4,7 +4,7 @@ pub use declaration::PluginDeclaration;
use crate::protocol::{LabeledError, PluginCall, PluginResponse}; use crate::protocol::{LabeledError, PluginCall, PluginResponse};
use crate::EncodingType; use crate::EncodingType;
use std::io::BufReader; use std::io::BufReader;
use std::path::Path; use std::path::{Path, PathBuf};
use std::process::{Command as CommandSys, Stdio}; use std::process::{Command as CommandSys, Stdio};
use nu_protocol::ShellError; use nu_protocol::ShellError;
@ -35,10 +35,15 @@ pub trait PluginEncoder: Clone {
) -> Result<PluginResponse, ShellError>; ) -> Result<PluginResponse, ShellError>;
} }
fn create_command(path: &Path) -> CommandSys { fn create_command(path: &Path, shell: &Option<PathBuf>) -> CommandSys {
let mut process = match path.extension() { let mut process = match (path.extension(), shell) {
None => std::process::Command::new(path), (_, Some(shell)) => {
Some(extension) => { let mut process = std::process::Command::new(shell);
process.arg(path);
process
}
(Some(extension), None) => {
let (shell, separator) = match extension.to_str() { let (shell, separator) = match extension.to_str() {
Some("cmd") | Some("bat") => (Some("cmd"), Some("/c")), Some("cmd") | Some("bat") => (Some("cmd"), Some("/c")),
Some("sh") => (Some("sh"), Some("-c")), Some("sh") => (Some("sh"), Some("-c")),
@ -63,6 +68,7 @@ fn create_command(path: &Path) -> CommandSys {
_ => std::process::Command::new(path), _ => std::process::Command::new(path),
} }
} }
(None, None) => std::process::Command::new(path),
}; };
// Both stdout and stdin are piped so we can receive information from the plugin // Both stdout and stdin are piped so we can receive information from the plugin
@ -71,8 +77,12 @@ fn create_command(path: &Path) -> CommandSys {
process process
} }
pub fn get_signature(path: &Path, encoding: &EncodingType) -> Result<Vec<Signature>, ShellError> { pub fn get_signature(
let mut plugin_cmd = create_command(path); path: &Path,
encoding: &EncodingType,
shell: &Option<PathBuf>,
) -> Result<Vec<Signature>, ShellError> {
let mut plugin_cmd = create_command(path, shell);
let mut child = plugin_cmd.spawn().map_err(|err| { let mut child = plugin_cmd.spawn().map_err(|err| {
ShellError::PluginFailedToLoad(format!("Error spawning child process: {}", err)) ShellError::PluginFailedToLoad(format!("Error spawning child process: {}", err))

View File

@ -48,8 +48,9 @@ pub trait Command: Send + Sync + CommandClone {
self.name().contains(' ') self.name().contains(' ')
} }
// Is a plugin command (returns plugin's path and encoding if yes) // Is a plugin command (returns plugin's path, encoding and type of shell
fn is_plugin(&self) -> Option<(&PathBuf, &str)> { // if the declaration is a plugin)
fn is_plugin(&self) -> Option<(&PathBuf, &str, &Option<PathBuf>)> {
None None
} }

View File

@ -230,12 +230,33 @@ impl EngineState {
self.plugin_decls().try_for_each(|decl| { self.plugin_decls().try_for_each(|decl| {
// A successful plugin registration already includes the plugin filename // A successful plugin registration already includes the plugin filename
// No need to check the None option // No need to check the None option
let (path, encoding) = decl.is_plugin().expect("plugin should have file name"); let (path, encoding, shell) =
let file_name = path.to_str().expect("path should be a str"); decl.is_plugin().expect("plugin should have file name");
let file_name = path
.to_str()
.expect("path was checked during registration as a str");
serde_json::to_string_pretty(&decl.signature()) serde_json::to_string_pretty(&decl.signature())
.map(|signature| { .map(|signature| {
format!("register {} -e {} {}\n\n", file_name, encoding, signature) // Extracting the possible path to the shell used to load the plugin
let shell_str = match shell {
Some(path) => format!(
"-s {}",
path.to_str().expect(
"shell path was checked during registration as a str"
)
),
None => "".into(),
};
// Each signature is stored in the plugin file with the required
// encoding, shell and signature
// This information will be used when loading the plugin
// information when nushell starts
format!(
"register {} -e {} {} {}\n\n",
file_name, encoding, shell_str, signature
)
}) })
.map_err(|err| ShellError::PluginFailedToLoad(err.to_string())) .map_err(|err| ShellError::PluginFailedToLoad(err.to_string()))
.and_then(|line| { .and_then(|line| {

View File

@ -21,6 +21,7 @@
# native python dictionaries. The encoding and decoding process could be improved # native python dictionaries. The encoding and decoding process could be improved
# by using libraries like pydantic and marshmallow # by using libraries like pydantic and marshmallow
# #
# This plugin uses python3
# Note: To debug plugins write to stderr using sys.stderr.write # Note: To debug plugins write to stderr using sys.stderr.write
import sys import sys
import json import json