summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthias Beyer <mail@beyermatthias.de>2017-12-08 15:47:28 +0100
committerGitHub <noreply@github.com>2017-12-08 15:47:28 +0100
commit3d96170021817b108a135b282b6f0d5eb5d22868 (patch)
treea0570cf73c40359c0ef858c8c5acd44ac78e1015
parent3fa2995c5f9981fcb15d925ea40f172b892972cf (diff)
parent13e9ee3ffabca59aacb67f3d0a4b5f3333e5bb48 (diff)
downloadimag-3d96170021817b108a135b282b6f0d5eb5d22868.zip
imag-3d96170021817b108a135b282b6f0d5eb5d22868.tar.gz
Merge pull request #1042 from matthiasbeyer/libimaghabit/init
libimaghabit: init
-rw-r--r--Cargo.toml1
-rw-r--r--doc/src/04020-module-habit.md8
-rw-r--r--doc/src/05100-lib-habit.md3
-rw-r--r--lib/domain/libimaghabit/Cargo.toml28
l---------lib/domain/libimaghabit/README.md1
-rw-r--r--lib/domain/libimaghabit/src/error.rs54
-rw-r--r--lib/domain/libimaghabit/src/habit.rs302
-rw-r--r--lib/domain/libimaghabit/src/instance.rs105
-rw-r--r--lib/domain/libimaghabit/src/iter.rs80
-rw-r--r--lib/domain/libimaghabit/src/lib.rs42
-rw-r--r--lib/domain/libimaghabit/src/result.rs25
-rw-r--r--lib/domain/libimaghabit/src/store.rs63
-rw-r--r--lib/domain/libimaghabit/src/util.rs92
13 files changed, 801 insertions, 3 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 7a0e8b3..0340033 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 1fb7160..3fa6f22 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 da2259e..3803bbc 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 0000000..28445e6
--- /dev/null
+++ b/lib/domain/libimaghabit/Cargo.toml
@@ -0,0 +1,28 @@
+[package]
+name = "libimaghabit"
+version = "0.5.0"
+authors = ["Matthias Beyer <mail@beyermatthias.de>"]
+
+description = "Library for the imag core distribution"
+
+keywords = ["imag", "PIM", "personal", "information", "management"]
+readme = "../../../README.md"
+license = "LGPL-2.1"
+
+documentation = "https://matthiasbeyer.github.io/imag/imag_documentation/index.html"
+repository = "https://github.com/matthiasbeyer/imag"
+homepage = "http://imag-pim.org"
+
+[dependencies]
+chrono = "0.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 0000000..ccb1039
--- /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 0000000..18f439c
--- /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 <mail@beyermatthias.de> and contributors
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; version
+// 2.1 of the License.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+//
+
+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 0000000..9a8196a
--- /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 <mail@beyermatthias.de> and contributors
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; version
+// 2.1 of the License.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+//
+
+use toml::Value;
+use 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<FileLockEntry<'a>>;
+
+ /// Get instances for this template
+ fn linked_instances(&self) -> Result<HabitInstanceStoreIdIterator>;
+
+ /// Get the date of the next date when the habit should be done
+ fn next_instance_date(&self) -> Result<NaiveDate>;
+
+ /// Check whether the instance is a habit by checking its headers for the habit data
+ fn is_habit_template(&self) -> Result<bool>;
+
+ fn habit_name(&self) -> Result<String>;
+ fn habit_basedate(&self) -> Result<String>;
+ fn habit_recur_spec(&self) -> Result<String>;
+ fn habit_comment(&self) -> Result<String>;
+
+ /// 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<StoreId>;
+}
+
+impl HabitTemplate for Entry {
+
+ fn create_instance<'a>(&self, store: &'a Store) -> Result<FileLockEntry<'a>> {
+ 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<HabitInstanceStoreIdIterator> {
+ 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<NaiveDate> {
+ use kairos::timetype::TimeType;
+ use kairos::parser::parse;
+ use kairos::parser::Parsed;
+ use kairos::iter::extensions::Every;
+
+ let date_from_s = |r: String| -> Result<TimeType> {
+ 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<bool> {
+ [
+ "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<String> {
+ get_string_header_from_habit(self, "habit.template.name")
+ }
+
+ fn habit_basedate(&self) -> Result<String> {
+ get_string_header_from_habit(self, "habit.template.basedate")
+ }
+
+ fn habit_recur_spec(&self) -> Result<String> {
+ get_string_header_from_habit(self, "habit.template.recurspec")
+ }
+
+ fn habit_comment(&self) -> Result<String> {
+ get_string_header_from_habit(self, "habit.template.comment")
+ }
+
+ fn instance_id_for(habit_name: &String, habit_date: &NaiveDate) -> Result<StoreId> {
+ 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<StoreId> {
+ 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<String> {
+ 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<String>,
+ comment: Option<String>,
+ basedate: Option<NaiveDate>,
+ recurspec: Option<String>,
+ }
+
+ 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<FileLockEntry<'a>> {
+ #[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<StoreId> {
+ 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 0000000..ae2aee6
--- /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 <mail@beyermatthias.de> and contributors
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; version
+// 2.1 of the License.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+//
+
+use chrono::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<bool>;
+
+ fn get_date(&self) -> Result<NaiveDate>;
+ fn set_date(&mut self, n: &NaiveDate) -> Result<()>;
+ fn get_comment(&self) -> Result<String>;
+ fn set_comment(&mut self, c: String) -> Result<()>;
+ fn get_template_name(&self) -> Result<String>;
+}
+
+impl HabitInstance for Entry {
+ fn is_habit_instance(&self) -> Result<bool> {
+ [
+ "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<NaiveDate> {
+ 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<String> {
+ 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<String> {
+ 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 0000000..4fcf7f1
--- /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 <mail@beyermatthias.de> and contributors
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; version
+// 2.1 of the License.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+//
+
+use toml::Value;
+use 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<Self::Item> {
+ while let Some(n) = self.0.next() {
+ if n.is_habit_template() {
+ return Some(n)
+ }
+ }
+ None
+ }
+}
+
+impl From<StoreIdIterator> 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<Self::Item> {
+ while let Some(n) = self.0.next() {
+ if n.is_habit_instance() {
+ return Some(n)
+ }
+ }
+ None
+ }
+}
+
+impl From<StoreIdIterator> 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 0000000..6cf5e81
--- /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 <mail@beyermatthias.de> and contributors
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; version
+// 2.1 of the License.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+//
+
+extern crate 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 0000000..83c503a
--- /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 <mail@beyermatthias.de> and contributors
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; version
+// 2.1 of the License.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+//
+
+use std::result::Result as RResult;
+
+use error::HabitError;
+
+pub type Result<T> = RResult<T, HabitError>;
+
diff --git a/lib/domain/libimaghabit/src/store.rs b/lib/domain/libimaghabit/src/store.rs
new file mode 100644
index 0000000..5d30c03
--- /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 <mail@beyermatthias.de> and contributors
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; version
+// 2.1 of the License.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+//
+
+use chrono::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<HabitTemplateStoreIdIterator>;
+
+ /// Get instances
+ fn all_habit_instances(&self) -> Result<HabitInstanceStoreIdIterator>;
+
+ // /// Get instances of a certain date
+ // fn all_habit_instances_on(&self, date: &NaiveDate) -> Result<HabitInstanceStoreIdIterator>;
+
+ // /// Get instances between two dates
+ // fn all_habit_instances_between(&self, start: &NaiveDate, end: &NaiveDate) -> Result<HabitInstanceStoreIdIterator>;
+}
+
+impl HabitStore for Store {
+ /// Get an iterator over all habits
+ fn all_habit_templates(&self) -> Result<HabitTemplateStoreIdIterator> {
+ self.entries().map(HabitTemplateStoreIdIterator::from).map_err(From::from)
+ }
+
+ fn all_habit_instances(&self) -> Result<HabitInstanceStoreIdIterator> {
+ 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 0000000..4acd628
--- /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 <mail@beyermatthias.de> and contributors
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; version
+// 2.1 of the License.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+//
+
+use std::ops::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> {
+ 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<bool> {
+ self.is_habit_instance().and_then(|b| self.is_habit_template().map(|a| a.bitxor(b)))
+ }
+}
+
+impl<H> 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()
+ }
+}
+