diff --git a/src/commands.rs b/src/commands.rs index 4f4fb02edd..6e8a96d54c 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -5,7 +5,9 @@ crate mod ls; crate mod ps; crate mod reject; crate mod select; +crate mod skip; crate mod take; crate mod to_array; +crate mod where_; crate use to_array::to_array; diff --git a/src/commands/skip.rs b/src/commands/skip.rs new file mode 100644 index 0000000000..d085e5fd8e --- /dev/null +++ b/src/commands/skip.rs @@ -0,0 +1,29 @@ +use crate::errors::ShellError; +use crate::prelude::*; +use derive_new::new; + +#[derive(new)] +pub struct Skip; + +// TODO: "Amount remaining" wrapper + +impl crate::Command for Skip { + fn run(&self, args: CommandArgs<'caller>) -> Result, ShellError> { + let amount = args.args[0].as_int()?; + + let amount = if args.input.len() > amount as usize { + amount as usize + } else { + args.input.len() + }; + + let out: VecDeque = args + .input + .into_iter() + .skip(amount) + .map(|v| ReturnValue::Value(v)) + .collect(); + + Ok(out) + } +} diff --git a/src/commands/where_.rs b/src/commands/where_.rs new file mode 100644 index 0000000000..07e7da6949 --- /dev/null +++ b/src/commands/where_.rs @@ -0,0 +1,30 @@ +use crate::errors::ShellError; +use crate::object::base::find; +use crate::prelude::*; +use derive_new::new; + +#[derive(new)] +pub struct Where; + +impl crate::Command for Where { + fn run(&self, args: CommandArgs<'caller>) -> Result, ShellError> { + if args.args.is_empty() { + return Err(ShellError::string("select requires a field")); + } + + let field: Result = args.args[0].as_string(); + let field = field?; + + let op: Result = args.args[1].as_string(); + let op = op?; + + let objects = args + .input + .iter() + .filter(|item| find(&item, &field, &op, &args.args[2])) + .map(|item| ReturnValue::Value(item.copy())) + .collect(); + + Ok(objects) + } +} diff --git a/src/main.rs b/src/main.rs index 6eba367222..7d12b9707c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -59,10 +59,12 @@ fn main() -> Result<(), Box> { ("ps", Box::new(ps::Ps)), ("ls", Box::new(ls::Ls)), ("cd", Box::new(cd::Cd)), + ("skip", Box::new(skip::Skip)), ("take", Box::new(take::Take)), ("select", Box::new(select::Select)), ("reject", Box::new(reject::Reject)), ("to-array", Box::new(to_array::ToArray)), + ("where", Box::new(where_::Where)) ]); } @@ -73,6 +75,9 @@ fn main() -> Result<(), Box> { )); match readline { + Ok(ref line) if line.trim() == "exit" => { + break; + } Ok(line) => { let result = crate::parser::shell_parser(&line) .map_err(|e| ShellError::string(format!("{:?}", e)))?; diff --git a/src/object/base.rs b/src/object/base.rs index 676d35f291..3720407cac 100644 --- a/src/object/base.rs +++ b/src/object/base.rs @@ -120,6 +120,17 @@ impl Value { } } + crate fn as_bool(&self) -> Result { + match self { + Value::Primitive(Primitive::Boolean(b)) => Ok(*b), + // TODO: this should definitely be more general with better errors + other => Err(ShellError::string(format!( + "Expected integer, got {:?}", + other + ))), + } + } + crate fn string(s: impl Into) -> Value { Value::Primitive(Primitive::String(s.into())) } @@ -132,6 +143,10 @@ impl Value { Value::Primitive(Primitive::Int(s.into())) } + crate fn bool(s: impl Into) -> Value { + Value::Primitive(Primitive::Boolean(s.into())) + } + #[allow(unused)] crate fn system_date(s: SystemTime) -> Value { Value::Primitive(Primitive::Date(s.into())) @@ -188,3 +203,86 @@ crate fn reject(obj: &Value, fields: &[String]) -> crate::object::Dictionary { out } + +crate fn find(obj: &Value, field: &str, op: &str, rhs: &Value) -> bool { + let descs = obj.data_descriptors(); + match descs.iter().find(|d| d.name == *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) { + ("-eq", Value::Primitive(Primitive::Boolean(b2))) => { + b == *b2 + } + ("-ne", Value::Primitive(Primitive::Boolean(b2))) => { + b != *b2 + } + _ => false + } + } + Value::Primitive(Primitive::Bytes(i)) => { + match (op, rhs) { + ("-lt", Value::Primitive(Primitive::Int(i2))) => { + i < (*i2 as u128) + } + ("-gt", Value::Primitive(Primitive::Int(i2))) => { + i > (*i2 as u128) + } + ("-le", Value::Primitive(Primitive::Int(i2))) => { + i <= (*i2 as u128) + } + ("-ge", Value::Primitive(Primitive::Int(i2))) => { + i >= (*i2 as u128) + } + ("-eq", Value::Primitive(Primitive::Int(i2))) => { + i == (*i2 as u128) + } + ("-ne", Value::Primitive(Primitive::Int(i2))) => { + i != (*i2 as u128) + } + _ => false + } + } + Value::Primitive(Primitive::Int(i)) => { + match (op, rhs) { + ("-lt", Value::Primitive(Primitive::Int(i2))) => { + i < *i2 + } + ("-gt", Value::Primitive(Primitive::Int(i2))) => { + i > *i2 + } + ("-le", Value::Primitive(Primitive::Int(i2))) => { + i <= *i2 + } + ("-ge", Value::Primitive(Primitive::Int(i2))) => { + i >= *i2 + } + ("-eq", Value::Primitive(Primitive::Int(i2))) => { + i == *i2 + } + ("-ne", Value::Primitive(Primitive::Int(i2))) => { + i != *i2 + } + _ => false + } + } + Value::Primitive(Primitive::String(s)) => { + match (op, rhs) { + ("-eq", Value::Primitive(Primitive::String(s2))) => { + s == *s2 + } + ("-ne", Value::Primitive(Primitive::String(s2))) => { + s != *s2 + } + _ => false + } + } + _ => false, + } + } + } +} diff --git a/src/parser/parse.rs b/src/parser/parse.rs index b2c68a899a..40e40198ee 100644 --- a/src/parser/parse.rs +++ b/src/parser/parse.rs @@ -13,6 +13,7 @@ pub enum Item { Quoted(String), Bare(String), Int(i64), + Boolean(bool), } impl Item { @@ -21,6 +22,7 @@ impl Item { Item::Quoted(s) => Value::Primitive(Primitive::String(s.clone())), Item::Bare(s) => Value::Primitive(Primitive::String(s.clone())), Item::Int(i) => Value::Primitive(Primitive::Int(*i)), + Item::Boolean(b) => Value::Primitive(Primitive::Boolean(*b)), } } } @@ -30,6 +32,7 @@ crate fn print_items(items: &[Item]) -> String { Item::Bare(s) => format!("{}", s), Item::Quoted(s) => format!("{:?}", s), Item::Int(i) => format!("{:?}", i), + Item::Boolean(b) => format!("{:?}", b), }); itertools::join(formatted, " ") @@ -40,7 +43,7 @@ impl Item { match self { Item::Quoted(s) => s, Item::Bare(s) => s, - Item::Int(_) => unimplemented!(), + Item::Boolean(_) | Item::Int(_) => unimplemented!(), } } } @@ -62,8 +65,13 @@ fn int(s: &str) -> IResult<&str, Item> { is_a("1234567890")(s).map(|(a, b)| (a, Item::Int(FromStr::from_str(b).unwrap()))) } +fn boolean(s: &str) -> IResult<&str, Item> { + alt((tag("true"), tag("false")))(s) + .map(|(a, b)| (a, Item::Boolean(FromStr::from_str(b).unwrap()))) +} + fn command_token(s: &str) -> IResult<&str, Item> { - alt((int, quoted, unquoted))(s) + alt((boolean, int, quoted, unquoted))(s) } fn command_args(s: &str) -> IResult<&str, Vec> {