bwkfanboy 1.4.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|