rutt 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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