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 <mail@beyermatthias.de>
This commit is contained in:
Matthias Beyer 2019-12-01 16:37:04 +01:00
parent c2d4ec5fef
commit 3a3f9ebeed
5 changed files with 310 additions and 378 deletions

View File

@ -28,6 +28,7 @@ walkdir = "2.2.8"
uuid = { version = "0.7.4", features = ["v4"] } uuid = { version = "0.7.4", features = ["v4"] }
serde_json = "1.0.39" serde_json = "1.0.39"
failure = "0.1.5" failure = "0.1.5"
resiter = "0.4"
libimagrt = { version = "0.10.0", path = "../../../lib/core/libimagrt" } libimagrt = { version = "0.10.0", path = "../../../lib/core/libimagrt" }
libimagstore = { version = "0.10.0", path = "../../../lib/core/libimagstore" } libimagstore = { version = "0.10.0", path = "../../../lib/core/libimagstore" }

View File

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

View File

@ -32,16 +32,16 @@
while_true, while_true,
)] )]
use std::process::exit;
use std::io::Read; use std::io::Read;
use std::io::Write; use std::io::Write;
use failure::Error;
use failure::err_msg; use failure::err_msg;
use failure::Fallible as Result; use failure::Fallible as Result;
use resiter::Filter;
use resiter::Map;
use resiter::AndThen;
use libimagrt::runtime::Runtime; use libimagrt::runtime::Runtime;
use libimagerror::trace::MapErrTrace;
use libimagstore::store::FileLockEntry; use libimagstore::store::FileLockEntry;
use libimagcontact::store::ContactStore; use libimagcontact::store::ContactStore;
use libimagentryref::reference::fassade::RefFassade; use libimagentryref::reference::fassade::RefFassade;
@ -49,61 +49,56 @@ use libimagentryref::hasher::default::DefaultHasher;
use libimagentryref::reference::Ref; use libimagentryref::reference::Ref;
use libimagentryref::reference::Config as RefConfig; 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 scmd = rt.cli().subcommand_matches("edit").unwrap();
let collection_name = rt.cli().value_of("contact-ref-collection-name").unwrap(); // default by clap 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 hash = scmd.value_of("hash").map(String::from).unwrap(); // safed by clap
let force_override = true; // when editing, we want to override, right? let force_override = true; // when editing, we want to override, right?
let retry = !scmd.is_present("fail-on-parse-error"); let retry = !scmd.is_present("fail-on-parse-error");
if rt.output_is_pipe() { if rt.output_is_pipe() {
error!("Cannot spawn editor if output is a pipe!"); return Err(err_msg("Cannot spawn editor if output is a pipe!"))
exit(1);
} }
let mut output = rt.stdout(); let mut output = rt.stdout();
let mut input = rt.stdin().unwrap_or_else(|| { let mut input = rt.stdin().ok_or_else(|| {
error!("No input stream. Cannot ask for permission."); err_msg("No input stream. Cannot ask for permission.")
exit(1) })?;
});
crate::util::find_contact_by_hash(rt, hash) crate::util::find_contact_by_hash(rt, hash)?
.for_each(|contact| { .filter_ok(|tpl| tpl.0)
.map_ok(|tpl| tpl.1)
.and_then_ok(|contact| {
loop { loop {
let res = edit_contact(&rt, &contact, &ref_config, collection_name, force_override); let res = edit_contact(&rt, &contact, &ref_config, collection_name, force_override);
if !retry { if !retry {
res.map_err_trace_exit_unwrap(); return res
} else if ask_continue(&mut input, &mut output) { } else if ask_continue(&mut input, &mut output)? {
continue; continue;
} else { } else {
exit(1) return res
} }
} }
}); })
.collect::<Result<Vec<_>>>()
.map(|_| ())
} }
fn edit_contact<'a>(rt: &Runtime, contact: &FileLockEntry<'a>, ref_config: &RefConfig, collection_name: &str, force_override: bool) -> Result<()> { fn edit_contact<'a>(rt: &Runtime, contact: &FileLockEntry<'a>, ref_config: &RefConfig, collection_name: &str, force_override: bool) -> Result<()> {
let filepath = contact let filepath = contact
.as_ref_with_hasher::<DefaultHasher>() .as_ref_with_hasher::<DefaultHasher>()
.get_path(ref_config) .get_path(ref_config)?;
.map_err_trace_exit_unwrap();
let success = rt.editor() let success = rt.editor()?
.map_err_trace_exit_unwrap() .ok_or_else(|| err_msg("I have no editor configured. Cannot continue!"))?
.ok_or_else(|| {
err_msg("I have no editor configured. Cannot continue!")
})
.map_err_trace_exit_unwrap()
.arg(&filepath) .arg(&filepath)
.status() .status()?
.map_err(Error::from)
.map_err_trace_exit_unwrap()
.success(); .success();
if !success { if !success {
error!("Editor failed!"); return Err(err_msg("Editor failed!"))
exit(1);
} }
rt.store() rt.store()
@ -111,8 +106,7 @@ fn edit_contact<'a>(rt: &Runtime, contact: &FileLockEntry<'a>, ref_config: &RefC
.map(|_| ()) .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<bool> {
::libimaginteraction::ask::ask_bool("Edit vcard", Some(true), inputstream, outputstream) ::libimaginteraction::ask::ask_bool("Edit vcard", Some(true), inputstream, outputstream)
.map_err_trace_exit_unwrap()
} }

View File

@ -44,6 +44,7 @@ extern crate walkdir;
extern crate uuid; extern crate uuid;
extern crate serde_json; extern crate serde_json;
#[macro_use] extern crate failure; #[macro_use] extern crate failure;
extern crate resiter;
extern crate libimagcontact; extern crate libimagcontact;
extern crate libimagstore; extern crate libimagstore;
@ -54,7 +55,6 @@ extern crate libimaginteraction;
extern crate libimagentryedit; extern crate libimagentryedit;
extern crate libimagentryref; extern crate libimagentryref;
use std::process::exit;
use std::path::PathBuf; use std::path::PathBuf;
use std::io::Write; use std::io::Write;
@ -67,13 +67,13 @@ use walkdir::WalkDir;
use failure::Error; use failure::Error;
use failure::err_msg; use failure::err_msg;
use failure::Fallible as Result; use failure::Fallible as Result;
use resiter::AndThen;
use resiter::IterInnerOkOrElse;
use resiter::Map;
use resiter::Filter;
use libimagrt::runtime::Runtime; use libimagrt::runtime::Runtime;
use libimagrt::application::ImagApplication; 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::store::ContactStore;
use libimagcontact::contact::Contact; use libimagcontact::contact::Contact;
use libimagcontact::deser::DeserVcard; use libimagcontact::deser::DeserVcard;
@ -94,26 +94,22 @@ use crate::edit::edit;
pub enum ImagContact {} pub enum ImagContact {}
impl ImagApplication for ImagContact { impl ImagApplication for ImagContact {
fn run(rt: Runtime) -> Result<()> { fn run(rt: Runtime) -> Result<()> {
if let Some(name) = rt.cli().subcommand_name() { match rt.cli().subcommand_name().ok_or_else(|| err_msg("No subcommand called"))? {
debug!("Call {}", name); "list" => list(&rt),
match name { "import" => import(&rt),
"list" => list(&rt), "show" => show(&rt),
"import" => import(&rt), "edit" => edit(&rt),
"show" => show(&rt), "find" => find(&rt),
"edit" => edit(&rt), "create" => create(&rt),
"find" => find(&rt), other => {
"create" => create(&rt), debug!("Unknown command");
other => { if rt.handle_unknown_subcommand("imag-contact", other, rt.cli())?.success() {
debug!("Unknown command"); Ok(())
let _ = rt.handle_unknown_subcommand("imag-contact", other, rt.cli()) } else {
.map_err_trace_exit_unwrap() Err(err_msg("Failed to handle unknown subcommand"))
.code() }
.map(::std::process::exit); },
},
}
} }
Ok(())
} }
fn build_cli<'a>(app: App<'a, 'a>) -> App<'a, 'a> { 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 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); debug!("List format: {:?}", list_format);
let iterator = rt let iterator = rt
.store() .store()
.all_contacts() .all_contacts()?
.map_err_trace_exit_unwrap()
.into_get_iter() .into_get_iter()
.trace_unwrap_exit() .map_inner_ok_or_else(|| err_msg("Did not find one entry"))
.map(|fle| fle.ok_or_else(|| err_msg("StoreId not found".to_owned()))) .and_then_ok(|fle| {
.trace_unwrap_exit() rt.report_touched(fle.get_location())?;
.map(|fle| { Ok(fle)
rt.report_touched(fle.get_location()).unwrap_or_exit();
fle
}) })
.map(|e| e.deser()) .and_then_ok(|e| e.deser());
.trace_unwrap_exit()
.enumerate();
if scmd.is_present("json") { if scmd.is_present("json") {
debug!("Listing as JSON"); debug!("Listing as JSON");
let v : Vec<DeserVcard> = iterator.map(|tpl| tpl.1).collect(); let v = iterator.collect::<Result<Vec<DeserVcard>>>()?;
let s = ::serde_json::to_string(&v)?;
match ::serde_json::to_string(&v) { writeln!(rt.stdout(), "{}", s).map_err(Error::from)
Ok(s) => writeln!(rt.stdout(), "{}", s).to_exit_code().unwrap_or_exit(),
Err(e) => {
error!("Error generating JSON: {:?}", e);
::std::process::exit(1)
}
}
} else { } else {
debug!("Not listing as JSON"); debug!("Not listing as JSON");
let output = rt.stdout(); let output = rt.stdout();
let mut output = output.lock(); let mut output = output.lock();
let mut i = 0;
iterator iterator
.map(|(i, dvcard)| build_data_object_for_handlebars(i, &dvcard)) .map_ok(|dvcard| {
.map(|data| list_format.render("format", &data).map_err(Error::from)) i += 1;
.trace_unwrap_exit() build_data_object_for_handlebars(i, &dvcard)
.for_each(|s| { })
writeln!(output, "{}", s).to_exit_code().unwrap_or_exit() .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::<Result<Vec<_>>>()
.map(|_| ())
} }
} }
fn import(rt: &Runtime) { fn import(rt: &Runtime) -> Result<()> {
let scmd = rt.cli().subcommand_matches("import").unwrap(); // secured by main let scmd = rt.cli().subcommand_matches("import").unwrap(); // secured by main
let force_override = scmd.is_present("force-override"); let force_override = scmd.is_present("force-override");
let path = scmd.value_of("path").map(PathBuf::from).unwrap(); // secured by clap 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 collection_name = rt.cli().value_of("contact-ref-collection-name").unwrap(); // default by clap
let ref_config = rt.config() let ref_config = rt.config()
.ok_or_else(|| format_err!("No configuration, cannot continue!")) .ok_or_else(|| format_err!("No configuration, cannot continue!"))?
.map_err_trace_exit_unwrap() .read_partial::<libimagentryref::reference::Config>()?
.read_partial::<libimagentryref::reference::Config>() .ok_or_else(|| format_err!("Configuration missing: {}", libimagentryref::reference::Config::LOCATION))?;
.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();
// TODO: Refactor the above to libimagutil or libimagrt? // TODO: Refactor the above to libimagutil or libimagrt?
if !path.exists() { if !path.exists() {
error!("Path does not exist"); return Err(format_err!("Path does not exist: {}", path.display()))
exit(1)
} }
if path.is_file() { if path.is_file() {
let entry = rt let entry = rt
.store() .store()
.retrieve_from_path(&path, &ref_config, &collection_name, force_override) .retrieve_from_path(&path, &ref_config, &collection_name, force_override)?;
.map_err_trace_exit_unwrap();
rt.report_touched(entry.get_location()).unwrap_or_exit(); rt.report_touched(entry.get_location()).map_err(Error::from)
} else if path.is_dir() { } else if path.is_dir() {
for entry in WalkDir::new(path).min_depth(1).into_iter() { WalkDir::new(path)
let entry = entry .min_depth(1)
.map_err(Error::from) .into_iter()
.map_err_trace_exit_unwrap(); .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() { rt.report_touched(fle.get_location())?;
let pb = PathBuf::from(entry.path()); info!("Imported: {}", entry.path().to_str().unwrap_or("<non UTF-8 path>"));
let fle = rt Ok(())
.store() } else {
.retrieve_from_path(&pb, &ref_config, &collection_name, force_override) warn!("Ignoring non-file: {}", entry.path().to_str().unwrap_or("<non UTF-8 path>"));
.map_err_trace_exit_unwrap(); Ok(())
}
rt.report_touched(fle.get_location()).unwrap_or_exit(); })
info!("Imported: {}", entry.path().to_str().unwrap_or("<non UTF-8 path>")); .collect::<Result<Vec<_>>>()
} else { .map(|_| ())
warn!("Ignoring non-file: {}", entry.path().to_str().unwrap_or("<non UTF-8 path>"));
}
}
} else { } else {
error!("Path is neither directory nor file"); Err(err_msg("Path is neither directory nor file"))
exit(1)
} }
} }
fn show(rt: &Runtime) { fn show(rt: &Runtime) -> Result<()> {
let scmd = rt.cli().subcommand_matches("show").unwrap(); let scmd = rt.cli().subcommand_matches("show").unwrap();
let hash = scmd.value_of("hash").map(String::from).unwrap(); // safed by clap 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 out = rt.stdout();
let mut outlock = out.lock(); 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() .enumerate()
.for_each(|(i, elem)| { .map(|(i, elem)| {
let elem = elem.deser().map_err_trace_exit_unwrap(); let elem = elem?.deser()?;
let data = build_data_object_for_handlebars(i, &elem); let data = build_data_object_for_handlebars(i, &elem);
let s = show_format let s = show_format.render("format", &data)?;
.render("format", &data) writeln!(outlock, "{}", s).map_err(Error::from)
.map_err(Error::from) })
.map_err_trace_exit_unwrap(); .collect::<Result<Vec<_>>>()
writeln!(outlock, "{}", s).to_exit_code().unwrap_or_exit(); .map(|_| ())
});
} }
fn find(rt: &Runtime) { fn find(rt: &Runtime) -> Result<()> {
let scmd = rt.cli().subcommand_matches("find").unwrap(); let scmd = rt.cli().subcommand_matches("find").unwrap();
let grepstring = scmd let grepstring = scmd
.values_of("string") .values_of("string")
@ -263,24 +248,16 @@ fn find(rt: &Runtime) {
.collect::<Vec<String>>(); .collect::<Vec<String>>();
// We don't know yet which we need, but we pay that price for simplicity of the codebase // 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 show_format = get_contact_print_format("contact.show_format", rt, &scmd)?;
let list_format = get_contact_print_format("contact.list_format", rt, &scmd); let list_format = get_contact_print_format("contact.list_format", rt, &scmd)?;
let iterator = rt let iterator = rt
.store() .store()
.all_contacts() .all_contacts()?
.map_err_trace_exit_unwrap()
.into_get_iter() .into_get_iter()
.map(|el| { .map_inner_ok_or_else(|| err_msg("Did not find one entry"))
el.map_err_trace_exit_unwrap() .and_then_ok(|entry| {
.ok_or_else(|| { let card = entry.deser()?;
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();
let str_contains_any = |s: &String, v: &Vec<String>| { let str_contains_any = |s: &String, v: &Vec<String>| {
v.iter().any(|i| s.contains(i)) 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)); || card.fullname().iter().any(|a| str_contains_any(a, &grepstring));
if take { 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 // optimization so we don't have to parse again in the next step
Some((entry, card)) Ok((true, entry, card))
} else { } else {
None Ok((false, entry, card))
} }
}) });
.enumerate();
let mut i = 0;
if !rt.output_is_pipe() || rt.ignore_ids() { if !rt.output_is_pipe() || rt.ignore_ids() {
if scmd.is_present("json") { if scmd.is_present("json") {
let v : Vec<DeserVcard> = iterator.map(|(_, tlp)| tlp.1).collect(); iterator
.filter_ok(|tpl| tpl.0)
match ::serde_json::to_string(&v) { .map_ok(|tpl| tpl.2)
Ok(s) => writeln!(rt.stdout(), "{}", s).to_exit_code().unwrap_or_exit(), .and_then_ok(|v| {
Err(e) => { let s = ::serde_json::to_string(&v)?;
error!("Error generating JSON: {:?}", e); writeln!(rt.stdout(), "{}", s).map_err(Error::from)
::std::process::exit(1) })
} .collect::<Result<Vec<_>>>()
} .map(|_| ())
} else if scmd.is_present("find-id") { } else if scmd.is_present("find-id") {
iterator iterator
.for_each(|(_i, (entry, _))| { .and_then_ok(|(take, entry, _)| {
writeln!(rt.stdout(), "{}", entry.get_location()) if take {
.to_exit_code() writeln!(rt.stdout(), "{}", entry.get_location()).map_err(Error::from)
.unwrap_or_exit(); } else {
Ok(())
}
}) })
.collect::<Result<Vec<_>>>()
.map(|_| ())
} else if scmd.is_present("find-full-id") { } else if scmd.is_present("find-full-id") {
let storepath = rt.store().path().display(); let storepath = rt.store().path().display();
iterator iterator
.for_each(|(_i, (entry, _))| { .and_then_ok(|(take, entry, _)| {
writeln!(rt.stdout(), "{}/{}", storepath, entry.get_location()) if take {
.to_exit_code() writeln!(rt.stdout(), "{}/{}", storepath, entry.get_location()).map_err(Error::from)
.unwrap_or_exit(); } else {
Ok(())
}
}) })
.collect::<Result<Vec<_>>>()
.map(|_| ())
} else { } else {
iterator iterator
.for_each(|(i, (_, card))| { .and_then_ok(|(take, _, card)| {
let fmt = if scmd.is_present("find-show") { if take {
&show_format i += 1;
} else { // default: find-list let fmt = if scmd.is_present("find-show") {
&list_format &show_format
}; } else { // default: find-list
&list_format
};
let data = build_data_object_for_handlebars(i, &card); let data = build_data_object_for_handlebars(i, &card);
let s = fmt let s = fmt.render("format", &data)?;
.render("format", &data)
.map_err(Error::from)
.map_err_trace_exit_unwrap();
writeln!(rt.stdout(), "{}", s) writeln!(rt.stdout(), "{}", s).map_err(Error::from)
.to_exit_code() } else {
.unwrap_or_exit(); Ok(())
}); }
})
.collect::<Result<Vec<_>>>()
.map(|_| ())
} }
} else { // if not printing, we still have to consume the iterator to report the touched IDs } else { // if not printing, we still have to consume the iterator to report the touched IDs
let _ = iterator.collect::<Vec<_>>(); let _ = iterator.collect::<Vec<_>>();
Ok(())
} }
} }
fn get_contact_print_format(config_value_path: &'static str, rt: &Runtime, scmd: &ArgMatches) -> Handlebars { fn get_contact_print_format(config_value_path: &'static str, rt: &Runtime, scmd: &ArgMatches) -> Result<Handlebars> {
let fmt = scmd let fmt = match scmd.value_of("format").map(String::from) {
.value_of("format") Some(s) => Ok(s),
.map(String::from) None => rt.config()
.unwrap_or_else(|| { .ok_or_else(|| err_msg("No configuration file"))?
rt.config() .read_string(config_value_path)?
.ok_or_else(|| err_msg("No configuration file")) .ok_or_else(|| err_msg("Configuration 'contact.list_format' does not exist")),
.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()
});
let mut hb = Handlebars::new(); let mut hb = Handlebars::new();
hb hb.register_template_string("format", fmt)?;
.register_template_string("format", fmt)
.map_err(Error::from)
.map_err_trace_exit_unwrap();
hb.register_escape_fn(::handlebars::no_escape); hb.register_escape_fn(::handlebars::no_escape);
::libimaginteraction::format::register_all_color_helpers(&mut hb); ::libimaginteraction::format::register_all_color_helpers(&mut hb);
::libimaginteraction::format::register_all_format_helpers(&mut hb); ::libimaginteraction::format::register_all_format_helpers(&mut hb);
hb Ok(hb)
} }

View File

@ -18,14 +18,15 @@
// //
use std::collections::BTreeMap; 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::deser::DeserVcard;
use libimagcontact::store::ContactStore; use libimagcontact::store::ContactStore;
use libimagcontact::contact::Contact; use libimagcontact::contact::Contact;
use libimagerror::exit::ExitUnwrap;
use libimagerror::iter::TraceIterator;
use libimagerror::trace::MapErrTrace;
use libimagrt::runtime::Runtime; use libimagrt::runtime::Runtime;
use libimagstore::store::FileLockEntry; 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<str>>(rt: &'a Runtime, hash: H) pub fn find_contact_by_hash<'a, H: AsRef<str>>(rt: &'a Runtime, hash: H)
-> impl Iterator<Item = FileLockEntry<'a>> -> Result<impl Iterator<Item = Result<(bool, FileLockEntry<'a>)>>>
{ {
rt.store() Ok(rt.store()
.all_contacts() .all_contacts()?
.map_err_trace_exit_unwrap()
.into_get_iter() .into_get_iter()
.trace_unwrap_exit() .map_inner_ok_or_else(|| err_msg("Did not find one entry"))
.map(|o| o.unwrap_or_else(|| { .and_then_ok(move |entry| {
error!("Failed to get entry"); let deser = entry.deser()?;
exit(1)
}))
.filter(move |entry| {
let deser = entry.deser().map_err_trace_exit_unwrap();
let id_starts_with_hash = deser.uid() let id_starts_with_hash = deser.uid()
.ok_or_else(|| { .ok_or_else(|| err_msg("Could not get StoreId from Store::all_contacts(). This is a BUG!"))?
error!("Could not get StoreId from Store::all_contacts(). This is a BUG!");
::std::process::exit(1)
})
.unwrap() // exited above
.starts_with(hash.as_ref()); .starts_with(hash.as_ref());
if id_starts_with_hash { if id_starts_with_hash {
rt.report_touched(entry.get_location()).unwrap_or_exit(); rt.report_touched(entry.get_location())?;
true
} else {
false
} }
})
Ok((id_starts_with_hash, entry))
}))
} }