From 707a0492f939e2bf1a5aef1a89d2a641cbd8c54d Mon Sep 17 00:00:00 2001 From: Eric Rodrigues Pires Date: Sat, 29 Nov 2025 15:44:19 -0300 Subject: [PATCH] Support casting and fix ambiguity with keys/operators --- Cargo.lock | 11 ++ duperq/Cargo.toml | 3 +- duperq/README.md | 2 +- duperq/src/filter.rs | 104 ++++++------- duperq/src/lib.rs | 1 + duperq/src/main.rs | 125 +++++++++++++--- duperq/src/processor.rs | 6 +- duperq/src/query.rs | 136 ++++++++++------- duperq/src/types.rs | 325 ++++++++++++++++++++++++++++++++++++++++ 9 files changed, 576 insertions(+), 137 deletions(-) create mode 100644 duperq/src/types.rs diff --git a/Cargo.lock b/Cargo.lock index afca199..9bd490e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1030,6 +1030,7 @@ dependencies = [ "duper", "futures", "glob", + "num_cpus", "regex", "smol", "temporal_rs", @@ -1912,6 +1913,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "object" version = "0.32.2" diff --git a/duperq/Cargo.toml b/duperq/Cargo.toml index 8e026a5..f44eba9 100644 --- a/duperq/Cargo.toml +++ b/duperq/Cargo.toml @@ -15,9 +15,10 @@ anyhow = "1.0.100" async-trait = "0.1.89" chumsky = "0.11.2" clap = { version = "4.5.53", features = ["derive"] } -duper = { version = "0.4.3", path = "../duper" } +duper = { version = "0.4.3", path = "../duper", features = ["ansi"] } futures = "0.3.31" glob = "0.3.3" +num_cpus = "1.17.0" regex = "1.12.2" smol = "2.0.2" temporal_rs = "0.1.2" diff --git a/duperq/README.md b/duperq/README.md index 5dadaf1..25f0c69 100644 --- a/duperq/README.md +++ b/duperq/README.md @@ -8,6 +8,6 @@ GitHub license

-A high-performance Duper filter and processor. +A high-performance Duper/JSON filter and processor. [Check out the official website for Duper.](https://duper.dev.br) diff --git a/duperq/src/filter.rs b/duperq/src/filter.rs index 280510b..2b32dbf 100644 --- a/duperq/src/filter.rs +++ b/duperq/src/filter.rs @@ -7,10 +7,10 @@ use temporal_rs::{ options::{Disambiguation, OffsetDisambiguation}, }; -use crate::accessor::DuperAccessor; +use crate::{accessor::DuperAccessor, types::DuperType}; pub(crate) trait DuperFilter { - fn filter<'v>(&self, value: &'v DuperValue<'_>) -> bool; + fn filter<'v>(&self, value: &DuperValue<'_>) -> bool; } // Branchless filters @@ -18,7 +18,7 @@ pub(crate) trait DuperFilter { pub(crate) struct TrueFilter; impl DuperFilter for TrueFilter { - fn filter<'v>(&self, _: &'v DuperValue<'v>) -> bool { + fn filter<'v>(&self, _: &DuperValue<'v>) -> bool { true } } @@ -29,7 +29,7 @@ impl DuperFilter for TrueFilter { pub(crate) struct AndFilter(pub(crate) Vec>); impl DuperFilter for AndFilter { - fn filter<'v>(&self, value: &'v DuperValue<'v>) -> bool { + fn filter<'v>(&self, value: &DuperValue<'v>) -> bool { self.0.iter().all(|inner| inner.filter(value)) } } @@ -50,7 +50,7 @@ impl chumsky::container::Container> for AndFilter { pub(crate) struct OrFilter(pub(crate) Vec>); impl DuperFilter for OrFilter { - fn filter<'v>(&self, value: &'v DuperValue<'v>) -> bool { + fn filter<'v>(&self, value: &DuperValue<'v>) -> bool { self.0.iter().any(|inner| inner.filter(value)) } } @@ -70,18 +70,32 @@ impl<'a> chumsky::container::Container> for OrFilter { pub(crate) struct NotFilter(pub(crate) Box); impl DuperFilter for NotFilter { - fn filter<'v>(&self, value: &'v DuperValue<'v>) -> bool { + fn filter<'v>(&self, value: &DuperValue<'v>) -> bool { !self.0.filter(value) } } +pub(crate) struct CastFilter { + pub(crate) typ: DuperType, + pub(crate) filter: Box, +} + +impl DuperFilter for CastFilter { + fn filter<'v>(&self, value: &DuperValue<'v>) -> bool { + self.typ + .cast(value) + .as_ref() + .is_some_and(|value| self.filter.filter(value)) + } +} + pub(crate) struct AccessorFilter { pub(crate) filter: Box, pub(crate) accessor: Box, } impl DuperFilter for AccessorFilter { - fn filter<'v>(&self, value: &'v DuperValue<'_>) -> bool { + fn filter<'v>(&self, value: &DuperValue<'_>) -> bool { self.accessor .access(value) .any(|inner| self.filter.filter(inner)) @@ -201,7 +215,7 @@ impl EqValue { pub(crate) struct EqFilter(pub(crate) EqValue); impl DuperFilter for EqFilter { - fn filter<'v>(&self, value: &'v DuperValue<'v>) -> bool { + fn filter<'v>(&self, value: &DuperValue<'v>) -> bool { match (&self.0, &value.inner) { (EqValue::Identifier(this), _) => match this { Some(this) => value @@ -272,7 +286,7 @@ impl DuperFilter for EqFilter { pub(crate) struct NeFilter(pub(crate) EqValue); impl DuperFilter for NeFilter { - fn filter<'v>(&self, value: &'v DuperValue<'v>) -> bool { + fn filter<'v>(&self, value: &DuperValue<'v>) -> bool { match (&self.0, &value.inner) { (EqValue::Identifier(this), _) => match this { Some(this) => value @@ -425,7 +439,7 @@ macro_rules! cmp_filter { pub(crate) struct $filter(pub(crate) CmpValue); impl DuperFilter for $filter { - fn filter<'v>(&self, value: &'v DuperValue<'v>) -> bool { + fn filter<'v>(&self, value: &DuperValue<'v>) -> bool { match (&self.0, &value.inner) { (CmpValue::Len(this), DuperInner::Object(that)) => { matches!(that.len().cmp(this), $ord) @@ -496,40 +510,20 @@ cmp_filter!(LeFilter, Ordering::Less | Ordering::Equal); cmp_filter!(LtFilter, Ordering::Less); #[derive(Clone)] -pub(crate) enum IsFilter { - Object, - Array, - Tuple, - String, - Bytes, - TemporalInstant, - TemporalZonedDateTime, - TemporalPlainDate, - TemporalPlainTime, - TemporalPlainDateTime, - TemporalPlainYearMonth, - TemporalPlainMonthDay, - TemporalDuration, - TemporalUnspecified, - Integer, - Float, - Number, - Boolean, - Null, -} +pub(crate) struct IsFilter(pub(crate) DuperType); impl DuperFilter for IsFilter { - fn filter<'v>(&self, value: &'v DuperValue<'v>) -> bool { - match (&self, &value.inner) { - (IsFilter::Object, DuperInner::Object(_)) => true, - (IsFilter::Array, DuperInner::Array(_)) => true, - (IsFilter::Tuple, DuperInner::Tuple(_)) => true, - (IsFilter::String, DuperInner::String(_)) => true, - (IsFilter::Bytes, DuperInner::Bytes(_)) => true, - (IsFilter::TemporalInstant, DuperInner::Temporal(that)) => { + fn filter<'v>(&self, value: &DuperValue<'v>) -> bool { + match (&self.0, &value.inner) { + (DuperType::Object, DuperInner::Object(_)) => true, + (DuperType::Array, DuperInner::Array(_)) => true, + (DuperType::Tuple, DuperInner::Tuple(_)) => true, + (DuperType::String, DuperInner::String(_)) => true, + (DuperType::Bytes, DuperInner::Bytes(_)) => true, + (DuperType::TemporalInstant, DuperInner::Temporal(that)) => { Instant::from_str(that.as_ref()).is_ok() } - (IsFilter::TemporalZonedDateTime, DuperInner::Temporal(that)) => { + (DuperType::TemporalZonedDateTime, DuperInner::Temporal(that)) => { ZonedDateTime::from_utf8( that.as_ref().as_bytes(), Disambiguation::Compatible, @@ -537,30 +531,30 @@ impl DuperFilter for IsFilter { ) .is_ok() } - (IsFilter::TemporalPlainDate, DuperInner::Temporal(that)) => { + (DuperType::TemporalPlainDate, DuperInner::Temporal(that)) => { PlainDate::from_str(that.as_ref()).is_ok() } - (IsFilter::TemporalPlainTime, DuperInner::Temporal(that)) => { + (DuperType::TemporalPlainTime, DuperInner::Temporal(that)) => { PlainTime::from_str(that.as_ref()).is_ok() } - (IsFilter::TemporalPlainDateTime, DuperInner::Temporal(that)) => { + (DuperType::TemporalPlainDateTime, DuperInner::Temporal(that)) => { PlainDateTime::from_str(that.as_ref()).is_ok() } - (IsFilter::TemporalPlainYearMonth, DuperInner::Temporal(that)) => { + (DuperType::TemporalPlainYearMonth, DuperInner::Temporal(that)) => { PlainYearMonth::from_str(that.as_ref()).is_ok() } - (IsFilter::TemporalPlainMonthDay, DuperInner::Temporal(that)) => { + (DuperType::TemporalPlainMonthDay, DuperInner::Temporal(that)) => { PlainMonthDay::from_str(that.as_ref()).is_ok() } - (IsFilter::TemporalDuration, DuperInner::Temporal(that)) => { + (DuperType::TemporalDuration, DuperInner::Temporal(that)) => { Duration::from_str(that.as_ref()).is_ok() } - (IsFilter::TemporalUnspecified, DuperInner::Temporal(_)) => true, - (IsFilter::Integer, DuperInner::Integer(_)) => true, - (IsFilter::Float, DuperInner::Float(_)) => true, - (IsFilter::Number, DuperInner::Integer(_) | DuperInner::Float(_)) => true, - (IsFilter::Boolean, DuperInner::Boolean(_)) => true, - (IsFilter::Null, DuperInner::Null) => true, + (DuperType::TemporalUnspecified, DuperInner::Temporal(_)) => true, + (DuperType::Integer, DuperInner::Integer(_)) => true, + (DuperType::Float, DuperInner::Float(_)) => true, + (DuperType::Number, DuperInner::Integer(_) | DuperInner::Float(_)) => true, + (DuperType::Boolean, DuperInner::Boolean(_)) => true, + (DuperType::Null, DuperInner::Null) => true, _ => false, } } @@ -569,7 +563,7 @@ impl DuperFilter for IsFilter { pub(crate) struct RegexFilter(pub(crate) regex::bytes::Regex); impl DuperFilter for RegexFilter { - fn filter<'v>(&self, value: &'v DuperValue<'v>) -> bool { + fn filter<'v>(&self, value: &DuperValue<'v>) -> bool { match &value.inner { DuperInner::String(string) => self.0.find(string.as_ref().as_bytes()).is_some(), DuperInner::Bytes(bytes) => self.0.find(bytes.as_ref()).is_some(), @@ -582,7 +576,7 @@ impl DuperFilter for RegexFilter { pub(crate) struct RegexIdentifierFilter(pub(crate) regex::Regex); impl DuperFilter for RegexIdentifierFilter { - fn filter<'v>(&self, value: &'v DuperValue<'v>) -> bool { + fn filter<'v>(&self, value: &DuperValue<'v>) -> bool { value .identifier .as_ref() @@ -593,7 +587,7 @@ impl DuperFilter for RegexIdentifierFilter { pub(crate) struct IsTruthyFilter; impl DuperFilter for IsTruthyFilter { - fn filter<'v>(&self, value: &'v DuperValue<'_>) -> bool { + fn filter<'v>(&self, value: &DuperValue<'_>) -> bool { match &value.inner { DuperInner::Object(object) => !object.is_empty(), DuperInner::Array(array) => !array.is_empty(), diff --git a/duperq/src/lib.rs b/duperq/src/lib.rs index c1d418c..96bb0c4 100644 --- a/duperq/src/lib.rs +++ b/duperq/src/lib.rs @@ -3,5 +3,6 @@ mod filter; mod formatter; mod processor; mod query; +mod types; pub use query::query; diff --git a/duperq/src/main.rs b/duperq/src/main.rs index a128e1c..c1954c5 100644 --- a/duperq/src/main.rs +++ b/duperq/src/main.rs @@ -1,3 +1,5 @@ +use std::{fmt::Display, path::PathBuf}; + use chumsky::Parser as _; use clap::Parser; use duper::DuperParser; @@ -23,6 +25,32 @@ struct Cli { disable_stderr: bool, } +enum FileReadError { + Glob(glob::GlobError), + Io(std::io::Error), +} + +impl From for FileReadError { + fn from(value: glob::GlobError) -> Self { + Self::Glob(value) + } +} + +impl From for FileReadError { + fn from(value: std::io::Error) -> Self { + Self::Io(value) + } +} + +impl Display for FileReadError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + FileReadError::Glob(error) => error.fmt(f), + FileReadError::Io(error) => error.fmt(f), + } + } +} + fn main() -> anyhow::Result<()> { let cli = Cli::parse(); @@ -42,7 +70,7 @@ fn main() -> anyhow::Result<()> { let mut sink = pipeline_fns .into_iter() .rfold(output, |mut output, pipeline_fn| { - let (sender, receiver) = smol::channel::bounded(1024); + let (sender, receiver) = smol::channel::bounded(128); tasks.push(executor.spawn(async move { while let Ok(value) = receiver.recv().await { output.process(value).await; @@ -57,41 +85,90 @@ fn main() -> anyhow::Result<()> { None }; - tasks.push(executor.spawn(async move { - if let Some(glob) = glob { - // Read from files + if let Some(glob) = glob { + let (pathbuf_sender, pathbuf_receiver) = + smol::channel::bounded::>(128); + let (file_sender, file_receiver) = + smol::channel::bounded::>(128); + // Iterate over glob + tasks.push(executor.spawn(async move { for entry in glob { match entry { - Ok(path) => match smol::fs::read_to_string(&path).await { - Ok(input) => match DuperParser::parse_duper_trunk(&input) { - Ok(trunk) => sink.process(trunk.static_clone()).await, - Err(errors) => { - if !cli.disable_stderr { - if let Ok(parse_error) = DuperParser::prettify_error( - &input, - &errors, - Some(path.to_string_lossy().as_ref()), - ) { - let _ = stderr.write_all(parse_error.as_bytes()).await; + Err(_) if cli.disable_stderr => continue, + Ok(_) | Err(_) => { + if pathbuf_sender + .send(entry.map_err(|error| error.into())) + .await + .is_err() + { + break; + } + } + } + } + })); + // Read files + tasks.extend((0..num_cpus::get()).map(|_| { + let file_sender = file_sender.clone(); + let pathbuf_receiver = pathbuf_receiver.clone(); + executor.spawn(async move { + while let Ok(msg) = pathbuf_receiver.recv().await { + match msg { + Ok(pathbuf) => { + let string = smol::fs::read_to_string(&pathbuf).await; + match string { + Err(_) if cli.disable_stderr => continue, + Ok(_) | Err(_) => { + if file_sender + .send( + string + .map(move |string| (pathbuf, string)) + .map_err(|error| error.into()), + ) + .await + .is_err() + { + break; } } } - }, + } Err(error) => { + if file_sender.send(Err(error)).await.is_err() { + break; + } + } + } + } + }) + })); + // Parse and process values + tasks.push(executor.spawn(async move { + while let Ok(input) = file_receiver.recv().await { + match input { + Ok((pathbuf, string)) => match DuperParser::parse_duper_trunk(&string) { + Ok(trunk) => sink.process(trunk.static_clone()).await, + Err(errors) => { if !cli.disable_stderr { - let _ = stderr.write_all(error.to_string().as_bytes()).await; + if let Ok(parse_error) = DuperParser::prettify_error( + &string, + &errors, + Some(pathbuf.to_string_lossy().as_ref()), + ) { + let _ = stderr.write_all(parse_error.as_bytes()).await; + } } } }, Err(error) => { - if !cli.disable_stderr { - let _ = stderr.write_all(error.to_string().as_bytes()).await; - } + let _ = stderr.write_all(error.to_string().as_bytes()).await; } } } - } else { - // Read from stdin + })); + } else { + // Read from stdin + tasks.push(executor.spawn(async move { let stdin = BufReader::new(Unblock::new(std::io::stdin())); let mut lines = stdin.lines(); while let Some(Ok(line)) = lines.next().await { @@ -108,8 +185,8 @@ fn main() -> anyhow::Result<()> { } } } - } - })); + })); + } smol::block_on(executor.run(async move { futures::future::join_all(tasks).await })); diff --git a/duperq/src/processor.rs b/duperq/src/processor.rs index 3da85ad..020c0b9 100644 --- a/duperq/src/processor.rs +++ b/duperq/src/processor.rs @@ -99,11 +99,11 @@ impl Processor for SkipProcessor { pub(crate) struct OutputProcessor { stdout: Unblock, - printer: Box) -> String>, + printer: Box) -> Vec>, } impl OutputProcessor { - pub(crate) fn new(printer: Box) -> String>) -> Self { + pub(crate) fn new(printer: Box) -> Vec>) -> Self { Self { stdout: Unblock::new(std::io::stdout()), printer, @@ -115,7 +115,7 @@ impl OutputProcessor { impl Processor for OutputProcessor { async fn process(&mut self, value: DuperValue<'static>) { self.stdout - .write_all((self.printer)(value).as_bytes()) + .write_all((self.printer)(value).as_ref()) .await .expect("stdout was closed"); self.stdout diff --git a/duperq/src/query.rs b/duperq/src/query.rs index bac647d..3cae284 100644 --- a/duperq/src/query.rs +++ b/duperq/src/query.rs @@ -1,6 +1,6 @@ use chumsky::prelude::*; use duper::{ - DuperInner, DuperValue, PrettyPrinter, Serializer, + Ansi, DuperInner, DuperValue, PrettyPrinter, Serializer, escape::unescape_str, parser::{identified_value, identifier, integer, object_key}, }; @@ -12,12 +12,13 @@ use crate::{ IndexAccessor, RangeIndexAccessor, ReverseIndexAccessor, }, filter::{ - AccessorFilter, AndFilter, CmpValue, DuperFilter, EqFilter, EqValue, GeFilter, GtFilter, - IsFilter, IsTruthyFilter, LeFilter, LtFilter, NeFilter, NotFilter, OrFilter, RegexFilter, - RegexIdentifierFilter, TrueFilter, TryFromDuperValueError, + AccessorFilter, AndFilter, CastFilter, CmpValue, DuperFilter, EqFilter, EqValue, GeFilter, + GtFilter, IsFilter, IsTruthyFilter, LeFilter, LtFilter, NeFilter, NotFilter, OrFilter, + RegexFilter, RegexIdentifierFilter, TrueFilter, TryFromDuperValueError, }, formatter::{Formatter, FormatterAtom}, processor::{FilterProcessor, OutputProcessor, Processor, SkipProcessor, TakeProcessor}, + types::DuperType, }; pub(crate) type CreateProcessorFn = @@ -65,17 +66,28 @@ pub fn query<'a>() .then( just('|') .padded() - .ignore_then(just("format").padded().ignore_then(fmt().padded()).or( + .ignore_then(choice(( + just("format").padded().ignore_then(fmt().padded()), + just("ansi").padded().map(|_| { + let mut ansi = Ansi::default(); + OutputProcessor::new(Box::new(move |value| { + ansi.to_ansi(value).unwrap_or_default() + })) + }), just("pretty-print").padded().map(|_| { let mut pretty_printer = PrettyPrinter::default(); - OutputProcessor::new(Box::new(move |value| pretty_printer.pretty_print(value))) + OutputProcessor::new(Box::new(move |value| { + pretty_printer.pretty_print(value).into_bytes() + })) }), - )) + ))) .or_not() .map(|processor| { Box::new(processor.unwrap_or_else(|| { let mut serializer = Serializer::default(); - OutputProcessor::new(Box::new(move |value| serializer.serialize(value))) + OutputProcessor::new(Box::new(move |value| { + serializer.serialize(value).into_bytes() + })) })) as Box }), ) @@ -84,8 +96,9 @@ pub fn query<'a>() fn filter<'a>() -> impl Parser<'a, &'a str, Box, extra::Err>> + Clone { recursive(|filter| { - let atom = leaf_filter(accessor()) - .or(filter.delimited_by(just('('), just(')'))) + let atom = filter + .delimited_by(just('('), just(')')) + .or(leaf_filter(accessor())) .padded(); let unary = just('!') @@ -126,8 +139,8 @@ fn filter<'a>() -> impl Parser<'a, &'a str, Box, extra::Err() -> impl Parser<'a, &'a str, Box, extra::Err>> + Clone { recursive(|accessor| { - let access = just('.').or_not().ignore_then(choice(( - object_key().padded().map(|key: duper::DuperKey<'a>| { + let access = just('.').ignore_then(choice(( + object_key().map(|key: duper::DuperKey<'a>| { Box::new(FieldAccessor(key.as_ref().into())) as Box }), integer() @@ -137,7 +150,6 @@ fn accessor<'a>() .then(just('=').or_not()) .then(integer().or_not().padded()) .delimited_by(just('['), just(']')) - .padded() .try_map(|((start, end_inclusive), end), span| match (start, end) { (Some(start), _) if start < 0 => { Err(Rich::custom(span, "range start must be positive")) @@ -174,7 +186,6 @@ fn accessor<'a>() integer() .padded() .delimited_by(just('['), just(']')) - .padded() .map(|int| { if int < 0 { Box::new(ReverseIndexAccessor(int.unsigned_abs() as usize)) @@ -186,11 +197,9 @@ fn accessor<'a>() leaf_filter(accessor) .padded() .delimited_by(just('['), just(']')) - .padded() .map(|filter| Box::new(FilterAccessor(filter)) as Box), text::whitespace() .delimited_by(just('['), just(']')) - .padded() .map(|_| Box::new(AnyAccessor) as Box), ))); @@ -207,6 +216,31 @@ fn accessor<'a>() fn leaf_filter<'a>( accessor: impl Parser<'a, &'a str, Box, extra::Err>> + Clone, ) -> impl Parser<'a, &'a str, Box, extra::Err>> + Clone { + type ConsumeAccessor = Box) -> Box>; + + let cast_accessor = just("cast") + .padded() + .ignore_then( + accessor + .clone() + .then_ignore(just(',')) + .then(duper_type().padded()) + .map(|(accessor, typ)| { + Box::new(|filter: Box| { + Box::new(AccessorFilter { + filter: Box::new(CastFilter { filter, typ }), + accessor, + }) as Box + }) as ConsumeAccessor + }) + .delimited_by(just('('), just(')')), + ) + .or(accessor.map(|accessor| { + Box::new(|filter: Box| { + Box::new(AccessorFilter { filter, accessor }) as Box + }) as ConsumeAccessor + })); + let eq_op = just("==").ignored().or(just('=').ignored()).padded(); let ne_op = just("!=").ignored().or(just("<>").ignored()).padded(); let lt_op = just("<").ignored().padded(); @@ -217,7 +251,7 @@ fn leaf_filter<'a>( let is_op = just("is").ignored().padded(); let len_filter = just("len") - .ignore_then(accessor.clone().delimited_by(just('('), just(')'))) + .ignore_then(cast_accessor.clone().delimited_by(just('('), just(')'))) .then(choice(( eq_op .clone() @@ -306,7 +340,7 @@ fn leaf_filter<'a>( ))); let identifier_filter = just("identifier") - .ignore_then(accessor.clone().delimited_by(just('('), just(')'))) + .ignore_then(cast_accessor.clone().delimited_by(just('('), just(')'))) .then(choice(( eq_op .clone() @@ -345,14 +379,14 @@ fn leaf_filter<'a>( ))); let exists_filter = just("exists") - .ignore_then(accessor.clone().delimited_by(just('('), just(')'))) + .ignore_then(cast_accessor.clone().delimited_by(just('('), just(')'))) .map(|accessor| (accessor, Box::new(TrueFilter) as Box)); choice(( len_filter, identifier_filter, exists_filter, - accessor.clone().then(choice(( + cast_accessor.clone().then(choice(( eq_op .ignore_then(identified_value().padded()) .try_map(|value, span| { @@ -407,40 +441,36 @@ fn leaf_filter<'a>( )), }), is_op - .ignore_then( - choice(( - just("Object").to(IsFilter::Object), - just("Array").to(IsFilter::Array), - just("Tuple").to(IsFilter::Tuple), - just("String").to(IsFilter::String), - just("Bytes").to(IsFilter::Bytes), - just("Instant").to(IsFilter::TemporalInstant), - just("ZonedDateTime").to(IsFilter::TemporalZonedDateTime), - just("PlainDate").to(IsFilter::TemporalPlainDate), - just("PlainTime").to(IsFilter::TemporalPlainTime), - just("PlainDateTime").to(IsFilter::TemporalPlainDateTime), - just("PlainYearMonth").to(IsFilter::TemporalPlainYearMonth), - just("PlainMonthDay").to(IsFilter::TemporalPlainMonthDay), - just("Duration").to(IsFilter::TemporalDuration), - just("Temporal").to(IsFilter::TemporalUnspecified), - just("Integer").to(IsFilter::Integer), - just("Float").to(IsFilter::Float), - just("Number").to(IsFilter::Number), - just("Boolean").to(IsFilter::Boolean), - just("Null").to(IsFilter::Null), - )) - .padded(), - ) - .map(|value| Box::new(value) as Box), + .ignore_then(duper_type().padded()) + .map(|typ| Box::new(IsFilter(typ)) as Box), ))), )) - .map(|(accessor, filter)| Box::new(AccessorFilter { accessor, filter }) as Box) - .or(accessor.map(|accessor| { - Box::new(AccessorFilter { - accessor, - filter: Box::new(IsTruthyFilter), - }) as Box - })) + .map(|(accessor, filter)| (accessor)(filter)) + .or(cast_accessor.map(|accessor| (accessor)(Box::new(IsTruthyFilter)))) +} + +fn duper_type<'a>() -> impl Parser<'a, &'a str, DuperType, extra::Err>> + Clone { + choice(( + just("Object").to(DuperType::Object), + just("Array").to(DuperType::Array), + just("Tuple").to(DuperType::Tuple), + just("String").to(DuperType::String), + just("Bytes").to(DuperType::Bytes), + just("Instant").to(DuperType::TemporalInstant), + just("ZonedDateTime").to(DuperType::TemporalZonedDateTime), + just("PlainDate").to(DuperType::TemporalPlainDate), + just("PlainTime").to(DuperType::TemporalPlainTime), + just("PlainDateTime").to(DuperType::TemporalPlainDateTime), + just("PlainYearMonth").to(DuperType::TemporalPlainYearMonth), + just("PlainMonthDay").to(DuperType::TemporalPlainMonthDay), + just("Duration").to(DuperType::TemporalDuration), + just("Temporal").to(DuperType::TemporalUnspecified), + just("Integer").to(DuperType::Integer), + just("Float").to(DuperType::Float), + just("Number").to(DuperType::Number), + just("Boolean").to(DuperType::Boolean), + just("Null").to(DuperType::Null), + )) } fn fmt<'a>() -> impl Parser<'a, &'a str, OutputProcessor, extra::Err>> { @@ -458,7 +488,7 @@ fn fmt<'a>() -> impl Parser<'a, &'a str, OutputProcessor, extra::Err( + &self, + value: &'value DuperValue<'value>, + ) -> Option> { + match (self, &value.inner) { + // Trivial casts + (DuperType::Object, DuperInner::Object(_)) => Some(value.clone()), + (DuperType::Array, DuperInner::Array(_)) => Some(value.clone()), + (DuperType::Tuple, DuperInner::Tuple(_)) => Some(value.clone()), + (DuperType::String, DuperInner::String(_)) => Some(value.clone()), + (DuperType::Bytes, DuperInner::Bytes(_)) => Some(value.clone()), + ( + DuperType::TemporalInstant + | DuperType::TemporalZonedDateTime + | DuperType::TemporalPlainDate + | DuperType::TemporalPlainTime + | DuperType::TemporalPlainDateTime + | DuperType::TemporalPlainYearMonth + | DuperType::TemporalPlainMonthDay + | DuperType::TemporalDuration + | DuperType::TemporalUnspecified, + DuperInner::Temporal(_), + ) => Some(value.clone()), + (DuperType::Integer, DuperInner::Integer(_)) => Some(value.clone()), + (DuperType::Float, DuperInner::Float(_)) => Some(value.clone()), + (DuperType::Number, DuperInner::Integer(_) | DuperInner::Float(_)) => { + Some(value.clone()) + } + (DuperType::Boolean, DuperInner::Boolean(_)) => Some(value.clone()), + (DuperType::Null, DuperInner::Null) => Some(value.clone()), + + // Non-trivial casts + (DuperType::Array, DuperInner::Tuple(tuple)) => Some(DuperValue { + identifier: value.identifier.clone(), + inner: DuperInner::Array(DuperArray::from( + tuple.iter().cloned().collect::>(), + )), + }), + (DuperType::Tuple, DuperInner::Array(array)) => Some(DuperValue { + identifier: value.identifier.clone(), + inner: DuperInner::Tuple(DuperTuple::from( + array.iter().cloned().collect::>(), + )), + }), + (DuperType::String, DuperInner::Bytes(bytes)) => str::from_utf8(bytes.as_ref()) + .ok() + .map(|string| DuperValue { + identifier: value.identifier.clone(), + inner: DuperInner::String(DuperString::from(Cow::Borrowed(string))), + }), + (DuperType::String, DuperInner::Temporal(temporal)) => Some(DuperValue { + identifier: value.identifier.clone(), + inner: DuperInner::String(DuperString::from(Cow::Borrowed(temporal.as_ref()))), + }), + (DuperType::Bytes, DuperInner::String(string)) => Some(DuperValue { + identifier: value.identifier.clone(), + inner: DuperInner::Bytes(DuperBytes::from(Cow::Borrowed( + string.as_ref().as_bytes(), + ))), + }), + (DuperType::Bytes, DuperInner::Temporal(temporal)) => Some(DuperValue { + identifier: value.identifier.clone(), + inner: DuperInner::Bytes(DuperBytes::from(Cow::Borrowed( + temporal.as_ref().as_bytes(), + ))), + }), + (DuperType::TemporalInstant, DuperInner::String(string)) => { + DuperTemporal::try_instant_from(Cow::Borrowed(string.as_ref())) + .ok() + .map(|temporal| DuperValue { + identifier: Some( + DuperIdentifier::try_from("Instant").expect("valid identifier"), + ), + inner: DuperInner::Temporal(temporal), + }) + } + (DuperType::TemporalInstant, DuperInner::Bytes(bytes)) => { + str::from_utf8(bytes.as_ref()) + .ok() + .and_then(|string| { + DuperTemporal::try_instant_from(Cow::Borrowed(string.as_ref())).ok() + }) + .map(|temporal| DuperValue { + identifier: Some( + DuperIdentifier::try_from("Instant").expect("valid identifier"), + ), + inner: DuperInner::Temporal(temporal), + }) + } + (DuperType::TemporalZonedDateTime, DuperInner::String(string)) => { + DuperTemporal::try_zoned_date_time_from(Cow::Borrowed(string.as_ref())) + .ok() + .map(|temporal| DuperValue { + identifier: Some( + DuperIdentifier::try_from("ZonedDateTime").expect("valid identifier"), + ), + inner: DuperInner::Temporal(temporal), + }) + } + (DuperType::TemporalZonedDateTime, DuperInner::Bytes(bytes)) => { + str::from_utf8(bytes.as_ref()) + .ok() + .and_then(|string| { + DuperTemporal::try_zoned_date_time_from(Cow::Borrowed(string.as_ref())).ok() + }) + .map(|temporal| DuperValue { + identifier: Some( + DuperIdentifier::try_from("ZonedDateTime").expect("valid identifier"), + ), + inner: DuperInner::Temporal(temporal), + }) + } + (DuperType::TemporalPlainDate, DuperInner::String(string)) => { + DuperTemporal::try_plain_date_from(Cow::Borrowed(string.as_ref())) + .ok() + .map(|temporal| DuperValue { + identifier: Some( + DuperIdentifier::try_from("PlainDate").expect("valid identifier"), + ), + inner: DuperInner::Temporal(temporal), + }) + } + (DuperType::TemporalPlainDate, DuperInner::Bytes(bytes)) => { + str::from_utf8(bytes.as_ref()) + .ok() + .and_then(|string| { + DuperTemporal::try_plain_date_from(Cow::Borrowed(string.as_ref())).ok() + }) + .map(|temporal| DuperValue { + identifier: Some( + DuperIdentifier::try_from("PlainDate").expect("valid identifier"), + ), + inner: DuperInner::Temporal(temporal), + }) + } + (DuperType::TemporalPlainTime, DuperInner::String(string)) => { + DuperTemporal::try_plain_time_from(Cow::Borrowed(string.as_ref())) + .ok() + .map(|temporal| DuperValue { + identifier: Some( + DuperIdentifier::try_from("PlainTime").expect("valid identifier"), + ), + inner: DuperInner::Temporal(temporal), + }) + } + (DuperType::TemporalPlainTime, DuperInner::Bytes(bytes)) => { + str::from_utf8(bytes.as_ref()) + .ok() + .and_then(|string| { + DuperTemporal::try_plain_time_from(Cow::Borrowed(string.as_ref())).ok() + }) + .map(|temporal| DuperValue { + identifier: Some( + DuperIdentifier::try_from("PlainTime").expect("valid identifier"), + ), + inner: DuperInner::Temporal(temporal), + }) + } + (DuperType::TemporalPlainDateTime, DuperInner::String(string)) => { + DuperTemporal::try_plain_date_time_from(Cow::Borrowed(string.as_ref())) + .ok() + .map(|temporal| DuperValue { + identifier: Some( + DuperIdentifier::try_from("PlainDateTime").expect("valid identifier"), + ), + inner: DuperInner::Temporal(temporal), + }) + } + (DuperType::TemporalPlainDateTime, DuperInner::Bytes(bytes)) => { + str::from_utf8(bytes.as_ref()) + .ok() + .and_then(|string| { + DuperTemporal::try_plain_date_time_from(Cow::Borrowed(string.as_ref())).ok() + }) + .map(|temporal| DuperValue { + identifier: Some( + DuperIdentifier::try_from("PlainDateTime").expect("valid identifier"), + ), + inner: DuperInner::Temporal(temporal), + }) + } + (DuperType::TemporalPlainYearMonth, DuperInner::String(string)) => { + DuperTemporal::try_plain_year_month_from(Cow::Borrowed(string.as_ref())) + .ok() + .map(|temporal| DuperValue { + identifier: Some( + DuperIdentifier::try_from("PlainYearMonth").expect("valid identifier"), + ), + inner: DuperInner::Temporal(temporal), + }) + } + (DuperType::TemporalPlainYearMonth, DuperInner::Bytes(bytes)) => { + str::from_utf8(bytes.as_ref()) + .ok() + .and_then(|string| { + DuperTemporal::try_plain_year_month_from(Cow::Borrowed(string.as_ref())) + .ok() + }) + .map(|temporal| DuperValue { + identifier: Some( + DuperIdentifier::try_from("PlainYearMonth").expect("valid identifier"), + ), + inner: DuperInner::Temporal(temporal), + }) + } + (DuperType::TemporalPlainMonthDay, DuperInner::String(string)) => { + DuperTemporal::try_plain_month_day_from(Cow::Borrowed(string.as_ref())) + .ok() + .map(|temporal| DuperValue { + identifier: Some( + DuperIdentifier::try_from("PlainMonthDay").expect("valid identifier"), + ), + inner: DuperInner::Temporal(temporal), + }) + } + (DuperType::TemporalPlainMonthDay, DuperInner::Bytes(bytes)) => { + str::from_utf8(bytes.as_ref()) + .ok() + .and_then(|string| { + DuperTemporal::try_plain_month_day_from(Cow::Borrowed(string.as_ref())).ok() + }) + .map(|temporal| DuperValue { + identifier: Some( + DuperIdentifier::try_from("PlainMonthDay").expect("valid identifier"), + ), + inner: DuperInner::Temporal(temporal), + }) + } + (DuperType::TemporalDuration, DuperInner::String(string)) => { + DuperTemporal::try_duration_from(Cow::Borrowed(string.as_ref())) + .ok() + .map(|temporal| DuperValue { + identifier: Some( + DuperIdentifier::try_from("Duration").expect("valid identifier"), + ), + inner: DuperInner::Temporal(temporal), + }) + } + (DuperType::TemporalDuration, DuperInner::Bytes(bytes)) => { + str::from_utf8(bytes.as_ref()) + .ok() + .and_then(|string| { + DuperTemporal::try_duration_from(Cow::Borrowed(string.as_ref())).ok() + }) + .map(|temporal| DuperValue { + identifier: Some( + DuperIdentifier::try_from("Duration").expect("valid identifier"), + ), + inner: DuperInner::Temporal(temporal), + }) + } + (DuperType::TemporalUnspecified, DuperInner::String(string)) => { + DuperTemporal::try_unspecified_from(Cow::Borrowed(string.as_ref())) + .ok() + .map(|temporal| DuperValue { + identifier: None, + inner: DuperInner::Temporal(temporal), + }) + } + (DuperType::TemporalUnspecified, DuperInner::Bytes(bytes)) => { + str::from_utf8(bytes.as_ref()) + .ok() + .and_then(|string| { + DuperTemporal::try_unspecified_from(Cow::Borrowed(string.as_ref())).ok() + }) + .map(|temporal| DuperValue { + identifier: None, + inner: DuperInner::Temporal(temporal), + }) + } + (DuperType::Integer, DuperInner::Float(float)) => Some(DuperValue { + identifier: value.identifier.clone(), + inner: DuperInner::Integer(*float as i64), + }), + (DuperType::Float, DuperInner::Integer(integer)) => Some(DuperValue { + identifier: value.identifier.clone(), + inner: DuperInner::Float(*integer as f64), + }), + (DuperType::Boolean, _) => Some(DuperValue { + identifier: value.identifier.clone(), + inner: DuperInner::Boolean(IsTruthyFilter.filter(value)), + }), + (DuperType::Null, _) => Some(DuperValue { + identifier: value.identifier.clone(), + inner: DuperInner::Null, + }), + + // Unsupported casts + _ => None, + } + } +} -- 2.51.2