use std::borrow::Cow;
use nom::{
bytes::complete::tag,
character::complete::{char, digit1, space0},
combinator::recognize,
sequence::separated_pair,
IResult,
};
use crate::elements::timestamp::{parse_inactive, Datetime, Timestamp};
use crate::parse::combinators::{blank_lines_count, eol};
#[cfg_attr(test, derive(PartialEq))]
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
#[cfg_attr(feature = "ser", serde(untagged))]
#[derive(Debug, Clone)]
pub enum Clock<'a> {
Closed {
start: Datetime<'a>,
end: Datetime<'a>,
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
repeater: Option<Cow<'a, str>>,
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
delay: Option<Cow<'a, str>>,
duration: Cow<'a, str>,
post_blank: usize,
},
Running {
start: Datetime<'a>,
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
repeater: Option<Cow<'a, str>>,
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
delay: Option<Cow<'a, str>>,
post_blank: usize,
},
}
impl Clock<'_> {
pub(crate) fn parse(input: &str) -> Option<(&str, Clock)> {
parse_internal(input).ok()
}
pub fn into_onwed(self) -> Clock<'static> {
match self {
Clock::Closed {
start,
end,
repeater,
delay,
duration,
post_blank,
} => Clock::Closed {
start: start.into_owned(),
end: end.into_owned(),
repeater: repeater.map(Into::into).map(Cow::Owned),
delay: delay.map(Into::into).map(Cow::Owned),
duration: duration.into_owned().into(),
post_blank,
},
Clock::Running {
start,
repeater,
delay,
post_blank,
} => Clock::Running {
start: start.into_owned(),
repeater: repeater.map(Into::into).map(Cow::Owned),
delay: delay.map(Into::into).map(Cow::Owned),
post_blank,
},
}
}
pub fn is_running(&self) -> bool {
match self {
Clock::Closed { .. } => false,
Clock::Running { .. } => true,
}
}
pub fn is_closed(&self) -> bool {
match self {
Clock::Closed { .. } => true,
Clock::Running { .. } => false,
}
}
pub fn duration(&self) -> Option<&str> {
match self {
Clock::Closed { duration, .. } => Some(duration),
Clock::Running { .. } => None,
}
}
pub fn value(&self) -> Timestamp {
match &*self {
Clock::Closed {
start,
end,
repeater,
delay,
..
} => Timestamp::InactiveRange {
start: start.clone(),
end: end.clone(),
repeater: repeater.clone(),
delay: delay.clone(),
},
Clock::Running {
start,
repeater,
delay,
..
} => Timestamp::Inactive {
start: start.clone(),
repeater: repeater.clone(),
delay: delay.clone(),
},
}
}
}
fn parse_internal(input: &str) -> IResult<&str, Clock, ()> {
let (input, _) = space0(input)?;
let (input, _) = tag("CLOCK:")(input)?;
let (input, _) = space0(input)?;
let (input, timestamp) = parse_inactive(input)?;
match timestamp {
Timestamp::InactiveRange {
start,
end,
repeater,
delay,
} => {
let (input, _) = space0(input)?;
let (input, _) = tag("=>")(input)?;
let (input, _) = space0(input)?;
let (input, duration) = recognize(separated_pair(digit1, char(':'), digit1))(input)?;
let (input, _) = eol(input)?;
let (input, blank) = blank_lines_count(input)?;
Ok((
input,
Clock::Closed {
start,
end,
repeater,
delay,
duration: duration.into(),
post_blank: blank,
},
))
}
Timestamp::Inactive {
start,
repeater,
delay,
} => {
let (input, _) = eol(input)?;
let (input, blank) = blank_lines_count(input)?;
Ok((
input,
Clock::Running {
start,
repeater,
delay,
post_blank: blank,
},
))
}
_ => unreachable!(
"`parse_inactive` only returns `Timestamp::InactiveRange` or `Timestamp::Inactive`."
),
}
}
#[test]
fn parse() {
assert_eq!(
Clock::parse("CLOCK: [2003-09-16 Tue 09:39]"),
Some((
"",
Clock::Running {
start: Datetime {
year: 2003,
month: 9,
day: 16,
dayname: "Tue".into(),
hour: Some(9),
minute: Some(39)
},
repeater: None,
delay: None,
post_blank: 0,
}
))
);
assert_eq!(
Clock::parse("CLOCK: [2003-09-16 Tue 09:39]--[2003-09-16 Tue 10:39] => 1:00\n\n"),
Some((
"",
Clock::Closed {
start: Datetime {
year: 2003,
month: 9,
day: 16,
dayname: "Tue".into(),
hour: Some(9),
minute: Some(39)
},
end: Datetime {
year: 2003,
month: 9,
day: 16,
dayname: "Tue".into(),
hour: Some(10),
minute: Some(39)
},
repeater: None,
delay: None,
duration: "1:00".into(),
post_blank: 1,
}
))
);
}