diff --git a/Cargo.lock b/Cargo.lock index 3b2ee1ec..e55af38a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -738,7 +738,7 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hl" -version = "0.27.3" +version = "0.27.4" dependencies = [ "atoi", "bincode", diff --git a/Cargo.toml b/Cargo.toml index c80729d3..dcf9e6fc 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.27.3" +version = "0.27.4" edition = "2021" build = "build.rs" diff --git a/src/error.rs b/src/error.rs index a9a538e0..c3e0d7e5 100644 --- a/src/error.rs +++ b/src/error.rs @@ -93,6 +93,8 @@ pub enum Error { LevelParseError(#[from] level::ParseError), #[error(transparent)] ParseFloatError(#[from] ParseFloatError), + #[error(transparent)] + ParseIntError(#[from] ParseIntError), } /// SizeParseError is an error which may occur when parsing size. diff --git a/src/model.rs b/src/model.rs index 0e592a04..24916291 100644 --- a/src/model.rs +++ b/src/model.rs @@ -1,5 +1,7 @@ // std imports -use std::{collections::HashMap, fmt, iter::IntoIterator, marker::PhantomData, ops::Range}; +use std::{ + cmp::Ordering, collections::HashMap, fmt, iter::IntoIterator, marker::PhantomData, ops::Range, str::FromStr, +}; // third-party imports use chrono::{DateTime, Utc}; @@ -1119,15 +1121,84 @@ impl<'a> KeyMatcher<'a> { // --- +#[derive(Debug)] +pub enum Number { + Integer(i128), + Float(f64), +} + +impl FromStr for Number { + type Err = Error; + + #[inline] + fn from_str(s: &str) -> Result { + if s.contains('.') { + Ok(Self::Float(s.parse().map_err(|e| Error::from(e))?)) + } else { + Ok(Self::Integer(s.parse().map_err(|e| Error::from(e))?)) + } + } +} + +impl PartialEq for Number { + #[inline(always)] + fn eq(&self, other: &Number) -> bool { + match self { + Self::Integer(a) => match other { + Self::Integer(b) => a == b, + Self::Float(b) => (*a as f64) == *b, + }, + Self::Float(a) => match other { + Self::Integer(b) => *a == (*b as f64), + Self::Float(b) => a == b, + }, + } + } +} + +impl Eq for Number {} + +impl From for Number { + #[inline(always)] + fn from(value: i128) -> Self { + Self::Integer(value) + } +} + +impl From for Number { + #[inline(always)] + fn from(value: f64) -> Self { + Self::Float(value) + } +} + +impl PartialOrd for Number { + #[inline(always)] + fn partial_cmp(&self, other: &Number) -> Option { + match self { + Self::Integer(a) => match other { + Self::Integer(b) => a.partial_cmp(b), + Self::Float(b) => (*a as f64).partial_cmp(b), + }, + Self::Float(a) => match other { + Self::Integer(b) => a.partial_cmp(&(*b as f64)), + Self::Float(b) => a.partial_cmp(b), + }, + } + } +} + +// --- + #[derive(Debug)] pub enum NumericOp { - Eq(f64), - Ne(f64), - Gt(f64), - Ge(f64), - Lt(f64), - Le(f64), - In(Vec), + Eq(Number), + Ne(Number), + Gt(Number), + Ge(Number), + Lt(Number), + Le(Number), + In(Vec), } // --- @@ -1150,7 +1221,7 @@ impl ValueMatchPolicy { Self::In(patterns) => patterns.iter().any(|pattern| subject == pattern), Self::WildCard(pattern) => pattern.matches(subject), Self::Numerically(op) => { - if let Some(value) = subject.parse::().ok() { + if let Some(value) = subject.parse::().ok() { match op { NumericOp::Eq(pattern) => value == *pattern, NumericOp::Ne(pattern) => value != *pattern, diff --git a/src/query.pest b/src/query.pest index 7ab0c95a..de7913de 100644 --- a/src/query.pest +++ b/src/query.pest @@ -105,7 +105,7 @@ simple_string = @{ simple_char+ } simple_char = @{ (LETTER | NUMBER | "@" | "." | "_" | "-" | ":" | "/" | "!" | "#" | "%" | "$" | "*" | "+" | "?") } number = @{ - "-"? ~ ("0" | ASCII_NONZERO_DIGIT ~ ASCII_DIGIT*) ~ ("." ~ ASCII_DIGIT*)? ~ (^"e" ~ ("+" | "-")? ~ ASCII_DIGIT+)? + "-"? ~ ("0" | ASCII_NONZERO_DIGIT ~ ASCII_DIGIT{,19}) ~ ("." ~ ASCII_DIGIT{,19})? ~ (^"e" ~ ("+" | "-")? ~ ASCII_DIGIT{1, 4})? } ws = _{ (" " | "\t" | "\r" | "\n") } diff --git a/src/query.rs b/src/query.rs index 9092e0a1..8de108b3 100644 --- a/src/query.rs +++ b/src/query.rs @@ -9,7 +9,7 @@ use wildflower::Pattern; use crate::error::Result; use crate::level::RelaxedLevel; use crate::model::{ - FieldFilter, FieldFilterKey, Level, NumericOp, Record, RecordFilter, UnaryBoolOp, ValueMatchPolicy, + FieldFilter, FieldFilterKey, Level, Number, NumericOp, Record, RecordFilter, UnaryBoolOp, ValueMatchPolicy, }; use crate::types::FieldKind; @@ -191,14 +191,14 @@ fn parse_string_set(pair: Pair) -> Result> { inner.map(|p| parse_string(p)).collect::>>() } -fn parse_number(pair: Pair) -> Result { +fn parse_number(pair: Pair) -> Result { assert_eq!(pair.as_rule(), Rule::number); let inner = pair.as_str(); Ok(inner.parse()?) } -fn parse_number_set(pair: Pair) -> Result> { +fn parse_number_set(pair: Pair) -> Result> { assert_eq!(pair.as_rule(), Rule::number_set); let inner = pair.into_inner();