From 9327ac204dd7087125fce42c22494a9d36b6f594 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Sat, 17 Jun 2023 23:28:14 +0300 Subject: [PATCH] melib/vcard: implement Card to VCard string conversion Concerns #54 --- melib/src/addressbook/vcard.rs | 100 ++++++++++++++++++++++++--------- 1 file changed, 73 insertions(+), 27 deletions(-) diff --git a/melib/src/addressbook/vcard.rs b/melib/src/addressbook/vcard.rs index 33de6324..d7841090 100644 --- a/melib/src/addressbook/vcard.rs +++ b/melib/src/addressbook/vcard.rs @@ -27,7 +27,7 @@ //! - Version 4 [RFC 6350: vCard Format Specification](https://datatracker.ietf.org/doc/rfc6350/) //! - Parameter escaping [RFC 6868 Parameter Value Encoding in iCalendar and vCard](https://datatracker.ietf.org/doc/rfc6868/) -use std::{collections::HashMap, convert::TryInto}; +use std::{collections::HashMap, convert::TryInto, fmt::Write}; use super::*; use crate::{ @@ -53,6 +53,7 @@ pub struct VCardVersion3; impl VCardVersion for VCardVersion3 {} pub struct CardDeserializer; +pub struct CardSerializer; const HEADER_CRLF: &str = "BEGIN:VCARD\r\n"; //VERSION:4.0\r\n"; const FOOTER_CRLF: &str = "END:VCARD\r\n"; @@ -171,6 +172,53 @@ impl CardDeserializer { } } +impl CardSerializer { + pub fn to_string(card: &Card) -> std::result::Result { + let mut ret = HEADER_CRLF.to_string(); + write!(&mut ret, "VERSION:4.0\r\n")?; + macro_rules! line { + ($hdr:literal, $field: expr) => {{ + let f = { $field }; + if !f.is_empty() { + write!(&mut ret, concat!($hdr, ":{}\r\n"), f)?; + } + }}; + } + line!("FN", &card.name); + line!("TITLE", &card.title); + line!("EMAIL", &card.email); + line!("URL", &card.url); + line!( + "BDAY;VALUE=text", + card.birthday + .as_ref() + .map(|t| t.to_string()) + .unwrap_or_default() + ); + // https://datatracker.ietf.org/doc/html/rfc6350#section-6.8.1 + // line!("KEY", card.key); + write!(&mut ret, "END:VCARD\r\n")?; + + Ok(ret) + } +} + +#[derive(Debug, Clone)] +#[repr(transparent)] +pub struct VCardSerializeError(std::fmt::Error); + +impl std::fmt::Display for VCardSerializeError { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(fmt, "{}", self.0) + } +} + +impl From for VCardSerializeError { + fn from(err: std::fmt::Error) -> Self { + Self(err) + } +} + impl TryInto for VCard { type Error = crate::error::Error; @@ -255,26 +303,6 @@ fn parse_card<'a>() -> impl Parser<'a, Vec<&'a str>> { } } -#[test] -fn test_load_cards() { - /* - let mut contents = String::with_capacity(256); - let p = &std::path::Path::new("/tmp/contacts.vcf"); - use std::io::Read; - contents.clear(); - std::fs::File::open(&p) - .unwrap() - .read_to_string(&mut contents) - .unwrap(); - for s in parse_card().parse(contents.as_str()).unwrap().1 { - println!(""); - println!("{}", s); - println!("{:?}", CardDeserializer::from_str(s)); - println!(""); - } - */ -} - pub fn load_cards(p: &std::path::Path) -> Result> { let vcf_dir = std::fs::read_dir(p); let mut ret: Vec> = Vec::new(); @@ -322,10 +350,28 @@ pub fn load_cards(p: &std::path::Path) -> Result> { ret.into_iter().collect::>>() } -#[test] -fn test_card() { - let j = "BEGIN:VCARD\r\nVERSION:4.0\r\nN:Gump;Forrest;;Mr.;\r\nFN:Forrest Gump\r\nORG:Bubba Gump Shrimp Co.\r\nTITLE:Shrimp Man\r\nPHOTO;MEDIATYPE=image/gif:http://www.example.com/dir_photos/my_photo.gif\r\nTEL;TYPE=work,voice;VALUE=uri:tel:+1-111-555-1212\r\nTEL;TYPE=home,voice;VALUE=uri:tel:+1-404-555-1212\r\nADR;TYPE=WORK;PREF=1;LABEL=\"100 Waters Edge\\nBaytown\\, LA 30314\\nUnited States of America\":;;100 Waters Edge;Baytown;LA;30314;United States of America\r\nADR;TYPE=HOME;LABEL=\"42 Plantation St.\\nBaytown\\, LA 30314\\nUnited States of America\":;;42 Plantation St.;Baytown;LA;30314;United States of America\r\nEMAIL:forrestgump@example.com\r\nREV:20080424T195243Z\r\nx-qq:21588891\r\nEND:VCARD\r\n"; - println!("results = {:#?}", CardDeserializer::from_str(j).unwrap()); - let j = "BEGIN:VCARD\nVERSION:4.0\nN:Gump;Forrest;;Mr.;\nFN:Forrest Gump\nORG:Bubba Gump Shrimp Co.\nTITLE:Shrimp Man\nPHOTO;MEDIATYPE=image/gif:http://www.example.com/dir_photos/my_photo.gif\nTEL;TYPE=work,voice;VALUE=uri:tel:+1-111-555-1212\nTEL;TYPE=home,voice;VALUE=uri:tel:+1-404-555-1212\nADR;TYPE=WORK;PREF=1;LABEL=\"100 Waters Edge\\nBaytown\\, LA 30314\\nUnited States of America\":;;100 Waters Edge;Baytown;LA;30314;United States of America\nADR;TYPE=HOME;LABEL=\"42 Plantation St.\\nBaytown\\, LA 30314\\nUnited States of America\":;;42 Plantation St.;Baytown;LA;30314;United States of America\nEMAIL:forrestgump@example.com\nREV:20080424T195243Z\nx-qq:21588891\nEND:VCARD\n"; - println!("results = {:#?}", CardDeserializer::from_str(j).unwrap()); +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_vcard_parse_card() { + let j = "BEGIN:VCARD\r\nVERSION:4.0\r\nN:Gump;Forrest;;Mr.;\r\nFN:Forrest Gump\r\nORG:Bubba Gump Shrimp Co.\r\nTITLE:Shrimp Man\r\nPHOTO;MEDIATYPE=image/gif:http://www.example.com/dir_photos/my_photo.gif\r\nTEL;TYPE=work,voice;VALUE=uri:tel:+1-111-555-1212\r\nTEL;TYPE=home,voice;VALUE=uri:tel:+1-404-555-1212\r\nADR;TYPE=WORK;PREF=1;LABEL=\"100 Waters Edge\\nBaytown\\, LA 30314\\nUnited States of America\":;;100 Waters Edge;Baytown;LA;30314;United States of America\r\nADR;TYPE=HOME;LABEL=\"42 Plantation St.\\nBaytown\\, LA 30314\\nUnited States of America\":;;42 Plantation St.;Baytown;LA;30314;United States of America\r\nEMAIL:forrestgump@example.com\r\nREV:20080424T195243Z\r\nx-qq:21588891\r\nEND:VCARD\r\n"; + println!("results = {:#?}", CardDeserializer::from_str(j).unwrap()); + let j = "BEGIN:VCARD\nVERSION:4.0\nN:Gump;Forrest;;Mr.;\nFN:Forrest Gump\nORG:Bubba Gump Shrimp Co.\nTITLE:Shrimp Man\nPHOTO;MEDIATYPE=image/gif:http://www.example.com/dir_photos/my_photo.gif\nTEL;TYPE=work,voice;VALUE=uri:tel:+1-111-555-1212\nTEL;TYPE=home,voice;VALUE=uri:tel:+1-404-555-1212\nADR;TYPE=WORK;PREF=1;LABEL=\"100 Waters Edge\\nBaytown\\, LA 30314\\nUnited States of America\":;;100 Waters Edge;Baytown;LA;30314;United States of America\nADR;TYPE=HOME;LABEL=\"42 Plantation St.\\nBaytown\\, LA 30314\\nUnited States of America\":;;42 Plantation St.;Baytown;LA;30314;United States of America\nEMAIL:forrestgump@example.com\nREV:20080424T195243Z\nx-qq:21588891\nEND:VCARD\n"; + println!("results = {:#?}", CardDeserializer::from_str(j).unwrap()); + } + + #[test] + fn test_vcard_export_card() { + let mut card = Card::new(); + card.set_title("Shrimp Man".into()) + .set_name("Forrest Gump".into()) + .set_email("forrestgump@example.com".into()); + assert_eq!( + &CardSerializer::to_string(&card).unwrap(), + "BEGIN:VCARD\r\nVERSION:4.0\r\nFN:Forrest Gump\r\nTITLE:Shrimp \ + Man\r\nEMAIL:forrestgump@example.com\r\nEND:VCARD\r\n" + ); + } }