jaq_std/
lib.rs

1//! Standard library for a JSON query language.
2//!
3//! The standard library provides a set of filters.
4//! These filters are either implemented as definitions or as functions.
5//! For example, the standard library provides the `map(f)` filter,
6//! which is defined using the more elementary filter `[.[] | f]`.
7//!
8//! If you want to use the standard library in jaq, then
9//! you'll likely only need [`funs`] and [`defs`].
10//! Most other functions are relevant if you
11//! want to implement your own native filters.
12#![no_std]
13#![forbid(unsafe_code)]
14#![warn(missing_docs)]
15
16extern crate alloc;
17#[cfg(feature = "std")]
18extern crate std;
19
20#[cfg(feature = "math")]
21mod math;
22#[cfg(feature = "regex")]
23mod regex;
24#[cfg(feature = "time")]
25mod time;
26
27use alloc::string::{String, ToString};
28use alloc::{borrow::ToOwned, boxed::Box, vec::Vec};
29use jaq_core::box_iter::{box_once, then, BoxIter};
30use jaq_core::{load, Bind, Cv, Error, Exn, FilterT, Native, RunPtr, UpdatePtr, ValR, ValX, ValXs};
31
32/// Definitions of the standard library.
33pub fn defs() -> impl Iterator<Item = load::parse::Def<&'static str>> {
34    load::parse(include_str!("defs.jq"), |p| p.defs())
35        .unwrap()
36        .into_iter()
37}
38
39/// Name, arguments, and implementation of a filter.
40pub type Filter<F> = (&'static str, Box<[Bind]>, F);
41
42/// Named filters available by default in jaq
43/// which are implemented as native filters, such as `length`, `keys`, ...,
44/// but also `now`, `debug`, `fromdateiso8601`, ...
45///
46/// This is the combination of [`base_funs`] and [`extra_funs`].
47/// It does not include filters implemented by definition, such as `map`.
48#[cfg(all(
49    feature = "std",
50    feature = "format",
51    feature = "log",
52    feature = "math",
53    feature = "regex",
54    feature = "time",
55))]
56pub fn funs<V: ValT>() -> impl Iterator<Item = Filter<Native<V>>> {
57    base_funs().chain(extra_funs())
58}
59
60/// Minimal set of filters that are generic over the value type.
61/// Return the minimal set of named filters available in jaq
62/// which are implemented as native filters, such as `length`, `keys`, ...,
63/// but not `now`, `debug`, `fromdateiso8601`, ...
64///
65/// Does not return filters from the standard library, such as `map`.
66pub fn base_funs<V: ValT>() -> impl Iterator<Item = Filter<Native<V>>> {
67    let base_run = base_run().into_vec().into_iter().map(run);
68    base_run.chain([upd(error())])
69}
70
71/// Supplementary set of filters that are generic over the value type.
72#[cfg(all(
73    feature = "std",
74    feature = "format",
75    feature = "log",
76    feature = "math",
77    feature = "regex",
78    feature = "time",
79))]
80pub fn extra_funs<V: ValT>() -> impl Iterator<Item = Filter<Native<V>>> {
81    [std(), format(), math(), regex(), time()]
82        .into_iter()
83        .flat_map(|fs| fs.into_vec().into_iter().map(run))
84        .chain([debug(), stderr()].map(upd))
85}
86
87/// Values that the core library can operate on.
88pub trait ValT: jaq_core::ValT + Ord + From<f64> {
89    /// Convert an array into a sequence.
90    ///
91    /// This returns the original value as `Err` if it is not an array.
92    fn into_seq<S: FromIterator<Self>>(self) -> Result<S, Self>;
93
94    /// Use the value as integer.
95    fn as_isize(&self) -> Option<isize>;
96
97    /// Use the value as floating-point number.
98    ///
99    /// This may fail in more complex ways than [`Self::as_isize`],
100    /// because the value may either be
101    /// not a number or a number that does not fit into [`f64`].
102    fn as_f64(&self) -> Result<f64, Error<Self>>;
103}
104
105/// Convenience trait for implementing the core functions.
106trait ValTx: ValT + Sized {
107    fn into_vec(self) -> Result<Vec<Self>, Error<Self>> {
108        self.into_seq().map_err(|v| Error::typ(v, "array"))
109    }
110
111    fn try_as_str(&self) -> Result<&str, Error<Self>> {
112        self.as_str()
113            .ok_or_else(|| Error::typ(self.clone(), "string"))
114    }
115
116    fn try_as_isize(&self) -> Result<isize, Error<Self>> {
117        self.as_isize()
118            .ok_or_else(|| Error::typ(self.clone(), "integer"))
119    }
120
121    #[cfg(feature = "math")]
122    /// Use as an i32 to be given as an argument to a libm function.
123    fn try_as_i32(&self) -> Result<i32, Error<Self>> {
124        self.try_as_isize()?.try_into().map_err(Error::str)
125    }
126
127    /// Apply a function to an array.
128    fn mutate_arr(self, f: impl FnOnce(&mut Vec<Self>)) -> ValR<Self> {
129        let mut a = self.into_vec()?;
130        f(&mut a);
131        Ok(Self::from_iter(a))
132    }
133
134    /// Apply a function to an array.
135    fn try_mutate_arr<'a, F>(self, f: F) -> ValX<'a, Self>
136    where
137        F: FnOnce(&mut Vec<Self>) -> Result<(), Exn<'a, Self>>,
138    {
139        let mut a = self.into_vec()?;
140        f(&mut a)?;
141        Ok(Self::from_iter(a))
142    }
143
144    fn mutate_str(self, f: impl FnOnce(&mut str)) -> ValR<Self> {
145        let mut s = self.try_as_str()?.to_owned();
146        f(&mut s);
147        Ok(Self::from(s))
148    }
149
150    fn round(self, f: impl FnOnce(f64) -> f64) -> ValR<Self> {
151        if self.as_isize().is_some() {
152            Ok(self)
153        } else {
154            Ok(Self::from(f(self.as_f64()?) as isize))
155        }
156    }
157
158    fn trim_with(self, f: impl FnOnce(&str) -> &str) -> ValR<Self> {
159        let s = self.try_as_str()?;
160        let t = f(s);
161        Ok(if core::ptr::eq(s, t) {
162            // the input was already trimmed, so do not allocate new memory
163            self
164        } else {
165            t.to_string().into()
166        })
167    }
168}
169impl<T: ValT> ValTx for T {}
170
171/// Convert a filter with a run pointer to a native filter.
172pub fn run<V>((name, arity, run): Filter<RunPtr<V>>) -> Filter<Native<V>> {
173    (name, arity, Native::new(run))
174}
175
176/// Convert a filter with a run and an update pointer to a native filter.
177fn upd<V>((name, arity, (run, update)): Filter<(RunPtr<V>, UpdatePtr<V>)>) -> Filter<Native<V>> {
178    (name, arity, Native::new(run).with_update(update))
179}
180
181/// Sort array by the given function.
182fn sort_by<'a, V: ValT>(xs: &mut [V], f: impl Fn(V) -> ValXs<'a, V>) -> Result<(), Exn<'a, V>> {
183    // Some(e) iff an error has previously occurred
184    let mut err = None;
185    xs.sort_by_cached_key(|x| {
186        if err.is_some() {
187            return Vec::new();
188        };
189        match f(x.clone()).collect() {
190            Ok(y) => y,
191            Err(e) => {
192                err = Some(e);
193                Vec::new()
194            }
195        }
196    });
197    err.map_or(Ok(()), Err)
198}
199
200/// Group an array by the given function.
201fn group_by<'a, V: ValT>(xs: Vec<V>, f: impl Fn(V) -> ValXs<'a, V>) -> ValX<'a, V> {
202    let mut yx: Vec<(Vec<V>, V)> = xs
203        .into_iter()
204        .map(|x| Ok((f(x.clone()).collect::<Result<_, _>>()?, x)))
205        .collect::<Result<_, Exn<_>>>()?;
206
207    yx.sort_by(|(y1, _), (y2, _)| y1.cmp(y2));
208
209    let mut grouped = Vec::new();
210    let mut yx = yx.into_iter();
211    if let Some((mut group_y, first_x)) = yx.next() {
212        let mut group = Vec::from([first_x]);
213        for (y, x) in yx {
214            if group_y != y {
215                grouped.push(V::from_iter(core::mem::take(&mut group)));
216                group_y = y;
217            }
218            group.push(x);
219        }
220        if !group.is_empty() {
221            grouped.push(V::from_iter(group));
222        }
223    }
224
225    Ok(V::from_iter(grouped))
226}
227
228/// Get the minimum or maximum element from an array according to the given function.
229fn cmp_by<'a, V: Clone, F, R>(xs: Vec<V>, f: F, replace: R) -> Result<Option<V>, Exn<'a, V>>
230where
231    F: Fn(V) -> ValXs<'a, V>,
232    R: Fn(&[V], &[V]) -> bool,
233{
234    let iter = xs.into_iter();
235    let mut iter = iter.map(|x| (x.clone(), f(x).collect::<Result<Vec<_>, _>>()));
236    let (mut mx, mut my) = if let Some((x, y)) = iter.next() {
237        (x, y?)
238    } else {
239        return Ok(None);
240    };
241    for (x, y) in iter {
242        let y = y?;
243        if replace(&my, &y) {
244            (mx, my) = (x, y);
245        }
246    }
247    Ok(Some(mx))
248}
249
250/// Convert a string into an array of its Unicode codepoints.
251fn explode<V: ValT>(s: &str) -> impl Iterator<Item = ValR<V>> + '_ {
252    // conversion from u32 to isize may fail on 32-bit systems for high values of c
253    let conv = |c: char| Ok(isize::try_from(c as u32).map_err(Error::str)?.into());
254    s.chars().map(conv)
255}
256
257/// Convert an array of Unicode codepoints into a string.
258fn implode<V: ValT>(xs: &[V]) -> Result<String, Error<V>> {
259    xs.iter().map(as_codepoint).collect()
260}
261
262/// If the value is an integer representing a valid Unicode codepoint, return it, else fail.
263fn as_codepoint<V: ValT>(v: &V) -> Result<char, Error<V>> {
264    let i = v.try_as_isize()?;
265    // conversion from isize to u32 may fail on 64-bit systems for high values of c
266    let u = u32::try_from(i).map_err(Error::str)?;
267    // may fail e.g. on `[1114112] | implode`
268    char::from_u32(u).ok_or_else(|| Error::str(format_args!("cannot use {u} as character")))
269}
270
271/// This implements a ~10x faster version of:
272/// ~~~ text
273/// def range($from; $to; $by): $from |
274///    if $by > 0 then while(.  < $to; . + $by)
275///  elif $by < 0 then while(.  > $to; . + $by)
276///    else            while(. != $to; . + $by)
277///    end;
278/// ~~~
279fn range<V: ValT>(mut from: ValX<V>, to: V, by: V) -> impl Iterator<Item = ValX<V>> {
280    use core::cmp::Ordering::{Equal, Greater, Less};
281    let cmp = by.partial_cmp(&V::from(0)).unwrap_or(Equal);
282    core::iter::from_fn(move || match from.clone() {
283        Ok(x) => match cmp {
284            Greater => x < to,
285            Less => x > to,
286            Equal => x != to,
287        }
288        .then(|| core::mem::replace(&mut from, (x + by.clone()).map_err(Exn::from))),
289        e @ Err(_) => {
290            // return None after the error
291            from = Ok(to.clone());
292            Some(e)
293        }
294    })
295}
296
297fn once_or_empty<'a, T: 'a, E: 'a>(r: Result<Option<T>, E>) -> BoxIter<'a, Result<T, E>> {
298    Box::new(r.transpose().into_iter())
299}
300
301/// Box Once and Map Errors to exceptions.
302fn bome<'a, V: 'a>(r: ValR<V>) -> ValXs<'a, V> {
303    box_once(r.map_err(Exn::from))
304}
305
306/// Create a filter that takes a single variable argument and whose output is given by
307/// the function `f` that takes the input value and the value of the variable.
308pub fn unary<'a, V: Clone>(mut cv: Cv<'a, V>, f: impl Fn(V, V) -> ValR<V> + 'a) -> ValXs<'a, V> {
309    bome(f(cv.1, cv.0.pop_var()))
310}
311
312/// Creates `n` variable arguments.
313pub fn v(n: usize) -> Box<[Bind]> {
314    core::iter::repeat(Bind::Var(())).take(n).collect()
315}
316
317#[allow(clippy::unit_arg)]
318fn base_run<V: ValT, F: FilterT<V = V>>() -> Box<[Filter<RunPtr<V, F>>]> {
319    let f = || [Bind::Fun(())].into();
320    let vf = [Bind::Var(()), Bind::Fun(())].into();
321    Box::new([
322        ("inputs", v(0), |_, cv| {
323            Box::new(
324                cv.0.inputs()
325                    .map(|r| r.map_err(|e| Exn::from(Error::str(e)))),
326            )
327        }),
328        ("floor", v(0), |_, cv| bome(cv.1.round(f64::floor))),
329        ("round", v(0), |_, cv| bome(cv.1.round(f64::round))),
330        ("ceil", v(0), |_, cv| bome(cv.1.round(f64::ceil))),
331        ("utf8bytelength", v(0), |_, cv| {
332            bome(cv.1.try_as_str().map(|s| (s.len() as isize).into()))
333        }),
334        ("explode", v(0), |_, cv| {
335            bome(cv.1.try_as_str().and_then(|s| explode(s).collect()))
336        }),
337        ("implode", v(0), |_, cv| {
338            bome(cv.1.into_vec().and_then(|s| implode(&s)).map(V::from))
339        }),
340        ("ascii_downcase", v(0), |_, cv| {
341            bome(cv.1.mutate_str(str::make_ascii_lowercase))
342        }),
343        ("ascii_upcase", v(0), |_, cv| {
344            bome(cv.1.mutate_str(str::make_ascii_uppercase))
345        }),
346        ("reverse", v(0), |_, cv| {
347            bome(cv.1.mutate_arr(|a| a.reverse()))
348        }),
349        ("sort", v(0), |_, cv| bome(cv.1.mutate_arr(|a| a.sort()))),
350        ("sort_by", f(), |lut, mut cv| {
351            let (f, fc) = cv.0.pop_fun();
352            let f = move |v| f.run(lut, (fc.clone(), v));
353            box_once(cv.1.try_mutate_arr(|a| sort_by(a, f)))
354        }),
355        ("group_by", f(), |lut, mut cv| {
356            let (f, fc) = cv.0.pop_fun();
357            let f = move |v| f.run(lut, (fc.clone(), v));
358            box_once((|| group_by(cv.1.into_vec()?, f))())
359        }),
360        ("min_by_or_empty", f(), |lut, mut cv| {
361            let (f, fc) = cv.0.pop_fun();
362            let f = move |a| cmp_by(a, |v| f.run(lut, (fc.clone(), v)), |my, y| y < my);
363            once_or_empty(cv.1.into_vec().map_err(Exn::from).and_then(f))
364        }),
365        ("max_by_or_empty", f(), |lut, mut cv| {
366            let (f, fc) = cv.0.pop_fun();
367            let f = move |a| cmp_by(a, |v| f.run(lut, (fc.clone(), v)), |my, y| y >= my);
368            once_or_empty(cv.1.into_vec().map_err(Exn::from).and_then(f))
369        }),
370        ("first", f(), |lut, mut cv| {
371            let (f, fc) = cv.0.pop_fun();
372            Box::new(f.run(lut, (fc, cv.1)).next().into_iter())
373        }),
374        ("last", f(), |lut, mut cv| {
375            let (f, fc) = cv.0.pop_fun();
376            once_or_empty(f.run(lut, (fc, cv.1)).try_fold(None, |_acc, x| x.map(Some)))
377        }),
378        ("limit", vf, |lut, mut cv| {
379            let (f, fc) = cv.0.pop_fun();
380            let n = cv.0.pop_var();
381            let pos = |n: isize| n.try_into().unwrap_or(0usize);
382            then(n.try_as_isize().map_err(Exn::from), |n| match pos(n) {
383                0 => Box::new(core::iter::empty()),
384                n => Box::new(f.run(lut, (fc, cv.1)).take(n)),
385            })
386        }),
387        ("range", v(3), |_, mut cv| {
388            let by = cv.0.pop_var();
389            let to = cv.0.pop_var();
390            let from = cv.0.pop_var();
391            Box::new(range(Ok(from), to, by))
392        }),
393        ("startswith", v(1), |_, cv| {
394            unary(cv, |v, s| {
395                Ok(v.try_as_str()?.starts_with(s.try_as_str()?).into())
396            })
397        }),
398        ("endswith", v(1), |_, cv| {
399            unary(cv, |v, s| {
400                Ok(v.try_as_str()?.ends_with(s.try_as_str()?).into())
401            })
402        }),
403        ("ltrimstr", v(1), |_, cv| {
404            unary(cv, |v, pre| {
405                Ok(v.try_as_str()?
406                    .strip_prefix(pre.try_as_str()?)
407                    .map_or_else(|| v.clone(), |s| V::from(s.to_owned())))
408            })
409        }),
410        ("rtrimstr", v(1), |_, cv| {
411            unary(cv, |v, suf| {
412                Ok(v.try_as_str()?
413                    .strip_suffix(suf.try_as_str()?)
414                    .map_or_else(|| v.clone(), |s| V::from(s.to_owned())))
415            })
416        }),
417        ("trim", v(0), |_, cv| bome(cv.1.trim_with(str::trim))),
418        ("ltrim", v(0), |_, cv| bome(cv.1.trim_with(str::trim_start))),
419        ("rtrim", v(0), |_, cv| bome(cv.1.trim_with(str::trim_end))),
420        ("escape_csv", v(0), |_, cv| {
421            bome(cv.1.try_as_str().map(|s| s.replace('"', "\"\"").into()))
422        }),
423        ("escape_sh", v(0), |_, cv| {
424            bome(cv.1.try_as_str().map(|s| s.replace('\'', r"'\''").into()))
425        }),
426    ])
427}
428
429#[cfg(feature = "std")]
430fn now<V: From<String>>() -> Result<f64, Error<V>> {
431    use std::time::{SystemTime, UNIX_EPOCH};
432    SystemTime::now()
433        .duration_since(UNIX_EPOCH)
434        .map(|x| x.as_secs_f64())
435        .map_err(Error::str)
436}
437
438#[cfg(feature = "std")]
439fn std<V: ValT>() -> Box<[Filter<RunPtr<V>>]> {
440    use std::env::vars;
441    Box::new([
442        ("env", v(0), |_, _| {
443            bome(V::from_map(vars().map(|(k, v)| (V::from(k), V::from(v)))))
444        }),
445        ("now", v(0), |_, _| bome(now().map(V::from))),
446        ("halt", v(0), |_, _| std::process::exit(0)),
447        ("halt_error", v(1), |_, mut cv| {
448            bome(cv.0.pop_var().try_as_isize().map(|exit_code| {
449                if let Some(s) = cv.1.as_str() {
450                    std::print!("{}", s);
451                } else {
452                    std::println!("{}", cv.1);
453                }
454                std::process::exit(exit_code as i32)
455            }))
456        }),
457    ])
458}
459
460#[cfg(feature = "format")]
461fn replace(s: &str, patterns: &[&str], replacements: &[&str]) -> String {
462    let ac = aho_corasick::AhoCorasick::new(patterns).unwrap();
463    ac.replace_all(s, replacements)
464}
465
466#[cfg(feature = "format")]
467fn format<V: ValT>() -> Box<[Filter<RunPtr<V>>]> {
468    Box::new([
469        ("escape_html", v(0), |_, cv| {
470            let pats = ["<", ">", "&", "\'", "\""];
471            let reps = ["&lt;", "&gt;", "&amp;", "&apos;", "&quot;"];
472            bome(cv.1.try_as_str().map(|s| replace(s, &pats, &reps).into()))
473        }),
474        ("escape_tsv", v(0), |_, cv| {
475            let pats = ["\n", "\r", "\t", "\\", "\0"];
476            let reps = ["\\n", "\\r", "\\t", "\\\\", "\\0"];
477            bome(cv.1.try_as_str().map(|s| replace(s, &pats, &reps).into()))
478        }),
479        ("encode_uri", v(0), |_, cv| {
480            use urlencoding::encode;
481            bome(cv.1.try_as_str().map(|s| encode(s).into_owned().into()))
482        }),
483        ("decode_uri", v(0), |_, cv| {
484            use urlencoding::decode;
485            bome(cv.1.try_as_str().and_then(|s| {
486                let d = decode(s).map_err(Error::str)?;
487                Ok(d.into_owned().into())
488            }))
489        }),
490        ("encode_base64", v(0), |_, cv| {
491            use base64::{engine::general_purpose::STANDARD, Engine};
492            bome(cv.1.try_as_str().map(|s| STANDARD.encode(s).into()))
493        }),
494        ("decode_base64", v(0), |_, cv| {
495            use base64::{engine::general_purpose::STANDARD, Engine};
496            use core::str::from_utf8;
497            bome(cv.1.try_as_str().and_then(|s| {
498                let d = STANDARD.decode(s).map_err(Error::str)?;
499                Ok(from_utf8(&d).map_err(Error::str)?.to_owned().into())
500            }))
501        }),
502    ])
503}
504
505#[cfg(feature = "math")]
506fn math<V: ValT>() -> Box<[Filter<RunPtr<V>>]> {
507    let rename = |name, (_name, arity, f): Filter<RunPtr<V>>| (name, arity, f);
508    Box::new([
509        math::f_f!(acos),
510        math::f_f!(acosh),
511        math::f_f!(asin),
512        math::f_f!(asinh),
513        math::f_f!(atan),
514        math::f_f!(atanh),
515        math::f_f!(cbrt),
516        math::f_f!(cos),
517        math::f_f!(cosh),
518        math::f_f!(erf),
519        math::f_f!(erfc),
520        math::f_f!(exp),
521        math::f_f!(exp10),
522        math::f_f!(exp2),
523        math::f_f!(expm1),
524        math::f_f!(fabs),
525        math::f_fi!(frexp),
526        math::f_i!(ilogb),
527        math::f_f!(j0),
528        math::f_f!(j1),
529        math::f_f!(lgamma),
530        math::f_f!(log),
531        math::f_f!(log10),
532        math::f_f!(log1p),
533        math::f_f!(log2),
534        // logb is implemented in jaq-std
535        math::f_ff!(modf),
536        rename("nearbyint", math::f_f!(round)),
537        // pow10 is implemented in jaq-std
538        math::f_f!(rint),
539        // significand is implemented in jaq-std
540        math::f_f!(sin),
541        math::f_f!(sinh),
542        math::f_f!(sqrt),
543        math::f_f!(tan),
544        math::f_f!(tanh),
545        math::f_f!(tgamma),
546        math::f_f!(trunc),
547        math::f_f!(y0),
548        math::f_f!(y1),
549        math::ff_f!(atan2),
550        math::ff_f!(copysign),
551        // drem is implemented in jaq-std
552        math::ff_f!(fdim),
553        math::ff_f!(fmax),
554        math::ff_f!(fmin),
555        math::ff_f!(fmod),
556        math::ff_f!(hypot),
557        math::if_f!(jn),
558        math::fi_f!(ldexp),
559        math::ff_f!(nextafter),
560        // nexttoward is implemented in jaq-std
561        math::ff_f!(pow),
562        math::ff_f!(remainder),
563        // scalb is implemented in jaq-std
564        rename("scalbln", math::fi_f!(scalbn)),
565        math::if_f!(yn),
566        math::fff_f!(fma),
567    ])
568}
569
570#[cfg(feature = "regex")]
571fn re<V: ValT>(s: bool, m: bool, mut cv: Cv<V>) -> ValR<V> {
572    let flags = cv.0.pop_var();
573    let re = cv.0.pop_var();
574
575    use crate::regex::Part::{Matches, Mismatch};
576    let fail_flag = |e| Error::str(format_args!("invalid regex flag: {e}"));
577    let fail_re = |e| Error::str(format_args!("invalid regex: {e}"));
578
579    let flags = regex::Flags::new(flags.try_as_str()?).map_err(fail_flag)?;
580    let re = flags.regex(re.try_as_str()?).map_err(fail_re)?;
581    let out = regex::regex(cv.1.try_as_str()?, &re, flags, (s, m));
582    let out = out.into_iter().map(|out| match out {
583        Matches(ms) => ms.into_iter().map(|m| V::from_map(m.fields())).collect(),
584        Mismatch(s) => Ok(V::from(s.to_string())),
585    });
586    out.collect()
587}
588
589#[cfg(feature = "regex")]
590fn regex<V: ValT>() -> Box<[Filter<RunPtr<V>>]> {
591    let vv = || [Bind::Var(()), Bind::Var(())].into();
592    Box::new([
593        ("matches", vv(), |_, cv| bome(re(false, true, cv))),
594        ("split_matches", vv(), |_, cv| bome(re(true, true, cv))),
595        ("split_", vv(), |_, cv| bome(re(true, false, cv))),
596    ])
597}
598
599#[cfg(feature = "time")]
600fn time<V: ValT>() -> Box<[Filter<RunPtr<V>>]> {
601    use chrono::{Local, Utc};
602    Box::new([
603        ("fromdateiso8601", v(0), |_, cv| {
604            bome(cv.1.try_as_str().and_then(time::from_iso8601))
605        }),
606        ("todateiso8601", v(0), |_, cv| {
607            bome(time::to_iso8601(&cv.1).map(V::from))
608        }),
609        ("strftime", v(1), |_, cv| {
610            unary(cv, |v, fmt| time::strftime(&v, fmt.try_as_str()?, Utc))
611        }),
612        ("strflocaltime", v(1), |_, cv| {
613            unary(cv, |v, fmt| time::strftime(&v, fmt.try_as_str()?, Local))
614        }),
615        ("gmtime", v(0), |_, cv| bome(time::gmtime(&cv.1, Utc))),
616        ("localtime", v(0), |_, cv| bome(time::gmtime(&cv.1, Local))),
617        ("strptime", v(1), |_, cv| {
618            unary(cv, |v, fmt| {
619                time::strptime(v.try_as_str()?, fmt.try_as_str()?)
620            })
621        }),
622        ("mktime", v(0), |_, cv| bome(time::mktime(&cv.1))),
623    ])
624}
625
626fn error<V, F>() -> Filter<(RunPtr<V, F>, UpdatePtr<V, F>)> {
627    fn err<V>(cv: Cv<V>) -> ValXs<V> {
628        bome(Err(Error::new(cv.1)))
629    }
630    ("error", v(0), (|_, cv| err(cv), |_, cv, _| err(cv)))
631}
632
633#[cfg(feature = "log")]
634/// Construct a filter that applies an effect function before returning its input.
635macro_rules! id_with {
636    ( $eff:expr ) => {
637        (
638            |_, cv| {
639                $eff(&cv.1);
640                box_once(Ok(cv.1))
641            },
642            |_, cv, f| {
643                $eff(&cv.1);
644                f(cv.1)
645            },
646        )
647    };
648}
649
650#[cfg(feature = "log")]
651fn debug<V: core::fmt::Display>() -> Filter<(RunPtr<V>, UpdatePtr<V>)> {
652    ("debug", v(0), id_with!(|x| log::debug!("{}", x)))
653}
654
655#[cfg(feature = "log")]
656fn stderr<V: ValT>() -> Filter<(RunPtr<V>, UpdatePtr<V>)> {
657    fn eprint_raw<V: ValT>(v: &V) {
658        if let Some(s) = v.as_str() {
659            log::error!("{}", s)
660        } else {
661            log::error!("{}", v)
662        }
663    }
664    ("stderr", v(0), id_with!(eprint_raw))
665}