rutt 0.3.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.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "launchy"
4
+ gem "ncurses"
5
+ gem "nokogiri"
6
+ gem "parallel"
7
+ gem "ruby-feedparser"
8
+ gem "sqlite3"
9
+ gem "oauth"
10
+
11
+ group :development do
12
+ gem "rspec", "~> 2.3.0"
13
+ gem "bundler", "~> 1.0.0"
14
+ gem "jeweler", "~> 1.5.2"
15
+ gem "rcov", ">= 0"
16
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,45 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ configuration (1.2.0)
5
+ diff-lcs (1.1.2)
6
+ git (1.2.5)
7
+ jeweler (1.5.2)
8
+ bundler (~> 1.0.0)
9
+ git (>= 1.2.5)
10
+ rake
11
+ launchy (0.4.0)
12
+ configuration (>= 0.0.5)
13
+ rake (>= 0.8.1)
14
+ ncurses (0.9.1)
15
+ nokogiri (1.4.4)
16
+ oauth (0.4.4)
17
+ parallel (0.5.3)
18
+ rake (0.8.7)
19
+ rcov (0.9.9)
20
+ rspec (2.3.0)
21
+ rspec-core (~> 2.3.0)
22
+ rspec-expectations (~> 2.3.0)
23
+ rspec-mocks (~> 2.3.0)
24
+ rspec-core (2.3.1)
25
+ rspec-expectations (2.3.0)
26
+ diff-lcs (~> 1.1.2)
27
+ rspec-mocks (2.3.0)
28
+ ruby-feedparser (0.7)
29
+ sqlite3 (1.3.3)
30
+
31
+ PLATFORMS
32
+ ruby
33
+
34
+ DEPENDENCIES
35
+ bundler (~> 1.0.0)
36
+ jeweler (~> 1.5.2)
37
+ launchy
38
+ ncurses
39
+ nokogiri
40
+ oauth
41
+ parallel
42
+ rcov
43
+ rspec (~> 2.3.0)
44
+ ruby-feedparser
45
+ sqlite3
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Abhi Yerra
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.org ADDED
@@ -0,0 +1,25 @@
1
+ * Rutt
2
+
3
+ ** Description
4
+
5
+ Rutt is the Mutt of news reader. It attempts to be the fastest way
6
+ to read news feeds.
7
+
8
+ Currently rutt is still in heavy development and it is a bit
9
+ unstable although the main features are largely implemented.
10
+ It still needs a bit of polishing before it can be considered
11
+ stable.
12
+
13
+ ** Dependencies
14
+ - Ruby 1.8
15
+ - elinks (For now.)
16
+
17
+ ** Download & Repository
18
+
19
+ Rutt is still in heavy development so please
20
+ check out the [[https://github.com/abhiyerra/rutt][repository]] to use it.
21
+
22
+ - [[https://github.com/abhiyerra/rutt]]
23
+
24
+ Downloads are located here:
25
+ - [[https://github.com/abhiyerra/rutt/downloads]]
data/Rakefile ADDED
@@ -0,0 +1,54 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
+ gem.name = "rutt"
16
+ gem.homepage = "http://github.com/abhiyerra/rutt"
17
+ gem.license = "BSD"
18
+ gem.summary = %Q{The Mutt of RSS/Atom feeds.}
19
+ gem.description = %Q{Read RSS feeds from the commandline }
20
+ gem.email = "abhi@berkeley.edu"
21
+ gem.authors = ["Abhi Yerra"]
22
+
23
+ gem.add_runtime_dependency "launchy"
24
+ gem.add_runtime_dependency "ncurses"
25
+ gem.add_runtime_dependency "nokogiri"
26
+ gem.add_runtime_dependency "parallel"
27
+ gem.add_runtime_dependency "ruby-feedparser"
28
+ gem.add_runtime_dependency "sqlite3"
29
+ gem.add_runtime_dependency "oauth"
30
+ end
31
+ Jeweler::RubygemsDotOrgTasks.new
32
+
33
+ require 'rspec/core'
34
+ require 'rspec/core/rake_task'
35
+ RSpec::Core::RakeTask.new(:spec) do |spec|
36
+ spec.pattern = FileList['spec/**/*_spec.rb']
37
+ end
38
+
39
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
40
+ spec.pattern = 'spec/**/*_spec.rb'
41
+ spec.rcov = true
42
+ end
43
+
44
+ task :default => :spec
45
+
46
+ require 'rake/rdoctask'
47
+ Rake::RDocTask.new do |rdoc|
48
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
49
+
50
+ rdoc.rdoc_dir = 'rdoc'
51
+ rdoc.title = "rutt #{version}"
52
+ rdoc.rdoc_files.include('README*')
53
+ rdoc.rdoc_files.include('lib/**/*.rb')
54
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.3.0
data/bin/rutt ADDED
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
3
+
4
+ require 'rubygems'
5
+ require 'launchy'
6
+ require 'ncurses'
7
+ require 'nokogiri'
8
+ require 'open-uri'
9
+ require 'optparse'
10
+ require 'rss/1.0'
11
+ require 'rss/2.0'
12
+ require 'sqlite3'
13
+ require 'feedparser'
14
+ require 'parallel'
15
+ require 'instapaper'
16
+
17
+ require 'rutt'
18
+ require 'instapaper'
19
+
20
+ $db = SQLite3::Database.new('rutt.db')
21
+ $db.results_as_hash = true
22
+
23
+ def main
24
+
25
+ Config::make_table!
26
+ Feed::make_table!
27
+ Item::make_table!
28
+
29
+ options = {}
30
+ OptionParser.new do |opts|
31
+ opts.banner = "Usage: rutt.rb [options]"
32
+
33
+ opts.on('-a', '--add FEED', "Add a feed") do |url|
34
+ Feed::add(url)
35
+ exit
36
+ end
37
+
38
+ opts.on('-r', '--refresh', "Refresh feeds.") do
39
+ Feed::refresh
40
+ exit
41
+ end
42
+
43
+ opts.on('-o', '--import-opml FILE', "Import opml") do |file|
44
+ urls = Opml::get_urls(file)
45
+
46
+ urls.each do |url|
47
+ puts "Adding #{url}"
48
+ Feed::add(url, false)
49
+ end
50
+
51
+ exit
52
+ end
53
+
54
+ opts.on('-s', '--set-key', "Set config") do |key, value|
55
+ Config.set(ARGV[0], ARGV[1])
56
+ exit
57
+ end
58
+
59
+ # opts.on('-l', '--list-feeds', action='store_true', help="List the feeds")
60
+
61
+ end.parse!
62
+
63
+ consumer_key = Config.get("instapaper.consumer-key")
64
+ consumer_secret = Config.get("instapaper.consumer-secret")
65
+ username = Config.get("instapaper.username")
66
+ password = Config.get("instapaper.password")
67
+
68
+ $instapaper = Instapaper::API.new(consumer_key, consumer_secret)
69
+ $instapaper.authorize(username, password)
70
+
71
+ start_screen
72
+ end
73
+
74
+ main
data/lib/instapaper.rb ADDED
@@ -0,0 +1,34 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'rubygems'
3
+ require 'oauth'
4
+
5
+ module Instapaper
6
+ class API
7
+ Url = "http://www.instapaper.com"
8
+
9
+ def initialize(consumer_key, consumer_secret)
10
+ @consumer_key = consumer_key
11
+ @consumer_secret = consumer_secret
12
+ end
13
+
14
+ def authorize(username, password)
15
+ @consumer = OAuth::Consumer.new(@consumer_key, @consumer_secret, {
16
+ :site => "https://www.instapaper.com",
17
+ :access_token_path => "/api/1/oauth/access_token",
18
+ :http_method => :post
19
+ })
20
+
21
+ access_token = @consumer.get_access_token(nil, {}, {
22
+ :x_auth_username => username,
23
+ :x_auth_password => password,
24
+ :x_auth_mode => "client_auth",
25
+ })
26
+
27
+ @access_token = OAuth::AccessToken.new(@consumer, access_token.token, access_token.secret)
28
+ end
29
+
30
+ def request(path, params={})
31
+ @access_token.request(:post, "#{Url}#{path}", params)
32
+ end
33
+ end
34
+ end
data/lib/rutt.rb ADDED
@@ -0,0 +1,477 @@
1
+ module Config
2
+ extend self
3
+
4
+ def make_table!
5
+ $db.execute(%{
6
+ create table if not exists config (
7
+ id integer PRIMARY KEY,
8
+ key text,
9
+ value text,
10
+ UNIQUE(key))
11
+ })
12
+ end
13
+
14
+ def get(key)
15
+ $db.get_first_value("select value from config where key = ?", key)
16
+ end
17
+
18
+ def set(key, value)
19
+ $db.execute("insert or replace into config(key, value) values (?, ?)", key, value)
20
+ end
21
+ end
22
+
23
+ module Opml
24
+ def self.get_urls(file)
25
+ doc = Nokogiri::XML(open(file))
26
+
27
+ urls = []
28
+
29
+ doc.xpath('opml/body/outline').each do |outline|
30
+ if outline['xmlUrl']
31
+ urls << outline['xmlUrl']
32
+ else
33
+ (outline/'outline').each do |outline2|
34
+ urls << outline2['xmlUrl']
35
+ end
36
+ end
37
+ end
38
+
39
+ return urls
40
+ end
41
+ end
42
+
43
+ module Feed
44
+ extend self
45
+
46
+ def make_table!
47
+ $db.execute(%{
48
+ create table if not exists feeds (
49
+ id integer PRIMARY KEY,
50
+ title text,
51
+ url text,
52
+ update_interval integer default 3600,
53
+ created_at NOT NULL DEFAULT CURRENT_TIMESTAMP,
54
+ updated_at NOT NULL DEFAULT CURRENT_TIMESTAMP,
55
+ UNIQUE(url))
56
+ })
57
+ end
58
+
59
+ def add(url, refresh=true)
60
+ $db.execute("insert or ignore into feeds (url) values ('#{url}')")
61
+ $db.execute("select * from feeds where id = (select last_insert_rowid())") do |feed|
62
+ refresh_for(feed)
63
+ end if refresh
64
+ end
65
+
66
+ def get(id)
67
+
68
+ end
69
+
70
+ def delete(feed)
71
+ $db.execute("delete from items where feed_id = ?", feed['id'])
72
+ $db.execute("delete from feeds where id = ?", feed['id'])
73
+ end
74
+
75
+ def all(min_limit=0, max_limit=-1)
76
+ $db.execute(%{
77
+ select f.*,
78
+ (select count(*) from items iu where iu.feed_id = f.id) as num_items,
79
+ (select count(*) from items ir where ir.read = 0 and ir.feed_id = f.id) as unread
80
+ from feeds f where unread > 0 order by id desc limit #{min_limit},#{max_limit}
81
+ })
82
+ end
83
+
84
+ def refresh
85
+ feeds = $db.execute("select * from feeds")
86
+ results = Parallel.map(feeds, :in_threads => 8) do |feed|
87
+ refresh_for(feed)
88
+ end
89
+ end
90
+
91
+ def refresh_for(feed)
92
+ content = open(feed['url']).read
93
+ rss = FeedParser::Feed::new(content)
94
+
95
+ puts rss.title
96
+
97
+ $db.execute("update feeds set title = ? where id = ?", rss.title, feed['id'])
98
+
99
+ rss.items.each do |item|
100
+ $db.execute("insert or ignore into items (feed_id, title, url, published_at) values (?, ?, ?, ?)", feed['id'], item.title, item.link, item.date.to_i)
101
+ end
102
+ rescue Exception => e
103
+ # no-op
104
+ end
105
+
106
+ def unread(feed_id)
107
+ $db.execute("select * from items where feed_id = ? and read = 0", feed)
108
+ end
109
+
110
+ def mark_as_read(feed)
111
+ $db.execute("update items set read = 1 where feed_id = ?", feed['id'])
112
+ end
113
+ end
114
+
115
+
116
+ module Item
117
+
118
+ extend self
119
+
120
+ def make_table!
121
+ $db.execute(%{
122
+ create table if not exists items (
123
+ id integer PRIMARY KEY,
124
+ feed_id integer,
125
+ title text,
126
+ url text,
127
+ description text,
128
+ read int default 0,
129
+ prioritize int default 0,
130
+ published_at DATE NOT NULL DEFAULT (datetime('now','localtime')),
131
+ created_at NOT NULL DEFAULT CURRENT_TIMESTAMP,
132
+ updated_at NOT NULL DEFAULT CURRENT_TIMESTAMP,
133
+ UNIQUE(url),
134
+ FOREIGN KEY(feed_id) REFERENCES feeds(id))
135
+ })
136
+ end
137
+
138
+ def all(feed, min_limit=0, max_limit=-1)
139
+ $db.execute("select * from items where feed_id = ? order by published_at desc limit #{min_limit},#{max_limit}", feed['id'])
140
+ end
141
+
142
+ def mark_as_unread(item)
143
+ $db.execute("update items set read = 0 where id = #{item['id']}")
144
+ end
145
+
146
+ def mark_as_read(item)
147
+ $db.execute("update items set read = 1 where id = #{item['id']}")
148
+ end
149
+
150
+ def sent_to_instapaper(item)
151
+ $db.execute("update items set read = 2 where id = #{item['id']}")
152
+ end
153
+ end
154
+
155
+ class Screen
156
+ def initialize(stdscr)
157
+ @stdscr = stdscr
158
+
159
+ @min_y = 1
160
+ @max_y = @stdscr.getmaxy
161
+
162
+ @min_limit = @min_y - 1
163
+ @max_limit = @max_y - 5
164
+
165
+ @cur_y = 1
166
+ @cur_x = 0
167
+ end
168
+
169
+ def incr_page
170
+ @min_limit = @max_limit
171
+ @max_limit += (@max_y - 5)
172
+ end
173
+
174
+ def decr_page
175
+ @max_limit = @min_limit
176
+ @min_limit -= (@max_y - 5)
177
+
178
+ if @max_limit <= 0
179
+ @min_limit = @min_y - 1
180
+ @max_limit = @max_y - 5
181
+ end
182
+ end
183
+
184
+ def display_menu
185
+ @stdscr.clear
186
+ @stdscr.move(0, 0)
187
+ @stdscr.addstr(" rutt #{@menu}\n")
188
+ end
189
+
190
+ def move_pointer(pos, move_to=false)
191
+ @stdscr.move(@cur_y, 0)
192
+ @stdscr.addstr(" ")
193
+
194
+ if move_to == true
195
+ @cur_y = pos
196
+ else
197
+ @cur_y += pos
198
+ end
199
+
200
+ @stdscr.move(@cur_y, 0)
201
+ @stdscr.addstr(">")
202
+ end
203
+ end
204
+
205
+
206
+ class FeedScreen < Screen
207
+ def initialize(stdscr)
208
+ @menu = "q:Quit d:delete r:refresh all"
209
+
210
+ super(stdscr)
211
+ end
212
+
213
+ def display_feeds
214
+ @cur_y = @min_y
215
+
216
+ @feeds = Feed::all(@min_limit, @max_limit)
217
+ @feeds.each do |feed|
218
+ # next if feed['unread'] == 0 # This should be configurable: feed.showread
219
+
220
+ @stdscr.move(@cur_y, 0)
221
+ @stdscr.addstr(" #{feed['unread']}/#{feed['num_items']}\t\t#{feed['title']}\n")
222
+
223
+ @cur_y += 1
224
+ end
225
+
226
+ @cur_y = @min_y
227
+ @stdscr.refresh
228
+ end
229
+
230
+ def window
231
+ @stdscr.clear
232
+
233
+ display_menu
234
+ display_feeds
235
+ move_pointer(0)
236
+ end
237
+
238
+ def loop
239
+ window
240
+
241
+ while true do
242
+ c = @stdscr.getch
243
+
244
+ if c > 0 && c < 255
245
+ case c.chr
246
+ when /q/i
247
+ break
248
+ when /a/i
249
+ # no-op
250
+ when /r/i
251
+ cur_y = @cur_y
252
+
253
+ Feed::refresh
254
+
255
+ window
256
+ move_pointer(cur_y, move_to=true)
257
+ when /d/i
258
+ cur_y = @cur_y - 1
259
+
260
+ @stdscr.clear
261
+ display_menu
262
+ feed = @feeds[cur_y]
263
+ @stdscr.move(2, 0)
264
+ @stdscr.addstr("Are you sure you want to delete #{feed['title']}? ")
265
+ d = @stdscr.getch
266
+ if d.chr =~ /y/i
267
+ Feed::delete(feed)
268
+ end
269
+ window
270
+ move_pointer(cur_y, move_to=true)
271
+ when /p/i
272
+ decr_page
273
+ window
274
+ when /n/i
275
+ incr_page
276
+ window
277
+ when / /
278
+ cur_y = @cur_y
279
+ @stdscr.addstr("#{@feeds[cur_y]}")
280
+ item_screen = ItemScreen.new(@stdscr, @feeds[cur_y - 1])
281
+ item_screen.loop
282
+
283
+ window
284
+ move_pointer(cur_y, move_to=true)
285
+ end
286
+ else
287
+ case c
288
+ when Ncurses::KEY_UP
289
+ move_pointer(-1)
290
+ when Ncurses::KEY_DOWN
291
+ move_pointer(1)
292
+ end
293
+ end
294
+ end
295
+ end
296
+ end
297
+
298
+ class ItemScreen < Screen
299
+ def initialize(stdscr, feed)
300
+ @feed = feed
301
+ @menu = " i:quit r:refresh m:mark as read u:mark as unread a:mark all as read b:open in browser"
302
+
303
+ super(stdscr)
304
+ end
305
+
306
+ def display_items
307
+ @cur_y = @min_y
308
+
309
+ @items = Item::all(@feed, @min_limit, @max_limit)
310
+ @items.each do |item|
311
+ item_status = case item['read'].to_i
312
+ when 0 then 'N'
313
+ when 1 then ' '
314
+ when 2 then 'I'
315
+ else ' '
316
+ end
317
+ @stdscr.addstr(" #{item_status}\t#{Time.at(item['published_at']).strftime("%b %d, %Y %R:%M")}\t#{item['title']}\n")
318
+ @cur_y += 1
319
+ end
320
+
321
+ @cur_y = @min_y
322
+ @stdscr.refresh
323
+ end
324
+
325
+ def window
326
+ @stdscr.clear
327
+
328
+ display_menu
329
+ display_items
330
+ move_pointer(0)
331
+ end
332
+
333
+ def loop
334
+ window
335
+
336
+ while true do
337
+ c = @stdscr.getch
338
+
339
+ if c > 0 && c < 255
340
+ case c.chr
341
+ when /[iq]/i
342
+ break
343
+ when /s/i
344
+ cur_y = @cur_y - 1
345
+ $instapaper.request('/api/1/bookmarks/add', {
346
+ 'url' => @items[cur_y]['url'],
347
+ 'title' => @items[cur_y]['title'],
348
+ })
349
+ Item::sent_to_instapaper(@items[cur_y])
350
+ window
351
+ move_pointer(@cur_y, move_to=true)
352
+ when /a/i
353
+ Feed::mark_as_read(@feed)
354
+ window
355
+ move_pointer(@cur_y, move_to=true)
356
+ when /p/i
357
+ decr_page
358
+ window
359
+ when /n/i
360
+ incr_page
361
+ window
362
+ when /b/i
363
+ cur_y = @cur_y - 1
364
+ Item::mark_as_read(@items[cur_y])
365
+ Launchy.open(@items[cur_y]['url'])
366
+ window
367
+ move_pointer(@cur_y, move_to=true)
368
+ when /m/i
369
+ cur_y = @cur_y - 1
370
+ Item::mark_as_read(@items[cur_y])
371
+ window
372
+ move_pointer(cur_y + 1, move_to=true)
373
+ when /u/i
374
+ cur_y = @cur_y - 1
375
+ Item::mark_as_unread(@items[cur_y])
376
+ window
377
+ move_pointer(cur_y + 1, move_to=true)
378
+ when /r/i
379
+ Feed::refresh_for(@feed)
380
+ window
381
+ when / /
382
+ content_screen = ContentScreen.new(@stdscr, @items[@cur_y - 1])
383
+ content_screen.loop
384
+
385
+ window
386
+ move_pointer(@cur_y, move_to=true)
387
+ end
388
+ else
389
+ case c
390
+ when Ncurses::KEY_UP
391
+ move_pointer(-1)
392
+ when Ncurses::KEY_DOWN
393
+ move_pointer(1)
394
+ end
395
+ end
396
+ end
397
+ end
398
+ end
399
+
400
+
401
+
402
+
403
+
404
+ class ContentScreen < Screen
405
+ def initialize(stdscr, item)
406
+ @item = item
407
+ @menu = "i:back b:open in browser"
408
+
409
+ super(stdscr)
410
+ end
411
+
412
+ def display_content
413
+ @content = `elinks -dump -dump-charset ascii -force-html #{@item['url']}`
414
+ @content = @content.split("\n")
415
+
416
+ @stdscr.addstr(" #{@item['title']} (#{@item['url']})\n\n")
417
+
418
+ lines = @content[@min_limit..@max_limit]
419
+ lines.each { |line| @stdscr.addstr(" #{line}\n") } if lines
420
+
421
+ @stdscr.refresh
422
+ end
423
+
424
+ def window
425
+ @stdscr.clear
426
+ display_menu
427
+ display_content
428
+ end
429
+
430
+ def loop
431
+ @cur_line = 0
432
+
433
+ window
434
+
435
+ while true do
436
+ c = @stdscr.getch
437
+ if c > 0 && c < 255
438
+ case c.chr
439
+ when /[iq]/i
440
+ Item::mark_as_read(@item)
441
+ break
442
+ when /b/i
443
+ Launchy.open(@item['url'])
444
+ end
445
+ else
446
+ case c
447
+ when Ncurses::KEY_UP
448
+ decr_page
449
+ window
450
+ when Ncurses::KEY_DOWN
451
+ incr_page
452
+ window
453
+ end
454
+ end
455
+ end
456
+
457
+ end
458
+ end
459
+
460
+
461
+
462
+ def start_screen
463
+ stdscr = Ncurses.initscr()
464
+
465
+ Ncurses.start_color();
466
+ Ncurses.cbreak();
467
+ Ncurses.noecho();
468
+ Ncurses.keypad(stdscr, true);
469
+
470
+ stdscr.clear
471
+
472
+ feed_screen = FeedScreen.new(stdscr)
473
+ feed_screen.loop
474
+ ensure
475
+ Ncurses.endwin()
476
+ end
477
+
data/spec/rutt_spec.rb ADDED
@@ -0,0 +1,7 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "Rutt" do
4
+ it "fails" do
5
+ fail "hey buddy, you should probably rename this file and start specing for real"
6
+ end
7
+ end
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'rutt'
5
+
6
+ # Requires supporting files with custom matchers and macros, etc,
7
+ # in ./support/ and its subdirectories.
8
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
9
+
10
+ RSpec.configure do |config|
11
+
12
+ end
metadata ADDED
@@ -0,0 +1,338 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rutt
3
+ version: !ruby/object:Gem::Version
4
+ hash: 19
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 3
9
+ - 0
10
+ version: 0.3.0
11
+ platform: ruby
12
+ authors:
13
+ - Abhi Yerra
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-04-23 00:00:00 +00:00
19
+ default_executable: rutt
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ type: :runtime
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
32
+ name: launchy
33
+ version_requirements: *id001
34
+ prerelease: false
35
+ - !ruby/object:Gem::Dependency
36
+ type: :runtime
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ hash: 3
43
+ segments:
44
+ - 0
45
+ version: "0"
46
+ name: ncurses
47
+ version_requirements: *id002
48
+ prerelease: false
49
+ - !ruby/object:Gem::Dependency
50
+ type: :runtime
51
+ requirement: &id003 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ hash: 3
57
+ segments:
58
+ - 0
59
+ version: "0"
60
+ name: nokogiri
61
+ version_requirements: *id003
62
+ prerelease: false
63
+ - !ruby/object:Gem::Dependency
64
+ type: :runtime
65
+ requirement: &id004 !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ hash: 3
71
+ segments:
72
+ - 0
73
+ version: "0"
74
+ name: parallel
75
+ version_requirements: *id004
76
+ prerelease: false
77
+ - !ruby/object:Gem::Dependency
78
+ type: :runtime
79
+ requirement: &id005 !ruby/object:Gem::Requirement
80
+ none: false
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ hash: 3
85
+ segments:
86
+ - 0
87
+ version: "0"
88
+ name: ruby-feedparser
89
+ version_requirements: *id005
90
+ prerelease: false
91
+ - !ruby/object:Gem::Dependency
92
+ type: :runtime
93
+ requirement: &id006 !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ hash: 3
99
+ segments:
100
+ - 0
101
+ version: "0"
102
+ name: sqlite3
103
+ version_requirements: *id006
104
+ prerelease: false
105
+ - !ruby/object:Gem::Dependency
106
+ type: :runtime
107
+ requirement: &id007 !ruby/object:Gem::Requirement
108
+ none: false
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ hash: 3
113
+ segments:
114
+ - 0
115
+ version: "0"
116
+ name: oauth
117
+ version_requirements: *id007
118
+ prerelease: false
119
+ - !ruby/object:Gem::Dependency
120
+ type: :development
121
+ requirement: &id008 !ruby/object:Gem::Requirement
122
+ none: false
123
+ requirements:
124
+ - - ~>
125
+ - !ruby/object:Gem::Version
126
+ hash: 3
127
+ segments:
128
+ - 2
129
+ - 3
130
+ - 0
131
+ version: 2.3.0
132
+ name: rspec
133
+ version_requirements: *id008
134
+ prerelease: false
135
+ - !ruby/object:Gem::Dependency
136
+ type: :development
137
+ requirement: &id009 !ruby/object:Gem::Requirement
138
+ none: false
139
+ requirements:
140
+ - - ~>
141
+ - !ruby/object:Gem::Version
142
+ hash: 23
143
+ segments:
144
+ - 1
145
+ - 0
146
+ - 0
147
+ version: 1.0.0
148
+ name: bundler
149
+ version_requirements: *id009
150
+ prerelease: false
151
+ - !ruby/object:Gem::Dependency
152
+ type: :development
153
+ requirement: &id010 !ruby/object:Gem::Requirement
154
+ none: false
155
+ requirements:
156
+ - - ~>
157
+ - !ruby/object:Gem::Version
158
+ hash: 7
159
+ segments:
160
+ - 1
161
+ - 5
162
+ - 2
163
+ version: 1.5.2
164
+ name: jeweler
165
+ version_requirements: *id010
166
+ prerelease: false
167
+ - !ruby/object:Gem::Dependency
168
+ type: :development
169
+ requirement: &id011 !ruby/object:Gem::Requirement
170
+ none: false
171
+ requirements:
172
+ - - ">="
173
+ - !ruby/object:Gem::Version
174
+ hash: 3
175
+ segments:
176
+ - 0
177
+ version: "0"
178
+ name: rcov
179
+ version_requirements: *id011
180
+ prerelease: false
181
+ - !ruby/object:Gem::Dependency
182
+ type: :runtime
183
+ requirement: &id012 !ruby/object:Gem::Requirement
184
+ none: false
185
+ requirements:
186
+ - - ">="
187
+ - !ruby/object:Gem::Version
188
+ hash: 3
189
+ segments:
190
+ - 0
191
+ version: "0"
192
+ name: launchy
193
+ version_requirements: *id012
194
+ prerelease: false
195
+ - !ruby/object:Gem::Dependency
196
+ type: :runtime
197
+ requirement: &id013 !ruby/object:Gem::Requirement
198
+ none: false
199
+ requirements:
200
+ - - ">="
201
+ - !ruby/object:Gem::Version
202
+ hash: 3
203
+ segments:
204
+ - 0
205
+ version: "0"
206
+ name: ncurses
207
+ version_requirements: *id013
208
+ prerelease: false
209
+ - !ruby/object:Gem::Dependency
210
+ type: :runtime
211
+ requirement: &id014 !ruby/object:Gem::Requirement
212
+ none: false
213
+ requirements:
214
+ - - ">="
215
+ - !ruby/object:Gem::Version
216
+ hash: 3
217
+ segments:
218
+ - 0
219
+ version: "0"
220
+ name: nokogiri
221
+ version_requirements: *id014
222
+ prerelease: false
223
+ - !ruby/object:Gem::Dependency
224
+ type: :runtime
225
+ requirement: &id015 !ruby/object:Gem::Requirement
226
+ none: false
227
+ requirements:
228
+ - - ">="
229
+ - !ruby/object:Gem::Version
230
+ hash: 3
231
+ segments:
232
+ - 0
233
+ version: "0"
234
+ name: parallel
235
+ version_requirements: *id015
236
+ prerelease: false
237
+ - !ruby/object:Gem::Dependency
238
+ type: :runtime
239
+ requirement: &id016 !ruby/object:Gem::Requirement
240
+ none: false
241
+ requirements:
242
+ - - ">="
243
+ - !ruby/object:Gem::Version
244
+ hash: 3
245
+ segments:
246
+ - 0
247
+ version: "0"
248
+ name: ruby-feedparser
249
+ version_requirements: *id016
250
+ prerelease: false
251
+ - !ruby/object:Gem::Dependency
252
+ type: :runtime
253
+ requirement: &id017 !ruby/object:Gem::Requirement
254
+ none: false
255
+ requirements:
256
+ - - ">="
257
+ - !ruby/object:Gem::Version
258
+ hash: 3
259
+ segments:
260
+ - 0
261
+ version: "0"
262
+ name: sqlite3
263
+ version_requirements: *id017
264
+ prerelease: false
265
+ - !ruby/object:Gem::Dependency
266
+ type: :runtime
267
+ requirement: &id018 !ruby/object:Gem::Requirement
268
+ none: false
269
+ requirements:
270
+ - - ">="
271
+ - !ruby/object:Gem::Version
272
+ hash: 3
273
+ segments:
274
+ - 0
275
+ version: "0"
276
+ name: oauth
277
+ version_requirements: *id018
278
+ prerelease: false
279
+ description: "Read RSS feeds from the commandline "
280
+ email: abhi@berkeley.edu
281
+ executables:
282
+ - rutt
283
+ extensions: []
284
+
285
+ extra_rdoc_files:
286
+ - LICENSE.txt
287
+ - README.org
288
+ files:
289
+ - .document
290
+ - .rspec
291
+ - Gemfile
292
+ - Gemfile.lock
293
+ - LICENSE.txt
294
+ - README.org
295
+ - Rakefile
296
+ - VERSION
297
+ - bin/rutt
298
+ - lib/instapaper.rb
299
+ - lib/rutt.rb
300
+ - spec/rutt_spec.rb
301
+ - spec/spec_helper.rb
302
+ has_rdoc: true
303
+ homepage: http://github.com/abhiyerra/rutt
304
+ licenses:
305
+ - BSD
306
+ post_install_message:
307
+ rdoc_options: []
308
+
309
+ require_paths:
310
+ - lib
311
+ required_ruby_version: !ruby/object:Gem::Requirement
312
+ none: false
313
+ requirements:
314
+ - - ">="
315
+ - !ruby/object:Gem::Version
316
+ hash: 3
317
+ segments:
318
+ - 0
319
+ version: "0"
320
+ required_rubygems_version: !ruby/object:Gem::Requirement
321
+ none: false
322
+ requirements:
323
+ - - ">="
324
+ - !ruby/object:Gem::Version
325
+ hash: 3
326
+ segments:
327
+ - 0
328
+ version: "0"
329
+ requirements: []
330
+
331
+ rubyforge_project:
332
+ rubygems_version: 1.6.2
333
+ signing_key:
334
+ specification_version: 3
335
+ summary: The Mutt of RSS/Atom feeds.
336
+ test_files:
337
+ - spec/rutt_spec.rb
338
+ - spec/spec_helper.rb