diff --git a/.imag-documentation/Cargo.toml b/.imag-documentation/Cargo.toml index 454ce38e..307987ea 100644 --- a/.imag-documentation/Cargo.toml +++ b/.imag-documentation/Cargo.toml @@ -15,6 +15,15 @@ homepage = "http://imag-pim.org" [dependencies] +[dependencies.libimagbookmark] +path = "../libimagbookmark" + +[dependencies.libimagcounter] +path = "../libimagcounter" + +[dependencies.libimagdiary] +path = "../libimagdiary" + [dependencies.libimagentryfilter] path = "../libimagentryfilter" diff --git a/Cargo.toml b/Cargo.toml index dee38680..c5212cda 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,13 +2,21 @@ members = [ ".imag-documentation", "bin", + "imag-bookmark", + "imag-counter", + "imag-diary", "imag-link", + "imag-mail", "imag-notes", "imag-ref", "imag-store", "imag-tag", + "imag-todo", "imag-view", "libimagannotation", + "libimagbookmark", + "libimagcounter", + "libimagdiary", "libimagentryedit", "libimagentryfilter", "libimagentrylink", @@ -18,11 +26,13 @@ members = [ "libimagentryview", "libimagerror", "libimaginteraction", + "libimagmail", "libimagnotes", "libimagref", "libimagrt", "libimagstore", "libimagstorestdhook", "libimagtimeui", + "libimagtodo", "libimagutil", ] diff --git a/imag-bookmark/Cargo.toml b/imag-bookmark/Cargo.toml new file mode 100644 index 00000000..61b54528 --- /dev/null +++ b/imag-bookmark/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "imag-bookmark" +version = "0.2.0" +authors = ["Matthias Beyer "] + +description = "Part of the imag core distribution: imag-bookmark command" + +keywords = ["imag", "PIM", "personal", "information", "management"] +readme = "../README.md" +license = "LGPL-2.1" + +documentation = "https://matthiasbeyer.github.io/imag/imag_documentation/index.html" +repository = "https://github.com/matthiasbeyer/imag" +homepage = "http://imag-pim.org" + +[dependencies] +clap = ">=2.17" +log = "0.3" +version = "2.0.1" + +[dependencies.libimagrt] +path = "../libimagrt" + +[dependencies.libimagutil] +path = "../libimagutil" + +[dependencies.libimagbookmark] +path = "../libimagbookmark" + +[dependencies.libimagerror] +path = "../libimagerror" + +[dependencies.libimagentrylink] +path = "../libimagentrylink" + +[dependencies.libimagentrytag] +path = "../libimagentrytag" + diff --git a/imag-bookmark/README.md b/imag-bookmark/README.md new file mode 120000 index 00000000..43e7a524 --- /dev/null +++ b/imag-bookmark/README.md @@ -0,0 +1 @@ +../doc/src/04020-module-bookmarks.md \ No newline at end of file diff --git a/imag-bookmark/src/main.rs b/imag-bookmark/src/main.rs new file mode 100644 index 00000000..2a726b5b --- /dev/null +++ b/imag-bookmark/src/main.rs @@ -0,0 +1,158 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer 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_defresult(|url| collection.add_link(BookmarkLink::from(url))) + }) + .map_err_trace() + .map_info_str("Ready") + .ok(); +} + +fn collection(rt: &Runtime) { + let scmd = rt.cli().subcommand_matches("collection").unwrap(); + + if scmd.is_present("add") { // adding a new collection + let name = scmd.value_of("add").unwrap(); + if let Ok(_) = BookmarkCollection::new(rt.store(), name) { + info!("Created: {}", name); + } else { + warn!("Creating collection {} failed", name); + exit(1); + } + } + + if scmd.is_present("remove") { // remove a collection + let name = scmd.value_of("remove").unwrap(); + if let Ok(_) = BookmarkCollection::delete(rt.store(), name) { + info!("Deleted: {}", name); + } else { + warn!("Deleting collection {} failed", name); + exit(1); + } + } +} + +fn list(rt: &Runtime) { + let scmd = rt.cli().subcommand_matches("list").unwrap(); + let coll = scmd.value_of("collection").unwrap(); // enforced by clap + + BookmarkCollection::get(rt.store(), coll) + .map(|collection| { + match collection.links() { + Ok(links) => { + debug!("Listing..."); + for (i, link) in links.enumerate() { + match link { + Ok(link) => println!("{: >3}: {}", i, link), + Err(e) => trace_error(&e) + } + }; + debug!("... ready with listing"); + }, + Err(e) => trace_error_exit(&e, 1), + } + }) + .ok(); + info!("Ready"); +} + +fn remove(rt: &Runtime) { + let scmd = rt.cli().subcommand_matches("remove").unwrap(); + let coll = scmd.value_of("collection").unwrap(); // enforced by clap + + BookmarkCollection::get(rt.store(), coll) + .map(|mut collection| { + for url in scmd.values_of("urls").unwrap() { // enforced by clap + collection.remove_link(BookmarkLink::from(url)).map_err(|e| trace_error(&e)).ok(); + } + }) + .ok(); + info!("Ready"); +} + diff --git a/imag-bookmark/src/ui.rs b/imag-bookmark/src/ui.rs new file mode 100644 index 00000000..0b142574 --- /dev/null +++ b/imag-bookmark/src/ui.rs @@ -0,0 +1,122 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use clap::{Arg, App, SubCommand}; + +use libimagentrytag::ui::tag_add_arg; +use libimagutil::cli_validators::*; + +pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { + app + .subcommand(SubCommand::with_name("add") + .about("Add bookmarks") + .version("0.1") + .arg(Arg::with_name("collection") + .long("collection") + .short("c") + .takes_value(true) + .required(true) + .multiple(false) + .value_name("COLLECTION") + .help("Add to this collection")) + .arg(Arg::with_name("urls") + .long("urls") + .short("u") + .takes_value(true) + .required(true) + .multiple(true) + .value_name("URL") + .validator(is_url) + .help("Add this URL, multiple possible")) + .arg(tag_add_arg()) + ) + + .subcommand(SubCommand::with_name("remove") + .about("Remove bookmarks") + .version("0.1") + .arg(Arg::with_name("collection") + .long("collection") + .short("c") + .takes_value(true) + .required(true) + .multiple(false) + .value_name("COLLECTION") + .help("Remove from this collection")) + .arg(Arg::with_name("urls") + .long("urls") + .short("u") + .takes_value(true) + .required(true) + .multiple(true) + .value_name("URL") + .validator(is_url) + .help("Remove these urls, regex supported")) + ) + + // .subcommand(SubCommand::with_name("open") + // .about("Open bookmarks (via xdg-open)") + // .version("0.1") + // .arg(Arg::with_name("collection") + // .long("collection") + // .short("c") + // .takes_value(true) + // .required(true) + // .multiple(false) + // .value_name("COLLECTION") + // .help("Select from this collection")) + // ) + + .subcommand(SubCommand::with_name("list") + .about("List bookmarks") + .version("0.1") + .arg(Arg::with_name("collection") + .long("collection") + .short("c") + .takes_value(true) + .required(true) + .multiple(false) + .value_name("COLLECTION") + .help("Select from this collection")) + .arg(Arg::with_name("tags") + .long("tags") + .short("t") + .takes_value(true) + .required(false) + .multiple(true) + .value_name("TAGS") + .help("Filter links to contain these tags. When multiple tags are specified, all of them must be set for the link to match.")) + ) + + .subcommand(SubCommand::with_name("collection") + .about("Collection commands") + .version("0.1") + .arg(Arg::with_name("add") + .long("add") + .short("a") + .takes_value(true) + .value_name("NAME") + .help("Add a collection with this name")) + .arg(Arg::with_name("remove") + .long("remove") + .short("r") + .takes_value(true) + .value_name("NAME") + .help("Remove a collection with this name (and all links)")) + ) +} diff --git a/imag-counter/Cargo.toml b/imag-counter/Cargo.toml new file mode 100644 index 00000000..016eadf5 --- /dev/null +++ b/imag-counter/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "imag-counter" +version = "0.2.0" +authors = ["Matthias Beyer "] + +description = "Part of the imag core distribution: imag-counter command" + +keywords = ["imag", "PIM", "personal", "information", "management"] +readme = "../README.md" +license = "LGPL-2.1" + +documentation = "https://matthiasbeyer.github.io/imag/imag_documentation/index.html" +repository = "https://github.com/matthiasbeyer/imag" +homepage = "http://imag-pim.org" + +[dependencies] +clap = ">=2.17" +log = "0.3" +version = "2.0.1" + +[dependencies.libimagrt] +path = "../libimagrt" + +[dependencies.libimagerror] +path = "../libimagerror" + +[dependencies.libimagutil] +path = "../libimagutil" + +[dependencies.libimagcounter] +path = "../libimagcounter" + diff --git a/imag-counter/README.md b/imag-counter/README.md new file mode 120000 index 00000000..b52d8802 --- /dev/null +++ b/imag-counter/README.md @@ -0,0 +1 @@ +../doc/src/04020-module-counter.md \ No newline at end of file diff --git a/imag-counter/src/create.rs b/imag-counter/src/create.rs new file mode 100644 index 00000000..9ef66387 --- /dev/null +++ b/imag-counter/src/create.rs @@ -0,0 +1,50 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use std::str::FromStr; + +use libimagrt::runtime::Runtime; +use libimagerror::trace::trace_error_exit; +use libimagcounter::counter::Counter; +use libimagcounter::counter::CounterUnit; + +pub fn create(rt: &Runtime) { + rt.cli() + .subcommand_matches("create") + .map(|scmd| { + debug!("Found 'create' subcommand..."); + + let name = scmd.value_of("name").unwrap(); // safe because clap enforces + let init : i64 = scmd + .value_of("initval") + .and_then(|i| FromStr::from_str(i).ok()) + .unwrap_or(0); + + let unit = scmd + .value_of("unit") + .map(CounterUnit::new); + + Counter::new(rt.store(), String::from(name), init) + .and_then(|c| c.with_unit(unit)) + .unwrap_or_else(|e| { + warn!("Could not create Counter '{}' with initial value '{}'", name, init); + trace_error_exit(&e, 1); + }); + }); +} diff --git a/imag-counter/src/delete.rs b/imag-counter/src/delete.rs new file mode 100644 index 00000000..8a358284 --- /dev/null +++ b/imag-counter/src/delete.rs @@ -0,0 +1,39 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use libimagrt::runtime::Runtime; +use libimagerror::trace::trace_error_exit; +use libimagcounter::counter::Counter; + +pub fn delete(rt: &Runtime) { + rt.cli() + .subcommand_matches("delete") + .map(|scmd| { + debug!("Found 'delete' subcommand..."); + + let name = String::from(scmd.value_of("name").unwrap()); // safe because clap enforces + + if let Err(e) = Counter::delete(name, rt.store()) { + trace_error_exit(&e, 1); + } + + info!("Ok"); + }); +} + diff --git a/imag-counter/src/interactive.rs b/imag-counter/src/interactive.rs new file mode 100644 index 00000000..c4a8fa65 --- /dev/null +++ b/imag-counter/src/interactive.rs @@ -0,0 +1,174 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer 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 = RResult; + +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 = 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) -> bool { + pairs.iter() + .any(|(_, bind)| { + match *bind { + Binding::Function(ref name, _) => name == "quit", + _ => false, + } + }) +} + +enum Binding<'a> { + Counter(Counter<'a>), + Function(String, Box bool>), +} + +impl<'a> Display for Binding<'a> { + + fn fmt(&self, fmt: &mut Formatter) -> RResult<(), Error> { + match *self { + Binding::Counter(ref c) => { + match c.name() { + Ok(name) => { + try!(write!(fmt, "{}", name)); + Ok(()) + }, + Err(e) => { + trace_error(&e); + Ok(()) // TODO: Find a better way to escalate here. + }, + } + }, + Binding::Function(ref name, _) => write!(fmt, "{}()", name), + } + } + +} + +fn compute_pair<'a>(rt: &'a Runtime, spec: &str) -> Result<(char, Binding<'a>)> { + let kv = String::from(spec).into_kv(); + if kv.is_none() { + warn_exit("Key-Value parsing failed!", 1); + } + let kv = kv.unwrap(); + + let (k, v) = kv.into(); + if !k.len() == 1 { + // We have a key which is not only a single character! + exit(1); + } + + if v == "quit" { + // TODO uncaught unwrap() + Ok((k.chars().next().unwrap(), Binding::Function(String::from("quit"), Box::new(quit)))) + } else { + // TODO uncaught unwrap() + Counter::load(v, rt.store()).and_then(|ctr| Ok((k.chars().next().unwrap(), Binding::Counter(ctr)))) + } +} + +fn quit() -> bool { + false +} + diff --git a/imag-counter/src/list.rs b/imag-counter/src/list.rs new file mode 100644 index 00000000..d8d3e86e --- /dev/null +++ b/imag-counter/src/list.rs @@ -0,0 +1,54 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use libimagrt::runtime::Runtime; +use libimagerror::trace::{MapErrTrace, trace_error}; +use libimagcounter::counter::Counter; + +pub fn list(rt: &Runtime) { + rt.cli() + .subcommand_matches("list") + .map(|_| { + debug!("Found 'list' subcommand..."); + + Counter::all_counters(rt.store()).map(|iterator| { + for counter in iterator { + counter.map(|c| { + let name = c.name(); + let value = c.value(); + let unit = c.unit(); + + if name.is_err() { + trace_error(&name.unwrap_err()); + } else if value.is_err() { + trace_error(&value.unwrap_err()); + } else if unit.is_none() { + println!("{} - {}", name.unwrap(), value.unwrap()); + } else { + println!("{} - {} {}", name.unwrap(), value.unwrap(), unit.unwrap()); + } + }) + .map_err_trace() + .ok(); + } + }) + .map_err_trace() + + }); +} diff --git a/imag-counter/src/main.rs b/imag-counter/src/main.rs new file mode 100644 index 00000000..35a22441 --- /dev/null +++ b/imag-counter/src/main.rs @@ -0,0 +1,139 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +#![deny( + non_camel_case_types, + non_snake_case, + path_statements, + trivial_numeric_casts, + unstable_features, + unused_allocation, + unused_import_braces, + unused_imports, + unused_must_use, + unused_mut, + unused_qualifications, + while_true, +)] + +#[macro_use] extern crate log; +#[macro_use] extern crate version; +extern crate clap; + +extern crate libimagcounter; +extern crate libimagrt; +extern crate libimagerror; +extern crate libimagutil; + +use std::process::exit; +use std::str::FromStr; + +use libimagrt::setup::generate_runtime_setup; +use libimagcounter::counter::Counter; +use libimagerror::trace::MapErrTrace; +use libimagutil::key_value_split::IntoKeyValue; +use libimagutil::info_result::*; + +mod create; +mod delete; +mod interactive; +mod list; +mod ui; + +use ui::build_ui; +use create::create; +use delete::delete; +use interactive::interactive; +use list::list; + +enum Action { + Inc, + Dec, + Reset, + Set, +} + +fn main() { + let rt = generate_runtime_setup("imag-counter", + &version!()[..], + "Counter tool to count things", + build_ui); + + rt.cli() + .subcommand_name() + .map_or_else(|| { + let (action, name) = { + if rt.cli().is_present("increment") { + (Action::Inc, rt.cli().value_of("increment").unwrap()) + } else if rt.cli().is_present("decrement") { + (Action::Dec, rt.cli().value_of("decrement").unwrap()) + } else if rt.cli().is_present("reset") { + (Action::Reset, rt.cli().value_of("reset").unwrap()) + } else /* rt.cli().is_present("set") */ { + (Action::Set, rt.cli().value_of("set").unwrap()) + } + }; + + match action { + Action::Inc => { + Counter::load(String::from(name), rt.store()) + .map(|mut c| c.inc().map_err_trace_exit(1).map_info_str("Ok")) + }, + Action::Dec => { + Counter::load(String::from(name), rt.store()) + .map(|mut c| c.dec().map_err_trace_exit(1).map_info_str("Ok")) + }, + Action::Reset => { + Counter::load(String::from(name), rt.store()) + .map(|mut c| c.reset().map_err_trace_exit(1).map_info_str("Ok")) + }, + Action::Set => { + let kv = String::from(name).into_kv(); + if kv.is_none() { + warn!("Not a key-value pair: '{}'", name); + exit(1); + } + let (key, value) = kv.unwrap().into(); + let value = FromStr::from_str(&value[..]); + if value.is_err() { + warn!("Not a integer: '{:?}'", value); + exit(1); + } + let value : i64 = value.unwrap(); + Counter::load(String::from(key), rt.store()) + .map(|mut c| c.set(value).map_err_trace_exit(1).map_info_str("Ok")) + }, + } + .map_err_trace() + .ok(); + }, + |name| { + debug!("Call: {}", name); + match name { + "create" => create(&rt), + "delete" => delete(&rt), + "interactive" => interactive(&rt), + "list" => list(&rt), + _ => { + debug!("Unknown command"); // More error handling + }, + }; + }) +} + diff --git a/imag-counter/src/ui.rs b/imag-counter/src/ui.rs new file mode 100644 index 00000000..2b928dd8 --- /dev/null +++ b/imag-counter/src/ui.rs @@ -0,0 +1,139 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer 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 = where KEY is the + key to bind (single character) and VALUE is the path to the counter to bind + to.") + .value_name("KEY=VALUE"))) +} diff --git a/imag-diary/Cargo.toml b/imag-diary/Cargo.toml new file mode 100644 index 00000000..0ec869af --- /dev/null +++ b/imag-diary/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "imag-diary" +version = "0.2.0" +authors = ["Matthias Beyer "] + +description = "Part of the imag core distribution: imag-diary command" + +keywords = ["imag", "PIM", "personal", "information", "management"] +readme = "../README.md" +license = "LGPL-2.1" + +documentation = "https://matthiasbeyer.github.io/imag/imag_documentation/index.html" +repository = "https://github.com/matthiasbeyer/imag" +homepage = "http://imag-pim.org" + +[dependencies] +chrono = "0.2" +version = "2.0" +clap = "2.*" +log = "0.3" + +[dependencies.libimagrt] +path = "../libimagrt" + +[dependencies.libimagdiary] +path = "../libimagdiary" + +[dependencies.libimagentryedit] +path = "../libimagentryedit" + +[dependencies.libimagentrylist] +path = "../libimagentrylist" + +[dependencies.libimagerror] +path = "../libimagerror" + +[dependencies.libimaginteraction] +path = "../libimaginteraction" + +[dependencies.libimagutil] +path = "../libimagutil" + +[dependencies.libimagstore] +path = "../libimagstore" + +[dependencies.libimagtimeui] +path = "../libimagtimeui" + diff --git a/imag-diary/README.md b/imag-diary/README.md new file mode 120000 index 00000000..1aab1ab7 --- /dev/null +++ b/imag-diary/README.md @@ -0,0 +1 @@ +../doc/src/04020-module-diary.md \ No newline at end of file diff --git a/imag-diary/src/create.rs b/imag-diary/src/create.rs new file mode 100644 index 00000000..78f679a7 --- /dev/null +++ b/imag-diary/src/create.rs @@ -0,0 +1,123 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer 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> { + use std::str::FromStr; + + let create = rt.cli().subcommand_matches("create").unwrap(); + if !create.is_present("timed") { + debug!("Creating non-timed entry"); + diary.new_entry_today() + } else { + let id = match create.value_of("timed") { + Some("h") | Some("hourly") => { + debug!("Creating hourly-timed entry"); + let time = DiaryId::now(String::from(diary.name())); + let hr = create + .value_of("hour") + .map(|v| { debug!("Creating hourly entry with hour = {:?}", v); v }) + .and_then(|s| { + FromStr::from_str(s) + .map_err(|_| warn!("Could not parse hour: '{}'", s)) + .ok() + }) + .unwrap_or(time.hour()); + + time.with_hour(hr).with_minute(0) + }, + + Some("m") | Some("minutely") => { + debug!("Creating minutely-timed entry"); + let time = DiaryId::now(String::from(diary.name())); + let hr = create + .value_of("hour") + .map(|h| { debug!("hour = {:?}", h); h }) + .and_then(|s| { + FromStr::from_str(s) + .map_err(|_| warn!("Could not parse hour: '{}'", s)) + .ok() + }) + .unwrap_or(time.hour()); + + let min = create + .value_of("minute") + .map(|m| { debug!("minute = {:?}", m); m }) + .and_then(|s| { + FromStr::from_str(s) + .map_err(|_| warn!("Could not parse minute: '{}'", s)) + .ok() + }) + .unwrap_or(time.minute()); + + time.with_hour(hr).with_minute(min) + }, + + Some(_) => { + warn!("Timed creation failed: Unknown spec '{}'", + create.value_of("timed").unwrap()); + exit(1); + }, + + None => warn_exit("Unexpected error, cannot continue", 1) + }; + + diary.new_entry_by_id(id) + } + } + + let diary = Diary::open(rt.store(), &diaryname[..]); + let res = create_entry(&diary, rt) + .and_then(|mut entry| { + if prevent_edit { + debug!("Not editing new diary entry"); + Ok(()) + } else { + debug!("Editing new diary entry"); + entry.edit_content(rt).map_err_into(DEK::DiaryEditError) + } + }); + + if let Err(e) = res { + trace_error(&e); + } else { + info!("Ok!"); + } +} + diff --git a/imag-diary/src/delete.rs b/imag-diary/src/delete.rs new file mode 100644 index 00000000..7325047b --- /dev/null +++ b/imag-diary/src/delete.rs @@ -0,0 +1,73 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use chrono::naive::datetime::NaiveDateTime; + +use libimagdiary::diary::Diary; +use libimagdiary::diaryid::DiaryId; +use libimagrt::runtime::Runtime; +use libimagerror::trace::trace_error_exit; +use libimagtimeui::datetime::DateTime; +use libimagtimeui::parse::Parse; +use libimagutil::warn_exit::warn_exit; + +use util::get_diary_name; + +pub fn delete(rt: &Runtime) { + use libimaginteraction::ask::ask_bool; + + let diaryname = get_diary_name(rt) + .unwrap_or_else(|| warn_exit("No diary selected. Use either the configuration file or the commandline option", 1)); + + let diary = Diary::open(rt.store(), &diaryname[..]); + debug!("Diary opened: {:?}", diary); + + let datetime : Option = rt + .cli() + .subcommand_matches("delete") + .unwrap() + .value_of("datetime") + .map(|dt| { debug!("DateTime = {:?}", dt); dt }) + .and_then(DateTime::parse) + .map(|dt| dt.into()); + + let to_del = match datetime { + Some(dt) => Some(diary.retrieve(DiaryId::from_datetime(diaryname.clone(), dt))), + None => diary.get_youngest_entry(), + }; + + let to_del = match to_del { + Some(Ok(e)) => e, + + Some(Err(e)) => trace_error_exit(&e, 1), + None => warn_exit("No entry", 1) + }; + + if !ask_bool(&format!("Deleting {:?}", to_del.get_location())[..], Some(true)) { + info!("Aborting delete action"); + return; + } + + if let Err(e) = diary.delete_entry(to_del) { + trace_error_exit(&e, 1) + } + + info!("Ok!"); +} + diff --git a/imag-diary/src/edit.rs b/imag-diary/src/edit.rs new file mode 100644 index 00000000..beed8c05 --- /dev/null +++ b/imag-diary/src/edit.rs @@ -0,0 +1,62 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use chrono::naive::datetime::NaiveDateTime; + +use libimagdiary::diary::Diary; +use libimagdiary::diaryid::DiaryId; +use libimagdiary::error::DiaryErrorKind as DEK; +use libimagdiary::error::MapErrInto; +use libimagentryedit::edit::Edit; +use libimagrt::runtime::Runtime; +use libimagerror::trace::MapErrTrace; +use libimagerror::into::IntoError; +use libimagtimeui::datetime::DateTime; +use libimagtimeui::parse::Parse; +use libimagutil::warn_exit::warn_exit; + +use util::get_diary_name; + +pub fn edit(rt: &Runtime) { + let diaryname = get_diary_name(rt).unwrap_or_else(|| warn_exit("No diary name", 1)); + let diary = Diary::open(rt.store(), &diaryname[..]); + + let datetime : Option = rt + .cli() + .subcommand_matches("edit") + .unwrap() + .value_of("datetime") + .and_then(DateTime::parse) + .map(|dt| dt.into()); + + let to_edit = match datetime { + Some(dt) => Some(diary.retrieve(DiaryId::from_datetime(diaryname.clone(), dt))), + None => diary.get_youngest_entry(), + }; + + match to_edit { + Some(Ok(mut e)) => e.edit_content(rt).map_err_into(DEK::IOError), + + Some(Err(e)) => Err(e), + None => Err(DEK::EntryNotInDiary.into_error()), + } + .map_err_trace().ok(); +} + + diff --git a/imag-diary/src/list.rs b/imag-diary/src/list.rs new file mode 100644 index 00000000..4ed3123c --- /dev/null +++ b/imag-diary/src/list.rs @@ -0,0 +1,68 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer 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("<>")) + } + + let diary = Diary::open(rt.store(), &diaryname[..]); + debug!("Diary opened: {:?}", diary); + diary.entries() + .and_then(|es| { + debug!("Iterator for listing: {:?}", es); + + let es = es + .filter_map(|entry| { + entry + .map_dbg(|e| format!("Filtering: {:?}", e)) + .map_err_trace() // error tracing here + .ok() // so we can ignore errors here + }) + .map(|e| e.into()); + + CoreLister::new(&entry_to_location_listing_string) + .list(es) + .map_err_into(DEK::IOError) + }) + .map_dbg_str("Ok") + .map_err_trace() + .ok(); +} + diff --git a/imag-diary/src/main.rs b/imag-diary/src/main.rs new file mode 100644 index 00000000..71e00cd6 --- /dev/null +++ b/imag-diary/src/main.rs @@ -0,0 +1,101 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +#![deny( + non_camel_case_types, + non_snake_case, + path_statements, + trivial_numeric_casts, + unstable_features, + unused_allocation, + unused_import_braces, + unused_imports, + unused_must_use, + unused_mut, + unused_qualifications, + while_true, +)] + +#[macro_use] extern crate log; +#[macro_use] extern crate version; +extern crate clap; +extern crate chrono; + +extern crate libimagdiary; +extern crate libimagentryedit; +extern crate libimagentrylist; +extern crate libimaginteraction; +extern crate libimagrt; +extern crate libimagstore; +extern crate libimagutil; +extern crate libimagtimeui; +#[macro_use] extern crate libimagerror; + +use std::process::exit; + +use libimagrt::runtime::Runtime; + +mod create; +mod delete; +mod edit; +mod list; +mod ui; +mod util; +mod view; + +use create::create; +use delete::delete; +use edit::edit; +use list::list; +use ui::build_ui; +use view::view; + +fn main() { + let name = "imag-diary"; + let version = &version!()[..]; + let about = "Personal Diary/Diaries"; + let ui = build_ui(Runtime::get_default_cli_builder(name, version, about)); + let rt = { + let rt = Runtime::new(ui); + if rt.is_ok() { + rt.unwrap() + } else { + println!("Could not set up Runtime"); + println!("{:?}", rt.err().unwrap()); + exit(1); + } + }; + + rt.cli() + .subcommand_name() + .map(|name| { + debug!("Call {}", name); + match name { + "create" => create(&rt), + "delete" => delete(&rt), + "edit" => edit(&rt), + "list" => list(&rt), + "view" => view(&rt), + _ => { + debug!("Unknown command"); // More error handling + }, + } + }); +} + diff --git a/imag-diary/src/ui.rs b/imag-diary/src/ui.rs new file mode 100644 index 00000000..91712fb6 --- /dev/null +++ b/imag-diary/src/ui.rs @@ -0,0 +1,126 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use clap::{Arg, ArgGroup, App, SubCommand}; + +pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { + app + .arg(Arg::with_name("diaryname") + .long("diary") + .short("d") + .takes_value(true) + .required(false) + .help("Use other than default diary")) + + .subcommand(SubCommand::with_name("create") + .about("Create a diary entry") + .version("0.1") + .arg(Arg::with_name("no-edit") + .long("no-edit") + .short("e") + .takes_value(false) + .required(false) + .help("Do not edit after creating")) + + .arg(Arg::with_name("timed") + .long("timed") + .short("t") + .takes_value(true) + .required(false) + .help("By default, one entry is created per day. With --timed=h[ourly] or + --timed=m[inutely] one can create per-hour and per-minute entries (more like + a microblog then")) + + .arg(Arg::with_name("hour") + .long("hour") + .takes_value(true) + .required(false) + .help("When using --timed, override the hour component")) + .arg(Arg::with_name("minute") + .long("minute") + .takes_value(true) + .required(false) + .help("When using --timed, override the minute component")) + + // When using --hour or --minute, --timed must be present + .group(ArgGroup::with_name("timing-hourly") + .args(&["hour"]) + .requires("timed")) + .group(ArgGroup::with_name("timing-minutely") + .args(&["minute"]) + .requires("timed")) + ) + + .subcommand(SubCommand::with_name("edit") + .about("Edit a diary entry") + .version("0.1") + .arg(Arg::with_name("datetime") + .long("datetime") + .short("d") + .takes_value(true) + .required(false) + .help("Specify the date and time which entry should be edited. If none is + specified, the last entry is edited. If the diary entry does not exist for + this time, this fails. Format: YYYY-MM-DDT[HH[:mm[:ss]]]")) + ) + + .subcommand(SubCommand::with_name("list") + .about("List diary entries") + .version("0.1")) + + .subcommand(SubCommand::with_name("delete") + .about("Delete a diary entry") + .version("0.1") + .arg(Arg::with_name("datetime") + .long("datetime") + .short("d") + .takes_value(true) + .required(false) + .help("Specify the date and time which entry should be deleted. If none is + specified, the last entry is deleted. If the diary entry does not exist for + this time, this fails. Format: YYYY-MM-DDT[HH[:mm[:ss]]]")) + + .arg(Arg::with_name("select") + .long("select") + .short("s") + .takes_value(false) + .required(false) + .help("Use interactive selection")) + + .arg(Arg::with_name("yes") + .long("yes") + .short("y") + .takes_value(false) + .required(false) + .help("Do not ask for confirmation.")) + ) + + .subcommand(SubCommand::with_name("view") + .about("View entries, currently only supports plain viewing") + .version("0.1") + + .arg(Arg::with_name("show-header") + .long("header") + .takes_value(false) + .required(false) + .help("Show the header when printing the entries")) + ) + +} + diff --git a/imag-diary/src/util.rs b/imag-diary/src/util.rs new file mode 100644 index 00000000..25498085 --- /dev/null +++ b/imag-diary/src/util.rs @@ -0,0 +1,28 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer 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 { + use libimagdiary::config::get_default_diary_name; + + get_default_diary_name(rt) + .or(rt.cli().value_of("diaryname").map(String::from)) +} + diff --git a/imag-diary/src/view.rs b/imag-diary/src/view.rs new file mode 100644 index 00000000..041a1fe0 --- /dev/null +++ b/imag-diary/src/view.rs @@ -0,0 +1,38 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use libimagdiary::diary::Diary; +use libimagdiary::viewer::DiaryViewer as DV; +use libimagrt::runtime::Runtime; +use libimagerror::trace::MapErrTrace; +use libimagutil::warn_exit::warn_exit; + +use util::get_diary_name; + +pub fn view(rt: &Runtime) { + let diaryname = get_diary_name(rt).unwrap_or_else(|| warn_exit("No diary name", 1)); + let diary = Diary::open(rt.store(), &diaryname[..]); + let hdr = rt.cli().subcommand_matches("view").unwrap().is_present("show-header"); + + diary.entries() + .and_then(|entries| DV::new(hdr).view_entries(entries.into_iter().filter_map(Result::ok))) + .map_err_trace() + .ok(); +} + diff --git a/imag-mail/Cargo.toml b/imag-mail/Cargo.toml new file mode 100644 index 00000000..86f2a1be --- /dev/null +++ b/imag-mail/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "imag-mail" +version = "0.2.0" +authors = ["Matthias Beyer "] + +[dependencies] +semver = "0.5" +clap = "2.*" +log = "0.3" +version = "2.0.1" +toml = "0.2.*" +url = "1.2" + +[dependencies.libimagrt] +path = "../libimagrt" + +[dependencies.libimagmail] +path = "../libimagmail" + +[dependencies.libimagerror] +path = "../libimagerror" + +[dependencies.libimagutil] +path = "../libimagutil" + +[dependencies.libimagref] +path = "../libimagref" + diff --git a/imag-mail/README.md b/imag-mail/README.md new file mode 120000 index 00000000..d5d8fb01 --- /dev/null +++ b/imag-mail/README.md @@ -0,0 +1 @@ +../doc/src/04020-module-mails.md \ No newline at end of file diff --git a/imag-mail/src/main.rs b/imag-mail/src/main.rs new file mode 100644 index 00000000..bdd51bdb --- /dev/null +++ b/imag-mail/src/main.rs @@ -0,0 +1,151 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer 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) => "".to_owned(), + Err(e) => { + trace_error(&e); + "".to_owned() + }, + }; + + let from = match m.get_from() { + Ok(Some(f)) => f, + Ok(None) => "".to_owned(), + Err(e) => { + trace_error(&e); + "".to_owned() + }, + }; + + let to = match m.get_to() { + Ok(Some(f)) => f, + Ok(None) => "".to_owned(), + Err(e) => { + trace_error(&e); + "".to_owned() + }, + }; + + let subject = match m.get_subject() { + Ok(Some(f)) => f, + Ok(None) => "".to_owned(), + Err(e) => { + trace_error(&e); + "".to_owned() + }, + }; + + println!("Mail: {id}\n\tFrom: {from}\n\tTo: {to}\n\t{subj}\n", + from = from, + id = id, + subj = subject, + to = to + ); + } + + // TODO: Implement lister type in libimagmail for this + for mail in iter { + list_mail(mail) + } +} + +fn mail_store(rt: &Runtime) { + let scmd = rt.cli().subcommand_matches("mail-store").unwrap(); + error!("This feature is currently not implemented."); + unimplemented!() +} + diff --git a/imag-mail/src/ui.rs b/imag-mail/src/ui.rs new file mode 100644 index 00000000..3e999949 --- /dev/null +++ b/imag-mail/src/ui.rs @@ -0,0 +1,74 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use clap::{Arg, ArgGroup, App, SubCommand}; + +pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { + app + .subcommand(SubCommand::with_name("import-mail") + .about("Import a mail (create a reference to it) (Maildir)") + .version("0.1") + .arg(Arg::with_name("path") + .long("path") + .short("p") + .takes_value(true) + .required(true) + .help("Path to the mail file or a directory which is then searched recursively") + .value_name("PATH")) + ) + + .subcommand(SubCommand::with_name("list") + .about("List all stored references to mails") + .version("0.1") + + // TODO: Thee following four arguments are the same as in imag-ref. + // We should make these importable from libimagref. + + .arg(Arg::with_name("check-dead") + .long("check-dead") + .short("d") + .help("Check each reference whether it is dead")) + + .arg(Arg::with_name("check-changed") + .long("check-changed") + .short("c") + .help("Check whether a reference had changed (content or permissions)")) + + .arg(Arg::with_name("check-changed-content") + .long("check-changed-content") + .short("C") + .help("Check whether the content of the referenced file changed")) + + .arg(Arg::with_name("check-changed-permissions") + .long("check-changed-perms") + .short("P") + .help("Check whether the permissions of the referenced file changed")) + + ) + + .subcommand(SubCommand::with_name("mail-store") + .about("Operations on (subsets of) all mails") + .version("0.1") + .subcommand(SubCommand::with_name("update-refs") + .about("Create references based on Message-IDs for all loaded mails") + .version("0.1")) + // TODO: We really should be able to filter here. + ) +} + diff --git a/imag-todo/Cargo.toml b/imag-todo/Cargo.toml new file mode 100644 index 00000000..7a3cbbe9 --- /dev/null +++ b/imag-todo/Cargo.toml @@ -0,0 +1,36 @@ +[package] +authors = ["mario "] +name = "imag-todo" +version = "0.2.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.2.*" +version = "2.0.1" + +[dependencies.libimagrt] +path = "../libimagrt" + +[dependencies.libimagstore] +path = "../libimagstore" + +[dependencies.libimagtodo] +path = "../libimagtodo" + +[dependencies.libimagerror] +path = "../libimagerror" diff --git a/imag-todo/etc/on-add.sh b/imag-todo/etc/on-add.sh new file mode 100644 index 00000000..a58e4989 --- /dev/null +++ b/imag-todo/etc/on-add.sh @@ -0,0 +1,4 @@ +#/!usr/bin/env bash + +imag todo tw-hook --add + diff --git a/imag-todo/etc/on-modify.sh b/imag-todo/etc/on-modify.sh new file mode 100644 index 00000000..89be96d0 --- /dev/null +++ b/imag-todo/etc/on-modify.sh @@ -0,0 +1,4 @@ +#/!usr/bin/env bash + +imag todo tw-hook --delete + diff --git a/imag-todo/src/main.rs b/imag-todo/src/main.rs new file mode 100644 index 00000000..bcdae191 --- /dev/null +++ b/imag-todo/src/main.rs @@ -0,0 +1,138 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer 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; +#[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) { + let subcmd = rt.cli().subcommand_matches("list").unwrap(); + let verbose = subcmd.is_present("verbose"); + + 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("todo.uuid") { + Ok(Some(Value::String(ref u))) => Some(u.clone()), + Ok(Some(_)) => { + warn!("Header type error"); + None + }, + Ok(None) => None, + Err(e) => { + trace_error(&e); + None + } + }, + Err(e) => { + trace_error(&e); + None + } + }) + .collect(); + + // compose a `task` call with them, ... + let outstring = if verbose { // ... if verbose + let output = Command::new("task") + .stdin(Stdio::null()) + .args(&uuids) + .spawn() + .unwrap_or_else(|e| { + trace_error(&e); + panic!("Failed to execute `task` on the commandline. I'm dying now."); + }) + .wait_with_output() + .unwrap_or_else(|e| panic!("failed to unwrap output: {}", e)); + + String::from_utf8(output.stdout) + .unwrap_or_else(|e| panic!("failed to execute: {}", e)) + } else { // ... else just join them + uuids.join("\n") + }; + + // and then print that + println!("{}", outstring); + }); + + res.map_err_trace().ok(); +} + diff --git a/imag-todo/src/ui.rs b/imag-todo/src/ui.rs new file mode 100644 index 00000000..795c76c6 --- /dev/null +++ b/imag-todo/src/ui.rs @@ -0,0 +1,61 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use clap::{Arg, App, ArgGroup, SubCommand}; + +pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { + app + .subcommand(SubCommand::with_name("tw-hook") + .about("For use in a taskwarrior hook") + .version("0.1") + + .arg(Arg::with_name("add") + .long("add") + .short("a") + .takes_value(false) + .required(false) + .help("For use in an on-add hook")) + + .arg(Arg::with_name("delete") + .long("delete") + .short("d") + .takes_value(false) + .required(false) + .help("For use in an on-delete hook")) + + .group(ArgGroup::with_name("taskwarrior hooks") + .args(&[ "add", + "delete", + ]) + .required(true)) + ) + + .subcommand(SubCommand::with_name("list") + .about("List all tasks") + .version("0.1") + + .arg(Arg::with_name("verbose") + .long("verbose") + .short("v") + .takes_value(false) + .required(false) + .help("Asks taskwarrior for all the details") + ) + ) +} diff --git a/libimagbookmark/Cargo.toml b/libimagbookmark/Cargo.toml new file mode 100644 index 00000000..0c50403c --- /dev/null +++ b/libimagbookmark/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "libimagbookmark" +version = "0.2.0" +authors = ["Matthias Beyer "] + +description = "Library for the imag core distribution" + +keywords = ["imag", "PIM", "personal", "information", "management"] +readme = "../README.md" +license = "LGPL-2.1" + +documentation = "https://matthiasbeyer.github.io/imag/imag_documentation/index.html" +repository = "https://github.com/matthiasbeyer/imag" +homepage = "http://imag-pim.org" + +[dependencies] +log = "0.3" +semver = "0.5" +url = "1.2" +regex = "0.1" + +[dependencies.libimagstore] +path = "../libimagstore" + +[dependencies.libimagerror] +path = "../libimagerror" + +[dependencies.libimagentrylink] +path = "../libimagentrylink" + diff --git a/libimagbookmark/README.md b/libimagbookmark/README.md new file mode 120000 index 00000000..f7c5cd05 --- /dev/null +++ b/libimagbookmark/README.md @@ -0,0 +1 @@ +../doc/src/05100-lib-bookmark.md \ No newline at end of file diff --git a/libimagbookmark/src/collection.rs b/libimagbookmark/src/collection.rs new file mode 100644 index 00000000..206e31cd --- /dev/null +++ b/libimagbookmark/src/collection.rs @@ -0,0 +1,222 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +//! BookmarkCollection module +//! +//! A BookmarkCollection is nothing more than a simple store entry. One can simply call functions +//! from the libimagentrylink::external::ExternalLinker trait on this to generate external links. +//! +//! The BookmarkCollection type offers helper functions to get all links or such things. +use std::ops::Deref; +use std::ops::DerefMut; + +use regex::Regex; + +use error::BookmarkErrorKind as BEK; +use error::MapErrInto; +use result::Result; +use module_path::ModuleEntryPath; + +use libimagstore::store::Store; +use libimagstore::storeid::IntoStoreId; +use libimagstore::store::FileLockEntry; +use libimagentrylink::external::ExternalLinker; +use libimagentrylink::external::iter::UrlIter; +use libimagentrylink::internal::InternalLinker; +use libimagentrylink::internal::Link as StoreLink; +use libimagerror::into::IntoError; + +use link::Link; + +use self::iter::LinksMatchingRegexIter; + +pub struct BookmarkCollection<'a> { + fle: FileLockEntry<'a>, + store: &'a Store, +} + +/// {Internal, External}Linker is implemented as Deref is implemented +impl<'a> Deref for BookmarkCollection<'a> { + type Target = FileLockEntry<'a>; + + fn deref(&self) -> &FileLockEntry<'a> { + &self.fle + } + +} + +impl<'a> DerefMut for BookmarkCollection<'a> { + + fn deref_mut(&mut self) -> &mut FileLockEntry<'a> { + &mut self.fle + } + +} + +impl<'a> BookmarkCollection<'a> { + + pub fn new(store: &'a Store, name: &str) -> Result> { + ModuleEntryPath::new(name) + .into_storeid() + .and_then(|id| store.create(id)) + .map(|fle| { + BookmarkCollection { + fle: fle, + store: store, + } + }) + .map_err_into(BEK::StoreReadError) + } + + pub fn get(store: &'a Store, name: &str) -> Result> { + ModuleEntryPath::new(name) + .into_storeid() + .and_then(|id| store.get(id)) + .map_err_into(BEK::StoreReadError) + .and_then(|fle| { + match fle { + None => Err(BEK::CollectionNotFound.into_error()), + Some(e) => Ok(BookmarkCollection { + fle: e, + store: store, + }), + } + }) + } + + pub fn delete(store: &Store, name: &str) -> Result<()> { + ModuleEntryPath::new(name) + .into_storeid() + .and_then(|id| store.delete(id)) + .map_err_into(BEK::StoreReadError) + } + + pub fn links(&self) -> Result { + self.fle.get_external_links(&self.store).map_err_into(BEK::LinkError) + } + + pub fn link_entries(&self) -> Result> { + use libimagentrylink::external::is_external_link_storeid; + + self.fle + .get_internal_links() + .map(|v| v.filter(|id| is_external_link_storeid(id)).collect()) + .map_err_into(BEK::StoreReadError) + } + + pub fn add_link(&mut self, l: Link) -> Result<()> { + use link::IntoUrl; + + l.into_url() + .and_then(|url| self.add_external_link(self.store, url).map_err_into(BEK::LinkingError)) + .map_err_into(BEK::LinkError) + } + + pub fn get_links_matching(&self, r: Regex) -> Result> { + use self::iter::IntoLinksMatchingRegexIter; + + self.get_external_links(self.store) + .map_err_into(BEK::LinkError) + .map(|iter| iter.matching_regex(r)) + } + + pub fn remove_link(&mut self, l: Link) -> Result<()> { + use link::IntoUrl; + + l.into_url() + .and_then(|url| { + self.remove_external_link(self.store, url).map_err_into(BEK::LinkingError) + }) + .map_err_into(BEK::LinkError) + } + +} + +pub mod iter { + use link::Link; + use result::Result; + use error::{MapErrInto, BookmarkErrorKind as BEK}; + + pub struct LinkIter(I) + where I: Iterator; + + impl> LinkIter { + pub fn new(i: I) -> LinkIter { + LinkIter(i) + } + } + + impl> Iterator for LinkIter { + type Item = Link; + + fn next(&mut self) -> Option { + self.0.next() + } + } + + impl From for LinkIter where I: Iterator { + fn from(i: I) -> LinkIter { + LinkIter(i) + } + } + + use libimagentrylink::external::iter::UrlIter; + use regex::Regex; + + pub struct LinksMatchingRegexIter<'a>(UrlIter<'a>, Regex); + + impl<'a> LinksMatchingRegexIter<'a> { + pub fn new(i: UrlIter<'a>, r: Regex) -> LinksMatchingRegexIter<'a> { + LinksMatchingRegexIter(i, r) + } + } + + impl<'a> Iterator for LinksMatchingRegexIter<'a> { + type Item = Result; + + fn next(&mut self) -> Option { + loop { + let n = match self.0.next() { + Some(Ok(n)) => n, + Some(Err(e)) => return Some(Err(e).map_err_into(BEK::LinkError)), + None => return None, + }; + + let s = n.into_string(); + if self.1.is_match(&s[..]) { + return Some(Ok(Link::from(s))) + } else { + continue; + } + } + } + } + + pub trait IntoLinksMatchingRegexIter<'a> { + fn matching_regex(self, Regex) -> LinksMatchingRegexIter<'a>; + } + + impl<'a> IntoLinksMatchingRegexIter<'a> for UrlIter<'a> { + fn matching_regex(self, r: Regex) -> LinksMatchingRegexIter<'a> { + LinksMatchingRegexIter(self, r) + } + } + +} + diff --git a/libimagbookmark/src/error.rs b/libimagbookmark/src/error.rs new file mode 100644 index 00000000..9b52a169 --- /dev/null +++ b/libimagbookmark/src/error.rs @@ -0,0 +1,33 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +generate_error_module!( + generate_error_types!(BookmarkError, BookmarkErrorKind, + StoreReadError => "Store read error", + LinkError => "Link error", + LinkParsingError => "Link parsing error", + LinkingError => "Error while linking", + CollectionNotFound => "Link-Collection not found" + ); +); + +pub use self::error::BookmarkError; +pub use self::error::BookmarkErrorKind; +pub use self::error::MapErrInto; + diff --git a/libimagbookmark/src/lib.rs b/libimagbookmark/src/lib.rs new file mode 100644 index 00000000..6b7de0dc --- /dev/null +++ b/libimagbookmark/src/lib.rs @@ -0,0 +1,48 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +#![deny( + non_camel_case_types, + non_snake_case, + path_statements, + trivial_numeric_casts, + unstable_features, + unused_allocation, + unused_import_braces, + unused_imports, + unused_mut, + unused_qualifications, + while_true, +)] + +#[macro_use] extern crate log; +extern crate semver; +extern crate url; +extern crate regex; + +#[macro_use] extern crate libimagstore; +#[macro_use] extern crate libimagerror; +extern crate libimagentrylink; + +module_entry_path_mod!("bookmark"); + +pub mod collection; +pub mod error; +pub mod link; +pub mod result; diff --git a/libimagbookmark/src/link.rs b/libimagbookmark/src/link.rs new file mode 100644 index 00000000..1da91c8a --- /dev/null +++ b/libimagbookmark/src/link.rs @@ -0,0 +1,76 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use std::ops::{Deref, DerefMut}; + +use result::Result; + +use url::Url; + +#[derive(Debug, Clone)] +pub struct Link(String); + +impl From for Link { + + fn from(s: String) -> Link { + Link(s) + } + +} + +impl<'a> From<&'a str> for Link { + + fn from(s: &'a str) -> Link { + Link(String::from(s)) + } + +} + +impl Deref for Link { + type Target = String; + + fn deref(&self) -> &String { + &self.0 + } + +} + +impl DerefMut for Link { + + fn deref_mut(&mut self) -> &mut String { + &mut self.0 + } + +} + +pub trait IntoUrl { + fn into_url(self) -> Result; +} + +impl IntoUrl for Link { + + fn into_url(self) -> Result { + use error::BookmarkErrorKind as BEK; + use error::MapErrInto; + + Url::parse(&self[..]).map_err_into(BEK::LinkParsingError) + } + +} + diff --git a/libimagbookmark/src/result.rs b/libimagbookmark/src/result.rs new file mode 100644 index 00000000..780a8035 --- /dev/null +++ b/libimagbookmark/src/result.rs @@ -0,0 +1,25 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use std::result::Result as RResult; + +use error::BookmarkError; + +pub type Result = RResult; + diff --git a/libimagcounter/Cargo.toml b/libimagcounter/Cargo.toml new file mode 100644 index 00000000..0551b42e --- /dev/null +++ b/libimagcounter/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "libimagcounter" +version = "0.2.0" +authors = ["Matthias Beyer "] + +description = "Library for the imag core distribution" + +keywords = ["imag", "PIM", "personal", "information", "management"] +readme = "../README.md" +license = "LGPL-2.1" + +documentation = "https://matthiasbeyer.github.io/imag/imag_documentation/index.html" +repository = "https://github.com/matthiasbeyer/imag" +homepage = "http://imag-pim.org" + +[dependencies] +log = "0.3" +toml = "0.2.*" +semver = "0.5" + +[dependencies.libimagstore] +path = "../libimagstore" + +[dependencies.libimagerror] +path = "../libimagerror" + diff --git a/libimagcounter/README.md b/libimagcounter/README.md new file mode 120000 index 00000000..90221b8a --- /dev/null +++ b/libimagcounter/README.md @@ -0,0 +1 @@ +../doc/src/05100-lib-counter.md \ No newline at end of file diff --git a/libimagcounter/src/counter.rs b/libimagcounter/src/counter.rs new file mode 100644 index 00000000..12bc0a93 --- /dev/null +++ b/libimagcounter/src/counter.rs @@ -0,0 +1,257 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use std::ops::DerefMut; + +use toml::Value; + +use std::collections::BTreeMap; +use std::fmt; +use std::fmt::Display; + +use libimagstore::store::Store; +use libimagstore::storeid::StoreIdIterator; +use libimagstore::store::FileLockEntry; +use libimagstore::storeid::StoreId; +use libimagstore::storeid::IntoStoreId; +use libimagerror::into::IntoError; + +use module_path::ModuleEntryPath; +use result::Result; +use error::CounterError as CE; +use error::CounterErrorKind as CEK; +use error::error::MapErrInto; + +pub type CounterName = String; + +#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] +pub struct CounterUnit(String); + +impl Display for CounterUnit { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "({})", self.0) + } +} + +impl CounterUnit { + pub fn new>(unit: S) -> CounterUnit { + CounterUnit(unit.into()) + } +} + +pub struct Counter<'a> { + fle: FileLockEntry<'a>, + unit: Option, +} + +impl<'a> Counter<'a> { + + pub fn new(store: &Store, name: CounterName, init: i64) -> Result { + use std::ops::DerefMut; + + debug!("Creating new counter: '{}' with value: {}", name, init); + let fle = { + let id = try!(ModuleEntryPath::new(name.clone()) + .into_storeid() + .map_err_into(CEK::StoreWriteError)); + let mut lockentry = try!(store.create(id).map_err_into(CEK::StoreWriteError)); + + { + let mut entry = lockentry.deref_mut(); + let mut header = entry.get_header_mut(); + let setres = header.set("counter", Value::Table(BTreeMap::new())); + if setres.is_err() { + return Err(CEK::StoreWriteError.into_error()); + } + + let setres = header.set("counter.name", Value::String(name)); + if setres.is_err() { + return Err(CEK::StoreWriteError.into_error()) + } + + let setres = header.set("counter.value", Value::Integer(init)); + if setres.is_err() { + return Err(CEK::StoreWriteError.into_error()) + } + } + + lockentry + }; + + Ok(Counter { fle: fle, unit: None }) + } + + pub fn with_unit(mut self, unit: Option) -> Result> { + self.unit = unit; + + if let Some(u) = self.unit.clone() { + let mut header = self.fle.deref_mut().get_header_mut(); + let setres = header.set("counter.unit", Value::String(u.0)); + if setres.is_err() { + self.unit = None; + return Err(CEK::StoreWriteError.into_error()) + } + }; + Ok(self) + } + + pub fn inc(&mut self) -> Result<()> { + let mut header = self.fle.deref_mut().get_header_mut(); + match header.read("counter.value") { + Ok(Some(Value::Integer(i))) => { + header.set("counter.value", Value::Integer(i + 1)) + .map_err_into(CEK::StoreWriteError) + .map(|_| ()) + }, + Err(e) => Err(CE::new(CEK::StoreReadError, Some(Box::new(e)))), + _ => Err(CE::new(CEK::StoreReadError, None)), + } + } + + pub fn dec(&mut self) -> Result<()> { + let mut header = self.fle.deref_mut().get_header_mut(); + match header.read("counter.value") { + Ok(Some(Value::Integer(i))) => { + header.set("counter.value", Value::Integer(i - 1)) + .map_err_into(CEK::StoreWriteError) + .map(|_| ()) + }, + Err(e) => Err(CE::new(CEK::StoreReadError, Some(Box::new(e)))), + _ => Err(CE::new(CEK::StoreReadError, None)), + } + } + + pub fn reset(&mut self) -> Result<()> { + self.set(0) + } + + pub fn set(&mut self, v: i64) -> Result<()> { + let mut header = self.fle.deref_mut().get_header_mut(); + header.set("counter.value", Value::Integer(v)) + .map_err_into(CEK::StoreWriteError) + .map(|_| ()) + } + + pub fn name(&self) -> Result { + self.read_header_at("counter.name", |v| match v { + Some(Value::String(s)) => Ok(s), + _ => Err(CEK::HeaderTypeError.into_error()), + }) + } + + pub fn value(&self) -> Result { + self.read_header_at("counter.value", |v| match v { + Some(Value::Integer(i)) => Ok(i), + _ => Err(CEK::HeaderTypeError.into_error()), + }) + } + + pub fn unit(&self) -> Option<&CounterUnit> { + self.unit.as_ref() + } + + pub fn read_unit(&self) -> Result> { + self.read_header_at("counter.unit", |s| match s { + Some(Value::String(s)) => Ok(Some(CounterUnit::new(s))), + Some(_) => Err(CEK::HeaderTypeError.into_error()), + None => Ok(None), + }) + } + + fn read_header_at(&self, name: &str, f: F) -> Result + where F: FnOnce(Option) -> Result + { + self.fle.get_header().read(name).map_err_into(CEK::StoreWriteError).and_then(f) + } + + pub fn load(name: CounterName, store: &Store) -> Result { + debug!("Loading counter: '{}'", name); + let id = try!(ModuleEntryPath::new(name) + .into_storeid() + .map_err_into(CEK::StoreWriteError)); + Counter::from_storeid(store, id) + } + + pub fn delete(name: CounterName, store: &Store) -> Result<()> { + debug!("Deleting counter: '{}'", name); + let id = try!(ModuleEntryPath::new(name) + .into_storeid() + .map_err_into(CEK::StoreWriteError)); + store.delete(id).map_err_into(CEK::StoreWriteError) + } + + pub fn all_counters(store: &Store) -> Result { + store.retrieve_for_module("counter") + .map(|iter| CounterIterator::new(store, iter)) + .map_err_into(CEK::StoreReadError) + } + +} + +trait FromStoreId { + fn from_storeid(&Store, StoreId) -> Result; +} + +impl<'a> FromStoreId for Counter<'a> { + + fn from_storeid(store: &Store, id: StoreId) -> Result { + debug!("Loading counter from storeid: '{:?}'", id); + match store.retrieve(id) { + Err(e) => Err(CE::new(CEK::StoreReadError, Some(Box::new(e)))), + Ok(c) => { + let mut counter = Counter { fle: c, unit: None }; + counter.read_unit() + .map_err_into(CEK::StoreReadError) + .and_then(|u| { + counter.unit = u; + Ok(counter) + }) + } + } + } + +} + +pub struct CounterIterator<'a> { + store: &'a Store, + iditer: StoreIdIterator, +} + +impl<'a> CounterIterator<'a> { + + pub fn new(store: &'a Store, iditer: StoreIdIterator) -> CounterIterator<'a> { + CounterIterator { + store: store, + iditer: iditer, + } + } + +} + +impl<'a> Iterator for CounterIterator<'a> { + type Item = Result>; + + fn next(&mut self) -> Option>> { + self.iditer + .next() + .map(|id| Counter::from_storeid(self.store, id)) + } + +} + diff --git a/libimagcounter/src/error.rs b/libimagcounter/src/error.rs new file mode 100644 index 00000000..c886d901 --- /dev/null +++ b/libimagcounter/src/error.rs @@ -0,0 +1,32 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +generate_error_module!( + generate_error_types!(CounterError, CounterErrorKind, + StoreIdError => "StoreId error", + StoreReadError => "Store read error", + StoreWriteError => "Store write error", + HeaderTypeError => "Header type error", + HeaderFieldMissingError => "Header field missing error" + ); +); + +pub use self::error::CounterError; +pub use self::error::CounterErrorKind; + diff --git a/libimagcounter/src/lib.rs b/libimagcounter/src/lib.rs new file mode 100644 index 00000000..3ad74aa9 --- /dev/null +++ b/libimagcounter/src/lib.rs @@ -0,0 +1,46 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +#![deny( + non_camel_case_types, + non_snake_case, + path_statements, + trivial_numeric_casts, + unstable_features, + unused_allocation, + unused_import_braces, + unused_imports, + unused_mut, + unused_qualifications, + while_true, +)] + +extern crate toml; +#[macro_use] extern crate log; +#[macro_use] extern crate semver; + +#[macro_use] extern crate libimagstore; +#[macro_use] extern crate libimagerror; + +module_entry_path_mod!("counter"); + +pub mod counter; +pub mod error; +pub mod result; + diff --git a/libimagcounter/src/result.rs b/libimagcounter/src/result.rs new file mode 100644 index 00000000..e24b90c9 --- /dev/null +++ b/libimagcounter/src/result.rs @@ -0,0 +1,25 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use std::result::Result as RResult; + +use error::CounterError; + +pub type Result = RResult; + diff --git a/libimagdiary/Cargo.toml b/libimagdiary/Cargo.toml new file mode 100644 index 00000000..ec9f02a7 --- /dev/null +++ b/libimagdiary/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "libimagdiary" +version = "0.2.0" +authors = ["Matthias Beyer "] + +description = "Library for the imag core distribution" + +keywords = ["imag", "PIM", "personal", "information", "management"] +readme = "../README.md" +license = "LGPL-2.1" + +documentation = "https://matthiasbeyer.github.io/imag/imag_documentation/index.html" +repository = "https://github.com/matthiasbeyer/imag" +homepage = "http://imag-pim.org" + +[dependencies] +chrono = "0.2" +log = "0.3" +semver = "0.5" +toml = "0.2.*" +regex = "0.1" +lazy_static = "0.2" +itertools = "0.5" + +[dependencies.libimagstore] +path = "../libimagstore" + +[dependencies.libimagerror] +path = "../libimagerror" + +[dependencies.libimagutil] +path = "../libimagutil" + +[dependencies.libimagrt] +path = "../libimagrt" + +[dependencies.libimagentryedit] +path = "../libimagentryedit" + +[dependencies.libimagentryview] +path = "../libimagentryview" + diff --git a/libimagdiary/README.md b/libimagdiary/README.md new file mode 120000 index 00000000..50c0ff34 --- /dev/null +++ b/libimagdiary/README.md @@ -0,0 +1 @@ +../doc/src/05100-lib-diary.md \ No newline at end of file diff --git a/libimagdiary/src/config.rs b/libimagdiary/src/config.rs new file mode 100644 index 00000000..b2e976a6 --- /dev/null +++ b/libimagdiary/src/config.rs @@ -0,0 +1,38 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use toml::Value; + +use libimagrt::runtime::Runtime; + +pub fn get_default_diary_name(rt: &Runtime) -> Option { + get_diary_config_section(rt) + .and_then(|config| { + match config.lookup("default_diary") { + Some(&Value::String(ref s)) => Some(s.clone()), + _ => None, + } + }) +} + +pub fn get_diary_config_section<'a>(rt: &'a Runtime) -> Option<&'a Value> { + rt.config() + .map(|config| config.config()) + .and_then(|config| config.lookup("diary")) +} diff --git a/libimagdiary/src/diary.rs b/libimagdiary/src/diary.rs new file mode 100644 index 00000000..1a19b052 --- /dev/null +++ b/libimagdiary/src/diary.rs @@ -0,0 +1,128 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use std::cmp::Ordering; + +use libimagstore::store::Store; +use libimagstore::storeid::IntoStoreId; +use libimagerror::trace::trace_error; + +use chrono::offset::local::Local; +use chrono::Datelike; +use itertools::Itertools; +use chrono::naive::datetime::NaiveDateTime; + +use entry::Entry; +use diaryid::DiaryId; +use error::DiaryError as DE; +use error::DiaryErrorKind as DEK; +use result::Result; +use iter::DiaryEntryIterator; +use is_in_diary::IsInDiary; + +#[derive(Debug)] +pub struct Diary<'a> { + store: &'a Store, + name: &'a str, +} + +impl<'a> Diary<'a> { + + pub fn open(store: &'a Store, name: &'a str) -> Diary<'a> { + Diary { + store: store, + name: name, + } + } + + // create or get a new entry for today + pub fn new_entry_today(&self) -> Result { + let dt = Local::now(); + let ndt = dt.naive_local(); + let id = DiaryId::new(String::from(self.name), ndt.year(), ndt.month(), ndt.day(), 0, 0); + self.new_entry_by_id(id) + } + + pub fn new_entry_by_id(&self, id: DiaryId) -> Result { + self.retrieve(id.with_diary_name(String::from(self.name))) + } + + pub fn retrieve(&self, id: DiaryId) -> Result { + id.into_storeid() + .and_then(|id| self.store.retrieve(id)) + .map(|fle| Entry::new(fle)) + .map_err(|e| DE::new(DEK::StoreWriteError, Some(Box::new(e)))) + } + + // Get an iterator for iterating over all entries + pub fn entries(&self) -> Result> { + self.store + .retrieve_for_module("diary") + .map(|iter| DiaryEntryIterator::new(self.name, self.store, iter)) + .map_err(|e| DE::new(DEK::StoreReadError, Some(Box::new(e)))) + } + + pub fn delete_entry(&self, entry: Entry) -> Result<()> { + if !entry.is_in_diary(self.name) { + return Err(DE::new(DEK::EntryNotInDiary, None)); + } + let id = entry.get_location().clone(); + drop(entry); + + self.store.delete(id) + .map_err(|e| DE::new(DEK::StoreWriteError, Some(Box::new(e)))) + } + + pub fn get_youngest_entry(&self) -> Option> { + match self.entries() { + Err(e) => Some(Err(e)), + Ok(entries) => { + entries.sorted_by(|a, b| { + match (a, b) { + (&Ok(ref a), &Ok(ref b)) => { + let a : NaiveDateTime = a.diary_id().into(); + let b : NaiveDateTime = b.diary_id().into(); + + a.cmp(&b) + }, + + (&Ok(_), &Err(ref e)) => { + trace_error(e); + Ordering::Less + }, + (&Err(ref e), &Ok(_)) => { + trace_error(e); + Ordering::Greater + }, + (&Err(ref e1), &Err(ref e2)) => { + trace_error(e1); + trace_error(e2); + Ordering::Equal + }, + } + }).into_iter().next() + } + } + } + + pub fn name(&self) -> &'a str { + &self.name + } +} + diff --git a/libimagdiary/src/diaryid.rs b/libimagdiary/src/diaryid.rs new file mode 100644 index 00000000..906097d8 --- /dev/null +++ b/libimagdiary/src/diaryid.rs @@ -0,0 +1,257 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use std::convert::Into; +use std::fmt::{Display, Formatter, Error as FmtError}; + +use chrono::naive::datetime::NaiveDateTime; +use chrono::naive::time::NaiveTime; +use chrono::naive::date::NaiveDate; +use chrono::Datelike; +use chrono::Timelike; + +use libimagstore::storeid::StoreId; +use libimagstore::storeid::IntoStoreId; +use libimagstore::store::Result as StoreResult; + +use error::DiaryError as DE; +use error::DiaryErrorKind as DEK; +use error::MapErrInto; +use libimagerror::into::IntoError; + +use module_path::ModuleEntryPath; + +#[derive(Debug, Clone)] +pub struct DiaryId { + name: String, + year: i32, + month: u32, + day: u32, + hour: u32, + minute: u32, +} + +impl DiaryId { + + pub fn new(name: String, y: i32, m: u32, d: u32, h: u32, min: u32) -> DiaryId { + DiaryId { + name: name, + year: y, + month: m, + day: d, + hour: h, + minute: min, + } + } + + pub fn from_datetime(diary_name: String, dt: DT) -> DiaryId { + DiaryId::new(diary_name, dt.year(), dt.month(), dt.day(), dt.hour(), dt.minute()) + } + + pub fn diary_name(&self) -> &String { + &self.name + } + + pub fn year(&self) -> i32 { + self.year + } + + pub fn month(&self) -> u32 { + self.month + } + + pub fn day(&self) -> u32 { + self.day + } + + pub fn hour(&self) -> u32 { + self.hour + } + + pub fn minute(&self) -> u32 { + self.minute + } + + pub fn with_diary_name(mut self, name: String) -> DiaryId { + self.name = name; + self + } + + pub fn with_year(mut self, year: i32) -> DiaryId { + self.year = year; + self + } + + pub fn with_month(mut self, month: u32) -> DiaryId { + self.month = month; + self + } + + pub fn with_day(mut self, day: u32) -> DiaryId { + self.day = day; + self + } + + pub fn with_hour(mut self, hour: u32) -> DiaryId { + self.hour = hour; + self + } + + pub fn with_minute(mut self, minute: u32) -> DiaryId { + self.minute = minute; + self + } + + pub fn now(name: String) -> DiaryId { + use chrono::offset::local::Local; + + let now = Local::now(); + let now_date = now.date().naive_local(); + let now_time = now.time(); + let dt = NaiveDateTime::new(now_date, now_time); + + DiaryId::new(name, dt.year(), dt.month(), dt.day(), dt.hour(), dt.minute()) + } + +} + +impl Default for DiaryId { + + /// Create a default DiaryId which is a diaryid for a diary named "default" with + /// time = 0000-00-00 00:00:00 + fn default() -> DiaryId { + let dt = NaiveDateTime::new(NaiveDate::from_ymd(0, 0, 0), NaiveTime::from_hms(0, 0, 0)); + DiaryId::from_datetime(String::from("default"), dt) + } +} + +impl IntoStoreId for DiaryId { + + fn into_storeid(self) -> StoreResult { + let s : String = self.into(); + ModuleEntryPath::new(s).into_storeid() + } + +} + +impl Into for DiaryId { + + fn into(self) -> String { + format!("{}/{:0>4}/{:0>2}/{:0>2}/{:0>2}:{:0>2}", + self.name, self.year, self.month, self.day, self.hour, self.minute) + } + +} + +impl Display for DiaryId { + + fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> { + write!(fmt, "{}/{:0>4}/{:0>2}/{:0>2}/{:0>2}:{:0>2}", + self.name, self.year, self.month, self.day, self.hour, self.minute) + } + +} + +impl Into for DiaryId { + + fn into(self) -> NaiveDateTime { + let d = NaiveDate::from_ymd(self.year, self.month, self.day); + let t = NaiveTime::from_hms(self.hour, self.minute, 0); + NaiveDateTime::new(d, t) + } + +} + +pub trait FromStoreId : Sized { + + fn from_storeid(&StoreId) -> Result; + +} + +use std::path::Component; + +fn component_to_str<'a>(com: Component<'a>) -> Result<&'a str, DE> { + match com { + Component::Normal(s) => Some(s), + _ => None, + }.and_then(|s| s.to_str()) + .ok_or(DEK::IdParseError.into_error()) +} + +impl FromStoreId for DiaryId { + + fn from_storeid(s: &StoreId) -> Result { + use std::str::FromStr; + + use std::path::Components; + use std::iter::Rev; + + fn next_component<'a>(components: &'a mut Rev) -> Result<&'a str, DE> { + components.next() + .ok_or(DEK::IdParseError.into_error()) + .and_then(component_to_str) + } + + let mut cmps = s.components().rev(); + + let (hour, minute) = try!(next_component(&mut cmps).and_then(|time| { + let mut time = time.split(":"); + let hour = time.next().and_then(|s| FromStr::from_str(s).ok()); + let minute = time.next() + .and_then(|s| s.split("~").next()) + .and_then(|s| FromStr::from_str(s).ok()); + + debug!("Hour = {:?}", hour); + debug!("Minute = {:?}", minute); + + match (hour, minute) { + (Some(h), Some(m)) => Ok((h, m)), + _ => return Err(DE::new(DEK::IdParseError, None)), + } + })); + + let day: Result = next_component(&mut cmps) + .and_then(|s| s.parse::() + .map_err_into(DEK::IdParseError)); + + let month: Result = next_component(&mut cmps) + .and_then(|s| s.parse::() + .map_err_into(DEK::IdParseError)); + + let year: Result = next_component(&mut cmps) + .and_then(|s| s.parse::() + .map_err_into(DEK::IdParseError)); + + let name = next_component(&mut cmps).map(String::from); + + debug!("Day = {:?}", day); + debug!("Month = {:?}", month); + debug!("Year = {:?}", year); + debug!("Name = {:?}", name); + + let day = try!(day); + let month = try!(month); + let year = try!(year); + let name = try!(name); + + Ok(DiaryId::new(name, year, month, day, hour, minute)) + } + +} + diff --git a/libimagdiary/src/entry.rs b/libimagdiary/src/entry.rs new file mode 100644 index 00000000..0148b59d --- /dev/null +++ b/libimagdiary/src/entry.rs @@ -0,0 +1,90 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use std::ops::Deref; +use std::ops::DerefMut; + +use libimagstore::store::FileLockEntry; +use libimagentryedit::edit::Edit; +use libimagentryedit::result::Result as EditResult; +use libimagrt::runtime::Runtime; + +use diaryid::DiaryId; +use diaryid::FromStoreId; + +#[derive(Debug)] +pub struct Entry<'a>(FileLockEntry<'a>); + +impl<'a> Deref for Entry<'a> { + type Target = FileLockEntry<'a>; + + fn deref(&self) -> &FileLockEntry<'a> { + &self.0 + } + +} + +impl<'a> DerefMut for Entry<'a> { + + fn deref_mut(&mut self) -> &mut FileLockEntry<'a> { + &mut self.0 + } + +} + +impl<'a> Entry<'a> { + + pub fn new(fle: FileLockEntry<'a>) -> Entry<'a> { + Entry(fle) + } + + /// Get the diary id for this entry. + /// + /// TODO: calls Option::unwrap() as it assumes that an existing Entry has an ID that is parsable + pub fn diary_id(&self) -> DiaryId { + DiaryId::from_storeid(&self.0.get_location().clone()).unwrap() + } + +} + +impl<'a> Into> for Entry<'a> { + + fn into(self) -> FileLockEntry<'a> { + self.0 + } + +} + +impl<'a> From> for Entry<'a> { + + fn from(fle: FileLockEntry<'a>) -> Entry<'a> { + Entry::new(fle) + } + +} + +impl<'a> Edit for Entry<'a> { + + fn edit_content(&mut self, rt: &Runtime) -> EditResult<()> { + self.0.edit_content(rt) + } + +} + + diff --git a/libimagdiary/src/error.rs b/libimagdiary/src/error.rs new file mode 100644 index 00000000..b5406b12 --- /dev/null +++ b/libimagdiary/src/error.rs @@ -0,0 +1,38 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +generate_error_module!( + generate_error_types!(DiaryError, DiaryErrorKind, + StoreWriteError => "Error writing store", + StoreReadError => "Error reading store", + CannotFindDiary => "Cannot find diary", + CannotCreateNote => "Cannot create Note object for diary entry", + DiaryEditError => "Cannot edit diary entry", + PathConversionError => "Error while converting paths internally", + EntryNotInDiary => "Entry not in Diary", + IOError => "IO Error", + ViewError => "Error viewing diary entry", + IdParseError => "Error while parsing ID" + ); +); + +pub use self::error::DiaryError; +pub use self::error::DiaryErrorKind; +pub use self::error::MapErrInto; + diff --git a/libimagdiary/src/is_in_diary.rs b/libimagdiary/src/is_in_diary.rs new file mode 100644 index 00000000..09228072 --- /dev/null +++ b/libimagdiary/src/is_in_diary.rs @@ -0,0 +1,44 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use libimagstore::store::Entry; +use libimagstore::storeid::StoreId; + +pub trait IsInDiary { + + fn is_in_diary(&self, name: &str) -> bool; + +} + +impl IsInDiary for Entry { + + fn is_in_diary(&self, name: &str) -> bool { + self.get_location().clone().is_in_diary(name) + } + +} + +impl IsInDiary for StoreId { + + fn is_in_diary(&self, name: &str) -> bool { + self.local().starts_with(format!("diary/{}", name)) + } + +} + diff --git a/libimagdiary/src/iter.rs b/libimagdiary/src/iter.rs new file mode 100644 index 00000000..de849872 --- /dev/null +++ b/libimagdiary/src/iter.rs @@ -0,0 +1,132 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use std::fmt::{Debug, Formatter, Error as FmtError}; +use std::result::Result as RResult; + +use libimagstore::store::Store; +use libimagstore::storeid::StoreIdIterator; + +use diaryid::DiaryId; +use diaryid::FromStoreId; +use is_in_diary::IsInDiary; +use entry::Entry as DiaryEntry; +use error::DiaryError as DE; +use error::DiaryErrorKind as DEK; +use result::Result; +use libimagerror::trace::trace_error; + +/// A iterator for iterating over diary entries +pub struct DiaryEntryIterator<'a> { + store: &'a Store, + name: &'a str, + iter: StoreIdIterator, + + year: Option, + month: Option, + day: Option, +} + +impl<'a> Debug for DiaryEntryIterator<'a> { + + fn fmt(&self, fmt: &mut Formatter) -> RResult<(), FmtError> { + write!(fmt, "DiaryEntryIterator", + self.name, self.year, self.month, self.day) + } + +} + +impl<'a> DiaryEntryIterator<'a> { + + pub fn new(diaryname: &'a str, store: &'a Store, iter: StoreIdIterator) -> DiaryEntryIterator<'a> { + DiaryEntryIterator { + store: store, + name: diaryname, + iter: iter, + + year: None, + month: None, + day: None, + } + } + + // Filter by year, get all diary entries for this year + pub fn year(mut self, year: i32) -> DiaryEntryIterator<'a> { + self.year = Some(year); + self + } + + // Filter by month, get all diary entries for this month (every year) + pub fn month(mut self, month: u32) -> DiaryEntryIterator<'a> { + self.month = Some(month); + self + } + + // Filter by day, get all diary entries for this day (every year, every year) + pub fn day(mut self, day: u32) -> DiaryEntryIterator<'a> { + self.day = Some(day); + self + } + +} + +impl<'a> Iterator for DiaryEntryIterator<'a> { + type Item = Result>; + + fn next(&mut self) -> Option>> { + loop { + let next = match self.iter.next() { + Some(s) => s, + None => return None, + }; + debug!("Next element: {:?}", next); + + if next.is_in_diary(self.name) { + debug!("Seems to be in diary: {:?}", next); + let id = match DiaryId::from_storeid(&next) { + Ok(i) => i, + Err(e) => { + trace_error(&e); + debug!("Couldn't parse {:?} into DiaryId: {:?}", next, e); + continue; + } + }; + debug!("Success parsing id = {:?}", id); + + let y = match self.year { None => true, Some(y) => y == id.year() }; + let m = match self.month { None => true, Some(m) => m == id.month() }; + let d = match self.day { None => true, Some(d) => d == id.day() }; + + if y && m && d { + debug!("Return = {:?}", id); + return Some(self + .store + .retrieve(next) + .map(|fle| DiaryEntry::new(fle)) + .map_err(|e| DE::new(DEK::StoreReadError, Some(Box::new(e)))) + ); + } + } else { + debug!("Not in the requested diary ({}): {:?}", self.name, next); + } + } + } + +} + diff --git a/libimagdiary/src/lib.rs b/libimagdiary/src/lib.rs new file mode 100644 index 00000000..f6756225 --- /dev/null +++ b/libimagdiary/src/lib.rs @@ -0,0 +1,62 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +#![deny( + dead_code, + non_camel_case_types, + non_snake_case, + path_statements, + trivial_numeric_casts, + unstable_features, + unused_allocation, + unused_import_braces, + unused_imports, + unused_must_use, + unused_mut, + unused_qualifications, + while_true, +)] + +extern crate chrono; +#[macro_use] extern crate log; +#[macro_use] extern crate lazy_static; +extern crate semver; +extern crate toml; +extern crate regex; +extern crate itertools; + +#[macro_use] extern crate libimagstore; +#[macro_use] extern crate libimagutil; +#[macro_use] extern crate libimagerror; +extern crate libimagentryedit; +extern crate libimagentryview; +extern crate libimagrt; + +module_entry_path_mod!("diary"); + +pub mod config; +pub mod error; +pub mod diaryid; +pub mod diary; +pub mod is_in_diary; +pub mod entry; +pub mod iter; +pub mod result; +pub mod viewer; + diff --git a/libimagdiary/src/result.rs b/libimagdiary/src/result.rs new file mode 100644 index 00000000..b4f5f382 --- /dev/null +++ b/libimagdiary/src/result.rs @@ -0,0 +1,24 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use std::result::Result as RResult; + +use error::DiaryError; + +pub type Result = RResult; diff --git a/libimagdiary/src/viewer.rs b/libimagdiary/src/viewer.rs new file mode 100644 index 00000000..93b155a7 --- /dev/null +++ b/libimagdiary/src/viewer.rs @@ -0,0 +1,64 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +//! A diary viewer built on libimagentryview. + +use entry::Entry; +use error::DiaryErrorKind as DEK; +use error::MapErrInto; +use result::Result; + +use libimagentryview::viewer::Viewer; +use libimagentryview::builtin::plain::PlainViewer; + +/// This viewer does _not_ implement libimagentryview::viewer::Viewer because we need to be able to +/// call some diary-type specific functions on the entries passed to this. +/// +/// This type is mainly just written to be constructed-called-deleted in one go: +/// +/// ```ignore +/// DiaryViewer::new(show_header).view_entries(entries); +/// ``` +/// +pub struct DiaryViewer(PlainViewer); + +impl DiaryViewer { + + pub fn new(show_header: bool) -> DiaryViewer { + DiaryViewer(PlainViewer::new(show_header)) + } + + /// View all entries from the iterator, or stop immediately if an error occurs, returning that + /// error. + pub fn view_entries<'a, I: Iterator>>(&self, entries: I) -> Result<()> { + for entry in entries { + let id = entry.diary_id(); + println!("{} :\n", id); + let _ = try!(self.0 + .view_entry(&entry) + .map_err_into(DEK::ViewError) + .map_err_into(DEK::IOError)); + println!("\n---\n"); + } + + Ok(()) + } + +} + diff --git a/libimagmail/Cargo.toml b/libimagmail/Cargo.toml new file mode 100644 index 00000000..6a9218c9 --- /dev/null +++ b/libimagmail/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "libimagmail" +version = "0.2.0" +authors = ["Matthias Beyer "] + +description = "Library for the imag core distribution" + +keywords = ["imag", "PIM", "personal", "information", "management"] +readme = "../README.md" +license = "LGPL-2.1" + +documentation = "https://matthiasbeyer.github.io/imag/imag_documentation/index.html" +repository = "https://github.com/matthiasbeyer/imag" +homepage = "http://imag-pim.org" + +[dependencies] +log = "0.3" +mailparse = "0.4" +semver = "0.5" +toml = "0.2.*" +filters = "0.1.*" + +[dependencies.libimagstore] +path = "../libimagstore" + +[dependencies.libimagerror] +path = "../libimagerror" + +[dependencies.libimagref] +path = "../libimagref" + diff --git a/libimagmail/src/error.rs b/libimagmail/src/error.rs new file mode 100644 index 00000000..a11a0a94 --- /dev/null +++ b/libimagmail/src/error.rs @@ -0,0 +1,16 @@ +generate_error_module!( + generate_error_types!(MailError, MailErrorKind, + RefCreationError => "Error creating a reference to a file/directory", + RefHandlingError => "Error while handling the internal reference object", + MailParsingError => "Error while parsing mail", + + FetchByHashError => "Error fetching mail from Store by hash", + FetchError => "Error fetching mail from Store", + IOError => "IO Error" + ); +); + +pub use self::error::MailError; +pub use self::error::MailErrorKind; +pub use self::error::MapErrInto; + diff --git a/libimagmail/src/hasher.rs b/libimagmail/src/hasher.rs new file mode 100644 index 00000000..dc1df124 --- /dev/null +++ b/libimagmail/src/hasher.rs @@ -0,0 +1,67 @@ +use std::io::Read; +use std::path::PathBuf; + +use mailparse::{MailHeader, parse_mail}; + +use libimagref::hasher::Hasher; +use libimagref::hasher::DefaultHasher; +use libimagref::error::RefErrorKind as REK; +use libimagref::error::MapErrInto; +use libimagref::result::Result as RResult; +use libimagerror::into::IntoError; + +use error::MailErrorKind as MEK; + +pub struct MailHasher { + defaulthasher: DefaultHasher, +} + +impl MailHasher { + + pub fn new() -> MailHasher { + MailHasher { defaulthasher: DefaultHasher::new() } + } + +} + +impl Hasher for MailHasher { + + fn hash_name(&self) -> &'static str { + "default_mail_hasher" + } + + fn create_hash(&mut self, pb: &PathBuf, c: &mut R) -> RResult { + use filters::filter::Filter; + + let mut s = String::new(); + try!(c.read_to_string(&mut s).map_err_into(REK::UTF8Error).map_err_into(REK::IOError)); + + parse_mail(&s.as_bytes()) + .map_err(Box::new) + .map_err(|e| MEK::MailParsingError.into_error_with_cause(e)) + .map_err_into(REK::RefHashingError) + .and_then(|mail| { + let has_key = |hdr: &MailHeader, exp: &str| + hdr.get_key().map(|s| s == exp).unwrap_or(false); + + let subject_filter = |hdr: &MailHeader| has_key(hdr, "Subject"); + let from_filter = |hdr: &MailHeader| has_key(hdr, "From"); + let to_filter = |hdr: &MailHeader| has_key(hdr, "To"); + + let filter = subject_filter.or(from_filter).or(to_filter); + + let mut v = vec![]; + for hdr in mail.headers.iter().filter(|item| filter.filter(item)) { + let s = try!(hdr.get_value() + .map_err(Box::new) + .map_err(|e| REK::RefHashingError.into_error_with_cause(e))); + + v.push(s); + } + let s : String = v.join(""); + + self.defaulthasher.create_hash(pb, &mut s.as_bytes()) + }) + } + +} diff --git a/libimagmail/src/iter.rs b/libimagmail/src/iter.rs new file mode 100644 index 00000000..365a0928 --- /dev/null +++ b/libimagmail/src/iter.rs @@ -0,0 +1,37 @@ +//! Module for the MailIter +//! +//! MailIter is a iterator which takes an Iterator that yields `Ref` and yields itself +//! `Result`, where `Err(_)` is returned if the Ref is not a Mail or parsing of the +//! referenced mail file failed. +//! + +use mail::Mail; +use result::Result; + +use libimagref::reference::Ref; + +use std::marker::PhantomData; + +struct MailIter<'a, I: 'a + Iterator>> { + _marker: PhantomData<&'a I>, + i: I, +} + +impl<'a, I: Iterator>> MailIter<'a, I> { + + pub fn new(i: I) -> MailIter<'a, I> { + MailIter { _marker: PhantomData, i: i } + } + +} + +impl<'a, I: Iterator>> Iterator for MailIter<'a, I> { + + type Item = Result>; + + fn next(&mut self) -> Option>> { + self.i.next().map(Mail::from_ref) + } + +} + diff --git a/libimagmail/src/lib.rs b/libimagmail/src/lib.rs new file mode 100644 index 00000000..1d88a069 --- /dev/null +++ b/libimagmail/src/lib.rs @@ -0,0 +1,16 @@ +#[macro_use] extern crate log; +extern crate mailparse; +extern crate semver; +extern crate toml; +extern crate filters; + +#[macro_use] extern crate libimagerror; +extern crate libimagstore; +extern crate libimagref; + +pub mod error; +pub mod hasher; +pub mod iter; +pub mod mail; +pub mod result; + diff --git a/libimagmail/src/mail.rs b/libimagmail/src/mail.rs new file mode 100644 index 00000000..b49e0ed5 --- /dev/null +++ b/libimagmail/src/mail.rs @@ -0,0 +1,120 @@ +use std::result::Result as RResult; +use std::path::Path; +use std::path::PathBuf; +use std::fs::File; +use std::io::Read; + +use libimagstore::store::{FileLockEntry, Store}; +use libimagref::reference::Ref; +use libimagref::flags::RefFlags; + +use mailparse::{MailParseError, ParsedMail, parse_mail}; + +use hasher::MailHasher; +use result::Result; +use error::{MapErrInto, MailErrorKind as MEK}; + +struct Buffer(String); + +impl Buffer { + pub fn parsed<'a>(&'a self) -> RResult, MailParseError> { + parse_mail(self.0.as_bytes()) + } +} + +impl From for Buffer { + fn from(data: String) -> Buffer { + Buffer(data) + } +} + +pub struct Mail<'a>(Ref<'a>, Buffer); + +impl<'a> Mail<'a> { + + /// Imports a mail from the Path passed + pub fn import_from_path>(store: &Store, p: P) -> Result { + let h = MailHasher::new(); + let f = RefFlags::default().with_content_hashing(true).with_permission_tracking(false); + let p = PathBuf::from(p.as_ref()); + + Ref::create_with_hasher(store, p, f, h) + .map_err_into(MEK::RefCreationError) + .and_then(|reference| { + reference.fs_file() + .map_err_into(MEK::RefHandlingError) + .and_then(|path| File::open(path).map_err_into(MEK::IOError)) + .and_then(|mut file| { + let mut s = String::new(); + file.read_to_string(&mut s) + .map(|_| s) + .map_err_into(MEK::IOError) + }) + .map(Buffer::from) + .map(|buffer| Mail(reference, buffer)) + }) + } + + /// Opens a mail by the passed hash + pub fn open>(store: &Store, hash: S) -> Result> { + Ref::get_by_hash(store, String::from(hash.as_ref())) + .map_err_into(MEK::FetchByHashError) + .map_err_into(MEK::FetchError) + .and_then(|o| match o { + Some(r) => Mail::from_ref(r).map(Some), + None => Ok(None), + }) + + } + + /// Implement me as TryFrom as soon as it is stable + pub fn from_ref(r: Ref<'a>) -> Result { + r.fs_file() + .map_err_into(MEK::RefHandlingError) + .and_then(|path| File::open(path).map_err_into(MEK::IOError)) + .and_then(|mut file| { + let mut s = String::new(); + file.read_to_string(&mut s) + .map(|_| s) + .map_err_into(MEK::IOError) + }) + .map(Buffer::from) + .map(|buffer| Mail(r, buffer)) + } + + pub fn get_field(&self, field: &str) -> Result> { + use mailparse::MailHeader; + + self.1 + .parsed() + .map_err_into(MEK::MailParsingError) + .map(|parsed| { + parsed.headers + .iter() + .filter(|hdr| hdr.get_key().map(|n| n == field).unwrap_or(false)) + .next() + .and_then(|field| field.get_value().ok()) + }) + } + + pub fn get_from(&self) -> Result> { + self.get_field("From") + } + + pub fn get_to(&self) -> Result> { + self.get_field("To") + } + + pub fn get_subject(&self) -> Result> { + self.get_field("Subject") + } + + pub fn get_message_id(&self) -> Result> { + self.get_field("Message-ID") + } + + pub fn get_in_reply_to(&self) -> Result> { + self.get_field("In-Reply-To") + } + +} diff --git a/libimagmail/src/result.rs b/libimagmail/src/result.rs new file mode 100644 index 00000000..e7715513 --- /dev/null +++ b/libimagmail/src/result.rs @@ -0,0 +1,6 @@ +use std::result::Result as RResult; + +use error::MailError; + +pub type Result = RResult; + diff --git a/libimagtodo/Cargo.toml b/libimagtodo/Cargo.toml new file mode 100644 index 00000000..7b2c9138 --- /dev/null +++ b/libimagtodo/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "libimagtodo" +version = "0.2.0" +authors = ["mario "] + +description = "Library for the imag core distribution" + +keywords = ["imag", "PIM", "personal", "information", "management"] +readme = "../README.md" +license = "LGPL-2.1" + +documentation = "https://matthiasbeyer.github.io/imag/imag_documentation/index.html" +repository = "https://github.com/matthiasbeyer/imag" +homepage = "http://imag-pim.org" + +[dependencies] +semver = "0.2" +task-hookrs = "0.2.2" +uuid = "0.3" +toml = "0.2.*" +log = "0.3" +serde_json = "0.8" + +[dependencies.libimagstore] +path = "../libimagstore" + +[dependencies.libimagerror] +path = "../libimagerror" + +[dependencies.libimagutil] +path = "../libimagutil" + diff --git a/libimagtodo/src/error.rs b/libimagtodo/src/error.rs new file mode 100644 index 00000000..c19a6e52 --- /dev/null +++ b/libimagtodo/src/error.rs @@ -0,0 +1,32 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +generate_error_module!( + generate_error_types!(TodoError, TodoErrorKind, + ConversionError => "Conversion Error", + StoreError => "Store Error", + StoreIdError => "Store Id handling error", + ImportError => "Error importing" + ); +); + +pub use self::error::TodoError; +pub use self::error::TodoErrorKind; +pub use self::error::MapErrInto; + diff --git a/libimagtodo/src/lib.rs b/libimagtodo/src/lib.rs new file mode 100644 index 00000000..5a7584ec --- /dev/null +++ b/libimagtodo/src/lib.rs @@ -0,0 +1,50 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +#![deny( + non_camel_case_types, + non_snake_case, + path_statements, + trivial_numeric_casts, + unstable_features, + unused_allocation, + unused_import_braces, + unused_imports, + unused_mut, + unused_qualifications, + while_true, +)] + +extern crate semver; +extern crate uuid; +extern crate toml; +#[macro_use] extern crate log; +extern crate serde_json; + +#[macro_use] extern crate libimagstore; +#[macro_use] extern crate libimagerror; +extern crate libimagutil; +extern crate task_hookrs; + +module_entry_path_mod!("todo"); + +pub mod error; +pub mod result; +pub mod task; + diff --git a/libimagtodo/src/result.rs b/libimagtodo/src/result.rs new file mode 100644 index 00000000..7962851c --- /dev/null +++ b/libimagtodo/src/result.rs @@ -0,0 +1,24 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use error::TodoError; + +use std::result::Result as RResult; + +pub type Result = RResult; diff --git a/libimagtodo/src/task.rs b/libimagtodo/src/task.rs new file mode 100644 index 00000000..d51d0407 --- /dev/null +++ b/libimagtodo/src/task.rs @@ -0,0 +1,293 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use std::collections::BTreeMap; +use std::ops::{Deref, DerefMut}; +use std::io::BufRead; +use std::result::Result as RResult; + +use toml::Value; +use uuid::Uuid; + +use task_hookrs::task::Task as TTask; +use task_hookrs::import::{import_task, import_tasks}; + +use libimagstore::store::{FileLockEntry, Store}; +use libimagstore::storeid::{IntoStoreId, StoreIdIterator, StoreId}; +use libimagerror::trace::MapErrTrace; +use libimagutil::debug_result::DebugResult; +use module_path::ModuleEntryPath; + +use error::{TodoError, TodoErrorKind, MapErrInto}; +use result::Result; + +/// Task struct containing a `FileLockEntry` +#[derive(Debug)] +pub struct Task<'a>(FileLockEntry<'a>); + +impl<'a> Task<'a> { + + /// Concstructs a new `Task` with a `FileLockEntry` + pub fn new(fle: FileLockEntry<'a>) -> Task<'a> { + Task(fle) + } + + pub fn import(store: &'a Store, mut r: R) -> Result<(Task<'a>, String, Uuid)> { + let mut line = String::new(); + r.read_line(&mut line); + import_task(&line.as_str()) + .map_err_into(TodoErrorKind::ImportError) + .map_dbg_err_str("Error while importing task") + .map_err_dbg_trace() + .and_then(|t| { + let uuid = t.uuid().clone(); + t.into_task(store).map(|t| (t, line, uuid)) + }) + } + + /// Get a task from an import string. That is: read the imported string, get the UUID from it + /// and try to load this UUID from store. + /// + /// Possible return values are: + /// + /// * Ok(Ok(Task)) + /// * Ok(Err(String)) - where the String is the String read from the `r` parameter + /// * Err(_) - where the error is an error that happened during evaluation + /// + pub fn get_from_import(store: &'a Store, mut r: R) -> Result, String>> + { + let mut line = String::new(); + r.read_line(&mut line); + Task::get_from_string(store, line) + } + + /// Get a task from a String. The String is expected to contain the JSON-representation of the + /// Task to get from the store (only the UUID really matters in this case) + /// + /// For an explanation on the return values see `Task::get_from_import()`. + pub fn get_from_string(store: &'a Store, s: String) -> Result, String>> { + import_task(s.as_str()) + .map_err_into(TodoErrorKind::ImportError) + .map_dbg_err_str("Error while importing task") + .map_err_dbg_trace() + .map(|t| t.uuid().clone()) + .and_then(|uuid| Task::get_from_uuid(store, uuid)) + .and_then(|o| match o { + None => Ok(Err(s)), + Some(t) => Ok(Ok(t)), + }) + } + + /// Get a task from an UUID. + /// + /// If there is no task with this UUID, this returns `Ok(None)`. + pub fn get_from_uuid(store: &'a Store, uuid: Uuid) -> Result>> { + ModuleEntryPath::new(format!("taskwarrior/{}", uuid)) + .into_storeid() + .and_then(|store_id| store.get(store_id)) + .map(|o| o.map(Task::new)) + .map_err_into(TodoErrorKind::StoreError) + } + + /// Same as Task::get_from_import() but uses Store::retrieve() rather than Store::get(), to + /// implicitely create the task if it does not exist. + pub fn retrieve_from_import(store: &'a Store, mut r: R) -> Result> { + let mut line = String::new(); + r.read_line(&mut line); + Task::retrieve_from_string(store, line) + } + + /// Retrieve a task from a String. The String is expected to contain the JSON-representation of + /// the Task to retrieve from the store (only the UUID really matters in this case) + pub fn retrieve_from_string(store: &'a Store, s: String) -> Result> { + Task::get_from_string(store, s) + .and_then(|opt| match opt { + Ok(task) => Ok(task), + Err(string) => import_task(string.as_str()) + .map_err_into(TodoErrorKind::ImportError) + .map_dbg_err_str("Error while importing task") + .map_err_dbg_trace() + .and_then(|t| t.into_task(store)), + }) + } + + pub fn delete_by_imports(store: &Store, r: R) -> Result<()> { + use serde_json::ser::to_string as serde_to_string; + use task_hookrs::status::TaskStatus; + + for (counter, res_ttask) in import_tasks(r).into_iter().enumerate() { + match res_ttask { + Ok(ttask) => { + if counter % 2 == 1 { + // Only every second task is needed, the first one is the + // task before the change, and the second one after + // the change. The (maybe modified) second one is + // expected by taskwarrior. + match serde_to_string(&ttask).map_err_into(TodoErrorKind::ImportError) { + // use println!() here, as we talk with TW + Ok(val) => println!("{}", val), + Err(e) => return Err(e), + } + + // Taskwarrior does not have the concept of deleted tasks, but only modified + // ones. + // + // Here we check if the status of a task is deleted and if yes, we delete it + // from the store. + if *ttask.status() == TaskStatus::Deleted { + match Task::delete_by_uuid(store, *ttask.uuid()) { + Ok(_) => info!("Deleted task {}", *ttask.uuid()), + Err(e) => return Err(e), + } + } + } // end if c % 2 + }, + Err(e) => return Err(e).map_err_into(TodoErrorKind::ImportError), + } + } + Ok(()) + } + + pub fn delete_by_uuid(store: &Store, uuid: Uuid) -> Result<()> { + ModuleEntryPath::new(format!("taskwarrior/{}", uuid)) + .into_storeid() + .and_then(|id| store.delete(id)) + .map_err(|e| TodoError::new(TodoErrorKind::StoreError, Some(Box::new(e)))) + } + + pub fn all_as_ids(store: &Store) -> Result { + store.retrieve_for_module("todo/taskwarrior") + .map_err(|e| TodoError::new(TodoErrorKind::StoreError, Some(Box::new(e)))) + } + + pub fn all(store: &Store) -> Result { + Task::all_as_ids(store) + .map(|iter| TaskIterator::new(store, iter)) + } + +} + +impl<'a> Deref for Task<'a> { + type Target = FileLockEntry<'a>; + + fn deref(&self) -> &FileLockEntry<'a> { + &self.0 + } + +} + +impl<'a> DerefMut for Task<'a> { + + fn deref_mut(&mut self) -> &mut FileLockEntry<'a> { + &mut self.0 + } + +} + +/// A trait to get a `libimagtodo::task::Task` out of the implementing object. +pub trait IntoTask<'a> { + + /// # Usage + /// ```ignore + /// use std::io::stdin; + /// + /// use task_hookrs::task::Task; + /// use task_hookrs::import::import; + /// use libimagstore::store::{Store, FileLockEntry}; + /// + /// if let Ok(task_hookrs_task) = import(stdin()) { + /// // Store is given at runtime + /// let task = task_hookrs_task.into_filelockentry(store); + /// println!("Task with uuid: {}", task.flentry.get_header().get("todo.uuid")); + /// } + /// ``` + fn into_task(self, store : &'a Store) -> Result>; + +} + +impl<'a> IntoTask<'a> for TTask { + + fn into_task(self, store : &'a Store) -> Result> { + let uuid = self.uuid(); + ModuleEntryPath::new(format!("taskwarrior/{}", uuid)) + .into_storeid() + .map_err_into(TodoErrorKind::StoreIdError) + .and_then(|id| { + store.retrieve(id) + .map_err_into(TodoErrorKind::StoreError) + .and_then(|mut fle| { + { + let mut hdr = fle.get_header_mut(); + let read = hdr.read("todo").map_err_into(TodoErrorKind::StoreError); + if try!(read).is_none() { + try!(hdr + .set("todo", Value::Table(BTreeMap::new())) + .map_err_into(TodoErrorKind::StoreError)); + } + + try!(hdr.set("todo.uuid", Value::String(format!("{}",uuid))) + .map_err_into(TodoErrorKind::StoreError)); + } + + // If none of the errors above have returned the function, everything is fine + Ok(Task::new(fle)) + }) + }) + } + +} + +trait FromStoreId { + fn from_storeid<'a>(&'a Store, StoreId) -> Result>; +} + +impl<'a> FromStoreId for Task<'a> { + + fn from_storeid<'b>(store: &'b Store, id: StoreId) -> Result> { + match store.retrieve(id) { + Err(e) => Err(TodoError::new(TodoErrorKind::StoreError, Some(Box::new(e)))), + Ok(c) => Ok(Task::new( c )), + } + } +} + +pub struct TaskIterator<'a> { + store: &'a Store, + iditer: StoreIdIterator, +} + +impl<'a> TaskIterator<'a> { + + pub fn new(store: &'a Store, iditer: StoreIdIterator) -> TaskIterator<'a> { + TaskIterator { + store: store, + iditer: iditer, + } + } + +} + +impl<'a> Iterator for TaskIterator<'a> { + type Item = Result>; + + fn next(&mut self) -> Option>> { + self.iditer.next().map(|id| Task::from_storeid(self.store, id)) + } +} +