Merge branch 'bm' into master
This commit is contained in:
commit
80643c0c89
9 changed files with 462 additions and 37 deletions
71
etc/cli.yml
71
etc/cli.yml
|
@ -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
|
||||
|
|
43
src/main.rs
43
src/main.rs
|
@ -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
173
src/module/bm/commands.rs
Normal 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
79
src/module/bm/header.rs
Normal 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
69
src/module/bm/mod.rs
Normal 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(())
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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)[..]);
|
||||
|
||||
|
@ -267,12 +260,12 @@ impl StorageBackendError {
|
|||
|
||||
fn build(action: &'static str,
|
||||
desc: &'static str,
|
||||
data : Option<String>) -> StorageBackendError
|
||||
data : Option<String>) -> 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)
|
||||
|
|
|
@ -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>> },
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue