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/doc/src/04020-module-habit.md b/doc/src/04020-module-habit.md index 1fb71608..3fa6f22d 100644 --- a/doc/src/04020-module-habit.md +++ b/doc/src/04020-module-habit.md @@ -1,8 +1,12 @@ ## Habit {#sec:modules:habit} -The Habit module is a habit tracker. One can add habits, specify how often they should be done and instantiate them. +The Habit module is a habit tracker. One can add habits, specify how often they +should be done and instantiate them. -Example: After creating a new habit "Sunday Run", which should be done on sundays, one can mark (only on sundays of course) that the habit was done. Statistics and number-crunching can be done later on, after there is some habit data there. +Example: After creating a new habit "Sunday Run", which should be done on +Sundays, one can mark that the habit was done. +Statistics and number-crunching can be done later on, after there is some habit +data there. Exports to CSV are possible. diff --git a/doc/src/05100-lib-habit.md b/doc/src/05100-lib-habit.md index da2259ec..3803bbcf 100644 --- a/doc/src/05100-lib-habit.md +++ b/doc/src/05100-lib-habit.md @@ -2,7 +2,8 @@ The habit library implements a habit tracker. -A habit can be instantiated with a name and a time-period in which it should be fullfilled (eg. daily, ever 3 days, weekly...). +A habit can be instantiated with a name and a time-period in which it should be +fullfilled (eg. daily, ever 3 days, weekly...). The module offers ways to generate statistics about habits. diff --git a/lib/domain/libimaghabit/Cargo.toml b/lib/domain/libimaghabit/Cargo.toml new file mode 100644 index 00000000..28445e68 --- /dev/null +++ b/lib/domain/libimaghabit/Cargo.toml @@ -0,0 +1,28 @@ +[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" +kairos = "0.1.0-beta-2" + +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" } +libimagentrylink = { version = "0.5.0", path = "../../../lib/entry/libimagentrylink" } diff --git a/lib/domain/libimaghabit/README.md b/lib/domain/libimaghabit/README.md new file mode 120000 index 00000000..ccb10390 --- /dev/null +++ b/lib/domain/libimaghabit/README.md @@ -0,0 +1 @@ +../../../doc/src/05100-lib-habit.md \ No newline at end of file diff --git a/lib/domain/libimaghabit/src/error.rs b/lib/domain/libimaghabit/src/error.rs new file mode 100644 index 00000000..18f439c9 --- /dev/null +++ b/lib/domain/libimaghabit/src/error.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 +// + +error_chain! { + types { + HabitError, HabitErrorKind, ResultExt, Result; + } + + links { + StoreError(::libimagstore::error::StoreError, ::libimagstore::error::StoreErrorKind); + LinkError(::libimagentrylink::error::LinkError, ::libimagentrylink::error::LinkErrorKind); + KairosError(::kairos::error::KairosError, ::kairos::error::KairosErrorKind); + } + + 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..9a8196a5 --- /dev/null +++ b/lib/domain/libimaghabit/src/habit.rs @@ -0,0 +1,302 @@ +// +// 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 util::date_to_string; +use util::date_from_string; +use util::IsHabitCheck; + +use libimagentrylink::internal::InternalLinker; +use libimagstore::store::Store; +use libimagstore::store::FileLockEntry; +use libimagstore::store::Entry; +use libimagstore::iter::get::StoreIdGetIteratorExtension; +use libimagstore::storeid::StoreId; +use libimagstore::storeid::IntoStoreId; +use libimagstore::storeid::StoreIdIterator; + +/// 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>; + + /// Get instances for this template + fn linked_instances(&self) -> Result; + + /// Get the date of the next date when the habit should be done + fn next_instance_date(&self) -> 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_basedate(&self) -> Result; + fn habit_recur_spec(&self) -> Result; + fn habit_comment(&self) -> Result; + + /// Create a StoreId for a habit name and a date the habit should be instantiated for + fn instance_id_for(habit_name: &String, habit_date: &NaiveDate) -> Result; +} + +impl HabitTemplate for Entry { + + fn create_instance<'a>(&self, store: &'a Store) -> Result> { + let name = self.habit_name()?; + let comment = self.habit_comment()?; + let date = date_to_string(&Local::today().naive_local()); + let id = instance_id_for_name_and_datestr(&name, &date)?; + + 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) + }) + } + + fn linked_instances(&self) -> Result { + let iter = self + .get_internal_links()? + .map(|link| link.get_store_id().clone()) + .filter(IsHabitCheck::is_habit_instance); + + let sidi = StoreIdIterator::new(Box::new(iter)); + Ok(HabitInstanceStoreIdIterator::new(sidi)) + } + + /// Get the date of the next date when the habit should be done + fn next_instance_date(&self) -> Result { + use kairos::timetype::TimeType; + use kairos::parser::parse; + use kairos::parser::Parsed; + use kairos::iter::extensions::Every; + + let date_from_s = |r: String| -> Result { + match parse(&r)? { + Parsed::TimeType(tt) => Ok(tt), + Parsed::Iterator(_) => { + Err(format!("'{}' yields an iterator. Cannot use.", r).into()) + }, + } + }; + + let today = TimeType::today(); + let today = today.get_moment().unwrap(); // we know this is safe. + debug!("Today is {:?}", today); + + let basedate = date_from_s(self.habit_basedate()?)?; + debug!("Basedate is {:?}", today); + + let increment = date_from_s(self.habit_recur_spec()?)?; + debug!("Increment is {:?}", today); + + for element in basedate.every(increment)? { + debug!("Calculating: {:?}", element); + let element = element?.calculate()?; + if let Some(ndt) = element.get_moment() { + if ndt > today { + debug!("-> {:?} > {:?}", ndt, today); + return Ok(ndt.date()) + } + } else { + return Err("Iterator seems to return bogus values.".to_owned().into()); + } + } + + unreachable!() // until we have habit-end-date support + } + + /// 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.basedate", + "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_basedate(&self) -> Result { + get_string_header_from_habit(self, "habit.template.basedate") + } + + fn habit_recur_spec(&self) -> Result { + get_string_header_from_habit(self, "habit.template.recurspec") + } + + fn habit_comment(&self) -> Result { + get_string_header_from_habit(self, "habit.template.comment") + } + + fn instance_id_for(habit_name: &String, habit_date: &NaiveDate) -> Result { + instance_id_for_name_and_datestr(habit_name, &date_to_string(habit_date)) + } + +} + +fn instance_id_for_name_and_datestr(habit_name: &String, habit_date: &String) -> Result { + use module_path::ModuleEntryPath; + + ModuleEntryPath::new(format!("instance/{}-{}", habit_name, habit_date)) + .into_storeid() + .map_err(HE::from) +} + +#[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 util::date_to_string; + use util::date_from_string; + + pub struct HabitBuilder { + name: Option, + comment: Option, + basedate: Option, + recurspec: Option, + } + + impl HabitBuilder { + + pub fn with_name(mut self, name: String) -> Self { + self.name = Some(name); + self + } + + pub fn with_comment(mut self, comment: String) -> Self { + self.comment = Some(comment); + self + } + + pub fn with_basedate(mut self, date: NaiveDate) -> Self { + self.basedate = Some(date); + self + } + + pub fn with_recurspec(mut self, spec: String) -> Self { + self.recurspec = Some(spec); + 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"))); + debug!("Success: Name present"); + + let dateobj = try!(self.basedate.ok_or_else(|| mkerr("date"))); + debug!("Success: Date present"); + + let recur = try!(self.recurspec.ok_or_else(|| mkerr("recurspec"))); + debug!("Success: Recurr spec present"); + + if let Err(e) = ::kairos::parser::parse(&recur) { + return Err(e).map_err(From::from); + } + let date = date_to_string(&dateobj); + debug!("Success: Date valid"); + + let comment = self.comment.unwrap_or_else(|| String::new()); + let sid = try!(build_habit_template_sid(&name)); + + debug!("Creating entry in store for: {:?}", sid); + 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.basedate", Value::String(date))); + try!(entry.get_header_mut().insert("habit.template.recurspec", Value::String(recur))); + try!(entry.get_header_mut().insert("habit.template.comment", Value::String(comment))); + + debug!("Success: Created entry in store and set headers"); + Ok(entry) + } + + } + + impl Default for HabitBuilder { + fn default() -> Self { + HabitBuilder { + name: None, + comment: None, + basedate: None, + recurspec: 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) + } + +} + diff --git a/lib/domain/libimaghabit/src/instance.rs b/lib/domain/libimaghabit/src/instance.rs new file mode 100644 index 00000000..ae2aee6d --- /dev/null +++ b/lib/domain/libimaghabit/src/instance.rs @@ -0,0 +1,105 @@ +// +// 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 toml::Value; +use toml_query::read::TomlValueReadExt; +use toml_query::set::TomlValueSetExt; + +use error::HabitError as HE; +use error::HabitErrorKind as HEK; +use error::*; +use habit::HabitTemplate; +use util::*; + +use libimagstore::store::Entry; + +/// 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(&mut self, n: &NaiveDate) -> Result<()>; + fn get_comment(&self) -> Result; + fn set_comment(&mut self, c: String) -> Result<()>; + fn get_template_name(&self) -> Result; +} + +impl HabitInstance for Entry { + fn is_habit_instance(&self) -> Result { + [ + "habit.instance.name", + "habit.instance.date", + "habit.instance.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 get_date(&self) -> Result { + match self.get_header().read("habit.instance.date")? { + Some(&Value::String(ref s)) => date_from_string(s), + Some(_) => Err(HEK::HeaderTypeError("habit.instance.date", "String").into()), + None => Err(HEK::HeaderFieldMissing("habit.instance.date").into()), + } + } + + fn set_date(&mut self, n: &NaiveDate) -> Result<()> { + // Using `set` here because when creating the entry, these headers should be made present. + self.get_header_mut() + .set("habit.instance.date", Value::String(date_to_string(n))) + .map_err(From::from) + .map(|_| ()) + } + + fn get_comment(&self) -> Result { + match self.get_header().read("habit.instance.comment")? { + Some(&Value::String(ref s)) => Ok(s.clone()), + Some(_) => Err(HEK::HeaderTypeError("habit.instance.comment", "String").into()), + None => Err(HEK::HeaderFieldMissing("habit.instance.comment").into()), + } + } + + fn set_comment(&mut self, c: String) -> Result<()> { + // Using `set` here because when creating the entry, these headers should be made present. + self.get_header_mut() + .set("habit.instance.comment", Value::String(c)) + .map_err(From::from) + .map(|_| ()) + } + + fn get_template_name(&self) -> Result { + match self.get_header().read("habit.instance.name")? { + Some(&Value::String(ref s)) => Ok(s.clone()), + Some(_) => Err(HEK::HeaderTypeError("habit.instance.name", "String").into()), + None => Err(HEK::HeaderFieldMissing("habit.instance.name").into()), + } + } + +} diff --git a/lib/domain/libimaghabit/src/iter.rs b/lib/domain/libimaghabit/src/iter.rs new file mode 100644 index 00000000..4fcf7f14 --- /dev/null +++ b/lib/domain/libimaghabit/src/iter.rs @@ -0,0 +1,80 @@ +// +// 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::store::Store; +use libimagstore::storeid::StoreIdIterator; +use libimagstore::storeid::StoreId; + +use error::HabitError as HE; +use error::HabitErrorKind as HEK; +use error::*; +use util::IsHabitCheck; + +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_habit_template() { + return Some(n) + } + } + None + } +} + +impl From for HabitTemplateStoreIdIterator { + fn from(sii: StoreIdIterator) -> Self { + HabitTemplateStoreIdIterator(sii) + } +} + +pub struct HabitInstanceStoreIdIterator(StoreIdIterator); + +impl HabitInstanceStoreIdIterator { + pub fn new(sid: StoreIdIterator) -> HabitInstanceStoreIdIterator { + HabitInstanceStoreIdIterator(sid) + } +} + +impl Iterator for HabitInstanceStoreIdIterator { + type Item = StoreId; + + fn next(&mut self) -> Option { + while let Some(n) = self.0.next() { + if n.is_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..6cf5e819 --- /dev/null +++ b/lib/domain/libimaghabit/src/lib.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 +// + +extern crate chrono; +extern crate toml; +extern crate toml_query; +extern crate kairos; +#[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; +extern crate libimagentrylink; + +module_entry_path_mod!("habit"); + +pub mod error; +pub mod habit; +pub mod instance; +pub mod iter; +pub mod result; +pub mod store; +pub mod util; + 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..5d30c03d --- /dev/null +++ b/lib/domain/libimaghabit/src/store.rs @@ -0,0 +1,63 @@ +// +// 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 error::HabitError as HE; +use habit::builder::HabitBuilder; +use iter::HabitTemplateStoreIdIterator; +use iter::HabitInstanceStoreIdIterator; + +use libimagstore::store::Store; +use libimagstore::store::FileLockEntry; +use libimagstore::storeid::StoreIdIterator; + +/// 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; + + /// Get instances + fn all_habit_instances(&self) -> Result; + + // /// Get instances of a certain date + // fn all_habit_instances_on(&self, date: &NaiveDate) -> Result; + + // /// Get instances between two dates + // fn all_habit_instances_between(&self, start: &NaiveDate, end: &NaiveDate) -> 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) + } + + fn all_habit_instances(&self) -> Result { + self.entries().map(HabitInstanceStoreIdIterator::from).map_err(From::from) + } +} + diff --git a/lib/domain/libimaghabit/src/util.rs b/lib/domain/libimaghabit/src/util.rs new file mode 100644 index 00000000..4acd6282 --- /dev/null +++ b/lib/domain/libimaghabit/src/util.rs @@ -0,0 +1,92 @@ +// +// 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::BitXor; + +use chrono::NaiveDate; +use error::Result; + +use habit::HabitTemplate; +use instance::HabitInstance; + +use libimagstore::storeid::StoreId; +use libimagstore::store::Entry; + +pub const NAIVE_DATE_STRING_FORMAT : &'static str = "%Y-%m-%d"; + +pub fn date_to_string(ndt: &NaiveDate) -> String { + ndt.format(NAIVE_DATE_STRING_FORMAT).to_string() +} + +pub fn date_from_string(s: &str) -> Result { + NaiveDate::parse_from_str(s, NAIVE_DATE_STRING_FORMAT).map_err(From::from) +} + +/// Helper trait to check whether a object which can be a habit instance and a habit template is +/// actually a valid object, whereas "valid" is defined that it is _either_ an instance or a +/// template (think XOR). +pub trait IsValidHabitObj : HabitInstance + HabitTemplate { + fn is_valid_havit_obj(&self) -> Result { + self.is_habit_instance().and_then(|b| self.is_habit_template().map(|a| a.bitxor(b))) + } +} + +impl IsValidHabitObj for H + where H: HabitInstance + HabitTemplate +{ + // Empty +} + +pub trait IsHabitCheck { + fn is_habit(&self) -> bool; + fn is_habit_instance(&self) -> bool; + fn is_habit_template(&self) -> bool; +} + +impl IsHabitCheck for StoreId { + fn is_habit(&self) -> bool { + self.is_in_collection(&["habit"]) + } + + fn is_habit_instance(&self) -> bool { + self.is_in_collection(&["habit", "instance"]) + } + + fn is_habit_template(&self) -> bool { + self.is_in_collection(&["habit", "template"]) + } +} + +impl IsHabitCheck for Entry { + /// Helper function to check whether an entry is a habit (either instance or template) + fn is_habit(&self) -> bool { + self.get_location().is_habit() + } + + /// Check whether an entry is a habit instance + fn is_habit_instance(&self) -> bool { + self.get_location().is_habit_instance() + } + + /// Check whether an entry is a habit template + fn is_habit_template(&self) -> bool { + self.get_location().is_habit_template() + } +} +