summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthias Beyer <mail@beyermatthias.de>2017-06-23 12:17:06 +0200
committerGitHub <noreply@github.com>2017-06-23 12:17:06 +0200
commit1b88c22decf168e2e02961c85cf5190ed44b6dc5 (patch)
tree79b0efa1df44df65a165977b7e330cbbe92adbb5
parentb47972beddc66c07d1b0bfdfb3947f5392e917cb (diff)
parentc03aa12e42704989221094b3b6a88c65aec11420 (diff)
Merge pull request #943 from matthiasbeyer/libimagentrycategory/init
libimagentrycategory/init
-rw-r--r--Cargo.toml1
-rw-r--r--libimagentrycategory/Cargo.toml30
-rw-r--r--libimagentrycategory/src/category.rs110
-rw-r--r--libimagentrycategory/src/error.rs36
-rw-r--r--libimagentrycategory/src/lib.rs38
-rw-r--r--libimagentrycategory/src/register.rs289
-rw-r--r--libimagentrycategory/src/result.rs26
7 files changed, 530 insertions, 0 deletions
diff --git a/Cargo.toml b/Cargo.toml
index df876c5a..f94a8249 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -17,6 +17,7 @@ members = [
"libimagbookmark",
"libimagcounter",
"libimagdiary",
+ "libimagentrycategory",
"libimagentrydatetime",
"libimagentryedit",
"libimagentryfilter",
diff --git a/libimagentrycategory/Cargo.toml b/libimagentrycategory/Cargo.toml
new file mode 100644
index 00000000..36035cf8
--- /dev/null
+++ b/libimagentrycategory/Cargo.toml
@@ -0,0 +1,30 @@
+[package]
+name = "libimagentrycategory"
+version = "0.3.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]
+log = "0.3"
+toml = "0.4"
+toml-query = "0.2"
+is-match = "0.1"
+
+[dev-dependencies]
+env_logger = "0.4"
+
+[dependencies.libimagerror]
+path = "../libimagerror"
+
+[dependencies.libimagstore]
+path = "../libimagstore"
+
diff --git a/libimagentrycategory/src/category.rs b/libimagentrycategory/src/category.rs
new file mode 100644
index 00000000..89763913
--- /dev/null
+++ b/libimagentrycategory/src/category.rs
@@ -0,0 +1,110 @@
+//
+// 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_query::insert::TomlValueInsertExt;
+use toml_query::read::TomlValueReadExt;
+use toml_query::error::ErrorKind as TQEK;
+use toml::Value;
+
+use libimagstore::store::Entry;
+use libimagerror::into::IntoError;
+
+use error::CategoryErrorKind as CEK;
+use error::MapErrInto;
+use result::Result;
+use register::CategoryRegister;
+
+#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
+pub struct Category(String);
+
+impl From<String> for Category {
+
+ fn from(s: String) -> Category {
+ Category(s)
+ }
+
+}
+
+impl Into<String> for Category {
+ fn into(self) -> String {
+ self.0
+ }
+}
+
+pub trait EntryCategory {
+
+ fn set_category(&mut self, s: Category) -> Result<()>;
+
+ fn set_category_checked(&mut self, register: &CategoryRegister, s: Category) -> Result<()>;
+
+ fn get_category(&self) -> Result<Option<Category>>;
+
+ fn has_category(&self) -> Result<bool>;
+
+}
+
+impl EntryCategory for Entry {
+
+ fn set_category(&mut self, s: Category) -> Result<()> {
+ self.get_header_mut()
+ .insert(&String::from("category.value"), Value::String(s.into()))
+ .map_err_into(CEK::HeaderWriteError)
+ .map(|_| ())
+ }
+
+ /// Check whether a category exists before setting it.
+ ///
+ /// This function should be used by default over EntryCategory::set_category()!
+ fn set_category_checked(&mut self, register: &CategoryRegister, s: Category) -> Result<()> {
+ register.category_exists(&s.0)
+ .and_then(|bl| if bl {
+ self.set_category(s)
+ } else {
+ Err(CEK::CategoryDoesNotExist.into_error())
+ })
+ }
+
+ fn get_category(&self) -> Result<Option<Category>> {
+ match self.get_header().read(&String::from("category.value")) {
+ Err(res) => match res.kind() {
+ &TQEK::IdentifierNotFoundInDocument(_) => Ok(None),
+ _ => Err(res),
+ }
+ .map_err_into(CEK::HeaderReadError),
+
+ Ok(&Value::String(ref s)) => Ok(Some(s.clone().into())),
+ Ok(_) => Err(CEK::TypeError.into_error()).map_err_into(CEK::HeaderReadError),
+ }
+ }
+
+ fn has_category(&self) -> Result<bool> {
+ let res = self.get_header().read(&String::from("category.value"));
+ if res.is_err() {
+ let res = res.unwrap_err();
+ match res.kind() {
+ &TQEK::IdentifierNotFoundInDocument(_) => Ok(false),
+ _ => Err(res),
+ }
+ .map_err_into(CEK::HeaderReadError)
+ } else {
+ Ok(true)
+ }
+ }
+
+}
diff --git a/libimagentrycategory/src/error.rs b/libimagentrycategory/src/error.rs
new file mode 100644
index 00000000..72824337
--- /dev/null
+++ b/libimagentrycategory/src/error.rs
@@ -0,0 +1,36 @@
+//
+// 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
+//
+
+generate_error_module!(
+ generate_error_types!(CategoryError, CategoryErrorKind,
+ StoreReadError => "Store Read error",
+ StoreWriteError => "Store Write error",
+ StoreIdHandlingError => "StoreId handling error",
+ HeaderReadError => "Header read error",
+ HeaderWriteError => "Header write error",
+ TypeError => "Found wrong type in header",
+
+ CategoryDoesNotExist => "Category does not exist"
+ );
+);
+
+pub use self::error::CategoryError;
+pub use self::error::CategoryErrorKind;
+pub use self::error::MapErrInto;
+
diff --git a/libimagentrycategory/src/lib.rs b/libimagentrycategory/src/lib.rs
new file mode 100644
index 00000000..8ffaafc7
--- /dev/null
+++ b/libimagentrycategory/src/lib.rs
@@ -0,0 +1,38 @@
+//
+// 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 toml_query;
+extern crate toml;
+#[macro_use]
+extern crate is_match;
+#[macro_use]
+extern crate log;
+
+#[macro_use]
+extern crate libimagerror;
+#[macro_use]
+extern crate libimagstore;
+
+pub mod category;
+pub mod error;
+pub mod register;
+pub mod result;
+
+module_entry_path_mod!("category");
+
diff --git a/libimagentrycategory/src/register.rs b/libimagentrycategory/src/register.rs
new file mode 100644
index 00000000..aa85d804
--- /dev/null
+++ b/libimagentrycategory/src/register.rs
@@ -0,0 +1,289 @@
+//
+// 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::path::PathBuf;
+
+use toml_query::insert::TomlValueInsertExt;
+use toml_query::read::TomlValueReadExt;
+use toml::Value;
+
+use libimagstore::store::Store;
+use libimagstore::store::FileLockEntry;
+use libimagstore::storeid::StoreId;
+use libimagstore::storeid::StoreIdIterator;
+use libimagerror::into::IntoError;
+
+use category::Category;
+use error::CategoryErrorKind as CEK;
+use error::MapErrInto;
+use result::Result;
+
+pub const CATEGORY_REGISTER_NAME_FIELD_PATH : &'static str = "category.register.name";
+
+/// Extension on the Store to make it a register for categories
+///
+/// The register writes files to the
+pub trait CategoryRegister {
+
+ fn category_exists(&self, name: &str) -> Result<bool>;
+
+ fn create_category(&self, name: &str) -> Result<bool>;
+
+ fn delete_category(&self, name: &str) -> Result<()>;
+
+ fn all_category_names(&self) -> Result<CategoryNameIter>;
+
+ fn get_category_by_name(&self, name: &str) -> Result<Option<FileLockEntry>>;
+
+}
+
+impl CategoryRegister for Store {
+
+ /// Check whether a category exists
+ fn category_exists(&self, name: &str) -> Result<bool> {
+ let sid = try!(mk_category_storeid(self.path().clone(), name));
+ represents_category(self, sid, name)
+ }
+
+ /// Create a category
+ ///
+ /// Fails if the category already exists (returns false then)
+ fn create_category(&self, name: &str) -> Result<bool> {
+ use libimagstore::error::StoreErrorKind as SEK;
+
+ let sid = try!(mk_category_storeid(self.path().clone(), name));
+
+
+ match self.create(sid) {
+ Ok(mut entry) => {
+ let val = Value::String(String::from(name));
+ entry.get_header_mut()
+ .insert(CATEGORY_REGISTER_NAME_FIELD_PATH, val)
+ .map(|opt| if opt.is_none() {
+ debug!("Setting category header worked")
+ } else {
+ warn!("Setting category header replaced existing value: {:?}", opt);
+ })
+ .map(|_| true)
+ .map_err_into(CEK::HeaderWriteError)
+ .map_err_into(CEK::StoreWriteError)
+ }
+ Err(store_error) => if is_match!(store_error.err_type(), SEK::EntryAlreadyExists) {
+ Ok(false)
+ } else {
+ Err(store_error).map_err_into(CEK::StoreWriteError)
+ }
+ }
+ }
+
+ /// Delete a category
+ fn delete_category(&self, name: &str) -> Result<()> {
+ let sid = try!(mk_category_storeid(self.path().clone(), name));
+
+ self.delete(sid).map_err_into(CEK::StoreWriteError)
+ }
+
+ /// Get all category names
+ fn all_category_names(&self) -> Result<CategoryNameIter> {
+ self.retrieve_for_module("category")
+ .map_err_into(CEK::StoreReadError)
+ .map(|iter| CategoryNameIter::new(self, iter))
+ }
+
+ /// Get a category by its name
+ ///
+ /// Returns the FileLockEntry which represents the category, so one can link to it and use it
+ /// like a normal file in the store (which is exactly what it is).
+ fn get_category_by_name(&self, name: &str) -> Result<Option<FileLockEntry>> {
+ let sid = try!(mk_category_storeid(self.path().clone(), name));
+
+ self.get(sid)
+ .map_err_into(CEK::StoreWriteError)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ extern crate env_logger;
+ use std::path::PathBuf;
+
+ use super::*;
+
+ use libimagstore::store::Store;
+
+ pub fn get_store() -> Store {
+ use libimagstore::store::InMemoryFileAbstraction;
+ let backend = Box::new(InMemoryFileAbstraction::new());
+ Store::new_with_backend(PathBuf::from("/"), None, backend).unwrap()
+ }
+
+ #[test]
+ fn test_non_existing_category_exists() {
+ let exists = get_store().category_exists("nonexistent");
+
+ assert!(exists.is_ok(), format!("Expected Ok(_), got: {:?}", exists));
+ let exists = exists.unwrap();
+
+ assert!(!exists);
+ }
+
+ #[test]
+ fn test_creating_category() {
+ let category_name = "examplecategory";
+ let store = get_store();
+ let res = store.create_category(category_name);
+
+ assert!(res.is_ok(), format!("Expected Ok(_), got: {:?}", res));
+ let res = res.unwrap();
+ assert!(res);
+ }
+
+ #[test]
+ fn test_creating_category_creates_store_entry() {
+ let category_name = "examplecategory";
+ let store = get_store();
+
+ let res = store.create_category(category_name);
+
+ assert!(res.is_ok(), format!("Expected Ok(_), got: {:?}", res));
+ let res = res.unwrap();
+ assert!(res);
+
+ let category = store.get(PathBuf::from(format!("category/{}", category_name)));
+
+ assert!(category.is_ok(), format!("Expected Ok(_), got: {:?}", category));
+ let category = category.unwrap();
+
+ assert!(category.is_some());
+ }
+
+ #[test]
+ fn test_creating_category_creates_store_entry_with_header_field_set() {
+ let _ = env_logger::init();
+ let category_name = "examplecategory";
+ let store = get_store();
+ let res = store.create_category(category_name);
+
+ assert!(res.is_ok(), format!("Expected Ok(_), got: {:?}", res));
+ let res = res.unwrap();
+ assert!(res);
+
+ let id = PathBuf::from(format!("category/{}", category_name));
+ println!("Trying: {:?}", id);
+ let category = store.get(id);
+
+ assert!(category.is_ok(), format!("Expected Ok(_), got: {:?}", category));
+ let category = category.unwrap();
+
+ assert!(category.is_some());
+ let category = category.unwrap();
+
+ let header_field = category.get_header().read(CATEGORY_REGISTER_NAME_FIELD_PATH);
+ assert!(header_field.is_ok(), format!("Expected Ok(_), got: {:?}", header_field));
+ let header_field = header_field.unwrap();
+
+ match *header_field {
+ Value::String(ref s) => assert_eq!(category_name, s),
+ _ => assert!(false, "Header field has wrong type"),
+ }
+ }
+}
+
+#[inline]
+fn mk_category_storeid(base: PathBuf, s: &str) -> Result<StoreId> {
+ use libimagstore::storeid::IntoStoreId;
+ ::module_path::ModuleEntryPath::new(s)
+ .into_storeid()
+ .map(|id| id.with_base(base))
+ .map_err_into(CEK::StoreIdHandlingError)
+}
+
+#[inline]
+fn represents_category(store: &Store, sid: StoreId, name: &str) -> Result<bool> {
+ sid.exists()
+ .map_err_into(CEK::StoreIdHandlingError)
+ .and_then(|bl| {
+ if bl {
+ store.get(sid)
+ .map_err_into(CEK::StoreReadError)
+ .and_then(|fle| {
+ if let Some(fle) = fle {
+ match fle.get_header()
+ .read(&String::from(CATEGORY_REGISTER_NAME_FIELD_PATH))
+ .map_err_into(CEK::HeaderReadError)
+ {
+ Ok(&Value::String(ref s)) => Ok(s == name),
+ Ok(_) => Err(CEK::TypeError.into_error()),
+ Err(e) => Err(e).map_err_into(CEK::HeaderReadError),
+ }
+ } else {
+ Ok(false)
+ }
+ })
+ } else {
+ Ok(bl) // false
+ }
+ })
+}
+
+/// Iterator for Category names
+///
+/// Iterates over Result<Category>
+///
+/// # Return values
+///
+/// In each iteration, a Option<Result<Category>> is returned. Error kinds are as follows:
+///
+/// * CategoryErrorKind::StoreReadError if a name could not be fetched from the store
+/// * CategoryErrorKind::HeaderReadError if the header of the fetched item couldn't be read
+/// * CategoryErrorKind::TypeError if the name could not be fetched because it is not a String
+///
+pub struct CategoryNameIter<'a>(&'a Store, StoreIdIterator);
+
+impl<'a> CategoryNameIter<'a> {
+
+ fn new(store: &'a Store, sidit: StoreIdIterator) -> CategoryNameIter<'a> {
+ CategoryNameIter(store, sidit)
+ }
+
+}
+
+impl<'a> Iterator for CategoryNameIter<'a> {
+ type Item = Result<Category>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ // TODO: Optimize me with lazy_static
+ let query = String::from(CATEGORY_REGISTER_NAME_FIELD_PATH);
+
+ self.1
+ .next()
+ .map(|sid| {
+ self.0
+ .get(sid)
+ .map_err_into(CEK::StoreReadError)
+ .and_then(|fle| fle.ok_or(CEK::StoreReadError.into_error()))
+ .and_then(|fle| match fle.get_header().read(&query) {
+ Ok(&Value::String(ref s)) => Ok(Category::from(s.clone())),
+ Ok(_) => Err(CEK::TypeError.into_error()),
+ Err(e) => Err(e).map_err_into(CEK::HeaderReadError),
+ })
+ })
+ }
+}
+
diff --git a/libimagentrycategory/src/result.rs b/libimagentrycategory/src/result.rs
new file mode 100644
index 00000000..517d0e96
--- /dev/null
+++ b/libimagentrycategory/src/result.rs
@@ -0,0 +1,26 @@
+
+//
+// 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::CategoryError;
+
+pub type Result<T> = RResult<T, CategoryError>;
+