summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthias Beyer <mail@beyermatthias.de>2018-05-03 10:22:20 +0200
committerGitHub <noreply@github.com>2018-05-03 10:22:20 +0200
commit9c36fc8ac010e41af1cee51404265f034df4ca57 (patch)
treec171976882360afb5feabbede9e0dcb34913ea8b
parent33b52cc928a4fc443bc044c4b14b6c32fb17cf6c (diff)
parent846de028cf46c38e50dcd4e5333a6477a1d39025 (diff)
downloadimag-9c36fc8ac010e41af1cee51404265f034df4ca57.zip
imag-9c36fc8ac010e41af1cee51404265f034df4ca57.tar.gz
Merge pull request #1468 from matthiasbeyer/imag-ids/filters
imag-ids: filters
-rw-r--r--bin/core/imag-ids/Cargo.toml10
-rw-r--r--bin/core/imag-ids/src/id_filters.rs740
-rw-r--r--bin/core/imag-ids/src/main.rs60
-rw-r--r--bin/core/imag-ids/src/ui.rs13
-rw-r--r--bin/core/imag-ids/static/language-doc.md51
5 files changed, 855 insertions, 19 deletions
diff --git a/bin/core/imag-ids/Cargo.toml b/bin/core/imag-ids/Cargo.toml
index d1874b5..8619e55 100644
--- a/bin/core/imag-ids/Cargo.toml
+++ b/bin/core/imag-ids/Cargo.toml
@@ -22,7 +22,12 @@ is-it-maintained-open-issues = { repository = "matthiasbeyer/imag" }
maintenance = { status = "actively-developed" }
[dependencies]
-filters = "0.3"
+filters = "0.3"
+nom = "3.2"
+log = "0.4"
+toml = "0.4"
+toml-query = "0.6"
+is-match = "0.1"
libimagstore = { version = "0.8.0", path = "../../../lib/core/libimagstore" }
libimagrt = { version = "0.8.0", path = "../../../lib/core/libimagrt" }
@@ -33,3 +38,6 @@ version = "^2.29"
default-features = false
features = ["color", "suggestions", "wrap_help"]
+[dev-dependencies]
+env_logger = "0.5"
+
diff --git a/bin/core/imag-ids/src/id_filters.rs b/bin/core/imag-ids/src/id_filters.rs
new file mode 100644
index 0000000..537e23a
--- /dev/null
+++ b/bin/core/imag-ids/src/id_filters.rs
@@ -0,0 +1,740 @@
+//
+// imag - the personal information management suite for the commandline
+// Copyright (C) 2015-2018 Matthias Beyer <mail@beyermatthias.de> and contributors
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; version
+// 2.1 of the License.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+//
+
+use filters::filter::Filter;
+
+use libimagstore::storeid::StoreId;
+
+pub struct IsInCollectionsFilter<'a, A>(Option<A>, ::std::marker::PhantomData<&'a str>)
+ where A: AsRef<[&'a str]>;
+
+impl<'a, A> IsInCollectionsFilter<'a, A>
+ where A: AsRef<[&'a str]>
+{
+ pub fn new(collections: Option<A>) -> Self {
+ IsInCollectionsFilter(collections, ::std::marker::PhantomData)
+ }
+}
+
+impl<'a, A> Filter<StoreId> for IsInCollectionsFilter<'a, A>
+ where A: AsRef<[&'a str]> + 'a
+{
+ fn filter(&self, sid: &StoreId) -> bool {
+ match self.0 {
+ Some(ref colls) => sid.is_in_collection(colls),
+ None => true,
+ }
+ }
+}
+
+/// Language definition for the header-filter language
+pub mod header_filter_lang {
+ use std::str;
+ use std::str::FromStr;
+ use std::process::exit;
+
+ use nom::digit;
+ use nom::multispace;
+
+ use libimagstore::store::Entry;
+ use libimagerror::trace::MapErrTrace;
+
+ #[derive(Debug, PartialEq, Eq)]
+ enum Unary {
+ Not
+ }
+
+ named!(unary_operator<Unary>, alt_complete!(
+ tag!("not") => { |_| { trace!("Unary::Not"); Unary::Not }}
+ ));
+
+ #[derive(Debug, PartialEq, Eq)]
+ enum CompareOp {
+ OpIs,
+ OpIn,
+ OpEq,
+ OpNeq,
+ OpGte, // >=
+ OpLte, // <=
+ OpLt, // <
+ OpGt, // >
+ }
+
+ named!(compare_op<CompareOp>, alt_complete!(
+ tag!("is" ) => { |_| { trace!("CompareOp::OpIs"); CompareOp::OpIs }} |
+ tag!("in" ) => { |_| { trace!("CompareOp::OpIn"); CompareOp::OpIn }} |
+ tag!("==" ) => { |_| { trace!("CompareOp::OpEq"); CompareOp::OpEq }} |
+ tag!("eq" ) => { |_| { trace!("CompareOp::OpEq"); CompareOp::OpEq }} |
+ tag!("!=" ) => { |_| { trace!("CompareOp::OpNeq"); CompareOp::OpNeq }} |
+ tag!("neq") => { |_| { trace!("CompareOp::OpNeq"); CompareOp::OpNeq }} |
+ tag!(">=" ) => { |_| { trace!("CompareOp::OpGte"); CompareOp::OpGte }} |
+ tag!("<=" ) => { |_| { trace!("CompareOp::OpLte"); CompareOp::OpLte }} |
+ tag!("<" ) => { |_| { trace!("CompareOp::OpLt"); CompareOp::OpLt }} |
+ tag!(">" ) => { |_| { trace!("CompareOp::OpGt"); CompareOp::OpGt }}
+ ));
+
+ #[derive(Debug, PartialEq, Eq)]
+ enum Operator {
+ Or,
+ And,
+ Xor,
+ }
+
+ named!(operator<Operator>, alt_complete!(
+ tag!("or") => { |_| { trace!("Operator::Or"); Operator::Or }} |
+ tag!("and") => { |_| { trace!("Operator::And"); Operator::And }} |
+ tag!("xor") => { |_| { trace!("Operator::Xor"); Operator::Xor }}
+ ));
+
+ #[derive(Debug, PartialEq, Eq)]
+ enum Function {
+ Length,
+ Keys,
+ Values,
+ }
+
+ named!(function<Function>, alt_complete!(
+ tag!("length") => { |_| { trace!("Function::Length"); Function::Length }} |
+ tag!("keys") => { |_| { trace!("Function::Keys"); Function::Keys }} |
+ tag!("values") => { |_| { trace!("Function::Values"); Function::Values }}
+ ));
+
+ #[derive(Debug, PartialEq, Eq)]
+ enum Value {
+ Boolean(bool),
+ Integer(i64),
+ String(String),
+ }
+
+ named!(int64<i64>, map!(digit, |r: &[u8]| {
+ let val = str::from_utf8(r).unwrap_or_else(|e| {
+ error!("Error = '{:?}'", e);
+ ::std::process::exit(1)
+ });
+
+ i64::from_str(val).unwrap_or_else(|e| {
+ error!("Error while parsing number: '{:?}'", e);
+ ::std::process::exit(1)
+ })
+ }));
+
+ named!(signed_digits<(Option<&[u8]>, i64)>,
+ pair!(opt!(alt!(tag_s!("+") | tag_s!("-"))), int64)
+ );
+ named!(integer<i64>, do_parse!(tpl: signed_digits >> ({
+ let v = match tpl.0 {
+ Some(b"-") => -tpl.1,
+ _ => tpl.1,
+ };
+ trace!("integer = {:?}", v);
+ v
+ })));
+
+ named!(boolean<bool>, alt_complete!(
+ tag!("false") => { |_| { trace!("'false'"); false }} |
+ tag!("true") => { |_| { trace!("'true'"); true }}
+ ));
+
+ named!(string<String>, do_parse!(
+ text: delimited!(char!('"'), take_until!("\""), char!('"'))
+ >> ({
+ let s = String::from_utf8(text.to_vec()).unwrap();
+ trace!("Parsed string: {:?}", s);
+ s
+ })
+ ));
+
+ named!(val<Value>, alt_complete!(
+ do_parse!(b: boolean >> ({
+ let v = Value::Boolean(b);
+ trace!("Value = {:?}", v);
+ v
+ })) |
+ do_parse!(number: integer >> ({
+ let v = Value::Integer(number);
+ trace!("Value = {:?}", v);
+ v
+ })) |
+ do_parse!(text: string >> ({
+ let v = Value::String(text);
+ trace!("Value = {:?}", v);
+ v
+ }))
+ ));
+
+ named!(list_of_val<Vec<Value>>, do_parse!(
+ char!('[') >>
+ list: many0!(
+ do_parse!(
+ list: terminated!(val, opt!(char!(','))) >>
+ opt!(multispace) >>
+ (list)
+ )) >>
+ char!(']') >> (list)
+ ));
+
+ #[derive(Debug, PartialEq, Eq)]
+ enum CompareValue {
+ Value(Value),
+ Values(Vec<Value>)
+ }
+
+ named!(compare_value<CompareValue>, alt_complete!(
+ do_parse!(list: list_of_val >> (CompareValue::Values(list))) |
+ do_parse!(val: val >> (CompareValue::Value(val)))
+ ));
+
+ #[derive(Debug, PartialEq, Eq)]
+ enum Selector {
+ Direct(String),
+ Function(Function, String)
+ }
+
+ impl Selector {
+ fn selector_str(&self) -> &String {
+ match *self {
+ Selector::Direct(ref s) => s,
+ Selector::Function(_, ref s) => s,
+ }
+ }
+ fn function(&self) -> Option<&Function> {
+ match *self {
+ Selector::Direct(_) => None,
+ Selector::Function(ref f, _) => Some(f),
+ }
+ }
+ }
+
+ named!(selector_str<String>, do_parse!(
+ selector: take_till!(|s: u8| s == b' ') >> (String::from_utf8(selector.to_vec()).unwrap())
+ ));
+
+ named!(bracketed,
+ delimited!(
+ tag!("("),
+ take_until!(")"),
+ tag!(")")
+ )
+ );
+
+ named!(selector<Selector>, alt_complete!(
+ do_parse!(fun: function >> sel: bracketed >> ({
+ let sel = Selector::Function(fun, String::from_utf8(sel.to_vec()).unwrap());
+ trace!("Building Selector object: {:?}", sel);
+ sel
+ })) |
+ do_parse!(sel: selector_str >> ({
+ let sel = Selector::Direct(sel);
+ trace!("Building Selector object: {:?}", sel);
+ sel
+ }))
+ ));
+
+ #[derive(Debug, PartialEq, Eq)]
+ struct Filter {
+ unary : Option<Unary>,
+ selector : Selector,
+ compare_operator : CompareOp,
+ compare_value : CompareValue,
+ }
+
+ named!(filter<Filter>, do_parse!(
+ unary: opt!(unary_operator) >>
+ selec: selector >> opt!(multispace) >>
+ comop: compare_op >> opt!(multispace) >>
+ cmval: compare_value >>
+ ({
+ let f = Filter {
+ unary: unary,
+ selector: selec,
+ compare_operator: comop,
+ compare_value: cmval,
+ };
+
+ trace!("Building Filter object: {:?}", f);
+ f
+ })
+ ));
+
+ #[derive(Debug, PartialEq, Eq)]
+ pub struct Query {
+ filter: Filter,
+ next_filters: Vec<(Operator, Filter)>,
+ }
+
+ named!(parse_query<Query>, do_parse!(
+ filt: filter >>
+ next: many0!(do_parse!(opt!(multispace) >> op: operator >> opt!(multispace) >> fil: filter >> ((op, fil)))) >>
+ ({
+ let q = Query {
+ filter: filt,
+ next_filters: next,
+ };
+
+ trace!("Building Query object: {:?}", q);
+
+ q
+ })
+ ));
+
+ /// Helper type which can filters::filter::Filter be implemented on so that the implementation
+ /// of ::filters::filter::Filter on self::Filter is less complex.
+ struct Comparator<'a>(&'a CompareOp, &'a CompareValue);
+
+ impl<'a> ::filters::filter::Filter<::toml::Value> for Comparator<'a> {
+ fn filter(&self, val: &::toml::Value) -> bool {
+ use self::CompareValue as CV;
+ use self::CompareOp as CO;
+ use toml::Value as TVal;
+
+ match *self.0 {
+ CO::OpIs => match self.1 {
+ &CV::Values(_) => error_exit("Cannot check whether a header field is the same type as mulitple values!"),
+ &CV::Value(ref v) => {
+ trace!("Checking whether {:?} and {:?} have same type", v, val);
+ match v {
+ &Value::Boolean(_) => is_match!(*val, TVal::Boolean(_)),
+ &Value::Integer(_) => is_match!(*val, TVal::Integer(_)),
+ &Value::String(_) => is_match!(val, &TVal::String(_)),
+ }
+ },
+ },
+ CO::OpIn => {
+ trace!("Checking whether {:?} is in {:?}", self.1, val);
+ match (self.1, val) {
+ (&CV::Value(Value::Boolean(i)), &TVal::Boolean(j)) => i == j,
+ (&CV::Value(Value::Integer(i)), &TVal::Integer(j)) => i == j,
+ (&CV::Value(Value::String(ref s)), &TVal::String(ref b)) => s.contains(b),
+ (&CV::Value(_), _) => false,
+
+ (&CV::Values(ref v), &TVal::Integer(j)) => v.iter().any(|e| match e {
+ &Value::Integer(i) => i == j,
+ _ => false
+ }),
+ (&CV::Values(ref v), &TVal::String(ref b)) => v.iter().any(|e| match e {
+ &Value::String(ref s) => s == b,
+ _ => false
+ }),
+ (&CV::Values(_), _) => false,
+ }
+ },
+ CO::OpEq => {
+ trace!("Checking whether {:?} == {:?}", self.1, val);
+ match (self.1, val) {
+ (&CV::Value(Value::Boolean(i)), &TVal::Boolean(j)) => i == j,
+ (&CV::Value(Value::Integer(i)), &TVal::Integer(j)) => i == j,
+ (&CV::Value(Value::String(ref s)), &TVal::String(ref b)) => s == b,
+ (&CV::Value(_), _) => false,
+ (&CV::Values(_), _) => error_exit("Cannot check a header field for equality to multiple header fields!"),
+ }
+ },
+ CO::OpNeq => {
+ trace!("Checking whether {:?} != {:?}", self.1, val);
+ match (self.1, val) {
+ (&CV::Value(Value::Boolean(i)), &TVal::Boolean(j)) => i != j,
+ (&CV::Value(Value::Integer(i)), &TVal::Integer(j)) => i != j,
+ (&CV::Value(Value::String(ref s)), &TVal::String(ref b)) => s != b,
+ (&CV::Value(_), _) => false,
+ (&CV::Values(_), _) => error_exit("Cannot check a header field for inequality to multiple header fields!"),
+ }
+ },
+ CO::OpGte => {
+ trace!("Checking whether {:?} >= {:?}", self.1, val);
+ match (self.1, val) {
+ (&CV::Value(Value::Integer(i)), &TVal::Integer(j)) => i >= j,
+ (&CV::Value(_), _) => false,
+ (&CV::Values(_), _) => error_exit("Cannot check a header field for greater_than_equal to multiple header fields!"),
+ }
+ },
+ CO::OpLte => {
+ trace!("Checking whether {:?} <= {:?}", self.1, val);
+ match (self.1, val) {
+ (&CV::Value(Value::Integer(i)), &TVal::Integer(j)) => i <= j,
+ (&CV::Value(_), _) => false,
+ (&CV::Values(_), _) => error_exit("Cannot check a header field for lesser_than_equal to multiple header fields!"),
+ }
+ },
+ CO::OpLt => {
+ trace!("Checking whether {:?} < {:?}", self.1, val);
+ match (self.1, val) {
+ (&CV::Value(Value::Integer(i)), &TVal::Integer(j)) => i < j,
+ (&CV::Value(_), _) => false,
+ (&CV::Values(_), _) => error_exit("Cannot check a header field for lesser_than to multiple header fields!"),
+ }
+ },
+ CO::OpGt => {
+ trace!("Checking whether {:?} > {:?}", self.1, val);
+ match (self.1, val) {
+ (&CV::Value(Value::Integer(i)), &TVal::Integer(j)) => i > j,
+ (&CV::Value(_), _) => false,
+ (&CV::Values(_), _) => {
+ error!("Cannot check a header field for greater_than to multiple header fields!");
+ exit(1)
+ },
+ }
+ },
+ }
+ }
+ }
+
+ impl ::filters::filter::Filter<Entry> for Filter {
+ fn filter(&self, entry: &Entry) -> bool {
+ use toml_query::read::TomlValueReadExt;
+
+ let selector_str = self.selector.selector_str();
+ trace!("Filtering {} at {}", entry.get_location(), selector_str);
+
+ entry
+ .get_header()
+ .read(selector_str)
+ .map_err_trace_exit_unwrap(1)
+ .map(|value| {
+ let comp = Comparator(&self.compare_operator, &self.compare_value);
+ let val = match self.selector.function() {
+ None => {
+ ::filters::filter::Filter::filter(&comp, value)
+ }
+ Some(func) => {
+ match *func {
+ Function::Length => {
+ let val = match value {
+ &::toml::Value::Array(ref a) => a.len() as i64,
+ &::toml::Value::String(ref s) => s.len() as i64,
+ _ => 1
+ };
+ let val = ::toml::Value::Integer(val);
+ ::filters::filter::Filter::filter(&comp, &val)
+ },
+ Function::Keys => {
+ let keys = match value {
+ &::toml::Value::Table(ref tab) => tab
+ .keys()
+ .cloned()
+ .map(::toml::Value::String)
+ .collect(),
+ _ => return false,
+ };
+ let keys = ::toml::Value::Array(keys);
+ ::filters::filter::Filter::filter(&comp, &keys)
+ },
+ Function::Values => {
+ let vals = match value {
+ &::toml::Value::Table(ref tab) => tab
+ .values()
+ .cloned()
+ .collect(),
+ _ => return false,
+ };
+ let vals = ::toml::Value::Array(vals);
+ ::filters::filter::Filter::filter(&comp, &vals)
+ },
+ }
+ }
+ };
+
+ match self.unary {
+ Some(Unary::Not) => !val,
+ _ => val
+ }
+ })
+ .unwrap_or(false)
+ }
+ }
+
+ impl ::filters::filter::Filter<Entry> for Query {
+
+ fn filter(&self, entry: &Entry) -> bool {
+ trace!("Filtering = {}", entry.get_location());
+ let mut res = self.filter.filter(entry);
+ trace!("First filter = {}", res);
+
+ for &(ref operator, ref next) in self.next_filters.iter() {
+ match *operator {
+ Operator::Or => {
+ trace!("Operator = {} OR {:?}", res, next);
+ res = res || ::filters::filter::Filter::filter(next, entry);
+ },
+ Operator::And => {
+ trace!("Operator = {} AND {:?}", res, next);
+ res = res && ::filters::filter::Filter::filter(next, entry);
+ },
+ Operator::Xor => {
+ trace!("Operator = {} XOR {:?}", res, next);
+ let other = ::filters::filter::Filter::filter(next, entry);
+ res = (res && !other) || (!res && other);
+ },
+ }
+ trace!("After applying next filter = {}", res);
+ }
+
+ res
+ }
+
+ }
+
+ fn error_exit(s: &'static str) -> ! {
+ error!("{}", s);
+ exit(1)
+ }
+
+ pub fn parse(s: &str) -> Query {
+ match parse_query(s.as_bytes()) {
+ ::nom::IResult::Done(_i, o) => o,
+ ::nom::IResult::Error(e) => {
+ error!("Error during parsing the query");
+ error!("Error = {:?}", e);
+ ::std::process::exit(1)
+ },
+ ::nom::IResult::Incomplete(needed) => {
+ error!("Error during parsing the query. Incomplete input.");
+ error!("Needed = {:?}", needed);
+ ::std::process::exit(1)
+ },
+ }
+ }
+
+ #[cfg(test)]
+ mod tests {
+ use super::*;
+
+ fn setup_logging() {
+ let _ = ::env_logger::try_init();
+ }
+
+ #[test]
+ fn test_unary() {
+ assert_eq!(unary_operator(b"not").unwrap().1, Unary::Not);
+ }
+
+ #[test]
+ fn test_compare_op() {
+ assert_eq!(compare_op(b"is" ).unwrap().1, CompareOp::OpIs );
+ assert_eq!(compare_op(b"in" ).unwrap().1, CompareOp::OpIn );
+ assert_eq!(compare_op(b"==" ).unwrap().1, CompareOp::OpEq );
+ assert_eq!(compare_op(b"eq" ).unwrap().1, CompareOp::OpEq );
+ assert_eq!(compare_op(b"!=" ).unwrap().1, CompareOp::OpNeq);
+ assert_eq!(compare_op(b"neq" ).unwrap().1, CompareOp::OpNeq);
+ assert_eq!(compare_op(b">=" ).unwrap().1, CompareOp::OpGte);
+ assert_eq!(compare_op(b"<=" ).unwrap().1, CompareOp::OpLte);
+ assert_eq!(compare_op(b"<" ).unwrap().1, CompareOp::OpLt );
+ assert_eq!(compare_op(b">" ).unwrap().1, CompareOp::OpGt );
+ }
+
+ #[test]
+ fn test_operator() {
+ assert_eq!(operator(b"or").unwrap().1, Operator::Or );
+ assert_eq!(operator(b"and").unwrap().1, Operator::And );
+ assert_eq!(operator(b"xor").unwrap().1, Operator::Xor );
+ }
+
+ #[test]
+ fn test_function() {
+ assert_eq!(function(b"length").unwrap().1, Function::Length );
+ assert_eq!(function(b"keys").unwrap().1, Function::Keys );
+ assert_eq!(function(b"values").unwrap().1, Function::Values );
+ }
+
+ #[test]
+ fn test_integer() {
+ assert_eq!(integer(b"12").unwrap().1, 12);
+ assert_eq!(integer(b"11292").unwrap().1, 11292);
+ assert_eq!(integer(b"-12").unwrap().1, -12);
+ assert_eq!(integer(b"10101012").unwrap().1, 10101012);
+ }
+
+ #[test]
+ fn test_string() {
+ assert_eq!(string(b"\"foo\"").unwrap().1, "foo");
+ }
+
+ #[test]
+ fn test_boolean() {
+ assert_eq!(boolean(b"false").unwrap().1, false);
+ assert_eq!(boolean(b"true").unwrap().1, true);
+ }
+
+ #[test]
+ fn test_val() {
+ assert_eq!(val(b"false").unwrap().1, Value::Boolean(false));
+ assert_eq!(val(b"true").unwrap().1, Value::Boolean(true));
+ assert_eq!(val(b"12").unwrap().1, Value::Integer(12));
+ assert_eq!(val(b"\"foobar\"").unwrap().1, Value::String(String::from("foobar")));
+ }
+
+ #[test]
+ fn test_list_of_val() {
+ {
+ let list = list_of_val(b"[]");
+ println!("list: {:?}", list);
+ let vals = list.unwrap().1;
+ assert_eq!(vals, vec![]);
+ }
+
+ {
+ let list = list_of_val(b"[1]");
+ println!("list: {:?}", list);
+ let vals = list.unwrap().1;
+ assert_eq!(vals, vec![Value::Integer(1)]);
+ }
+
+ {
+ let list = list_of_val(b"[12,13]");
+ println!("list: {:?}", list);
+ let vals = list.unwrap().1;
+ assert_eq!(vals, vec![Value::Integer(12), Value::Integer(13)]);
+ }
+
+ {
+ let vals = list_of_val(b"[\"foobar\",\"bazbaz\"]").unwrap().1;
+ let expt = vec![Value::String(String::from("foobar")),
+ Value::String(String::from("bazbaz"))];
+ assert_eq!(vals, expt)
+ }
+
+ {
+ let vals = list_of_val(b"[\"1\", \"2\"]").unwrap().1;
+ let expt = vec![Value::String(String::from("1")),
+ Value::String(String::from("2"))];
+ assert_eq!(vals, expt)
+ }
+ }
+
+ #[test]
+ fn test_selector_str() {
+ assert_eq!(selector_str(b"foo.bar baz").unwrap().1, String::from("foo.bar"));
+ }
+
+ #[test]
+ fn test_selector() {
+ assert_eq!(selector(b"foo.bar baz").unwrap().1, Selector::Direct(String::from("foo.bar")));
+
+ assert_eq!(function(b"length").unwrap().1, Function::Length);
+
+ let exp = Selector::Function(Function::Length, String::from("foo.bar"));
+ assert_eq!(selector(b"length(foo.bar)").unwrap().1, exp);
+ }
+
+ #[test]
+ fn test_filter_1() {
+ setup_logging();
+ trace!("Setup worked");
+ let text = b"imag.header == 1";
+ let exp = Filter {
+ unary: None,
+ selector: Selector::Direct(String::from("imag.header")),
+ compare_operator: CompareOp::OpEq,
+ compare_value: CompareValue::Value(Value::Integer(1))
+ };
+
+ let parsed = filter(text);
+ trace!("{:?}", parsed);
+ assert_eq!(parsed.unwrap().1, exp);
+ }
+
+ #[test]
+ fn test_filter_2() {
+ setup_logging();
+ trace!("Setup worked");
+ let text = b"imag.header in [1, 2]";
+ let exp = Filter {
+ unary: None,
+ selector: Selector::Direct(String::from("imag.header")),
+ compare_operator: CompareOp::OpIn,
+ compare_value: CompareValue::Values(vec![Value::Integer(1), Value::Integer(2)])
+ };
+
+ let parsed = filter(text);
+ trace!("{:?}", parsed);
+ assert_eq!(parsed.unwrap().1, exp);
+ }
+
+ #[test]
+ fn test_filter_3() {
+ setup_logging();
+ trace!("Setup worked");
+ let text = b"length(imag.header) > 12";
+ let exp = Filter {
+ unary: None,
+ selector: Selector::Function(Function::Length, String::from("imag.header")),
+ compare_operator: CompareOp::OpGt,
+ compare_value: CompareValue::Value(Value::Integer(12))
+ };
+
+ let parsed = filter(text);
+ trace!("{:?}", parsed);
+ assert_eq!(parsed.unwrap().1, exp);
+ }
+
+ #[test]
+ fn test_query_1() {
+ setup_logging();
+ trace!("Setup worked");
+ let text = b"length(imag.header) > 12 or imag.foobar <= 125";
+
+ let filter_1 = Filter {
+ unary: None,
+ selector: Selector::Function(Function::Length, String::from("imag.header")),
+ compare_operator: CompareOp::OpGt,
+ compare_value: CompareValue::Value(Value::Integer(12))
+ };
+
+ let filter_2 = Filter {
+ unary: None,
+ selector: Selector::Direct(String::from("imag.foobar")),
+ compare_operator: CompareOp::OpLte,
+ compare_value: CompareValue::Value(Value::Integer(125))
+ };
+
+ let operator = Operator::Or;
+
+ let query = Query {
+ filter: filter_1,
+ next_filters: vec![(operator, filter_2)],
+ };
+
+ let parsed = parse_query(text);
+ trace!("{:?}", parsed);
+ assert_eq!(parsed.unwrap().1, query);
+ }
+
+ #[test]
+ fn test_query_2() {
+ setup_logging();
+ trace!("Setup worked");
+ let text = r#"imag.version == "0.7.0""#;
+
+ let filter_1 = Filter {
+ unary: None,
+ selector: Selector::Direct(String::from("imag.version")),
+ compare_operator: CompareOp::OpEq,
+ compare_value: CompareValue::Value(Value::String(String::from("0.7.0")))
+ };
+
+ let query = Query {
+ filter: filter_1,
+ next_filters: vec![],
+ };
+
+ let parsed = parse_query(text.as_bytes());
+ trace!("{:?}", parsed);
+ assert_eq!(parsed.unwrap().1, query);
+ }
+ }
+}
+
diff --git a/bin/core/imag-ids/src/main.rs b/bin/core/imag-ids/src/main.rs
index 240a03e..c8da3ce 100644
--- a/bin/core/imag-ids/src/main.rs
+++ b/bin/core/imag-ids/src/main.rs
@@ -34,12 +34,21 @@
extern crate clap;
extern crate filters;
+#[macro_use] extern crate nom;
+#[macro_use] extern crate log;
+#[macro_use] extern crate is_match;
+extern crate toml;
+extern crate toml_query;
+
+#[cfg(test)]
+extern crate env_logger;
extern crate libimagerror;
extern crate libimagstore;
#[macro_use] extern crate libimagrt;
use std::io::Write;
+use std::process::exit;
use filters::filter::Filter;
@@ -48,25 +57,12 @@ use libimagerror::trace::MapErrTrace;
use libimagerror::iter::TraceIterator;
use libimagerror::exit::ExitUnwrap;
use libimagerror::io::ToExitCode;
-use libimagstore::storeid::StoreId;
+mod id_filters;
mod ui;
-use ui::build_ui;
-
-pub struct IsInCollectionsFilter<'a, A>(Option<A>, ::std::marker::PhantomData<&'a str>)
- where A: AsRef<[&'a str]>;
-
-impl<'a, A> Filter<StoreId> for IsInCollectionsFilter<'a, A>
- where A: AsRef<[&'a str]> + 'a
-{
- fn filter(&self, sid: &StoreId) -> bool {
- match self.0 {
- Some(ref colls) => sid.is_in_collection(colls),
- None => true,
- }
- }
-}
+use ui::build_ui;
+use id_filters::IsInCollectionsFilter;
fn main() {
let version = make_imag_version!();
@@ -82,13 +78,43 @@ fn main() {
.values_of("in-collection-filter")
.map(|v| v.collect::<Vec<&str>>());
- let collection_filter = IsInCollectionsFilter(values, ::std::marker::PhantomData);
+ let collection_filter = IsInCollectionsFilter::new(values);
+ let query_filter : Option<id_filters::header_filter_lang::Query> = rt
+ .cli()
+ .subcommand_matches("where")
+ .map(|matches| {
+ let query = matches.value_of("where-filter").unwrap(); // safe by clap
+ id_filters::header_filter_lang::parse(&query)
+ });
rt.store()
.entries()
.map_err_trace_exit_unwrap(1)
.trace_unwrap_exit(1)
+ .enumerate()
+ .map(|(i, e)| {
+ if i % 100 == 0 {
+ let _ = rt.store().flush_cache();
+ }
+
+ e
+ })
.filter(|id| collection_filter.filter(id))
+ .filter(|id| match query_filter.as_ref() {
+ None => true,
+ Some(qf) => {
+ let entry = rt
+ .store()
+ .get(id.clone())
+ .map_err_trace_exit_unwrap(1)
+ .unwrap_or_else(|| {
+ error!("Tried to get '{}', but it does not exist!", id);
+ exit(1)
+ });
+
+ qf.filter(&entry)
+ }
+ })
.map(|id| if print_storepath {
id
} else {
diff --git a/bin/core/imag-ids/src/ui.rs b/bin/core/imag-ids/src/ui.rs
index 000539d..61007f1 100644
--- a/bin/core/imag-ids/src/ui.rs
+++ b/bin/core/imag-ids/src/ui.rs
@@ -17,7 +17,7 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
-use clap::{Arg, App};
+use clap::{Arg, App, SubCommand};
pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
app
@@ -36,5 +36,16 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.multiple(true)
.value_names(&["COLLECTION"])
.help("Filter for ids which are only in these collections"))
+
+ .subcommand(SubCommand::with_name("where")
+ .arg(Arg::with_name("where-filter")
+ .index(1)
+ .required(true)
+ .takes_value(true)
+ .multiple(false)
+ .value_names(&["QUERY"])
+ .help("Query the header of the entries and filter them"))
+ )
+ .after_help(include_str!("../static/language-doc.md"))
}
diff --git a/bin/core/imag-ids/static/language-doc.md b/bin/core/imag-ids/static/language-doc.md
new file mode 100644
index 0000000..362c587
--- /dev/null
+++ b/bin/core/imag-ids/static/language-doc.md
@@ -0,0 +1,51 @@
+Language documentation for the imag-ids query language
+======================================================
+
+The query language imag-ids supports is rather simple.
+It can be used to filter the printed imag ids by the values in the header of the
+entries. It has no way to access the content of the entries (yet).
+
+Following is a BNF-like structure shown how the language definition works.
+This definition may change over time, as the language grews more powerful.
+
+```ignore
+query = filter (operator filter)*
+
+filter = unary? ( (function "(" selector ")" ) | selector ) op val
+
+unary = "not"
+
+op =
+ "is" |
+ "in" |
+ "==" |
+ "eq" |
+ "!=" |
+ "neq" |
+ ">=" |
+ "<=" |
+ "<" |
+ ">" |
+ "any" |
+ "all"
+
+val = val | listofval
+
+val = string | int | bool
+listofval = "[" (val ",")* "]"
+
+operator =
+ "or" |
+ "or_not" |
+ "and" |
+ "and_not" |
+ "xor"
+
+function =
+ "length" |
+ "keys" |
+ "values"
+```
+
+A "string" quoted with double-quotes.
+A "val" does not yet support floats.