summaryrefslogtreecommitdiffstats
path: root/lib/core/libimagstore/src/storeid.rs
diff options
context:
space:
mode:
Diffstat (limited to 'lib/core/libimagstore/src/storeid.rs')
-rw-r--r--lib/core/libimagstore/src/storeid.rs391
1 files changed, 391 insertions, 0 deletions
diff --git a/lib/core/libimagstore/src/storeid.rs b/lib/core/libimagstore/src/storeid.rs
new file mode 100644
index 00000000..211efbe8
--- /dev/null
+++ b/lib/core/libimagstore/src/storeid.rs
@@ -0,0 +1,391 @@
+//
+// 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::Deref;
+use std::path::Path;
+use std::path::PathBuf;
+
+use std::fmt::{Display, Debug, Formatter};
+use std::fmt::Error as FmtError;
+use std::result::Result as RResult;
+use std::path::Components;
+
+use libimagerror::into::IntoError;
+
+use error::StoreErrorKind as SEK;
+use error::MapErrInto;
+use store::Result;
+
+/// The Index into the Store
+#[derive(Debug, Clone, Hash, Eq, PartialOrd, Ord)]
+pub struct StoreId {
+ base: Option<PathBuf>,
+ id: PathBuf,
+}
+
+impl PartialEq for StoreId {
+ fn eq(&self, other: &StoreId) -> bool {
+ self.id == other.id
+ }
+}
+
+impl StoreId {
+
+ pub fn new(base: Option<PathBuf>, id: PathBuf) -> Result<StoreId> {
+ StoreId::new_baseless(id).map(|mut sid| { sid.base = base; sid })
+ }
+
+ /// Try to create a StoreId object from a filesystem-absolute path.
+ ///
+ /// Automatically creates a StoreId object which has a `base` set to `store_part` if stripping
+ /// the `store_part` from the `full_path` succeeded.
+ ///
+ /// Returns a `StoreErrorKind::StoreIdBuildFromFullPathError` if stripping failes.
+ pub fn from_full_path<D>(store_part: &PathBuf, full_path: D) -> Result<StoreId>
+ where D: Deref<Target = Path>
+ {
+ let p = try!(
+ full_path.strip_prefix(store_part).map_err_into(SEK::StoreIdBuildFromFullPathError)
+ );
+ StoreId::new(Some(store_part.clone()), PathBuf::from(p))
+ }
+
+ pub fn new_baseless(id: PathBuf) -> Result<StoreId> {
+ debug!("Trying to get a new baseless id from: {:?}", id);
+ if id.is_absolute() {
+ Err(SEK::StoreIdLocalPartAbsoluteError.into_error())
+ } else {
+ Ok(StoreId {
+ base: None,
+ id: id
+ })
+ }
+ }
+
+ pub fn without_base(mut self) -> StoreId {
+ self.base = None;
+ self
+ }
+
+ pub fn with_base(mut self, base: PathBuf) -> Self {
+ self.base = Some(base);
+ self
+ }
+
+ /// Transform the StoreId object into a PathBuf, error if the base of the StoreId is not
+ /// specified.
+ pub fn into_pathbuf(self) -> Result<PathBuf> {
+ let mut base = try!(self.base.ok_or(SEK::StoreIdHasNoBaseError.into_error()));
+ base.push(self.id);
+ Ok(base)
+ }
+
+ pub fn exists(&self) -> Result<bool> {
+ self.clone().into_pathbuf().map(|pb| pb.exists())
+ }
+
+ pub fn to_str(&self) -> Result<String> {
+ self.base
+ .as_ref()
+ .cloned()
+ .map(|mut base| { base.push(self.id.clone()); base })
+ .unwrap_or_else(|| self.id.clone())
+ .to_str()
+ .map(String::from)
+ .ok_or(SEK::StoreIdHandlingError.into_error())
+ }
+
+ /// Returns the components of the `id` part of the StoreId object.
+ ///
+ /// Can be used to check whether a StoreId points to an entry in a specific collection of
+ /// StoreIds.
+ pub fn components(&self) -> Components {
+ self.id.components()
+ }
+
+ /// Get the _local_ part of a StoreId object, as in "the part from the store root to the entry".
+ pub fn local(&self) -> &PathBuf {
+ &self.id
+ }
+
+ /// Check whether a StoreId points to an entry in a specific collection.
+ ///
+ /// A "collection" here is simply a directory. So `foo/bar/baz` is an entry which is in
+ /// collection ["foo", "bar", "baz"], but also in ["foo", "bar"] and ["foo"].
+ ///
+ /// # Warning
+ ///
+ /// The collection specification _has_ to start with the module name. Otherwise this function
+ /// may return false negatives.
+ ///
+ pub fn is_in_collection(&self, colls: &[&str]) -> bool {
+ use std::path::Component;
+
+ self.id
+ .components()
+ .zip(colls)
+ .map(|(component, pred_coll)| match component {
+ Component::Normal(ref s) => s.to_str().map(|ref s| s == pred_coll).unwrap_or(false),
+ _ => false
+ })
+ .all(|x| x)
+ }
+
+ pub fn local_push<P: AsRef<Path>>(&mut self, path: P) {
+ self.id.push(path)
+ }
+
+}
+
+impl Display for StoreId {
+
+ fn fmt(&self, fmt: &mut Formatter) -> RResult<(), FmtError> {
+ match self.id.to_str() {
+ Some(s) => write!(fmt, "{}", s),
+ None => write!(fmt, "{}", self.id.to_string_lossy()),
+ }
+ }
+
+}
+
+/// This Trait allows you to convert various representations to a single one
+/// suitable for usage in the Store
+pub trait IntoStoreId {
+ fn into_storeid(self) -> Result<StoreId>;
+}
+
+impl IntoStoreId for StoreId {
+ fn into_storeid(self) -> Result<StoreId> {
+ Ok(self)
+ }
+}
+
+impl IntoStoreId for PathBuf {
+ fn into_storeid(self) -> Result<StoreId> {
+ StoreId::new_baseless(self)
+ }
+}
+
+#[macro_export]
+macro_rules! module_entry_path_mod {
+ ($name:expr) => (
+ #[deny(missing_docs,
+ missing_copy_implementations,
+ trivial_casts, trivial_numeric_casts,
+ unsafe_code,
+ unstable_features,
+ unused_import_braces, unused_qualifications,
+ unused_imports)]
+ /// A helper module to create valid module entry paths
+ pub mod module_path {
+ use std::convert::AsRef;
+ use std::path::Path;
+ use std::path::PathBuf;
+
+ use $crate::storeid::StoreId;
+ use $crate::store::Result;
+
+ /// A Struct giving you the ability to choose store entries assigned
+ /// to it.
+ ///
+ /// It is created through a call to `new`.
+ pub struct ModuleEntryPath(PathBuf);
+
+ impl ModuleEntryPath {
+ /// Path has to be a valid UTF-8 string or this will panic!
+ pub fn new<P: AsRef<Path>>(pa: P) -> ModuleEntryPath {
+ let mut path = PathBuf::new();
+ path.push(format!("{}", $name));
+ path.push(pa.as_ref().clone());
+ let name = pa.as_ref().file_name().unwrap()
+ .to_str().unwrap();
+ path.set_file_name(name);
+ ModuleEntryPath(path)
+ }
+ }
+
+ impl $crate::storeid::IntoStoreId for ModuleEntryPath {
+ fn into_storeid(self) -> Result<$crate::storeid::StoreId> {
+ StoreId::new(None, self.0)
+ }
+ }
+ }
+ )
+}
+
+pub struct StoreIdIterator {
+ iter: Box<Iterator<Item = StoreId>>,
+}
+
+impl Debug for StoreIdIterator {
+
+ fn fmt(&self, fmt: &mut Formatter) -> RResult<(), FmtError> {
+ write!(fmt, "StoreIdIterator")
+ }
+
+}
+
+impl StoreIdIterator {
+
+ pub fn new(iter: Box<Iterator<Item = StoreId>>) -> StoreIdIterator {
+ StoreIdIterator {
+ iter: iter,
+ }
+ }
+
+}
+
+impl Iterator for StoreIdIterator {
+ type Item = StoreId;
+
+ fn next(&mut self) -> Option<StoreId> {
+ self.iter.next()
+ }
+
+}
+
+#[cfg(test)]
+mod test {
+ use std::path::PathBuf;
+
+ use storeid::StoreId;
+ use storeid::IntoStoreId;
+ use error::StoreErrorKind as SEK;
+
+ module_entry_path_mod!("test");
+
+ #[test]
+ fn test_correct_path() {
+ let p = module_path::ModuleEntryPath::new("test");
+
+ assert_eq!(p.into_storeid().unwrap().to_str().unwrap(), "test/test");
+ }
+
+ #[test]
+ fn test_baseless_path() {
+ let id = StoreId::new_baseless(PathBuf::from("test"));
+ assert!(id.is_ok());
+ assert_eq!(id.unwrap(), StoreId {
+ base: None,
+ id: PathBuf::from("test")
+ });
+ }
+
+ #[test]
+ fn test_base_path() {
+ let id = StoreId::from_full_path(&PathBuf::from("/tmp/"), PathBuf::from("/tmp/test"));
+ assert!(id.is_ok());
+ assert_eq!(id.unwrap(), StoreId {
+ base: Some(PathBuf::from("/tmp/")),
+ id: PathBuf::from("test")
+ });
+ }
+
+ #[test]
+ fn test_adding_base_to_baseless_path() {
+ let id = StoreId::new_baseless(PathBuf::from("test"));
+
+ assert!(id.is_ok());
+ let id = id.unwrap();
+
+ assert_eq!(id, StoreId { base: None, id: PathBuf::from("test") });
+
+ let id = id.with_base(PathBuf::from("/tmp/"));
+ assert_eq!(id, StoreId {
+ base: Some(PathBuf::from("/tmp/")),
+ id: PathBuf::from("test")
+ });
+ }
+
+ #[test]
+ fn test_removing_base_from_base_path() {
+ let id = StoreId::from_full_path(&PathBuf::from("/tmp/"), PathBuf::from("/tmp/test"));
+
+ assert!(id.is_ok());
+ let id = id.unwrap();
+
+ assert_eq!(id, StoreId {
+ base: Some(PathBuf::from("/tmp/")),
+ id: PathBuf::from("test")
+ });
+
+ let id = id.without_base();
+ assert_eq!(id, StoreId {
+ base: None,
+ id: PathBuf::from("test")
+ });
+ }
+
+ #[test]
+ fn test_baseless_into_pathbuf_is_err() {
+ let id = StoreId::new_baseless(PathBuf::from("test"));
+ assert!(id.is_ok());
+ assert!(id.unwrap().into_pathbuf().is_err());
+ }
+
+ #[test]
+ fn test_baseless_into_pathbuf_is_storeidhasnobaseerror() {
+ let id = StoreId::new_baseless(PathBuf::from("test"));
+ assert!(id.is_ok());
+
+ let pb = id.unwrap().into_pathbuf();
+ assert!(pb.is_err());
+
+ assert_eq!(pb.unwrap_err().err_type(), SEK::StoreIdHasNoBaseError);
+ }
+
+ #[test]
+ fn test_basefull_into_pathbuf_is_ok() {
+ let id = StoreId::from_full_path(&PathBuf::from("/tmp/"), PathBuf::from("/tmp/test"));
+ assert!(id.is_ok());
+ assert!(id.unwrap().into_pathbuf().is_ok());
+ }
+
+ #[test]
+ fn test_basefull_into_pathbuf_is_correct() {
+ let id = StoreId::from_full_path(&PathBuf::from("/tmp/"), PathBuf::from("/tmp/test"));
+ assert!(id.is_ok());
+
+ let pb = id.unwrap().into_pathbuf();
+ assert!(pb.is_ok());
+
+ assert_eq!(pb.unwrap(), PathBuf::from("/tmp/test"));
+ }
+
+ #[test]
+ fn storeid_in_collection() {
+ let p = module_path::ModuleEntryPath::new("1/2/3/4/5/6/7/8/9/0").into_storeid().unwrap();
+
+ assert!(p.is_in_collection(&["test", "1"]));
+ assert!(p.is_in_collection(&["test", "1", "2"]));
+ assert!(p.is_in_collection(&["test", "1", "2", "3"]));
+ assert!(p.is_in_collection(&["test", "1", "2", "3", "4"]));
+ assert!(p.is_in_collection(&["test", "1", "2", "3", "4", "5"]));
+ assert!(p.is_in_collection(&["test", "1", "2", "3", "4", "5", "6"]));
+ assert!(p.is_in_collection(&["test", "1", "2", "3", "4", "5", "6", "7"]));
+ assert!(p.is_in_collection(&["test", "1", "2", "3", "4", "5", "6", "7", "8"]));
+ assert!(p.is_in_collection(&["test", "1", "2", "3", "4", "5", "6", "7", "8", "9"]));
+ assert!(p.is_in_collection(&["test", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0"]));
+
+ assert!(!p.is_in_collection(&["test", "0", "2", "3", "4", "5", "6", "7", "8", "9", "0"]));
+ assert!(!p.is_in_collection(&["test", "1", "2", "3", "4", "5", "6", "8"]));
+ assert!(!p.is_in_collection(&["test", "1", "2", "3", "leet", "5", "6", "7"]));
+ }
+
+}