mirror of
https://github.com/nushell/nushell.git
synced 2025-08-14 03:58:28 +02:00
engine-q merge
This commit is contained in:
22
old_nushell/crates/nu-plugin/Cargo.toml
Normal file
22
old_nushell/crates/nu-plugin/Cargo.toml
Normal file
@ -0,0 +1,22 @@
|
||||
[package]
|
||||
authors = ["The Nu Project Contributors"]
|
||||
description = "Nushell Plugin"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
name = "nu-plugin"
|
||||
version = "0.43.0"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
nu-errors = { path="../nu-errors", version = "0.43.0" }
|
||||
nu-protocol = { path="../nu-protocol", version = "0.43.0" }
|
||||
nu-source = { path="../nu-source", version = "0.43.0" }
|
||||
nu-test-support = { path="../nu-test-support", version = "0.43.0" }
|
||||
nu-value-ext = { path="../nu-value-ext", version = "0.43.0" }
|
||||
indexmap = { version="1.6.1", features=["serde-1"] }
|
||||
serde = { version="1.0", features=["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
||||
[build-dependencies]
|
48
old_nushell/crates/nu-plugin/src/jsonrpc.rs
Normal file
48
old_nushell/crates/nu-plugin/src/jsonrpc.rs
Normal file
@ -0,0 +1,48 @@
|
||||
use nu_protocol::{CallInfo, Value};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::Write;
|
||||
|
||||
#[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);
|
||||
|
||||
let mut stdout = std::io::stdout();
|
||||
|
||||
match response_raw {
|
||||
Ok(response) => {
|
||||
let _ = writeln!(stdout, "{}", response);
|
||||
}
|
||||
Err(err) => {
|
||||
let _ = writeln!(stdout, "{}", 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,
|
||||
}
|
6
old_nushell/crates/nu-plugin/src/lib.rs
Normal file
6
old_nushell/crates/nu-plugin/src/lib.rs
Normal file
@ -0,0 +1,6 @@
|
||||
pub mod jsonrpc;
|
||||
mod plugin;
|
||||
|
||||
pub mod test_helpers;
|
||||
|
||||
pub use crate::plugin::{serve_plugin, Plugin};
|
131
old_nushell/crates/nu-plugin/src/plugin.rs
Normal file
131
old_nushell/crates/nu-plugin/src/plugin.rs
Normal file
@ -0,0 +1,131 @@
|
||||
use crate::jsonrpc::{send_response, NuCommand};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{CallInfo, ReturnValue, Signature, Value};
|
||||
use std::io;
|
||||
|
||||
/// The `Plugin` trait defines the API which plugins may use to "hook" into nushell.
|
||||
pub trait Plugin {
|
||||
/// The `config` method is used to configure a plugin's user interface / signature.
|
||||
///
|
||||
/// This is where the "name" of the plugin (ex `fetch`), description, any required/optional fields, and flags
|
||||
/// can be defined. This information will displayed in nushell when running help <plugin name>
|
||||
fn config(&mut self) -> Result<Signature, ShellError>;
|
||||
|
||||
/// `begin_filter` is the first method to be called if the `Signature` of the plugin is configured to be filterable.
|
||||
/// Any setup required for the plugin such as parsing arguments from `CallInfo` or initializing data structures
|
||||
/// can be done here. The `CallInfo` parameter will contain data configured in the `config` method of the Plugin trait.
|
||||
fn begin_filter(&mut self, _call_info: CallInfo) -> Result<Vec<ReturnValue>, ShellError> {
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
/// `filter` is called for every `Value` that is processed by the plugin.
|
||||
/// This method requires the plugin `Signature` to be configured as filterable.
|
||||
fn filter(&mut self, _input: Value) -> Result<Vec<ReturnValue>, ShellError> {
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
/// `end_filter` is the last method to be called by the plugin after all `Value`s are processed by the plugin.
|
||||
/// This method requires the plugin `Signature` to be configured as filterable.
|
||||
fn end_filter(&mut self) -> Result<Vec<ReturnValue>, ShellError> {
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
/// `sink` consumes the `Value`s that are passed in, preventing further processing.
|
||||
/// This method requires the plugin `Signature` to be configured without filtering.
|
||||
fn sink(&mut self, _call_info: CallInfo, _input: Vec<Value>) {}
|
||||
|
||||
fn quit(&mut self) {}
|
||||
}
|
||||
|
||||
pub fn serve_plugin(plugin: &mut dyn Plugin) {
|
||||
let mut args = std::env::args();
|
||||
if args.len() > 1 {
|
||||
let input = args.nth(1);
|
||||
|
||||
let input = match input {
|
||||
Some(arg) => std::fs::read_to_string(arg),
|
||||
None => {
|
||||
send_response(ShellError::untagged_runtime_error("No input given."));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if let Ok(input) = input {
|
||||
let command = serde_json::from_str::<NuCommand>(&input);
|
||||
match command {
|
||||
Ok(NuCommand::config) => {
|
||||
send_response(plugin.config());
|
||||
}
|
||||
Ok(NuCommand::begin_filter { params }) => {
|
||||
send_response(plugin.begin_filter(params));
|
||||
}
|
||||
Ok(NuCommand::filter { params }) => {
|
||||
send_response(plugin.filter(params));
|
||||
}
|
||||
Ok(NuCommand::end_filter) => {
|
||||
send_response(plugin.end_filter());
|
||||
}
|
||||
|
||||
Ok(NuCommand::sink { params }) => {
|
||||
plugin.sink(params.0, params.1);
|
||||
}
|
||||
Ok(NuCommand::quit) => {
|
||||
plugin.quit();
|
||||
}
|
||||
e => {
|
||||
send_response(ShellError::untagged_runtime_error(format!(
|
||||
"Could not handle plugin message: {} {:?}",
|
||||
input, e
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
loop {
|
||||
let mut input = String::new();
|
||||
match io::stdin().read_line(&mut input) {
|
||||
Ok(_) => {
|
||||
let command = serde_json::from_str::<NuCommand>(&input);
|
||||
match command {
|
||||
Ok(NuCommand::config) => {
|
||||
send_response(plugin.config());
|
||||
break;
|
||||
}
|
||||
Ok(NuCommand::begin_filter { params }) => {
|
||||
send_response(plugin.begin_filter(params));
|
||||
}
|
||||
Ok(NuCommand::filter { params }) => {
|
||||
send_response(plugin.filter(params));
|
||||
}
|
||||
Ok(NuCommand::end_filter) => {
|
||||
send_response(plugin.end_filter());
|
||||
break;
|
||||
}
|
||||
Ok(NuCommand::sink { params }) => {
|
||||
plugin.sink(params.0, params.1);
|
||||
break;
|
||||
}
|
||||
Ok(NuCommand::quit) => {
|
||||
plugin.quit();
|
||||
break;
|
||||
}
|
||||
e => {
|
||||
send_response(ShellError::untagged_runtime_error(format!(
|
||||
"Could not handle plugin message: {} {:?}",
|
||||
input, e
|
||||
)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
e => {
|
||||
send_response(ShellError::untagged_runtime_error(format!(
|
||||
"Could not handle plugin message: {:?}",
|
||||
e,
|
||||
)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
155
old_nushell/crates/nu-plugin/src/test_helpers.rs
Normal file
155
old_nushell/crates/nu-plugin/src/test_helpers.rs
Normal file
@ -0,0 +1,155 @@
|
||||
use crate::Plugin;
|
||||
use indexmap::IndexMap;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
CallInfo, EvaluatedArgs, Primitive, ReturnSuccess, ReturnValue, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::Tag;
|
||||
use nu_test_support::value::column_path;
|
||||
use nu_value_ext::ValueExt;
|
||||
|
||||
pub struct PluginTest<'a, T: Plugin> {
|
||||
plugin: &'a mut T,
|
||||
call_info: CallInfo,
|
||||
input: Value,
|
||||
}
|
||||
|
||||
impl<'a, T: Plugin> PluginTest<'a, T> {
|
||||
pub fn for_plugin(plugin: &'a mut T) -> Self {
|
||||
PluginTest {
|
||||
plugin,
|
||||
call_info: CallStub::new().create(),
|
||||
input: UntaggedValue::nothing().into_value(Tag::unknown()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn args(&mut self, call_info: CallInfo) -> &mut PluginTest<'a, T> {
|
||||
self.call_info = call_info;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn configure(&mut self, callback: impl FnOnce(Vec<String>)) -> &mut PluginTest<'a, T> {
|
||||
let signature = self
|
||||
.plugin
|
||||
.config()
|
||||
.expect("There was a problem configuring the plugin.");
|
||||
callback(signature.named.keys().map(String::from).collect());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn input(&mut self, value: Value) -> &mut PluginTest<'a, T> {
|
||||
self.input = value;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn test(&mut self) -> Result<Vec<ReturnValue>, ShellError> {
|
||||
let return_values = self.plugin.filter(self.input.clone());
|
||||
let mut return_values = return_values?;
|
||||
let end = self.plugin.end_filter();
|
||||
|
||||
return_values.extend(end?);
|
||||
|
||||
self.plugin.quit();
|
||||
Ok(return_values)
|
||||
}
|
||||
|
||||
pub fn setup(
|
||||
&mut self,
|
||||
callback: impl FnOnce(&mut T, Result<Vec<ReturnValue>, ShellError>),
|
||||
) -> &mut PluginTest<'a, T> {
|
||||
let call_stub = self.call_info.clone();
|
||||
|
||||
self.configure(|flags_configured| {
|
||||
let flags_registered = &call_stub.args.named;
|
||||
|
||||
let flag_passed = flags_registered
|
||||
.as_ref()
|
||||
.map(|names| names.keys().map(String::from).collect::<Vec<String>>());
|
||||
|
||||
if let Some(flags) = flag_passed {
|
||||
for flag in flags {
|
||||
assert!(
|
||||
flags_configured.iter().any(|f| *f == flag),
|
||||
"The flag you passed is not configured in the plugin.",
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let return_values = self.plugin.begin_filter(call_stub);
|
||||
|
||||
callback(self.plugin, return_values);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plugin<T: Plugin>(plugin: &mut T) -> PluginTest<T> {
|
||||
PluginTest::for_plugin(plugin)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct CallStub {
|
||||
positionals: Vec<Value>,
|
||||
flags: IndexMap<String, Value>,
|
||||
}
|
||||
|
||||
impl CallStub {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn with_named_parameter(&mut self, name: &str, value: Value) -> &mut Self {
|
||||
self.flags.insert(name.to_string(), value);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_long_flag(&mut self, name: &str) -> &mut Self {
|
||||
self.flags.insert(
|
||||
name.to_string(),
|
||||
UntaggedValue::boolean(true).into_value(Tag::unknown()),
|
||||
);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_parameter(&mut self, name: &str) -> Result<&mut Self, ShellError> {
|
||||
let cp = column_path(name)
|
||||
.as_column_path()
|
||||
.expect("Failed! Expected valid column path.");
|
||||
let cp = UntaggedValue::Primitive(Primitive::ColumnPath(cp.item)).into_value(cp.tag);
|
||||
|
||||
self.positionals.push(cp);
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn create(&self) -> CallInfo {
|
||||
CallInfo {
|
||||
args: EvaluatedArgs::new(Some(self.positionals.clone()), Some(self.flags.clone())),
|
||||
name_tag: Tag::unknown(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_return_value_at(
|
||||
for_results: Result<Vec<Result<ReturnSuccess, ShellError>>, ShellError>,
|
||||
at: usize,
|
||||
) -> Value {
|
||||
let return_values = for_results
|
||||
.expect("Failed! This seems to be an error getting back the results from the plugin.");
|
||||
|
||||
for (idx, item) in return_values.iter().enumerate() {
|
||||
let item = match item {
|
||||
Ok(return_value) => return_value,
|
||||
Err(_) => panic!("Unexpected value"),
|
||||
};
|
||||
|
||||
if idx == at {
|
||||
if let Some(value) = item.raw_value() {
|
||||
return value;
|
||||
} else {
|
||||
panic!("Internal error: could not get raw value in expect_return_value_at")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
panic!("Couldn't get return value from stream.")
|
||||
}
|
Reference in New Issue
Block a user