rutt 0.3.2 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -16,7 +16,7 @@ Jeweler::Tasks.new do |gem|
16
16
  gem.homepage = "http://github.com/abhiyerra/rutt"
17
17
  gem.license = "BSD"
18
18
  gem.summary = %Q{The Mutt of RSS/Atom feeds.}
19
- gem.description = %Q{Read RSS feeds from the commandline }
19
+ gem.description = %Q{The Mutt of RSS/Atom feeds.}
20
20
  gem.email = "abhi@berkeley.edu"
21
21
  gem.authors = ["Abhi Yerra"]
22
22
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.2
1
+ 0.3.3
data/bin/rutt CHANGED
@@ -13,46 +13,63 @@ require 'sqlite3'
13
13
  require 'feedparser'
14
14
  require 'parallel'
15
15
  require 'instapaper'
16
+ require 'oauth'
16
17
 
17
18
  require 'rutt'
18
- require 'instapaper'
19
19
 
20
20
  $db = SQLite3::Database.new('rutt.db')
21
21
  $db.results_as_hash = true
22
22
 
23
+ def start_screen
24
+ stdscr = Ncurses.initscr()
25
+
26
+ Ncurses.start_color();
27
+ Ncurses.cbreak();
28
+ Ncurses.noecho();
29
+ Ncurses.keypad(stdscr, true);
30
+
31
+ stdscr.clear
32
+
33
+ feed_screen = Rutt::FeedScreen.new(stdscr)
34
+ feed_screen.loop
35
+ ensure
36
+ Ncurses.endwin()
37
+ end
38
+
39
+
23
40
  def main
24
41
 
25
- Config::make_table!
26
- Feed::make_table!
27
- Item::make_table!
42
+ Rutt::Config.make_table!
43
+ Rutt::Feed.make_table!
44
+ Rutt::Item.make_table!
28
45
 
29
46
  options = {}
30
47
  OptionParser.new do |opts|
31
48
  opts.banner = "Usage: rutt.rb [options]"
32
49
 
33
50
  opts.on('-a', '--add FEED', "Add a feed") do |url|
34
- Feed::add(url)
51
+ Rutt::Feed::add(url)
35
52
  exit
36
53
  end
37
54
 
38
55
  opts.on('-r', '--refresh', "Refresh feeds.") do
39
- Feed::refresh
56
+ Rutt::Feed::refresh
40
57
  exit
41
58
  end
42
59
 
43
60
  opts.on('-o', '--import-opml FILE', "Import opml") do |file|
44
- urls = Opml::get_urls(file)
61
+ urls = Rutt::Opml::get_urls(file)
45
62
 
46
63
  urls.each do |url|
47
64
  puts "Adding #{url}"
48
- Feed::add(url, false)
65
+ Rutt::Feed::add(url, false)
49
66
  end
50
67
 
51
68
  exit
52
69
  end
53
70
 
54
71
  opts.on('-s', '--set-key', "Set config") do |key, value|
55
- Config.set(ARGV[0], ARGV[1])
72
+ Rutt::Config.set(ARGV[0], ARGV[1])
56
73
  exit
57
74
  end
58
75
 
@@ -60,12 +77,12 @@ def main
60
77
 
61
78
  end.parse!
62
79
 
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")
80
+ consumer_key = Rutt::Config.get("instapaper.consumer-key")
81
+ consumer_secret = Rutt::Config.get("instapaper.consumer-secret")
82
+ username = Rutt::Config.get("instapaper.username")
83
+ password = Rutt::Config.get("instapaper.password")
67
84
 
68
- $instapaper = Instapaper::API.new(consumer_key, consumer_secret)
85
+ $instapaper = Rutt::Instapaper::API.new(consumer_key, consumer_secret)
69
86
  $instapaper.authorize(username, password)
70
87
 
71
88
  start_screen
data/lib/rutt.rb CHANGED
@@ -1,477 +1,9 @@
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
-
1
+ require 'rutt/config'
2
+ require 'rutt/opml'
3
+ require 'rutt/instapaper'
4
+ require 'rutt/feed'
5
+ require 'rutt/item'
6
+ require 'rutt/screen'
7
+ require 'rutt/feed_screen'
8
+ require 'rutt/item_screen'
9
+ require 'rutt/content_screen'
@@ -0,0 +1,23 @@
1
+ module Rutt
2
+ module Config
3
+ extend self
4
+
5
+ def make_table!
6
+ $db.execute(%{
7
+ create table if not exists config (
8
+ id integer PRIMARY KEY,
9
+ key text,
10
+ value text,
11
+ UNIQUE(key))
12
+ })
13
+ end
14
+
15
+ def get(key)
16
+ $db.get_first_value("select value from config where key = ?", key)
17
+ end
18
+
19
+ def set(key, value)
20
+ $db.execute("insert or replace into config(key, value) values (?, ?)", key, value)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,57 @@
1
+ module Rutt
2
+ class ContentScreen < Screen
3
+ def initialize(stdscr, item)
4
+ @item = item
5
+ @menu = "i:back b:open in browser"
6
+
7
+ super(stdscr)
8
+ end
9
+
10
+ def display_content
11
+ @content = `elinks -dump -dump-charset ascii -force-html #{@item['url']}`
12
+ @content = @content.split("\n")
13
+
14
+ @stdscr.addstr(" #{@item['title']} (#{@item['url']})\n\n")
15
+
16
+ lines = @content[@min_limit..@max_limit]
17
+ lines.each { |line| @stdscr.addstr(" #{line}\n") } if lines
18
+
19
+ @stdscr.refresh
20
+ end
21
+
22
+ def window
23
+ @stdscr.clear
24
+ display_menu
25
+ display_content
26
+ end
27
+
28
+ def loop
29
+ @cur_line = 0
30
+
31
+ window
32
+
33
+ while true do
34
+ c = @stdscr.getch
35
+ if c > 0 && c < 255
36
+ case c.chr
37
+ when /[iq]/i
38
+ Item::mark_as_read(@item)
39
+ break
40
+ when /b/i
41
+ Launchy.open(@item['url'])
42
+ end
43
+ else
44
+ case c
45
+ when Ncurses::KEY_UP
46
+ decr_page
47
+ window
48
+ when Ncurses::KEY_DOWN
49
+ incr_page
50
+ window
51
+ end
52
+ end
53
+ end
54
+
55
+ end
56
+ end
57
+ end
data/lib/rutt/feed.rb ADDED
@@ -0,0 +1,74 @@
1
+ module Rutt
2
+
3
+ module Feed
4
+ extend self
5
+
6
+ def make_table!
7
+ $db.execute(%{
8
+ create table if not exists feeds (
9
+ id integer PRIMARY KEY,
10
+ title text,
11
+ url text,
12
+ update_interval integer default 3600,
13
+ created_at NOT NULL DEFAULT CURRENT_TIMESTAMP,
14
+ updated_at NOT NULL DEFAULT CURRENT_TIMESTAMP,
15
+ UNIQUE(url))
16
+ })
17
+ end
18
+
19
+ def add(url, refresh=true)
20
+ $db.execute("insert or ignore into feeds (url) values ('#{url}')")
21
+ $db.execute("select * from feeds where id = (select last_insert_rowid())") do |feed|
22
+ refresh_for(feed)
23
+ end if refresh
24
+ end
25
+
26
+ def get(id)
27
+
28
+ end
29
+
30
+ def delete(feed)
31
+ $db.execute("delete from items where feed_id = ?", feed['id'])
32
+ $db.execute("delete from feeds where id = ?", feed['id'])
33
+ end
34
+
35
+ def all(min_limit=0, max_limit=-1)
36
+ $db.execute(%{
37
+ select f.*,
38
+ (select count(*) from items iu where iu.feed_id = f.id) as num_items,
39
+ (select count(*) from items ir where ir.read = 0 and ir.feed_id = f.id) as unread
40
+ from feeds f where unread > 0 order by id desc limit #{min_limit},#{max_limit}
41
+ })
42
+ end
43
+
44
+ def refresh
45
+ feeds = $db.execute("select * from feeds")
46
+ results = Parallel.map(feeds, :in_threads => 8) do |feed|
47
+ refresh_for(feed)
48
+ end
49
+ end
50
+
51
+ def refresh_for(feed)
52
+ content = open(feed['url']).read
53
+ rss = FeedParser::Feed::new(content)
54
+
55
+ puts rss.title
56
+
57
+ $db.execute("update feeds set title = ? where id = ?", rss.title, feed['id'])
58
+
59
+ rss.items.each do |item|
60
+ $db.execute("insert or ignore into items (feed_id, title, url, published_at) values (?, ?, ?, ?)", feed['id'], item.title, item.link, item.date.to_i)
61
+ end
62
+ rescue Exception => e
63
+ # no-op
64
+ end
65
+
66
+ def unread(feed_id)
67
+ $db.execute("select * from items where feed_id = ? and read = 0", feed)
68
+ end
69
+
70
+ def mark_as_read(feed)
71
+ $db.execute("update items set read = 1 where feed_id = ?", feed['id'])
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,93 @@
1
+ module Rutt
2
+ class FeedScreen < Screen
3
+ def initialize(stdscr)
4
+ @menu = "q:Quit d:delete r:refresh all"
5
+
6
+ super(stdscr)
7
+ end
8
+
9
+ def display_feeds
10
+ @cur_y = @min_y
11
+
12
+ @feeds = Feed::all(@min_limit, @max_limit)
13
+ @feeds.each do |feed|
14
+ # next if feed['unread'] == 0 # This should be configurable: feed.showread
15
+
16
+ @stdscr.move(@cur_y, 0)
17
+ @stdscr.addstr(" #{feed['unread']}/#{feed['num_items']}\t\t#{feed['title']}\n")
18
+
19
+ @cur_y += 1
20
+ end
21
+
22
+ @cur_y = @min_y
23
+ @stdscr.refresh
24
+ end
25
+
26
+ def window
27
+ @stdscr.clear
28
+
29
+ display_menu
30
+ display_feeds
31
+ move_pointer(0)
32
+ end
33
+
34
+ def loop
35
+ window
36
+
37
+ while true do
38
+ c = @stdscr.getch
39
+
40
+ if c > 0 && c < 255
41
+ case c.chr
42
+ when /q/i
43
+ break
44
+ when /a/i
45
+ # no-op
46
+ when /r/i
47
+ cur_y = @cur_y
48
+
49
+ Feed::refresh
50
+
51
+ window
52
+ move_pointer(cur_y, move_to=true)
53
+ when /d/i
54
+ cur_y = @cur_y - 1
55
+
56
+ @stdscr.clear
57
+ display_menu
58
+ feed = @feeds[cur_y]
59
+ @stdscr.move(2, 0)
60
+ @stdscr.addstr("Are you sure you want to delete #{feed['title']}? ")
61
+ d = @stdscr.getch
62
+ if d.chr =~ /y/i
63
+ Feed::delete(feed)
64
+ end
65
+ window
66
+ move_pointer(cur_y, move_to=true)
67
+ when /p/i
68
+ decr_page
69
+ window
70
+ when /n/i
71
+ incr_page
72
+ window
73
+ when / /
74
+ cur_y = @cur_y
75
+ @stdscr.addstr("#{@feeds[cur_y]}")
76
+ item_screen = ItemScreen.new(@stdscr, @feeds[cur_y - 1])
77
+ item_screen.loop
78
+
79
+ window
80
+ move_pointer(cur_y, move_to=true)
81
+ end
82
+ else
83
+ case c
84
+ when Ncurses::KEY_UP
85
+ move_pointer(-1)
86
+ when Ncurses::KEY_DOWN
87
+ move_pointer(1)
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,32 @@
1
+ module Rutt
2
+ module Instapaper
3
+ class API
4
+ Url = "http://www.instapaper.com"
5
+
6
+ def initialize(consumer_key, consumer_secret)
7
+ @consumer_key = consumer_key
8
+ @consumer_secret = consumer_secret
9
+ end
10
+
11
+ def authorize(username, password)
12
+ @consumer = OAuth::Consumer.new(@consumer_key, @consumer_secret, {
13
+ :site => "https://www.instapaper.com",
14
+ :access_token_path => "/api/1/oauth/access_token",
15
+ :http_method => :post
16
+ })
17
+
18
+ access_token = @consumer.get_access_token(nil, {}, {
19
+ :x_auth_username => username,
20
+ :x_auth_password => password,
21
+ :x_auth_mode => "client_auth",
22
+ })
23
+
24
+ @access_token = OAuth::AccessToken.new(@consumer, access_token.token, access_token.secret)
25
+ end
26
+
27
+ def request(path, params={})
28
+ @access_token.request(:post, "#{Url}#{path}", params)
29
+ end
30
+ end
31
+ end
32
+ end
data/lib/rutt/item.rb ADDED
@@ -0,0 +1,40 @@
1
+ module Rutt
2
+ module Item
3
+
4
+ extend self
5
+
6
+ def make_table!
7
+ $db.execute(%{
8
+ create table if not exists items (
9
+ id integer PRIMARY KEY,
10
+ feed_id integer,
11
+ title text,
12
+ url text,
13
+ description text,
14
+ read int default 0,
15
+ prioritize int default 0,
16
+ published_at DATE NOT NULL DEFAULT (datetime('now','localtime')),
17
+ created_at NOT NULL DEFAULT CURRENT_TIMESTAMP,
18
+ updated_at NOT NULL DEFAULT CURRENT_TIMESTAMP,
19
+ UNIQUE(url),
20
+ FOREIGN KEY(feed_id) REFERENCES feeds(id))
21
+ })
22
+ end
23
+
24
+ def all(feed, min_limit=0, max_limit=-1)
25
+ $db.execute("select * from items where feed_id = ? order by published_at desc limit #{min_limit},#{max_limit}", feed['id'])
26
+ end
27
+
28
+ def mark_as_unread(item)
29
+ $db.execute("update items set read = 0 where id = #{item['id']}")
30
+ end
31
+
32
+ def mark_as_read(item)
33
+ $db.execute("update items set read = 1 where id = #{item['id']}")
34
+ end
35
+
36
+ def sent_to_instapaper(item)
37
+ $db.execute("update items set read = 2 where id = #{item['id']}")
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,103 @@
1
+ module Rutt
2
+ class ItemScreen < Screen
3
+ def initialize(stdscr, feed)
4
+ @feed = feed
5
+ @menu = " i:quit r:refresh m:mark as read u:mark as unread a:mark all as read b:open in browser"
6
+
7
+ super(stdscr)
8
+ end
9
+
10
+ def display_items
11
+ @cur_y = @min_y
12
+
13
+ @items = Item::all(@feed, @min_limit, @max_limit)
14
+ @items.each do |item|
15
+ item_status = case item['read'].to_i
16
+ when 0 then 'N'
17
+ when 1 then ' '
18
+ when 2 then 'I'
19
+ else ' '
20
+ end
21
+ @stdscr.addstr(" #{item_status}\t#{Time.at(item['published_at']).strftime("%b %d, %Y %R:%M")}\t#{item['title']}\n")
22
+ @cur_y += 1
23
+ end
24
+
25
+ @cur_y = @min_y
26
+ @stdscr.refresh
27
+ end
28
+
29
+ def window
30
+ @stdscr.clear
31
+
32
+ display_menu
33
+ display_items
34
+ move_pointer(0)
35
+ end
36
+
37
+ def loop
38
+ window
39
+
40
+ while true do
41
+ c = @stdscr.getch
42
+
43
+ if c > 0 && c < 255
44
+ case c.chr
45
+ when /[iq]/i
46
+ break
47
+ when /s/i
48
+ cur_y = @cur_y - 1
49
+ $instapaper.request('/api/1/bookmarks/add', {
50
+ 'url' => @items[cur_y]['url'],
51
+ 'title' => @items[cur_y]['title'],
52
+ })
53
+ Item::sent_to_instapaper(@items[cur_y])
54
+ window
55
+ move_pointer(@cur_y, move_to=true)
56
+ when /a/i
57
+ Feed::mark_as_read(@feed)
58
+ window
59
+ move_pointer(@cur_y, move_to=true)
60
+ when /p/i
61
+ decr_page
62
+ window
63
+ when /n/i
64
+ incr_page
65
+ window
66
+ when /b/i
67
+ cur_y = @cur_y - 1
68
+ Item::mark_as_read(@items[cur_y])
69
+ Launchy.open(@items[cur_y]['url'])
70
+ window
71
+ move_pointer(@cur_y, move_to=true)
72
+ when /m/i
73
+ cur_y = @cur_y - 1
74
+ Item::mark_as_read(@items[cur_y])
75
+ window
76
+ move_pointer(cur_y + 1, move_to=true)
77
+ when /u/i
78
+ cur_y = @cur_y - 1
79
+ Item::mark_as_unread(@items[cur_y])
80
+ window
81
+ move_pointer(cur_y + 1, move_to=true)
82
+ when /r/i
83
+ Feed::refresh_for(@feed)
84
+ window
85
+ when / /
86
+ content_screen = ContentScreen.new(@stdscr, @items[@cur_y - 1])
87
+ content_screen.loop
88
+
89
+ window
90
+ move_pointer(@cur_y, move_to=true)
91
+ end
92
+ else
93
+ case c
94
+ when Ncurses::KEY_UP
95
+ move_pointer(-1)
96
+ when Ncurses::KEY_DOWN
97
+ move_pointer(1)
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
data/lib/rutt/opml.rb ADDED
@@ -0,0 +1,21 @@
1
+ module Rutt
2
+ module Opml
3
+ def self.get_urls(file)
4
+ doc = Nokogiri::XML(open(file))
5
+
6
+ urls = []
7
+
8
+ doc.xpath('opml/body/outline').each do |outline|
9
+ if outline['xmlUrl']
10
+ urls << outline['xmlUrl']
11
+ else
12
+ (outline/'outline').each do |outline2|
13
+ urls << outline2['xmlUrl']
14
+ end
15
+ end
16
+ end
17
+
18
+ return urls
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,51 @@
1
+ module Rutt
2
+ class Screen
3
+ def initialize(stdscr)
4
+ @stdscr = stdscr
5
+
6
+ @min_y = 1
7
+ @max_y = @stdscr.getmaxy
8
+
9
+ @min_limit = @min_y - 1
10
+ @max_limit = @max_y - 5
11
+
12
+ @cur_y = 1
13
+ @cur_x = 0
14
+ end
15
+
16
+ def incr_page
17
+ @min_limit = @max_limit
18
+ @max_limit += (@max_y - 5)
19
+ end
20
+
21
+ def decr_page
22
+ @max_limit = @min_limit
23
+ @min_limit -= (@max_y - 5)
24
+
25
+ if @max_limit <= 0
26
+ @min_limit = @min_y - 1
27
+ @max_limit = @max_y - 5
28
+ end
29
+ end
30
+
31
+ def display_menu
32
+ @stdscr.clear
33
+ @stdscr.move(0, 0)
34
+ @stdscr.addstr(" rutt #{@menu}\n")
35
+ end
36
+
37
+ def move_pointer(pos, move_to=false)
38
+ @stdscr.move(@cur_y, 0)
39
+ @stdscr.addstr(" ")
40
+
41
+ if move_to == true
42
+ @cur_y = pos
43
+ else
44
+ @cur_y += pos
45
+ end
46
+
47
+ @stdscr.move(@cur_y, 0)
48
+ @stdscr.addstr(">")
49
+ end
50
+ end
51
+ end
data/rutt.gemspec CHANGED
@@ -5,13 +5,13 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{rutt}
8
- s.version = "0.3.2"
8
+ s.version = "0.3.3"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Abhi Yerra"]
12
12
  s.date = %q{2011-04-23}
13
13
  s.default_executable = %q{rutt}
14
- s.description = %q{Read RSS feeds from the commandline }
14
+ s.description = %q{The Mutt of RSS/Atom feeds.}
15
15
  s.email = %q{abhi@berkeley.edu}
16
16
  s.executables = ["rutt"]
17
17
  s.extra_rdoc_files = [
@@ -28,8 +28,16 @@ Gem::Specification.new do |s|
28
28
  "Rakefile",
29
29
  "VERSION",
30
30
  "bin/rutt",
31
- "lib/instapaper.rb",
32
31
  "lib/rutt.rb",
32
+ "lib/rutt/config.rb",
33
+ "lib/rutt/content_screen.rb",
34
+ "lib/rutt/feed.rb",
35
+ "lib/rutt/feed_screen.rb",
36
+ "lib/rutt/instapaper.rb",
37
+ "lib/rutt/item.rb",
38
+ "lib/rutt/item_screen.rb",
39
+ "lib/rutt/opml.rb",
40
+ "lib/rutt/screen.rb",
33
41
  "rutt.gemspec",
34
42
  "spec/rutt_spec.rb",
35
43
  "spec/spec_helper.rb"
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rutt
3
3
  version: !ruby/object:Gem::Version
4
- hash: 23
4
+ hash: 21
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 3
9
- - 2
10
- version: 0.3.2
9
+ - 3
10
+ version: 0.3.3
11
11
  platform: ruby
12
12
  authors:
13
13
  - Abhi Yerra
@@ -178,7 +178,7 @@ dependencies:
178
178
  name: rcov
179
179
  version_requirements: *id011
180
180
  prerelease: false
181
- description: "Read RSS feeds from the commandline "
181
+ description: The Mutt of RSS/Atom feeds.
182
182
  email: abhi@berkeley.edu
183
183
  executables:
184
184
  - rutt
@@ -197,8 +197,16 @@ files:
197
197
  - Rakefile
198
198
  - VERSION
199
199
  - bin/rutt
200
- - lib/instapaper.rb
201
200
  - lib/rutt.rb
201
+ - lib/rutt/config.rb
202
+ - lib/rutt/content_screen.rb
203
+ - lib/rutt/feed.rb
204
+ - lib/rutt/feed_screen.rb
205
+ - lib/rutt/instapaper.rb
206
+ - lib/rutt/item.rb
207
+ - lib/rutt/item_screen.rb
208
+ - lib/rutt/opml.rb
209
+ - lib/rutt/screen.rb
202
210
  - rutt.gemspec
203
211
  - spec/rutt_spec.rb
204
212
  - spec/spec_helper.rb
data/lib/instapaper.rb DELETED
@@ -1,34 +0,0 @@
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