summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthias Beyer <mail@beyermatthias.de>2018-03-12 18:35:10 +0100
committerGitHub <noreply@github.com>2018-03-12 18:35:10 +0100
commit5609651f118389a4904224efce74252c909e139f (patch)
treef9d1a4860720bf448a419f20c2c2e504d0d676be
parent6ec509709dc1d8af444ffbb537a8e5f260e31977 (diff)
parente755bfd9de6980af14d2abb605dce63ece805461 (diff)
downloadimag-5609651f118389a4904224efce74252c909e139f.zip
imag-5609651f118389a4904224efce74252c909e139f.tar.gz
Merge pull request #1338 from matthiasbeyer/libimagcontact/to-json-output
libimagcontact/imag-contact: json output support
-rw-r--r--bin/domain/imag-contact/Cargo.toml8
-rw-r--r--bin/domain/imag-contact/src/main.rs78
-rw-r--r--bin/domain/imag-contact/src/ui.rs15
-rw-r--r--lib/domain/libimagcontact/Cargo.toml18
-rw-r--r--lib/domain/libimagcontact/src/deser.rs170
-rw-r--r--lib/domain/libimagcontact/src/lib.rs10
6 files changed, 271 insertions, 28 deletions
diff --git a/bin/domain/imag-contact/Cargo.toml b/bin/domain/imag-contact/Cargo.toml
index b44fc22..56f1a95 100644
--- a/bin/domain/imag-contact/Cargo.toml
+++ b/bin/domain/imag-contact/Cargo.toml
@@ -29,16 +29,22 @@ handlebars = "0.29"
vobject = "0.4"
walkdir = "1"
uuid = { version = "0.6", features = ["v4"] }
+serde_json = "1"
libimagrt = { version = "0.7.0", path = "../../../lib/core/libimagrt" }
libimagstore = { version = "0.7.0", path = "../../../lib/core/libimagstore" }
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" }
libimagentryref = { version = "0.7.0", path = "../../../lib/entry/libimagentryref" }
libimagentryedit = { version = "0.7.0", path = "../../../lib/entry/libimagentryedit" }
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]
version = ">=2.29"
default-features = false
diff --git a/bin/domain/imag-contact/src/main.rs b/bin/domain/imag-contact/src/main.rs
index 62196f0..3c898d6 100644
--- a/bin/domain/imag-contact/src/main.rs
+++ b/bin/domain/imag-contact/src/main.rs
@@ -40,6 +40,7 @@ extern crate toml_query;
extern crate handlebars;
extern crate walkdir;
extern crate uuid;
+extern crate serde_json;
extern crate libimagcontact;
extern crate libimagstore;
@@ -70,6 +71,7 @@ use libimagcontact::store::ContactStore;
use libimagcontact::store::UniqueContactPathGenerator;
use libimagcontact::error::ContactError as CE;
use libimagcontact::contact::Contact;
+use libimagcontact::deser::DeserVcard;
use libimagstore::iter::get::StoreIdGetIteratorExtension;
use libimagentryref::reference::Ref;
use libimagentryref::refstore::RefStore;
@@ -111,7 +113,7 @@ fn list(rt: &Runtime) {
let scmd = rt.cli().subcommand_matches("list").unwrap();
let list_format = get_contact_print_format("contact.list_format", rt, &scmd);
- let _ = rt
+ let iterator = rt
.store()
.all_contacts()
.map_err_trace_exit_unwrap(1)
@@ -126,27 +128,46 @@ fn list(rt: &Runtime) {
.get_contact_data()
.map(|cd| (fle, cd))
.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)
})
- .enumerate()
- .map(|(i, (fle, vcard))| {
- let hash = String::from(fle.get_hash().map_err_trace_exit_unwrap(1));
- let vcard = vcard.unwrap_or_else(|e| {
- error!("Element is not a VCARD object: {:?}", e);
- exit(1)
+ .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
+ .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) {
@@ -227,7 +248,8 @@ fn find(rt: &Runtime) {
let show_format = get_contact_print_format("contact.show_format", rt, &scmd);
let list_format = get_contact_print_format("contact.list_format", rt, &scmd);
- rt.store()
+ let iterator = rt
+ .store()
.all_contacts()
.map_err_trace_exit_unwrap(1)
.into_get_iter(rt.store())
@@ -267,7 +289,20 @@ fn find(rt: &Runtime) {
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))| {
let fmt = if scmd.is_present("find-show") {
&show_format
@@ -289,6 +324,7 @@ fn find(rt: &Runtime) {
.to_exit_code()
.unwrap_or_exit();
});
+ }
}
fn get_contact_print_format(config_value_path: &'static str, rt: &Runtime, scmd: &ArgMatches) -> Handlebars {
diff --git a/bin/domain/imag-contact/src/ui.rs b/bin/domain/imag-contact/src/ui.rs
index 79149a9..7ebc78a 100644
--- a/bin/domain/imag-contact/src/ui.rs
+++ b/bin/domain/imag-contact/src/ui.rs
@@ -38,6 +38,12 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.multiple(false)
.value_name("FORMAT")
.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")
@@ -102,6 +108,15 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.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")
diff --git a/lib/domain/libimagcontact/Cargo.toml b/lib/domain/libimagcontact/Cargo.toml
index cc1d3cb..795d1e8 100644
--- a/lib/domain/libimagcontact/Cargo.toml
+++ b/lib/domain/libimagcontact/Cargo.toml
@@ -20,12 +20,14 @@ is-it-maintained-open-issues = { repository = "matthiasbeyer/imag" }
maintenance = { status = "actively-developed" }
[dependencies]
-error-chain = "0.11"
-log = "0.3"
-toml = "0.4"
-toml-query = "0.4"
-vobject = "0.4"
-uuid = { version = "0.6", features = ["v4"] }
+error-chain = "0.11"
+log = "0.3"
+toml = "0.4"
+toml-query = "0.4"
+vobject = "0.4"
+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" }
libimagerror = { version = "0.7.0", path = "../../../lib/core/libimagerror" }
@@ -37,3 +39,7 @@ path = "../../../lib/entry/libimagentryref/"
default-features = false
features = ["generators", "generators-sha1"]
+[features]
+default = []
+deser = ["serde", "serde_derive"]
+
diff --git a/lib/domain/libimagcontact/src/deser.rs b/lib/domain/libimagcontact/src/deser.rs
new file mode 100644
index 0000000..84855cd
--- /dev/null
+++ b/lib/domain/libimagcontact/src/deser.rs
@@ -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()),
+ }
+ }
+}
+
diff --git a/lib/domain/libimagcontact/src/lib.rs b/lib/domain/libimagcontact/src/lib.rs
index 31d0ac6..6e55e57 100644
--- a/lib/domain/libimagcontact/src/lib.rs
+++ b/lib/domain/libimagcontact/src/lib.rs
@@ -53,3 +53,13 @@ pub mod iter;
pub mod store;
mod util;
+
+#[cfg(feature = "serde")]
+extern crate serde;
+
+#[cfg(feature = "serde")]
+#[macro_use] extern crate serde_derive;
+
+#[cfg(feature = "deser")]
+pub mod deser;
+