Merge pull request #807 from matthiasbeyer/libimagentrylink/external-iterator-interface

libimagentrylink: external linking iterator interface
This commit is contained in:
Matthias Beyer 2016-10-19 14:32:38 +02:00 committed by GitHub
commit 55a99024e7
4 changed files with 271 additions and 39 deletions

View file

@ -127,8 +127,11 @@ fn list(rt: &Runtime) {
match collection.links() {
Ok(links) => {
debug!("Listing...");
for (i, link) in links.iter().enumerate() {
println!("{: >3}: {}", i, link);
for (i, link) in links.enumerate() {
match link {
Ok(link) => println!("{: >3}: {}", i, link),
Err(e) => trace_error(&e)
}
};
debug!("... ready with listing");
},

View file

@ -296,8 +296,11 @@ fn set_links_for_entry(store: &Store, matches: &ArgMatches, entry: &mut FileLock
fn list_links_for_entry(store: &Store, entry: &mut FileLockEntry) {
entry.get_external_links(store)
.and_then(|links| {
for (i, link) in links.iter().enumerate() {
println!("{: <3}: {}", i, link);
for (i, link) in links.enumerate() {
match link {
Ok(link) => println!("{: <3}: {}", i, link),
Err(e) => trace_error(&e),
}
}
Ok(())
})

View file

@ -37,13 +37,15 @@ use libimagstore::store::Store;
use libimagstore::storeid::IntoStoreId;
use libimagstore::store::FileLockEntry;
use libimagentrylink::external::ExternalLinker;
use libimagentrylink::external::iter::UrlIter;
use libimagentrylink::internal::InternalLinker;
use libimagentrylink::internal::Link as StoreLink;
use libimagerror::into::IntoError;
use url::Url;
use link::Link;
use self::iter::LinksMatchingRegexIter;
pub struct BookmarkCollection<'a> {
fle: FileLockEntry<'a>,
store: &'a Store,
@ -105,7 +107,7 @@ impl<'a> BookmarkCollection<'a> {
.map_err_into(BEK::StoreReadError)
}
pub fn links(&self) -> Result<Vec<Url>> {
pub fn links(&self) -> Result<UrlIter> {
self.fle.get_external_links(&self.store).map_err_into(BEK::LinkError)
}
@ -126,16 +128,12 @@ impl<'a> BookmarkCollection<'a> {
.map_err_into(BEK::LinkError)
}
pub fn get_links_matching(&self, r: Regex) -> Result<Vec<Link>> {
pub fn get_links_matching(&self, r: Regex) -> Result<LinksMatchingRegexIter<'a>> {
use self::iter::IntoLinksMatchingRegexIter;
self.get_external_links(self.store)
.map_err_into(BEK::LinkError)
.map(|v| {
v.into_iter()
.map(Url::into_string)
.filter(|urlstr| r.is_match(&urlstr[..]))
.map(Link::from)
.collect()
})
.map(|iter| iter.matching_regex(r))
}
pub fn remove_link(&mut self, l: Link) -> Result<()> {
@ -150,3 +148,75 @@ impl<'a> BookmarkCollection<'a> {
}
pub mod iter {
use link::Link;
use result::Result;
use error::{MapErrInto, BookmarkErrorKind as BEK};
pub struct LinkIter<I>(I)
where I: Iterator<Item = Link>;
impl<I: Iterator<Item = Link>> LinkIter<I> {
pub fn new(i: I) -> LinkIter<I> {
LinkIter(i)
}
}
impl<I: Iterator<Item = Link>> Iterator for LinkIter<I> {
type Item = Link;
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
}
impl<I> From<I> for LinkIter<I> where I: Iterator<Item = Link> {
fn from(i: I) -> LinkIter<I> {
LinkIter(i)
}
}
use libimagentrylink::external::iter::UrlIter;
use regex::Regex;
pub struct LinksMatchingRegexIter<'a>(UrlIter<'a>, Regex);
impl<'a> LinksMatchingRegexIter<'a> {
pub fn new(i: UrlIter<'a>, r: Regex) -> LinksMatchingRegexIter<'a> {
LinksMatchingRegexIter(i, r)
}
}
impl<'a> Iterator for LinksMatchingRegexIter<'a> {
type Item = Result<Link>;
fn next(&mut self) -> Option<Self::Item> {
loop {
let n = match self.0.next() {
Some(Ok(n)) => n,
Some(Err(e)) => return Some(Err(e).map_err_into(BEK::LinkError)),
None => return None,
};
let s = n.into_string();
if self.1.is_match(&s[..]) {
return Some(Ok(Link::from(s)))
} else {
continue;
}
}
}
}
pub trait IntoLinksMatchingRegexIter<'a> {
fn matching_regex(self, Regex) -> LinksMatchingRegexIter<'a>;
}
impl<'a> IntoLinksMatchingRegexIter<'a> for UrlIter<'a> {
fn matching_regex(self, r: Regex) -> LinksMatchingRegexIter<'a> {
LinksMatchingRegexIter(self, r)
}
}
}

View file

@ -47,6 +47,8 @@ use result::Result;
use internal::InternalLinker;
use module_path::ModuleEntryPath;
use self::iter::*;
use toml::Value;
use url::Url;
use crypto::sha1::Sha1;
@ -98,7 +100,7 @@ impl<'a> Link<'a> {
pub trait ExternalLinker : InternalLinker {
/// Get the external links from the implementor object
fn get_external_links(&self, store: &Store) -> Result<Vec<Url>>;
fn get_external_links<'a>(&self, store: &'a Store) -> Result<UrlIter<'a>>;
/// Set the external links for the implementor object
fn set_external_links(&mut self, store: &Store, links: Vec<Url>) -> Result<()>;
@ -111,6 +113,174 @@ pub trait ExternalLinker : InternalLinker {
}
pub mod iter {
//! Iterator helpers for external linking stuff
//!
//! Contains also helpers to filter iterators for external/internal links
//!
//!
//! # Warning
//!
//! This module uses `internal::Link` as link type, so we operate on _store ids_ here.
//!
//! Not to confuse with `external::Link` which is a real `FileLockEntry` under the hood.
//!
use libimagutil::debug_result::*;
use libimagstore::store::Store;
use internal::Link;
use internal::iter::LinkIter;
use error::LinkErrorKind as LEK;
use error::MapErrInto;
use result::Result;
use url::Url;
/// Helper for building `OnlyExternalIter` and `NoExternalIter`
///
/// The boolean value defines, how to interpret the `is_external_link_storeid()` return value
/// (here as "pred"):
///
/// pred | bool | xor | take?
/// ---- | ---- | --- | ----
/// 0 | 0 | 0 | 1
/// 0 | 1 | 1 | 0
/// 1 | 1 | 1 | 0
/// 1 | 1 | 0 | 1
///
/// If `bool` says "take if return value is false", we take the element if the `pred` returns
/// false... and so on.
///
/// As we can see, the operator between these two operants is `!(a ^ b)`.
pub struct ExternalFilterIter(LinkIter, bool);
impl Iterator for ExternalFilterIter {
type Item = Link;
fn next(&mut self) -> Option<Self::Item> {
use super::is_external_link_storeid;
while let Some(elem) = self.0.next() {
if !(self.1 ^ is_external_link_storeid(&elem)) {
return Some(elem);
}
}
None
}
}
/// Helper trait to be implemented on `LinkIter` to select or deselect all external links
///
/// # See also
///
/// Also see `OnlyExternalIter` and `NoExternalIter` and the helper traits/functions
/// `OnlyInteralLinks`/`only_internal_links()` and `OnlyExternalLinks`/`only_external_links()`.
pub trait SelectExternal {
fn select_external_links(self, b: bool) -> ExternalFilterIter;
}
impl SelectExternal for LinkIter {
fn select_external_links(self, b: bool) -> ExternalFilterIter {
ExternalFilterIter(self, b)
}
}
pub struct OnlyExternalIter(ExternalFilterIter);
impl OnlyExternalIter {
pub fn new(li: LinkIter) -> OnlyExternalIter {
OnlyExternalIter(ExternalFilterIter(li, true))
}
pub fn urls<'a>(self, store: &'a Store) -> UrlIter<'a> {
UrlIter(self, store)
}
}
impl Iterator for OnlyExternalIter {
type Item = Link;
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
}
pub struct NoExternalIter(ExternalFilterIter);
impl NoExternalIter {
pub fn new(li: LinkIter) -> NoExternalIter {
NoExternalIter(ExternalFilterIter(li, false))
}
}
impl Iterator for NoExternalIter {
type Item = Link;
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
}
pub trait OnlyExternalLinks : Sized {
fn only_external_links(self) -> OnlyExternalIter ;
fn no_internal_links(self) -> OnlyExternalIter {
self.only_external_links()
}
}
impl OnlyExternalLinks for LinkIter {
fn only_external_links(self) -> OnlyExternalIter {
OnlyExternalIter::new(self)
}
}
pub trait OnlyInternalLinks : Sized {
fn only_internal_links(self) -> NoExternalIter;
fn no_external_links(self) -> NoExternalIter {
self.only_internal_links()
}
}
impl OnlyInternalLinks for LinkIter {
fn only_internal_links(self) -> NoExternalIter {
NoExternalIter::new(self)
}
}
pub struct UrlIter<'a>(OnlyExternalIter, &'a Store);
impl<'a> Iterator for UrlIter<'a> {
type Item = Result<Url>;
fn next(&mut self) -> Option<Self::Item> {
use super::get_external_link_from_file;
self.0
.next()
.map(|id| {
debug!("Retrieving entry for id: '{:?}'", id);
self.1
.retrieve(id.clone())
.map_err_into(LEK::StoreReadError)
.map_dbg_err(|_| format!("Retrieving entry for id: '{:?}' failed", id))
.and_then(|f| {
debug!("Store::retrieve({:?}) succeeded", id);
debug!("getting external link from file now");
get_external_link_from_file(&f)
.map_dbg_err(|e| format!("URL -> Err = {:?}", e))
})
})
}
}
}
/// Check whether the StoreId starts with `/link/external/`
pub fn is_external_link_storeid(id: &StoreId) -> bool {
debug!("Checking whether this is a 'links/external/': '{:?}'", id);
@ -128,33 +298,16 @@ fn get_external_link_from_file(entry: &FileLockEntry) -> Result<Url> {
impl ExternalLinker for Entry {
/// Get the external links from the implementor object
fn get_external_links(&self, store: &Store) -> Result<Vec<Url>> {
fn get_external_links<'a>(&self, store: &'a Store) -> Result<UrlIter<'a>> {
// Iterate through all internal links and filter for FileLockEntries which live in
// /link/external/<SHA> -> load these files and get the external link from their headers,
// put them into the return vector.
self.get_internal_links()
.map_err(|e| LE::new(LEK::StoreReadError, Some(Box::new(e))))
.map(|iter| {
debug!("Getting external links");
iter.filter(|l| is_external_link_storeid(l))
.map(|id| {
debug!("Retrieving entry for id: '{:?}'", id);
match store.retrieve(id.clone()) {
Ok(f) => {
debug!("Store::retrieve({:?}) succeeded", id);
debug!("getting external link from file now");
get_external_link_from_file(&f)
.map_err(|e| { debug!("URL -> Err = {:?}", e); e })
},
Err(e) => {
debug!("Retrieving entry for id: '{:?}' failed", id);
Err(LE::new(LEK::StoreReadError, Some(Box::new(e))))
}
}
})
.filter_map(|x| x.ok()) // TODO: Do not ignore error here
.collect()
iter.only_external_links().urls(store)
})
.map_err(|e| LE::new(LEK::StoreReadError, Some(Box::new(e))))
}
/// Set the external links for the implementor object
@ -233,7 +386,9 @@ impl ExternalLinker for Entry {
// get external links, add this one, save them
debug!("Getting links");
self.get_external_links(store)
.and_then(|mut links| {
.and_then(|links| {
// TODO: Do not ignore errors here
let mut links = links.filter_map(Result::ok).collect::<Vec<_>>();
debug!("Adding link = '{:?}' to links = {:?}", link, links);
links.push(link);
debug!("Setting {} links = {:?}", links.len(), links);
@ -246,10 +401,11 @@ impl ExternalLinker for Entry {
// get external links, remove this one, save them
self.get_external_links(store)
.and_then(|links| {
debug!("Removing link = '{:?}' from links = {:?}", link, links);
let links = links.into_iter()
debug!("Removing link = '{:?}'", link);
let links = links
.filter_map(Result::ok)
.filter(|l| l.as_str() != link.as_str())
.collect();
.collect::<Vec<_>>();
self.set_external_links(store, links)
})
}