use chrono::Duration;
use std::{fmt::Debug, str::FromStr};
pub use self::properties::{Related, Trigger};
use self::properties::*;
use super::*;
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Alarm {
pub(crate) inner: InnerComponent,
}
impl Alarm {
pub(self) fn default() -> Self {
Self {
inner: Default::default(),
}
}
pub fn audio<T: Into<Trigger>>(trigger: T) -> Self {
let trigger: Trigger = trigger.into();
Alarm::default()
.append_property(Action::Audio)
.append_property(trigger)
.done()
}
pub fn display(description: &str, trigger: impl Into<Trigger>) -> Self {
let trigger: Trigger = trigger.into();
Alarm::default()
.append_property(Action::Display)
.append_property(trigger)
.add_property("DESCRIPTION", description)
.done()
}
#[allow(dead_code)]
fn email(description: &str, trigger: impl Into<Trigger>, summary: &str) -> Self {
let trigger: Trigger = trigger.into();
Alarm::default()
.append_property(Action::Email)
.append_property(trigger)
.add_property("DESCRIPTION", description)
.add_property("SUMMARY", summary)
.done()
}
pub fn duration_and_repeat<R: Copy + Clone + Into<Repeat>>(
mut self,
duration: Duration,
repeat_count: R,
) -> Self {
self.append_property(duration);
let repeat: Repeat = repeat_count.into();
self.append_property(repeat);
self
}
#[cfg(test)]
pub(self) fn get_action(&self) -> Option<Action> {
self.property_value("ACTION")
.and_then(|p| Action::from_str(p).ok())
}
#[cfg(test)]
pub(self) fn get_trigger(&self) -> Option<Trigger> {
self.inner
.properties
.get("TRIGGER")
.and_then(|prop| Trigger::try_from(prop).ok())
}
#[cfg(test)]
pub(self) fn get_description(&self) -> Option<&str> {
self.inner.property_value("DESCRIPTION")
}
#[cfg(test)]
pub(self) fn get_repeat(&self) -> usize {
self.inner
.property_value("REPEAT")
.and_then(|repeat| repeat.parse().ok())
.unwrap_or(0)
}
pub fn done(&mut self) -> Self {
Alarm {
inner: self.inner.done(),
}
}
}
#[test]
fn test_audio() {
let alarm = Alarm::audio((Duration::minutes(15), Related::Start))
.duration_and_repeat(Duration::minutes(5), 3)
.done();
assert_eq!(alarm.get_action(), Some(Action::Audio));
assert_eq!(
alarm.get_trigger(),
Some(Trigger::Duration(
Duration::minutes(15),
Related::Start.into()
))
);
assert_eq!(
alarm.get_trigger().unwrap().as_duration(),
Some(&Duration::minutes(15))
);
assert_eq!(alarm.get_trigger().unwrap().related(), Some(Related::Start));
assert_eq!(alarm.get_repeat(), 3);
alarm.print().unwrap();
}
#[test]
fn test_display() {
let now = CalendarDateTime::now();
let alarm = Alarm::display("test alarm with display", now.clone());
assert_eq!(alarm.get_action(), Some(Action::Display));
assert_eq!(alarm.get_trigger().unwrap().as_date_time().unwrap(), &now);
assert_eq!(alarm.get_description(), Some("test alarm with display"));
}
#[test]
#[ignore]
fn test_email() {
let now = CalendarDateTime::now();
let alarm = Alarm::email("test alarm with email", now.clone(), "important email");
assert_eq!(alarm.get_action(), Some(Action::Email));
assert_eq!(alarm.get_trigger().unwrap().as_date_time().unwrap(), &now);
assert_eq!(alarm.get_description(), Some("test alarm with email"));
assert_eq!(alarm.get_summary(), Some("important email"));
todo!("add attendee handling");
}
pub mod properties {
use crate::components::date_time::parse_duration;
use super::*;
#[derive(Debug, PartialEq, Eq)]
pub(crate) enum Action {
Audio,
Email,
Display,
Other(String),
}
impl FromStr for Action {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"AUDIO" => Action::Audio,
"EMAIL" => Action::Email,
"DISPLAY" => Action::Display,
other => Action::Other(other.into()),
})
}
}
impl fmt::Display for Action {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Action::Audio => write!(f, "AUDIO"),
Action::Email => write!(f, "EMAIL"),
Action::Display => write!(f, "DISPLAY"),
Action::Other(other) => write!(f, "{other}"),
}
}
}
impl From<Action> for Property {
fn from(action: Action) -> Self {
Property {
key: String::from("ACTION"),
val: action.to_string(),
params: Default::default(),
}
}
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct Repeat(
u32, );
impl From<u32> for Repeat {
fn from(count: u32) -> Self {
Repeat(count)
}
}
impl From<Repeat> for Property {
fn from(r: Repeat) -> Self {
Property::new("REPEAT", r.0.to_string())
}
}
impl FromStr for Repeat {
type Err = ();
fn from_str(_: &str) -> Result<Self, Self::Err> {
todo!()
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Related {
Start,
End,
}
impl From<Related> for Parameter {
fn from(related: Related) -> Self {
match related {
Related::Start => Parameter::new("RELATED", "START"),
Related::End => Parameter::new("RELATED", "END"),
}
}
}
impl FromStr for Related {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"START" => Ok(Related::Start),
"END" => Ok(Related::End),
_ => Err(()),
}
}
}
#[test]
fn test_repeat_default() {
assert_eq!(
Property::from(Repeat::default()).try_into(),
Ok(String::from("REPEAT:0\r\n"))
)
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Trigger {
Duration(Duration, Option<Related>),
DateTime(CalendarDateTime),
}
impl Trigger {
pub fn after_start(duration: Duration) -> Trigger {
Trigger::Duration(duration, Some(Related::Start))
}
pub fn after_end(duration: Duration) -> Trigger {
Trigger::Duration(duration, Some(Related::End))
}
pub fn before_start(duration: Duration) -> Trigger {
Trigger::Duration(-duration, Some(Related::Start))
}
pub fn before_end(duration: Duration) -> Trigger {
Trigger::Duration(-duration, Some(Related::End))
}
pub fn related(&self) -> Option<Related> {
match self {
Trigger::Duration(_, related) => *related,
Trigger::DateTime(_) => None,
}
}
pub fn as_duration(&self) -> Option<&Duration> {
match self {
Trigger::Duration(duration, _) => Some(duration),
Trigger::DateTime(_) => None,
}
}
pub fn as_date_time(&self) -> Option<&CalendarDateTime> {
match self {
Trigger::Duration(..) => None,
Trigger::DateTime(dt) => Some(dt),
}
}
}
impl From<Duration> for Trigger {
fn from(duration: Duration) -> Self {
Trigger::Duration(duration, None)
}
}
impl<T> From<T> for Trigger
where
CalendarDateTime: From<T>,
{
fn from(dt: T) -> Self {
Trigger::DateTime(CalendarDateTime::from(dt))
}
}
impl From<(Duration, Related)> for Trigger {
fn from((duration, related): (Duration, Related)) -> Self {
Trigger::Duration(duration, Some(related))
}
}
impl TryFrom<&Property> for Trigger {
type Error = ();
fn try_from(prop: &Property) -> Result<Self, Self::Error> {
match (prop.key(), prop.params().get("VALUE").map(Parameter::value)) {
("TRIGGER", Some("DATE-TIME")) => {
CalendarDateTime::from_str(prop.value()).map(Trigger::from)
}
("TRIGGER", Some("DURATION") | None) => {
let param_related = prop.get_param_as("RELATED", |s| Related::from_str(s).ok());
let parsed_duration = prop.get_value_as(parse_duration);
if let Some(duration) = parsed_duration {
Ok(Trigger::Duration(duration, param_related))
} else {
Err(())
}
}
_ => Err(()),
}
}
}
impl From<Trigger> for Property {
fn from(trigger: Trigger) -> Self {
match trigger {
Trigger::Duration(duration, Some(related)) => {
Property::new("TRIGGER", duration.to_string())
.append_parameter(related)
.done()
}
Trigger::Duration(duration, None) => Property::new("TRIGGER", duration.to_string()),
Trigger::DateTime(dt) => dt
.to_property("TRIGGER")
.add_parameter("VALUE", "DATE-TIME")
.done(),
}
}
}
#[test]
fn test_trigger() {
let prop: Property = Trigger::from(Duration::weeks(14)).into();
let mut out = String::new();
prop.fmt_write(&mut out).unwrap();
}
#[test]
fn test_trigger_abs_from_str() {
let now: CalendarDateTime = NaiveDate::from_ymd_opt(2022, 11, 17)
.unwrap()
.and_hms_opt(21, 32, 45)
.unwrap()
.into();
let alarm_with_abs_trigger = Alarm::default()
.append_property(Trigger::from(now.clone()))
.done();
alarm_with_abs_trigger.print().unwrap();
pretty_assertions::assert_eq!(
alarm_with_abs_trigger.get_trigger(),
Some(Trigger::DateTime(now))
);
}
#[test]
fn test_trigger_abs_from_str_naive() {
let now: CalendarDateTime = NaiveDate::from_ymd_opt(2022, 11, 17)
.unwrap()
.and_hms_opt(21, 32, 45)
.unwrap()
.into();
let alarm_with_abs_trigger = Alarm::default()
.append_property(Trigger::from(now.clone()))
.done();
alarm_with_abs_trigger.print().unwrap();
pretty_assertions::assert_eq!(
alarm_with_abs_trigger.get_trigger(),
Some(Trigger::DateTime(now))
);
}
#[test]
fn test_trigger_dur_from_str() {
let dur = Duration::minutes(15);
let alarm_with_rel_trigger = Alarm::default().append_property(Trigger::from(dur)).done();
alarm_with_rel_trigger.print().unwrap();
pretty_assertions::assert_eq!(
alarm_with_rel_trigger.get_trigger(),
Some(Trigger::Duration(dur, None))
);
}
#[test]
fn test_trigger_dur_from_str_start() {
let dur = Duration::minutes(15);
let alarm_with_rel_start_trigger = Alarm::default()
.append_property(Trigger::from((dur, Related::Start)))
.done();
alarm_with_rel_start_trigger.print().unwrap();
pretty_assertions::assert_eq!(
alarm_with_rel_start_trigger.get_trigger(),
Some(Trigger::Duration(dur, Some(Related::Start)))
);
}
}