use core::{str::FromStr, time::Duration};
use derive_more::{Display, From, Into, IsVariant, TryUnwrap, Unwrap};
use crate::{
error::{
ParseCentisecondError, ParseHourError, ParseMinuteError, ParseSecondError, TimestampError,
},
types::{Buffer, Centisecond, Minute, Second},
utils::u64_digits,
};
use super::{Options, ParseAssError};
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, From, Into)]
#[repr(transparent)]
pub struct Hour(pub(crate) u64);
impl FromStr for Hour {
type Err = ParseHourError;
#[cfg_attr(not(tarpaulin), inline(always))]
fn from_str(s: &str) -> Result<Self, Self::Err> {
parse_hour_bytes(s.as_bytes())
}
}
impl Hour {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new() -> Self {
Self(0)
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn with(value: u64) -> Self {
Self(value)
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn as_u64(&self) -> u64 {
self.0
}
}
impl core::fmt::Display for Hour {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.0)
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
fn parse_hour_bytes(bytes: &[u8]) -> Result<Hour, ParseHourError> {
if bytes.is_empty() {
return Err(ParseHourError::NotPadded);
}
let mut value: u64 = 0;
for &byte in bytes {
if !byte.is_ascii_digit() {
return Err(ParseHourError::NotPadded);
}
value = value
.checked_mul(10)
.and_then(|v| v.checked_add((byte - b'0') as u64))
.ok_or(ParseHourError::HourOverflow)?;
}
Ok(Hour(value))
}
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[display("{}", self.encode().as_str())]
pub struct Timestamp {
hours: Hour,
minutes: Minute,
seconds: Second,
centis: Centisecond,
}
impl Default for Timestamp {
#[cfg_attr(not(tarpaulin), inline(always))]
fn default() -> Self {
Self::new()
}
}
impl From<Duration> for Timestamp {
#[cfg_attr(not(tarpaulin), inline(always))]
fn from(value: Duration) -> Self {
Self::from_duration(value)
}
}
impl FromStr for Timestamp {
type Err = ParseAssError;
#[cfg_attr(not(tarpaulin), inline(always))]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse(s)
}
}
impl Timestamp {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new() -> Self {
Self::from_hmsc(
Hour::new(),
Minute::new(),
Second::new(),
Centisecond::new(),
)
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn from_hmsc(
hours: Hour,
minutes: Minute,
seconds: Second,
centis: Centisecond,
) -> Self {
Self {
hours,
minutes,
seconds,
centis,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn hours(&self) -> Hour {
self.hours
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn minutes(&self) -> Minute {
self.minutes
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn seconds(&self) -> Second {
self.seconds
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn centis(&self) -> Centisecond {
self.centis
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn with_hours(mut self, hours: Hour) -> Self {
self.hours = hours;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn with_minutes(mut self, minutes: Minute) -> Self {
self.minutes = minutes;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn with_seconds(mut self, seconds: Second) -> Self {
self.seconds = seconds;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn with_centis(mut self, centis: Centisecond) -> Self {
self.centis = centis;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_hours(&mut self, hours: Hour) -> &mut Self {
self.hours = hours;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_minutes(&mut self, minutes: Minute) -> &mut Self {
self.minutes = minutes;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_seconds(&mut self, seconds: Second) -> &mut Self {
self.seconds = seconds;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_centis(&mut self, centis: Centisecond) -> &mut Self {
self.centis = centis;
self
}
pub fn parse(s: &str) -> Result<Self, ParseAssError> {
let bytes = s.as_bytes();
let len = bytes.len();
if len < 10 {
return Err(ParseAssError::InvalidTimestamp(
TimestampError::InvalidLength,
));
}
if bytes[len - 3] != b'.' || bytes[len - 6] != b':' || bytes[len - 9] != b':' {
return Err(ParseAssError::InvalidTimestamp(
TimestampError::InvalidFormat,
));
}
let centis_val = two_digits(&bytes[len - 2..]).ok_or(ParseAssError::InvalidTimestamp(
TimestampError::InvalidDigits,
))?;
let seconds_val = two_digits(&bytes[len - 5..len - 3]).ok_or(
ParseAssError::InvalidTimestamp(TimestampError::InvalidDigits),
)?;
let minutes_val = two_digits(&bytes[len - 8..len - 6]).ok_or(
ParseAssError::InvalidTimestamp(TimestampError::InvalidDigits),
)?;
let centis = Centisecond::try_with(centis_val).ok_or(ParseAssError::ParseCentisecond(
ParseCentisecondError::Overflow(centis_val),
))?;
let seconds = Second::try_with(seconds_val).ok_or(ParseAssError::ParseSecond(
ParseSecondError::Overflow(seconds_val),
))?;
let minutes = Minute::try_with(minutes_val).ok_or(ParseAssError::ParseMinute(
ParseMinuteError::Overflow(minutes_val),
))?;
let hours = parse_hour_bytes(&bytes[..len - 9])?;
Ok(Self::from_hmsc(hours, minutes, seconds, centis))
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn to_duration(&self) -> Duration {
let secs = self
.hours
.0
.saturating_mul(3_600)
.saturating_add(self.minutes.0 as u64 * 60)
.saturating_add(self.seconds.0 as u64);
Duration::new(secs, self.centis.0 as u32 * 10_000_000)
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn from_duration(dur: Duration) -> Self {
let total_secs = dur.as_secs();
Self {
hours: Hour::with(total_secs / 3_600),
minutes: Minute::with(((total_secs % 3_600) / 60) as u8),
seconds: Second::with((total_secs % 60) as u8),
centis: Centisecond::with((dur.subsec_millis() / 10) as u8),
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn encoded_len(&self) -> usize {
u64_digits(self.hours.0) + 9
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn encode(&self) -> Buffer<30> {
let mut buffer = Buffer::new();
buffer.fmt_u64(self.hours.0);
buffer.write_str(":");
buffer.write_str(self.minutes.as_str());
buffer.write_str(":");
buffer.write_str(self.seconds.as_str());
buffer.write_str(".");
buffer.write_str(self.centis.as_str());
buffer
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
fn two_digits(bytes: &[u8]) -> Option<u8> {
let high = bytes[0].wrapping_sub(b'0');
let low = bytes[1].wrapping_sub(b'0');
if high > 9 || low > 9 {
return None;
}
Some(high * 10 + low)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, IsVariant, Unwrap, TryUnwrap)]
#[unwrap(ref, ref_mut)]
#[try_unwrap(ref, ref_mut)]
pub enum Section<'a> {
ScriptInfo,
V4Styles,
V4PlusStyles,
Events,
Fonts,
Graphics,
Other(&'a str),
}
impl<'a> Section<'a> {
pub fn new(name: &'a str) -> Self {
if name.eq_ignore_ascii_case("Script Info") {
Self::ScriptInfo
} else if name.eq_ignore_ascii_case("V4 Styles") {
Self::V4Styles
} else if name.eq_ignore_ascii_case("V4+ Styles") {
Self::V4PlusStyles
} else if name.eq_ignore_ascii_case("Events") {
Self::Events
} else if name.eq_ignore_ascii_case("Fonts") {
Self::Fonts
} else if name.eq_ignore_ascii_case("Graphics") {
Self::Graphics
} else {
Self::Other(name)
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn as_str(&self) -> &'a str {
match self {
Self::ScriptInfo => "Script Info",
Self::V4Styles => "V4 Styles",
Self::V4PlusStyles => "V4+ Styles",
Self::Events => "Events",
Self::Fonts => "Fonts",
Self::Graphics => "Graphics",
Self::Other(name) => name,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn is_resource(&self) -> bool {
matches!(self, Self::Fonts | Self::Graphics)
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn is_styles(&self) -> bool {
match self {
Self::V4Styles | Self::V4PlusStyles => true,
Self::Other(name) => {
let bytes = name.trim_end().as_bytes();
bytes.len() >= 6 && bytes[bytes.len() - 6..].eq_ignore_ascii_case(b"Styles")
}
_ => false,
}
}
}
impl core::fmt::Display for Section<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "[{}]", self.as_str())
}
}
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, Hash, IsVariant)]
pub enum EventKind {
#[display("Dialogue")]
Dialogue,
#[display("Comment")]
Comment,
#[display("Picture")]
Picture,
#[display("Sound")]
Sound,
#[display("Movie")]
Movie,
#[display("Command")]
Command,
}
impl EventKind {
pub fn new(keyword: &str) -> Option<Self> {
if keyword.eq_ignore_ascii_case("Dialogue") {
Some(Self::Dialogue)
} else if keyword.eq_ignore_ascii_case("Comment") {
Some(Self::Comment)
} else if keyword.eq_ignore_ascii_case("Picture") {
Some(Self::Picture)
} else if keyword.eq_ignore_ascii_case("Sound") {
Some(Self::Sound)
} else if keyword.eq_ignore_ascii_case("Movie") {
Some(Self::Movie)
} else if keyword.eq_ignore_ascii_case("Command") {
Some(Self::Command)
} else {
None
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn as_str(&self) -> &'static str {
match self {
Self::Dialogue => "Dialogue",
Self::Comment => "Comment",
Self::Picture => "Picture",
Self::Sound => "Sound",
Self::Movie => "Movie",
Self::Command => "Command",
}
}
}
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, Hash, IsVariant)]
pub enum EventField {
#[display("ReadOrder")]
ReadOrder,
#[display("Marked")]
Marked,
#[display("Layer")]
Layer,
#[display("Start")]
Start,
#[display("End")]
End,
#[display("Style")]
Style,
#[display("Name")]
Name,
#[display("MarginL")]
MarginL,
#[display("MarginR")]
MarginR,
#[display("MarginV")]
MarginV,
#[display("Effect")]
Effect,
#[display("Text")]
Text,
}
impl EventField {
const ALL: [Self; 12] = [
Self::ReadOrder,
Self::Marked,
Self::Layer,
Self::Start,
Self::End,
Self::Style,
Self::Name,
Self::MarginL,
Self::MarginR,
Self::MarginV,
Self::Effect,
Self::Text,
];
pub fn new(name: &str) -> Option<Self> {
if name.eq_ignore_ascii_case("Name") || name.eq_ignore_ascii_case("Actor") {
return Some(Self::Name);
}
Self::ALL
.into_iter()
.find(|field| name.eq_ignore_ascii_case(field.as_str()))
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn as_str(&self) -> &'static str {
match self {
Self::ReadOrder => "ReadOrder",
Self::Marked => "Marked",
Self::Layer => "Layer",
Self::Start => "Start",
Self::End => "End",
Self::Style => "Style",
Self::Name => "Name",
Self::MarginL => "MarginL",
Self::MarginR => "MarginR",
Self::MarginV => "MarginV",
Self::Effect => "Effect",
Self::Text => "Text",
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
const fn slot(&self) -> usize {
match self {
Self::ReadOrder => 0,
Self::Marked => 1,
Self::Layer => 2,
Self::Start => 3,
Self::End => 4,
Self::Style => 5,
Self::Name => 6,
Self::MarginL => 7,
Self::MarginR => 8,
Self::MarginV => 9,
Self::Effect => 10,
Self::Text => 11,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct EventFormat {
slots: [Option<u8>; 12],
fields: u8,
}
impl EventFormat {
pub const MAX_FIELDS: usize = u8::MAX as usize;
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn empty() -> Self {
Self {
slots: [None; 12],
fields: 0,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn ass() -> Self {
Self::from_order(&[
EventField::Layer,
EventField::Start,
EventField::End,
EventField::Style,
EventField::Name,
EventField::MarginL,
EventField::MarginR,
EventField::MarginV,
EventField::Effect,
EventField::Text,
])
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn ssa() -> Self {
Self::from_order(&[
EventField::Marked,
EventField::Start,
EventField::End,
EventField::Style,
EventField::Name,
EventField::MarginL,
EventField::MarginR,
EventField::MarginV,
EventField::Effect,
EventField::Text,
])
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn matroska() -> Self {
Self::from_order(&[
EventField::ReadOrder,
EventField::Layer,
EventField::Style,
EventField::Name,
EventField::MarginL,
EventField::MarginR,
EventField::MarginV,
EventField::Effect,
EventField::Text,
])
}
#[cfg_attr(not(tarpaulin), inline(always))]
const fn from_order(order: &[EventField]) -> Self {
let mut slots = [None; 12];
let mut i = 0;
while i < order.len() {
slots[order[i].slot()] = Some(i as u8);
i += 1;
}
Self {
slots,
fields: order.len() as u8,
}
}
pub fn new(declaration: &str) -> Self {
if declaration.trim().is_empty() {
return Self::empty();
}
let mut slots: [Option<u8>; 12] = [None; 12];
let mut fields = 0usize;
for name in declaration.split(',') {
if fields >= Self::MAX_FIELDS {
break;
}
if let Some(field) = EventField::new(name.trim()) {
slots[field.slot()] = Some(fields as u8);
}
fields += 1;
}
Self {
slots,
fields: fields as u8,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn len(&self) -> usize {
self.fields as usize
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn is_empty(&self) -> bool {
self.fields == 0
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn index_of(&self, field: EventField) -> Option<usize> {
match self.slots[field.slot()] {
Some(index) => Some(index as usize),
None => None,
}
}
pub const fn field_at(&self, index: usize) -> Option<EventField> {
if index > u8::MAX as usize {
return None;
}
let mut slot = 0;
while slot < 12 {
if let Some(declared) = self.slots[slot]
&& declared as usize == index
{
return Some(EventField::ALL[slot]);
}
slot += 1;
}
None
}
}
impl Default for EventFormat {
#[cfg_attr(not(tarpaulin), inline(always))]
fn default() -> Self {
Self::ass()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Event<'a> {
kind: EventKind,
read_order: Option<u32>,
marked: Option<&'a str>,
layer: Option<i32>,
start: Option<Timestamp>,
end: Option<Timestamp>,
style: Option<&'a str>,
name: Option<&'a str>,
margin_l: Option<u32>,
margin_r: Option<u32>,
margin_v: Option<u32>,
effect: Option<&'a str>,
text: &'a str,
row: &'a str,
columns: u8,
}
macro_rules! event_accessors {
($(
$(#[$meta:meta])*
$field:ident: $ty:ty, $with:ident, $set:ident;
)*) => {
$(
$(#[$meta])*
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn $field(&self) -> Option<$ty> {
self.$field
}
#[doc = concat!("Sets the `", stringify!($field), "` field (builder pattern).")]
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn $with(mut self, value: Option<$ty>) -> Self {
self.$field = value;
self
}
#[doc = concat!("Sets the `", stringify!($field), "` field.")]
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn $set(&mut self, value: Option<$ty>) -> &mut Self {
self.$field = value;
self
}
)*
};
}
impl<'a> Event<'a> {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(kind: EventKind, text: &'a str) -> Self {
Self {
kind,
read_order: None,
marked: None,
layer: None,
start: None,
end: None,
style: None,
name: None,
margin_l: None,
margin_r: None,
margin_v: None,
effect: None,
text,
row: "",
columns: 0,
}
}
pub fn field(&self, index: usize) -> Option<&'a str> {
if self.columns == 0 {
return None;
}
self.row.splitn(self.columns as usize, ',').nth(index)
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn kind(&self) -> EventKind {
self.kind
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn with_kind(mut self, kind: EventKind) -> Self {
self.kind = kind;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_kind(&mut self, kind: EventKind) -> &mut Self {
self.kind = kind;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn text(&self) -> &'a str {
self.text
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn with_text(mut self, text: &'a str) -> Self {
self.text = text;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_text(&mut self, text: &'a str) -> &mut Self {
self.text = text;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn plain_text(&self) -> super::text::PlainText<'a> {
super::text::PlainText::new(self.text)
}
event_accessors! {
read_order: u32, with_read_order, set_read_order;
marked: &'a str, with_marked, set_marked;
layer: i32, with_layer, set_layer;
start: Timestamp, with_start, set_start;
end: Timestamp, with_end, set_end;
style: &'a str, with_style, set_style;
name: &'a str, with_name, set_name;
margin_l: u32, with_margin_l, set_margin_l;
margin_r: u32, with_margin_r, set_margin_r;
margin_v: u32, with_margin_v, set_margin_v;
effect: &'a str, with_effect, set_effect;
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn parse(line: &'a str, format: &EventFormat) -> Result<Self, ParseAssError> {
Self::parse_with(line, format, &Options::strict())
}
pub fn parse_with(
line: &'a str,
format: &EventFormat,
options: &Options,
) -> Result<Self, ParseAssError> {
let (keyword, rest) = line.split_once(':').ok_or(ParseAssError::UnexpectedLine)?;
let kind = EventKind::new(keyword.trim()).ok_or(ParseAssError::UnexpectedLine)?;
Self::parse_fields_with(kind, rest.trim_start_matches([' ', '\t']), format, options)
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn parse_fields(
kind: EventKind,
fields: &'a str,
format: &EventFormat,
) -> Result<Self, ParseAssError> {
Self::parse_fields_with(kind, fields, format, &Options::strict())
}
pub fn parse_fields_with(
kind: EventKind,
fields: &'a str,
format: &EventFormat,
options: &Options,
) -> Result<Self, ParseAssError> {
let declared = format.len();
if declared == 0 {
return Err(ParseAssError::MissingFormat);
}
let mut event = Self::new(kind, "");
event.row = fields;
event.columns = declared as u8;
let mut found = 0usize;
for (index, value) in fields.splitn(declared, ',').enumerate() {
found = index + 1;
let Some(field) = format.field_at(index) else {
continue;
};
if matches!(field, EventField::Text) {
event.text = value;
continue;
}
let value = value.trim();
if value.is_empty() {
continue;
}
match field {
EventField::ReadOrder => {
event.read_order = parse_number(value, field, options)?;
}
EventField::Marked => event.marked = Some(value),
EventField::Layer => {
event.layer = parse_number(value, field, options)?;
}
EventField::Start => {
event.start = parse_timestamp(value, options)?;
}
EventField::End => {
event.end = parse_timestamp(value, options)?;
}
EventField::Style => event.style = Some(value),
EventField::Name => event.name = Some(value),
EventField::MarginL => {
event.margin_l = parse_number(value, field, options)?;
}
EventField::MarginR => {
event.margin_r = parse_number(value, field, options)?;
}
EventField::MarginV => {
event.margin_v = parse_number(value, field, options)?;
}
EventField::Effect => event.effect = Some(value),
EventField::Text => unreachable!("handled above"),
}
}
if found < declared && !options.allow_short_event() {
return Err(ParseAssError::TooFewFields {
expected: declared,
found,
});
}
Ok(event)
}
}
fn parse_number<T: FromStr>(
value: &str,
field: EventField,
options: &Options,
) -> Result<Option<T>, ParseAssError> {
match value.parse::<T>() {
Ok(parsed) => Ok(Some(parsed)),
Err(_) if options.allow_malformed_fields() => Ok(None),
Err(_) => Err(ParseAssError::InvalidField(field)),
}
}
fn parse_timestamp(value: &str, options: &Options) -> Result<Option<Timestamp>, ParseAssError> {
match Timestamp::parse(value) {
Ok(parsed) => Ok(Some(parsed)),
Err(_) if options.allow_malformed_fields() => Ok(None),
Err(err) => Err(err),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Property<'a> {
key: &'a str,
value: &'a str,
}
impl<'a> Property<'a> {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(key: &'a str, value: &'a str) -> Self {
Self { key, value }
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn key(&self) -> &'a str {
self.key
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn value(&self) -> &'a str {
self.value
}
}
impl core::fmt::Display for Property<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}: {}", self.key, self.value)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Format<'a> {
raw: &'a str,
}
impl<'a> Format<'a> {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(raw: &'a str) -> Self {
Self { raw }
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn as_str(&self) -> &'a str {
self.raw
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn fields(&self) -> Fields<'a> {
Fields {
inner: self.raw.split(','),
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn event_format(&self) -> EventFormat {
EventFormat::new(self.raw)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct StyleRow<'a> {
raw: &'a str,
}
impl<'a> StyleRow<'a> {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(raw: &'a str) -> Self {
Self { raw }
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn as_str(&self) -> &'a str {
self.raw
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn fields(&self) -> Fields<'a> {
Fields {
inner: self.raw.split(','),
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn field(&self, index: usize) -> Option<&'a str> {
self.fields().nth(index)
}
}
#[derive(Debug, Clone)]
pub struct Fields<'a> {
inner: core::str::Split<'a, char>,
}
impl<'a> Iterator for Fields<'a> {
type Item = &'a str;
#[cfg_attr(not(tarpaulin), inline(always))]
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(str::trim)
}
}
impl DoubleEndedIterator for Fields<'_> {
#[cfg_attr(not(tarpaulin), inline(always))]
fn next_back(&mut self) -> Option<Self::Item> {
self.inner.next_back().map(str::trim)
}
}
#[derive(Debug, Clone, PartialEq, Eq, IsVariant, Unwrap, TryUnwrap)]
#[unwrap(ref, ref_mut)]
#[try_unwrap(ref, ref_mut)]
pub enum Block<'a> {
Section(Section<'a>),
Comment(&'a str),
Format(Format<'a>),
Style(StyleRow<'a>),
Event(Event<'a>),
Data(&'a str),
Property(Property<'a>),
}