bwkfanboy 1.4.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +51 -0
- data/Procfile +1 -0
- data/README.rdoc +40 -77
- data/Rakefile +13 -48
- data/bin/bwkfanboy +47 -166
- data/bin/bwkfanboy_generate +7 -19
- data/bin/bwkfanboy_parse +21 -17
- data/bwkfanboy.gemspec +40 -0
- data/config.ru +3 -0
- data/doc/NEWS.rdoc +21 -79
- data/doc/plugin.rdoc +63 -79
- data/etc/bwkfanboy.yaml +2 -0
- data/etc/sinatra.rb +34 -0
- data/lib/bwkfanboy/cliconfig.rb +141 -0
- data/lib/bwkfanboy/cliutils.rb +114 -0
- data/lib/bwkfanboy/fetch.rb +22 -24
- data/lib/bwkfanboy/generator.rb +78 -0
- data/lib/bwkfanboy/home.rb +53 -0
- data/lib/bwkfanboy/meta.rb +5 -2
- data/lib/bwkfanboy/plugin.rb +247 -0
- data/lib/bwkfanboy/plugin_skeleton.erb +19 -23
- data/lib/bwkfanboy/server.rb +73 -0
- data/lib/bwkfanboy/utils.rb +39 -129
- data/plugins/bwk.rb +25 -0
- data/plugins/econlib.rb +22 -0
- data/plugins/freebsd-ports-update.rb +73 -0
- data/plugins/inc.rb +29 -0
- data/plugins/test.rb +29 -0
- data/public/.gitattributes +1 -0
- data/public/favicon.ico +0 -0
- data/public/jquery-1.7.2.min.js +0 -0
- data/public/list.js +111 -0
- data/public/loading.gif +0 -0
- data/public/style.css +54 -0
- data/shotgun.rb +20 -0
- data/test/example/.gitattributes +1 -0
- data/test/example/.gitignore +1 -0
- data/test/example/02/plugins/bwk.html +0 -0
- data/test/{plugins → example/02/plugins}/empty.rb +0 -0
- data/test/example/02/plugins/garbage.rb +1 -0
- data/test/example/02/plugins/inc.html +0 -0
- data/test/helper.rb +30 -27
- data/test/helper_cliutils.rb +34 -0
- data/test/test_cli.rb +86 -0
- data/test/test_fetch.rb +49 -18
- data/test/test_generate.rb +43 -16
- data/test/test_home.rb +33 -0
- data/test/test_plugin.rb +141 -0
- data/test/test_server.rb +21 -32
- data/views/list.haml +38 -0
- metadata +223 -110
- data/bin/bwkfanboy_fetch +0 -13
- data/bin/bwkfanboy_server +0 -126
- data/doc/README.erb +0 -114
- data/doc/README.rdoc +0 -141
- data/doc/TODO +0 -7
- data/doc/bwkfanboy_fetch.rdoc +0 -4
- data/doc/bwkfanboy_generate.rdoc +0 -7
- data/doc/bwkfanboy_parse.rdoc +0 -7
- data/doc/bwkfanboy_server.rdoc +0 -35
- data/doc/rakefile.rb +0 -59
- data/lib/bwkfanboy/generate.rb +0 -63
- data/lib/bwkfanboy/parser.rb +0 -156
- data/lib/bwkfanboy/plugins/bwk.rb +0 -33
- data/lib/bwkfanboy/plugins/econlib.rb +0 -34
- data/lib/bwkfanboy/plugins/freebsd-ports-update.rb +0 -76
- data/lib/bwkfanboy/plugins/inc.rb +0 -37
- data/lib/bwkfanboy/schema.js +0 -39
- data/test/popen4.sh +0 -4
- data/test/rake_git.rb +0 -36
- data/test/semis/Rakefile +0 -35
- data/test/semis/bwk.html +0 -393
- data/test/semis/bwk.json +0 -82
- data/test/semis/econlib.html +0 -21
- data/test/semis/inc.html +0 -1067
- data/test/semis/links.txt +0 -4
- data/test/test_parse.rb +0 -27
- data/test/xml-clean.sh +0 -8
- data/web/bwkfanboy.cgi +0 -36
data/plugins/bwk.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
@uri << 'http://www.dailyprincetonian.com/advanced_search/?author=Brian+Kernighan'
|
2
|
+
@copyright = "See bwkfanboy's LICENSE file"
|
3
|
+
@title = "Brian Kernighan's articles from Daily Princetonian"
|
4
|
+
@content_type = 'html'
|
5
|
+
@version = 2
|
6
|
+
|
7
|
+
# [streams] an array of IO streamss
|
8
|
+
def parse streams
|
9
|
+
streams.each do |io|
|
10
|
+
baseurl = "http://www.dailyprincetonian.com"
|
11
|
+
|
12
|
+
doc = Nokogiri::HTML io, nil, enc
|
13
|
+
doc.xpath("//div[@class='article_item']").each do |idx|
|
14
|
+
t = idx.xpath("h2/a").children.text
|
15
|
+
link = idx.xpath("h2/a")[0].attributes['href'].value
|
16
|
+
l = baseurl + link + "print"
|
17
|
+
u = BH.date idx.xpath("h2").children[1].text
|
18
|
+
a = idx.xpath("div/span/a[1]").children.text
|
19
|
+
c = idx.xpath("div[@class='summary']").text
|
20
|
+
|
21
|
+
self << { 'title' => t, 'link' => l, 'updated' => u,
|
22
|
+
'author' => a, 'content' => c }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/plugins/econlib.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
@uri << 'http://www.econlib.org/cgi-bin/searcharticles.pl?sortby=DD&query=ha*'
|
2
|
+
@version = 1
|
3
|
+
@copyright = "See bwkfanboy's LICENSE file"
|
4
|
+
@title = "Latest articles from econlib.org"
|
5
|
+
@content_type = 'html'
|
6
|
+
|
7
|
+
def parse streams
|
8
|
+
baseurl = 'http://www.econlib.org'
|
9
|
+
|
10
|
+
doc = Nokogiri::HTML streams.first, nil, @enc
|
11
|
+
doc.xpath("//*[@id='divResults']//tr").each {|idx|
|
12
|
+
t = idx.xpath("td[3]//a").text
|
13
|
+
next if t == ""
|
14
|
+
l = baseurl + idx.xpath("td[3]//a")[0].attributes['href'].value
|
15
|
+
u = BH.date idx.xpath("td[4]").children.text
|
16
|
+
a = idx.xpath("td[3]/div").children[2].text
|
17
|
+
c = idx.xpath("td[4]").children[2].text
|
18
|
+
|
19
|
+
self << { 'title' => t, 'link' => l, 'updated' => u,
|
20
|
+
'author' => a, 'content' => c }
|
21
|
+
}
|
22
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
|
3
|
+
@uri << '/usr/ports/UPDATING'
|
4
|
+
@enc = 'ASCII'
|
5
|
+
@version = 3
|
6
|
+
@copyright = "See bwkfanboy's LICENSE file"
|
7
|
+
@title = "News from FreeBSD ports"
|
8
|
+
@content_type = 'text'
|
9
|
+
|
10
|
+
def my_add ready, t, l, u, a, c
|
11
|
+
return true if ! ready
|
12
|
+
return false if full?
|
13
|
+
|
14
|
+
self << { 'title' => t, 'link' => l, 'updated' => u,
|
15
|
+
'author' => a, 'content' => c.rstrip } if ready
|
16
|
+
true
|
17
|
+
end
|
18
|
+
|
19
|
+
def my_clean t
|
20
|
+
t = t[2..-1] if t[0] != "\t"
|
21
|
+
return '' if t == nil
|
22
|
+
t
|
23
|
+
end
|
24
|
+
|
25
|
+
def parse streams
|
26
|
+
re_u = /^(\d{8}):$/
|
27
|
+
re_t1 = /^ {2}AFFECTS:\s+(.+)$/
|
28
|
+
re_t2 = /^\s+(.+)$/
|
29
|
+
re_a = /^ {2}AUTHORS?:\s+(.+)$/
|
30
|
+
|
31
|
+
ready = false
|
32
|
+
mode = nil
|
33
|
+
t = l = u = a = c = nil
|
34
|
+
while line = streams.first.gets
|
35
|
+
line.rstrip!
|
36
|
+
|
37
|
+
if line =~ re_u then
|
38
|
+
# add a new entry
|
39
|
+
break if ! my_add(ready, t, l, u, a, c)
|
40
|
+
ready = true
|
41
|
+
u = BH.date($1)
|
42
|
+
l = $1 # partial, see below
|
43
|
+
t = a = c = nil
|
44
|
+
next
|
45
|
+
end
|
46
|
+
|
47
|
+
if ready then
|
48
|
+
if line =~ re_t1 then
|
49
|
+
mode = 'title'
|
50
|
+
t = $1
|
51
|
+
c = my_clean($&) + "\n"
|
52
|
+
# link should be unique
|
53
|
+
l = "file://#{@uri.first}\##{l}-#{Digest::MD5.hexdigest($1)}"
|
54
|
+
elsif line =~ re_a
|
55
|
+
mode = 'author'
|
56
|
+
a = $1
|
57
|
+
c += my_clean($&) + "\n"
|
58
|
+
elsif line =~ re_t2 && mode == 'title'
|
59
|
+
t += ' ' + $1
|
60
|
+
c += my_clean($&) + "\n"
|
61
|
+
else
|
62
|
+
# content
|
63
|
+
c += my_clean(line) + "\n"
|
64
|
+
mode = nil
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# skipping the preamble
|
69
|
+
end
|
70
|
+
|
71
|
+
# add last entry
|
72
|
+
my_add(ready, t, l, u, a, c)
|
73
|
+
end
|
data/plugins/inc.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
@opt.each {|i| @uri << 'http://www.inc.com/author/' + i }
|
2
|
+
@version = 1
|
3
|
+
@copyright = 'See bwkfanboy\'s LICENSE file'
|
4
|
+
@title = "Articles (per-user) from inc.com"
|
5
|
+
@content_type = 'html'
|
6
|
+
|
7
|
+
def parse streams
|
8
|
+
streams.each_with_index do |io, index|
|
9
|
+
profile = @opt[index]
|
10
|
+
|
11
|
+
doc = Nokogiri::HTML(io, nil, @enc)
|
12
|
+
doc.xpath("//div[@id='articleriver']/div/div").each do |idx|
|
13
|
+
t = idx.xpath("h3").text
|
14
|
+
l = idx.xpath("h3/a")[0].attributes['href'].value
|
15
|
+
|
16
|
+
next if (u = idx.xpath("div[@class='byline']/span")).size == 0
|
17
|
+
u = BH.date u.text
|
18
|
+
|
19
|
+
a = idx.xpath("div[@class='byline']/a").text
|
20
|
+
|
21
|
+
c = idx.xpath("p[@class='summary']")
|
22
|
+
c.xpath("a").remove
|
23
|
+
c = c.inner_html encoding: @enc
|
24
|
+
|
25
|
+
self << { 'title' => t, 'link' => l, 'updated' => u,
|
26
|
+
'author' => a, 'content' => c }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/plugins/test.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
@opt.each { @uri << "#{@syslib}/../../test/example/02/plugins/inc.html" }
|
2
|
+
@version = 0
|
3
|
+
@copyright = 'takoe'
|
4
|
+
@title = "Test plugin that requires additional options"
|
5
|
+
@content_type = 'html'
|
6
|
+
|
7
|
+
def parse streams
|
8
|
+
streams.each_with_index do |io, index|
|
9
|
+
profile = @opt[index]
|
10
|
+
|
11
|
+
doc = Nokogiri::HTML(io, nil, @enc)
|
12
|
+
doc.xpath("//div[@id='articleriver']/div/div").each do |idx|
|
13
|
+
t = idx.xpath("h3").text
|
14
|
+
l = idx.xpath("h3/a")[0].attributes['href'].value
|
15
|
+
|
16
|
+
next if (u = idx.xpath("div[@class='byline']/span")).size == 0
|
17
|
+
u = BH.date u.text
|
18
|
+
|
19
|
+
a = idx.xpath("div[@class='byline']/a").text
|
20
|
+
|
21
|
+
c = idx.xpath("p[@class='summary']")
|
22
|
+
c.xpath("a").remove
|
23
|
+
c = c.inner_html encoding: @enc
|
24
|
+
|
25
|
+
self << { 'title' => t, 'link' => l, 'updated' => u,
|
26
|
+
'author' => a, 'content' => c }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
jquery-* binary
|
data/public/favicon.ico
ADDED
Binary file
|
Binary file
|
data/public/list.js
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
/*
|
2
|
+
Display plugin info on click.
|
3
|
+
*/
|
4
|
+
|
5
|
+
function List() {}
|
6
|
+
|
7
|
+
List.INFO = '#info'
|
8
|
+
List.FORM = 'form'
|
9
|
+
List.OPTS = 'form input[name="opts"]'
|
10
|
+
|
11
|
+
List.prototype.mybind = function() {
|
12
|
+
var o = this
|
13
|
+
$('li > span').click(function() {
|
14
|
+
o.getInfo(this)
|
15
|
+
})
|
16
|
+
$(List.FORM).submit(function() {
|
17
|
+
o.getInfo($('li > span[class="pluginSelected"]'))
|
18
|
+
return false;
|
19
|
+
})
|
20
|
+
$(List.INFO).ajaxStart(function() {
|
21
|
+
o.progressAnimation(true)
|
22
|
+
})
|
23
|
+
$(List.INFO).ajaxStop(function() {
|
24
|
+
$(List.OPTS).focus()
|
25
|
+
})
|
26
|
+
|
27
|
+
$(List.OPTS).focus()
|
28
|
+
}
|
29
|
+
|
30
|
+
// Select current plugin, sent GET request & fill List.INFO.
|
31
|
+
List.prototype.getInfo = function(plugin) {
|
32
|
+
if (!plugin || plugin.length == 0) return
|
33
|
+
|
34
|
+
this.selectCurrent(plugin)
|
35
|
+
var name = $(plugin).text()
|
36
|
+
var url = '/info' + this.atom(name)
|
37
|
+
|
38
|
+
o = this
|
39
|
+
r = $.getJSON(url, function(json) {
|
40
|
+
o.drawPluginInfo(name, json)
|
41
|
+
})
|
42
|
+
.error(function() {
|
43
|
+
$(List.INFO).text('Error: ' + r.responseText)
|
44
|
+
})
|
45
|
+
|
46
|
+
}
|
47
|
+
|
48
|
+
List.prototype.selectCurrent = function(e) {
|
49
|
+
$('li > span').each(function(idx) {
|
50
|
+
if ($(e).text() == $(this).text()) {
|
51
|
+
$(this).addClass('pluginSelected')
|
52
|
+
$(this).removeClass('pluginUnselected')
|
53
|
+
} else {
|
54
|
+
$(this).removeClass('pluginSelected')
|
55
|
+
$(this).addClass('pluginUnselected')
|
56
|
+
}
|
57
|
+
})
|
58
|
+
}
|
59
|
+
|
60
|
+
List.prototype.progressAnimation = function(enable) {
|
61
|
+
if (enable) {
|
62
|
+
t = '<img src="/loading.gif" alt="Loading..." />'
|
63
|
+
$(List.INFO).html(t)
|
64
|
+
} else {
|
65
|
+
$(List.INFO + ' img').remove()
|
66
|
+
}
|
67
|
+
}
|
68
|
+
|
69
|
+
List.prototype.drawPluginInfo = function(plugin, json) {
|
70
|
+
$(List.INFO).html('')
|
71
|
+
|
72
|
+
var opts = this.getOpts()
|
73
|
+
var atom = '/' + plugin + (opts ? '?o='+opts : '')
|
74
|
+
|
75
|
+
var t = '<table border="1" cellpadding="3">'
|
76
|
+
t += '<tr><td>Atom</td><td>' + '<a href="'+atom+'">RSS reader link</a>' + '</td></tr>'
|
77
|
+
t += '<tr><td>Title</td><td>' + json["title"] + '</td></tr>'
|
78
|
+
t += '<tr><td>Version</td><td>' + json["version"] + '</td></tr>'
|
79
|
+
t += '<tr><td>Copyright</td><td>' + json["copyright"] + '</td></tr>'
|
80
|
+
|
81
|
+
// list of URI's
|
82
|
+
t += '<tr><td>URI (' + json['uri'].length + ')</td><td><ul>'
|
83
|
+
for (i in json['uri']) {
|
84
|
+
t += '<li> <a href="' + json['uri'][i] + '">'+ json['uri'][i] + '</a></li>'
|
85
|
+
}
|
86
|
+
t += '</ul></td>'
|
87
|
+
|
88
|
+
t += '</table>'
|
89
|
+
|
90
|
+
$(List.INFO).html(t)
|
91
|
+
}
|
92
|
+
|
93
|
+
List.prototype.getOpts = function() {
|
94
|
+
var opts = $(List.OPTS).val()
|
95
|
+
if (!opts) return ''
|
96
|
+
return opts.replace(/\s+/g, ' ').trim()
|
97
|
+
}
|
98
|
+
|
99
|
+
// Return a proper URL to a atom feed of [plugin]
|
100
|
+
List.prototype.atom = function(plugin) {
|
101
|
+
var opts = this.getOpts()
|
102
|
+
return '/' + plugin + (opts ? '?o='+opts : '')
|
103
|
+
}
|
104
|
+
|
105
|
+
|
106
|
+
// main
|
107
|
+
|
108
|
+
$(function() {
|
109
|
+
var list = new List()
|
110
|
+
list.mybind()
|
111
|
+
})
|
data/public/loading.gif
ADDED
Binary file
|
data/public/style.css
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
#header {
|
2
|
+
/* background: green;*/
|
3
|
+
}
|
4
|
+
|
5
|
+
#list {
|
6
|
+
float: left;
|
7
|
+
width: 42%;
|
8
|
+
padding: 0 0 0 1%;
|
9
|
+
/* background: #b0c4de;*/
|
10
|
+
}
|
11
|
+
|
12
|
+
#plugin {
|
13
|
+
float: right;
|
14
|
+
width: 56%;
|
15
|
+
padding: 0 0 1% 1%;
|
16
|
+
/* background: #ffe81a;*/
|
17
|
+
}
|
18
|
+
|
19
|
+
#footer {
|
20
|
+
clear: both;
|
21
|
+
/* background: orange;*/
|
22
|
+
}
|
23
|
+
|
24
|
+
.footer {
|
25
|
+
float: right;
|
26
|
+
/* color: gray;*/
|
27
|
+
}
|
28
|
+
|
29
|
+
hr {
|
30
|
+
height: 1px;
|
31
|
+
border-width: 0;
|
32
|
+
background-color: black;
|
33
|
+
}
|
34
|
+
|
35
|
+
span:hover {
|
36
|
+
background: black;
|
37
|
+
color: white;
|
38
|
+
cursor: pointer;
|
39
|
+
}
|
40
|
+
|
41
|
+
.pluginSelected {
|
42
|
+
background: white;
|
43
|
+
color: blue;
|
44
|
+
}
|
45
|
+
|
46
|
+
.pluginUnselected {
|
47
|
+
background: white;
|
48
|
+
color: black;
|
49
|
+
}
|
50
|
+
|
51
|
+
table ul {
|
52
|
+
padding: 0 0 0 1em;
|
53
|
+
margin: 0;
|
54
|
+
}
|
data/shotgun.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
require 'erb'
|
3
|
+
require 'etc'
|
4
|
+
require 'fakefs/safe'
|
5
|
+
require 'fileutils'
|
6
|
+
require 'haml'
|
7
|
+
require 'json'
|
8
|
+
require 'logger'
|
9
|
+
require 'msgpack'
|
10
|
+
require 'nokogiri'
|
11
|
+
require 'open-uri'
|
12
|
+
require 'open4'
|
13
|
+
require 'optparse'
|
14
|
+
require 'pathname'
|
15
|
+
require 'pp'
|
16
|
+
require 'rss/maker'
|
17
|
+
require 'shellwords'
|
18
|
+
require 'sinatra/base'
|
19
|
+
require 'stringio'
|
20
|
+
require 'yaml'
|
@@ -0,0 +1 @@
|
|
1
|
+
*.html binary
|
@@ -0,0 +1 @@
|
|
1
|
+
01
|
Binary file
|
File without changes
|
@@ -0,0 +1 @@
|
|
1
|
+
garbage
|
Binary file
|
data/test/helper.rb
CHANGED
@@ -1,33 +1,36 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
include FileUtils
|
1
|
+
# This is supposed to be your helper for all your test. Feel free to
|
2
|
+
# add staff here.
|
4
3
|
|
5
|
-
require_relative '
|
6
|
-
include Bwkfanboy
|
4
|
+
require_relative 'helper_cliutils'
|
7
5
|
|
8
|
-
|
9
|
-
|
10
|
-
require 'minitest/unit'
|
11
|
-
else
|
12
|
-
require 'minitest/autorun'
|
13
|
-
end
|
6
|
+
class MyTestRunner
|
7
|
+
class Unit < MiniTest::Unit
|
14
8
|
|
15
|
-
|
16
|
-
|
17
|
-
case File.basename(Dir.pwd)
|
18
|
-
when Meta::NAME.downcase
|
19
|
-
# test probably is executed from the Rakefile
|
20
|
-
Dir.chdir('test')
|
21
|
-
when 'test'
|
22
|
-
# we are in the test directory, there is nothing special to do
|
23
|
-
else
|
24
|
-
# tests were invoked by 'gem check -t bwkfanboy'
|
25
|
-
begin
|
26
|
-
Dir.chdir(Utils.gem_dir_system + '/../../test')
|
27
|
-
rescue
|
28
|
-
raise "running tests from '#{Dir.pwd}' isn't supported: #{$!}"
|
9
|
+
def before_suites
|
10
|
+
# code to run before the first test
|
29
11
|
end
|
30
|
-
end
|
31
12
|
|
32
|
-
|
13
|
+
def after_suites
|
14
|
+
# code to run after the last test
|
15
|
+
end
|
16
|
+
|
17
|
+
def _run_suites(suites, type)
|
18
|
+
begin
|
19
|
+
before_suites
|
20
|
+
super(suites, type)
|
21
|
+
ensure
|
22
|
+
after_suites
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def _run_suite(suite, type)
|
27
|
+
begin
|
28
|
+
suite.before_suite if suite.respond_to?(:before_suite)
|
29
|
+
super(suite, type)
|
30
|
+
ensure
|
31
|
+
suite.after_suite if suite.respond_to?(:after_suite)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
33
36
|
end
|