summaryrefslogtreecommitdiff
path: root/bin/domain
diff options
context:
space:
mode:
Diffstat (limited to 'bin/domain')
-rw-r--r--bin/domain/imag-bookmark/Cargo.toml26
l---------bin/domain/imag-bookmark/README.md1
-rw-r--r--bin/domain/imag-bookmark/src/main.rs158
-rw-r--r--bin/domain/imag-bookmark/src/ui.rs122
-rw-r--r--bin/domain/imag-counter/Cargo.toml25
l---------bin/domain/imag-counter/README.md1
-rw-r--r--bin/domain/imag-counter/src/create.rs50
-rw-r--r--bin/domain/imag-counter/src/delete.rs39
-rw-r--r--bin/domain/imag-counter/src/interactive.rs174
-rw-r--r--bin/domain/imag-counter/src/list.rs54
-rw-r--r--bin/domain/imag-counter/src/main.rs139
-rw-r--r--bin/domain/imag-counter/src/ui.rs139
-rw-r--r--bin/domain/imag-diary/Cargo.toml31
l---------bin/domain/imag-diary/README.md1
-rw-r--r--bin/domain/imag-diary/src/create.rs123
-rw-r--r--bin/domain/imag-diary/src/delete.rs73
-rw-r--r--bin/domain/imag-diary/src/edit.rs62
-rw-r--r--bin/domain/imag-diary/src/list.rs68
-rw-r--r--bin/domain/imag-diary/src/main.rs101
-rw-r--r--bin/domain/imag-diary/src/ui.rs126
-rw-r--r--bin/domain/imag-diary/src/util.rs28
-rw-r--r--bin/domain/imag-diary/src/view.rs38
-rw-r--r--bin/domain/imag-mail/Cargo.toml25
l---------bin/domain/imag-mail/README.md1
-rw-r--r--bin/domain/imag-mail/src/main.rs151
-rw-r--r--bin/domain/imag-mail/src/ui.rs74
-rw-r--r--bin/domain/imag-notes/Cargo.toml28
-rw-r--r--bin/domain/imag-notes/src/main.rs136
-rw-r--r--bin/domain/imag-notes/src/ui.rs73
-rw-r--r--bin/domain/imag-timetrack/Cargo.toml30
-rw-r--r--bin/domain/imag-timetrack/src/cont.rs112
-rw-r--r--bin/domain/imag-timetrack/src/day.rs119
-rw-r--r--bin/domain/imag-timetrack/src/list.rs124
-rw-r--r--bin/domain/imag-timetrack/src/main.rs93
-rw-r--r--bin/domain/imag-timetrack/src/month.rs135
-rw-r--r--bin/domain/imag-timetrack/src/start.rs56
-rw-r--r--bin/domain/imag-timetrack/src/stop.rs98
-rw-r--r--bin/domain/imag-timetrack/src/track.rs75
-rw-r--r--bin/domain/imag-timetrack/src/ui.rs178
-rw-r--r--bin/domain/imag-timetrack/src/week.rs126
-rw-r--r--bin/domain/imag-timetrack/src/year.rs126
-rw-r--r--bin/domain/imag-todo/Cargo.toml31
-rw-r--r--bin/domain/imag-todo/etc/on-add.sh4
-rw-r--r--bin/domain/imag-todo/etc/on-modify.sh4
-rw-r--r--bin/domain/imag-todo/src/main.rs154
-rw-r--r--bin/domain/imag-todo/src/ui.rs61
46 files changed, 3593 insertions, 0 deletions
diff --git a/bin/domain/imag-bookmark/Cargo.toml b/bin/domain/imag-bookmark/Cargo.toml
new file mode 100644
index 0000000..658566d
--- /dev/null
+++ b/bin/domain/imag-bookmark/Cargo.toml
@@ -0,0 +1,26 @@
+[package]
+name = "imag-bookmark"
+version = "0.4.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"
+
+libimagrt = { version = "0.4.0", path = "../../../lib/core/libimagrt" }
+libimagerror = { version = "0.4.0", path = "../../../lib/core/libimagerror" }
+libimagbookmark = { version = "0.4.0", path = "../../../lib/domain/libimagbookmark" }
+libimagentrylink = { version = "0.4.0", path = "../../../lib/entry/libimagentrylink" }
+libimagentrytag = { version = "0.4.0", path = "../../../lib/entry/libimagentrytag" }
+libimagutil = { version = "0.4.0", path = "../../../lib/etc/libimagutil" }
diff --git a/bin/domain/imag-bookmark/README.md b/bin/domain/imag-bookmark/README.md
new file mode 120000
index 0000000..43e7a52
--- /dev/null
+++ b/bin/domain/imag-bookmark/README.md
@@ -0,0 +1 @@
+../doc/src/04020-module-bookmarks.md \ No newline at end of file
diff --git a/bin/domain/imag-bookmark/src/main.rs b/bin/domain/imag-bookmark/src/main.rs
new file mode 100644
index 0000000..e063516
--- /dev/null
+++ b/bin/domain/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/bin/domain/imag-bookmark/src/ui.rs b/bin/domain/imag-bookmark/src/ui.rs
new file mode 100644
index 0000000..0b14257
--- /dev/null
+++ b/bin/domain/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/bin/domain/imag-counter/Cargo.toml b/bin/domain/imag-counter/Cargo.toml
new file mode 100644
index 0000000..cc90503
--- /dev/null
+++ b/bin/domain/imag-counter/Cargo.toml
@@ -0,0 +1,25 @@
+[package]
+name = "imag-counter"
+version = "0.4.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"
+
+libimagrt = { version = "0.4.0", path = "../../../lib/core/libimagrt" }
+libimagerror = { version = "0.4.0", path = "../../../lib/core/libimagerror" }
+libimagutil = { version = "0.4.0", path = "../../../lib/etc/libimagutil" }
+libimagcounter = { version = "0.4.0", path = "../../../lib/domain/libimagcounter" }
+
diff --git a/bin/domain/imag-counter/README.md b/bin/domain/imag-counter/README.md
new file mode 120000
index 0000000..b52d880
--- /dev/null
+++ b/bin/domain/imag-counter/README.md
@@ -0,0 +1 @@
+../doc/src/04020-module-counter.md \ No newline at end of file
diff --git a/bin/domain/imag-counter/src/create.rs b/bin/domain/imag-counter/src/create.rs
new file mode 100644
index 0000000..9ef6638
--- /dev/null
+++ b/bin/domain/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/bin/domain/imag-counter/src/delete.rs b/bin/domain/imag-counter/src/delete.rs
new file mode 100644
index 0000000..8a35828
--- /dev/null
+++ b/bin/domain/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/bin/domain/imag-counter/src/interactive.rs b/bin/domain/imag-counter/src/interactive.rs
new file mode 100644
index 0000000..c4a8fa6
--- /dev/null
+++ b/bin/domain/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/bin/domain/imag-counter/src/list.rs b/bin/domain/imag-counter/src/list.rs
new file mode 100644
index 0000000..d8d3e86
--- /dev/null
+++ b/bin/domain/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/bin/domain/imag-counter/src/main.rs b/bin/domain/imag-counter/src/main.rs
new file mode 100644
index 0000000..35a2244
--- /dev/null
+++ b/bin/domain/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/bin/domain/imag-counter/src/ui.rs b/bin/domain/imag-counter/src/ui.rs
new file mode 100644
index 0000000..2b928dd
--- /dev/null
+++ b/bin/domain/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/bin/domain/imag-diary/Cargo.toml b/bin/domain/imag-diary/Cargo.toml
new file mode 100644
index 0000000..e7e75b4
--- /dev/null
+++ b/bin/domain/imag-diary/Cargo.toml
@@ -0,0 +1,31 @@
+[package]
+name = "imag-diary"
+version = "0.4.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.4"
+version = "2.0"
+clap = "2.*"
+log = "0.3"
+
+libimagerror = { version = "0.4.0", path = "../../../lib/core/libimagerror" }
+libimagstore = { version = "0.4.0", path = "../../../lib/core/libimagstore" }
+libimagrt = { version = "0.4.0", path = "../../../lib/core/libimagrt" }
+libimagdiary = { version = "0.4.0", path = "../../../lib/domain/libimagdiary" }
+libimagentryedit = { version = "0.4.0", path = "../../../lib/entry/libimagentryedit" }
+libimagentrylist = { version = "0.4.0", path = "../../../lib/entry/libimagentrylist" }
+libimaginteraction = { version = "0.4.0", path = "../../../lib/etc/libimaginteraction" }
+libimagutil = { version = "0.4.0", path = "../../../lib/etc/libimagutil" }
+libimagtimeui = { version = "0.4.0", path = "../../../lib/etc/libimagtimeui" }
+
diff --git a/bin/domain/imag-diary/README.md b/bin/domain/imag-diary/README.md
new file mode 120000
index 0000000..1aab1ab
--- /dev/null
+++ b/bin/domain/imag-diary/README.md
@@ -0,0 +1 @@
+../doc/src/04020-module-diary.md \ No newline at end of file
diff --git a/bin/domain/imag-diary/src/create.rs b/bin/domain/imag-diary/src/create.rs
new file mode 100644
index 0000000..78f679a
--- /dev/null
+++ b/bin/domain/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/bin/domain/imag-diary/src/delete.rs b/bin/domain/imag-diary/src/delete.rs
new file mode 100644
index 0000000..da44bc9
--- /dev/null
+++ b/bin/domain/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::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/bin/domain/imag-diary/src/edit.rs b/bin/domain/imag-diary/src/edit.rs
new file mode 100644
index 0000000..1cd5f9e
--- /dev/null
+++ b/bin/domain/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::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/bin/domain/imag-diary/src/list.rs b/bin/domain/imag-diary/src/list.rs
new file mode 100644
index 0000000..4ed3123
--- /dev/null
+++ b/bin/domain/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/bin/domain/imag-diary/src/main.rs b/bin/domain/imag-diary/src/main.rs
new file mode 100644
index 0000000..844ef3e
--- /dev/null
+++ b/bin/domain/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 libimagerror;
+extern crate libimaginteraction;
+extern crate libimagrt;
+extern crate libimagstore;
+extern crate libimagtimeui;
+extern crate libimagutil;
+
+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/bin/domain/imag-diary/src/ui.rs b/bin/domain/imag-diary/src/ui.rs
new file mode 100644
index 0000000..91712fb
--- /dev/null
+++ b/bin/domain/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/bin/domain/imag-diary/src/util.rs b/bin/domain/imag-diary/src/util.rs
new file mode 100644
index 0000000..2549808
--- /dev/null
+++ b/bin/domain/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/bin/domain/imag-diary/src/view.rs b/bin/domain/imag-diary/src/view.rs
new file mode 100644
index 0000000..041a1fe
--- /dev/null
+++ b/bin/domain/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/bin/domain/imag-mail/Cargo.toml b/bin/domain/imag-mail/Cargo.toml
new file mode 100644
index 0000000..489881a
--- /dev/null
+++ b/bin/domain/imag-mail/Cargo.toml
@@ -0,0 +1,25 @@
+[package]
+name = "imag-mail"
+version = "0.4.0"
+authors = ["Matthias Beyer <mail@beyermatthias.de>"]
+
+description = "Part of the imag core distribution: imag-notes command"
+
+keywords = ["imag", "PIM", "personal", "information", "management"]
+readme = "../README.md"
+license = "LGPL-2.1"
+
+[dependencies]
+semver = "0.5"
+clap = "2.*"
+log = "0.3"
+version = "2.0.1"
+toml = "0.4.*"
+url = "1.2"
+
+libimagrt = { version = "0.4.0", path = "../../../lib/core/libimagrt" }
+libimagerror = { version = "0.4.0", path = "../../../lib/core/libimagerror" }
+libimagmail = { version = "0.4.0", path = "../../../lib/domain/libimagmail" }
+libimagref = { version = "0.4.0", path = "../../../lib/entry/libimagentryref" }
+libimagutil = { version = "0.4.0", path = "../../../lib/etc/libimagutil" }
+
diff --git a/bin/domain/imag-mail/README.md b/bin/domain/imag-mail/README.md
new file mode 120000
index 0000000..d5d8fb0
--- /dev/null
+++ b/bin/domain/imag-mail/README.md
@@ -0,0 +1 @@
+../doc/src/04020-module-mails.md \ No newline at end of file
diff --git a/bin/domain/imag-mail/src/main.rs b/bin/domain/imag-mail/src/main.rs
new file mode 100644
index 0000000..bdd51bd
--- /dev/null
+++ b/bin/domain/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/bin/domain/imag-mail/src/ui.rs b/bin/domain/imag-mail/src/ui.rs
new file mode 100644
index 0000000..3e99994
--- /dev/null
+++ b/bin/domain/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/bin/domain/imag-notes/Cargo.toml b/bin/domain/imag-notes/Cargo.toml
new file mode 100644
index 0000000..e274e3d
--- /dev/null
+++ b/bin/domain/imag-notes/Cargo.toml
@@ -0,0 +1,28 @@
+[package]
+name = "imag-notes"
+version = "0.4.0"
+authors = ["Matthias Beyer <mail@beyermatthias.de>"]
+
+description = "Part of the imag core distribution: imag-notes 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]
+semver = "0.2.1"
+clap = ">=2.17"
+log = "0.3"
+version = "2.0.1"
+itertools = "0.5"
+
+libimagrt = { version = "0.4.0", path = "../../../lib/core/libimagrt" }
+libimagerror = { version = "0.4.0", path = "../../../lib/core/libimagerror" }
+libimagnotes = { version = "0.4.0", path = "../../../lib/domain/libimagnotes" }
+libimagentryedit = { version = "0.4.0", path = "../../../lib/entry/libimagentryedit" }
+libimagentrytag = { version = "0.4.0", path = "../../../lib/entry/libimagentrytag" }
+libimagutil = { version = "0.4.0", path = "../../../lib/etc/libimagutil" }
diff --git a/bin/domain/imag-notes/src/main.rs b/bin/domain/imag-notes/src/main.rs
new file mode 100644
index 0000000..697c034
--- /dev/null
+++ b/bin/domain/imag-notes/src/main.rs
@@ -0,0 +1,136 @@
+//
+// 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;
+#[macro_use] extern crate log;
+extern crate semver;
+#[macro_use] extern crate version;
+extern crate itertools;
+
+extern crate libimagnotes;
+extern crate libimagrt;
+extern crate libimagentryedit;
+extern crate libimagentrytag;
+extern crate libimagerror;
+extern crate libimagutil;
+
+use std::process::exit;
+
+use itertools::Itertools;
+
+use libimagentryedit::edit::Edit;
+use libimagrt::runtime::Runtime;
+use libimagrt::setup::generate_runtime_setup;
+use libimagnotes::note::Note;
+use libimagerror::trace::{MapErrTrace, trace_error};
+use libimagutil::info_result::*;
+use libimagutil::warn_result::WarnResult;
+
+mod ui;
+use ui::build_ui;
+
+fn main() {
+ let rt = generate_runtime_setup("imag-notes",
+ &version!()[..],
+ "Note taking helper",
+ build_ui);
+
+ rt.cli()
+ .subcommand_name()
+ .map(|name| {
+ debug!("Call: {}", name);
+ match name {
+ "create" => create(&rt),
+ "delete" => delete(&rt),
+ "edit" => edit(&rt),
+ "list" => list(&rt),
+ _ => {
+ debug!("Unknown command"); // More error handling
+ },
+ };
+ });
+}
+
+fn name_from_cli(rt: &Runtime, subcmd: &str) -> String {
+ rt.cli().subcommand_matches(subcmd).unwrap().value_of("name").map(String::from).unwrap()
+}
+
+fn create(rt: &Runtime) {
+ let name = name_from_cli(rt, "create");
+ Note::new(rt.store(), name.clone(), String::new()).map_err_trace().ok();
+
+ if rt.cli().subcommand_matches("create").unwrap().is_present("edit") &&
+ !edit_entry(rt, name) {
+ exit(1);
+ }
+}
+
+fn delete(rt: &Runtime) {
+ Note::delete(rt.store(), String::from(name_from_cli(rt, "delete")))
+ .map_err_trace()
+ .map_info_str("Ok")
+ .ok();
+}
+
+fn edit(rt: &Runtime) {
+ edit_entry(rt, name_from_cli(rt, "edit"));
+}
+
+fn edit_entry(rt: &Runtime, name: String) -> bool {
+ let mut note = match Note::get(rt.store(), name) {
+ Ok(Some(note)) => note,
+ Ok(None) => {
+ warn!("Cannot edit nonexistent Note");
+ return false
+ },
+ Err(e) => {
+ trace_error(&e);
+ warn!("Cannot edit nonexistent Note");
+ return false
+ },
+ };
+
+ note.edit_content(rt).map_err_trace().map_warn_err_str("Editing failed").is_ok()
+}
+
+fn list(rt: &Runtime) {
+ use std::cmp::Ordering;
+
+ Note::all_notes(rt.store())
+ .map_err_trace_exit(1)
+ .map(|iter| {
+ let notes = iter.filter_map(|note| note.map_err_trace().ok())
+ .sorted_by(|note_a, note_b| {
+ if let (Ok(a), Ok(b)) = (note_a.get_name(), note_b.get_name()) {
+ return a.cmp(&b)
+ } else {
+ return Ordering::Greater;
+ }
+ });
+
+ for note in notes.iter() {
+ note.get_name()
+ .map(|name| println!("{}", name))
+ .map_err_trace()
+ .ok();
+ }
+ })
+ .ok();
+}
+
diff --git a/bin/domain/imag-notes/src/ui.rs b/bin/domain/imag-notes/src/ui.rs
new file mode 100644
index 0000000..906c552
--- /dev/null
+++ b/bin/domain/imag-notes/src/ui.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 clap::{Arg, App, SubCommand};
+
+use libimagentrytag::ui::tag_argument;
+
+pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
+ app
+ .subcommand(SubCommand::with_name("create")
+ .about("Create a note")
+ .version("0.1")
+ .arg(Arg::with_name("name")
+ .long("name")
+ .short("n")
+ .takes_value(true)
+ .required(true)
+ .help("Create Note with this name")
+ .value_name("NAME"))
+ .arg(Arg::with_name("edit")
+ .long("edit")
+ .short("e")
+ .takes_value(false)
+ .required(false)
+ .help("Edit after creating"))
+ )
+
+ .subcommand(SubCommand::with_name("delete")
+ .about("Delete a Note")
+ .version("0.1")
+ .arg(Arg::with_name("name")
+ .long("name")
+ .short("n")
+ .takes_value(true)
+ .required(true)
+ .help("Delete Note with this name")
+ .value_name("NAME")))
+
+ .subcommand(SubCommand::with_name("edit")
+ .about("Edit a Note")
+ .version("0.1")
+ .arg(Arg::with_name("name")
+ .long("name")
+ .short("n")
+ .takes_value(true)
+ .required(true)
+ .help("Edit Note with this name")
+ .value_name("NAME"))
+
+ .arg(tag_argument())
+ )
+
+ .subcommand(SubCommand::with_name("list")
+ .about("List Notes")
+ .version("0.1"))
+
+}
diff --git a/bin/domain/imag-timetrack/Cargo.toml b/bin/domain/imag-timetrack/Cargo.toml
new file mode 100644
index 0000000..8c19478
--- /dev/null
+++ b/bin/domain/imag-timetrack/Cargo.toml
@@ -0,0 +1,30 @@
+[package]
+name = "imag-timetrack"
+version = "0.4.0"
+authors = ["Matthias Beyer <mail@beyermatthias.de>"]
+
+description = "Part of the imag core distribution: imag-tag 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.*"
+log = "0.3"
+version = "2.0.1"
+semver = "0.2"
+toml = "^0.4"
+chrono = "^0.4"
+filters = "0.1.1"
+itertools = "0.6"
+
+libimagstore = { version = "0.4.0", path = "../../../lib/core/libimagstore" }
+libimagrt = { version = "0.4.0", path = "../../../lib/core/libimagrt" }
+libimagerror = { version = "0.4.0", path = "../../../lib/core/libimagerror" }
+libimagentrytimetrack = { version = "0.4.0", path = "../../../lib/entry/libimagentrytimetrack" }
+libimagutil = { version = "0.4.0", path = "../../../lib/etc/libimagutil" }
diff --git a/bin/domain/imag-timetrack/src/cont.rs b/bin/domain/imag-timetrack/src/cont.rs
new file mode 100644
index 0000000..8656367
--- /dev/null
+++ b/bin/domain/imag-timetrack/src/cont.rs
@@ -0,0 +1,112 @@
+//
+// 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::Ord;
+use std::cmp::Ordering;
+
+use filters::ops::not::Not;
+use filters::filter::Filter;
+use itertools::Itertools;
+use itertools::MinMaxResult;
+use chrono::NaiveDateTime;
+
+use libimagerror::trace::trace_error;
+use libimagerror::trace::MapErrTrace;
+use libimagerror::iter::TraceIterator;
+use libimagentrytimetrack::timetrackingstore::TimeTrackStore;
+use libimagentrytimetrack::timetracking::TimeTracking;
+use libimagentrytimetrack::iter::filter::*;
+
+use libimagrt::runtime::Runtime;
+
+pub fn cont(rt: &Runtime) -> i32 {
+ rt.store()
+ .get_timetrackings()
+ .and_then(|iter| {
+ let groups = iter
+ // unwrap everything, trace errors
+ .trace_unwrap()
+
+ // I want all entries with an end time
+ .filter(|e| has_end_time.filter(&e))
+
+ // Now group them by the end time
+ .group_by(|elem| match elem.get_end_datetime() {
+ Ok(Some(dt)) => dt,
+ Ok(None) => {
+ // error. We expect all of them having an end-time.
+ error!("Has no end time, but should be filtered out: {:?}", elem);
+ error!("This is a bug. Please report.");
+ error!("Will panic now");
+ panic!("Unknown bug")
+ }
+ Err(e) => {
+ trace_error(&e);
+ NaiveDateTime::from_timestamp(0, 0) // placeholder
+ }
+ });
+
+ // sort the trackings by key, so by end datetime
+ let elements = {
+ let mut v = vec![];
+ for (key, value) in groups.into_iter() {
+ v.push((key, value));
+ }
+
+ v.into_iter()
+ .sorted_by(|t1, t2| {
+ let (k1, _) = *t1;
+ let (k2, _) = *t2;
+ Ord::cmp(&k1, &k2)
+ })
+ .into_iter()
+
+ // get the last one, which should be the highest one
+ .last() // -> Option<_>
+ };
+
+ match elements {
+ Some((_, trackings)) => {
+ // and then, for all trackings
+ trackings
+ .fold(Ok(0), |acc, tracking| {
+ debug!("Having tracking: {:?}", tracking);
+
+ acc.and_then(|_| {
+ // create a new tracking with the same tag
+ tracking
+ .get_timetrack_tag()
+ .and_then(|tag| rt.store().create_timetracking_now(&tag))
+ .map(|_| 0)
+ .map_err_trace()
+ })
+ })
+ },
+
+ None => {
+ info!("No trackings to continue");
+ Ok(1)
+ },
+ }
+ })
+ .map(|_| 0)
+ .map_err_trace()
+ .unwrap_or(1)
+}
+
diff --git a/bin/domain/imag-timetrack/src/day.rs b/bin/domain/imag-timetrack/src/day.rs
new file mode 100644
index 0000000..04d3604
--- /dev/null
+++ b/bin/domain/imag-timetrack/src/day.rs
@@ -0,0 +1,119 @@
+//
+// 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::Ord;
+use std::cmp::Ordering;
+use std::str::FromStr;
+
+use filters::ops::not::Not;
+use filters::filter::Filter;
+use itertools::Itertools;
+use itertools::MinMaxResult;
+use chrono::NaiveDateTime;
+
+use libimagerror::trace::trace_error;
+use libimagerror::trace::MapErrTrace;
+use libimagerror::iter::TraceIterator;
+use libimagstore::store::FileLockEntry;
+use libimagentrytimetrack::timetrackingstore::TimeTrackStore;
+use libimagentrytimetrack::timetracking::TimeTracking;
+use libimagentrytimetrack::tag::TimeTrackingTag;
+use libimagentrytimetrack::iter::filter::*;
+
+use libimagrt::runtime::Runtime;
+
+
+pub fn day(rt: &Runtime) -> i32 {
+ let (_, cmd) = rt.cli().subcommand();
+ let cmd = cmd.unwrap(); // checked in main()
+
+ let filter = {
+ let start = match cmd.value_of("start").map(::chrono::naive::NaiveDateTime::from_str) {
+ None => ::chrono::offset::Local::today().and_hms(0, 0, 0).naive_local(),
+ Some(Ok(dt)) => dt,
+ Some(Err(e)) => {
+ trace_error(&e);
+ return 1
+ }
+ };
+
+ let end = match cmd.value_of("end").map(::chrono::naive::NaiveDateTime::from_str) {
+ None => ::chrono::offset::Local::today().and_hms(23, 59, 59).naive_local(),
+ Some(Ok(dt)) => dt,
+ Some(Err(e)) => {
+ trace_error(&e);
+ return 1
+ }
+ };
+
+ let tags = cmd
+ .values_of("tags")
+ .map(|ts| ts.into_iter().map(String::from).map(TimeTrackingTag::from).collect());
+
+ let start_time_filter = has_start_time_where(move |dt: &NaiveDateTime| {
+ start <= *dt
+ });
+
+ let end_time_filter = has_end_time_where(move |dt: &NaiveDateTime| {
+ end >= *dt
+ });
+
+ let tags_filter = move |fle: &FileLockEntry| {
+ match tags {
+ Some(ref tags) => has_one_of_tags(&tags).filter(fle),
+ None => true,
+ }
+ };
+
+ tags_filter.and(start_time_filter).and(end_time_filter)
+ };
+
+ rt.store()
+ .get_timetrackings()
+ .and_then(|iter| {
+ iter.trace_unwrap()
+ .filter(|e| filter.filter(e))
+ .fold(Ok(()), |acc, e| {
+ acc.and_then(|_| {
+ debug!("Processing {:?}", e.get_location());
+
+ let tag = try!(e.get_timetrack_tag());
+ debug!(" -> tag = {:?}", tag);
+
+ let start = try!(e.get_start_datetime());
+ debug!(" -> start = {:?}", start);
+
+ let end = try!(e.get_end_datetime());
+ debug!(" -> end = {:?}", end);
+
+ match (start, end) {
+ (None, _) => println!("{} has no start time.", tag),
+ (Some(s), None) => println!("{} | {} - ...", tag, s),
+ (Some(s), Some(e)) => println!("{} | {} - {}", tag, s, e),
+ }
+
+ Ok(())
+ })
+ })
+ })
+ .map(|_| 0)
+ .map_err_trace()
+ .unwrap_or(1)
+}
+
diff --git a/bin/domain/imag-timetrack/src/list.rs b/bin/domain/imag-timetrack/src/list.rs
new file mode 100644
index 0000000..b7d8dcd
--- /dev/null
+++ b/bin/domain/imag-timetrack/src/list.rs
@@ -0,0 +1,124 @@
+//
+// imag - the personal information management suite for the commandline
+// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; version
+// 2.1 of the License.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+//
+
+use std::cmp::Ord;
+use std::cmp::Ordering;
+use std::str::FromStr;
+
+use filters::ops::not::Not;
+use filters::filter::Filter;
+use itertools::Itertools;
+use itertools::MinMaxResult;
+use chrono::NaiveDateTime;
+
+use libimagerror::trace::trace_error;
+use libimagerror::trace::MapErrTrace;
+use libimagerror::iter::TraceIterator;
+use libimagstore::store::FileLockEntry;
+use libimagentrytimetrack::timetrackingstore::TimeTrackStore;
+use libimagentrytimetrack::timetracking::TimeTracking;
+use libimagentrytimetrack::iter::filter::*;
+
+use libimagrt::runtime::Runtime;
+
+pub fn list(rt: &Runtime) -> i32 {
+ let (_, cmd) = rt.cli().subcommand();
+ let cmd = cmd.unwrap(); // checked in main()
+
+ let start = match cmd.value_of("start-time").map(::chrono::naive::NaiveDateTime::from_str) {
+ None => None,
+ Some(Ok(dt)) => Some(dt),
+ Some(Err(e)) => {
+ trace_error(&e);
+ None
+ }
+ };
+ let end = match cmd.value_of("end-time").map(::chrono::naive::NaiveDateTime::from_str) {
+ None => None,
+ Some(Ok(dt)) => Some(dt),
+ Some(Err(e)) => {
+ trace_error(&e);
+ None
+ }
+ };
+
+ let list_not_ended = cmd.is_present("list-not-ended");
+
+ let start_time_filter = |timetracking: &FileLockEntry| {
+ start.map(|s| match timetracking.get_start_datetime() {
+ Ok(Some(dt)) => dt >= s,
+ Ok(None) => {
+ warn!("Funny things are happening: Timetracking has no start time");
+ false
+ }
+ Err(e) => {
+ trace_error(&e);
+ false
+ }
+ })
+ .unwrap_or(true)
+ };
+
+ let end_time_filter = |timetracking: &FileLockEntry| {
+ start.map(|s| match timetracking.get_end_datetime() {
+ Ok(Some(dt)) => dt <= s,
+ Ok(None) => list_not_ended,
+ Err(e) => {
+ trace_error(&e);
+ false
+ }
+ })
+ .unwrap_or(true)
+ };
+
+ let filter = start_time_filter.and(end_time_filter);
+
+ rt.store()
+ .get_timetrackings()
+ .and_then(|iter| {
+ iter.trace_unwrap()
+ .filter(|e| filter.filter(e))
+ .fold(Ok(()), |acc, e| {
+ acc.and_then(|_| {
+ debug!("Processing {:?}", e.get_location());
+
+ let tag = try!(e.get_timetrack_tag());
+ debug!(" -> tag = {:?}", tag);
+
+ let start = try!(e.get_start_datetime());
+ debug!(" -> start = {:?}", start);
+
+ let end = try!(e.get_end_datetime());
+ debug!(" -> end = {:?}", end);
+
+ match (start, end) {
+ (None, _) => println!("{} has no start time.", tag),
+ (Some(s), None) => println!("{} | {} - ...", tag, s),
+ (Some(s), Some(e)) => println!("{} | {} - {}", tag, s, e),
+ }
+
+ Ok(())
+ })
+ })
+ })
+ .map(|_| 0)
+ .map_err_trace()
+ .unwrap_or(1)
+}
+
diff --git a/bin/domain/imag-timetrack/src/main.rs b/bin/domain/imag-timetrack/src/main.rs
new file mode 100644
index 0000000..7546e6b
--- /dev/null
+++ b/bin/domain/imag-timetrack/src/main.rs
@@ -0,0 +1,93 @@
+//
+// imag - the personal information management suite for the commandline
+// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; version
+// 2.1 of the License.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+//
+
+#[macro_use]
+extern crate log;
+
+#[macro_use]
+extern crate version;
+
+extern crate clap;
+extern crate semver;
+extern crate toml;
+extern crate chrono;
+extern crate filters;
+extern crate itertools;
+
+extern crate libimagerror;
+extern crate libimagstore;
+extern crate libimagrt;
+extern crate libimagentrytimetrack;
+extern crate libimagutil;
+
+mod cont;
+mod day;
+mod list;
+mod month;
+mod start;
+mod stop;
+mod track;
+mod ui;
+mod week;
+mod year;
+
+use cont::cont;
+use day::day;
+use list::list;
+use month::month;
+use start::start;
+use stop::stop;
+use track::track;
+use ui::build_ui;
+use week::week;
+use year::year;
+
+use libimagrt::setup::generate_runtime_setup;
+
+fn main() {
+ let rt = generate_runtime_setup("imag-timetrack",
+ &version!()[..],
+ "Time tracking module",
+ build_ui);
+
+ let command = rt.cli().subcommand_name();
+ let retval = if let Some(command) = command {
+ debug!("Call: {}", command);
+ match command {
+ "continue" => cont(&rt),
+ "day" => day(&rt),
+ "list" => list(&rt),
+ "month" => month(&rt),
+ "start" => start(&rt),
+ "stop" => stop(&rt),
+ "track" => track(&rt),
+ "week" => week(&rt),
+ "year" => year(&rt),
+ _ => {
+ error!("Unknown command");
+ 1
+ },
+ }
+ } else {
+ error!("No command");
+ 1
+ };
+
+ ::std::process::exit(retval);
+}
diff --git a/bin/domain/imag-timetrack/src/month.rs b/bin/domain/imag-timetrack/src/month.rs
new file mode 100644
index 0000000..0f9872c
--- /dev/null
+++ b/bin/domain/imag-timetrack/src/month.rs
@@ -0,0 +1,135 @@
+//
+// 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::Ord;
+use std::cmp::Ordering;
+use std::str::FromStr;
+
+use filters::ops::not::Not;
+use filters::filter::Filter;
+use itertools::Itertools;
+use itertools::MinMaxResult;
+use chrono::NaiveDateTime;
+
+use libimagerror::trace::trace_error;
+use libimagerror::trace::MapErrTrace;
+use libimagerror::iter::TraceIterator;
+use libimagstore::store::FileLockEntry;
+use libimagentrytimetrack::timetrackingstore::TimeTrackStore;
+use libimagentrytimetrack::timetracking::TimeTracking;
+use libimagentrytimetrack::tag::TimeTrackingTag;
+use libimagentrytimetrack::iter::filter::*;
+
+use libimagrt::runtime::Runtime;
+
+pub fn month(rt: &Runtime) -> i32 {
+ let cmd = rt.cli().subcommand().1.unwrap(); // checked in main
+
+ let filter = {
+ use chrono::offset::Local;
+ use chrono::naive::NaiveDate;
+ use chrono::Weekday;
+ use chrono::Datelike;
+
+ let now = Local::now();
+
+ let start = match cmd.value_of("start").map(::chrono::naive::NaiveDateTime::from_str) {
+ None => NaiveDate::from_ymd(now.year(), now.month(), 1).and_hms(0, 0, 0),
+ Some(Ok(dt)) => dt,
+ Some(Err(e)) => {
+ trace_error(&e);
+ return 1
+ }
+ };
+
+ let end = match cmd.value_of("end").map(::chrono::naive::NaiveDateTime::from_str) {
+ None => {
+
+ // Is it much harder to go to the last second of the current month than to the first
+ // second of the next month, right?
+ let (year, month) = if now.month() == 12 {
+ (now.year() + 1, 1)
+ } else {
+ (now.year(), now.month())
+ };
+
+ NaiveDate::from_ymd(year, month, 1).and_hms(0, 0, 0)
+ },
+ Some(Ok(dt)) => dt,
+ Some(Err(e)) => {
+ trace_error(&e);
+ return 1
+ }
+ };
+
+ let tags = cmd
+ .values_of("tags")
+ .map(|ts| ts.into_iter().map(String::from).map(TimeTrackingTag::from).collect());
+
+ let start_time_filter = has_start_time_where(move |dt: &NaiveDateTime| {
+ start <= *dt
+ });
+
+ let end_time_filter = has_end_time_where(move |dt: &NaiveDateTime| {
+ end >= *dt
+ });
+
+ let tags_filter = move |fle: &FileLockEntry| {
+ match tags {
+ Some(ref tags) => has_one_of_tags(&tags).filter(fle),
+ None => true,
+ }
+ };
+
+ tags_filter.and(start_time_filter).and(end_time_filter)
+ };
+
+ rt.store()
+ .get_timetrackings()
+ .and_then(|iter| {
+ iter.trace_unwrap()
+ .filter(|e| filter.filter(e))
+ .fold(Ok(()), |acc, e| {
+ acc.and_then(|_| {
+ debug!("Processing {:?}", e.get_location());
+
+ let tag = try!(e.get_timetrack_tag());
+ debug!(" -> tag = {:?}", tag);
+
+ let start = try!(e.get_start_datetime());
+ debug!(" -> start = {:?}", start);
+
+ let end = try!(e.get_end_datetime());
+ debug!(" -> end = {:?}", end);
+
+ match (start, end) {
+ (None, _) => println!("{} has no start time.", tag),
+ (Some(s), None) => println!("{} | {} - ...", tag, s),
+ (Some(s), Some(e)) => println!("{} | {} - {}", tag, s, e),
+ }
+
+ Ok(())
+ })
+ })
+ })
+ .map(|_| 0)
+ .map_err_trace()
+ .unwrap_or(1)
+}
+
diff --git a/bin/domain/imag-timetrack/src/start.rs b/bin/domain/imag-timetrack/src/start.rs
new file mode 100644
index 0000000..8518f56
--- /dev/null
+++ b/bin/domain/imag-timetrack/src/start.rs
@@ -0,0 +1,56 @@
+//
+// 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;
+use libimagentrytimetrack::tag::TimeTrackingTag;
+use libimagentrytimetrack::timetrackingstore::TimeTrackStore;
+use libimagerror::trace::MapErrTrace;
+
+pub fn start(rt: &Runtime) -> i32 {
+ let (_, cmd) = rt.cli().subcommand();
+ let cmd = cmd.unwrap(); // checked in main()
+
+ let start = match cmd.value_of("start-time") {
+ None | Some("now") => ::chrono::offset::Local::now().naive_local(),
+ Some(ndt) => match ::chrono::naive::NaiveDateTime::from_str(ndt) {
+ Ok(ndt) => ndt,
+ Err(e) => {
+ trace_error(&e);
+ error!("Cannot continue, not having start time");
+ return 1
+ },
+ }
+ };
+
+ cmd.values_of("tags")
+ .unwrap() // enforced by clap
+ .map(String::from)
+ .map(TimeTrackingTag::from)
+ .fold(0, |acc, ttt| {
+ rt.store()
+ .create_timetracking_at(&start, &ttt)
+ .map_err_trace()
+ .map(|_| acc)
+ .unwrap_or(1)
+ })
+}
+
diff --git a/bin/domain/imag-timetrack/src/stop.rs b/bin/domain/imag-timetrack/src/stop.rs
new file mode 100644
index 0000000..29b03a0
--- /dev/null
+++ b/bin/domain/imag-timetrack/src/stop.rs
@@ -0,0 +1,98 @@
+//
+// 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 filters::filter::Filter;
+
+use libimagerror::trace::trace_error;
+use libimagerror::iter::TraceIterator;
+use libimagrt::runtime::Runtime;
+use libimagrt::setup::generate_runtime_setup;
+
+use libimagentrytimetrack::timetracking::TimeTracking;
+use libimagentrytimetrack::tag::TimeTrackingTag;
+use libimagentrytimetrack::timetrackingstore::*;
+use libimagentrytimetrack::iter::get::GetTimeTrackIter;
+use libimagentrytimetrack::iter::filter::has_end_time;
+use libimagentrytimetrack::iter::filter::has_one_of_tags;
+
+pub fn stop(rt: &Runtime) -> i32 {
+ let (_, cmd) = rt.cli().subcommand();
+ let cmd = cmd.unwrap(); // checked in main()
+
+ let stop_time = match cmd.value_of("stop-time") {
+ None | Some("now") => ::chrono::offset::Local::now().naive_local(),
+ Some(ndt) => match ::chrono::naive::NaiveDateTime::from_str(ndt) {
+ Ok(ndt) => ndt,
+ Err(e) => {
+ trace_error(&e);
+ error!("Cannot continue, not having start time");
+ return 1
+ },
+ }
+ };
+
+
+ // TODO: We do not yet support stopping all tags by simply calling the "stop" subcommand!
+
+ let tags : Vec<TimeTrackingTag> = cmd.values_of("tags")
+ .unwrap() // enforced by clap
+ .map(String::from)
+ .map(TimeTrackingTag::from)
+ .collect();
+
+ let iter : GetTimeTrackIter = match rt.store().get_timetrackings() {
+ Ok(i) => i,
+ Err(e) => {
+ error!("Getting timetrackings failed");
+ trace_error(&e);
+ return 1
+ }
+
+ };
+
+ let filter = has_end_time.not().and(has_one_of_tags(&tags));
+
+ // Filter all timetrackings for the ones that are not yet ended.
+ iter.trace_unwrap()
+ .filter_map(|elem| {
+ if filter.filter(&elem) {
+ Some(elem)
+ } else {
+ None
+ }
+ })
+
+ // for each of these timetrackings, end them
+ // for each result, print the backtrace (if any)
+ .fold(0, |acc, mut elem| match elem.set_end_datetime(stop_time.clone()) {
+ Err(e) => { // if there was an error
+ trace_error(&e); // trace
+ 1 // set exit code to 1
+ },
+ Ok(_) => {
+ debug!("Setting end time worked: {:?}", elem);
+
+ // Keep the exit code
+ acc
+ }
+ })
+}
+
diff --git a/bin/domain/imag-timetrack/src/track.rs b/bin/domain/imag-timetrack/src/track.rs
new file mode 100644
index 0000000..a723b90
--- /dev/null
+++ b/bin/domain/imag-timetrack/src/track.rs
@@ -0,0 +1,75 @@
+//
+// 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 clap::ArgMatches;
+use chrono::naive::NaiveDateTime;
+
+use libimagrt::runtime::Runtime;
+use libimagerror::trace::trace_error;
+use libimagentrytimetrack::tag::TimeTrackingTag;
+use libimagentrytimetrack::timetrackingstore::TimeTrackStore;
+use libimagerror::trace::MapErrTrace;
+
+pub fn track(rt: &Runtime) -> i32 {
+ let (_, cmd) = rt.cli().subcommand();
+ let cmd = cmd.unwrap(); // checked in main()
+
+ // Gets the appropriate time from the commandline or None on error (errors already logged, so
+ // callee can directly return in case of error
+ fn get_time(cmd: &ArgMatches, clap_name: &str, errname: &str) -> Option<NaiveDateTime> {
+ let val = cmd
+ .value_of(clap_name)
+ .map(::chrono::naive::NaiveDateTime::from_str)
+ .unwrap(); // clap has our back
+
+ match val {
+ Ok(ndt) => Some(ndt),
+ Err(e) => {
+ trace_error(&e);
+ error!("Cannot continue, not having {} time", errname);
+ None
+ },
+ }
+ }
+
+ let start = match get_time(&cmd, "start-time", "start") {
+ Some(t) => t,
+ None => return 1,
+ };
+
+ let stop = match get_time(&cmd, "stop-time", "stop") {
+ Some(t) => t,
+ None => return 1,
+ };
+
+ cmd.values_of("tags")
+ .unwrap() // enforced by clap
+ .map(String::from)
+ .map(TimeTrackingTag::from)
+ .fold(0, |acc, ttt| {
+ rt.store()
+ .create_timetracking(&start, &stop, &ttt)
+ .map_err_trace()
+ .map(|_| acc)
+ .unwrap_or(1)
+ })
+}
+
diff --git a/bin/domain/imag-timetrack/src/ui.rs b/bin/domain/imag-timetrack/src/ui.rs
new file mode 100644
index 0000000..cefcef8
--- /dev/null
+++ b/bin/domain/imag-timetrack/src/ui.rs
@@ -0,0 +1,178 @@
+//
+// imag - the personal information management suite for the commandline
+// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; version
+// 2.1 of the License.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+//
+
+use clap::{Arg, App, SubCommand};
+
+pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
+ app
+ .subcommand(SubCommand::with_name("list")
+ .about("List time trackings")
+ .version("0.1")
+ .arg(Arg::with_name("start-time")
+ .short("f")
+ .long("from")
+ .takes_value(true)
+ .multiple(false)
+ .required(false)
+ .help("Set earliest time from which on time trackings should be shown (use 'now' for current time)"))
+ .arg(Arg::with_name("end-time")
+ .short("t")
+ .long("to")
+ .takes_value(true)
+ .multiple(false)
+ .required(false)
+ .help("Set latest time of time trackings to be shown (use 'now' for current time)"))
+
+ .arg(Arg::with_name("list-not-ended")
+ .short("l")
+ .long("list-not-ended")
+ .takes_value(false)
+ .multiple(false)
+ .required(false)
+ .help("List not yet ended timetrackings even if after 'end-time'"))
+ )
+
+ .subcommand(SubCommand::with_name("start")
+ .about("Start time tracking")
+ .version("0.1")
+ .arg(Arg::with_name("start-time")
+ .index(1)
+ .required(true)
+ .help("Start-time when to start the timetracking (use 'now' for current time)"))
+ .arg(Arg::with_name("tags")
+ .index(2)
+ .required(true)
+ .multiple(true)
+ .help("Tags to start"))
+ )
+
+ .subcommand(SubCommand::with_name("stop")
+ .about("Stop time tracking")
+ .version("0.1")
+ .arg(Arg::with_name("end-time")
+ .index(1)
+ .required(true)
+ .help("End-time when to stop the timetracking (use 'now' for current time)"))
+ .arg(Arg::with_name("tags")
+ .index(2)
+ .required(true)
+ .multiple(true)
+ .help("Tags to stop"))
+ )
+
+ .subcommand(SubCommand::with_name("track")
+ .about("Track time in given range")
+ .version("0.1")
+ .arg(Arg::with_name("start-time")
+ .index(1)
+ .required(true)
+ .help("Start-time when to start the timetracking"))
+ .arg(Arg::with_name("end-time")
+ .index(2)
+ .required(true)
+ .help("End-time when to stop the timetracking"))
+ .arg(Arg::with_name("tags")
+ .index(3)
+ .required(true)
+ .multiple(true)
+ .help("Tags to stop"))
+ )
+
+ .subcommand(SubCommand::with_name("continue")
+ .about("Continue last stopped time tracking")
+ .version("0.1")
+ )
+
+ .subcommand(SubCommand::with_name("day")
+ .about("Print stats about day")
+ .version("0.1")
+ .arg(Arg::with_name("start")
+ .index(1)
+ .required(false)
+ .help("Limit to specific date and time, start time (default: today, 00:00:00)"))
+ .arg(Arg::with_name("end")
+ .index(2)
+ .required(false)
+ .help("Limit to specific date and time, end time (default: today, 23:59:59)"))
+ .arg(Arg::with_name("tags")
+ .long("tags")
+ .short("t")
+ .required(false)
+ .multiple(true)
+ .help("Limit to certain tags"))
+ )
+
+ .subcommand(SubCommand::with_name("week")
+ .about("Print stats about week")
+ .version("0.1")
+ .arg(Arg::with_name("start")
+ .index(1)
+ .required(false)
+ .help("Limit to specific date and time, start time (default: today, 00:00:00)"))
+ .arg(Arg::with_name("end")
+ .index(2)
+ .required(false)
+ .help("Limit to specific date and time, end time (default: today, 23:59:59)"))
+ .arg(Arg::with_name("tags")
+ .long("tags")
+ .short("t")
+ .required(false)
+ .multiple(true)
+ .help("Limit to certain tags"))
+ )
+
+ .subcommand(SubCommand::with_name("month")
+ .about("Print stats about month")
+ .version("0.1")
+ .arg(Arg::with_name("start")
+ .index(1)
+ .required(false)
+ .help("Limit to specific date and time, start time (default: today, 00:00:00)"))
+ .arg(Arg::with_name("end")
+ .index(2)
+ .required(false)
+ .help("Limit to specific date and time, end time (default: today, 23:59:59)"))
+ .arg(Arg::with_name("tags")
+ .long("tags")
+ .short("t")
+ .required(false)
+ .multiple(true)
+ .help("Limit to certain tags"))
+ )
+
+ .subcommand(SubCommand::with_name("year")
+ .about("Print stats about year")
+ .version("0.1")
+ .arg(Arg::with_name("start")
+ .index(1)
+ .required(false)
+ .help("Limit to specific date and time, start time (default: today, 00:00:00)"))
+ .arg(Arg::with_name("end")
+ .index(2)
+ .required(false)
+ .help("Limit to specific date and time, end time (default: today, 23:59:59)"))
+ .arg(Arg::with_name("tags")
+ .long("tags")
+ .short("t")
+ .required(false)
+ .multiple(true)
+ .help("Limit to certain tags"))
+ )
+
+}
diff --git a/bin/domain/imag-timetrack/src/week.rs b/bin/domain/imag-timetrack/src/week.rs
new file mode 100644
index 0000000..4793c4e
--- /dev/null
+++ b/bin/domain/imag-timetrack/src/week.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 std::cmp::Ord;
+use std::cmp::Ordering;
+use std::str::FromStr;
+
+use filters::ops::not::Not;
+use filters::filter::Filter;
+use itertools::Itertools;
+use itertools::MinMaxResult;
+use chrono::NaiveDateTime;
+
+use libimagerror::trace::trace_error;
+use libimagerror::trace::MapErrTrace;
+use libimagerror::iter::TraceIterator;
+use libimagstore::store::FileLockEntry;
+use libimagentrytimetrack::timetrackingstore::TimeTrackStore;
+use libimagentrytimetrack::timetracking::TimeTracking;
+use libimagentrytimetrack::tag::TimeTrackingTag;
+use libimagentrytimetrack::iter::filter::*;
+
+use libimagrt::runtime::Runtime;
+
+pub fn week(rt: &Runtime) -> i32 {
+ let cmd = rt.cli().subcommand().1.unwrap(); // checked in main
+
+ let filter = {
+ use chrono::offset::Local;
+ use chrono::naive::NaiveDate;
+ use chrono::Weekday;
+ use chrono::Datelike;
+
+ let this_week = Local::now().iso_week();
+
+ let start = match cmd.value_of("start").map(::chrono::naive::NaiveDateTime::from_str) {
+ None => NaiveDate::from_isoywd(this_week.year(), this_week.week(), Weekday::Mon)
+ .and_hms(0, 0, 0),
+ Some(Ok(dt)) => dt,
+ Some(Err(e)) => {
+ trace_error(&e);
+ return 1
+ }
+ };
+
+ let end = match cmd.value_of("end").map(::chrono::naive::NaiveDateTime::from_str) {
+ None => NaiveDate::from_isoywd(this_week.year(), this_week.week(), Weekday::Sun)
+ .and_hms(23, 59, 59),
+ Some(Ok(dt)) => dt,
+ Some(Err(e)) => {
+ trace_error(&e);
+ return 1
+ }
+ };
+
+ let tags = cmd
+ .values_of("tags")
+ .map(|ts| ts.into_iter().map(String::from).map(TimeTrackingTag::from).collect());
+
+ let start_time_filter = has_start_time_where(move |dt: &NaiveDateTime| {
+ start <= *dt
+ });
+
+ let end_time_filter = has_end_time_where(move |dt: &NaiveDateTime| {
+ end >= *dt
+ });
+
+ let tags_filter = move |fle: &FileLockEntry| {
+ match tags {
+ Some(ref tags) => has_one_of_tags(&tags).filter(fle),
+ None => true,
+ }
+ };
+
+ tags_filter.and(start_time_filter).and(end_time_filter)
+ };
+
+ rt.store()
+ .get_timetrackings()
+ .and_then(|iter| {
+ iter.trace_unwrap()
+ .filter(|e| filter.filter(e))
+ .fold(Ok(()), |acc, e| {
+ acc.and_then(|_| {
+ debug!("Processing {:?}", e.get_location());
+
+ let tag = try!(e.get_timetrack_tag());
+ debug!(" -> tag = {:?}", tag);
+
+ let start = try!(e.get_start_datetime());
+ debug!(" -> start = {:?}", start);
+
+ let end = try!(e.get_end_datetime());
+ debug!(" -> end = {:?}", end);
+
+ match (start, end) {
+ (None, _) => println!("{} has no start time.", tag),
+ (Some(s), None) => println!("{} | {} - ...", tag, s),
+ (Some(s), Some(e)) => println!("{} | {} - {}", tag, s, e),
+ }
+
+ Ok(())
+ })
+ })
+ })
+ .map(|_| 0)
+ .map_err_trace()
+ .unwrap_or(1)
+}
+
diff --git a/bin/domain/imag-timetrack/src/year.rs b/bin/domain/imag-timetrack/src/year.rs
new file mode 100644
index 0000000..8534de1
--- /dev/null
+++ b/bin/domain/imag-timetrack/src/year.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 std::cmp::Ord;
+use std::cmp::Ordering;
+use std::str::FromStr;
+
+use filters::ops::not::Not;
+use filters::filter::Filter;
+use itertools::Itertools;
+use itertools::MinMaxResult;
+use chrono::NaiveDateTime;
+
+use libimagerror::trace::trace_error;
+use libimagerror::trace::MapErrTrace;
+use libimagerror::iter::TraceIterator;
+use libimagstore::store::FileLockEntry;
+use libimagentrytimetrack::timetrackingstore::TimeTrackStore;
+use libimagentrytimetrack::timetracking::TimeTracking;
+use libimagentrytimetrack::tag::TimeTrackingTag;
+use libimagentrytimetrack::iter::filter::*;
+
+use libimagrt::runtime::Runtime;
+
+pub fn year(rt: &Runtime) -> i32 {
+ let cmd = rt.cli().subcommand().1.unwrap(); // checked in main
+
+ let filter = {
+ use chrono::offset::Local;
+ use chrono::naive::NaiveDate;
+ use chrono::Weekday;
+ use chrono::Datelike;
+
+ let now = Local::now();
+
+ let start = match cmd.value_of("start").map(::chrono::naive::NaiveDateTime::from_str) {
+ None => NaiveDate::from_ymd(now.year(), 1, 1).and_hms(0, 0, 0),
+ Some(Ok(dt)) => dt,
+ Some(Err(e)) => {
+ trace_error(&e);
+ return 1
+ }
+ };
+
+ let end = match cmd.value_of("end").map(::chrono::naive::NaiveDateTime::from_str) {
+ None => {
+ NaiveDate::from_ymd(now.year() + 1, 1, 1).and_hms(0, 0, 0)
+ },
+ Some(Ok(dt)) => dt,
+ Some(Err(e)) => {
+ trace_error(&e);
+ return 1
+ }
+ };
+
+ let tags = cmd
+ .values_of("tags")
+ .map(|ts| ts.into_iter().map(String::from).map(TimeTrackingTag::from).collect());
+
+ let start_time_filter = has_start_time_where(move |dt: &NaiveDateTime| {
+ start <= *dt
+ });
+
+ let end_time_filter = has_end_time_where(move |dt: &NaiveDateTime| {
+ end >= *dt
+ });
+
+ let tags_filter = move |fle: &FileLockEntry| {
+ match tags {
+ Some(ref tags) => has_one_of_tags(&tags).filter(fle),
+ None => true,
+ }
+ };
+
+ tags_filter.and(start_time_filter).and(end_time_filter)
+ };
+
+ rt.store()
+ .get_timetrackings()
+ .and_then(|iter| {
+ iter.trace_unwrap()
+ .filter(|e| filter.filter(e))
+ .fold(Ok(()), |acc, e| {
+ acc.and_then(|_| {
+ debug!("Processing {:?}", e.get_location());
+
+ let tag = try!(e.get_timetrack_tag());
+ debug!(" -> tag = {:?}", tag);
+
+ let start = try!(e.get_start_datetime());
+ debug!(" -> start = {:?}", start);
+
+ let end = try!(e.get_end_datetime());
+ debug!(" -> end = {:?}", end);
+
+ match (start, end) {
+ (None, _) => println!("{} has no start time.", tag),
+ (Some(s), None) => println!("{} | {} - ...", tag, s),
+ (Some(s), Some(e)) => println!("{} | {} - {}", tag, s, e),
+ }
+
+ Ok(())
+ })
+ })
+ })
+ .map(|_| 0)
+ .map_err_trace()
+ .unwrap_or(1)
+}
+
diff --git a/bin/domain/imag-todo/Cargo.toml b/bin/domain/imag-todo/Cargo.toml
new file mode 100644
index 0000000..047cdd5
--- /dev/null
+++ b/bin/domain/imag-todo/Cargo.toml
@@ -0,0 +1,31 @@
+[package]
+authors = ["mario <mario-krehl@gmx.de>"]
+name = "imag-todo"
+version = "0.4.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.3.*"
+is-match = "0.1.*"
+version = "2.0.1"
+
+libimagrt = { version = "0.4.0", path = "../../../lib/core/libimagrt" }
+libimagstore = { version = "0.4.0", path = "../../../lib/core/libimagstore" }
+libimagerror = { version = "0.4.0", path = "../../../lib/core/libimagerror" }
+libimagtodo = { version = "0.4.0", path = "../../../lib/domain/libimagtodo" }
diff --git a/bin/domain/imag-todo/etc/on-add.sh b/bin/domain/imag-todo/etc/on-add.sh
new file mode 100644
index 0000000..a58e498
--- /dev/null
+++ b/bin/domain/imag-todo/etc/on-add.sh
@@ -0,0 +1,4 @@
+#/!usr/bin/env bash
+
+imag todo tw-hook --add
+
diff --git a/bin/domain/imag-todo/etc/on-modify.sh b/bin/domain/imag-todo/etc/on-modify.sh
new file mode 100644
index 0000000..89be96d
--- /dev/null
+++ b/bin/domain/imag-todo/etc/on-modify.sh
@@ -0,0 +1,4 @@
+#/!usr/bin/env bash
+
+imag todo tw-hook --delete
+
diff --git a/bin/domain/imag-todo/src/main.rs b/bin/domain/imag-todo/src/main.rs
new file mode 100644
index 0000000..b9bd887
--- /dev/null
+++ b/bin/domain/imag-todo/src/main.rs
@@ -0,0 +1,154 @@
+//
+// 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(Some(&Value::String(ref u))) => Some(u.clone()),
+ Ok(Some(_)) => {
+ warn!("Header type error");
+ None
+ },
+ Ok(None) => {
+ warn!("Header missing field");
+ 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/bin/domain/imag-todo/src/ui.rs b/bin/domain/imag-todo/src/ui.rs
new file mode 100644
index 0000000..795c76c
--- /dev/null
+++ b/bin/domain/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")
+ )
+ )
+}