Merge branch 'imag-ids-split-into-more-tools' into master

This commit is contained in:
Matthias Beyer 2019-07-28 12:54:34 +02:00
commit 598b6ec9a6
10 changed files with 221 additions and 856 deletions

View file

@ -9,6 +9,7 @@ members = [
"bin/core/imag-gps", "bin/core/imag-gps",
"bin/core/imag-grep", "bin/core/imag-grep",
"bin/core/imag-header", "bin/core/imag-header",
"bin/core/imag-id-in-collection",
"bin/core/imag-ids", "bin/core/imag-ids",
"bin/core/imag-init", "bin/core/imag-init",
"bin/core/imag-link", "bin/core/imag-link",

View file

@ -0,0 +1,40 @@
[package]
name = "imag-id-in-collection"
version = "0.10.0"
authors = ["Matthias Beyer <mail@beyermatthias.de>"]
description = "Part of the imag core distribution: imag-ids command"
keywords = ["imag", "PIM", "personal", "information", "management"]
readme = "../../../README.md"
license = "LGPL-2.1"
documentation = "https://imag-pim.org/doc/"
repository = "https://github.com/matthiasbeyer/imag"
homepage = "http://imag-pim.org"
[badges]
travis-ci = { repository = "matthiasbeyer/imag" }
is-it-maintained-issue-resolution = { repository = "matthiasbeyer/imag" }
is-it-maintained-open-issues = { repository = "matthiasbeyer/imag" }
maintenance = { status = "actively-developed" }
[dependencies]
filters = "0.3.0"
log = "0.4.6"
toml = "0.5.1"
toml-query = "0.9.2"
failure = "0.1.5"
libimagstore = { version = "0.10.0", path = "../../../lib/core/libimagstore" }
libimagrt = { version = "0.10.0", path = "../../../lib/core/libimagrt" }
libimagerror = { version = "0.10.0", path = "../../../lib/core/libimagerror" }
[dependencies.clap]
version = "2.33.0"
default-features = false
features = ["color", "suggestions", "wrap_help"]
[dev-dependencies]
env_logger = "0.6.1"

View file

@ -0,0 +1,120 @@
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015-2019 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
//
#![forbid(unsafe_code)]
#![deny(
non_camel_case_types,
non_snake_case,
path_statements,
trivial_numeric_casts,
unstable_features,
unused_allocation,
unused_import_braces,
unused_imports,
unused_must_use,
unused_mut,
unused_qualifications,
while_true,
)]
extern crate clap;
extern crate filters;
#[macro_use] extern crate log;
extern crate toml;
extern crate toml_query;
extern crate failure;
#[cfg(test)]
extern crate env_logger;
extern crate libimagerror;
extern crate libimagstore;
#[macro_use] extern crate libimagrt;
use std::io::Write;
use filters::filter::Filter;
use libimagstore::storeid::StoreId;
use libimagrt::setup::generate_runtime_setup;
use libimagerror::trace::MapErrTrace;
use libimagerror::exit::ExitUnwrap;
use libimagerror::io::ToExitCode;
mod ui;
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,
}
}
}
fn main() {
let version = make_imag_version!();
let rt = generate_runtime_setup("imag-id-in-collection",
&version,
"filter ids by collection",
crate::ui::build_ui);
let values = rt
.cli()
.values_of("in-collection-filter")
.map(|v| v.collect::<Vec<&str>>());
let collection_filter = IsInCollectionsFilter::new(values);
let mut stdout = rt.stdout();
trace!("Got output: {:?}", stdout);
rt.ids::<crate::ui::PathProvider>()
.map_err_trace_exit_unwrap()
.unwrap_or_else(|| {
error!("No ids supplied");
::std::process::exit(1);
})
.iter()
.filter(|id| collection_filter.filter(id))
.for_each(|id| {
rt.report_touched(&id).unwrap_or_exit();
if !rt.output_is_pipe() {
let id = id.to_str().map_err_trace_exit_unwrap();
trace!("Writing to {:?}", stdout);
writeln!(stdout, "{}", id).to_exit_code().unwrap_or_exit();
}
})
}

View file

@ -0,0 +1,58 @@
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015-2019 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 std::path::PathBuf;
use clap::{Arg, ArgMatches, App};
use failure::Fallible as Result;
use libimagstore::storeid::StoreId;
use libimagrt::runtime::IdPathProvider;
pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
app
.arg(Arg::with_name("in-collection-filter")
.index(1)
.required(true)
.takes_value(true)
.multiple(false)
.value_name("COLLECTION")
.help("Filter for ids which are in this collection"))
.arg(Arg::with_name("ids")
.index(2)
.required(false)
.takes_value(true)
.multiple(true)
.value_names(&["IDs"])
.help("Ids to filter"))
}
pub struct PathProvider;
impl IdPathProvider for PathProvider {
fn get_ids(matches: &ArgMatches) -> Result<Option<Vec<StoreId>>> {
if let Some(ids) = matches.values_of("ids") {
ids.map(|i| StoreId::new(PathBuf::from(i)))
.collect::<Result<Vec<StoreId>>>()
.map(Some)
} else {
Ok(None)
}
}
}

View file

@ -20,12 +20,9 @@ is-it-maintained-open-issues = { repository = "matthiasbeyer/imag" }
maintenance = { status = "actively-developed" } maintenance = { status = "actively-developed" }
[dependencies] [dependencies]
filters = "0.3.0"
nom = "3.2.0"
log = "0.4.6" log = "0.4.6"
toml = "0.5.1" toml = "0.5.1"
toml-query = "0.9.2" toml-query = "0.9.2"
is-match = "0.1.0"
failure = "0.1.5" failure = "0.1.5"
libimagstore = { version = "0.10.0", path = "../../../lib/core/libimagstore" } libimagstore = { version = "0.10.0", path = "../../../lib/core/libimagstore" }

View file

@ -1,743 +0,0 @@
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015-2019 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 failure::Error;
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(Error::from)
.map_err_trace_exit_unwrap()
.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() {
setup_logging();
{
let list = list_of_val(b"[]");
debug!("list: {:?}", list);
let vals = list.unwrap().1;
assert_eq!(vals, vec![]);
}
{
let list = list_of_val(b"[1]");
debug!("list: {:?}", list);
let vals = list.unwrap().1;
assert_eq!(vals, vec![Value::Integer(1)]);
}
{
let list = list_of_val(b"[12,13]");
debug!("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);
}
}
}

View file

@ -35,10 +35,7 @@
)] )]
extern crate clap; extern crate clap;
extern crate filters;
#[macro_use] extern crate nom;
#[macro_use] extern crate log; #[macro_use] extern crate log;
#[macro_use] extern crate is_match;
extern crate toml; extern crate toml;
extern crate toml_query; extern crate toml_query;
#[macro_use] extern crate failure; #[macro_use] extern crate failure;
@ -51,9 +48,6 @@ extern crate libimagstore;
#[macro_use] extern crate libimagrt; #[macro_use] extern crate libimagrt;
use std::io::Write; use std::io::Write;
use std::process::exit;
use filters::filter::Filter;
use libimagstore::storeid::StoreId; use libimagstore::storeid::StoreId;
use libimagrt::setup::generate_runtime_setup; use libimagrt::setup::generate_runtime_setup;
@ -62,11 +56,9 @@ use libimagerror::iter::TraceIterator;
use libimagerror::exit::ExitUnwrap; use libimagerror::exit::ExitUnwrap;
use libimagerror::io::ToExitCode; use libimagerror::io::ToExitCode;
mod id_filters;
mod ui; mod ui;
use crate::ui::build_ui; use crate::ui::build_ui;
use crate::id_filters::IsInCollectionsFilter;
fn main() { fn main() {
let version = make_imag_version!(); let version = make_imag_version!();
@ -77,20 +69,6 @@ fn main() {
let print_storepath = rt.cli().is_present("print-storepath"); let print_storepath = rt.cli().is_present("print-storepath");
let values = rt
.cli()
.values_of("in-collection-filter")
.map(|v| v.collect::<Vec<&str>>());
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)
});
let iterator = if rt.ids_from_stdin() { let iterator = if rt.ids_from_stdin() {
debug!("Fetching IDs from stdin..."); debug!("Fetching IDs from stdin...");
let ids = rt let ids = rt
@ -107,22 +85,6 @@ fn main() {
as Box<Iterator<Item = Result<StoreId, _>>> as Box<Iterator<Item = Result<StoreId, _>>>
} }
.trace_unwrap_exit() .trace_unwrap_exit()
.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()
.unwrap_or_else(|| {
error!("Tried to get '{}', but it does not exist!", id);
exit(1)
});
qf.filter(&entry)
}
})
.map(|id| if print_storepath { .map(|id| if print_storepath {
(Some(rt.store().path()), id) (Some(rt.store().path()), id)
} else { } else {

View file

@ -17,7 +17,7 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
// //
use clap::{Arg, ArgMatches, App, SubCommand}; use clap::{Arg, ArgMatches, App};
use failure::Fallible as Result; use failure::Fallible as Result;
use libimagstore::storeid::StoreId; use libimagstore::storeid::StoreId;
@ -31,26 +31,6 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.required(false) .required(false)
.multiple(false) .multiple(false)
.help("Print the storepath for each id")) .help("Print the storepath for each id"))
.arg(Arg::with_name("in-collection-filter")
.long("in-collection")
.short("c")
.required(false)
.takes_value(true)
.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"))
} }
pub struct PathProvider; pub struct PathProvider;

View file

@ -1,51 +0,0 @@
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.

View file

@ -62,6 +62,7 @@ CRATES=(
./bin/core/imag-init ./bin/core/imag-init
./bin/core/imag-edit ./bin/core/imag-edit
./bin/core/imag-ids ./bin/core/imag-ids
./bin/core/imag-id-in-collection
./bin/core/imag-git ./bin/core/imag-git
./bin/core/imag-category ./bin/core/imag-category
./bin/core/imag-header ./bin/core/imag-header