diff options
author | Matthias Beyer <mail@beyermatthias.de> | 2017-06-23 12:17:06 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-06-23 12:17:06 +0200 |
commit | 1b88c22decf168e2e02961c85cf5190ed44b6dc5 (patch) | |
tree | 79b0efa1df44df65a165977b7e330cbbe92adbb5 | |
parent | b47972beddc66c07d1b0bfdfb3947f5392e917cb (diff) | |
parent | c03aa12e42704989221094b3b6a88c65aec11420 (diff) |
Merge pull request #943 from matthiasbeyer/libimagentrycategory/init
libimagentrycategory/init
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | libimagentrycategory/Cargo.toml | 30 | ||||
-rw-r--r-- | libimagentrycategory/src/category.rs | 110 | ||||
-rw-r--r-- | libimagentrycategory/src/error.rs | 36 | ||||
-rw-r--r-- | libimagentrycategory/src/lib.rs | 38 | ||||
-rw-r--r-- | libimagentrycategory/src/register.rs | 289 | ||||
-rw-r--r-- | libimagentrycategory/src/result.rs | 26 |
7 files changed, 530 insertions, 0 deletions
@@ -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>; + |