engine-q merge

This commit is contained in:
Fernando Herrera
2022-02-07 19:11:34 +00:00
1965 changed files with 119062 additions and 20 deletions

View 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]

View 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,
}

View File

@ -0,0 +1,6 @@
pub mod jsonrpc;
mod plugin;
pub mod test_helpers;
pub use crate::plugin::{serve_plugin, Plugin};

View 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;
}
}
}
}
}

View 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.")
}