rss-opds 0.0.1 → 0.0.2

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/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