summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthias Beyer <mail@beyermatthias.de>2018-01-05 20:36:57 +0100
committerMatthias Beyer <mail@beyermatthias.de>2018-11-11 13:16:42 +0100
commit5336077733e72e8c57b8c1954d7d1233ed2d6f63 (patch)
tree4ea4f9263b67ef5622754ac64b00ec52914352c0
parent4e516ee19d682e77fa72a93cf15e3b8832346fbb (diff)
libimagflashcard: Initial import
-rw-r--r--Cargo.toml1
-rw-r--r--doc/src/04020-module-flashcards.md6
-rw-r--r--doc/src/05100-lib-flashcard.md59
-rw-r--r--doc/src/05100-lib-flashcards.md2
-rw-r--r--lib/domain/libimagflashcard/Cargo.toml30
l---------lib/domain/libimagflashcard/README.md1
-rw-r--r--lib/domain/libimagflashcard/src/card.rs78
-rw-r--r--lib/domain/libimagflashcard/src/error.rs49
-rw-r--r--lib/domain/libimagflashcard/src/group.rs130
-rw-r--r--lib/domain/libimagflashcard/src/iter.rs118
-rw-r--r--lib/domain/libimagflashcard/src/lib.rs42
-rw-r--r--lib/domain/libimagflashcard/src/pathes.rs41
-rw-r--r--lib/domain/libimagflashcard/src/session.rs109
-rw-r--r--lib/domain/libimagflashcard/src/store.rs60
-rw-r--r--lib/etc/libimagutil/src/date.rs12
15 files changed, 730 insertions, 8 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 742f533..56d2271 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -32,6 +32,7 @@ members = [
"lib/domain/libimagbookmark",
"lib/domain/libimagcontact",
"lib/domain/libimagdiary",
+ "lib/domain/libimagflashcard",
"lib/domain/libimaghabit",
"lib/domain/libimaglog",
"lib/domain/libimagmail",
diff --git a/doc/src/04020-module-flashcards.md b/doc/src/04020-module-flashcards.md
new file mode 100644
index 0000000..a628e13
--- /dev/null
+++ b/doc/src/04020-module-flashcards.md
@@ -0,0 +1,6 @@
+## Flashcards {#sec:modules:flashcards}
+
+The flashcards module implements "flashcards-learning" like you probably did it
+in school.
+
+
diff --git a/doc/src/05100-lib-flashcard.md b/doc/src/05100-lib-flashcard.md
new file mode 100644
index 0000000..865c47d
--- /dev/null
+++ b/doc/src/05100-lib-flashcard.md
@@ -0,0 +1,59 @@
+## libimagflashcard
+
+Flash card program.
+
+Asks the user a question and uses heuristics to check whether the answer is
+correct (string matching).
+
+Each flashcard is one imag entry. Entries can be grouped together (imag
+links). A flashcard can be in several groups.
+The Sessions-Feature can be used to track progress and add a value to each
+flashcard, so when starting a new learning session, better learned cards show
+up less often.
+
+### Architecture
+
+The architecture is layed out as follows:
+
+```
++-------+ +-------+ +------+
+| Store +------>+ Group +------>+ Card |
++-------+ +---+---+ +------+
+ |
+ |
+ v
+ +----+----+
+ | Session |
+ +---------+
+```
+
+Via the `Store`, a `Group` can be fetched/created.
+Via a `Group`, `Card`s can be created.
+A `Group` can also be used to create a `Session`, which records the current
+learning state (which card was answered correctly, which was answered wrong
+during a learning-session).
+
+A `Group` and a `Card` are linked (via `libimagentrylink`).
+A `Session` is linked to both its `Group` (via `libimagentrylink`)
+and all `Cards` accessed during the session.
+The link from a `Session` to the `Card` objects is not done via
+`libimagentrylink` because the `Session` object must store a list of correctly
+and wrong answered questions, so it holds the names of the `Card`s in a
+non-`libimagentrylink` way.
+
+### Names
+
+A `Group` has a StoreId at `flashcard/<groupname>/group`.
+All `Card`s of the group are stored in
+
+```
+flashcard/<group name>/cards/<hash of the question>
+```
+
+Thus, each question is unique in its group.
+`Session` objects are located in
+
+```
+flashcard/sessions/<group name>/<datetime>
+```
+
diff --git a/doc/src/05100-lib-flashcards.md b/doc/src/05100-lib-flashcards.md
new file mode 100644
index 0000000..af8c2e7
--- /dev/null
+++ b/doc/src/05100-lib-flashcards.md
@@ -0,0 +1,2 @@
+## libimagflashcards
+
diff --git a/lib/domain/libimagflashcard/Cargo.toml b/lib/domain/libimagflashcard/Cargo.toml
new file mode 100644
index 0000000..74447f2
--- /dev/null
+++ b/lib/domain/libimagflashcard/Cargo.toml
@@ -0,0 +1,30 @@
+[package]
+name = "libimagflashcard"
+version = "0.8.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"
+toml = "0.4"
+toml-query = "0.6.0"
+is-match = "0.1"
+error-chain = "0.11"
+filters = "0.2"
+
+libimagentryedit = { version = "0.8.0", path = "../../../lib/entry/libimagentryedit" }
+libimagentrylink = { version = "0.8.0", path = "../../../lib/entry/libimagentrylink" }
+libimagentryutil = { version = "0.8.0", path = "../../../lib/entry/libimagentryutil" }
+libimagerror = { version = "0.8.0", path = "../../../lib/core/libimagerror" }
+libimagstore = { version = "0.8.0", path = "../../../lib/core/libimagstore" }
+libimagutil = { version = "0.8.0", path = "../../../lib/etc/libimagutil" }
+
diff --git a/lib/domain/libimagflashcard/README.md b/lib/domain/libimagflashcard/README.md
new file mode 120000
index 0000000..2364057
--- /dev/null
+++ b/lib/domain/libimagflashcard/README.md
@@ -0,0 +1 @@
+../../../doc/src/05100-lib-flashcard.md \ No newline at end of file
diff --git a/lib/domain/libimagflashcard/src/card.rs b/lib/domain/libimagflashcard/src/card.rs
new file mode 100644
index 0000000..3b9c1c5
--- /dev/null
+++ b/lib/domain/libimagflashcard/src/card.rs
@@ -0,0 +1,78 @@
+//
+// imag - the personal information management suite for the commandline
+// Copyright (C) 2015-2018 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_query::read::TomlValueReadExt;
+use toml::Value;
+
+use libimagstore::store::Entry;
+use libimagstore::storeid::StoreId;
+use libimagstore::storeid::StoreIdIterator;
+use libimagentryutil::isa::Is;
+use libimagentryutil::isa::IsKindHeaderPathProvider;
+
+provide_kindflag_path!(pub IsCard, "flashcard.is_card");
+
+use error::Result;
+use error::FlashcardError as FE;
+use error::FlashcardErrorKind as FEK;
+
+pub trait Card {
+
+ fn is_card(&self) -> Result<bool>;
+ fn question(&self) -> Result<String>;
+ fn answers(&self) -> Result<Vec<String>>;
+
+}
+
+impl Card for Entry {
+
+ fn is_card(&self) -> Result<bool> {
+ self.is::<IsCard>().map_err(From::from)
+ }
+
+ fn question(&self) -> Result<String> {
+ let field = "flashcard.card.question";
+
+ match self.get_header().read(field).map_err(FE::from)? {
+ Some(&Value::String(ref s)) => Ok(s.clone()),
+ None => Err(FEK::HeaderFieldMissing(field)),
+ Some(_) => Err(FEK::HeaderTypeError("String")),
+ }.map_err(FE::from)
+ }
+
+ fn answers(&self) -> Result<Vec<String>> {
+ let field = "flashcard.card.answers";
+
+ match self.get_header().read(field).map_err(FE::from)? {
+ Some(&Value::Array(ref a)) => {
+ let mut res = vec![];
+ for elem in a {
+ match *elem {
+ Value::String(ref s) => res.push(s.clone()),
+ _ => return Err(FEK::HeaderTypeError("Array<String>").into()),
+ }
+ }
+ Ok(res)
+ },
+ None => Err(FEK::HeaderFieldMissing(field)),
+ Some(_) => Err(FEK::HeaderTypeError("String")),
+ }.map_err(FE::from)
+ }
+
+}
diff --git a/lib/domain/libimagflashcard/src/error.rs b/lib/domain/libimagflashcard/src/error.rs
new file mode 100644
index 0000000..097d8b2
--- /dev/null
+++ b/lib/domain/libimagflashcard/src/error.rs
@@ -0,0 +1,49 @@
+//
+// imag - the personal information management suite for the commandline
+// Copyright (C) 2015-2018 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 {
+ FlashcardError, FlashcardErrorKind, ResultExt, Result;
+ }
+
+ links {
+ StoreError(::libimagstore::error::StoreError, ::libimagstore::error::StoreErrorKind);
+ LinkError(::libimagentrylink::error::LinkError, ::libimagentrylink::error::LinkErrorKind);
+ EntryUtilError(::libimagentryutil::error::EntryUtilError, ::libimagentryutil::error::EntryUtilErrorKind);
+ }
+
+ foreign_links {
+ TomlQueryError(::toml_query::error::Error);
+ ChronoError(::chrono::format::ParseError);
+ }
+
+ errors {
+ HeaderTypeError(expected: &'static str) {
+ description("Header field type error")
+ display("Header field type error, expected '{}'", expected)
+ }
+
+ HeaderFieldMissing(name: &'static str) {
+ description("Header field missing")
+ display("Header field missing: '{}'", name)
+ }
+ }
+}
+
+
diff --git a/lib/domain/libimagflashcard/src/group.rs b/lib/domain/libimagflashcard/src/group.rs
new file mode 100644
index 0000000..f785ca3
--- /dev/null
+++ b/lib/domain/libimagflashcard/src/group.rs
@@ -0,0 +1,130 @@
+//
+// imag - the personal information management suite for the commandline
+// Copyright (C) 2015-2018 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::path::PathBuf;
+
+use libimagstore::store::Store;
+use libimagstore::store::Entry;
+use libimagstore::store::FileLockEntry;
+use libimagstore::storeid::IntoStoreId;
+use libimagstore::storeid::StoreId;
+use libimagstore::storeid::StoreIdIterator;
+use libimagentryutil::isa::Is;
+use libimagentryutil::isa::IsKindHeaderPathProvider;
+use libimagentrylink::internal::InternalLinker;
+
+use toml::Value;
+use toml_query::read::TomlValueReadExt;
+use toml_query::insert::TomlValueInsertExt;
+
+use error::Result;
+use error::FlashcardErrorKind as FCEK;
+use iter::CardsInGroup;
+use iter::SessionsForGroup;
+use card::IsCard;
+use pathes::mk_card_path;
+use pathes::mk_session_path;
+
+provide_kindflag_path!(pub IsCardGroup, "flashcard.is_group");
+
+pub trait CardGroup {
+ // Based on libimagentrylink
+
+ fn is_cardgroup(&self) -> Result<bool>;
+ fn group_name(&self) -> Result<String>;
+
+ fn create_card<'a>(&mut self, store: &'a Store, question: String, answers: Vec<String>)
+ -> Result<FileLockEntry<'a>>;
+
+ fn get_cards<'a>(&self, store: &Store) -> Result<CardsInGroup>;
+
+ fn make_session<'a>(&mut self, store: &'a Store) -> Result<FileLockEntry<'a>>;
+
+ fn sessions<'a>(&mut self, store: &'a Store) -> Result<SessionsForGroup>;
+
+ // TODO: Some stat-functions for the group
+ // like percent learned
+ // no of cards
+ // no of learned cards
+ // etc
+
+}
+
+impl CardGroup for Entry {
+ fn is_cardgroup(&self) -> Result<bool> {
+ self.is::<IsCardGroup>().map_err(From::from)
+ }
+
+ fn group_name(&self) -> Result<String> {
+ match self.get_header().read("flashcard.group.name")? {
+ Some(&Value::String(ref s)) => Ok(s.clone()),
+ Some(_) => Err(FCEK::HeaderTypeError("string")),
+ None => Err(FCEK::HeaderFieldMissing("flashcard.group.name")),
+ }.map_err(Into::into)
+ }
+
+ fn create_card<'a>(&mut self, store: &'a Store, question: String, answers: Vec<String>)
+ -> Result<FileLockEntry<'a>>
+ {
+ let name = self.group_name()?;
+ let cardpath = mk_card_path(&name, &question)?;
+ let id = ::module_path::ModuleEntryPath::new(cardpath).into_storeid()?;
+ let mut card = store.create(id)?;
+
+ card.set_isflag::<IsCard>()?;
+ {
+ let hdr = card.get_header_mut();
+ let answers = answers.into_iter().map(Value::String).collect();
+
+ let _ = hdr.insert("flashcard.card.question", Value::String(question))?;
+ let _ = hdr.insert("flashcard.card.answers", Value::Array(answers))?;
+ }
+
+ let _ = self.add_internal_link(&mut card)?;
+ Ok(card)
+ }
+
+ fn get_cards<'a>(&self, store: &Store) -> Result<CardsInGroup> {
+ Ok(CardsInGroup::new(store.entries()?.without_store(), self.group_name()?))
+ }
+
+ fn make_session<'a>(&mut self, store: &'a Store) -> Result<FileLockEntry<'a>> {
+ use session::Session;
+ use session::IsSession;
+ use libimagutil::date::datetime_to_string;
+ use module_path::ModuleEntryPath;
+
+ let gname = self.group_name()?;
+ let now = ::chrono::offset::Local::now().naive_local();
+ let id = mk_session_path(&gname, &now);
+ let id = ModuleEntryPath::new(id).into_storeid()?;
+ let mut fle = store.create(id)?;
+ let _ = fle.set_isflag::<IsSession>()?;
+ let _ = fle.start()?;
+ let _ = fle.get_header_mut().insert("flashcard.group.name", Value::String(gname))?;
+ let _ = self.add_internal_link(&mut fle)?;
+ Ok(fle)
+ }
+
+ fn sessions<'a>(&mut self, store: &'a Store) -> Result<SessionsForGroup> {
+ Ok(SessionsForGroup::new(store.entries()?.without_store(), self.group_name()?))
+ }
+
+}
+
diff --git a/lib/domain/libimagflashcard/src/iter.rs b/lib/domain/libimagflashcard/src/iter.rs
new file mode 100644
index 0000000..c486725
--- /dev/null
+++ b/lib/domain/libimagflashcard/src/iter.rs
@@ -0,0 +1,118 @@
+//
+// imag - the personal information management suite for the commandline
+// Copyright (C) 2015-2018 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 libimagstore::storeid::StoreIdIterator;
+use libimagstore::storeid::StoreId;
+
+/// Iterator which gets all "group" files for all groups
+pub struct CardGroupIds(StoreIdIterator);
+
+impl CardGroupIds {
+ pub fn new(inner: StoreIdIterator) -> Self {
+ CardGroupIds(inner)
+ }
+}
+
+impl Iterator for CardGroupIds {
+ type Item = StoreId;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ while let Some(next) = self.0.next() {
+ // is in collection "flashcard/groups" and ends with "group"
+ if next.is_in_collection(&["flashcard", "groups"]) && next.local().ends_with("group") {
+ return Some(next);
+ }
+ }
+
+ None
+ }
+}
+
+/// Iterator which gets all cards for a group
+pub struct CardsInGroup {
+ inner: StoreIdIterator,
+ groupname: String,
+}
+
+impl CardsInGroup {
+ pub(crate) fn new(inner: StoreIdIterator, groupname: String) -> Self {
+ CardsInGroup { inner, groupname }
+ }
+}
+
+impl Iterator for CardsInGroup {
+ type Item = StoreId;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ while let Some(next) = self.inner.next() {
+ if next.is_in_collection(&["flashcard", "groups", &self.groupname, "cards"]) {
+ return Some(next);
+ }
+ }
+
+ None
+ }
+}
+
+/// Iterator which gets all sessions
+pub struct SessionIds(StoreIdIterator);
+
+impl SessionIds {
+ pub fn new(inner: StoreIdIterator) -> Self {
+ SessionIds(inner)
+ }
+}
+
+impl Iterator for SessionIds {
+ type Item = StoreId;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ while let Some(next) = self.0.next() {
+ if next.is_in_collection(&["flashcard", "sessions"]) {
+ return Some(next);
+ }
+ }
+
+ None
+ }
+}
+
+/// Iterator which gets all sessions for a group
+pub struct SessionsForGroup(StoreIdIterator, String);
+
+impl SessionsForGroup {
+ pub fn new (inner: StoreIdIterator, groupname: String) -> Self {
+ SessionsForGroup(inner, groupname)
+ }
+}
+
+impl Iterator for SessionsForGroup {
+ type Item = StoreId;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ while let Some(next) = self.0.next() {
+ if next.is_in_collection(&["flashcard", "sessions", &self.1]) {
+ return Some(next);
+ }
+ }
+
+ None
+ }
+}
+
diff --git a/lib/domain/libimagflashcard/src/lib.rs b/lib/domain/libimagflashcard/src/lib.rs
new file mode 100644
index 0000000..ada0463
--- /dev/null
+++ b/lib/domain/libimagflashcard/src/lib.rs
@@ -0,0 +1,42 @@
+//
+// imag - the personal information management suite for the commandline
+// Copyright (C) 2015-2018 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
+//
+
+#[macro_use] extern crate error_chain;
+#[macro_use] extern crate is_match;
+extern crate filters;
+extern crate toml_query;
+extern crate chrono;
+extern crate toml;
+
+#[macro_use] extern crate libimagstore;
+extern crate libimagerror;
+extern crate libimagentrylink;
+#[macro_use] extern crate libimagentryutil;
+extern crate libimagutil;
+
+module_entry_path_mod!("flashcard");
+
+pub mod card;
+pub mod error;
+pub mod group;
+pub mod iter;
+pub mod pathes;
+pub mod session;
+pub mod store;
+
diff --git a/lib/domain/libimagflashcard/src/pathes.rs b/lib/domain/libimagflashcard/src/pathes.rs
new file mode 100644
index 0000000..cb64e81
--- /dev/null
+++ b/lib/domain/libimagflashcard/src/pathes.rs
@@ -0,0 +1,41 @@
+//
+// imag - the personal information management suite for the commandline
+// Copyright (C) 2015-2018 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::path::PathBuf;
+
+use chrono::NaiveDateTime;
+
+use libimagutil::date::datetime_to_string;
+
+use error::Result;
+
+pub fn mk_group_path(name: &String) -> PathBuf {
+ PathBuf::from(format!("groups/{}/group", name))
+}
+
+pub fn mk_session_path(groupname: &String, datetime: &NaiveDateTime) -> PathBuf {
+ let datetime = datetime_to_string(datetime);
+ PathBuf::from(format!("sessions/{group}/{dt}", group = groupname, dt = datetime))
+}
+
+pub fn mk_card_path(groupname: &String, question: &String) -> Result<PathBuf> {
+ // let question = hashof(question); TODO: Hash me
+ Ok(PathBuf::from(format!("groups/{group}/cards/{id}", group = groupname, id = question)))
+}
+
diff --git a/lib/domain/libimagflashcard/src/session.rs b/lib/domain/libimagflashcard/src/session.rs
new file mode 100644
index 0000000..2912695
--- /dev/null
+++ b/lib/domain/libimagflashcard/src/session.rs
@@ -0,0 +1,109 @@
+//
+// imag - the personal information management suite for the commandline
+// Copyright (C) 2015-2018 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 libimagstore::storeid::StoreIdIterator;
+use libimagstore::store::FileLockEntry;
+use libimagstore::store::Store;
+use libimagstore::store::Entry;
+use libimagstore::storeid::StoreId;
+use libimagentryutil::isa::Is;
+use libimagentryutil::isa::IsKindHeaderPathProvider;
+use libimagutil::date::datetime_to_string;
+use libimagutil::date::datetime_from_string;
+
+provide_kindflag_path!(pub IsSession, "flashcard.is_session");
+
+use chrono::NaiveDateTime;
+use toml::Value;
+use toml_query::insert::TomlValueInsertExt;
+use toml_query::read::TomlValueReadExt;
+
+use error::Result;
+use error::FlashcardErrorKind as FCEK;
+use card::Card;
+
+pub trait Session {
+ fn is_session(&self) -> Result<bool>;
+
+ fn start_at(&mut self, ndt: &NaiveDateTime) -> Result<()>;
+ fn end_at(&mut self, ndt: &NaiveDateTime) -> Result<()>;
+
+ fn start(&mut self) -> Result<()> {
+ let now = ::chrono::offset::Local::now().naive_local();
+ self.start_at(&now)
+ }
+
+ fn end(&mut self) -> Result<()> {
+ let now = ::chrono::offset::Local::now().naive_local();
+ self.end_at(&now)
+ }
+
+ fn started_at(&self) -> Result<Option<NaiveDateTime>>;
+ fn ended_at(&self) -> Result<Option<NaiveDateTime>>;
+
+ fn answer(&mut self, card: &Card, answer: &str) -> Result<bool>;
+
+ /// Get the group this session was created for.
+ fn group<'a>(&self, store: &'a Store) -> Result<FileLockEntry<'a>>;
+}
+
+impl Session for Entry {
+ fn is_session(&self) -> Result<bool> {
+ self.is::<IsSession>().map_err(From::from)
+ }
+
+ fn start_at(&mut self, ndt: &NaiveDateTime) -> Result<()> {
+ self.get_header_mut()
+ .insert("flashcard.session.start", Value::String(datetime_to_string(ndt)))
+ .map(|_| ())
+ .map_err(From::from)
+ }
+
+ fn end_at(&mut self, ndt: &NaiveDateTime) -> Result<()> {
+ self.get_header_mut()
+ .insert("flashcard.session.end", Value::String(datetime_to_string(ndt)))
+ .map(|_| ())
+ .map_err(From::from)
+ }
+
+ fn started_at(&self) -> Result<Option<NaiveDateTime>> {
+ match self.get_header().read("flashcard.session.start")? {
+ Some(&Value::String(ref s)) => datetime_from_string(s).map(Some).map_err(From::from),
+ Some(_) => Err(FCEK::HeaderTypeError("string").into()),
+ None => Err(FCEK::HeaderFieldMissing("flashcard.session.start").into())
+ }
+ }
+
+ fn ended_at(&self) -> Result<Option<NaiveDateTime>> {
+ match self.get_header().read("flashcard.session.end")? {
+ Some(&Value::String(ref s)) => datetime_from_string(s).map(Some).map_err(From::from),
+ Some(_) => Err(FCEK::HeaderTypeError("string").into()),
+ None => Err(FCEK::HeaderFieldMissing("flashcard.session.end").into())
+ }
+ }
+
+ fn answer(&mut self, card: &Card, answer: &str) -> Result<bool> {
+ unimplemented!()
+ }
+
+ fn group<'a>(&self, store: &'a Store) -> Result<FileLockEntry<'a>> {
+ unimplemented!()
+ }
+}
+
diff --git a/lib/domain/libimagflashcard/src/store.rs b/lib/domain/libimagflashcard/src/store.rs
new file mode 100644
index 0000000..96eb407
--- /dev/null
+++ b/lib/domain/libimagflashcard/src/store.rs
@@ -0,0 +1,60 @@
+//
+// imag - the personal information management suite for the commandline
+// Copyright (C) 2015-2018 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::path::PathBuf;
+
+use libimagstore::storeid::StoreIdIterator;
+use libimagstore::store::Store;
+use libimagstore::store::FileLockEntry;
+use libimagstore::storeid::IntoStoreId;
+
+use group::CardGroup;
+use iter::CardGroupIds;
+use iter::SessionIds;
+use error::Result;
+use pathes::mk_group_path;
+
+pub trait CardStore<'a> {
+ fn new_group(&'a self, name: &String) -> Result<FileLockEntry<'a>>;
+ fn get_group_by_name(&'a self, name: &String) -> Result<Option<FileLockEntry<'a>>>;
+ fn all_groups(&self) -> Result<CardGroupIds>;
+ fn all_sessions(&self) -> Result<SessionIds>;
+}
+
+impl<'a> CardStore<'a> for Store {
+
+ fn new_group(&'a self, name: &String) -> Result<FileLockEntry<'a>> {
+ let id = ::module_path::ModuleEntryPath::new(mk_group_path(name)).into_storeid()?;
+ self.create(id).map_err(From::from)
+ }
+
+ fn get_group_by_name(&'a self, name: &String) -> Result<Option<FileLockEntry<'a>>> {
+ let id = ::module_path::ModuleEntryPath::new(mk_group_path(name)).into_storeid()?;
+ self.get(id).map_err(From::from)
+ }
+
+ fn all_groups(&self) -> Result<CardGroupIds> {
+ self.entries().map(|it| CardGroupIds::new(it.without_store())).map_err(From::from)
+ }
+
+ fn all_sessions(&self) -> Result<SessionIds> {
+ self.entries().map(|it| SessionIds::new(it.without_store())).map_err(From::from)
+ }
+
+}
diff --git a/lib/etc/libimagutil/src/date.rs b/lib/etc/libimagutil/src/date.rs
index edbd8ac..37e192f 100644
--- a/lib/etc/libimagutil/src/date.rs
+++ b/lib/etc/libimagutil/src/date.rs
@@ -27,10 +27,8 @@ pub fn date_to_string(ndt: &NaiveDate) -> String {
ndt.format(NAIVE_DATE_STRING_FORMAT).to_string()
}
-pub fn date_from_string<S>(s: S) -> Result<NaiveDate, ParseError>
- where S: AsRef<str>
-{
- NaiveDate::parse_from_str(s.as_ref(), NAIVE_DATE_STRING_FORMAT)
+pub fn date_from_string(s: String) -> Result<NaiveDate, ParseError> {
+ NaiveDate::parse_from_str(&s, NAIVE_DATE_STRING_FORMAT)
}
pub const NAIVE_DATETIME_STRING_FORMAT : &str = "%Y-%m-%d %H:%M:%S";
@@ -39,9 +37,7 @@ pub fn datetime_to_string(ndt: &NaiveDateTime) -> String {
ndt.format(NAIVE_DATETIME_STRING_FORMAT).to_string()
}
-pub fn datetime_from_string<S>(s: S) -> Result<NaiveDateTime, ParseError>
- where S: AsRef<str>
-{
- NaiveDateTime::parse_from_str(s.as_ref(), NAIVE_DATETIME_STRING_FORMAT)
+pub fn datetime_from_string(s: &str) -> Result<NaiveDateTime, ParseError> {
+ NaiveDateTime::parse_from_str(s, NAIVE_DATETIME_STRING_FORMAT)
}