use alloc::vec::Vec;
#[cfg(not(feature = "std"))]
extern crate alloc;
use super::Span;
#[cfg(debug_assertions)]
use core::ops::Range;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ScriptInfo<'a> {
pub fields: Vec<(&'a str, &'a str)>,
pub span: Span,
}
impl<'a> ScriptInfo<'a> {
#[must_use]
pub fn get_field(&self, key: &str) -> Option<&'a str> {
self.fields.iter().find(|(k, _)| *k == key).map(|(_, v)| *v)
}
#[must_use]
pub fn title(&self) -> &str {
self.get_field("Title").unwrap_or("<untitled>")
}
#[must_use]
pub fn script_type(&self) -> Option<&'a str> {
self.get_field("ScriptType")
}
#[must_use]
pub fn play_resolution(&self) -> Option<(u32, u32)> {
let width = self.get_field("PlayResX")?.parse().ok()?;
let height = self.get_field("PlayResY")?.parse().ok()?;
Some((width, height))
}
#[must_use]
pub fn layout_resolution(&self) -> Option<(u32, u32)> {
let width = self.get_field("LayoutResX")?.parse().ok()?;
let height = self.get_field("LayoutResY")?.parse().ok()?;
Some((width, height))
}
#[must_use]
pub fn wrap_style(&self) -> u8 {
self.get_field("WrapStyle")
.and_then(|s| s.parse().ok())
.unwrap_or(0)
}
#[must_use]
pub fn to_ass_string(&self) -> alloc::string::String {
use core::fmt::Write;
let mut result = alloc::string::String::from("[Script Info]\n");
for (key, value) in &self.fields {
let _ = writeln!(result, "{key}: {value}");
}
result
}
#[cfg(debug_assertions)]
#[must_use]
pub fn validate_spans(&self, source_range: &Range<usize>) -> bool {
self.fields.iter().all(|(key, value)| {
let key_ptr = key.as_ptr() as usize;
let value_ptr = value.as_ptr() as usize;
source_range.contains(&key_ptr) && source_range.contains(&value_ptr)
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(not(feature = "std"))]
use alloc::vec;
#[test]
fn script_info_field_access() {
let fields = vec![("Title", "Test Script"), ("ScriptType", "v4.00+")];
let info = ScriptInfo {
fields,
span: Span::new(0, 0, 0, 0),
};
assert_eq!(info.title(), "Test Script");
assert_eq!(info.script_type(), Some("v4.00+"));
assert_eq!(info.get_field("Unknown"), None);
}
#[test]
fn script_info_defaults() {
let info = ScriptInfo {
fields: Vec::new(),
span: Span::new(0, 0, 0, 0),
};
assert_eq!(info.title(), "<untitled>");
assert_eq!(info.wrap_style(), 0);
assert_eq!(info.layout_resolution(), None);
assert_eq!(info.play_resolution(), None);
}
#[test]
fn script_info_play_resolution() {
let fields = vec![("PlayResX", "1920"), ("PlayResY", "1080")];
let info = ScriptInfo {
fields,
span: Span::new(0, 0, 0, 0),
};
assert_eq!(info.play_resolution(), Some((1920, 1080)));
}
#[test]
fn script_info_partial_play_resolution() {
let fields = vec![("PlayResX", "1920")];
let info = ScriptInfo {
fields,
span: Span::new(0, 0, 0, 0),
};
assert_eq!(info.play_resolution(), None);
}
#[test]
fn script_info_layout_resolution() {
let fields = vec![("LayoutResX", "1920"), ("LayoutResY", "1080")];
let info = ScriptInfo {
fields,
span: Span::new(0, 0, 0, 0),
};
assert_eq!(info.layout_resolution(), Some((1920, 1080)));
}
#[test]
fn script_info_partial_layout_resolution() {
let fields = vec![("LayoutResX", "1920")];
let info = ScriptInfo {
fields,
span: Span::new(0, 0, 0, 0),
};
assert_eq!(info.layout_resolution(), None);
}
#[test]
fn script_info_wrap_style() {
let fields = vec![("WrapStyle", "2")];
let info = ScriptInfo {
fields,
span: Span::new(0, 0, 0, 0),
};
assert_eq!(info.wrap_style(), 2);
}
#[test]
fn script_info_invalid_wrap_style() {
let fields = vec![("WrapStyle", "invalid")];
let info = ScriptInfo {
fields,
span: Span::new(0, 0, 0, 0),
};
assert_eq!(info.wrap_style(), 0); }
#[test]
fn script_info_invalid_resolution() {
let fields = vec![("PlayResX", "invalid"), ("PlayResY", "1080")];
let info = ScriptInfo {
fields,
span: Span::new(0, 0, 0, 0),
};
assert_eq!(info.play_resolution(), None);
}
#[test]
fn script_info_case_sensitive_keys() {
let fields = vec![("title", "Test"), ("Title", "Correct")];
let info = ScriptInfo {
fields,
span: Span::new(0, 0, 0, 0),
};
assert_eq!(info.get_field("Title"), Some("Correct"));
assert_eq!(info.get_field("title"), Some("Test"));
}
#[test]
fn script_info_to_ass_string() {
let fields = vec![
("Title", "Test Script"),
("ScriptType", "v4.00+"),
("WrapStyle", "0"),
("ScaledBorderAndShadow", "yes"),
("YCbCr Matrix", "None"),
];
let info = ScriptInfo {
fields,
span: Span::new(0, 0, 0, 0),
};
let ass_string = info.to_ass_string();
assert!(ass_string.starts_with("[Script Info]\n"));
assert!(ass_string.contains("Title: Test Script\n"));
assert!(ass_string.contains("ScriptType: v4.00+\n"));
assert!(ass_string.contains("WrapStyle: 0\n"));
assert!(ass_string.contains("ScaledBorderAndShadow: yes\n"));
assert!(ass_string.contains("YCbCr Matrix: None\n"));
}
#[test]
fn script_info_to_ass_string_empty() {
let info = ScriptInfo {
fields: vec![],
span: Span::new(0, 0, 0, 0),
};
let ass_string = info.to_ass_string();
assert_eq!(ass_string, "[Script Info]\n");
}
}