fn escape(value: &str) -> String {
let mut out = String::with_capacity(value.len());
for ch in value.chars() {
match ch {
'\\' => out.push_str("\\\\"),
',' => out.push_str("\\,"),
';' => out.push_str("\\;"),
'\n' => out.push_str("\\n"),
'\r' => {}
_ => out.push(ch),
}
}
out
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct BusinessCard {
full_name: String,
first_name: Option<String>,
last_name: Option<String>,
organization: Option<String>,
title: Option<String>,
phone: Option<String>,
email: Option<String>,
url: Option<String>,
address: Option<String>,
note: Option<String>,
}
impl BusinessCard {
#[must_use]
pub fn new(full_name: impl Into<String>) -> Self {
BusinessCard {
full_name: full_name.into(),
..Default::default()
}
}
#[must_use]
pub fn name(mut self, first: impl Into<String>, last: impl Into<String>) -> Self {
self.first_name = Some(first.into());
self.last_name = Some(last.into());
self
}
#[must_use]
pub fn organization(mut self, org: impl Into<String>) -> Self {
self.organization = Some(org.into());
self
}
#[must_use]
pub fn title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.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 note(mut self, note: impl Into<String>) -> Self {
self.note = Some(note.into());
self
}
#[must_use]
pub fn to_vcard(&self) -> String {
let mut s = String::from("BEGIN:VCARD\r\nVERSION:3.0\r\n");
if self.first_name.is_some() || self.last_name.is_some() {
let last = self.last_name.as_deref().unwrap_or_default();
let first = self.first_name.as_deref().unwrap_or_default();
s.push_str(&format!("N:{};{};;;\r\n", escape(last), escape(first)));
}
s.push_str(&format!("FN:{}\r\n", escape(&self.full_name)));
for (prop, value) in [
("ORG", &self.organization),
("TITLE", &self.title),
("URL", &self.url),
("NOTE", &self.note),
] {
if let Some(v) = value {
s.push_str(&format!("{prop}:{}\r\n", escape(v)));
}
}
if let Some(phone) = &self.phone {
s.push_str(&format!("TEL;TYPE=CELL:{}\r\n", escape(phone)));
}
if let Some(email) = &self.email {
s.push_str(&format!("EMAIL:{}\r\n", escape(email)));
}
if let Some(address) = &self.address {
s.push_str(&format!("ADR;TYPE=WORK:;;{};;;;\r\n", escape(address)));
}
s.push_str("END:VCARD");
s
}
}
impl core::fmt::Display for BusinessCard {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(&self.to_vcard())
}
}