use winnow::{ModalParser, ModalResult, Parser, error::StrContext};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TimeCode {
pub hours: u32,
pub minutes: u32,
pub seconds: u32,
pub frames: u32,
pub drop_frame: bool,
}
pub fn digits(s: &mut &[u8]) -> ModalResult<u32> {
use winnow::stream::AsChar;
use winnow::token::take_while;
take_while(0.., AsChar::is_dec_digit)
.try_map(std::str::from_utf8)
.try_map(|s: &str| s.parse::<u32>())
.parse_next(s)
}
pub fn digits_range<'a, R: std::ops::RangeBounds<u32>>(
range: R,
) -> impl ModalParser<&'a [u8], u32, winnow::error::ContextError> {
move |s: &mut &'a [u8]| {
digits
.verify(|v| range.contains(v))
.context(StrContext::Label("digits out of range"))
.parse_next(s)
}
}
pub fn timecode(s: &mut &[u8]) -> ModalResult<TimeCode> {
use winnow::token::one_of;
(
digits,
one_of(':'),
digits_range(0..60),
one_of(':'),
digits_range(0..60),
one_of(|c| b":.;,".contains(&c)),
digits,
)
.map(|(hours, _, minutes, _, seconds, sep, frames)| TimeCode {
hours,
minutes,
seconds,
frames,
drop_frame: sep == b';' || sep == b',',
})
.context(StrContext::Label("invalid timecode"))
.parse_next(s)
}
pub fn end_of_line(s: &mut &[u8]) -> ModalResult<()> {
use winnow::combinator::alt;
use winnow::combinator::{eof, opt};
use winnow::token::literal;
(opt(alt((literal("\r\n"), literal("\n")))), eof)
.map(|_| ())
.parse_next(s)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_timecode() {
let mut input = b"11:12:13;14".as_slice();
assert_eq!(
timecode(&mut input),
Ok(TimeCode {
hours: 11,
minutes: 12,
seconds: 13,
frames: 14,
drop_frame: true
})
);
assert!(input.is_empty());
let mut input = b"11:12:13:14".as_slice();
assert_eq!(
timecode(&mut input),
Ok(TimeCode {
hours: 11,
minutes: 12,
seconds: 13,
frames: 14,
drop_frame: false
})
);
assert!(input.is_empty());
let mut input = b"11:12:13:14abcd".as_slice();
assert_eq!(
timecode(&mut input),
Ok(TimeCode {
hours: 11,
minutes: 12,
seconds: 13,
frames: 14,
drop_frame: false
})
);
assert_eq!(input, b"abcd");
let mut input = b"abcd11:12:13:14".as_slice();
assert!(timecode(&mut input).is_err());
}
}