diff --git a/src/cli.rs b/src/cli.rs deleted file mode 100644 index e422f565..00000000 --- a/src/cli.rs +++ /dev/null @@ -1,93 +0,0 @@ -use std::fmt::{Debug, Formatter, Error}; - -extern crate clap; -use clap::{App, ArgMatches}; - -pub struct ModuleConfig { - pub load : bool, -} - -impl ModuleConfig { - pub fn new() -> ModuleConfig { - ModuleConfig { - load: false, - } - } -} - -pub struct CliConfig<'a> { - pub module_configs : Vec, - pub cli_matches : ArgMatches<'a, 'a>, -} - -impl<'a> CliConfig<'a> { - pub fn new(app : clap::App<'a, 'a, 'a, 'a, 'a, 'a>) -> CliConfig<'a> { - CliConfig { - module_configs: vec![], - cli_matches: app.get_matches(), - } - } - - /** - * Check whether the CLI says we should run verbose - */ - pub fn is_verbose(&self) -> bool { - self.cli_matches.is_present("verbose") || self.is_debugging() - } - - /** - * Check whether the CLI says we should run in debugging - */ - pub fn is_debugging(&self) -> bool { - self.cli_matches.is_present("debug") - } - - /** - * Check whether the CLI says we should run with reporting - */ - pub fn report_exit(&self) -> bool { - self.cli_matches.is_present("report") - } - - /** - * Get the runtime path the CLI configured - */ - pub fn get_rtp(&self) -> Option { - self.cli_matches.value_of("rtp").and_then(|s| Some(String::from(s))) - } - - /** - * Get the store path the CLI configured - * - * TODO: Implement properly. Not working by now. - */ - pub fn store_path(&self) -> Option { - self.get_rtp().and_then(|rtp| { - self.cli_matches - .value_of("storepath") - .and_then(|s| Some(rtp + s)) - }) - } - - pub fn editor(&self) -> Option { - self.cli_matches.value_of("editor").and_then(|s| Some(String::from(s))) - } - - pub fn editor_opts(&self) -> String { - self.cli_matches - .value_of("editor_opts") - .map(|s| String::from(s)) - .unwrap_or(String::from("")) - } -} - -impl<'a> Debug for CliConfig<'a> { - - fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { - write!(f, "CliConfig (verbose: {}, debugging: {}, rtp: {})", - self.is_verbose(), - self.is_debugging(), - self.get_rtp().or(Some(String::from("NONE"))).unwrap()) - } - -} diff --git a/src/configuration.rs b/src/configuration.rs deleted file mode 100644 index 2fbaf70e..00000000 --- a/src/configuration.rs +++ /dev/null @@ -1,124 +0,0 @@ -use std::fmt::{Debug, Formatter, Error}; -use std::path::Path; - -use config::reader::from_file; -use config::types::Config as Cfg; -use cli::CliConfig; - -/** - * Configuration object which represents the configuration file. - * - * It gets passed a CliConfig object on ::new(), retreives some data from this one which is then - * provided as default value to the callee if there is no value for it in the configuration. - * - * TODO: Setup is kinda ugly, as we re-use data from the CLI, which is the job of the Runtime - * object later. - */ -pub struct Configuration { - pub rtp : String, - pub store_sub : String, - pub editor : Option, - pub editor_opts : String, - pub report_exit : bool, -} - -impl Configuration { - - pub fn new(config: &CliConfig) -> Configuration { - let rtp = rtp_path(config).or(default_path()).unwrap_or(String::from("/tmp/")); - - - let cfg = fetch_config(&rtp); - - let store_sub = String::from(cfg.lookup_str("store").unwrap_or("/store")); - let editor = cfg.lookup_str("editor").map(String::from); - let editor_opts = String::from(cfg.lookup_str("editor-opts").unwrap_or("")); - let report_exit = cfg.lookup_boolean("report-exit").unwrap_or(false); - - debug!("Building configuration"); - debug!(" - store sub : {}", store_sub); - debug!(" - runtimepath: {}", rtp); - debug!(" - editor : {:?}", editor); - debug!(" - editor-opts: {}", editor_opts); - debug!(" - report exit: {}", report_exit); - - Configuration { - store_sub: store_sub, - rtp: rtp, - editor: editor, - editor_opts: editor_opts, - report_exit: report_exit, - } - } - - /** - * Get the store path the configuration configured - */ - pub fn store_path(&self) -> String { - format!("{}{}", self.rtp, self.store_sub) - } - - /** - * Get the runtime path the configuration configured - */ - pub fn get_rtp(&self) -> String { - self.rtp.clone() - } - - pub fn editor(&self) -> Option { - self.editor.clone() - } - - pub fn editor_opts(&self) -> String { - self.editor_opts.clone() - } - - pub fn report_exit(&self) -> bool { - self.report_exit - } - -} - -/** - * Helper to get the runtimepath from the CLI - */ -fn rtp_path(config: &CliConfig) -> Option { - config.cli_matches.value_of("rtp") - .and_then(|s| Some(String::from(s))) -} - -fn fetch_config(rtp: &String) -> Cfg { - use std::process::exit; - - let configpath = format!("{}{}", rtp, "/config"); - from_file(Path::new(&configpath)).map_err(|e| { - println!("Error loading config at '{}' -> {:?}", configpath, e); - println!("Exiting now."); - exit(1) - }).unwrap() -} - -/** - * Default runtime path, if available. - */ -fn default_path() -> Option { - use std::env::home_dir; - - home_dir().and_then(|mut buf| { - buf.push("/.imag"); - buf.to_str().map(|s| String::from(s)) - }) - -} - -impl Debug for Configuration { - - fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { - write!(f, "Configuration (rtp: {}, store path: {})", - self.get_rtp(), - self.store_path() - ) - } - -} - diff --git a/src/main.rs b/src/main.rs index 08d2ffcf..32486748 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,80 +1,4 @@ -#![deny(unused_imports)] -#![deny(unused_variables)] -#![deny(unused_must_use)] -#![deny(unused_mut)] -#![deny(non_shorthand_field_patterns)] - -#[macro_use] extern crate clap; -#[macro_use] extern crate log; -#[macro_use] extern crate serde; -#[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 hoedown; -extern crate url; -extern crate config; -extern crate open; -extern crate itertools; -extern crate ansi_term; -extern crate rand; -extern crate yaml_rust; - -pub use cli::CliConfig; -pub use configuration::Configuration; -pub use runtime::{ImagLogger, Runtime}; -pub use clap::App; -pub use module::Module; - -pub mod cli; -pub mod configuration; -pub mod runtime; -pub mod module; -pub mod storage; -pub mod ui; -pub mod util; - -pub use module::bm::BM; -pub use module::notes::Notes; - fn main() { - use std::process::exit; - use ansi_term::Colour::Yellow; - - let yaml = load_yaml!("../etc/cli.yml"); - let app = App::from_yaml(yaml); - let config = CliConfig::new(app); - - ImagLogger::init(&config).map_err(|e| { - error!("Could not initialize logger"); - debug!("Could not initialize logger: {:?}", e); - exit(1); - }).ok(); - - let configuration = Configuration::new(&config); - - debug!("Logger created!"); - debug!("CliConfig : {:?}", &config); - debug!("Configuration: {:?}", &configuration); - - let rt = Runtime::new(configuration, config); - - debug!("Runtime : {:?}", &rt); - - let res = match rt.config.cli_matches.subcommand_name() { - Some("bm") => BM::new(&rt).exec(rt.config.cli_matches.subcommand_matches("bm").unwrap()), - Some("notes") => Notes::new(&rt).exec(rt.config.cli_matches.subcommand_matches("notes").unwrap()), - _ => false, - }; - - info!("{}", Yellow.paint(format!("Module execution ended with {}", res))); - - if rt.report_exit() { - if res { - println!("Ok"); - } else { - println!("Error"); - } - } + println!("It works"); } + diff --git a/src/module/bm/header.rs b/src/module/bm/header.rs deleted file mode 100644 index 3bbe2861..00000000 --- a/src/module/bm/header.rs +++ /dev/null @@ -1,40 +0,0 @@ -use module::helpers::header as headerhelpers; -use storage::file::header::data::FileHeaderData as FHD; -use storage::file::header::spec::FileHeaderSpec as FHS; - -pub fn get_spec() -> FHS { - FHS::Map { - keys: vec![ - headerhelpers::tags::spec::url_key(), - headerhelpers::tags::spec::tags_key(), - ] - } -} - -pub fn build_header(url: String, tags: Vec) -> 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(headerhelpers::tags::data::build_tag_array(tags)) - } - ] - } -} - -pub fn get_tags_from_header(header: &FHD) -> Vec { - headerhelpers::tags::data::get_tags_from_header(header) -} - -pub fn get_url_from_header(header: &FHD) -> Option { - headerhelpers::data::get_url_from_header(header) -} - -pub fn rebuild_header_with_tags(header: &FHD, tags: Vec) -> Option { - get_url_from_header(header).map(|url| build_header(url, tags)) -} - diff --git a/src/module/bm/mod.rs b/src/module/bm/mod.rs deleted file mode 100644 index f0663290..00000000 --- a/src/module/bm/mod.rs +++ /dev/null @@ -1,343 +0,0 @@ -use std::fmt::{Debug, Formatter}; -use std::fmt; -use std::ops::Deref; - -use clap::ArgMatches; - -use runtime::Runtime; -use module::Module; - -use storage::parser::Parser; -use storage::json::parser::JsonHeaderParser; -use module::helpers::cli::create_tag_filter; -use module::helpers::cli::create_hash_filter; -use module::helpers::cli::create_text_header_field_grep_filter; -use module::helpers::cli::CliFileFilter; - -mod header; - -use self::header::get_url_from_header; -use self::header::get_tags_from_header; - -pub struct BM<'a> { - rt: &'a Runtime<'a>, - parser: Parser, -} - -impl<'a> BM<'a> { - - pub fn new(rt: &'a Runtime<'a>) -> BM<'a> { - BM { - rt: rt, - parser: Parser::new(JsonHeaderParser::new(None)), - } - } - - /** - * Subcommand: add - */ - fn command_add(&self, matches: &ArgMatches) -> bool { - use ansi_term::Colour::{Green, Yellow, Red}; - use std::process::exit; - use self::header::build_header; - - let url = matches.value_of("url").map(String::from).unwrap(); // clap ensures this is present - - if !self.validate_url(&url) { - error!("URL validation failed, exiting."); - exit(1); - } else { - debug!("Verification succeeded"); - } - - let tags = matches.value_of("tags").and_then(|s| { - Some(s.split(",").map(String::from).collect()) - }).unwrap_or(vec![]); - - debug!("Building header with"); - debug!(" url = '{:?}'", url); - debug!(" tags = '{:?}'", tags); - let header = build_header(url, tags); - - let fileid = self.rt - .store() - .new_file_with_header(self, header); - - let result = self.rt - .store() - .load(self, &self.parser, &fileid) - .map(|file| { - info!("{}", Yellow.paint(format!("Created file in memory: {}", fileid))); - self.rt - .store() - .persist(&self.parser, file) - }) - .unwrap_or(false); - - if result { - info!("{}", Red.paint("Adding worked")); - } else { - info!("{}", Green.paint("Adding failed")); - } - - result - } - - fn validate_url(&self, url: &String) -> bool - { - use util::is_url; - - if !is_url(url) { - error!("Url '{}' is not a valid URL. Will not store.", url); - return false; - } - - let is_in_store = self.rt - .store() - .load_for_module(self, &self.parser) - .iter() - .any(|file| { - let f = file.deref().borrow(); - get_url_from_header(f.header()).map(|url_in_store| { - &url_in_store == url - }).unwrap_or(false) - }); - - if is_in_store { - error!("URL '{}' seems to be in the store already", url); - return false; - } - - return true; - } - - /** - * Subcommand: list - */ - fn command_list(&self, matches: &ArgMatches) -> bool { - use ui::file::{FilePrinter, TablePrinter}; - use std::ops::Deref; - - let filter = { - let hash_filter = create_hash_filter(matches, "id", true); - let text_filter = create_text_header_field_grep_filter(matches, "match", "URL", true); - let tags_filter = create_tag_filter(matches, "tags", true); - hash_filter.and(Box::new(text_filter)).and(Box::new(tags_filter)) - }; - - let files = self.rt - .store() - .load_for_module(self, &self.parser) - .into_iter() - .filter(|file| filter.filter_file(file)); - let pretty = matches.is_present("pretty"); - let printer = TablePrinter::new(self.rt.is_verbose(), self.rt.is_debugging(), pretty); - - printer.print_files_custom(files, - &|file| { - let fl = file.deref().borrow(); - let hdr = fl.header(); - let url = get_url_from_header(hdr).unwrap_or(String::from("Parser error")); - let tags = get_tags_from_header(hdr); - - debug!("Custom printer field: url = '{:?}'", url); - debug!("Custom printer field: tags = '{:?}'", tags); - - vec![url, tags.join(", ")] - } - ); - true - } - - /** - * Subcommand: open - */ - fn command_open(&self, matches: &ArgMatches) -> bool { - use ansi_term::Colour::{Green, Red}; - use open; - - let filter : Box = { - let hash_filter = create_hash_filter(matches, "id", true); - let text_filter = create_text_header_field_grep_filter(matches, "match", "URL", true); - let tags_filter = create_tag_filter(matches, "tags", true); - Box::new(hash_filter.and(Box::new(text_filter)).and(Box::new(tags_filter))) - }; - let result = self.rt - .store() - .load_for_module(self, &self.parser) - .iter() - .filter(|file| filter.filter_file(file)) - .map(|file| { - debug!("File loaded, can open now: {:?}", file); - let f = file.deref().borrow(); - get_url_from_header(f.header()).map(|url| { - if open::that(&url[..]).is_ok() { - info!("{}", Green.paint(format!("open({})", url))); - true - } else { - info!("{}", Red.paint(format!("could not open({})", url))); - false - } - }) - .unwrap_or(false) - }) - .fold((0, 0), |acc, succeeded| { - let (worked, failed) = acc; - if succeeded { - (worked + 1, failed) - } else { - (worked, failed + 1) - } - }); - - let (succ, fail) = result; - info!("{}", Green.paint(format!("open() succeeded for {} files", succ))); - info!("{}", Red.paint(format!( "open() failed for {} files", fail))); - return fail == 0; - } - - /** - * Subcommand: remove - */ - fn command_remove(&self, matches: &ArgMatches) -> bool { - use ansi_term::Colour::{Green, Red}; - - let filter = { - let hash_filter = create_hash_filter(matches, "id", false); - let text_filter = create_text_header_field_grep_filter(matches, "match", "URL", false); - let tags_filter = create_tag_filter(matches, "tags", false); - hash_filter.or(Box::new(text_filter)).or(Box::new(tags_filter)) - }; - - let result = self.rt - .store() - .load_for_module(self, &self.parser) - .iter() - .filter(|file| filter.filter_file(file)) - .map(|file| { - debug!("File loaded, can remove now: {:?}", file); - let f = file.deref().borrow(); - self.rt.store().remove(f.id().clone()) - }) - .fold((0, 0), |acc, succeeded| { - let (worked, failed) = acc; - if succeeded { - (worked + 1, failed) - } else { - (worked, failed + 1) - } - }); - - let (worked, failed) = result; - - info!("{}", Green.paint(format!("Removing succeeded for {} files", worked))); - info!("{}", Red.paint(format!( "Removing failed for {} files", failed))); - - return failed == 0; - } - - /** - * Subcommand: add_tags - */ - fn command_add_tags(&self, matches: &ArgMatches) -> bool { - use module::helpers::header::tags::data::alter_tags_in_files; - use self::header::rebuild_header_with_tags; - - alter_tags_in_files(self, matches, &self.parser, |old_tags, cli_tags| { - let mut new_tags = old_tags.clone(); - new_tags.append(&mut cli_tags.clone()); - new_tags - }, rebuild_header_with_tags) - } - - /** - * Subcommand: rm_tags - */ - fn command_rm_tags(&self, matches: &ArgMatches) -> bool { - use module::helpers::header::tags::data::alter_tags_in_files; - use self::header::rebuild_header_with_tags; - - alter_tags_in_files(self, matches, &self.parser, |old_tags, cli_tags| { - old_tags.clone() - .into_iter() - .filter(|tag| !cli_tags.contains(tag)) - .collect() - }, rebuild_header_with_tags) - } - - /** - * Subcommand: set_tags - */ - fn command_set_tags(&self, matches: &ArgMatches) -> bool { - use module::helpers::header::tags::data::alter_tags_in_files; - use self::header::rebuild_header_with_tags; - - alter_tags_in_files(self, matches, &self.parser, |_, cli_tags| { - cli_tags.clone() - }, rebuild_header_with_tags) - } - -} - -/** - * Trait implementation for BM module - */ -impl<'a> Module<'a> for BM<'a> { - - fn exec(&self, matches: &ArgMatches) -> bool { - use ansi_term::Colour::Red; - - match matches.subcommand_name() { - Some("add") => { - self.command_add(matches.subcommand_matches("add").unwrap()) - }, - - Some("list") => { - self.command_list(matches.subcommand_matches("list").unwrap()) - }, - - Some("open") => { - self.command_open(matches.subcommand_matches("open").unwrap()) - }, - - Some("remove") => { - self.command_remove(matches.subcommand_matches("remove").unwrap()) - }, - - Some("add_tags") => { - self.command_add_tags(matches.subcommand_matches("add_tags").unwrap()) - }, - - Some("rm_tags") => { - self.command_rm_tags(matches.subcommand_matches("rm_tags").unwrap()) - }, - - Some("set_tags") => { - self.command_set_tags(matches.subcommand_matches("set_tags").unwrap()) - }, - - Some(_) | None => { - info!("{}", Red.bold().paint("No command given, doing nothing")); - false - }, - } - } - - fn name(&self) -> &'static str { - "bookmark" - } - - fn runtime(&self) -> &Runtime { - self.rt - } -} - -impl<'a> Debug for BM<'a> { - - fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { - try!(write!(fmt, "BM")); - Ok(()) - } - -} - diff --git a/src/module/helpers/cli.rs b/src/module/helpers/cli.rs deleted file mode 100644 index bd644d91..00000000 --- a/src/module/helpers/cli.rs +++ /dev/null @@ -1,224 +0,0 @@ -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; - -pub trait CliFileFilter { - - fn filter_file(&self, &Rc>) -> bool; - - fn not(self) -> CliFileFilterNot - where Self: Sized + 'static - { - CliFileFilterNot { - a: Box::new(self), - } - } - - fn or(self, other: Box) -> CliFileFilterOr - where Self: Sized + 'static - { - CliFileFilterOr { - a: Box::new(self), - b: other - } - } - - fn and(self, other: Box) -> CliFileFilterAnd - where Self: Sized + 'static - { - CliFileFilterAnd { - a: Box::new(self), - b: other - } - } - -} - -pub struct CliFileFilterNot { - a: Box, -} - -impl CliFileFilter for CliFileFilterNot { - - fn filter_file(&self, f: &Rc>) -> bool { - !self.a.filter_file(f) - } - -} - -pub struct CliFileFilterOr { - a: Box, - b: Box -} - -impl CliFileFilter for CliFileFilterOr { - - fn filter_file(&self, f: &Rc>) -> bool { - self.a.filter_file(f) || self.b.filter_file(f) - } - -} - -pub struct CliFileFilterAnd { - a: Box, - b: Box -} - -impl CliFileFilter for CliFileFilterAnd { - - fn filter_file(&self, f: &Rc>) -> bool { - self.a.filter_file(f) && self.b.filter_file(f) - } - -} - -pub struct CliFileFilterByHash { - default: bool, - hash: Option, -} - -impl CliFileFilter for CliFileFilterByHash { - - fn filter_file(&self, file: &Rc>) -> bool { - self.hash.clone().map(|h| { - debug!("Filtering file with hash = {}", h); - let f = file.deref().borrow(); - f.id().get_id() == h - }) - .unwrap_or(self.default) - } - -} - -pub struct CliFileFilterByDataRegex { - default: bool, - regex: Option, -} - -impl CliFileFilter for CliFileFilterByDataRegex { - - fn filter_file(&self, file: &Rc>) -> bool { - self.regex.clone().map(|r| { - debug!("Filtering file with regex = {:?}", r); - let f = file.deref().borrow(); - r.is_match(&f.data()[..]) - }) - .unwrap_or(self.default) - } - -} - -pub struct CliFileFilterByHeaderRegex { - default: bool, - header_field_name: &'static str, - regex: Option, -} - -impl CliFileFilter for CliFileFilterByHeaderRegex { - - fn filter_file(&self, file: &Rc>) -> bool { - use module::helpers::header::data::get_named_text_from_header; - - self.regex.clone().map(|r| { - debug!("Filtering file (header field = {}) with regex = {:?}", self.header_field_name, r); - - let f = file.deref().borrow(); - get_named_text_from_header(self.header_field_name, f.header()) - .map(|headerfield| r.is_match(&headerfield[..])) - .unwrap_or(self.default) - }) - .unwrap_or(self.default) - } - -} - -pub struct CliFileFilterByTags { - default: bool, - tags: Option>, -} - -impl CliFileFilter for CliFileFilterByTags { - - fn filter_file(&self, file: &Rc>) -> bool { - use module::helpers::header::tags::data::get_tags_from_header; - - self.tags.clone().map(|ts| { - debug!("Filtering file with tags = {:?}", ts); - - let f = file.deref().borrow(); - get_tags_from_header(f.header()) - .iter() - .any(|tag| ts.iter().any(|remtag| remtag == tag)) - }) - .unwrap_or(self.default) - } - -} - -/* - * - * - * Functions to generate filters - * - * - */ - -pub fn create_hash_filter(matches: &ArgMatches, id_key: &'static str, default: bool) -> CliFileFilterByHash { - CliFileFilterByHash { - hash: matches.value_of(id_key).map(FileHash::from), - default: default - } -} - -pub fn create_content_grep_filter(matches: &ArgMatches, match_key: &'static str, default: bool) -> CliFileFilterByDataRegex { - use std::process::exit; - - CliFileFilterByDataRegex { - regex: matches.value_of(match_key).map(|m| { - Regex::new(&m[..]).unwrap_or_else(|e| { - error!("Regex compiler error: {}", e); - exit(1); - }) - }), - default: default, - } -} - -pub fn create_text_header_field_grep_filter(matches: &ArgMatches, - match_key: &'static str, - header_field_name: &'static str, - default: bool) - -> CliFileFilterByHeaderRegex -{ - CliFileFilterByHeaderRegex { - default: default, - header_field_name: header_field_name, - regex: matches.value_of(match_key) - .map(|m| { - Regex::new(&m[..]).unwrap_or_else(|e| { - error!("Regex compiler error: {}", e); - exit(1); - }) - }), - } -} - -pub fn create_tag_filter(matches: &ArgMatches, tag_key: &'static str, default: bool) -> CliFileFilterByTags { - - CliFileFilterByTags { - default: default, - tags: matches.value_of(tag_key) - .map(|m| m.split(",") - .map(String::from) - .collect::>() - ), - } -} - diff --git a/src/module/helpers/content.rs b/src/module/helpers/content.rs deleted file mode 100644 index befe850e..00000000 --- a/src/module/helpers/content.rs +++ /dev/null @@ -1,97 +0,0 @@ -pub mod markdown { - use hoedown::renderer::Render; - use hoedown::Buffer; - use hoedown::Markdown; - - pub type LinkTitle = String; - pub type LinkURL = String; - - pub struct Link { - pub title: LinkTitle, - pub url: LinkURL, - } - - struct LinkExtractRenderer { - links : Vec - } - - impl LinkExtractRenderer { - - fn new() -> LinkExtractRenderer { - LinkExtractRenderer { - links: vec![], - } - } - - fn extract(self) -> Vec { - self.links - } - - } - - impl Render for LinkExtractRenderer { - - fn link(&mut self, - _: &mut Buffer, - _: &Buffer, - link: &Buffer, - title: &Buffer) -> bool { - - let l = String::from(link.to_str().unwrap_or("<>")); - let t = String::from(title.to_str().unwrap_or("<>")); - - debug!("[Markdown] Push link: '{}' -> '{}'", t, l); - self.links.push(Link { - title: t, - url: l, - }); - true - } - - } - - pub struct MarkdownParser { - text: Markdown, - } - - impl MarkdownParser { - - pub fn new(s: &String) -> MarkdownParser { - MarkdownParser { - text: Markdown::new(&s[..]) - } - } - - pub fn links(&self) -> Vec { - let mut renderer = LinkExtractRenderer::new(); - renderer.render(&self.text); - renderer.extract() - } - - pub fn to_html(self) -> String { - use hoedown::renderer::html::Html; - use hoedown::renderer::html; - - String::from( - Html::new(html::Flags::empty(), 0) - .render(&self.text) - .to_str() - .unwrap_or("UTF8Error")) - } - - pub fn to_html_page(self) -> String { - let header = " - - - - - - "; - let content = self.to_html(); - let footer = ""; - format!("{}{}{}", header, content, footer) - } - - } - -} diff --git a/src/module/helpers/header/mod.rs b/src/module/helpers/header/mod.rs deleted file mode 100644 index a496b9e6..00000000 --- a/src/module/helpers/header/mod.rs +++ /dev/null @@ -1,81 +0,0 @@ -/*! - * Helpers for headers - */ - -pub mod tags; - -/** - * Utility helpers for header data - */ -pub mod data { - use std::ops::Deref; - use storage::file::header::data::FileHeaderData as FHD; - - /** - * Get an URL from a header, whereas the header has to have the following format: - * - * { ..., "URL": "", ... } - * - * Does no spec verification. - */ - pub fn get_url_from_header(header: &FHD) -> Option { - get_named_text_from_header("URL", header) - } - - /** - * Get an NAME from a header, whereas the header has to have the following format: - * - * { ..., "NAME": "", ... } - * - * Does no spec verification. - */ - pub fn get_name_from_header(header: &FHD) -> Option { - get_named_text_from_header("NAME", header) - } - - - /** - * Get a named field from the header, which has to be of this format - * - * { ..., "": "", ... } - * - * Does no spec verification. - */ - pub fn get_named_text_from_header(name: &'static str, header: &FHD) -> Option { - match header { - &FHD::Map{keys: ref ks} => { - ks.clone() - .iter() - .find(|k| { - match k.deref() { - &FHD::Key{name: ref n, value: _} => n == name, - _ => false - } - }).and_then(|urlkey| { - match urlkey.deref().clone() { - FHD::Key{name: ref n, value: ref v} => { - match v.deref().clone() { - FHD::Text(s) => Some(s), - _ => { - warn!("Malformed Header Data: Expected Text, found non-Text"); - debug!(" in {}", n); - None - }, - } - } - _ => { - warn!("Malformed Header Data: Expected Text, found non-Text"); - None - }, - } - }) - }, - _ => { - warn!("Malformed Header Data: Expected Map, found non-Map"); - None - } - } - } - -} - diff --git a/src/module/helpers/header/tags.rs b/src/module/helpers/header/tags.rs deleted file mode 100644 index 52e9ebaa..00000000 --- a/src/module/helpers/header/tags.rs +++ /dev/null @@ -1,177 +0,0 @@ -/*! - * Helpers for headers - Tags - */ - -/** - * Spec helpers for header-tags - */ -pub mod spec { - use storage::file::header::spec::FileHeaderSpec as FHS; - use module::helpers::spec::{named_text, named_text_array}; - - /** - * helper for a Header spec for - * - * { "URL": "" } - */ - pub fn url_key() -> FHS { - named_text("URL") - } - - /** - * helper for a Header spec for - * - * { "TAGS": [ "", ... ] } - */ - pub fn tags_key() -> FHS { - named_text_array("TAGS") - } - -} - -/** - * Data helpers for header-tags - */ -pub mod data { - use std::ops::Deref; - use storage::file::header::data::FileHeaderData as FHD; - use module::Module; - use clap::ArgMatches; - use storage::parser::Parser; - use storage::parser::FileHeaderParser; - - /** - * Use a Vec to build a Tag-Array: - * - * [ "", ... ] - */ - pub fn build_tag_array(tags: Vec) -> FHD { - let texttags = tags.into_iter().map(|t| FHD::Text(t.clone())).collect(); - FHD::Array { values: Box::new(texttags) } - } - - /** - * Fetch tags from a header, whereas the header looks like this: - * - * { ..., - * "TAGS": [ "", ... ], - * ... - * } - * - * Does no spec verification. - */ - pub fn get_tags_from_header(header: &FHD) -> Vec { - let tags : Vec = vec![]; - - fn match_array(a: &Box) -> Vec { - let mut tags : Vec = vec![]; - - match a.deref() { - &FHD::Array{values: ref vs} => { - let values : Vec = 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 = ks.clone(); - for key in keys { - match key { - FHD::Key{ref name, value: ref v} => { - if name == "TAGS" { - return match_array(v) - } - }, - _ => warn!("Malformed Header Data: Expected Key, found non-Key"), - } - } - }, - _ => warn!("Malformed Header Data: Expected Map, found non-Map"), - } - - tags - } - - /** - * Helper function to alter the tags in a file - */ - pub fn alter_tags_in_files(m: &Module, - matches: &ArgMatches, - parser: &Parser, - generate_new_tags: F, - rebuild_header: R) -> bool - where HP: FileHeaderParser, - F: Fn(Vec, &Vec) -> Vec, - R: Fn(&FHD, Vec) -> Option - { - use std::process::exit; - use module::helpers::cli::create_tag_filter; - use module::helpers::cli::create_hash_filter; - use module::helpers::cli::create_text_header_field_grep_filter; - use module::helpers::cli::CliFileFilter; - - let cli_tags = matches.value_of("tags") - .map(|ts| { - ts.split(",") - .map(String::from) - .collect::>() - }) - .unwrap_or(vec![]); - - let filter = { - let hash_filter = create_hash_filter(matches, "with:id", false); - let text_filter = create_text_header_field_grep_filter(matches, "with_match", "URL", false); - let tags_filter = create_tag_filter(matches, "with_tags", false); - hash_filter.or(Box::new(text_filter)).or(Box::new(tags_filter)) - }; - - m.runtime() - .store() - .load_for_module(m, &parser) - .into_iter() - .filter(|file| filter.filter_file(file)) - .map(|file| { - debug!("Alter tags in file: {:?}", file); - - let hdr = { - let f = file.deref().borrow(); - f.header().clone() - }; - - debug!("Tags:..."); - let old_tags = get_tags_from_header(&hdr); - debug!(" old_tags = {:?}", &old_tags); - debug!(" cli_tags = {:?}", &cli_tags); - - let new_tags = generate_new_tags(old_tags, &cli_tags); - debug!(" new_tags = {:?}", &new_tags); - - let new_header = rebuild_header(&hdr, new_tags) - .unwrap_or_else(|| { - error!("Could not rebuild header for file"); - exit(1); - }); - { - let mut f_mut = file.deref().borrow_mut(); - f_mut.set_header(new_header); - } - - m.runtime().store().persist(&parser, file); - true - }) - .all(|x| x) - } - - -} - diff --git a/src/module/helpers/mod.rs b/src/module/helpers/mod.rs deleted file mode 100644 index 81c63dbb..00000000 --- a/src/module/helpers/mod.rs +++ /dev/null @@ -1,44 +0,0 @@ -/*! - * Utility helpers for modules - */ - -pub mod cli; -pub mod header; -pub mod utils; -pub mod content; - -/** - * Helpers for header specs - */ -pub mod spec { - use storage::file::header::spec::FileHeaderSpec as FHS; - - /** - * Helper to get a spec for a Key-Value for a named text: - * - * { '': "" } - */ - pub fn named_text(name: &str) -> FHS { - FHS::Key { name: String::from(name), value_type: Box::new(FHS::Text) } - } - - /** - * Helper to get a spec for a Key-Value for a named array: - * - * { '': [ "", ...] } - */ - pub fn named_text_array(name: &str) -> FHS { - FHS::Key { name: String::from(name), value_type: Box::new(text_array()) } - } - - /** - * Helper to get a spec for Array: - * - * [ "", ...] - */ - pub fn text_array() -> FHS { - FHS::Array { allowed_types: vec![FHS::Text] } - } - -} - diff --git a/src/module/helpers/utils.rs b/src/module/helpers/utils.rs deleted file mode 100644 index 3e39813a..00000000 --- a/src/module/helpers/utils.rs +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Utility helpers for CLI - */ -pub mod cli { - use clap::ArgMatches; - - /** - * Get a commandline option "tags" and split the argument by "," to be able to provide a - * Vec with the argument as array. - */ - pub fn get_tags<'a>(sub: &ArgMatches<'a, 'a>) -> Vec { - - fn reject_if_with_spaces(e: &String) -> bool { - if e.contains(" ") { - warn!("Tag contains spaces: '{}'", e); - false - } else { - true - } - } - - debug!("Fetching tags from commandline"); - sub.value_of("tags").and_then(|tags| { - Some(tags.split(",") - .into_iter() - .map(|s| s.to_string()) - .filter(|e| reject_if_with_spaces(e)) - .collect() - ) - }).or(Some(vec![])).unwrap() - } - -} diff --git a/src/module/mod.rs b/src/module/mod.rs deleted file mode 100644 index 910995cb..00000000 --- a/src/module/mod.rs +++ /dev/null @@ -1,20 +0,0 @@ -use std::fmt::Debug; - -use clap::ArgMatches; - -use runtime::Runtime; - -pub mod bm; -pub mod helpers; -pub mod notes; - -/** - * Module interface, each module has to implement this. - */ -pub trait Module<'a> : Debug { - fn exec(&self, matches: &ArgMatches) -> bool; - fn name(&self) -> &'static str; - - fn runtime(&self) -> &Runtime; -} - diff --git a/src/module/notes/header.rs b/src/module/notes/header.rs deleted file mode 100644 index 76fc1880..00000000 --- a/src/module/notes/header.rs +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Lets talk about header data first. - * We need: - * - * - tags - * - name (not unique) - * - * So an header could look like this: - * - * ```json - * { - * 'name': "kittennotes", - * 'tags': ['foo', 'bar', 'baz'], - * } - * ``` - * - * Nothing more is required for the header, I guess - * - */ - -use module::helpers; -use module::helpers::header as headerhelpers; -use storage::file::header::data::FileHeaderData as FHD; -use storage::file::header::spec::FileHeaderSpec as FHS; - -pub fn get_spec() -> FHS { - FHS::Map { keys: vec![ helpers::spec::named_text("NAME"), - headerhelpers::tags::spec::tags_key() ] } -} - - -pub fn build_header(name: String, tags: Vec) -> FHD { - FHD::Map { - keys: vec![ - FHD::Key { - name: String::from("NAME"), - value: Box::new(FHD::Text(name.clone())) - }, - FHD::Key { - name: String::from("TAGS"), - value: Box::new(headerhelpers::tags::data::build_tag_array(tags)) - } - ] - } -} - -pub fn get_tags_from_header(header: &FHD) -> Vec { - headerhelpers::tags::data::get_tags_from_header(header) -} - -/** - * Get the name from the Header - * - * Returns empty string if there is no NAME field - */ -pub fn get_name_from_header(header: &FHD) -> String { - headerhelpers::data::get_name_from_header(header).unwrap_or(String::from("")) -} - -pub fn rebuild_header_with_tags(header: &FHD, tags: Vec) -> Option { - let name = get_name_from_header(header); - Some(build_header(name, tags)) -} - diff --git a/src/module/notes/mod.rs b/src/module/notes/mod.rs deleted file mode 100644 index ca21cbae..00000000 --- a/src/module/notes/mod.rs +++ /dev/null @@ -1,545 +0,0 @@ -use std::fmt::{Debug, Formatter}; -use std::fmt::Result as FMTResult; -use std::ops::Deref; -use std::rc::Rc; -use std::cell::RefCell; - -use clap::ArgMatches; - -mod header; - -use module::Module; -use runtime::Runtime; -use storage::file::File; -use storage::parser::Parser; -use storage::yaml::parser::YamlHeaderParser; -use module::helpers::cli::create_tag_filter; -use module::helpers::cli::create_hash_filter; -use module::helpers::cli::create_text_header_field_grep_filter; -use module::helpers::cli::create_content_grep_filter; -use module::helpers::cli::CliFileFilter; - -pub struct Notes<'a> { - rt: &'a Runtime<'a>, - parser: Parser, -} - -impl<'a> Notes<'a> { - - pub fn new(rt: &'a Runtime<'a>) -> Notes<'a> { - Notes { - rt: rt, - parser: Parser::new(YamlHeaderParser::new(None)), - } - } - - fn command_add(&self, matches: &ArgMatches) -> bool { - use ansi_term::Colour::Yellow; - use self::header::build_header; - use ui::external::editor::let_user_provide_content; - - let name = matches.value_of("name") - .map(String::from) - .unwrap_or(String::from("")); - let tags = matches.value_of("tags") - .and_then(|s| Some(s.split(",").map(String::from).collect())) - .unwrap_or(vec![]); - - debug!("Building header with"); - debug!(" name = '{:?}'", name); - debug!(" tags = '{:?}'", tags); - let header = build_header(name, tags); - - let content = let_user_provide_content(self.runtime()).unwrap_or(String::from("")); - - let fileid = self.rt.store().new_file_with_content(self, header, content); - self.rt - .store() - .load(self, &self.parser, &fileid) - .and_then(|file| { - info!("{}", Yellow.paint(format!("Created file in memory: {}", fileid))); - Some(self.rt.store().persist(&self.parser, file)) - }) - .unwrap_or(false) - } - - fn command_edit(&self, matches: &ArgMatches) -> bool { - use ansi_term::Colour::{Red, Green}; - use ui::external::editor::edit_content; - - let filter = { - let hash_filter = create_hash_filter(matches, "id", false); - let head_filter = create_text_header_field_grep_filter(matches, "namematch", "NAME", false); - let text_filter = create_content_grep_filter(matches, "match", false); - let tags_filter = create_tag_filter(matches, "tags", false); - hash_filter.or(Box::new(head_filter)).or(Box::new(text_filter)).or(Box::new(tags_filter)) - }; - - let result = self.rt - .store() - .load_for_module(self, &self.parser) - .into_iter() - .filter(|f| filter.filter_file(f)) - .map(|file| { - debug!("File loaded, can edit now: {:?}", file); - - let old_content = { - let f = file.deref().borrow(); - f.data().clone() - }; - - debug!("Editing content now..."); - let (new_content, editing_worked) = edit_content(self.runtime(), old_content); - debug!("... ready with editing"); - - if editing_worked { - debug!("Editing worked"); - { - let mut f = file.deref().borrow_mut(); - f.set_data(new_content); - } - self.runtime().store().persist(&self.parser, file) - } else { - debug!("Editing didn't work"); - false - } - }) - .fold((0, 0), |acc, succeeded| { - let (worked, failed) = acc; - if succeeded { - (worked + 1, failed) - } else { - (worked, failed + 1) - } - }); - - let (worked, failed) = result; - - info!("{}", Green.paint(format!("Editing succeeded for {} files", worked))); - info!("{}", Red.paint(format!( "Editing failed for {} files", failed))); - - return failed == 0; - } - - fn command_show(&self, matches: &ArgMatches) -> bool { - use self::header::get_name_from_header; - use self::header::get_tags_from_header; - - let filter = { - let hash_filter = create_hash_filter(matches, "id", true); - let head_filter = create_text_header_field_grep_filter(matches, "match", "NAME", true); - let text_filter = create_content_grep_filter(matches, "match", true); - let tags_filter = create_tag_filter(matches, "tags", true); - hash_filter.and(Box::new(head_filter)).and(Box::new(text_filter)).and(Box::new(tags_filter)) - }; - - self.rt - .store() - .load_for_module(self, &self.parser) - .into_iter() - .filter(|file| { - let res = filter.filter_file(file); - debug!("Filter: {} -> {}", file.deref().borrow().id(), res); - res - }) - .map(|file| { - let content = file.deref().borrow().data().clone(); - - let text = if matches.is_present("plain") { - self.parser.write((file.deref().borrow().header(), &content)) - .unwrap_or(format!("Parser error for file: {}", file.deref().borrow().id())) - } else { - let tags = get_tags_from_header(file.deref().borrow().header()); - let name = get_name_from_header(file.deref().borrow().header()); - format!("Name = '{}'\nTags = '{}'\n\n{}\n\n", - name, tags.join(", "), content) - }; - - println!("{:-<79}", "-"); - println!("{}", text); - true - }) - .all(|x| x) - } - - fn command_open(&self, matches: &ArgMatches) -> bool { - let filter = { - let hash_filter = create_hash_filter(matches, "id", true); - let head_filter = create_text_header_field_grep_filter(matches, "match", "NAME", true); - let text_filter = create_content_grep_filter(matches, "match", true); - let tags_filter = create_tag_filter(matches, "tags", true); - hash_filter.and(Box::new(head_filter)).and(Box::new(text_filter)).and(Box::new(tags_filter)) - }; - - let files = self.rt - .store() - .load_for_module(self, &self.parser) - .into_iter() - .filter(|file| { - let res = filter.filter_file(file); - debug!("Filter: {} -> {}", file.deref().borrow().id(), res); - res - }); - - if matches.is_present("onepage") { - let tmpcontent = files.fold(String::new(), |acc, file| { - let content = self.preprocess_file_for_markdown(file); - format!("{}\n\n{}", acc, content) - }); - self.open_tmpcontent(tmpcontent) - } else { - let result = files.map(|file| { - self.open_tmpcontent(self.preprocess_file_for_markdown(file)) - }) - .fold((0, 0), |acc, succeeded| { - let (worked, failed) = acc; - if succeeded { - (worked + 1, failed) - } else { - (worked, failed + 1) - } - }); - - let (worked, failed) = result; - - info!("Opening as HTML page succeeded for {} files", worked); - info!("Opening as HTML page failed for {} files", failed); - - failed == 0 - } - - } - - fn preprocess_file_for_markdown(&self, file: Rc>) -> String { - use self::header::get_name_from_header; - use self::header::get_tags_from_header; - - let tagsstr = { - let tags = get_tags_from_header(file.deref().borrow().header()); - if tags.len() != 0 { - format!(" ({})", tags.join(", ")) - } else { - format!(" (No Tags)") - } - }; - - let (name, id) = { - let notename = get_name_from_header(file.deref().borrow().header()); - if notename.len() == 0 { - (format!("{}", file.deref().borrow().id()), String::new()) - } else { - (notename, format!("{}", file.deref().borrow().id())) - } - }; - - format!("

{}

{}{}\n\n{}", name, id, tagsstr, - file.deref().borrow().data()) - } - - fn open_tmpcontent(&self, s: String) -> bool { - use std::process::exit; - use std::io::Write; - use open; - use ui::external::get_tempfile; - use module::helpers::content::markdown::MarkdownParser; - - let (temppath, mut tempfile) = match get_tempfile("html") { - Some(tpl) => tpl, - None => { - error!("Could not create tempfile"); - exit(1); - } - }; - - tempfile.write_all(MarkdownParser::new(&s).to_html_page().as_ref()) - .map_err(|e| { - error!("Could not write HTML to file: {}", temppath); - debug!("Could not write HTML to file: {:?}", e); - }) - .ok(); - open::that(&temppath[..]).is_ok() - } - - fn command_list(&self, matches: &ArgMatches) -> bool { - use ui::file::{FilePrinter, TablePrinter}; - use self::header::get_name_from_header; - use self::header::get_tags_from_header; - use module::helpers::cli::CliFileFilter; - - let filter = { - let hash_filter = create_hash_filter(matches, "id", true); - let head_filter = create_text_header_field_grep_filter(matches, "match", "NAME", true); - let text_filter = create_content_grep_filter(matches, "match", true); - let tags_filter = create_tag_filter(matches, "tags", true); - hash_filter.or(Box::new(head_filter)).and(Box::new(text_filter)).and(Box::new(tags_filter)) - }; - - let pretty = matches.is_present("pretty"); - debug!("Printing pretty table = {}", pretty); - let printer = TablePrinter::new(self.rt.is_verbose(), self.rt.is_debugging(), pretty); - - printer.print_files_custom( - self.rt.store() - .load_for_module(self, &self.parser) - .into_iter() - .filter(|f| filter.filter_file(f)), - &|file| { - let fl = file.deref().borrow(); - let hdr = fl.header(); - let name = get_name_from_header(hdr); - let tags = get_tags_from_header(hdr); - - debug!("Custom printer field: name = '{:?}'", name); - debug!("Custom printer field: tags = '{:?}'", tags); - - vec![name, tags.join(", ")] - } - ); - true - } - - fn command_links(&self, matches: &ArgMatches) -> bool { - use ansi_term::Colour::{Red, Green}; - use module::helpers::content::markdown::MarkdownParser; - use util::is_url; - use prettytable::Table; - use prettytable::row::Row; - use prettytable::cell::Cell; - use itertools::Itertools; - - debug!("Going to list links in files..."); - - let list_intern = matches.is_present("internal"); - let list_extern = matches.is_present("external"); - debug!("list internal links = {}", list_intern); - debug!("list external links = {}", list_extern); - - let titles = row!["#", "Text", "Link", "Direction"]; - let mut table = Table::new(); - table.set_titles(titles); - debug!("Table setup finished"); - - let filter = { - let hash_filter = create_hash_filter(matches, "id", false); - let text_filter = create_text_header_field_grep_filter(matches, "match", "URL", false); - let tags_filter = create_tag_filter(matches, "tags", false); - hash_filter.or(Box::new(text_filter)).or(Box::new(tags_filter)) - }; - - let result = self.rt - .store() - .load_for_module(self, &self.parser) - .iter() - .filter(|file| { - let res = filter.filter_file(file); - debug!("Filter: {} -> {}", file.deref().borrow().id(), res); - res - }) - .map(|file| { - debug!("File loaded, can parse for links now: {}", file.deref().borrow().id()); - let data = { - let f = file.deref().borrow(); - debug!("Parsing markdown in file = {:?}", f); - f.data().clone() - }; - let links = MarkdownParser::new(&data).links(); - debug!("Retreived {} links from {}", links.len(), file.deref().borrow().id()); - links - }) - .flatten() - .filter(|link| { - let url = &link.url; - let is_extern = is_url(&url); - debug!("Is external URL {} -> {}", url, is_extern); - debug!("List external URLs -> {}", list_extern); - debug!("List internal URLs -> {}", list_intern); - ((!list_intern && !list_extern) || - (is_extern && list_extern) || - (!is_extern && list_intern)) - }) - .enumerate() - .map(|(i_link, link)| { - let title = &link.title; - let url = &link.url; - let is_url = is_url(&url); - debug!("Listing: {} -> {}", title, url); - - let linkno_cell = Cell::new(&format!("{}", i_link)[..]); - let title_cell = Cell::new(&format!("{}", title)[..]); - let url_cell = Cell::new(&format!("{}", url)[..]); - let dir_cell = Cell::new(if is_url { "extern" } else { "intern" }); - - let r = Row::new(vec![linkno_cell, - title_cell, - url_cell, - dir_cell]); - table.add_row(r); - true - }) - .fold((0, 0), |acc, succeeded| { - let (worked, failed) = acc; - if succeeded { - (worked + 1, failed) - } else { - (worked, failed + 1) - } - }); - - let (worked, failed) = result; - - if worked != 0 { - debug!("Printing table entries"); - table.printstd(); - } else { - debug!("Not printing table as there wouldn't be any entries in it"); - } - - info!("{}", Green.paint(format!("Listing links succeeded for {} files", worked))); - info!("{}", Red.paint( format!("Listing links failed for {} files", failed))); - - return failed == 0; - } - - fn command_remove(&self, matches: &ArgMatches) -> bool { - use ansi_term::Colour::{Red, Green}; - - let filter = { - let hash_filter = create_hash_filter(matches, "id", false); - let text_filter = create_text_header_field_grep_filter(matches, "match", "URL", false); - let tags_filter = create_tag_filter(matches, "tags", false); - hash_filter.or(Box::new(text_filter)).or(Box::new(tags_filter)) - }; - - let result = self.rt - .store() - .load_for_module(self, &self.parser) - .iter() - .filter(|file| filter.filter_file(file)) - .map(|file| { - debug!("File loaded, can remove now: {:?}", file); - let f = file.deref().borrow(); - self.rt.store().remove(f.id().clone()) - }) - .fold((0, 0), |acc, succeeded| { - let (worked, failed) = acc; - if succeeded { - (worked + 1, failed) - } else { - (worked, failed + 1) - } - }); - - let (worked, failed) = result; - - - info!("{}", Green.paint(format!("Removing succeeded for {} files", worked))); - info!("{}", Red.paint( format!("Removing failed for {} files", failed))); - - return failed == 0; - } - - fn command_add_tags(&self, matches: &ArgMatches) -> bool { - use module::helpers::header::tags::data::alter_tags_in_files; - use self::header::rebuild_header_with_tags; - - alter_tags_in_files(self, matches, &self.parser, |old_tags, cli_tags| { - let mut new_tags = old_tags.clone(); - new_tags.append(&mut cli_tags.clone()); - new_tags - }, rebuild_header_with_tags) - } - - fn command_rm_tags(&self, matches: &ArgMatches) -> bool { - use module::helpers::header::tags::data::alter_tags_in_files; - use self::header::rebuild_header_with_tags; - - alter_tags_in_files(self, matches, &self.parser, |old_tags, cli_tags| { - old_tags.clone() - .into_iter() - .filter(|tag| !cli_tags.contains(tag)) - .collect() - }, rebuild_header_with_tags) - } - - fn command_set_tags(&self, matches: &ArgMatches) -> bool { - use module::helpers::header::tags::data::alter_tags_in_files; - use self::header::rebuild_header_with_tags; - - alter_tags_in_files(self, matches, &self.parser, |_, cli_tags| { - cli_tags.clone() - }, rebuild_header_with_tags) - } - -} - -impl<'a> Module<'a> for Notes<'a> { - - fn exec(&self, matches: &ArgMatches) -> bool { - use ansi_term::Colour::Red; - - match matches.subcommand_name() { - Some("add") => { - self.command_add(matches.subcommand_matches("add").unwrap()) - }, - - Some("edit") => { - self.command_edit(matches.subcommand_matches("edit").unwrap()) - }, - - Some("show") => { - self.command_show(matches.subcommand_matches("show").unwrap()) - }, - - Some("open") => { - self.command_open(matches.subcommand_matches("open").unwrap()) - }, - - Some("list") => { - self.command_list(matches.subcommand_matches("list").unwrap()) - }, - - Some("links") => { - self.command_links(matches.subcommand_matches("links").unwrap()) - }, - - Some("remove") => { - self.command_remove(matches.subcommand_matches("remove").unwrap()) - }, - - Some("add_tags") => { - self.command_add_tags(matches.subcommand_matches("add_tags").unwrap()) - }, - - Some("rm_tags") => { - self.command_rm_tags(matches.subcommand_matches("rm_tags").unwrap()) - }, - - Some("set_tags") => { - self.command_set_tags(matches.subcommand_matches("set_tags").unwrap()) - }, - - Some(_) | None => { - info!("{}", Red.bold().paint("No command given, doing nothing")); - false - }, - } - } - - fn name(&self) -> &'static str{ - "notes" - } - - fn runtime(&self) -> &Runtime { - self.rt - } - -} - -impl<'a> Debug for Notes<'a> { - - fn fmt(&self, fmt: &mut Formatter) -> FMTResult { - try!(write!(fmt, "[Module][Notes]")); - Ok(()) - } - -} diff --git a/src/runtime.rs b/src/runtime.rs deleted file mode 100644 index b9991a54..00000000 --- a/src/runtime.rs +++ /dev/null @@ -1,142 +0,0 @@ -use std::fmt::{Debug, Formatter, Error}; -use std::process::Command; - -extern crate log; -use log::{LogRecord, LogLevel, LogLevelFilter, LogMetadata, SetLoggerError}; - -pub use cli::CliConfig; -pub use configuration::Configuration as Cfg; - -pub struct ImagLogger { - lvl: LogLevel, -} - -impl ImagLogger { - - pub fn new(lvl: LogLevel) -> ImagLogger { - ImagLogger { - lvl: lvl, - } - } - - pub fn init(config: &CliConfig) -> Result<(), SetLoggerError> { - let lvl = if config.is_debugging() { - LogLevelFilter::Debug - } else if config.is_verbose() { - LogLevelFilter::Info - } else { - LogLevelFilter::Error - }; - - log::set_logger(|max_log_lvl| { - max_log_lvl.set(lvl); - // debug!("Init logger with: {}", lvl); - Box::new(ImagLogger::new(lvl.to_log_level().unwrap())) - }) - } - -} - -impl log::Log for ImagLogger { - - fn enabled(&self, metadata: &LogMetadata) -> bool { - metadata.level() <= self.lvl - } - - fn log(&self, record: &LogRecord) { - if self.enabled(record.metadata()) { - println!("[{}]: {}", record.level(), record.args()); - } - } -} - -/** - * Runtime object, represents a single interface to both the CLI configuration and the - * configuration file. - */ -pub struct Runtime<'a> { - pub config : CliConfig<'a>, - pub configuration : Cfg, -} - -impl<'a> Runtime<'a> { - - pub fn new(cfg: Cfg, config : CliConfig<'a>) -> Runtime<'a> { - let sp = config.store_path().unwrap_or(cfg.store_path()); - Runtime { - config: config, - configuration: cfg, - } - } - - /** - * Check whether we run verbose - */ - pub fn is_verbose(&self) -> bool { - self.config.is_verbose() - } - - /** - * Check whether we run in debugging - */ - pub fn is_debugging(&self) -> bool { - self.config.is_debugging() - } - - /** - * Get the store path we are currently using - */ - pub fn store_path(&self) -> String { - self.config.store_path().unwrap_or(self.configuration.store_path()) - } - - /** - * Get the runtime path we are currently using - */ - pub fn get_rtp(&self) -> String { - if let Some(rtp) = self.config.get_rtp() { - rtp - } else { - self.configuration.get_rtp() - } - } - - pub fn editor(&self) -> Command { - use std::env::var; - - let (editor, args) : (String, String) = { - if let Some(editor) = self.config.editor() { - (editor, self.config.editor_opts()) - } else if let Some(editor) = self.configuration.editor() { - (editor, self.configuration.editor_opts()) - } else if let Ok(editor) = var("EDITOR") { - (editor, String::from("")) - } else { - (String::from("vim"), String::from("")) - } - }; - - let mut e = Command::new(editor); - for arg in args.split(" ") { - e.arg(arg); - } - e - } - - pub fn report_exit(&self) -> bool { - self.config.report_exit() || self.configuration.report_exit() - } - -} - -impl<'a> Debug for Runtime<'a> { - - fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { - write!(f, "Runtime (verbose: {}, debugging: {}, rtp: {})", - self.is_verbose(), - self.is_debugging(), - self.get_rtp()) - } - -} - diff --git a/src/storage/file/hash.rs b/src/storage/file/hash.rs deleted file mode 100644 index ff42bee1..00000000 --- a/src/storage/file/hash.rs +++ /dev/null @@ -1,67 +0,0 @@ -use std::convert::{From, Into}; -use std::fmt::{Display, Formatter}; -use std::fmt; -use std::hash::Hash; -use uuid::Uuid; - -#[derive(Clone)] -#[derive(Debug)] -#[derive(PartialEq)] -#[derive(Eq)] -#[derive(Hash)] -/** - * FileHash type - * - * Simple abstraction over String by now. - */ -pub struct FileHash { - hash: String, -} - -impl From for FileHash { - - fn from(s: String) -> FileHash { - FileHash { hash: s } - } - -} - -impl<'a> From<&'a String> for FileHash { - - fn from(s: &'a String) -> FileHash { - FileHash::from(s.clone()) - } - -} - -impl From for FileHash { - - fn from(u: Uuid) -> FileHash { - FileHash::from(u.to_hyphenated_string()) - } - -} - -impl<'a> From<&'a str> for FileHash { - - fn from(s: &str) -> FileHash { - FileHash::from(String::from(s)) - } - -} - -impl Into for FileHash { - - fn into(self) -> String { - self.hash - } -} - -impl Display for FileHash { - - fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { - try!(write!(fmt, "{}", self.hash)); - Ok(()) - } - -} diff --git a/src/storage/file/header/data.rs b/src/storage/file/header/data.rs deleted file mode 100644 index f94a4b26..00000000 --- a/src/storage/file/header/data.rs +++ /dev/null @@ -1,37 +0,0 @@ -use regex::Regex; - -#[derive(Debug)] -#[derive(Clone)] -pub enum FileHeaderData { - Null, - Bool(bool), - Integer(i64), - UInteger(u64), - Float(f64), - Text(String), - Key { name: String, value: Box }, - Map { keys: Vec }, - Array { values: Box> }, -} - -impl FileHeaderData { - - pub fn matches_with(&self, r: &Regex) -> bool { - match self { - &FileHeaderData::Text(ref t) => r.is_match(&t[..]), - &FileHeaderData::Key{name: ref n, value: ref val} => { - r.is_match(n) || val.matches_with(r) - }, - - &FileHeaderData::Map{keys: ref dks} => { - dks.iter().any(|x| x.matches_with(r)) - }, - - &FileHeaderData::Array{values: ref vs} => { - vs.iter().any(|x| x.matches_with(r)) - } - - _ => false, - } - } -} diff --git a/src/storage/file/header/mod.rs b/src/storage/file/header/mod.rs deleted file mode 100644 index cf0e8535..00000000 --- a/src/storage/file/header/mod.rs +++ /dev/null @@ -1,126 +0,0 @@ -use std::error::Error; -use std::fmt::{Debug, Display, Formatter}; -use std::fmt; - -pub mod spec; -pub mod data; - -use self::data::*; -use self::spec::*; - -pub struct MatchError<'a> { - summary: String, - expected: &'a FileHeaderSpec, - found: &'a FileHeaderData -} - -impl<'a> MatchError<'a> { - - pub fn new(s: String, - ex: &'a FileHeaderSpec, - found: &'a FileHeaderData) -> MatchError<'a> { - MatchError { - summary: s, - expected: ex, - found: found, - } - } - - pub fn format(&self) -> String { - format!("MatchError: {:?}\nExpected: {:?}\nFound: {:?}\n", - self.summary, self.expected, self.found) - } -} - -impl<'a> Error for MatchError<'a> { - - fn description(&self) -> &str { - &self.summary[..] - } - - fn cause(&self) -> Option<&Error> { - None - } - -} - -impl<'a> Debug for MatchError<'a> { - - fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { - try!(write!(fmt, "{}", self.format())); - Ok(()) - } - -} - -impl<'a> Display for MatchError<'a> { - - fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { - try!(write!(fmt, "{}", self.format())); - Ok(()) - } - -} - -pub fn match_header_spec<'a>(spec: &'a FileHeaderSpec, data: &'a FileHeaderData) - -> Option> -{ - debug!("Start matching:\n'{:?}'\non\n{:?}", spec, data); - match (spec, data) { - (&FileHeaderSpec::Null, &FileHeaderData::Null) => { } - (&FileHeaderSpec::Bool, &FileHeaderData::Bool(_)) => { } - (&FileHeaderSpec::Integer, &FileHeaderData::Integer(_)) => { } - (&FileHeaderSpec::UInteger, &FileHeaderData::UInteger(_)) => { } - (&FileHeaderSpec::Float, &FileHeaderData::Float(_)) => { } - (&FileHeaderSpec::Text, &FileHeaderData::Text(_)) => { } - - ( - &FileHeaderSpec::Key{name: ref kname, value_type: ref vtype}, - &FileHeaderData::Key{name: ref n, value: ref val} - ) => { - debug!("Matching Key: '{:?}' == '{:?}', Value: '{:?}' == '{:?}'", - kname, n, - vtype, val); - if kname != n { - debug!("Keys not matching"); - unimplemented!(); - } - return match_header_spec(&*vtype, &*val); - } - - ( - &FileHeaderSpec::Map{keys: ref sks}, - &FileHeaderData::Map{keys: ref dks} - ) => { - debug!("Matching Map: '{:?}' == '{:?}'", sks, dks); - - for (s, d) in sks.iter().zip(dks.iter()) { - let res = match_header_spec(s, d); - if res.is_some() { - return res; - } - } - } - - ( - &FileHeaderSpec::Array{allowed_types: ref vtypes}, - &FileHeaderData::Array{values: ref vs} - ) => { - debug!("Matching Array: '{:?}' == '{:?}'", vtypes, vs); - for (t, v) in vtypes.iter().zip(vs.iter()) { - let res = match_header_spec(t, v); - if res.is_some() { - return res; - } - } - } - - (k, v) => { - return Some(MatchError::new(String::from("Expected type does not match found type"), - k, v - )) - } - } - None -} - diff --git a/src/storage/file/header/spec.rs b/src/storage/file/header/spec.rs deleted file mode 100644 index 885e0beb..00000000 --- a/src/storage/file/header/spec.rs +++ /dev/null @@ -1,41 +0,0 @@ -use std::fmt::{Display, Formatter}; -use std::fmt; - -#[derive(Debug)] -#[derive(Clone)] -pub enum FileHeaderSpec { - Null, - Bool, - Integer, - UInteger, - Float, - Text, - Key { name: String, value_type: Box }, - Map { keys: Vec }, - Array { allowed_types: Vec }, -} - -impl Display for FileHeaderSpec { - - fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { - match self { - &FileHeaderSpec::Null => write!(fmt, "NULL"), - &FileHeaderSpec::Bool => write!(fmt, "Bool"), - &FileHeaderSpec::Integer => write!(fmt, "Integer"), - &FileHeaderSpec::UInteger => write!(fmt, "UInteger"), - &FileHeaderSpec::Float => write!(fmt, "Float"), - &FileHeaderSpec::Text => write!(fmt, "Text"), - &FileHeaderSpec::Key{name: ref n, value_type: ref vt} => { - write!(fmt, "Key({:?}) -> {:?}", n, vt) - } - &FileHeaderSpec::Map{keys: ref ks} => { - write!(fmt, "Map -> {:?}", ks) - } - &FileHeaderSpec::Array{allowed_types: ref at} => { - write!(fmt, "Array({:?})", at) - } - } - } - -} - diff --git a/src/storage/file/id.rs b/src/storage/file/id.rs deleted file mode 100644 index 4fcdeb45..00000000 --- a/src/storage/file/id.rs +++ /dev/null @@ -1,182 +0,0 @@ -use std::convert::{From, Into}; -use std::fmt::{Debug, Display, Formatter}; -use std::fmt; -use std::result::Result; -use std::str::FromStr; - -use regex::Regex; - -use storage::file::id_type::FileIDType; -use storage::file::hash::FileHash; - -#[derive(Clone)] -#[derive(Hash)] -#[derive(Eq)] -#[derive(PartialEq)] -/** - * FileID - * - * A FileID contains of two parts: The ID type and the Hash. For example the FileID - * - * UUID-235-1215-1212 - * - * has a type ("UUID") and a Hash ("235-1215-1212"). - */ -pub struct FileID { - id: FileHash, - id_type: FileIDType, -} - -impl FileID { - - pub fn new(id_type: FileIDType, id: FileHash) -> FileID { - FileID { - id: id, - id_type: id_type, - } - } - - /** - * Get the type of the FileID - */ - pub fn get_type(&self) -> FileIDType { - self.id_type.clone() - } - - /** - * Get the Hash of the FileID - */ - pub fn get_id(&self) -> FileHash { - self.id.clone() - } - - /** - * Parse a String into a FileID, if possible - */ - pub fn parse(string: &String) -> Option { - // we assume that it is an path - let regex = Regex::new(r"([:alnum:]*)-([:upper:]*)-([A-Za-z0-9-_]*)\.(.*)").unwrap(); - let s = string.split("/").last().unwrap_or(""); - - debug!("Regex build: {:?}", regex); - debug!("Matching string: '{}'", s); - regex.captures(s).and_then(|capts| { - // first one is the whole string, index 1-N are the matches. - if capts.len() != 5 { - debug!("Matches, but not expected number of groups"); - return None; - } - debug!("Matches: {}", capts.len()); - - let modname = capts.at(1).unwrap(); - let hashname = capts.at(2).unwrap(); - let hash = capts.at(3).unwrap(); - - debug!("Destructure FilePath to ID:"); - debug!(" FilePath: {:?}", s); - debug!(" Module Name: {:?}", modname); - debug!(" Hash Name: {:?}", hashname); - debug!(" Hash: {:?}", hash); - - FileIDType::from_str(hashname).map(|idtype| { - debug!("ID type = {:?}", idtype); - Some(FileID { - id: FileHash::from(hash), - id_type: idtype, - }) - }).ok() - }).unwrap_or({ - debug!("Did not match"); - debug!("It is no path, actually. So we assume it is an ID already"); - None - }) - } - -} - -impl Debug for FileID { - - fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { - try!(write!(fmt, "FileID[{:?}]: {:?}", - self.id_type, - self.id)); - Ok(()) - } - -} - -impl Display for FileID { - - fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { - try!(write!(fmt, "{}-{}", self.id_type, self.id)); - Ok(()) - } - -} - -impl Into for FileID { - - fn into(self) -> String { - let typestr : String = self.id_type.into(); - let idstr : String = self.id.into(); - typestr + "-" + &idstr[..] - } -} - -#[cfg(test)] -mod test { - - use storage::file::id::FileID; - use storage::file::id_type::FileIDType; - - #[test] - fn file_id_from_string() { - setup_logger(); - - let s1 = String::from("/home/user/testmodule-UUID-some-id.imag"); - let s2 = String::from("/home/user/testmodule-UUID-some-id.extension.imag"); - let s3 = String::from("/home/user/testmodule-NOHASH-some-id.imag"); - - let id1 = FileID::parse(&s1).unwrap(); - let id2 = FileID::parse(&s2).unwrap(); - assert!(FileID::parse(&s3).is_none()); - - println!("Id 1 : {:?}", id1); - println!("Id 2 : {:?}", id2); - - assert_eq!(FileIDType::UUID, id1.get_type()); - assert_eq!(FileIDType::UUID, id2.get_type()); - - let h1 : String = id1.get_id().into(); - let h2 : String = id2.get_id().into(); - - assert_eq!(String::from("some-id"), h1); - assert_eq!(String::from("some-id"), h2); - - let f1 : String = id1.into(); - let f2 : String = id2.into(); - - assert_eq!(String::from("UUID-some-id"), f1); - assert_eq!(String::from("UUID-some-id"), f2); - } - - fn setup_logger() { - extern crate log; - use log::{LogLevelFilter, set_logger}; - use runtime::ImagLogger; - - log::set_logger(|max_log_lvl| { - let lvl = LogLevelFilter::Debug; - max_log_lvl.set(lvl); - Box::new(ImagLogger::new(lvl.to_log_level().unwrap())) - }) - .map_err(|e| { - println!("Error setting logger: {:?}", e); - assert!(false); - }) - .ok(); - debug!("Init logger for test"); - } - -} - diff --git a/src/storage/file/id_type.rs b/src/storage/file/id_type.rs deleted file mode 100644 index fe7a2661..00000000 --- a/src/storage/file/id_type.rs +++ /dev/null @@ -1,52 +0,0 @@ -use std::fmt::{Display, Formatter}; -use std::fmt; -use std::convert::{From, Into}; -use std::str::FromStr; - -#[derive(Debug)] -#[derive(Clone)] -#[derive(PartialEq)] -#[derive(Eq)] -#[derive(Hash)] -/** - * File ID type - * - * Currently only UUID is available. Maybe this will be the only type available at all. - */ -pub enum FileIDType { - UUID, -} - -pub enum FileIDTypeParseError { - UnknownType -} - -impl FromStr for FileIDType { - type Err = FileIDTypeParseError; - fn from_str(s: &str) -> Result { - match s { - "UUID" => Ok(FileIDType::UUID), - _ => Err(FileIDTypeParseError::UnknownType) - } - } -} - -impl Into for FileIDType { - - fn into(self) -> String { - match self { - FileIDType::UUID => String::from("UUID"), - } - } -} - -impl Display for FileIDType { - - fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { - match self { - &FileIDType::UUID => try!(write!(fmt, "UUID")), - } - Ok(()) - } -} - diff --git a/src/storage/file/mod.rs b/src/storage/file/mod.rs deleted file mode 100644 index cd1f47c5..00000000 --- a/src/storage/file/mod.rs +++ /dev/null @@ -1,159 +0,0 @@ -use std::fmt::{Debug, Display, Formatter}; -use std::fmt; - -use regex::Regex; - -pub mod id; -pub mod id_type; -pub mod header; -pub mod hash; - -use storage::file::id::*; -use self::header::data::*; - -/** - * Internal abstract view on a file. Does not neccessarily exist on the FS and is just kept - * internally until it is written to disk. - */ -pub struct File { - /// The name of the module which owns this file - pub owning_module_name : &'static str, - - /// The header of the file - pub header : FileHeaderData, - - /// The content part of the file - pub data : String, - - /// The ID of the file - pub id : FileID, -} - -impl File { - - /** - * Get the owner module name of the file - */ - pub fn owner_name(&self) -> &'static str { - self.owning_module_name - } - - /** - * Get the header of the file - */ - pub fn header(&self) -> &FileHeaderData { - &self.header - } - - /** - * Set the header of the file - */ - pub fn set_header(&mut self, new_header: FileHeaderData) { - self.header = new_header; - } - - /** - * Set the data of the file - */ - pub fn data(&self) -> &String { - &self.data - } - - /** - * Set the data - */ - pub fn set_data(&mut self, new_data: String) { - self.data = new_data; - } - - /** - * Set the (header, data) of the file - */ - pub fn contents(&self) -> (&FileHeaderData, &String) { - (self.header(), self.data()) - } - - /** - * Set the id of the file - */ - pub fn id(&self) -> &FileID { - &self.id - } - - /** - * Check whether the header or the data of the file match some regex - */ - pub fn matches_with(&self, r: &Regex) -> bool { - r.is_match(&self.data[..]) || self.header.matches_with(r) - } - -} - -impl Display for File { - - fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { - try!(write!(fmt, -"[File] Owner : '{:?}' - FileID: '{:?}' - Header: '{:?}' - Data : '{:?}'", - self.owning_module_name, - self.header, - self.data, - self.id)); - Ok(()) - } - -} - -impl Debug for File { - - fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { - try!(write!(fmt, -"[File] Owner : '{:?}' - FileID: '{:?}' - Header: '{:?}' - Data : '{:?}'", - self.owning_module_name, - self.id, - self.header, - self.data)); - Ok(()) - } - -} - -#[cfg(test)] -mod test { - // we use the JSON parser here, so we can generate FileHeaderData - use storage::json::parser::JsonHeaderParser; - use storage::file::header::match_header_spec; - use storage::parser::FileHeaderParser; - use storage::file::header::spec::FileHeaderSpec as FHS; - - #[test] - fn test_spec_matching() { - let text = String::from("{\"a\": 1, \"b\": -2}"); - let spec = FHS::Map { - keys: vec![ - FHS::Key { - name: String::from("a"), - value_type: Box::new(FHS::UInteger) - }, - FHS::Key { - name: String::from("b"), - value_type: Box::new(FHS::Integer) - } - ] - }; - - let parser = JsonHeaderParser::new(Some(spec.clone())); - let datares = parser.read(Some(text.clone())); - assert!(datares.is_ok(), "Text could not be parsed: '{}'", text); - let data = datares.unwrap(); - - let matchres = match_header_spec(&spec, &data); - assert!(matchres.is_none(), "Matching returns error: {:?}", matchres); - } -} - diff --git a/src/storage/json/mod.rs b/src/storage/json/mod.rs deleted file mode 100644 index 67c567fa..00000000 --- a/src/storage/json/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod parser; diff --git a/src/storage/json/parser.rs b/src/storage/json/parser.rs deleted file mode 100644 index dbcadd70..00000000 --- a/src/storage/json/parser.rs +++ /dev/null @@ -1,281 +0,0 @@ -use std::collections::HashMap; -use std::error::Error; -use std::fmt::{Debug, Display, Formatter}; -use std::fmt; - -use serde_json::{Value, from_str}; -use serde_json::error::Result as R; -use serde_json::Serializer; -use serde::ser::Serialize; -use serde::ser::Serializer as Ser; - -use storage::parser::{FileHeaderParser, ParserError}; -use storage::file::header::spec::FileHeaderSpec; -use storage::file::header::data::FileHeaderData; - -pub struct JsonHeaderParser { - spec: Option, -} - -impl JsonHeaderParser { - - pub fn new(spec: Option) -> JsonHeaderParser { - JsonHeaderParser { - spec: spec - } - } - -} - -impl Display for JsonHeaderParser { - - fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { - try!(write!(fmt, "JsonHeaderParser")); - Ok(()) - } - -} - -impl Debug for JsonHeaderParser { - - fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { - try!(write!(fmt, "JsonHeaderParser, Spec: {:?}", self.spec)); - Ok(()) - } - -} - -impl FileHeaderParser for JsonHeaderParser { - - fn read(&self, string: Option) - -> Result - { - if string.is_some() { - let s = string.unwrap(); - debug!("Deserializing: {}", s); - let fromstr : R = from_str(&s[..]); - if let Ok(ref content) = fromstr { - return Ok(visit_json(&content)) - } - let oe = fromstr.err().unwrap(); - let s = format!("JSON parser error: {}", oe.description()); - let e = ParserError::short(&s[..], s.clone(), 0); - Err(e) - } else { - Ok(FileHeaderData::Null) - } - } - - fn write(&self, data: &FileHeaderData) -> Result { - let mut s = Vec::::new(); - { - let mut ser = Serializer::pretty(&mut s); - data.serialize(&mut ser).map_err(|e| { - debug!("Serializer error: {:?}", e); - }).ok(); - } - - String::from_utf8(s).or( - Err(ParserError::short("Cannot parse utf8 bytes", - String::from(""), - 0))) - } - -} - -// TODO: This function must be able to return a parser error -fn visit_json(v: &Value) -> FileHeaderData { - match v { - &Value::Null => FileHeaderData::Null, - &Value::Bool(b) => FileHeaderData::Bool(b), - &Value::I64(i) => FileHeaderData::Integer(i), - &Value::U64(u) => FileHeaderData::UInteger(u), - &Value::F64(f) => FileHeaderData::Float(f), - &Value::String(ref s) => FileHeaderData::Text(s.clone()), - &Value::Array(ref vec) => { - FileHeaderData::Array { - values: Box::new(vec.clone().into_iter().map(|i| visit_json(&i)).collect()) - } - }, - &Value::Object(ref btree) => { - let btree = btree.clone(); - FileHeaderData::Map{ - keys: btree.into_iter().map(|(k, v)| - FileHeaderData::Key { - name: k, - value: Box::new(visit_json(&v)), - } - ).collect() - } - } - } -} - -impl Serialize for FileHeaderData { - - fn serialize(&self, ser: &mut S) -> Result<(), S::Error> - where S: Ser - { - match self { - &FileHeaderData::Null => { - let o : Option = None; - o.serialize(ser) - }, - &FileHeaderData::Bool(ref b) => b.serialize(ser), - &FileHeaderData::Integer(ref i) => i.serialize(ser), - &FileHeaderData::UInteger(ref u) => u.serialize(ser), - &FileHeaderData::Float(ref f) => f.serialize(ser), - &FileHeaderData::Text(ref s) => (&s[..]).serialize(ser), - &FileHeaderData::Array{values: ref vs} => vs.serialize(ser), - &FileHeaderData::Map{keys: ref ks} => { - let mut hm = HashMap::new(); - - for key in ks { - if let &FileHeaderData::Key{name: ref n, value: ref v} = key { - hm.insert(n, v); - } else { - panic!("Not a key: {:?}", key); - } - } - - hm.serialize(ser) - }, - &FileHeaderData::Key{name: _, value: _} => unreachable!(), - - } - } - -} - -#[cfg(test)] -mod test { - - use std::ops::Deref; - - use super::JsonHeaderParser; - use storage::parser::FileHeaderParser; - use storage::file::header::data::FileHeaderData as FHD; - use storage::file::header::spec::FileHeaderSpec as FHS; - - #[test] - fn test_deserialization() { - let text = String::from("{\"a\": 1, \"b\": -2}"); - let spec = FHS::Map { - keys: vec![ - FHS::Key { - name: String::from("a"), - value_type: Box::new(FHS::UInteger) - }, - FHS::Key { - name: String::from("b"), - value_type: Box::new(FHS::Integer) - } - ] - }; - - let parser = JsonHeaderParser::new(Some(spec)); - let parsed = parser.read(Some(text)); - assert!(parsed.is_ok(), "Parsed is not ok: {:?}", parsed); - - match parsed.ok() { - Some(FHD::Map{keys}) => { - for k in keys { - match k { - FHD::Key{name, value} => { - assert!(name == "a" || name == "b", "Key unknown"); - match value.deref() { - &FHD::UInteger(u) => assert_eq!(u, 1), - &FHD::Integer(i) => assert_eq!(i, -2), - _ => assert!(false, "Integers are not here"), - } - }, - _ => assert!(false, "Key is not a Key"), - } - } - }, - - _ => assert!(false, "Parsed is not a map"), - } - } - - #[test] - fn test_deserialization_without_spec() { - let text = String::from("{\"a\": [1], \"b\": {\"c\": -2}}"); - let parser = JsonHeaderParser::new(None); - let parsed = parser.read(Some(text)); - - assert!(parsed.is_ok(), "Parsed is not ok: {:?}", parsed); - - match parsed.ok() { - Some(FHD::Map{keys}) => { - for k in keys { - match_key(&k); - } - }, - - _ => assert!(false, "Parsed is not a map"), - } - } - - fn match_key(k: &FHD) { - use std::ops::Deref; - - match k { - &FHD::Key{ref name, ref value} => { - assert!(name == "a" || name == "b", "Key unknown"); - match value.deref() { - &FHD::Array{ref values} => { - for value in values.iter() { - match value { - &FHD::UInteger(u) => assert_eq!(u, 1), - _ => assert!(false, "UInt is not an UInt"), - } - } - } - - &FHD::Map{ref keys} => { - for key in keys.iter() { - match key { - &FHD::Key{ref name, ref value} => { - match value.deref() { - &FHD::Integer(i) => { - assert_eq!(i, -2); - assert_eq!(name, "c"); - }, - _ => assert!(false, "Int is not an Int"), - }; - }, - _ => assert!(false, "Key is not a Key"), - } - } - } - _ => assert!(false, "Integers are not here"), - } - }, - _ => assert!(false, "Key in main Map is not a Key"), - } - } - - #[test] - fn test_desser() { - use serde_json::error::Result as R; - use serde_json::{Value, from_str}; - - let text = String::from("{\"a\": [1], \"b\": {\"c\": -2}}"); - let parser = JsonHeaderParser::new(None); - - let des = parser.read(Some(text.clone())); - assert!(des.is_ok(), "Deserializing failed"); - - let ser = parser.write(&des.unwrap()); - assert!(ser.is_ok(), "Parser error when serializing deserialized text"); - - let json_text : R = from_str(&text[..]); - let json_ser : R = from_str(&ser.unwrap()[..]); - - assert!(json_text.is_ok(), "Could not use serde to serialize text for comparison"); - assert!(json_ser.is_ok(), "Could not use serde to serialize serialized-deserialized text for comparison"); - assert_eq!(json_text.unwrap(), json_ser.unwrap()); - } - -} diff --git a/src/storage/mod.rs b/src/storage/mod.rs deleted file mode 100644 index 5378bfcf..00000000 --- a/src/storage/mod.rs +++ /dev/null @@ -1,372 +0,0 @@ -use std::rc::Rc; -use std::cell::RefCell; -use std::collections::HashMap; -use std::fs::File as FSFile; -use std::ops::Deref; -use std::io::Write; -use std::io::Read; - -pub mod path; -pub mod file; -pub mod parser; -pub mod json; -pub mod yaml; - -use module::Module; -use storage::file::File; -use storage::file::id::FileID; -use storage::file::id_type::FileIDType; -use storage::file::hash::FileHash; -use storage::parser::{FileHeaderParser, Parser}; -use storage::file::header::data::FileHeaderData; - -type Cache = HashMap>>; - -pub struct Store { - storepath: String, - cache : RefCell, -} - -/** - * Store object - * - * This object is an abstraction layer over FS and an interface to the object store of this - * software. - */ -impl Store { - - pub fn new(storepath: String) -> Store { - Store { - storepath: storepath, - cache: RefCell::new(HashMap::new()), - } - } - - /** - * Put a file into the cache - */ - fn put_in_cache(&self, f: File) -> FileID { - let res = f.id().clone(); - self.cache.borrow_mut().insert(f.id().clone(), Rc::new(RefCell::new(f))); - res - } - - /** - * Generate a new file for a module. - * - * Returns the new FileID object then - */ - pub fn new_file(&self, module: &Module) - -> FileID - { - let f = File { - owning_module_name: module.name(), - header: FileHeaderData::Null, - data: String::from(""), - id: self.get_new_file_id(), - }; - - debug!("Create new File object: {:?}", &f); - self.put_in_cache(f) - } - - /** - * Generate a new file from a parser result. - * - * @deprecated This function shouldn't be needed anymore - */ - pub fn new_file_from_parser_result(&self, - module: &Module, - id: FileID, - header: FileHeaderData, - data: String) - -> FileID - { - let f = File { - owning_module_name: module.name(), - header: header, - data: data, - id: id, - }; - debug!("Create new File object from parser result: {:?}", f); - self.put_in_cache(f) - } - - /** - * Generate a new file for a module, providing some header data - * - * Returns the new FileID object then - */ - pub fn new_file_with_header(&self, - module: &Module, - h: FileHeaderData) - -> FileID - { - let f = File { - owning_module_name: module.name(), - header: h, - data: String::from(""), - id: self.get_new_file_id(), - }; - debug!("Create new File object with header: {:?}", f); - self.put_in_cache(f) - } - - /** - * Generate a new file for a module, providing some initial data - * - * Returns the new FileID object then - */ - pub fn new_file_with_data(&self, module: &Module, d: String) - -> FileID - { - let f = File { - owning_module_name: module.name(), - header: FileHeaderData::Null, - data: d, - id: self.get_new_file_id(), - }; - debug!("Create new File object with data: {:?}", f); - self.put_in_cache(f) - } - - - /** - * Generate a new file for a module, providing some initial data and some header - * - * Returns the new FileID object then - */ - pub fn new_file_with_content(&self, - module: &Module, - h: FileHeaderData, - d: String) - -> FileID - { - let f = File { - owning_module_name: module.name(), - header: h, - data: d, - id: self.get_new_file_id(), - }; - debug!("Create new File object with content: {:?}", f); - self.put_in_cache(f) - } - - /** - * Persist a File on the filesystem - * - * Returns true if this worked - */ - pub fn persist(&self, - p: &Parser, - f: Rc>) -> bool - where HP: FileHeaderParser - { - let file = f.deref().borrow(); - let text = p.write(file.contents()); - if text.is_err() { - error!("Error: {}", text.err().unwrap()); - return false; - } - - let path = { - let ids : String = file.id().clone().into(); - format!("{}/{}-{}.imag", self.storepath, file.owning_module_name, ids) - }; - - self.ensure_store_path_exists(); - - FSFile::create(&path).map(|mut fsfile| { - fsfile.write_all(&text.unwrap().clone().into_bytes()[..]) - }).map_err(|writeerr| { - debug!("Could not create file at '{}'", path); - debug!(" error: {:?}", writeerr); - }).and(Ok(true)).unwrap() - - // TODO: Is this unwrap() save? - } - - /** - * Helper to generate the store path - * - * Kills the program if it fails - */ - fn ensure_store_path_exists(&self) { - use std::fs::create_dir_all; - use std::process::exit; - - create_dir_all(&self.storepath).unwrap_or_else(|e| { - error!("Could not create store: '{}'", self.storepath); - error!("Error : '{}'", e); - error!("Killing myself now"); - exit(1); - }) - } - - /** - * Load a file by ID into the cache and return it afterwards - * - * Returns None if the file could be loaded from the Filesystem - */ - fn load_into_cache(&self, m: &Module, parser: &Parser, id: &FileID) - -> bool - where HP: FileHeaderParser - { - let idstr : String = id.clone().into(); - let path = format!("{}/{}-{}.imag", self.storepath, m.name(), idstr); - debug!("Loading path = '{}'", path); - let mut string = String::new(); - - FSFile::open(&path).map(|mut file| { - file.read_to_string(&mut string) - .map_err(|e| { - error!("Failed reading file: '{}'", path); - debug!(" error {}", e); - }) - .is_ok(); - }) - .map_err(|e| { - error!("Error opening file: {}", path); - debug!("Error opening file: {:?}", e); - }).ok(); - - parser.read(string).map(|(header, data)| { - self.new_file_from_parser_result(m, id.clone(), header, data); - true - }).unwrap_or(false) - } - - /** - * Load a file from the cache by FileID - * - * TODO: Semantics: This function should load from FS if the file is not in the cache yet or - * fail if the file is not available. - */ - pub fn load(&self, m: &Module, parser: &Parser, id: &FileID) - -> Option>> - where HP: FileHeaderParser - { - if !self.cache.borrow().contains_key(id) { - self.load_into_cache(m, parser, id); - } - debug!("Loading '{:?}'", id); - self.cache.borrow().get(id).cloned() - } - - /** - * Load a file from the filesystem/cache by a FileHash - */ - pub fn load_by_hash(&self, - m: &Module, - parser: &Parser, - hash: FileHash) - -> Option>> - where HP: FileHeaderParser - { - macro_rules! try_some { - ($expr:expr) => (match $expr { - ::std::option::Option::Some(val) => val, - ::std::option::Option::None => return ::std::option::Option::None, - }); - - ($expr:expr => return) => (match $expr { - ::std::option::Option::Some(val) => val, - ::std::option::Option::None => return, - }) - } - - use glob::glob; - - let hashstr : String = hash.into(); - let globstr = format!("{}/*-{}.imag", self.storepath, hashstr); - debug!("glob({})", globstr); - - let globs = glob(&globstr[..]); - if globs.is_err() { - return None; - } - - let path = globs.unwrap().last(); - debug!("path = {:?}", path); - - let pathbuf = try_some!(path); - if pathbuf.is_err() { return None; } - - let pathbuf_un = pathbuf.unwrap(); - let filename = pathbuf_un.file_name(); - let s = try_some!(filename).to_str(); - let string = String::from(try_some!(s)); - let id = try_some!(FileID::parse(&string)); - - debug!("Loaded ID = '{:?}'", id); - - self.load(m, parser, &id) - } - - /** - * Load all files for a module - */ - pub fn load_for_module(&self, m: &Module, parser: &Parser) - -> Vec>> - where HP: FileHeaderParser - { - use glob::glob; - - let globstr = format!("{}/{}-*.imag", self.storepath, m.name()); - let mut res = vec![]; - - glob(&globstr[..]).map(|paths| { - for path in paths { - if let Ok(pathbuf) = path { - let fname = pathbuf.file_name().and_then(|s| s.to_str()); - fname.map(|s| { - FileID::parse(&String::from(s)).map(|id| { - self.load(m, parser, &id).map(|file| { - res.push(file); - }) - }); - }); - } - } - }) - .map_err(|e| { - error!("Could not glob: '{}'", globstr); - debug!("Could not glob(): {:?}", e); - }) - .ok(); - res - } - - /** - * Remove a file from the filesystem by FileID - * - * Returns true if this works. - */ - pub fn remove(&self, id: FileID) -> bool { - use std::fs::remove_file; - - self.cache - .borrow_mut() - .remove(&id) - .map(|file| { - let idstr : String = id.into(); - let path = format!("{}/{}-{}.imag", - self.storepath, - file.deref().borrow().owner_name(), - idstr); - debug!("Removing file NOW: '{}'", path); - remove_file(path).is_ok() - }) - .unwrap_or(false) - } - - /** - * Helper to generate a new FileID object - */ - fn get_new_file_id(&self) -> FileID { - use uuid::Uuid; - let hash = FileHash::from(Uuid::new_v4().to_hyphenated_string()); - FileID::new(FileIDType::UUID, hash) - } - -} diff --git a/src/storage/parser.rs b/src/storage/parser.rs deleted file mode 100644 index f6ebb281..00000000 --- a/src/storage/parser.rs +++ /dev/null @@ -1,202 +0,0 @@ -use std::error::Error; -use std::fmt::{Debug, Display, Formatter}; -use std::fmt; - -use regex::Regex; - -use super::file::header::data::FileHeaderData; - -pub struct ParserError { - summary: String, - parsertext: String, - index: i32, - explanation: Option, - caused_by: Option>, -} - -impl ParserError { - pub fn new(sum: &'static str, text: String, idx: i32, expl: &'static str) -> ParserError { - ParserError { - summary: String::from(sum), - parsertext: text, - index: idx, - explanation: Some(String::from(expl)), - caused_by: None, - } - } - - pub fn short(sum: &str, text: String, idx: i32) -> ParserError { - ParserError { - summary: String::from(sum), - parsertext: text, - index: idx, - explanation: None, - caused_by: None, - } - } - - pub fn with_cause(mut self, e: Box) -> ParserError { - self.caused_by = Some(e); - self - } - -} - -impl Error for ParserError { - - fn description(&self) -> &str { - &self.summary[..] - } - - fn cause(&self) -> Option<&Error> { - self.caused_by.as_ref().map(|e| &**e) - } - -} - -impl Debug for ParserError { - - fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { - try!(write!(fmt, "ParserError: {}\n\n", self.summary)); - - if let Some(ref e) = self.explanation { - try!(write!(fmt, "{}\n\n", e)); - } - - try!(write!(fmt, "On position {}\nin\n{}", self.index, self.parsertext)); - Ok(()) - } - -} - -impl Display for ParserError { - - fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { - try!(write!(fmt, "ParserError: {}", self.summary)); - - if let Some(ref e) = self.explanation { - try!(write!(fmt, "\n\n{}", e)); - } - - Ok(()) - } - -} - -/** - * Trait for a header parser. - * - * This parser type has to provide two functions: - * - read(), which reads an String into a FileHeaderData structure - * - write(), which parses a FileHeaderData structure into a String - * - * TODO: Use Write/Read traits? - */ -pub trait FileHeaderParser : Sized + Debug + Display { - fn read(&self, string: Option) -> Result; - fn write(&self, data: &FileHeaderData) -> Result; -} - -/** - * Parser - * - * This Parser object is an abstraction which uses the FileHeaderParser to parse the whole contents - * of a file into a header (FileHeaderData) structure and the content (String). - */ -pub struct Parser { - headerp : HP, -} - -impl Parser { - - pub fn new(headerp: HP) -> Parser { - Parser { - headerp: headerp, - } - } - - /** - * Read the String which is the contents of a file into a (FileHeaderData, String) tuple, which - * is the header and the content of the file. - */ - pub fn read(&self, s: String) -> Result<(FileHeaderData, String), ParserError> { - debug!("Reading into internal datastructure: '{}'", s); - let divided = self.divide_text(&s); - - if divided.is_err() { - debug!("Error reading into internal datastructure"); - let p = ParserError::new("Dividing text failed", s, 0, - "Dividing text with divide_text() failed"); - return Err(p.with_cause(Box::new(divided.err().unwrap()))); - } - - let (header, data) = divided.ok().unwrap(); - debug!("Header = '{:?}'", header); - debug!("Data = '{:?}'", data); - - let h_parseres = try!(self.headerp.read(header)); - debug!("Success parsing header"); - - Ok((h_parseres, data.unwrap_or(String::new()))) - } - - /** - * Write the FileHeaderData and String (header and content) of the tuple into a String, which - * can then simply be written into the store as a file. - */ - pub fn write(&self, tpl : (&FileHeaderData, &String)) -> Result { - debug!("Parsing internal datastructure to String"); - let (header, data) = tpl; - let h_text = try!(self.headerp.write(&header)); - debug!("Success translating header"); - - let text = format!("---\n{}\n---\n{}", h_text, data); - Ok(text) - } - - /** - * Helper to parse the full-text of a file into a header part (String) and a content part - * (String) - */ - fn divide_text(&self, text: &String) -> Result<(Option, Option), ParserError> { - let re = Regex::new(r"(?sm)^---$(.*)^---$(.*)").unwrap(); - - debug!("Splitting: '{}'", text); - debug!(" regex = {:?}", re); - - re.captures(text).map(|captures| { - - if captures.len() != 3 { - debug!("Unexpected amount of captures"); - return Err(ParserError::new("Unexpected Regex output", - text.clone(), 0, - "The regex to divide text into header and content had an unexpected output.")) - } - - let header = captures.at(1).map(|s| String::from(s)); - let content = captures.at(2).map(|s| String::from(s)); - - debug!("Splitted, Header = '{:?}'", header.clone().unwrap_or("NONE".into())); - debug!("Splitted, Data = '{:?}'", content.clone().unwrap_or("NONE".into())); - Ok((header, content)) - }).or_else(|| { - debug!("Cannot capture from text"); - let e = ParserError::new("Cannot run regex on text", - text.clone(), 0, - "Cannot run regex on text to divide it into header and content."); - Some(Err(e)) - }).unwrap() - } - -} - -impl Debug for Parser - where HP: FileHeaderParser -{ - - fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { - try!(write!(fmt, "Parser<{:?}>", self.headerp)); - Ok(()) - } - -} diff --git a/src/storage/path.rs b/src/storage/path.rs deleted file mode 100644 index 58d8fb05..00000000 --- a/src/storage/path.rs +++ /dev/null @@ -1,108 +0,0 @@ -use std::path::PathBuf; - -use glob::glob; -use glob::Paths; -use glob::PatternError; - -use storage::file::id::FileID; -use storage::file::id_type::FileIDType; -use storage::file::hash::FileHash; -use module::Module; - -/* - * A path represents either a GLOB ("/tmp/store/module-*-*.imag" for example) or a full path - * - * It can be used to generate a File or iterate over some files - * - */ -struct Path<'a> { - - /* - * The base part ("/tmp/") - */ - base: PathBuf, - - /* - * The store part ("/store/") - */ - store: PathBuf, - - /* - * The module - */ - module: &'a Module<'a>, - - /* - * The ID - */ - idtype: Option, - idhash: Option, - id: Option, - -} - -impl<'a> Path<'a> { - - fn new(base: PathBuf, store: PathBuf, m: &'a Module<'a>, id: FileID) -> Path<'a> { - Path { - base: base, - store: store, - module: m, - idtype: Some(id.get_type()), - idhash: Some(id.get_id()), - id: Some(id), - } - } - - fn new_with_idtype(base: PathBuf, store: PathBuf, m: &'a Module<'a>, id: FileIDType) -> Path<'a> { - Path { - base: base, - store: store, - module: m, - idtype: Some(id), - idhash: None, - id: None, - } - } - - fn new_with_idhash(base: PathBuf, store: PathBuf, m: &'a Module<'a>, id: FileHash) -> Path<'a> { - Path { - base: base, - store: store, - module: m, - idtype: None, - idhash: Some(id), - id: None, - } - } - -} - -/* - * Transform Path into str, so we can call glob() on it - */ -impl<'a> Into for Path<'a> { - - fn into(self) -> String { - let mut s = self.base.clone(); - s.push(self.store.clone()); - s.push(self.module.name()); - if self.id.is_some() { - let idstr : String = self.id.unwrap().into(); - s.push(idstr); - } else { - s.push("*"); - } - s.set_extension("imag"); - s.to_str().unwrap_or("").into() - } -} - -impl<'a> Into> for Path<'a> { - - fn into(self) -> Result { - let s : String = self.into(); - glob(&s[..]) - } - -} diff --git a/src/storage/yaml/mod.rs b/src/storage/yaml/mod.rs deleted file mode 100644 index 67c567fa..00000000 --- a/src/storage/yaml/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod parser; diff --git a/src/storage/yaml/parser.rs b/src/storage/yaml/parser.rs deleted file mode 100644 index 239159db..00000000 --- a/src/storage/yaml/parser.rs +++ /dev/null @@ -1,256 +0,0 @@ -use std::fmt::{Debug, Display, Formatter}; -use std::fmt; - -use yaml_rust::Yaml; - -use storage::parser::{FileHeaderParser, ParserError}; -use storage::file::header::spec::FileHeaderSpec; -use storage::file::header::data::FileHeaderData; - -pub struct YamlHeaderParser { - spec: Option, -} - -impl YamlHeaderParser { - - pub fn new(spec: Option) -> YamlHeaderParser { - YamlHeaderParser { - spec: spec - } - } - -} - -impl Display for YamlHeaderParser { - - fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { - try!(write!(fmt, "YamlHeaderParser")); - Ok(()) - } - -} - -impl Debug for YamlHeaderParser { - - fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { - try!(write!(fmt, "YamlHeaderParser, Spec: {:?}", self.spec)); - Ok(()) - } - -} - -impl FileHeaderParser for YamlHeaderParser { - - fn read(&self, string: Option) -> Result { - use yaml_rust::YamlLoader; - if string.is_some() { - let s = string.unwrap(); - YamlLoader::load_from_str(&s[..]) - .map(|mut vec_yaml| { - vec_yaml.pop().map(|f| { - visit_yaml(f) - }).unwrap() - }) - .map_err(|e| { - debug!("YAML parser error: {:?}", e); - ParserError::short(&s[..], s.clone(), 0) - }) - } else { - Ok(FileHeaderData::Null) - } - - } - - fn write(&self, data: &FileHeaderData) -> Result { - use yaml_rust::YamlEmitter; - - let mut buffer = String::new(); - let result = { - let mut emitter = YamlEmitter::new(&mut buffer); - emitter.dump(&visit_header(data)) - }; - result - .map_err(|e| { - error!("Error emitting YAML."); - debug!("YAML parser error: {:?}", e); - ParserError::short(&buffer[..], buffer.clone(), 0) - }) - .map(|_| buffer) - } - -} - -fn visit_yaml(v: Yaml) -> FileHeaderData { - use std::process::exit; - - match v { - Yaml::Real(_) => FileHeaderData::Float(v.as_f64().unwrap()), - Yaml::Integer(i) => { - if i > 0 { - debug!("Castring {} : i64 -> u64", i); - FileHeaderData::UInteger(i as u64) - } else { - FileHeaderData::Integer(i) - } - }, - Yaml::String(s) => FileHeaderData::Text(s), - Yaml::Boolean(b) => FileHeaderData::Bool(b), - - Yaml::Array(vec) => { - FileHeaderData::Array { - values: Box::new(vec.clone().into_iter().map(|i| visit_yaml(i)).collect()) - } - }, - - Yaml::Hash(btree) => { - let btree = btree.clone(); - FileHeaderData::Map{ - keys: btree.into_iter().map(|(k, v)| - FileHeaderData::Key { - name: String::from(k.as_str().unwrap()), - value: Box::new(visit_yaml(v)), - } - ).collect() - } - }, - - Yaml::Alias(_) => { - warn!("YAML::ALIAS is not yet fully supported by rust-yaml"); - FileHeaderData::Null - }, - - Yaml::Null => FileHeaderData::Null, - - Yaml::BadValue => { - warn!("YAML parsing error"); - exit(1); - }, - } -} - -fn visit_header(h: &FileHeaderData) -> Yaml { - use std::ops::Deref; - use std::collections::BTreeMap; - use std::process::exit; - - match h { - &FileHeaderData::Null => Yaml::Null, - &FileHeaderData::Float(f) => Yaml::Real(format!("{}", f)), - &FileHeaderData::Integer(i) => Yaml::Integer(i), - &FileHeaderData::UInteger(u) => { - debug!("Might be losing data now: u64 -> i64 cast"); - Yaml::Integer(u as i64) - }, - &FileHeaderData::Text(ref s) => Yaml::String(s.clone()), - &FileHeaderData::Bool(b) => Yaml::Boolean(b), - - &FileHeaderData::Array{values: ref a} => { - Yaml::Array(a.deref().into_iter().map(|e| visit_header(e)).collect()) - }, - - &FileHeaderData::Key{name: _, value: _} => { - error!("Something went terribly wrong when trying to emit YAML"); - exit(1); - }, - - &FileHeaderData::Map{ref keys} => { - let mut map : BTreeMap = BTreeMap::new(); - - let failed = keys.into_iter().map(|key| { - match key { - &FileHeaderData::Key{ref name, ref value} => { - let k = Yaml::String(name.clone()); - let v = visit_header(value.deref()); - - map.insert(k, v).is_none() - }, - - _ => { - error!("Something went terribly wrong when trying to emit YAML"); - exit(1); - } - } - }) - .fold(0, |acc, succeeded : bool| { - if !succeeded { acc + 1 } else { acc } - }); - - debug!("Failed to insert {} keys", failed); - Yaml::Hash(map) - }, - } -} - -#[cfg(test)] -mod test { - use std::ops::Deref; - - use super::YamlHeaderParser; - use storage::parser::FileHeaderParser; - use storage::file::header::data::FileHeaderData as FHD; - use storage::file::header::spec::FileHeaderSpec as FHS; - - #[test] - fn test_deserialization() { - let text = String::from("a: 1\nb: 2"); - let spec = FHS::Array { allowed_types: vec![ - FHS::Map { - keys: vec![ - FHS::Key { - name: String::from("a"), - value_type: Box::new(FHS::UInteger) - }, - ] - } - ] - }; - - let parser = YamlHeaderParser::new(Some(spec)); - let parsed = parser.read(Some(text)); - assert!(parsed.is_ok(), "Parsed is not ok: {:?}", parsed); - debug!("Parsed: {:?}", parsed); - - match parsed.ok() { - Some(FHD::Map{ref keys}) => { - keys.into_iter().map(|k| { - match k { - &FHD::Key{ref name, ref value} => { - assert!(name == "a" || name == "b", "Key unknown"); - match value.deref() { - &FHD::UInteger(u) => assert!(u == 1 || u == 2), - &FHD::Integer(_) => assert!(false, "Found Integer, expected UInteger"), - _ => assert!(false, "Integers are not here"), - }; - }, - _ => assert!(false, "Key is not a Key"), - }; - }) - .all(|x| x == ()); - }, - _ => assert!(false, "Map is not a Map"), - } - } - - #[test] - fn test_desser() { - use yaml_rust::YamlLoader; - - let text = String::from("a: [1, 32, 42]\nb: -2"); - let parser = YamlHeaderParser::new(None); - - let des = parser.read(Some(text.clone())); - assert!(des.is_ok(), "Deserializing failed"); - - let ser = parser.write(&des.unwrap()); - assert!(ser.is_ok(), "Parser error when serializing deserialized text"); - - let yaml_text = YamlLoader::load_from_str(&text[..]); - let yaml_ser = YamlLoader::load_from_str(&ser.unwrap()[..]); - - assert!(yaml_text.is_ok(), "Could not use yaml_rust to serialize text for comparison"); - assert!(yaml_ser.is_ok(), "Could not use yaml_rust to serialize serialized-deserialized text for comparison"); - assert_eq!(yaml_text.unwrap(), yaml_ser.unwrap()); - } - -} - diff --git a/src/ui/external/editor.rs b/src/ui/external/editor.rs deleted file mode 100644 index e93b8f32..00000000 --- a/src/ui/external/editor.rs +++ /dev/null @@ -1,122 +0,0 @@ -use runtime::Runtime; - -/** - * A function which lets the user provide content by editing a temp files which gets removed after - * the function got the content from it. - */ -pub fn let_user_provide_content(rt: &Runtime) -> Option { - use std::io::Read; - use std::fs::File; - use std::process::exit; - - let filepath = "/tmp/imag-tmp.md"; - let file_created = File::create(filepath) - .map(|_| true) - .unwrap_or(false); - - if !file_created { - warn!("Could not create temporary file for user input!"); - return None; - } - - let output = { - let mut cmd = rt.editor(); - cmd.arg(filepath); - debug!("cmd = {:?}", cmd); - cmd.spawn() - .and_then(|child| { - child.wait_with_output() - }) - }; - - let process_out = output.map_err(|e| { - error!("Editor call failed"); - debug!("Editor call failed: {:?}", e); - return None as Option; - }).unwrap(); - - if !process_out.status.success() { - error!("Editor call failed"); - debug!("status = {:?}", process_out.status); - debug!("stdout = {:?}", String::from_utf8(process_out.stdout)); - debug!("stderr = {:?}", String::from_utf8(process_out.stderr)); - return None; - } - - let mut contents = String::new(); - File::open(filepath).map(|mut file| { - file.read_to_string(&mut contents) - .map_err(|e| { - error!("Error reading content: {}", e); - debug!("Error reading content: {:?}", e); - exit(1); - }) - .is_ok(); - Some(contents) - }).unwrap_or(None) -} - -/** - * Edit some content in a temporary file. If anything failes within this routine, it returns the - * old content and false. - * If the editing succeeded, it returns the new content and true - */ -pub fn edit_content(rt: &Runtime, old_content: String) -> (String, bool) { - use std::io::Read; - use std::io::Write; - use std::fs::File; - use std::process::exit; - - let filepath = "/tmp/imag-tmp.md"; - { - let mut file = match File::create(filepath) { - Ok(f) => f, - Err(e) => { - error!("Error creating file {}", filepath); - debug!("Error creating file at '{}', error = {}", filepath, e); - exit(1); - } - }; - - file.write(old_content.as_ref()) - .map_err(|e| { - error!("Error writing content: {}", e); - debug!("Error writing content: {:?}", e); - exit(1); - }).is_ok(); - } - debug!("Ready with putting old content into the file"); - - let output = { - let mut cmd = rt.editor(); - cmd.arg(filepath); - debug!("cmd = {:?}", cmd); - cmd.spawn() - .and_then(|child| child.wait_with_output()) - }; - - let process_out = output.map_err(|e| { - error!("Editor call failed"); - debug!("Editor call failed: {:?}", e); - return None as Option; - }).unwrap(); - - if !process_out.status.success() { - error!("Editor call failed"); - debug!("status = {:?}", process_out.status); - debug!("stdout = {:?}", String::from_utf8(process_out.stdout)); - debug!("stderr = {:?}", String::from_utf8(process_out.stderr)); - return (old_content, false); - } - - let mut contents = String::new(); - File::open(filepath).map(|mut file| { - file.read_to_string(&mut contents).map_err(|e| { - error!("Error reading content: {}", e); - debug!("Error reading content: {:?}", e); - exit(1); - }).is_ok(); - (contents, true) - }).unwrap_or((old_content, false)) -} - diff --git a/src/ui/external/mod.rs b/src/ui/external/mod.rs deleted file mode 100644 index a6a128c1..00000000 --- a/src/ui/external/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -use std::fs::File; - -pub mod editor; - -pub fn get_tempfile(ext: &str) -> Option<(String, File)> { - use rand::random; - - let randomname = format!("/tmp/imag-{}.{}", random::(), ext); - debug!("Attempting to create tempfile at {}", randomname); - File::create(randomname.clone()) - .map_err(|e| debug!(" Error -> {}", e)) - .ok() - .map(|f| (randomname, f)) -} diff --git a/src/ui/file.rs b/src/ui/file.rs deleted file mode 100644 index 505c16ca..00000000 --- a/src/ui/file.rs +++ /dev/null @@ -1,242 +0,0 @@ -use std::cell::RefCell; -use std::iter::Iterator; -use std::rc::Rc; -use std::ops::Deref; - -use storage::file::File; - -/** - * Trait for a printer which can be used to print data from files - */ -pub trait FilePrinter { - - /* - * Print a single file - */ - fn print_file(&self, Rc>); - - /* - * Print a list of files - */ - fn print_files>>>(&self, files: I) { - for file in files { - self.print_file(file); - } - } - - fn print_file_custom(&self, file: Rc>, f: &F) - where F: Fn(Rc>) -> Vec - { - info!("{}", f(file).join(" ")); - } - - fn print_files_custom(&self, files: I, f: &F) - where I: Iterator>>, - F: Fn(Rc>) -> Vec - { - for file in files { - self.print_file_custom(file, f); - } - } - -} - -/** - * Printer which prints in debug mode if enabled - */ -struct DebugPrinter { - debug: bool, -} - -impl DebugPrinter { - - pub fn new(debug: bool) -> DebugPrinter { - DebugPrinter { - debug: debug, - } - } - -} - -impl FilePrinter for DebugPrinter { - - fn print_file(&self, f: Rc>) { - if self.debug { - debug!("[DebugPrinter] ->\n{:?}", f); - } - } - - fn print_file_custom(&self, file: Rc>, f: &F) - where F: Fn(Rc>) -> Vec - { - if self.debug { - debug!("[DebugPrinter] ->\n{:?}", f(file).join(" ")); - } - } - -} - -/** - * Simple printer, which just uses the info!() macro or debug!() macro if in debug mode. - */ -struct SimplePrinter { - verbose: bool, - debug: bool, -} - -impl SimplePrinter { - - pub fn new(verbose: bool, debug: bool) -> SimplePrinter { - SimplePrinter { - debug: debug, - verbose: verbose, - } - } - -} - -impl FilePrinter for SimplePrinter { - - fn print_file(&self, f: Rc>) { - use ansi_term::Colour::Cyan; - - if self.debug { - debug!("{:?}", f); - } else if self.verbose { - info!("{}", &*f.deref().borrow()); - } else { - info!("{}: {}", Cyan.paint("[File]"), f.deref().borrow().id()); - } - } - - fn print_file_custom(&self, file: Rc>, f: &F) - where F: Fn(Rc>) -> Vec - { - use ansi_term::Colour::Cyan; - - let s = f(file).join(" "); - if self.debug { - debug!("{:?}", s); - } else if self.verbose { - info!("{}", s); - } else { - info!("{}: {}", Cyan.paint("[File]"), s); - } - } - -} - -/** - * Table printer to print file information in a nice ASCII-table - */ -pub struct TablePrinter { - sp: SimplePrinter, - pretty: bool, -} - -impl TablePrinter { - - pub fn new(verbose: bool, debug: bool, pretty: bool) -> TablePrinter { - TablePrinter { - sp: SimplePrinter::new(verbose, debug), - pretty: pretty, - } - } - -} - -impl FilePrinter for TablePrinter { - - fn print_file(&self, f: Rc>) { - self.sp.print_file(f); - } - - fn print_files>>>(&self, files: I) { - use prettytable::Table; - use prettytable::format::TableFormat; - use prettytable::row::Row; - use prettytable::cell::Cell; - - let titles = row!["File#", "Owner", "ID"]; - - let mut tab = Table::new(); - - if !self.pretty { - let plain_format = TableFormat::new(None, None, None); - debug!("Setting plain format for table"); - tab.set_format(plain_format); - } - - tab.set_titles(titles); - - let mut i = 0; - for file in files { - debug!("Printing file: {:?}", file); - i += 1; - let cell_i = Cell::new(&format!("{}", i)[..]); - let cell_o = Cell::new(&format!("{}", file.deref().borrow().owner_name())[..]); - - let id : String = file.deref().borrow().id().clone().into(); - let cell_id = Cell::new(&id[..]); - let row = Row::new(vec![cell_i, cell_o, cell_id]); - tab.add_row(row); - } - - if i != 0 { - debug!("Printing {} table entries", i); - tab.printstd(); - } else { - debug!("Not printing table because there are zero entries"); - } - } - - fn print_files_custom(&self, files: I, f: &F) - where I: Iterator>>, - F: Fn(Rc>) -> Vec - { - use prettytable::Table; - use prettytable::format::TableFormat; - use prettytable::row::Row; - use prettytable::cell::Cell; - - let titles = row!["#", "Module", "ID", "..."]; - - let mut tab = Table::new(); - - if !self.pretty { - let plain_format = TableFormat::new(None, None, None); - debug!("Setting plain format for table"); - tab.set_format(plain_format); - } - - tab.set_titles(titles); - - let mut i = 0; - for file in files { - debug!("Printing file: {:?}", file); - i += 1; - let cell_i = Cell::new(&format!("{}", i)[..]); - let cell_o = Cell::new(&format!("{}", file.deref().borrow().owner_name())[..]); - - let id : String = file.deref().borrow().id().clone().into(); - let cell_id = Cell::new(&id[..]); - - let mut row = Row::new(vec![cell_i, cell_o, cell_id]); - - for cell in f(file).iter() { - debug!("Adding custom cell: {:?}", cell); - row.add_cell(Cell::new(&cell[..])) - } - - tab.add_row(row); - } - - if i != 0 { - debug!("Printing {} table entries", i); - tab.printstd(); - } else { - debug!("Not printing table because there are zero entries"); - } - } - -} diff --git a/src/ui/mod.rs b/src/ui/mod.rs deleted file mode 100644 index 7f1bc2ed..00000000 --- a/src/ui/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod file; -pub mod external; diff --git a/src/util/mod.rs b/src/util/mod.rs deleted file mode 100644 index 41df92d6..00000000 --- a/src/util/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -use url::Url; - -/** - * Util: Check wether a String can be parsed as an URL - */ -pub fn is_url(url: &String) -> bool { - Url::parse(&url[..]).is_ok() -} -