diff --git a/Cargo.toml b/Cargo.toml index 7a0e8b3a..03400335 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ members = [ "lib/domain/libimagbookmark", "lib/domain/libimagcontact", "lib/domain/libimagdiary", + "lib/domain/libimaghabit", "lib/domain/libimagmail", "lib/domain/libimagnotes", "lib/domain/libimagtimetrack", diff --git a/lib/domain/libimaghabit/Cargo.toml b/lib/domain/libimaghabit/Cargo.toml new file mode 100644 index 00000000..59d3949d --- /dev/null +++ b/lib/domain/libimaghabit/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "libimaghabit" +version = "0.5.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.4" +log = "0.3" +toml = "0.4" +toml-query = "0.4.0" +error-chain = "0.11" +is-match = "0.1" + +libimagstore = { version = "0.5.0", path = "../../../lib/core/libimagstore" } +libimagerror = { version = "0.5.0", path = "../../../lib/core/libimagerror" } +libimagentryedit = { version = "0.5.0", path = "../../../lib/entry/libimagentryedit" } diff --git a/lib/domain/libimaghabit/src/error.rs b/lib/domain/libimaghabit/src/error.rs new file mode 100644 index 00000000..0ec0ca3f --- /dev/null +++ b/lib/domain/libimaghabit/src/error.rs @@ -0,0 +1,52 @@ +// +// 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 +// + +error_chain! { + types { + HabitError, HabitErrorKind, ResultExt, Result; + } + + links { + StoreError(::libimagstore::error::StoreError, ::libimagstore::error::StoreErrorKind); + } + + foreign_links { + TomlError(::toml_query::error::Error); + ChronoError(::chrono::format::ParseError); + } + + errors { + HabitBuilderMissing(variable_name: &'static str) { + description("Habbit builder has not all required information") + display("Habit builder misses {}", variable_name) + } + + HeaderFieldMissing(path: &'static str) { + description("Header field missing") + display("Header field missing: {}", path) + } + + HeaderTypeError(path: &'static str, required_type: &'static str) { + description("Header type error") + display("Header type error: Expected {} at {}, found other type", required_type, path) + } + + } +} + diff --git a/lib/domain/libimaghabit/src/habit.rs b/lib/domain/libimaghabit/src/habit.rs new file mode 100644 index 00000000..c69efcbc --- /dev/null +++ b/lib/domain/libimaghabit/src/habit.rs @@ -0,0 +1,210 @@ +// +// 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 toml_query::read::TomlValueReadExt; +use toml_query::insert::TomlValueInsertExt; +use chrono::NaiveDateTime; +use chrono::Local; +use chrono::NaiveDate; + +use error::HabitError as HE; +use error::HabitErrorKind as HEK; +use error::*; +use iter::HabitInstanceStoreIdIterator; + +use libimagstore::store::Store; +use libimagstore::store::FileLockEntry; +use libimagstore::store::Entry; +use libimagstore::iter::get::StoreIdGetIteratorExtension; +use libimagstore::storeid::IntoStoreId; + +pub const NAIVE_DATE_STRING_FORMAT : &'static str = "%Y-%m-%d"; + +/// A HabitTemplate is a "template" of a habit. A user may define a habit "Eat vegetable". +/// If the user ate a vegetable, she should create a HabitInstance from the Habit with the +/// appropriate date (and optionally a comment) set. +pub trait HabitTemplate : Sized { + + /// Create an instance from this habit template + /// + /// By default creates an instance with the name of the template, the current time and the + /// current date and copies the comment from the template to the instance. + /// + /// It uses `Store::retrieve()` underneath + fn create_instance<'a>(&self, store: &'a Store) -> Result>; + + /// Check whether the instance is a habit by checking its headers for the habit data + fn is_habit_template(&self) -> Result; + + fn habit_name(&self) -> Result; + fn habit_date(&self) -> Result; + fn habit_comment(&self) -> Result; + +} + +impl HabitTemplate for Entry { + + fn create_instance<'a>(&self, store: &'a Store) -> Result> { + use module_path::ModuleEntryPath; + let date = date_to_string(&Local::today().naive_local()); + let name = self.habit_name()?; + let comment = self.habit_comment()?; + let id = ModuleEntryPath::new(format!("instance/{}-{}", name, date)) + .into_storeid() + .map_err(HE::from)?; + + store.retrieve(id) + .map_err(From::from) + .and_then(|mut entry| { + { + let mut hdr = entry.get_header_mut(); + try!(hdr.insert("habit.instance.name", Value::String(name))); + try!(hdr.insert("habit.instance.date", Value::String(date))); + try!(hdr.insert("habit.instance.comment", Value::String(comment))); + } + Ok(entry) + }) + } + + /// Check whether the instance is a habit by checking its headers for the habit data + fn is_habit_template(&self) -> Result { + [ + "habit.template.name", + "habit.template.date", + "habit.template.comment", + ].iter().fold(Ok(true), |acc, path| acc.and_then(|b| { + self.get_header() + .read(path) + .map(|o| is_match!(o, Some(&Value::String(_)))) + .map_err(From::from) + })) + } + + fn habit_name(&self) -> Result { + get_string_header_from_habit(self, "habit.template.name") + } + + fn habit_date(&self) -> Result { + get_string_header_from_habit(self, "habit.template.date") + } + + fn habit_comment(&self) -> Result { + get_string_header_from_habit(self, "habit.template.comment") + } + +} + +#[inline] +fn get_string_header_from_habit(e: &Entry, path: &'static str) -> Result { + match e.get_header().read(path)? { + Some(&Value::String(ref s)) => Ok(s.clone()), + Some(_) => Err(HEK::HeaderTypeError(path, "String").into()), + None => Err(HEK::HeaderFieldMissing(path).into()), + } +} + +pub mod builder { + use toml::Value; + use toml_query::insert::TomlValueInsertExt; + use chrono::NaiveDate; + + use libimagstore::store::Store; + use libimagstore::storeid::StoreId; + use libimagstore::storeid::IntoStoreId; + use libimagstore::store::FileLockEntry; + + use error::HabitError as HE; + use error::HabitErrorKind as HEK; + use error::*; + + use super::date_to_string; + use super::date_from_string; + + pub struct HabitBuilder { + name: Option, + comment: Option, + date: Option, + } + + impl HabitBuilder { + + pub fn with_name(&mut self, name: String) -> &mut Self { + self.name = Some(name); + self + } + + pub fn with_comment(&mut self, comment: String) -> &mut Self { + self.comment = Some(comment); + self + } + + pub fn with_date(&mut self, date: NaiveDate) -> &mut Self { + self.date = Some(date); + self + } + + pub fn build<'a>(self, store: &'a Store) -> Result> { + #[inline] + fn mkerr(s: &'static str) -> HE { + HE::from_kind(HEK::HabitBuilderMissing(s)) + } + + let name = try!(self.name.ok_or_else(|| mkerr("name"))); + let dateobj = try!(self.date.ok_or_else(|| mkerr("date"))); + let date = date_to_string(&dateobj); + let comment = self.comment.unwrap_or_else(|| String::new()); + let sid = try!(build_habit_template_sid(&name)); + let mut entry = try!(store.create(sid)); + + try!(entry.get_header_mut().insert("habit.template.name", Value::String(name))); + try!(entry.get_header_mut().insert("habit.template.date", Value::String(date))); + try!(entry.get_header_mut().insert("habit.template.comment", Value::String(comment))); + + Ok(entry) + } + + } + + impl Default for HabitBuilder { + fn default() -> Self { + HabitBuilder { + name: None, + comment: None, + date: None, + } + } + } + + /// Buld a StoreId for a Habit from a date object and a name of a habit + fn build_habit_template_sid(name: &String) -> Result { + use module_path::ModuleEntryPath; + ModuleEntryPath::new(format!("template/{}", name)).into_storeid().map_err(From::from) + } + +} + +fn date_to_string(ndt: &NaiveDate) -> String { + ndt.format(NAIVE_DATE_STRING_FORMAT).to_string() +} + +fn date_from_string(s: &str) -> Result { + NaiveDate::parse_from_str(s, NAIVE_DATE_STRING_FORMAT).map_err(From::from) +} + diff --git a/lib/domain/libimaghabit/src/instance.rs b/lib/domain/libimaghabit/src/instance.rs new file mode 100644 index 00000000..917ae4f2 --- /dev/null +++ b/lib/domain/libimaghabit/src/instance.rs @@ -0,0 +1,42 @@ +// +// 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::NaiveDate; + +use error::Result; +use habit::HabitTemplate; + +/// An instance of a habit is created for each time a habit is done. +/// +/// # Note +/// +/// A habit is a daily thing, so we only provide "date" as granularity for its time data. +/// +pub trait HabitInstance { + /// Check whether the instance is a habit instance by checking its headers for the habit + /// data + fn is_habit_instance(&self) -> Result; + + fn get_date(&self) -> Result; + fn set_date(&self, n: NaiveDate) -> Result<()>; + fn get_comment(&self) -> Result; + fn set_comment(&self, c: String) -> Result<()>; + fn get_template_name(&self) -> Result; +} + diff --git a/lib/domain/libimaghabit/src/iter.rs b/lib/domain/libimaghabit/src/iter.rs new file mode 100644 index 00000000..d3c96508 --- /dev/null +++ b/lib/domain/libimaghabit/src/iter.rs @@ -0,0 +1,72 @@ +// +// 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 toml_query::read::TomlValueReadExt; + +use libimagstore::store::FileLockEntry; +use libimagstore::storeid::StoreIdIterator; +use libimagstore::storeid::StoreId; + +use error::HabitError as HE; +use error::HabitErrorKind as HEK; +use error::*; + +pub struct HabitTemplateStoreIdIterator(StoreIdIterator); + +impl Iterator for HabitTemplateStoreIdIterator { + type Item = StoreId; + + fn next(&mut self) -> Option { + while let Some(n) = self.0.next() { + if n.is_in_collection(&["habit", "template"]) { + return Some(n) + } + } + None + } +} + +impl From for HabitTemplateStoreIdIterator { + fn from(sii: StoreIdIterator) -> Self { + HabitTemplateStoreIdIterator(sii) + } +} + +pub struct HabitInstanceStoreIdIterator(StoreIdIterator); + +impl Iterator for HabitInstanceStoreIdIterator { + type Item = StoreId; + + fn next(&mut self) -> Option { + while let Some(n) = self.0.next() { + if n.is_in_collection(&["habit", "instance"]) { + return Some(n) + } + } + None + } +} + +impl From for HabitInstanceStoreIdIterator { + fn from(sii: StoreIdIterator) -> Self { + HabitInstanceStoreIdIterator(sii) + } +} + diff --git a/lib/domain/libimaghabit/src/lib.rs b/lib/domain/libimaghabit/src/lib.rs new file mode 100644 index 00000000..e1730678 --- /dev/null +++ b/lib/domain/libimaghabit/src/lib.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 +// + +extern crate chrono; +extern crate toml; +extern crate toml_query; +#[macro_use] extern crate log; +#[macro_use] extern crate error_chain; +#[macro_use] extern crate is_match; + +#[macro_use] extern crate libimagerror; +#[macro_use] extern crate libimagstore; +extern crate libimagentryedit; + +module_entry_path_mod!("habit"); + +pub mod error; +pub mod habit; +pub mod instance; +pub mod iter; +pub mod result; +pub mod store; + diff --git a/lib/domain/libimaghabit/src/result.rs b/lib/domain/libimaghabit/src/result.rs new file mode 100644 index 00000000..83c503aa --- /dev/null +++ b/lib/domain/libimaghabit/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::HabitError; + +pub type Result = RResult; + diff --git a/lib/domain/libimaghabit/src/store.rs b/lib/domain/libimaghabit/src/store.rs new file mode 100644 index 00000000..6ac5e889 --- /dev/null +++ b/lib/domain/libimaghabit/src/store.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 error::Result; +use habit::builder::HabitBuilder; +use iter::HabitTemplateStoreIdIterator; + +use libimagstore::store::Store; + +/// Extension trait for libimagstore::store::Store which is basically our Habit-Store +pub trait HabitStore { + + /// Create a new habit + fn create_habit(&self) -> HabitBuilder { + HabitBuilder::default() + } + + /// Get an iterator over all habits + fn all_habit_templates(&self) -> Result; + +} + +impl HabitStore for Store { + /// Get an iterator over all habits + fn all_habit_templates(&self) -> Result { + self.entries().map(HabitTemplateStoreIdIterator::from).map_err(From::from) + } +}