ass_core/parser/streaming/
state.rs1use alloc::string::String;
7
8#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum ParserState {
14 ExpectingSection,
16 InSection(SectionKind),
18 InEvent {
20 section: SectionKind,
22 fields_seen: usize,
24 },
25}
26
27impl ParserState {
28 #[must_use]
30 pub const fn is_in_section(&self) -> bool {
31 matches!(self, Self::InSection(_) | Self::InEvent { .. })
32 }
33
34 #[must_use]
36 pub const fn current_section(&self) -> Option<SectionKind> {
37 match self {
38 Self::ExpectingSection => None,
39 Self::InSection(kind) => Some(*kind),
40 Self::InEvent { section, .. } => Some(*section),
41 }
42 }
43
44 pub fn enter_section(&mut self, kind: SectionKind) {
46 *self = Self::InSection(kind);
47 }
48
49 pub fn enter_event(&mut self, section: SectionKind) {
51 *self = Self::InEvent {
52 section,
53 fields_seen: 0,
54 };
55 }
56
57 pub fn exit_section(&mut self) {
59 *self = Self::ExpectingSection;
60 }
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub enum SectionKind {
69 ScriptInfo,
71 Styles,
73 Events,
75 Fonts,
77 Graphics,
79 Unknown,
81}
82
83impl SectionKind {
84 #[must_use]
98 pub fn from_header(header: &str) -> Self {
99 match header.trim() {
100 "Script Info" => Self::ScriptInfo,
101 "V4+ Styles" | "V4 Styles" => Self::Styles,
102 "Events" => Self::Events,
103 "Fonts" => Self::Fonts,
104 "Graphics" => Self::Graphics,
105 _ => Self::Unknown,
106 }
107 }
108
109 #[must_use]
111 pub const fn expects_format(&self) -> bool {
112 matches!(self, Self::Styles | Self::Events)
113 }
114
115 #[must_use]
117 pub const fn is_timed(&self) -> bool {
118 matches!(self, Self::Events)
119 }
120
121 #[must_use]
123 pub const fn is_binary(&self) -> bool {
124 matches!(self, Self::Fonts | Self::Graphics)
125 }
126}
127
128#[derive(Debug, Clone)]
133pub struct StreamingContext {
134 pub line_number: usize,
136 pub current_section: Option<SectionKind>,
138 pub events_format: Option<String>,
140 pub styles_format: Option<String>,
142}
143
144impl StreamingContext {
145 #[must_use]
147 pub const fn new() -> Self {
148 Self {
149 line_number: 0,
150 current_section: None,
151 events_format: None,
152 styles_format: None,
153 }
154 }
155
156 pub fn next_line(&mut self) {
158 self.line_number += 1;
159 }
160
161 pub fn enter_section(&mut self, kind: SectionKind) {
163 self.current_section = Some(kind);
164 }
165
166 pub fn exit_section(&mut self) {
168 self.current_section = None;
169 }
170
171 pub fn set_events_format(&mut self, format: String) {
173 self.events_format = Some(format);
174 }
175
176 pub fn set_styles_format(&mut self, format: String) {
178 self.styles_format = Some(format);
179 }
180
181 pub fn reset(&mut self) {
183 self.line_number = 0;
184 self.current_section = None;
185 self.events_format = None;
186 self.styles_format = None;
187 }
188}
189
190impl Default for StreamingContext {
191 fn default() -> Self {
192 Self::new()
193 }
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199 #[cfg(not(feature = "std"))]
200 use alloc::{format, string::ToString};
201
202 #[test]
203 fn parser_state_transitions() {
204 let mut state = ParserState::ExpectingSection;
205 assert!(!state.is_in_section());
206 assert_eq!(state.current_section(), None);
207
208 state.enter_section(SectionKind::Events);
209 assert!(state.is_in_section());
210 assert_eq!(state.current_section(), Some(SectionKind::Events));
211
212 state.enter_event(SectionKind::Events);
213 assert!(state.is_in_section());
214 assert_eq!(state.current_section(), Some(SectionKind::Events));
215
216 state.exit_section();
217 assert!(!state.is_in_section());
218 assert_eq!(state.current_section(), None);
219 }
220
221 #[test]
222 fn section_kind_from_header() {
223 assert_eq!(
224 SectionKind::from_header("Script Info"),
225 SectionKind::ScriptInfo
226 );
227 assert_eq!(SectionKind::from_header("V4+ Styles"), SectionKind::Styles);
228 assert_eq!(SectionKind::from_header("V4 Styles"), SectionKind::Styles);
229 assert_eq!(SectionKind::from_header("Events"), SectionKind::Events);
230 assert_eq!(SectionKind::from_header("Fonts"), SectionKind::Fonts);
231 assert_eq!(SectionKind::from_header("Graphics"), SectionKind::Graphics);
232 assert_eq!(
233 SectionKind::from_header("Unknown Section"),
234 SectionKind::Unknown
235 );
236 }
237
238 #[test]
239 fn section_kind_properties() {
240 assert!(SectionKind::Styles.expects_format());
241 assert!(SectionKind::Events.expects_format());
242 assert!(!SectionKind::ScriptInfo.expects_format());
243
244 assert!(SectionKind::Events.is_timed());
245 assert!(!SectionKind::Styles.is_timed());
246
247 assert!(SectionKind::Fonts.is_binary());
248 assert!(SectionKind::Graphics.is_binary());
249 assert!(!SectionKind::Events.is_binary());
250 }
251
252 #[test]
253 fn streaming_context_operations() {
254 let mut context = StreamingContext::new();
255 assert_eq!(context.line_number, 0);
256 assert_eq!(context.current_section, None);
257
258 context.next_line();
259 assert_eq!(context.line_number, 1);
260
261 context.enter_section(SectionKind::Events);
262 assert_eq!(context.current_section, Some(SectionKind::Events));
263
264 context.set_events_format("Layer,Start,End,Text".to_string());
265 assert!(context.events_format.is_some());
266
267 context.reset();
268 assert_eq!(context.line_number, 0);
269 assert_eq!(context.current_section, None);
270 assert!(context.events_format.is_none());
271 }
272
273 #[test]
274 fn parser_state_debug_and_clone() {
275 let state = ParserState::ExpectingSection;
276 let debug_str = format!("{state:?}");
277 assert!(debug_str.contains("ExpectingSection"));
278
279 let cloned = state.clone();
280 assert_eq!(state, cloned);
281
282 let section_state = ParserState::InSection(SectionKind::Events);
283 let section_debug = format!("{section_state:?}");
284 assert!(section_debug.contains("InSection"));
285 assert!(section_debug.contains("Events"));
286
287 let event_state = ParserState::InEvent {
288 section: SectionKind::Events,
289 fields_seen: 3,
290 };
291 let event_debug = format!("{event_state:?}");
292 assert!(event_debug.contains("InEvent"));
293 assert!(event_debug.contains("fields_seen"));
294 }
295
296 #[test]
297 fn parser_state_equality() {
298 let state1 = ParserState::ExpectingSection;
299 let state2 = ParserState::ExpectingSection;
300 assert_eq!(state1, state2);
301
302 let state3 = ParserState::InSection(SectionKind::Events);
303 let state4 = ParserState::InSection(SectionKind::Events);
304 assert_eq!(state3, state4);
305
306 let state5 = ParserState::InEvent {
307 section: SectionKind::Events,
308 fields_seen: 2,
309 };
310 let state6 = ParserState::InEvent {
311 section: SectionKind::Events,
312 fields_seen: 2,
313 };
314 assert_eq!(state5, state6);
315
316 assert_ne!(state1, state3);
318 assert_ne!(state3, state5);
319
320 let state7 = ParserState::InEvent {
321 section: SectionKind::Events,
322 fields_seen: 3,
323 };
324 assert_ne!(state5, state7);
325 }
326
327 #[test]
328 fn parser_state_all_variants() {
329 let expecting = ParserState::ExpectingSection;
331 assert!(!expecting.is_in_section());
332 assert_eq!(expecting.current_section(), None);
333
334 for &kind in &[
336 SectionKind::ScriptInfo,
337 SectionKind::Styles,
338 SectionKind::Events,
339 SectionKind::Fonts,
340 SectionKind::Graphics,
341 SectionKind::Unknown,
342 ] {
343 let in_section = ParserState::InSection(kind);
344 assert!(in_section.is_in_section());
345 assert_eq!(in_section.current_section(), Some(kind));
346 }
347
348 let in_event = ParserState::InEvent {
350 section: SectionKind::Events,
351 fields_seen: 5,
352 };
353 assert!(in_event.is_in_section());
354 assert_eq!(in_event.current_section(), Some(SectionKind::Events));
355 }
356
357 #[test]
358 fn section_kind_all_variants() {
359 let kinds = [
360 SectionKind::ScriptInfo,
361 SectionKind::Styles,
362 SectionKind::Events,
363 SectionKind::Fonts,
364 SectionKind::Graphics,
365 SectionKind::Unknown,
366 ];
367
368 for &kind in &kinds {
369 let debug_str = format!("{kind:?}");
370 assert!(!debug_str.is_empty());
371
372 let copied = kind;
374 assert_eq!(kind, copied);
375 }
376 }
377
378 #[test]
379 fn section_kind_header_parsing_edge_cases() {
380 assert_eq!(
382 SectionKind::from_header(" Script Info "),
383 SectionKind::ScriptInfo
384 );
385 assert_eq!(
386 SectionKind::from_header("\tV4+ Styles\t"),
387 SectionKind::Styles
388 );
389
390 assert_eq!(SectionKind::from_header(""), SectionKind::Unknown);
392 assert_eq!(SectionKind::from_header(" "), SectionKind::Unknown);
393
394 assert_eq!(SectionKind::from_header("Script"), SectionKind::Unknown);
396 assert_eq!(SectionKind::from_header("Info"), SectionKind::Unknown);
397 assert_eq!(SectionKind::from_header("Styles"), SectionKind::Unknown);
398
399 assert_eq!(SectionKind::from_header("V4 Styles"), SectionKind::Styles);
401 assert_eq!(SectionKind::from_header("V4+ Styles"), SectionKind::Styles);
402 }
403
404 #[test]
405 fn section_kind_all_properties() {
406 assert!(SectionKind::Styles.expects_format());
408 assert!(SectionKind::Events.expects_format());
409 assert!(!SectionKind::ScriptInfo.expects_format());
410 assert!(!SectionKind::Fonts.expects_format());
411 assert!(!SectionKind::Graphics.expects_format());
412 assert!(!SectionKind::Unknown.expects_format());
413
414 assert!(SectionKind::Events.is_timed());
416 assert!(!SectionKind::ScriptInfo.is_timed());
417 assert!(!SectionKind::Styles.is_timed());
418 assert!(!SectionKind::Fonts.is_timed());
419 assert!(!SectionKind::Graphics.is_timed());
420 assert!(!SectionKind::Unknown.is_timed());
421
422 assert!(SectionKind::Fonts.is_binary());
424 assert!(SectionKind::Graphics.is_binary());
425 assert!(!SectionKind::ScriptInfo.is_binary());
426 assert!(!SectionKind::Styles.is_binary());
427 assert!(!SectionKind::Events.is_binary());
428 assert!(!SectionKind::Unknown.is_binary());
429 }
430
431 #[test]
432 fn streaming_context_default() {
433 let context = StreamingContext::default();
434 assert_eq!(context.line_number, 0);
435 assert_eq!(context.current_section, None);
436 assert!(context.events_format.is_none());
437 assert!(context.styles_format.is_none());
438 }
439
440 #[test]
441 fn streaming_context_debug_and_clone() {
442 let context = StreamingContext::new();
443 let debug_str = format!("{context:?}");
444 assert!(debug_str.contains("StreamingContext"));
445 assert!(debug_str.contains("line_number"));
446
447 let mut context_with_data = StreamingContext::new();
448 context_with_data.next_line();
449 context_with_data.enter_section(SectionKind::Events);
450 context_with_data.set_events_format("Test Format".to_string());
451
452 let cloned = context_with_data.clone();
453 assert_eq!(cloned.line_number, context_with_data.line_number);
454 assert_eq!(cloned.current_section, context_with_data.current_section);
455 assert_eq!(cloned.events_format, context_with_data.events_format);
456 }
457
458 #[test]
459 fn streaming_context_format_management() {
460 let mut context = StreamingContext::new();
461
462 assert!(context.events_format.is_none());
464 context.set_events_format("Layer, Start, End, Style, Text".to_string());
465 assert!(context.events_format.is_some());
466 assert_eq!(
467 context.events_format.as_ref().unwrap(),
468 "Layer, Start, End, Style, Text"
469 );
470
471 assert!(context.styles_format.is_none());
473 context.set_styles_format("Name, Fontname, Fontsize".to_string());
474 assert!(context.styles_format.is_some());
475 assert_eq!(
476 context.styles_format.as_ref().unwrap(),
477 "Name, Fontname, Fontsize"
478 );
479
480 context.reset();
482 assert!(context.events_format.is_none());
483 assert!(context.styles_format.is_none());
484 }
485
486 #[test]
487 fn streaming_context_section_management() {
488 let mut context = StreamingContext::new();
489 assert_eq!(context.current_section, None);
490
491 context.enter_section(SectionKind::ScriptInfo);
492 assert_eq!(context.current_section, Some(SectionKind::ScriptInfo));
493
494 context.enter_section(SectionKind::Events);
495 assert_eq!(context.current_section, Some(SectionKind::Events));
496
497 context.exit_section();
498 assert_eq!(context.current_section, None);
499 }
500
501 #[test]
502 fn streaming_context_line_tracking() {
503 let mut context = StreamingContext::new();
504 assert_eq!(context.line_number, 0);
505
506 for expected_line in 1..=100 {
507 context.next_line();
508 assert_eq!(context.line_number, expected_line);
509 }
510
511 context.reset();
512 assert_eq!(context.line_number, 0);
513 }
514
515 #[test]
516 fn parser_state_transition_sequences() {
517 let mut state = ParserState::ExpectingSection;
518
519 state.enter_section(SectionKind::Events);
521 assert!(state.is_in_section());
522 assert_eq!(state.current_section(), Some(SectionKind::Events));
523
524 state.enter_event(SectionKind::Events);
525 assert!(state.is_in_section());
526 assert_eq!(state.current_section(), Some(SectionKind::Events));
527
528 state.exit_section();
529 assert!(!state.is_in_section());
530 assert_eq!(state.current_section(), None);
531
532 state.enter_event(SectionKind::Styles);
534 assert!(state.is_in_section());
535 assert_eq!(state.current_section(), Some(SectionKind::Styles));
536 }
537
538 #[test]
539 fn complex_state_context_interaction() {
540 let mut state = ParserState::ExpectingSection;
541 let mut context = StreamingContext::new();
542
543 context.next_line(); state.enter_section(SectionKind::ScriptInfo);
546 context.enter_section(SectionKind::ScriptInfo);
547
548 context.next_line(); context.next_line(); state.enter_section(SectionKind::Events);
552 context.enter_section(SectionKind::Events);
553 context.set_events_format("Layer, Start, End, Text".to_string());
554
555 context.next_line(); state.enter_event(SectionKind::Events);
557
558 assert_eq!(context.line_number, 4);
559 assert!(context.events_format.is_some());
560 assert_eq!(context.current_section, Some(SectionKind::Events));
561 assert_eq!(state.current_section(), Some(SectionKind::Events));
562 }
563}