1#![no_std]
13#![forbid(unsafe_code)]
14#![warn(missing_docs)]
15
16extern crate alloc;
17#[cfg(feature = "std")]
18extern crate std;
19
20pub mod input;
21#[cfg(feature = "math")]
22mod math;
23#[cfg(feature = "regex")]
24mod regex;
25#[cfg(feature = "time")]
26mod time;
27
28#[cfg(feature = "std")]
29use alloc::string::String;
30#[cfg(feature = "format")]
31use alloc::string::ToString;
32use alloc::{boxed::Box, vec::Vec};
33#[cfg(feature = "log")]
34use bstr::BStr;
35use bstr::ByteSlice;
36use jaq_core::box_iter::{box_once, BoxIter};
37use jaq_core::native::{bome, run, unary, v, Filter, Fun};
38#[cfg(feature = "regex")]
39use jaq_core::Cv;
40#[cfg(any(feature = "regex", feature = "std"))]
41use jaq_core::ValT as _;
42use jaq_core::{load, Bind, DataT, Error, Exn, RunPtr, ValR, ValX, ValXs};
43
44pub fn defs() -> impl Iterator<Item = load::parse::Def<&'static str>> {
46 load::parse(include_str!("defs.jq"), |p| p.defs())
47 .unwrap()
48 .into_iter()
49}
50
51#[cfg(all(
58 feature = "std",
59 feature = "format",
60 feature = "log",
61 feature = "math",
62 feature = "regex",
63 feature = "time",
64))]
65pub fn funs<D: DataT>() -> impl Iterator<Item = Fun<D>>
66where
67 for<'a> D::V<'a>: ValT,
68{
69 base_funs().chain(extra_funs())
70}
71
72pub fn base_funs<D: DataT>() -> impl Iterator<Item = Fun<D>>
79where
80 for<'a> D::V<'a>: ValT,
81{
82 base_run().into_vec().into_iter().map(run)
83}
84
85#[cfg(all(
87 feature = "std",
88 feature = "format",
89 feature = "log",
90 feature = "math",
91 feature = "regex",
92 feature = "time",
93))]
94pub fn extra_funs<D: DataT>() -> impl Iterator<Item = Fun<D>>
95where
96 for<'a> D::V<'a>: ValT,
97{
98 [std(), format(), math(), regex(), time(), log()]
99 .into_iter()
100 .flat_map(|fs| fs.into_vec().into_iter().map(run))
101}
102
103pub trait ValT: jaq_core::ValT + Ord + From<f64> + From<usize> {
105 fn into_seq<S: FromIterator<Self>>(self) -> Result<S, Self>;
109
110 fn is_int(&self) -> bool;
112
113 fn as_isize(&self) -> Option<isize>;
119
120 fn as_f64(&self) -> Option<f64>;
125
126 fn is_utf8_str(&self) -> bool;
128
129 fn as_bytes(&self) -> Option<&[u8]>;
131
132 fn as_utf8_bytes(&self) -> Option<&[u8]> {
134 self.is_utf8_str().then(|| self.as_bytes()).flatten()
135 }
136
137 fn try_as_bytes(&self) -> Result<&[u8], Error<Self>> {
139 self.as_bytes().ok_or_else(|| self.fail_str())
140 }
141
142 fn try_as_utf8_bytes(&self) -> Result<&[u8], Error<Self>> {
144 self.as_utf8_bytes().ok_or_else(|| self.fail_str())
145 }
146
147 fn as_sub_str(&self, sub: &[u8]) -> Self;
150
151 fn from_utf8_bytes(b: impl AsRef<[u8]> + Send + 'static) -> Self;
153}
154
155trait ValTx: ValT + Sized {
157 fn into_vec(self) -> Result<Vec<Self>, Error<Self>> {
158 self.into_seq().map_err(|v| Error::typ(v, "array"))
159 }
160
161 fn try_as_isize(&self) -> Result<isize, Error<Self>> {
162 self.as_isize()
163 .ok_or_else(|| Error::typ(self.clone(), "integer"))
164 }
165
166 fn try_as_i32(&self) -> Result<i32, Error<Self>> {
167 self.try_as_isize()?.try_into().map_err(Error::str)
168 }
169
170 fn try_as_f64(&self) -> Result<f64, Error<Self>> {
171 self.as_f64()
172 .ok_or_else(|| Error::typ(self.clone(), "number"))
173 }
174
175 fn mutate_arr(self, f: impl FnOnce(&mut Vec<Self>)) -> ValR<Self> {
177 let mut a = self.into_vec()?;
178 f(&mut a);
179 Ok(Self::from_iter(a))
180 }
181
182 fn try_mutate_arr<'a, F>(self, f: F) -> ValX<'a, Self>
184 where
185 F: FnOnce(&mut Vec<Self>) -> Result<(), Exn<'a, Self>>,
186 {
187 let mut a = self.into_vec()?;
188 f(&mut a)?;
189 Ok(Self::from_iter(a))
190 }
191
192 fn round(self, f: impl FnOnce(f64) -> f64) -> ValR<Self> {
193 Ok(if self.is_int() {
194 self
195 } else {
196 let f = f(self.try_as_f64()?);
197 if f.is_finite() {
198 if isize::MIN as f64 <= f && f <= isize::MAX as f64 {
199 Self::from(f as isize)
200 } else {
201 Self::from_num(&alloc::format!("{f:.0}"))?
204 }
205 } else {
206 Self::from(f)
207 }
208 })
209 }
210
211 #[cfg(any(feature = "regex", feature = "time"))]
214 fn try_as_str(&self) -> Result<&str, Error<Self>> {
215 self.try_as_utf8_bytes()
216 .and_then(|s| core::str::from_utf8(s).map_err(Error::str))
217 }
218
219 fn map_utf8_str<B>(self, f: impl FnOnce(&[u8]) -> B) -> ValR<Self>
220 where
221 B: AsRef<[u8]> + Send + 'static,
222 {
223 Ok(Self::from_utf8_bytes(f(self.try_as_utf8_bytes()?)))
224 }
225
226 fn trim_utf8_with(&self, f: impl FnOnce(&[u8]) -> &[u8]) -> ValR<Self> {
227 Ok(self.as_sub_str(f(self.try_as_utf8_bytes()?)))
228 }
229
230 fn strip_fix<F>(self, fix: &Self, f: F) -> Result<Self, Error<Self>>
232 where
233 F: for<'a> FnOnce(&'a [u8], &[u8]) -> Option<&'a [u8]>,
234 {
235 Ok(match f(self.try_as_bytes()?, fix.try_as_bytes()?) {
236 Some(sub) => self.as_sub_str(sub),
237 None => self,
238 })
239 }
240
241 fn fail_str(&self) -> Error<Self> {
242 Error::typ(self.clone(), "string")
243 }
244}
245impl<T: ValT> ValTx for T {}
246
247fn sort_by<'a, V: ValT>(xs: &mut [V], f: impl Fn(V) -> ValXs<'a, V>) -> Result<(), Exn<'a, V>> {
249 let mut err = None;
251 xs.sort_by_cached_key(|x| {
252 if err.is_some() {
253 return Vec::new();
254 };
255 match f(x.clone()).collect() {
256 Ok(y) => y,
257 Err(e) => {
258 err = Some(e);
259 Vec::new()
260 }
261 }
262 });
263 err.map_or(Ok(()), Err)
264}
265
266fn group_by<'a, V: ValT>(xs: Vec<V>, f: impl Fn(V) -> ValXs<'a, V>) -> ValX<'a, V> {
268 let mut yx: Vec<(Vec<V>, V)> = xs
269 .into_iter()
270 .map(|x| Ok((f(x.clone()).collect::<Result<_, _>>()?, x)))
271 .collect::<Result<_, Exn<_>>>()?;
272
273 yx.sort_by(|(y1, _), (y2, _)| y1.cmp(y2));
274
275 let mut grouped = Vec::new();
276 let mut yx = yx.into_iter();
277 if let Some((mut group_y, first_x)) = yx.next() {
278 let mut group = Vec::from([first_x]);
279 for (y, x) in yx {
280 if group_y != y {
281 grouped.push(V::from_iter(core::mem::take(&mut group)));
282 group_y = y;
283 }
284 group.push(x);
285 }
286 if !group.is_empty() {
287 grouped.push(V::from_iter(group));
288 }
289 }
290
291 Ok(V::from_iter(grouped))
292}
293
294fn cmp_by<'a, V: Clone, F, R>(xs: Vec<V>, f: F, replace: R) -> Result<Option<V>, Exn<'a, V>>
296where
297 F: Fn(V) -> ValXs<'a, V>,
298 R: Fn(&[V], &[V]) -> bool,
299{
300 let iter = xs.into_iter();
301 let mut iter = iter.map(|x| (x.clone(), f(x).collect::<Result<Vec<_>, _>>()));
302 let (mut mx, mut my) = if let Some((x, y)) = iter.next() {
303 (x, y?)
304 } else {
305 return Ok(None);
306 };
307 for (x, y) in iter {
308 let y = y?;
309 if replace(&my, &y) {
310 (mx, my) = (x, y);
311 }
312 }
313 Ok(Some(mx))
314}
315
316fn explode<V: ValT>(s: &[u8]) -> impl Iterator<Item = ValR<V>> + '_ {
318 let invalid = [].iter();
319 Explode { s, invalid }.map(|r| match r {
320 Err(b) => Ok((-(b as isize)).into()),
321 Ok(c) => Ok(isize::try_from(c as u32).map_err(Error::str)?.into()),
323 })
324}
325
326struct Explode<'a> {
327 s: &'a [u8],
328 invalid: core::slice::Iter<'a, u8>,
329}
330impl Iterator for Explode<'_> {
331 type Item = Result<char, u8>;
332 fn next(&mut self) -> Option<Self::Item> {
333 self.invalid.next().map(|next| Err(*next)).or_else(|| {
334 let (c, size) = bstr::decode_utf8(self.s);
335 let (consumed, rest) = self.s.split_at(size);
336 self.s = rest;
337 c.map(Ok).or_else(|| {
338 self.invalid = consumed.iter();
340 self.invalid.next().map(|next| Err(*next))
341 })
342 })
343 }
344 fn size_hint(&self) -> (usize, Option<usize>) {
345 let max = self.s.len();
346 let min = self.s.len() / 4;
347 let inv = self.invalid.as_slice().len();
348 (min + inv, Some(max + inv))
349 }
350}
351
352fn implode<V: ValT>(xs: &[V]) -> Result<Vec<u8>, Error<V>> {
354 let mut v = Vec::with_capacity(xs.len());
355 for x in xs {
356 let i = x.try_as_isize()?;
358 if let Ok(b) = u8::try_from(-i) {
359 v.push(b)
360 } else {
361 let c = u32::try_from(i).ok().and_then(char::from_u32);
363 let c = c.ok_or_else(|| Error::str(format_args!("cannot use {i} as character")))?;
364 v.extend(c.encode_utf8(&mut [0; 4]).as_bytes())
365 }
366 }
367 Ok(v)
368}
369
370fn once_or_empty<'a, T: 'a, E: 'a>(r: Result<Option<T>, E>) -> BoxIter<'a, Result<T, E>> {
371 Box::new(r.transpose().into_iter())
372}
373
374fn floor(x: f64) -> f64 {
377 #[cfg(feature = "std")]
378 return x.floor();
379 #[cfg(not(feature = "std"))]
380 return no_std_float::floor(x);
381}
382
383fn round(x: f64) -> f64 {
384 #[cfg(feature = "std")]
385 return x.round();
386 #[cfg(not(feature = "std"))]
387 return no_std_float::round(x);
388}
389
390fn ceil(x: f64) -> f64 {
391 #[cfg(feature = "std")]
392 return x.ceil();
393 #[cfg(not(feature = "std"))]
394 return no_std_float::ceil(x);
395}
396
397#[cfg(any(not(feature = "std"), test))]
398mod no_std_float {
399 const SIGN_MASK: u64 = 1 << 63;
400 const SIG_BITS: i32 = 52;
401 const SIG_MASK: u64 = (1 << SIG_BITS) - 1;
402 const EXPONENT_BIAS: i32 = 1023;
403
404 fn trunc(x: f64) -> f64 {
407 let xi = x.to_bits();
408 let e = ((xi >> SIG_BITS) & 0x7ff) as i32 - EXPONENT_BIAS;
409 if e >= SIG_BITS {
410 return x;
411 }
412 let clear_mask = if e < 0 { !SIGN_MASK } else { SIG_MASK >> e };
413 let cleared = xi & clear_mask;
414 f64::from_bits(xi ^ cleared)
415 }
416
417 pub fn floor(x: f64) -> f64 {
418 let trunc = trunc(x);
419 if x < trunc {
420 trunc - 1.0
421 } else {
422 trunc
423 }
424 }
425
426 pub fn round(x: f64) -> f64 {
427 let trunc = trunc(x);
428 let fract = x - trunc;
429 if fract >= 0.5 {
430 trunc + 1.0
431 } else if fract <= -0.5 {
432 trunc - 1.0
433 } else {
434 trunc
435 }
436 }
437
438 pub fn ceil(x: f64) -> f64 {
439 let trunc = trunc(x);
440 if x > trunc {
441 trunc + 1.0
442 } else {
443 trunc
444 }
445 }
446
447 #[cfg(all(test, feature = "std"))]
448 mod tests {
449 #[track_caller]
450 fn assert_same(actual: f64, expected: f64) {
451 if expected.is_nan() {
452 assert!(actual.is_nan());
453 } else {
454 assert_eq!(actual.to_bits(), expected.to_bits());
455 }
456 }
457
458 #[test]
459 fn matches_std() {
460 let values = [
461 f64::NEG_INFINITY,
462 -((1_u64 << 53) as f64),
463 -1.5,
464 -0.5,
465 -f64::from_bits(1),
466 -0.0,
467 0.0,
468 f64::from_bits(1),
469 0.5,
470 1.5,
471 (1_u64 << 53) as f64,
472 f64::INFINITY,
473 f64::NAN,
474 ];
475 for x in values {
476 assert_same(super::trunc(x), x.trunc());
477 assert_same(super::floor(x), x.floor());
478 assert_same(super::round(x), x.round());
479 assert_same(super::ceil(x), x.ceil());
480 }
481 }
482 }
483}
484
485#[allow(clippy::unit_arg)]
486fn base_run<D: DataT>() -> Box<[Filter<RunPtr<D>>]>
487where
488 for<'a> D::V<'a>: ValT,
489{
490 let f = || [Bind::Fun(())].into();
491 Box::new([
492 ("floor", v(0), |cv| bome(cv.1.round(floor))),
493 ("round", v(0), |cv| bome(cv.1.round(round))),
494 ("ceil", v(0), |cv| bome(cv.1.round(ceil))),
495 ("utf8bytelength", v(0), |cv| {
496 bome(cv.1.try_as_utf8_bytes().map(|s| (s.len() as isize).into()))
497 }),
498 ("explode", v(0), |cv| {
499 bome(cv.1.try_as_utf8_bytes().and_then(|s| explode(s).collect()))
500 }),
501 ("implode", v(0), |cv| {
502 let implode = |s: Vec<_>| implode(&s);
503 bome(cv.1.into_vec().and_then(implode).map(D::V::from_utf8_bytes))
504 }),
505 ("ascii_downcase", v(0), |cv| {
506 bome(cv.1.map_utf8_str(ByteSlice::to_ascii_lowercase))
507 }),
508 ("ascii_upcase", v(0), |cv| {
509 bome(cv.1.map_utf8_str(ByteSlice::to_ascii_uppercase))
510 }),
511 ("reverse", v(0), |cv| bome(cv.1.mutate_arr(|a| a.reverse()))),
512 ("sort", v(0), |cv| bome(cv.1.mutate_arr(|a| a.sort()))),
513 ("sort_by", f(), |mut cv| {
514 let (f, fc) = cv.0.pop_fun();
515 let f = move |v| f.run((fc.clone(), v));
516 box_once(cv.1.try_mutate_arr(|a| sort_by(a, f)))
517 }),
518 ("group_by", f(), |mut cv| {
519 let (f, fc) = cv.0.pop_fun();
520 let f = move |v| f.run((fc.clone(), v));
521 box_once((|| group_by(cv.1.into_vec()?, f))())
522 }),
523 ("min_by_or_empty", f(), |mut cv| {
524 let (f, fc) = cv.0.pop_fun();
525 let f = move |a| cmp_by(a, |v| f.run((fc.clone(), v)), |my, y| y < my);
526 once_or_empty(cv.1.into_vec().map_err(Exn::from).and_then(f))
527 }),
528 ("max_by_or_empty", f(), |mut cv| {
529 let (f, fc) = cv.0.pop_fun();
530 let f = move |a| cmp_by(a, |v| f.run((fc.clone(), v)), |my, y| y >= my);
531 once_or_empty(cv.1.into_vec().map_err(Exn::from).and_then(f))
532 }),
533 ("startswith", v(1), |cv| {
534 unary(cv, |v, s| {
535 Ok(v.try_as_bytes()?.starts_with(s.try_as_bytes()?).into())
536 })
537 }),
538 ("endswith", v(1), |cv| {
539 unary(cv, |v, s| {
540 Ok(v.try_as_bytes()?.ends_with(s.try_as_bytes()?).into())
541 })
542 }),
543 ("ltrimstr", v(1), |cv| {
544 unary(cv, |v, pre| v.strip_fix(&pre, <[u8]>::strip_prefix))
545 }),
546 ("rtrimstr", v(1), |cv| {
547 unary(cv, |v, suf| v.strip_fix(&suf, <[u8]>::strip_suffix))
548 }),
549 ("trim", v(0), |cv| {
550 bome(cv.1.trim_utf8_with(ByteSlice::trim))
551 }),
552 ("ltrim", v(0), |cv| {
553 bome(cv.1.trim_utf8_with(ByteSlice::trim_start))
554 }),
555 ("rtrim", v(0), |cv| {
556 bome(cv.1.trim_utf8_with(ByteSlice::trim_end))
557 }),
558 ("escape_sh", v(0), |cv| {
559 bome(
560 cv.1.try_as_utf8_bytes()
561 .map(|s| ValT::from_utf8_bytes(s.replace(b"'", b"'\\''"))),
562 )
563 }),
564 ("halt", v(1), |mut cv| {
565 let exit_code = cv.0.pop_var().try_as_i32().map_err(Exn::from);
566 box_once(exit_code.and_then(|exit_code| Err(Exn::halt(exit_code))))
567 }),
568 ])
569}
570
571#[cfg(feature = "std")]
572fn now<V: From<String>>() -> Result<f64, Error<V>> {
573 use std::time::{SystemTime, UNIX_EPOCH};
574 SystemTime::now()
575 .duration_since(UNIX_EPOCH)
576 .map(|x| x.as_secs_f64())
577 .map_err(Error::str)
578}
579
580#[cfg(feature = "std")]
581fn std<D: DataT>() -> Box<[Filter<RunPtr<D>>]>
582where
583 for<'a> D::V<'a>: ValT,
584{
585 use std::env::vars;
586 Box::new([
587 ("env", v(0), |_| {
588 bome(D::V::from_map(
589 vars().map(|(k, v)| (D::V::from(k), D::V::from(v))),
590 ))
591 }),
592 ("now", v(0), |_| bome(now().map(D::V::from))),
593 ])
594}
595
596#[cfg(feature = "format")]
597fn replace(s: &[u8], patterns: &[&str], replacements: &[&str]) -> Vec<u8> {
598 let ac = aho_corasick::AhoCorasick::new(patterns).unwrap();
599 ac.replace_all_bytes(s, replacements)
600}
601
602#[cfg(feature = "format")]
603fn format<D: DataT>() -> Box<[Filter<RunPtr<D>>]>
604where
605 for<'a> D::V<'a>: ValT,
606{
607 const HTML_PATS: [&str; 5] = ["<", ">", "&", "\'", "\""];
608 const HTML_REPS: [&str; 5] = ["<", ">", "&", "'", """];
609 Box::new([
610 ("escape_html", v(0), |cv| {
611 bome(cv.1.map_utf8_str(|s| replace(s, &HTML_PATS, &HTML_REPS)))
612 }),
613 ("unescape_html", v(0), |cv| {
614 bome(cv.1.map_utf8_str(|s| replace(s, &HTML_REPS, &HTML_PATS)))
615 }),
616 ("encode_uri", v(0), |cv| {
617 bome(cv.1.map_utf8_str(|s| urlencoding::encode_binary(s).to_string()))
618 }),
619 ("decode_uri", v(0), |cv| {
620 bome(cv.1.map_utf8_str(|s| urlencoding::decode_binary(s).to_vec()))
621 }),
622 ("encode_base64", v(0), |cv| {
623 use base64::{engine::general_purpose::STANDARD, Engine};
624 bome(cv.1.map_utf8_str(|s| STANDARD.encode(s)))
625 }),
626 ("decode_base64", v(0), |cv| {
627 use base64::{engine::general_purpose::STANDARD, Engine};
628 bome(cv.1.try_as_utf8_bytes().and_then(|s| {
629 STANDARD
630 .decode(s)
631 .map_err(Error::str)
632 .map(ValT::from_utf8_bytes)
633 }))
634 }),
635 ])
636}
637
638#[cfg(feature = "math")]
639fn math<D: DataT>() -> Box<[Filter<RunPtr<D>>]>
640where
641 for<'a> D::V<'a>: ValT,
642{
643 let rename = |name, (_name, arity, f): Filter<RunPtr<D>>| (name, arity, f);
644 Box::new([
645 math::f_f!(acos),
646 math::f_f!(acosh),
647 math::f_f!(asin),
648 math::f_f!(asinh),
649 math::f_f!(atan),
650 math::f_f!(atanh),
651 math::f_f!(cbrt),
652 math::f_f!(cos),
653 math::f_f!(cosh),
654 math::f_f!(erf),
655 math::f_f!(erfc),
656 math::f_f!(exp),
657 math::f_f!(exp10),
658 math::f_f!(exp2),
659 math::f_f!(expm1),
660 math::f_f!(fabs),
661 math::f_fi!(frexp),
662 math::f_i!(ilogb),
663 math::f_f!(j0),
664 math::f_f!(j1),
665 math::f_f!(lgamma),
666 math::f_f!(log),
667 math::f_f!(log10),
668 math::f_f!(log1p),
669 math::f_f!(log2),
670 math::f_ff!(modf),
672 rename("nearbyint", math::f_f!(round)),
673 math::f_f!(rint),
675 math::f_f!(sin),
677 math::f_f!(sinh),
678 math::f_f!(sqrt),
679 math::f_f!(tan),
680 math::f_f!(tanh),
681 math::f_f!(tgamma),
682 math::f_f!(trunc),
683 math::f_f!(y0),
684 math::f_f!(y1),
685 math::ff_f!(atan2),
686 math::ff_f!(copysign),
687 math::ff_f!(fdim),
689 math::ff_f!(fmax),
690 math::ff_f!(fmin),
691 math::ff_f!(fmod),
692 math::ff_f!(hypot),
693 math::if_f!(jn),
694 math::fi_f!(ldexp),
695 math::ff_f!(nextafter),
696 math::ff_f!(pow),
698 math::ff_f!(remainder),
699 rename("scalbln", math::fi_f!(scalbn)),
701 math::if_f!(yn),
702 math::fff_f!(fma),
703 ])
704}
705
706#[cfg(feature = "regex")]
707fn re<'a, D: DataT>(s: bool, m: bool, mut cv: Cv<'a, D>) -> ValR<D::V<'a>>
708where
709 D::V<'a>: ValT,
710{
711 let flags = cv.0.pop_var();
712 let re = cv.0.pop_var();
713
714 use crate::regex::Part::{Matches, Mismatch};
715 let fail_flag = |e| Error::str(format_args!("invalid regex flag: {e}"));
716 let fail_re = |e| Error::str(format_args!("invalid regex: {e}"));
717
718 let flags = regex::Flags::new(flags.try_as_str()?).map_err(fail_flag)?;
719 let re = flags.regex(re.try_as_str()?).map_err(fail_re)?;
720 let out = regex::regex(cv.1.try_as_utf8_bytes()?, &re, flags, (s, m));
721 let sub = |s| cv.1.as_sub_str(s);
722 let out = out.into_iter().map(|out| match out {
723 Matches(ms) => ms
724 .into_iter()
725 .map(|m| D::V::from_map(m.fields(sub)))
726 .collect(),
727 Mismatch(s) => Ok(sub(s)),
728 });
729 out.collect()
730}
731
732#[cfg(feature = "regex")]
733fn regex<D: DataT>() -> Box<[Filter<RunPtr<D>>]>
734where
735 for<'a> D::V<'a>: ValT,
736{
737 let vv = || [Bind::Var(()), Bind::Var(())].into();
738 Box::new([
739 ("matches", vv(), |cv| bome(re(false, true, cv))),
740 ("split_matches", vv(), |cv| bome(re(true, true, cv))),
741 ("split_", vv(), |cv| bome(re(true, false, cv))),
742 ])
743}
744
745#[cfg(feature = "time")]
746fn time<D: DataT>() -> Box<[Filter<RunPtr<D>>]>
747where
748 for<'a> D::V<'a>: ValT,
749{
750 use jiff::tz::TimeZone;
751 Box::new([
752 ("fromdateiso8601", v(0), |cv| {
753 bome(cv.1.try_as_str().and_then(time::from_iso8601))
754 }),
755 ("todateiso8601", v(0), |cv| {
756 bome(time::to_iso8601(&cv.1).map(D::V::from))
757 }),
758 ("strftime", v(1), |cv| {
759 unary(cv, |v, fmt| {
760 time::strftime(&v, fmt.try_as_str()?, TimeZone::UTC)
761 })
762 }),
763 ("strflocaltime", v(1), |cv| {
764 unary(cv, |v, fmt| {
765 time::strftime(&v, fmt.try_as_str()?, TimeZone::system())
766 })
767 }),
768 ("gmtime", v(0), |cv| {
769 bome(time::gmtime(&cv.1, TimeZone::UTC))
770 }),
771 ("localtime", v(0), |cv| {
772 bome(time::gmtime(&cv.1, TimeZone::system()))
773 }),
774 ("strptime", v(1), |cv| {
775 unary(cv, |v, fmt| {
776 time::strptime(v.try_as_str()?, fmt.try_as_str()?)
777 })
778 }),
779 ("mktime", v(0), |cv| bome(time::mktime(&cv.1))),
780 ])
781}
782
783#[cfg(feature = "log")]
784fn log<D: DataT>() -> Box<[Filter<RunPtr<D>>]>
785where
786 for<'a> D::V<'a>: ValT,
787{
788 fn eprint_raw<V: ValT>(v: &V) {
789 if let Some(s) = v.as_utf8_bytes() {
790 log::error!("{}", BStr::new(s))
791 } else {
792 log::error!("{v}")
793 }
794 }
795 macro_rules! empty_with {
797 ( $eff:expr ) => {
798 |cv| {
799 $eff(&cv.1);
800 Box::new(core::iter::empty())
801 }
802 };
803 }
804 Box::new([
805 ("debug_empty", v(0), empty_with!(|x| log::debug!("{x}"))),
806 ("stderr_empty", v(0), empty_with!(eprint_raw)),
807 ])
808}