use std::{
fmt::{self, Display, Formatter, Write},
str::FromStr,
};
use crate::{
error::InvalidValueError,
fold::FoldingWriter,
parameters::Parameters,
syntax::{is_token, split_unescaped, unescape_text, write_escaped_text},
values::{
AddressValue, ClientPidMapValue, DateAndOrTime, DateAndOrTimeOrText, EmailValue,
GenderValue, GramGenderValue, KindValue, LanguageTag, NameValue, OrgValue, TelValue,
TextOrUri, Timestamp, Token, TzValue, Uri, UtcOffset,
},
};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct GroupName(String);
impl GroupName {
#[inline]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl FromStr for GroupName {
type Err = InvalidValueError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
if is_token(s) {
Ok(Self(s.to_string()))
} else {
Err(InvalidValueError::new("group name"))
}
}
}
impl Display for GroupName {
#[inline]
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str(&self.0)
}
}
pub trait PropertyValue: Sized {
fn write_value(&self, w: &mut FoldingWriter) -> fmt::Result;
#[inline]
fn explicit_value_type(&self) -> Option<&'static str> {
None
}
fn parse_value(raw: &str, value_type: Option<&str>) -> Result<Self, InvalidValueError>;
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct Property<V> {
pub group: Option<GroupName>,
pub parameters: Parameters,
pub value: V,
}
impl<V> Property<V> {
#[inline]
pub fn new(value: V) -> Self {
Self {
group: None,
parameters: Parameters::new(),
value,
}
}
}
impl<V> From<V> for Property<V> {
#[inline]
fn from(value: V) -> Self {
Self::new(value)
}
}
impl From<&str> for Property<String> {
#[inline]
fn from(value: &str) -> Self {
Self::new(value.to_string())
}
}
pub(crate) fn write_property<V: PropertyValue>(
w: &mut FoldingWriter,
name: &str,
property: &Property<V>,
) -> fmt::Result {
if let Some(group) = &property.group {
write!(w, "{group}.")?;
}
w.write_str(name)?;
if let Some(value_type) = property.value.explicit_value_type() {
write!(w, ";VALUE={value_type}")?;
}
property.parameters.write(w)?;
w.write_char(':')?;
property.value.write_value(w)?;
w.end_line()
}
#[derive(Debug, Clone, PartialEq)]
pub struct ExtensionProperty {
pub group: Option<GroupName>,
pub name: Token,
pub parameters: Parameters,
pub value: String,
}
impl ExtensionProperty {
#[inline]
pub fn new(name: Token, value: String) -> Self {
Self {
group: None,
name,
parameters: Parameters::new(),
value,
}
}
pub fn from_text(name: Token, text: &str) -> Self {
let mut value = String::with_capacity(text.len());
write_escaped_text(&mut value, text, false).unwrap();
Self::new(name, value)
}
pub(crate) fn write(&self, w: &mut FoldingWriter) -> fmt::Result {
if let Some(group) = &self.group {
write!(w, "{group}.")?;
}
write!(w, "{}", self.name)?;
self.parameters.write(w)?;
w.write_char(':')?;
w.write_str(&self.value)?;
w.end_line()
}
}
impl PropertyValue for String {
#[inline]
fn write_value(&self, w: &mut FoldingWriter) -> fmt::Result {
write_escaped_text(w, self, false)
}
#[inline]
fn parse_value(raw: &str, _value_type: Option<&str>) -> Result<Self, InvalidValueError> {
Ok(unescape_text(raw))
}
}
impl PropertyValue for Vec<String> {
fn write_value(&self, w: &mut FoldingWriter) -> fmt::Result {
for (i, item) in self.iter().enumerate() {
if i > 0 {
w.write_char(',')?;
}
write_escaped_text(w, item, false)?;
}
Ok(())
}
fn parse_value(raw: &str, _value_type: Option<&str>) -> Result<Self, InvalidValueError> {
if raw.is_empty() {
return Ok(Vec::new());
}
Ok(split_unescaped(raw, b',').into_iter().map(unescape_text).collect())
}
}
impl PropertyValue for Uri {
#[inline]
fn write_value(&self, w: &mut FoldingWriter) -> fmt::Result {
w.write_str(self.as_str())
}
#[inline]
fn parse_value(raw: &str, _value_type: Option<&str>) -> Result<Self, InvalidValueError> {
Self::from_str(raw)
}
}
impl PropertyValue for LanguageTag {
#[inline]
fn write_value(&self, w: &mut FoldingWriter) -> fmt::Result {
w.write_str(self.as_str())
}
#[inline]
fn parse_value(raw: &str, _value_type: Option<&str>) -> Result<Self, InvalidValueError> {
Self::from_str(raw)
}
}
impl PropertyValue for Timestamp {
#[inline]
fn write_value(&self, w: &mut FoldingWriter) -> fmt::Result {
write!(w, "{self}")
}
#[inline]
fn parse_value(raw: &str, _value_type: Option<&str>) -> Result<Self, InvalidValueError> {
Self::from_str(raw)
}
}
impl PropertyValue for EmailValue {
#[inline]
fn write_value(&self, w: &mut FoldingWriter) -> fmt::Result {
write_escaped_text(w, self.as_str(), false)
}
#[inline]
fn parse_value(raw: &str, _value_type: Option<&str>) -> Result<Self, InvalidValueError> {
Self::from_str(&unescape_text(raw))
}
}
macro_rules! impl_property_value_by_display {
($($t:ty),* $(,)?) => {
$(
impl PropertyValue for $t {
#[inline]
fn write_value(&self, w: &mut FoldingWriter) -> fmt::Result {
write!(w, "{self}")
}
#[inline]
fn parse_value(raw: &str, _value_type: Option<&str>) -> Result<Self, InvalidValueError> {
Self::from_str(raw)
}
}
)*
};
}
impl_property_value_by_display!(
KindValue,
NameValue,
AddressValue,
GenderValue,
GramGenderValue,
OrgValue,
ClientPidMapValue,
);
impl PropertyValue for DateAndOrTimeOrText {
#[inline]
fn write_value(&self, w: &mut FoldingWriter) -> fmt::Result {
match self {
Self::DateAndOrTime(value) => write!(w, "{value}"),
Self::Text(text) => write_escaped_text(w, text, false),
}
}
#[inline]
fn explicit_value_type(&self) -> Option<&'static str> {
match self {
Self::DateAndOrTime(_) => None,
Self::Text(_) => Some("text"),
}
}
#[inline]
fn parse_value(raw: &str, value_type: Option<&str>) -> Result<Self, InvalidValueError> {
match value_type {
Some("text") => Ok(Self::Text(unescape_text(raw))),
_ => DateAndOrTime::from_str(raw).map(Self::DateAndOrTime),
}
}
}
impl PropertyValue for TelValue {
#[inline]
fn write_value(&self, w: &mut FoldingWriter) -> fmt::Result {
match self {
Self::Uri(uri) => w.write_str(uri.as_str()),
Self::Text(text) => write_escaped_text(w, text, false),
}
}
#[inline]
fn explicit_value_type(&self) -> Option<&'static str> {
match self {
Self::Uri(_) => Some("uri"),
Self::Text(_) => None,
}
}
#[inline]
fn parse_value(raw: &str, value_type: Option<&str>) -> Result<Self, InvalidValueError> {
match value_type {
Some("uri") => Uri::from_str(raw).map(Self::Uri),
_ => Ok(Self::Text(unescape_text(raw))),
}
}
}
impl PropertyValue for TextOrUri {
#[inline]
fn write_value(&self, w: &mut FoldingWriter) -> fmt::Result {
match self {
Self::Uri(uri) => w.write_str(uri.as_str()),
Self::Text(text) => write_escaped_text(w, text, false),
}
}
#[inline]
fn explicit_value_type(&self) -> Option<&'static str> {
match self {
Self::Uri(_) => None,
Self::Text(_) => Some("text"),
}
}
#[inline]
fn parse_value(raw: &str, value_type: Option<&str>) -> Result<Self, InvalidValueError> {
match value_type {
Some("text") => Ok(Self::Text(unescape_text(raw))),
_ => Uri::from_str(raw).map(Self::Uri),
}
}
}
impl PropertyValue for TzValue {
#[inline]
fn write_value(&self, w: &mut FoldingWriter) -> fmt::Result {
match self {
Self::Text(text) => write_escaped_text(w, text, false),
Self::Uri(uri) => w.write_str(uri.as_str()),
Self::UtcOffset(offset) => write!(w, "{offset}"),
}
}
#[inline]
fn explicit_value_type(&self) -> Option<&'static str> {
match self {
Self::Text(_) => None,
Self::Uri(_) => Some("uri"),
Self::UtcOffset(_) => Some("utc-offset"),
}
}
#[inline]
fn parse_value(raw: &str, value_type: Option<&str>) -> Result<Self, InvalidValueError> {
match value_type {
Some("uri") => Uri::from_str(raw).map(Self::Uri),
Some("utc-offset") => UtcOffset::from_str(raw).map(Self::UtcOffset),
_ => Ok(Self::Text(unescape_text(raw))),
}
}
}
pub type Source = Property<Uri>;
pub type Kind = Property<KindValue>;
pub type Xml = Property<String>;
pub type FormattedName = Property<String>;
pub type Name = Property<NameValue>;
pub type Nickname = Property<Vec<String>>;
pub type Photo = Property<Uri>;
pub type Birthday = Property<DateAndOrTimeOrText>;
pub type Anniversary = Property<DateAndOrTimeOrText>;
pub type Gender = Property<GenderValue>;
pub type Address = Property<AddressValue>;
pub type Tel = Property<TelValue>;
pub type Email = Property<EmailValue>;
pub type Impp = Property<Uri>;
pub type Lang = Property<LanguageTag>;
pub type TimeZone = Property<TzValue>;
pub type Geo = Property<Uri>;
pub type Title = Property<String>;
pub type Role = Property<String>;
pub type Logo = Property<Uri>;
pub type Org = Property<OrgValue>;
pub type Member = Property<Uri>;
pub type Related = Property<TextOrUri>;
pub type Categories = Property<Vec<String>>;
pub type Note = Property<String>;
pub type ProdId = Property<String>;
pub type Rev = Property<Timestamp>;
pub type Sound = Property<Uri>;
pub type Uid = Property<TextOrUri>;
pub type ClientPidMap = Property<ClientPidMapValue>;
pub type Url = Property<Uri>;
pub type Key = Property<TextOrUri>;
pub type Fburl = Property<Uri>;
pub type CalendarAddressUri = Property<Uri>;
pub type CalendarUri = Property<Uri>;
pub type Created = Property<Timestamp>;
pub type GramGender = Property<GramGenderValue>;
pub type Language = Property<LanguageTag>;
pub type Pronouns = Property<String>;
pub type SocialProfile = Property<TextOrUri>;