diff --git a/duper/src/parser/mod.rs b/duper/src/parser/mod.rs index 0983de7..1d16d81 100644 --- a/duper/src/parser/mod.rs +++ b/duper/src/parser/mod.rs @@ -209,7 +209,7 @@ pub fn identified_value<'a>() }) } -pub(crate) fn object<'a>( +pub fn object<'a>( identified_value: impl Parser<'a, &'a str, DuperValue<'a>, extra::Err>> + Clone, ) -> impl Parser<'a, &'a str, DuperObject<'a>, extra::Err>> + Clone { object_key() @@ -244,7 +244,7 @@ pub fn object_key<'a>() -> impl Parser<'a, &'a str, DuperKey<'a>, extra::Err( +pub fn array<'a>( identified_value: impl Parser<'a, &'a str, DuperValue<'a>, extra::Err>> + Clone, ) -> impl Parser<'a, &'a str, DuperArray<'a>, extra::Err>> + Clone { identified_value @@ -267,7 +267,7 @@ pub(crate) fn array<'a>( .map(|_| DuperArray(vec![]))) } -pub(crate) fn tuple<'a>( +pub fn tuple<'a>( identified_value: impl Parser<'a, &'a str, DuperValue<'a>, extra::Err>> + Clone, ) -> impl Parser<'a, &'a str, DuperTuple<'a>, extra::Err>> + Clone { identified_value @@ -290,15 +290,14 @@ pub(crate) fn tuple<'a>( .map(|_| DuperTuple(vec![]))) } -pub(crate) fn quoted_string<'a>() +pub fn quoted_string<'a>() -> impl Parser<'a, &'a str, Cow<'a, str>, extra::Err>> + Clone { quoted_inner() .try_map(|str, span| unescape_str(str).map_err(|err| Rich::custom(span, err))) .delimited_by(just('"'), just('"')) } -pub(crate) fn base64_bytes<'a>() --> impl Parser<'a, &'a str, Vec, extra::Err>> + Clone { +pub fn base64_bytes<'a>() -> impl Parser<'a, &'a str, Vec, extra::Err>> + Clone { base64_digit() .padded() .repeated() @@ -316,15 +315,14 @@ pub(crate) fn base64_bytes<'a>() .delimited_by(just("b64\""), just('"')) } -pub(crate) fn quoted_bytes<'a>() +pub fn quoted_bytes<'a>() -> impl Parser<'a, &'a str, Cow<'a, [u8]>, extra::Err>> + Clone { quoted_inner() .try_map(|bytes, span| unescape_bytes(bytes).map_err(|err| Rich::custom(span, err))) .delimited_by(just("b\""), just('"')) } -pub(crate) fn quoted_inner<'a>() --> impl Parser<'a, &'a str, &'a str, extra::Err>> + Clone { +pub fn quoted_inner<'a>() -> impl Parser<'a, &'a str, &'a str, extra::Err>> + Clone { let escaped_characters = just('\\') .then(choice(( one_of("\"\\/bfnrt0").to_slice(), @@ -342,8 +340,7 @@ pub(crate) fn quoted_inner<'a>() .to_slice() } -pub(crate) fn raw_string<'a>() --> impl Parser<'a, &'a str, &'a str, extra::Err>> + Clone { +pub fn raw_string<'a>() -> impl Parser<'a, &'a str, &'a str, extra::Err>> + Clone { let hashtags = just('#') .repeated() .count() @@ -381,8 +378,7 @@ pub(crate) fn raw_string<'a>() ) } -pub(crate) fn raw_bytes<'a>() --> impl Parser<'a, &'a str, &'a [u8], extra::Err>> + Clone { +pub fn raw_bytes<'a>() -> impl Parser<'a, &'a str, &'a [u8], extra::Err>> + Clone { let hashtags = just('#') .repeated() .to_slice() @@ -422,7 +418,7 @@ pub(crate) fn raw_bytes<'a>() ) } -pub(crate) fn float<'a>() -> impl Parser<'a, &'a str, f64, extra::Err>> + Clone { +pub fn float<'a>() -> impl Parser<'a, &'a str, f64, extra::Err>> + Clone { let decimal = one_of("+-").or_not().then(integer_digits()).to_slice(); let fractional = just('.').then( @@ -503,11 +499,11 @@ pub fn integer<'a>() -> impl Parser<'a, &'a str, i64, extra::Err> choice((hex_integer, octal_integer, binary_integer, decimal_integer)) } -pub(crate) fn boolean<'a>() -> impl Parser<'a, &'a str, bool, extra::Err>> + Clone { +pub fn boolean<'a>() -> impl Parser<'a, &'a str, bool, extra::Err>> + Clone { choice((just("true").to(true), just("false").to(false))) } -pub(crate) fn null<'a>() -> impl Parser<'a, &'a str, (), extra::Err>> + Clone { +pub fn null<'a>() -> impl Parser<'a, &'a str, (), extra::Err>> + Clone { just("null").to(()) } diff --git a/duper/src/parser/temporal.rs b/duper/src/parser/temporal.rs index caa4737..b592b16 100644 --- a/duper/src/parser/temporal.rs +++ b/duper/src/parser/temporal.rs @@ -9,7 +9,7 @@ use crate::{ // Duper Temporal values -pub(crate) fn temporal_specified<'a>() +pub fn temporal_specified<'a>() -> impl Parser<'a, &'a str, DuperTemporal<'a>, extra::Err>> + Clone { choice(( temporal_instant(), @@ -24,7 +24,7 @@ pub(crate) fn temporal_specified<'a>() .padded_by(whitespace_and_comments()) } -pub(crate) fn temporal_instant<'a>() +pub fn temporal_instant<'a>() -> impl Parser<'a, &'a str, DuperTemporal<'a>, extra::Err>> + Clone { just("Instant") .padded_by(whitespace_and_comments()) @@ -42,7 +42,7 @@ pub(crate) fn temporal_instant<'a>() .map(|instant| DuperTemporal::Instant(DuperTemporalInner(Cow::Borrowed(instant)))) } -pub(crate) fn temporal_zoned_date_time<'a>() +pub fn temporal_zoned_date_time<'a>() -> impl Parser<'a, &'a str, DuperTemporal<'a>, extra::Err>> + Clone { just("ZonedDateTime") .padded_by(whitespace_and_comments()) @@ -60,7 +60,7 @@ pub(crate) fn temporal_zoned_date_time<'a>() .map(|instant| DuperTemporal::ZonedDateTime(DuperTemporalInner(Cow::Borrowed(instant)))) } -pub(crate) fn temporal_plain_date<'a>() +pub fn temporal_plain_date<'a>() -> impl Parser<'a, &'a str, DuperTemporal<'a>, extra::Err>> + Clone { just("PlainDate") .padded_by(whitespace_and_comments()) @@ -78,7 +78,7 @@ pub(crate) fn temporal_plain_date<'a>() .map(|instant| DuperTemporal::PlainDate(DuperTemporalInner(Cow::Borrowed(instant)))) } -pub(crate) fn temporal_plain_time<'a>() +pub fn temporal_plain_time<'a>() -> impl Parser<'a, &'a str, DuperTemporal<'a>, extra::Err>> + Clone { just("PlainTime") .padded_by(whitespace_and_comments()) @@ -96,7 +96,7 @@ pub(crate) fn temporal_plain_time<'a>() .map(|instant| DuperTemporal::PlainTime(DuperTemporalInner(Cow::Borrowed(instant)))) } -pub(crate) fn temporal_plain_date_time<'a>() +pub fn temporal_plain_date_time<'a>() -> impl Parser<'a, &'a str, DuperTemporal<'a>, extra::Err>> + Clone { just("PlainDateTime") .padded_by(whitespace_and_comments()) @@ -114,7 +114,7 @@ pub(crate) fn temporal_plain_date_time<'a>() .map(|instant| DuperTemporal::PlainDateTime(DuperTemporalInner(Cow::Borrowed(instant)))) } -pub(crate) fn temporal_plain_year_month<'a>() +pub fn temporal_plain_year_month<'a>() -> impl Parser<'a, &'a str, DuperTemporal<'a>, extra::Err>> + Clone { just("PlainYearMonth") .padded_by(whitespace_and_comments()) @@ -132,7 +132,7 @@ pub(crate) fn temporal_plain_year_month<'a>() .map(|instant| DuperTemporal::PlainYearMonth(DuperTemporalInner(Cow::Borrowed(instant)))) } -pub(crate) fn temporal_plain_month_day<'a>() +pub fn temporal_plain_month_day<'a>() -> impl Parser<'a, &'a str, DuperTemporal<'a>, extra::Err>> + Clone { just("PlainMonthDay") .padded_by(whitespace_and_comments()) @@ -150,7 +150,7 @@ pub(crate) fn temporal_plain_month_day<'a>() .map(|instant| DuperTemporal::PlainMonthDay(DuperTemporalInner(Cow::Borrowed(instant)))) } -pub(crate) fn temporal_duration<'a>() +pub fn temporal_duration<'a>() -> impl Parser<'a, &'a str, DuperTemporal<'a>, extra::Err>> + Clone { just("Duration") .padded_by(whitespace_and_comments()) @@ -168,7 +168,7 @@ pub(crate) fn temporal_duration<'a>() .map(|duration| DuperTemporal::Duration(DuperTemporalInner(Cow::Borrowed(duration)))) } -pub(crate) fn temporal_unspecified<'a>() +pub fn temporal_unspecified<'a>() -> impl Parser<'a, &'a str, DuperTemporal<'a>, extra::Err>> + Clone { unspecified() .to_slice() @@ -181,7 +181,7 @@ pub(crate) fn temporal_unspecified<'a>() // Inner values -pub(crate) fn zoned_date_time<'a>() +pub fn zoned_date_time<'a>() -> impl Parser<'a, &'a str, (), extra::Err>> + Clone { date_time() .then(time_offset()) @@ -191,7 +191,7 @@ pub(crate) fn zoned_date_time<'a>() .ignored() } -pub(crate) fn non_z_zoned_date_time<'a>() +pub fn non_z_zoned_date_time<'a>() -> impl Parser<'a, &'a str, (), extra::Err>> + Clone { date_time() .then(time_num_offset()) @@ -201,14 +201,14 @@ pub(crate) fn non_z_zoned_date_time<'a>() .ignored() } -pub(crate) fn instant<'a>() -> impl Parser<'a, &'a str, (), extra::Err>> + Clone { +pub fn instant<'a>() -> impl Parser<'a, &'a str, (), extra::Err>> + Clone { choice(( zoned_date_time(), date_time().then(time_offset()).padded().ignored(), )) } -pub(crate) fn non_z_instant<'a>() -> impl Parser<'a, &'a str, (), extra::Err>> + Clone +pub fn non_z_instant<'a>() -> impl Parser<'a, &'a str, (), extra::Err>> + Clone { choice(( non_z_zoned_date_time(), @@ -216,21 +216,21 @@ pub(crate) fn non_z_instant<'a>() -> impl Parser<'a, &'a str, (), extra::Err() -> impl Parser<'a, &'a str, (), extra::Err>> + Clone { +pub fn plain_date<'a>() -> impl Parser<'a, &'a str, (), extra::Err>> + Clone { choice(( plain_date_time(), date().then(suffix_tag().repeated()).padded().ignored(), )) } -pub(crate) fn plain_time<'a>() -> impl Parser<'a, &'a str, (), extra::Err>> + Clone { +pub fn plain_time<'a>() -> impl Parser<'a, &'a str, (), extra::Err>> + Clone { choice(( plain_date_time(), time().then(suffix_tag().repeated()).padded().ignored(), )) } -pub(crate) fn plain_date_time<'a>() +pub fn plain_date_time<'a>() -> impl Parser<'a, &'a str, (), extra::Err>> + Clone { choice(( non_z_instant(), @@ -238,7 +238,7 @@ pub(crate) fn plain_date_time<'a>() )) } -pub(crate) fn plain_year_month<'a>() +pub fn plain_year_month<'a>() -> impl Parser<'a, &'a str, (), extra::Err>> + Clone { choice(( plain_date(), @@ -249,7 +249,7 @@ pub(crate) fn plain_year_month<'a>() )) } -pub(crate) fn plain_month_day<'a>() +pub fn plain_month_day<'a>() -> impl Parser<'a, &'a str, (), extra::Err>> + Clone { choice(( plain_date(), @@ -257,7 +257,7 @@ pub(crate) fn plain_month_day<'a>() )) } -pub(crate) fn duration<'a>() -> impl Parser<'a, &'a str, (), extra::Err>> + Clone { +pub fn duration<'a>() -> impl Parser<'a, &'a str, (), extra::Err>> + Clone { let fractional = text::int(10).then(just('.').then(text::digits(10).at_most(9)).or_not()); let duration_time = one_of("Tt").then(choice(( @@ -320,7 +320,7 @@ pub(crate) fn duration<'a>() -> impl Parser<'a, &'a str, (), extra::Err() -> impl Parser<'a, &'a str, (), extra::Err>> + Clone +pub fn unspecified<'a>() -> impl Parser<'a, &'a str, (), extra::Err>> + Clone { choice(( instant(), diff --git a/duperq/src/filter.rs b/duperq/src/filter.rs index c79e34c..280510b 100644 --- a/duperq/src/filter.rs +++ b/duperq/src/filter.rs @@ -495,6 +495,7 @@ cmp_filter!(GtFilter, Ordering::Greater); cmp_filter!(LeFilter, Ordering::Less | Ordering::Equal); cmp_filter!(LtFilter, Ordering::Less); +#[derive(Clone)] pub(crate) enum IsFilter { Object, Array, diff --git a/duperq/src/formatter.rs b/duperq/src/formatter.rs index 53ab542..3561b94 100644 --- a/duperq/src/formatter.rs +++ b/duperq/src/formatter.rs @@ -25,7 +25,13 @@ pub(crate) struct Formatter { impl Formatter { pub(crate) fn new(atoms: Vec) -> Self { Self { - atoms, + atoms: atoms + .into_iter() + .filter_map(|atom| match &atom { + FormatterAtom::Fixed(string) if string.is_empty() => None, + _ => Some(atom), + }) + .collect(), visitor: FormatterVisitor { buf: String::new() }, } } diff --git a/duperq/src/processor.rs b/duperq/src/processor.rs index 3fcdca0..3da85ad 100644 --- a/duperq/src/processor.rs +++ b/duperq/src/processor.rs @@ -60,6 +60,38 @@ impl Processor for TakeProcessor { self.available = 0; } else { self.available = self.available.saturating_sub(1); + if self.available == 0 { + self.sender.close(); + } + } + } + } +} + +pub(crate) struct SkipProcessor { + to_skip: usize, + sender: channel::Sender>, + is_open: bool, +} + +impl SkipProcessor { + pub(crate) fn new(sender: channel::Sender>, to_skip: usize) -> Self { + Self { + sender, + to_skip, + is_open: true, + } + } +} + +#[async_trait(?Send)] +impl Processor for SkipProcessor { + async fn process(&mut self, value: DuperValue<'static>) { + if self.to_skip > 0 { + self.to_skip = self.to_skip.saturating_sub(1); + } else if self.is_open { + if self.sender.send(value).await.is_err() { + self.is_open = false; } } } diff --git a/duperq/src/query.rs b/duperq/src/query.rs index e8f969a..bac647d 100644 --- a/duperq/src/query.rs +++ b/duperq/src/query.rs @@ -17,7 +17,7 @@ use crate::{ RegexIdentifierFilter, TrueFilter, TryFromDuperValueError, }, formatter::{Formatter, FormatterAtom}, - processor::{FilterProcessor, OutputProcessor, Processor, TakeProcessor}, + processor::{FilterProcessor, OutputProcessor, Processor, SkipProcessor, TakeProcessor}, }; pub(crate) type CreateProcessorFn = @@ -47,6 +47,18 @@ pub fn query<'a>() )) } }), + just("skip") + .padded() + .ignore_then(integer()) + .try_map(|skip, span| { + if skip >= 0 { + Ok(Box::new(move |sender| { + Box::new(SkipProcessor::new(sender, skip as usize)) as Box + }) as CreateProcessorFn) + } else { + Err(Rich::custom(span, "skip parameter must be positive")) + } + }), )) .separated_by(just('|')) .collect() @@ -301,7 +313,7 @@ fn leaf_filter<'a>( .ignore_then( identifier() .map(|identifier| Some(identifier.to_string())) - .or(just("null").map(|_| None)) + .or(just("null").to(None)) .padded(), ) .map(|value| { @@ -312,7 +324,7 @@ fn leaf_filter<'a>( .ignore_then( identifier() .map(|identifier| Some(identifier.to_string())) - .or(just("null").map(|_| None)) + .or(just("null").to(None)) .padded(), ) .map(|value| { @@ -397,25 +409,25 @@ fn leaf_filter<'a>( is_op .ignore_then( choice(( - just("Object").map(|_| IsFilter::Object), - just("Array").map(|_| IsFilter::Array), - just("Tuple").map(|_| IsFilter::Tuple), - just("String").map(|_| IsFilter::String), - just("Bytes").map(|_| IsFilter::Bytes), - just("Instant").map(|_| IsFilter::TemporalInstant), - just("ZonedDateTime").map(|_| IsFilter::TemporalZonedDateTime), - just("PlainDate").map(|_| IsFilter::TemporalPlainDate), - just("PlainTime").map(|_| IsFilter::TemporalPlainTime), - just("PlainDateTime").map(|_| IsFilter::TemporalPlainDateTime), - just("PlainYearMonth").map(|_| IsFilter::TemporalPlainYearMonth), - just("PlainMonthDay").map(|_| IsFilter::TemporalPlainMonthDay), - just("Duration").map(|_| IsFilter::TemporalDuration), - just("Temporal").map(|_| IsFilter::TemporalUnspecified), - just("Integer").map(|_| IsFilter::Integer), - just("Float").map(|_| IsFilter::Float), - just("Number").map(|_| IsFilter::Number), - just("Boolean").map(|_| IsFilter::Boolean), - just("Null").map(|_| IsFilter::Null), + 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(), ) @@ -435,15 +447,12 @@ fn fmt<'a>() -> impl Parser<'a, &'a str, OutputProcessor, extra::Err Ok(FormatterAtom::Fixed(unescaped.clone().into_owned())), Err(error) => Err(Rich::custom(span, error.to_string())), - })) + }), + ) .repeated() .collect::>() .delimited_by(just('"'), just('"')) @@ -452,3 +461,36 @@ fn fmt<'a>() -> impl Parser<'a, &'a str, OutputProcessor, extra::Err() -> impl Parser<'a, &'a str, &'a str, extra::Err>> + Clone { + let escaped_characters = just('\\') + .then(choice(( + one_of("\"\\/bfnrt0").to_slice(), + just('x').then(hex_digit().repeated().exactly(2)).to_slice(), + just('u').then(hex_digit().repeated().exactly(4)).to_slice(), + just('U').then(hex_digit().repeated().exactly(8)).to_slice(), + ))) + .to_slice(); + + none_of("\"\\$") + .and_is(control_character().not()) + .to_slice() + .or(escaped_characters) + .or(just('$').then(just('{').not()).to_slice()) + .repeated() + .at_least(1) + .to_slice() +} + +fn hex_digit<'a>() -> impl Parser<'a, &'a str, char, extra::Err>> + Clone { + choice((one_of('0'..='9'), one_of('a'..='f'), one_of('A'..='F'))) + .labelled("a hexadecimal digit") +} +fn control_character<'a>() -> impl Parser<'a, &'a str, char, extra::Err>> + Clone { + choice(( + one_of('\u{0000}'..='\u{0009}'), + one_of('\u{000b}'..='\u{001f}'), + just('\u{007f}'), + )) + .labelled("a control character or tab, excluding new line") +}