From 3a3f9ebeed5336bb2e1baf7aa9b0ae6decd961d6 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Sun, 1 Dec 2019 16:37:04 +0100 Subject: [PATCH] Convert imag-contact to propagate errors to main() This change exploded (in complexity) much more than I expected. So this diff converts the whole codebase of imag-contact to propagate errors up to the main() function. Changes in functionality might happen but are unintended. Signed-off-by: Matthias Beyer --- bin/domain/imag-contact/Cargo.toml | 1 + bin/domain/imag-contact/src/create.rs | 268 ++++++++++------------ bin/domain/imag-contact/src/edit.rs | 64 +++--- bin/domain/imag-contact/src/lib.rs | 314 ++++++++++++-------------- bin/domain/imag-contact/src/util.rs | 41 ++-- 5 files changed, 310 insertions(+), 378 deletions(-) diff --git a/bin/domain/imag-contact/Cargo.toml b/bin/domain/imag-contact/Cargo.toml index 6260fbb8..da559479 100644 --- a/bin/domain/imag-contact/Cargo.toml +++ b/bin/domain/imag-contact/Cargo.toml @@ -28,6 +28,7 @@ walkdir = "2.2.8" uuid = { version = "0.7.4", features = ["v4"] } serde_json = "1.0.39" failure = "0.1.5" +resiter = "0.4" libimagrt = { version = "0.10.0", path = "../../../lib/core/libimagrt" } libimagstore = { version = "0.10.0", path = "../../../lib/core/libimagstore" } 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 { ::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::() - .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::()? + .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, Option, 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(); + .context("Cannot create/open destination File. Stopping.")?; - 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) - }) - }); + 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 { +fn parse_toml_into_vcard(output: &mut dyn Write, input: &mut dyn Read, toml: Value, uuid: String) -> Result> { 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> { +fn read_strary_from_toml(toml: &Value, path: &'static str) -> Result>> { 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 match *elem { Value::String(ref s) => v.push(s.clone()), _ => { - error!("Type Error: '{}' must be Array", path); - return None + return Err(format_err!("Type Error: '{}' must be Array", path)) }, } } - Some(v) + Ok(Some(v)) } Ok(Some(&Value::String(ref s))) => { warn!("Having String, wanting Array ... going to auto-fix"); - Some(vec![s.clone()]) + Ok(Some(vec![s.clone()])) }, Ok(Some(_)) => { - error!("Type Error: '{}' must be Array", path); - None + return Err(format_err!("Type Error: '{}' must be Array", 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 { - 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> { + 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(); diff --git a/bin/domain/imag-contact/src/edit.rs b/bin/domain/imag-contact/src/edit.rs index 78e436ba..ecfeffef 100644 --- a/bin/domain/imag-contact/src/edit.rs +++ b/bin/domain/imag-contact/src/edit.rs @@ -32,16 +32,16 @@ while_true, )] -use std::process::exit; use std::io::Read; use std::io::Write; -use failure::Error; use failure::err_msg; use failure::Fallible as Result; +use resiter::Filter; +use resiter::Map; +use resiter::AndThen; use libimagrt::runtime::Runtime; -use libimagerror::trace::MapErrTrace; use libimagstore::store::FileLockEntry; use libimagcontact::store::ContactStore; use libimagentryref::reference::fassade::RefFassade; @@ -49,61 +49,56 @@ use libimagentryref::hasher::default::DefaultHasher; use libimagentryref::reference::Ref; use libimagentryref::reference::Config as RefConfig; -pub fn edit(rt: &Runtime) { +pub fn edit(rt: &Runtime) -> Result<()> { let scmd = rt.cli().subcommand_matches("edit").unwrap(); let collection_name = rt.cli().value_of("contact-ref-collection-name").unwrap(); // default by clap - let ref_config = libimagentryref::util::get_ref_config(&rt, "imag-contact").map_err_trace_exit_unwrap(); + let ref_config = libimagentryref::util::get_ref_config(&rt, "imag-contact")?; let hash = scmd.value_of("hash").map(String::from).unwrap(); // safed by clap let force_override = true; // when editing, we want to override, right? let retry = !scmd.is_present("fail-on-parse-error"); if rt.output_is_pipe() { - error!("Cannot spawn editor if output is a pipe!"); - exit(1); + return Err(err_msg("Cannot spawn editor if output is a pipe!")) } let mut output = rt.stdout(); - 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.") + })?; - crate::util::find_contact_by_hash(rt, hash) - .for_each(|contact| { + crate::util::find_contact_by_hash(rt, hash)? + .filter_ok(|tpl| tpl.0) + .map_ok(|tpl| tpl.1) + .and_then_ok(|contact| { loop { let res = edit_contact(&rt, &contact, &ref_config, collection_name, force_override); + if !retry { - res.map_err_trace_exit_unwrap(); - } else if ask_continue(&mut input, &mut output) { - continue; -} else { - exit(1) -} + return res + } else if ask_continue(&mut input, &mut output)? { + continue; + } else { + return res + } } - }); + }) + .collect::>>() + .map(|_| ()) } fn edit_contact<'a>(rt: &Runtime, contact: &FileLockEntry<'a>, ref_config: &RefConfig, collection_name: &str, force_override: bool) -> Result<()> { let filepath = contact .as_ref_with_hasher::() - .get_path(ref_config) - .map_err_trace_exit_unwrap(); + .get_path(ref_config)?; - let success = rt.editor() - .map_err_trace_exit_unwrap() - .ok_or_else(|| { - err_msg("I have no editor configured. Cannot continue!") - }) - .map_err_trace_exit_unwrap() + let success = rt.editor()? + .ok_or_else(|| err_msg("I have no editor configured. Cannot continue!"))? .arg(&filepath) - .status() - .map_err(Error::from) - .map_err_trace_exit_unwrap() + .status()? .success(); if !success { - error!("Editor failed!"); - exit(1); + return Err(err_msg("Editor failed!")) } rt.store() @@ -111,8 +106,7 @@ fn edit_contact<'a>(rt: &Runtime, contact: &FileLockEntry<'a>, ref_config: &RefC .map(|_| ()) } -fn ask_continue(inputstream: &mut dyn Read, outputstream: &mut dyn Write) -> bool { +fn ask_continue(inputstream: &mut dyn Read, outputstream: &mut dyn Write) -> Result { ::libimaginteraction::ask::ask_bool("Edit vcard", Some(true), inputstream, outputstream) - .map_err_trace_exit_unwrap() } diff --git a/bin/domain/imag-contact/src/lib.rs b/bin/domain/imag-contact/src/lib.rs index 0285f128..873c822a 100644 --- a/bin/domain/imag-contact/src/lib.rs +++ b/bin/domain/imag-contact/src/lib.rs @@ -44,6 +44,7 @@ extern crate walkdir; extern crate uuid; extern crate serde_json; #[macro_use] extern crate failure; +extern crate resiter; extern crate libimagcontact; extern crate libimagstore; @@ -54,7 +55,6 @@ extern crate libimaginteraction; extern crate libimagentryedit; extern crate libimagentryref; -use std::process::exit; use std::path::PathBuf; use std::io::Write; @@ -67,13 +67,13 @@ use walkdir::WalkDir; use failure::Error; use failure::err_msg; use failure::Fallible as Result; +use resiter::AndThen; +use resiter::IterInnerOkOrElse; +use resiter::Map; +use resiter::Filter; use libimagrt::runtime::Runtime; use libimagrt::application::ImagApplication; -use libimagerror::trace::MapErrTrace; -use libimagerror::io::ToExitCode; -use libimagerror::exit::ExitUnwrap; -use libimagerror::iter::TraceIterator; use libimagcontact::store::ContactStore; use libimagcontact::contact::Contact; use libimagcontact::deser::DeserVcard; @@ -94,26 +94,22 @@ use crate::edit::edit; pub enum ImagContact {} impl ImagApplication for ImagContact { fn run(rt: Runtime) -> Result<()> { - if let Some(name) = rt.cli().subcommand_name() { - debug!("Call {}", name); - match name { - "list" => list(&rt), - "import" => import(&rt), - "show" => show(&rt), - "edit" => edit(&rt), - "find" => find(&rt), - "create" => create(&rt), - other => { - debug!("Unknown command"); - let _ = rt.handle_unknown_subcommand("imag-contact", other, rt.cli()) - .map_err_trace_exit_unwrap() - .code() - .map(::std::process::exit); - }, - } + match rt.cli().subcommand_name().ok_or_else(|| err_msg("No subcommand called"))? { + "list" => list(&rt), + "import" => import(&rt), + "show" => show(&rt), + "edit" => edit(&rt), + "find" => find(&rt), + "create" => create(&rt), + other => { + debug!("Unknown command"); + if rt.handle_unknown_subcommand("imag-contact", other, rt.cli())?.success() { + Ok(()) + } else { + Err(err_msg("Failed to handle unknown subcommand")) + } + }, } - - Ok(()) } fn build_cli<'a>(app: App<'a, 'a>) -> App<'a, 'a> { @@ -133,128 +129,117 @@ impl ImagApplication for ImagContact { } } -fn list(rt: &Runtime) { +fn list(rt: &Runtime) -> Result<()> { let scmd = rt.cli().subcommand_matches("list").unwrap(); - let list_format = get_contact_print_format("contact.list_format", rt, &scmd); + let list_format = get_contact_print_format("contact.list_format", rt, &scmd)?; debug!("List format: {:?}", list_format); let iterator = rt .store() - .all_contacts() - .map_err_trace_exit_unwrap() + .all_contacts()? .into_get_iter() - .trace_unwrap_exit() - .map(|fle| fle.ok_or_else(|| err_msg("StoreId not found".to_owned()))) - .trace_unwrap_exit() - .map(|fle| { - rt.report_touched(fle.get_location()).unwrap_or_exit(); - fle + .map_inner_ok_or_else(|| err_msg("Did not find one entry")) + .and_then_ok(|fle| { + rt.report_touched(fle.get_location())?; + Ok(fle) }) - .map(|e| e.deser()) - .trace_unwrap_exit() - .enumerate(); + .and_then_ok(|e| e.deser()); if scmd.is_present("json") { debug!("Listing as JSON"); - let v : Vec = iterator.map(|tpl| tpl.1).collect(); - - match ::serde_json::to_string(&v) { - Ok(s) => writeln!(rt.stdout(), "{}", s).to_exit_code().unwrap_or_exit(), - Err(e) => { - error!("Error generating JSON: {:?}", e); - ::std::process::exit(1) - } - } + let v = iterator.collect::>>()?; + let s = ::serde_json::to_string(&v)?; + writeln!(rt.stdout(), "{}", s).map_err(Error::from) } else { debug!("Not listing as JSON"); let output = rt.stdout(); let mut output = output.lock(); + let mut i = 0; iterator - .map(|(i, dvcard)| build_data_object_for_handlebars(i, &dvcard)) - .map(|data| list_format.render("format", &data).map_err(Error::from)) - .trace_unwrap_exit() - .for_each(|s| { - writeln!(output, "{}", s).to_exit_code().unwrap_or_exit() - }); + .map_ok(|dvcard| { + i += 1; + build_data_object_for_handlebars(i, &dvcard) + }) + .and_then_ok(|data| list_format.render("format", &data).map_err(Error::from)) + .and_then_ok(|s| writeln!(output, "{}", s).map_err(Error::from)) + .collect::>>() + .map(|_| ()) } } -fn import(rt: &Runtime) { +fn import(rt: &Runtime) -> Result<()> { let scmd = rt.cli().subcommand_matches("import").unwrap(); // secured by main let force_override = scmd.is_present("force-override"); let path = scmd.value_of("path").map(PathBuf::from).unwrap(); // secured by clap let collection_name = rt.cli().value_of("contact-ref-collection-name").unwrap(); // default by clap let ref_config = rt.config() - .ok_or_else(|| format_err!("No configuration, cannot continue!")) - .map_err_trace_exit_unwrap() - .read_partial::() - .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(|| format_err!("No configuration, cannot continue!"))? + .read_partial::()? + .ok_or_else(|| format_err!("Configuration missing: {}", libimagentryref::reference::Config::LOCATION))?; + // TODO: Refactor the above to libimagutil or libimagrt? - if !path.exists() { - error!("Path does not exist"); - exit(1) + return Err(format_err!("Path does not exist: {}", path.display())) } if path.is_file() { let entry = rt .store() - .retrieve_from_path(&path, &ref_config, &collection_name, force_override) - .map_err_trace_exit_unwrap(); + .retrieve_from_path(&path, &ref_config, &collection_name, force_override)?; - rt.report_touched(entry.get_location()).unwrap_or_exit(); + rt.report_touched(entry.get_location()).map_err(Error::from) } else if path.is_dir() { - for entry in WalkDir::new(path).min_depth(1).into_iter() { - let entry = entry - .map_err(Error::from) - .map_err_trace_exit_unwrap(); + WalkDir::new(path) + .min_depth(1) + .into_iter() + .map(|r| r.map_err(Error::from)) + .and_then_ok(|entry| { + if entry.file_type().is_file() { + let pb = PathBuf::from(entry.path()); + let fle = rt + .store() + .retrieve_from_path(&pb, &ref_config, &collection_name, force_override)?; - if entry.file_type().is_file() { - let pb = PathBuf::from(entry.path()); - let fle = rt - .store() - .retrieve_from_path(&pb, &ref_config, &collection_name, force_override) - .map_err_trace_exit_unwrap(); - - rt.report_touched(fle.get_location()).unwrap_or_exit(); - info!("Imported: {}", entry.path().to_str().unwrap_or("")); - } else { - warn!("Ignoring non-file: {}", entry.path().to_str().unwrap_or("")); - } - } + rt.report_touched(fle.get_location())?; + info!("Imported: {}", entry.path().to_str().unwrap_or("")); + Ok(()) + } else { + warn!("Ignoring non-file: {}", entry.path().to_str().unwrap_or("")); + Ok(()) + } + }) + .collect::>>() + .map(|_| ()) } else { - error!("Path is neither directory nor file"); - exit(1) + Err(err_msg("Path is neither directory nor file")) } } -fn show(rt: &Runtime) { +fn show(rt: &Runtime) -> Result<()> { let scmd = rt.cli().subcommand_matches("show").unwrap(); let hash = scmd.value_of("hash").map(String::from).unwrap(); // safed by clap - let show_format = get_contact_print_format("contact.show_format", rt, &scmd); + let show_format = get_contact_print_format("contact.show_format", rt, &scmd)?; let out = rt.stdout(); let mut outlock = out.lock(); - util::find_contact_by_hash(rt, hash) + util::find_contact_by_hash(rt, hash)? + .filter_ok(|tpl| tpl.0) + .map_ok(|tpl| tpl.1) .enumerate() - .for_each(|(i, elem)| { - let elem = elem.deser().map_err_trace_exit_unwrap(); + .map(|(i, elem)| { + let elem = elem?.deser()?; let data = build_data_object_for_handlebars(i, &elem); - let s = show_format - .render("format", &data) - .map_err(Error::from) - .map_err_trace_exit_unwrap(); - writeln!(outlock, "{}", s).to_exit_code().unwrap_or_exit(); - }); + let s = show_format.render("format", &data)?; + writeln!(outlock, "{}", s).map_err(Error::from) + }) + .collect::>>() + .map(|_| ()) } -fn find(rt: &Runtime) { +fn find(rt: &Runtime) -> Result<()> { let scmd = rt.cli().subcommand_matches("find").unwrap(); let grepstring = scmd .values_of("string") @@ -263,24 +248,16 @@ fn find(rt: &Runtime) { .collect::>(); // We don't know yet which we need, but we pay that price for simplicity of the codebase - let show_format = get_contact_print_format("contact.show_format", rt, &scmd); - let list_format = get_contact_print_format("contact.list_format", rt, &scmd); + let show_format = get_contact_print_format("contact.show_format", rt, &scmd)?; + let list_format = get_contact_print_format("contact.list_format", rt, &scmd)?; let iterator = rt .store() - .all_contacts() - .map_err_trace_exit_unwrap() + .all_contacts()? .into_get_iter() - .map(|el| { - el.map_err_trace_exit_unwrap() - .ok_or_else(|| { - error!("Could not get StoreId from Store::all_contacts(). This is a BUG!"); - ::std::process::exit(1) - }) - .unwrap() // safed above - }) - .filter_map(|entry| { - let card = entry.deser().map_err_trace_exit_unwrap(); + .map_inner_ok_or_else(|| err_msg("Did not find one entry")) + .and_then_ok(|entry| { + let card = entry.deser()?; let str_contains_any = |s: &String, v: &Vec| { v.iter().any(|i| s.contains(i)) @@ -291,91 +268,94 @@ fn find(rt: &Runtime) { || card.fullname().iter().any(|a| str_contains_any(a, &grepstring)); if take { - rt.report_touched(entry.get_location()).unwrap_or_exit(); + rt.report_touched(entry.get_location())?; // optimization so we don't have to parse again in the next step - Some((entry, card)) + Ok((true, entry, card)) } else { - None + Ok((false, entry, card)) } - }) - .enumerate(); + }); + + let mut i = 0; if !rt.output_is_pipe() || rt.ignore_ids() { if scmd.is_present("json") { - let v : Vec = iterator.map(|(_, tlp)| tlp.1).collect(); - - match ::serde_json::to_string(&v) { - Ok(s) => writeln!(rt.stdout(), "{}", s).to_exit_code().unwrap_or_exit(), - Err(e) => { - error!("Error generating JSON: {:?}", e); - ::std::process::exit(1) - } - } + iterator + .filter_ok(|tpl| tpl.0) + .map_ok(|tpl| tpl.2) + .and_then_ok(|v| { + let s = ::serde_json::to_string(&v)?; + writeln!(rt.stdout(), "{}", s).map_err(Error::from) + }) + .collect::>>() + .map(|_| ()) } else if scmd.is_present("find-id") { iterator - .for_each(|(_i, (entry, _))| { - writeln!(rt.stdout(), "{}", entry.get_location()) - .to_exit_code() - .unwrap_or_exit(); + .and_then_ok(|(take, entry, _)| { + if take { + writeln!(rt.stdout(), "{}", entry.get_location()).map_err(Error::from) + } else { + Ok(()) + } }) + .collect::>>() + .map(|_| ()) } else if scmd.is_present("find-full-id") { let storepath = rt.store().path().display(); iterator - .for_each(|(_i, (entry, _))| { - writeln!(rt.stdout(), "{}/{}", storepath, entry.get_location()) - .to_exit_code() - .unwrap_or_exit(); + .and_then_ok(|(take, entry, _)| { + if take { + writeln!(rt.stdout(), "{}/{}", storepath, entry.get_location()).map_err(Error::from) + } else { + Ok(()) + } }) + .collect::>>() + .map(|_| ()) } else { iterator - .for_each(|(i, (_, card))| { - let fmt = if scmd.is_present("find-show") { - &show_format - } else { // default: find-list - &list_format - }; + .and_then_ok(|(take, _, card)| { + if take { + i += 1; + let fmt = if scmd.is_present("find-show") { + &show_format + } else { // default: find-list + &list_format + }; - let data = build_data_object_for_handlebars(i, &card); - let s = fmt - .render("format", &data) - .map_err(Error::from) - .map_err_trace_exit_unwrap(); + let data = build_data_object_for_handlebars(i, &card); + let s = fmt.render("format", &data)?; - writeln!(rt.stdout(), "{}", s) - .to_exit_code() - .unwrap_or_exit(); - }); + writeln!(rt.stdout(), "{}", s).map_err(Error::from) + } else { + Ok(()) + } + }) + .collect::>>() + .map(|_| ()) } } else { // if not printing, we still have to consume the iterator to report the touched IDs let _ = iterator.collect::>(); + Ok(()) } } -fn get_contact_print_format(config_value_path: &'static str, rt: &Runtime, scmd: &ArgMatches) -> Handlebars { - let fmt = scmd - .value_of("format") - .map(String::from) - .unwrap_or_else(|| { - rt.config() - .ok_or_else(|| err_msg("No configuration file")) - .map_err_trace_exit_unwrap() - .read_string(config_value_path) - .map_err(Error::from) - .map_err_trace_exit_unwrap() - .ok_or_else(|| err_msg("Configuration 'contact.list_format' does not exist")) - .map_err_trace_exit_unwrap() - }); +fn get_contact_print_format(config_value_path: &'static str, rt: &Runtime, scmd: &ArgMatches) -> Result { + let fmt = match scmd.value_of("format").map(String::from) { + Some(s) => Ok(s), + None => rt.config() + .ok_or_else(|| err_msg("No configuration file"))? + .read_string(config_value_path)? + .ok_or_else(|| err_msg("Configuration 'contact.list_format' does not exist")), + }?; let mut hb = Handlebars::new(); - hb - .register_template_string("format", fmt) - .map_err(Error::from) - .map_err_trace_exit_unwrap(); + hb.register_template_string("format", fmt)?; hb.register_escape_fn(::handlebars::no_escape); ::libimaginteraction::format::register_all_color_helpers(&mut hb); ::libimaginteraction::format::register_all_format_helpers(&mut hb); - hb + Ok(hb) } diff --git a/bin/domain/imag-contact/src/util.rs b/bin/domain/imag-contact/src/util.rs index 61d061cc..0910f644 100644 --- a/bin/domain/imag-contact/src/util.rs +++ b/bin/domain/imag-contact/src/util.rs @@ -18,14 +18,15 @@ // use std::collections::BTreeMap; -use std::process::exit; + +use failure::Fallible as Result; +use failure::err_msg; +use resiter::IterInnerOkOrElse; +use resiter::AndThen; use libimagcontact::deser::DeserVcard; use libimagcontact::store::ContactStore; use libimagcontact::contact::Contact; -use libimagerror::exit::ExitUnwrap; -use libimagerror::iter::TraceIterator; -use libimagerror::trace::MapErrTrace; use libimagrt::runtime::Runtime; use libimagstore::store::FileLockEntry; @@ -85,34 +86,24 @@ pub fn build_data_object_for_handlebars(i: usize, vcard: &DeserVcard) -> BTreeMa } pub fn find_contact_by_hash<'a, H: AsRef>(rt: &'a Runtime, hash: H) - -> impl Iterator> + -> Result)>>> { - rt.store() - .all_contacts() - .map_err_trace_exit_unwrap() + Ok(rt.store() + .all_contacts()? .into_get_iter() - .trace_unwrap_exit() - .map(|o| o.unwrap_or_else(|| { - error!("Failed to get entry"); - exit(1) - })) - .filter(move |entry| { - let deser = entry.deser().map_err_trace_exit_unwrap(); + .map_inner_ok_or_else(|| err_msg("Did not find one entry")) + .and_then_ok(move |entry| { + let deser = entry.deser()?; let id_starts_with_hash = deser.uid() - .ok_or_else(|| { - error!("Could not get StoreId from Store::all_contacts(). This is a BUG!"); - ::std::process::exit(1) - }) - .unwrap() // exited above + .ok_or_else(|| err_msg("Could not get StoreId from Store::all_contacts(). This is a BUG!"))? .starts_with(hash.as_ref()); if id_starts_with_hash { - rt.report_touched(entry.get_location()).unwrap_or_exit(); - true - } else { - false + rt.report_touched(entry.get_location())?; } - }) + + Ok((id_starts_with_hash, entry)) + })) }