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