Skip to main content

max / audiofiles

4.2 KB · 139 lines History Blame Raw
1 //! Host API functions registered into the Rhai engine.
2 //!
3 //! These functions provide safe, sandboxed helpers that plugin scripts can call.
4 //! No filesystem access, no network, no process spawning — only pure string
5 //! and data manipulation.
6
7 use rhai::Engine;
8
9 /// Register all host API functions into the engine.
10 pub fn register(engine: &mut Engine) {
11 // String helpers
12 engine.register_fn("pad_left", pad_left);
13 engine.register_fn("pad_right", pad_right);
14 engine.register_fn("truncate", truncate);
15 engine.register_fn("to_upper", to_upper);
16 engine.register_fn("to_lower", to_lower);
17 engine.register_fn("replace_char", replace_char);
18 engine.register_fn("strip_non_ascii", strip_non_ascii);
19
20 // Format helpers
21 engine.register_fn("format_index", format_index);
22 engine.register_fn("file_stem", file_stem);
23 engine.register_fn("file_extension", file_extension);
24 }
25
26 /// Pad a string on the left to a given width with a fill character.
27 /// Width is capped at 10,000 to prevent OOM from Rhai scripts.
28 fn pad_left(s: &str, width: i64, fill: &str) -> String {
29 let w = width.clamp(0, 10_000) as usize;
30 let fill_ch = fill.chars().next().unwrap_or(' ');
31 let len = s.chars().count();
32 if len >= w {
33 return s.to_string();
34 }
35 let padding: String = std::iter::repeat_n(fill_ch, w - len).collect();
36 format!("{padding}{s}")
37 }
38
39 /// Pad a string on the right to a given width with a fill character.
40 /// Width is capped at 10,000 to prevent OOM from Rhai scripts.
41 fn pad_right(s: &str, width: i64, fill: &str) -> String {
42 let w = width.clamp(0, 10_000) as usize;
43 let fill_ch = fill.chars().next().unwrap_or(' ');
44 let len = s.chars().count();
45 if len >= w {
46 return s.to_string();
47 }
48 let padding: String = std::iter::repeat_n(fill_ch, w - len).collect();
49 format!("{s}{padding}")
50 }
51
52 /// Truncate a string to a maximum number of characters.
53 fn truncate(s: &str, max_len: i64) -> String {
54 let max = max_len.max(0) as usize;
55 s.chars().take(max).collect()
56 }
57
58 /// Convert to uppercase.
59 fn to_upper(s: &str) -> String {
60 s.to_uppercase()
61 }
62
63 /// Convert to lowercase.
64 fn to_lower(s: &str) -> String {
65 s.to_lowercase()
66 }
67
68 /// Replace all occurrences of one character with another.
69 fn replace_char(s: &str, from: &str, to: &str) -> String {
70 let from_ch = from.chars().next().unwrap_or(' ');
71 let to_ch = to.chars().next().unwrap_or(' ');
72 s.chars()
73 .map(|c| if c == from_ch { to_ch } else { c })
74 .collect()
75 }
76
77 /// Strip non-ASCII characters from a string.
78 fn strip_non_ascii(s: &str) -> String {
79 s.chars().filter(|c| c.is_ascii()).collect()
80 }
81
82 /// Format an index as a zero-padded string (e.g. index 3, width 3 → "003").
83 /// Width is capped at 10,000 to prevent OOM from Rhai scripts.
84 fn format_index(index: i64, width: i64) -> String {
85 let idx = index.max(0);
86 let w = width.clamp(1, 10_000) as usize;
87 format!("{idx:0>w$}")
88 }
89
90 /// Extract the stem (filename without extension) from a path string.
91 fn file_stem(path: &str) -> String {
92 std::path::Path::new(path)
93 .file_stem()
94 .and_then(|s| s.to_str())
95 .unwrap_or(path)
96 .to_string()
97 }
98
99 /// Extract the extension from a path string (without the dot).
100 fn file_extension(path: &str) -> String {
101 std::path::Path::new(path)
102 .extension()
103 .and_then(|s| s.to_str())
104 .unwrap_or("")
105 .to_string()
106 }
107
108 #[cfg(test)]
109 mod tests {
110 use super::*;
111
112 #[test]
113 fn pad_left_works() {
114 assert_eq!(pad_left("42", 5, "0"), "00042");
115 assert_eq!(pad_left("hello", 3, " "), "hello");
116 }
117
118 #[test]
119 fn truncate_works() {
120 assert_eq!(truncate("hello world", 5), "hello");
121 assert_eq!(truncate("hi", 10), "hi");
122 }
123
124 #[test]
125 fn format_index_works() {
126 assert_eq!(format_index(3, 3), "003");
127 assert_eq!(format_index(42, 2), "42");
128 assert_eq!(format_index(1, 4), "0001");
129 }
130
131 #[test]
132 fn file_stem_and_extension() {
133 assert_eq!(file_stem("kick.wav"), "kick");
134 assert_eq!(file_extension("kick.wav"), "wav");
135 assert_eq!(file_stem("noext"), "noext");
136 assert_eq!(file_extension("noext"), "");
137 }
138 }
139