pub mod available;
pub mod daylight;
pub mod participant;
pub mod spec;
pub mod standard;
pub mod valarm;
pub mod vavailability;
pub mod vcalendar;
pub mod vevent;
pub mod vfreebusy;
pub mod vjournal;
pub mod vlocation;
pub mod vresource;
pub mod vtimezone;
pub mod vtodo;
use core::{error, fmt, ops, str};
use alloc::{
borrow::Cow,
string::{String, ToString},
vec::Vec,
};
use crate::{prop::IcalProp, value::owned};
#[derive(Debug)]
pub struct ParseIcalComponentKindError(
String,
);
impl fmt::Display for ParseIcalComponentKindError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Cannot parse iCalendar component `{}`", self.0)
}
}
impl error::Error for ParseIcalComponentKindError {}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct IcalComponent<'a> {
pub name: IcalComponentName<'a>,
pub props: Vec<IcalProp<'a>>,
pub components: Vec<IcalComponent<'a>>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum IcalComponentName<'a> {
Kind(IcalComponentKind),
Unknown(Cow<'a, str>),
}
impl ops::Deref for IcalComponentName<'_> {
type Target = str;
fn deref(&self) -> &Self::Target {
match self {
Self::Kind(kind) => kind,
Self::Unknown(name) => name,
}
}
}
impl From<IcalComponentKind> for IcalComponentName<'_> {
fn from(kind: IcalComponentKind) -> Self {
Self::Kind(kind)
}
}
impl<'a> From<Cow<'a, str>> for IcalComponentName<'a> {
fn from(name: Cow<'a, str>) -> Self {
match name.parse().ok() {
Some(kind) => Self::Kind(kind),
None => Self::Unknown(name),
}
}
}
impl<'a> From<&'a str> for IcalComponentName<'a> {
fn from(name: &'a str) -> Self {
Cow::Borrowed(name).into()
}
}
impl IcalComponentName<'_> {
pub fn into_owned(self) -> IcalComponentName<'static> {
match self {
Self::Kind(kind) => IcalComponentName::Kind(kind),
Self::Unknown(name) => IcalComponentName::Unknown(owned(name)),
}
}
}
impl IcalComponent<'_> {
pub fn into_owned(self) -> IcalComponent<'static> {
IcalComponent {
name: self.name.into_owned(),
props: self.props.into_iter().map(IcalProp::into_owned).collect(),
components: self
.components
.into_iter()
.map(IcalComponent::into_owned)
.collect(),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum IcalComponentKind {
VCalendar,
VEvent,
VTodo,
VJournal,
VFreeBusy,
VTimezone,
Standard,
Daylight,
VAlarm,
Participant,
VLocation,
VResource,
VAvailability,
Available,
}
impl IcalComponentKind {
pub const ALL: [Self; 14] = [
Self::VCalendar,
Self::VEvent,
Self::VTodo,
Self::VJournal,
Self::VFreeBusy,
Self::VTimezone,
Self::Standard,
Self::Daylight,
Self::VAlarm,
Self::Participant,
Self::VLocation,
Self::VResource,
Self::VAvailability,
Self::Available,
];
}
impl str::FromStr for IcalComponentKind {
type Err = ParseIcalComponentKindError;
fn from_str(kind: &str) -> Result<Self, Self::Err> {
let kind = match kind {
kind if kind.eq_ignore_ascii_case("VCALENDAR") => Self::VCalendar,
kind if kind.eq_ignore_ascii_case("VEVENT") => Self::VEvent,
kind if kind.eq_ignore_ascii_case("VTODO") => Self::VTodo,
kind if kind.eq_ignore_ascii_case("VJOURNAL") => Self::VJournal,
kind if kind.eq_ignore_ascii_case("VFREEBUSY") => Self::VFreeBusy,
kind if kind.eq_ignore_ascii_case("VTIMEZONE") => Self::VTimezone,
kind if kind.eq_ignore_ascii_case("STANDARD") => Self::Standard,
kind if kind.eq_ignore_ascii_case("DAYLIGHT") => Self::Daylight,
kind if kind.eq_ignore_ascii_case("VALARM") => Self::VAlarm,
kind if kind.eq_ignore_ascii_case("PARTICIPANT") => Self::Participant,
kind if kind.eq_ignore_ascii_case("VLOCATION") => Self::VLocation,
kind if kind.eq_ignore_ascii_case("VRESOURCE") => Self::VResource,
kind if kind.eq_ignore_ascii_case("VAVAILABILITY") => Self::VAvailability,
kind if kind.eq_ignore_ascii_case("AVAILABLE") => Self::Available,
_ => return Err(ParseIcalComponentKindError(kind.to_string())),
};
Ok(kind)
}
}
impl ops::Deref for IcalComponentKind {
type Target = str;
fn deref(&self) -> &Self::Target {
match self {
Self::VCalendar => "VCALENDAR",
Self::VEvent => "VEVENT",
Self::VTodo => "VTODO",
Self::VJournal => "VJOURNAL",
Self::VFreeBusy => "VFREEBUSY",
Self::VTimezone => "VTIMEZONE",
Self::Standard => "STANDARD",
Self::Daylight => "DAYLIGHT",
Self::VAlarm => "VALARM",
Self::Participant => "PARTICIPANT",
Self::VLocation => "VLOCATION",
Self::VResource => "VRESOURCE",
Self::VAvailability => "VAVAILABILITY",
Self::Available => "AVAILABLE",
}
}
}
#[cfg(test)]
mod tests {
use core::str::FromStr;
use crate::component::IcalComponentKind;
#[test]
fn round_trips_every_kind_through_its_wire_name() {
for kind in IcalComponentKind::ALL {
assert_eq!(IcalComponentKind::from_str(&kind).ok(), Some(kind));
}
assert_eq!(
IcalComponentKind::from_str("vevent").ok(),
Some(IcalComponentKind::VEvent),
);
assert!(IcalComponentKind::from_str("VUNKNOWN").is_err());
}
}