rss-opds 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -1,4 +1,2 @@
1
1
  source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in rss-opds.gemspec
4
2
  gemspec
@@ -2,7 +2,7 @@ RSS::OPDS
2
2
  =========
3
3
 
4
4
  [OPDS][opds] parser and maker.
5
- OPDS(Open Publication Distribution System) is feed
5
+ OPDS(Open Publication Distribution System) is feed which syndicates information about ebooks.
6
6
 
7
7
  [opds]:http://opds-spec.org/specs/opds-catalog-1-1
8
8
 
@@ -26,15 +26,74 @@ Or install it yourself as:
26
26
  Usage
27
27
  -----
28
28
 
29
+ ### Parsing OPDS
30
+
29
31
  require 'open-uri'
30
32
  require 'rss/opds'
31
33
 
32
34
  opds = RSS::Parser.parse open(uri)
33
35
  opds.entries.each do |entry|
34
- price_elem = entry.links.select {|link| link.opds_price}.first
35
- puts price_elem
36
+ price_elem = entry.links.select {|link| link.opds_price}.first
37
+ puts price_elem
36
38
  end
37
39
 
40
+ If you need performance, install [rss-nokogiri][rss-nokogiri] gem and write `require 'rss/nokogiri'`
41
+ before calling `RSS::Parser.parse`.
42
+
43
+ [rss-nokogiri]: https://rubygems.org/gems/rss-nokogiri
44
+
45
+ ### Making OPDS
46
+
47
+ You may make OPDS feeds as well as normal Atom feeds.
48
+
49
+ require 'rss/opds'
50
+
51
+ recent = RSS::Maker.make('atom') do |maker|
52
+ maker.channel.about = 'http://example.net/'
53
+ maker.channel.title = 'Example New Catalog'
54
+ maker.channel.description = 'New books in this site'
55
+ maker.channel.links.new_link do |link|
56
+ link.href = 'http://example.net/new.opds'
57
+ link.rel = 'self'
58
+ link.type = RSS::OPDS::TYPES['acquisition']
59
+ end
60
+ maker.channel.links.new_link do |link|
61
+ link.href = 'http://example.net/root.opds'
62
+ link.rel = RSS::OPDS::RELATIONS['start']
63
+ link.type = RSS::OPDS::TYPES['navigation']
64
+ end
65
+ maker.channel.updated = '2012-08-14T04:23:00'
66
+ maker.channel.author = 'KITAITI Makoto'
67
+
68
+ new_books.each do |book|
69
+ maker.items.new_item do |entry|
70
+ entry.title = book.title
71
+ entry.updated = book.updated
72
+ entry.summary = book.summary
73
+ entry.link = book.link
74
+ end
75
+ end
76
+ end
77
+ puts recent # => output OPDS feed
78
+
79
+ And now, this library provides utility methods which help you make OPDS navigation feeds.
80
+
81
+ root = RSS::Maker.make('atom') {|maker|
82
+ maker.channel.about = ...
83
+
84
+ maker.items.add_relation recent, RSS::OPDS::RELATIONS['new']
85
+ # or just:
86
+ # maker.items.add_new recent
87
+ #
88
+ # these are equivalent to:
89
+ # maker.items.new_item do |item|
90
+ # item.id = recent.id
91
+ # item.title = recent.title
92
+ # # ...
93
+ # end
94
+ }
95
+ puts root # => output XML including entry with 'new' sorting relation
96
+
38
97
  Contributing
39
98
  ------------
40
99
 
@@ -47,10 +106,10 @@ Contributing
47
106
  Development Plan
48
107
  ----------------
49
108
 
50
- 1. Parser for OPDS catalogs
109
+ 1. Parser for OPDS catalogs[DONE]
51
110
  2. Validation as OPDS
52
111
  3. Utility methods like, for example, `#buy`(gets `link` element with `rel="acquisition/buy"`), `#price`(`opds:price` element) and so on
53
- 4. Maker for OPDS feeds and entries
112
+ 4. Maker for OPDS feeds and entries[DONE]
54
113
 
55
114
  References
56
115
  ----------
data/Rakefile CHANGED
@@ -1,2 +1,10 @@
1
1
  #!/usr/bin/env rake
2
2
  require "bundler/gem_tasks"
3
+ require 'rake/clean'
4
+ require 'rake/testtask'
5
+ require 'yard'
6
+
7
+ task :default => :test
8
+
9
+ Rake::TestTask.new
10
+ YARD::Rake::YardocTask.new
@@ -0,0 +1,138 @@
1
+ require 'rss'
2
+ require 'rss/opds'
3
+ require 'rss/maker/opds'
4
+
5
+ Book = Struct.new 'Book',
6
+ :title, :author, :summary, :updated, :link, :popularity
7
+
8
+ def main
9
+ puts root
10
+ puts popular
11
+ puts recent
12
+ end
13
+
14
+ def root
15
+ RSS::Maker.make('atom') {|maker|
16
+ maker.channel.about = 'http://example.net/'
17
+ maker.channel.title = 'Example Catalog Root'
18
+ maker.channel.description = 'Sample OPDS'
19
+ maker.channel.links.new_link do |link|
20
+ link.href = 'http://example.net/root.opds'
21
+ link.rel = RSS::OPDS::RELATIONS['self']
22
+ link.type = RSS::OPDS::TYPES['navigation']
23
+ end
24
+ maker.channel.links.new_link do |link|
25
+ link.href = 'http://example.net/root.opds'
26
+ link.rel = RSS::OPDS::RELATIONS['start']
27
+ link.type = RSS::OPDS::TYPES['navigation']
28
+ end
29
+ maker.channel.updated = '2012-08-14T04:23:00'
30
+ maker.channel.author = 'KITAITI Makoto'
31
+
32
+ maker.items.new_item do |entry|
33
+ entry.links.new_link do |link|
34
+ link.href = 'http://example.net/popular.opds'
35
+ link.rel = RSS::OPDS::RELATIONS['popular']
36
+ link.type = RSS::OPDS::TYPES['acquisition']
37
+ end
38
+ entry.title = 'Example Popular Books'
39
+ entry.updated = '2012-07-31T00:00:00'
40
+ entry.summary = 'Popular books in this site'
41
+ end
42
+ maker.items.new_item do |entry|
43
+ entry.links.new_link do |link|
44
+ link.href = 'http://example.net/new.opds'
45
+ link.rel = RSS::OPDS::RELATIONS['new']
46
+ link.type = RSS::OPDS::TYPES['acquisition']
47
+ end
48
+ entry.title = 'Example New Books'
49
+ entry.updated = '2012-08-14T04:23:00'
50
+ entry.summary = 'New books in this site'
51
+ end
52
+ }
53
+ end
54
+
55
+ def popular
56
+ RSS::Maker.make('atom') do |maker|
57
+ maker.channel.about = 'http://example.net/'
58
+ maker.channel.title = 'Example Popular Catalog'
59
+ maker.channel.description = 'Popular books in this site'
60
+ maker.channel.links.new_link do |link|
61
+ link.href = 'http://example.net/popular.opds'
62
+ link.rel = RSS::OPDS::RELATIONS['self']
63
+ link.type = RSS::OPDS::TYPES['acquisition']
64
+ end
65
+ maker.channel.links.new_link do |link|
66
+ link.href = 'http://example.net/root.opds'
67
+ link.rel = RSS::OPDS::RELATIONS['start']
68
+ link.type = RSS::OPDS::TYPES['navigation']
69
+ end
70
+ maker.channel.updated = '2012-07-31T00:00:00'
71
+ maker.channel.author = 'KITAITI Makoto'
72
+
73
+ popular_books.each do |book|
74
+ maker.items.new_item do |entry|
75
+ entry.title = book.title
76
+ entry.updated = book.updated
77
+ entry.summary = book.summary
78
+ entry.link = book.link
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ def recent
85
+ RSS::Maker.make('atom') do |maker|
86
+ maker.channel.about = 'http://example.net/'
87
+ maker.channel.title = 'Example New Catalog'
88
+ maker.channel.description = 'New books in this site'
89
+ maker.channel.links.new_link do |link|
90
+ link.href = 'http://example.net/new.opds'
91
+ link.rel = 'self'
92
+ link.type = RSS::OPDS::TYPES['acquisition']
93
+ end
94
+ maker.channel.links.new_link do |link|
95
+ link.href = 'http://example.net/root.opds'
96
+ link.rel = RSS::OPDS::RELATIONS['start']
97
+ link.type = RSS::OPDS::TYPES['navigation']
98
+ end
99
+ maker.channel.updated = '2012-08-14T04:23:00'
100
+ maker.channel.author = 'KITAITI Makoto'
101
+
102
+ new_books.each do |book|
103
+ maker.items.new_item do |entry|
104
+ entry.title = book.title
105
+ entry.updated = book.updated
106
+ entry.summary = book.summary
107
+
108
+ entry.links.new_link do |link|
109
+ link.href = book.link
110
+ link.opds_prices.new_opds_price do |price|
111
+ price.value = 100
112
+ price.currencycode = 'JPY'
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
119
+
120
+ def books
121
+ [
122
+ Book.new('book1', 'author1', 'summary1', Time.gm(2012, 8, 14, 3), 'http://example.net/book/book1', 1),
123
+ Book.new('book2', 'author2', 'summary2', Time.gm(2012, 8, 10, 14), 'http://example.net/book/book2', 2),
124
+ Book.new('book3', 'author3', 'summary3', Time.gm(2012, 6, 30, 12), 'http://example.net/book/book3', 3)
125
+ ]
126
+ end
127
+
128
+ def popular_books
129
+ books.sort_by {|book| book.popularity}.reverse_each
130
+ end
131
+
132
+ def new_books
133
+ books.sort_by {|book| book.updated}.reverse_each
134
+ end
135
+
136
+ if $0 == __FILE__
137
+ main
138
+ end
@@ -0,0 +1,83 @@
1
+ require 'rss/maker'
2
+ require 'rss/opds'
3
+
4
+ module RSS
5
+ module Maker
6
+ module Atom
7
+ class Feed < RSSBase
8
+ class Items < ItemsBase
9
+ class Item < ItemBase
10
+ class Links < LinksBase
11
+ class Link < LinkBase
12
+ %w[facetGroup activeFacet].each do |attr|
13
+ def_other_element attr
14
+ end
15
+ def_classed_elements 'opds_price', 'value', 'Prices'
16
+
17
+ # @note Defined to prevent NoMethodError
18
+ def setup_opds_prices(feed, current)
19
+ end
20
+
21
+ # @note Should provide this method as the one of a module
22
+ def to_feed(feed, current)
23
+ super # AtomLink#to_feed
24
+ opds_prices.to_feed(feed, current.links.last)
25
+ end
26
+
27
+ class Prices < Base
28
+ def_array_element 'opds_price', nil, 'Price'
29
+
30
+ class Price < Base
31
+ %w[value currencycode].each do |attr|
32
+ attr_accessor attr
33
+ end
34
+
35
+ def to_feed(feed, current)
36
+ price = ::RSS::OPDS::Price.new
37
+ price.value = value
38
+ price.currencycode = currencycode
39
+ current.opds_prices << price
40
+ set_parent price, current
41
+ setup_other_elements(feed)
42
+ end
43
+
44
+ private
45
+
46
+ def required_variable_names
47
+ %w[currencycode]
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ def add_relation(feed, relation, href=nil)
56
+ self_link = maker.channel.links.find {|link| link.rel == RSS::OPDS::RELATIONS['self']}
57
+ is_navigation_feed = self_link && self_link.type == RSS::OPDS::TYPES['navigation']
58
+ raise TypeError, 'Only navigatfion feed can accept feed' unless is_navigation_feed
59
+ raise ArgumentError, 'Only acquisition feed can be accepted' unless feed.acquisition_feed?
60
+ new_item do |entry|
61
+ [:id, :title, :dc_description, :updated].each do |attr|
62
+ val = feed.__send__(attr)
63
+ entry.__send__("#{attr}=", val.content) if val
64
+ end
65
+ entry.links.new_link do |link|
66
+ href = feed.links.find {|ln| ln.rel == RSS::OPDS::RELATIONS['self']}.href unless href
67
+ link.href = href
68
+ link.rel = relation
69
+ link.type = RSS::OPDS::TYPES['acquisition']
70
+ end
71
+ end
72
+ end
73
+
74
+ ::RSS::OPDS::CATALOG_RELATIONS.each_pair do |type, uri|
75
+ define_method "add_#{type}" do |feed, href=nil|
76
+ add_relation feed, uri, href
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -5,13 +5,13 @@ require 'rss/opds/version'
5
5
 
6
6
  module RSS
7
7
  module Utils
8
- module TrueOther
8
+ module TrueNil
9
9
  module_function
10
10
  def parse(value)
11
11
  if value === true
12
12
  value
13
13
  else
14
- /\Atrue\z/i.match(value.to_s) ? true : false
14
+ /\Atrue\z/i.match(value.to_s) ? true : nil
15
15
  end
16
16
  end
17
17
  end
@@ -19,22 +19,23 @@ module RSS
19
19
 
20
20
  module BaseModel
21
21
  private
22
- def true_other_attr_reader(*attrs)
22
+
23
+ def true_nil_attr_reader(*attrs)
23
24
  attrs.each do |attr|
24
25
  attr = attr.id2name if attr.kind_of?(Integer)
25
26
  module_eval(<<-EOC, __FILE__, __LINE__ + 1)
26
27
  attr_reader(:#{attr})
27
28
  def #{attr}?
28
- TrueOther.parse(@#{attr})
29
+ TrueNil.parse(@#{attr})
29
30
  end
30
31
  EOC
31
32
  end
32
33
  end
33
34
 
34
- def true_other_writer(name, disp_name=name)
35
+ def true_nil_writer(name, disp_name=name)
35
36
  module_eval(<<-EOC, __FILE__, __LINE__ + 1)
36
37
  def #{name}=(new_value)
37
- new_value = 'true' if new_value === true
38
+ new_value = [true, 'true'].include?(new_value) ? 'true' : nil
38
39
  @#{name} = new_value
39
40
  end
40
41
  EOC
@@ -46,8 +47,9 @@ module RSS
46
47
  alias rss_def_corresponded_attr_writer def_corresponded_attr_writer
47
48
  def def_corresponded_attr_writer(name, type=nil, disp_name=nil)
48
49
  disp_name ||= name
49
- if type == :true_other
50
- true_other_writer name, disp_name
50
+ case type
51
+ when :true_nil
52
+ true_nil_writer name, disp_name
51
53
  else
52
54
  rss_def_corresponded_attr_writer name, type, disp_name
53
55
  end
@@ -55,8 +57,9 @@ module RSS
55
57
 
56
58
  alias rss_def_corresponded_attr_reader def_corresponded_attr_reader
57
59
  def def_corresponded_attr_reader(name, type=nil)
58
- if type == :true_other
59
- true_other_attr_reader name, type
60
+ case type
61
+ when :true_nil
62
+ true_nil_attr_reader name, type
60
63
  else
61
64
  rss_def_corresponded_attr_reader name, type
62
65
  end
@@ -71,14 +74,102 @@ module RSS
71
74
  'navigation' => 'application/atom+xml;profile=opds-catalog;kind=navigation',
72
75
  'acquisition' => 'application/atom+xml;profile=opds-catalog;kind=acquisition'
73
76
  }
77
+ REGISTERED_RELATIONS = %w[
78
+ alternate
79
+ appendix
80
+ archives
81
+ author
82
+ bookmark
83
+ canonical
84
+ chapter
85
+ collection
86
+ contents
87
+ copyright
88
+ current
89
+ describedby
90
+ disclosure
91
+ duplicate
92
+ edit
93
+ edit-media
94
+ enclosure
95
+ first
96
+ glossary
97
+ help
98
+ hosts
99
+ hub
100
+ icon
101
+ index
102
+ item
103
+ last
104
+ latest-version
105
+ license
106
+ lrdd
107
+ monitor
108
+ monitor-group
109
+ next
110
+ next-archive
111
+ nofollow
112
+ noreferrer
113
+ payment
114
+ predecessor-version
115
+ prefetch
116
+ prev
117
+ previous
118
+ prev-archive
119
+ related
120
+ replies
121
+ search
122
+ section
123
+ self
124
+ service
125
+ start
126
+ stylesheet
127
+ subsection
128
+ successor-version
129
+ tag
130
+ up
131
+ version-history
132
+ via
133
+ working-copy
134
+ working-copy-of
135
+ ].reduce({}) {|relations, relation|
136
+ relations[relation] = relation
137
+ relations
138
+ }
139
+ CATALOG_RELATIONS = {
140
+ 'start' => 'start',
141
+ 'subsection' => 'subsection',
142
+ 'new' => 'http://opds-spec.org/sort/new',
143
+ 'popular' => 'http://opds-spec.org/sort/popular',
144
+ 'featured' => 'http://opds-spec.org/featured',
145
+ 'recommended' => 'http://opds-spec.org/recommended',
146
+ 'shelf' => 'http://opds-spec.org/shelf',
147
+ 'subscriptions' => 'http://opds-spec.org/subscriptions',
148
+ 'facet' => 'http://opds-spec.org/facet',
149
+ 'crawlable' => 'http://opds-spec.org/crawlable'
150
+ }
151
+ ENTRY_RELATIONS = {
152
+ 'acquisition' => 'http://opds-spec.org/acquisition',
153
+ 'open-access' => 'http://opds-spec.org/acquisition/open-access',
154
+ 'borrow' => 'http://opds-spec.org/acquisition/borrow',
155
+ 'buy' => 'http://opds-spec.org/acquisition/buy',
156
+ 'sample' => 'http://opds-spec.org/acquisition/sample',
157
+ 'subscribe' => 'http://opds-spec.org/acquisition/subscribe',
158
+ 'image' => 'http://opds-spec.org/image',
159
+ 'thumbnail' => 'http://opds-spec.org/image/thumbnail'
160
+ }
161
+ RELATIONS = Hash.new {|h, k|
162
+ ENTRY_RELATIONS[k] or CATALOG_RELATIONS[k] or REGISTERED_RELATIONS[k] or
163
+ raise KeyError, "Unsupported relation type: #{k.inspect}"
164
+ }
74
165
 
75
166
  class Price < Element
76
167
  include Atom::CommonModel
77
- include Atom::ContentModel
78
168
 
79
169
  install_ns(PREFIX, URI)
80
170
  install_must_call_validator('opds', URI)
81
171
  install_get_attribute('currencycode', '')
172
+ attr_accessor :value
82
173
 
83
174
  class << self
84
175
  def required_prefix
@@ -99,9 +190,6 @@ module RSS
99
190
  def full_name
100
191
  tag_name_with_prefix(PREFIX)
101
192
  end
102
-
103
- alias value content
104
- alias value= content=
105
193
  end
106
194
 
107
195
  BaseListener.install_class_name(URI, 'price', 'Price')
@@ -121,22 +209,24 @@ module RSS
121
209
  end
122
210
  end
123
211
 
124
- class Link
212
+ class Link < Element
125
213
  [
126
214
  ['facetGroup', nil],
127
- ['activeFacet', [:true_other, :true_other]]
215
+ ['activeFacet', [:true_nil, :true_nil]]
128
216
  ].each do |name, type|
129
217
  disp_name = "#{OPDS::PREFIX}:#{name}"
130
218
  install_get_attribute(name, OPDS::URI, false, type, nil, disp_name)
131
219
  alias_method to_attr_name(name), name
132
220
  alias_method "#{to_attr_name(name)}=", "#{name}="
133
221
  end
222
+
223
+ Price = OPDS::Price
224
+ install_have_children_element 'opds_price', OPDS::URI, '*'
134
225
  end
135
226
 
136
227
  class Entry
137
- class Link
138
- Price = OPDS::Price
139
- install_have_children_element 'price', OPDS::URI, '*', 'opds_price'
228
+ def price
229
+ links.find {|link| link.opds_price}.opds_price
140
230
  end
141
231
  end
142
232
  end
@@ -1,5 +1,5 @@
1
1
  module RSS
2
2
  module OPDS
3
- VERSION = "0.0.1"
3
+ VERSION = "0.0.2"
4
4
  end
5
5
  end
@@ -6,7 +6,8 @@ Gem::Specification.new do |gem|
6
6
  gem.email = ["KitaitiMakoto@gmail.com"]
7
7
  gem.description = %q{OPDS parser and maker}
8
8
  gem.summary = %q{This gem extends Ruby bundled RSS library to parse and make OPDS catalogs}
9
- gem.homepage = ""
9
+ gem.homepage = "http://rss-ext.rubyforge.org/"
10
+ gem.rubyforge_project = "http://rss-ext.rubyforge.org/"
10
11
 
11
12
  gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
12
13
  gem.files = `git ls-files`.split("\n")
@@ -15,11 +16,14 @@ Gem::Specification.new do |gem|
15
16
  gem.require_paths = ["lib"]
16
17
  gem.version = RSS::OPDS::VERSION
17
18
 
19
+ gem.add_runtime_dependency 'rss-dcterms'
20
+ gem.add_runtime_dependency 'rss-atom-feed_history'
21
+ gem.add_runtime_dependency 'money'
22
+
18
23
  gem.add_development_dependency 'test-unit'
19
24
  gem.add_development_dependency 'simplecov'
20
25
  gem.add_development_dependency 'yard'
26
+ gem.add_development_dependency 'redcarpet'
21
27
  gem.add_development_dependency 'pry'
22
-
23
- gem.add_runtime_dependency 'rss-dcterms'
24
- gem.add_runtime_dependency 'rss-atom-feed_history'
28
+ gem.add_development_dependency 'pry-doc'
25
29
  end
@@ -0,0 +1,19 @@
1
+ require 'simplecov'
2
+ SimpleCov.start do
3
+ add_filter '/test|vendor/'
4
+ end
5
+
6
+ require 'rss/opds'
7
+ require 'test/unit'
8
+ require 'pathname'
9
+ require 'rexml/document'
10
+
11
+ class TestOPDS < Test::Unit::TestCase
12
+ def setup
13
+ @fixtures_dir = Pathname(__FILE__).dirname.expand_path + 'fixtures'
14
+ end
15
+ end
16
+
17
+ Book = Struct.new 'Book',
18
+ :title, :author, :summary, :updated, :link, :popularity
19
+
@@ -0,0 +1,227 @@
1
+ require_relative 'helper'
2
+ require 'rss/maker/opds'
3
+
4
+ class TestMaker < TestOPDS
5
+ def test_set_price
6
+ feed = RSS::Maker.make('atom') {|maker|
7
+ %w[id author title].each do |property|
8
+ maker.channel.send "#{property}=", property
9
+ end
10
+ maker.channel.updated = Time.now
11
+
12
+ maker.items.new_item do |entry|
13
+ entry.id = 'entry id'
14
+ entry.title = 'entry title'
15
+ entry.author = 'entry author'
16
+ entry.updated = Time.now
17
+
18
+ entry.links.new_link do |link|
19
+ link.href = 'uri/to/purchase'
20
+ link.opds_prices.new_opds_price do |price|
21
+ price.value = 100
22
+ price.currencycode = 'JPY'
23
+ end
24
+ end
25
+ end
26
+ }
27
+ doc = REXML::Document.new(feed.to_s)
28
+ assert_not_empty REXML::XPath.match(doc, "//*[namespace-uri()='http://opds-spec.org/2010/catalog']")
29
+ end
30
+
31
+ def test_set_active_facet
32
+ feed = RSS::Maker.make('atom') {|maker|
33
+ %w[id author title].each do |property|
34
+ maker.channel.send "#{property}=", property
35
+ end
36
+ maker.channel.updated = Time.now
37
+
38
+ maker.items.new_item do |entry|
39
+ entry.id = 'entry id'
40
+ entry.title = 'entry title'
41
+ entry.author = 'entry author'
42
+ entry.updated = Time.now
43
+
44
+ entry.links.new_link do |link|
45
+ link.href = 'uri/to/facet'
46
+ link.activeFacet = true
47
+ end
48
+ end
49
+ }
50
+ doc = REXML::Document.new(feed.to_s)
51
+ links = REXML::XPath.match(doc, "//[@opds:activeFacet]")
52
+ assert_not_empty links
53
+ assert_equal 'true', links.first.attributes['opds:activeFacet']
54
+ end
55
+
56
+ def test_set_inactive_facet
57
+ feed = RSS::Maker.make('atom') {|maker|
58
+ %w[id author title].each do |property|
59
+ maker.channel.send "#{property}=", property
60
+ end
61
+ maker.channel.updated = Time.now
62
+
63
+ maker.items.new_item do |entry|
64
+ entry.id = 'entry id'
65
+ entry.title = 'entry title'
66
+ entry.author = 'entry author'
67
+ entry.updated = Time.now
68
+
69
+ entry.links.new_link do |link|
70
+ link.href = 'uri/to/facet'
71
+ link.activeFacet = false
72
+ end
73
+ end
74
+ }
75
+ doc = REXML::Document.new(feed.to_s)
76
+ assert_empty REXML::XPath.match(doc, "//[@opds:activeFacet]")
77
+ end
78
+
79
+ def test_navigation_feed_accepts_aqcuisition_feed_as_item
80
+ catalog_root = nil
81
+ assert_nothing_raised do
82
+ catalog_root = RSS::Maker.make('atom') {|maker|
83
+ maker.channel.about = 'http://example.net/'
84
+ maker.channel.title = 'Example Catalog Root'
85
+ maker.channel.description = 'Sample OPDS'
86
+ maker.channel.links.new_link do |link|
87
+ link.href = 'http://example.net/root.opds'
88
+ link.rel = RSS::OPDS::RELATIONS['self']
89
+ link.type = RSS::OPDS::TYPES['navigation']
90
+ end
91
+ maker.channel.links.new_link do |link|
92
+ link.href = 'http://example.net/root.opds'
93
+ link.rel = RSS::OPDS::RELATIONS['start']
94
+ link.type = RSS::OPDS::TYPES['navigation']
95
+ end
96
+ maker.channel.updated = '2012-08-14T05:30:00'
97
+ maker.channel.author = 'KITAITI Makoto'
98
+
99
+ maker.items.add_relation recent, RSS::OPDS::RELATIONS['new']
100
+ maker.items.add_popular popular
101
+ }
102
+ end
103
+ doc = REXML::Document.new(catalog_root.to_s)
104
+ links = REXML::XPath.match(doc, "/feed/entry/link")
105
+ assert_equal 'http://opds-spec.org/sort/new', links.first.attributes['rel']
106
+ assert_equal 'http://opds-spec.org/sort/popular', links[1].attributes['rel']
107
+ end
108
+
109
+ def test_utility_to_add_entry_relations
110
+ pend
111
+ end
112
+
113
+ def root
114
+ RSS::Maker.make('atom') {|maker|
115
+ maker.channel.about = 'http://example.net/'
116
+ maker.channel.title = 'Example Catalog Root'
117
+ maker.channel.description = 'Sample OPDS'
118
+ maker.channel.links.new_link do |link|
119
+ link.href = 'http://example.net/root.opds'
120
+ link.rel = RSS::OPDS::RELATIONS['self']
121
+ link.type = RSS::OPDS::TYPES['navigation']
122
+ end
123
+ maker.channel.links.new_link do |link|
124
+ link.href = 'http://example.net/root.opds'
125
+ link.rel = RSS::OPDS::RELATIONS['start']
126
+ link.type = RSS::OPDS::TYPES['navigation']
127
+ end
128
+ maker.channel.updated = '2012-08-14T04:23:00'
129
+ maker.channel.author = 'KITAITI Makoto'
130
+
131
+ maker.items.new_item do |entry|
132
+ entry.links.new_link do |link|
133
+ link.href = 'http://example.net/popular.opds'
134
+ link.rel = RSS::OPDS::RELATIONS['popular']
135
+ link.type = RSS::OPDS::TYPES['acquisition']
136
+ end
137
+ entry.title = 'Example Popular Books'
138
+ entry.updated = '2012-07-31T00:00:00'
139
+ entry.summary = 'Popular books in this site'
140
+ end
141
+ maker.items.new_item do |entry|
142
+ entry.links.new_link do |link|
143
+ link.href = 'http://example.net/new.opds'
144
+ link.rel = RSS::OPDS::RELATIONS['new']
145
+ link.type = RSS::OPDS::TYPES['acquisition']
146
+ end
147
+ entry.title = 'Example New Books'
148
+ entry.updated = '2012-08-14T04:23:00'
149
+ entry.summary = 'New books in this site'
150
+ end
151
+ }
152
+ end
153
+
154
+ def popular
155
+ RSS::Maker.make('atom') do |maker|
156
+ maker.channel.about = 'http://example.net/'
157
+ maker.channel.title = 'Example Popular Catalog'
158
+ maker.channel.description = 'Popular books in this site'
159
+ maker.channel.links.new_link do |link|
160
+ link.href = 'http://example.net/popular.opds'
161
+ link.rel = RSS::OPDS::RELATIONS['self']
162
+ link.type = RSS::OPDS::TYPES['acquisition']
163
+ end
164
+ maker.channel.links.new_link do |link|
165
+ link.href = 'http://example.net/root.opds'
166
+ link.rel = RSS::OPDS::RELATIONS['start']
167
+ link.type = RSS::OPDS::TYPES['navigation']
168
+ end
169
+ maker.channel.updated = '2012-07-31T00:00:00'
170
+ maker.channel.author = 'KITAITI Makoto'
171
+
172
+ popular_books.each do |book|
173
+ maker.items.new_item do |entry|
174
+ entry.title = book.title
175
+ entry.updated = book.updated
176
+ entry.summary = book.summary
177
+ entry.link = book.link
178
+ end
179
+ end
180
+ end
181
+ end
182
+
183
+ def recent
184
+ RSS::Maker.make('atom') do |maker|
185
+ maker.channel.about = 'http://example.net/'
186
+ maker.channel.title = 'Example New Catalog'
187
+ maker.channel.description = 'New books in this site'
188
+ maker.channel.links.new_link do |link|
189
+ link.href = 'http://example.net/new.opds'
190
+ link.rel = 'self'
191
+ link.type = RSS::OPDS::TYPES['acquisition']
192
+ end
193
+ maker.channel.links.new_link do |link|
194
+ link.href = 'http://example.net/root.opds'
195
+ link.rel = RSS::OPDS::RELATIONS['start']
196
+ link.type = RSS::OPDS::TYPES['navigation']
197
+ end
198
+ maker.channel.updated = '2012-08-14T04:23:00'
199
+ maker.channel.author = 'KITAITI Makoto'
200
+
201
+ new_books.each do |book|
202
+ maker.items.new_item do |entry|
203
+ entry.title = book.title
204
+ entry.updated = book.updated
205
+ entry.summary = book.summary
206
+ entry.link = book.link
207
+ end
208
+ end
209
+ end
210
+ end
211
+
212
+ def books
213
+ [
214
+ Book.new('book1', 'author1', 'summary1', Time.gm(2012, 8, 14, 3), 'http://example.net/book/book1', 1),
215
+ Book.new('book2', 'author2', 'summary2', Time.gm(2012, 8, 10, 14), 'http://example.net/book/book2', 2),
216
+ Book.new('book3', 'author3', 'summary3', Time.gm(2012, 6, 30, 12), 'http://example.net/book/book3', 3)
217
+ ]
218
+ end
219
+
220
+ def popular_books
221
+ books.sort_by {|book| book.popularity}.reverse_each
222
+ end
223
+
224
+ def new_books
225
+ books.sort_by {|book| book.updated}.reverse_each
226
+ end
227
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rss-opds
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,44 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-04-06 00:00:00.000000000Z
12
+ date: 2012-10-20 00:00:00.000000000 Z
13
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rss-dcterms
16
+ requirement: &11264540 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *11264540
25
+ - !ruby/object:Gem::Dependency
26
+ name: rss-atom-feed_history
27
+ requirement: &11264060 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *11264060
36
+ - !ruby/object:Gem::Dependency
37
+ name: money
38
+ requirement: &11263600 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *11263600
14
47
  - !ruby/object:Gem::Dependency
15
48
  name: test-unit
16
- requirement: &6367980 !ruby/object:Gem::Requirement
49
+ requirement: &11263060 !ruby/object:Gem::Requirement
17
50
  none: false
18
51
  requirements:
19
52
  - - ! '>='
@@ -21,10 +54,10 @@ dependencies:
21
54
  version: '0'
22
55
  type: :development
23
56
  prerelease: false
24
- version_requirements: *6367980
57
+ version_requirements: *11263060
25
58
  - !ruby/object:Gem::Dependency
26
59
  name: simplecov
27
- requirement: &6383140 !ruby/object:Gem::Requirement
60
+ requirement: &11262540 !ruby/object:Gem::Requirement
28
61
  none: false
29
62
  requirements:
30
63
  - - ! '>='
@@ -32,10 +65,10 @@ dependencies:
32
65
  version: '0'
33
66
  type: :development
34
67
  prerelease: false
35
- version_requirements: *6383140
68
+ version_requirements: *11262540
36
69
  - !ruby/object:Gem::Dependency
37
70
  name: yard
38
- requirement: &6382160 !ruby/object:Gem::Requirement
71
+ requirement: &11262100 !ruby/object:Gem::Requirement
39
72
  none: false
40
73
  requirements:
41
74
  - - ! '>='
@@ -43,10 +76,10 @@ dependencies:
43
76
  version: '0'
44
77
  type: :development
45
78
  prerelease: false
46
- version_requirements: *6382160
79
+ version_requirements: *11262100
47
80
  - !ruby/object:Gem::Dependency
48
- name: pry
49
- requirement: &6381280 !ruby/object:Gem::Requirement
81
+ name: redcarpet
82
+ requirement: &11261600 !ruby/object:Gem::Requirement
50
83
  none: false
51
84
  requirements:
52
85
  - - ! '>='
@@ -54,29 +87,29 @@ dependencies:
54
87
  version: '0'
55
88
  type: :development
56
89
  prerelease: false
57
- version_requirements: *6381280
90
+ version_requirements: *11261600
58
91
  - !ruby/object:Gem::Dependency
59
- name: rss-dcterms
60
- requirement: &6380580 !ruby/object:Gem::Requirement
92
+ name: pry
93
+ requirement: &11261140 !ruby/object:Gem::Requirement
61
94
  none: false
62
95
  requirements:
63
96
  - - ! '>='
64
97
  - !ruby/object:Gem::Version
65
98
  version: '0'
66
- type: :runtime
99
+ type: :development
67
100
  prerelease: false
68
- version_requirements: *6380580
101
+ version_requirements: *11261140
69
102
  - !ruby/object:Gem::Dependency
70
- name: rss-atom-feed_history
71
- requirement: &6379920 !ruby/object:Gem::Requirement
103
+ name: pry-doc
104
+ requirement: &11260700 !ruby/object:Gem::Requirement
72
105
  none: false
73
106
  requirements:
74
107
  - - ! '>='
75
108
  - !ruby/object:Gem::Version
76
109
  version: '0'
77
- type: :runtime
110
+ type: :development
78
111
  prerelease: false
79
- version_requirements: *6379920
112
+ version_requirements: *11260700
80
113
  description: OPDS parser and maker
81
114
  email:
82
115
  - KitaitiMakoto@gmail.com
@@ -89,11 +122,15 @@ files:
89
122
  - LICENSE
90
123
  - README.markdown
91
124
  - Rakefile
125
+ - examples/make-catalog.rb
126
+ - lib/rss/maker/opds.rb
92
127
  - lib/rss/opds.rb
93
128
  - lib/rss/opds/version.rb
94
129
  - rss-opds.gemspec
95
130
  - setup.rb
96
- homepage: ''
131
+ - test/helper.rb
132
+ - test/test_maker.rb
133
+ homepage: http://rss-ext.rubyforge.org/
97
134
  licenses: []
98
135
  post_install_message:
99
136
  rdoc_options: []
@@ -107,7 +144,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
107
144
  version: '0'
108
145
  segments:
109
146
  - 0
110
- hash: -2402742248161802058
147
+ hash: -1275098366759959895
111
148
  required_rubygems_version: !ruby/object:Gem::Requirement
112
149
  none: false
113
150
  requirements:
@@ -116,9 +153,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
116
153
  version: '0'
117
154
  segments:
118
155
  - 0
119
- hash: -2402742248161802058
156
+ hash: -1275098366759959895
120
157
  requirements: []
121
- rubyforge_project:
158
+ rubyforge_project: http://rss-ext.rubyforge.org/
122
159
  rubygems_version: 1.8.8
123
160
  signing_key:
124
161
  specification_version: 3