Implement create subcommand
The complexity of the create() routine justifies the move to another file. This commit implements the create() functionality which creates a TOML tempfile which the user should edit and then reads the contents to build the Vcard object which then gets written to either stdout or the destination file. Besides that: * Move helper function to util module * Rewrite and fix tests
This commit is contained in:
parent
ab8c8e4e41
commit
e211aba341
7 changed files with 772 additions and 108 deletions
|
@ -33,6 +33,3 @@ libimagentryref = { version = "0.5.0", path = "../../../lib/entry/libimagentryre
|
||||||
libimagentryedit = { version = "0.5.0", path = "../../../lib/entry/libimagentryedit" }
|
libimagentryedit = { version = "0.5.0", path = "../../../lib/entry/libimagentryedit" }
|
||||||
libimaginteraction = { version = "0.5.0", path = "../../../lib/etc/libimaginteraction" }
|
libimaginteraction = { version = "0.5.0", path = "../../../lib/etc/libimaginteraction" }
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
regex = "0.2"
|
|
||||||
|
|
||||||
|
|
443
bin/domain/imag-contact/src/create.rs
Normal file
443
bin/domain/imag-contact/src/create.rs
Normal file
|
@ -0,0 +1,443 @@
|
||||||
|
//
|
||||||
|
// imag - the personal information management suite for the commandline
|
||||||
|
// Copyright (C) 2015, 2016 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 std::collections::BTreeMap;
|
||||||
|
use std::process::exit;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::io::stdout;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::fs::OpenOptions;
|
||||||
|
|
||||||
|
use vobject::vcard::Vcard;
|
||||||
|
use vobject::write_component;
|
||||||
|
use toml_query::read::TomlValueReadExt;
|
||||||
|
use toml::Value;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use libimagrt::runtime::Runtime;
|
||||||
|
use libimagerror::trace::MapErrTrace;
|
||||||
|
use libimagerror::trace::trace_error;
|
||||||
|
use libimagerror::trace::trace_error_exit;
|
||||||
|
use libimagutil::warn_result::WarnResult;
|
||||||
|
use libimagentryref::refstore::RefStore;
|
||||||
|
use libimagentryref::flags::RefFlags;
|
||||||
|
|
||||||
|
const TEMPLATE : &'static str = include_str!("../static/new-contact-template.toml");
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use toml::Value;
|
||||||
|
use super::TEMPLATE;
|
||||||
|
|
||||||
|
const TEMPLATE_WITH_DATA : &'static str = include_str!("../static/new-contact-template-test.toml");
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_validity_template_toml() {
|
||||||
|
let _ : Value = ::toml::de::from_str(TEMPLATE).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_validity_template_toml_without_comments() {
|
||||||
|
let _ : Value = ::toml::de::from_str(TEMPLATE_WITH_DATA).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! ask_continue {
|
||||||
|
() => {
|
||||||
|
if ::libimaginteraction::ask::ask_bool("Edit tempfile", Some(true)) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create(rt: &Runtime) {
|
||||||
|
let scmd = rt.cli().subcommand_matches("create").unwrap();
|
||||||
|
let mut template = String::from(TEMPLATE);
|
||||||
|
|
||||||
|
let (mut dest, location) : (Box<Write>, Option<PathBuf>) = {
|
||||||
|
if let Some(mut fl) = scmd.value_of("file-location").map(PathBuf::from) {
|
||||||
|
if fl.is_file() {
|
||||||
|
error!("File does exist, cannot create/override");
|
||||||
|
exit(1);
|
||||||
|
} else if fl.is_dir() {
|
||||||
|
fl.set_file_name(Uuid::new_v4().hyphenated().to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let file = OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.create_new(true)
|
||||||
|
.open(fl.clone())
|
||||||
|
.map_warn_err_str("Cannot create/open destination File. Stopping.")
|
||||||
|
.map_err_trace_exit_unwrap(1);
|
||||||
|
|
||||||
|
(Box::new(file), Some(fl))
|
||||||
|
} else {
|
||||||
|
(Box::new(stdout()), None)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loop {
|
||||||
|
::libimagentryedit::edit::edit_in_tmpfile(&rt, &mut template)
|
||||||
|
.map_warn_err_str("Editing failed.")
|
||||||
|
.map_err_trace_exit_unwrap(1);
|
||||||
|
|
||||||
|
if template == TEMPLATE || template.is_empty() {
|
||||||
|
error!("No (changed) content in tempfile. Not doing anything.");
|
||||||
|
exit(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
match ::toml::de::from_str(&template) {
|
||||||
|
Err(e) => {
|
||||||
|
error!("Error parsing template");
|
||||||
|
trace_error(&e);
|
||||||
|
ask_continue!();
|
||||||
|
},
|
||||||
|
|
||||||
|
Ok(toml) => {
|
||||||
|
debug!("");
|
||||||
|
let mut vcard = Vcard::default();
|
||||||
|
|
||||||
|
{ // parse name
|
||||||
|
debug!("Parsing name");
|
||||||
|
let firstname = read_str_from_toml(&toml, "name.first");
|
||||||
|
trace!("firstname = {:?}", firstname);
|
||||||
|
|
||||||
|
let lastname = read_str_from_toml(&toml, "name.last");
|
||||||
|
trace!("lastname = {:?}", lastname);
|
||||||
|
|
||||||
|
vcard = vcard.with_name(parameters!(),
|
||||||
|
read_str_from_toml(&toml, "name.prefix"),
|
||||||
|
firstname.clone(),
|
||||||
|
read_str_from_toml(&toml, "name.additional"),
|
||||||
|
lastname.clone(),
|
||||||
|
read_str_from_toml(&toml, "name.suffix"));
|
||||||
|
|
||||||
|
if let (Some(first), Some(last)) = (firstname, lastname) {
|
||||||
|
trace!("Building fullname: '{} {}'", first, last);
|
||||||
|
vcard = vcard.with_fullname(format!("{} {}", first, last));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // parse nicknames
|
||||||
|
debug!("Parsing nicknames");
|
||||||
|
match toml.read("nickname").map_err_trace_exit_unwrap(1) {
|
||||||
|
Some(&Value::Array(ref ary)) => {
|
||||||
|
for (i, element) in ary.iter().enumerate() {
|
||||||
|
let nicktype = match read_str_from_toml(element, "type") {
|
||||||
|
Some(p) => {
|
||||||
|
let mut m = BTreeMap::new();
|
||||||
|
m.insert("TYPE".into(), p);
|
||||||
|
m
|
||||||
|
},
|
||||||
|
None => BTreeMap::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let name = match read_str_from_toml(element, "name") {
|
||||||
|
None => {
|
||||||
|
error!("Key 'nickname.[{}].name' missing", i);
|
||||||
|
ask_continue!()
|
||||||
|
},
|
||||||
|
Some(p) => p,
|
||||||
|
};
|
||||||
|
|
||||||
|
trace!("nick type = {:?}", nicktype);
|
||||||
|
trace!("name = {:?}", name);
|
||||||
|
|
||||||
|
vcard = vcard.with_nickname(nicktype, name);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
Some(&Value::String(ref name)) => {
|
||||||
|
vcard = vcard.with_nickname(parameters!(), name.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(_) => {
|
||||||
|
error!("Type Error: Expected Array or String at 'nickname'");
|
||||||
|
ask_continue!();
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
// nothing
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // parse organisation
|
||||||
|
debug!("Parsing organisation");
|
||||||
|
|
||||||
|
if let Some(orgs) = read_strary_from_toml(&toml, "organisation.name") {
|
||||||
|
trace!("orgs = {:?}", orgs);
|
||||||
|
vcard = vcard.with_org(orgs);
|
||||||
|
} else {
|
||||||
|
error!("Key 'organisation.name' missing");
|
||||||
|
ask_continue!();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(title) = read_str_from_toml(&toml, "organisation.title") {
|
||||||
|
trace!("title = {:?}", title);
|
||||||
|
vcard = vcard.with_title(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(role) = read_str_from_toml(&toml, "organisation.role") {
|
||||||
|
trace!("role = {:?}", role);
|
||||||
|
vcard = vcard.with_role(role);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // parse phone
|
||||||
|
debug!("Parse phone");
|
||||||
|
match toml.read("phone").map_err_trace_exit_unwrap(1) {
|
||||||
|
Some(&Value::Array(ref ary)) => {
|
||||||
|
for (i, element) in ary.iter().enumerate() {
|
||||||
|
let phonetype = match read_str_from_toml(element, "type") {
|
||||||
|
Some(p) => p,
|
||||||
|
None => {
|
||||||
|
error!("Key 'phones.[{}].type' missing", i);
|
||||||
|
ask_continue!()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let number = match read_str_from_toml(element, "number") {
|
||||||
|
Some(p) => p,
|
||||||
|
None => {
|
||||||
|
error!("Key 'phones.[{}].number' missing", i);
|
||||||
|
ask_continue!()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
trace!("phonetype = {:?}", phonetype);
|
||||||
|
trace!("number = {:?}", number);
|
||||||
|
|
||||||
|
vcard = vcard.with_tel(parameters!("TYPE" => phonetype), number);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
Some(_) => {
|
||||||
|
error!("Expected Array at 'phones'.");
|
||||||
|
ask_continue!()
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
// nothing
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // parse address
|
||||||
|
debug!("Parsing address");
|
||||||
|
match toml.read("addresses").map_err_trace_exit_unwrap(1) {
|
||||||
|
Some(&Value::Array(ref ary)) => {
|
||||||
|
for (i, element) in ary.iter().enumerate() {
|
||||||
|
let adrtype = match read_str_from_toml(element, "type") {
|
||||||
|
None => {
|
||||||
|
error!("Key 'adresses.[{}].type' missing", i);
|
||||||
|
ask_continue!()
|
||||||
|
},
|
||||||
|
Some(p) => p,
|
||||||
|
};
|
||||||
|
trace!("adrtype = {:?}", adrtype);
|
||||||
|
|
||||||
|
let bx = read_str_from_toml(element, "box");
|
||||||
|
let extended = read_str_from_toml(element, "extended");
|
||||||
|
let street = read_str_from_toml(element, "street");
|
||||||
|
let code = read_str_from_toml(element, "code");
|
||||||
|
let city = read_str_from_toml(element, "city");
|
||||||
|
let region = read_str_from_toml(element, "region");
|
||||||
|
let country = read_str_from_toml(element, "country");
|
||||||
|
|
||||||
|
trace!("bx = {:?}", bx);
|
||||||
|
trace!("extended = {:?}", extended);
|
||||||
|
trace!("street = {:?}", street);
|
||||||
|
trace!("code = {:?}", code);
|
||||||
|
trace!("city = {:?}", city);
|
||||||
|
trace!("region = {:?}", region);
|
||||||
|
trace!("country = {:?}", country);
|
||||||
|
|
||||||
|
vcard = vcard.with_adr(
|
||||||
|
parameters!("TYPE" => adrtype),
|
||||||
|
bx, extended, street, code, city, region, country
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
Some(_) => {
|
||||||
|
error!("Type Error: Expected Array at 'addresses'");
|
||||||
|
ask_continue!();
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
// nothing
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // parse email
|
||||||
|
debug!("Parsing email");
|
||||||
|
match toml.read("email").map_err_trace_exit_unwrap(1) {
|
||||||
|
Some(&Value::Array(ref ary)) => {
|
||||||
|
for (i, element) in ary.iter().enumerate() {
|
||||||
|
let mailtype = match read_str_from_toml(element, "type") {
|
||||||
|
None => {
|
||||||
|
error!("Error: 'email.[{}].type' missing", i);
|
||||||
|
ask_continue!()
|
||||||
|
},
|
||||||
|
Some(p) => p,
|
||||||
|
}; // TODO: Unused, because unsupported by vobject
|
||||||
|
|
||||||
|
let mail = match read_str_from_toml(element, "addr") {
|
||||||
|
None => {
|
||||||
|
error!("Error: 'email.[{}].addr' missing", i);
|
||||||
|
ask_continue!()
|
||||||
|
},
|
||||||
|
Some(p) => p,
|
||||||
|
};
|
||||||
|
|
||||||
|
trace!("mailtype = {:?} (UNUSED)", mailtype);
|
||||||
|
trace!("mail = {:?}", mail);
|
||||||
|
|
||||||
|
vcard = vcard.with_email(mail);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
Some(_) => {
|
||||||
|
error!("Type Error: Expected Array at 'email'");
|
||||||
|
ask_continue!();
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
// nothing
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // parse others
|
||||||
|
debug!("Parsing others");
|
||||||
|
if let Some(categories) = read_strary_from_toml(&toml, "others.categories") {
|
||||||
|
vcard = vcard.with_categories(categories);
|
||||||
|
} else {
|
||||||
|
debug!("No categories");
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(webpage) = read_str_from_toml(&toml, "others.webpage") {
|
||||||
|
vcard = vcard.with_url(webpage);
|
||||||
|
} else {
|
||||||
|
debug!("No webpage");
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(note) = read_str_from_toml(&toml, "others.note") {
|
||||||
|
vcard = vcard.with_note(note);
|
||||||
|
} else {
|
||||||
|
debug!("No note");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//println!("{:#?}", vcard);
|
||||||
|
if template == TEMPLATE || template.is_empty() {
|
||||||
|
if ::libimaginteraction::ask::ask_bool("Abort contact creating", Some(false)) {
|
||||||
|
exit(1);
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let vcard_string = write_component(&vcard);
|
||||||
|
if let Err(e) = dest.write_all(&vcard_string.as_bytes()) {
|
||||||
|
warn!("Error while writing out vcard content");
|
||||||
|
trace_error_exit(&e, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(location) = location {
|
||||||
|
if !scmd.is_present("dont-track") {
|
||||||
|
let flags = RefFlags::default()
|
||||||
|
.with_content_hashing(true)
|
||||||
|
.with_permission_tracking(false);
|
||||||
|
|
||||||
|
RefStore::create(rt.store(), location, flags)
|
||||||
|
.map_err_trace_exit_unwrap(1);
|
||||||
|
|
||||||
|
info!("Created entry in store");
|
||||||
|
} else {
|
||||||
|
info!("Not creating entry in store");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
info!("Cannot track stdout-created contact information");
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Ready");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_strary_from_toml(toml: &Value, path: &'static str) -> Option<Vec<String>> {
|
||||||
|
match toml.read(path).map_warn_err_str(&format!("Failed to read value at '{}'", path)) {
|
||||||
|
Ok(Some(&Value::Array(ref vec))) => {
|
||||||
|
let mut v = Vec::new();
|
||||||
|
for elem in vec {
|
||||||
|
match *elem {
|
||||||
|
Value::String(ref s) => v.push(s.clone()),
|
||||||
|
_ => {
|
||||||
|
error!("Type Error: '{}' must be Array<String>", path);
|
||||||
|
return None
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(v)
|
||||||
|
}
|
||||||
|
Ok(Some(&Value::String(ref s))) => {
|
||||||
|
warn!("Having String, wanting Array<String> ... going to auto-fix");
|
||||||
|
Some(vec![s.clone()])
|
||||||
|
},
|
||||||
|
Ok(Some(_)) => {
|
||||||
|
error!("Type Error: '{}' must be Array<String>", path);
|
||||||
|
None
|
||||||
|
},
|
||||||
|
Ok(None) => None,
|
||||||
|
Err(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_str_from_toml(toml: &Value, path: &'static str) -> Option<String> {
|
||||||
|
let v = toml.read(path)
|
||||||
|
.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
|
||||||
|
},
|
||||||
|
Ok(None) => {
|
||||||
|
error!("Expected '{}' to be present, but is not.", path);
|
||||||
|
None
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
trace_error(&e);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test_entry {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
|
@ -32,8 +32,6 @@
|
||||||
while_true,
|
while_true,
|
||||||
)]
|
)]
|
||||||
|
|
||||||
#[cfg(test)] extern crate regex;
|
|
||||||
|
|
||||||
extern crate clap;
|
extern crate clap;
|
||||||
#[macro_use] extern crate log;
|
#[macro_use] extern crate log;
|
||||||
#[macro_use] extern crate version;
|
#[macro_use] extern crate version;
|
||||||
|
@ -54,7 +52,6 @@ extern crate libimagentryref;
|
||||||
extern crate libimagentryedit;
|
extern crate libimagentryedit;
|
||||||
|
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use std::collections::BTreeMap;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use handlebars::Handlebars;
|
use handlebars::Handlebars;
|
||||||
|
@ -75,8 +72,12 @@ use libimagentryref::reference::Ref;
|
||||||
use libimagentryref::refstore::RefStore;
|
use libimagentryref::refstore::RefStore;
|
||||||
|
|
||||||
mod ui;
|
mod ui;
|
||||||
|
mod util;
|
||||||
|
mod create;
|
||||||
|
|
||||||
use ui::build_ui;
|
use ui::build_ui;
|
||||||
|
use util::build_data_object_for_handlebars;
|
||||||
|
use create::create;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let rt = generate_runtime_setup("imag-contact",
|
let rt = generate_runtime_setup("imag-contact",
|
||||||
|
@ -93,6 +94,7 @@ fn main() {
|
||||||
"list" => list(&rt),
|
"list" => list(&rt),
|
||||||
"import" => import(&rt),
|
"import" => import(&rt),
|
||||||
"show" => show(&rt),
|
"show" => show(&rt),
|
||||||
|
"create" => create(&rt),
|
||||||
_ => {
|
_ => {
|
||||||
error!("Unknown command"); // More error handling
|
error!("Unknown command"); // More error handling
|
||||||
},
|
},
|
||||||
|
@ -211,108 +213,6 @@ fn show(rt: &Runtime) {
|
||||||
info!("Ok");
|
info!("Ok");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_data_object_for_handlebars<'a>(i: usize, hash: String, vcard: &Vcard) -> BTreeMap<&'static str, String> {
|
|
||||||
let mut data = BTreeMap::new();
|
|
||||||
{
|
|
||||||
data.insert("i" , format!("{}", i));
|
|
||||||
|
|
||||||
/// The hash (as in libimagentryref) of the contact
|
|
||||||
data.insert("id" , hash);
|
|
||||||
|
|
||||||
data.insert("ADR" , vcard.adr()
|
|
||||||
.into_iter().map(|c| c.raw().clone()).collect());
|
|
||||||
|
|
||||||
data.insert("ANNIVERSARY" , vcard.anniversary()
|
|
||||||
.map(|c| c.raw().clone()).unwrap_or(String::new()));
|
|
||||||
|
|
||||||
data.insert("BDAY" , vcard.bday()
|
|
||||||
.map(|c| c.raw().clone()).unwrap_or(String::new()));
|
|
||||||
|
|
||||||
data.insert("CATEGORIES" , vcard.categories()
|
|
||||||
.into_iter().map(|c| c.raw().clone()).collect());
|
|
||||||
|
|
||||||
data.insert("CLIENTPIDMAP" , vcard.clientpidmap()
|
|
||||||
.map(|c| c.raw().clone()).unwrap_or(String::new()));
|
|
||||||
|
|
||||||
data.insert("EMAIL" , vcard.email()
|
|
||||||
.into_iter().map(|c| c.raw().clone()).collect());
|
|
||||||
|
|
||||||
data.insert("FN" , vcard.fullname()
|
|
||||||
.into_iter().map(|c| c.raw().clone()).collect());
|
|
||||||
|
|
||||||
data.insert("GENDER" , vcard.gender()
|
|
||||||
.map(|c| c.raw().clone()).unwrap_or(String::new()));
|
|
||||||
|
|
||||||
data.insert("GEO" , vcard.geo()
|
|
||||||
.into_iter().map(|c| c.raw().clone()).collect());
|
|
||||||
|
|
||||||
data.insert("IMPP" , vcard.impp()
|
|
||||||
.into_iter().map(|c| c.raw().clone()).collect());
|
|
||||||
|
|
||||||
data.insert("KEY" , vcard.key()
|
|
||||||
.into_iter().map(|c| c.raw().clone()).collect());
|
|
||||||
|
|
||||||
data.insert("LANG" , vcard.lang()
|
|
||||||
.into_iter().map(|c| c.raw().clone()).collect());
|
|
||||||
|
|
||||||
data.insert("LOGO" , vcard.logo()
|
|
||||||
.into_iter().map(|c| c.raw().clone()).collect());
|
|
||||||
|
|
||||||
data.insert("MEMBER" , vcard.member()
|
|
||||||
.into_iter().map(|c| c.raw().clone()).collect());
|
|
||||||
|
|
||||||
data.insert("N" , vcard.name()
|
|
||||||
.map(|c| c.raw().clone()).unwrap_or(String::new()));
|
|
||||||
|
|
||||||
data.insert("NICKNAME" , vcard.nickname()
|
|
||||||
.into_iter().map(|c| c.raw().clone()).collect());
|
|
||||||
|
|
||||||
data.insert("NOTE" , vcard.note()
|
|
||||||
.into_iter().map(|c| c.raw().clone()).collect());
|
|
||||||
|
|
||||||
data.insert("ORG" , vcard.org()
|
|
||||||
.into_iter().map(|c| c.raw().clone()).collect());
|
|
||||||
|
|
||||||
data.insert("PHOTO" , vcard.photo()
|
|
||||||
.into_iter().map(|c| c.raw().clone()).collect());
|
|
||||||
|
|
||||||
data.insert("PRIOD" , vcard.proid()
|
|
||||||
.map(|c| c.raw().clone()).unwrap_or(String::new()));
|
|
||||||
|
|
||||||
data.insert("RELATED" , vcard.related()
|
|
||||||
.into_iter().map(|c| c.raw().clone()).collect());
|
|
||||||
|
|
||||||
data.insert("REV" , vcard.rev()
|
|
||||||
.map(|c| c.raw().clone()).unwrap_or(String::new()));
|
|
||||||
|
|
||||||
data.insert("ROLE" , vcard.role()
|
|
||||||
.into_iter().map(|c| c.raw().clone()).collect());
|
|
||||||
|
|
||||||
data.insert("SOUND" , vcard.sound()
|
|
||||||
.into_iter().map(|c| c.raw().clone()).collect());
|
|
||||||
|
|
||||||
data.insert("TEL" , vcard.tel()
|
|
||||||
.into_iter().map(|c| c.raw().clone()).collect());
|
|
||||||
|
|
||||||
data.insert("TITLE" , vcard.title()
|
|
||||||
.into_iter().map(|c| c.raw().clone()).collect());
|
|
||||||
|
|
||||||
data.insert("TZ" , vcard.tz()
|
|
||||||
.into_iter().map(|c| c.raw().clone()).collect());
|
|
||||||
|
|
||||||
data.insert("UID" , vcard.uid()
|
|
||||||
.map(|c| c.raw().clone()).unwrap_or(String::new()));
|
|
||||||
|
|
||||||
data.insert("URL" , vcard.url()
|
|
||||||
.into_iter().map(|c| c.raw().clone()).collect());
|
|
||||||
|
|
||||||
data.insert("VERSION" , vcard.version()
|
|
||||||
.map(|c| c.raw().clone()).unwrap_or(String::new()));
|
|
||||||
}
|
|
||||||
|
|
||||||
data
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
||||||
let fmt = scmd
|
let fmt = scmd
|
||||||
.value_of("format")
|
.value_of("format")
|
||||||
|
|
|
@ -70,4 +70,24 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
|
||||||
.value_name("FORMAT")
|
.value_name("FORMAT")
|
||||||
.help("Format to format the contact when printing it"))
|
.help("Format to format the contact when printing it"))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
.subcommand(SubCommand::with_name("create")
|
||||||
|
.about("Create a contact file (.vcf) and track it in imag.")
|
||||||
|
.version("0.1")
|
||||||
|
.arg(Arg::with_name("file-location")
|
||||||
|
.short("F")
|
||||||
|
.long("file")
|
||||||
|
.takes_value(true)
|
||||||
|
.required(false)
|
||||||
|
.multiple(false)
|
||||||
|
.value_name("PATH")
|
||||||
|
.help("Create this file. If a directory is passed, a file with a uuid as name will be created. vcf contents are dumped to stdout if this is not passed."))
|
||||||
|
.arg(Arg::with_name("dont-track")
|
||||||
|
.short("T")
|
||||||
|
.long("no-track")
|
||||||
|
.takes_value(false)
|
||||||
|
.required(false)
|
||||||
|
.multiple(false)
|
||||||
|
.help("Don't track the new vcf file if one is created."))
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
124
bin/domain/imag-contact/src/util.rs
Normal file
124
bin/domain/imag-contact/src/util.rs
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
//
|
||||||
|
// imag - the personal information management suite for the commandline
|
||||||
|
// Copyright (C) 2015, 2016 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 std::collections::BTreeMap;
|
||||||
|
use vobject::vcard::Vcard;
|
||||||
|
|
||||||
|
pub fn build_data_object_for_handlebars<'a>(i: usize, hash: String, vcard: &Vcard) -> BTreeMap<&'static str, String> {
|
||||||
|
let mut data = BTreeMap::new();
|
||||||
|
{
|
||||||
|
data.insert("i" , format!("{}", i));
|
||||||
|
|
||||||
|
/// The hash (as in libimagentryref) of the contact
|
||||||
|
data.insert("id" , hash);
|
||||||
|
|
||||||
|
data.insert("ADR" , vcard.adr()
|
||||||
|
.into_iter().map(|c| c.raw().clone()).collect());
|
||||||
|
|
||||||
|
data.insert("ANNIVERSARY" , vcard.anniversary()
|
||||||
|
.map(|c| c.raw().clone()).unwrap_or(String::new()));
|
||||||
|
|
||||||
|
data.insert("BDAY" , vcard.bday()
|
||||||
|
.map(|c| c.raw().clone()).unwrap_or(String::new()));
|
||||||
|
|
||||||
|
data.insert("CATEGORIES" , vcard.categories()
|
||||||
|
.into_iter().map(|c| c.raw().clone()).collect());
|
||||||
|
|
||||||
|
data.insert("CLIENTPIDMAP" , vcard.clientpidmap()
|
||||||
|
.map(|c| c.raw().clone()).unwrap_or(String::new()));
|
||||||
|
|
||||||
|
data.insert("EMAIL" , vcard.email()
|
||||||
|
.into_iter().map(|c| c.raw().clone()).collect());
|
||||||
|
|
||||||
|
data.insert("FN" , vcard.fullname()
|
||||||
|
.into_iter().map(|c| c.raw().clone()).collect());
|
||||||
|
|
||||||
|
data.insert("GENDER" , vcard.gender()
|
||||||
|
.map(|c| c.raw().clone()).unwrap_or(String::new()));
|
||||||
|
|
||||||
|
data.insert("GEO" , vcard.geo()
|
||||||
|
.into_iter().map(|c| c.raw().clone()).collect());
|
||||||
|
|
||||||
|
data.insert("IMPP" , vcard.impp()
|
||||||
|
.into_iter().map(|c| c.raw().clone()).collect());
|
||||||
|
|
||||||
|
data.insert("KEY" , vcard.key()
|
||||||
|
.into_iter().map(|c| c.raw().clone()).collect());
|
||||||
|
|
||||||
|
data.insert("LANG" , vcard.lang()
|
||||||
|
.into_iter().map(|c| c.raw().clone()).collect());
|
||||||
|
|
||||||
|
data.insert("LOGO" , vcard.logo()
|
||||||
|
.into_iter().map(|c| c.raw().clone()).collect());
|
||||||
|
|
||||||
|
data.insert("MEMBER" , vcard.member()
|
||||||
|
.into_iter().map(|c| c.raw().clone()).collect());
|
||||||
|
|
||||||
|
data.insert("N" , vcard.name()
|
||||||
|
.map(|c| c.raw().clone()).unwrap_or(String::new()));
|
||||||
|
|
||||||
|
data.insert("NICKNAME" , vcard.nickname()
|
||||||
|
.into_iter().map(|c| c.raw().clone()).collect());
|
||||||
|
|
||||||
|
data.insert("NOTE" , vcard.note()
|
||||||
|
.into_iter().map(|c| c.raw().clone()).collect());
|
||||||
|
|
||||||
|
data.insert("ORG" , vcard.org()
|
||||||
|
.into_iter().map(|c| c.raw().clone()).collect());
|
||||||
|
|
||||||
|
data.insert("PHOTO" , vcard.photo()
|
||||||
|
.into_iter().map(|c| c.raw().clone()).collect());
|
||||||
|
|
||||||
|
data.insert("PRIOD" , vcard.proid()
|
||||||
|
.map(|c| c.raw().clone()).unwrap_or(String::new()));
|
||||||
|
|
||||||
|
data.insert("RELATED" , vcard.related()
|
||||||
|
.into_iter().map(|c| c.raw().clone()).collect());
|
||||||
|
|
||||||
|
data.insert("REV" , vcard.rev()
|
||||||
|
.map(|c| c.raw().clone()).unwrap_or(String::new()));
|
||||||
|
|
||||||
|
data.insert("ROLE" , vcard.role()
|
||||||
|
.into_iter().map(|c| c.raw().clone()).collect());
|
||||||
|
|
||||||
|
data.insert("SOUND" , vcard.sound()
|
||||||
|
.into_iter().map(|c| c.raw().clone()).collect());
|
||||||
|
|
||||||
|
data.insert("TEL" , vcard.tel()
|
||||||
|
.into_iter().map(|c| c.raw().clone()).collect());
|
||||||
|
|
||||||
|
data.insert("TITLE" , vcard.title()
|
||||||
|
.into_iter().map(|c| c.raw().clone()).collect());
|
||||||
|
|
||||||
|
data.insert("TZ" , vcard.tz()
|
||||||
|
.into_iter().map(|c| c.raw().clone()).collect());
|
||||||
|
|
||||||
|
data.insert("UID" , vcard.uid()
|
||||||
|
.map(|c| c.raw().clone()).unwrap_or(String::new()));
|
||||||
|
|
||||||
|
data.insert("URL" , vcard.url()
|
||||||
|
.into_iter().map(|c| c.raw().clone()).collect());
|
||||||
|
|
||||||
|
data.insert("VERSION" , vcard.version()
|
||||||
|
.map(|c| c.raw().clone()).unwrap_or(String::new()));
|
||||||
|
}
|
||||||
|
|
||||||
|
data
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
# Contact template for imag-contact version 0.5.0
|
||||||
|
#
|
||||||
|
# This file is explicitely _not_ distributed under the terms of the original imag license, but
|
||||||
|
# public domain.
|
||||||
|
#
|
||||||
|
# Use this TOML formatted template to create a new contact.
|
||||||
|
|
||||||
|
[name]
|
||||||
|
|
||||||
|
# every entry may contain a string or a list of strings
|
||||||
|
# E.G.:
|
||||||
|
# first = "Foo"
|
||||||
|
# last = [ "bar", "bar", "a" ]
|
||||||
|
prefix = "test"
|
||||||
|
first = "test"
|
||||||
|
additional = "test"
|
||||||
|
last = "test"
|
||||||
|
suffix = "test"
|
||||||
|
|
||||||
|
[person]
|
||||||
|
|
||||||
|
# Birthday
|
||||||
|
# Format: YYYY-MM-DD
|
||||||
|
birthday = "2017-01-01"
|
||||||
|
|
||||||
|
# Nickname
|
||||||
|
# "type" is optional
|
||||||
|
[[nickname]]
|
||||||
|
type = "work"
|
||||||
|
name = "boss"
|
||||||
|
|
||||||
|
[organisation]
|
||||||
|
|
||||||
|
# Organisation name
|
||||||
|
# May contain a string or a list of strings
|
||||||
|
name = "test"
|
||||||
|
|
||||||
|
# Organisation title and role
|
||||||
|
# May contain a string or a list of strings
|
||||||
|
title = "test"
|
||||||
|
|
||||||
|
# Role at organisation
|
||||||
|
# May contain a string or a list of strings
|
||||||
|
role = "test"
|
||||||
|
|
||||||
|
# allowed types:
|
||||||
|
# vcard 3.0: At least one of bbs, car, cell, fax, home, isdn, msg, modem,
|
||||||
|
# pager, pcs, pref, video, voice, work
|
||||||
|
# vcard 4.0: At least one of home, work, pref, text, voice, fax, cell, video,
|
||||||
|
# pager, textphone
|
||||||
|
phone = [
|
||||||
|
{ "type" = "home", "number" = "0123 123456789" },
|
||||||
|
]
|
||||||
|
|
||||||
|
#
|
||||||
|
# Email addresses
|
||||||
|
#
|
||||||
|
email = [
|
||||||
|
{ "type" = "home", "addr" = "examle@examplemail.org" },
|
||||||
|
]
|
||||||
|
|
||||||
|
# post addresses
|
||||||
|
#
|
||||||
|
# allowed types:
|
||||||
|
# vcard 3.0: At least one of dom, intl, home, parcel, postal, pref, work
|
||||||
|
# vcard 4.0: At least one of home, pref, work
|
||||||
|
[[addresses]]
|
||||||
|
type = "home"
|
||||||
|
box = "test"
|
||||||
|
extended = "test"
|
||||||
|
street = "test"
|
||||||
|
code = "test"
|
||||||
|
city = "test"
|
||||||
|
region = "test"
|
||||||
|
country = "test"
|
||||||
|
|
||||||
|
[other]
|
||||||
|
|
||||||
|
# categories or tags
|
||||||
|
# May contain a string or a list of strings
|
||||||
|
categories = "test"
|
||||||
|
|
||||||
|
# Web pages
|
||||||
|
# May contain a string or a list of strings
|
||||||
|
webpage = "test"
|
||||||
|
|
||||||
|
# Notes
|
||||||
|
# May contain a string or a list of strings
|
||||||
|
note = "test"
|
||||||
|
|
90
bin/domain/imag-contact/static/new-contact-template.toml
Normal file
90
bin/domain/imag-contact/static/new-contact-template.toml
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
# Contact template for imag-contact version 0.5.0
|
||||||
|
#
|
||||||
|
# This file is explicitely _not_ distributed under the terms of the original imag license, but
|
||||||
|
# public domain.
|
||||||
|
#
|
||||||
|
# Use this TOML formatted template to create a new contact.
|
||||||
|
|
||||||
|
[name]
|
||||||
|
|
||||||
|
# every entry may contain a string or a list of strings
|
||||||
|
# E.G.:
|
||||||
|
# first = "Foo"
|
||||||
|
# last = [ "bar", "bar", "a" ]
|
||||||
|
#prefix = ""
|
||||||
|
first = ""
|
||||||
|
#additional = ""
|
||||||
|
last = ""
|
||||||
|
#suffix = ""
|
||||||
|
|
||||||
|
[person]
|
||||||
|
|
||||||
|
# Birthday
|
||||||
|
# Format: YYYY-MM-DD
|
||||||
|
#birthday = ""
|
||||||
|
|
||||||
|
# Nickname
|
||||||
|
# "type" is optional
|
||||||
|
#[[nickname]]
|
||||||
|
#type = "work"
|
||||||
|
#name = "boss"
|
||||||
|
|
||||||
|
[organisation]
|
||||||
|
|
||||||
|
# Organisation name
|
||||||
|
# May contain a string or a list of strings
|
||||||
|
#name = ""
|
||||||
|
|
||||||
|
# Organisation title and role
|
||||||
|
# May contain a string or a list of strings
|
||||||
|
#title = ""
|
||||||
|
|
||||||
|
# Role at organisation
|
||||||
|
# May contain a string or a list of strings
|
||||||
|
#role = ""
|
||||||
|
|
||||||
|
# allowed types:
|
||||||
|
# vcard 3.0: At least one of bbs, car, cell, fax, home, isdn, msg, modem,
|
||||||
|
# pager, pcs, pref, video, voice, work
|
||||||
|
# vcard 4.0: At least one of home, work, pref, text, voice, fax, cell, video,
|
||||||
|
# pager, textphone
|
||||||
|
#phone = [
|
||||||
|
# { "type" = "home", "number" = "0123 123456789" },
|
||||||
|
#]
|
||||||
|
|
||||||
|
#
|
||||||
|
# Email addresses
|
||||||
|
#
|
||||||
|
#email = [
|
||||||
|
# { "type" = "home", "addr" = "examle@examplemail.org" },
|
||||||
|
#]
|
||||||
|
|
||||||
|
# post addresses
|
||||||
|
#
|
||||||
|
# allowed types:
|
||||||
|
# vcard 3.0: At least one of dom, intl, home, parcel, postal, pref, work
|
||||||
|
# vcard 4.0: At least one of home, pref, work
|
||||||
|
#[[addresses]]
|
||||||
|
#type = "home"
|
||||||
|
#box = ""
|
||||||
|
#extended = ""
|
||||||
|
#street = ""
|
||||||
|
#code = ""
|
||||||
|
#city = ""
|
||||||
|
#region = ""
|
||||||
|
#country = ""
|
||||||
|
|
||||||
|
[other]
|
||||||
|
|
||||||
|
# categories or tags
|
||||||
|
# May contain a string or a list of strings
|
||||||
|
#categories = ""
|
||||||
|
|
||||||
|
# Web pages
|
||||||
|
# May contain a string or a list of strings
|
||||||
|
#webpage = ""
|
||||||
|
|
||||||
|
# Notes
|
||||||
|
# May contain a string or a list of strings
|
||||||
|
#note = ""
|
||||||
|
|
Loading…
Reference in a new issue