use chrono::Duration;
use std::{fmt, mem, ops::Deref};
use crate::{components::*, Parameter, Property};
mod calendar_component;
pub use calendar_component::CalendarComponent;
#[derive(Debug, PartialEq, Eq)]
pub struct Calendar {
pub properties: Vec<Property>,
pub components: Vec<CalendarComponent>,
}
impl Default for Calendar {
fn default() -> Self {
Self {
properties: Property::from_array([
("VERSION", "2.0"),
("PRODID", "ICALENDAR-RS"),
("CALSCALE", "GREGORIAN"),
]),
components: Default::default(),
}
}
}
impl Calendar {
pub fn new() -> Self {
Default::default()
}
pub fn empty() -> Self {
Self {
properties: Default::default(),
components: Default::default(),
}
}
#[deprecated(note = "Use .push() instead")]
#[doc(hidden)]
pub fn add<T: Into<CalendarComponent>>(&mut self, component: T) -> &mut Self {
self.push(component)
}
pub fn append(&mut self, other: &mut Calendar) {
self.components.append(&mut other.components);
}
pub fn append_property(&mut self, property: impl Into<Property>) -> &mut Self {
self.properties.push(property.into());
self
}
pub fn property_value(&self, key: &str) -> Option<&str> {
Some(
self.properties
.iter()
.find(|property| property.key() == key)?
.value(),
)
}
pub fn extend<T, U>(&mut self, other: T)
where
T: IntoIterator<Item = U>,
U: Into<CalendarComponent>,
{
self.components.extend(other.into_iter().map(Into::into));
}
pub fn push<T: Into<CalendarComponent>>(&mut self, component: T) -> &mut Self {
self.components.push(component.into());
self
}
pub fn name(&mut self, name: &str) -> &mut Self {
self.append_property(Property::new("NAME", name));
self.append_property(Property::new("X-WR-CALNAME", name));
self
}
pub fn get_name(&self) -> Option<&str> {
self.property_value("NAME")
.or_else(|| self.property_value("X-WR-CALNAME"))
}
pub fn description(&mut self, description: &str) -> &mut Self {
self.append_property(Property::new("DESCRIPTION", description));
self.append_property(Property::new("X-WR-CALDESC", description));
self
}
pub fn get_description(&self) -> Option<&str> {
self.property_value("DESCRIPTION")
.or_else(|| self.property_value("X-WR-CALDESC"))
}
pub fn timezone(&mut self, timezone: &str) -> &mut Self {
self.append_property(Property::new("TIMEZONE-ID", timezone));
self.append_property(Property::new("X-WR-TIMEZONE", timezone));
self
}
pub fn get_timezone(&self) -> Option<&str> {
self.property_value("TIMEZONE_ID")
.or_else(|| self.property_value("X-WR-TIMEZONE"))
}
pub fn ttl(&mut self, duration: &Duration) -> &mut Self {
let duration_string = duration.to_string();
self.append_property(
Property::new("REFRESH-INTERVAL", &duration_string)
.append_parameter(Parameter::new("VALUE", "DURATION"))
.done(),
);
self.append_property(Property::new("X-PUBLISHED-TTL", &duration_string));
self
}
pub fn done(&mut self) -> Self {
Calendar {
properties: mem::take(&mut self.properties),
components: mem::take(&mut self.components),
}
}
fn fmt_write<W: fmt::Write>(&self, out: &mut W) -> Result<(), fmt::Error> {
write_crlf!(out, "BEGIN:VCALENDAR")?;
for property in &self.properties {
property.fmt_write(out)?;
}
for component in &self.components {
component.fmt_write(out)?;
}
write_crlf!(out, "END:VCALENDAR")?;
Ok(())
}
pub fn print(&self) -> Result<(), fmt::Error> {
print_crlf!("{}", self);
Ok(())
}
}
impl fmt::Display for Calendar {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.fmt_write(f)
}
}
impl TryInto<String> for &Calendar {
type Error = fmt::Error;
fn try_into(self) -> Result<String, Self::Error> {
let mut out_string = String::new();
self.fmt_write(&mut out_string)?;
Ok(out_string)
}
}
impl Deref for Calendar {
type Target = [CalendarComponent];
fn deref(&self) -> &[CalendarComponent] {
self.components.deref()
}
}
impl AsRef<[CalendarComponent]> for Calendar {
fn as_ref(&self) -> &[CalendarComponent] {
self.components.deref()
}
}
impl<T: Into<CalendarComponent>, const N: usize> From<[T; N]> for Calendar {
fn from(elements: [T; N]) -> Self {
elements.into_iter().collect()
}
}
impl<C: Into<CalendarComponent>> From<C> for Calendar {
fn from(element: C) -> Self {
Calendar {
components: vec![element.into()],
..Default::default()
}
}
}
impl<C: Into<CalendarComponent>> FromIterator<C> for Calendar {
fn from_iter<T: IntoIterator<Item = C>>(iter: T) -> Self {
Calendar {
components: iter.into_iter().map(Into::into).collect(),
..Default::default()
}
}
}
#[test]
fn from_adds_default_properties() {
let todo = Todo::default();
let cal = Calendar::from([todo]);
assert!(cal.property_value("VERSION").is_some());
assert!(cal.property_value("CALSCALE").is_some());
assert!(cal.property_value("PRODID").is_some());
assert!(cal
.property_value("VERSION")
.and(cal.property_value("PRODID"))
.and(cal.property_value("CALSCALE"))
.is_some());
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn calendar_extend_components() {
let mut calendar = Calendar::new();
let components = vec![
CalendarComponent::Event(Event::new()),
CalendarComponent::Event(Event::new()),
];
calendar.extend(components);
assert_eq!(calendar.components.len(), 2);
}
#[test]
fn calendar_extend_events() {
let mut calendar = Calendar::new();
let events = vec![Event::new(), Event::new()];
calendar.extend(events);
assert_eq!(calendar.components.len(), 2);
}
#[test]
fn get_properties_unset() {
let calendar = Calendar::new();
assert_eq!(calendar.get_name(), None);
assert_eq!(calendar.get_description(), None);
assert_eq!(calendar.get_timezone(), None);
}
#[test]
fn get_properties_set() {
let calendar = Calendar::new()
.name("name")
.description("description")
.timezone("timezone")
.done();
assert_eq!(calendar.get_name(), Some("name"));
assert_eq!(calendar.get_description(), Some("description"));
assert_eq!(calendar.get_timezone(), Some("timezone"));
}
#[test]
fn get_properties_alternate() {
let calendar = Calendar::new()
.append_property(Property::new("X-WR-CALNAME", "name"))
.append_property(Property::new("X-WR-CALDESC", "description"))
.append_property(Property::new("X-WR-TIMEZONE", "timezone"))
.done();
assert_eq!(calendar.get_name(), Some("name"));
assert_eq!(calendar.get_description(), Some("description"));
assert_eq!(calendar.get_timezone(), Some("timezone"));
}
#[test]
#[cfg(feature = "parser")]
fn emit_parse_icalendar() {
use std::str::FromStr;
let mut original = Calendar::new();
original.append_property(Property::new("FOOBAR", "foobar"));
let emitted = original.to_string();
let parsed = Calendar::from_str(&emitted).unwrap();
pretty_assertions::assert_eq!(parsed.property_value("FOOBAR"), Some("foobar"));
}
}