Merge pull request #1132 from matthiasbeyer/imag-contact/init
Imag contact/init
This commit is contained in:
commit
b2377c3bc8
11 changed files with 1314 additions and 1 deletions
|
@ -12,6 +12,7 @@ members = [
|
|||
"bin/core/imag-tag",
|
||||
"bin/core/imag-view",
|
||||
"bin/domain/imag-bookmark",
|
||||
"bin/domain/imag-contact",
|
||||
"bin/domain/imag-diary",
|
||||
"bin/domain/imag-mail",
|
||||
"bin/domain/imag-notes",
|
||||
|
|
35
bin/domain/imag-contact/Cargo.toml
Normal file
35
bin/domain/imag-contact/Cargo.toml
Normal file
|
@ -0,0 +1,35 @@
|
|||
[package]
|
||||
name = "imag-contact"
|
||||
version = "0.5.0"
|
||||
authors = ["Matthias Beyer <mail@beyermatthias.de>"]
|
||||
|
||||
description = "Part of the imag core distribution: imag-contact command"
|
||||
|
||||
keywords = ["imag", "PIM", "personal", "information", "management"]
|
||||
readme = "../../../README.md"
|
||||
license = "LGPL-2.1"
|
||||
|
||||
documentation = "https://matthiasbeyer.github.io/imag/imag_documentation/index.html"
|
||||
repository = "https://github.com/matthiasbeyer/imag"
|
||||
homepage = "http://imag-pim.org"
|
||||
|
||||
[dependencies]
|
||||
clap = ">=2.17"
|
||||
log = "0.3"
|
||||
version = "2.0.1"
|
||||
toml = "0.4"
|
||||
toml-query = "^0.3.1"
|
||||
handlebars = "0.29"
|
||||
vobject = "0.4"
|
||||
walkdir = "1"
|
||||
uuid = { version = "0.5", features = ["v4"] }
|
||||
|
||||
libimagrt = { version = "0.5.0", path = "../../../lib/core/libimagrt" }
|
||||
libimagstore = { version = "0.5.0", path = "../../../lib/core/libimagstore" }
|
||||
libimagerror = { version = "0.5.0", path = "../../../lib/core/libimagerror" }
|
||||
libimagcontact = { version = "0.5.0", path = "../../../lib/domain/libimagcontact" }
|
||||
libimagutil = { version = "0.5.0", path = "../../../lib/etc/libimagutil" }
|
||||
libimagentryref = { version = "0.5.0", path = "../../../lib/entry/libimagentryref" }
|
||||
libimagentryedit = { version = "0.5.0", path = "../../../lib/entry/libimagentryedit" }
|
||||
libimaginteraction = { version = "0.5.0", path = "../../../lib/etc/libimaginteraction" }
|
||||
|
570
bin/domain/imag-contact/src/create.rs
Normal file
570
bin/domain/imag-contact/src/create.rs
Normal file
|
@ -0,0 +1,570 @@
|
|||
//
|
||||
// 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 {
|
||||
{ yes => $yes:expr; no => $no:expr } => {
|
||||
if ::libimaginteraction::ask::ask_bool("Edit tempfile", Some(true)) {
|
||||
$yes
|
||||
} else {
|
||||
$no
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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.push(Uuid::new_v4().hyphenated().to_string());
|
||||
info!("Creating file: {:?}", fl);
|
||||
}
|
||||
|
||||
debug!("Destination = {:?}", fl);
|
||||
|
||||
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).map(parse_toml_into_vcard) {
|
||||
Err(e) => {
|
||||
error!("Error parsing template");
|
||||
trace_error(&e);
|
||||
ask_continue! { yes => continue; no => exit(1) };
|
||||
},
|
||||
|
||||
Ok(None) => continue,
|
||||
Ok(Some(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 parse_toml_into_vcard(toml: Value) -> Option<Vcard> {
|
||||
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 personal
|
||||
debug!("Parsing person information");
|
||||
let birthday = read_str_from_toml(&toml, "person.birthday");
|
||||
trace!("birthday = {:?}", birthday);
|
||||
|
||||
if let Some(bday) = birthday {
|
||||
vcard = vcard.with_bday(parameters!(), bday);
|
||||
}
|
||||
}
|
||||
|
||||
{ // 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") {
|
||||
None => BTreeMap::new(),
|
||||
Some(p) => {
|
||||
let mut m = BTreeMap::new();
|
||||
m.insert("TYPE".into(), p);
|
||||
m
|
||||
},
|
||||
};
|
||||
|
||||
let name = match read_str_from_toml(element, "name") {
|
||||
Some(p) => p,
|
||||
None => {
|
||||
error!("Key 'nickname.[{}].name' missing", i);
|
||||
ask_continue! { yes => return None; no => exit(1) };
|
||||
},
|
||||
};
|
||||
|
||||
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! { yes => return None; no => exit(1) };
|
||||
},
|
||||
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! { yes => return None; no => exit(1) };
|
||||
}
|
||||
|
||||
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("person.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! { yes => return None; no => exit(1) };
|
||||
}
|
||||
};
|
||||
|
||||
let number = match read_str_from_toml(element, "number") {
|
||||
Some(p) => p,
|
||||
None => {
|
||||
error!("Key 'phones.[{}].number' missing", i);
|
||||
ask_continue! { yes => return None; no => exit(1) };
|
||||
}
|
||||
};
|
||||
|
||||
trace!("phonetype = {:?}", phonetype);
|
||||
trace!("number = {:?}", number);
|
||||
|
||||
vcard = vcard.with_tel(parameters!("TYPE" => phonetype), number);
|
||||
}
|
||||
},
|
||||
|
||||
Some(_) => {
|
||||
error!("Expected Array at 'phones'.");
|
||||
ask_continue! { yes => return None; no => exit(1) };
|
||||
},
|
||||
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! { yes => return None; no => exit(1) };
|
||||
},
|
||||
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! { yes => return None; no => exit(1) };
|
||||
},
|
||||
None => {
|
||||
// nothing
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
{ // parse email
|
||||
debug!("Parsing email");
|
||||
match toml.read("person.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! { yes => return None; no => exit(1) };
|
||||
},
|
||||
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! { yes => return None; no => exit(1) };
|
||||
},
|
||||
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! { yes => return None; no => exit(1) };
|
||||
},
|
||||
None => {
|
||||
// nothing
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
{ // parse others
|
||||
debug!("Parsing others");
|
||||
if let Some(categories) = read_strary_from_toml(&toml, "other.categories") {
|
||||
vcard = vcard.with_categories(categories);
|
||||
} else {
|
||||
debug!("No categories");
|
||||
}
|
||||
|
||||
if let Some(webpage) = read_str_from_toml(&toml, "other.webpage") {
|
||||
vcard = vcard.with_url(webpage);
|
||||
} else {
|
||||
debug!("No webpage");
|
||||
}
|
||||
|
||||
if let Some(note) = read_str_from_toml(&toml, "other.note") {
|
||||
vcard = vcard.with_note(note);
|
||||
} else {
|
||||
debug!("No note");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Some(vcard)
|
||||
}
|
||||
|
||||
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_parsing {
|
||||
use super::parse_toml_into_vcard;
|
||||
|
||||
// TODO
|
||||
const TEMPLATE : &'static str = include_str!("../static/new-contact-template-test.toml");
|
||||
|
||||
#[test]
|
||||
fn test_template_names() {
|
||||
let vcard = parse_toml_into_vcard(::toml::de::from_str(TEMPLATE).unwrap());
|
||||
assert!(vcard.is_some(), "Failed to parse test template.");
|
||||
let vcard = vcard.unwrap();
|
||||
|
||||
assert!(vcard.name().is_some());
|
||||
|
||||
assert_eq!(vcard.name().unwrap().surname().unwrap(), "test");
|
||||
assert_eq!(vcard.name().unwrap().given_name().unwrap(), "test");
|
||||
assert_eq!(vcard.name().unwrap().additional_names().unwrap(), "test");
|
||||
assert_eq!(vcard.name().unwrap().honorific_prefixes().unwrap(), "test");
|
||||
assert_eq!(vcard.name().unwrap().honorific_suffixes().unwrap(), "test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_template_person() {
|
||||
let vcard = parse_toml_into_vcard(::toml::de::from_str(TEMPLATE).unwrap());
|
||||
assert!(vcard.is_some(), "Failed to parse test template.");
|
||||
let vcard = vcard.unwrap();
|
||||
|
||||
assert!(vcard.bday().is_some());
|
||||
|
||||
assert_eq!(vcard.bday().unwrap().raw(), "2017-01-01");
|
||||
|
||||
assert_eq!(vcard.nickname().len(), 1);
|
||||
assert_eq!(vcard.nickname()[0].raw(), "boss");
|
||||
|
||||
// TODO: parameters() not yet implemented in underlying API
|
||||
// assert!(vcard.nickname()[0].parameters().contains_key("work"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_template_organization() {
|
||||
let vcard = parse_toml_into_vcard(::toml::de::from_str(TEMPLATE).unwrap());
|
||||
assert!(vcard.is_some(), "Failed to parse test template.");
|
||||
let vcard = vcard.unwrap();
|
||||
|
||||
assert_eq!(vcard.org().len(), 1);
|
||||
assert_eq!(vcard.org()[0].raw(), "test");
|
||||
|
||||
assert_eq!(vcard.title().len(), 1);
|
||||
assert_eq!(vcard.title()[0].raw(), "test");
|
||||
|
||||
assert_eq!(vcard.role().len(), 1);
|
||||
assert_eq!(vcard.role()[0].raw(), "test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_template_phone() {
|
||||
let vcard = parse_toml_into_vcard(::toml::de::from_str(TEMPLATE).unwrap());
|
||||
assert!(vcard.is_some(), "Failed to parse test template.");
|
||||
let vcard = vcard.unwrap();
|
||||
|
||||
assert_eq!(vcard.tel().len(), 1);
|
||||
assert_eq!(vcard.tel()[0].raw(), "0123 123456789");
|
||||
|
||||
// TODO: parameters() not yet implemented in underlying API
|
||||
// assert!(vcard.tel()[0].parameters().contains_key("type"));
|
||||
// assert_eq!(vcard.tel()[0].parameters().get("type").unwrap(), "home");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_template_email() {
|
||||
let vcard = parse_toml_into_vcard(::toml::de::from_str(TEMPLATE).unwrap());
|
||||
assert!(vcard.is_some(), "Failed to parse test template.");
|
||||
let vcard = vcard.unwrap();
|
||||
|
||||
assert_eq!(vcard.email().len(), 1);
|
||||
assert_eq!(vcard.email()[0].raw(), "examle@examplemail.org");
|
||||
|
||||
// TODO: parameters() not yet implemented in underlying API
|
||||
// assert!(vcard.email()[0].parameters().contains_key("type"));
|
||||
// assert_eq!(vcard.email()[0].parameters().get("type").unwrap(), "home");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_template_addresses() {
|
||||
let vcard = parse_toml_into_vcard(::toml::de::from_str(TEMPLATE).unwrap());
|
||||
assert!(vcard.is_some(), "Failed to parse test template.");
|
||||
let vcard = vcard.unwrap();
|
||||
|
||||
assert_eq!(vcard.adr().len(), 1);
|
||||
assert_eq!(vcard.adr()[0].raw(), "testbox;testextended;teststreet;testcode;testcity;testregion;testcountry");
|
||||
|
||||
// TODO: parameters() not yet implemented in underlying API
|
||||
//for e in &["box", "extended", "street", "code", "city", "region", "country"] {
|
||||
// assert!(vcard.adr()[0].parameters().contains_key(e));
|
||||
// assert_eq!(vcard.adr()[0].parameters().get(e).unwrap(), "test");
|
||||
//}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_template_other() {
|
||||
let vcard = parse_toml_into_vcard(::toml::de::from_str(TEMPLATE).unwrap());
|
||||
assert!(vcard.is_some(), "Failed to parse test template.");
|
||||
let vcard = vcard.unwrap();
|
||||
|
||||
assert_eq!(vcard.categories().len(), 1);
|
||||
assert_eq!(vcard.categories()[0].raw(), "test");
|
||||
|
||||
assert_eq!(vcard.url().len(), 1);
|
||||
assert_eq!(vcard.url()[0].raw(), "test");
|
||||
|
||||
assert_eq!(vcard.note().len(), 1);
|
||||
assert_eq!(vcard.note()[0].raw(), "test");
|
||||
}
|
||||
}
|
||||
|
248
bin/domain/imag-contact/src/main.rs
Normal file
248
bin/domain/imag-contact/src/main.rs
Normal file
|
@ -0,0 +1,248 @@
|
|||
//
|
||||
// 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
|
||||
//
|
||||
|
||||
#![deny(
|
||||
non_camel_case_types,
|
||||
non_snake_case,
|
||||
path_statements,
|
||||
trivial_numeric_casts,
|
||||
unstable_features,
|
||||
unused_allocation,
|
||||
unused_import_braces,
|
||||
unused_imports,
|
||||
unused_must_use,
|
||||
unused_mut,
|
||||
unused_qualifications,
|
||||
while_true,
|
||||
)]
|
||||
|
||||
extern crate clap;
|
||||
#[macro_use] extern crate log;
|
||||
#[macro_use] extern crate version;
|
||||
#[macro_use] extern crate vobject;
|
||||
extern crate toml;
|
||||
extern crate toml_query;
|
||||
extern crate handlebars;
|
||||
extern crate walkdir;
|
||||
extern crate uuid;
|
||||
|
||||
extern crate libimagcontact;
|
||||
extern crate libimagstore;
|
||||
extern crate libimagrt;
|
||||
extern crate libimagerror;
|
||||
extern crate libimagutil;
|
||||
extern crate libimaginteraction;
|
||||
extern crate libimagentryref;
|
||||
extern crate libimagentryedit;
|
||||
|
||||
use std::process::exit;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use handlebars::Handlebars;
|
||||
use clap::ArgMatches;
|
||||
use vobject::vcard::Vcard;
|
||||
use toml_query::read::TomlValueReadExt;
|
||||
use toml::Value;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use libimagrt::runtime::Runtime;
|
||||
use libimagrt::setup::generate_runtime_setup;
|
||||
use libimagerror::trace::MapErrTrace;
|
||||
use libimagcontact::store::ContactStore;
|
||||
use libimagcontact::error::ContactError as CE;
|
||||
use libimagcontact::contact::Contact;
|
||||
use libimagstore::iter::get::StoreIdGetIteratorExtension;
|
||||
use libimagentryref::reference::Ref;
|
||||
use libimagentryref::refstore::RefStore;
|
||||
|
||||
mod ui;
|
||||
mod util;
|
||||
mod create;
|
||||
|
||||
use ui::build_ui;
|
||||
use util::build_data_object_for_handlebars;
|
||||
use create::create;
|
||||
|
||||
fn main() {
|
||||
let rt = generate_runtime_setup("imag-contact",
|
||||
&version!()[..],
|
||||
"Contact management tool",
|
||||
build_ui);
|
||||
|
||||
|
||||
rt.cli()
|
||||
.subcommand_name()
|
||||
.map(|name| {
|
||||
debug!("Call {}", name);
|
||||
match name {
|
||||
"list" => list(&rt),
|
||||
"import" => import(&rt),
|
||||
"show" => show(&rt),
|
||||
"create" => create(&rt),
|
||||
_ => {
|
||||
error!("Unknown command"); // More error handling
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
.store()
|
||||
.all_contacts()
|
||||
.map_err_trace_exit(1)
|
||||
.unwrap() // safed by above call
|
||||
.into_get_iter(rt.store())
|
||||
.map(|fle| {
|
||||
let fle = fle
|
||||
.map_err_trace_exit(1)
|
||||
.unwrap()
|
||||
.ok_or_else(|| CE::from("StoreId not found".to_owned()))
|
||||
.map_err_trace_exit(1)
|
||||
.unwrap();
|
||||
|
||||
fle
|
||||
.get_contact_data()
|
||||
.map(|cd| (fle, cd))
|
||||
.map(|(fle, cd)| (fle, cd.into_inner()))
|
||||
.map(|(fle, cd)| (fle, Vcard::from_component(cd)))
|
||||
.map_err_trace_exit(1)
|
||||
.unwrap()
|
||||
})
|
||||
.enumerate()
|
||||
.map(|(i, (fle, vcard))| {
|
||||
let hash = fle.get_path_hash().map_err_trace_exit(1).unwrap();
|
||||
let vcard = vcard.unwrap_or_else(|e| {
|
||||
error!("Element is not a VCARD object: {:?}", e);
|
||||
exit(1)
|
||||
});
|
||||
|
||||
let data = build_data_object_for_handlebars(i, hash, &vcard);
|
||||
|
||||
let s = list_format.render("format", &data)
|
||||
.map_err_trace_exit(1)
|
||||
.unwrap();
|
||||
println!("{}", s);
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
}
|
||||
|
||||
fn import(rt: &Runtime) {
|
||||
let scmd = rt.cli().subcommand_matches("import").unwrap(); // secured by main
|
||||
let path = scmd.value_of("path").map(PathBuf::from).unwrap(); // secured by clap
|
||||
|
||||
if !path.exists() {
|
||||
error!("Path does not exist");
|
||||
exit(1)
|
||||
}
|
||||
|
||||
if path.is_file() {
|
||||
let _ = rt
|
||||
.store()
|
||||
.create_from_path(&path)
|
||||
.map_err_trace_exit(1)
|
||||
.unwrap();
|
||||
} else if path.is_dir() {
|
||||
for entry in WalkDir::new(path).min_depth(1).into_iter() {
|
||||
let entry = entry.map_err_trace_exit(1).unwrap();
|
||||
if entry.file_type().is_file() {
|
||||
let pb = PathBuf::from(entry.path());
|
||||
let _ = rt
|
||||
.store()
|
||||
.create_from_path(&pb)
|
||||
.map_err_trace_exit(1)
|
||||
.unwrap();
|
||||
info!("Imported: {}", entry.path().to_str().unwrap_or("<non UTF-8 path>"));
|
||||
} else {
|
||||
warn!("Ignoring non-file: {}", entry.path().to_str().unwrap_or("<non UTF-8 path>"));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error!("Path is neither directory nor file");
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
fn show(rt: &Runtime) {
|
||||
let scmd = rt.cli().subcommand_matches("show").unwrap();
|
||||
let hash = scmd.value_of("hash").map(String::from).unwrap(); // safed by clap
|
||||
|
||||
let contact_data = rt.store()
|
||||
.get_by_hash(hash.clone())
|
||||
.map_err_trace_exit(1)
|
||||
.unwrap()
|
||||
.ok_or(CE::from(format!("No entry for hash {}", hash)))
|
||||
.map_err_trace_exit(1)
|
||||
.unwrap()
|
||||
.get_contact_data()
|
||||
.map_err_trace_exit(1)
|
||||
.unwrap()
|
||||
.into_inner();
|
||||
let vcard = Vcard::from_component(contact_data)
|
||||
.unwrap_or_else(|e| {
|
||||
error!("Element is not a VCARD object: {:?}", e);
|
||||
exit(1)
|
||||
});
|
||||
|
||||
let show_format = get_contact_print_format("contact.show_format", rt, &scmd);
|
||||
let data = build_data_object_for_handlebars(0, hash, &vcard);
|
||||
|
||||
let s = show_format.render("format", &data)
|
||||
.map_err_trace_exit(1)
|
||||
.unwrap();
|
||||
println!("{}", s);
|
||||
info!("Ok");
|
||||
}
|
||||
|
||||
fn get_contact_print_format(config_value_path: &'static str, rt: &Runtime, scmd: &ArgMatches) -> Handlebars {
|
||||
let fmt = scmd
|
||||
.value_of("format")
|
||||
.map(String::from)
|
||||
.unwrap_or_else(|| {
|
||||
rt.config()
|
||||
.ok_or_else(|| CE::from("No configuration file".to_owned()))
|
||||
.map_err_trace_exit(1)
|
||||
.unwrap()
|
||||
.read(config_value_path)
|
||||
.map_err_trace_exit(1)
|
||||
.unwrap()
|
||||
.ok_or_else(|| CE::from("Configuration 'contact.list_format' does not exist".to_owned()))
|
||||
.and_then(|value| match *value {
|
||||
Value::String(ref s) => Ok(s.clone()),
|
||||
_ => Err(CE::from("Type error: Expected String at 'contact.list_format'. Have non-String".to_owned()))
|
||||
})
|
||||
.map_err_trace_exit(1)
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
let mut hb = Handlebars::new();
|
||||
let _ = hb
|
||||
.register_template_string("format", fmt)
|
||||
.map_err_trace_exit(1)
|
||||
.unwrap();
|
||||
|
||||
hb.register_escape_fn(::handlebars::no_escape);
|
||||
::libimaginteraction::format::register_all_color_helpers(&mut hb);
|
||||
::libimaginteraction::format::register_all_format_helpers(&mut hb);
|
||||
hb
|
||||
}
|
||||
|
93
bin/domain/imag-contact/src/ui.rs
Normal file
93
bin/domain/imag-contact/src/ui.rs
Normal file
|
@ -0,0 +1,93 @@
|
|||
//
|
||||
// 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 clap::{Arg, App, SubCommand};
|
||||
|
||||
pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
|
||||
app
|
||||
.subcommand(SubCommand::with_name("list")
|
||||
.about("List contacts")
|
||||
.version("0.1")
|
||||
.arg(Arg::with_name("filter")
|
||||
.index(1)
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.multiple(true)
|
||||
.value_name("FILTER")
|
||||
.help("Filter by these properties (not implemented yet)"))
|
||||
.arg(Arg::with_name("format")
|
||||
.long("format")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.multiple(false)
|
||||
.value_name("FORMAT")
|
||||
.help("Format to format the listing"))
|
||||
)
|
||||
|
||||
.subcommand(SubCommand::with_name("import")
|
||||
.about("Import contacts")
|
||||
.version("0.1")
|
||||
.arg(Arg::with_name("path")
|
||||
.index(1)
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.multiple(false)
|
||||
.value_name("PATH")
|
||||
.help("Import from this file/directory"))
|
||||
)
|
||||
|
||||
.subcommand(SubCommand::with_name("show")
|
||||
.about("Show contact")
|
||||
.version("0.1")
|
||||
.arg(Arg::with_name("hash")
|
||||
.index(1)
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.multiple(false)
|
||||
.value_name("HASH")
|
||||
.help("Show the contact pointed to by this reference hash"))
|
||||
.arg(Arg::with_name("format")
|
||||
.long("format")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.multiple(false)
|
||||
.value_name("FORMAT")
|
||||
.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"
|
||||
|
||||
# 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 = "testbox"
|
||||
extended = "testextended"
|
||||
street = "teststreet"
|
||||
code = "testcode"
|
||||
city = "testcity"
|
||||
region = "testregion"
|
||||
country = "testcountry"
|
||||
|
||||
# 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"
|
||||
|
||||
[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 = ""
|
||||
|
||||
# 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 = ""
|
||||
|
||||
# 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 = ""
|
||||
|
||||
[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 = ""
|
||||
|
|
@ -33,6 +33,7 @@ This section contains the changelog from the last release to the next release.
|
|||
* The runtime does not read the config file for editor settings anymore.
|
||||
Specifying an editor either via CLI or via the `$EDITOR` environment
|
||||
variable still possible.
|
||||
* `imag-contact` was added (with basic contact support so far).
|
||||
|
||||
* Minor changes
|
||||
* `libimagentryannotation` got a rewrite, is not based on `libimagnotes`
|
||||
|
|
61
imagrc.toml
61
imagrc.toml
|
@ -255,3 +255,64 @@ default_collection = "default"
|
|||
editor = "vim -R {{entry}}"
|
||||
web = "chromium {{entry}}"
|
||||
|
||||
[contact]
|
||||
|
||||
# Format for listing contacts
|
||||
#
|
||||
# Available variables:
|
||||
# * "i" : Integer, counts the output lines
|
||||
# * "id" : The hash which can be used to print the entry itself.
|
||||
# * "ADR" : Array
|
||||
# * "ANNIVERSARY" : String
|
||||
# * "BDAY" : String
|
||||
# * "CATEGORIES" : Array<String>
|
||||
# * "CLIENTPIDMAP" : String
|
||||
# * "EMAIL" : Array<String>
|
||||
# * "FN" : Array<String>
|
||||
# * "GENDER" : String
|
||||
# * "GEO" : Array<String>
|
||||
# * "IMPP" : Array<String>
|
||||
# * "KEY" : Array<String>
|
||||
# * "LANG" : Array<String>
|
||||
# * "LOGO" : Array<String>
|
||||
# * "MEMBER" : Array<String>
|
||||
# * "N" : String
|
||||
# * "NICKNAME" : Array<String>
|
||||
# * "NOTE" : Array<String>
|
||||
# * "ORG" : Array<String>
|
||||
# * "PHOTO" : Array<String>
|
||||
# * "PRIOD" : String
|
||||
# * "RELATED" : Array<String>
|
||||
# * "REV" : String
|
||||
# * "ROLE" : Array<String>
|
||||
# * "SOUND" : Array<String>
|
||||
# * "TEL" : Array<String>
|
||||
# * "TITLE" : Array<String>
|
||||
# * "TZ" : Array<String>
|
||||
# * "UID" : String
|
||||
# * "URL" : Array<String>
|
||||
# * "VERSION" : String
|
||||
#
|
||||
# Multiple lines shouldn't be used, as this is for listing multiple entries.
|
||||
#
|
||||
# Note: Abbreviating the hash ("id") is not yet supported in the "show" command,
|
||||
# thus we print the id here without abbreviating it. To abbreviate it to 5
|
||||
# characters, use:
|
||||
#
|
||||
# {{abbrev 5 id}}
|
||||
#
|
||||
list_format = "{{lpad 5 i}} | {{id}} | {{FN}} | {{mail}} | {{adr}}"
|
||||
|
||||
# The format when printing a single contact
|
||||
#
|
||||
# Here, the same rules like for the list format apply.
|
||||
# Multiple lines should work fine.
|
||||
# The "i" variable defaults to zero (0)
|
||||
show_format = """
|
||||
{{id}} - {{UID}}
|
||||
|
||||
Full name: {{FN}}
|
||||
Email : {{EMAIL}}
|
||||
Address : {{ADR}}
|
||||
"""
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ error-chain = "0.11"
|
|||
log = "0.3"
|
||||
toml = "0.4"
|
||||
toml-query = "0.4"
|
||||
vobject = { git = 'https://github.com/matthiasbeyer/rust-vobject', branch = "next" }
|
||||
vobject = "0.4"
|
||||
|
||||
libimagstore = { version = "0.5.0", path = "../../../lib/core/libimagstore" }
|
||||
libimagerror = { version = "0.5.0", path = "../../../lib/core/libimagerror" }
|
||||
|
|
Loading…
Reference in a new issue