Auto merge of #51 - matthiasbeyer:notes-list-links, r=matthiasbeyer

[Notes] list links

Closes #46
This commit is contained in:
Homu 2016-01-04 00:37:55 +09:00
commit 221fbee4a7
7 changed files with 257 additions and 12 deletions

34
Cargo.lock generated
View file

@ -6,10 +6,11 @@ dependencies = [
"clap 1.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
"config 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"glob 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
"hoedown 3.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"itertools 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"open 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"prettytable-rs 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"pulldown-cmark 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
"rustty 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -76,15 +77,34 @@ dependencies = [
]
[[package]]
name = "getopts"
version = "0.2.14"
name = "gcc"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"advapi32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "glob"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "hoedown"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"gcc 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "itertools"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "kernel32-sys"
version = "0.1.4"
@ -162,14 +182,6 @@ dependencies = [
"unicode-width 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "pulldown-cmark"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand"
version = "0.3.11"

View file

@ -27,5 +27,6 @@ term = "0.2.12"
term_grid = "0.1.2"
prettytable-rs = "0.4.0"
open = "1.1.0"
pulldown-cmark = "0.0.3"
itertools = "0.4.5"
hoedown = "3.0.3"

View file

@ -340,6 +340,52 @@ subcommands:
required: false
takes_value: true
- links:
about: List links in notes
version: 0.1
author: Matthias Beyer <mail@beyermatthias.de>
args:
- internal:
short: i
long: intern
help: List only links to imag content
required: false
takes_value: false
- external:
short: e
long: extern
help: List only links to outside of imag
required: false
takes_value: false
- id:
long: id
help: Delete Note by ID
required: false
takes_value: true
- namegrep:
short: n
long: name
help: Filter for name which matches this regex
required: false
takes_value: true
- grep:
short: g
long: grep
help: grep with regex
required: false
takes_value: true
- tags:
short: t
long: tags
help: Filter for these tags
required: false
takes_value: true
- remove:
about: Remove note(s)
version: 0.1

View file

@ -6,9 +6,11 @@
#[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;
pub use cli::CliConfig;
pub use configuration::Configuration;

View file

@ -0,0 +1,73 @@
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<Link>
}
impl LinkExtractRenderer {
fn new() -> LinkExtractRenderer {
LinkExtractRenderer {
links: vec![],
}
}
fn extract(self) -> Vec<Link> {
self.links
}
}
impl Render for LinkExtractRenderer {
fn link(&mut self,
output: &mut Buffer,
content: &Buffer,
link: &Buffer,
title: &Buffer) -> bool {
let l = String::from(link.to_str().unwrap_or("<<UTF8 Error>>"));
let t = String::from(title.to_str().unwrap_or("<<UTF8 Error>>"));
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<Link> {
let mut renderer = LinkExtractRenderer::new();
renderer.render(&self.text);
renderer.extract()
}
}
}

View file

@ -5,6 +5,7 @@
pub mod cli;
pub mod header;
pub mod utils;
pub mod content;
/**
* Helpers for header specs
@ -40,3 +41,4 @@ pub mod spec {
}
}

View file

@ -161,6 +161,111 @@ impl<'a> Notes<'a> {
true
}
fn command_links(&self, matches: &ArgMatches) -> bool {
use module::helpers::content::markdown::MarkdownParser;
use ui::file::{FilePrinter, TablePrinter};
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 printer = TablePrinter::new(self.rt.is_verbose(), self.rt.is_debugging());
let titles = row!["#", "Text", "Link", "Direction"];
let mut table = Table::new();
table.set_titles(titles);
debug!("Table setup finished");
let parser = Parser::new(JsonHeaderParser::new(None));
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, &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 title = &link.title;
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!("Listing links succeeded for {} files", worked);
info!("Listing links failed for {} files", failed);
return failed == 0;
}
fn command_remove(&self, matches: &ArgMatches) -> bool {
let parser = Parser::new(JsonHeaderParser::new(None));
@ -251,6 +356,10 @@ impl<'a> Module<'a> for Notes<'a> {
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())
},