1use std::path::Path;
2
3fn bool_str(v: bool) -> String {
5 if v { "1".into() } else { String::new() }
6}
7
8pub fn call_function(name: &str, args: &[String]) -> Option<String> {
9 match name {
10 "left" => {
12 let s = args.first()?;
13 let n: usize = args.get(1)?.parse().ok()?;
14 Some(s.chars().take(n).collect())
15 }
16 "right" => {
17 let s = args.first()?;
18 let n: usize = args.get(1)?.parse().ok()?;
19 let chars: Vec<char> = s.chars().collect();
20 let start = chars.len().saturating_sub(n);
21 Some(chars[start..].iter().collect())
22 }
23 "pad" => {
24 let s = args.first()?;
25 let n: usize = args.get(1)?.parse().ok()?;
26 Some(format!("{s:>n$}"))
27 }
28 "pad_right" => {
29 let s = args.first()?;
30 let n: usize = args.get(1)?.parse().ok()?;
31 Some(format!("{s:<n$}"))
32 }
33 "padcut" => {
34 let s = args.first()?;
35 let n: usize = args.get(1)?.parse().ok()?;
36 let padded = format!("{s:>n$}");
37 Some(padded.chars().take(n).collect())
38 }
39 "padcut_right" => {
40 let s = args.first()?;
41 let n: usize = args.get(1)?.parse().ok()?;
42 let padded = format!("{s:<n$}");
43 Some(padded.chars().take(n).collect())
44 }
45 "replace" => {
46 let s = args.first()?;
47 let from = args.get(1)?;
48 let to = args.get(2)?;
49 Some(s.replace(from.as_str(), to.as_str()))
50 }
51 "trim" => Some(args.first()?.trim().to_string()),
52 "lower" => Some(args.first()?.to_lowercase()),
53 "upper" => Some(args.first()?.to_uppercase()),
54 "caps" => {
55 let s = args.first()?;
56 Some(capitalize_words(s))
57 }
58 "caps2" => {
59 let s = args.first()?;
60 Some(capitalize_words_smart(s))
61 }
62 "abbr" => {
63 let s = args.first()?;
64 Some(abbreviate(s))
65 }
66 "substr" => {
67 let s = args.first()?;
68 let from: usize = args.get(1)?.parse().ok()?;
69 let to: usize = args.get(2)?.parse().ok()?;
70 let chars: Vec<char> = s.chars().collect();
71 let start = from.min(chars.len());
72 let end = to.min(chars.len());
73 if start > end {
74 Some(String::new())
75 } else {
76 Some(chars[start..end].iter().collect())
77 }
78 }
79 "insert" => {
80 let s = args.first()?;
81 let sub = args.get(1)?;
82 let pos: usize = args.get(2)?.parse().ok()?;
83 let mut chars: Vec<char> = s.chars().collect();
84 let idx = pos.min(chars.len());
85 for (i, c) in sub.chars().enumerate() {
86 chars.insert(idx + i, c);
87 }
88 Some(chars.into_iter().collect())
89 }
90 "repeat" => {
91 let s = args.first()?;
92 let n: usize = args.get(1)?.parse().ok()?;
93 Some(s.repeat(n))
94 }
95 "stripprefix" => {
96 let s = args.first()?;
97 let prefixes = if args.len() > 1 {
99 args[1..].to_vec()
100 } else {
101 vec!["A ".into(), "The ".into()]
102 };
103 for prefix in &prefixes {
104 if let Some(rest) = s.strip_prefix(prefix.as_str()) {
105 return Some(rest.to_string());
106 }
107 }
108 Some(s.clone())
109 }
110 "swapprefix" => {
111 let s = args.first()?;
112 let prefixes = if args.len() > 1 {
113 args[1..].to_vec()
114 } else {
115 vec!["A ".into(), "The ".into()]
116 };
117 for prefix in &prefixes {
118 if let Some(rest) = s.strip_prefix(prefix.as_str()) {
119 return Some(format!("{}, {}", rest, prefix.trim()));
120 }
121 }
122 Some(s.clone())
123 }
124 "rot13" => {
125 let s = args.first()?;
126 Some(
127 s.chars()
128 .map(|c| match c {
129 'a'..='m' | 'A'..='M' => (c as u8 + 13) as char,
130 'n'..='z' | 'N'..='Z' => (c as u8 - 13) as char,
131 _ => c,
132 })
133 .collect(),
134 )
135 }
136 "fix_eol" => {
137 let s = args.first()?;
138 let replacement = args.get(1).map(|s| s.as_str()).unwrap_or(" ");
139 Some(s.replace(['\r', '\n'], replacement))
140 }
141
142 "strchr" => {
144 let s = args.first()?;
145 let c = args.get(1)?.chars().next()?;
146 Some(s.find(c).map_or(String::new(), |i| (i + 1).to_string()))
147 }
148 "strrchr" => {
149 let s = args.first()?;
150 let c = args.get(1)?.chars().next()?;
151 Some(s.rfind(c).map_or(String::new(), |i| (i + 1).to_string()))
152 }
153 "strstr" => {
154 let s = args.first()?;
155 let sub = args.get(1)?;
156 Some(
157 s.find(sub.as_str())
158 .map_or(String::new(), |i| (i + 1).to_string()),
159 )
160 }
161
162 "strcmp" => {
164 let a = args.first()?;
165 let b = args.get(1)?;
166 Some(bool_str(a == b))
167 }
168 "stricmp" => {
169 let a = args.first()?;
170 let b = args.get(1)?;
171 Some(bool_str(a.to_lowercase() == b.to_lowercase()))
172 }
173 "longer" => {
174 let a = args.first()?;
175 let b = args.get(1)?;
176 Some(bool_str(a.len() > b.len()))
177 }
178 "longest" => args.iter().max_by_key(|s| s.len()).cloned(),
179 "shortest" => args.iter().min_by_key(|s| s.len()).cloned(),
180
181 "if" => {
183 let cond = args.first()?;
184 if !cond.is_empty() {
185 Some(args.get(1).cloned().unwrap_or_default())
187 } else {
188 Some(args.get(2).cloned().unwrap_or_default())
189 }
190 }
191 "if2" => {
192 let a = args.first()?;
193 if !a.is_empty() {
194 Some(a.clone())
195 } else {
196 Some(args.get(1)?.clone())
197 }
198 }
199 "if3" => args
200 .iter()
201 .find(|a| !a.is_empty())
202 .cloned()
203 .or(Some(String::new())),
204 "ifequal" => {
205 let a: i64 = args.first()?.parse().ok()?;
206 let b: i64 = args.get(1)?.parse().ok()?;
207 if a == b {
208 Some(args.get(2).cloned().unwrap_or_default())
209 } else {
210 Some(args.get(3).cloned().unwrap_or_default())
211 }
212 }
213 "ifgreater" => {
214 let a: i64 = args.first()?.parse().ok()?;
215 let b: i64 = args.get(1)?.parse().ok()?;
216 if a > b {
217 Some(args.get(2).cloned().unwrap_or_default())
218 } else {
219 Some(args.get(3).cloned().unwrap_or_default())
220 }
221 }
222 "iflonger" => {
223 let s = args.first()?;
224 let n: usize = args.get(1)?.parse().ok()?;
225 if s.len() > n {
226 Some(args.get(2).cloned().unwrap_or_default())
227 } else {
228 Some(args.get(3).cloned().unwrap_or_default())
229 }
230 }
231 "select" => {
232 let n: usize = args.first()?.parse().ok()?;
233 if n == 0 || n > args.len() - 1 {
234 Some(String::new())
235 } else {
236 Some(args[n].clone())
237 }
238 }
239 "not" => {
240 let a = args.first()?;
241 Some(bool_str(a.is_empty()))
242 }
243 "and" => {
244 let a = args.first()?;
245 let b = args.get(1)?;
246 Some(bool_str(!a.is_empty() && !b.is_empty()))
247 }
248 "or" => {
249 let a = args.first()?;
250 let b = args.get(1)?;
251 Some(bool_str(!a.is_empty() || !b.is_empty()))
252 }
253 "xor" => {
254 let a = args.first()?;
255 let b = args.get(1)?;
256 Some(bool_str(a.is_empty() != b.is_empty()))
257 }
258 "greater" => {
259 let a: i64 = args.first()?.parse().ok()?;
260 let b: i64 = args.get(1)?.parse().ok()?;
261 Some(bool_str(a > b))
262 }
263
264 "num" => {
266 let n = args.first()?;
267 let digits: usize = args.get(1)?.parse().ok()?;
268 Some(format!("{n:0>digits$}"))
269 }
270 "add" => {
271 let a: i64 = args.first()?.parse().ok()?;
272 let b: i64 = args.get(1)?.parse().ok()?;
273 Some((a + b).to_string())
274 }
275 "sub" => {
276 let a: i64 = args.first()?.parse().ok()?;
277 let b: i64 = args.get(1)?.parse().ok()?;
278 Some((a - b).to_string())
279 }
280 "mul" => {
281 let a: i64 = args.first()?.parse().ok()?;
282 let b: i64 = args.get(1)?.parse().ok()?;
283 Some((a * b).to_string())
284 }
285 "muldiv" => {
286 let a: i64 = args.first()?.parse().ok()?;
287 let b: i64 = args.get(1)?.parse().ok()?;
288 let c: i64 = args.get(2)?.parse().ok()?;
289 if c == 0 {
290 return Some(String::new());
291 }
292 Some(((a as i128 * b as i128) / c as i128).to_string())
293 }
294 "div" => {
295 let a: i64 = args.first()?.parse().ok()?;
296 let b: i64 = args.get(1)?.parse().ok()?;
297 if b == 0 {
298 return Some(String::new());
299 }
300 Some((a / b).to_string())
301 }
302 "mod" => {
303 let a: i64 = args.first()?.parse().ok()?;
304 let b: i64 = args.get(1)?.parse().ok()?;
305 if b == 0 {
306 return Some(String::new());
307 }
308 Some((a % b).to_string())
309 }
310 "max" => {
311 let a: i64 = args.first()?.parse().ok()?;
312 let b: i64 = args.get(1)?.parse().ok()?;
313 Some(a.max(b).to_string())
314 }
315 "min" => {
316 let a: i64 = args.first()?.parse().ok()?;
317 let b: i64 = args.get(1)?.parse().ok()?;
318 Some(a.min(b).to_string())
319 }
320 "hex" => {
321 let n: i64 = args.first()?.parse().ok()?;
322 let digits = args
323 .get(1)
324 .and_then(|s| s.parse::<usize>().ok())
325 .unwrap_or(0);
326 if digits > 0 {
327 Some(format!("{n:0>width$X}", width = digits))
328 } else {
329 Some(format!("{n:X}"))
330 }
331 }
332
333 "directory" => {
335 let p = Path::new(args.first()?);
336 p.parent()
337 .and_then(|p| p.file_name())
338 .and_then(|n| n.to_str())
339 .map(String::from)
340 }
341 "directory_path" => {
342 let p = Path::new(args.first()?);
343 p.parent().and_then(|p| p.to_str()).map(String::from)
344 }
345 "ext" => {
346 let p = Path::new(args.first()?);
347 p.extension().and_then(|e| e.to_str()).map(String::from)
348 }
349 "filename" => {
350 let p = Path::new(args.first()?);
351 p.file_stem().and_then(|s| s.to_str()).map(String::from)
352 }
353
354 "tab" => {
356 let n = args
357 .first()
358 .and_then(|s| s.parse::<usize>().ok())
359 .unwrap_or(1);
360 Some("\t".repeat(n))
361 }
362 "crlf" => Some("\r\n".into()),
363 "char" => {
364 let n: u32 = args.first()?.parse().ok()?;
365 char::from_u32(n).map(|c| c.to_string())
366 }
367
368 "info" => Some(args.first()?.clone()),
370 "len" => Some(args.first()?.len().to_string()),
371
372 _ => None,
373 }
374}
375
376fn capitalize_words(s: &str) -> String {
377 s.split_inclusive(char::is_whitespace)
378 .map(|word| {
379 let mut chars = word.chars();
380 match chars.next() {
381 Some(c) => {
382 let upper: String = c.to_uppercase().collect();
383 upper + &chars.as_str().to_lowercase()
384 }
385 None => String::new(),
386 }
387 })
388 .collect()
389}
390
391fn capitalize_words_smart(s: &str) -> String {
393 const LOWER_WORDS: &[&str] = &[
394 "a", "an", "the", "and", "or", "nor", "but", "in", "on", "at", "to", "for", "of", "with",
395 "by", "from", "as", "is", "vs",
396 ];
397 let words: Vec<&str> = s.split_inclusive(char::is_whitespace).collect();
398 words
399 .iter()
400 .enumerate()
401 .map(|(i, word)| {
402 let trimmed = word.trim().to_lowercase();
403 if i > 0 && LOWER_WORDS.contains(&trimmed.as_str()) {
404 word.to_lowercase()
405 } else {
406 let mut chars = word.chars();
407 match chars.next() {
408 Some(c) => {
409 let upper: String = c.to_uppercase().collect();
410 upper + &chars.as_str().to_lowercase()
411 }
412 None => String::new(),
413 }
414 }
415 })
416 .collect()
417}
418
419fn abbreviate(s: &str) -> String {
421 s.split_whitespace()
422 .filter_map(|w| w.chars().next())
423 .collect()
424}
425
426#[cfg(test)]
427mod tests {
428 use super::*;
429
430 #[test]
433 fn left() {
434 assert_eq!(
435 call_function("left", &s(&["hello", "3"])),
436 Some("hel".into())
437 );
438 }
439
440 #[test]
441 fn left_exceeds_length() {
442 assert_eq!(call_function("left", &s(&["hi", "10"])), Some("hi".into()));
443 }
444
445 #[test]
446 fn left_zero() {
447 assert_eq!(call_function("left", &s(&["hello", "0"])), Some("".into()));
448 }
449
450 #[test]
451 fn left_unicode() {
452 assert_eq!(
453 call_function("left", &s(&["\u{00e9}l\u{00e8}ve", "3"])),
454 Some("\u{00e9}l\u{00e8}".into())
455 );
456 }
457
458 #[test]
459 fn right() {
460 assert_eq!(
461 call_function("right", &s(&["hello", "3"])),
462 Some("llo".into())
463 );
464 }
465
466 #[test]
467 fn right_exceeds_length() {
468 assert_eq!(call_function("right", &s(&["hi", "10"])), Some("hi".into()));
469 }
470
471 #[test]
472 fn pad_left() {
473 assert_eq!(call_function("pad", &s(&["42", "5"])), Some(" 42".into()));
474 }
475
476 #[test]
477 fn pad_left_already_wider() {
478 assert_eq!(
479 call_function("pad", &s(&["hello", "3"])),
480 Some("hello".into())
481 );
482 }
483
484 #[test]
485 fn pad_right() {
486 assert_eq!(
487 call_function("pad_right", &s(&["42", "5"])),
488 Some("42 ".into())
489 );
490 }
491
492 #[test]
493 fn padcut() {
494 assert_eq!(
495 call_function("padcut", &s(&["hi", "5"])),
496 Some(" hi".into())
497 );
498 assert_eq!(
499 call_function("padcut", &s(&["hello world", "5"])),
500 Some("hello".into())
501 );
502 }
503
504 #[test]
505 fn padcut_right() {
506 assert_eq!(
507 call_function("padcut_right", &s(&["hi", "5"])),
508 Some("hi ".into())
509 );
510 assert_eq!(
511 call_function("padcut_right", &s(&["hello world", "5"])),
512 Some("hello".into())
513 );
514 }
515
516 #[test]
517 fn replace() {
518 assert_eq!(
519 call_function("replace", &s(&["hello world", "world", "rust"])),
520 Some("hello rust".into())
521 );
522 }
523
524 #[test]
525 fn replace_no_match() {
526 assert_eq!(
527 call_function("replace", &s(&["hello", "xyz", "abc"])),
528 Some("hello".into())
529 );
530 }
531
532 #[test]
533 fn trim() {
534 assert_eq!(
535 call_function("trim", &s(&[" hello "])),
536 Some("hello".into())
537 );
538 }
539
540 #[test]
541 fn trim_empty() {
542 assert_eq!(call_function("trim", &s(&[" "])), Some("".into()));
543 }
544
545 #[test]
546 fn lower() {
547 assert_eq!(call_function("lower", &s(&["HELLO"])), Some("hello".into()));
548 }
549
550 #[test]
551 fn upper() {
552 assert_eq!(call_function("upper", &s(&["hello"])), Some("HELLO".into()));
553 }
554
555 #[test]
556 fn caps() {
557 assert_eq!(
558 call_function("caps", &s(&["hello world"])),
559 Some("Hello World".into())
560 );
561 }
562
563 #[test]
564 fn caps_mixed_case() {
565 assert_eq!(
566 call_function("caps", &s(&["hELLO wORLD"])),
567 Some("Hello World".into())
568 );
569 }
570
571 #[test]
572 fn caps2_articles() {
573 assert_eq!(
574 call_function("caps2", &s(&["the quick and the dead"])),
575 Some("The Quick and the Dead".into())
576 );
577 }
578
579 #[test]
580 fn abbr() {
581 assert_eq!(
582 call_function("abbr", &s(&["Aphex Twin"])),
583 Some("AT".into())
584 );
585 assert_eq!(
586 call_function("abbr", &s(&["The Chemical Brothers"])),
587 Some("TCB".into())
588 );
589 }
590
591 #[test]
592 fn substr() {
593 assert_eq!(
594 call_function("substr", &s(&["hello world", "6", "11"])),
595 Some("world".into())
596 );
597 }
598
599 #[test]
600 fn substr_out_of_bounds() {
601 assert_eq!(
602 call_function("substr", &s(&["hi", "0", "100"])),
603 Some("hi".into())
604 );
605 }
606
607 #[test]
608 fn insert() {
609 assert_eq!(
610 call_function("insert", &s(&["hello", "XX", "2"])),
611 Some("heXXllo".into())
612 );
613 }
614
615 #[test]
616 fn repeat() {
617 assert_eq!(
618 call_function("repeat", &s(&["ab", "3"])),
619 Some("ababab".into())
620 );
621 }
622
623 #[test]
624 fn repeat_zero() {
625 assert_eq!(call_function("repeat", &s(&["ab", "0"])), Some("".into()));
626 }
627
628 #[test]
629 fn stripprefix_the() {
630 assert_eq!(
631 call_function("stripprefix", &s(&["The Beatles"])),
632 Some("Beatles".into())
633 );
634 }
635
636 #[test]
637 fn stripprefix_a() {
638 assert_eq!(
639 call_function("stripprefix", &s(&["A Perfect Circle"])),
640 Some("Perfect Circle".into())
641 );
642 }
643
644 #[test]
645 fn stripprefix_no_match() {
646 assert_eq!(
647 call_function("stripprefix", &s(&["Radiohead"])),
648 Some("Radiohead".into())
649 );
650 }
651
652 #[test]
653 fn swapprefix() {
654 assert_eq!(
655 call_function("swapprefix", &s(&["The Beatles"])),
656 Some("Beatles, The".into())
657 );
658 }
659
660 #[test]
661 fn swapprefix_no_match() {
662 assert_eq!(
663 call_function("swapprefix", &s(&["Radiohead"])),
664 Some("Radiohead".into())
665 );
666 }
667
668 #[test]
669 fn rot13() {
670 assert_eq!(call_function("rot13", &s(&["Hello"])), Some("Uryyb".into()));
671 assert_eq!(call_function("rot13", &s(&["Uryyb"])), Some("Hello".into()));
672 }
673
674 #[test]
675 fn fix_eol() {
676 assert_eq!(
677 call_function("fix_eol", &s(&["line1\nline2\rline3"])),
678 Some("line1 line2 line3".into())
679 );
680 }
681
682 #[test]
683 fn fix_eol_custom() {
684 assert_eq!(
685 call_function("fix_eol", &s(&["a\nb", " | "])),
686 Some("a | b".into())
687 );
688 }
689
690 #[test]
693 fn strchr_found() {
694 assert_eq!(
695 call_function("strchr", &s(&["hello", "l"])),
696 Some("3".into())
697 );
698 }
699
700 #[test]
701 fn strchr_not_found() {
702 assert_eq!(
703 call_function("strchr", &s(&["hello", "z"])),
704 Some("".into())
705 );
706 }
707
708 #[test]
709 fn strrchr_found() {
710 assert_eq!(
711 call_function("strrchr", &s(&["hello", "l"])),
712 Some("4".into())
713 );
714 }
715
716 #[test]
717 fn strstr_found() {
718 assert_eq!(
719 call_function("strstr", &s(&["hello world", "world"])),
720 Some("7".into())
721 );
722 }
723
724 #[test]
725 fn strstr_not_found() {
726 assert_eq!(
727 call_function("strstr", &s(&["hello", "xyz"])),
728 Some("".into())
729 );
730 }
731
732 #[test]
735 fn strcmp_equal() {
736 assert_eq!(
737 call_function("strcmp", &s(&["hello", "hello"])),
738 Some("1".into())
739 );
740 }
741
742 #[test]
743 fn strcmp_not_equal() {
744 assert_eq!(
745 call_function("strcmp", &s(&["hello", "HELLO"])),
746 Some("".into())
747 );
748 }
749
750 #[test]
751 fn stricmp_equal() {
752 assert_eq!(
753 call_function("stricmp", &s(&["hello", "HELLO"])),
754 Some("1".into())
755 );
756 }
757
758 #[test]
759 fn stricmp_not_equal() {
760 assert_eq!(
761 call_function("stricmp", &s(&["hello", "world"])),
762 Some("".into())
763 );
764 }
765
766 #[test]
767 fn stricmp_various_artists() {
768 assert_eq!(
769 call_function("stricmp", &s(&["Various Artists", "Various Artists"])),
770 Some("1".into())
771 );
772 assert_eq!(
773 call_function("stricmp", &s(&["various artists", "Various Artists"])),
774 Some("1".into())
775 );
776 assert_eq!(
777 call_function("stricmp", &s(&["Aphex Twin", "Various Artists"])),
778 Some("".into())
779 );
780 }
781
782 #[test]
783 fn longer() {
784 assert_eq!(
785 call_function("longer", &s(&["hello", "hi"])),
786 Some("1".into())
787 );
788 assert_eq!(
789 call_function("longer", &s(&["hi", "hello"])),
790 Some("".into())
791 );
792 assert_eq!(call_function("longer", &s(&["hi", "hi"])), Some("".into()));
793 }
794
795 #[test]
796 fn longest() {
797 assert_eq!(
798 call_function("longest", &s(&["a", "hello", "hi"])),
799 Some("hello".into())
800 );
801 }
802
803 #[test]
804 fn shortest() {
805 assert_eq!(
806 call_function("shortest", &s(&["hello", "a", "hi"])),
807 Some("a".into())
808 );
809 }
810
811 #[test]
814 fn if_nonempty() {
815 assert_eq!(
816 call_function("if", &s(&["yes", "true", "false"])),
817 Some("true".into())
818 );
819 }
820
821 #[test]
822 fn if_empty() {
823 assert_eq!(
824 call_function("if", &s(&["", "true", "false"])),
825 Some("false".into())
826 );
827 }
828
829 #[test]
830 fn if_empty_then_branch() {
831 assert_eq!(
833 call_function("if", &s(&["yes", "", "fallback"])),
834 Some("".into())
835 );
836 }
837
838 #[test]
839 fn if_no_else() {
840 assert_eq!(call_function("if", &s(&["", "true"])), Some("".into()));
842 }
843
844 #[test]
845 fn if2_first() {
846 assert_eq!(
847 call_function("if2", &s(&["first", "second"])),
848 Some("first".into())
849 );
850 }
851
852 #[test]
853 fn if2_fallback() {
854 assert_eq!(
855 call_function("if2", &s(&["", "second"])),
856 Some("second".into())
857 );
858 }
859
860 #[test]
861 fn if3_third() {
862 assert_eq!(
863 call_function("if3", &s(&["", "", "third"])),
864 Some("third".into())
865 );
866 }
867
868 #[test]
869 fn if3_all_empty() {
870 assert_eq!(call_function("if3", &s(&["", "", ""])), Some("".into()));
871 }
872
873 #[test]
874 fn ifequal_match() {
875 assert_eq!(
876 call_function("ifequal", &s(&["5", "5", "yes", "no"])),
877 Some("yes".into())
878 );
879 }
880
881 #[test]
882 fn ifequal_no_match() {
883 assert_eq!(
884 call_function("ifequal", &s(&["5", "3", "yes", "no"])),
885 Some("no".into())
886 );
887 }
888
889 #[test]
890 fn ifgreater_true() {
891 assert_eq!(
892 call_function("ifgreater", &s(&["10", "5", "yes", "no"])),
893 Some("yes".into())
894 );
895 }
896
897 #[test]
898 fn ifgreater_false() {
899 assert_eq!(
900 call_function("ifgreater", &s(&["3", "5", "yes", "no"])),
901 Some("no".into())
902 );
903 }
904
905 #[test]
906 fn iflonger_true() {
907 assert_eq!(
908 call_function("iflonger", &s(&["hello", "3", "yes", "no"])),
909 Some("yes".into())
910 );
911 }
912
913 #[test]
914 fn iflonger_false() {
915 assert_eq!(
916 call_function("iflonger", &s(&["hi", "5", "yes", "no"])),
917 Some("no".into())
918 );
919 }
920
921 #[test]
922 fn select_valid() {
923 assert_eq!(
924 call_function("select", &s(&["2", "a", "b", "c"])),
925 Some("b".into())
926 );
927 }
928
929 #[test]
930 fn select_out_of_range() {
931 assert_eq!(
932 call_function("select", &s(&["0", "a", "b"])),
933 Some("".into())
934 );
935 assert_eq!(
936 call_function("select", &s(&["99", "a", "b"])),
937 Some("".into())
938 );
939 }
940
941 #[test]
942 fn not_truthy() {
943 assert_eq!(call_function("not", &s(&["hello"])), Some("".into()));
944 }
945
946 #[test]
947 fn not_falsy() {
948 assert_eq!(call_function("not", &s(&[""])), Some("1".into()));
949 }
950
951 #[test]
952 fn and_both_truthy() {
953 assert_eq!(call_function("and", &s(&["a", "b"])), Some("1".into()));
954 }
955
956 #[test]
957 fn and_one_empty() {
958 assert_eq!(call_function("and", &s(&["a", ""])), Some("".into()));
959 }
960
961 #[test]
962 fn or_one_truthy() {
963 assert_eq!(call_function("or", &s(&["", "b"])), Some("1".into()));
964 }
965
966 #[test]
967 fn or_both_empty() {
968 assert_eq!(call_function("or", &s(&["", ""])), Some("".into()));
969 }
970
971 #[test]
972 fn xor_different() {
973 assert_eq!(call_function("xor", &s(&["a", ""])), Some("1".into()));
974 }
975
976 #[test]
977 fn xor_same() {
978 assert_eq!(call_function("xor", &s(&["a", "b"])), Some("".into()));
979 assert_eq!(call_function("xor", &s(&["", ""])), Some("".into()));
980 }
981
982 #[test]
983 fn greater_true() {
984 assert_eq!(call_function("greater", &s(&["10", "5"])), Some("1".into()));
985 }
986
987 #[test]
988 fn greater_false() {
989 assert_eq!(call_function("greater", &s(&["3", "5"])), Some("".into()));
990 }
991
992 #[test]
993 fn greater_equal() {
994 assert_eq!(call_function("greater", &s(&["5", "5"])), Some("".into()));
995 }
996
997 #[test]
1000 fn num_zero_pad() {
1001 assert_eq!(call_function("num", &s(&["5", "3"])), Some("005".into()));
1002 }
1003
1004 #[test]
1005 fn num_already_wide() {
1006 assert_eq!(
1007 call_function("num", &s(&["12345", "3"])),
1008 Some("12345".into())
1009 );
1010 }
1011
1012 #[test]
1013 fn add() {
1014 assert_eq!(call_function("add", &s(&["3", "4"])), Some("7".into()));
1015 }
1016
1017 #[test]
1018 fn add_negative() {
1019 assert_eq!(call_function("add", &s(&["10", "-3"])), Some("7".into()));
1020 }
1021
1022 #[test]
1023 fn sub() {
1024 assert_eq!(call_function("sub", &s(&["10", "3"])), Some("7".into()));
1025 }
1026
1027 #[test]
1028 fn mul() {
1029 assert_eq!(call_function("mul", &s(&["3", "4"])), Some("12".into()));
1030 }
1031
1032 #[test]
1033 fn muldiv() {
1034 assert_eq!(
1035 call_function("muldiv", &s(&["10", "3", "2"])),
1036 Some("15".into())
1037 );
1038 }
1039
1040 #[test]
1041 fn muldiv_by_zero() {
1042 assert_eq!(
1043 call_function("muldiv", &s(&["10", "3", "0"])),
1044 Some("".into())
1045 );
1046 }
1047
1048 #[test]
1049 fn div() {
1050 assert_eq!(call_function("div", &s(&["10", "3"])), Some("3".into()));
1051 }
1052
1053 #[test]
1054 fn div_by_zero() {
1055 assert_eq!(call_function("div", &s(&["10", "0"])), Some("".into()));
1056 }
1057
1058 #[test]
1059 fn modulo() {
1060 assert_eq!(call_function("mod", &s(&["10", "3"])), Some("1".into()));
1061 }
1062
1063 #[test]
1064 fn mod_by_zero() {
1065 assert_eq!(call_function("mod", &s(&["10", "0"])), Some("".into()));
1066 }
1067
1068 #[test]
1069 fn max() {
1070 assert_eq!(call_function("max", &s(&["3", "7"])), Some("7".into()));
1071 }
1072
1073 #[test]
1074 fn min() {
1075 assert_eq!(call_function("min", &s(&["3", "7"])), Some("3".into()));
1076 }
1077
1078 #[test]
1079 fn hex_basic() {
1080 assert_eq!(call_function("hex", &s(&["255"])), Some("FF".into()));
1081 }
1082
1083 #[test]
1084 fn hex_padded() {
1085 assert_eq!(call_function("hex", &s(&["255", "4"])), Some("00FF".into()));
1086 }
1087
1088 #[test]
1091 fn directory() {
1092 assert_eq!(
1093 call_function("directory", &s(&["/music/artist/album/track.flac"])),
1094 Some("album".into())
1095 );
1096 }
1097
1098 #[test]
1099 fn directory_path() {
1100 assert_eq!(
1101 call_function("directory_path", &s(&["/music/artist/album/track.flac"])),
1102 Some("/music/artist/album".into())
1103 );
1104 }
1105
1106 #[test]
1107 fn ext() {
1108 assert_eq!(
1109 call_function("ext", &s(&["track.flac"])),
1110 Some("flac".into())
1111 );
1112 }
1113
1114 #[test]
1115 fn ext_no_extension() {
1116 assert_eq!(call_function("ext", &s(&["track"])), None);
1117 }
1118
1119 #[test]
1120 fn filename() {
1121 assert_eq!(
1122 call_function("filename", &s(&["track.flac"])),
1123 Some("track".into())
1124 );
1125 }
1126
1127 #[test]
1128 fn filename_with_path() {
1129 assert_eq!(
1130 call_function("filename", &s(&["/music/track.flac"])),
1131 Some("track".into())
1132 );
1133 }
1134
1135 #[test]
1138 fn tab() {
1139 assert_eq!(call_function("tab", &s(&[])), Some("\t".into()));
1140 assert_eq!(call_function("tab", &s(&["3"])), Some("\t\t\t".into()));
1141 }
1142
1143 #[test]
1144 fn crlf() {
1145 assert_eq!(call_function("crlf", &s(&[])), Some("\r\n".into()));
1146 }
1147
1148 #[test]
1149 fn char_function() {
1150 assert_eq!(call_function("char", &s(&["65"])), Some("A".into()));
1151 assert_eq!(
1152 call_function("char", &s(&["8226"])),
1153 Some("\u{2022}".into())
1154 );
1155 }
1156
1157 #[test]
1160 fn info() {
1161 assert_eq!(call_function("info", &s(&["test"])), Some("test".into()));
1162 }
1163
1164 #[test]
1165 fn len() {
1166 assert_eq!(call_function("len", &s(&["hello"])), Some("5".into()));
1167 }
1168
1169 #[test]
1170 fn len_empty() {
1171 assert_eq!(call_function("len", &s(&[""])), Some("0".into()));
1172 }
1173
1174 #[test]
1175 fn unknown_function() {
1176 assert_eq!(call_function("nonexistent", &s(&["arg"])), None);
1177 }
1178
1179 #[test]
1182 fn left_no_args() {
1183 assert_eq!(call_function("left", &s(&[])), None);
1184 }
1185
1186 #[test]
1187 fn div_non_numeric() {
1188 assert_eq!(call_function("div", &s(&["abc", "3"])), None);
1189 }
1190
1191 fn s(args: &[&str]) -> Vec<String> {
1193 args.iter().map(|s| (*s).to_string()).collect()
1194 }
1195}