Merge pull request #1042 from matthiasbeyer/libimaghabit/init
libimaghabit: init
This commit is contained in:
commit
3d96170021
|
@ -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",
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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" }
|
|
@ -0,0 +1 @@
|
|||
../../../doc/src/05100-lib-habit.md
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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()),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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>;
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue