feedcellar 0.1.3 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/NEWS.md +22 -0
- data/README.md +4 -0
- data/TODO.md +5 -0
- data/lib/feedcellar.rb +2 -0
- data/lib/feedcellar/command.rb +51 -90
- data/lib/feedcellar/feed.rb +52 -0
- data/lib/feedcellar/groonga_database.rb +21 -13
- data/lib/feedcellar/opml.rb +26 -0
- data/lib/feedcellar/resource.rb +36 -0
- data/lib/feedcellar/version.rb +1 -1
- data/test/test-command.rb +43 -2
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e574a4dc6570abaed41db607382a859f4e99d245
|
4
|
+
data.tar.gz: 57d69eaac737b2f5369bd37cd67b8dc3e8292af2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8a8dcd5f66b52a3036edb153abc251cef13015b77e7fd9ed3fd94b5aa6c257216d4da571bc0ba77a11e7dc3e77585b243c21ab94637dfcdab0663d90f2d2eda3
|
7
|
+
data.tar.gz: 1b9bdea33770e3959a27bedc815c4712d31d0eaefa9b3057d6d652556657bb888b9bdcf6e7c8455e7fe77f1ea0b7de2d883edc347bbd982cf8841afe18c3cd32
|
data/NEWS.md
CHANGED
@@ -1,5 +1,27 @@
|
|
1
1
|
# NEWS
|
2
2
|
|
3
|
+
## 0.2.0: 2013-06-09
|
4
|
+
|
5
|
+
Improve something to do with output release!
|
6
|
+
|
7
|
+
### Changes
|
8
|
+
|
9
|
+
* Improvements
|
10
|
+
* Change default format of search command to one liner
|
11
|
+
* Change registers list format to one liner
|
12
|
+
* Add export command
|
13
|
+
* Add unregister command
|
14
|
+
* Use "short_text" instead of "text"
|
15
|
+
* Support raw object for API access
|
16
|
+
|
17
|
+
* Fixes
|
18
|
+
* Convert line-feed to space in feed title
|
19
|
+
* Extract parse of URL for resource of feed
|
20
|
+
* Extract parse of RSS feed
|
21
|
+
* Fix default database directory
|
22
|
+
* Fix groonga zone
|
23
|
+
* Fix description and date for atom
|
24
|
+
|
3
25
|
## 0.1.3: 2013-06-06
|
4
26
|
|
5
27
|
Experimental functions release!
|
data/README.md
CHANGED
data/TODO.md
CHANGED
data/lib/feedcellar.rb
CHANGED
data/lib/feedcellar/command.rb
CHANGED
@@ -1,14 +1,16 @@
|
|
1
1
|
require "thor"
|
2
|
-
require "rss"
|
3
2
|
require "feedcellar/version"
|
4
3
|
require "feedcellar/groonga_database"
|
5
4
|
require "feedcellar/opml"
|
5
|
+
require "feedcellar/feed"
|
6
|
+
require "feedcellar/resource"
|
6
7
|
|
7
8
|
module Feedcellar
|
8
9
|
class Command < Thor
|
9
10
|
def initialize(*args)
|
10
11
|
super
|
11
|
-
@
|
12
|
+
@base_dir = File.join(File.expand_path("~"), ".feedcellar")
|
13
|
+
@database_dir = File.join(@base_dir, "db")
|
12
14
|
end
|
13
15
|
|
14
16
|
desc "version", "Show version number."
|
@@ -18,134 +20,93 @@ module Feedcellar
|
|
18
20
|
|
19
21
|
desc "register URL", "Register a URL."
|
20
22
|
def register(url)
|
21
|
-
|
22
|
-
|
23
|
-
begin
|
24
|
-
rss = RSS::Parser.parse(url)
|
25
|
-
rescue RSS::InvalidRSSError
|
26
|
-
rss = RSS::Parser.parse(url, false)
|
27
|
-
rescue
|
28
|
-
$stderr.puts "WARNNING: #{$!} (#{url})"
|
29
|
-
return 1
|
30
|
-
end
|
31
|
-
|
32
|
-
unless rss
|
33
|
-
$stderr.puts "ERROR: Invalid URL"
|
34
|
-
return 1
|
35
|
-
end
|
36
|
-
|
37
|
-
resource = {}
|
38
|
-
if rss.is_a?(RSS::Atom::Feed)
|
39
|
-
resource["xmlUrl"] = url
|
40
|
-
resource["title"] = rss.title.content
|
41
|
-
resource["htmlUrl"] = rss.link.href
|
42
|
-
resource["description"] = rss.dc_description
|
43
|
-
else
|
44
|
-
resource["xmlUrl"] = url
|
45
|
-
resource["title"] = rss.channel.title
|
46
|
-
resource["htmlUrl"] = rss.channel.link
|
47
|
-
resource["description"] = rss.channel.description
|
48
|
-
end
|
23
|
+
resource = Resource.parse(url)
|
24
|
+
return 1 unless resource
|
49
25
|
|
26
|
+
GroongaDatabase.new.open(@database_dir) do |database|
|
50
27
|
database.register(resource["title"], resource)
|
51
28
|
end
|
52
29
|
end
|
53
30
|
|
31
|
+
desc "unregister TITLE_OR_URL", "Unregister a resource of feed."
|
32
|
+
def unregister(title_or_url)
|
33
|
+
GroongaDatabase.new.open(@database_dir) do |database|
|
34
|
+
database.unregister(title_or_url)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
54
38
|
desc "import FILE", "Import feed resources by OPML format."
|
55
39
|
def import(opml_xml)
|
56
|
-
@
|
57
|
-
@database.open(@work_dir) do |database|
|
40
|
+
GroongaDatabase.new.open(@database_dir) do |database|
|
58
41
|
Feedcellar::Opml.parse(opml_xml).each do |resource|
|
59
42
|
database.register(resource["title"], resource)
|
60
43
|
end
|
61
44
|
end
|
62
45
|
end
|
63
46
|
|
64
|
-
desc "
|
47
|
+
desc "export", "Export feed resources by OPML format."
|
48
|
+
def export
|
49
|
+
GroongaDatabase.new.open(@database_dir) do |database|
|
50
|
+
puts Opml.build(database.resources.records)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
desc "list", "Show registered resources list of title and URL."
|
65
55
|
def list
|
66
|
-
@
|
67
|
-
@database.open(@work_dir) do |database|
|
56
|
+
GroongaDatabase.new.open(@database_dir) do |database|
|
68
57
|
database.resources.each do |record|
|
69
|
-
puts record.title
|
70
|
-
puts " #{record.xmlUrl}"
|
71
|
-
puts
|
58
|
+
puts "#{record.title} #{record.xmlUrl}"
|
72
59
|
end
|
73
60
|
end
|
74
61
|
end
|
75
62
|
|
76
|
-
desc "collect", "Collect feeds."
|
63
|
+
desc "collect", "Collect feeds from WWW."
|
77
64
|
def collect
|
78
|
-
@
|
79
|
-
|
80
|
-
resources = database.resources
|
81
|
-
|
82
|
-
resources.each do |record|
|
65
|
+
GroongaDatabase.new.open(@database_dir) do |database|
|
66
|
+
database.resources.each do |record|
|
83
67
|
feed_url = record["xmlUrl"]
|
84
68
|
next unless feed_url
|
85
69
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
rescue
|
96
|
-
$stderr.puts "WARNNING: #{$!} (#{feed_url})"
|
97
|
-
next
|
98
|
-
end
|
99
|
-
next unless rss
|
100
|
-
|
101
|
-
rss.items.each do |item|
|
102
|
-
if rss.is_a?(RSS::Atom::Feed)
|
103
|
-
title = item.title.content
|
104
|
-
link = item.link.href if item.link
|
105
|
-
description = item.dc_description
|
106
|
-
date = item.dc_date
|
107
|
-
else
|
108
|
-
title = item.title
|
109
|
-
link = item.link
|
110
|
-
description = item.description
|
111
|
-
date = item.date
|
112
|
-
end
|
113
|
-
|
114
|
-
unless link
|
115
|
-
$stderr.puts "WARNNING: missing link (#{title})"
|
116
|
-
next
|
117
|
-
end
|
118
|
-
|
119
|
-
database.add(feed_url, title, link, description, date)
|
70
|
+
items = Feed.parse(feed_url)
|
71
|
+
next unless items
|
72
|
+
|
73
|
+
items.each do |item|
|
74
|
+
database.add(feed_url,
|
75
|
+
item.title,
|
76
|
+
item.link,
|
77
|
+
item.description,
|
78
|
+
item.date)
|
120
79
|
end
|
121
80
|
end
|
122
81
|
end
|
123
82
|
end
|
124
83
|
|
125
|
-
desc "search WORD", "Search feeds."
|
84
|
+
desc "search WORD", "Search feeds from local database."
|
126
85
|
option :desc, :type => :boolean, :aliases => "-d", :desc => "show description"
|
127
|
-
option :simple, :type => :boolean, :desc => "simple format as one liner"
|
128
86
|
option :browser, :type => :boolean, :desc => "open *ALL* links in browser"
|
129
|
-
def search(word)
|
130
|
-
@
|
131
|
-
|
132
|
-
|
133
|
-
resources = @database.resources
|
87
|
+
def search(word, api=false)
|
88
|
+
GroongaDatabase.new.open(@database_dir) do |database|
|
89
|
+
feeds = database.feeds
|
90
|
+
resources = database.resources
|
134
91
|
|
135
92
|
records = feeds.select do |v|
|
136
93
|
(v.title =~ word) | (v.description =~ word)
|
137
94
|
end
|
138
95
|
|
139
|
-
records.sort([{:key => "date", :order => "ascending"}])
|
96
|
+
sorted_records = records.sort([{:key => "date", :order => "ascending"}])
|
97
|
+
return sorted_records if api
|
98
|
+
|
99
|
+
sorted_records.each do |record|
|
140
100
|
feed_resources = resources.select {|v| v.xmlUrl =~ record.resource }
|
141
101
|
next unless feed_resources
|
142
|
-
|
143
|
-
|
102
|
+
next unless feed_resources.first # FIXME
|
103
|
+
|
104
|
+
unless options[:desc]
|
144
105
|
date = record.date.strftime("%Y/%m/%d")
|
145
|
-
title= record.title
|
106
|
+
title = record.title
|
146
107
|
resource = feed_resources.first.title
|
147
108
|
link = record.link
|
148
|
-
puts "#{date} #{title} - #{resource}"
|
109
|
+
puts "#{date} #{title.gsub(/\n/, " ")} - #{resource}"
|
149
110
|
else
|
150
111
|
puts feed_resources.first.title
|
151
112
|
puts " #{record.title}"
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require "rss"
|
2
|
+
|
3
|
+
module Feedcellar
|
4
|
+
class Feed
|
5
|
+
attr_reader :title, :link, :description, :date
|
6
|
+
def initialize(title, link, description, date)
|
7
|
+
@title = title
|
8
|
+
@link = link
|
9
|
+
@description = description
|
10
|
+
@date = date
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.parse(feed_url)
|
14
|
+
feeds = []
|
15
|
+
|
16
|
+
begin
|
17
|
+
rss = RSS::Parser.parse(feed_url)
|
18
|
+
rescue RSS::InvalidRSSError
|
19
|
+
begin
|
20
|
+
rss = RSS::Parser.parse(feed_url, false)
|
21
|
+
rescue
|
22
|
+
$stderr.puts "WARNNING: #{$!} (#{feed_url})"
|
23
|
+
return nil
|
24
|
+
end
|
25
|
+
rescue
|
26
|
+
$stderr.puts "WARNNING: #{$!} (#{feed_url})"
|
27
|
+
return nil
|
28
|
+
end
|
29
|
+
return nil unless rss
|
30
|
+
|
31
|
+
rss.items.each do |item|
|
32
|
+
if rss.is_a?(RSS::Atom::Feed)
|
33
|
+
title = item.title.content
|
34
|
+
link = item.link.href if item.link
|
35
|
+
description = item.summary.content if item.summary
|
36
|
+
date = item.updated.content if item.updated
|
37
|
+
else
|
38
|
+
title = item.title
|
39
|
+
link = item.link
|
40
|
+
description = item.description
|
41
|
+
date = item.date
|
42
|
+
end
|
43
|
+
|
44
|
+
next unless link
|
45
|
+
|
46
|
+
feeds << new(title, link, description, date)
|
47
|
+
end
|
48
|
+
|
49
|
+
feeds
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -39,6 +39,14 @@ module Feedcellar
|
|
39
39
|
:date => date)
|
40
40
|
end
|
41
41
|
|
42
|
+
def unregister(title_or_url)
|
43
|
+
feeds = Groonga["Resources"]
|
44
|
+
feeds.delete do |record|
|
45
|
+
(record.title == title_or_url) |
|
46
|
+
(record.xmlUrl == title_or_url)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
42
50
|
def close
|
43
51
|
@database.close
|
44
52
|
@database = nil
|
@@ -71,23 +79,23 @@ module Feedcellar
|
|
71
79
|
Groonga::Schema.define do |schema|
|
72
80
|
schema.create_table("Resources", :type => :hash) do |table|
|
73
81
|
table.text("text")
|
74
|
-
table.
|
75
|
-
table.
|
76
|
-
table.
|
77
|
-
table.
|
82
|
+
table.short_text("isComment")
|
83
|
+
table.short_text("isBreakpoint")
|
84
|
+
table.short_text("created")
|
85
|
+
table.short_text("category")
|
78
86
|
table.text("description")
|
79
|
-
table.
|
80
|
-
table.
|
81
|
-
table.
|
82
|
-
table.
|
83
|
-
table.
|
84
|
-
table.
|
87
|
+
table.short_text("url")
|
88
|
+
table.short_text("htmlUrl")
|
89
|
+
table.short_text("xmlUrl")
|
90
|
+
table.short_text("title")
|
91
|
+
table.short_text("version")
|
92
|
+
table.short_text("language")
|
85
93
|
end
|
86
94
|
|
87
95
|
schema.create_table("Feeds", :type => :hash) do |table|
|
88
|
-
table.
|
89
|
-
table.
|
90
|
-
table.
|
96
|
+
table.short_text("resource")
|
97
|
+
table.short_text("title")
|
98
|
+
table.short_text("link")
|
91
99
|
table.text("description")
|
92
100
|
table.time("date")
|
93
101
|
end
|
data/lib/feedcellar/opml.rb
CHANGED
@@ -31,5 +31,31 @@ module Feedcellar
|
|
31
31
|
end
|
32
32
|
outlines
|
33
33
|
end
|
34
|
+
|
35
|
+
def self.build(items)
|
36
|
+
document = REXML::Document.new
|
37
|
+
|
38
|
+
xml_decl = REXML::XMLDecl.new
|
39
|
+
xml_decl.version = "1.0"
|
40
|
+
xml_decl.encoding = "UTF-8"
|
41
|
+
document.add(xml_decl)
|
42
|
+
|
43
|
+
root = document.add_element("opml")
|
44
|
+
root.add_attributes("version" => "1.0")
|
45
|
+
|
46
|
+
head = root.add_element("head")
|
47
|
+
title = head.add_element("title")
|
48
|
+
title.add_text("registers in feedcellar")
|
49
|
+
|
50
|
+
body = root.add_element("body")
|
51
|
+
items.each do |item|
|
52
|
+
outline = body.add_element("outline")
|
53
|
+
item.attributes.each do |key, value|
|
54
|
+
outline.add_attributes(key => value)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
document.to_s
|
59
|
+
end
|
34
60
|
end
|
35
61
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require "rss"
|
2
|
+
|
3
|
+
module Feedcellar
|
4
|
+
class Resource
|
5
|
+
def self.parse(url)
|
6
|
+
begin
|
7
|
+
rss = RSS::Parser.parse(url)
|
8
|
+
rescue RSS::InvalidRSSError
|
9
|
+
rss = RSS::Parser.parse(url, false)
|
10
|
+
rescue
|
11
|
+
$stderr.puts "WARNNING: #{$!} (#{url})"
|
12
|
+
return nil
|
13
|
+
end
|
14
|
+
|
15
|
+
unless rss
|
16
|
+
$stderr.puts "ERROR: Invalid URL"
|
17
|
+
return nil
|
18
|
+
end
|
19
|
+
|
20
|
+
resource = {}
|
21
|
+
if rss.is_a?(RSS::Atom::Feed)
|
22
|
+
resource["xmlUrl"] = url
|
23
|
+
resource["title"] = rss.title.content
|
24
|
+
resource["htmlUrl"] = rss.link.href
|
25
|
+
resource["description"] = rss.dc_description
|
26
|
+
else
|
27
|
+
resource["xmlUrl"] = url
|
28
|
+
resource["title"] = rss.channel.title
|
29
|
+
resource["htmlUrl"] = rss.channel.link
|
30
|
+
resource["description"] = rss.channel.description
|
31
|
+
end
|
32
|
+
|
33
|
+
resource
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/feedcellar/version.rb
CHANGED
data/test/test-command.rb
CHANGED
@@ -9,10 +9,11 @@ class CommandTest < Test::Unit::TestCase
|
|
9
9
|
@tmpdir = File.join(File.dirname(__FILE__), "tmp", "database")
|
10
10
|
FileUtils.mkdir_p(@tmpdir)
|
11
11
|
@command = Feedcellar::Command.new
|
12
|
-
@command.instance_variable_set(:@
|
12
|
+
@command.instance_variable_set(:@database_dir, @tmpdir)
|
13
13
|
end
|
14
14
|
|
15
15
|
def test_command
|
16
|
+
# confirm version command
|
16
17
|
s = ""
|
17
18
|
io = StringIO.new(s)
|
18
19
|
$stdout = io
|
@@ -20,6 +21,7 @@ class CommandTest < Test::Unit::TestCase
|
|
20
21
|
assert_equal("#{Feedcellar::VERSION}\n", s)
|
21
22
|
$stdout = STDOUT
|
22
23
|
|
24
|
+
# confirm register command if invalid URL
|
23
25
|
s = ""
|
24
26
|
io = StringIO.new(s)
|
25
27
|
$stderr = io
|
@@ -27,12 +29,14 @@ class CommandTest < Test::Unit::TestCase
|
|
27
29
|
assert_equal("ERROR: Invalid URL\n", s)
|
28
30
|
$stderr = STDERR
|
29
31
|
|
32
|
+
# confirm register command
|
30
33
|
@command.register("http://myokoym.github.io/entries.rss")
|
31
34
|
@command.register("https://rubygems.org/gems/mister_fairy/versions.atom")
|
32
35
|
Feedcellar::GroongaDatabase.new.open(@tmpdir) do |database|
|
33
36
|
assert_equal(2, database.resources.size)
|
34
37
|
end
|
35
38
|
|
39
|
+
# confirm import command
|
36
40
|
file = File.join(File.dirname(__FILE__), "fixtures", "subscriptions.xml")
|
37
41
|
@command.import(file)
|
38
42
|
@command.collect
|
@@ -41,11 +45,48 @@ class CommandTest < Test::Unit::TestCase
|
|
41
45
|
assert_true(database.feeds.count > 0)
|
42
46
|
end
|
43
47
|
|
48
|
+
# confirm export command
|
49
|
+
s = ""
|
50
|
+
io = StringIO.new(s)
|
51
|
+
$stdout = io
|
52
|
+
@command.export
|
53
|
+
assert_equal(1, s.scan(/<opml/).size)
|
54
|
+
assert_equal(4, s.scan(/<outline/).size)
|
55
|
+
$stdout = STDOUT
|
56
|
+
|
57
|
+
# confirm search command
|
44
58
|
s = ""
|
45
59
|
io = StringIO.new(s)
|
46
60
|
$stdout = io
|
47
61
|
@command.search("ruby")
|
48
|
-
assert_true(s.size >
|
62
|
+
assert_true(s.size > 100)
|
63
|
+
|
64
|
+
# confirm search command in case of API
|
65
|
+
s = ""
|
66
|
+
io = StringIO.new(s)
|
67
|
+
ret = @command.search("ruby", true)
|
68
|
+
assert_equal(Groonga::Array, ret.class)
|
69
|
+
assert_equal(0, s.size)
|
70
|
+
assert_not_equal(0, ret.size)
|
71
|
+
$stdout = STDOUT
|
72
|
+
|
73
|
+
# confirm unregister command
|
74
|
+
@command.unregister("my_letter")
|
75
|
+
Feedcellar::GroongaDatabase.new.open(@tmpdir) do |database|
|
76
|
+
assert_equal(3, database.resources.size)
|
77
|
+
end
|
78
|
+
@command.unregister("https://rubygems.org/gems/mister_fairy/versions.atom")
|
79
|
+
Feedcellar::GroongaDatabase.new.open(@tmpdir) do |database|
|
80
|
+
assert_equal(2, database.resources.size)
|
81
|
+
end
|
82
|
+
|
83
|
+
# confirm search command after unregister
|
84
|
+
s = ""
|
85
|
+
io = StringIO.new(s)
|
86
|
+
ret = @command.search("ruby", true)
|
87
|
+
assert_equal(Groonga::Array, ret.class)
|
88
|
+
assert_equal(0, s.size)
|
89
|
+
assert_not_equal(0, ret.size)
|
49
90
|
$stdout = STDOUT
|
50
91
|
end
|
51
92
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: feedcellar
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Masafumi Yokoyama
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-06-
|
11
|
+
date: 2013-06-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rroonga
|
@@ -127,8 +127,10 @@ files:
|
|
127
127
|
- feedcellar.gemspec
|
128
128
|
- lib/feedcellar.rb
|
129
129
|
- lib/feedcellar/command.rb
|
130
|
+
- lib/feedcellar/feed.rb
|
130
131
|
- lib/feedcellar/groonga_database.rb
|
131
132
|
- lib/feedcellar/opml.rb
|
133
|
+
- lib/feedcellar/resource.rb
|
132
134
|
- lib/feedcellar/version.rb
|
133
135
|
- test/fixtures/subscriptions.xml
|
134
136
|
- test/run-test.rb
|