fn escape(value: &str) -> String {
let mut out = String::with_capacity(value.len());
for ch in value.chars() {
if matches!(ch, '\\' | ';' | ':' | ',') {
out.push('\\');
}
out.push(ch);
}
out
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct MeCard {
name: String,
reading: Option<String>,
phone: Option<String>,
email: Option<String>,
url: Option<String>,
address: Option<String>,
birthday: Option<String>,
note: Option<String>,
}
impl MeCard {
#[must_use]
pub fn new(name: impl Into<String>) -> Self {
MeCard {
name: name.into(),
..Default::default()
}
}
#[must_use]
pub fn reading(mut self, reading: impl Into<String>) -> Self {
self.reading = Some(reading.into());
self
}
#[must_use]
pub fn phone(mut self, phone: impl Into<String>) -> Self {
self.phone = Some(phone.into());
self
}
#[must_use]
pub fn email(mut self, email: impl Into<String>) -> Self {
self.email = Some(email.into());
self
}
#[must_use]
pub fn url(mut self, url: impl Into<String>) -> Self {
self.url = Some(url.into());
self
}
#[must_use]
pub fn address(mut self, address: impl Into<String>) -> Self {
self.address = Some(address.into());
self
}
#[must_use]
pub fn birthday(mut self, birthday: impl Into<String>) -> Self {
self.birthday = Some(birthday.into());
self
}
#[must_use]
pub fn note(mut self, note: impl Into<String>) -> Self {
self.note = Some(note.into());
self
}
#[must_use]
pub fn to_mecard(&self) -> String {
let mut s = format!("MECARD:N:{};", escape(&self.name));
for (tag, value) in [
("SOUND", &self.reading),
("TEL", &self.phone),
("EMAIL", &self.email),
("URL", &self.url),
("ADR", &self.address),
("BDAY", &self.birthday),
("NOTE", &self.note),
] {
if let Some(v) = value {
s.push_str(&format!("{tag}:{};", escape(v)));
}
}
s.push(';');
s
}
}
impl core::fmt::Display for MeCard {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(&self.to_mecard())
}
}