summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthias Beyer <mail@beyermatthias.de>2019-06-29 22:53:06 +0200
committerMatthias Beyer <mail@beyermatthias.de>2019-11-09 17:35:06 +0100
commit775d3f0a809e46aa71f9b38e7bd58d76af973b5b (patch)
tree38dab9891936104e9da90741bfb1ace16fee10cc
parent21cc901d06c8ad5ab8bdc5f995296053e025d7d0 (diff)
Reimplement libimagtodo
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
-rw-r--r--lib/domain/libimagtodo/Cargo.toml32
-rw-r--r--lib/domain/libimagtodo/src/entry.rs138
-rw-r--r--lib/domain/libimagtodo/src/iter.rs133
-rw-r--r--lib/domain/libimagtodo/src/lib.rs44
-rw-r--r--lib/domain/libimagtodo/src/priority.rs68
-rw-r--r--lib/domain/libimagtodo/src/status.rs72
-rw-r--r--lib/domain/libimagtodo/src/store.rs152
7 files changed, 632 insertions, 7 deletions
diff --git a/lib/domain/libimagtodo/Cargo.toml b/lib/domain/libimagtodo/Cargo.toml
index 8b2b9f39..ace432dc 100644
--- a/lib/domain/libimagtodo/Cargo.toml
+++ b/lib/domain/libimagtodo/Cargo.toml
@@ -20,13 +20,31 @@ is-it-maintained-open-issues = { repository = "matthiasbeyer/imag" }
maintenance = { status = "actively-developed" }
[dependencies]
-task-hookrs = "0.6.0"
-uuid = "0.7.4"
-toml = "0.5.1"
-toml-query = "0.9.2"
-log = "0.4.6"
-serde_json = "1.0.39"
-failure = "0.1.5"
+failure = "0.1"
+filters = "0.3"
+log = "0.4"
+serde = "1"
+serde_derive = "1"
+serde_json = "1"
+toml = "0.5"
libimagstore = { version = "0.10.0", path = "../../../lib/core/libimagstore" }
libimagerror = { version = "0.10.0", path = "../../../lib/core/libimagerror" }
+libimagentryutil = { version = "0.10.0", path = "../../../lib/entry/libimagentryutil" }
+libimagutil = { version = "0.10.0", path = "../../../lib/etc/libimagutil" }
+
+[dependencies.toml-query]
+version = "0.9"
+default-features = false
+features = ["typed"]
+
+[dependencies.chrono]
+version = "0.4"
+default-features = false
+features = ["serde"]
+
+[dependencies.uuid]
+version = "0.7"
+default-features = false
+features = ["serde", "v4"]
+
diff --git a/lib/domain/libimagtodo/src/entry.rs b/lib/domain/libimagtodo/src/entry.rs
new file mode 100644
index 00000000..35fd9b3a
--- /dev/null
+++ b/lib/domain/libimagtodo/src/entry.rs
@@ -0,0 +1,138 @@
+//
+// imag - the personal information management suite for the commandline
+// Copyright (C) 2015-2019 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 libimagentryutil::isa::Is;
+use libimagentryutil::isa::IsKindHeaderPathProvider;
+use libimagstore::store::Entry;
+use libimagutil::date::datetime_from_string;
+
+use failure::Fallible as Result;
+use failure::Error;
+use failure::ResultExt;
+use chrono::NaiveDateTime;
+use toml_query::read::Partial;
+use toml_query::read::TomlValueReadExt;
+use toml_query::insert::TomlValueInsertExt;
+use uuid::Uuid;
+
+use crate::status::Status;
+use crate::priority::Priority;
+
+#[derive(Serialize, Deserialize, Debug)]
+pub(crate) struct TodoHeader {
+ pub(crate) uuid: Uuid,
+ pub(crate) status: Status,
+ pub(crate) scheduled: Option<String>,
+ pub(crate) hidden: Option<String>,
+ pub(crate) due: Option<String>,
+ pub(crate) priority: Option<Priority>,
+}
+
+impl<'a> Partial<'a> for TodoHeader {
+ const LOCATION: &'static str = "todo";
+ type Output = Self;
+}
+
+pub trait Todo {
+ fn is_todo(&self) -> Result<bool>;
+ fn get_uuid(&self) -> Result<Uuid>;
+ fn get_status(&self) -> Result<Status>;
+ fn set_status(&mut self, status: Status) -> Result<()>;
+ fn get_scheduled(&self) -> Result<Option<NaiveDateTime>>;
+ fn set_scheduled(&mut self, scheduled: NaiveDateTime) -> Result<()>;
+ fn get_hidden(&self) -> Result<Option<NaiveDateTime>>;
+ fn set_hidden(&mut self, hidden: NaiveDateTime) -> Result<()>;
+ fn get_due(&self) -> Result<Option<NaiveDateTime>>;
+ fn set_due(&mut self, due: NaiveDateTime) -> Result<()>;
+ fn get_priority(&self) -> Result<Option<Priority>>;
+ fn set_priority(&mut self, prio: Priority) -> Result<()>;
+}
+
+provide_kindflag_path!(pub IsTodo, "todo.is_todo");
+
+impl Todo for Entry {
+ fn is_todo(&self) -> Result<bool> {
+ self.is::<IsTodo>().context("Cannot check whether Entry is a todo").map_err(From::from)
+ }
+
+ fn get_uuid(&self) -> Result<Uuid> {
+ get_header(self).map(|hdr| hdr.uuid)
+ }
+
+ fn get_status(&self) -> Result<Status> {
+ get_header(self).map(|hdr| hdr.status)
+ }
+
+ fn set_status(&mut self, status: Status) -> Result<()> {
+ self.get_header_mut().insert_serialized("todo.status", status)?;
+ Ok(())
+ }
+
+ fn get_scheduled(&self) -> Result<Option<NaiveDateTime>> {
+ get_optional_ndt(self, |hdr| hdr.scheduled)
+ }
+
+ fn set_scheduled(&mut self, scheduled: NaiveDateTime) -> Result<()> {
+ self.get_header_mut().insert_serialized("todo.scheduled", scheduled)?;
+ Ok(())
+ }
+
+ fn get_hidden(&self) -> Result<Option<NaiveDateTime>> {
+ get_optional_ndt(self, |hdr| hdr.hidden)
+ }
+
+ fn set_hidden(&mut self, hidden: NaiveDateTime) -> Result<()> {
+ self.get_header_mut().insert_serialized("todo.hidden", hidden)?;
+ Ok(())
+ }
+
+ fn get_due(&self) -> Result<Option<NaiveDateTime>> {
+ get_optional_ndt(self, |hdr| hdr.due)
+ }
+
+ fn set_due(&mut self, due: NaiveDateTime) -> Result<()> {
+ self.get_header_mut().insert_serialized("todo.due", due)?;
+ Ok(())
+ }
+
+ fn get_priority(&self) -> Result<Option<Priority>> {
+ get_header(self).map(|hdr| hdr.priority)
+ }
+
+ fn set_priority(&mut self, priority: Priority) -> Result<()> {
+ self.get_header_mut().insert_serialized("todo.priority", priority)?;
+ Ok(())
+ }
+
+}
+
+fn get_header(entry: &Entry) -> Result<TodoHeader> {
+ entry.get_header()
+ .read_partial::<TodoHeader>()?
+ .ok_or_else(|| {
+ format_err!("{} does not contain a TODO header", entry.get_location())
+ })
+}
+
+fn get_optional_ndt<F>(entry: &Entry, extractor: F)
+ -> Result<Option<NaiveDateTime>>
+ where F: FnOnce(TodoHeader) -> Option<String>
+{
+ get_header(entry).map(extractor)?.map(datetime_from_string).transpose().map_err(Error::from)
+}
diff --git a/lib/domain/libimagtodo/src/iter.rs b/lib/domain/libimagtodo/src/iter.rs
new file mode 100644
index 00000000..61379964
--- /dev/null
+++ b/lib/domain/libimagtodo/src/iter.rs
@@ -0,0 +1,133 @@
+//
+// imag - the personal information management suite for the commandline
+// Copyright (C) 2015-2019 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 failure::Fallible as Result;
+use failure::Error;
+use filters::failable::filter::FailableFilter;
+
+use libimagstore::store::FileLockEntry;
+use libimagstore::store::Entry;
+
+use crate::entry::Todo;
+use crate::status::Status;
+use crate::priority::Priority;
+
+/// Iterator adaptor which filters an Iterator<Item = FileLockEntry> so that only todos are left
+pub struct OnlyTodos<'a>(Box<dyn Iterator<Item = FileLockEntry<'a>>>);
+
+impl<'a> OnlyTodos<'a> {
+ pub fn new(it: Box<dyn Iterator<Item = FileLockEntry<'a>>>) -> Self {
+ OnlyTodos(it)
+ }
+}
+
+impl<'a> Iterator for OnlyTodos<'a> {
+ type Item = Result<FileLockEntry<'a>>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ while let Some(next) = self.0.next() {
+ match next.is_todo() {
+ Ok(true) => return Some(Ok(next)),
+ Ok(false) => continue,
+ Err(e) => return Some(Err(e)),
+ }
+ }
+
+ None
+ }
+}
+
+/// Helper filter type
+///
+/// Can be used to filter an Iterator<Item = FileLockEntry> of Todos by status
+///
+pub struct StatusFilter(Status);
+
+impl FailableFilter<Entry> for StatusFilter {
+ type Error = Error;
+
+ fn filter(&self, entry: &Entry) -> RResult<bool, Self::Error> {
+ Ok(entry.get_status()? == self.0)
+ }
+}
+
+/// Helper filter type
+///
+/// Can be used to filter an Iterator<Item = FileLockEntry> of Todos for scheduled todos
+///
+pub struct IsScheduledFilter;
+
+impl FailableFilter<Entry> for IsScheduledFilter {
+ type Error = Error;
+
+ fn filter(&self, entry: &Entry) -> RResult<bool, Self::Error> {
+ entry.get_scheduled().map(|s| s.is_some())
+ }
+}
+
+/// Helper filter type
+///
+/// Can be used to filter an Iterator<Item = FileLockEntry> of Todos for hidden todos
+///
+pub struct IsHiddenFilter;
+
+impl FailableFilter<Entry> for IsHiddenFilter {
+ type Error = Error;
+
+ fn filter(&self, entry: &Entry) -> RResult<bool, Self::Error> {
+ entry.get_hidden().map(|s| s.is_some())
+ }
+}
+
+
+/// Helper filter type
+///
+/// Can be used to filter an Iterator<Item = FileLockEntry> of Todos for due todos
+///
+pub struct IsDueFilter;
+
+impl FailableFilter<Entry> for IsDueFilter {
+ type Error = Error;
+
+ fn filter(&self, entry: &Entry) -> RResult<bool, Self::Error> {
+ entry.get_due().map(|s| s.is_some())
+ }
+}
+
+
+/// Helper filter type
+///
+/// Can be used to filter an Iterator<Item = FileLockEntry> of Todos for priority
+///
+/// # Warning
+///
+/// If no priority is set for the entry, this filters out the entry
+///
+pub struct PriorityFilter(Priority);
+
+impl FailableFilter<Entry> for PriorityFilter {
+ type Error = Error;
+
+ fn filter(&self, entry: &Entry) -> RResult<bool, Self::Error> {
+ Ok(entry.get_priority()?.map(|p| p == self.0).unwrap_or(false))
+ }
+}
+
diff --git a/lib/domain/libimagtodo/src/lib.rs b/lib/domain/libimagtodo/src/lib.rs
new file mode 100644
index 00000000..8901077d
--- /dev/null
+++ b/lib/domain/libimagtodo/src/lib.rs
@@ -0,0 +1,44 @@
+//
+// imag - the personal information management suite for the commandline
+// Copyright (C) 2015-2019 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 serde;
+extern crate toml;
+extern crate toml_query;
+extern crate uuid;
+extern crate filters;
+
+#[macro_use] extern crate serde_derive;
+#[macro_use] extern crate failure;
+#[macro_use] extern crate log;
+
+extern crate libimagerror;
+extern crate libimagutil;
+#[macro_use] extern crate libimagstore;
+#[macro_use] extern crate libimagentryutil;
+
+pub mod entry;
+pub mod iter;
+pub mod priority;
+pub mod status;
+pub mod store;
+
+module_entry_path_mod!("todo");
+
diff --git a/lib/domain/libimagtodo/src/priority.rs b/lib/domain/libimagtodo/src/priority.rs
new file mode 100644
index 00000000..e612093d
--- /dev/null
+++ b/lib/domain/libimagtodo/src/priority.rs
@@ -0,0 +1,68 @@
+//
+// imag - the personal information management suite for the commandline
+// Copyright (C) 2015-2019 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::cmp::PartialOrd;
+use std::cmp::Ordering;
+
+#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
+pub enum Priority {
+ #[serde(rename = "h")]
+ High,
+
+ #[serde(rename = "m")]
+ Medium,
+
+ #[serde(rename = "l")]
+ Low,
+}
+
+impl Priority {
+ pub fn as_str(&self) -> &str {
+ match self {
+ Priority::High => "h",
+ Priority::Medium => "m",
+ Priority::Low => "l",
+ }
+ }
+}
+
+impl PartialOrd for Priority {
+ fn partial_cmp(&self, other: &Priority) -> Option<Ordering> {
+ Some(match (self, other) {
+ (Priority::Low, Priority::Low) => Ordering::Equal,
+ (Priority::Low, Priority::Medium) => Ordering::Less,
+ (Priority::Low, Priority::High) => Ordering::Less,
+
+ (Priority::Medium, Priority::Low) => Ordering::Greater,
+ (Priority::Medium, Priority::Medium) => Ordering::Equal,
+ (Priority::Medium, Priority::High) => Ordering::Less,
+
+ (Priority::High, Priority::Low) => Ordering::Greater,
+ (Priority::High, Priority::Medium) => Ordering::Greater,
+ (Priority::High, Priority::High) => Ordering::Equal,
+ })
+ }
+}
+
+impl Ord for Priority {
+ fn cmp(&self, other: &Self) -> Ordering {
+ self.partial_cmp(&other).unwrap() // save by impl above
+ }
+}
+
diff --git a/lib/domain/libimagtodo/src/status.rs b/lib/domain/libimagtodo/src/status.rs
new file mode 100644
index 00000000..63d8fa03
--- /dev/null
+++ b/lib/domain/libimagtodo/src/status.rs
@@ -0,0 +1,72 @@
+//
+// imag - the personal information management suite for the commandline
+// Copyright (C) 2015-2019 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 failure::Fallible as Result;
+
+#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
+pub enum Status {
+ #[serde(rename = "pending")]
+ Pending,
+
+ #[serde(rename = "done")]
+ Done,
+
+ #[serde(rename = "deleted")]
+ Deleted,
+}
+
+impl Status {
+ pub fn as_str(&self) -> &str {
+ match self {
+ Status::Pending => "pending",
+ Status::Done => "done",
+ Status::Deleted => "deleted",
+ }
+ }
+
+ pub fn from_str(s: &str) -> Result<Self> {
+ match s {
+ "pending" => Ok(Status::Pending),
+ "done" => Ok(Status::Done),
+ "deleted" => Ok(Status::Deleted),
+ other => Err(format_err!("{} is not a valid status", other)),
+ }
+ }
+}
+
+#[test]
+fn test_serializing() {
+ assert_eq!(Status::Pending.as_str(), "pending");
+ assert_eq!(Status::Done.as_str(), "done");
+ assert_eq!(Status::Deleted.as_str(), "deleted");
+}
+
+#[test]
+fn test_deserializing() {
+ assert_eq!(Status::from_str("pending").unwrap(), Status::Pending);
+ assert_eq!(Status::from_str("done").unwrap(), Status::Done);
+ assert_eq!(Status::from_str("deleted").unwrap(), Status::Deleted);
+}
+
+#[test]
+fn test_serializing_deserializing() {
+ assert_eq!(Status::Pending.as_str(), Status::from_str("pending").unwrap().as_str());
+ assert_eq!(Status::Done.as_str(), Status::from_str("done").unwrap().as_str());
+ assert_eq!(Status::Deleted.as_str(), Status::from_str("deleted").unwrap().as_str());
+}
diff --git a/lib/domain/libimagtodo/src/store.rs b/lib/domain/libimagtodo/src/store.rs
new file mode 100644
index 00000000..cc1f2aa0
--- /dev/null
+++ b/lib/domain/libimagtodo/src/store.rs
@@ -0,0 +1,152 @@
+//
+// imag - the personal information management suite for the commandline
+// Copyright (C) 2015-2019 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 failure::Fallible as Result;
+use chrono::NaiveDateTime;
+use uuid::Uuid;
+use toml_query::insert::TomlValueInsertExt;
+
+use libimagstore::store::FileLockEntry;
+use libimagstore::store::Store;
+use libimagstore::iter::Entries;
+use libimagutil::date::datetime_to_string;
+use libimagentryutil::isa::Is;
+
+use crate::status::Status;
+use crate::priority::Priority;
+use crate::entry::TodoHeader;
+use crate::entry::IsTodo;
+
+pub trait TodoStore<'a> {
+ fn create_todo(&'a self,
+ status: Status,
+ scheduled: Option<NaiveDateTime>,
+ hidden: Option<NaiveDateTime>,
+ due: Option<NaiveDateTime>,
+ prio: Option<Priority>,
+ check_sanity: bool) -> Result<FileLockEntry<'a>>;
+
+ fn get_todo_by_uuid(&'a self, uuid: &Uuid) -> Result<Option<FileLockEntry<'a>>>;
+
+ fn get_todos(&self) -> Result<Entries>;
+}
+
+impl<'a> TodoStore<'a> for Store {
+
+ /// Create a new todo entry
+ ///
+ /// # Warning
+ ///
+ /// If check_sanity is set to false, this does not sanity-check the scheduled/hidden/due dates.
+ /// This might result in unintended behaviour (hidden after due date, scheduled before hidden
+ /// date... etc)
+ ///
+ /// An user of this function might want to use `date_sanity_check()` to perform sanity checks
+ /// before calling TodoStore::create_todo() and show the Err(String) as a warning to user in an
+ /// interactive way.
+ fn create_todo(&'a self,
+ status: Status,
+ scheduled: Option<NaiveDateTime>,
+ hidden: Option<NaiveDateTime>,
+ due: Option<NaiveDateTime>,
+ prio: Option<Priority>,
+ check_sanity: bool) -> Result<FileLockEntry<'a>>
+ {
+ if check_sanity {
+ trace!("Checking sanity before creating todo");
+ if let Err(s) = date_sanity_check(scheduled.as_ref(), hidden.as_ref(), due.as_ref()) {
+ trace!("Not sane.");
+ return Err(format_err!("{}", s))
+ }
+ }
+
+ let uuid = Uuid::new_v4();
+ let uuid_s = format!("{}", uuid.to_hyphenated_ref()); // TODO: not how it is supposed to be
+ debug!("Created new UUID for todo = {}", uuid_s);
+
+ let mut entry = crate::module_path::new_id(uuid_s).and_then(|id| self.create(id))?;
+
+ let header = TodoHeader {
+ uuid,
+ status,
+ scheduled: scheduled.as_ref().map(datetime_to_string),
+ hidden: hidden.as_ref().map(datetime_to_string),
+ due: due.as_ref().map(datetime_to_string),
+ priority: prio
+ };
+
+ debug!("Created header for todo: {:?}", header);
+
+ let _ = entry.get_header_mut().insert_serialized("todo", header)?;
+ let _ = entry.set_isflag::<IsTodo>()?;
+
+ Ok(entry)
+ }
+
+ fn get_todo_by_uuid(&'a self, uuid: &Uuid) -> Result<Option<FileLockEntry<'a>>> {
+ let uuid_s = format!("{}", uuid.to_hyphenated_ref()); // TODO: not how it is supposed to be
+ debug!("Created new UUID for todo = {}", uuid_s);
+ let id = crate::module_path::new_id(uuid_s)?;
+ self.get(id)
+ }
+
+ /// Get all todos using Store::entries()
+ fn get_todos(&self) -> Result<Entries> {
+ self.entries().and_then(|es| es.in_collection("todo"))
+ }
+}
+
+/// Perform a sanity check on the scheduled/hidden/due dates
+///
+/// This function returns a String as error, which can be shown as a warning to the user or as an
+/// error.
+pub fn date_sanity_check(scheduled: Option<&NaiveDateTime>,
+ hidden: Option<&NaiveDateTime>,
+ due: Option<&NaiveDateTime>)
+ -> RResult<(), String>
+{
+ if let (Some(sched), Some(hid)) = (scheduled.as_ref(), hidden.as_ref()) {
+ if sched > hid {
+ return Err(format!("Scheduled date after hidden date: {s}, {h}",
+ s = sched,
+ h = hid))
+ }
+ }
+
+ if let (Some(hid), Some(due)) = (hidden.as_ref(), due.as_ref()) {
+ if hid > due {
+ return Err(format!("Hidden date after due date: {h}, {d}",
+ h = hid,
+ d = due))
+ }
+ }
+
+ if let (Some(sched), Some(due)) = (scheduled.as_ref(), due.as_ref()) {
+ if sched > due {
+ return Err(format!("Scheduled date after due date: {s}, {d}",
+ s = sched,
+ d = due))
+ }
+ }
+
+ Ok(())
+}
+