Auto merge of #35 - matthiasbeyer:refactor, r=matthiasbeyer

Refactor

Refactor for better and more generic CLI-filter abstractions for modules.
This commit is contained in:
Homu 2015-12-30 23:08:20 +09:00
commit 9b1ba78c1f
4 changed files with 234 additions and 107 deletions

View file

@ -18,6 +18,8 @@ use storage::file::File;
use storage::parser::FileHeaderParser; use storage::parser::FileHeaderParser;
use storage::parser::Parser; use storage::parser::Parser;
use storage::json::parser::JsonHeaderParser; use storage::json::parser::JsonHeaderParser;
use module::helpers::cli::get_file_filter_by_cli;
use module::helpers::cli::CliFileFilter;
mod header; mod header;
@ -136,15 +138,13 @@ impl<'a> BM<'a> {
fn command_remove(&self, matches: &ArgMatches) -> bool { fn command_remove(&self, matches: &ArgMatches) -> bool {
use std::process::exit; use std::process::exit;
let (filtered, files) = self.get_files(matches, "id", "match", "tags"); let parser = Parser::new(JsonHeaderParser::new(None));
let filter : Box<CliFileFilter> = get_file_filter_by_cli(&parser, matches, "id", "match", "tags", None);
if !filtered { let result = self.rt
error!("Unexpected error. Exiting"); .store()
exit(1); .load_for_module(self, &parser)
}
let result = files
.iter() .iter()
.filter(|file| filter.filter_file(file))
.map(|file| { .map(|file| {
debug!("File loaded, can remove now: {:?}", file); debug!("File loaded, can remove now: {:?}", file);
let f = file.deref().borrow(); let f = file.deref().borrow();
@ -209,15 +209,18 @@ impl<'a> BM<'a> {
}) })
.unwrap_or(vec![]); .unwrap_or(vec![]);
let (filter, files) = self.get_files(matches, "with_id", "with_match", "with_tags");
if !filter {
warn!("There were no filter applied when loading the files");
}
let parser = Parser::new(JsonHeaderParser::new(None)); let parser = Parser::new(JsonHeaderParser::new(None));
files let filter : Box<CliFileFilter> = get_file_filter_by_cli(&parser,
matches,
"with_id",
"with_match",
"with_tags",
None);
self.rt
.store()
.load_for_module(self, &parser)
.into_iter() .into_iter()
.filter(|file| filter.filter_file(file))
.map(|file| { .map(|file| {
debug!("Remove tags from file: {:?}", file); debug!("Remove tags from file: {:?}", file);
@ -250,96 +253,6 @@ impl<'a> BM<'a> {
.all(|x| x) .all(|x| x)
} }
/**
* Helper function to get files from the store filtered by the constraints passed via the
* CLI
*/
fn get_files(&self,
matches: &ArgMatches,
id_key: &'static str,
match_key: &'static str,
tag_key: &'static str)
-> (bool, Vec<Rc<RefCell<File>>>)
{
if matches.is_present(id_key) {
let hash = FileHash::from(matches.value_of(id_key).unwrap());
(true, self.get_files_by_id(hash))
} else if matches.is_present(match_key) {
let matcher = String::from(matches.value_of(match_key).unwrap());
(true, self.get_files_by_match(matcher))
} else if matches.is_present(tag_key) {
let tags = matches.value_of(tag_key)
.unwrap()
.split(",")
.map(String::from)
.collect::<Vec<String>>();
(true, self.get_files_by_tags(tags))
} else {
// get all files
let parser = Parser::new(JsonHeaderParser::new(None));
(false, self.rt.store().load_for_module(self, &parser))
}
}
/**
* Get files from the store, filtere by ID
*/
fn get_files_by_id(&self, hash: FileHash) -> Vec<Rc<RefCell<File>>> {
let parser = Parser::new(JsonHeaderParser::new(None));
self.rt
.store()
.load_by_hash(self, &parser, hash)
.map(|f| vec![f])
.unwrap_or(vec![])
}
/**
* Get files from the store, filtere by Regex
*/
fn get_files_by_match(&self, matcher: String) -> Vec<Rc<RefCell<File>>> {
let parser = Parser::new(JsonHeaderParser::new(None));
let re = Regex::new(&matcher[..]).unwrap_or_else(|e| {
error!("Cannot build regex out of '{}'", matcher);
error!("{}", e);
exit(1);
});
debug!("Compiled '{}' to regex: '{:?}'", matcher, re);
self.rt
.store()
.load_for_module(self, &parser)
.into_iter()
.filter(|file| {
let f = file.deref().borrow();
let url = get_url_from_header(f.header());
debug!("url = {:?}", url);
url.map(|u| {
debug!("Matching '{}' ~= '{}'", re.as_str(), u);
re.is_match(&u[..])
}).unwrap_or(false)
})
.collect()
}
/**
* Get files from the store, filtere by tags
*/
fn get_files_by_tags(&self, tags: Vec<String>) -> Vec<Rc<RefCell<File>>> {
let parser = Parser::new(JsonHeaderParser::new(None));
self.rt
.store()
.load_for_module(self, &parser)
.into_iter()
.filter(|file| {
let f = file.deref().borrow();
get_tags_from_header(f.header()).iter().any(|tag| {
tags.iter().any(|remtag| remtag == tag)
})
})
.collect()
}
} }
/** /**

189
src/module/helpers/cli.rs Normal file
View file

@ -0,0 +1,189 @@
use std::rc::Rc;
use std::cell::RefCell;
use std::ops::Deref;
use std::process::exit;
use clap::ArgMatches;
use regex::Regex;
use storage::file::File;
use storage::file::hash::FileHash;
use storage::file::header::data::FileHeaderData;
use storage::file::id::FileID;
use storage::json::parser::JsonHeaderParser;
use storage::parser::FileHeaderParser;
use storage::parser::Parser;
pub trait CliFileFilter {
fn filter_file(&self, &Rc<RefCell<File>>) -> bool;
}
struct CliFileFilterDefault {
default: bool,
}
impl CliFileFilter for CliFileFilterDefault {
fn filter_file(&self, _: &Rc<RefCell<File>>) -> bool {
debug!("Filtering file with default value = {}", self.default);
return self.default
}
}
struct CliFileFilterByHash {
hash: FileHash,
}
impl CliFileFilter for CliFileFilterByHash {
fn filter_file(&self, file: &Rc<RefCell<File>>) -> bool {
debug!("Filtering file with hash = {}", self.hash);
let f = file.deref().borrow();
f.id().get_id() == self.hash
}
}
struct CliFileFilterByDataRegex {
regex: Regex,
}
impl CliFileFilter for CliFileFilterByDataRegex {
fn filter_file(&self, file: &Rc<RefCell<File>>) -> bool {
debug!("Filtering file with regex = {:?}", self.regex);
let f = file.deref().borrow();
self.regex.is_match(&f.data()[..])
}
}
struct CliFileFilterByHeaderRegex {
header_field_name: &'static str,
regex: Regex,
}
impl CliFileFilter for CliFileFilterByHeaderRegex {
fn filter_file(&self, file: &Rc<RefCell<File>>) -> bool {
use module::helpers::header::data::get_named_text_from_header;
debug!("Filtering file (header field = {}) with regex = {:?}",
self.header_field_name,
self.regex);
let f = file.deref().borrow();
get_named_text_from_header(self.header_field_name, f.header())
.map(|headerfield| self.regex.is_match(&headerfield[..]))
.unwrap_or(false)
}
}
struct CliFileFilterByTags {
tags: Vec<String>,
}
impl CliFileFilter for CliFileFilterByTags {
fn filter_file(&self, file: &Rc<RefCell<File>>) -> bool {
use module::helpers::header::tags::data::get_tags_from_header;
debug!("Filtering file with tags = {:?}", self.tags);
let f = file.deref().borrow();
get_tags_from_header(f.header())
.iter()
.any(|tag| self.tags.iter().any(|remtag| remtag == tag))
}
}
/**
* Helper function to get files from the store filtered by the constraints passed via the
* CLI
*/
pub fn get_file_filter_by_cli<HP>(parser: &Parser<HP>,
matches: &ArgMatches,
id_key: &'static str,
match_key: &'static str,
tag_key: &'static str,
header_field_name: Option<&'static str>)
-> Box<CliFileFilter>
where HP: FileHeaderParser,
{
if matches.is_present(id_key) {
Box::new(CliFileFilterByHash { hash: FileHash::from(matches.value_of(id_key).unwrap()) })
} else if matches.is_present(match_key) {
let matcher = String::from(matches.value_of(match_key).unwrap());
header_field_name
.and_then(|header_field_name| {
Some(get_files_by_header_field_match_filter(parser,
&matcher,
header_field_name))
})
.unwrap_or(get_file_by_match_filter(parser, &matcher))
} else if matches.is_present(tag_key) {
let tags = matches.value_of(tag_key)
.unwrap()
.split(",")
.map(String::from)
.collect::<Vec<String>>();
get_file_by_tags_filter(tags)
} else {
Box::new(CliFileFilterDefault { default: true })
}
}
/**
* Get files from the store, filtere by Regex
*/
fn get_file_by_match_filter<HP>(parser: &Parser<HP>, matcher: &String)
-> Box<CliFileFilter>
where HP: FileHeaderParser
{
let parser = Parser::new(JsonHeaderParser::new(None));
let re = Regex::new(&matcher[..]).unwrap_or_else(|e| {
error!("Cannot build regex out of '{}'", matcher);
error!("{}", e);
exit(1);
});
debug!("Compiled '{}' to regex: '{:?}'", matcher, re);
Box::new(CliFileFilterByDataRegex { regex: re })
}
fn get_files_by_header_field_match_filter<HP>(parser: &Parser<HP>,
matcher: &String,
header_field_name: &'static str)
-> Box<CliFileFilter>
where HP: FileHeaderParser,
{
let parser = Parser::new(JsonHeaderParser::new(None));
let re = Regex::new(&matcher[..]).unwrap_or_else(|e| {
error!("Cannot build regex out of '{}'", matcher);
error!("{}", e);
exit(1);
});
debug!("Compiled '{}' to regex: '{:?}'", matcher, re);
Box::new(CliFileFilterByHeaderRegex {
header_field_name: header_field_name,
regex: re
})
}
/**
* Get files from the store, filtere by tags
*/
fn get_file_by_tags_filter(tags: Vec<String>)
-> Box<CliFileFilter>
{
Box::new(CliFileFilterByTags { tags: tags })
}

View file

@ -19,21 +19,45 @@ pub mod data {
* Does no spec verification. * Does no spec verification.
*/ */
pub fn get_url_from_header(header: &FHD) -> Option<String> { pub fn get_url_from_header(header: &FHD) -> Option<String> {
get_named_text_from_header("URL", header)
}
/**
* Get an NAME from a header, whereas the header has to have the following format:
*
* { ..., "NAME": "<NAME>", ... }
*
* Does no spec verification.
*/
pub fn get_name_from_header(header: &FHD) -> Option<String> {
get_named_text_from_header("NAME", header)
}
/**
* Get a named field from the header, which has to be of this format
*
* { ..., "<name of field>": "<content as string>", ... }
*
* Does no spec verification.
*/
pub fn get_named_text_from_header(name: &'static str, header: &FHD) -> Option<String> {
match header { match header {
&FHD::Map{keys: ref ks} => { &FHD::Map{keys: ref ks} => {
let mut keys : Vec<FHD> = ks.clone(); let mut keys : Vec<FHD> = ks.clone();
keys.iter().find(|k| { keys.iter().find(|k| {
match k.deref() { match k.deref() {
&FHD::Key{name: ref n, value: ref v} => n == "URL", &FHD::Key{name: ref n, value: ref v} => n == name,
_ => false _ => false
} }
}).and_then(|urlkey| { }).and_then(|urlkey| {
match urlkey.deref().clone() { match urlkey.deref().clone() {
FHD::Key{name: _, value: ref v} => { FHD::Key{name: ref n, value: ref v} => {
match v.deref().clone() { match v.deref().clone() {
FHD::Text(s) => Some(s), FHD::Text(s) => Some(s),
_ => { _ => {
warn!("Malformed Header Data: Expected Text, found non-Text"); warn!("Malformed Header Data: Expected Text, found non-Text");
debug!(" in {}", n);
None None
}, },
} }

View file

@ -2,6 +2,7 @@
* Utility helpers for modules * Utility helpers for modules
*/ */
pub mod cli;
pub mod header; pub mod header;
pub mod utils; pub mod utils;