harvester 0.8.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/CHANGELOG.rdoc +45 -0
  2. data/README.rdoc +74 -0
  3. data/Rakefile +28 -0
  4. data/bin/harvester +13 -0
  5. data/bin/harvester-chart +5 -0
  6. data/bin/harvester-clock +35 -0
  7. data/bin/harvester-db +15 -0
  8. data/bin/harvester-fetch +5 -0
  9. data/bin/harvester-generate +5 -0
  10. data/bin/harvester-jabber +6 -0
  11. data/bin/harvester-new +25 -0
  12. data/bin/harvester-post +5 -0
  13. data/bin/harvester-run +14 -0
  14. data/collections.yaml +15 -0
  15. data/config.yaml +13 -0
  16. data/data/ent/HTMLlat1.ent +194 -0
  17. data/data/ent/HTMLspecial.ent +77 -0
  18. data/data/ent/HTMLsymbol.ent +241 -0
  19. data/data/sql/dbd-mysql-isotime.diff +11 -0
  20. data/data/sql/harvester-0.6-mysql.diff +59 -0
  21. data/data/sql/harvester-0.7-mysql.diff +39 -0
  22. data/data/sql/mysql/chart.sql +1 -0
  23. data/data/sql/mysql/create.table.enclosures.sql +9 -0
  24. data/data/sql/mysql/create.table.items.sql +8 -0
  25. data/data/sql/mysql/create.table.jabbersettings.sql +5 -0
  26. data/data/sql/mysql/create.table.jabbersubscriptions.sql +5 -0
  27. data/data/sql/mysql/create.table.sources.sql +9 -0
  28. data/data/sql/mysql/create.view.last48hours.sql +1 -0
  29. data/data/sql/postgresql/chart.sql +1 -0
  30. data/data/sql/postgresql/create.table.enclosures.sql +9 -0
  31. data/data/sql/postgresql/create.table.items.sql +8 -0
  32. data/data/sql/postgresql/create.table.jabbersettings.sql +5 -0
  33. data/data/sql/postgresql/create.table.jabbersubscriptions.sql +5 -0
  34. data/data/sql/postgresql/create.table.sources.sql +9 -0
  35. data/data/sql/postgresql/create.view.last48hours.sql +1 -0
  36. data/data/sql/sqlite3/chart.sql +1 -0
  37. data/data/sql/sqlite3/create.table.enclosures.sql +9 -0
  38. data/data/sql/sqlite3/create.table.items.sql +8 -0
  39. data/data/sql/sqlite3/create.table.jabbersettings.sql +5 -0
  40. data/data/sql/sqlite3/create.table.jabbersubscriptions.sql +5 -0
  41. data/data/sql/sqlite3/create.table.sources.sql +9 -0
  42. data/data/sql/sqlite3/create.view.last48hours.sql +1 -0
  43. data/data/templates/atom-all.xml +88 -0
  44. data/data/templates/atom.xml +88 -0
  45. data/data/templates/index.html +412 -0
  46. data/data/templates/rss-all.rdf +86 -0
  47. data/data/templates/rss.rdf +85 -0
  48. data/data/templates/static/harvester.css +365 -0
  49. data/data/templates/static/harvester.gif +0 -0
  50. data/data/templates/static/harvester_ie7.css +15 -0
  51. data/data/templates/static/harvester_lte_ie6.css +27 -0
  52. data/harvester.gemspec +35 -0
  53. data/lib/harvester.rb +132 -0
  54. data/lib/harvester/chart.rb +72 -0
  55. data/lib/harvester/db.rb +123 -0
  56. data/lib/harvester/fetch.rb +96 -0
  57. data/lib/harvester/generate.rb +152 -0
  58. data/lib/harvester/generator/entity_translator.rb +46 -0
  59. data/lib/harvester/generator/link_absolutizer.rb +39 -0
  60. data/lib/harvester/jabber.rb +443 -0
  61. data/lib/harvester/mrss.rb +355 -0
  62. data/lib/harvester/post.rb +19 -0
  63. metadata +237 -0
@@ -0,0 +1,355 @@
1
+ # encoding: utf-8
2
+ # Magic RSS
3
+ # Helps getting around with RSS while ignoring standards and tolerating much
4
+
5
+ require 'rexml/document'
6
+ require 'cgi'
7
+ require 'iconv'
8
+
9
+ # Search macros
10
+ class REXML::Element
11
+ def s(expr)
12
+ each_element(expr) { |e| return e }
13
+ nil
14
+ end
15
+
16
+ def s_text(expr)
17
+ if (e = s(expr))
18
+ (e.cdatas.size > 0) ? e.cdatas.to_s : e.text
19
+ else
20
+ nil
21
+ end
22
+ end
23
+
24
+ def s_attr(expr, attr)
25
+ (e = s(expr)) ? e.attributes[attr] : nil
26
+ end
27
+
28
+ def rss_content(expr) # TODO: understand & refactor
29
+ if e = s(expr)
30
+ CGI::unescapeHTML(e.children.join)
31
+ end
32
+ end
33
+
34
+ def atom_content(expr)
35
+ e = s(expr)
36
+ if e
37
+ type = e.attributes['type']
38
+ mode = e.attributes['mode']
39
+
40
+ if type == 'xhtml' or mode == 'xml'
41
+ e.children.join
42
+ elsif type == 'html' or mode == 'escaped'
43
+ if e.cdatas.size > 0
44
+ e.cdatas.to_s
45
+ else
46
+ e.text
47
+ end
48
+ else # 'text'
49
+ if e.cdatas.size > 0
50
+ CGI::escapeHTML(e.cdatas.to_s)
51
+ else
52
+ CGI::escapeHTML(e.text.to_s)
53
+ end
54
+ end
55
+ else
56
+ nil
57
+ end
58
+ end
59
+
60
+ def atom_content_text(expr)
61
+ c = atom_content(expr)
62
+ if c
63
+ c.gsub!(/<.+?>/, '')
64
+ CGI::unescapeHTML(c)
65
+ else
66
+ nil
67
+ end
68
+ end
69
+
70
+ def atom_link
71
+ link, rank = 0, 0
72
+
73
+ each_element('link') do |e|
74
+ rel = e.attributes['rel']
75
+ type = e.attributes['type']
76
+
77
+ e_rank = (rel == 'alternate' ? 2 : 1) *
78
+ (rel == 'enclosure' ? 0 : 1) *
79
+ ((type == 'text/html' ? 2 : 0) +
80
+ (type == 'application/xhtml+xml' ? 1 : 0) +
81
+ 1)
82
+
83
+ if e_rank > rank
84
+ link = e.attributes['href']
85
+ rank = e_rank
86
+ end
87
+ end
88
+
89
+ link
90
+ end
91
+ end
92
+
93
+
94
+ class MRSS
95
+ class AbstractMethod < RuntimeError; end
96
+
97
+ def initialize(root)
98
+ @root = root
99
+ end
100
+
101
+ # Common properties
102
+ def items
103
+ res = []
104
+
105
+ @root.each_element(items_expr) do |i|
106
+ item = self.class::Item.new(i)
107
+
108
+ res << item unless item.title.to_s == '' and item.link.to_s == ''
109
+ end
110
+
111
+ res
112
+ end
113
+
114
+ # Abstract stuff
115
+ def title
116
+ raise AbstractMethod.new
117
+ end
118
+
119
+ def type
120
+ raise AbstractMethod.new
121
+ end
122
+
123
+ def link
124
+ raise AbstractMethod.new
125
+ end
126
+
127
+ def description
128
+ raise AbstractMethod.new
129
+ end
130
+
131
+ def feed_expr
132
+ raise AbstractMethod.new
133
+ end
134
+
135
+ def items_expr
136
+ raise AbstractMethod.new
137
+ end
138
+
139
+ # Item
140
+
141
+ class Item
142
+ def initialize(e)
143
+ @e = e
144
+ end
145
+ def title
146
+ raise AbstractMethod.new
147
+ end
148
+ def link
149
+ raise AbstractMethod.new
150
+ end
151
+ def description
152
+ raise AbstractMethod.new
153
+ end
154
+ def date
155
+ raise AbstractMethod.new
156
+ end
157
+ def enclosures
158
+ raise AbstractMethod.new
159
+ end
160
+ end
161
+
162
+ # Flavours
163
+
164
+ # RSS 0.9 & 2.0
165
+ class RSS < MRSS
166
+ def title
167
+ @root.s_text "#{feed_expr}/title"
168
+ end
169
+ def feed_expr
170
+ "/rss/channel"
171
+ end
172
+ def items_expr
173
+ "/rss/channel/item"
174
+ end
175
+ def link
176
+ @root.s_text "#{feed_expr}/link"
177
+ end
178
+ def description
179
+ @root.s_text "#{feed_expr}/description"
180
+ end
181
+
182
+ class Item < ::MRSS::Item
183
+ def title
184
+ @e.s_text "title"
185
+ end
186
+ def link
187
+ @e.s_text "link"
188
+ end
189
+ def description
190
+ @e.rss_content("content:encoded") ||
191
+ @e.rss_content("encoded") ||
192
+ @e.rss_content("description")
193
+ end
194
+ def date
195
+ d = @e.s_text('date') || @e.s_text('pubDate') || @e.s_text('dc:date')
196
+ MRSS::Util.detect_time(d).localtime
197
+ end
198
+ def enclosures
199
+ r = []
200
+ @e.each_element('enclosure') do |e|
201
+ h = {
202
+ 'href' => e.attributes['url'],
203
+ 'type' => e.attributes['type'],
204
+ 'title' => e.attributes['title'],
205
+ 'length' => e.attributes['length']
206
+ }
207
+ r << h
208
+ end
209
+ r
210
+ end
211
+ end
212
+ end
213
+
214
+ # RSS 1.0
215
+ class RDF < RSS
216
+ def feed_expr
217
+ "/*/channel"
218
+ end
219
+ def items_expr
220
+ "/*/item"
221
+ end
222
+ end
223
+
224
+ # ATOM 0.3 & 1.0
225
+ class ATOM < MRSS
226
+ def feed_expr
227
+ "/feed"
228
+ end
229
+ def items_expr
230
+ "/feed/entry"
231
+ end
232
+ def title
233
+ @root.atom_content_text "title"
234
+ end
235
+ def link
236
+ @root.atom_link
237
+ end
238
+ def description
239
+ @root.atom_content_text('tagline') ||
240
+ @root.atom_content_text('subtitle')
241
+ end
242
+
243
+ class Item < ::MRSS::Item
244
+ def title
245
+ @e.atom_content_text "title"
246
+ end
247
+ def link
248
+ @e.atom_link
249
+ end
250
+ def description
251
+ @e.atom_content("content") ||
252
+ @e.atom_content("summary")
253
+ end
254
+ def date
255
+ d = @e.s_text('published') || @e.s_text('issued') || @e.s_text('updated')
256
+ MRSS::Util.detect_time(d).localtime
257
+ end
258
+ def enclosures
259
+ r = []
260
+ @e.each_element("link") do |e|
261
+ if e.attributes['rel'] == 'enclosure'
262
+ h = {
263
+ 'href' => e.attributes['href'],
264
+ 'type' => e.attributes['type'],
265
+ 'title' => e.attributes['title'],
266
+ 'length' => e.attributes['length']
267
+ }
268
+ r << h
269
+ end
270
+ end
271
+ r
272
+ end
273
+ end
274
+ end
275
+
276
+
277
+ def self.parse(body)
278
+ begin
279
+ xml = REXML::Document.new(body).root
280
+ rescue REXML::ParseException
281
+ xml = REXML::Document.new(Iconv::iconv('UTF-8', 'ISO-8859-15', body).to_s).root
282
+ end
283
+
284
+ begin
285
+ # Try to validate UTF-8
286
+ Iconv::iconv('UTF-8', 'UTF-8', xml.to_s).to_s
287
+ rescue Iconv::IllegalSequence, Iconv::InvalidCharacter
288
+ xml = REXML::Document.new(Iconv::iconv('UTF-8', 'ISO-8859-15', body).to_s).root
289
+ end
290
+
291
+ k = case xml.name
292
+ when 'rss'
293
+ RSS
294
+ when 'rdf'
295
+ RDF
296
+ when 'RDF'
297
+ RDF
298
+ when 'feed'
299
+ ATOM
300
+ else
301
+ raise "Unknwon feed format: #{xml.name} (#{xml.namespace})"
302
+ end
303
+ k.new(xml)
304
+ end
305
+
306
+ module Util
307
+ def self.detect_time(s)
308
+ tz_offset = 0
309
+
310
+ # Fix it up
311
+ s.strip!
312
+
313
+ s.scan(/([\+\-])(\d\d):?(\d\d)$/).each do |plus_minus,hours,minutes|
314
+ tz_offset = ((hours.to_i * 60) + minutes.to_i) * 60
315
+ tz_offset *= (plus_minus == '+') ? -1 : 1
316
+ end
317
+
318
+ # 2004-04-01T21:23+00:00
319
+ # 2004-09-05T21:23Z
320
+ s.scan(/^(\d+)-(\d+)-(\d+)T(\d+):(\d+)/).each do |y,mo,d,h,m|
321
+ return Time.gm(y.to_i, mo.to_i, d.to_i, h.to_i, m.to_i) + tz_offset
322
+ end
323
+
324
+ # 2004-04-01T21:23:23+00:00
325
+ s.scan(/^(\d+)-(\d+)-(\d+)T(\d+):(\d+):(\d+)\+(\d+):(\d+)$/).each do |y,mo,d,h,m,s,tz_h,tz_m|
326
+ return Time.gm(y.to_i, mo.to_i, d.to_i, h.to_i, m.to_i, s.to_i) + tz_offset
327
+ end
328
+
329
+ # Wed, 20 Apr 2005 19:38:15 +0200
330
+ # Fri, 22 Apr 2005 10:31:12 GMT
331
+ months = {"Jan" => 1, "Feb" => 2, "Mar" => 3, "Mär" => 3, "Apr" => 4, "May" => 5, "Mai" => 5, "Jun" => 6, "Jul" => 7, "Aug" => 8, "Sep" => 9, "Oct" => 10, "Okt" => 10, "Nov" => 11, "Dec" => 12, "Dez" => 12 }
332
+ s.scan(/^(.+?), +(\d+) (.+?) (\d+) (\d+):(\d+):(\d+) /).each do |wday,d,mo,y,h,m,s|
333
+ return Time.gm(y.to_i, months[mo], d.to_i, h.to_i, m.to_i, s.to_i) + tz_offset
334
+ end
335
+
336
+ # 06 May 2007 02:20:00
337
+ s.scan(/^(\d+) (.+?) (\d{4}) (\d\d):(\d\d):(\d\d)/).each do |d,mo,y,h,m,s|
338
+ return Time.gm(y.to_i, months[mo], d.to_i, h.to_i, m.to_i, s.to_i) + tz_offset
339
+ end
340
+
341
+ # Mon Apr 25 00:00:00 +0000 2011
342
+ s.scan(/^(.+?) (.+?) (\d+) (\d+):(\d+):(\d+) .* (\d{4})/).each do |wday,mo,d,h,m,s,y|
343
+ return Time.gm(y.to_i, months[mo], d.to_i, h.to_i, m.to_i, s.to_i) + tz_offset
344
+ end
345
+
346
+ # 2011-05-27
347
+ s.scan(/^(\d{4})-(\d\d)-(\d\d)/).each do |y,mo,d|
348
+ return Time.gm(y.to_i, months[mo], d.to_i) + tz_offset
349
+ end
350
+
351
+ # ugly <now> fallback
352
+ Time.new
353
+ end
354
+ end
355
+ end
@@ -0,0 +1,19 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative '../harvester'
4
+
5
+ class Harvester
6
+ module POST; end
7
+
8
+ # runs the configured post processing scripts
9
+ def post!(path = nil)
10
+ task "post process" do
11
+ if post_script = path || @config['settings']['post_script']
12
+ error "Cannot find an executable script at #{ post_script }" unless test('x', post_script)
13
+ exec post_script, @config['settings']['output']
14
+ else
15
+ warn 'No post processing script configured or passed as argument!'
16
+ end
17
+ end#task
18
+ end
19
+ end
metadata ADDED
@@ -0,0 +1,237 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: harvester
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: 6
5
+ version: 0.8.0.pre.1
6
+ platform: ruby
7
+ authors:
8
+ - astro
9
+ - Neingeist
10
+ - Tigion
11
+ - Josef Spillner
12
+ - Jan Lelis
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-05-27 00:00:00 Z
18
+ dependencies:
19
+ - !ruby/object:Gem::Dependency
20
+ name: rdbi
21
+ prerelease: false
22
+ requirement: &id001 !ruby/object:Gem::Requirement
23
+ none: false
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: "0"
28
+ type: :runtime
29
+ version_requirements: *id001
30
+ - !ruby/object:Gem::Dependency
31
+ name: rdbi-driver-sqlite3
32
+ prerelease: false
33
+ requirement: &id002 !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: "0"
39
+ type: :runtime
40
+ version_requirements: *id002
41
+ - !ruby/object:Gem::Dependency
42
+ name: logger-colors
43
+ prerelease: false
44
+ requirement: &id003 !ruby/object:Gem::Requirement
45
+ none: false
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: "0"
50
+ type: :runtime
51
+ version_requirements: *id003
52
+ - !ruby/object:Gem::Dependency
53
+ name: eventmachine
54
+ prerelease: false
55
+ requirement: &id004 !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: "0"
61
+ type: :runtime
62
+ version_requirements: *id004
63
+ - !ruby/object:Gem::Dependency
64
+ name: em-http-request
65
+ prerelease: false
66
+ requirement: &id005 !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: "0"
72
+ type: :runtime
73
+ version_requirements: *id005
74
+ - !ruby/object:Gem::Dependency
75
+ name: ruby-xslt
76
+ prerelease: false
77
+ requirement: &id006 !ruby/object:Gem::Requirement
78
+ none: false
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: "0"
83
+ type: :runtime
84
+ version_requirements: *id006
85
+ - !ruby/object:Gem::Dependency
86
+ name: hpricot
87
+ prerelease: false
88
+ requirement: &id007 !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: "0"
94
+ type: :runtime
95
+ version_requirements: *id007
96
+ - !ruby/object:Gem::Dependency
97
+ name: rmagick
98
+ prerelease: false
99
+ requirement: &id008 !ruby/object:Gem::Requirement
100
+ none: false
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: "0"
105
+ type: :runtime
106
+ version_requirements: *id008
107
+ - !ruby/object:Gem::Dependency
108
+ name: gruff
109
+ prerelease: false
110
+ requirement: &id009 !ruby/object:Gem::Requirement
111
+ none: false
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: "0"
116
+ type: :runtime
117
+ version_requirements: *id009
118
+ - !ruby/object:Gem::Dependency
119
+ name: clockwork
120
+ prerelease: false
121
+ requirement: &id010 !ruby/object:Gem::Requirement
122
+ none: false
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: "0"
127
+ type: :runtime
128
+ version_requirements: *id010
129
+ description: The harvester collects your favourite feeds and generates static html/feed pages
130
+ email: mail@janlelis.de
131
+ executables:
132
+ - harvester-new
133
+ - harvester
134
+ - harvester-clock
135
+ - harvester-db
136
+ - harvester-chart
137
+ - harvester-run
138
+ - harvester-generate
139
+ - harvester-fetch
140
+ - harvester-jabber
141
+ - harvester-post
142
+ extensions: []
143
+
144
+ extra_rdoc_files: []
145
+
146
+ files:
147
+ - lib/harvester/generate.rb
148
+ - lib/harvester/generator/entity_translator.rb
149
+ - lib/harvester/generator/link_absolutizer.rb
150
+ - lib/harvester/db.rb
151
+ - lib/harvester/chart.rb
152
+ - lib/harvester/post.rb
153
+ - lib/harvester/jabber.rb
154
+ - lib/harvester/fetch.rb
155
+ - lib/harvester/mrss.rb
156
+ - lib/harvester.rb
157
+ - bin/harvester-new
158
+ - bin/harvester
159
+ - bin/harvester-clock
160
+ - bin/harvester-db
161
+ - bin/harvester-chart
162
+ - bin/harvester-run
163
+ - bin/harvester-generate
164
+ - bin/harvester-fetch
165
+ - bin/harvester-jabber
166
+ - bin/harvester-post
167
+ - README.rdoc
168
+ - CHANGELOG.rdoc
169
+ - data/ent/HTMLsymbol.ent
170
+ - data/ent/HTMLspecial.ent
171
+ - data/ent/HTMLlat1.ent
172
+ - data/templates/rss.rdf
173
+ - data/templates/atom.xml
174
+ - data/templates/index.html
175
+ - data/templates/static/harvester_lte_ie6.css
176
+ - data/templates/static/harvester_ie7.css
177
+ - data/templates/static/harvester.css
178
+ - data/templates/static/harvester.gif
179
+ - data/templates/rss-all.rdf
180
+ - data/templates/atom-all.xml
181
+ - data/sql/sqlite3/create.table.jabbersettings.sql
182
+ - data/sql/sqlite3/create.table.items.sql
183
+ - data/sql/sqlite3/create.table.jabbersubscriptions.sql
184
+ - data/sql/sqlite3/create.table.sources.sql
185
+ - data/sql/sqlite3/create.view.last48hours.sql
186
+ - data/sql/sqlite3/chart.sql
187
+ - data/sql/sqlite3/create.table.enclosures.sql
188
+ - data/sql/harvester-0.6-mysql.diff
189
+ - data/sql/dbd-mysql-isotime.diff
190
+ - data/sql/harvester-0.7-mysql.diff
191
+ - data/sql/postgresql/create.table.jabbersettings.sql
192
+ - data/sql/postgresql/create.table.items.sql
193
+ - data/sql/postgresql/create.table.jabbersubscriptions.sql
194
+ - data/sql/postgresql/create.table.sources.sql
195
+ - data/sql/postgresql/create.view.last48hours.sql
196
+ - data/sql/postgresql/chart.sql
197
+ - data/sql/postgresql/create.table.enclosures.sql
198
+ - data/sql/mysql/create.table.jabbersettings.sql
199
+ - data/sql/mysql/create.table.items.sql
200
+ - data/sql/mysql/create.table.jabbersubscriptions.sql
201
+ - data/sql/mysql/create.table.sources.sql
202
+ - data/sql/mysql/create.view.last48hours.sql
203
+ - data/sql/mysql/chart.sql
204
+ - data/sql/mysql/create.table.enclosures.sql
205
+ - collections.yaml
206
+ - config.yaml
207
+ - Rakefile
208
+ - harvester.gemspec
209
+ homepage: https://github.com/janlelis/harvester
210
+ licenses:
211
+ - AGPL
212
+ post_install_message:
213
+ rdoc_options: []
214
+
215
+ require_paths:
216
+ - lib
217
+ required_ruby_version: !ruby/object:Gem::Requirement
218
+ none: false
219
+ requirements:
220
+ - - ">="
221
+ - !ruby/object:Gem::Version
222
+ version: 1.9.2
223
+ required_rubygems_version: !ruby/object:Gem::Requirement
224
+ none: false
225
+ requirements:
226
+ - - ">="
227
+ - !ruby/object:Gem::Version
228
+ version: 1.3.6
229
+ requirements: []
230
+
231
+ rubyforge_project:
232
+ rubygems_version: 1.8.1
233
+ signing_key:
234
+ specification_version: 3
235
+ summary: Web-based feed aggregator
236
+ test_files: []
237
+