pub mod aalarm;
pub mod acknowledged;
pub mod action;
pub mod attach;
pub mod attendee;
pub mod busytype;
pub mod calendar_address;
pub mod calscale;
pub mod cardinality;
pub mod categories;
pub mod class;
pub mod color;
pub mod comment;
pub mod completed;
pub mod concept;
pub mod conference;
pub mod contact;
pub mod created;
pub mod dalarm;
pub mod description;
pub mod dtend;
pub mod dtstamp;
pub mod dtstart;
pub mod due;
pub mod duration;
pub mod exdate;
pub mod exrule;
pub mod freebusy;
pub mod geo;
pub mod image;
pub mod last_modified;
pub mod link;
pub mod location;
pub mod location_type;
pub mod malarm;
pub mod method;
pub mod name;
pub mod organizer;
pub mod palarm;
pub mod participant_type;
pub mod percent_complete;
pub mod priority;
pub mod prodid;
pub mod proximity;
pub mod rdate;
pub mod recurrence_id;
pub mod refid;
pub mod refresh_interval;
pub mod related_to;
pub mod repeat;
pub mod request_status;
pub mod resource_type;
pub mod resources;
pub mod rnum;
pub mod rrule;
pub mod sequence;
pub mod source;
pub mod spec;
pub mod status;
pub mod structured_data;
pub mod styled_description;
pub mod summary;
pub mod transp;
pub mod trigger;
pub mod tz;
pub mod tzid;
pub mod tzname;
pub mod tzoffsetfrom;
pub mod tzoffsetto;
pub mod tzurl;
pub mod uid;
pub mod url;
use core::{error, fmt, ops, str};
use alloc::{
borrow::Cow,
string::{String, ToString},
vec::Vec,
};
use crate::{
param::IcalParam,
value::{IcalValue, owned},
};
#[derive(Debug)]
pub struct ParseIcalPropKindError(
String,
);
impl fmt::Display for ParseIcalPropKindError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Cannot parse iCalendar property `{}`", self.0)
}
}
impl error::Error for ParseIcalPropKindError {}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct IcalProp<'a> {
pub name: IcalPropName<'a>,
pub params: Vec<IcalParam<'a>>,
pub value: IcalValue<'a>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum IcalPropName<'a> {
Kind(IcalPropKind),
Unknown(Cow<'a, str>),
}
impl ops::Deref for IcalPropName<'_> {
type Target = str;
fn deref(&self) -> &Self::Target {
match self {
Self::Kind(kind) => kind,
Self::Unknown(name) => name,
}
}
}
impl From<IcalPropKind> for IcalPropName<'_> {
fn from(kind: IcalPropKind) -> Self {
Self::Kind(kind)
}
}
impl From<&IcalPropKind> for IcalPropName<'_> {
fn from(kind: &IcalPropKind) -> Self {
Self::Kind(*kind)
}
}
impl<'a> From<Cow<'a, str>> for IcalPropName<'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 IcalPropName<'a> {
fn from(name: &'a str) -> Self {
Cow::Borrowed(name).into()
}
}
impl IcalPropName<'_> {
pub fn into_owned(self) -> IcalPropName<'static> {
match self {
Self::Kind(kind) => IcalPropName::Kind(kind),
Self::Unknown(name) => IcalPropName::Unknown(owned(name)),
}
}
}
impl IcalProp<'_> {
pub fn into_owned(self) -> IcalProp<'static> {
IcalProp {
name: self.name.into_owned(),
params: self.params.into_iter().map(IcalParam::into_owned).collect(),
value: self.value.into_owned(),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum IcalPropKind {
CalScale,
Method,
ProdId,
Attach,
Categories,
Class,
Comment,
Description,
Geo,
Location,
PercentComplete,
Priority,
Resources,
Status,
Summary,
Completed,
DtEnd,
Due,
DtStart,
Duration,
FreeBusy,
Transp,
TzId,
TzName,
TzOffsetFrom,
TzOffsetTo,
TzUrl,
Attendee,
Contact,
Organizer,
RecurrenceId,
RelatedTo,
Url,
Uid,
ExDate,
RDate,
RRule,
ExRule,
Action,
Repeat,
Trigger,
Created,
DtStamp,
LastModified,
Sequence,
RequestStatus,
Name,
RefreshInterval,
Source,
Color,
Image,
Conference,
ParticipantType,
ResourceType,
CalendarAddress,
LocationType,
StructuredData,
Link,
Refid,
Concept,
BusyType,
StyledDescription,
Acknowledged,
Proximity,
Tz,
AAlarm,
DAlarm,
MAlarm,
PAlarm,
RNum,
}
impl IcalPropKind {
pub const ALL: [Self; 70] = [
Self::CalScale,
Self::Method,
Self::ProdId,
Self::Attach,
Self::Categories,
Self::Class,
Self::Comment,
Self::Description,
Self::Geo,
Self::Location,
Self::PercentComplete,
Self::Priority,
Self::Resources,
Self::Status,
Self::Summary,
Self::Completed,
Self::DtEnd,
Self::Due,
Self::DtStart,
Self::Duration,
Self::FreeBusy,
Self::Transp,
Self::TzId,
Self::TzName,
Self::TzOffsetFrom,
Self::TzOffsetTo,
Self::TzUrl,
Self::Attendee,
Self::Contact,
Self::Organizer,
Self::RecurrenceId,
Self::RelatedTo,
Self::Url,
Self::Uid,
Self::ExDate,
Self::RDate,
Self::RRule,
Self::ExRule,
Self::Action,
Self::Repeat,
Self::Trigger,
Self::Created,
Self::DtStamp,
Self::LastModified,
Self::Sequence,
Self::RequestStatus,
Self::Name,
Self::RefreshInterval,
Self::Source,
Self::Color,
Self::Image,
Self::Conference,
Self::ParticipantType,
Self::ResourceType,
Self::CalendarAddress,
Self::LocationType,
Self::StructuredData,
Self::Link,
Self::Refid,
Self::Concept,
Self::BusyType,
Self::StyledDescription,
Self::Acknowledged,
Self::Proximity,
Self::Tz,
Self::AAlarm,
Self::DAlarm,
Self::MAlarm,
Self::PAlarm,
Self::RNum,
];
}
impl str::FromStr for IcalPropKind {
type Err = ParseIcalPropKindError;
fn from_str(kind: &str) -> Result<Self, Self::Err> {
let kind = match kind {
kind if kind.eq_ignore_ascii_case("CALSCALE") => Self::CalScale,
kind if kind.eq_ignore_ascii_case("METHOD") => Self::Method,
kind if kind.eq_ignore_ascii_case("PRODID") => Self::ProdId,
kind if kind.eq_ignore_ascii_case("ATTACH") => Self::Attach,
kind if kind.eq_ignore_ascii_case("CATEGORIES") => Self::Categories,
kind if kind.eq_ignore_ascii_case("CLASS") => Self::Class,
kind if kind.eq_ignore_ascii_case("COMMENT") => Self::Comment,
kind if kind.eq_ignore_ascii_case("DESCRIPTION") => Self::Description,
kind if kind.eq_ignore_ascii_case("GEO") => Self::Geo,
kind if kind.eq_ignore_ascii_case("LOCATION") => Self::Location,
kind if kind.eq_ignore_ascii_case("PERCENT-COMPLETE") => Self::PercentComplete,
kind if kind.eq_ignore_ascii_case("PRIORITY") => Self::Priority,
kind if kind.eq_ignore_ascii_case("RESOURCES") => Self::Resources,
kind if kind.eq_ignore_ascii_case("STATUS") => Self::Status,
kind if kind.eq_ignore_ascii_case("SUMMARY") => Self::Summary,
kind if kind.eq_ignore_ascii_case("COMPLETED") => Self::Completed,
kind if kind.eq_ignore_ascii_case("DTEND") => Self::DtEnd,
kind if kind.eq_ignore_ascii_case("DUE") => Self::Due,
kind if kind.eq_ignore_ascii_case("DTSTART") => Self::DtStart,
kind if kind.eq_ignore_ascii_case("DURATION") => Self::Duration,
kind if kind.eq_ignore_ascii_case("FREEBUSY") => Self::FreeBusy,
kind if kind.eq_ignore_ascii_case("TRANSP") => Self::Transp,
kind if kind.eq_ignore_ascii_case("TZID") => Self::TzId,
kind if kind.eq_ignore_ascii_case("TZNAME") => Self::TzName,
kind if kind.eq_ignore_ascii_case("TZOFFSETFROM") => Self::TzOffsetFrom,
kind if kind.eq_ignore_ascii_case("TZOFFSETTO") => Self::TzOffsetTo,
kind if kind.eq_ignore_ascii_case("TZURL") => Self::TzUrl,
kind if kind.eq_ignore_ascii_case("ATTENDEE") => Self::Attendee,
kind if kind.eq_ignore_ascii_case("CONTACT") => Self::Contact,
kind if kind.eq_ignore_ascii_case("ORGANIZER") => Self::Organizer,
kind if kind.eq_ignore_ascii_case("RECURRENCE-ID") => Self::RecurrenceId,
kind if kind.eq_ignore_ascii_case("RELATED-TO") => Self::RelatedTo,
kind if kind.eq_ignore_ascii_case("URL") => Self::Url,
kind if kind.eq_ignore_ascii_case("UID") => Self::Uid,
kind if kind.eq_ignore_ascii_case("EXDATE") => Self::ExDate,
kind if kind.eq_ignore_ascii_case("RDATE") => Self::RDate,
kind if kind.eq_ignore_ascii_case("RRULE") => Self::RRule,
kind if kind.eq_ignore_ascii_case("EXRULE") => Self::ExRule,
kind if kind.eq_ignore_ascii_case("ACTION") => Self::Action,
kind if kind.eq_ignore_ascii_case("REPEAT") => Self::Repeat,
kind if kind.eq_ignore_ascii_case("TRIGGER") => Self::Trigger,
kind if kind.eq_ignore_ascii_case("CREATED") => Self::Created,
kind if kind.eq_ignore_ascii_case("DTSTAMP") => Self::DtStamp,
kind if kind.eq_ignore_ascii_case("LAST-MODIFIED") => Self::LastModified,
kind if kind.eq_ignore_ascii_case("SEQUENCE") => Self::Sequence,
kind if kind.eq_ignore_ascii_case("REQUEST-STATUS") => Self::RequestStatus,
kind if kind.eq_ignore_ascii_case("NAME") => Self::Name,
kind if kind.eq_ignore_ascii_case("REFRESH-INTERVAL") => Self::RefreshInterval,
kind if kind.eq_ignore_ascii_case("SOURCE") => Self::Source,
kind if kind.eq_ignore_ascii_case("COLOR") => Self::Color,
kind if kind.eq_ignore_ascii_case("IMAGE") => Self::Image,
kind if kind.eq_ignore_ascii_case("CONFERENCE") => Self::Conference,
kind if kind.eq_ignore_ascii_case("PARTICIPANT-TYPE") => Self::ParticipantType,
kind if kind.eq_ignore_ascii_case("RESOURCE-TYPE") => Self::ResourceType,
kind if kind.eq_ignore_ascii_case("CALENDAR-ADDRESS") => Self::CalendarAddress,
kind if kind.eq_ignore_ascii_case("LOCATION-TYPE") => Self::LocationType,
kind if kind.eq_ignore_ascii_case("STRUCTURED-DATA") => Self::StructuredData,
kind if kind.eq_ignore_ascii_case("LINK") => Self::Link,
kind if kind.eq_ignore_ascii_case("REFID") => Self::Refid,
kind if kind.eq_ignore_ascii_case("CONCEPT") => Self::Concept,
kind if kind.eq_ignore_ascii_case("BUSYTYPE") => Self::BusyType,
kind if kind.eq_ignore_ascii_case("STYLED-DESCRIPTION") => Self::StyledDescription,
kind if kind.eq_ignore_ascii_case("ACKNOWLEDGED") => Self::Acknowledged,
kind if kind.eq_ignore_ascii_case("PROXIMITY") => Self::Proximity,
kind if kind.eq_ignore_ascii_case("TZ") => Self::Tz,
kind if kind.eq_ignore_ascii_case("AALARM") => Self::AAlarm,
kind if kind.eq_ignore_ascii_case("DALARM") => Self::DAlarm,
kind if kind.eq_ignore_ascii_case("MALARM") => Self::MAlarm,
kind if kind.eq_ignore_ascii_case("PALARM") => Self::PAlarm,
kind if kind.eq_ignore_ascii_case("RNUM") => Self::RNum,
_ => return Err(ParseIcalPropKindError(kind.to_string())),
};
Ok(kind)
}
}
impl ops::Deref for IcalPropKind {
type Target = str;
fn deref(&self) -> &Self::Target {
match self {
Self::CalScale => "CALSCALE",
Self::Method => "METHOD",
Self::ProdId => "PRODID",
Self::Attach => "ATTACH",
Self::Categories => "CATEGORIES",
Self::Class => "CLASS",
Self::Comment => "COMMENT",
Self::Description => "DESCRIPTION",
Self::Geo => "GEO",
Self::Location => "LOCATION",
Self::PercentComplete => "PERCENT-COMPLETE",
Self::Priority => "PRIORITY",
Self::Resources => "RESOURCES",
Self::Status => "STATUS",
Self::Summary => "SUMMARY",
Self::Completed => "COMPLETED",
Self::DtEnd => "DTEND",
Self::Due => "DUE",
Self::DtStart => "DTSTART",
Self::Duration => "DURATION",
Self::FreeBusy => "FREEBUSY",
Self::Transp => "TRANSP",
Self::TzId => "TZID",
Self::TzName => "TZNAME",
Self::TzOffsetFrom => "TZOFFSETFROM",
Self::TzOffsetTo => "TZOFFSETTO",
Self::TzUrl => "TZURL",
Self::Attendee => "ATTENDEE",
Self::Contact => "CONTACT",
Self::Organizer => "ORGANIZER",
Self::RecurrenceId => "RECURRENCE-ID",
Self::RelatedTo => "RELATED-TO",
Self::Url => "URL",
Self::Uid => "UID",
Self::ExDate => "EXDATE",
Self::RDate => "RDATE",
Self::RRule => "RRULE",
Self::ExRule => "EXRULE",
Self::Action => "ACTION",
Self::Repeat => "REPEAT",
Self::Trigger => "TRIGGER",
Self::Created => "CREATED",
Self::DtStamp => "DTSTAMP",
Self::LastModified => "LAST-MODIFIED",
Self::Sequence => "SEQUENCE",
Self::RequestStatus => "REQUEST-STATUS",
Self::Name => "NAME",
Self::RefreshInterval => "REFRESH-INTERVAL",
Self::Source => "SOURCE",
Self::Color => "COLOR",
Self::Image => "IMAGE",
Self::Conference => "CONFERENCE",
Self::ParticipantType => "PARTICIPANT-TYPE",
Self::ResourceType => "RESOURCE-TYPE",
Self::CalendarAddress => "CALENDAR-ADDRESS",
Self::LocationType => "LOCATION-TYPE",
Self::StructuredData => "STRUCTURED-DATA",
Self::Link => "LINK",
Self::Refid => "REFID",
Self::Concept => "CONCEPT",
Self::BusyType => "BUSYTYPE",
Self::StyledDescription => "STYLED-DESCRIPTION",
Self::Acknowledged => "ACKNOWLEDGED",
Self::Proximity => "PROXIMITY",
Self::Tz => "TZ",
Self::AAlarm => "AALARM",
Self::DAlarm => "DALARM",
Self::MAlarm => "MALARM",
Self::PAlarm => "PALARM",
Self::RNum => "RNUM",
}
}
}
#[cfg(test)]
mod tests {
use core::str::FromStr;
use alloc::borrow::Cow;
use crate::{
param::IcalParam,
prop::{IcalProp, IcalPropKind, IcalPropName},
value::{IcalValue, text::IcalText},
};
#[test]
fn names_the_property_and_wraps_the_value() {
let prop = IcalProp {
name: IcalPropKind::Summary.into(),
params: [].into(),
value: IcalValue::Text(IcalText(Cow::Borrowed("Lunch"))),
};
assert_eq!(prop.name, IcalPropName::Kind(IcalPropKind::Summary));
assert_eq!(&*prop.name, "SUMMARY");
assert!(prop.params.is_empty());
}
#[test]
fn carries_the_given_parameters() {
let prop = IcalProp {
name: IcalPropKind::Attendee.into(),
params: [IcalParam::Role(Cow::Borrowed("CHAIR"))].into(),
value: IcalValue::CalAddress("mailto:a@b.example".into()),
};
assert_eq!(&*prop.name, "ATTENDEE");
assert_eq!(prop.params.len(), 1);
}
#[test]
fn round_trips_every_kind_through_its_wire_name() {
for kind in IcalPropKind::ALL {
assert_eq!(IcalPropKind::from_str(&kind).ok(), Some(kind));
}
assert_eq!(
IcalPropKind::from_str("summary").ok(),
Some(IcalPropKind::Summary),
);
assert!(IcalPropKind::from_str("X-CUSTOM").is_err());
}
}