feedcellar 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c686258435c3f623b6acaf22885f36748c242c75
4
+ data.tar.gz: 1e6c1e76be9198e7488fbf7129ec680671c25ae2
5
+ SHA512:
6
+ metadata.gz: 5bb06174c971e2e78b8628c3d40d499fd6850969a16a7349907c932a79e6c7839d7837586a416ed8337b237f0983020b9a3d87f40f508c478f082be6aa5c8d5f
7
+ data.tar.gz: 086f9636511ead4c620bcb19b99e047e1894f4ca94168d50c5d07feff75b2fcd8dadf9b2de0f7bdfd3f0d6a9d5845dd449396a63aeea7755cc8121f8843e87d1
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in feedcellar.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Masafumi Yokoyama
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/NEWS.md ADDED
@@ -0,0 +1,20 @@
1
+ # NEWS
2
+
3
+ ## Feedcellar 0.0.3: 2013-06-02
4
+
5
+ ### Changes
6
+
7
+ * Improvements
8
+ * Support Atom feed.
9
+ * Add search command.
10
+
11
+ ## Feedcellar 0.0.2: 2013-06-02
12
+
13
+ ### Changes
14
+
15
+ * Improvements
16
+ * Support register a URL.
17
+
18
+ ## Feedcellar 0.0.1: 2013-06-01
19
+
20
+ Initial release!
@@ -0,0 +1,55 @@
1
+ # Feedcellar - Storage for Feed Reader
2
+
3
+ A storage for feed reader. Powered by rroonga.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'feedcellar'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install feedcellar
18
+
19
+ ## Usage
20
+
21
+ Show help
22
+
23
+ $ feedcellar
24
+
25
+ Register URL
26
+
27
+ $ feedcellar register http://example.net/rss
28
+
29
+ Import URL from OPML
30
+
31
+ $ feedcellar import registers.xml
32
+
33
+ Show registers
34
+
35
+ $ feedcellar list
36
+
37
+ Collect feeds (It takes several minutes)
38
+
39
+ $ feedcellar collect
40
+
41
+ Word search from titles and descriptions
42
+
43
+ $ feedcellar search ruby
44
+
45
+ Delete database
46
+
47
+ $ rm -r ~/.feedcellar
48
+
49
+ ## Contributing
50
+
51
+ 1. Fork it
52
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
53
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
54
+ 4. Push to the branch (`git push origin my-new-feature`)
55
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/TODO.md ADDED
@@ -0,0 +1,9 @@
1
+ # TODO
2
+
3
+ ## Improvements
4
+
5
+ * Manage TAG outline from OPML format.
6
+
7
+ ## Release
8
+
9
+ * Release to RubyGems.org
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "feedcellar/command"
4
+
5
+ Feedcellar::Command.start
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'feedcellar/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "feedcellar"
8
+ spec.version = Feedcellar::VERSION
9
+ spec.authors = ["Masafumi Yokoyama"]
10
+ spec.email = ["myokoym@gmail.com"]
11
+ spec.description = %q{A storage for feed reader by rroonga.}
12
+ spec.summary = %q{Storage for Feed Reader}
13
+ spec.homepage = "https://github.com/myokoym/feedcellar"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) {|f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency("rroonga")
22
+ spec.add_runtime_dependency("thor")
23
+
24
+ spec.add_development_dependency("test-unit")
25
+ spec.add_development_dependency("test-unit-notify")
26
+ spec.add_development_dependency("test-unit-rr")
27
+ spec.add_development_dependency("bundler", "~> 1.3")
28
+ spec.add_development_dependency("rake")
29
+ end
@@ -0,0 +1,7 @@
1
+ require "feedcellar/version"
2
+ require "feedcellar/groonga_database"
3
+ require "feedcellar/opml"
4
+ require "feedcellar/command"
5
+
6
+ module Feedcellar
7
+ end
@@ -0,0 +1,127 @@
1
+ require "thor"
2
+ require "rss"
3
+ require "feedcellar/groonga_database"
4
+ require "feedcellar/opml"
5
+
6
+ module Feedcellar
7
+ class Command < Thor
8
+ def initialize(*args)
9
+ super
10
+ @work_dir = File.join(File.expand_path("~"), ".feedcellar")
11
+ end
12
+
13
+ desc "register URL", "Register a URL."
14
+ def register(url)
15
+ @database = GroongaDatabase.new
16
+ @database.open(@work_dir) do |database|
17
+ begin
18
+ rss = RSS::Parser.parse(url)
19
+ rescue RSS::InvalidRSSError
20
+ rss = RSS::Parser.parse(url, false)
21
+ rescue
22
+ $stderr.puts "Warnning: #{$!} (#{url})"
23
+ return 1
24
+ end
25
+
26
+ unless rss
27
+ $stderr.puts "Error: Invalid URL"
28
+ return 1
29
+ end
30
+
31
+ resource = {}
32
+ resource["xmlUrl"] = url
33
+ resource["title"] = rss.channel.title
34
+ resource["htmlUrl"] = rss.channel.link
35
+ resource["description"] = rss.channel.description
36
+
37
+ database.register(rss.channel.title, resource)
38
+ end
39
+ end
40
+
41
+ desc "import FILE", "Import feed resources by OPML format."
42
+ def import(opml_xml)
43
+ @database = GroongaDatabase.new
44
+ @database.open(@work_dir) do |database|
45
+ Feedcellar::Opml.parse(opml_xml).each do |resource|
46
+ database.register(resource["title"], resource)
47
+ end
48
+ end
49
+ end
50
+
51
+ desc "list", "Show feed url list."
52
+ def list
53
+ @database = GroongaDatabase.new
54
+ @database.open(@work_dir) do |database|
55
+ database.resources.each do |record|
56
+ puts record.title
57
+ puts " #{record.xmlUrl}"
58
+ puts
59
+ end
60
+ end
61
+ end
62
+
63
+ desc "collect", "Collect feeds."
64
+ def collect
65
+ @database = GroongaDatabase.new
66
+ @database.open(@work_dir) do |database|
67
+ resources = database.resources
68
+
69
+ resources.each do |record|
70
+ feed_url = record["xmlUrl"]
71
+ next unless feed_url
72
+
73
+ begin
74
+ rss = RSS::Parser.parse(feed_url)
75
+ rescue RSS::InvalidRSSError
76
+ rss = RSS::Parser.parse(feed_url, false)
77
+ rescue
78
+ $stderr.puts "Warnning: #{$!} (#{feed_url})"
79
+ next
80
+ end
81
+ next unless rss
82
+
83
+ rss.items.each do |item|
84
+ if rss.is_a?(RSS::Atom::Feed)
85
+ title = item.title.content
86
+ link = item.link.href if item.link
87
+ description = item.dc_description
88
+ date = item.dc_date
89
+ else
90
+ title = item.title
91
+ link = item.link
92
+ description = item.description
93
+ date = item.date
94
+ end
95
+
96
+ unless link
97
+ $stderr.puts "Warnning: missing link (#{title})"
98
+ next
99
+ end
100
+
101
+ database.add(feed_url, title, link, description, date)
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ desc "search WORD", "Search feeds."
108
+ def search(word)
109
+ @database = GroongaDatabase.new
110
+ @database.open(@work_dir) do |database|
111
+ feeds = @database.feeds
112
+ resources = @database.resources
113
+
114
+ feeds.select {|v| (v.title =~ word) |
115
+ (v.description =~ word) }.each do |record|
116
+ feed_resources = resources.select {|v| v.xmlUrl =~ record.resource }
117
+ next unless feed_resources
118
+ puts feed_resources.first.title
119
+ puts " #{record.title}"
120
+ puts " #{record.date}"
121
+ puts " #{record.link}"
122
+ puts
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,97 @@
1
+ require "groonga"
2
+
3
+ module Feedcellar
4
+ class GroongaDatabase
5
+ def initialize
6
+ @database = nil
7
+ end
8
+
9
+ def open(base_path, encoding=:utf8)
10
+ reset_context(encoding)
11
+ path = File.join(base_path, "feedcellar.db")
12
+ if File.exist?(path)
13
+ @database = Groonga::Database.open(path)
14
+ populate_schema
15
+ else
16
+ FileUtils.mkdir_p(base_path)
17
+ populate(path)
18
+ end
19
+ if block_given?
20
+ begin
21
+ yield(self)
22
+ ensure
23
+ close unless closed?
24
+ end
25
+ end
26
+ end
27
+
28
+ def register(key, attributes)
29
+ feeds = Groonga["Resources"]
30
+ feeds.add(key, attributes)
31
+ end
32
+
33
+ def add(resource, title, link, description, date)
34
+ feeds = Groonga["Feeds"]
35
+ feeds.add(link, :resource => resource,
36
+ :title => title,
37
+ :link => link,
38
+ :description => description,
39
+ :date => date)
40
+ end
41
+
42
+ def close
43
+ @database.close
44
+ @database = nil
45
+ end
46
+
47
+ def closed?
48
+ @database.nil? or @database.closed?
49
+ end
50
+
51
+ def resources
52
+ @resources ||= Groonga["Resources"]
53
+ end
54
+
55
+ def feeds
56
+ @feeds ||= Groonga["Feeds"]
57
+ end
58
+
59
+ private
60
+ def reset_context(encoding)
61
+ Groonga::Context.default_options = {:encoding => encoding}
62
+ Groonga::Context.default = nil
63
+ end
64
+
65
+ def populate(path)
66
+ @database = Groonga::Database.create(:path => path)
67
+ populate_schema
68
+ end
69
+
70
+ def populate_schema
71
+ Groonga::Schema.define do |schema|
72
+ schema.create_table("Resources", :type => :hash) do |table|
73
+ table.text("text")
74
+ table.text("isComment")
75
+ table.text("isBreakpoint")
76
+ table.text("created")
77
+ table.text("category")
78
+ table.text("description")
79
+ table.text("url")
80
+ table.text("htmlUrl")
81
+ table.text("xmlUrl")
82
+ table.text("title")
83
+ table.text("version")
84
+ table.text("language")
85
+ end
86
+
87
+ schema.create_table("Feeds", :type => :hash) do |table|
88
+ table.text("resource")
89
+ table.text("title")
90
+ table.text("link")
91
+ table.text("description")
92
+ table.time("date")
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,35 @@
1
+ require "rexml/document"
2
+
3
+ module Feedcellar
4
+ class Opml
5
+ OUTLINE_ATTRIBUTES = [
6
+ "text",
7
+ "isComment",
8
+ "isBreakpoint",
9
+ "created",
10
+ "category",
11
+ "description",
12
+ "url",
13
+ "htmlUrl",
14
+ "xmlUrl",
15
+ "title",
16
+ "version",
17
+ "language",
18
+ ]
19
+
20
+ def self.parse(file)
21
+ # FIXME improve valiable names
22
+ # FIXME outline for tags
23
+ outlines = []
24
+ doc = REXML::Document.new(File.open(file))
25
+ REXML::XPath.each(doc, "//outline") do |outline|
26
+ attributes = {}
27
+ OUTLINE_ATTRIBUTES.each do |attribute|
28
+ attributes[attribute] = outline.attributes[attribute]
29
+ end
30
+ outlines << attributes
31
+ end
32
+ outlines
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,3 @@
1
+ module Feedcellar
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,17 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <opml version="1.0">
3
+ <head>
4
+ <title>M の リーダー登録フィード</title>
5
+ </head>
6
+ <body>
7
+ <outline text="my_letter" title="my_letter" type="rss"
8
+ xmlUrl="http://myokoym.github.com/entries.rss" htmlUrl="http://myokoym.github.com/"/>
9
+ <outline text="Rubygems | Latest Versions for shogi-ruby"
10
+ title="Rubygems | Latest Versions for shogi-ruby" type="rss"
11
+ xmlUrl="https://rubygems.org/gems/shogi-ruby/versions.atom" htmlUrl="https://rubygems.org/gems"/>
12
+ <outline title="shogi" text="shogi">
13
+ <outline text="函館将棋情報のブログ" title="函館将棋情報のブログ" type="rss"
14
+ xmlUrl="http://blogs.yahoo.co.jp/mi807258/rss.xml" htmlUrl="http://rd.yahoo.co.jp/rss/l/blog/myblog/rss2/url/*http://blogs.yahoo.co.jp/mi807258"/>
15
+ </outline>
16
+ </body>
17
+ </opml>
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "test-unit"
4
+ require "test/unit/notify"
5
+ require "test/unit/rr"
6
+
7
+ base_dir = File.expand_path(File.join(File.dirname(__FILE__), ".."))
8
+ $LOAD_PATH.unshift(File.join(base_dir, "lib"))
9
+ $LOAD_PATH.unshift(File.join(base_dir, "test"))
10
+
11
+ exit Test::Unit::AutoRunner.run(true)
@@ -0,0 +1,46 @@
1
+ require "fileutils"
2
+ require "stringio"
3
+ require "feedcellar/command"
4
+ require "feedcellar/groonga_database"
5
+
6
+ class CommandTest < Test::Unit::TestCase
7
+ def setup
8
+ @tmpdir = File.join(File.dirname(__FILE__), "tmp", "database")
9
+ FileUtils.mkdir_p(@tmpdir)
10
+ @command = Feedcellar::Command.new
11
+ @command.instance_variable_set(:@work_dir, @tmpdir)
12
+ end
13
+
14
+ def test_command
15
+ s = ""
16
+ io = StringIO.new(s)
17
+ $stderr = io
18
+ assert_equal(1, @command.register("hoge"))
19
+ assert_equal("Error: Invalid URL\n", s)
20
+ $stderr = STDERR
21
+
22
+ @command.register("http://myokoym.github.io/entries.rss")
23
+ Feedcellar::GroongaDatabase.new.open(@tmpdir) do |database|
24
+ assert_equal(1, database.resources.size)
25
+ end
26
+
27
+ file = File.join(File.dirname(__FILE__), "fixtures", "subscriptions.xml")
28
+ @command.import(file)
29
+ @command.collect
30
+ Feedcellar::GroongaDatabase.new.open(@tmpdir) do |database|
31
+ assert_equal(4, database.resources.size)
32
+ assert_true(database.feeds.count > 0)
33
+ end
34
+
35
+ s = ""
36
+ io = StringIO.new(s)
37
+ $stdout = io
38
+ @command.search("ruby")
39
+ assert_true(s.size > 500)
40
+ $stdout = STDOUT
41
+ end
42
+
43
+ def teardown
44
+ FileUtils.rm_rf(@tmpdir)
45
+ end
46
+ end
@@ -0,0 +1,12 @@
1
+ require "feedcellar/opml"
2
+
3
+ class OpmlTest < Test::Unit::TestCase
4
+ def setup
5
+ end
6
+
7
+ def test_parse
8
+ file = File.join(File.dirname(__FILE__), "fixtures", "subscriptions.xml")
9
+ outlines = Feedcellar::Opml.parse(file)
10
+ assert_equal(4, outlines.size) # FIXME (feed * 3) + (tag * 1)
11
+ end
12
+ end
metadata ADDED
@@ -0,0 +1,166 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: feedcellar
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Masafumi Yokoyama
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-06-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rroonga
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: thor
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: test-unit
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: test-unit-notify
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: test-unit-rr
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: bundler
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ~>
88
+ - !ruby/object:Gem::Version
89
+ version: '1.3'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ~>
95
+ - !ruby/object:Gem::Version
96
+ version: '1.3'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rake
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: A storage for feed reader by rroonga.
112
+ email:
113
+ - myokoym@gmail.com
114
+ executables:
115
+ - feedcellar
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - .gitignore
120
+ - Gemfile
121
+ - LICENSE.txt
122
+ - NEWS.md
123
+ - README.md
124
+ - Rakefile
125
+ - TODO.md
126
+ - bin/feedcellar
127
+ - feedcellar.gemspec
128
+ - lib/feedcellar.rb
129
+ - lib/feedcellar/command.rb
130
+ - lib/feedcellar/groonga_database.rb
131
+ - lib/feedcellar/opml.rb
132
+ - lib/feedcellar/version.rb
133
+ - test/fixtures/subscriptions.xml
134
+ - test/run-test.rb
135
+ - test/test-command.rb
136
+ - test/test-opml.rb
137
+ homepage: https://github.com/myokoym/feedcellar
138
+ licenses:
139
+ - MIT
140
+ metadata: {}
141
+ post_install_message:
142
+ rdoc_options: []
143
+ require_paths:
144
+ - lib
145
+ required_ruby_version: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - '>='
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ required_rubygems_version: !ruby/object:Gem::Requirement
151
+ requirements:
152
+ - - '>='
153
+ - !ruby/object:Gem::Version
154
+ version: '0'
155
+ requirements: []
156
+ rubyforge_project:
157
+ rubygems_version: 2.0.2
158
+ signing_key:
159
+ specification_version: 4
160
+ summary: Storage for Feed Reader
161
+ test_files:
162
+ - test/fixtures/subscriptions.xml
163
+ - test/run-test.rb
164
+ - test/test-command.rb
165
+ - test/test-opml.rb
166
+ has_rdoc: