Merge pull request #1338 from matthiasbeyer/libimagcontact/to-json-output

libimagcontact/imag-contact: json output support
This commit is contained in:
Matthias Beyer 2018-03-12 18:35:10 +01:00 committed by GitHub
commit 5609651f11
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 271 additions and 28 deletions

View file

@ -29,16 +29,22 @@ handlebars = "0.29"
vobject = "0.4" vobject = "0.4"
walkdir = "1" walkdir = "1"
uuid = { version = "0.6", features = ["v4"] } uuid = { version = "0.6", features = ["v4"] }
serde_json = "1"
libimagrt = { version = "0.7.0", path = "../../../lib/core/libimagrt" } libimagrt = { version = "0.7.0", path = "../../../lib/core/libimagrt" }
libimagstore = { version = "0.7.0", path = "../../../lib/core/libimagstore" } libimagstore = { version = "0.7.0", path = "../../../lib/core/libimagstore" }
libimagerror = { version = "0.7.0", path = "../../../lib/core/libimagerror" } libimagerror = { version = "0.7.0", path = "../../../lib/core/libimagerror" }
libimagcontact = { version = "0.7.0", path = "../../../lib/domain/libimagcontact" }
libimagutil = { version = "0.7.0", path = "../../../lib/etc/libimagutil" } libimagutil = { version = "0.7.0", path = "../../../lib/etc/libimagutil" }
libimagentryref = { version = "0.7.0", path = "../../../lib/entry/libimagentryref" } libimagentryref = { version = "0.7.0", path = "../../../lib/entry/libimagentryref" }
libimagentryedit = { version = "0.7.0", path = "../../../lib/entry/libimagentryedit" } libimagentryedit = { version = "0.7.0", path = "../../../lib/entry/libimagentryedit" }
libimaginteraction = { version = "0.7.0", path = "../../../lib/etc/libimaginteraction" } libimaginteraction = { version = "0.7.0", path = "../../../lib/etc/libimaginteraction" }
[dependencies.libimagcontact]
version = "0.7.0"
path = "../../../lib/domain/libimagcontact"
default-features = false
features = ["deser"]
[dependencies.clap] [dependencies.clap]
version = ">=2.29" version = ">=2.29"
default-features = false default-features = false

View file

@ -40,6 +40,7 @@ extern crate toml_query;
extern crate handlebars; extern crate handlebars;
extern crate walkdir; extern crate walkdir;
extern crate uuid; extern crate uuid;
extern crate serde_json;
extern crate libimagcontact; extern crate libimagcontact;
extern crate libimagstore; extern crate libimagstore;
@ -70,6 +71,7 @@ use libimagcontact::store::ContactStore;
use libimagcontact::store::UniqueContactPathGenerator; use libimagcontact::store::UniqueContactPathGenerator;
use libimagcontact::error::ContactError as CE; use libimagcontact::error::ContactError as CE;
use libimagcontact::contact::Contact; use libimagcontact::contact::Contact;
use libimagcontact::deser::DeserVcard;
use libimagstore::iter::get::StoreIdGetIteratorExtension; use libimagstore::iter::get::StoreIdGetIteratorExtension;
use libimagentryref::reference::Ref; use libimagentryref::reference::Ref;
use libimagentryref::refstore::RefStore; use libimagentryref::refstore::RefStore;
@ -111,7 +113,7 @@ fn list(rt: &Runtime) {
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);
let _ = rt let iterator = rt
.store() .store()
.all_contacts() .all_contacts()
.map_err_trace_exit_unwrap(1) .map_err_trace_exit_unwrap(1)
@ -126,27 +128,46 @@ fn list(rt: &Runtime) {
.get_contact_data() .get_contact_data()
.map(|cd| (fle, cd)) .map(|cd| (fle, cd))
.map(|(fle, cd)| (fle, cd.into_inner())) .map(|(fle, cd)| (fle, cd.into_inner()))
.map(|(fle, cd)| (fle, Vcard::from_component(cd))) .map(|(fle, cd)| {
let card = Vcard::from_component(cd).unwrap_or_else(|e| {
error!("Element is not a VCARD object: {:?}", e);
exit(1)
});
(fle, card)
})
.map_err_trace_exit_unwrap(1) .map_err_trace_exit_unwrap(1)
}) })
.enumerate() .enumerate();
.map(|(i, (fle, vcard))| {
let hash = String::from(fle.get_hash().map_err_trace_exit_unwrap(1)); if scmd.is_present("json") {
let vcard = vcard.unwrap_or_else(|e| { let v : Vec<DeserVcard> = iterator
error!("Element is not a VCARD object: {:?}", e); .map(|(_, (_, vcard))| DeserVcard::from(vcard)).collect();
exit(1) 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)
}
}
} else {
iterator
.map(|(i, (fle, vcard))| {
let hash = String::from(fle.get_hash().map_err_trace_exit_unwrap(1));
let data = build_data_object_for_handlebars(i, hash, &vcard);
list_format.render("format", &data)
.err_from_str()
.map_err(CE::from)
.map_err_trace_exit_unwrap(1)
})
// collect, so that we can have rendered all the things and printing is faster.
.collect::<Vec<String>>()
.into_iter()
.for_each(|s| {
writeln!(rt.stdout(), "{}", s).to_exit_code().unwrap_or_exit()
}); });
}
let data = build_data_object_for_handlebars(i, hash, &vcard);
let s = list_format.render("format", &data)
.err_from_str()
.map_err(CE::from)
.map_err_trace_exit_unwrap(1);
writeln!(rt.stdout(), "{}", s).to_exit_code().unwrap_or_exit()
})
.collect::<Vec<_>>();
} }
fn import(rt: &Runtime) { fn import(rt: &Runtime) {
@ -227,7 +248,8 @@ fn find(rt: &Runtime) {
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);
rt.store() let iterator = rt
.store()
.all_contacts() .all_contacts()
.map_err_trace_exit_unwrap(1) .map_err_trace_exit_unwrap(1)
.into_get_iter(rt.store()) .into_get_iter(rt.store())
@ -267,7 +289,20 @@ fn find(rt: &Runtime) {
None None
} }
}) })
.enumerate() .enumerate();
if scmd.is_present("json") {
let v : Vec<DeserVcard> = iterator
.map(|(_, (_, vcard))| DeserVcard::from(vcard)).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)
}
}
} else {
iterator
.for_each(|(i, (fle, card))| { .for_each(|(i, (fle, card))| {
let fmt = if scmd.is_present("find-show") { let fmt = if scmd.is_present("find-show") {
&show_format &show_format
@ -289,6 +324,7 @@ fn find(rt: &Runtime) {
.to_exit_code() .to_exit_code()
.unwrap_or_exit(); .unwrap_or_exit();
}); });
}
} }
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) -> Handlebars {

View file

@ -38,6 +38,12 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.multiple(false) .multiple(false)
.value_name("FORMAT") .value_name("FORMAT")
.help("Format to format the listing")) .help("Format to format the listing"))
.arg(Arg::with_name("json")
.long("json")
.takes_value(false)
.required(false)
.multiple(false)
.help("Print output as JSON"))
) )
.subcommand(SubCommand::with_name("import") .subcommand(SubCommand::with_name("import")
@ -102,6 +108,15 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.conflicts_with("find-show") .conflicts_with("find-show")
) )
.arg(Arg::with_name("json")
.long("json")
.takes_value(false)
.required(false)
.multiple(false)
.help("Print output as JSON")
.conflicts_with("find-show")
.conflicts_with("find-list"))
) )
.subcommand(SubCommand::with_name("create") .subcommand(SubCommand::with_name("create")

View file

@ -20,12 +20,14 @@ is-it-maintained-open-issues = { repository = "matthiasbeyer/imag" }
maintenance = { status = "actively-developed" } maintenance = { status = "actively-developed" }
[dependencies] [dependencies]
error-chain = "0.11" error-chain = "0.11"
log = "0.3" log = "0.3"
toml = "0.4" toml = "0.4"
toml-query = "0.4" toml-query = "0.4"
vobject = "0.4" vobject = "0.4"
uuid = { version = "0.6", features = ["v4"] } uuid = { version = "0.6", features = ["v4"] }
serde = { version = "1", optional = true }
serde_derive = { version = "1", optional = true }
libimagstore = { version = "0.7.0", path = "../../../lib/core/libimagstore" } libimagstore = { version = "0.7.0", path = "../../../lib/core/libimagstore" }
libimagerror = { version = "0.7.0", path = "../../../lib/core/libimagerror" } libimagerror = { version = "0.7.0", path = "../../../lib/core/libimagerror" }
@ -37,3 +39,7 @@ path = "../../../lib/entry/libimagentryref/"
default-features = false default-features = false
features = ["generators", "generators-sha1"] features = ["generators", "generators-sha1"]
[features]
default = []
deser = ["serde", "serde_derive"]

View file

@ -0,0 +1,170 @@
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015-2018 Matthias Beyer <mail@beyermatthias.de> and contributors
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; version
// 2.1 of the License.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
use vobject::vcard::Vcard;
/// A type which can be build from a Vcard and be serialized.
///
/// # Details
///
/// Deserializing is not supported by libimagcontact yet
/// Elements which are "empty" (as in empty list) or optional and not present are not serialized.
///
#[derive(Serialize, Debug)]
pub struct DeserVcard {
#[serde(skip_serializing_if = "Vec::is_empty")]
adr : Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
anniversary : Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
bday : Option<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
categories : Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
clientpidmap : Option<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
email : Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
fullname : Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
gender : Option<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
geo : Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
impp : Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
key : Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
lang : Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
logo : Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
member : Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
name : Option<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
nickname : Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
note : Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
org : Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
photo : Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
proid : Option<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
related : Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
rev : Option<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
role : Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
sound : Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
tel : Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
title : Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
tz : Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
uid : Option<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
url : Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
version : Option<String>
}
impl From<Vcard> for DeserVcard {
fn from(card: Vcard) -> DeserVcard {
macro_rules! arystr {
($v:expr) => {
$v.into_iter().map(|o| o.raw().clone()).collect()
};
};
macro_rules! optstr {
($o:expr) => {
$o.map(|o| o.raw().clone())
};
};
DeserVcard {
adr : arystr!(card.adr()),
anniversary : optstr!(card.anniversary()),
bday : optstr!(card.bday()),
categories : arystr!(card.categories()),
clientpidmap : optstr!(card.clientpidmap()),
email : arystr!(card.email()),
fullname : arystr!(card.fullname()),
gender : optstr!(card.gender()),
geo : arystr!(card.geo()),
impp : arystr!(card.impp()),
key : arystr!(card.key()),
lang : arystr!(card.lang()),
logo : arystr!(card.logo()),
member : arystr!(card.member()),
name : optstr!(card.name()),
nickname : arystr!(card.nickname()),
note : arystr!(card.note()),
org : arystr!(card.org()),
photo : arystr!(card.photo()),
proid : optstr!(card.proid()),
related : arystr!(card.related()),
rev : optstr!(card.rev()),
role : arystr!(card.role()),
sound : arystr!(card.sound()),
tel : arystr!(card.tel()),
title : arystr!(card.title()),
tz : arystr!(card.tz()),
uid : optstr!(card.uid()),
url : arystr!(card.url()),
version : optstr!(card.version()),
}
}
}

View file

@ -53,3 +53,13 @@ pub mod iter;
pub mod store; pub mod store;
mod util; mod util;
#[cfg(feature = "serde")]
extern crate serde;
#[cfg(feature = "serde")]
#[macro_use] extern crate serde_derive;
#[cfg(feature = "deser")]
pub mod deser;