mirror of
https://github.com/nushell/nushell.git
synced 2024-11-22 00:13:21 +01:00
parent
d45750617b
commit
d5255f6dbf
12
Cargo.lock
generated
12
Cargo.lock
generated
@ -786,6 +786,16 @@ dependencies = [
|
||||
"futures-preview 0.3.0-alpha.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getset"
|
||||
version = "0.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.3.0"
|
||||
@ -1000,6 +1010,7 @@ dependencies = [
|
||||
"futures-preview 0.3.0-alpha.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures-sink-preview 0.3.0-alpha.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures_codec 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"getset 0.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lalrpop-util 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -2020,6 +2031,7 @@ dependencies = [
|
||||
"checksum futures-sink-preview 0.3.0-alpha.16 (registry+https://github.com/rust-lang/crates.io-index)" = "49dcfdacd6b5974ca0b9b78bc38ffd1071da0206179735c3df82e279f5b784e4"
|
||||
"checksum futures-util-preview 0.3.0-alpha.16 (registry+https://github.com/rust-lang/crates.io-index)" = "f7a0451b9c5047c2b9ab93425ffd0793165511e93c04b977cd45fbd41c6e34b2"
|
||||
"checksum futures_codec 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b60f48aa03e365df015d2fbf0b79f17b440350c268a5e20305da17b394adcc1e"
|
||||
"checksum getset 0.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "19fbde0fad0c1c1f9474694b1f5c9ba22b09f2f74f74e6d2bd19c43f6656e2cb"
|
||||
"checksum hashbrown 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "570178d5e4952010d138b0f1d581271ff3a02406d990f887d1e87e3d6e43b0ac"
|
||||
"checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114"
|
||||
"checksum ident_case 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
@ -40,6 +40,7 @@ regex = "1.1.6"
|
||||
serde = "1.0.91"
|
||||
serde_json = "1.0.39"
|
||||
serde_derive = "1.0.91"
|
||||
getset = "0.0.7"
|
||||
|
||||
[dependencies.pancurses]
|
||||
version = "0.16"
|
||||
|
@ -6,4 +6,9 @@ args = ["src/parser/parser.lalrpop"]
|
||||
[tasks.build]
|
||||
command = "cargo"
|
||||
args = ["build"]
|
||||
dependencies = ["lalrpop"]
|
||||
dependencies = ["lalrpop"]
|
||||
|
||||
[tasks.run]
|
||||
command = "cargo"
|
||||
args = ["run"]
|
||||
dependencies = ["build"]
|
70
src/cli.rs
70
src/cli.rs
@ -6,6 +6,7 @@ use crate::commands::classified::{
|
||||
};
|
||||
use crate::context::Context;
|
||||
crate use crate::errors::ShellError;
|
||||
use crate::evaluate::Scope;
|
||||
crate use crate::format::{EntriesListView, GenericView};
|
||||
use crate::object::Value;
|
||||
use crate::parser::{ParsedCommand, Pipeline};
|
||||
@ -17,7 +18,6 @@ use rustyline::{self, ColorMode, Config, Editor};
|
||||
use std::collections::VecDeque;
|
||||
use std::error::Error;
|
||||
use std::iter::Iterator;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MaybeOwned<'a, T> {
|
||||
@ -41,23 +41,23 @@ pub async fn cli() -> Result<(), Box<Error>> {
|
||||
use crate::commands::*;
|
||||
|
||||
context.add_commands(vec![
|
||||
("format-list", Arc::new(format_list)),
|
||||
("ps", Arc::new(ps::ps)),
|
||||
("ls", Arc::new(ls::ls)),
|
||||
("cd", Arc::new(cd::cd)),
|
||||
("view", Arc::new(view::view)),
|
||||
("skip", Arc::new(skip::skip)),
|
||||
("first", Arc::new(take::take)),
|
||||
("size", Arc::new(size::size)),
|
||||
("from-json", Arc::new(from_json::from_json)),
|
||||
("open", Arc::new(open::open)),
|
||||
("column", Arc::new(column::column)),
|
||||
("split", Arc::new(split::split)),
|
||||
("reject", Arc::new(reject::reject)),
|
||||
("to-array", Arc::new(to_array::to_array)),
|
||||
("to-json", Arc::new(to_json::to_json)),
|
||||
("where", Arc::new(where_::r#where)),
|
||||
("sort-by", Arc::new(sort_by::sort_by)),
|
||||
command("format-list", format_list),
|
||||
command("ps", ps::ps),
|
||||
command("ls", ls::ls),
|
||||
command("cd", cd::cd),
|
||||
command("view", view::view),
|
||||
command("skip", skip::skip),
|
||||
command("first", take::take),
|
||||
command("size", size::size),
|
||||
command("from-json", from_json::from_json),
|
||||
command("open", open::open),
|
||||
command("column", column::column),
|
||||
command("split", split::split),
|
||||
command("reject", reject::reject),
|
||||
command("to-array", to_array::to_array),
|
||||
command("to-json", to_json::to_json),
|
||||
Arc::new(Where),
|
||||
command("sort-by", sort_by::sort_by),
|
||||
]);
|
||||
}
|
||||
|
||||
@ -196,9 +196,15 @@ async fn process_line(readline: Result<String, ReadlineError>, ctx: &mut Context
|
||||
},
|
||||
|
||||
(
|
||||
Some(ClassifiedCommand::Internal(_)),
|
||||
Some(ClassifiedCommand::External(_)),
|
||||
) => return LineResult::Error(format!("Unimplemented Internal -> External",)),
|
||||
Some(ClassifiedCommand::Internal(ref i)),
|
||||
Some(ClassifiedCommand::External(ref e)),
|
||||
) => {
|
||||
return LineResult::Error(format!(
|
||||
"Unimplemented Internal({}) -> External({})",
|
||||
i.name(),
|
||||
e.name()
|
||||
))
|
||||
}
|
||||
|
||||
(
|
||||
Some(ClassifiedCommand::External(left)),
|
||||
@ -272,22 +278,28 @@ fn classify_command(
|
||||
let command_name = &command.name[..];
|
||||
let args = &command.args;
|
||||
|
||||
let arg_list: Vec<Value> = args.iter().map(|i| Value::from_expr(i)).collect();
|
||||
let arg_list_strings: Vec<String> = args.iter().map(|i| i.print()).collect();
|
||||
|
||||
match command_name {
|
||||
other => match context.has_command(command_name) {
|
||||
true => {
|
||||
let command = context.get_command(command_name);
|
||||
let config = command.config();
|
||||
let scope = Scope::empty();
|
||||
|
||||
let args = config.evaluate_args(args.iter(), &scope)?;
|
||||
|
||||
Ok(ClassifiedCommand::Internal(InternalCommand {
|
||||
command,
|
||||
args: arg_list,
|
||||
args,
|
||||
}))
|
||||
}
|
||||
false => {
|
||||
let arg_list_strings: Vec<String> = args.iter().map(|i| i.print()).collect();
|
||||
|
||||
Ok(ClassifiedCommand::External(ExternalCommand {
|
||||
name: other.to_string(),
|
||||
args: arg_list_strings,
|
||||
}))
|
||||
}
|
||||
false => Ok(ClassifiedCommand::External(ExternalCommand {
|
||||
name: other.to_string(),
|
||||
args: arg_list_strings,
|
||||
})),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -18,4 +18,6 @@ crate mod to_json;
|
||||
crate mod view;
|
||||
crate mod where_;
|
||||
|
||||
crate use command::command;
|
||||
crate use to_array::stream_to_array;
|
||||
crate use where_::Where;
|
||||
|
@ -105,6 +105,10 @@ impl InternalCommand {
|
||||
|
||||
Ok(stream.boxed() as InputStream)
|
||||
}
|
||||
|
||||
crate fn name(&self) -> &str {
|
||||
self.command.name()
|
||||
}
|
||||
}
|
||||
|
||||
crate struct ExternalCommand {
|
||||
@ -163,4 +167,8 @@ impl ExternalCommand {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
crate fn name(&self) -> &str {
|
||||
&self.name[..]
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::errors::ShellError;
|
||||
use crate::object::Value;
|
||||
use crate::parser::CommandConfig;
|
||||
use crate::prelude::*;
|
||||
use std::path::PathBuf;
|
||||
|
||||
@ -44,13 +45,40 @@ impl ReturnValue {
|
||||
|
||||
pub trait Command {
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError>;
|
||||
}
|
||||
fn name(&self) -> &str;
|
||||
|
||||
impl<F> Command for F
|
||||
where
|
||||
F: Fn(CommandArgs) -> Result<OutputStream, ShellError>,
|
||||
{
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
self(args)
|
||||
fn config(&self) -> CommandConfig {
|
||||
CommandConfig {
|
||||
name: self.name().to_string(),
|
||||
mandatory_positional: vec![],
|
||||
optional_positional: vec![],
|
||||
rest_positional: true,
|
||||
named: indexmap::IndexMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FnCommand {
|
||||
name: String,
|
||||
func: fn(CommandArgs) -> Result<OutputStream, ShellError>,
|
||||
}
|
||||
|
||||
impl Command for FnCommand {
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
(self.func)(args)
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
}
|
||||
|
||||
pub fn command(
|
||||
name: &str,
|
||||
func: fn(CommandArgs) -> Result<OutputStream, ShellError>,
|
||||
) -> Arc<dyn Command> {
|
||||
Arc::new(FnCommand {
|
||||
name: name.to_string(),
|
||||
func,
|
||||
})
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ use crate::errors::ShellError;
|
||||
use crate::prelude::*;
|
||||
|
||||
pub fn skip(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let amount = args.args[0].as_int()?;
|
||||
let amount = args.args[0].as_i64()?;
|
||||
|
||||
let input = args.input;
|
||||
|
||||
|
@ -11,8 +11,8 @@ pub fn sort_by(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
vec.sort_by_key(|item| {
|
||||
fields
|
||||
.iter()
|
||||
.map(|f| item.get_data_by_key(f).borrow().copy())
|
||||
.collect::<Vec<Value>>()
|
||||
.map(|f| item.get_data_by_key(f).map(|i| i.copy()))
|
||||
.collect::<Vec<Option<Value>>>()
|
||||
});
|
||||
|
||||
vec.into_iter()
|
||||
|
@ -4,7 +4,7 @@ use crate::prelude::*;
|
||||
// TODO: "Amount remaining" wrapper
|
||||
|
||||
pub fn take(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let amount = args.args[0].as_int()?;
|
||||
let amount = args.args[0].as_i64()?;
|
||||
|
||||
let input = args.input;
|
||||
|
||||
|
@ -1,21 +1,48 @@
|
||||
use crate::errors::ShellError;
|
||||
use crate::object::base::find;
|
||||
use crate::parser::registry::PositionalType;
|
||||
use crate::parser::CommandConfig;
|
||||
use crate::prelude::*;
|
||||
|
||||
pub struct Where;
|
||||
|
||||
impl Command for Where {
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
r#where(args)
|
||||
}
|
||||
fn name(&self) -> &str {
|
||||
"where"
|
||||
}
|
||||
|
||||
fn config(&self) -> CommandConfig {
|
||||
CommandConfig {
|
||||
name: self.name().to_string(),
|
||||
mandatory_positional: vec![PositionalType::Block("condition".to_string())],
|
||||
optional_positional: vec![],
|
||||
rest_positional: false,
|
||||
named: indexmap::IndexMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn r#where(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
if args.args.is_empty() {
|
||||
return Err(ShellError::string("select requires a field"));
|
||||
}
|
||||
|
||||
let operation = args.args[0].as_operation()?;
|
||||
let field = operation.left.as_string()?;
|
||||
let operator = operation.operator;
|
||||
let right = operation.right;
|
||||
let block = args.args[0].as_block()?;
|
||||
let input = args.input;
|
||||
|
||||
let objects = input
|
||||
.filter(move |item| futures::future::ready(find(&item, &field, &operator, &right)))
|
||||
.map(|item| ReturnValue::Value(item.copy()));
|
||||
let objects = input.filter_map(move |item| {
|
||||
let result = block.invoke(&item);
|
||||
|
||||
let return_value = match result {
|
||||
Err(err) => Some(ReturnValue::Value(Value::Error(Box::new(err)))),
|
||||
Ok(v) if v.is_true() => Some(ReturnValue::Value(item.copy())),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
futures::future::ready(return_value)
|
||||
});
|
||||
|
||||
Ok(objects.boxed())
|
||||
}
|
||||
|
@ -20,9 +20,9 @@ impl Context {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn add_commands(&mut self, commands: Vec<(&str, Arc<dyn Command>)>) {
|
||||
for (name, command) in commands {
|
||||
self.commands.insert(name.to_string(), command);
|
||||
pub fn add_commands(&mut self, commands: Vec<Arc<dyn Command>>) {
|
||||
for command in commands {
|
||||
self.commands.insert(command.name().to_string(), command);
|
||||
}
|
||||
}
|
||||
|
||||
|
93
src/evaluate/evaluator.rs
Normal file
93
src/evaluate/evaluator.rs
Normal file
@ -0,0 +1,93 @@
|
||||
use crate::parser::ast;
|
||||
use crate::prelude::*;
|
||||
use derive_new::new;
|
||||
|
||||
#[derive(new)]
|
||||
crate struct Scope {
|
||||
it: Value,
|
||||
}
|
||||
|
||||
impl Scope {
|
||||
crate fn empty() -> Scope {
|
||||
Scope {
|
||||
it: Value::nothing(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
crate fn evaluate_expr(expr: &ast::Expression, scope: &Scope) -> Result<Value, ShellError> {
|
||||
use ast::*;
|
||||
|
||||
match expr {
|
||||
Expression::Leaf(l) => Ok(evaluate_leaf(l)),
|
||||
Expression::Parenthesized(p) => evaluate_expr(&p.expr, scope),
|
||||
Expression::Block(b) => evaluate_block(&b, scope),
|
||||
Expression::Path(p) => evaluate_path(&p, scope),
|
||||
Expression::Binary(b) => evaluate_binary(b, scope),
|
||||
Expression::VariableReference(r) => evaluate_reference(r, scope),
|
||||
}
|
||||
}
|
||||
|
||||
fn evaluate_leaf(leaf: &ast::Leaf) -> Value {
|
||||
use ast::*;
|
||||
|
||||
match leaf {
|
||||
Leaf::String(s) => Value::string(s),
|
||||
Leaf::Bare(s) => Value::string(s),
|
||||
Leaf::Boolean(b) => Value::boolean(*b),
|
||||
Leaf::Int(i) => Value::int(*i),
|
||||
}
|
||||
}
|
||||
|
||||
fn evaluate_reference(r: &ast::Variable, scope: &Scope) -> Result<Value, ShellError> {
|
||||
use ast::Variable::*;
|
||||
|
||||
match r {
|
||||
It => Ok(scope.it.copy()),
|
||||
True => Ok(Value::boolean(true)),
|
||||
False => Ok(Value::boolean(false)),
|
||||
Other(s) => Err(ShellError::string(&format!(
|
||||
"Unimplemented variable reference: {}",
|
||||
s
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
fn evaluate_binary(binary: &ast::Binary, scope: &Scope) -> Result<Value, ShellError> {
|
||||
let left = evaluate_expr(&binary.left, scope)?;
|
||||
let right = evaluate_expr(&binary.right, scope)?;
|
||||
|
||||
match left.compare(binary.operator, &right) {
|
||||
Some(v) => Ok(Value::boolean(v)),
|
||||
None => Err(ShellError::string(&format!(
|
||||
"Unimplemented evaluate_binary:\n{:#?}",
|
||||
binary
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
fn evaluate_block(block: &ast::Block, _scope: &Scope) -> Result<Value, ShellError> {
|
||||
Ok(Value::block(block.expr.clone()))
|
||||
}
|
||||
|
||||
fn evaluate_path(path: &ast::Path, scope: &Scope) -> Result<Value, ShellError> {
|
||||
let head = path.head();
|
||||
let mut value = &evaluate_expr(head, scope)?;
|
||||
|
||||
for name in path.tail() {
|
||||
let next = value.get_data_by_key(&name);
|
||||
|
||||
match next {
|
||||
None => {
|
||||
return Err(ShellError::string(&format!(
|
||||
"No key {} found in {}",
|
||||
name,
|
||||
path.print(),
|
||||
)))
|
||||
}
|
||||
Some(v) => value = v,
|
||||
}
|
||||
}
|
||||
|
||||
Ok(value.copy())
|
||||
}
|
3
src/evaluate/mod.rs
Normal file
3
src/evaluate/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
crate mod evaluator;
|
||||
|
||||
crate use evaluator::{evaluate_expr, Scope};
|
@ -21,20 +21,6 @@ impl RenderView for GenericView<'value> {
|
||||
}
|
||||
|
||||
Ok(())
|
||||
// let mut list: Vec<String> = vec![];
|
||||
// for item in l {
|
||||
// match item {
|
||||
// Value::Primitive(p) => list.push(p.format()),
|
||||
// Value::List(l) => list.push(format!("{:?}", l)),
|
||||
// Value::Object(o) => {
|
||||
// let view = o.to_entries_view();
|
||||
// let out = view.render_view(host);
|
||||
// list.extend(out);
|
||||
// }
|
||||
// }
|
||||
// list.push("\n".to_string());
|
||||
// }
|
||||
// list
|
||||
}
|
||||
|
||||
o @ Value::Object(_) => {
|
||||
@ -43,11 +29,10 @@ impl RenderView for GenericView<'value> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Value::Operation(o) => {
|
||||
host.stdout(&format!(
|
||||
"Unexpectedly trying to print an operation: {:?}",
|
||||
o
|
||||
));
|
||||
b @ Value::Block(_) => {
|
||||
let printed = b.format_leaf(None);
|
||||
let view = EntriesView::from_value(&Value::string(&printed));
|
||||
view.render_view(host)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ mod commands;
|
||||
mod context;
|
||||
mod env;
|
||||
mod errors;
|
||||
mod evaluate;
|
||||
mod format;
|
||||
mod object;
|
||||
mod parser;
|
||||
|
@ -1,6 +1,7 @@
|
||||
use crate::errors::ShellError;
|
||||
use crate::evaluate::{evaluate_expr, Scope};
|
||||
use crate::object::DataDescriptor;
|
||||
use crate::parser::tokens::{self, Operator};
|
||||
use crate::parser::ast::{self, Operator};
|
||||
use crate::prelude::*;
|
||||
use ansi_term::Color;
|
||||
use chrono::{DateTime, Utc};
|
||||
@ -82,12 +83,33 @@ pub struct Operation {
|
||||
crate right: Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, new)]
|
||||
pub struct Block {
|
||||
crate expression: ast::Expression,
|
||||
}
|
||||
|
||||
impl Serialize for Block {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.expression.print())
|
||||
}
|
||||
}
|
||||
|
||||
impl Block {
|
||||
pub fn invoke(&self, value: &Value) -> Result<Value, ShellError> {
|
||||
let scope = Scope::new(value.copy());
|
||||
evaluate_expr(&self.expression, &scope)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone)]
|
||||
pub enum Value {
|
||||
Primitive(Primitive),
|
||||
Object(crate::object::Dictionary),
|
||||
List(Vec<Value>),
|
||||
Operation(Box<Operation>),
|
||||
Block(Block),
|
||||
|
||||
#[allow(unused)]
|
||||
Error(Box<ShellError>),
|
||||
@ -102,59 +124,27 @@ impl Serialize for Value {
|
||||
Value::Primitive(p) => p.serialize(serializer),
|
||||
Value::Object(o) => o.serialize(serializer),
|
||||
Value::List(l) => l.serialize(serializer),
|
||||
Value::Operation(o) => o.serialize(serializer),
|
||||
Value::Block(b) => b.serialize(serializer),
|
||||
Value::Error(e) => e.serialize(serializer),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Value {
|
||||
crate fn from_leaf(leaf: &tokens::Leaf) -> Value {
|
||||
use tokens::*;
|
||||
|
||||
match leaf {
|
||||
Leaf::String(s) => Value::string(s),
|
||||
Leaf::Bare(s) => Value::string(s),
|
||||
Leaf::Boolean(b) => Value::boolean(*b),
|
||||
Leaf::Int(i) => Value::int(*i),
|
||||
}
|
||||
}
|
||||
|
||||
crate fn from_expr(expr: &tokens::Expression) -> Value {
|
||||
use tokens::*;
|
||||
|
||||
match expr {
|
||||
Expression::Leaf(leaf) => Value::from_leaf(leaf),
|
||||
|
||||
Expression::Binary(Binary {
|
||||
left,
|
||||
operator,
|
||||
right,
|
||||
}) => Value::Operation(Box::new(Operation::new(
|
||||
Value::from_leaf(left),
|
||||
*operator,
|
||||
Value::from_leaf(right),
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
crate fn data_descriptors(&self) -> Vec<DataDescriptor> {
|
||||
match self {
|
||||
Value::Primitive(_) => vec![DataDescriptor::value_of()],
|
||||
Value::Object(o) => o.data_descriptors(),
|
||||
Value::Block(_) => vec![DataDescriptor::value_of()],
|
||||
Value::List(_) => vec![],
|
||||
Value::Operation(_) => vec![],
|
||||
Value::Error(_) => vec![],
|
||||
Value::Error(_) => vec![DataDescriptor::value_of()],
|
||||
}
|
||||
}
|
||||
|
||||
crate fn get_data_by_key(&'a self, name: &str) -> MaybeOwned<'a, Value> {
|
||||
crate fn get_data_by_key(&'a self, name: &str) -> Option<&Value> {
|
||||
match self {
|
||||
Value::Primitive(_) => MaybeOwned::Owned(Value::nothing()),
|
||||
Value::Object(o) => o.get_data_by_key(name),
|
||||
Value::List(_) => MaybeOwned::Owned(Value::nothing()),
|
||||
Value::Operation(_) => MaybeOwned::Owned(Value::nothing()),
|
||||
Value::Error(_) => MaybeOwned::Owned(Value::nothing()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -162,9 +152,9 @@ impl Value {
|
||||
match self {
|
||||
p @ Value::Primitive(_) => MaybeOwned::Borrowed(p),
|
||||
Value::Object(o) => o.get_data(desc),
|
||||
Value::Block(_) => MaybeOwned::Owned(Value::nothing()),
|
||||
Value::List(_) => MaybeOwned::Owned(Value::nothing()),
|
||||
Value::Operation(_) => MaybeOwned::Owned(Value::nothing()),
|
||||
Value::Error(_) => MaybeOwned::Owned(Value::nothing()),
|
||||
Value::Error(e) => MaybeOwned::Owned(Value::string(&format!("{:#?}", e))),
|
||||
}
|
||||
}
|
||||
|
||||
@ -172,11 +162,11 @@ impl Value {
|
||||
match self {
|
||||
Value::Primitive(p) => Value::Primitive(p.clone()),
|
||||
Value::Object(o) => Value::Object(o.copy_dict()),
|
||||
Value::Block(b) => Value::Block(b.clone()),
|
||||
Value::List(l) => {
|
||||
let list = l.iter().map(|i| i.copy()).collect();
|
||||
Value::List(list)
|
||||
}
|
||||
Value::Operation(o) => Value::Operation(o.clone()),
|
||||
Value::Error(e) => Value::Error(Box::new(e.copy_error())),
|
||||
}
|
||||
}
|
||||
@ -184,17 +174,41 @@ impl Value {
|
||||
crate fn format_leaf(&self, field_name: Option<&str>) -> String {
|
||||
match self {
|
||||
Value::Primitive(p) => p.format(field_name),
|
||||
Value::Block(b) => b.expression.print(),
|
||||
Value::Object(_) => format!("[object Object]"),
|
||||
Value::List(_) => format!("[list List]"),
|
||||
Value::Operation(_) => format!("[operation Operation]"),
|
||||
Value::Error(e) => format!("{}", e),
|
||||
}
|
||||
}
|
||||
|
||||
crate fn compare(&self, operator: ast::Operator, other: &Value) -> Option<bool> {
|
||||
match operator {
|
||||
ast::Operator::Equal | ast::Operator::NotEqual => unimplemented!(),
|
||||
_ => {
|
||||
let coerced = coerce_compare(self, other)?;
|
||||
let ordering = coerced.compare();
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
let result = match (operator, ordering) {
|
||||
(Operator::Equal, Ordering::Equal) => true,
|
||||
(Operator::LessThan, Ordering::Less) => true,
|
||||
(Operator::GreaterThan, Ordering::Greater) => true,
|
||||
(Operator::GreaterThanOrEqual, Ordering::Greater)
|
||||
| (Operator::GreaterThanOrEqual, Ordering::Equal) => true,
|
||||
(Operator::LessThanOrEqual, Ordering::Less)
|
||||
| (Operator::LessThanOrEqual, Ordering::Equal) => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
Some(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
crate fn as_string(&self) -> Result<String, ShellError> {
|
||||
match self {
|
||||
Value::Primitive(Primitive::String(s)) => Ok(s.to_string()),
|
||||
|
||||
// TODO: this should definitely be more general with better errors
|
||||
other => Err(ShellError::string(format!(
|
||||
"Expected string, got {:?}",
|
||||
@ -203,24 +217,24 @@ impl Value {
|
||||
}
|
||||
}
|
||||
|
||||
crate fn as_operation(&self) -> Result<Operation, ShellError> {
|
||||
crate fn as_i64(&self) -> Result<i64, ShellError> {
|
||||
match self {
|
||||
Value::Operation(o) => Ok(*o.clone()),
|
||||
|
||||
Value::Primitive(Primitive::Int(i)) => Ok(*i),
|
||||
Value::Primitive(Primitive::Bytes(b)) if *b <= std::i64::MAX as u128 => Ok(*b as i64),
|
||||
// TODO: this should definitely be more general with better errors
|
||||
other => Err(ShellError::string(format!(
|
||||
"Expected operation, got {:?}",
|
||||
"Expected integer, got {:?}",
|
||||
other
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
crate fn as_int(&self) -> Result<i64, ShellError> {
|
||||
crate fn as_block(&self) -> Result<Block, ShellError> {
|
||||
match self {
|
||||
Value::Primitive(Primitive::Int(i)) => Ok(*i),
|
||||
Value::Block(block) => Ok(block.clone()),
|
||||
// TODO: this should definitely be more general with better errors
|
||||
other => Err(ShellError::string(format!(
|
||||
"Expected integer, got {:?}",
|
||||
"Expected block, got {:?}",
|
||||
other
|
||||
))),
|
||||
}
|
||||
@ -238,6 +252,17 @@ impl Value {
|
||||
}
|
||||
}
|
||||
|
||||
crate fn is_true(&self) -> bool {
|
||||
match self {
|
||||
Value::Primitive(Primitive::Boolean(true)) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
crate fn block(e: ast::Expression) -> Value {
|
||||
Value::Block(Block::new(e))
|
||||
}
|
||||
|
||||
crate fn string(s: impl Into<String>) -> Value {
|
||||
Value::Primitive(Primitive::String(s.into()))
|
||||
}
|
||||
@ -316,13 +341,13 @@ crate fn reject_fields(obj: &Value, fields: &[String]) -> crate::object::Diction
|
||||
out
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
crate fn find(obj: &Value, field: &str, op: &Operator, rhs: &Value) -> bool {
|
||||
let descs = obj.data_descriptors();
|
||||
match descs.iter().find(|d| d.name.is_string(field)) {
|
||||
None => false,
|
||||
Some(desc) => {
|
||||
let v = obj.get_data(desc).borrow().copy();
|
||||
//println!("'{:?}' '{:?}' '{:?}'", v, op, rhs);
|
||||
|
||||
match v {
|
||||
Value::Primitive(Primitive::Boolean(b)) => match (op, rhs) {
|
||||
@ -398,3 +423,39 @@ crate fn find(obj: &Value, field: &str, op: &Operator, rhs: &Value) -> bool {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum CompareValues {
|
||||
Ints(i64, i64),
|
||||
Bytes(i128, i128),
|
||||
String(String, String),
|
||||
}
|
||||
|
||||
impl CompareValues {
|
||||
fn compare(&self) -> std::cmp::Ordering {
|
||||
match self {
|
||||
CompareValues::Ints(left, right) => left.cmp(right),
|
||||
CompareValues::Bytes(left, right) => left.cmp(right),
|
||||
CompareValues::String(left, right) => left.cmp(right),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn coerce_compare(left: &Value, right: &Value) -> Option<CompareValues> {
|
||||
match (left, right) {
|
||||
(Value::Primitive(left), Value::Primitive(right)) => coerce_compare_primitive(left, right),
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn coerce_compare_primitive(left: &Primitive, right: &Primitive) -> Option<CompareValues> {
|
||||
use Primitive::*;
|
||||
|
||||
match (left, right) {
|
||||
(Int(left), Int(right)) => Some(CompareValues::Ints(*left, *right)),
|
||||
(Int(left), Bytes(right)) => Some(CompareValues::Bytes(*left as i128, *right as i128)),
|
||||
(Bytes(left), Int(right)) => Some(CompareValues::Bytes(*left as i128, *right as i128)),
|
||||
(String(left), String(right)) => Some(CompareValues::String(left.clone(), right.clone())),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -80,14 +80,14 @@ impl Dictionary {
|
||||
}
|
||||
}
|
||||
|
||||
crate fn get_data_by_key(&self, name: &str) -> MaybeOwned<'_, Value> {
|
||||
crate fn get_data_by_key(&self, name: &str) -> Option<&Value> {
|
||||
match self
|
||||
.entries
|
||||
.iter()
|
||||
.find(|(desc_name, _)| desc_name.name.is_string(name))
|
||||
{
|
||||
Some((_, v)) => MaybeOwned::Borrowed(v),
|
||||
None => MaybeOwned::Owned(Value::Primitive(Primitive::Nothing)),
|
||||
Some((_, v)) => Some(v),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
crate mod ast;
|
||||
crate mod completer;
|
||||
crate mod parser;
|
||||
crate mod registry;
|
||||
crate mod tokens;
|
||||
|
||||
crate use ast::{ParsedCommand, Pipeline};
|
||||
crate use registry::{CommandConfig, CommandRegistry};
|
||||
crate use tokens::{ParsedCommand, Pipeline};
|
||||
|
||||
use crate::errors::ShellError;
|
||||
use parser::PipelineParser;
|
||||
|
@ -1,4 +1,5 @@
|
||||
use derive_new::new;
|
||||
use getset::Getters;
|
||||
use std::str::FromStr;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
@ -40,25 +41,106 @@ impl FromStr for Operator {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||
pub enum Expression {
|
||||
Leaf(Leaf),
|
||||
Binary(Binary),
|
||||
Parenthesized(Box<Parenthesized>),
|
||||
Block(Box<Block>),
|
||||
Binary(Box<Binary>),
|
||||
Path(Box<Path>),
|
||||
VariableReference(Variable),
|
||||
}
|
||||
|
||||
impl Expression {
|
||||
crate fn print(&self) -> String {
|
||||
match self {
|
||||
Expression::Leaf(l) => l.print(),
|
||||
Expression::Parenthesized(p) => p.print(),
|
||||
Expression::Block(b) => b.print(),
|
||||
Expression::VariableReference(r) => r.print(),
|
||||
Expression::Path(p) => p.print(),
|
||||
Expression::Binary(b) => b.print(),
|
||||
}
|
||||
}
|
||||
|
||||
crate fn as_string(&self) -> Option<String> {
|
||||
match self {
|
||||
Expression::Leaf(Leaf::String(s)) | Expression::Leaf(Leaf::Bare(s)) => {
|
||||
Some(s.to_string())
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, new)]
|
||||
pub struct Block {
|
||||
crate expr: Expression,
|
||||
}
|
||||
|
||||
impl Block {
|
||||
fn print(&self) -> String {
|
||||
format!("{{ {} }}", self.expr.print())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, new)]
|
||||
pub struct Parenthesized {
|
||||
crate expr: Expression,
|
||||
}
|
||||
|
||||
impl Parenthesized {
|
||||
fn print(&self) -> String {
|
||||
format!("({})", self.expr.print())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Getters, new)]
|
||||
pub struct Path {
|
||||
#[get = "crate"]
|
||||
head: Expression,
|
||||
|
||||
#[get = "crate"]
|
||||
tail: Vec<String>,
|
||||
}
|
||||
|
||||
impl Path {
|
||||
crate fn print(&self) -> String {
|
||||
let mut out = self.head.print();
|
||||
|
||||
for item in self.tail.iter() {
|
||||
out.push_str(&format!(".{}", item));
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||
pub enum Variable {
|
||||
It,
|
||||
True,
|
||||
False,
|
||||
Other(String),
|
||||
}
|
||||
|
||||
impl Variable {
|
||||
fn print(&self) -> String {
|
||||
match self {
|
||||
Variable::It => format!("$it"),
|
||||
Variable::True => format!("$true"),
|
||||
Variable::False => format!("$false"),
|
||||
Variable::Other(s) => format!("${}", s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||
pub enum Leaf {
|
||||
String(String),
|
||||
Bare(String),
|
||||
|
||||
#[allow(unused)]
|
||||
Boolean(bool),
|
||||
Int(i64),
|
||||
}
|
||||
@ -74,11 +156,11 @@ impl Leaf {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, new)]
|
||||
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, new)]
|
||||
pub struct Binary {
|
||||
crate left: Leaf,
|
||||
crate left: Expression,
|
||||
crate operator: Operator,
|
||||
crate right: Leaf,
|
||||
crate right: Expression,
|
||||
}
|
||||
|
||||
impl Binary {
|
@ -1,6 +1,6 @@
|
||||
use std::str::FromStr;
|
||||
use crate::parser::tokens::*;
|
||||
use byte_unit::Byte;
|
||||
use crate::parser::ast::*;
|
||||
|
||||
grammar;
|
||||
|
||||
@ -10,12 +10,59 @@ pub Pipeline: Pipeline = {
|
||||
}
|
||||
|
||||
Command: ParsedCommand = {
|
||||
<command:RawBareWord> <expr:Expr*> => ParsedCommand::new(command, expr)
|
||||
<command:RawBareWord> <expr:Expr*> => ParsedCommand::new(command, expr),
|
||||
<command:RawBareWord> <expr:BinaryExpression> => ParsedCommand::new(command, vec![expr]),
|
||||
}
|
||||
|
||||
Leaf: Expression = {
|
||||
<String> => Expression::Leaf(Leaf::String(<>)),
|
||||
<Num> => Expression::Leaf(Leaf::Int(<>)),
|
||||
<Variable> => Expression::VariableReference(<>),
|
||||
}
|
||||
|
||||
BinaryExpression: Expression = {
|
||||
<left:Expr> <op:Operator> <right:Leaf> => Expression::Binary(Box::new(Binary::new(left, op, right))),
|
||||
}
|
||||
|
||||
Parenthesized: Expression = {
|
||||
"(" <Leaf> ")" => Expression::Parenthesized(Box::new(Parenthesized::new(<>))),
|
||||
"(" <BinaryExpression> ")" => Expression::Parenthesized(Box::new(Parenthesized::new(<>))),
|
||||
}
|
||||
|
||||
AtomicExpression: Expression = {
|
||||
<Parenthesized>,
|
||||
<Leaf>,
|
||||
}
|
||||
|
||||
Block: Expression = {
|
||||
"{" <AtomicExpression> "}" => Expression::Block(Box::new(Block::new(<>))),
|
||||
"{" <BinaryExpression> "}" => Expression::Block(Box::new(Block::new(<>))),
|
||||
}
|
||||
|
||||
WholeExpression: Expression = {
|
||||
<AtomicExpression>,
|
||||
<Block>,
|
||||
}
|
||||
|
||||
PathExpression: Expression = {
|
||||
<head:WholeExpression> <tail: ( "." <Member> )*> => Expression::Path(Box::new(Path::new(head, tail)))
|
||||
}
|
||||
|
||||
Expr: Expression = {
|
||||
<Leaf> => Expression::Leaf(<>),
|
||||
<Binary> => Expression::Binary(<>),
|
||||
<RawBareWord> => Expression::Leaf(Leaf::Bare(<>)),
|
||||
<PathExpression>
|
||||
}
|
||||
|
||||
Variable: Variable = {
|
||||
"$true" => Variable::True,
|
||||
"$false" => Variable::False,
|
||||
"$it" => Variable::It,
|
||||
"$" <RawBareWord> => Variable::Other(<>.to_string()),
|
||||
}
|
||||
|
||||
Member: String = {
|
||||
<RawBareWord>,
|
||||
<String>
|
||||
}
|
||||
|
||||
Operator: Operator = {
|
||||
@ -27,10 +74,6 @@ Operator: Operator = {
|
||||
">=" => Operator::GreaterThanOrEqual
|
||||
}
|
||||
|
||||
Binary: Binary = {
|
||||
<left:Leaf> <op:Operator> <right:Leaf> => Binary::new(left, op, right),
|
||||
}
|
||||
|
||||
Flag: Flag = {
|
||||
"-" <RawBareWord> => Flag::Shorthand(<>.to_string()),
|
||||
"--" <RawBareWord> => Flag::Longhand(<>.to_string()),
|
||||
@ -41,18 +84,7 @@ String: String = {
|
||||
DQString,
|
||||
}
|
||||
|
||||
Leaf: Leaf = {
|
||||
<String> => Leaf::String(<>),
|
||||
<Size> => Leaf::Int(<>),
|
||||
<Num> => Leaf::Int(<>),
|
||||
<RawBareWord> => match <>.as_ref() {
|
||||
"true" => Leaf::Boolean(true),
|
||||
"false" => Leaf::Boolean(false),
|
||||
_ => Leaf::Bare(<>),
|
||||
}
|
||||
}
|
||||
|
||||
RawBareWord: String = <s:r#"[^0-9"'\-][^\s]*"#> => <>.to_string();
|
||||
RawBareWord: String = <s:r#"[^0-9"'\-][^\s"']*"#> => <>.to_string();
|
||||
DQString: String = <s:r#""([^"]|\\")*""#> => s[1..s.len() - 1].to_string();
|
||||
SQString: String = <s:r#"'([^']|\\')*'"#> => s[1..s.len() - 1].to_string();
|
||||
Num: i64 = <s:r"-?[0-9]+"> => i64::from_str(s).unwrap();
|
||||
|
2146
src/parser/parser.rs
2146
src/parser/parser.rs
File diff suppressed because it is too large
Load Diff
@ -1,19 +1,110 @@
|
||||
use crate::evaluate::{evaluate_expr, Scope};
|
||||
use crate::prelude::*;
|
||||
use indexmap::IndexMap;
|
||||
|
||||
#[allow(unused)]
|
||||
pub enum CommandType {
|
||||
Switch,
|
||||
Single,
|
||||
Array,
|
||||
#[derive(Debug)]
|
||||
pub enum NamedType {
|
||||
Switch(String),
|
||||
Single(String),
|
||||
Array(String),
|
||||
Block(String),
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum PositionalType {
|
||||
Value(String),
|
||||
Block(String),
|
||||
}
|
||||
|
||||
impl PositionalType {
|
||||
crate fn name(&self) -> String {
|
||||
match self {
|
||||
PositionalType::Value(s) => s.clone(),
|
||||
PositionalType::Block(s) => s.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
crate fn evaluate(&self, arg: ast::Expression, scope: &Scope) -> Result<Value, ShellError> {
|
||||
match self {
|
||||
PositionalType::Value(_) => evaluate_expr(&arg, scope),
|
||||
PositionalType::Block(_) => match arg {
|
||||
ast::Expression::Block(b) => Ok(Value::block(b.expr)),
|
||||
ast::Expression::Binary(b) => {
|
||||
if let Some(s) = b.left.as_string() {
|
||||
Ok(Value::block(ast::Expression::Binary(Box::new(
|
||||
ast::Binary::new(
|
||||
ast::Expression::Path(Box::new(ast::Path::new(
|
||||
ast::Expression::VariableReference(ast::Variable::It),
|
||||
vec![s],
|
||||
))),
|
||||
b.operator,
|
||||
b.right,
|
||||
),
|
||||
))))
|
||||
} else {
|
||||
Ok(Value::block(ast::Expression::Binary(b)))
|
||||
}
|
||||
}
|
||||
other => Ok(Value::block(other)), // other =>
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CommandConfig {
|
||||
crate name: String,
|
||||
crate mandatory_positional: Vec<String>,
|
||||
crate optional_positional: Vec<String>,
|
||||
crate mandatory_positional: Vec<PositionalType>,
|
||||
crate optional_positional: Vec<PositionalType>,
|
||||
crate rest_positional: bool,
|
||||
crate named: IndexMap<String, CommandType>,
|
||||
crate named: IndexMap<String, NamedType>,
|
||||
}
|
||||
|
||||
impl CommandConfig {
|
||||
crate fn evaluate_args(
|
||||
&self,
|
||||
mut args: impl Iterator<Item = &'expr ast::Expression>,
|
||||
scope: &Scope,
|
||||
) -> Result<Vec<Value>, ShellError> {
|
||||
let mut results: Vec<Value> = vec![];
|
||||
|
||||
for param in &self.mandatory_positional {
|
||||
let arg = args.next();
|
||||
|
||||
let value = match arg {
|
||||
None => {
|
||||
return Err(ShellError::string(format!(
|
||||
"expected mandatory positional argument {}",
|
||||
param.name()
|
||||
)))
|
||||
}
|
||||
|
||||
Some(arg) => param.evaluate(arg.clone(), scope)?,
|
||||
};
|
||||
|
||||
results.push(value);
|
||||
}
|
||||
|
||||
if self.rest_positional {
|
||||
let rest: Result<Vec<Value>, _> =
|
||||
args.map(|i| evaluate_expr(i, &Scope::empty())).collect();
|
||||
results.extend(rest?);
|
||||
} else {
|
||||
match args.next() {
|
||||
None => {}
|
||||
Some(_) => return Err(ShellError::string("Too many arguments")),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
crate fn signature(&self) -> String {
|
||||
format!("TODO")
|
||||
}
|
||||
}
|
||||
|
||||
pub trait CommandRegistry {
|
||||
|
@ -5,6 +5,7 @@ crate use crate::env::host::handle_unexpected;
|
||||
crate use crate::env::{Environment, Host};
|
||||
crate use crate::errors::ShellError;
|
||||
crate use crate::object::Value;
|
||||
crate use crate::parser::ast;
|
||||
crate use crate::stream::{single_output, InputStream, OutputStream};
|
||||
crate use futures::{FutureExt, SinkExt, StreamExt};
|
||||
crate use std::collections::VecDeque;
|
||||
|
Loading…
Reference in New Issue
Block a user