diff --git a/Cargo.lock b/Cargo.lock index a06ce06..842179f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,11 +10,6 @@ name = "autocfg" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "byteorder" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "cfg-if" version = "0.1.9" @@ -28,7 +23,7 @@ dependencies = [ "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", "num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.98 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -46,7 +41,8 @@ version = "0.1.0" dependencies = [ "chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)", + "nom 5.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.98 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", "zip 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -61,6 +57,18 @@ name = "itoa" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "lexical-core" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "stackvector 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "static_assertions 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "libc" version = "0.2.60" @@ -68,16 +76,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "libflate" -version = "0.1.25" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "rle-decode-fast 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "take_mut 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "memchr" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "nom" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lexical-core 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "num-integer" version = "0.1.41" @@ -126,27 +148,48 @@ name = "rle-decode-fast" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "ryu" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "serde" -version = "1.0.97" +name = "semver" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "serde_derive 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)", + "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde_derive 1.0.98 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "serde_derive" -version = "1.0.97" +version = "1.0.98" 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.13 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.40 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -156,12 +199,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.98 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "stackvector" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "static_assertions" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "syn" -version = "0.15.40" +version = "0.15.44" 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)", @@ -189,6 +246,24 @@ name = "unicode-xid" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "unreachable" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "version_check" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "winapi" version = "0.3.7" @@ -214,21 +289,23 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libflate 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", + "libflate 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", "podio 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [metadata] "checksum adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7e522997b529f05601e05166c07ed17789691f562762c7f3b987263d2dedee5c" "checksum autocfg 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "22130e92352b948e7e82a49cdb0aa94f2211761117f29e052dd397c1ac33542b" -"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" "checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" "checksum chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "77d81f58b7301084de3b958691458a53c3f7e0b1d702f77e550b6a88e3a88abe" "checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" "checksum hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" "checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" +"checksum lexical-core 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b8b0f90c979adde96d19eb10eb6431ba0c441e2f9e9bdff868b2f6f5114ff519" "checksum libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)" = "d44e80633f007889c7eff624b709ab43c92d708caad982295768a7b13ca3b5eb" -"checksum libflate 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)" = "90c6f86f4b0caa347206f916f8b687b51d77c6ef8ff18d52dd007491fd580529" +"checksum libflate 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)" = "45c97cf62125b79dcac52d506acdc4799f21a198597806947fd5f40dc7b93412" +"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" +"checksum nom 5.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e9761d859320e381010a4f7f8ed425f2c924de33ad121ace447367c713ad561b" "checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" "checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" "checksum podio 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "780fb4b6698bbf9cf2444ea5d22411cef2953f0824b98f33cf454ec5615645bd" @@ -236,14 +313,22 @@ dependencies = [ "checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" "checksum rle-decode-fast 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cabe4fa914dec5870285fa7f71f602645da47c486e68486d2b4ceb4a343e90ac" +"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" "checksum ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c92464b447c0ee8c4fb3824ecc8383b81717b9f1e74ba2e72540aef7b9f82997" -"checksum serde 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)" = "d46b3dfedb19360a74316866cef04687cd4d6a70df8e6a506c63512790769b72" -"checksum serde_derive 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)" = "c22a0820adfe2f257b098714323563dd06426502abbbce4f51b72ef544c5027f" +"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +"checksum serde 1.0.98 (registry+https://github.com/rust-lang/crates.io-index)" = "7fe5626ac617da2f2d9c48af5515a21d5a480dbd151e01bb1c355e26a3e68113" +"checksum serde_derive 1.0.98 (registry+https://github.com/rust-lang/crates.io-index)" = "01e69e1b8a631f245467ee275b8c757b818653c6d704cdbcaeb56b56767b529c" "checksum serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)" = "051c49229f282f7c6f3813f8286cc1e3323e8051823fce42c7ea80fe13521704" -"checksum syn 0.15.40 (registry+https://github.com/rust-lang/crates.io-index)" = "bc945221ccf4a7e8c31222b9d1fc77aefdd6638eb901a6ce457a3dc29d4c31e8" +"checksum stackvector 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "1c4725650978235083241fab0fdc8e694c3de37821524e7534a1a9061d1068af" +"checksum static_assertions 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b4f8de36da215253eb5f24020bfaa0646613b48bf7ebe36cdfa37c3b3b33b241" +"checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" "checksum take_mut 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" +"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" +"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" "checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index 4814f9d..f82368b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,9 @@ features = ["serde"] [dependencies.hex] version = "*" +[dependencies.nom] +version = "5" + [dependencies.serde] version = "*" features = ["derive"] diff --git a/src/main.rs b/src/main.rs index 78619d0..9f027d0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ pub mod archive; pub mod error; +pub mod query; use std::env::args; use std::time::Instant; diff --git a/src/query/mod.rs b/src/query/mod.rs new file mode 100644 index 0000000..8a6bd6b --- /dev/null +++ b/src/query/mod.rs @@ -0,0 +1,6 @@ +//! Query module. + +mod optimizer; +mod parser; + +pub use self::parser::parse; diff --git a/src/query/optimizer.rs b/src/query/optimizer.rs new file mode 100644 index 0000000..3ffbc33 --- /dev/null +++ b/src/query/optimizer.rs @@ -0,0 +1,87 @@ +//! Query optimizer. + +use std::str::FromStr; + +use chrono::prelude::*; + +use super::parser::{Operator, Source}; +use crate::archive::Story; +use crate::error::{Error, Result}; + +use Operator::*; +use Source::*; + +type Filter = Box bool + Sync>; + +type IntFn = Box i64 + Sync>; +type StrFn = Box &str + Sync>; +type DtuFn = Box &Option> + Sync>; + +macro_rules! ok { + ($func:expr) => { + Ok(Box::new($func)) + }; +} + +pub fn optimize(src: Source, op: Operator, value: &str) -> Result { + match src { + StrFn(f) => strfn(f, op, value), + IntFn(f) => intfn(f, op, value), + DtuFn(f) => dtufn(f, op, value), + } +} + +fn strfn(f: StrFn, op: Operator, value: &str) -> Result { + let value: String = match op { + Fuzzy => value.to_lowercase(), + _ => value.to_owned(), + }; + + match op { + Exact => ok!(move |s| f(s) == &value), + Fuzzy => ok!(move |s| f(s).to_lowercase().contains(&value)), + _ => Err(Error::query("Invalid operation for text type")), + } +} + +fn intfn(f: IntFn, op: Operator, value: &str) -> Result { + let value: i64 = value.parse().map_err(|e| match e { + _ => Error::query("Invalid value for number type"), + })?; + + match op { + Exact => ok!(move |s| f(s) == value), + Fuzzy => ok!(move |s| f(s) == value), + LessThan => ok!(move |s| f(s) < value), + MoreThan => ok!(move |s| f(s) > value), + } +} + +fn dtufn(f: DtuFn, op: Operator, value: &str) -> Result { + let parsed = DateTime::from_str(value); + + let value: DateTime = parsed.map_err(|e| match e { + _ => Error::query("Invalid value for date type"), + })?; + + let date = value.date(); + + match op { + Exact => ok!(move |s| match f(s) { + Some(dt) => dt == &value, + None => false, + }), + Fuzzy => ok!(move |s| match f(s) { + Some(dt) => dt.date() == date, + None => false, + }), + LessThan => ok!(move |s| match f(s) { + Some(dt) => dt < &value, + None => false, + }), + MoreThan => ok!(move |s| match f(s) { + Some(dt) => dt > &value, + None => false, + }), + } +} diff --git a/src/query/parser.rs b/src/query/parser.rs new file mode 100644 index 0000000..83e2dc0 --- /dev/null +++ b/src/query/parser.rs @@ -0,0 +1,215 @@ +//! Query parser. + +use chrono::prelude::*; + +use nom::character::complete::*; +use nom::error::ErrorKind as NomErrorKind; +use nom::sequence::*; +use nom::*; + +use super::optimizer::optimize; +use crate::archive::Story; +use crate::error::*; + +type Filter = Box bool + Sync>; + +pub enum Source { + IntFn(Box i64 + Sync>), + StrFn(Box &str + Sync>), + DtuFn(Box &Option> + Sync>), +} + +pub enum Operator { + Exact, + Fuzzy, + LessThan, + MoreThan, +} + +macro_rules! sfn { + ($func:expr) => { + |_| Source::StrFn(Box::new($func)) + }; +} + +macro_rules! ifn { + ($func:expr) => { + |_| Source::IntFn(Box::new($func)) + }; +} + +macro_rules! dfn { + ($func:expr) => { + |_| Source::DtuFn(Box::new($func)) + }; +} + +named!(source<&str, Source>, preceded!(space0, alt!( + tag!("id") => { ifn!(|s| s.id as i64) } | + + tag!("story") => { sfn!(|s| &s.title) } | + tag!("title") => { sfn!(|s| &s.title) } | + + tag!("description") => { sfn!(|s| &s.description_html) } | + tag!("short description") => { sfn!(|s| &s.short_description) } | + tag!("url") => { sfn!(|s| &s.url) } | + + tag!("modified") => { dfn!(|s| &s.date_modified) } | + tag!("published") => { dfn!(|s| &s.date_published) } | + tag!("updated") => { dfn!(|s| &s.date_updated) } | + + tag!("chapters") => { ifn!(|s| s.num_chapters as i64) } | + tag!("comments") => { ifn!(|s| s.num_comments as i64) } | + tag!("dislikes") => { ifn!(|s| s.num_dislikes as i64) } | + tag!("likes") => { ifn!(|s| s.num_likes as i64) } | + tag!("total views") => { ifn!(|s| s.total_num_views as i64) } | + tag!("views") => { ifn!(|s| s.num_views as i64) } | + tag!("words") => { ifn!(|s| s.num_words as i64) } | + + tag!("author") => { sfn!(|s| &s.author.name) } | + tag!("author name") => { sfn!(|s| &s.author.name) } | + + tag!("author id") => { ifn!(|s| s.author.id as i64) } | + tag!("author joined") => { dfn!(|s| &s.author.date_joined) } | + + tag!("path") => { sfn!(|s| &s.archive.path) } | + tag!("archive") => { sfn!(|s| &s.archive.path) } | + tag!("archive path") => { sfn!(|s| &s.archive.path) } | + + tag!("entry checked") => { dfn!(|s| &s.archive.date_checked) } | + tag!("entry created") => { dfn!(|s| &s.archive.date_created) } | + tag!("entry fetched") => { dfn!(|s| &s.archive.date_fetched) } | + tag!("entry updated") => { dfn!(|s| &s.archive.date_updated) } +))); + +named!(operator<&str, Operator>, preceded!(space0, alt!( + tag!("=") => { |_| Operator::Exact } | + tag!(":") => { |_| Operator::Fuzzy } | + tag!("<") => { |_| Operator::LessThan } | + tag!(">") => { |_| Operator::MoreThan } +))); + +fn unescape(input: &str) -> String { + input + .replace("\\)", ")") + .replace("\\,", ",") + .replace("\\|", "|") + .replace("\\\\", "\\") +} + +named!(value<&str, &str>, + escaped!(none_of!("),|\\"), '\\', one_of!("),|\\")) +); + +named!(target<&str, String>, preceded!(space0, + map!(value, |value| unescape(value.trim())) +)); + +fn item(input: &str) -> IResult<&str, Filter> { + let result = tuple((source, operator, target))(input)?; + let (left, (src, op, value)) = result; + + let filter = optimize(src, op, &value).map_err(|e| match e { + _ => Err::Failure((input, NomErrorKind::Permutation)), + })?; + + Ok((left, filter)) +} + +named!(parens<&str, Filter>, alt!( + delimited!( + preceded!(space0, tag!("(")), + preceded!(space0, call!(ofunc)), + preceded!(space0, tag!(")")) + ) | + call!(item) +)); + +fn negate(input: &str) -> IResult<&str, Filter> { + let (left, filter) = parens(input)?; + + Ok((left, Box::new(move |s| !filter(s)))) +} + +named!(nlist<&str, Filter>, preceded!(space0, alt!( + preceded!(char('!'), call!(negate)) | call!(parens) +))); + +named!(alist<&str, Vec>, separated_nonempty_list!( + preceded!(space0, char(',')), call!(nlist) +)); + +fn afunc(input: &str) -> IResult<&str, Filter> { + let (left, mut filters) = alist(input)?; + + if filters.len() == 1 { + return Ok((left, filters.remove(0))); + } + + let filter: Filter = Box::new(move |story| { + for filter in filters.iter() { + if !filter(story) { + return false; + } + } + + true + }); + + Ok((left, filter)) +} + +named!(olist<&str, Vec>, separated_nonempty_list!( + preceded!(space0, char('|')), call!(afunc) +)); + +fn ofunc(input: &str) -> IResult<&str, Filter> { + let (left, mut filters) = olist(input)?; + + if filters.len() == 1 { + return Ok((left, filters.remove(0))); + } + + let filter: Filter = Box::new(move |story| { + for filter in filters.iter() { + if filter(story) { + return true; + } + } + + false + }); + + Ok((left, filter)) +} + +fn format_error(query: &str, input: &str, error: NomErrorKind) -> String { + let description = error.description().to_lowercase(); + let position = query.len() - input.len(); + + format!("Invalid {} at {}", description, position) +} + +fn translate_error(query: &str, error: Err<(&str, NomErrorKind)>) -> Error { + let message = match error { + Err::Error((i, e)) => format_error(query, i, e), + Err::Failure((i, e)) => format_error(query, i, e), + Err::Incomplete(_) => String::from("Incomplete input"), + }; + + Error::query(message) +} + +fn translate_incomplete(query: &str, input: &str) -> Error { + let position = query.len() - input.len(); + + Error::query(format!("Incomplete input at {}", position)) +} + +pub fn parse(query: &str) -> Result { + match ofunc(query.trim()) { + Ok(("", filter)) => Ok(filter), + Ok((i, _)) => Err(translate_incomplete(query, i)), + Err(e) => Err(translate_error(query, e)), + } +}