Merge branch 'bm' into master

This commit is contained in:
Matthias Beyer 2015-12-02 12:29:31 +01:00
commit 80643c0c89
9 changed files with 462 additions and 37 deletions

View file

@ -113,6 +113,77 @@ subcommands:
help: Sets the level of debugging information
required: false
subcommands:
- add:
about: Add bookmark
version: 0.1
author: Matthias Beyer <mail@beyermatthias.de>
args:
- url:
short: u
long: url
help: Add a new URL as bookmark
required: true
takes_value: true
- tags:
short: t
long: tags
help: Add these tags to the URL
required: false
takes_value: true
- list:
about: List bookmarks
version: 0.1
author: Matthias Beyer <mail@beyermatthias.de>
args:
- match:
short: m
long: match
help: Match for regex
required: false
takes_value: true
- tags:
short: t
long: tags
help: Filter for these tags
required: false
takes_value: true
- remove:
about: Remove bookmark(s)
version: 0.1
author: Matthias Beyer <mail@beyermatthias.de>
args:
- id:
long: id
help: Delete Bookmark with ID
required: false
takes_value: true
- match:
short: m
long: match
help: Match for regex
required: false
takes_value: true
- tags:
short: t
long: tags
help: Filter for these tags
required: false
takes_value: true
- check:
short: c
long: check
help: Ensure there are no references to this link
required: false
takes_value: false
- todo:
about: Todo module
version: 0.1

View file

@ -4,14 +4,19 @@
#[macro_use] extern crate serde_json;
#[macro_use] extern crate glob;
#[macro_use] extern crate uuid;
#[macro_use] extern crate regex;
#[macro_use] extern crate prettytable;
extern crate config;
extern crate regex;
use cli::CliConfig;
use configuration::Configuration;
use runtime::{ImagLogger, Runtime};
use clap::App;
use module::Module;
use module::ModuleError;
use module::CommandEnv;
use module::bm::BMModule;
use storage::backend::StorageBackend;
mod cli;
mod configuration;
@ -20,6 +25,8 @@ mod module;
mod storage;
mod ui;
use std::process::exit;
fn main() {
let yaml = load_yaml!("../etc/cli.yml");
let app = App::from_yaml(yaml);
@ -36,5 +43,39 @@ fn main() {
debug!("Runtime : {:?}", &rt);
let backend = StorageBackend::new(&rt).unwrap_or_else(|e| {
error!("Error: {}", e);
exit(1);
});
if let Some(matches) = rt.config.cli_matches.subcommand_matches("bm") {
let module = BMModule::new(&rt);
let commands = module.get_commands(&rt);
if let Some(command) = matches.subcommand_name() {
debug!("Subcommand: {}", command);
let cmdenv = CommandEnv {
rt: &rt,
bk: &backend,
matches: matches.subcommand_matches(command).unwrap(),
};
let result = match commands.get(command) {
Some(f) => f(&module, cmdenv),
None => Err(ModuleError::new("No subcommand found")),
};
debug!("Result of command: {:?}", result);
} else {
debug!("No subcommand");
}
module.shutdown(&rt);
} else {
// Err(ModuleError::mk("No commandline call"))
info!("No commandline call...")
}
info!("Hello, world!");
}

173
src/module/bm/commands.rs Normal file
View file

@ -0,0 +1,173 @@
use runtime::Runtime;
use storage::backend::{StorageBackendError, StorageBackend};
use module::Module;
use module::ModuleError;
use module::CommandResult;
use module::CommandEnv;
use module::bm::header::build_header;
use module::bm::header::get_tags_from_header;
use storage::json::parser::JsonHeaderParser;
use storage::parser::{Parser, FileHeaderParser};
use storage::file::File;
use ui::file::{FilePrinter, TablePrinter};
use std::vec::IntoIter;
use clap::ArgMatches;
use regex::Regex;
pub fn add_command(module: &Module, env: CommandEnv) -> CommandResult {
let url = env.matches.value_of("url").unwrap();
let tags = get_tags(env.rt, env.matches);
info!("Adding url '{}' with tags '{:?}'", url, tags);
let header = build_header(&String::from(url), &tags);
let file = File::new_with_header(module, header);
let parser = Parser::new(JsonHeaderParser::new(None));
let putres = env.bk.put_file(file, &parser);
putres.map_err(|sberr| {
let mut err = ModuleError::new("Storage Backend Error");
err.caused_by = Some(Box::new(sberr));
err
})
}
pub fn list_command(module: &Module, env: CommandEnv) -> CommandResult {
let printer = TablePrinter::new(env.rt.is_verbose(), env.rt.is_debugging());
let files = get_filtered_files_from_backend(module, &env);
debug!("Printing files now");
printer.print_files(files);
Ok(())
}
pub fn remove_command(module: &Module, env: CommandEnv) -> CommandResult {
let checked : bool = run_removal_checking(&env);
debug!("Checked mode: {}", checked);
if let Some(id) = get_id(env.rt, env.matches) {
debug!("Remove by id: {}", id);
let parser = Parser::new(JsonHeaderParser::new(None));
let file = env.bk.get_file_by_id(module, &id, &parser).unwrap();
debug!("Remove file : {:?}", file);
if let Err(e) = env.bk.remove_file(module, file, checked) {
debug!("Remove failed");
let mut err = ModuleError::new("Removing file failed");
err.caused_by = Some(Box::new(e));
Err(err)
} else {
debug!("Remove worked");
Ok(())
}
} else {
debug!("Remove more than one file");
let files = get_filtered_files_from_backend(module, &env);
let nfiles = files.len();
info!("Removing {} Files", nfiles);
let errs = files.map(|file| {
debug!("Remove file: {:?}", file);
env.bk.remove_file(module, file, checked)
})
.filter(|e| e.is_err())
.map(|e| {
let err = e.err().unwrap();
warn!("Error occured in Filesystem operation: {}", err);
err
})
.collect::<Vec<StorageBackendError>>();
let nerrs = errs.len();
if nerrs != 0 {
warn!("{} Errors occured while removing {} files", nerrs, nfiles);
let moderr = ModuleError::new("File removal failed");
// TODO : Collect StorageBackendErrors
Err(moderr)
} else {
Ok(())
}
}
}
/*
*
* Private helpers
*
*/
fn get_filtered_files_from_backend<'a>(module: &'a Module,
env: &CommandEnv) -> IntoIter<File<'a>>
{
let parser = Parser::new(JsonHeaderParser::new(None));
let tags = get_tags(env.rt, env.matches);
debug!("Tags: {:?}", tags);
env.bk.iter_files(module, &parser).and_then(|files| {
let f = files.filter(|file| {
if tags.len() != 0 {
debug!("Checking tags of: {:?}", file.id());
get_tags_from_header(&file.header()).iter()
.any(|t| tags.contains(t))
} else {
true
}
}).filter(|file| {
debug!("Checking matches of: {:?}", file.id());
get_matcher(env.rt, env.matches)
.and_then(|r| Some(file.matches_with(&r)))
.unwrap_or(true)
}).collect::<Vec<File>>();
Some(f)
}).unwrap_or(Vec::<File>::new()).into_iter()
}
fn get_tags<'a>(rt: &Runtime, sub: &ArgMatches<'a, 'a>) -> Vec<String> {
debug!("Fetching tags from commandline");
sub.value_of("tags").and_then(|tags|
Some(tags.split(",")
.into_iter()
.map(|s| s.to_string())
.filter(|e|
if e.contains(" ") {
warn!("Tag contains spaces: '{}'", e);
false
} else {
true
}).collect()
)
).or(Some(vec![])).unwrap()
}
fn get_matcher<'a>(rt: &Runtime, sub: &ArgMatches<'a, 'a>) -> Option<Regex> {
debug!("Fetching matcher from commandline");
if let Some(s) = sub.value_of("match") {
if let Ok(r) = Regex::new(s) {
return Some(r)
} else {
error!("Regex error, continuing without regex");
}
}
None
}
fn get_id<'a>(rt: &Runtime, sub: &ArgMatches<'a, 'a>) -> Option<String> {
debug!("Fetching id from commandline");
sub.value_of("id").and_then(|s| Some(String::from(s)))
}
/*
* Checks whether the commandline call was set to run the removal "checked",
* so if another entry from the store refers to this ID, do not remove the file.
*/
fn run_removal_checking(env: &CommandEnv) -> bool {
env.matches.is_present("check")
}

79
src/module/bm/header.rs Normal file
View file

@ -0,0 +1,79 @@
use storage::file::FileHeaderSpec as FHS;
use storage::file::FileHeaderData as FHD;
use std::ops::Deref;
pub fn get_spec() -> FHS {
FHS::Map { keys: vec![ url_key(), tags_key() ] }
}
fn url_key() -> FHS {
FHS::Key { name: String::from("URL"), value_type: Box::new(FHS::Text) }
}
fn tags_key() -> FHS {
FHS::Key { name: String::from("TAGS"), value_type: Box::new(text_array()) }
}
fn text_array() -> FHS {
FHS::Array { allowed_types: vec![FHS::Text] }
}
pub fn build_header(url: &String, tags: &Vec<String>) -> FHD {
FHD::Map {
keys: vec![
FHD::Key {
name: String::from("URL"),
value: Box::new(FHD::Text(url.clone()))
},
FHD::Key {
name: String::from("TAGS"),
value: Box::new(build_tag_array(tags))
}
]
}
}
fn build_tag_array(tags: &Vec<String>) -> FHD {
let texttags = tags.into_iter().map(|t| FHD::Text(t.clone())).collect();
FHD::Array { values: Box::new(texttags) }
}
pub fn get_tags_from_header(header: &FHD) -> Vec<String> {
let mut tags : Vec<String> = vec![];
fn match_array(a: &Box<FHD>) -> Vec<String> {
let mut tags : Vec<String> = vec![];
match a.deref() {
&FHD::Array{values: ref vs} => {
let values : Vec<FHD> = vs.deref().clone();
for value in values {
match value {
FHD::Text(t) => tags.push(t),
_ => warn!("Malformed Header Data: Expected Text, found non-Text"),
}
}
}
_ => warn!("Malformed Header Data: Expected Array, found non-Array"),
}
tags
}
match header {
&FHD::Map{keys: ref ks} => {
let keys : Vec<FHD> = ks.clone();
for key in keys {
match key {
FHD::Key{name: _, value: ref v} => return match_array(v),
_ => warn!("Malformed Header Data: Expected Key, found non-Key"),
}
}
},
_ => warn!("Malformed Header Data: Expected Map, found non-Map"),
}
tags
}

69
src/module/bm/mod.rs Normal file
View file

@ -0,0 +1,69 @@
use runtime::Runtime;
use module::Module;
use module::CommandMap;
use module::ModuleResult;
use module::ModuleError;
use std::path::Path;
use std::result::Result;
use std::fmt::Result as FMTResult;
use std::fmt::Formatter;
use std::fmt::Debug;
use clap::ArgMatches;
use regex::Regex;
mod header;
mod commands;
use self::header::build_header;
use storage::json::parser::JsonHeaderParser;
use storage::parser::FileHeaderParser;
use self::commands::*;
pub struct BMModule {
path: Option<String>,
}
const CALLNAMES : &'static [&'static str] = &[ "bm", "bookmark" ];
impl BMModule {
pub fn new(rt : &Runtime) -> BMModule {
BMModule {
path: None
}
}
}
impl Module for BMModule {
fn callnames(&self) -> &'static [&'static str] {
CALLNAMES
}
fn name(&self) -> &'static str{
"bookmark"
}
fn shutdown(&self, rt : &Runtime) -> ModuleResult {
Ok(())
}
fn get_commands(&self, rt: &Runtime) -> CommandMap {
let mut hm = CommandMap::new();
hm.insert("add", add_command);
hm.insert("list", list_command);
hm.insert("remove", remove_command);
hm
}
}
impl Debug for BMModule {
fn fmt(&self, fmt: &mut Formatter) -> FMTResult {
write!(fmt, "[Module][BM]");
Ok(())
}
}

View file

@ -1,11 +0,0 @@
use std::result::Result;
use super::ModuleError;
use storage::backend::{StorageBackend, StorageBackendError};
type CommandError = Result<ModuleError, StorageBackendError>;
type CommandResult = Result<(), Result<ModuleError, CommandError>>;
pub trait ExecutableCommand {
fn exec(StorageBackend) -> CommandResult;
}

View file

@ -3,21 +3,28 @@ use std::error::Error;
use std::fmt::Formatter;
use std::fmt::Result as FMTResult;
use std::fmt::Display;
use std::fmt::Debug;
use std::path::Path;
use std::result::Result;
use std::collections::HashMap;
use storage::backend::StorageBackend;
use self::command::ExecutableCommand;
mod command;
use clap::{App, ArgMatches};
use storage::backend::{StorageBackend, StorageBackendError};
pub mod bm;
#[derive(Debug)]
pub struct ModuleError {
desc: String,
caused_by: Option<Box<Error>>,
}
impl ModuleError {
fn mk(desc: &'static str) -> ModuleError {
pub fn new(desc: &'static str) -> ModuleError {
ModuleError {
desc: desc.to_owned().to_string(),
caused_by: None,
}
}
}
@ -29,7 +36,7 @@ impl Error for ModuleError {
}
fn cause(&self) -> Option<&Error> {
None
self.caused_by.as_ref().map(|e| &**e)
}
}
@ -40,20 +47,23 @@ impl Display for ModuleError {
}
}
pub struct CommandEnv<'a> {
pub rt: &'a Runtime<'a>,
pub bk: &'a StorageBackend,
pub matches: &'a ArgMatches<'a, 'a>,
}
pub type ModuleResult = Result<(), ModuleError>;
pub type CommandResult = ModuleResult;
pub type CommandMap<'a> = HashMap<&'a str, fn(&Module, CommandEnv) -> CommandResult>;
pub trait Module {
pub trait Module : Debug {
fn new(rt : &Runtime) -> Self;
fn callnames() -> &'static [&'static str];
fn callnames(&self) -> &'static [&'static str];
fn name(&self) -> &'static str;
fn execute(&self, rt : &Runtime) -> ModuleResult;
fn shutdown(&self, rt : &Runtime) -> ModuleResult;
fn getCommandBuilder<T, F>() -> F
where F: FnOnce(StorageBackend) -> T,
T: ExecutableCommand;
fn get_commands(&self, rt: &Runtime) -> CommandMap;
}

View file

@ -52,13 +52,6 @@ impl StorageBackend {
})
}
fn build<M: Module>(rt: &Runtime, m: &M) -> StorageBackend {
let path = rt.get_rtp() + m.name() + "/store";
// TODO: Don't use "/store" but value from configuration
debug!("Building StorageBackend for {}", path);
StorageBackend::new(path)
}
fn get_file_ids(&self, m: &Module) -> Option<Vec<FileID>> {
let list = glob(&self.prefix_of_files_for_module(m)[..]);
@ -272,7 +265,7 @@ impl StorageBackendError {
StorageBackendError {
action: String::from(action),
desc: String::from(desc),
dataDump: data,
data_dump: data,
caused_by: None,
}
}
@ -291,7 +284,7 @@ impl Error for StorageBackendError {
}
impl Display for StorageBackendError {
impl<'a> Display for StorageBackendError {
fn fmt(&self, f: &mut Formatter) -> FMTResult {
write!(f, "StorageBackendError[{}]: {}",
self.action, self.desc)

View file

@ -16,7 +16,7 @@ pub enum FileHeaderSpec {
UInteger,
Float,
Text,
Key { name: &'static str, value_type: Box<FileHeaderSpec> },
Key { name: String, value_type: Box<FileHeaderSpec> },
Map { keys: Vec<FileHeaderSpec> },
Array { allowed_types: Vec<FileHeaderSpec> },
}
@ -30,7 +30,7 @@ pub enum FileHeaderData {
UInteger(u64),
Float(f64),
Text(String),
Key { name: &'static str, value: Box<FileHeaderData> },
Key { name: String, value: Box<FileHeaderData> },
Map { keys: Vec<FileHeaderData> },
Array { values: Box<Vec<FileHeaderData>> },
}