diff --git a/Cargo.lock b/Cargo.lock index d73b59d..afca199 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -928,7 +928,7 @@ dependencies = [ [[package]] name = "duper" -version = "0.4.2" +version = "0.4.3" dependencies = [ "ariadne", "base64", @@ -1028,14 +1028,11 @@ dependencies = [ "chumsky", "clap", "duper", + "futures", "glob", - "owo-colors", "regex", "smol", "temporal_rs", - "tinyvec", - "tracing", - "tracing-subscriber", ] [[package]] diff --git a/duper/Cargo.toml b/duper/Cargo.toml index cd04708..a656671 100644 --- a/duper/Cargo.toml +++ b/duper/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "duper" description = "The format that's super!" -version = "0.4.2" +version = "0.4.3" edition = "2024" rust-version = "1.88" license = "MIT" diff --git a/duper/src/parser/mod.rs b/duper/src/parser/mod.rs index ad76aaa..0983de7 100644 --- a/duper/src/parser/mod.rs +++ b/duper/src/parser/mod.rs @@ -66,12 +66,12 @@ impl DuperParser { // Base rules -pub fn duper_trunk<'a>() +pub(crate) fn duper_trunk<'a>() -> impl Parser<'a, &'a str, DuperValue<'a>, extra::Err>> + Clone { identified_trunk().then_ignore(end()) } -pub fn duper_value<'a>() +pub(crate) fn duper_value<'a>() -> impl Parser<'a, &'a str, DuperValue<'a>, extra::Err>> + Clone { identified_value().then_ignore(end()) } @@ -124,7 +124,7 @@ pub fn identifier<'a>() .map(|identifier| DuperIdentifier(Cow::Borrowed(identifier))) } -pub(crate) fn identified_trunk<'a>() +pub fn identified_trunk<'a>() -> impl Parser<'a, &'a str, DuperValue<'a>, extra::Err>> + Clone { let inner_trunk = choice(( object(identified_value()).map(DuperInner::Object), @@ -146,7 +146,7 @@ pub(crate) fn identified_trunk<'a>() .padded_by(whitespace_and_comments()) } -pub(crate) fn identified_value<'a>() +pub fn identified_value<'a>() -> impl Parser<'a, &'a str, DuperValue<'a>, extra::Err>> + Clone { recursive(move |identified_value| { let inner_value = choice(( diff --git a/duperq/Cargo.toml b/duperq/Cargo.toml index bead6b4..8e026a5 100644 --- a/duperq/Cargo.toml +++ b/duperq/Cargo.toml @@ -15,12 +15,9 @@ anyhow = "1.0.100" async-trait = "0.1.89" chumsky = "0.11.2" clap = { version = "4.5.53", features = ["derive"] } -duper = { version = "0.4.2", path = "../duper" } +duper = { version = "0.4.3", path = "../duper" } +futures = "0.3.31" glob = "0.3.3" -owo-colors = "4.2.3" regex = "1.12.2" smol = "2.0.2" temporal_rs = "0.1.2" -tinyvec = { version = "1.10.0", features = ["alloc"] } -tracing = "0.1.41" -tracing-subscriber = "0.3.20" diff --git a/duperq/src/accessor.rs b/duperq/src/accessor.rs index 44bce65..6d5de31 100644 --- a/duperq/src/accessor.rs +++ b/duperq/src/accessor.rs @@ -4,11 +4,13 @@ use duper::{DuperInner, DuperValue}; use crate::filter::DuperFilter; +type AccessorReturn<'value> = Box> + 'value>; + pub(crate) trait DuperAccessor { fn access<'accessor: 'value, 'value>( &'accessor self, value: &'value DuperValue<'value>, - ) -> Box> + 'value>; + ) -> AccessorReturn<'value>; } // Flattened accessor @@ -19,13 +21,11 @@ impl DuperAccessor for FlattenedAccessor { fn access<'accessor: 'value, 'value>( &'accessor self, value: &'value DuperValue<'value>, - ) -> Box> + 'value> { - let mut values: Box> + 'value> = - Box::new(iter::once(value)); - for accessor in self.0.iter() { - values = Box::new(values.flat_map(|value| accessor.access(value))); - } - values + ) -> AccessorReturn<'value> { + self.0.iter().fold( + Box::new(iter::once(value)) as AccessorReturn<'value>, + |values, accessor| Box::new(values.flat_map(|value| accessor.access(value))), + ) } } @@ -37,7 +37,7 @@ impl DuperAccessor for FieldAccessor { fn access<'accessor: 'value, 'value>( &'accessor self, value: &'value DuperValue<'value>, - ) -> Box> + 'value> { + ) -> AccessorReturn<'value> { if let DuperInner::Object(object) = &value.inner { Box::new( object @@ -69,7 +69,7 @@ impl DuperAccessor for IndexAccessor { fn access<'accessor: 'value, 'value>( &'accessor self, value: &'value DuperValue<'value>, - ) -> Box> + 'value> { + ) -> AccessorReturn<'value> { if let DuperInner::Array(array) = &value.inner { Box::new(array.get(self.0).into_iter()) } else { @@ -84,7 +84,7 @@ impl DuperAccessor for ReverseIndexAccessor { fn access<'accessor: 'value, 'value>( &'accessor self, value: &'value DuperValue<'value>, - ) -> Box> + 'value> { + ) -> AccessorReturn<'value> { if let DuperInner::Array(array) = &value.inner { Box::new(array.get(array.len() - self.0).into_iter()) } else { @@ -102,7 +102,7 @@ impl DuperAccessor for RangeIndexAccessor { fn access<'accessor: 'value, 'value>( &'accessor self, value: &'value DuperValue<'value>, - ) -> Box> + 'value> { + ) -> AccessorReturn<'value> { if let DuperInner::Array(array) = &value.inner { let start = match self.start { Bound::Included(i) => i, @@ -126,7 +126,7 @@ impl DuperAccessor for AnyAccessor { fn access<'accessor: 'value, 'value>( &'accessor self, value: &'value DuperValue<'value>, - ) -> Box> + 'value> { + ) -> AccessorReturn<'value> { if let DuperInner::Array(array) = &value.inner { Box::new(array.iter()) } else { @@ -141,7 +141,7 @@ impl DuperAccessor for FilterAccessor { fn access<'accessor: 'value, 'value>( &'accessor self, value: &'value DuperValue<'value>, - ) -> Box> + 'value> { + ) -> AccessorReturn<'value> { if let DuperInner::Array(array) = &value.inner { Box::new(array.iter().filter_map(|value| { if self.0.filter(value) { diff --git a/duperq/src/filter.rs b/duperq/src/filter.rs index b0ffb43..c79e34c 100644 --- a/duperq/src/filter.rs +++ b/duperq/src/filter.rs @@ -258,6 +258,10 @@ impl DuperFilter for EqFilter { (EqValue::Float(this, epsilon), DuperInner::Float(that)) => { (this - that).abs() <= epsilon.unwrap_or(0.0).abs() } + (EqValue::Integer(this), DuperInner::Float(that)) => *this == *that as i64, + (EqValue::Float(this, epsilon), DuperInner::Integer(that)) => { + (this - *that as f64).abs() <= epsilon.unwrap_or(0.0).abs() + } (EqValue::Boolean(this), DuperInner::Boolean(that)) => this == that, (EqValue::Null, DuperInner::Null) => true, _ => false, @@ -340,6 +344,10 @@ impl DuperFilter for NeFilter { (EqValue::Float(this, epsilon), DuperInner::Float(that)) => { (this - that).abs() > epsilon.unwrap_or(0.0).abs() } + (EqValue::Integer(this), DuperInner::Float(that)) => *this != *that as i64, + (EqValue::Float(this, epsilon), DuperInner::Integer(that)) => { + (this - *that as f64).abs() > epsilon.unwrap_or(0.0).abs() + } (EqValue::Boolean(this), DuperInner::Boolean(that)) => this != that, (EqValue::Null, DuperInner::Null) => false, _ => true, @@ -420,20 +428,20 @@ macro_rules! cmp_filter { fn filter<'v>(&self, value: &'v DuperValue<'v>) -> bool { match (&self.0, &value.inner) { (CmpValue::Len(this), DuperInner::Object(that)) => { - matches!(this.cmp(&that.len()), $ord) + matches!(that.len().cmp(this), $ord) } (CmpValue::Len(this), DuperInner::Array(that)) => { - matches!(this.cmp(&that.len()), $ord) + matches!(that.len().cmp(this), $ord) } (CmpValue::Len(this), DuperInner::String(that)) => { - matches!(this.cmp(&that.as_ref().len()), $ord) + matches!(that.as_ref().len().cmp(this), $ord) } (CmpValue::Len(this), DuperInner::Bytes(that)) => { - matches!(this.cmp(&that.as_ref().len()), $ord) + matches!(that.as_ref().len().cmp(this), $ord) } (CmpValue::TemporalInstant(this), DuperInner::Temporal(that)) => { Instant::from_str(that.as_ref()) - .is_ok_and(|that| matches!(this.cmp(&that), $ord)) + .is_ok_and(|that| matches!(that.cmp(this), $ord)) } (CmpValue::TemporalZonedDateTime(this), DuperInner::Temporal(that)) => { ZonedDateTime::from_utf8( @@ -441,33 +449,39 @@ macro_rules! cmp_filter { Disambiguation::Compatible, OffsetDisambiguation::Prefer, ) - .is_ok_and(|that| matches!(this.compare_instant(&that), $ord)) + .is_ok_and(|that| matches!(that.compare_instant(this), $ord)) } (CmpValue::TemporalPlainDate(this), DuperInner::Temporal(that)) => { PlainDate::from_str(that.as_ref()) - .is_ok_and(|that| matches!(this.compare_iso(&that), $ord)) + .is_ok_and(|that| matches!(that.compare_iso(this), $ord)) } (CmpValue::TemporalPlainTime(this), DuperInner::Temporal(that)) => { PlainTime::from_str(that.as_ref()) - .is_ok_and(|that| matches!(this.cmp(&that), $ord)) + .is_ok_and(|that| matches!(that.cmp(this), $ord)) } (CmpValue::TemporalPlainDateTime(this), DuperInner::Temporal(that)) => { PlainDateTime::from_str(that.as_ref()) - .is_ok_and(|that| matches!(this.compare_iso(&that), $ord)) + .is_ok_and(|that| matches!(that.compare_iso(this), $ord)) } (CmpValue::TemporalPlainYearMonth(this), DuperInner::Temporal(that)) => { PlainYearMonth::from_str(that.as_ref()) - .is_ok_and(|that| matches!(this.compare_iso(&that), $ord)) + .is_ok_and(|that| matches!(that.compare_iso(this), $ord)) } (CmpValue::TemporalDuration(this), DuperInner::Temporal(that)) => { Duration::from_str(that.as_ref()) - .is_ok_and(|that| matches!(this.partial_cmp(&that), Some($ord))) + .is_ok_and(|that| matches!(that.partial_cmp(this), Some($ord))) } (CmpValue::Integer(this), DuperInner::Integer(that)) => { - matches!(this.cmp(that), $ord) + matches!(that.cmp(this), $ord) } (CmpValue::Float(this), DuperInner::Float(that)) => { - matches!(this.partial_cmp(that), Some($ord)) + matches!(that.partial_cmp(this), Some($ord)) + } + (CmpValue::Integer(this), DuperInner::Float(that)) => { + matches!((*that as i64).cmp(this), $ord) + } + (CmpValue::Float(this), DuperInner::Integer(that)) => { + matches!((*that as f64).partial_cmp(this), Some($ord)) } _ => false, } diff --git a/duperq/src/main.rs b/duperq/src/main.rs index fc8f19c..a128e1c 100644 --- a/duperq/src/main.rs +++ b/duperq/src/main.rs @@ -25,34 +25,42 @@ struct Cli { fn main() -> anyhow::Result<()> { let cli = Cli::parse(); - smol::block_on(async move { - let mut stderr = Unblock::new(std::io::stderr()); - let (pipeline_fns, output) = match query().parse(&cli.query).into_result() { - Ok(pipeline) => pipeline, - Err(errors) => { - return Err(anyhow::anyhow!(DuperParser::prettify_error( - &cli.query, &errors, None - )?)); - } - }; - let executor = LocalExecutor::new(); - let mut tasks = Vec::with_capacity(pipeline_fns.len()); - let mut sink = pipeline_fns - .into_iter() - .rfold(output, |mut output, pipeline_fn| { - let (sender, receiver) = smol::channel::bounded(1024); - tasks.push(executor.spawn(async move { - while let Ok(value) = receiver.recv().await { - output.process(value).await; - } - })); - (pipeline_fn)(sender) - }); + let mut stderr = Unblock::new(std::io::stderr()); + let (pipeline_fns, output) = match query().parse(&cli.query).into_result() { + Ok(pipeline) => pipeline, + Err(errors) => { + return Err(anyhow::anyhow!(DuperParser::prettify_error( + &cli.query, &errors, None + )?)); + } + }; + + let executor = LocalExecutor::new(); - if let Some(duper_glob) = cli.glob { + let mut tasks = Vec::with_capacity(pipeline_fns.len()); + let mut sink = pipeline_fns + .into_iter() + .rfold(output, |mut output, pipeline_fn| { + let (sender, receiver) = smol::channel::bounded(1024); + tasks.push(executor.spawn(async move { + while let Ok(value) = receiver.recv().await { + output.process(value).await; + } + })); + (pipeline_fn)(sender) + }); + + let glob = if let Some(duper_glob) = cli.glob { + Some(glob(&duper_glob)?) + } else { + None + }; + + tasks.push(executor.spawn(async move { + if let Some(glob) = glob { // Read from files - for entry in glob(&duper_glob)? { + for entry in glob { match entry { Ok(path) => match smol::fs::read_to_string(&path).await { Ok(input) => match DuperParser::parse_duper_trunk(&input) { @@ -101,11 +109,9 @@ fn main() -> anyhow::Result<()> { } } } + })); - for task in tasks.into_iter().rev() { - task.await; - } + smol::block_on(executor.run(async move { futures::future::join_all(tasks).await })); - Ok(()) - }) + Ok(()) } diff --git a/duperq/src/query.rs b/duperq/src/query.rs index 7500fb8..e8f969a 100644 --- a/duperq/src/query.rs +++ b/duperq/src/query.rs @@ -2,7 +2,7 @@ use chumsky::prelude::*; use duper::{ DuperInner, DuperValue, PrettyPrinter, Serializer, escape::unescape_str, - parser::{duper_value, identifier, integer, object_key}, + parser::{identified_value, identifier, integer, object_key}, }; use smol::channel; @@ -320,7 +320,7 @@ fn leaf_filter<'a>( }), re_op .clone() - .ignore_then(duper_value().padded()) + .ignore_then(identified_value().padded()) .try_map(|value, span| match value.inner { DuperInner::String(string) => regex::Regex::new(string.as_ref()) .map(|regex| Box::new(RegexIdentifierFilter(regex)) as Box) @@ -342,49 +342,49 @@ fn leaf_filter<'a>( exists_filter, accessor.clone().then(choice(( eq_op - .ignore_then(duper_value().padded()) + .ignore_then(identified_value().padded()) .try_map(|value, span| { EqValue::try_from_duper(value, None) .map(|value| Box::new(EqFilter(value)) as Box) .map_err(|error| Rich::custom(span, error)) }), ne_op - .ignore_then(duper_value().padded()) + .ignore_then(identified_value().padded()) .try_map(|value, span| { EqValue::try_from_duper(value, None) .map(|value| Box::new(NeFilter(value)) as Box) .map_err(|error| Rich::custom(span, error)) }), lt_op - .ignore_then(duper_value().padded()) + .ignore_then(identified_value().padded()) .try_map(|value, span| { CmpValue::try_from(value) .map(|value| Box::new(LtFilter(value)) as Box) .map_err(|error| Rich::custom(span, error)) }), le_op - .ignore_then(duper_value().padded()) + .ignore_then(identified_value().padded()) .try_map(|value, span| { CmpValue::try_from(value) .map(|value| Box::new(LeFilter(value)) as Box) .map_err(|error| Rich::custom(span, error)) }), gt_op - .ignore_then(duper_value().padded()) + .ignore_then(identified_value().padded()) .try_map(|value, span| { CmpValue::try_from(value) .map(|value| Box::new(GtFilter(value)) as Box) .map_err(|error| Rich::custom(span, error)) }), ge_op - .ignore_then(duper_value().padded()) + .ignore_then(identified_value().padded()) .try_map(|value, span| { CmpValue::try_from(value) .map(|value| Box::new(GeFilter(value)) as Box) .map_err(|error| Rich::custom(span, error)) }), re_op - .ignore_then(duper_value().padded()) + .ignore_then(identified_value().padded()) .try_map(|value, span| match value.inner { DuperInner::String(string) => regex::bytes::Regex::new(string.as_ref()) .map(|regex| Box::new(RegexFilter(regex)) as Box)