summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthias Beyer <mail@beyermatthias.de>2017-05-31 12:15:13 +0200
committerGitHub <noreply@github.com>2017-05-31 12:15:13 +0200
commitdd24ce810a80222a625b5f24e6e2b7cb132a91c1 (patch)
tree16f84ccb871b68342619b1065a7d83924a3570a6
parent5ba2568415615b7fcf3f2dce939ee2695bf498ff (diff)
parent4ca6f4c386d96f4d7e55b7df9d592b10f929ae40 (diff)
downloadimag-dd24ce810a80222a625b5f24e6e2b7cb132a91c1.zip
imag-dd24ce810a80222a625b5f24e6e2b7cb132a91c1.tar.gz
Merge pull request #946 from matthiasbeyer/revert-854
Revert "Remove stuff for the focus-shift"
-rw-r--r--.imag-documentation/Cargo.toml9
-rw-r--r--Cargo.toml10
-rw-r--r--imag-bookmark/Cargo.toml38
l---------imag-bookmark/README.md1
-rw-r--r--imag-bookmark/src/main.rs158
-rw-r--r--imag-bookmark/src/ui.rs122
-rw-r--r--imag-counter/Cargo.toml32
l---------imag-counter/README.md1
-rw-r--r--imag-counter/src/create.rs50
-rw-r--r--imag-counter/src/delete.rs39
-rw-r--r--imag-counter/src/interactive.rs174
-rw-r--r--imag-counter/src/list.rs54
-rw-r--r--imag-counter/src/main.rs139
-rw-r--r--imag-counter/src/ui.rs139
-rw-r--r--imag-diary/Cargo.toml48
l---------imag-diary/README.md1
-rw-r--r--imag-diary/src/create.rs123
-rw-r--r--imag-diary/src/delete.rs73
-rw-r--r--imag-diary/src/edit.rs62
-rw-r--r--imag-diary/src/list.rs68
-rw-r--r--imag-diary/src/main.rs101
-rw-r--r--imag-diary/src/ui.rs126
-rw-r--r--imag-diary/src/util.rs28
-rw-r--r--imag-diary/src/view.rs38
-rw-r--r--imag-mail/Cargo.toml28
l---------imag-mail/README.md1
-rw-r--r--imag-mail/src/main.rs151
-rw-r--r--imag-mail/src/ui.rs74
-rw-r--r--imag-todo/Cargo.toml38
-rw-r--r--imag-todo/etc/on-add.sh4
-rw-r--r--imag-todo/etc/on-modify.sh4
-rw-r--r--imag-todo/src/main.rs150
-rw-r--r--imag-todo/src/ui.rs61
-rw-r--r--libimagannotation/Cargo.toml2
-rw-r--r--libimagbookmark/Cargo.toml31
l---------libimagbookmark/README.md1
-rw-r--r--libimagbookmark/src/collection.rs222
-rw-r--r--libimagbookmark/src/error.rs33
-rw-r--r--libimagbookmark/src/lib.rs49
-rw-r--r--libimagbookmark/src/link.rs76
-rw-r--r--libimagbookmark/src/result.rs25
-rw-r--r--libimagcounter/Cargo.toml27
l---------libimagcounter/README.md1
-rw-r--r--libimagcounter/src/counter.rs263
-rw-r--r--libimagcounter/src/error.rs32
-rw-r--r--libimagcounter/src/lib.rs47
-rw-r--r--libimagcounter/src/result.rs25
-rw-r--r--libimagdiary/Cargo.toml43
l---------libimagdiary/README.md1
-rw-r--r--libimagdiary/src/config.rs40
-rw-r--r--libimagdiary/src/diary.rs128
-rw-r--r--libimagdiary/src/diaryid.rs257
-rw-r--r--libimagdiary/src/entry.rs90
-rw-r--r--libimagdiary/src/error.rs38
-rw-r--r--libimagdiary/src/is_in_diary.rs44
-rw-r--r--libimagdiary/src/iter.rs132
-rw-r--r--libimagdiary/src/lib.rs63
-rw-r--r--libimagdiary/src/result.rs24
-rw-r--r--libimagdiary/src/viewer.rs64
-rw-r--r--libimagmail/Cargo.toml31
-rw-r--r--libimagmail/src/error.rs16
-rw-r--r--libimagmail/src/hasher.rs67
-rw-r--r--libimagmail/src/iter.rs37
-rw-r--r--libimagmail/src/lib.rs16
-rw-r--r--libimagmail/src/mail.rs120
-rw-r--r--libimagmail/src/result.rs6
-rw-r--r--libimagtodo/Cargo.toml34
-rw-r--r--libimagtodo/src/error.rs32
-rw-r--r--libimagtodo/src/lib.rs52
-rw-r--r--libimagtodo/src/result.rs24
-rw-r--r--libimagtodo/src/task.rs311
71 files changed, 4648 insertions, 1 deletions
diff --git a/.imag-documentation/Cargo.toml b/.imag-documentation/Cargo.toml
index 454ce38..307987e 100644
--- a/.imag-documentation/Cargo.toml
+++ b/.imag-documentation/Cargo.toml
@@ -15,6 +15,15 @@ homepage = "http://imag-pim.org"
[dependencies]
+[dependencies.libimagbookmark]
+path = "../libimagbookmark"
+
+[dependencies.libimagcounter]
+path = "../libimagcounter"
+
+[dependencies.libimagdiary]
+path = "../libimagdiary"
+
[dependencies.libimagentryfilter]
path = "../libimagentryfilter"
diff --git a/Cargo.toml b/Cargo.toml
index dee3868..c5212cd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,13 +2,21 @@
members = [
".imag-documentation",
"bin",
+ "imag-bookmark",
+ "imag-counter",
+ "imag-diary",
"imag-link",
+ "imag-mail",
"imag-notes",
"imag-ref",
"imag-store",
"imag-tag",
+ "imag-todo",
"imag-view",
"libimagannotation",
+ "libimagbookmark",
+ "libimagcounter",
+ "libimagdiary",
"libimagentryedit",
"libimagentryfilter",
"libimagentrylink",
@@ -18,11 +26,13 @@ members = [
"libimagentryview",
"libimagerror",
"libimaginteraction",
+ "libimagmail",
"libimagnotes",
"libimagref",
"libimagrt",
"libimagstore",
"libimagstorestdhook",
"libimagtimeui",
+ "libimagtodo",
"libimagutil",
]
diff --git a/imag-bookmark/Cargo.toml b/imag-bookmark/Cargo.toml
new file mode 100644
index 0000000..23af516
--- /dev/null
+++ b/imag-bookmark/Cargo.toml
@@ -0,0 +1,38 @@
+[package]
+name = "imag-bookmark"
+version = "0.3.0"
+authors = ["Matthias Beyer <mail@beyermatthias.de>"]
+
+description = "Part of the imag core distribution: imag-bookmark 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"
+
+[dependencies.libimagrt]
+path = "../libimagrt"
+
+[dependencies.libimagutil]
+path = "../libimagutil"
+
+[dependencies.libimagbookmark]
+path = "../libimagbookmark"
+
+[dependencies.libimagerror]
+path = "../libimagerror"
+
+[dependencies.libimagentrylink]
+path = "../libimagentrylink"
+
+[dependencies.libimagentrytag]
+path = "../libimagentrytag"
+
diff --git a/imag-bookmark/README.md b/imag-bookmark/README.md
new file mode 120000
index 0000000..43e7a52
--- /dev/null
+++ b/imag-bookmark/README.md
@@ -0,0 +1 @@
+../doc/src/04020-module-bookmarks.md \ No newline at end of file
diff --git a/imag-bookmark/src/main.rs b/imag-bookmark/src/main.rs
new file mode 100644
index 0000000..e063516
--- /dev/null
+++ b/imag-bookmark/src/main.rs
@@ -0,0 +1,158 @@
+//
+// 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;
+
+extern crate libimagbookmark;
+extern crate libimagentrylink;
+extern crate libimagentrytag;
+extern crate libimagrt;
+extern crate libimagerror;
+extern crate libimagutil;
+
+use std::process::exit;
+
+use libimagrt::runtime::Runtime;
+use libimagrt::setup::generate_runtime_setup;
+use libimagbookmark::collection::BookmarkCollection;
+use libimagbookmark::link::Link as BookmarkLink;
+use libimagerror::trace::{MapErrTrace, trace_error, trace_error_exit};
+use libimagutil::info_result::*;
+use libimagutil::iter::*;
+
+mod ui;
+
+use ui::build_ui;
+
+fn main() {
+ let rt = generate_runtime_setup("imag-bookmark",
+ &version!()[..],
+ "Bookmark collection tool",
+ build_ui);
+
+ rt.cli()
+ .subcommand_name()
+ .map(|name| {
+ debug!("Call {}", name);
+ match name {
+ "add" => add(&rt),
+ "collection" => collection(&rt),
+ "list" => list(&rt),
+ "remove" => remove(&rt),
+ _ => {
+ debug!("Unknown command"); // More error handling
+ },
+ }
+ });
+}
+
+fn add(rt: &Runtime) {
+ let scmd = rt.cli().subcommand_matches("add").unwrap();
+ let coll = scmd.value_of("collection").unwrap(); // enforced by clap
+
+ BookmarkCollection::get(rt.store(), coll)
+ .and_then(|mut collection| {
+ scmd.values_of("urls")
+ .unwrap() // enforced by clap
+ .fold_result(|url| collection.add_link(BookmarkLink::from(url)))
+ })
+ .map_err_trace()
+ .map_info_str("Ready")
+ .ok();
+}
+
+fn collection(rt: &Runtime) {
+ let scmd = rt.cli().subcommand_matches("collection").unwrap();
+
+ if scmd.is_present("add") { // adding a new collection
+ let name = scmd.value_of("add").unwrap();
+ if let Ok(_) = BookmarkCollection::new(rt.store(), name) {
+ info!("Created: {}", name);
+ } else {
+ warn!("Creating collection {} failed", name);
+ exit(1);
+ }
+ }
+
+ if scmd.is_present("remove") { // remove a collection
+ let name = scmd.value_of("remove").unwrap();
+ if let Ok(_) = BookmarkCollection::delete(rt.store(), name) {
+ info!("Deleted: {}", name);
+ } else {
+ warn!("Deleting collection {} failed", name);
+ exit(1);
+ }
+ }
+}
+
+fn list(rt: &Runtime) {
+ let scmd = rt.cli().subcommand_matches("list").unwrap();
+ let coll = scmd.value_of("collection").unwrap(); // enforced by clap
+
+ BookmarkCollection::get(rt.store(), coll)
+ .map(|collection| {
+ match collection.links() {
+ Ok(links) => {
+ debug!("Listing...");
+ for (i, link) in links.enumerate() {
+ match link {
+ Ok(link) => println!("{: >3}: {}", i, link),
+ Err(e) => trace_error(&e)
+ }
+ };
+ debug!("... ready with listing");
+ },
+ Err(e) => trace_error_exit(&e, 1),
+ }
+ })
+ .ok();
+ info!("Ready");
+}
+
+fn remove(rt: &Runtime) {
+ let scmd = rt.cli().subcommand_matches("remove").unwrap();
+ let coll = scmd.value_of("collection").unwrap(); // enforced by clap
+
+ BookmarkCollection::get(rt.store(), coll)
+ .map(|mut collection| {
+ for url in scmd.values_of("urls").unwrap() { // enforced by clap
+ collection.remove_link(BookmarkLink::from(url)).map_err(|e| trace_error(&e)).ok();
+ }
+ })
+ .ok();
+ info!("Ready");
+}
+
diff --git a/imag-bookmark/src/ui.rs b/imag-bookmark/src/ui.rs
new file mode 100644
index 0000000..0b14257
--- /dev/null
+++ b/imag-bookmark/src/ui.rs
@@ -0,0 +1,122 @@
+//
+// 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};
+
+use libimagentrytag::ui::tag_add_arg;
+use libimagutil::cli_validators::*;
+
+pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
+ app
+ .subcommand(SubCommand::with_name("add")
+ .about("Add bookmarks")
+ .version("0.1")
+ .arg(Arg::with_name("collection")
+ .long("collection")
+ .short("c")
+ .takes_value(true)
+ .required(true)
+ .multiple(false)
+ .value_name("COLLECTION")
+ .help("Add to this collection"))
+ .arg(Arg::with_name("urls")
+ .long("urls")
+ .short("u")
+ .takes_value(true)
+ .required(true)
+ .multiple(true)
+ .value_name("URL")
+ .validator(is_url)
+ .help("Add this URL, multiple possible"))
+ .arg(tag_add_arg())
+ )
+
+ .subcommand(SubCommand::with_name("remove")
+ .about("Remove bookmarks")
+ .version("0.1")
+ .arg(Arg::with_name("collection")
+ .long("collection")
+ .short("c")
+ .takes_value(true)
+ .required(true)
+ .multiple(false)
+ .value_name("COLLECTION")
+ .help("Remove from this collection"))
+ .arg(Arg::with_name("urls")
+ .long("urls")
+ .short("u")
+ .takes_value(true)
+ .required(true)
+ .multiple(true)
+ .value_name("URL")
+ .validator(is_url)
+ .help("Remove these urls, regex supported"))
+ )
+
+ // .subcommand(SubCommand::with_name("open")
+ // .about("Open bookmarks (via xdg-open)")
+ // .version("0.1")
+ // .arg(Arg::with_name("collection")
+ // .long("collection")
+ // .short("c")
+ // .takes_value(true)
+ // .required(true)
+ // .multiple(false)
+ // .value_name("COLLECTION")
+ // .help("Select from this collection"))
+ // )
+
+ .subcommand(SubCommand::with_name("list")
+ .about("List bookmarks")
+ .version("0.1")
+ .arg(Arg::with_name("collection")
+ .long("collection")
+ .short("c")
+ .takes_value(true)
+ .required(true)
+ .multiple(false)
+ .value_name("COLLECTION")
+ .help("Select from this collection"))
+ .arg(Arg::with_name("tags")
+ .long("tags")
+ .short("t")
+ .takes_value(true)
+ .required(false)
+ .multiple(true)
+ .value_name("TAGS")
+ .help("Filter links to contain these tags. When multiple tags are specified, all of them must be set for the link to match."))
+ )
+
+ .subcommand(SubCommand::with_name("collection")
+ .about("Collection commands")
+ .version("0.1")
+ .arg(Arg::with_name("add")
+ .long("add")
+ .short("a")
+ .takes_value(true)
+ .value_name("NAME")
+ .help("Add a collection with this name"))
+ .arg(Arg::with_name("remove")
+ .long("remove")
+ .short("r")
+ .takes_value(true)
+ .value_name("NAME")
+ .help("Remove a collection with this name (and all links)"))
+ )
+}
diff --git a/imag-counter/Cargo.toml b/imag-counter/Cargo.toml
new file mode 100644
index 0000000..9e7cc07
--- /dev/null
+++ b/imag-counter/Cargo.toml
@@ -0,0 +1,32 @@
+[package]
+name = "imag-counter"
+version = "0.3.0"
+authors = ["Matthias Beyer <mail@beyermatthias.de>"]
+
+description = "Part of the imag core distribution: imag-counter 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"
+
+[dependencies.libimagrt]
+path = "../libimagrt"
+
+[dependencies.libimagerror]
+path = "../libimagerror"
+
+[dependencies.libimagutil]
+path = "../libimagutil"
+
+[dependencies.libimagcounter]
+path = "../libimagcounter"
+
diff --git a/imag-counter/README.md b/imag-counter/README.md
new file mode 120000
index 0000000..b52d880
--- /dev/null
+++ b/imag-counter/README.md
@@ -0,0 +1 @@
+../doc/src/04020-module-counter.md \ No newline at end of file
diff --git a/imag-counter/src/create.rs b/imag-counter/src/create.rs
new file mode 100644
index 0000000..9ef6638
--- /dev/null
+++ b/imag-counter/src/create.rs
@@ -0,0 +1,50 @@
+//
+// 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::str::FromStr;
+
+use libimagrt::runtime::Runtime;
+use libimagerror::trace::trace_error_exit;
+use libimagcounter::counter::Counter;
+use libimagcounter::counter::CounterUnit;
+
+pub fn create(rt: &Runtime) {
+ rt.cli()
+ .subcommand_matches("create")
+ .map(|scmd| {
+ debug!("Found 'create' subcommand...");
+
+ let name = scmd.value_of("name").unwrap(); // safe because clap enforces
+ let init : i64 = scmd
+ .value_of("initval")
+ .and_then(|i| FromStr::from_str(i).ok())
+ .unwrap_or(0);
+
+ let unit = scmd
+ .value_of("unit")
+ .map(CounterUnit::new);
+
+ Counter::new(rt.store(), String::from(name), init)
+ .and_then(|c| c.with_unit(unit))
+ .unwrap_or_else(|e| {
+ warn!("Could not create Counter '{}' with initial value '{}'", name, init);
+ trace_error_exit(&e, 1);
+ });
+ });
+}
diff --git a/imag-counter/src/delete.rs b/imag-counter/src/delete.rs
new file mode 100644
index 0000000..8a35828
--- /dev/null
+++ b/imag-counter/src/delete.rs
@@ -0,0 +1,39 @@
+//
+// 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 libimagrt::runtime::Runtime;
+use libimagerror::trace::trace_error_exit;
+use libimagcounter::counter::Counter;
+
+pub fn delete(rt: &Runtime) {
+ rt.cli()
+ .subcommand_matches("delete")
+ .map(|scmd| {
+ debug!("Found 'delete' subcommand...");
+
+ let name = String::from(scmd.value_of("name").unwrap()); // safe because clap enforces
+
+ if let Err(e) = Counter::delete(name, rt.store()) {
+ trace_error_exit(&e, 1);
+ }
+
+ info!("Ok");
+ });
+}
+
diff --git a/imag-counter/src/interactive.rs b/imag-counter/src/interactive.rs
new file mode 100644
index 0000000..c4a8fa6
--- /dev/null
+++ b/imag-counter/src/interactive.rs
@@ -0,0 +1,174 @@
+//
+// 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::fmt::{Display, Formatter, Error};
+use std::io::Write;
+use std::io::stderr;
+use std::io::stdin;
+use std::process::exit;
+use std::result::Result as RResult;
+
+use libimagcounter::counter::Counter;
+use libimagcounter::error::CounterError;
+use libimagrt::runtime::Runtime;
+use libimagutil::key_value_split::IntoKeyValue;
+use libimagutil::warn_exit::warn_exit;
+use libimagerror::trace::{trace_error, trace_error_exit};
+
+type Result<T> = RResult<T, CounterError>;
+
+pub fn interactive(rt: &Runtime) {
+ let scmd = rt.cli().subcommand_matches("interactive");
+ if scmd.is_none() {
+ warn_exit("No subcommand", 1);
+ }
+ let scmd = scmd.unwrap();
+ debug!("Found 'interactive' command");
+
+ let mut pairs : BTreeMap<char, Binding> = BTreeMap::new();
+
+ for spec in scmd.values_of("spec").unwrap() {
+ match compute_pair(rt, &spec) {
+ Ok((k, v)) => { pairs.insert(k, v); },
+ Err(e) => { trace_error(&e); },
+ }
+ }
+
+ if !has_quit_binding(&pairs) {
+ pairs.insert('q', Binding::Function(String::from("quit"), Box::new(quit)));
+ }
+
+ stderr().flush().ok();
+ loop {
+ println!("---");
+ for (k, v) in &pairs {
+ println!("\t[{}] => {}", k, v);
+ }
+ println!("---");
+ print!("counter > ");
+
+ let mut input = String::new();
+ if let Err(e) = stdin().read_line(&mut input) {
+ trace_error_exit(&e, 1);
+ }
+
+ let cont = if !input.is_empty() {
+ let increment = match input.chars().next() { Some('-') => false, _ => true };
+ input.chars().all(|chr| {
+ match pairs.get_mut(&chr) {
+ Some(&mut Binding::Counter(ref mut ctr)) => {
+ if increment {
+ debug!("Incrementing");
+ if let Err(e) = ctr.inc() {
+ trace_error(&e);
+ }
+ } else {
+ debug!("Decrementing");
+ if let Err(e) = ctr.dec() {
+ trace_error(&e);
+ }
+ }
+ true
+ },
+ Some(&mut Binding::Function(ref name, ref f)) => {
+ debug!("Calling {}", name);
+ f()
+ },
+ None => true,
+ }
+ })
+ } else {
+ println!("No input...");
+ println!("\tUse a single character to increment the counter which is bound to it");
+ println!("\tUse 'q' (or the character bound to quit()) to exit");
+ println!("\tPrefix the line with '-' to decrement instead of increment the counters");
+ println!("");
+ true
+ };
+
+ if !cont {
+ break;
+ }
+ }
+}
+
+fn has_quit_binding(pairs: &BTreeMap<char, Binding>) -> bool {
+ pairs.iter()
+ .any(|(_, bind)| {
+ match *bind {
+ Binding::Function(ref name, _) => name == "quit",
+ _ => false,
+ }
+ })
+}
+
+enum Binding<'a> {
+ Counter(Counter<'a>),
+ Function(String, Box<Fn() -> bool>),
+}
+
+impl<'a> Display for Binding<'a> {
+
+ fn fmt(&self, fmt: &mut Formatter) -> RResult<(), Error> {
+ match *self {
+ Binding::Counter(ref c) => {
+ match c.name() {
+ Ok(name) => {
+ try!(write!(fmt, "{}", name));
+ Ok(())
+ },
+ Err(e) => {
+ trace_error(&e);
+ Ok(()) // TODO: Find a better way to escalate here.
+ },
+ }
+ },
+ Binding::Function(ref name, _) => write!(fmt, "{}()", name),
+ }
+ }
+
+}
+
+fn compute_pair<'a>(rt: &'a Runtime, spec: &str) -> Result<(char, Binding<'a>)> {
+ let kv = String::from(spec).into_kv();
+ if kv.is_none() {
+ warn_exit("Key-Value parsing failed!", 1);
+ }
+ let kv = kv.unwrap();
+
+ let (k, v) = kv.into();
+ if !k.len() == 1 {
+ // We have a key which is not only a single character!
+ exit(1);
+ }
+
+ if v == "quit" {
+ // TODO uncaught unwrap()
+ Ok((k.chars().next().unwrap(), Binding::Function(String::from("quit"), Box::new(quit))))
+ } else {
+ // TODO uncaught unwrap()
+ Counter::load(v, rt.store()).and_then(|ctr| Ok((k.chars().next().unwrap(), Binding::Counter(ctr))))
+ }
+}
+
+fn quit() -> bool {
+ false
+}
+
diff --git a/imag-counter/src/list.rs b/imag-counter/src/list.rs
new file mode 100644
index 0000000..d8d3e86
--- /dev/null
+++ b/imag-counter/src/list.rs
@@ -0,0 +1,54 @@
+//
+// 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 libimagrt::runtime::Runtime;
+use libimagerror::trace::{MapErrTrace, trace_error};
+use libimagcounter::counter::Counter;
+
+pub fn list(rt: &Runtime) {
+ rt.cli()
+ .subcommand_matches("list")
+ .map(|_| {
+ debug!("Found 'list' subcommand...");
+
+ Counter::all_counters(rt.store()).map(|iterator| {
+ for counter in iterator {
+ counter.map(|c| {
+ let name = c.name();
+ let value = c.value();
+ let unit = c.unit();
+
+ if name.is_err() {
+ trace_error(&name.unwrap_err());
+ } else if value.is_err() {
+ trace_error(&value.unwrap_err());
+ } else if unit.is_none() {
+ println!("{} - {}", name.unwrap(), value.unwrap());
+ } else {
+ println!("{} - {} {}", name.unwrap(), value.unwrap(), unit.unwrap());
+ }
+ })
+ .map_err_trace()
+ .ok();
+ }
+ })
+ .map_err_trace()
+
+ });
+}
diff --git a/imag-counter/src/main.rs b/imag-counter/src/main.rs
new file mode 100644
index 0000000..35a2244
--- /dev/null
+++ b/imag-counter/src/main.rs
@@ -0,0 +1,139 @@
+//
+// 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,
+)]
+
+#[macro_use] extern crate log;
+#[macro_use] extern crate version;
+extern crate clap;
+
+extern crate libimagcounter;
+extern crate libimagrt;
+extern crate libimagerror;
+extern crate libimagutil;
+
+use std::process::exit;
+use std::str::FromStr;
+
+use libimagrt::setup::generate_runtime_setup;
+use libimagcounter::counter::Counter;
+use libimagerror::trace::MapErrTrace;
+use libimagutil::key_value_split::IntoKeyValue;
+use libimagutil::info_result::*;
+
+mod create;
+mod delete;
+mod interactive;
+mod list;
+mod ui;
+
+use ui::build_ui;
+use create::create;
+use delete::delete;
+use interactive::interactive;
+use list::list;
+
+enum Action {
+ Inc,
+ Dec,
+ Reset,
+ Set,
+}
+
+fn main() {
+ let rt = generate_runtime_setup("imag-counter",
+ &version!()[..],
+ "Counter tool to count things",
+ build_ui);
+
+ rt.cli()
+ .subcommand_name()
+ .map_or_else(|| {
+ let (action, name) = {
+ if rt.cli().is_present("increment") {
+ (Action::Inc, rt.cli().value_of("increment").unwrap())
+ } else if rt.cli().is_present("decrement") {
+ (Action::Dec, rt.cli().value_of("decrement").unwrap())
+ } else if rt.cli().is_present("reset") {
+ (Action::Reset, rt.cli().value_of("reset").unwrap())
+ } else /* rt.cli().is_present("set") */ {
+ (Action::Set, rt.cli().value_of("set").unwrap())
+ }
+ };
+
+ match action {
+ Action::Inc => {
+ Counter::load(String::from(name), rt.store())
+ .map(|mut c| c.inc().map_err_trace_exit(1).map_info_str("Ok"))
+ },
+ Action::Dec => {
+ Counter::load(String::from(name), rt.store())
+ .map(|mut c| c.dec().map_err_trace_exit(1).map_info_str("Ok"))
+ },
+ Action::Reset => {
+ Counter::load(String::from(name), rt.store())
+ .map(|mut c| c.reset().map_err_trace_exit(1).map_info_str("Ok"))
+ },
+ Action::Set => {
+ let kv = String::from(name).into_kv();
+ if kv.is_none() {
+ warn!("Not a key-value pair: '{}'", name);
+ exit(1);
+ }
+ let (key, value) = kv.unwrap().into();
+ let value = FromStr::from_str(&value[..]);
+ if value.is_err() {
+ warn!("Not a integer: '{:?}'", value);
+ exit(1);
+ }
+ let value : i64 = value.unwrap();
+ Counter::load(String::from(key), rt.store())
+ .map(|mut c| c.set(value).map_err_trace_exit(1).map_info_str("Ok"))
+ },
+ }
+ .map_err_trace()
+ .ok();
+ },
+ |name| {
+ debug!("Call: {}", name);
+ match name {
+ "create" => create(&rt),
+ "delete" => delete(&rt),
+ "interactive" => interactive(&rt),
+ "list" => list(&rt),
+ _ => {
+ debug!("Unknown command"); // More error handling
+ },
+ };
+ })
+}
+
diff --git a/imag-counter/src/ui.rs b/imag-counter/src/ui.rs
new file mode 100644
index 0000000..2b928dd
--- /dev/null
+++ b/imag-counter/src/ui.rs
@@ -0,0 +1,139 @@
+//
+// 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
+ .arg(Arg::with_name("increment")
+ .long("inc")
+ .short("i")
+ .takes_value(true)
+ .required(false)
+ .help("Increment a counter")
+ .value_name("COUNTER"))
+
+ .arg(Arg::with_name("decrement")
+ .long("dec")
+ .short("d")
+ .takes_value(true)
+ .required(false)
+ .help("Decrement a counter")
+ .value_name("COUNTER"))
+
+ .arg(Arg::with_name("reset")
+ .long("reset")
+ .takes_value(true)
+ .required(false)
+ .help("Reset a counter")
+ .value_name("COUNTER"))
+
+ .arg(Arg::with_name("set")
+ .long("set")
+ .takes_value(true)
+ .required(false)
+ .help("Set a counter")
+ .value_name("COUNTER"))
+
+ .subcommand(SubCommand::with_name("create")
+ .about("Create a counter")
+ .version("0.1")
+ .arg(Arg::with_name("name")
+ .long("name")
+ .short("n")
+ .takes_value(true)
+ .required(true)
+ .help("Create counter with this name")
+ .value_name("NAME"))
+ .arg(Arg::with_name("initval")
+ .long("init")
+ .short("i")
+ .takes_value(true)
+ .required(false)
+ .help("Initial value")
+ .value_name("VALUE"))
+ .arg(Arg::with_name("unit")
+ .long("unit")
+ .short("u")
+ .takes_value(true)
+ .required(false)
+ .help("measurement unit")
+ .value_name("UNIT")))
+
+ .subcommand(SubCommand::with_name("delete")
+ .about("Delete a counter")
+ .version("0.1")
+ .arg(Arg::with_name("name")
+ .long("name")
+ .short("n")
+ .takes_value(true)
+ .required(true)
+ .help("Create counter with this name")
+ .value_name("NAME")))
+
+ .subcommand(SubCommand::with_name("list")
+ .about("List counters")
+ .version("0.1")
+ .arg(Arg::with_name("name")
+ .long("name")
+ .short("n")
+ .takes_value(true)
+ .required(false)
+ .help("List counters with this name (foo/bar and baz/bar would match 'bar')")
+ .value_name("NAME"))
+
+ .arg(Arg::with_name("greater-than")
+ .long("greater")
+ .short("g")
+ .takes_value(true)
+ .required(false)
+ .help("List counters which are greater than VALUE")
+ .value_name("VALUE"))
+
+ .arg(Arg::with_name("lower-than")
+ .long("lower")
+ .short("l")
+ .takes_value(true)
+ .required(false)
+ .help("List counters which are lower than VALUE")
+ .value_name("VALUE"))
+
+ .arg(Arg::with_name("equals")
+ .long("equal")
+ .short("e")
+ .takes_value(true)
+ .required(false)
+ .help("List counters which equal VALUE")
+ .value_name("VALUE"))
+ )
+
+ .subcommand(SubCommand::with_name("interactive")
+ .about("Interactively count things")
+ .version("0.1")
+ .arg(Arg::with_name("spec")
+ .long("spec")
+ .short("s")
+ .takes_value(true)
+ .multiple(true)
+ .required(true)
+ .help("Specification for key-bindings. Use <KEY>=<VALUE> where KEY is the
+ key to bind (single character) and VALUE is the path to the counter to bind
+ to.")
+ .value_name("KEY=VALUE")))
+}
diff --git a/imag-diary/Cargo.toml b/imag-diary/Cargo.toml
new file mode 100644
index 0000000..0a6e304
--- /dev/null
+++ b/imag-diary/Cargo.toml
@@ -0,0 +1,48 @@
+[package]
+name = "imag-diary"
+version = "0.3.0"
+authors = ["Matthias Beyer <mail@beyermatthias.de>"]
+
+description = "Part of the imag core distribution: imag-diary 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]
+chrono = "0.2"
+version = "2.0"
+clap = "2.*"
+log = "0.3"
+
+[dependencies.libimagrt]
+path = "../libimagrt"
+
+[dependencies.libimagdiary]
+path = "../libimagdiary"
+
+[dependencies.libimagentryedit]
+path = "../libimagentryedit"
+
+[dependencies.libimagentrylist]
+path = "../libimagentrylist"
+
+[dependencies.libimagerror]
+path = "../libimagerror"
+
+[dependencies.libimaginteraction]
+path = "../libimaginteraction"
+
+[dependencies.libimagutil]
+path = "../libimagutil"
+
+[dependencies.libimagstore]
+path = "../libimagstore"
+
+[dependencies.libimagtimeui]
+path = "../libimagtimeui"
+
diff --git a/imag-diary/README.md b/imag-diary/README.md
new file mode 120000
index 0000000..1aab1ab
--- /dev/null
+++ b/imag-diary/README.md
@@ -0,0 +1 @@
+../doc/src/04020-module-diary.md \ No newline at end of file
diff --git a/imag-diary/src/create.rs b/imag-diary/src/create.rs
new file mode 100644
index 0000000..78f679a
--- /dev/null
+++ b/imag-diary/src/create.rs
@@ -0,0 +1,123 @@
+//
+// 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::process::exit;
+
+use libimagdiary::diary::Diary;
+use libimagdiary::diaryid::DiaryId;
+use libimagdiary::error::DiaryErrorKind as DEK;
+use libimagdiary::error::MapErrInto;
+use libimagentryedit::edit::Edit;
+use libimagrt::runtime::Runtime;
+use libimagerror::trace::trace_error;
+use libimagdiary::entry::Entry;
+use libimagdiary::result::Result;
+use libimagutil::warn_exit::warn_exit;
+
+use util::get_diary_name;
+
+pub fn create(rt: &Runtime) {
+ let diaryname = get_diary_name(rt)
+ .unwrap_or_else( || warn_exit("No diary selected. Use either the configuration file or the commandline option", 1));
+
+ let prevent_edit = rt.cli().subcommand_matches("create").unwrap().is_present("no-edit");
+
+ fn create_entry<'a>(diary: &'a Diary, rt: &Runtime) -> Result<Entry<'a>> {
+ use std::str::FromStr;
+
+ let create = rt.cli().subcommand_matches("create").unwrap();
+ if !create.is_present("timed") {
+ debug!("Creating non-timed entry");
+ diary.new_entry_today()
+ } else {
+ let id = match create.value_of("timed") {
+ Some("h") | Some("hourly") => {
+ debug!("Creating hourly-timed entry");
+ let time = DiaryId::now(String::from(diary.name()));
+ let hr = create
+ .value_of("hour")
+ .map(|v| { debug!("Creating hourly entry with hour = {:?}", v); v })
+ .and_then(|s| {
+ FromStr::from_str(s)
+ .map_err(|_| warn!("Could not parse hour: '{}'", s))
+ .ok()
+ })
+ .unwrap_or(time.hour());
+
+ time.with_hour(hr).with_minute(0)
+ },
+
+ Some("m") | Some("minutely") => {
+ debug!("Creating minutely-timed entry");
+ let time = DiaryId::now(String::from(diary.name()));
+ let hr = create
+ .value_of("hour")
+ .map(|h| { debug!("hour = {:?}", h); h })
+ .and_then(|s| {
+ FromStr::from_str(s)
+ .map_err(|_| warn!("Could not parse hour: '{}'", s))
+ .ok()
+ })
+ .unwrap_or(time.hour());
+
+ let min = create
+ .value_of("minute")
+ .map(|m| { debug!("minute = {:?}", m); m })
+ .and_then(|s| {
+ FromStr::from_str(s)
+ .map_err(|_| warn!("Could not parse minute: '{}'", s))
+ .ok()
+ })
+ .unwrap_or(time.minute());
+
+ time.with_hour(hr).with_minute(min)
+ },
+
+ Some(_) => {
+ warn!("Timed creation failed: Unknown spec '{}'",
+ create.value_of("timed").unwrap());
+ exit(1);
+ },
+
+ None => warn_exit("Unexpected error, cannot continue", 1)
+ };
+
+ diary.new_entry_by_id(id)
+ }
+ }
+
+ let diary = Diary::open(rt.store(), &diaryname[..]);
+ let res = create_entry(&diary, rt)
+ .and_then(|mut entry| {
+ if prevent_edit {
+ debug!("Not editing new diary entry");
+ Ok(())
+ } else {
+ debug!("Editing new diary entry");
+ entry.edit_content(rt).map_err_into(DEK::DiaryEditError)
+ }
+ });
+
+ if let Err(e) = res {
+ trace_error(&e);
+ } else {
+ info!("Ok!");
+ }
+}
+
diff --git a/imag-diary/src/delete.rs b/imag-diary/src/delete.rs
new file mode 100644
index 0000000..7325047
--- /dev/null
+++ b/imag-diary/src/delete.rs
@@ -0,0 +1,73 @@
+//
+// 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 chrono::naive::datetime::NaiveDateTime;
+
+use libimagdiary::diary::Diary;
+use libimagdiary::diaryid::DiaryId;
+use libimagrt::runtime::Runtime;
+use libimagerror::trace::trace_error_exit;
+use libimagtimeui::datetime::DateTime;
+use libimagtimeui::parse::Parse;
+use libimagutil::warn_exit::warn_exit;
+
+use util::get_diary_name;
+
+pub fn delete(rt: &Runtime) {
+ use libimaginteraction::ask::ask_bool;
+
+ let diaryname = get_diary_name(rt)
+ .unwrap_or_else(|| warn_exit("No diary selected. Use either the configuration file or the commandline option", 1));
+
+ let diary = Diary::open(rt.store(), &diaryname[..]);
+ debug!("Diary opened: {:?}", diary);
+
+ let datetime : Option<NaiveDateTime> = rt
+ .cli()
+ .subcommand_matches("delete")
+ .unwrap()
+ .value_of("datetime")
+ .map(|dt| { debug!("DateTime = {:?}", dt); dt })
+ .and_then(DateTime::parse)
+ .map(|dt| dt.into());
+
+ let to_del = match datetime {
+ Some(dt) => Some(diary.retrieve(DiaryId::from_datetime(diaryname.clone(), dt))),
+ None => diary.get_youngest_entry(),
+ };
+
+ let to_del = match to_del {
+ Some(Ok(e)) => e,
+
+ Some(Err(e)) => trace_error_exit(&e, 1),
+ None => warn_exit("No entry", 1)
+ };
+
+ if !ask_bool(&format!("Deleting {:?}", to_del.get_location())[..], Some(true)) {
+ info!("Aborting delete action");
+ return;
+ }
+
+ if let Err(e) = diary.delete_entry(to_del) {
+ trace_error_exit(&e, 1)
+ }
+
+ info!("Ok!");
+}
+
diff --git a/imag-diary/src/edit.rs b/imag-diary/src/edit.rs
new file mode 100644
index 0000000..beed8c0
--- /dev/null
+++ b/imag-diary/src/edit.rs
@@ -0,0 +1,62 @@
+//
+// 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 chrono::naive::datetime::NaiveDateTime;
+
+use libimagdiary::diary::Diary;
+use libimagdiary::diaryid::DiaryId;
+use libimagdiary::error::DiaryErrorKind as DEK;
+use libimagdiary::error::MapErrInto;
+use libimagentryedit::edit::Edit;
+use libimagrt::runtime::Runtime;
+use libimagerror::trace::MapErrTrace;
+use libimagerror::into::IntoError;
+use libimagtimeui::datetime::DateTime;
+use libimagtimeui::parse::Parse;
+use libimagutil::warn_exit::warn_exit;
+
+use util::get_diary_name;
+
+pub fn edit(rt: &Runtime) {
+ let diaryname = get_diary_name(rt).unwrap_or_else(|| warn_exit("No diary name", 1));
+ let diary = Diary::open(rt.store(), &diaryname[..]);
+
+ let datetime : Option<NaiveDateTime> = rt
+ .cli()
+ .subcommand_matches("edit")
+ .unwrap()
+ .value_of("datetime")
+ .and_then(DateTime::parse)
+ .map(|dt| dt.into());
+
+ let to_edit = match datetime {
+ Some(dt) => Some(diary.retrieve(DiaryId::from_datetime(diaryname.clone(), dt))),
+ None => diary.get_youngest_entry(),
+ };
+
+ match to_edit {
+ Some(Ok(mut e)) => e.edit_content(rt).map_err_into(DEK::IOError),
+
+ Some(Err(e)) => Err(e),
+ None => Err(DEK::EntryNotInDiary.into_error()),
+ }
+ .map_err_trace().ok();
+}
+
+
diff --git a/imag-diary/src/list.rs b/imag-diary/src/list.rs
new file mode 100644
index 0000000..4ed3123
--- /dev/null
+++ b/imag-diary/src/list.rs
@@ -0,0 +1,68 @@
+//
+// 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 libimagdiary::diary::Diary;
+use libimagdiary::error::DiaryErrorKind as DEK;
+use libimagdiary::error::MapErrInto;
+use libimagentrylist::listers::core::CoreLister;
+use libimagentrylist::lister::Lister;
+use libimagrt::runtime::Runtime;
+use libimagstore::store::Entry;
+use libimagutil::warn_exit::warn_exit;
+use libimagerror::trace::MapErrTrace;
+use libimagutil::debug_result::*;
+
+use util::get_diary_name;
+
+pub fn list(rt: &Runtime) {
+ let diaryname = get_diary_name(rt)
+ .unwrap_or_else(|| warn_exit("No diary selected. Use either the configuration file or the commandline option", 1));
+
+ fn entry_to_location_listing_string(e: &Entry) -> String {
+ e.get_location().clone()
+ .without_base()
+ .to_str()
+ .map_err_trace()
+ .unwrap_or(String::from("<<Path Parsing Error>>"))
+ }
+
+ let diary = Diary::open(rt.store(), &diaryname[..]);
+ debug!("Diary opened: {:?}", diary);
+ diary.entries()
+ .and_then(|es| {
+ debug!("Iterator for listing: {:?}", es);
+
+ let es = es
+ .filter_map(|entry| {
+ entry
+ .map_dbg(|e| format!("Filtering: {:?}", e))
+ .map_err_trace() // error tracing here
+ .ok() // so we can ignore errors here
+ })
+ .map(|e| e.into());
+
+ CoreLister::new(&entry_to_location_listing_string)
+ .list(es)
+ .map_err_into(DEK::IOError)
+ })
+ .map_dbg_str("Ok")
+ .map_err_trace()
+ .ok();
+}
+
diff --git a/imag-diary/src/main.rs b/imag-diary/src/main.rs
new file mode 100644
index 0000000..71e00cd
--- /dev/null
+++ b/imag-diary/src/main.rs
@@ -0,0 +1,101 @@
+//
+// 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,
+)]
+
+#[macro_use] extern crate log;
+#[macro_use] extern crate version;
+extern crate clap;
+extern crate chrono;
+
+extern crate libimagdiary;
+extern crate libimagentryedit;
+extern crate libimagentrylist;
+extern crate libimaginteraction;
+extern crate libimagrt;
+extern crate libimagstore;
+extern crate libimagutil;
+extern crate libimagtimeui;
+#[macro_use] extern crate libimagerror;
+
+use std::process::exit;
+
+use libimagrt::runtime::Runtime;
+
+mod create;
+mod delete;
+mod edit;
+mod list;
+mod ui;
+mod util;
+mod view;
+
+use create::create;
+use delete::delete;
+use edit::edit;
+use list::list;
+use ui::build_ui;
+use view::view;
+
+fn main() {
+ let name = "imag-diary";
+ let version = &version!()[..];
+ let about = "Personal Diary/Diaries";
+ let ui = build_ui(Runtime::get_default_cli_builder(name, version, about));
+ let rt = {
+ let rt = Runtime::new(ui);
+ if rt.is_ok() {
+ rt.unwrap()
+ } else {
+ println!("Could not set up Runtime");
+ println!("{:?}", rt.err().unwrap());
+ exit(1);
+ }
+ };
+
+ rt.cli()
+ .subcommand_name()
+ .map(|name| {
+ debug!("Call {}", name);
+ match name {
+ "create" => create(&rt),
+ "delete" => delete(&rt),
+ "edit" => edit(&rt),
+ "list" => list(&rt),
+ "view" => view(&rt),
+ _ => {
+ debug!("Unknown command"); // More error handling
+ },
+ }
+ });
+}
+
diff --git a/imag-diary/src/ui.rs b/imag-diary/src/ui.rs
new file mode 100644
index 0000000..91712fb
--- /dev/null
+++ b/imag-diary/src/ui.rs
@@ -0,0 +1,126 @@
+//
+// 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, ArgGroup, App, SubCommand};
+
+pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
+ app
+ .arg(Arg::with_name("diaryname")
+ .long("diary")
+ .short("d")
+ .takes_value(true)
+ .required(false)
+ .help("Use other than default diary"))
+
+ .subcommand(SubCommand::with_name("create")
+ .about("Create a diary entry")
+ .version("0.1")
+ .arg(Arg::with_name("no-edit")
+ .long("no-edit")
+ .short("e")
+ .takes_value(false)
+ .required(false)
+ .help("Do not edit after creating"))
+
+ .arg(Arg::with_name("timed")
+ .long("timed")
+ .short("t")
+ .takes_value(true)
+ .required(false)
+ .help("By default, one entry is created per day. With --timed=h[ourly] or
+ --timed=m[inutely] one can create per-hour and per-minute entries (more like
+ a microblog then"))
+
+ .arg(Arg::with_name("hour")
+ .long("hour")
+ .takes_value(true)
+ .required(false)
+ .help("When using --timed, override the hour component"))
+ .arg(Arg::with_name("minute")
+ .long("minute")
+ .takes_value(true)
+ .required(false)
+ .help("When using --timed, override the minute component"))
+
+ // When using --hour or --minute, --timed must be present
+ .group(ArgGroup::with_name("timing-hourly")
+ .args(&["hour"])
+ .requires("timed"))
+ .group(ArgGroup::with_name("timing-minutely")
+ .args(&["minute"])
+ .requires("timed"))
+ )
+
+ .subcommand(SubCommand::with_name("edit")
+ .about("Edit a diary entry")
+ .version("0.1")
+ .arg(Arg::with_name("datetime")
+ .long("datetime")
+ .short("d")
+ .takes_value(true)
+ .required(false)
+ .help("Specify the date and time which entry should be edited. If none is
+ specified, the last entry is edited. If the diary entry does not exist for
+ this time, this fails. Format: YYYY-MM-DDT[HH[:mm[:ss]]]"))
+ )
+
+ .subcommand(SubCommand::with_name("list")
+ .about("List diary entries")
+ .version("0.1"))
+
+ .subcommand(SubCommand::with_name("delete")
+ .about("Delete a diary entry")
+ .version("0.1")
+ .arg(Arg::with_name("datetime")
+ .long("datetime")
+ .short("d")
+ .takes_value(true)
+ .required(false)
+ .help("Specify the date and time which entry should be deleted. If none is
+ specified, the last entry is deleted. If the diary entry does not exist for
+ this time, this fails. Format: YYYY-MM-DDT[HH[:mm[:ss]]]"))
+
+ .arg(Arg::with_name("select")
+ .long("select")
+ .short("s")
+ .takes_value(false)
+ .required(false)
+ .help("Use interactive selection"))
+
+ .arg(Arg::with_name("yes")
+ .long("yes")
+ .short("y")
+ .takes_value(false)
+ .required(false)
+ .help("Do not ask for confirmation."))
+ )
+
+ .subcommand(SubCommand::with_name("view")
+ .about("View entries, currently only supports plain viewing")
+ .version("0.1")
+
+ .arg(Arg::with_name("show-header")
+ .long("header")
+ .takes_value(false)
+ .required(false)
+ .help("Show the header when printing the entries"))
+ )
+
+}
+
diff --git a/imag-diary/src/util.rs b/imag-diary/src/util.rs
new file mode 100644
index 0000000..2549808
--- /dev/null
+++ b/imag-diary/src/util.rs
@@ -0,0 +1,28 @@
+//
+// 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 libimagrt::runtime::Runtime;
+
+pub fn get_diary_name(rt: &Runtime) -> Option<String> {
+ use libimagdiary::config::get_default_diary_name;
+
+ get_default_diary_name(rt)
+ .or(rt.cli().value_of("diaryname").map(String::from))
+}
+
diff --git a/imag-diary/src/view.rs b/imag-diary/src/view.rs
new file mode 100644
index 0000000..041a1fe
--- /dev/null
+++ b/imag-diary/src/view.rs
@@ -0,0 +1,38 @@
+//
+// 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 libimagdiary::diary::Diary;
+use libimagdiary::viewer::DiaryViewer as DV;
+use libimagrt::runtime::Runtime;
+use libimagerror::trace::MapErrTrace;
+use libimagutil::warn_exit::warn_exit;
+
+use util::get_diary_name;
+
+pub fn view(rt: &Runtime) {
+ let diaryname = get_diary_name(rt).unwrap_or_else(|| warn_exit("No diary name", 1));
+ let diary = Diary::open(rt.store(), &diaryname[..]);
+ let hdr = rt.cli().subcommand_matches("view").unwrap().is_present("show-header");
+
+ diary.entries()
+ .and_then(|entries| DV::new(hdr).view_entries(entries.into_iter().filter_map(Result::ok)))
+ .map_err_trace()
+ .ok();
+}
+
diff --git a/imag-mail/Cargo.toml b/imag-mail/Cargo.toml
new file mode 100644
index 0000000..42e928c
--- /dev/null
+++ b/imag-mail/Cargo.toml
@@ -0,0 +1,28 @@
+[package]
+name = "imag-mail"
+version = "0.3.0"
+authors = ["Matthias Beyer <mail@beyermatthias.de>"]
+
+[dependencies]
+semver = "0.5"
+clap = "2.*"
+log = "0.3"
+version = "2.0.1"
+toml = "0.4.*"
+url = "1.2"
+
+[dependencies.libimagrt]
+path = "../libimagrt"
+
+[dependencies.libimagmail]
+path = "../libimagmail"
+
+[dependencies.libimagerror]
+path = "../libimagerror"
+
+[dependencies.libimagutil]
+path = "../libimagutil"
+
+[dependencies.libimagref]
+path = "../libimagref"
+
diff --git a/imag-mail/README.md b/imag-mail/README.md
new file mode 120000
index 0000000..d5d8fb0
--- /dev/null
+++ b/imag-mail/README.md
@@ -0,0 +1 @@
+../doc/src/04020-module-mails.md \ No newline at end of file
diff --git a/imag-mail/src/main.rs b/imag-mail/src/main.rs
new file mode 100644
index 0000000..bdd51bd
--- /dev/null
+++ b/imag-mail/src/main.rs
@@ -0,0 +1,151 @@
+//
+// 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
+//
+
+extern crate semver;
+extern crate clap;
+extern crate toml;
+extern crate url;
+#[macro_use] extern crate log;
+#[macro_use] extern crate version;
+
+extern crate libimagrt;
+extern crate libimagmail;
+extern crate libimagerror;
+extern crate libimagutil;
+extern crate libimagref;
+
+use libimagerror::trace::{MapErrTrace, trace_error, trace_error_exit};
+use libimagmail::mail::Mail;
+use libimagref::reference::Ref;
+use libimagrt::runtime::Runtime;
+use libimagrt::setup::generate_runtime_setup;
+use libimagutil::debug_result::*;
+use libimagutil::info_result::*;
+
+mod ui;
+
+use ui::build_ui;
+
+fn main() {
+ let rt = generate_runtime_setup("imag-mail",
+ &version!()[..],
+ "Mail collection tool",
+ build_ui);
+
+ rt.cli()
+ .subcommand_name()
+ .map(|name| {
+ debug!("Call {}", name);
+ match name {
+ "import-mail" => import_mail(&rt),
+ "list" => list(&rt),
+ "mail-store" => mail_store(&rt),
+ _ => debug!("Unknown command") // More error handling
+ }
+ });
+}
+
+fn import_mail(rt: &Runtime) {
+ let scmd = rt.cli().subcommand_matches("import-mail").unwrap();
+ let path = scmd.value_of("path").unwrap(); // enforced by clap
+
+ Mail::import_from_path(rt.store(), path)
+ .map_err_trace()
+ .map_info_str("Ok");
+}
+
+fn list(rt: &Runtime) {
+ use libimagmail::error::MailErrorKind as MEK;
+ use libimagmail::error::MapErrInto;
+
+ let scmd = rt.cli().subcommand_matches("list").unwrap();
+ let do_check_dead = scmd.is_present("check-dead");
+ let do_check_changed = scmd.is_present("check-changed");
+ let do_check_changed_content = scmd.is_present("check-changed-content");
+ let do_check_changed_permiss = scmd.is_present("check-changed-permissions");
+ let store = rt.store();
+
+ let iter = match store.retrieve_for_module("ref") {
+ Ok(iter) => iter.filter_map(|id| {
+ Ref::get(store, id)
+ .map_err_into(MEK::RefHandlingError)
+ .and_then(|rf| Mail::from_ref(rf))
+ .map_err_trace()
+ .ok()
+ }),
+ Err(e) => trace_error_exit(&e, 1),
+ };
+
+ fn list_mail(m: Mail) {
+ let id = match m.get_message_id() {
+ Ok(Some(f)) => f,
+ Ok(None) => "<no id>".to_owned(),
+ Err(e) => {
+ trace_error(&e);
+ "<error>".to_owned()
+ },
+ };
+
+ let from = match m.get_from() {
+ Ok(Some(f)) => f,
+ Ok(None) => "<no from>".to_owned(),
+ Err(e) => {
+ trace_error(&e);
+ "<error>".to_owned()
+ },
+ };
+
+ let to = match m.get_to() {
+ Ok(Some(f)) => f,
+ Ok(None) => "<no to>".to_owned(),
+ Err(e) => {
+ trace_error(&e);
+ "<error>".to_owned()
+ },
+ };
+
+ let subject = match m.get_subject() {
+ Ok(Some(f)) => f,
+ Ok(None) => "<no subject>".to_owned(),
+ Err(e) => {
+ trace_error(&e);
+ "<error>".to_owned()
+ },
+ };
+
+ println!("Mail: {id}\n\tFrom: {from}\n\tTo: {to}\n\t{subj}\n",
+ from = from,
+ id = id,
+ subj = subject,
+ to = to
+ );
+ }
+
+ // TODO: Implement lister type in libimagmail for this
+ for mail in iter {
+ list_mail(mail)
+ }
+}
+
+fn mail_store(rt: &Runtime) {
+ let scmd = rt.cli().subcommand_matches("mail-store").unwrap();
+ error!("This feature is currently not implemented.");
+ unimplemented!()
+}
+
diff --git a/imag-mail/src/ui.rs b/imag-mail/src/ui.rs
new file mode 100644
index 0000000..3e99994
--- /dev/null
+++ b/imag-mail/src/ui.rs
@@ -0,0 +1,74 @@
+//
+// 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, ArgGroup, App, SubCommand};
+
+pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
+ app
+ .subcommand(SubCommand::with_name("import-mail")
+ .about("Import a mail (create a reference to it) (Maildir)")
+ .version("0.1")
+ .arg(Arg::with_name("path")
+ .long("path")
+ .short("p")
+ .takes_value(true)
+ .required(true)
+ .help("Path to the mail file or a directory which is then searched recursively")
+ .value_name("PATH"))
+ )
+
+ .subcommand(SubCommand::with_name("list")
+ .about("List all stored references to mails")
+ .version("0.1")
+
+ // TODO: Thee following four arguments are the same as in imag-ref.
+ // We should make these importable from libimagref.
+
+ .arg(Arg::with_name("check-dead")
+ .long("check-dead")
+ .short("d")
+ .help("Check each reference whether it is dead"))
+
+ .arg(Arg::with_name("check-changed")
+ .long("check-changed")
+ .short("c")
+ .help("Check whether a reference had changed (content or permissions)"))
+
+ .arg(Arg::with_name("check-changed-content")
+ .long("check-changed-content")
+ .short("C")
+ .help("Check whether the content of the referenced file changed"))
+
+ .arg(Arg::with_name("check-changed-permissions")
+ .long("check-changed-perms")
+ .short("P")
+ .help("Check whether the permissions of the referenced file changed"))
+
+ )
+
+ .subcommand(SubCommand::with_name("mail-store")
+ .about("Operations on (subsets of) all mails")
+ .version("0.1")
+ .subcommand(SubCommand::with_name("update-refs")
+ .about("Create references based on Message-IDs for all loaded mails")
+ .version("0.1"))
+ // TODO: We really should be able to filter here.
+ )
+}
+
diff --git a/imag-todo/Cargo.toml b/imag-todo/Cargo.toml
new file mode 100644
index 0000000..5e454ec
--- /dev/null
+++ b/imag-todo/Cargo.toml
@@ -0,0 +1,38 @@
+[package]
+authors = ["mario <mario-krehl@gmx.de>"]
+name = "imag-todo"
+version = "0.3.0"
+
+description = "Part of the imag core distribution: imag-todo 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"
+glob = "0.2.11"
+log = "0.3.6"
+semver = "0.5.1"
+serde_json = "0.8.3"
+task-hookrs = "0.2.2"
+toml = "0.4.*"
+toml-query = "0.1.*"
+is-match = "0.1.*"
+version = "2.0.1"
+
+[dependencies.libimagrt]
+path = "../libimagrt"
+
+[dependencies.libimagstore]
+path = "../libimagstore"
+
+[dependencies.libimagtodo]
+path = "../libimagtodo"
+
+[dependencies.libimagerror]
+path = "../libimagerror"
diff --git a/imag-todo/etc/on-add.sh b/imag-todo/etc/on-add.sh
new file mode 100644
index 0000000..a58e498
--- /dev/null
+++ b/imag-todo/etc/on-add.sh
@@ -0,0 +1,4 @@
+#/!usr/bin/env bash
+
+imag todo tw-hook --add
+
diff --git a/imag-todo/etc/on-modify.sh b/imag-todo/etc/on-modify.sh
new file mode 100644
index 0000000..89be96d
--- /dev/null
+++ b/imag-todo/etc/on-modify.sh
@@ -0,0 +1,4 @@
+#/!usr/bin/env bash
+
+imag todo tw-hook --delete
+
diff --git a/imag-todo/src/main.rs b/imag-todo/src/main.rs
new file mode 100644
index 0000000..4d6396b
--- /dev/null
+++ b/imag-todo/src/main.rs
@@ -0,0 +1,150 @@
+//
+// 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
+//
+
+extern crate clap;
+extern crate glob;
+#[macro_use] extern crate log;
+extern crate serde_json;
+extern crate semver;
+extern crate toml;
+extern crate toml_query;
+#[macro_use] extern crate is_match;
+#[macro_use] extern crate version;
+
+extern crate task_hookrs;
+
+extern crate libimagrt;
+extern crate libimagstore;
+extern crate libimagerror;
+extern crate libimagtodo;
+
+use std::process::{Command, Stdio};
+use std::io::stdin;
+
+use toml::Value;
+
+use libimagrt::runtime::Runtime;
+use libimagrt::setup::generate_runtime_setup;
+use libimagtodo::task::Task;
+use libimagerror::trace::{MapErrTrace, trace_error, trace_error_exit};
+
+mod ui;
+
+use ui::build_ui;
+fn main() {
+ let rt = generate_runtime_setup("imag-todo",
+ &version!()[..],
+ "Interface with taskwarrior",
+ build_ui);
+
+ match rt.cli().subcommand_name() {
+ Some("tw-hook") => tw_hook(&rt),
+ Some("list") => list(&rt),
+ None => {
+ warn!("No command");
+ },
+ _ => unreachable!(),
+ } // end match scmd
+} // end main
+
+fn tw_hook(rt: &Runtime) {
+ let subcmd = rt.cli().subcommand_matches("tw-hook").unwrap();
+ if subcmd.is_present("add") {
+ let stdin = stdin();
+ let stdin = stdin.lock(); // implements BufRead which is required for `Task::import()`
+
+ match Task::import(rt.store(), stdin) {
+ Ok((_, line, uuid)) => println!("{}\nTask {} stored in imag", line, uuid),
+ Err(e) => trace_error_exit(&e, 1),
+ }
+ } else if subcmd.is_present("delete") {
+ // The used hook is "on-modify". This hook gives two json-objects
+ // per usage und wants one (the second one) back.
+ let stdin = stdin();
+ Task::delete_by_imports(rt.store(), stdin.lock()).map_err_trace().ok();
+ } else {
+ // Should not be possible, as one argument is required via
+ // ArgGroup
+ unreachable!();
+ }
+}
+
+fn list(rt: &Runtime) {
+ use toml_query::read::TomlValueReadExt;
+
+ let subcmd = rt.cli().subcommand_matches("list").unwrap();
+ let verbose = subcmd.is_present("verbose");
+
+ // Helper for toml_query::read::TomlValueReadExt::read() return value, which does only
+ // return Result<T> instead of Result<Option<T>>, which is a real inconvenience.
+ //
+ let no_identifier = |e: &::toml_query::error::Error| -> bool {
+ is_match!(e.kind(), &::toml_query::error::ErrorKind::IdentifierNotFoundInDocument(_))
+ };
+
+ let res = Task::all(rt.store()) // get all tasks
+ .map(|iter| { // and if this succeeded
+ // filter out the ones were we can read the uuid
+ let uuids : Vec<_> = iter.filter_map(|t| match t {
+ Ok(v) => match v.get_header().read(&String::from("todo.uuid")) {
+ Ok(&Value::String(ref u)) => Some(u.clone()),
+ Ok(_) => {
+ warn!("Header type error");
+ None
+ },
+ Err(e) => {
+ if !no_identifier(&e) {
+ trace_error(&e);
+ }
+ None
+ }
+ },
+ Err(e) => {
+ trace_error(&e);
+ None
+ }
+ })
+ .collect();
+
+ // compose a `task` call with them, ...
+ let outstring = if verbose { // ... if verbose
+ let output = Command::new("task")
+ .stdin(Stdio::null())
+ .args(&uuids)
+ .spawn()
+ .unwrap_or_else(|e| {
+ trace_error(&e);
+ panic!("Failed to execute `task` on the commandline. I'm dying now.");
+ })
+ .wait_with_output()
+ .unwrap_or_else(|e| panic!("failed to unwrap output: {}", e));
+
+ String::from_utf8(output.stdout)
+ .unwrap_or_else(|e| panic!("failed to execute: {}", e))
+ } else { // ... else just join them
+ uuids.join("\n")
+ };
+
+ // and then print that
+ println!("{}", outstring);
+ });
+
+ res.map_err_trace().ok();
+}
+
diff --git a/imag-todo/src/ui.rs b/imag-todo/src/ui.rs
new file mode 100644
index 0000000..795c76c
--- /dev/null
+++ b/imag-todo/src/ui.rs
@@ -0,0 +1,61 @@
+//
+// 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, ArgGroup, SubCommand};
+
+pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
+ app
+ .subcommand(SubCommand::with_name("tw-hook")
+ .about("For use in a taskwarrior hook")
+ .version("0.1")
+
+ .arg(Arg::with_name("add")
+ .long("add")
+ .short("a")
+ .takes_value(false)
+ .required(false)
+ .help("For use in an on-add hook"))
+
+ .arg(Arg::with_name("delete")
+ .long("delete")
+ .short("d")
+ .takes_value(false)
+ .required(false)
+ .help("For use in an on-delete hook"))
+
+ .group(ArgGroup::with_name("taskwarrior hooks")
+ .args(&[ "add",
+ "delete",
+ ])
+ .required(true))
+ )
+
+ .subcommand(SubCommand::with_name("list")
+ .about("List all tasks")
+ .version("0.1")
+
+ .arg(Arg::with_name("verbose")
+ .long("verbose")
+ .short("v")
+ .takes_value(false)
+ .required(false)
+ .help("Asks taskwarrior for all the details")
+ )
+ )
+}
diff --git a/libimagannotation/Cargo.toml b/libimagannotation/Cargo.toml
index a94353d..1c512b6 100644
--- a/libimagannotation/Cargo.toml
+++ b/libimagannotation/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "libimagannotation"
-version = "0.2.0"
+version = "0.3.0"
authors = ["Matthias Beyer <mail@beyermatthias.de>"]
[dependencies]
diff --git a/libimagbookmark/Cargo.toml b/libimagbookmark/Cargo.toml
new file mode 100644
index 0000000..c14962f
--- /dev/null
+++ b/libimagbookmark/Cargo.toml
@@ -0,0 +1,31 @@
+[package]
+name = "libimagbookmark"
+version = "0.3.0"
+authors = ["Matthias Beyer <mail@beyermatthias.de>"]
+
+description = "Library for the imag core distribution"
+
+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]
+log = "0.3"
+semver = "0.5"
+url = "1.2"
+regex = "0.1"
+is-match = "0.1"
+
+[dependencies.libimagstore]
+path = "../libimagstore"
+
+[dependencies.libimagerror]
+path = "../libimagerror"
+
+[dependencies.libimagentrylink]
+path = "../libimagentrylink"
+
diff --git a/libimagbookmark/README.md b/libimagbookmark/README.md
new file mode 120000
index 0000000..f7c5cd0
--- /dev/null
+++ b/libimagbookmark/README.md
@@ -0,0 +1 @@
+../doc/src/05100-lib-bookmark.md \ No newline at end of file
diff --git a/libimagbookmark/src/collection.rs b/libimagbookmark/src/collection.rs
new file mode 100644
index 0000000..206e31c
--- /dev/null
+++ b/libimagbookmark/src/collection.rs
@@ -0,0 +1,222 @@
+//
+// 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
+//
+
+//! BookmarkCollection module
+//!
+//! A BookmarkCollection is nothing more than a simple store entry. One can simply call functions
+//! from the libimagentrylink::external::ExternalLinker trait on this to generate external links.
+//!
+//! The BookmarkCollection type offers helper functions to get all links or such things.
+use std::ops::Deref;
+use std::ops::DerefMut;
+
+use regex::Regex;
+
+use error::BookmarkErrorKind as BEK;
+use error::MapErrInto;
+use result::Result;
+use module_path::ModuleEntryPath;
+
+use libimagstore::store::Store;
+use libimagstore::storeid::IntoStoreId;
+use libimagstore::store::FileLockEntry;
+use libimagentrylink::external::ExternalLinker;
+use libimagentrylink::external::iter::UrlIter;
+use libimagentrylink::internal::InternalLinker;
+use libimagentrylink::internal::Link as StoreLink;
+use libimagerror::into::IntoError;
+
+use link::Link;
+
+use self::iter::LinksMatchingRegexIter;
+
+pub struct BookmarkCollection<'a> {
+ fle: FileLockEntry<'a>,
+ store: &'a Store,
+}
+
+/// {Internal, External}Linker is implemented as Deref is implemented
+impl<'a> Deref for BookmarkCollection<'a> {
+ type Target = FileLockEntry<'a>;
+
+ fn deref(&self) -> &FileLockEntry<'a> {
+ &self.fle
+ }
+
+}
+
+impl<'a> DerefMut for BookmarkCollection<'a> {
+
+ fn deref_mut(&mut self) -> &mut FileLockEntry<'a> {
+ &mut self.fle
+ }
+
+}
+
+impl<'a> BookmarkCollection<'a> {
+
+ pub fn new(store: &'a Store, name: &str) -> Result<BookmarkCollection<'a>> {
+ ModuleEntryPath::new(name)
+ .into_storeid()
+ .and_then(|id| store.create(id))
+ .map(|fle| {
+ BookmarkCollection {
+ fle: fle,
+ store: store,
+ }
+ })
+ .map_err_into(BEK::StoreReadError)
+ }
+
+ pub fn get(store: &'a Store, name: &str) -> Result<BookmarkCollection<'a>> {
+ ModuleEntryPath::new(name)
+ .into_storeid()
+ .and_then(|id| store.get(id))
+ .map_err_into(BEK::StoreReadError)
+ .and_then(|fle| {
+ match fle {
+ None => Err(BEK::CollectionNotFound.into_error()),
+ Some(e) => Ok(BookmarkCollection {
+ fle: e,
+ store: store,
+ }),
+ }
+ })
+ }
+
+ pub fn delete(store: &Store, name: &str) -> Result<()> {
+ ModuleEntryPath::new(name)
+ .into_storeid()
+ .and_then(|id| store.delete(id))
+ .map_err_into(BEK::StoreReadError)
+ }
+
+ pub fn links(&self) -> Result<UrlIter> {
+ self.fle.get_external_links(&self.store).map_err_into(BEK::LinkError)
+ }
+
+ pub fn link_entries(&self) -> Result<Vec<StoreLink>> {
+ use libimagentrylink::external::is_external_link_storeid;
+
+ self.fle
+ .get_internal_links()
+ .map(|v| v.filter(|id| is_external_link_storeid(id)).collect())
+ .map_err_into(BEK::StoreReadError)
+ }
+
+ pub fn add_link(&mut self, l: Link) -> Result<()> {
+ use link::IntoUrl;
+
+ l.into_url()
+ .and_then(|url| self.add_external_link(self.store, url).map_err_into(BEK::LinkingError))
+ .map_err_into(BEK::LinkError)
+ }
+
+ pub fn get_links_matching(&self, r: Regex) -> Result<LinksMatchingRegexIter<'a>> {
+ use self::iter::IntoLinksMatchingRegexIter;
+
+ self.get_external_links(self.store)
+ .map_err_into(BEK::LinkError)
+ .map(|iter| iter.matching_regex(r))
+ }
+
+ pub fn remove_link(&mut self, l: Link) -> Result<()> {
+ use link::IntoUrl;
+
+ l.into_url()
+ .and_then(|url| {
+ self.remove_external_link(self.store, url).map_err_into(BEK::LinkingError)
+ })
+ .map_err_into(BEK::LinkError)
+ }
+
+}
+
+pub mod iter {
+ use link::Link;
+ use result::Result;
+ use error::{MapErrInto, BookmarkErrorKind as BEK};
+
+ pub struct LinkIter<I>(I)
+ where I: Iterator<Item = Link>;
+
+ impl<I: Iterator<Item = Link>> LinkIter<I> {
+ pub fn new(i: I) -> LinkIter<I> {
+ LinkIter(i)
+ }
+ }
+
+ impl<I: Iterator<Item = Link>> Iterator for LinkIter<I> {
+ type Item = Link;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ self.0.next()
+ }
+ }
+
+ impl<I> From<I> for LinkIter<I> where I: Iterator<Item = Link> {
+ fn from(i: I) -> LinkIter<I> {
+ LinkIter(i)
+ }
+ }
+
+ use libimagentrylink::external::iter::UrlIter;
+ use regex::Regex;
+
+ pub struct LinksMatchingRegexIter<'a>(UrlIter<'a>, Regex);
+
+ impl<'a> LinksMatchingRegexIter<'a> {
+ pub fn new(i: UrlIter<'a>, r: Regex) -> LinksMatchingRegexIter<'a> {
+ LinksMatchingRegexIter(i, r)
+ }
+ }
+
+ impl<'a> Iterator for LinksMatchingRegexIter<'a> {
+ type Item = Result<Link>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ loop {
+ let n = match self.0.next() {
+ Some(Ok(n)) => n,
+ Some(Err(e)) => return Some(Err(e).map_err_into(BEK::LinkError)),
+ None => return None,
+ };
+
+ let s = n.into_string();
+ if self.1.is_match(&s[..]) {
+ return Some(Ok(Link::from(s)))
+ } else {
+ continue;
+ }
+ }
+ }
+ }
+
+ pub trait IntoLinksMatchingRegexIter<'a> {
+ fn matching_regex(self, Regex) -> LinksMatchingRegexIter<'a>;
+ }
+
+ impl<'a> IntoLinksMatchingRegexIter<'a> for UrlIter<'a> {
+ fn matching_regex(self, r: Regex) -> LinksMatchingRegexIter<'a> {
+ LinksMatchingRegexIter(self, r)
+ }
+ }
+
+}
+
diff --git a/libimagbookmark/src/error.rs b/libimagbookmark/src/error.rs
new file mode 100644
index 0000000..9b52a16
--- /dev/null
+++ b/libimagbookmark/src/error.rs
@@ -0,0 +1,33 @@
+//
+// 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
+//
+
+generate_error_module!(
+ generate_error_types!(BookmarkError, BookmarkErrorKind,
+ StoreReadError => "Store read error",
+ LinkError => "Link error",
+ LinkParsingError => "Link parsing error",
+ LinkingError => "Error while linking",
+ CollectionNotFound => "Link-Collection not found"
+ );
+);
+
+pub use self::error::BookmarkError;
+pub use self::error::BookmarkErrorKind;
+pub use self::error::MapErrInto;
+
diff --git a/libimagbookmark/src/lib.rs b/libimagbookmark/src/lib.rs
new file mode 100644
index 0000000..3c1af4c
--- /dev/null
+++ b/libimagbookmark/src/lib.rs
@@ -0,0 +1,49 @@
+//
+// 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_mut,
+ unused_qualifications,
+ while_true,
+)]
+
+#[macro_use] extern crate log;
+#[macro_use] extern crate is_match;
+extern crate semver;
+extern crate url;
+extern crate regex;
+
+#[macro_use] extern crate libimagstore;
+#[macro_use] extern crate libimagerror;
+extern crate libimagentrylink;
+
+module_entry_path_mod!("bookmark");
+
+pub mod collection;
+pub mod error;
+pub mod link;
+pub mod result;
diff --git a/libimagbookmark/src/link.rs b/libimagbookmark/src/link.rs
new file mode 100644
index 0000000..1da91c8
--- /dev/null
+++ b/libimagbookmark/src/link.rs
@@ -0,0 +1,76 @@
+//
+// 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::ops::{Deref, DerefMut};
+
+use result::Result;
+
+use url::Url;
+
+#[derive(Debug, Clone)]
+pub struct Link(String);
+
+impl From<String> for Link {
+
+ fn from(s: String) -> Link {
+ Link(s)
+ }
+
+}
+
+impl<'a> From<&'a str> for Link {
+
+ fn from(s: &'a str) -> Link {
+ Link(String::from(s))
+ }
+
+}
+
+impl Deref for Link {
+ type Target = String;
+
+ fn deref(&self) -> &String {
+ &self.0
+ }
+
+}
+
+impl DerefMut for Link {
+
+ fn deref_mut(&mut self) -> &mut String {
+ &mut self.0
+ }
+
+}
+
+pub trait IntoUrl {
+ fn into_url(self) -> Result<Url>;
+}
+
+impl IntoUrl for Link {
+
+ fn into_url(self) -> Result<Url> {
+ use error::BookmarkErrorKind as BEK;
+ use error::MapErrInto;
+
+ Url::parse(&self[..]).map_err_into(BEK::LinkParsingError)
+ }
+
+}
+
diff --git a/libimagbookmark/src/result.rs b/libimagbookmark/src/result.rs
new file mode 100644
index 0000000..780a803
--- /dev/null
+++ b/libimagbookmark/src/result.rs
@@ -0,0 +1,25 @@
+//
+// 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::result::Result as RResult;
+
+use error::BookmarkError;
+
+pub type Result<T> = RResult<T, BookmarkError>;
+
diff --git a/libimagcounter/Cargo.toml b/libimagcounter/Cargo.toml
new file mode 100644
index 0000000..b1133a1
--- /dev/null
+++ b/libimagcounter/Cargo.toml
@@ -0,0 +1,27 @@
+[package]
+name = "libimagcounter"
+version = "0.3.0"
+authors = ["Matthias Beyer <mail@beyermatthias.de>"]
+
+description = "Library for the imag core distribution"
+
+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]
+log = "0.3"
+toml = "0.4.*"
+toml-query = "0.1.*"
+semver = "0.5"
+
+[dependencies.libimagstore]
+path = "../libimagstore"
+
+[dependencies.libimagerror]
+path = "../libimagerror"
+
diff --git a/libimagcounter/README.md b/libimagcounter/README.md
new file mode 120000
index 0000000..90221b8
--- /dev/null
+++ b/libimagcounter/README.md
@@ -0,0 +1 @@
+../doc/src/05100-lib-counter.md \ No newline at end of file
diff --git a/libimagcounter/src/counter.rs b/libimagcounter/src/counter.rs
new file mode 100644
index 0000000..9c69901
--- /dev/null
+++ b/libimagcounter/src/counter.rs
@@ -0,0 +1,263 @@
+//
+// 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::ops::DerefMut;
+
+use toml::Value;
+use toml_query::read::TomlValueReadExt;
+use toml_query::set::TomlValueSetExt;
+
+use std::collections::BTreeMap;
+use std::fmt;
+use std::fmt::Display;
+
+use libimagstore::store::Store;
+use libimagstore::storeid::StoreIdIterator;
+use libimagstore::store::FileLockEntry;
+use libimagstore::storeid::StoreId;
+use libimagstore::storeid::IntoStoreId;
+use libimagerror::into::IntoError;
+
+use module_path::ModuleEntryPath;
+use result::Result;
+use error::CounterError as CE;
+use error::CounterErrorKind as CEK;
+use error::error::MapErrInto;
+
+pub type CounterName = String;
+
+#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
+pub struct CounterUnit(String);
+
+impl Display for CounterUnit {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "({})", self.0)
+ }
+}
+
+impl CounterUnit {
+ pub fn new<S: Into<String>>(unit: S) -> CounterUnit {
+ CounterUnit(unit.into())
+ }
+}
+
+pub struct Counter<'a> {
+ fle: FileLockEntry<'a>,
+ unit: Option<CounterUnit>,
+}
+
+impl<'a> Counter<'a> {
+
+ pub fn new(store: &Store, name: CounterName, init: i64) -> Result<Counter> {
+ use std::ops::DerefMut;
+
+ debug!("Creating new counter: '{}' with value: {}", name, init);
+ let fle = {
+ let id = try!(ModuleEntryPath::new(name.clone())
+ .into_storeid()
+ .map_err_into(CEK::StoreWriteError));
+ let mut lockentry = try!(store.create(id).map_err_into(CEK::StoreWriteError));
+
+ {
+ let mut entry = lockentry.deref_mut();
+ let mut header = entry.get_header_mut();
+ let setres = header.set(&String::from("counter"), Value::Table(BTreeMap::new()));
+ if setres.is_err() {
+ return Err(CEK::StoreWriteError.into_error());
+ }
+
+ let setres = header.set(&String::from("counter.name"), Value::String(name));
+ if setres.is_err() {
+ return Err(CEK::StoreWriteError.into_error())
+ }
+
+ let setres = header.set(&String::from("counter.value"), Value::Integer(init));
+ if setres.is_err() {
+ return Err(CEK::StoreWriteError.into_error())
+ }
+ }
+
+ lockentry
+ };
+
+ Ok(Counter { fle: fle, unit: None })
+ }
+
+ pub fn with_unit(mut self, unit: Option<CounterUnit>) -> Result<Counter<'a>> {
+ self.unit = unit;
+
+ if let Some(u) = self.unit.clone() {
+ let mut header = self.fle.deref_mut().get_header_mut();
+ let setres = header.set(&String::from("counter.unit"), Value::String(u.0));
+ if setres.is_err() {
+ self.unit = None;
+ return Err(CEK::StoreWriteError.into_error())
+ }
+ };
+ Ok(self)
+ }
+
+ pub fn inc(&mut self) -> Result<()> {
+ let mut header = self.fle.deref_mut().get_header_mut();
+ let query = String::from("counter.value");
+ match try!(header.read(&query).map_err_into(CEK::StoreReadError)) {
+ &Value::Integer(i) => {
+ header.set(&query, Value::Integer(i + 1))
+ .map_err_into(CEK::StoreWriteError)
+ .map(|_| ())
+ },
+ _ => Err(CE::new(CEK::StoreReadError, None)),
+ }
+ }
+
+ pub fn dec(&mut self) -> Result<()> {
+ let mut header = self.fle.deref_mut().get_header_mut();
+ let query = String::from("counter.value");
+ match try!(header.read(&query).map_err_into(CEK::StoreReadError)) {
+ &Value::Integer(i) => {
+ header.set(&query, Value::Integer(i - 1))
+ .map_err_into(CEK::StoreWriteError)
+ .map(|_| ())
+ },
+ _ => Err(CE::new(CEK::StoreReadError, None)),
+ }
+ }
+
+ pub fn reset(&mut self) -> Result<()> {
+ self.set(0)
+ }
+
+ pub fn set(&mut self, v: i64) -> Result<()> {
+ let mut header = self.fle.deref_mut().get_header_mut();
+ header.set(&String::from("counter.value"), Value::Integer(v))
+ .map_err_into(CEK::StoreWriteError)
+ .map(|_| ())
+ }
+
+ pub fn name(&self) -> Result<CounterName> {
+ self.read_header_at("counter.name", |v| match v {
+ &Value::String(ref s) => Ok(s.clone()),
+ _ => Err(CEK::HeaderTypeError.into_error()),
+ })
+ }
+
+ pub fn value(&self) -> Result<i64> {
+ self.read_header_at("counter.value", |v| match v {
+ &Value::Integer(i) => Ok(i),
+ _ => Err(CEK::HeaderTypeError.into_error()),
+ })
+ }
+
+ pub fn unit(&self) -> Option<&CounterUnit> {
+ self.unit.as_ref()
+ }
+
+ pub fn read_unit(&self) -> Result<Option<CounterUnit>> {
+ self.read_header_at("counter.unit", |s| match s {
+ &Value::String(ref s) => Ok(Some(CounterUnit::new(s.clone()))),
+ _ => Err(CEK::HeaderTypeError.into_error()),
+ })
+ }
+
+ fn read_header_at<T, F>(&self, name: &str, f: F) -> Result<T>
+ where F: FnOnce(&Value) -> Result<T>
+ {
+
+ self.fle
+ .get_header()
+ .read(&String::from(name))
+ .map_err_into(CEK::StoreWriteError)
+ .and_then(f)
+ }
+
+ pub fn load(name: CounterName, store: &Store) -> Result<Counter> {
+ debug!("Loading counter: '{}'", name);
+ let id = try!(ModuleEntryPath::new(name)
+ .into_storeid()
+ .map_err_into(CEK::StoreWriteError));
+ Counter::from_storeid(store, id)
+ }
+
+ pub fn delete(name: CounterName, store: &Store) -> Result<()> {
+ debug!("Deleting counter: '{}'", name);
+ let id = try!(ModuleEntryPath::new(name)
+ .into_storeid()
+ .map_err_into(CEK::StoreWriteError));
+ store.delete(id).map_err_into(CEK::StoreWriteError)
+ }
+
+ pub fn all_counters(store: &Store) -> Result<CounterIterator> {
+ store.retrieve_for_module("counter")
+ .map(|iter| CounterIterator::new(store, iter))
+ .map_err_into(CEK::StoreReadError)
+ }
+
+}
+
+trait FromStoreId {
+ fn from_storeid(&Store, StoreId) -> Result<Counter>;
+}
+
+impl<'a> FromStoreId for Counter<'a> {
+
+ fn from_storeid(store: &Store, id: StoreId) -> Result<Counter> {
+ debug!("Loading counter from storeid: '{:?}'", id);
+ match store.retrieve(id) {
+ Err(e) => Err(CE::new(CEK::StoreReadError, Some(Box::new(e)))),
+ Ok(c) => {
+ let mut counter = Counter { fle: c, unit: None };
+ counter.read_unit()
+ .map_err_into(CEK::StoreReadError)
+ .and_then(|u| {
+ counter.unit = u;
+ Ok(counter)
+ })
+ }
+ }
+ }
+
+}
+
+pub struct CounterIterator<'a> {
+ store: &'a Store,
+ iditer: StoreIdIterator,
+}
+
+impl<'a> CounterIterator<'a> {
+
+ pub fn new(store: &'a Store, iditer: StoreIdIterator) -> CounterIterator<'a> {
+ CounterIterator {
+ store: store,
+ iditer: iditer,
+ }
+ }
+
+}
+
+impl<'a> Iterator for CounterIterator<'a> {
+ type Item = Result<Counter<'a>>;
+
+ fn next(&mut self) -> Option<Result<Counter<'a>>> {
+ self.iditer
+ .next()
+ .map(|id| Counter::from_storeid(self.store, id))
+ }
+
+}
+
diff --git a/libimagcounter/src/error.rs b/libimagcounter/src/error.rs
new file mode 100644
index 0000000..c886d90
--- /dev/null
+++ b/libimagcounter/src/error.rs
@@ -0,0 +1,32 @@
+//
+// 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
+//
+
+generate_error_module!(
+ generate_error_types!(CounterError, CounterErrorKind,
+ StoreIdError => "StoreId error",
+ StoreReadError => "Store read error",
+ StoreWriteError => "Store write error",
+ HeaderTypeError => "Header type error",
+ HeaderFieldMissingError => "Header field missing error"
+ );
+);
+
+pub use self::error::CounterError;
+pub use self::error::CounterErrorKind;
+
diff --git a/libimagcounter/src/lib.rs b/libimagcounter/src/lib.rs
new file mode 100644
index 0000000..da7d466
--- /dev/null
+++ b/libimagcounter/src/lib.rs
@@ -0,0 +1,47 @@
+//
+// 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_mut,
+ unused_qualifications,
+ while_true,
+)]
+
+extern crate toml;
+extern crate toml_query;
+#[macro_use] extern crate log;
+#[macro_use] extern crate semver;
+
+#[macro_use] extern crate libimagstore;
+#[macro_use] extern crate libimagerror;
+
+module_entry_path_mod!("counter");
+
+pub mod counter;
+pub mod error;
+pub mod result;
+
diff --git a/libimagcounter/src/result.rs b/libimagcounter/src/result.rs
new file mode 100644
index 0000000..e24b90c
--- /dev/null
+++ b/libimagcounter/src/result.rs
@@ -0,0 +1,25 @@
+//
+// 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::result::Result as RResult;
+
+use error::CounterError;
+
+pub type Result<T> = RResult<T, CounterError>;
+
diff --git a/libimagdiary/Cargo.toml b/libimagdiary/Cargo.toml
new file mode 100644
index 0000000..bddb1a0
--- /dev/null
+++ b/libimagdiary/Cargo.toml
@@ -0,0 +1,43 @@
+[package]
+name = "libimagdiary"
+version = "0.3.0"
+authors = ["Matthias Beyer <mail@beyermatthias.de>"]
+
+description = "Library for the imag core distribution"
+
+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]
+chrono = "0.2"
+log = "0.3"
+semver = "0.5"
+toml = "0.4.*"
+toml-query = "0.1.*"
+regex = "0.1"
+lazy_static = "0.2"
+itertools = "0.5"
+
+[dependencies.libimagstore]
+path = "../libimagstore"
+
+[dependencies.libimagerror]
+path = "../libimagerror"
+
+[dependencies.libimagutil]
+path = "../libimagutil"
+
+[dependencies.libimagrt]
+path = "../libimagrt"
+
+[dependencies.libimagentryedit]
+path = "../libimagentryedit"
+
+[dependencies.libimagentryview]
+path = "../libimagentryview"
+
diff --git a/libimagdiary/README.md b/libimagdiary/README.md
new file mode 120000
index 0000000..50c0ff3
--- /dev/null
+++ b/libimagdiary/README.md
@@ -0,0 +1 @@
+../doc/src/05100-lib-diary.md \ No newline at end of file
diff --git a/libimagdiary/src/config.rs b/libimagdiary/src/config.rs
new file mode 100644
index 0000000..a341b24
--- /dev/null
+++ b/libimagdiary/src/config.rs
@@ -0,0 +1,40 @@
+//
+// 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 toml::Value;
+
+use libimagrt::runtime::Runtime;
+
+use toml_query::read::TomlValueReadExt;
+
+pub fn get_default_diary_name(rt: &Runtime) -> Option<String> {
+ get_diary_config_section(rt)
+ .and_then(|config| {
+ match config.read(&String::from("default_diary")) {
+ Ok(&Value::String(ref s)) => Some(s.clone()),
+ _ => None,
+ }
+ })
+}
+
+pub fn get_diary_config_section<'a>(rt: &'a Runtime) -> Option<&'a Value> {
+ rt.config()
+ .map(|config| config.config())
+ .and_then(|config| config.read(&String::from("diary")).ok())
+}
diff --git a/libimagdiary/src/diary.rs b/libimagdiary/src/diary.rs
new file mode 100644
index 0000000..1a19b05
--- /dev/null
+++ b/libimagdiary/src/diary.rs
@@ -0,0 +1,128 @@
+//
+// 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::cmp::Ordering;
+
+use libimagstore::store::Store;
+use libimagstore::storeid::IntoStoreId;
+use libimagerror::trace::trace_error;
+
+use chrono::offset::local::Local;
+use chrono::Datelike;
+use itertools::Itertools;
+use chrono::naive::datetime::NaiveDateTime;
+
+use entry::Entry;
+use diaryid::DiaryId;
+use error::DiaryError as DE;
+use error::DiaryErrorKind as DEK;
+use result::Result;
+use iter::DiaryEntryIterator;
+use is_in_diary::IsInDiary;
+
+#[derive(Debug)]
+pub struct Diary<'a> {
+ store: &'a Store,
+ name: &'a str,
+}
+
+impl<'a> Diary<'a> {
+
+ pub fn open(store: &'a Store, name: &'a str) -> Diary<'a> {
+ Diary {
+ store: store,
+ name: name,
+ }
+ }
+
+ // create or get a new entry for today
+ pub fn new_entry_today(&self) -> Result<Entry> {
+ let dt = Local::now();
+ let ndt = dt.naive_local();
+ let id = DiaryId::new(String::from(self.name), ndt.year(), ndt.month(), ndt.day(), 0, 0);
+ self.new_entry_by_id(id)
+ }
+
+ pub fn new_entry_by_id(&self, id: DiaryId) -> Result<Entry> {
+ self.retrieve(id.with_diary_name(String::from(self.name)))
+ }
+
+ pub fn retrieve(&self, id: DiaryId) -> Result<Entry> {
+ id.into_storeid()
+ .and_then(|id| self.store.retrieve(id))
+ .map(|fle| Entry::new(fle))
+ .map_err(|e| DE::new(DEK::StoreWriteError, Some(Box::new(e))))
+ }
+
+ // Get an iterator for iterating over all entries
+ pub fn entries(&self) -> Result<DiaryEntryIterator<'a>> {
+ self.store
+ .retrieve_for_module("diary")
+ .map(|iter| DiaryEntryIterator::new(self.name, self.store, iter))
+ .map_err(|e| DE::new(DEK::StoreReadError, Some(Box::new(e))))
+ }
+
+ pub fn delete_entry(&self, entry: Entry) -> Result<()> {
+ if !entry.is_in_diary(self.name) {
+ return Err(DE::new(DEK::EntryNotInDiary, None));
+ }
+ let id = entry.get_location().clone();
+ drop(entry);
+
+ self.store.delete(id)
+ .map_err(|e| DE::new(DEK::StoreWriteError, Some(Box::new(e))))
+ }
+
+ pub fn get_youngest_entry(&self) -> Option<Result<Entry>> {
+ match self.entries() {
+ Err(e) => Some(Err(e)),
+ Ok(entries) => {
+ entries.sorted_by(|a, b| {
+ match (a, b) {
+ (&Ok(ref a), &Ok(ref b)) => {
+ let a : NaiveDateTime = a.diary_id().into();
+ let b : NaiveDateTime = b.diary_id().into();
+
+ a.cmp(&b)
+ },
+
+ (&Ok(_), &Err(ref e)) => {
+ trace_error(e);
+ Ordering::Less
+ },
+ (&Err(ref e), &Ok(_)) => {
+ trace_error(e);
+ Ordering::Greater
+ },
+ (&Err(ref e1), &Err(ref e2)) => {
+ trace_error(e1);
+ trace_error(e2);
+ Ordering::Equal
+ },
+ }
+ }).into_iter().next()
+ }
+ }
+ }
+
+ pub fn name(&self) -> &'a str {
+ &self.name
+ }
+}
+
diff --git a/libimagdiary/src/diaryid.rs b/libimagdiary/src/diaryid.rs
new file mode 100644
index 0000000..906097d
--- /dev/null
+++ b/libimagdiary/src/diaryid.rs
@@ -0,0 +1,257 @@
+//
+// 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::convert::Into;
+use std::fmt::{Display, Formatter, Error as FmtError};
+
+use chrono::naive::datetime::NaiveDateTime;
+use chrono::naive::time::NaiveTime;
+use chrono::naive::date::NaiveDate;
+use chrono::Datelike;
+use chrono::Timelike;
+
+use libimagstore::storeid::StoreId;
+use libimagstore::storeid::IntoStoreId;
+use libimagstore::store::Result as StoreResult;
+
+use error::DiaryError as DE;
+use error::DiaryErrorKind as DEK;
+use error::MapErrInto;
+use libimagerror::into::IntoError;
+
+use module_path::ModuleEntryPath;
+
+#[derive(Debug, Clone)]
+pub struct DiaryId {
+ name: String,
+ year: i32,
+ month: u32,
+ day: u32,
+ hour: u32,
+ minute: u32,
+}
+
+impl DiaryId {
+
+ pub fn new(name: String, y: i32, m: u32, d: u32, h: u32, min: u32) -> DiaryId {
+ DiaryId {
+ name: name,
+ year: y,
+ month: m,
+ day: d,
+ hour: h,
+ minute: min,
+ }
+ }
+
+ pub fn from_datetime<DT: Datelike + Timelike>(diary_name: String, dt: DT) -> DiaryId {
+ DiaryId::new(diary_name, dt.year(), dt.month(), dt.day(), dt.hour(), dt.minute())
+ }
+
+ pub fn diary_name(&self) -> &String {
+ &self.name
+ }
+
+ pub fn year(&self) -> i32 {
+ self.year
+ }
+
+ pub fn month(&self) -> u32 {
+ self.month
+ }
+
+ pub fn day(&self) -> u32 {
+ self.day
+ }
+
+ pub fn hour(&self) -> u32 {
+ self.hour
+ }
+
+ pub fn minute(&self) -> u32 {
+ self.minute
+ }
+
+ pub fn with_diary_name(mut self, name: String) -> DiaryId {
+ self.name = name;
+ self
+ }
+
+ pub fn with_year(mut self, year: i32) -> DiaryId {
+ self.year = year;
+ self
+ }
+
+ pub fn with_month(mut self, month: u32) -> DiaryId {
+ self.month = month;
+ self
+ }
+
+ pub fn with_day(mut self, day: u32) -> DiaryId {
+ self.day = day;
+ self
+ }
+
+ pub fn with_hour(mut self, hour: u32) -> DiaryId {
+ self.hour = hour;
+ self
+ }
+
+ pub fn with_minute(mut self, minute: u32) -> DiaryId {
+ self.minute = minute;
+ self
+ }
+
+ pub fn now(name: String) -> DiaryId {
+ use chrono::offset::local::Local;
+
+ let now = Local::now();
+ let now_date = now.date().naive_local();
+ let now_time = now.time();
+ let dt = NaiveDateTime::new(now_date, now_time);
+
+ DiaryId::new(name, dt.year(), dt.month(), dt.day(), dt.hour(), dt.minute())
+ }
+
+}
+
+impl Default for DiaryId {
+
+ /// Create a default DiaryId which is a diaryid for a diary named "default" with
+ /// time = 0000-00-00 00:00:00
+ fn default() -> DiaryId {
+ let dt = NaiveDateTime::new(NaiveDate::from_ymd(0, 0, 0), NaiveTime::from_hms(0, 0, 0));
+ DiaryId::from_datetime(String::from("default"), dt)
+ }
+}
+
+impl IntoStoreId for DiaryId {
+
+ fn into_storeid(self) -> StoreResult<StoreId> {
+ let s : String = self.into();
+ ModuleEntryPath::new(s).into_storeid()
+ }
+
+}
+
+impl Into<String> for DiaryId {
+
+ fn into(self) -> String {
+ format!("{}/{:0>4}/{:0>2}/{:0>2}/{:0>2}:{:0>2}",
+ self.name, self.year, self.month, self.day, self.hour, self.minute)
+ }
+
+}
+
+impl Display for DiaryId {
+
+ fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> {
+ write!(fmt, "{}/{:0>4}/{:0>2}/{:0>2}/{:0>2}:{:0>2}",
+ self.name, self.year, self.month, self.day, self.hour, self.minute)
+ }
+
+}
+
+impl Into<NaiveDateTime> for DiaryId {
+
+ fn into(self) -> NaiveDateTime {
+ let d = NaiveDate::from_ymd(self.year, self.month, self.day);
+ let t = NaiveTime::from_hms(self.hour, self.minute, 0);
+ NaiveDateTime::new(d, t)
+ }
+
+}
+
+pub trait FromStoreId : Sized {
+
+ fn from_storeid(&StoreId) -> Result<Self, DE>;
+
+}
+
+use std::path::Component;
+
+fn component_to_str<'a>(com: Component<'a>) -> Result<&'a str, DE> {
+ match com {
+ Component::Normal(s) => Some(s),
+ _ => None,
+ }.and_then(|s| s.to_str())
+ .ok_or(DEK::IdParseError.into_error())
+}
+
+impl FromStoreId for DiaryId {
+
+ fn from_storeid(s: &StoreId) -> Result<DiaryId, DE> {
+ use std::str::FromStr;
+
+ use std::path::Components;
+ use std::iter::Rev;
+
+ fn next_component<'a>(components: &'a mut Rev<Components>) -> Result<&'a str, DE> {
+ components.next()
+ .ok_or(DEK::IdParseError.into_error())
+ .and_then(component_to_str)
+ }
+
+ let mut cmps = s.components().rev();
+
+ let (hour, minute) = try!(next_component(&mut cmps).and_then(|time| {
+ let mut time = time.split(":");
+ let hour = time.next().and_then(|s| FromStr::from_str(s).ok());
+ let minute = time.next()
+ .and_then(|s| s.split("~").next())
+ .and_then(|s| FromStr::from_str(s).ok());
+
+ debug!("Hour = {:?}", hour);
+ debug!("Minute = {:?}", minute);
+
+ match (hour, minute) {
+ (Some(h), Some(m)) => Ok((h, m)),
+ _ => return Err(DE::new(DEK::IdParseError, None)),
+ }
+ }));
+
+ let day: Result<u32,_> = next_component(&mut cmps)
+ .and_then(|s| s.parse::<u32>()
+ .map_err_into(DEK::IdParseError));
+
+ let month: Result<u32,_> = next_component(&mut cmps)
+ .and_then(|s| s.parse::<u32>()
+ .map_err_into(DEK::IdParseError));
+
+ let year: Result<i32,_> = next_component(&mut cmps)
+ .and_then(|s| s.parse::<i32>()
+ .map_err_into(DEK::IdParseError));
+
+ let name = next_component(&mut cmps).map(String::from);
+
+ debug!("Day = {:?}", day);
+ debug!("Month = {:?}", month);
+ debug!("Year = {:?}", year);
+ debug!("Name = {:?}", name);
+
+ let day = try!(day);
+ let month = try!(month);
+ let year = try!(year);
+ let name = try!(name);
+
+ Ok(DiaryId::new(name, year, month, day, hour, minute))
+ }
+
+}
+
diff --git a/libimagdiary/src/entry.rs b/libimagdiary/src/entry.rs
new file mode 100644
index 0000000..0148b59
--- /dev/null
+++ b/libimagdiary/src/entry.rs
@@ -0,0 +1,90 @@
+//
+// 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::ops::Deref;
+use std::ops::DerefMut;
+
+use libimagstore::store::FileLockEntry;
+use libimagentryedit::edit::Edit;
+use libimagentryedit::result::Result as EditResult;
+use libimagrt::runtime::Runtime;
+
+use diaryid::DiaryId;
+use diaryid::FromStoreId;
+
+#[derive(Debug)]
+pub struct Entry<'a>(FileLockEntry<'a>);
+
+impl<'a> Deref for Entry<'a> {
+ type Target = FileLockEntry<'a>;
+
+ fn deref(&self) -> &FileLockEntry<'a> {
+ &self.0
+ }
+
+}
+
+impl<'a> DerefMut for Entry<'a> {
+
+ fn deref_mut(&mut self) -> &mut FileLockEntry<'a> {
+ &mut self.0
+ }
+
+}
+
+impl<'a> Entry<'a> {
+
+ pub fn new(fle: FileLockEntry<'a>) -> Entry<'a> {
+ Entry(fle)
+ }
+
+ /// Get the diary id for this entry.
+ ///
+ /// TODO: calls Option::unwrap() as it assumes that an existing Entry has an ID that is parsable
+ pub fn diary_id(&self) -> DiaryId {
+ DiaryId::from_storeid(&self.0.get_location().clone()).unwrap()
+ }
+
+}
+
+impl<'a> Into<FileLockEntry<'a>> for Entry<'a> {
+
+ fn into(self) -> FileLockEntry<'a> {
+ self.0
+ }
+
+}
+
+impl<'a> From<FileLockEntry<'a>> for Entry<'a> {
+
+ fn from(fle: FileLockEntry<'a>) -> Entry<'a> {
+ Entry::new(fle)
+ }
+
+}
+
+impl<'a> Edit for Entry<'a> {
+
+ fn edit_content(&mut self, rt: &Runtime) -> EditResult<()> {
+ self.0.edit_content(rt)
+ }
+
+}
+
+
diff --git a/libimagdiary/src/error.rs b/libimagdiary/src/error.rs
new file mode 100644
index 0000000..b5406b1
--- /dev/null
+++ b/libimagdiary/src/error.rs
@@ -0,0 +1,38 @@
+//
+// 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
+//
+
+generate_error_module!(
+ generate_error_types!(DiaryError, DiaryErrorKind,
+ StoreWriteError => "Error writing store",
+ StoreReadError => "Error reading store",
+ CannotFindDiary => "Cannot find diary",
+ CannotCreateNote => "Cannot create Note object for diary entry",
+ DiaryEditError => "Cannot edit diary entry",
+ PathConversionError => "Error while converting paths internally",
+ EntryNotInDiary => "Entry not in Diary",
+ IOError => "IO Error",
+ ViewError => "Error viewing diary entry",
+ IdParseError => "Error while parsing ID"
+ );
+);
+
+pub use self::error::DiaryError;
+pub use self::error::DiaryErrorKind;
+pub use self::error::MapErrInto;
+
diff --git a/libimagdiary/src/is_in_diary.rs b/libimagdiary/src/is_in_diary.rs
new file mode 100644
index 0000000..0922807
--- /dev/null
+++ b/libimagdiary/src/is_in_diary.rs
@@ -0,0 +1,44 @@
+//
+// 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 libimagstore::store::Entry;
+use libimagstore::storeid::StoreId;
+
+pub trait IsInDiary {
+
+ fn is_in_diary(&self, name: &str) -> bool;
+
+}
+
+impl IsInDiary for Entry {
+
+ fn is_in_diary(&self, name: &str) -> bool {
+ self.get_location().clone().is_in_diary(name)
+ }
+
+}
+
+impl IsInDiary for StoreId {
+
+ fn is_in_diary(&self, name: &str) -> bool {
+ self.local().starts_with(format!("diary/{}", name))
+ }
+
+}
+
diff --git a/libimagdiary/src/iter.rs b/libimagdiary/src/iter.rs
new file mode 100644
index 0000000..de84987
--- /dev/null
+++ b/libimagdiary/src/iter.rs
@@ -0,0 +1,132 @@
+//
+// 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::fmt::{Debug, Formatter, Error as FmtError};
+use std::result::Result as RResult;
+
+use libimagstore::store::Store;
+use libimagstore::storeid::StoreIdIterator;
+
+use diaryid::DiaryId;
+use diaryid::FromStoreId;
+use is_in_diary::IsInDiary;
+use entry::Entry as DiaryEntry;
+use error::DiaryError as DE;
+use error::DiaryErrorKind as DEK;
+use result::Result;
+use libimagerror::trace::trace_error;
+
+/// A iterator for iterating over diary entries
+pub struct DiaryEntryIterator<'a> {
+ store: &'a Store,
+ name: &'a str,
+ iter: StoreIdIterator,
+
+ year: Option<i32>,
+ month: Option<u32>,
+ day: Option<u32>,
+}
+
+impl<'a> Debug for DiaryEntryIterator<'a> {
+
+ fn fmt(&self, fmt: &mut Formatter) -> RResult<(), FmtError> {
+ write!(fmt, "DiaryEntryIterator<name = {}, year = {:?}, month = {:?}, day = {:?}>",
+ self.name, self.year, self.month, self.day)
+ }
+
+}
+
+impl<'a> DiaryEntryIterator<'a> {
+
+ pub fn new(diaryname: &'a str, store: &'a Store, iter: StoreIdIterator) -> DiaryEntryIterator<'a> {
+ DiaryEntryIterator {
+ store: store,
+ name: diaryname,
+ iter: iter,
+
+ year: None,
+ month: None,
+ day: None,
+ }
+ }
+
+ // Filter by year, get all diary entries for this year
+ pub fn year(mut self, year: i32) -> DiaryEntryIterator<'a> {
+ self.year = Some(year);
+ self
+ }
+
+ // Filter by month, get all diary entries for this month (every year)
+ pub fn month(mut self, month: u32) -> DiaryEntryIterator<'a> {
+ self.month = Some(month);
+ self
+ }
+
+ // Filter by day, get all diary entries for this day (every year, every year)
+ pub fn day(mut self, day: u32) -> DiaryEntryIterator<'a> {
+ self.day = Some(day);
+ self
+ }
+
+}
+
+impl<'a> Iterator for DiaryEntryIterator<'a> {
+ type Item = Result<DiaryEntry<'a>>;
+
+ fn next(&mut self) -> Option<Result<DiaryEntry<'a>>> {
+ loop {
+ let next = match self.iter.next() {
+ Some(s) => s,
+ None => return None,
+ };
+ debug!("Next element: {:?}", next);
+
+ if next.is_in_diary(self.name) {
+ debug!("Seems to be in diary: {:?}", next);
+ let id = match DiaryId::from_storeid(&next) {
+ Ok(i) => i,
+ Err(e) => {
+ trace_error(&e);
+ debug!("Couldn't parse {:?} into DiaryId: {:?}", next, e);
+ continue;
+ }
+ };
+ debug!("Success parsing id = {:?}", id);
+
+ let y = match self.year { None => true, Some(y) => y == id.year() };
+ let m = match self.month { None => true, Some(m) => m == id.month() };
+ let d = match self.day { None => true, Some(d) => d == id.day() };
+
+ if y && m && d {
+ debug!("Return = {:?}", id);
+ return Some(self
+ .store
+ .retrieve(next)
+ .map(|fle| DiaryEntry::new(fle))
+ .map_err(|e| DE::new(DEK::StoreReadError, Some(Box::new(e))))
+ );
+ }
+ } else {
+ debug!("Not in the requested diary ({}): {:?}", self.name, next);
+ }
+ }
+ }
+
+}
+
diff --git a/libimagdiary/src/lib.rs b/libimagdiary/src/lib.rs
new file mode 100644
index 0000000..1dcd496
--- /dev/null
+++ b/libimagdiary/src/lib.rs
@@ -0,0 +1,63 @@
+//
+// 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(
+ dead_code,
+ 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 chrono;
+#[macro_use] extern crate log;
+#[macro_use] extern crate lazy_static;
+extern crate semver;
+extern crate toml;
+extern crate toml_query;
+extern crate regex;
+extern crate itertools;
+
+#[macro_use] extern crate libimagstore;
+#[macro_use] extern crate libimagutil;
+#[macro_use] extern crate libimagerror;
+extern crate libimagentryedit;
+extern crate libimagentryview;
+extern crate libimagrt;
+
+module_entry_path_mod!("diary");
+
+pub mod config;
+pub mod error;
+pub mod diaryid;
+pub mod diary;
+pub mod is_in_diary;
+pub mod entry;
+pub mod iter;
+pub mod result;
+pub mod viewer;
+
diff --git a/libimagdiary/src/result.rs b/libimagdiary/src/result.rs
new file mode 100644
index 0000000..b4f5f38
--- /dev/null
+++ b/libimagdiary/src/result.rs
@@ -0,0 +1,24 @@
+//
+// 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::result::Result as RResult;
+
+use error::DiaryError;
+
+pub type Result<T> = RResult<T, DiaryError>;
diff --git a/libimagdiary/src/viewer.rs b/libimagdiary/src/viewer.rs
new file mode 100644
index 0000000..93b155a
--- /dev/null
+++ b/libimagdiary/src/viewer.rs
@@ -0,0 +1,64 @@
+//
+// 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
+//
+
+//! A diary viewer built on libimagentryview.
+
+use entry::Entry;
+use error::DiaryErrorKind as DEK;
+use error::MapErrInto;
+use result::Result;
+
+use libimagentryview::viewer::Viewer;
+use libimagentryview::builtin::plain::PlainViewer;
+
+/// This viewer does _not_ implement libimagentryview::viewer::Viewer because we need to be able to
+/// call some diary-type specific functions on the entries passed to this.
+///
+/// This type is mainly just written to be constructed-called-deleted in one go:
+///
+/// ```ignore
+/// DiaryViewer::new(show_header).view_entries(entries);
+/// ```
+///
+pub struct DiaryViewer(PlainViewer);
+
+impl DiaryViewer {
+
+ pub fn new(show_header: bool) -> DiaryViewer {
+ DiaryViewer(PlainViewer::new(show_header))
+ }
+
+ /// View all entries from the iterator, or stop immediately if an error occurs, returning that
+ /// error.
+ pub fn view_entries<'a, I: Iterator<Item = Entry<'a>>>(&self, entries: I) -> Result<()> {
+ for entry in entries {
+ let id = entry.diary_id();
+ println!("{} :\n", id);
+ let _ = try!(self.0
+ .view_entry(&entry)
+ .map_err_into(DEK::ViewError)
+ .map_err_into(DEK::IOError));
+ println!("\n---\n");
+ }
+
+ Ok(())
+ }
+
+}
+
diff --git a/libimagmail/Cargo.toml b/libimagmail/Cargo.toml
new file mode 100644
index 0000000..98452ce
--- /dev/null
+++ b/libimagmail/Cargo.toml
@@ -0,0 +1,31 @@
+[package]
+name = "libimagmail"
+version = "0.3.0"
+authors = ["Matthias Beyer <mail@beyermatthias.de>"]
+
+description = "Library for the imag core distribution"
+
+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]
+log = "0.3"
+mailparse = "0.4"
+semver = "0.5"
+toml = "0.4.*"
+filters = "0.1.*"
+
+[dependencies.libimagstore]
+path = "../libimagstore"
+
+[dependencies.libimagerror]
+path = "../libimagerror"
+
+[dependencies.libimagref]
+path = "../libimagref"
+
diff --git a/libimagmail/src/error.rs b/libimagmail/src/error.rs
new file mode 100644
index 0000000..a11a0a9
--- /dev/null
+++ b/libimagmail/src/error.rs
@@ -0,0 +1,16 @@
+generate_error_module!(
+ generate_error_types!(MailError, MailErrorKind,
+ RefCreationError => "Error creating a reference to a file/directory",
+ RefHandlingError => "Error while handling the internal reference object",
+ MailParsingError => "Error while parsing mail",
+
+ FetchByHashError => "Error fetching mail from Store by hash",
+ FetchError => "Error fetching mail from Store",
+ IOError => "IO Error"
+ );
+);
+
+pub use self::error::MailError;
+pub use self::error::MailErrorKind;
+pub use self::error::MapErrInto;
+
diff --git a/libimagmail/src/hasher.rs b/libimagmail/src/hasher.rs
new file mode 100644
index 0000000..dc1df12
--- /dev/null
+++ b/libimagmail/src/hasher.rs
@@ -0,0 +1,67 @@
+use std::io::Read;
+use std::path::PathBuf;
+
+use mailparse::{MailHeader, parse_mail};
+
+use libimagref::hasher::Hasher;
+use libimagref::hasher::DefaultHasher;
+use libimagref::error::RefErrorKind as REK;
+use libimagref::error::MapErrInto;
+use libimagref::result::Result as RResult;
+use libimagerror::into::IntoError;
+
+use error::MailErrorKind as MEK;
+
+pub struct MailHasher {
+ defaulthasher: DefaultHasher,
+}
+
+impl MailHasher {
+
+ pub fn new() -> MailHasher {
+ MailHasher { defaulthasher: DefaultHasher::new() }
+ }
+
+}
+
+impl Hasher for MailHasher {
+
+ fn hash_name(&self) -> &'static str {
+ "default_mail_hasher"
+ }
+
+ fn create_hash<R: Read>(&mut self, pb: &PathBuf, c: &mut R) -> RResult<String> {
+ use filters::filter::Filter;
+
+ let mut s = String::new();
+ try!(c.read_to_string(&mut s).map_err_into(REK::UTF8Error).map_err_into(REK::IOError));
+
+ parse_mail(&s.as_bytes())
+ .map_err(Box::new)
+ .map_err(|e| MEK::MailParsingError.into_error_with_cause(e))
+ .map_err_into(REK::RefHashingError)
+ .and_then(|mail| {
+ let has_key = |hdr: &MailHeader, exp: &str|
+ hdr.get_key().map(|s| s == exp).unwrap_or(false);
+
+ let subject_filter = |hdr: &MailHeader| has_key(hdr, "Subject");
+ let from_filter = |hdr: &MailHeader| has_key(hdr, "From");
+ let to_filter = |hdr: &MailHeader| has_key(hdr, "To");
+
+ let filter = subject_filter.or(from_filter).or(to_filter);
+
+ let mut v = vec![];
+ for hdr in mail.headers.iter().filter(|item| filter.filter(item)) {
+ let s = try!(hdr.get_value()
+ .map_err(Box::new)
+ .map_err(|e| REK::RefHashingError.into_error_with_cause(e)));
+
+ v.push(s);
+ }
+ let s : String = v.join("");
+
+ self.defaulthasher.create_hash(pb, &mut s.as_bytes())
+ })
+ }
+
+}
diff --git a/libimagmail/src/iter.rs b/libimagmail/src/iter.rs
new file mode 100644
index 0000000..365a092
--- /dev/null
+++ b/libimagmail/src/iter.rs
@@ -0,0 +1,37 @@
+//! Module for the MailIter
+//!
+//! MailIter is a iterator which takes an Iterator that yields `Ref` and yields itself
+//! `Result<Mail>`, where `Err(_)` is returned if the Ref is not a Mail or parsing of the
+//! referenced mail file failed.
+//!
+
+use mail::Mail;
+use result::Result;
+
+use libimagref::reference::Ref;
+
+use std::marker::PhantomData;
+
+struct MailIter<'a, I: 'a + Iterator<Item = Ref<'a>>> {
+ _marker: PhantomData<&'a I>,
+ i: I,
+}
+
+impl<'a, I: Iterator<Item = Ref<'a>>> MailIter<'a, I> {
+
+ pub fn new(i: I) -> MailIter<'a, I> {
+ MailIter { _marker: PhantomData, i: i }
+ }
+
+}
+
+impl<'a, I: Iterator<Item = Ref<'a>>> Iterator for MailIter<'a, I> {
+
+ type Item = Result<Mail<'a>>;
+
+ fn next(&mut self) -> Option<Result<Mail<'a>>> {
+ self.i.next().map(Mail::from_ref)
+ }
+
+}
+
diff --git a/libimagmail/src/lib.rs b/libimagmail/src/lib.rs
new file mode 100644
index 0000000..1d88a06
--- /dev/null
+++ b/libimagmail/src/lib.rs
@@ -0,0 +1,16 @@
+#[macro_use] extern crate log;
+extern crate mailparse;
+extern crate semver;
+extern crate toml;
+extern crate filters;
+
+#[macro_use] extern crate libimagerror;
+extern crate libimagstore;
+extern crate libimagref;
+
+pub mod error;
+pub mod hasher;
+pub mod iter;
+pub mod mail;
+pub mod result;
+
diff --git a/libimagmail/src/mail.rs b/libimagmail/src/mail.rs
new file mode 100644
index 0000000..b49e0ed
--- /dev/null
+++ b/libimagmail/src/mail.rs
@@ -0,0 +1,120 @@
+use std::result::Result as RResult;
+use std::path::Path;
+use std::path::PathBuf;
+use std::fs::File;
+use std::io::Read;
+
+use libimagstore::store::{FileLockEntry, Store};
+use libimagref::reference::Ref;
+use libimagref::flags::RefFlags;
+
+use mailparse::{MailParseError, ParsedMail, parse_mail};
+
+use hasher::MailHasher;
+use result::Result;
+use error::{MapErrInto, MailErrorKind as MEK};
+
+struct Buffer(String);
+
+impl Buffer {
+ pub fn parsed<'a>(&'a self) -> RResult<ParsedMail<'a>, MailParseError> {
+ parse_mail(self.0.as_bytes())
+ }
+}
+
+impl From<String> for Buffer {
+ fn from(data: String) -> Buffer {
+ Buffer(data)
+ }
+}
+
+pub struct Mail<'a>(Ref<'a>, Buffer);
+
+impl<'a> Mail<'a> {
+
+ /// Imports a mail from the Path passed
+ pub fn import_from_path<P: AsRef<Path>>(store: &Store, p: P) -> Result<Mail> {
+ let h = MailHasher::new();
+ let f = RefFlags::default().with_content_hashing(true).with_permission_tracking(false);
+ let p = PathBuf::from(p.as_ref());
+
+ Ref::create_with_hasher(store, p, f, h)
+ .map_err_into(MEK::RefCreationError)
+ .and_then(|reference| {
+ reference.fs_file()
+ .map_err_into(MEK::RefHandlingError)
+ .and_then(|path| File::open(path).map_err_into(MEK::IOError))
+ .and_then(|mut file| {
+ let mut s = String::new();
+ file.read_to_string(&mut s)
+ .map(|_| s)
+ .map_err_into(MEK::IOError)
+ })
+ .map(Buffer::from)
+ .map(|buffer| Mail(reference, buffer))
+ })
+ }
+
+ /// Opens a mail by the passed hash
+ pub fn open<S: AsRef<str>>(store: &Store, hash: S) -> Result<Option<Mail>> {
+ Ref::get_by_hash(store, String::from(hash.as_ref()))
+ .map_err_into(MEK::FetchByHashError)
+ .map_err_into(MEK::FetchError)
+ .and_then(|o| match o {
+ Some(r) => Mail::from_ref(r).map(Some),
+ None => Ok(None),
+ })
+
+ }
+
+ /// Implement me as TryFrom as soon as it is stable
+ pub fn from_ref(r: Ref<'a>) -> Result<Mail> {
+ r.fs_file()
+ .map_err_into(MEK::RefHandlingError)
+ .and_then(|path| File::open(path).map_err_into(MEK::IOError))
+ .and_then(|mut file| {
+ let mut s = String::new();
+ file.read_to_string(&mut s)
+ .map(|_| s)
+ .map_err_into(MEK::IOError)
+ })
+ .map(Buffer::from)
+ .map(|buffer| Mail(r, buffer))
+ }
+
+ pub fn get_field(&self, field: &str) -> Result<Option<String>> {
+ use mailparse::MailHeader;
+
+ self.1
+ .parsed()
+ .map_err_into(MEK::MailParsingError)
+ .map(|parsed| {
+ parsed.headers
+ .iter()
+ .filter(|hdr| hdr.get_key().map(|n| n == field).unwrap_or(false))
+ .next()
+ .and_then(|field| field.get_value().ok())
+ })
+ }
+
+ pub fn get_from(&self) -> Result<Option<String>> {
+ self.get_field("From")
+ }
+
+ pub fn get_to(&self) -> Result<Option<String>> {
+ self.get_field("To")
+ }
+
+ pub fn get_subject(&self) -> Result<Option<String>> {
+ self.get_field("Subject")
+ }
+
+ pub fn get_message_id(&self) -> Result<Option<String>> {
+ self.get_field("Message-ID")
+ }
+
+ pub fn get_in_reply_to(&self) -> Result<Option<String>> {
+ self.get_field("In-Reply-To")
+ }
+
+}
diff --git a/libimagmail/src/result.rs b/libimagmail/src/result.rs
new file mode 100644
index 0000000..e771551
--- /dev/null
+++ b/libimagmail/src/result.rs
@@ -0,0 +1,6 @@
+use std::result::Result as RResult;
+
+use error::MailError;
+
+pub type Result<T> = RResult<T, MailError>;
+
diff --git a/libimagtodo/Cargo.toml b/libimagtodo/Cargo.toml
new file mode 100644
index 0000000..cf971e6
--- /dev/null
+++ b/libimagtodo/Cargo.toml
@@ -0,0 +1,34 @@
+[package]
+name = "libimagtodo"
+version = "0.3.0"
+authors = ["mario <mario-krehl@gmx.de>"]
+
+description = "Library for the imag core distribution"
+
+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]
+semver = "0.2"
+task-hookrs = "0.2.2"
+uuid = "0.3"
+toml = "0.4.*"
+toml-query = "0.1.*"
+is-match = "0.1.*"
+log = "0.3"
+serde_json = "0.8"
+
+[dependencies.libimagstore]
+path = "../libimagstore"
+
+[dependencies.libimagerror]
+path = "../libimagerror"
+
+[dependencies.libimagutil]
+path = "../libimagutil"
+
diff --git a/libimagtodo/src/error.rs b/libimagtodo/src/error.rs
new file mode 100644
index 0000000..c19a6e5
--- /dev/null
+++ b/libimagtodo/src/error.rs
@@ -0,0 +1,32 @@
+//
+// 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
+//
+
+generate_error_module!(
+ generate_error_types!(TodoError, TodoErrorKind,
+ ConversionError => "Conversion Error",
+ StoreError => "Store Error",
+ StoreIdError => "Store Id handling error",
+ ImportError => "Error importing"
+ );
+);
+
+pub use self::error::TodoError;
+pub use self::error::TodoErrorKind;
+pub use self::error::MapErrInto;
+
diff --git a/libimagtodo/src/lib.rs b/libimagtodo/src/lib.rs
new file mode 100644
index 0000000..16fcc11
--- /dev/null
+++ b/libimagtodo/src/lib.rs
@@ -0,0 +1,52 @@
+//
+// 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_mut,
+ unused_qualifications,
+ while_true,
+)]
+
+extern crate semver;
+extern crate uuid;
+extern crate toml;
+extern crate toml_query;
+#[macro_use] extern crate is_match;
+#[macro_use] extern crate log;
+extern crate serde_json;
+
+#[macro_use] extern crate libimagstore;
+#[macro_use] extern crate libimagerror;
+extern crate libimagutil;
+extern crate task_hookrs;
+
+module_entry_path_mod!("todo");
+
+pub mod error;
+pub mod result;
+pub mod task;
+
diff --git a/libimagtodo/src/result.rs b/libimagtodo/src/result.rs
new file mode 100644
index 0000000..7962851
--- /dev/null
+++ b/libimagtodo/src/result.rs
@@ -0,0 +1,24 @@
+//
+// 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 error::TodoError;
+
+use std::result::Result as RResult;
+
+pub type Result<T> = RResult<T, TodoError>;
diff --git a/libimagtodo/src/task.rs b/libimagtodo/src/task.rs
new file mode 100644
index 0000000..1f826fb
--- /dev/null
+++ b/libimagtodo/src/task.rs
@@ -0,0 +1,311 @@
+//
+// 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::ops::{Deref, DerefMut};
+use std::io::BufRead;
+use std::result::Result as RResult;
+
+use toml::Value;
+use uuid::Uuid;
+
+use task_hookrs::task::Task as TTask;
+use task_hookrs::import::{import_task, import_tasks};
+
+use libimagstore::store::{FileLockEntry, Store};
+use libimagstore::storeid::{IntoStoreId, StoreIdIterator, StoreId};
+use libimagerror::trace::MapErrTrace;
+use libimagutil::debug_result::DebugResult;
+use module_path::ModuleEntryPath;
+
+use error::{TodoError, TodoErrorKind, MapErrInto};
+use result::Result;
+
+/// Task struct containing a `FileLockEntry`
+#[derive(Debug)]
+pub struct Task<'a>(FileLockEntry<'a>);
+
+impl<'a> Task<'a> {
+
+ /// Concstructs a new `Task` with a `FileLockEntry`
+ pub fn new(fle: FileLockEntry<'a>) -> Task<'a> {
+ Task(fle)
+ }
+
+ pub fn import<R: BufRead>(store: &'a Store, mut r: R) -> Result<(Task<'a>, String, Uuid)> {
+ let mut line = String::new();
+ r.read_line(&mut line);
+ import_task(&line.as_str())
+ .map_err_into(TodoErrorKind::ImportError)
+ .map_dbg_err_str("Error while importing task")
+ .map_err_dbg_trace()
+ .and_then(|t| {
+ let uuid = t.uuid().clone();
+ t.into_task(store).map(|t| (t, line, uuid))
+ })
+ }
+
+ /// Get a task from an import string. That is: read the imported string, get the UUID from it
+ /// and try to load this UUID from store.
+ ///
+ /// Possible return values are:
+ ///
+ /// * Ok(Ok(Task))
+ /// * Ok(Err(String)) - where the String is the String read from the `r` parameter
+ /// * Err(_) - where the error is an error that happened during evaluation
+ ///
+ pub fn get_from_import<R: BufRead>(store: &'a Store, mut r: R) -> Result<RResult<Task<'a>, String>>
+ {
+ let mut line = String::new();
+ r.read_line(&mut line);
+ Task::get_from_string(store, line)
+ }
+
+ /// Get a task from a String. The String is expected to contain the JSON-representation of the
+ /// Task to get from the store (only the UUID really matters in this case)
+ ///
+ /// For an explanation on the return values see `Task::get_from_import()`.
+ pub fn get_from_string(store: &'a Store, s: String) -> Result<RResult<Task<'a>, String>> {
+ import_task(s.as_str())
+ .map_err_into(TodoErrorKind::ImportError)
+ .map_dbg_err_str("Error while importing task")
+ .map_err_dbg_trace()
+ .map(|t| t.uuid().clone())
+ .and_then(|uuid| Task::get_from_uuid(store, uuid))
+ .and_then(|o| match o {
+ None => Ok(Err(s)),
+ Some(t) => Ok(Ok(t)),
+ })
+ }
+
+ /// Get a task from an UUID.
+ ///
+ /// If there is no task with this UUID, this returns `Ok(None)`.
+ pub fn get_from_uuid(store: &'a Store, uuid: Uuid) -> Result<Option<Task<'a>>> {
+ ModuleEntryPath::new(format!("taskwarrior/{}", uuid))
+ .into_storeid()
+ .and_then(|store_id| store.get(store_id))
+ .map(|o| o.map(Task::new))
+ .map_err_into(TodoErrorKind::StoreError)
+ }
+
+ /// Same as Task::get_from_import() but uses Store::retrieve() rather than Store::get(), to
+ /// implicitely create the task if it does not exist.
+ pub fn retrieve_from_import<R: BufRead>(store: &'a Store, mut r: R) -> Result<Task<'a>> {
+ let mut line = String::new();
+ r.read_line(&mut line);
+ Task::retrieve_from_string(store, line)
+ }
+
+ /// Retrieve a task from a String. The String is expected to contain the JSON-representation of
+ /// the Task to retrieve from the store (only the UUID really matters in this case)
+ pub fn retrieve_from_string(store: &'a Store, s: String) -> Result<Task<'a>> {
+ Task::get_from_string(store, s)
+ .and_then(|opt| match opt {
+ Ok(task) => Ok(task),
+ Err(string) => import_task(string.as_str())
+ .map_err_into(TodoErrorKind::ImportError)
+ .map_dbg_err_str("Error while importing task")
+ .map_err_dbg_trace()
+ .and_then(|t| t.into_task(store)),
+ })
+ }
+
+ pub fn delete_by_imports<R: BufRead>(store: &Store, r: R) -> Result<()> {
+ use serde_json::ser::to_string as serde_to_string;
+ use task_hookrs::status::TaskStatus;
+
+ for (counter, res_ttask) in import_tasks(r).into_iter().enumerate() {
+ match res_ttask {
+ Ok(ttask) => {
+ if counter % 2 == 1 {
+ // Only every second task is needed, the first one is the
+ // task before the change, and the second one after
+ // the change. The (maybe modified) second one is
+ // expected by taskwarrior.
+ match serde_to_string(&ttask).map_err_into(TodoErrorKind::ImportError) {
+ // use println!() here, as we talk with TW
+ Ok(val) => println!("{}", val),
+ Err(e) => return Err(e),
+ }
+
+ // Taskwarrior does not have the concept of deleted tasks, but only modified
+ // ones.
+ //
+ // Here we check if the status of a task is deleted and if yes, we delete it
+ // from the store.
+ if *ttask.status() == TaskStatus::Deleted {
+ match Task::delete_by_uuid(store, *ttask.uuid()) {
+ Ok(_) => info!("Deleted task {}", *ttask.uuid()),
+ Err(e) => return Err(e),
+ }
+ }
+ } // end if c % 2
+ },
+ Err(e) => return Err(e).map_err_into(TodoErrorKind::ImportError),
+ }
+ }
+ Ok(())
+ }
+
+ pub fn delete_by_uuid(store: &Store, uuid: Uuid) -> Result<()> {
+ ModuleEntryPath::new(format!("taskwarrior/{}", uuid))
+ .into_storeid()
+ .and_then(|id| store.delete(id))
+ .map_err(|e| TodoError::new(TodoErrorKind::StoreError, Some(Box::new(e))))
+ }
+
+ pub fn all_as_ids(store: &Store) -> Result<StoreIdIterator> {
+ store.retrieve_for_module("todo/taskwarrior")
+ .map_err(|e| TodoError::new(TodoErrorKind::StoreError, Some(Box::new(e))))
+ }
+
+ pub fn all(store: &Store) -> Result<TaskIterator> {
+ Task::all_as_ids(store)
+ .map(|iter| TaskIterator::new(store, iter))
+ }
+
+}
+
+impl<'a> Deref for Task<'a> {
+ type Target = FileLockEntry<'a>;
+
+ fn deref(&self) -> &FileLockEntry<'a> {
+ &self.0
+ }
+
+}
+
+impl<'a> DerefMut for Task<'a> {
+
+ fn deref_mut(&mut self) -> &mut FileLockEntry<'a> {
+ &mut self.0
+ }
+
+}
+
+/// A trait to get a `libimagtodo::task::Task` out of the implementing object.
+pub trait IntoTask<'a> {
+
+ /// # Usage
+ /// ```ignore
+ /// use std::io::stdin;
+ ///
+ /// use task_hookrs::task::Task;
+ /// use task_hookrs::import::import;
+ /// use libimagstore::store::{Store, FileLockEntry};
+ ///
+ /// if let Ok(task_hookrs_task) = import(stdin()) {
+ /// // Store is given at runtime
+ /// let task = task_hookrs_task.into_filelockentry(store);
+ /// println!("Task with uuid: {}", task.flentry.get_header().get("todo.uuid"));
+ /// }
+ /// ```
+ fn into_task(self, store : &'a Store) -> Result<Task<'a>>;
+
+}
+
+impl<'a> IntoTask<'a> for TTask {
+
+ fn into_task(self, store : &'a Store) -> Result<Task<'a>> {
+ use toml_query::read::TomlValueReadExt;
+ use toml_query::set::TomlValueSetExt;
+ use libimagerror::into::IntoError;
+
+ // Helper for toml_query::read::TomlValueReadExt::read() return value, which does only
+ // return Result<T> instead of Result<Option<T>>, which is a real inconvenience.
+ //
+ let no_identifier = |e: &::toml_query::error::Error| -> bool {
+ is_match!(e.kind(), &::toml_query::error::ErrorKind::IdentifierNotFoundInDocument(_))
+ };
+
+ let uuid = self.uuid();
+ ModuleEntryPath::new(format!("taskwarrior/{}", uuid))
+ .into_storeid()
+ .map_err_into(TodoErrorKind::StoreIdError)
+ .and_then(|id| {
+ store.retrieve(id)
+ .map_err_into(TodoErrorKind::StoreError)
+ .and_then(|mut fle| {
+ {
+ let mut hdr = fle.get_header_mut();
+ let todo_query = String::from("todo");
+
+ if let Err(e) = hdr.read(&todo_query) {
+ if no_identifier(&e) {
+ try!(hdr
+ .set(&String::from("todo"), Value::Table(BTreeMap::new()))
+ .map_err_into(TodoErrorKind::StoreError));
+ } else {
+ let e = Box::new(e);
+ return Err(TodoErrorKind::StoreError.into_error_with_cause(e))
+ }
+ }
+
+ try!(hdr.set(&String::from("todo.uuid"),
+ Value::String(format!("{}", uuid)))
+ .map_err_into(TodoErrorKind::StoreError));
+ }
+
+ // If none of the errors above have returned the function, everything is fine
+ Ok(Task::new(fle))
+ })
+ })
+ }
+
+}
+
+trait FromStoreId {
+ fn from_storeid<'a>(&'a Store, StoreId) -> Result<Task<'a>>;
+}
+
+impl<'a> FromStoreId for Task<'a> {
+
+ fn from_storeid<'b>(store: &'b Store, id: StoreId) -> Result<Task<'b>> {
+ match store.retrieve(id) {
+ Err(e) => Err(TodoError::new(TodoErrorKind::StoreError, Some(Box::new(e)))),
+ Ok(c) => Ok(Task::new( c )),
+ }
+ }
+}
+
+pub struct TaskIterator<'a> {
+ store: &'a Store,
+ iditer: StoreIdIterator,
+}
+
+impl<'a> TaskIterator<'a> {
+
+ pub fn new(store: &'a Store, iditer: StoreIdIterator) -> TaskIterator<'a> {
+ TaskIterator {
+ store: store,
+ iditer: iditer,
+ }
+ }
+
+}
+
+impl<'a> Iterator for TaskIterator<'a> {
+ type Item = Result<Task<'a>>;
+
+ fn next(&mut self) -> Option<Result<Task<'a>>> {
+ self.iditer.next().map(|id| Task::from_storeid(self.store, id))
+ }
+}
+