diff --git a/Cargo.lock b/Cargo.lock index 48f5e814..3eaec063 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -739,7 +739,7 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hl" -version = "0.20.0-beta.14.8.1" +version = "0.20.0-beta.14.8.2" dependencies = [ "atoi", "bincode", diff --git a/Cargo.toml b/Cargo.toml index 649d3186..eabdec70 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ categories = ["command-line-utilities"] description = "Utility for viewing json-formatted log files." keywords = ["cli", "human", "log"] name = "hl" -version = "0.20.0-beta.14.8.1" +version = "0.20.0-beta.14.8.2" edition = "2021" build = "build.rs" diff --git a/src/model.rs b/src/model.rs index 77229b31..e72fd980 100644 --- a/src/model.rs +++ b/src/model.rs @@ -40,75 +40,8 @@ impl<'a> Record<'a> { self.extra.iter().chain(self.extrax.iter()) } - pub fn matches(&self, filter: &Filter) -> bool { - if filter.is_empty() { - return true; - } - - if filter.since.is_some() || filter.until.is_some() { - if let Some(ts) = self.ts.as_ref().and_then(|ts| ts.parse()) { - if let Some(since) = filter.since { - if ts < since { - return false; - } - } - if let Some(until) = filter.until { - if ts > until { - return false; - } - } - } - } - - if let Some(bound) = &filter.level { - if let Some(level) = self.level.as_ref() { - if level > bound { - return false; - } - } - } - - if !filter.fields.0.is_empty() { - for field in filter.fields.0.iter() { - match &field.key[..] { - "msg" | "message" => { - if !field.match_value(self.message.map(|x| x.get()), true) { - return false; - } - } - "logger" => { - if !field.match_value(self.logger, false) { - return false; - } - } - "caller" => { - if !field.match_value(self.caller, false) { - return false; - } - } - _ => { - let mut matched = false; - for (k, v) in self.fields() { - match field.match_key(*k) { - None => {} - Some(KeyMatch::Full) => { - let escaped = v.get().starts_with('"'); - matched |= field.match_value(Some(v.get()), escaped); - } - Some(KeyMatch::Partial(subkey)) => { - matched |= field.match_value_partial(subkey, *v); - } - } - } - if !matched { - return false; - } - } - } - } - } - - return true; + pub fn matches(&self, filter: &F) -> bool { + filter.apply(self) } fn with_capacity(capacity: usize) -> Self { @@ -130,6 +63,54 @@ impl<'a> Record<'a> { // --- +pub trait RecordFilter { + fn apply<'a>(&self, record: &'a Record<'a>) -> bool; + + fn and(self, rhs: F) -> RecordFilterAnd + where + Self: Sized, + F: RecordFilter, + { + RecordFilterAnd { lhs: self, rhs } + } + + fn or(self, rhs: F) -> RecordFilterOr + where + Self: Sized, + F: RecordFilter, + { + RecordFilterOr { lhs: self, rhs } + } +} + +// --- + +pub struct RecordFilterAnd { + lhs: L, + rhs: R, +} + +impl RecordFilter for RecordFilterAnd { + fn apply<'a>(&self, record: &'a Record<'a>) -> bool { + self.lhs.apply(record) && self.rhs.apply(record) + } +} + +// --- + +pub struct RecordFilterOr { + lhs: L, + rhs: R, +} + +impl RecordFilter for RecordFilterOr { + fn apply<'a>(&self, record: &'a Record<'a>) -> bool { + self.lhs.apply(record) || self.rhs.apply(record) + } +} + +// --- + #[derive(Default)] pub struct ParserSettings { fields: HashMap, @@ -565,6 +546,48 @@ impl FieldFilter { } } +impl RecordFilter for FieldFilter { + fn apply<'a>(&self, record: &'a Record<'a>) -> bool { + match &self.key[..] { + "msg" | "message" => { + if !self.match_value(record.message.map(|x| x.get()), true) { + return false; + } + } + "logger" => { + if !self.match_value(record.logger, false) { + return false; + } + } + "caller" => { + if !self.match_value(record.caller, false) { + return false; + } + } + _ => { + let mut matched = false; + for (k, v) in record.fields() { + match self.match_key(*k) { + None => {} + Some(KeyMatch::Full) => { + let escaped = v.get().starts_with('"'); + matched |= self.match_value(Some(v.get()), escaped); + } + Some(KeyMatch::Partial(subkey)) => { + matched |= self.match_value_partial(subkey, *v); + } + } + } + if !matched { + return false; + } + } + } + + true + } +} + // --- #[derive(Debug, Default)] @@ -580,6 +603,12 @@ impl FieldFilterSet { } } +impl RecordFilter for FieldFilterSet { + fn apply<'a>(&self, record: &'a Record<'a>) -> bool { + self.0.iter().all(|field| field.apply(record)) + } +} + // --- #[derive(Debug, Default)] @@ -596,6 +625,43 @@ impl Filter { } } +impl RecordFilter for Filter { + fn apply<'a>(&self, record: &'a Record<'a>) -> bool { + if self.is_empty() { + return true; + } + + if self.since.is_some() || self.until.is_some() { + if let Some(ts) = record.ts.as_ref().and_then(|ts| ts.parse()) { + if let Some(since) = self.since { + if ts < since { + return false; + } + } + if let Some(until) = self.until { + if ts > until { + return false; + } + } + } + } + + if let Some(bound) = &self.level { + if let Some(level) = record.level.as_ref() { + if level > bound { + return false; + } + } + } + + if !self.fields.apply(record) { + return false; + } + + true + } +} + // --- pub struct Object<'a> {