1use alloc::vec::Vec;
8
9#[cfg(not(feature = "std"))]
10extern crate alloc;
11
12use super::Span;
13#[cfg(debug_assertions)]
14use core::ops::Range;
15
16#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct ScriptInfo<'a> {
35 pub fields: Vec<(&'a str, &'a str)>,
37 pub span: Span,
39}
40
41impl<'a> ScriptInfo<'a> {
42 #[must_use]
66 pub fn get_field(&self, key: &str) -> Option<&'a str> {
67 self.fields.iter().find(|(k, _)| *k == key).map(|(_, v)| *v)
68 }
69
70 #[must_use]
75 pub fn title(&self) -> &str {
76 self.get_field("Title").unwrap_or("<untitled>")
77 }
78
79 #[must_use]
84 pub fn script_type(&self) -> Option<&'a str> {
85 self.get_field("ScriptType")
86 }
87
88 #[must_use]
98 pub fn play_resolution(&self) -> Option<(u32, u32)> {
99 let width = self.get_field("PlayResX")?.parse().ok()?;
100 let height = self.get_field("PlayResY")?.parse().ok()?;
101 Some((width, height))
102 }
103
104 #[must_use]
115 pub fn layout_resolution(&self) -> Option<(u32, u32)> {
116 let width = self.get_field("LayoutResX")?.parse().ok()?;
117 let height = self.get_field("LayoutResY")?.parse().ok()?;
118 Some((width, height))
119 }
120
121 #[must_use]
133 pub fn wrap_style(&self) -> u8 {
134 self.get_field("WrapStyle")
135 .and_then(|s| s.parse().ok())
136 .unwrap_or(0)
137 }
138
139 #[must_use]
155 pub fn to_ass_string(&self) -> alloc::string::String {
156 use core::fmt::Write;
157 let mut result = alloc::string::String::from("[Script Info]\n");
158 for (key, value) in &self.fields {
159 let _ = writeln!(result, "{key}: {value}");
160 }
161 result
162 }
163
164 #[cfg(debug_assertions)]
172 #[must_use]
173 pub fn validate_spans(&self, source_range: &Range<usize>) -> bool {
174 self.fields.iter().all(|(key, value)| {
175 let key_ptr = key.as_ptr() as usize;
176 let value_ptr = value.as_ptr() as usize;
177 source_range.contains(&key_ptr) && source_range.contains(&value_ptr)
178 })
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185 #[cfg(not(feature = "std"))]
186 use alloc::vec;
187
188 #[test]
189 fn script_info_field_access() {
190 let fields = vec![("Title", "Test Script"), ("ScriptType", "v4.00+")];
191 let info = ScriptInfo {
192 fields,
193 span: Span::new(0, 0, 0, 0),
194 };
195
196 assert_eq!(info.title(), "Test Script");
197 assert_eq!(info.script_type(), Some("v4.00+"));
198 assert_eq!(info.get_field("Unknown"), None);
199 }
200
201 #[test]
202 fn script_info_defaults() {
203 let info = ScriptInfo {
204 fields: Vec::new(),
205 span: Span::new(0, 0, 0, 0),
206 };
207 assert_eq!(info.title(), "<untitled>");
208 assert_eq!(info.wrap_style(), 0);
209 assert_eq!(info.layout_resolution(), None);
210 assert_eq!(info.play_resolution(), None);
211 }
212
213 #[test]
214 fn script_info_play_resolution() {
215 let fields = vec![("PlayResX", "1920"), ("PlayResY", "1080")];
216 let info = ScriptInfo {
217 fields,
218 span: Span::new(0, 0, 0, 0),
219 };
220 assert_eq!(info.play_resolution(), Some((1920, 1080)));
221 }
222
223 #[test]
224 fn script_info_partial_play_resolution() {
225 let fields = vec![("PlayResX", "1920")];
226 let info = ScriptInfo {
227 fields,
228 span: Span::new(0, 0, 0, 0),
229 };
230 assert_eq!(info.play_resolution(), None);
231 }
232
233 #[test]
234 fn script_info_layout_resolution() {
235 let fields = vec![("LayoutResX", "1920"), ("LayoutResY", "1080")];
236 let info = ScriptInfo {
237 fields,
238 span: Span::new(0, 0, 0, 0),
239 };
240 assert_eq!(info.layout_resolution(), Some((1920, 1080)));
241 }
242
243 #[test]
244 fn script_info_partial_layout_resolution() {
245 let fields = vec![("LayoutResX", "1920")];
246 let info = ScriptInfo {
247 fields,
248 span: Span::new(0, 0, 0, 0),
249 };
250 assert_eq!(info.layout_resolution(), None);
251 }
252
253 #[test]
254 fn script_info_wrap_style() {
255 let fields = vec![("WrapStyle", "2")];
256 let info = ScriptInfo {
257 fields,
258 span: Span::new(0, 0, 0, 0),
259 };
260 assert_eq!(info.wrap_style(), 2);
261 }
262
263 #[test]
264 fn script_info_invalid_wrap_style() {
265 let fields = vec![("WrapStyle", "invalid")];
266 let info = ScriptInfo {
267 fields,
268 span: Span::new(0, 0, 0, 0),
269 };
270 assert_eq!(info.wrap_style(), 0); }
272
273 #[test]
274 fn script_info_invalid_resolution() {
275 let fields = vec![("PlayResX", "invalid"), ("PlayResY", "1080")];
276 let info = ScriptInfo {
277 fields,
278 span: Span::new(0, 0, 0, 0),
279 };
280 assert_eq!(info.play_resolution(), None);
281 }
282
283 #[test]
284 fn script_info_case_sensitive_keys() {
285 let fields = vec![("title", "Test"), ("Title", "Correct")];
286 let info = ScriptInfo {
287 fields,
288 span: Span::new(0, 0, 0, 0),
289 };
290 assert_eq!(info.get_field("Title"), Some("Correct"));
291 assert_eq!(info.get_field("title"), Some("Test"));
292 }
293
294 #[test]
295 fn script_info_to_ass_string() {
296 let fields = vec![
297 ("Title", "Test Script"),
298 ("ScriptType", "v4.00+"),
299 ("WrapStyle", "0"),
300 ("ScaledBorderAndShadow", "yes"),
301 ("YCbCr Matrix", "None"),
302 ];
303 let info = ScriptInfo {
304 fields,
305 span: Span::new(0, 0, 0, 0),
306 };
307
308 let ass_string = info.to_ass_string();
309 assert!(ass_string.starts_with("[Script Info]\n"));
310 assert!(ass_string.contains("Title: Test Script\n"));
311 assert!(ass_string.contains("ScriptType: v4.00+\n"));
312 assert!(ass_string.contains("WrapStyle: 0\n"));
313 assert!(ass_string.contains("ScaledBorderAndShadow: yes\n"));
314 assert!(ass_string.contains("YCbCr Matrix: None\n"));
315 }
316
317 #[test]
318 fn script_info_to_ass_string_empty() {
319 let info = ScriptInfo {
320 fields: vec![],
321 span: Span::new(0, 0, 0, 0),
322 };
323
324 let ass_string = info.to_ass_string();
325 assert_eq!(ass_string, "[Script Info]\n");
326 }
327}