1#[cfg(not(feature = "std"))]
37extern crate alloc;
38
39mod event;
40mod media;
41mod script_info;
42mod section;
43mod style;
44pub use event::{Event, EventType};
46pub use media::{Font, Graphic};
47pub use script_info::ScriptInfo;
48pub use section::{Section, SectionType};
49pub use style::Style;
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub struct Span {
54 pub start: usize,
56 pub end: usize,
58 pub line: u32,
60 pub column: u32,
62}
63
64impl Span {
65 #[must_use]
67 pub const fn new(start: usize, end: usize, line: u32, column: u32) -> Self {
68 Self {
69 start,
70 end,
71 line,
72 column,
73 }
74 }
75
76 #[must_use]
78 pub const fn contains(&self, offset: usize) -> bool {
79 offset >= self.start && offset < self.end
80 }
81
82 #[must_use]
84 pub fn merge(&self, other: &Self) -> Self {
85 use core::cmp::Ordering;
86
87 Self {
88 start: self.start.min(other.start),
89 end: self.end.max(other.end),
90 line: self.line.min(other.line),
91 column: match self.line.cmp(&other.line) {
92 Ordering::Less => self.column,
93 Ordering::Greater => other.column,
94 Ordering::Equal => self.column.min(other.column),
95 },
96 }
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103 #[cfg(not(feature = "std"))]
104 use alloc::vec;
105
106 #[test]
107 fn test_span_creation() {
108 let span = Span::new(0, 10, 1, 1);
109 assert_eq!(span.start, 0);
110 assert_eq!(span.end, 10);
111 assert_eq!(span.line, 1);
112 assert_eq!(span.column, 1);
113 }
114
115 #[test]
116 fn test_span_contains() {
117 let span = Span::new(0, 10, 1, 1);
118 assert!(span.contains(0));
119 assert!(span.contains(5));
120 assert!(span.contains(9));
121 assert!(!span.contains(10));
122 assert!(!span.contains(15));
123 }
124
125 #[test]
126 fn test_span_merge() {
127 let span1 = Span::new(0, 10, 1, 1);
128 let span2 = Span::new(5, 15, 1, 6);
129 let merged = span1.merge(&span2);
130
131 assert_eq!(merged.start, 0);
132 assert_eq!(merged.end, 15);
133 assert_eq!(merged.line, 1);
134 assert_eq!(merged.column, 1);
135
136 let span3 = Span::new(20, 30, 2, 5);
138 let span4 = Span::new(25, 35, 3, 10);
139 let merged2 = span3.merge(&span4);
140
141 assert_eq!(merged2.start, 20);
142 assert_eq!(merged2.end, 35);
143 assert_eq!(merged2.line, 2);
144 assert_eq!(merged2.column, 5);
145 }
146
147 #[test]
148 fn ast_integration_script_info() {
149 let fields = vec![("Title", "Integration Test"), ("ScriptType", "v4.00+")];
150 let info = ScriptInfo {
151 fields,
152 span: Span::new(0, 0, 0, 0),
153 };
154 let section = Section::ScriptInfo(info);
155
156 assert_eq!(section.section_type(), SectionType::ScriptInfo);
157 }
158
159 #[test]
160 fn ast_integration_events() {
161 let event = Event {
162 event_type: EventType::Dialogue,
163 start: "0:00:05.00",
164 end: "0:00:10.00",
165 style: "Default",
166 text: "Test dialogue",
167 ..Event::default()
168 };
169
170 let events = vec![event];
171 let section = Section::Events(events);
172
173 assert_eq!(section.section_type(), SectionType::Events);
174 }
175
176 #[test]
177 fn ast_integration_styles() {
178 let style = Style {
179 name: "TestStyle",
180 fontname: "Arial",
181 fontsize: "20",
182 ..Style::default()
183 };
184
185 let styles = vec![style];
186 let section = Section::Styles(styles);
187
188 assert_eq!(section.section_type(), SectionType::Styles);
189 }
190
191 #[test]
192 fn ast_integration_fonts() {
193 let font = Font {
194 filename: "test.ttf",
195 data_lines: vec!["encoded data line 1", "encoded data line 2"],
196 span: Span::new(0, 0, 0, 0),
197 };
198
199 let fonts = vec![font];
200 let section = Section::Fonts(fonts);
201
202 assert_eq!(section.section_type(), SectionType::Fonts);
203 }
204
205 #[test]
206 fn ast_integration_graphics() {
207 let graphic = Graphic {
208 filename: "logo.png",
209 data_lines: vec!["encoded image data"],
210 span: Span::new(0, 0, 0, 0),
211 };
212
213 let graphics = vec![graphic];
214 let section = Section::Graphics(graphics);
215
216 assert_eq!(section.section_type(), SectionType::Graphics);
217 }
218
219 #[test]
220 fn event_type_round_trip() {
221 let types = [
222 EventType::Dialogue,
223 EventType::Comment,
224 EventType::Picture,
225 EventType::Sound,
226 EventType::Movie,
227 EventType::Command,
228 ];
229
230 for event_type in types {
231 let str_repr = event_type.as_str();
232 let parsed = EventType::parse_type(str_repr);
233 assert_eq!(parsed, Some(event_type));
234 }
235 }
236
237 #[test]
238 fn section_type_properties() {
239 assert!(SectionType::ScriptInfo.is_required());
240 assert!(SectionType::Events.is_required());
241 assert!(!SectionType::Styles.is_required());
242
243 assert!(SectionType::Events.is_timed());
244 assert!(!SectionType::ScriptInfo.is_timed());
245
246 assert_eq!(SectionType::ScriptInfo.header_name(), "Script Info");
247 assert_eq!(SectionType::Events.header_name(), "Events");
248 }
249}