1use std::collections::HashMap;
3
4use lazy_static::lazy_static;
5use regex::{Captures, Regex};
6use serde_json::value::{to_value, Value};
7use unic_segment::GraphemeIndices;
8
9#[cfg(feature = "urlencode")]
10use percent_encoding::{percent_encode, AsciiSet, NON_ALPHANUMERIC};
11
12use crate::errors::{Error, Result};
13use crate::utils;
14
15#[cfg(feature = "urlencode")]
17const FRAGMENT_ENCODE_SET: &AsciiSet =
18 &percent_encoding::CONTROLS.add(b' ').add(b'"').add(b'<').add(b'>').add(b'`');
19
20#[cfg(feature = "urlencode")]
22const PATH_ENCODE_SET: &AsciiSet = &FRAGMENT_ENCODE_SET.add(b'#').add(b'?').add(b'{').add(b'}');
23
24#[cfg(feature = "urlencode")]
26const USERINFO_ENCODE_SET: &AsciiSet = &PATH_ENCODE_SET
27 .add(b'/')
28 .add(b':')
29 .add(b';')
30 .add(b'=')
31 .add(b'@')
32 .add(b'[')
33 .add(b'\\')
34 .add(b']')
35 .add(b'^')
36 .add(b'|');
37
38#[cfg(feature = "urlencode")]
42const PYTHON_ENCODE_SET: &AsciiSet = &USERINFO_ENCODE_SET
43 .remove(b'/')
44 .add(b':')
45 .add(b'?')
46 .add(b'#')
47 .add(b'[')
48 .add(b']')
49 .add(b'@')
50 .add(b'!')
51 .add(b'$')
52 .add(b'&')
53 .add(b'\'')
54 .add(b'(')
55 .add(b')')
56 .add(b'*')
57 .add(b'+')
58 .add(b',')
59 .add(b';')
60 .add(b'=');
61
62lazy_static! {
63 static ref STRIPTAGS_RE: Regex = Regex::new(r"(<!--.*?-->|<[^>]*>)").unwrap();
64 static ref WORDS_RE: Regex = Regex::new(r"\b(?P<first>[\w'])(?P<rest>[\w']*)\b").unwrap();
65 static ref SPACELESS_RE: Regex = Regex::new(r">\s+<").unwrap();
66}
67
68pub fn upper(value: &Value, _: &HashMap<String, Value>) -> Result<Value> {
70 let s = try_get_value!("upper", "value", String, value);
71
72 Ok(to_value(s.to_uppercase()).unwrap())
73}
74
75pub fn lower(value: &Value, _: &HashMap<String, Value>) -> Result<Value> {
77 let s = try_get_value!("lower", "value", String, value);
78
79 Ok(to_value(s.to_lowercase()).unwrap())
80}
81
82pub fn trim(value: &Value, _: &HashMap<String, Value>) -> Result<Value> {
84 let s = try_get_value!("trim", "value", String, value);
85
86 Ok(to_value(s.trim()).unwrap())
87}
88
89pub fn trim_start(value: &Value, _: &HashMap<String, Value>) -> Result<Value> {
91 let s = try_get_value!("trim_start", "value", String, value);
92
93 Ok(to_value(s.trim_start()).unwrap())
94}
95
96pub fn trim_end(value: &Value, _: &HashMap<String, Value>) -> Result<Value> {
98 let s = try_get_value!("trim_end", "value", String, value);
99
100 Ok(to_value(s.trim_end()).unwrap())
101}
102
103pub fn trim_start_matches(value: &Value, args: &HashMap<String, Value>) -> Result<Value> {
105 let s = try_get_value!("trim_start_matches", "value", String, value);
106
107 let pat = match args.get("pat") {
108 Some(pat) => {
109 let p = try_get_value!("trim_start_matches", "pat", String, pat);
110 p.replace("\\n", "\n").replace("\\t", "\t")
114 }
115 None => return Err(Error::msg("Filter `trim_start_matches` expected an arg called `pat`")),
116 };
117
118 Ok(to_value(s.trim_start_matches(&pat)).unwrap())
119}
120
121pub fn trim_end_matches(value: &Value, args: &HashMap<String, Value>) -> Result<Value> {
123 let s = try_get_value!("trim_end_matches", "value", String, value);
124
125 let pat = match args.get("pat") {
126 Some(pat) => {
127 let p = try_get_value!("trim_end_matches", "pat", String, pat);
128 p.replace("\\n", "\n").replace("\\t", "\t")
132 }
133 None => return Err(Error::msg("Filter `trim_end_matches` expected an arg called `pat`")),
134 };
135
136 Ok(to_value(s.trim_end_matches(&pat)).unwrap())
137}
138
139pub fn truncate(value: &Value, args: &HashMap<String, Value>) -> Result<Value> {
158 let s = try_get_value!("truncate", "value", String, value);
159 let length = match args.get("length") {
160 Some(l) => try_get_value!("truncate", "length", usize, l),
161 None => 255,
162 };
163 let end = match args.get("end") {
164 Some(l) => try_get_value!("truncate", "end", String, l),
165 None => "…".to_string(),
166 };
167
168 let graphemes = GraphemeIndices::new(&s).collect::<Vec<(usize, &str)>>();
169
170 if length >= graphemes.len() {
172 return Ok(to_value(&s).unwrap());
173 }
174
175 let result = s[..graphemes[length].0].to_string() + &end;
176 Ok(to_value(result).unwrap())
177}
178
179pub fn wordcount(value: &Value, _: &HashMap<String, Value>) -> Result<Value> {
181 let s = try_get_value!("wordcount", "value", String, value);
182
183 Ok(to_value(s.split_whitespace().count()).unwrap())
184}
185
186pub fn replace(value: &Value, args: &HashMap<String, Value>) -> Result<Value> {
188 let s = try_get_value!("replace", "value", String, value);
189
190 let from = match args.get("from") {
191 Some(val) => try_get_value!("replace", "from", String, val),
192 None => return Err(Error::msg("Filter `replace` expected an arg called `from`")),
193 };
194
195 let to = match args.get("to") {
196 Some(val) => try_get_value!("replace", "to", String, val),
197 None => return Err(Error::msg("Filter `replace` expected an arg called `to`")),
198 };
199
200 Ok(to_value(s.replace(&from, &to)).unwrap())
201}
202
203pub fn capitalize(value: &Value, _: &HashMap<String, Value>) -> Result<Value> {
205 let s = try_get_value!("capitalize", "value", String, value);
206 let mut chars = s.chars();
207 match chars.next() {
208 None => Ok(to_value("").unwrap()),
209 Some(f) => {
210 let res = f.to_uppercase().collect::<String>() + &chars.as_str().to_lowercase();
211 Ok(to_value(res).unwrap())
212 }
213 }
214}
215
216#[cfg(feature = "urlencode")]
218pub fn urlencode(value: &Value, _: &HashMap<String, Value>) -> Result<Value> {
219 let s = try_get_value!("urlencode", "value", String, value);
220 let encoded = percent_encode(s.as_bytes(), PYTHON_ENCODE_SET).to_string();
221 Ok(Value::String(encoded))
222}
223
224#[cfg(feature = "urlencode")]
226pub fn urlencode_strict(value: &Value, _: &HashMap<String, Value>) -> Result<Value> {
227 let s = try_get_value!("urlencode_strict", "value", String, value);
228 let encoded = percent_encode(s.as_bytes(), NON_ALPHANUMERIC).to_string();
229 Ok(Value::String(encoded))
230}
231
232pub fn addslashes(value: &Value, _: &HashMap<String, Value>) -> Result<Value> {
234 let s = try_get_value!("addslashes", "value", String, value);
235 Ok(to_value(s.replace('\\', "\\\\").replace('\"', "\\\"").replace('\'', "\\\'")).unwrap())
236}
237
238#[cfg(feature = "builtins")]
240pub fn slugify(value: &Value, _: &HashMap<String, Value>) -> Result<Value> {
241 let s = try_get_value!("slugify", "value", String, value);
242 Ok(to_value(slug::slugify(s)).unwrap())
243}
244
245pub fn title(value: &Value, _: &HashMap<String, Value>) -> Result<Value> {
247 let s = try_get_value!("title", "value", String, value);
248
249 Ok(to_value(WORDS_RE.replace_all(&s, |caps: &Captures| {
250 let first = caps["first"].to_uppercase();
251 let rest = caps["rest"].to_lowercase();
252 format!("{}{}", first, rest)
253 }))
254 .unwrap())
255}
256
257pub fn linebreaksbr(value: &Value, _: &HashMap<String, Value>) -> Result<Value> {
261 let s = try_get_value!("linebreaksbr", "value", String, value);
262 Ok(to_value(s.replace("\r\n", "<br>").replace('\n', "<br>")).unwrap())
263}
264
265pub fn indent(value: &Value, args: &HashMap<String, Value>) -> Result<Value> {
277 let s = try_get_value!("indent", "value", String, value);
278
279 let prefix = match args.get("prefix") {
280 Some(p) => try_get_value!("indent", "prefix", String, p),
281 None => " ".to_string(),
282 };
283 let first = match args.get("first") {
284 Some(f) => try_get_value!("indent", "first", bool, f),
285 None => false,
286 };
287 let blank = match args.get("blank") {
288 Some(b) => try_get_value!("indent", "blank", bool, b),
289 None => false,
290 };
291
292 let mut out = String::with_capacity(
294 s.len() + (prefix.len() * (s.chars().filter(|&c| c == '\n').count() + 1)),
295 );
296 let mut first_pass = true;
297
298 for line in s.lines() {
299 if first_pass {
300 if first {
301 out.push_str(&prefix);
302 }
303 first_pass = false;
304 } else {
305 out.push('\n');
306 if blank || !line.trim_start().is_empty() {
307 out.push_str(&prefix);
308 }
309 }
310 out.push_str(line);
311 }
312
313 Ok(to_value(&out).unwrap())
314}
315
316pub fn striptags(value: &Value, _: &HashMap<String, Value>) -> Result<Value> {
318 let s = try_get_value!("striptags", "value", String, value);
319 Ok(to_value(STRIPTAGS_RE.replace_all(&s, "")).unwrap())
320}
321
322pub fn spaceless(value: &Value, _: &HashMap<String, Value>) -> Result<Value> {
324 let s = try_get_value!("spaceless", "value", String, value);
325 Ok(to_value(SPACELESS_RE.replace_all(&s, "><")).unwrap())
326}
327
328pub fn escape_html(value: &Value, _: &HashMap<String, Value>) -> Result<Value> {
330 let s = try_get_value!("escape_html", "value", String, value);
331 Ok(Value::String(utils::escape_html(&s)))
332}
333
334pub fn escape_xml(value: &Value, _: &HashMap<String, Value>) -> Result<Value> {
337 let s = try_get_value!("escape_html", "value", String, value);
338
339 let mut output = String::with_capacity(s.len() * 2);
340 for c in s.chars() {
341 match c {
342 '&' => output.push_str("&"),
343 '<' => output.push_str("<"),
344 '>' => output.push_str(">"),
345 '"' => output.push_str("""),
346 '\'' => output.push_str("'"),
347 _ => output.push(c),
348 }
349 }
350 Ok(Value::String(output))
351}
352
353pub fn split(value: &Value, args: &HashMap<String, Value>) -> Result<Value> {
355 let s = try_get_value!("split", "value", String, value);
356
357 let pat = match args.get("pat") {
358 Some(pat) => {
359 let p = try_get_value!("split", "pat", String, pat);
360 p.replace("\\n", "\n").replace("\\t", "\t")
364 }
365 None => return Err(Error::msg("Filter `split` expected an arg called `pat`")),
366 };
367
368 Ok(to_value(s.split(&pat).collect::<Vec<_>>()).unwrap())
369}
370
371pub fn int(value: &Value, args: &HashMap<String, Value>) -> Result<Value> {
373 let default = match args.get("default") {
374 Some(d) => try_get_value!("int", "default", i64, d),
375 None => 0,
376 };
377 let base = match args.get("base") {
378 Some(b) => try_get_value!("int", "base", u32, b),
379 None => 10,
380 };
381
382 let v = match value {
383 Value::String(s) => {
384 let s = s.trim();
385 let s = match base {
386 2 => s.trim_start_matches("0b"),
387 8 => s.trim_start_matches("0o"),
388 16 => s.trim_start_matches("0x"),
389 _ => s,
390 };
391
392 match i64::from_str_radix(s, base) {
393 Ok(v) => v,
394 Err(_) => {
395 if s.contains('.') {
396 match s.parse::<f64>() {
397 Ok(f) => f as i64,
398 Err(_) => default,
399 }
400 } else {
401 default
402 }
403 }
404 }
405 }
406 Value::Number(n) => match n.as_f64() {
407 Some(f) => f as i64,
408 None => match n.as_i64() {
409 Some(i) => i,
410 None => default,
411 },
412 },
413 _ => return Err(Error::msg("Filter `int` received an unexpected type")),
414 };
415
416 Ok(to_value(v).unwrap())
417}
418
419pub fn float(value: &Value, args: &HashMap<String, Value>) -> Result<Value> {
421 let default = match args.get("default") {
422 Some(d) => try_get_value!("float", "default", f64, d),
423 None => 0.0,
424 };
425
426 let v = match value {
427 Value::String(s) => {
428 let s = s.trim();
429 s.parse::<f64>().unwrap_or(default)
430 }
431 Value::Number(n) => match n.as_f64() {
432 Some(f) => f,
433 None => match n.as_i64() {
434 Some(i) => i as f64,
435 None => default,
436 },
437 },
438 _ => return Err(Error::msg("Filter `float` received an unexpected type")),
439 };
440
441 Ok(to_value(v).unwrap())
442}
443
444#[cfg(test)]
445mod tests {
446 use std::collections::HashMap;
447
448 use serde_json::value::to_value;
449
450 use super::*;
451
452 #[test]
453 fn test_upper() {
454 let result = upper(&to_value("hello").unwrap(), &HashMap::new());
455 assert!(result.is_ok());
456 assert_eq!(result.unwrap(), to_value("HELLO").unwrap());
457 }
458
459 #[test]
460 fn test_upper_error() {
461 let result = upper(&to_value(50).unwrap(), &HashMap::new());
462 assert!(result.is_err());
463 assert_eq!(
464 result.err().unwrap().to_string(),
465 "Filter `upper` was called on an incorrect value: got `50` but expected a String"
466 );
467 }
468
469 #[test]
470 fn test_trim() {
471 let result = trim(&to_value(" hello ").unwrap(), &HashMap::new());
472 assert!(result.is_ok());
473 assert_eq!(result.unwrap(), to_value("hello").unwrap());
474 }
475
476 #[test]
477 fn test_trim_start() {
478 let result = trim_start(&to_value(" hello ").unwrap(), &HashMap::new());
479 assert!(result.is_ok());
480 assert_eq!(result.unwrap(), to_value("hello ").unwrap());
481 }
482
483 #[test]
484 fn test_trim_end() {
485 let result = trim_end(&to_value(" hello ").unwrap(), &HashMap::new());
486 assert!(result.is_ok());
487 assert_eq!(result.unwrap(), to_value(" hello").unwrap());
488 }
489
490 #[test]
491 fn test_trim_start_matches() {
492 let tests: Vec<(_, _, _)> = vec![
493 ("/a/b/cde/", "/", "a/b/cde/"),
494 ("\nhello\nworld\n", "\n", "hello\nworld\n"),
495 (", hello, world, ", ", ", "hello, world, "),
496 ];
497 for (input, pat, expected) in tests {
498 let mut args = HashMap::new();
499 args.insert("pat".to_string(), to_value(pat).unwrap());
500 let result = trim_start_matches(&to_value(input).unwrap(), &args);
501 assert!(result.is_ok());
502 assert_eq!(result.unwrap(), to_value(expected).unwrap());
503 }
504 }
505
506 #[test]
507 fn test_trim_end_matches() {
508 let tests: Vec<(_, _, _)> = vec![
509 ("/a/b/cde/", "/", "/a/b/cde"),
510 ("\nhello\nworld\n", "\n", "\nhello\nworld"),
511 (", hello, world, ", ", ", ", hello, world"),
512 ];
513 for (input, pat, expected) in tests {
514 let mut args = HashMap::new();
515 args.insert("pat".to_string(), to_value(pat).unwrap());
516 let result = trim_end_matches(&to_value(input).unwrap(), &args);
517 assert!(result.is_ok());
518 assert_eq!(result.unwrap(), to_value(expected).unwrap());
519 }
520 }
521
522 #[test]
523 fn test_truncate_smaller_than_length() {
524 let mut args = HashMap::new();
525 args.insert("length".to_string(), to_value(255).unwrap());
526 let result = truncate(&to_value("hello").unwrap(), &args);
527 assert!(result.is_ok());
528 assert_eq!(result.unwrap(), to_value("hello").unwrap());
529 }
530
531 #[test]
532 fn test_truncate_when_required() {
533 let mut args = HashMap::new();
534 args.insert("length".to_string(), to_value(2).unwrap());
535 let result = truncate(&to_value("日本語").unwrap(), &args);
536 assert!(result.is_ok());
537 assert_eq!(result.unwrap(), to_value("日本…").unwrap());
538 }
539
540 #[test]
541 fn test_truncate_custom_end() {
542 let mut args = HashMap::new();
543 args.insert("length".to_string(), to_value(2).unwrap());
544 args.insert("end".to_string(), to_value("").unwrap());
545 let result = truncate(&to_value("日本語").unwrap(), &args);
546 assert!(result.is_ok());
547 assert_eq!(result.unwrap(), to_value("日本").unwrap());
548 }
549
550 #[test]
551 fn test_truncate_multichar_grapheme() {
552 let mut args = HashMap::new();
553 args.insert("length".to_string(), to_value(5).unwrap());
554 args.insert("end".to_string(), to_value("…").unwrap());
555 let result = truncate(&to_value("👨👩👧👦 family").unwrap(), &args);
556 assert!(result.is_ok());
557 assert_eq!(result.unwrap(), to_value("👨👩👧👦 fam…").unwrap());
558 }
559
560 #[test]
561 fn test_lower() {
562 let result = lower(&to_value("HELLO").unwrap(), &HashMap::new());
563 assert!(result.is_ok());
564 assert_eq!(result.unwrap(), to_value("hello").unwrap());
565 }
566
567 #[test]
568 fn test_wordcount() {
569 let result = wordcount(&to_value("Joel is a slug").unwrap(), &HashMap::new());
570 assert!(result.is_ok());
571 assert_eq!(result.unwrap(), to_value(4).unwrap());
572 }
573
574 #[test]
575 fn test_replace() {
576 let mut args = HashMap::new();
577 args.insert("from".to_string(), to_value("Hello").unwrap());
578 args.insert("to".to_string(), to_value("Goodbye").unwrap());
579 let result = replace(&to_value("Hello world!").unwrap(), &args);
580 assert!(result.is_ok());
581 assert_eq!(result.unwrap(), to_value("Goodbye world!").unwrap());
582 }
583
584 #[test]
586 fn test_replace_newline() {
587 let mut args = HashMap::new();
588 args.insert("from".to_string(), to_value("\n").unwrap());
589 args.insert("to".to_string(), to_value("<br>").unwrap());
590 let result = replace(&to_value("Animal Alphabets\nB is for Bee-Eater").unwrap(), &args);
591 assert!(result.is_ok());
592 assert_eq!(result.unwrap(), to_value("Animal Alphabets<br>B is for Bee-Eater").unwrap());
593 }
594
595 #[test]
596 fn test_replace_missing_arg() {
597 let mut args = HashMap::new();
598 args.insert("from".to_string(), to_value("Hello").unwrap());
599 let result = replace(&to_value("Hello world!").unwrap(), &args);
600 assert!(result.is_err());
601 assert_eq!(
602 result.err().unwrap().to_string(),
603 "Filter `replace` expected an arg called `to`"
604 );
605 }
606
607 #[test]
608 fn test_capitalize() {
609 let tests = vec![("CAPITAL IZE", "Capital ize"), ("capital ize", "Capital ize")];
610 for (input, expected) in tests {
611 let result = capitalize(&to_value(input).unwrap(), &HashMap::new());
612 assert!(result.is_ok());
613 assert_eq!(result.unwrap(), to_value(expected).unwrap());
614 }
615 }
616
617 #[test]
618 fn test_addslashes() {
619 let tests = vec![
620 (r#"I'm so happy"#, r#"I\'m so happy"#),
621 (r#"Let "me" help you"#, r#"Let \"me\" help you"#),
622 (r#"<a>'"#, r#"<a>\'"#),
623 (
624 r#""double quotes" and \'single quotes\'"#,
625 r#"\"double quotes\" and \\\'single quotes\\\'"#,
626 ),
627 (r#"\ : backslashes too"#, r#"\\ : backslashes too"#),
628 ];
629 for (input, expected) in tests {
630 let result = addslashes(&to_value(input).unwrap(), &HashMap::new());
631 assert!(result.is_ok());
632 assert_eq!(result.unwrap(), to_value(expected).unwrap());
633 }
634 }
635
636 #[cfg(feature = "builtins")]
637 #[test]
638 fn test_slugify() {
639 let tests =
642 vec![(r#"Hello world"#, r#"hello-world"#), (r#"Hello 世界"#, r#"hello-shi-jie"#)];
643 for (input, expected) in tests {
644 let result = slugify(&to_value(input).unwrap(), &HashMap::new());
645 assert!(result.is_ok());
646 assert_eq!(result.unwrap(), to_value(expected).unwrap());
647 }
648 }
649
650 #[cfg(feature = "urlencode")]
651 #[test]
652 fn test_urlencode() {
653 let tests = vec![
654 (
655 r#"https://www.example.org/foo?a=b&c=d"#,
656 r#"https%3A//www.example.org/foo%3Fa%3Db%26c%3Dd"#,
657 ),
658 (
659 r#"https://www.example.org/apples-&-oranges/"#,
660 r#"https%3A//www.example.org/apples-%26-oranges/"#,
661 ),
662 (r#"https://www.example.org/"#, r#"https%3A//www.example.org/"#),
663 (r#"/test&"/me?/"#, r#"/test%26%22/me%3F/"#),
664 (r#"escape/slash"#, r#"escape/slash"#),
665 ];
666 for (input, expected) in tests {
667 let args = HashMap::new();
668 let result = urlencode(&to_value(input).unwrap(), &args);
669 assert!(result.is_ok());
670 assert_eq!(result.unwrap(), to_value(expected).unwrap());
671 }
672 }
673
674 #[cfg(feature = "urlencode")]
675 #[test]
676 fn test_urlencode_strict() {
677 let tests = vec![
678 (
679 r#"https://www.example.org/foo?a=b&c=d"#,
680 r#"https%3A%2F%2Fwww%2Eexample%2Eorg%2Ffoo%3Fa%3Db%26c%3Dd"#,
681 ),
682 (
683 r#"https://www.example.org/apples-&-oranges/"#,
684 r#"https%3A%2F%2Fwww%2Eexample%2Eorg%2Fapples%2D%26%2Doranges%2F"#,
685 ),
686 (r#"https://www.example.org/"#, r#"https%3A%2F%2Fwww%2Eexample%2Eorg%2F"#),
687 (r#"/test&"/me?/"#, r#"%2Ftest%26%22%2Fme%3F%2F"#),
688 (r#"escape/slash"#, r#"escape%2Fslash"#),
689 ];
690 for (input, expected) in tests {
691 let args = HashMap::new();
692 let result = urlencode_strict(&to_value(input).unwrap(), &args);
693 assert!(result.is_ok());
694 assert_eq!(result.unwrap(), to_value(expected).unwrap());
695 }
696 }
697
698 #[test]
699 fn test_title() {
700 let tests = vec![
701 ("foo bar", "Foo Bar"),
702 ("foo\tbar", "Foo\tBar"),
703 ("foo bar", "Foo Bar"),
704 ("f bar f", "F Bar F"),
705 ("foo-bar", "Foo-Bar"),
706 ("FOO\tBAR", "Foo\tBar"),
707 ("foo (bar)", "Foo (Bar)"),
708 ("foo (bar) ", "Foo (Bar) "),
709 ("foo {bar}", "Foo {Bar}"),
710 ("foo [bar]", "Foo [Bar]"),
711 ("foo <bar>", "Foo <Bar>"),
712 (" foo bar", " Foo Bar"),
713 ("\tfoo\tbar\t", "\tFoo\tBar\t"),
714 ("foo bar ", "Foo Bar "),
715 ("foo bar\t", "Foo Bar\t"),
716 ("foo's bar", "Foo's Bar"),
717 ];
718 for (input, expected) in tests {
719 let result = title(&to_value(input).unwrap(), &HashMap::new());
720 assert!(result.is_ok());
721 assert_eq!(result.unwrap(), to_value(expected).unwrap());
722 }
723 }
724
725 #[test]
726 fn test_indent_defaults() {
727 let args = HashMap::new();
728 let result = indent(&to_value("one\n\ntwo\nthree").unwrap(), &args);
729 assert!(result.is_ok());
730 assert_eq!(result.unwrap(), to_value("one\n\n two\n three").unwrap());
731 }
732
733 #[test]
734 fn test_indent_args() {
735 let mut args = HashMap::new();
736 args.insert("first".to_string(), to_value(true).unwrap());
737 args.insert("prefix".to_string(), to_value(" ").unwrap());
738 args.insert("blank".to_string(), to_value(true).unwrap());
739 let result = indent(&to_value("one\n\ntwo\nthree").unwrap(), &args);
740 assert!(result.is_ok());
741 assert_eq!(result.unwrap(), to_value(" one\n \n two\n three").unwrap());
742 }
743
744 #[test]
745 fn test_striptags() {
746 let tests = vec![
747 (r"<b>Joel</b> <button>is</button> a <span>slug</span>", "Joel is a slug"),
748 (
749 r#"<p>just a small \n <a href="x"> example</a> link</p>\n<p>to a webpage</p><!-- <p>and some commented stuff</p> -->"#,
750 r#"just a small \n example link\nto a webpage"#,
751 ),
752 (
753 r"<p>See: 'é is an apostrophe followed by e acute</p>",
754 r"See: 'é is an apostrophe followed by e acute",
755 ),
756 (r"<adf>a", "a"),
757 (r"</adf>a", "a"),
758 (r"<asdf><asdf>e", "e"),
759 (r"hi, <f x", "hi, <f x"),
760 ("234<235, right?", "234<235, right?"),
761 ("a4<a5 right?", "a4<a5 right?"),
762 ("b7>b2!", "b7>b2!"),
763 ("</fe", "</fe"),
764 ("<x>b<y>", "b"),
765 (r#"a<p a >b</p>c"#, "abc"),
766 (r#"d<a:b c:d>e</p>f"#, "def"),
767 (r#"<strong>foo</strong><a href="http://example.com">bar</a>"#, "foobar"),
768 ];
769 for (input, expected) in tests {
770 let result = striptags(&to_value(input).unwrap(), &HashMap::new());
771 assert!(result.is_ok());
772 assert_eq!(result.unwrap(), to_value(expected).unwrap());
773 }
774 }
775
776 #[test]
777 fn test_spaceless() {
778 let tests = vec![
779 ("<p>\n<a>test</a>\r\n </p>", "<p><a>test</a></p>"),
780 ("<p>\n<a> </a>\r\n </p>", "<p><a></a></p>"),
781 ("<p> </p>", "<p></p>"),
782 ("<p> <a>", "<p><a>"),
783 ("<p> test</p>", "<p> test</p>"),
784 ("<p>\r\n</p>", "<p></p>"),
785 ];
786 for (input, expected) in tests {
787 let result = spaceless(&to_value(input).unwrap(), &HashMap::new());
788 assert!(result.is_ok());
789 assert_eq!(result.unwrap(), to_value(expected).unwrap());
790 }
791 }
792
793 #[test]
794 fn test_split() {
795 let tests: Vec<(_, _, &[&str])> = vec![
796 ("a/b/cde", "/", &["a", "b", "cde"]),
797 ("hello\nworld", "\n", &["hello", "world"]),
798 ("hello, world", ", ", &["hello", "world"]),
799 ];
800 for (input, pat, expected) in tests {
801 let mut args = HashMap::new();
802 args.insert("pat".to_string(), to_value(pat).unwrap());
803 let result = split(&to_value(input).unwrap(), &args).unwrap();
804 let result = result.as_array().unwrap();
805 assert_eq!(result.len(), expected.len());
806 for (result, expected) in result.iter().zip(expected.iter()) {
807 assert_eq!(result, expected);
808 }
809 }
810 }
811
812 #[test]
813 fn test_xml_escape() {
814 let tests = vec![
815 (r"hey-&-ho", "hey-&-ho"),
816 (r"hey-'-ho", "hey-'-ho"),
817 (r"hey-&'-ho", "hey-&'-ho"),
818 (r#"hey-&'"-ho"#, "hey-&'"-ho"),
819 (r#"hey-&'"<-ho"#, "hey-&'"<-ho"),
820 (r#"hey-&'"<>-ho"#, "hey-&'"<>-ho"),
821 ];
822 for (input, expected) in tests {
823 let result = escape_xml(&to_value(input).unwrap(), &HashMap::new());
824 assert!(result.is_ok());
825 assert_eq!(result.unwrap(), to_value(expected).unwrap());
826 }
827 }
828
829 #[test]
830 fn test_int_decimal_strings() {
831 let tests: Vec<(&str, i64)> = vec![
832 ("0", 0),
833 ("-5", -5),
834 ("9223372036854775807", i64::max_value()),
835 ("0b1010", 0),
836 ("1.23", 1),
837 ];
838 for (input, expected) in tests {
839 let args = HashMap::new();
840 let result = int(&to_value(input).unwrap(), &args);
841
842 assert!(result.is_ok());
843 assert_eq!(result.unwrap(), to_value(expected).unwrap());
844 }
845 }
846
847 #[test]
848 fn test_int_others() {
849 let mut args = HashMap::new();
850
851 let result = int(&to_value(1.23).unwrap(), &args);
852 assert!(result.is_ok());
853 assert_eq!(result.unwrap(), to_value(1).unwrap());
854
855 let result = int(&to_value(-5).unwrap(), &args);
856 assert!(result.is_ok());
857 assert_eq!(result.unwrap(), to_value(-5).unwrap());
858
859 args.insert("default".to_string(), to_value(5).unwrap());
860 args.insert("base".to_string(), to_value(2).unwrap());
861 let tests: Vec<(&str, i64)> =
862 vec![("0", 0), ("-3", 5), ("1010", 10), ("0b1010", 10), ("0xF00", 5)];
863 for (input, expected) in tests {
864 let result = int(&to_value(input).unwrap(), &args);
865 assert!(result.is_ok());
866 assert_eq!(result.unwrap(), to_value(expected).unwrap());
867 }
868
869 args.insert("default".to_string(), to_value(-4).unwrap());
870 args.insert("base".to_string(), to_value(8).unwrap());
871 let tests: Vec<(&str, i64)> =
872 vec![("21", 17), ("-3", -3), ("9OO", -4), ("0o567", 375), ("0b101", -4)];
873 for (input, expected) in tests {
874 let result = int(&to_value(input).unwrap(), &args);
875 assert!(result.is_ok());
876 assert_eq!(result.unwrap(), to_value(expected).unwrap());
877 }
878
879 args.insert("default".to_string(), to_value(0).unwrap());
880 args.insert("base".to_string(), to_value(16).unwrap());
881 let tests: Vec<(&str, i64)> = vec![("1011", 4113), ("0xC3", 195)];
882 for (input, expected) in tests {
883 let result = int(&to_value(input).unwrap(), &args);
884 assert!(result.is_ok());
885 assert_eq!(result.unwrap(), to_value(expected).unwrap());
886 }
887
888 args.insert("default".to_string(), to_value(0).unwrap());
889 args.insert("base".to_string(), to_value(5).unwrap());
890 let tests: Vec<(&str, i64)> = vec![("4321", 586), ("-100", -25), ("0b100", 0)];
891 for (input, expected) in tests {
892 let result = int(&to_value(input).unwrap(), &args);
893 assert!(result.is_ok());
894 assert_eq!(result.unwrap(), to_value(expected).unwrap());
895 }
896 }
897
898 #[test]
899 fn test_float() {
900 let mut args = HashMap::new();
901
902 let tests: Vec<(&str, f64)> = vec![("0", 0.0), ("-5.3", -5.3)];
903 for (input, expected) in tests {
904 let result = float(&to_value(input).unwrap(), &args);
905
906 assert!(result.is_ok());
907 assert_eq!(result.unwrap(), to_value(expected).unwrap());
908 }
909
910 args.insert("default".to_string(), to_value(3.18).unwrap());
911 let result = float(&to_value("bad_val").unwrap(), &args);
912 assert!(result.is_ok());
913 assert_eq!(result.unwrap(), to_value(3.18).unwrap());
914
915 let result = float(&to_value(1.23).unwrap(), &args);
916 assert!(result.is_ok());
917 assert_eq!(result.unwrap(), to_value(1.23).unwrap());
918 }
919
920 #[test]
921 fn test_linebreaksbr() {
922 let args = HashMap::new();
923 let tests: Vec<(&str, &str)> = vec![
924 ("hello world", "hello world"),
925 ("hello\nworld", "hello<br>world"),
926 ("hello\r\nworld", "hello<br>world"),
927 ("hello\n\rworld", "hello<br>\rworld"),
928 ("hello\r\n\nworld", "hello<br><br>world"),
929 ("hello<br>world\n", "hello<br>world<br>"),
930 ];
931 for (input, expected) in tests {
932 let result = linebreaksbr(&to_value(input).unwrap(), &args);
933 assert!(result.is_ok());
934 assert_eq!(result.unwrap(), to_value(expected).unwrap());
935 }
936 }
937}