summaryrefslogtreecommitdiffstats
path: root/bin/domain/imag-contact/src/create.rs
diff options
context:
space:
mode:
Diffstat (limited to 'bin/domain/imag-contact/src/create.rs')
-rw-r--r--bin/domain/imag-contact/src/create.rs270
1 files changed, 118 insertions, 152 deletions
diff --git a/bin/domain/imag-contact/src/create.rs b/bin/domain/imag-contact/src/create.rs
index bcd87730..b75a167e 100644
--- a/bin/domain/imag-contact/src/create.rs
+++ b/bin/domain/imag-contact/src/create.rs
@@ -33,7 +33,6 @@
)]
use std::collections::BTreeMap;
-use std::process::exit;
use std::io::Read;
use std::io::Write;
use std::path::PathBuf;
@@ -48,12 +47,11 @@ use toml::Value;
use uuid::Uuid;
use failure::Error;
use failure::err_msg;
+use failure::Fallible as Result;
+use failure::ResultExt;
use libimagcontact::store::ContactStore;
use libimagrt::runtime::Runtime;
-use libimagerror::trace::MapErrTrace;
-use libimagerror::trace::trace_error;
-use libimagerror::exit::ExitUnwrap;
use libimagutil::warn_result::WarnResult;
const TEMPLATE : &str = include_str!("../static/new-contact-template.toml");
@@ -76,32 +74,26 @@ mod test {
}
}
-fn ask_continue(inputstream: &mut dyn Read, outputstream: &mut dyn Write) -> bool {
+fn ask_continue(inputstream: &mut dyn Read, outputstream: &mut dyn Write) -> Result<bool> {
::libimaginteraction::ask::ask_bool("Edit tempfile", Some(true), inputstream, outputstream)
- .map_err_trace_exit_unwrap()
}
-pub fn create(rt: &Runtime) {
+pub fn create(rt: &Runtime) -> Result<()> {
let scmd = rt.cli().subcommand_matches("create").unwrap();
let mut template = String::from(TEMPLATE);
let collection_name = rt.cli().value_of("ref-collection-name").unwrap_or("contacts");
let collection_name = String::from(collection_name);
let ref_config = rt // TODO: Re-Deserialize to libimagentryref::reference::Config
.config()
- .ok_or_else(|| err_msg("Configuration missing, cannot continue!"))
- .map_err_trace_exit_unwrap()
- .read_partial::<libimagentryref::reference::Config>()
- .map_err(Error::from)
- .map_err_trace_exit_unwrap()
- .ok_or_else(|| format_err!("Configuration missing: {}", libimagentryref::reference::Config::LOCATION))
- .map_err_trace_exit_unwrap();
+ .ok_or_else(|| err_msg("Configuration missing, cannot continue!"))?
+ .read_partial::<libimagentryref::reference::Config>()?
+ .ok_or_else(|| format_err!("Configuration missing: {}", libimagentryref::reference::Config::LOCATION))?;
// TODO: Refactor the above to libimagutil or libimagrt?
let (mut dest, location, uuid) : (Box<dyn Write>, Option<PathBuf>, String) = {
if let Some(mut fl) = scmd.value_of("file-location").map(PathBuf::from) {
let uuid = if fl.is_file() {
- error!("File does exist, cannot create/override");
- exit(1)
+ return Err(err_msg("File does exist, cannot create/override"))
} else if fl.is_dir() {
let uuid = Uuid::new_v4().to_hyphenated().to_string();
fl.push(uuid.clone());
@@ -121,7 +113,7 @@ pub fn create(rt: &Runtime) {
.map(|f| format!(" '{}' ", f)) // ugly
.unwrap_or_else(|| String::from(" ")); // hack
- warn!("File{}has no extension 'vcf'", f); // ahead
+ warn!("File {} has no extension 'vcf'", f); // ahead
warn!("other tools might not recognize this as contact.");
warn!("Continuing...");
}
@@ -135,20 +127,16 @@ pub fn create(rt: &Runtime) {
.write(true)
.create_new(true)
.open(fl.clone())
- .map_warn_err_str("Cannot create/open destination File. Stopping.")
.map_err(Error::from)
- .map_err_trace_exit_unwrap();
-
- let uuid_string = uuid
- .unwrap_or_else(|| {
- fl.file_name()
- .and_then(|fname| fname.to_str())
- .map(String::from)
- .unwrap_or_else(|| {
- error!("Cannot calculate UUID for vcard");
- exit(1)
- })
- });
+ .context("Cannot create/open destination File. Stopping.")?;
+
+ let uuid_string = match uuid {
+ Some(s) => s,
+ None => fl.file_name()
+ .and_then(|fname| fname.to_str())
+ .map(String::from)
+ .ok_or_else(|| err_msg("Cannot calculate UUID for vcard"))?,
+ };
(Box::new(file), Some(fl), uuid_string)
} else {
@@ -158,56 +146,47 @@ pub fn create(rt: &Runtime) {
}
};
- let mut input = rt.stdin().unwrap_or_else(|| {
- error!("No input stream. Cannot ask for permission");
- exit(1)
- });
+ let mut input = rt.stdin().ok_or_else(|| {
+ err_msg("No input stream. Cannot ask for permission")
+ })?;
let mut output = rt.stdout();
loop {
- ::libimagentryedit::edit::edit_in_tmpfile(&rt, &mut template)
- .map_warn_err_str("Editing failed.")
- .map_err_trace_exit_unwrap();
+ ::libimagentryedit::edit::edit_in_tmpfile(&rt, &mut template)?;
if template == TEMPLATE || template.is_empty() {
- error!("No (changed) content in tempfile. Not doing anything.");
- exit(2);
+ return Err(err_msg("No (changed) content in tempfile. Not doing anything."))
}
match ::toml::de::from_str(&template)
- .map(|toml| parse_toml_into_vcard(&mut output, &mut input, toml, uuid.clone()))
.map_err(Error::from)
+ .and_then(|toml| parse_toml_into_vcard(&mut output, &mut input, toml, uuid.clone()))
{
Err(e) => {
error!("Error parsing template");
- trace_error(&e);
- if ask_continue(&mut input, &mut output) {
+ if ask_continue(&mut input, &mut output)? {
continue;
} else {
- exit(1)
+ return Err(e)
}
},
Ok(None) => continue,
Ok(Some(vcard)) => {
if template == TEMPLATE || template.is_empty() {
- if ::libimaginteraction::ask::ask_bool("Abort contact creating", Some(false), &mut input, &mut output)
- .map_err_trace_exit_unwrap()
- {
- exit(1)
+ let q = "Abort contact creating";
+
+ if ::libimaginteraction::ask::ask_bool(q, Some(false), &mut input, &mut output)? {
+ return Ok(())
} else {
continue;
}
}
let vcard_string = write_component(&vcard);
- dest
- .write_all(&vcard_string.as_bytes())
- .map_err(Error::from)
- .map_err_trace_exit_unwrap();
-
+ dest.write_all(&vcard_string.as_bytes())?;
break;
}
}
@@ -215,11 +194,8 @@ pub fn create(rt: &Runtime) {
if let Some(location) = location {
if !scmd.is_present("dont-track") {
- let entry = rt.store()
- .create_from_path(&location, &ref_config, &collection_name)
- .map_err_trace_exit_unwrap();
-
- rt.report_touched(entry.get_location()).unwrap_or_exit();
+ let entry = rt.store().create_from_path(&location, &ref_config, &collection_name)?;
+ rt.report_touched(entry.get_location())?;
info!("Created entry in store");
} else {
@@ -230,26 +206,27 @@ pub fn create(rt: &Runtime) {
}
info!("Ready");
+ Ok(())
}
#[clippy::cognitive_complexity = "71"]
-fn parse_toml_into_vcard(output: &mut dyn Write, input: &mut dyn Read, toml: Value, uuid: String) -> Option<Vcard> {
+fn parse_toml_into_vcard(output: &mut dyn Write, input: &mut dyn Read, toml: Value, uuid: String) -> Result<Option<Vcard>> {
let mut vcard = VcardBuilder::new().with_uid(uuid);
{ // parse name
debug!("Parsing name");
- let firstname = read_str_from_toml(&toml, "name.first", true);
+ let firstname = read_str_from_toml(&toml, "name.first", true)?;
trace!("firstname = {:?}", firstname);
- let lastname = read_str_from_toml(&toml, "name.last", true);
+ let lastname = read_str_from_toml(&toml, "name.last", true)?;
trace!("lastname = {:?}", lastname);
vcard = vcard.with_name(parameters!(),
- read_str_from_toml(&toml, "name.prefix", false),
+ read_str_from_toml(&toml, "name.prefix", false)?,
firstname.clone(),
- read_str_from_toml(&toml, "name.additional", false),
+ read_str_from_toml(&toml, "name.additional", false)?,
lastname.clone(),
- read_str_from_toml(&toml, "name.suffix", false));
+ read_str_from_toml(&toml, "name.suffix", false)?);
if let (Some(first), Some(last)) = (firstname, lastname) {
trace!("Building fullname: '{} {}'", first, last);
@@ -259,7 +236,7 @@ fn parse_toml_into_vcard(output: &mut dyn Write, input: &mut dyn Read, toml: Val
{ // parse personal
debug!("Parsing person information");
- let birthday = read_str_from_toml(&toml, "person.birthday", false);
+ let birthday = read_str_from_toml(&toml, "person.birthday", false)?;
trace!("birthday = {:?}", birthday);
if let Some(bday) = birthday {
@@ -269,10 +246,10 @@ fn parse_toml_into_vcard(output: &mut dyn Write, input: &mut dyn Read, toml: Val
{ // parse nicknames
debug!("Parsing nicknames");
- match toml.read("nickname").map_err(Error::from).map_err_trace_exit_unwrap() {
+ match toml.read("nickname").map_err(Error::from)? {
Some(&Value::Array(ref ary)) => {
for (i, element) in ary.iter().enumerate() {
- let nicktype = match read_str_from_toml(element, "type", false) {
+ let nicktype = match read_str_from_toml(element, "type", false)? {
None => BTreeMap::new(),
Some(p) => {
let mut m = BTreeMap::new();
@@ -281,14 +258,14 @@ fn parse_toml_into_vcard(output: &mut dyn Write, input: &mut dyn Read, toml: Val
},
};
- let name = match read_str_from_toml(element, "name", false) {
+ let name = match read_str_from_toml(element, "name", false)? {
Some(p) => p,
None => {
error!("Key 'nickname.[{}].name' missing", i);
- if ask_continue(input, output) {
- return None
+ if ask_continue(input, output)? {
+ return Ok(None)
} else {
- exit(1)
+ return Err(format_err!("Key 'nickname.[{}].name' missing", i))
}
},
};
@@ -306,10 +283,10 @@ fn parse_toml_into_vcard(output: &mut dyn Write, input: &mut dyn Read, toml: Val
Some(_) => {
error!("Type Error: Expected Array or String at 'nickname'");
- if ask_continue(input, output) {
- return None
+ if ask_continue(input, output)? {
+ return Ok(None)
} else {
- exit(1)
+ return Err(format_err!("Type Error: Expected Array or String at 'nickname'"))
}
},
None => {
@@ -321,17 +298,17 @@ fn parse_toml_into_vcard(output: &mut dyn Write, input: &mut dyn Read, toml: Val
{ // parse organisation
debug!("Parsing organisation");
- if let Some(orgs) = read_strary_from_toml(&toml, "organisation.name") {
+ if let Some(orgs) = read_strary_from_toml(&toml, "organisation.name")? {
trace!("orgs = {:?}", orgs);
vcard = vcard.with_org(orgs);
}
- if let Some(title) = read_str_from_toml(&toml, "organisation.title", false) {
+ if let Some(title) = read_str_from_toml(&toml, "organisation.title", false)? {
trace!("title = {:?}", title);
vcard = vcard.with_title(title);
}
- if let Some(role) = read_str_from_toml(&toml, "organisation.role", false) {
+ if let Some(role) = read_str_from_toml(&toml, "organisation.role", false)? {
trace!("role = {:?}", role);
vcard = vcard.with_role(role);
}
@@ -339,29 +316,29 @@ fn parse_toml_into_vcard(output: &mut dyn Write, input: &mut dyn Read, toml: Val
{ // parse phone
debug!("Parse phone");
- match toml.read("person.phone").map_err(Error::from).map_err_trace_exit_unwrap() {
+ match toml.read("person.phone")? {
Some(&Value::Array(ref ary)) => {
for (i, element) in ary.iter().enumerate() {
- let phonetype = match read_str_from_toml(element, "type", false) {
+ let phonetype = match read_str_from_toml(element, "type", false)? {
Some(p) => p,
None => {
error!("Key 'phones.[{}].type' missing", i);
- if ask_continue(input, output) {
- return None
+ if ask_continue(input, output)? {
+ return Ok(None)
} else {
- exit(1)
+ return Err(format_err!("Key 'phones.[{}].type' missing", i))
}
}
};
- let number = match read_str_from_toml(element, "number", false) {
+ let number = match read_str_from_toml(element, "number", false)? {
Some(p) => p,
None => {
error!("Key 'phones.[{}].number' missing", i);
- if ask_continue(input, output) {
- return None
+ if ask_continue(input, output)? {
+ return Ok(None)
} else {
- exit(1)
+ return Err(format_err!("Key 'phones.[{}].number' missing", i))
}
}
};
@@ -375,10 +352,10 @@ fn parse_toml_into_vcard(output: &mut dyn Write, input: &mut dyn Read, toml: Val
Some(_) => {
error!("Expected Array at 'phones'.");
- if ask_continue(input, output) {
- return None
+ if ask_continue(input, output)? {
+ return Ok(None)
} else {
- exit(1)
+ return Err(format_err!("Expected Array at 'phones'."))
}
},
None => {
@@ -389,29 +366,29 @@ fn parse_toml_into_vcard(output: &mut dyn Write, input: &mut dyn Read, toml: Val
{ // parse address
debug!("Parsing address");
- match toml.read("addresses").map_err(Error::from).map_err_trace_exit_unwrap() {
+ match toml.read("addresses")? {
Some(&Value::Array(ref ary)) => {
for (i, element) in ary.iter().enumerate() {
- let adrtype = match read_str_from_toml(element, "type", false) {
+ let adrtype = match read_str_from_toml(element, "type", false)? {
None => {
error!("Key 'adresses.[{}].type' missing", i);
- if ask_continue(input, output) {
- return None
+ if ask_continue(input, output)? {
+ return Ok(None)
} else {
- exit(1)
+ return Err(format_err!("Key 'adresses.[{}].type' missing", i))
}
},
Some(p) => p,
};
trace!("adrtype = {:?}", adrtype);
- let bx = read_str_from_toml(element, "box", false);
- let extended = read_str_from_toml(element, "extended", false);
- let street = read_str_from_toml(element, "street", false);
- let code = read_str_from_toml(element, "code", false);
- let city = read_str_from_toml(element, "city", false);
- let region = read_str_from_toml(element, "region", false);
- let country = read_str_from_toml(element, "country", false);
+ let bx = read_str_from_toml(element, "box", false)?;
+ let extended = read_str_from_toml(element, "extended", false)?;
+ let street = read_str_from_toml(element, "street", false)?;
+ let code = read_str_from_toml(element, "code", false)?;
+ let city = read_str_from_toml(element, "city", false)?;
+ let region = read_str_from_toml(element, "region", false)?;
+ let country = read_str_from_toml(element, "country", false)?;
trace!("bx = {:?}", bx);
trace!("extended = {:?}", extended);
@@ -430,10 +407,10 @@ fn parse_toml_into_vcard(output: &mut dyn Write, input: &mut dyn Read, toml: Val
Some(_) => {
error!("Type Error: Expected Array at 'addresses'");
- if ask_continue(input, output) {
- return None
+ if ask_continue(input, output)? {
+ return Ok(None)
} else {
- exit(1)
+ return Err(format_err!("Type Error: Expected Array at 'addresses'"))
}
},
None => {
@@ -444,28 +421,28 @@ fn parse_toml_into_vcard(output: &mut dyn Write, input: &mut dyn Read, toml: Val
{ // parse email
debug!("Parsing email");
- match toml.read("person.email").map_err(Error::from).map_err_trace_exit_unwrap() {
+ match toml.read("person.email")? {
Some(&Value::Array(ref ary)) => {
for (i, element) in ary.iter().enumerate() {
- let mailtype = match read_str_from_toml(element, "type", false) {
+ let mailtype = match read_str_from_toml(element, "type", false)? {
None => {
error!("Error: 'email.[{}].type' missing", i);
- if ask_continue(input, output) {
- return None
+ if ask_continue(input, output)? {
+ return Ok(None)
} else {
- exit(1)
+ return Err(format_err!("Error: 'email.[{}].type' missing", i))
}
},
Some(p) => p,
}; // TODO: Unused, because unsupported by vobject
- let mail = match read_str_from_toml(element, "addr", false) {
+ let mail = match read_str_from_toml(element, "addr", false)? {
None => {
error!("Error: 'email.[{}].addr' missing", i);
- if ask_continue(input, output) {
- return None
+ if ask_continue(input, output)? {
+ return Ok(None)
} else {
- exit(1)
+ return Err(format_err!("Error: 'email.[{}].addr' missing", i))
}
},
Some(p) => p,
@@ -480,10 +457,10 @@ fn parse_toml_into_vcard(output: &mut dyn Write, input: &mut dyn Read, toml: Val
Some(_) => {
error!("Type Error: Expected Array at 'email'");
- if ask_continue(input, output) {
- return None
+ if ask_continue(input, output)? {
+ return Ok(None)
} else {
- exit(1)
+ return Err(format_err!("Type Error: Expected Array at 'email'"))
}
},
None => {
@@ -494,19 +471,19 @@ fn parse_toml_into_vcard(output: &mut dyn Write, input: &mut dyn Read, toml: Val
{ // parse others
debug!("Parsing others");
- if let Some(categories) = read_strary_from_toml(&toml, "other.categories") {
+ if let Some(categories) = read_strary_from_toml(&toml, "other.categories")? {
vcard = vcard.with_categories(categories);
} else {
debug!("No categories");
}
- if let Some(webpage) = read_str_from_toml(&toml, "other.webpage", false) {
+ if let Some(webpage) = read_str_from_toml(&toml, "other.webpage", false)? {
vcard = vcard.with_url(webpage);
} else {
debug!("No webpage");
}
- if let Some(note) = read_str_from_toml(&toml, "other.note", false) {
+ if let Some(note) = read_str_from_toml(&toml, "other.note", false)? {
vcard = vcard.with_note(note);
} else {
debug!("No note");
@@ -517,10 +494,10 @@ fn parse_toml_into_vcard(output: &mut dyn Write, input: &mut dyn Read, toml: Val
let vcard = vcard
.build()
.unwrap(); // TODO: This unwrap does not fail with rust-vobject, why is there a Result<> returned?
- Some(vcard)
+ Ok(Some(vcard))
}
-fn read_strary_from_toml(toml: &Value, path: &'static str) -> Option<Vec<String>> {
+fn read_strary_from_toml(toml: &Value, path: &'static str) -> Result<Option<Vec<String>>> {
match toml.read(path).map_err(Error::from).map_warn_err_str(&format!("Failed to read value at '{}'", path)) {
Ok(Some(&Value::Array(ref vec))) => {
let mut v = Vec::new();
@@ -528,48 +505,37 @@ fn read_strary_from_toml(toml: &Value, path: &'static str) -> Option<Vec<String>
match *elem {
Value::String(ref s) => v.push(s.clone()),
_ => {
- error!("Type Error: '{}' must be Array<String>", path);
- return None
+ return Err(format_err!("Type Error: '{}' must be Array<String>", path))
},
}
}
- Some(v)
+ Ok(Some(v))
}
Ok(Some(&Value::String(ref s))) => {
warn!("Having String, wanting Array<String> ... going to auto-fix");
- Some(vec![s.clone()])
+ Ok(Some(vec![s.clone()]))
},
Ok(Some(_)) => {
- error!("Type Error: '{}' must be Array<String>", path);
- None
+ return Err(format_err!("Type Error: '{}' must be Array<String>", path))
},
- Ok(None) => None,
- Err(_) => None,
+ Ok(None) => Ok(None),
+ Err(_) => Ok(None),
}
}
-fn read_str_from_toml(toml: &Value, path: &'static str, must_be_there: bool) -> Option<String> {
- let v = toml.read(path)
- .map_err(Error::from)
- .map_warn_err_str(&format!("Failed to read value at '{}'", path));
-
- match v {
- Ok(Some(&Value::String(ref s))) => Some(s.clone()),
- Ok(Some(_)) => {
- error!("Type Error: '{}' must be String", path);
- None
+fn read_str_from_toml(toml: &Value, path: &'static str, must_be_there: bool) -> Result<Option<String>> {
+ match toml.read(path)? {
+ Some(&Value::String(ref s)) => Ok(Some(s.clone())),
+ Some(_) => {
+ Err(format_err!("Type Error: '{}' must be String", path))
},
- Ok(None) => {
+ None => {
if must_be_there {
- error!("Expected '{}' to be present, but is not.", path);
+ return Err(format_err!("Expected '{}' to be present, but is not.", path))
}
- None
+ Ok(None)
},
- Err(e) => {
- trace_error(&e);
- None
- }
}
}
@@ -585,7 +551,7 @@ mod test_parsing {
fn test_template_names() {
let uid = String::from("uid");
let mut output = Vec::new();
- let vcard = parse_toml_into_vcard(&mut output, &mut empty(), ::toml::de::from_str(TEMPLATE).unwrap(), uid);
+ let vcard = parse_toml_into_vcard(&mut output, &mut empty(), ::toml::de::from_str(TEMPLATE).unwrap(), uid).unwrap();
assert!(vcard.is_some(), "Failed to parse test template.");
let vcard = vcard.unwrap();
@@ -603,7 +569,7 @@ mod test_parsing {
fn test_template_person() {
let uid = String::from("uid");
let mut output = Vec::new();
- let vcard = parse_toml_into_vcard(&mut output, &mut empty(), ::toml::de::from_str(TEMPLATE).unwrap(), uid);
+ let vcard = parse_toml_into_vcard(&mut output, &mut empty(), ::toml::de::from_str(TEMPLATE).unwrap(), uid).unwrap();
assert!(vcard.is_some(), "Failed to parse test template.");
let vcard = vcard.unwrap();
@@ -622,7 +588,7 @@ mod test_parsing {
fn test_template_organization() {
let uid = String::from("uid");
let mut output = Vec::new();
- let vcard = parse_toml_into_vcard(&mut output, &mut empty(), ::toml::de::from_str(TEMPLATE).unwrap(), uid);
+ let vcard = parse_toml_into_vcard(&mut output, &mut empty(), ::toml::de::from_str(TEMPLATE).unwrap(), uid).unwrap();
assert!(vcard.is_some(), "Failed to parse test template.");
let vcard = vcard.unwrap();
@@ -640,7 +606,7 @@ mod test_parsing {
fn test_template_phone() {
let uid = String::from("uid");
let mut output = Vec::new();
- let vcard = parse_toml_into_vcard(&mut output, &mut empty(), ::toml::de::from_str(TEMPLATE).unwrap(), uid);
+ let vcard = parse_toml_into_vcard(&mut output, &mut empty(), ::toml::de::from_str(TEMPLATE).unwrap(), uid).unwrap();
assert!(vcard.is_some(), "Failed to parse test template.");
let vcard = vcard.unwrap();
@@ -656,7 +622,7 @@ mod test_parsing {
fn test_template_email() {
let uid = String::from("uid");
let mut output = Vec::new();
- let vcard = parse_toml_into_vcard(&mut output, &mut empty(), ::toml::de::from_str(TEMPLATE).unwrap(), uid);
+ let vcard = parse_toml_into_vcard(&mut output, &mut empty(), ::toml::de::from_str(TEMPLATE).unwrap(), uid).unwrap();
assert!(vcard.is_some(), "Failed to parse test template.");
let vcard = vcard.unwrap();
@@ -672,7 +638,7 @@ mod test_parsing {
fn test_template_addresses() {
let uid = String::from("uid");
let mut output = Vec::new();
- let vcard = parse_toml_into_vcard(&mut output, &mut empty(), ::toml::de::from_str(TEMPLATE).unwrap(), uid);
+ let vcard = parse_toml_into_vcard(&mut output, &mut empty(), ::toml::de::from_str(TEMPLATE).unwrap(), uid).unwrap();
assert!(vcard.is_some(), "Failed to parse test template.");
let vcard = vcard.unwrap();
@@ -690,7 +656,7 @@ mod test_parsing {
fn test_template_other() {
let uid = String::from("uid");
let mut output = Vec::new();
- let vcard = parse_toml_into_vcard(&mut output, &mut empty(), ::toml::de::from_str(TEMPLATE).unwrap(), uid);
+ let vcard = parse_toml_into_vcard(&mut output, &mut empty(), ::toml::de::from_str(TEMPLATE).unwrap(), uid).unwrap();
assert!(vcard.is_some(), "Failed to parse test template.");
let vcard = vcard.unwrap();