forked from extern/nushell
Plugin option for shell (#517)
* calling plugin without shell * spelling error * option on register to select a shell
This commit is contained in:
parent
b3b328d19d
commit
8933dde324
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
0
crates/nu-plugin/src/plugin/is_plugin
Normal file
0
crates/nu-plugin/src/plugin/is_plugin
Normal 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))
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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| {
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user