mirror of
https://github.com/nushell/nushell.git
synced 2024-11-22 00:13:21 +01:00
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,
|
||||
"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)
|
||||
}
|
||||
|
||||
|
@ -1195,7 +1195,7 @@ pub fn parse_register(
|
||||
.map(|expr| {
|
||||
let name_expr = working_set.get_span_contents(expr.span);
|
||||
String::from_utf8(name_expr.to_vec())
|
||||
.map_err(|_| ParseError::NonUtf8(spans[1]))
|
||||
.map_err(|_| ParseError::NonUtf8(expr.span))
|
||||
.and_then(|name| {
|
||||
canonicalize(&name).map_err(|_| ParseError::FileNotFound(name, expr.span))
|
||||
})
|
||||
@ -1224,7 +1224,7 @@ pub fn parse_register(
|
||||
.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
|
||||
let signature = call.positional.get(1).map(|expr| {
|
||||
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 {
|
||||
Some(signature) => arguments.and_then(|(path, encoding)| {
|
||||
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.mark_plugins_file_dirty();
|
||||
})
|
||||
}),
|
||||
None => arguments.and_then(|(path, encoding)| {
|
||||
get_signature(path.as_path(), &encoding)
|
||||
get_signature(path.as_path(), &encoding, &shell)
|
||||
.map_err(|err| {
|
||||
ParseError::LabeledError(
|
||||
"Error getting signatures".into(),
|
||||
@ -1258,8 +1294,12 @@ pub fn parse_register(
|
||||
for signature in signatures {
|
||||
// create plugin command declaration (need struct impl Command)
|
||||
// store declaration in working set
|
||||
let plugin_decl =
|
||||
PluginDeclaration::new(path.clone(), signature, encoding.clone());
|
||||
let plugin_decl = PluginDeclaration::new(
|
||||
path.clone(),
|
||||
signature,
|
||||
encoding.clone(),
|
||||
shell.clone(),
|
||||
);
|
||||
|
||||
working_set.add_decl(Box::new(plugin_decl));
|
||||
}
|
||||
|
@ -14,16 +14,23 @@ pub struct PluginDeclaration {
|
||||
name: String,
|
||||
signature: Signature,
|
||||
filename: PathBuf,
|
||||
shell: Option<PathBuf>,
|
||||
encoding: EncodingType,
|
||||
}
|
||||
|
||||
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 {
|
||||
name: signature.name.clone(),
|
||||
signature,
|
||||
filename,
|
||||
encoding,
|
||||
shell,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -52,7 +59,7 @@ impl Command for PluginDeclaration {
|
||||
// Decode information from plugin
|
||||
// Create PipelineData
|
||||
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 decl = engine_state.get_decl(call.decl_id);
|
||||
@ -131,7 +138,7 @@ impl Command for PluginDeclaration {
|
||||
Ok(pipeline_data)
|
||||
}
|
||||
|
||||
fn is_plugin(&self) -> Option<(&PathBuf, &str)> {
|
||||
Some((&self.filename, self.encoding.to_str()))
|
||||
fn is_plugin(&self) -> Option<(&PathBuf, &str, &Option<PathBuf>)> {
|
||||
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::EncodingType;
|
||||
use std::io::BufReader;
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command as CommandSys, Stdio};
|
||||
|
||||
use nu_protocol::ShellError;
|
||||
@ -35,10 +35,15 @@ pub trait PluginEncoder: Clone {
|
||||
) -> Result<PluginResponse, ShellError>;
|
||||
}
|
||||
|
||||
fn create_command(path: &Path) -> CommandSys {
|
||||
let mut process = match path.extension() {
|
||||
None => std::process::Command::new(path),
|
||||
Some(extension) => {
|
||||
fn create_command(path: &Path, shell: &Option<PathBuf>) -> CommandSys {
|
||||
let mut process = match (path.extension(), shell) {
|
||||
(_, Some(shell)) => {
|
||||
let mut process = std::process::Command::new(shell);
|
||||
process.arg(path);
|
||||
|
||||
process
|
||||
}
|
||||
(Some(extension), None) => {
|
||||
let (shell, separator) = match extension.to_str() {
|
||||
Some("cmd") | Some("bat") => (Some("cmd"), Some("/c")),
|
||||
Some("sh") => (Some("sh"), Some("-c")),
|
||||
@ -63,6 +68,7 @@ fn create_command(path: &Path) -> CommandSys {
|
||||
_ => 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
|
||||
@ -71,8 +77,12 @@ fn create_command(path: &Path) -> CommandSys {
|
||||
process
|
||||
}
|
||||
|
||||
pub fn get_signature(path: &Path, encoding: &EncodingType) -> Result<Vec<Signature>, ShellError> {
|
||||
let mut plugin_cmd = create_command(path);
|
||||
pub fn get_signature(
|
||||
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| {
|
||||
ShellError::PluginFailedToLoad(format!("Error spawning child process: {}", err))
|
||||
|
@ -48,8 +48,9 @@ pub trait Command: Send + Sync + CommandClone {
|
||||
self.name().contains(' ')
|
||||
}
|
||||
|
||||
// Is a plugin command (returns plugin's path and encoding if yes)
|
||||
fn is_plugin(&self) -> Option<(&PathBuf, &str)> {
|
||||
// Is a plugin command (returns plugin's path, encoding and type of shell
|
||||
// if the declaration is a plugin)
|
||||
fn is_plugin(&self) -> Option<(&PathBuf, &str, &Option<PathBuf>)> {
|
||||
None
|
||||
}
|
||||
|
||||
|
@ -230,12 +230,33 @@ impl EngineState {
|
||||
self.plugin_decls().try_for_each(|decl| {
|
||||
// A successful plugin registration already includes the plugin filename
|
||||
// No need to check the None option
|
||||
let (path, encoding) = decl.is_plugin().expect("plugin should have file name");
|
||||
let file_name = path.to_str().expect("path should be a str");
|
||||
let (path, encoding, shell) =
|
||||
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())
|
||||
.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()))
|
||||
.and_then(|line| {
|
||||
|
@ -21,6 +21,7 @@
|
||||
# native python dictionaries. The encoding and decoding process could be improved
|
||||
# by using libraries like pydantic and marshmallow
|
||||
#
|
||||
# This plugin uses python3
|
||||
# Note: To debug plugins write to stderr using sys.stderr.write
|
||||
import sys
|
||||
import json
|
||||
|
Loading…
Reference in New Issue
Block a user