summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthias Beyer <mail@beyermatthias.de>2019-06-21 22:30:03 +0200
committerMatthias Beyer <mail@beyermatthias.de>2019-06-26 20:26:41 +0200
commit0eac6d9931577da49e3e546a8484ec039d41506e (patch)
tree9ddb8c42032b4d12ac41fc40b2a3b62edd64f159
parent56f8aecdbfd656b32f8a434ee978ac5a61a3c018 (diff)
downloadimag-0eac6d9931577da49e3e546a8484ec039d41506e.zip
imag-0eac6d9931577da49e3e546a8484ec039d41506e.tar.gz
Add support for directional links
This patch adds support for directional links in libimagentrylink. A directional link is added to the same header as the "internal" links, which are unidirectional. The "outgoing" link is added in "links.to", the "incoming" links are added to "links.from". This way we can always traverse all linkage. The `Linkable::links()` method is changed to always return _all_ link. The `Link` type was extended for notions of "to" and "from" links. Also adds testing. Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
-rw-r--r--lib/entry/libimagentrylink/src/link.rs31
-rw-r--r--lib/entry/libimagentrylink/src/linkable.rs167
2 files changed, 193 insertions, 5 deletions
diff --git a/lib/entry/libimagentrylink/src/link.rs b/lib/entry/libimagentrylink/src/link.rs
index 8237011..b60e22c 100644
--- a/lib/entry/libimagentrylink/src/link.rs
+++ b/lib/entry/libimagentrylink/src/link.rs
@@ -30,6 +30,8 @@ use failure::Error;
#[derive(Eq, PartialOrd, Ord, Hash, Debug, Clone)]
pub enum Link {
Id { link: StoreId },
+ LinkTo { link: StoreId },
+ LinkFrom { link: StoreId },
}
impl Link {
@@ -37,6 +39,8 @@ impl Link {
pub fn exists(&self, store: &Store) -> Result<bool> {
match *self {
Link::Id { ref link } => store.exists(link.clone()),
+ Link::LinkTo { ref link } => store.exists(link.clone()),
+ Link::LinkFrom { ref link } => store.exists(link.clone()),
}
.map_err(From::from)
}
@@ -44,6 +48,8 @@ impl Link {
pub fn to_str(&self) -> Result<String> {
match *self {
Link::Id { ref link } => link.to_str(),
+ Link::LinkTo { ref link } => link.to_str(),
+ Link::LinkFrom { ref link } => link.to_str(),
}
.map_err(From::from)
}
@@ -52,6 +58,8 @@ impl Link {
pub(crate) fn eq_store_id(&self, id: &StoreId) -> bool {
match self {
&Link::Id { link: ref s } => s.eq(id),
+ &Link::LinkTo { ref link } => link.eq(id),
+ &Link::LinkFrom { ref link } => link.eq(id),
}
}
@@ -59,17 +67,21 @@ impl Link {
pub fn get_store_id(&self) -> &StoreId {
match self {
&Link::Id { link: ref s } => s,
+ &Link::LinkTo { ref link } => link,
+ &Link::LinkFrom { ref link } => link,
}
}
pub(crate) fn to_value(&self) -> Result<Value> {
match self {
- Link::Id { ref link } => link
- .to_str()
- .map(Value::String)
- .context(EM::ConversionError)
- .map_err(Error::from),
+ Link::Id { ref link } => link,
+ Link::LinkTo { ref link } => link,
+ Link::LinkFrom { ref link } => link,
}
+ .to_str()
+ .map(Value::String)
+ .context(EM::ConversionError)
+ .map_err(Error::from)
}
}
@@ -78,6 +90,9 @@ impl ::std::cmp::PartialEq for Link {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(&Link::Id { link: ref a }, &Link::Id { link: ref b }) => a.eq(&b),
+ (&Link::LinkTo { link: ref a }, &Link::LinkTo { link: ref b })=> a.eq(&b),
+ (&Link::LinkFrom { link: ref a }, &Link::LinkFrom { link: ref b })=> a.eq(&b),
+ _ => false,
}
}
}
@@ -93,6 +108,8 @@ impl Into<StoreId> for Link {
fn into(self) -> StoreId {
match self {
Link::Id { link } => link,
+ Link::LinkTo { link } => link,
+ Link::LinkFrom { link } => link,
}
}
}
@@ -101,6 +118,8 @@ impl IntoStoreId for Link {
fn into_storeid(self) -> Result<StoreId> {
match self {
Link::Id { link } => Ok(link),
+ Link::LinkTo { link } => Ok(link),
+ Link::LinkFrom { link } => Ok(link),
}
}
}
@@ -109,6 +128,8 @@ impl AsRef<StoreId> for Link {
fn as_ref(&self) -> &StoreId {
match self {
&Link::Id { ref link } => &link,
+ &Link::LinkTo { ref link } => &link,
+ &Link::LinkFrom { ref link } => &link,
}
}
}
diff --git a/lib/entry/libimagentrylink/src/linkable.rs b/lib/entry/libimagentrylink/src/linkable.rs
index 104e642..2c07af4 100644
--- a/lib/entry/libimagentrylink/src/linkable.rs
+++ b/lib/entry/libimagentrylink/src/linkable.rs
@@ -38,6 +38,15 @@ pub trait Linkable {
/// Get all links
fn links(&self) -> Result<LinkIter>;
+ /// Get all links which are unidirectional links
+ fn unidirectional_links(&self) -> Result<LinkIter>;
+
+ /// Get all links which are directional links, outgoing
+ fn directional_links_to(&self) -> Result<LinkIter>;
+
+ /// Get all links which are directional links, incoming
+ fn directional_links_from(&self) -> Result<LinkIter>;
+
/// Add an internal link to the implementor object
fn add_link(&mut self, link: &mut Entry) -> Result<()>;
@@ -47,17 +56,27 @@ pub trait Linkable {
/// Remove _all_ internal links
fn unlink(&mut self, store: &Store) -> Result<()>;
+ /// Add a directional link: self -> otehr
+ fn add_link_to(&mut self, other: &mut Entry) -> Result<()>;
+
+ /// Remove a directional link: self -> otehr
+ fn remove_link_to(&mut self, other: &mut Entry) -> Result<()>;
+
}
#[derive(Serialize, Deserialize, Debug)]
struct LinkPartial {
internal: Option<Vec<String>>,
+ from: Option<Vec<String>>,
+ to: Option<Vec<String>>,
}
impl Default for LinkPartial {
fn default() -> Self {
LinkPartial {
internal: None,
+ from: None,
+ to: None,
}
}
}
@@ -82,6 +101,8 @@ impl Linkable for Entry {
.internal
.unwrap_or_else(|| vec![])
.into_iter()
+ .chain(partial.from.unwrap_or_else(|| vec![]).into_iter())
+ .chain(partial.to.unwrap_or_else(|| vec![]).into_iter())
.map(PathBuf::from)
.map(StoreId::new)
.map(|r| r.map(Link::from))
@@ -89,6 +110,48 @@ impl Linkable for Entry {
.map(LinkIter::new)
}
+ /// Get all links which are unidirectional links
+ fn unidirectional_links(&self) -> Result<LinkIter> {
+ trace!("Getting unidirectional links from header of '{}' = {:?}", self.get_location(), self.get_header());
+
+ let iter = self.get_header()
+ .read_partial::<LinkPartial>()?
+ .unwrap_or_else(Default::default)
+ .internal
+ .unwrap_or_else(|| vec![])
+ .into_iter();
+
+ link_string_iter_to_link_iter(iter)
+ }
+
+ /// Get all links which are directional links, outgoing
+ fn directional_links_to(&self) -> Result<LinkIter> {
+ trace!("Getting unidirectional (to) links from header of '{}' = {:?}", self.get_location(), self.get_header());
+
+ let iter = self.get_header()
+ .read_partial::<LinkPartial>()?
+ .unwrap_or_else(Default::default)
+ .to
+ .unwrap_or_else(|| vec![])
+ .into_iter();
+
+ link_string_iter_to_link_iter(iter)
+ }
+
+ /// Get all links which are directional links, incoming
+ fn directional_links_from(&self) -> Result<LinkIter> {
+ trace!("Getting unidirectional (from) links from header of '{}' = {:?}", self.get_location(), self.get_header());
+
+ let iter = self.get_header()
+ .read_partial::<LinkPartial>()?
+ .unwrap_or_else(Default::default)
+ .from
+ .unwrap_or_else(|| vec![])
+ .into_iter();
+
+ link_string_iter_to_link_iter(iter)
+ }
+
fn add_link(&mut self, other: &mut Entry) -> Result<()> {
debug!("Adding internal link: {:?}", other);
let left_location = self.get_location().to_str()?;
@@ -150,6 +213,53 @@ impl Linkable for Entry {
Ok(())
}
+ fn add_link_to(&mut self, other: &mut Entry) -> Result<()> {
+ let left_location = self.get_location().to_str()?;
+ let right_location = other.get_location().to_str()?;
+
+ alter_linking(self, other, |mut left, mut right| {
+ let mut left_to = left.to.unwrap_or_else(|| vec![]);
+ left_to.push(right_location);
+
+ let mut right_from = right.from.unwrap_or_else(|| vec![]);
+ right_from.push(left_location);
+
+ left.to = Some(left_to);
+ right.from = Some(right_from);
+
+ Ok((left, right))
+ })
+ }
+
+ /// Remove a directional link: self -> otehr
+ fn remove_link_to(&mut self, other: &mut Entry) -> Result<()> {
+ let left_location = self.get_location().to_str()?;
+ let right_location = other.get_location().to_str()?;
+
+ alter_linking(self, other, |mut left, mut right| {
+ let mut left_to = left.to.unwrap_or_else(|| vec![]);
+ left_to.retain(|l| *l != right_location);
+
+ let mut right_from = right.from.unwrap_or_else(|| vec![]);
+ right_from.retain(|l| *l != left_location);
+
+ left.to = Some(left_to);
+ right.from = Some(right_from);
+
+ Ok((left, right))
+ })
+ }
+
+}
+
+fn link_string_iter_to_link_iter<I>(iter: I) -> Result<LinkIter>
+ where I: Iterator<Item = String>
+{
+ iter.map(PathBuf::from)
+ .map(StoreId::new)
+ .map(|r| r.map(Link::from))
+ .collect::<Result<Vec<Link>>>()
+ .map(LinkIter::new)
}
fn alter_linking<F>(left: &mut Entry, right: &mut Entry, f: F) -> Result<()>
@@ -392,4 +502,61 @@ mod test {
assert_eq!(e3.links().unwrap().collect::<Vec<_>>().len(), 0);
}
+ #[test]
+ fn test_directional_link() {
+ use libimagstore::store::Entry;
+
+ setup_logging();
+ let store = get_store();
+ let mut entry1 = store.create(PathBuf::from("test_directional_link-1")).unwrap();
+ let mut entry2 = store.create(PathBuf::from("test_directional_link-2")).unwrap();
+
+ assert!(entry1.unidirectional_links().unwrap().collect::<Vec<_>>().is_empty());
+ assert!(entry2.unidirectional_links().unwrap().collect::<Vec<_>>().is_empty());
+
+ assert!(entry1.directional_links_to().unwrap().collect::<Vec<_>>().is_empty());
+ assert!(entry2.directional_links_to().unwrap().collect::<Vec<_>>().is_empty());
+
+ assert!(entry1.directional_links_from().unwrap().collect::<Vec<_>>().is_empty());
+ assert!(entry2.directional_links_from().unwrap().collect::<Vec<_>>().is_empty());
+
+ assert!(entry1.add_link_to(&mut entry2).is_ok());
+
+ assert_eq!(entry1.unidirectional_links().unwrap().collect::<Vec<_>>(), vec![]);
+ assert_eq!(entry2.unidirectional_links().unwrap().collect::<Vec<_>>(), vec![]);
+
+ let get_directional_links_to = |e: &Entry| -> Result<Vec<String>, _> {
+ e.directional_links_to()
+ .unwrap()
+ .map(|l| l.to_str())
+ .collect::<Result<Vec<_>, _>>()
+ };
+
+ let get_directional_links_from = |e: &Entry| {
+ e.directional_links_from()
+ .unwrap()
+ .map(|l| l.to_str())
+ .collect::<Result<Vec<_>, _>>()
+ };
+
+ {
+ let entry1_dir_links = get_directional_links_to(&entry1).unwrap();
+ assert_eq!(entry1_dir_links, vec!["test_directional_link-2"]);
+ }
+ {
+ let entry2_dir_links = get_directional_links_to(&entry2).unwrap();
+ assert!(entry2_dir_links.is_empty());
+ }
+
+ {
+ let entry1_dir_links = get_directional_links_from(&entry1).unwrap();
+ assert!(entry1_dir_links.is_empty());
+ }
+ {
+ let entry2_dir_links = get_directional_links_from(&entry2).unwrap();
+ assert_eq!(entry2_dir_links, vec!["test_directional_link-1"]);
+ }
+
+ }
+
}