1#![forbid(unsafe_code)]
21
22#![deny(
23 non_camel_case_types,
24 non_snake_case,
25 path_statements,
26 trivial_numeric_casts,
27 unstable_features,
28 unused_allocation,
29 unused_import_braces,
30 unused_imports,
31 unused_must_use,
32 unused_mut,
33 unused_qualifications,
34 while_true,
35)]
36
37extern crate clap;
38#[macro_use] extern crate log;
39#[macro_use] extern crate vobject;
40extern crate toml;
41extern crate toml_query;
42extern crate handlebars;
43extern crate walkdir;
44extern crate uuid;
45extern crate serde_json;
46#[macro_use] extern crate failure;
47extern crate resiter;
48
49extern crate libimagcontact;
50extern crate libimagstore;
51extern crate libimagrt;
52extern crate libimagerror;
53extern crate libimagutil;
54extern crate libimaginteraction;
55extern crate libimagentryedit;
56extern crate libimagentryref;
57
58use std::path::PathBuf;
59use std::io::Write;
60
61use handlebars::Handlebars;
62use clap::{App, ArgMatches};
63use toml_query::read::TomlValueReadExt;
64use toml_query::read::TomlValueReadTypeExt;
65use toml_query::read::Partial;
66use walkdir::WalkDir;
67use failure::Error;
68use failure::err_msg;
69use failure::Fallible as Result;
70use resiter::AndThen;
71use resiter::IterInnerOkOrElse;
72use resiter::Map;
73use resiter::Filter;
74
75use libimagrt::runtime::Runtime;
76use libimagrt::application::ImagApplication;
77use libimagcontact::store::ContactStore;
78use libimagcontact::contact::Contact;
79use libimagcontact::deser::DeserVcard;
80
81mod ui;
82mod util;
83mod create;
84mod edit;
85
86use crate::util::build_data_object_for_handlebars;
87use crate::create::create;
88use crate::edit::edit;
89
90pub enum ImagContact {}
95impl ImagApplication for ImagContact {
96 fn run(rt: Runtime) -> Result<()> {
97 match rt.cli().subcommand_name().ok_or_else(|| err_msg("No subcommand called"))? {
98 "list" => list(&rt),
99 "import" => import(&rt),
100 "show" => show(&rt),
101 "edit" => edit(&rt),
102 "find" => find(&rt),
103 "create" => create(&rt),
104 other => {
105 debug!("Unknown command");
106 if rt.handle_unknown_subcommand("imag-contact", other, rt.cli())?.success() {
107 Ok(())
108 } else {
109 Err(err_msg("Failed to handle unknown subcommand"))
110 }
111 },
112 }
113 }
114
115 fn build_cli<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
116 ui::build_ui(app)
117 }
118
119 fn name() -> &'static str {
120 env!("CARGO_PKG_NAME")
121 }
122
123 fn description() -> &'static str {
124 "Contact management tool"
125 }
126
127 fn version() -> &'static str {
128 env!("CARGO_PKG_VERSION")
129 }
130}
131
132fn list(rt: &Runtime) -> Result<()> {
133 let scmd = rt.cli().subcommand_matches("list").unwrap();
134 let list_format = get_contact_print_format("contact.list_format", rt, &scmd)?;
135 debug!("List format: {:?}", list_format);
136
137 let iterator = rt
138 .store()
139 .all_contacts()?
140 .into_get_iter()
141 .map_inner_ok_or_else(|| err_msg("Did not find one entry"))
142 .and_then_ok(|fle| {
143 rt.report_touched(fle.get_location())?;
144 Ok(fle)
145 })
146 .and_then_ok(|e| e.deser());
147
148 if scmd.is_present("json") {
149 debug!("Listing as JSON");
150 let v = iterator.collect::<Result<Vec<DeserVcard>>>()?;
151 let s = ::serde_json::to_string(&v)?;
152 writeln!(rt.stdout(), "{}", s).map_err(Error::from)
153 } else {
154 debug!("Not listing as JSON");
155 let output = rt.stdout();
156 let mut output = output.lock();
157 let mut i = 0;
158 iterator
159 .map_ok(|dvcard| {
160 i += 1;
161 build_data_object_for_handlebars(i, &dvcard)
162 })
163 .and_then_ok(|data| list_format.render("format", &data).map_err(Error::from))
164 .and_then_ok(|s| writeln!(output, "{}", s).map_err(Error::from))
165 .collect::<Result<Vec<_>>>()
166 .map(|_| ())
167 }
168}
169
170fn import(rt: &Runtime) -> Result<()> {
171 let scmd = rt.cli().subcommand_matches("import").unwrap(); let force_override = scmd.is_present("force-override");
173 let path = scmd.value_of("path").map(PathBuf::from).unwrap(); let collection_name = rt.cli().value_of("contact-ref-collection-name").unwrap(); let ref_config = rt.config()
177 .ok_or_else(|| format_err!("No configuration, cannot continue!"))?
178 .read_partial::<libimagentryref::reference::Config>()?
179 .ok_or_else(|| format_err!("Configuration missing: {}", libimagentryref::reference::Config::LOCATION))?;
180
181 if !path.exists() {
184 return Err(format_err!("Path does not exist: {}", path.display()))
185 }
186
187 if path.is_file() {
188 let entry = rt
189 .store()
190 .retrieve_from_path(&path, &ref_config, &collection_name, force_override)?;
191
192 rt.report_touched(entry.get_location()).map_err(Error::from)
193 } else if path.is_dir() {
194 WalkDir::new(path)
195 .min_depth(1)
196 .into_iter()
197 .map(|r| r.map_err(Error::from))
198 .and_then_ok(|entry| {
199 if entry.file_type().is_file() {
200 let pb = PathBuf::from(entry.path());
201 let fle = rt
202 .store()
203 .retrieve_from_path(&pb, &ref_config, &collection_name, force_override)?;
204
205 rt.report_touched(fle.get_location())?;
206 info!("Imported: {}", entry.path().to_str().unwrap_or("<non UTF-8 path>"));
207 Ok(())
208 } else {
209 warn!("Ignoring non-file: {}", entry.path().to_str().unwrap_or("<non UTF-8 path>"));
210 Ok(())
211 }
212 })
213 .collect::<Result<Vec<_>>>()
214 .map(|_| ())
215 } else {
216 Err(err_msg("Path is neither directory nor file"))
217 }
218}
219
220fn show(rt: &Runtime) -> Result<()> {
221 let scmd = rt.cli().subcommand_matches("show").unwrap();
222 let hash = scmd.value_of("hash").map(String::from).unwrap(); let show_format = get_contact_print_format("contact.show_format", rt, &scmd)?;
224 let out = rt.stdout();
225 let mut outlock = out.lock();
226
227 util::find_contact_by_hash(rt, hash)?
228 .filter_ok(|tpl| tpl.0)
229 .map_ok(|tpl| tpl.1)
230 .enumerate()
231 .map(|(i, elem)| {
232 let elem = elem?.deser()?;
233 let data = build_data_object_for_handlebars(i, &elem);
234
235 let s = show_format.render("format", &data)?;
236 writeln!(outlock, "{}", s).map_err(Error::from)
237 })
238 .collect::<Result<Vec<_>>>()
239 .map(|_| ())
240}
241
242fn find(rt: &Runtime) -> Result<()> {
243 let scmd = rt.cli().subcommand_matches("find").unwrap();
244 let grepstring = scmd
245 .values_of("string")
246 .unwrap() .map(String::from)
248 .collect::<Vec<String>>();
249
250 let show_format = get_contact_print_format("contact.show_format", rt, &scmd)?;
252 let list_format = get_contact_print_format("contact.list_format", rt, &scmd)?;
253
254 let iterator = rt
255 .store()
256 .all_contacts()?
257 .into_get_iter()
258 .map_inner_ok_or_else(|| err_msg("Did not find one entry"))
259 .and_then_ok(|entry| {
260 let card = entry.deser()?;
261
262 let str_contains_any = |s: &String, v: &Vec<String>| {
263 v.iter().any(|i| s.contains(i))
264 };
265
266 let take = card.adr().iter().any(|a| str_contains_any(a, &grepstring))
267 || card.email().iter().any(|a| str_contains_any(&a.address, &grepstring))
268 || card.fullname().iter().any(|a| str_contains_any(a, &grepstring));
269
270 if take {
271 rt.report_touched(entry.get_location())?;
272
273 Ok((true, entry, card))
275 } else {
276 Ok((false, entry, card))
277 }
278 });
279
280 let mut i = 0;
281
282 if !rt.output_is_pipe() || rt.ignore_ids() {
283 if scmd.is_present("json") {
284 iterator
285 .filter_ok(|tpl| tpl.0)
286 .map_ok(|tpl| tpl.2)
287 .and_then_ok(|v| {
288 let s = ::serde_json::to_string(&v)?;
289 writeln!(rt.stdout(), "{}", s).map_err(Error::from)
290 })
291 .collect::<Result<Vec<_>>>()
292 .map(|_| ())
293 } else if scmd.is_present("find-id") {
294 iterator
295 .and_then_ok(|(take, entry, _)| {
296 if take {
297 writeln!(rt.stdout(), "{}", entry.get_location()).map_err(Error::from)
298 } else {
299 Ok(())
300 }
301 })
302 .collect::<Result<Vec<_>>>()
303 .map(|_| ())
304 } else if scmd.is_present("find-full-id") {
305 let storepath = rt.store().path().display();
306 iterator
307 .and_then_ok(|(take, entry, _)| {
308 if take {
309 writeln!(rt.stdout(), "{}/{}", storepath, entry.get_location()).map_err(Error::from)
310 } else {
311 Ok(())
312 }
313 })
314 .collect::<Result<Vec<_>>>()
315 .map(|_| ())
316 } else {
317 iterator
318 .and_then_ok(|(take, _, card)| {
319 if take {
320 i += 1;
321 let fmt = if scmd.is_present("find-show") {
322 &show_format
323 } else { &list_format
325 };
326
327 let data = build_data_object_for_handlebars(i, &card);
328 let s = fmt.render("format", &data)?;
329
330 writeln!(rt.stdout(), "{}", s).map_err(Error::from)
331 } else {
332 Ok(())
333 }
334 })
335 .collect::<Result<Vec<_>>>()
336 .map(|_| ())
337 }
338 } else { let _ = iterator.collect::<Vec<_>>();
340 Ok(())
341 }
342}
343
344fn get_contact_print_format(config_value_path: &'static str, rt: &Runtime, scmd: &ArgMatches) -> Result<Handlebars> {
345 let fmt = match scmd.value_of("format").map(String::from) {
346 Some(s) => Ok(s),
347 None => rt.config()
348 .ok_or_else(|| err_msg("No configuration file"))?
349 .read_string(config_value_path)?
350 .ok_or_else(|| err_msg("Configuration 'contact.list_format' does not exist")),
351 }?;
352
353 let mut hb = Handlebars::new();
354 hb.register_template_string("format", fmt)?;
355
356 hb.register_escape_fn(::handlebars::no_escape);
357 ::libimaginteraction::format::register_all_color_helpers(&mut hb);
358 ::libimaginteraction::format::register_all_format_helpers(&mut hb);
359 Ok(hb)
360}
361