feedcellar 0.1.3 → 0.2.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.
- 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
|