1#![doc = include_str ! ("./../README.md")]
2#![forbid(unsafe_code)]
3
4pub mod types {
5 pub type Expr = resolver::Expr;
6 pub type Value = resolver::Value;
7
8 pub fn to_value<S: serde::Serialize>(v: S) -> Value {
9 resolver::to_value(v)
10 }
11}
12
13pub mod template {
14 use lazy_static::lazy_static;
15 use regex::Regex;
16
17 use crate::types::*;
18
19 lazy_static! {
20 static ref CONDITION_PATTERN: Regex = Regex::new(r"(<\?([^\?]*)\?>)").unwrap();
21 static ref CONTEXT_SYM: String = String::from("$");
22 }
23
24 pub fn resolve_template(
25 template: String,
26 context: Value,
27 ) -> Result<String, resolver::Error> {
28 let mut map = std::collections::HashMap::<String, String>::new();
29 for cap in CONDITION_PATTERN.captures_iter(&*template) {
30 let a = &cap[1];
31 let b = cap[2].trim();
32 if !b.is_empty() {
33 let mut expr = Expr::new(b)
34 .value(CONTEXT_SYM.to_string(), &context);
35 let value = expr.exec()?;
36 let value_str = match value {
37 Value::Null => "null".into(),
38 Value::Bool(boolean) => boolean.to_string(),
39 Value::Number(number) => number.to_string(),
40 Value::String(string) => string,
41 Value::Array(arr) => serde_json::to_string(&arr)
42 .unwrap_or_else(|_| "null".into()),
43 Value::Object(obj) => serde_json::to_string(&obj)
44 .unwrap_or_else(|_| "null".into())
45 };
46 map.insert(a.to_string(), value_str);
47 } else {
48 map.insert(a.to_string(), "".into());
49 }
50 }
51
52 let mut result = template;
53 for (key, value) in map.iter() {
54 result = result.replace(key, value);
56 }
57
58 Ok(result)
59 }
60}
61
62pub mod eval_wrapper {
63 use chrono::{Datelike, Timelike};
64 use resolver::{to_value, Expr};
65 use regex::Regex;
66 use string_utility::prelude::*;
68
69 use crate::types::*;
70
71 #[derive(Debug, Clone)]
72 pub struct EvalConfig {
73 pub include_maths: bool,
74 pub include_datetime: bool,
75 pub include_cast: bool,
76 pub include_regex: bool,
77 }
78
79 impl EvalConfig {
80 pub fn any(&self) -> bool {
81 self.include_maths
82 || self.include_datetime
83 || self.include_cast
84 || self.include_regex
85 }
86 }
87
88 impl Default for EvalConfig {
89 fn default() -> Self {
90 Self {
91 include_maths: true,
92 include_datetime: true,
93 include_cast: true,
94 include_regex: true,
95 }
96 }
97 }
98
99 fn value_to_string(val: &Value) -> String {
100 match val {
101 Value::Number(x) => x.as_f64().unwrap().to_string(),
102 Value::Bool(x) => x.to_string(),
103 Value::String(x) => x.to_string(),
104 Value::Array(x) => serde_json::to_string(x)
105 .unwrap_or_else(|_| "null".into()),
106 Value::Object(x) => serde_json::to_string(x)
107 .unwrap_or_else(|_| "null".into()),
108 _ => String::from("null"),
109 }
110 }
111
112 pub fn math_consts() -> Value {
113 serde_json::json!{{
114 "MIN_INT": i64::MIN,
115 "MAX_INT": i64::MAX,
116 "MAX_FLOAT": f64::MAX,
117 "MIN_FLOAT": f64::MIN,
118 "INC": f64::NAN,
119 "NOT_A_NUMBER": f64::NAN,
120 "INFINITE": f64::INFINITY,
121 "NEG_INFINITE": f64::NEG_INFINITY,
122 "E": std::f64::consts::E,
123 "FRAC_1_SQRT_2": std::f64::consts::FRAC_1_SQRT_2,
124 "FRAC_2_SQRT_PI": std::f64::consts::FRAC_2_SQRT_PI,
125 "FRAC_1_PI": std::f64::consts::FRAC_1_PI,
126 "FRAC_PI_2": std::f64::consts::FRAC_PI_2,
127 "FRAC_PI_3": std::f64::consts::FRAC_PI_3,
128 "FRAC_PI_4": std::f64::consts::FRAC_PI_4,
129 "FRAC_PI_6": std::f64::consts::FRAC_PI_6,
130 "FRAC_PI_8": std::f64::consts::FRAC_PI_8,
131 "LN_2": std::f64::consts::LN_2,
132 "LN_10": std::f64::consts::LN_10,
133 "LOG2_10": std::f64::consts::LOG2_10,
134 "LOG2_E": std::f64::consts::LOG2_E,
135 "LOG10_2": std::f64::consts::LOG10_2,
136 "LOG10_E": std::f64::consts::LOG10_E,
137 "PI": std::f64::consts::PI,
138 "SQRT_2": std::f64::consts::SQRT_2,
139 "TAU": std::f64::consts::TAU,
140 }}
141 }
142
143 #[derive(Clone)]
144 pub struct ExprWrapper {
145 expr: Expr,
146 config: EvalConfig,
147 }
148
149 impl ExprWrapper {
150 pub fn new<S: AsRef<str>>(expression: S) -> ExprWrapper {
151 ExprWrapper {
152 expr: Expr::new(expression.as_ref()),
153 config: Default::default(),
154 }
155 }
156
157 pub fn config(mut self, config: EvalConfig) -> ExprWrapper {
158 self.config = config;
159 self
160 }
161
162 pub fn init(mut self) -> ExprWrapper {
163 self.expr = expr_wrapper(self.expr.clone(), self.config.clone());
164 self
165 }
166
167 pub fn value<T, V>(mut self, name: T, value: V) -> ExprWrapper
168 where T: Into<String>,
169 V: serde::Serialize
170 {
171 self.expr = self.expr.value(name, value);
172 self
173 }
174
175 pub fn function<T, F>(mut self, name: T, function: F) -> ExprWrapper
176 where T: Into<String>,
177 F: 'static + Fn(Vec<Value>) -> Result<Value, resolver::Error> + Sync + Send
178 {
179 self.expr = self.expr.function(name, function);
180 self
181 }
182
183 pub fn exec(&mut self) -> Result<Value, resolver::Error> {
184 self.expr.exec()
185 }
186 }
187
188 #[deprecated]
190 pub fn expr_wrapper(exp: Expr, config: EvalConfig) -> Expr {
191 if !config.any() {
192 return exp;
193 }
194
195 let mut result = exp;
196
197 if config.include_cast {
198 result = result
199 .function("int", |value| {
200 if value.is_empty() {
201 return Ok(to_value(0_i64));
202 }
203 let v = match value.get(0) {
204 None => to_value(0),
205 Some(value) => value.to_owned(),
206 };
207
208 let num: i64 = match v {
209 Value::Number(x) => {
210 if x.is_f64() {
211 x.as_f64().unwrap_or(0_f64) as i64
212 } else {
213 x.as_i64().unwrap_or(0)
214 }
215 }
216 Value::Bool(x) => {
217 if x {
218 1
219 } else {
220 0
221 }
222 }
223 Value::String(x) => atoi(x),
224 _ => 0,
225 };
226 Ok(to_value(num))
227 })
228 .function("float", |value| {
229 if value.is_empty() {
230 return Ok(to_value(f64::NAN));
231 }
232 let v = match value.get(0) {
233 None => to_value(0_f64),
234 Some(value) => value.to_owned(),
235 };
236 let num: f64 = match v {
237 Value::Number(x) => x.as_f64().unwrap_or(0_f64),
238 Value::Bool(x) => {
239 if x {
240 1.0
241 } else {
242 0.0
243 }
244 }
245 Value::String(x) => match x.parse::<f64>() {
246 Ok(x) => x,
247 _ => f64::NAN,
248 },
249 _ => f64::NAN,
250 };
251
252 Ok(to_value(num))
253 })
254 .function("bool", |value| {
255 if value.is_empty() {
256 return Ok(to_value(false));
257 }
258 let v = match value.get(0) {
259 None => to_value(false),
260 Some(value) => value.to_owned(),
261 };
262
263 let result: bool = match v {
264 Value::Number(x) => x.as_f64().unwrap_or(0_f64) != 0.0,
265 Value::Bool(x) => x,
266 Value::String(x) => !x.is_empty(),
267 Value::Array(x) => !x.is_empty(),
268 Value::Object(x) => !x.is_empty(),
269 _ => false,
270 };
271
272 Ok(to_value(result))
273 })
274 .function("str", |value| {
275 if value.is_empty() {
276 return Ok(to_value("".to_string()));
277 }
278 let v = match value.get(0) {
279 None => to_value("".to_string()),
280 Some(value) => value.to_owned(),
281 };
282
283 let result: String = match v {
284 Value::Number(x) => {
285 if x.is_f64() {
286 x.as_f64().unwrap_or(0_f64).to_string()
287 } else {
288 x.as_i64().unwrap_or(0_i64).to_string()
289 }
290 }
291 Value::Bool(x) => x.to_string(),
292 Value::String(x) => x,
293 Value::Array(x) => serde_json::to_string(&x)
294 .unwrap_or_else(|_| "null".to_string()),
295 Value::Object(x) => serde_json::to_string(&x)
296 .unwrap_or_else(|_| "null".to_string()),
297 _ => "null".to_string(),
298 };
299 Ok(to_value(result))
300 });
301 }
302
303 if config.include_maths {
304 result = result
305 .value("maths", math_consts())
306 .value("NAN", to_value(f64::NAN))
307 .value("INFINITY", to_value(f64::INFINITY))
308 .value("NEG_INFINITY", to_value(f64::NEG_INFINITY));
309 }
310
311 if config.include_regex {
312 result = result.function("is_match", |value| {
313 if value.len() < 2 {
314 return Ok(to_value(false));
315 }
316
317 let v = value.get(0).unwrap();
318 let pattern = value.get(1).unwrap().as_str().unwrap();
319
320 let value: String = value_to_string(v);
321
322 let prog = Regex::new(pattern).unwrap();
323 let is_match = prog.is_match(&value);
324 Ok(to_value(is_match))
325 }).function("extract", |value| {
326 if value.len() < 2 {
327 return Ok(to_value(false));
328 }
329
330 let v = value
331 .get(0).expect("missing first positional argument (string)");
332 let pattern = value
333 .get(1).expect("missing second positional argument (pattern)")
334 .as_str().expect("second positional arguments needs to be a string");
335
336 let value: String = value_to_string(v);
337 let prog = Regex::new(pattern).unwrap();
338 match prog.find(&value) {
339 None => Ok(to_value("".to_string())),
340 Some(m) => {
341 let (start, end) = (m.start(), m.end());
342 Ok(to_value(value.substring(start..end)))
343 }
344 }
345 });
346 }
347
348 if config.include_datetime {
349 result = result
350 .function("get_day", |values| {
351 let current_time = eval_tz_parse_args(values, 1);
352 Ok(to_value(current_time.date_naive().day()))
353 })
354 .function("get_month", |values| {
355 let current_time = eval_tz_parse_args(values, 1);
356 Ok(to_value(current_time.date_naive().month()))
357 })
358 .function("get_year", |values| {
359 let current_time = eval_tz_parse_args(values, 1);
360 Ok(to_value(current_time.date_naive().year()))
361 })
362 .function("get_weekday", |values| {
363 let current_time = eval_tz_parse_args(values, 1);
364 Ok(to_value(
365 current_time.date_naive().weekday().number_from_monday(),
366 ))
367 })
368 .function("is_weekday", |values| {
369 let current_time = eval_tz_parse_args(values, 1);
370 let weekday = current_time.date_naive().weekday().number_from_monday();
371 Ok(to_value(weekday < 6))
372 })
373 .function("is_weekend", |values| {
374 let current_time = eval_tz_parse_args(values, 1);
375 let weekday = current_time.date_naive().weekday();
376 let weekends = [chrono::Weekday::Sat, chrono::Weekday::Sun];
377 Ok(to_value(weekends.contains(&weekday)))
378 })
379 .function("get_time", |extract| {
380 if extract.len() < 2 {
381 let t = now("_".to_owned());
382 return Ok(to_value(t.hour()));
383 }
384
385 let v: String = match extract.get(1).unwrap() {
386 Value::Number(x) => {
387 if x.is_f64() {
388 x.as_f64().unwrap().to_string()
389 } else if x.is_i64() {
390 x.as_i64().unwrap().to_string()
391 } else if x.is_u64() {
392 x.as_u64().unwrap().to_string()
393 } else {
394 x.to_string()
395 }
396 }
397 Value::Bool(x) => x.to_string(),
398 Value::String(x) => x.to_string(),
399 Value::Array(x) => serde_json::to_string(x).unwrap(),
400 Value::Object(x) => serde_json::to_string(x).unwrap(),
401 _ => String::from("null"),
402 };
403
404 let dt = eval_tz_parse_args(extract, 2);
405 let current_time = dt.time();
406
407 let result = match v.as_str() {
408 "h" | "hour" | "hours" => current_time.hour(),
409 "m" | "minute" | "minutes" => current_time.minute(),
410 "s" | "second" | "seconds" => current_time.second(),
411 _ => current_time.hour(),
412 };
413 Ok(to_value(result))
414 });
415 }
416
417 result
418
419 }
422
423 fn eval_tz_parse_args(
424 arguments: Vec<Value>,
425 min_args: usize,
426 ) -> chrono::DateTime<chrono_tz::Tz> {
427 let default_tz = "_".to_owned();
428 if arguments.is_empty() || arguments.len() < min_args {
429 log::warn!("No arguments");
430 return now(default_tz);
431 }
432
433 let v: Option<String> = match arguments.get(0).unwrap() {
434 Value::String(x) => Some(x.to_string()),
435 _ => None,
436 };
437
438 match v {
439 None => {
440 log::warn!("Invalid Timezone");
441 now(default_tz)
442 }
443 Some(timezone) => now(timezone)
444 }
445 }
446
447 fn now(tz: String) -> chrono::DateTime<chrono_tz::Tz> {
448 chrono::offset::Utc::now()
449 .with_timezone(&str_to_tz(tz))
450 }
451
452 fn str_to_tz(timezone: String) -> chrono_tz::Tz {
453 match timezone.parse() {
454 Ok(tz) => tz,
455 Err(_err) => {
456 log::warn!("Defaulted to UTC timezone");
457 chrono_tz::UTC
458 }
459 }
460 }
461
462 fn atoi(s: String) -> i64 {
463 let mut item = s
464 .trim()
465 .split(char::is_whitespace)
466 .next()
467 .unwrap_or("")
468 .split(char::is_alphabetic)
469 .next()
470 .unwrap_or("");
471
472 let mut end_idx = 0;
473 for (pos, c) in item.chars().enumerate() {
474 if pos == 0 {
475 continue;
476 }
477
478 if !c.is_alphanumeric() {
479 end_idx = pos;
480 break;
481 }
482 }
483
484 if end_idx > 0 {
485 item = &item[0..end_idx];
486 }
487
488 let result = item.parse::<i64>();
489 match result {
490 Ok(v) => v,
491 Err(error) => match error.kind() {
492 std::num::IntErrorKind::NegOverflow => i64::MIN,
493 std::num::IntErrorKind::PosOverflow => i64::MAX,
494 std::num::IntErrorKind::InvalidDigit => {
495 let result = item.parse::<f64>();
496 match result {
497 Ok(v) => v.round() as i64,
498 _ => 0,
499 }
500 }
501 _ => 0,
502 },
503 }
504 }
505}
506
507
508#[cfg(test)]
509mod eval {
510 use chrono::offset::Utc as Date;
511 use chrono::{Datelike, Timelike};
512 use resolver::to_value;
513 use serde_json::json;
514
515 use crate::{eval_wrapper::{EvalConfig, ExprWrapper}, template};
516
517 #[derive(Default)]
518 struct Spec;
519
520 impl Spec {
521 pub fn eval<S: AsRef<str>>(&self, expression: S) -> resolver::Value {
522 let mut expr = ExprWrapper::new(expression.as_ref())
523 .config(EvalConfig {
524 include_maths: true,
525 include_regex: true,
526 include_datetime: true,
527 include_cast: true,
528 })
529 .init();
530 let result = expr.exec();
531
532 if result.is_err() {
533 panic!(
534 "Failed to parse expression: \"{}\" {:?}",
535 expression.as_ref(),
536 result
537 )
538 }
539
540 result.unwrap()
541 }
542 }
543
544 #[test]
545 fn maths_consts() {
546 let user_spec = Spec::default();
547 assert_eq!(user_spec.eval("NAN"), to_value(f64::NAN));
548 assert_eq!(user_spec.eval("INFINITY"), to_value(f64::INFINITY));
549 assert_eq!(user_spec.eval("NEG_INFINITY"), to_value(f64::NEG_INFINITY));
550 assert_eq!(user_spec.eval("maths.MAX_INT"), to_value(i64::MAX));
551 assert_eq!(user_spec.eval("maths.MAX_FLOAT"), to_value(f64::MAX));
552 assert_eq!(user_spec.eval("maths.MIN_FLOAT"), to_value(f64::MIN));
553 assert_eq!(user_spec.eval("maths.INC"), to_value(f64::NAN));
554 assert_eq!(user_spec.eval("maths.NOT_A_NUMBER"), to_value(f64::NAN));
555 assert_eq!(user_spec.eval("maths.INFINITE"), to_value(f64::INFINITY));
556 assert_eq!(user_spec.eval("maths.NEG_INFINITE"), to_value(f64::NEG_INFINITY));
557 assert_eq!(user_spec.eval("maths.E"), to_value(std::f64::consts::E));
558 assert_eq!(user_spec.eval("maths.FRAC_1_SQRT_2"), to_value(std::f64::consts::FRAC_1_SQRT_2));
559 assert_eq!(user_spec.eval("maths.FRAC_2_SQRT_PI"), to_value(std::f64::consts::FRAC_2_SQRT_PI));
560 assert_eq!(user_spec.eval("maths.FRAC_1_PI"), to_value(std::f64::consts::FRAC_1_PI));
561 assert_eq!(user_spec.eval("maths.FRAC_PI_2"), to_value(std::f64::consts::FRAC_PI_2));
562 assert_eq!(user_spec.eval("maths.FRAC_PI_3"), to_value(std::f64::consts::FRAC_PI_3));
563 assert_eq!(user_spec.eval("maths.FRAC_PI_4"), to_value(std::f64::consts::FRAC_PI_4));
564 assert_eq!(user_spec.eval("maths.FRAC_PI_6"), to_value(std::f64::consts::FRAC_PI_6));
565 assert_eq!(user_spec.eval("maths.FRAC_PI_8"), to_value(std::f64::consts::FRAC_PI_8));
566 assert_eq!(user_spec.eval("maths.LN_2"), to_value(std::f64::consts::LN_2));
567 assert_eq!(user_spec.eval("maths.LN_10"), to_value(std::f64::consts::LN_10));
568 assert_eq!(user_spec.eval("maths.LOG2_10"), to_value(std::f64::consts::LOG2_10));
569 assert_eq!(user_spec.eval("maths.LOG2_E"), to_value(std::f64::consts::LOG2_E));
570 assert_eq!(user_spec.eval("maths.LOG10_2"), to_value(std::f64::consts::LOG10_2));
571 assert_eq!(user_spec.eval("maths.LOG10_E"), to_value(std::f64::consts::LOG10_E));
572 assert_eq!(user_spec.eval("maths.PI"), to_value(std::f64::consts::PI));
573 assert_eq!(user_spec.eval("maths.SQRT_2"), to_value(std::f64::consts::SQRT_2));
574 assert_eq!(user_spec.eval("maths.TAU"), to_value(std::f64::consts::TAU));
575 }
576
577 #[test]
578 fn literal() {
579 let user_spec = Spec::default();
580
581 assert_eq!(user_spec.eval("42"), 42);
582 assert_eq!(user_spec.eval("0-42"), -42);
583 assert_eq!(user_spec.eval("true"), true);
584 assert_eq!(user_spec.eval("false"), false);
585 assert_eq!(user_spec.eval("\"42\""), "42");
586 assert_eq!(user_spec.eval("'42'"), "42");
587 assert_eq!(user_spec.eval("array(42, 42)"), to_value(vec![42; 2]));
588 assert_eq!(user_spec.eval("array()"), to_value(vec![0; 0]));
589 assert_eq!(user_spec.eval("0..5"), to_value(vec![0, 1, 2, 3, 4]));
590 }
591
592 #[test]
593 fn _str() {
594 let user_spec = Spec::default();
595 assert_eq!(user_spec.eval("str(42)"), "42");
596 assert_eq!(user_spec.eval("str(42.42)"), "42.42");
597 assert_eq!(user_spec.eval("str(true)"), "true");
598 assert_eq!(user_spec.eval("str(array(42, 42))"), to_value("[42,42]"));
599 assert_eq!(user_spec.eval("str(array())"), to_value("[]"));
600 assert_eq!(user_spec.eval("str(null)"), to_value("null"));
601 }
602
603 #[test]
604 fn bool() {
605 let user_spec = Spec::default();
606
607 assert_eq!(user_spec.eval("bool(1)"), true);
608 assert_eq!(user_spec.eval("bool(1.0)"), true);
609 assert_eq!(user_spec.eval("bool(0)"), false);
610 assert_eq!(user_spec.eval("bool(0.0)"), false);
611 assert_eq!(user_spec.eval("bool(true)"), true);
612 assert_eq!(user_spec.eval("bool(false)"), false);
613
614 assert_eq!(user_spec.eval("bool(42)"), true);
615 assert_eq!(user_spec.eval("bool(42.42)"), true);
616 assert_eq!(user_spec.eval("bool(0-42)"), true);
617 assert_eq!(user_spec.eval("bool(0-42.42)"), true);
618
619 assert_eq!(user_spec.eval("bool('')"), false);
620 assert_eq!(user_spec.eval("bool(\"\")"), false);
621 assert_eq!(user_spec.eval("bool('42')"), true);
622 assert_eq!(user_spec.eval("bool(\"42\")"), true);
623
624 assert_eq!(user_spec.eval("bool(array(42, 42))"), true);
625 assert_eq!(user_spec.eval("bool(array())"), false);
626 assert_eq!(user_spec.eval("bool(0..42)"), true);
627 assert_eq!(user_spec.eval("bool(0..0)"), false);
628 assert_eq!(user_spec.eval("bool(null)"), false);
629 }
630
631 #[test]
632 fn float() {
633 let user_spec = Spec::default();
634 assert_eq!(user_spec.eval("float(42)"), 42.0);
635 assert_eq!(user_spec.eval("float(42.42)"), 42.42);
636 assert_eq!(user_spec.eval("float('42.42')"), 42.42);
637 assert_eq!(user_spec.eval("float('42')"), 42.0);
638 assert_eq!(user_spec.eval("float(true)"), 1.0);
639 assert_eq!(user_spec.eval("float(false)"), 0.0);
640 assert_eq!(user_spec.eval("float('')"), to_value(f64::NAN));
641 assert_eq!(
642 user_spec.eval("float('not a num')"),
643 to_value(f64::NAN)
644 );
645 assert_eq!(user_spec.eval("float(ctx)"), to_value(f64::NAN));
646 assert_eq!(
647 user_spec.eval("float(array(42, 42))"),
648 to_value(f64::NAN)
649 );
650 assert_eq!(user_spec.eval("float(0..42)"), to_value(f64::NAN));
651 assert_eq!(user_spec.eval("float(null)"), to_value(f64::NAN));
652 }
653
654 #[test]
655 fn int() {
656 let user_spec = Spec::default();
657 assert_eq!(user_spec.eval("int(42)"), 42);
658 assert_eq!(user_spec.eval("int(42.42)"), 42);
659 assert_eq!(user_spec.eval("int('42.42')"), 42);
660 assert_eq!(user_spec.eval("int('42')"), 42);
661 assert_eq!(user_spec.eval("int(true)"), 1);
662 assert_eq!(user_spec.eval("int(false)"), 0);
663 assert_eq!(user_spec.eval("int('')"), 0);
664 assert_eq!(user_spec.eval("int('not a num')"), 0);
665 assert_eq!(user_spec.eval("int(ctx)"), 0);
666 assert_eq!(user_spec.eval("int(array(42, 42))"), 0);
667 assert_eq!(user_spec.eval("int(0..42)"), 0);
668 assert_eq!(user_spec.eval("int(null)"), 0);
669 }
670
671 #[test]
672 fn day() {
673 let user_spec = Spec::default();
674 let date = Date::now().date_naive();
675 let day = date.day();
676
677 assert_eq!(user_spec.eval("get_day()"), day);
678 assert_eq!(user_spec.eval("get_day('_')"), day);
679 }
680
681 #[test]
682 fn month() {
683 let user_spec = Spec::default();
684 let date = Date::now().date_naive();
685 let month = date.month();
686
687 assert_eq!(user_spec.eval("get_month()"), month);
688 assert_eq!(user_spec.eval("get_month('_')"), month);
689 }
690
691 #[test]
692 fn year() {
693 let user_spec = Spec::default();
694 let date = Date::now().date_naive();
695 let year = date.year();
696 assert_eq!(user_spec.eval("get_year()"), year);
697 assert_eq!(user_spec.eval("get_year('_')"), year);
698 }
699
700 #[test]
701 fn weekday() {
702 let user_spec = Spec::default();
703 let weekday_num = Date::now().weekday().number_from_monday();
704 assert_eq!(user_spec.eval("get_weekday('_')"), weekday_num);
705 assert_eq!(user_spec.eval("is_weekday('_')"), weekday_num < 6);
706
707 assert_eq!(user_spec.eval("get_weekday()"), weekday_num);
708 assert_eq!(user_spec.eval("is_weekday()"), weekday_num < 6);
709 }
710
711 #[test]
712 fn time() {
713 let user_spec = Spec::default();
714 assert_eq!(user_spec.eval("get_time('_', 'h')"), Date::now().time().hour());
715 assert_eq!(user_spec.eval("get_time('_', 'm')"), Date::now().time().minute());
716 assert_eq!(user_spec.eval("get_time('_', 's')"), Date::now().time().second());
717
718 assert_eq!(user_spec.eval("get_time('_', 'hour')"), Date::now().time().hour());
719 assert_eq!(
720 user_spec.eval("get_time('_', 'minute')"),
721 Date::now().time().minute()
722 );
723 assert_eq!(
724 user_spec.eval("get_time('_', 'second')"),
725 Date::now().time().second()
726 );
727
728 assert_eq!(user_spec.eval("get_time('_', 'hours')"), Date::now().time().hour());
729 assert_eq!(user_spec.eval("get_time()"), Date::now().time().hour());
730 assert_eq!(
731 user_spec.eval("get_time('_', 'minutes')"),
732 Date::now().time().minute()
733 );
734 assert_eq!(
735 user_spec.eval("get_time('_', 'seconds')"),
736 Date::now().time().second()
737 );
738 }
739
740 #[test]
741 fn is_match() {
742 let user_spec = Spec::default();
743 assert_eq!(user_spec.eval("is_match('http', '^https?$')"), to_value(true));
744 assert_eq!(user_spec.eval("is_match('http', 'https')"), to_value(false));
745 assert_eq!(user_spec.eval("is_match('http://', '^udp://')"), to_value(false));
746 assert_eq!(user_spec.eval("is_match('http://', '^(https?|wss?)://$')"), to_value(true));
747 assert_eq!(user_spec.eval(r"is_match('2014-01-01', '^\d{4}-\d{2}-\d{2}$')"), to_value(true));
748 }
749
750 #[test]
751 fn extract() {
752 let user_spec = Spec::default();
753 assert_eq!(user_spec.eval("extract('http://www.floa', 'https?://')"), "http://");
754 assert_eq!(user_spec.eval("extract('foo', 'bar')"), "");
755 }
756
757 #[test]
758 fn template_engine() {
759 let context = json! {{
760 "name": "Kar",
761 "location": "foo-bar",
762 "some": {
763 "deep": {
764 "value": 42
765 }
766 }
767 }};
768
769 assert_eq!(
770 template::resolve_template(
771 "Hi, my name is <? $.name ?> and I live in <? $.location ?> <? $.some.deep.value ?>".to_string(),
772 context.clone(),
773 ).expect("Failed to resolve template"),
774 "Hi, my name is Kar and I live in foo-bar 42".to_string()
775 );
776
777 assert_eq!(
778 template::resolve_template(
779 "Hi, my name is Kar and I live in foo-bar 42".to_string(),
780 context.clone(),
781 ).expect("Failed to resolve template"),
782 "Hi, my name is Kar and I live in foo-bar 42".to_string()
783 );
784
785 assert_eq!(
786 template::resolve_template(
787 "".to_string(),
788 context.clone(),
789 ).expect("Failed to resolve template"),
790 "".to_string()
791 );
792
793 assert_eq!(
794 template::resolve_template(
795 "Hello, <? ?>".to_string(),
796 context,
797 ).expect("Failed to resolve template"),
798 "Hello, ".to_string(),
799 );
800 }
801}