1use alloc::vec::Vec;
8
9#[cfg(not(feature = "std"))]
10extern crate alloc;
11
12use super::{Event, Font, Graphic, ScriptInfo, Span, Style};
13#[cfg(debug_assertions)]
14use core::ops::Range;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
31pub enum SectionType {
32 ScriptInfo,
34 Styles,
36 Events,
38 Fonts,
40 Graphics,
42}
43
44#[derive(Debug, Clone, PartialEq, Eq)]
60pub enum Section<'a> {
61 ScriptInfo(ScriptInfo<'a>),
66
67 Styles(Vec<Style<'a>>),
73
74 Events(Vec<Event<'a>>),
79
80 Fonts(Vec<Font<'a>>),
85
86 Graphics(Vec<Graphic<'a>>),
91}
92
93impl Section<'_> {
94 #[must_use]
99 pub fn span(&self) -> Option<Span> {
100 match self {
101 Section::ScriptInfo(info) => Some(info.span),
102 Section::Styles(styles) => {
103 if styles.is_empty() {
104 None
105 } else {
106 let first = &styles[0].span;
108 let last = &styles[styles.len() - 1].span;
109 Some(Span::new(first.start, last.end, first.line, first.column))
110 }
111 }
112 Section::Events(events) => {
113 if events.is_empty() {
114 None
115 } else {
116 let first = &events[0].span;
118 let last = &events[events.len() - 1].span;
119 Some(Span::new(first.start, last.end, first.line, first.column))
120 }
121 }
122 Section::Fonts(fonts) => {
123 if fonts.is_empty() {
124 None
125 } else {
126 let first = &fonts[0].span;
128 let last = &fonts[fonts.len() - 1].span;
129 Some(Span::new(first.start, last.end, first.line, first.column))
130 }
131 }
132 Section::Graphics(graphics) => {
133 if graphics.is_empty() {
134 None
135 } else {
136 let first = &graphics[0].span;
138 let last = &graphics[graphics.len() - 1].span;
139 Some(Span::new(first.start, last.end, first.line, first.column))
140 }
141 }
142 }
143 }
144
145 #[must_use]
158 pub const fn section_type(&self) -> SectionType {
159 match self {
160 Section::ScriptInfo(_) => SectionType::ScriptInfo,
161 Section::Styles(_) => SectionType::Styles,
162 Section::Events(_) => SectionType::Events,
163 Section::Fonts(_) => SectionType::Fonts,
164 Section::Graphics(_) => SectionType::Graphics,
165 }
166 }
167
168 #[cfg(debug_assertions)]
185 #[must_use]
186 pub fn validate_spans(&self, source_range: &Range<usize>) -> bool {
187 match self {
188 Section::ScriptInfo(info) => info.validate_spans(source_range),
189 Section::Styles(styles) => styles.iter().all(|s| s.validate_spans(source_range)),
190 Section::Events(events) => events.iter().all(|e| e.validate_spans(source_range)),
191 Section::Fonts(fonts) => fonts.iter().all(|f| f.validate_spans(source_range)),
192 Section::Graphics(graphics) => graphics.iter().all(|g| g.validate_spans(source_range)),
193 }
194 }
195}
196
197impl SectionType {
198 #[must_use]
211 pub const fn header_name(self) -> &'static str {
212 match self {
213 Self::ScriptInfo => "Script Info",
214 Self::Styles => "V4+ Styles",
215 Self::Events => "Events",
216 Self::Fonts => "Fonts",
217 Self::Graphics => "Graphics",
218 }
219 }
220
221 #[must_use]
226 pub const fn is_required(self) -> bool {
227 matches!(self, Self::ScriptInfo | Self::Events)
228 }
229
230 #[must_use]
235 pub const fn is_timed(self) -> bool {
236 matches!(self, Self::Events)
237 }
238}
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243 use crate::parser::ast::{Event, EventType, Span, Style};
244 #[cfg(not(feature = "std"))]
245 use alloc::vec;
246
247 #[test]
248 fn section_type_discrimination() {
249 let info = Section::ScriptInfo(ScriptInfo {
250 fields: Vec::new(),
251 span: Span::new(0, 0, 0, 0),
252 });
253 assert_eq!(info.section_type(), SectionType::ScriptInfo);
254
255 let styles = Section::Styles(Vec::new());
256 assert_eq!(styles.section_type(), SectionType::Styles);
257
258 let events = Section::Events(Vec::new());
259 assert_eq!(events.section_type(), SectionType::Events);
260 }
261
262 #[test]
263 fn section_span_script_info() {
264 let info = Section::ScriptInfo(ScriptInfo {
265 fields: vec![("Title", "Test")],
266 span: Span::new(10, 50, 2, 1),
267 });
268
269 let span = info.span();
270 assert!(span.is_some());
271 let span = span.unwrap();
272 assert_eq!(span.start, 10);
273 assert_eq!(span.end, 50);
274 assert_eq!(span.line, 2);
275 }
276
277 #[test]
278 fn section_span_empty_styles() {
279 let styles = Section::Styles(Vec::new());
280 assert!(styles.span().is_none());
281 }
282
283 #[test]
284 fn section_span_single_style() {
285 let style = Style {
286 name: "Default",
287 parent: None,
288 fontname: "Arial",
289 fontsize: "20",
290 primary_colour: "&H00FFFFFF",
291 secondary_colour: "&H000000FF",
292 outline_colour: "&H00000000",
293 back_colour: "&H00000000",
294 bold: "0",
295 italic: "0",
296 underline: "0",
297 strikeout: "0",
298 scale_x: "100",
299 scale_y: "100",
300 spacing: "0",
301 angle: "0",
302 border_style: "1",
303 outline: "0",
304 shadow: "0",
305 alignment: "2",
306 margin_l: "0",
307 margin_r: "0",
308 margin_v: "0",
309 margin_t: None,
310 margin_b: None,
311 encoding: "1",
312 relative_to: None,
313 span: Span::new(100, 200, 5, 1),
314 };
315
316 let styles = Section::Styles(vec![style]);
317 let span = styles.span();
318 assert!(span.is_some());
319 let span = span.unwrap();
320 assert_eq!(span.start, 100);
321 assert_eq!(span.end, 200);
322 }
323
324 #[test]
325 fn section_span_multiple_events() {
326 let event1 = Event {
327 event_type: EventType::Dialogue,
328 layer: "0",
329 start: "0:00:00.00",
330 end: "0:00:05.00",
331 style: "Default",
332 name: "",
333 margin_l: "0",
334 margin_r: "0",
335 margin_v: "0",
336 margin_t: None,
337 margin_b: None,
338 effect: "",
339 text: "First",
340 span: Span::new(100, 150, 10, 1),
341 };
342
343 let event2 = Event {
344 event_type: EventType::Dialogue,
345 layer: "0",
346 start: "0:00:05.00",
347 end: "0:00:10.00",
348 style: "Default",
349 name: "",
350 margin_l: "0",
351 margin_r: "0",
352 margin_v: "0",
353 margin_t: None,
354 margin_b: None,
355 effect: "",
356 text: "Second",
357 span: Span::new(151, 200, 11, 1),
358 };
359
360 let events_section = Section::Events(vec![event1, event2]);
361 let span = events_section.span();
362 assert!(span.is_some());
363 let span = span.unwrap();
364 assert_eq!(span.start, 100);
365 assert_eq!(span.end, 200);
366 assert_eq!(span.line, 10);
367 }
368
369 #[test]
370 #[allow(clippy::similar_names)]
371 fn section_span_multiple_events_similar_names() {
372 }
374
375 #[test]
376 fn section_type_header_names() {
377 assert_eq!(SectionType::ScriptInfo.header_name(), "Script Info");
378 assert_eq!(SectionType::Styles.header_name(), "V4+ Styles");
379 assert_eq!(SectionType::Events.header_name(), "Events");
380 assert_eq!(SectionType::Fonts.header_name(), "Fonts");
381 assert_eq!(SectionType::Graphics.header_name(), "Graphics");
382 }
383
384 #[test]
385 fn section_type_required() {
386 assert!(SectionType::ScriptInfo.is_required());
387 assert!(SectionType::Events.is_required());
388 assert!(!SectionType::Styles.is_required());
389 assert!(!SectionType::Fonts.is_required());
390 assert!(!SectionType::Graphics.is_required());
391 }
392
393 #[test]
394 fn section_type_timed() {
395 assert!(SectionType::Events.is_timed());
396 assert!(!SectionType::ScriptInfo.is_timed());
397 assert!(!SectionType::Styles.is_timed());
398 assert!(!SectionType::Fonts.is_timed());
399 assert!(!SectionType::Graphics.is_timed());
400 }
401
402 #[test]
403 fn section_type_copy_clone() {
404 let section_type = SectionType::ScriptInfo;
405 let copied = section_type;
406 let cloned = section_type;
407
408 assert_eq!(section_type, copied);
409 assert_eq!(section_type, cloned);
410 }
411
412 #[test]
413 fn section_type_hash() {
414 use alloc::collections::BTreeSet;
415
416 let mut set = BTreeSet::new();
417 set.insert(SectionType::ScriptInfo);
418 set.insert(SectionType::Events);
419
420 assert!(set.contains(&SectionType::ScriptInfo));
421 assert!(set.contains(&SectionType::Events));
422 assert!(!set.contains(&SectionType::Styles));
423 }
424}