rutt 0.3.3 → 0.4.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/Gemfile CHANGED
@@ -7,6 +7,7 @@ gem "parallel"
7
7
  gem "ruby-feedparser"
8
8
  gem "sqlite3"
9
9
  gem "oauth"
10
+ gem "ruby-readability", :require => 'readability'
10
11
 
11
12
  group :development do
12
13
  gem "rspec", "~> 2.3.0"
data/Gemfile.lock CHANGED
@@ -26,6 +26,8 @@ GEM
26
26
  diff-lcs (~> 1.1.2)
27
27
  rspec-mocks (2.3.0)
28
28
  ruby-feedparser (0.7)
29
+ ruby-readability (0.2.3)
30
+ nokogiri (>= 1.4.2)
29
31
  sqlite3 (1.3.3)
30
32
 
31
33
  PLATFORMS
@@ -42,4 +44,5 @@ DEPENDENCIES
42
44
  rcov
43
45
  rspec (~> 2.3.0)
44
46
  ruby-feedparser
47
+ ruby-readability
45
48
  sqlite3
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.3
1
+ 0.4.0
data/bin/rutt CHANGED
@@ -14,6 +14,8 @@ require 'feedparser'
14
14
  require 'parallel'
15
15
  require 'instapaper'
16
16
  require 'oauth'
17
+ require 'readability'
18
+ require 'open-uri'
17
19
 
18
20
  require 'rutt'
19
21
 
@@ -30,7 +32,7 @@ def start_screen
30
32
 
31
33
  stdscr.clear
32
34
 
33
- feed_screen = Rutt::FeedScreen.new(stdscr)
35
+ feed_screen = Rutt::Screen::Feed.new(stdscr)
34
36
  feed_screen.loop
35
37
  ensure
36
38
  Ncurses.endwin()
@@ -39,21 +41,21 @@ end
39
41
 
40
42
  def main
41
43
 
42
- Rutt::Config.make_table!
43
- Rutt::Feed.make_table!
44
- Rutt::Item.make_table!
44
+ Rutt::DB::Config.make_table!
45
+ Rutt::DB::Feed.make_table!
46
+ Rutt::DB::Item.make_table!
45
47
 
46
48
  options = {}
47
49
  OptionParser.new do |opts|
48
50
  opts.banner = "Usage: rutt.rb [options]"
49
51
 
50
52
  opts.on('-a', '--add FEED', "Add a feed") do |url|
51
- Rutt::Feed::add(url)
53
+ Rutt::DB::Feed::add(url)
52
54
  exit
53
55
  end
54
56
 
55
57
  opts.on('-r', '--refresh', "Refresh feeds.") do
56
- Rutt::Feed::refresh
58
+ Rutt::DB::Feed::refresh
57
59
  exit
58
60
  end
59
61
 
@@ -62,14 +64,14 @@ def main
62
64
 
63
65
  urls.each do |url|
64
66
  puts "Adding #{url}"
65
- Rutt::Feed::add(url, false)
67
+ Rutt::DB::Feed::add(url, false)
66
68
  end
67
69
 
68
70
  exit
69
71
  end
70
72
 
71
73
  opts.on('-s', '--set-key', "Set config") do |key, value|
72
- Rutt::Config.set(ARGV[0], ARGV[1])
74
+ Rutt::DB::Config.set(ARGV[0], ARGV[1])
73
75
  exit
74
76
  end
75
77
 
@@ -77,10 +79,10 @@ def main
77
79
 
78
80
  end.parse!
79
81
 
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")
82
+ consumer_key = Rutt::DB::Config.get("instapaper.consumer-key")
83
+ consumer_secret = Rutt::DB::Config.get("instapaper.consumer-secret")
84
+ username = Rutt::DB::Config.get("instapaper.username")
85
+ password = Rutt::DB::Config.get("instapaper.password")
84
86
 
85
87
  $instapaper = Rutt::Instapaper::API.new(consumer_key, consumer_secret)
86
88
  $instapaper.authorize(username, password)
data/lib/rutt.rb CHANGED
@@ -1,9 +1,10 @@
1
- require 'rutt/config'
2
- require 'rutt/opml'
3
- require 'rutt/instapaper'
4
- require 'rutt/feed'
5
- require 'rutt/item'
1
+ require 'rutt/db/config'
2
+ require 'rutt/db/feed'
3
+ require 'rutt/db/item'
6
4
  require 'rutt/screen'
7
- require 'rutt/feed_screen'
8
- require 'rutt/item_screen'
9
- require 'rutt/content_screen'
5
+ require 'rutt/screen/feed'
6
+ require 'rutt/screen/item'
7
+ require 'rutt/screen/content'
8
+ require 'rutt/share/instapaper'
9
+ require 'rutt/opml'
10
+
@@ -0,0 +1,25 @@
1
+ module Rutt
2
+ module DB
3
+ module Config
4
+ extend self
5
+
6
+ def make_table!
7
+ $db.execute(%{
8
+ create table if not exists config (
9
+ id integer PRIMARY KEY,
10
+ key text,
11
+ value text,
12
+ UNIQUE(key))
13
+ })
14
+ end
15
+
16
+ def get(key)
17
+ $db.get_first_value("select value from config where key = ?", key)
18
+ end
19
+
20
+ def set(key, value)
21
+ $db.execute("insert or replace into config(key, value) values (?, ?)", key, value)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,75 @@
1
+ module Rutt
2
+ module DB
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
75
+ end
@@ -0,0 +1,43 @@
1
+ module Rutt
2
+ module DB
3
+ module Item
4
+
5
+ extend self
6
+
7
+ def make_table!
8
+ $db.execute(%{
9
+ create table if not exists items (
10
+ id integer PRIMARY KEY,
11
+ feed_id integer,
12
+ title text,
13
+ url text,
14
+ description text,
15
+ read int default 0,
16
+ prioritize int default 0,
17
+ published_at DATE NOT NULL DEFAULT (datetime('now','localtime')),
18
+ created_at NOT NULL DEFAULT CURRENT_TIMESTAMP,
19
+ updated_at NOT NULL DEFAULT CURRENT_TIMESTAMP,
20
+ UNIQUE(url),
21
+ FOREIGN KEY(feed_id) REFERENCES feeds(id))
22
+ })
23
+ end
24
+
25
+ def all(feed, min_limit=0, max_limit=-1)
26
+ $db.execute("select * from items where feed_id = ? order by published_at desc limit #{min_limit},#{max_limit}", feed['id'])
27
+ end
28
+
29
+ def mark_as_unread(item)
30
+ $db.execute("update items set read = 0 where id = #{item['id']}")
31
+ end
32
+
33
+ def mark_as_read(item)
34
+ $db.execute("update items set read = 1 where id = #{item['id']}")
35
+ end
36
+
37
+ # Weak abstraction.
38
+ def sent_to_instapaper(item)
39
+ $db.execute("update items set read = 2 where id = #{item['id']}")
40
+ end
41
+ end
42
+ end
43
+ end
data/lib/rutt/screen.rb CHANGED
@@ -1,51 +1,53 @@
1
1
  module Rutt
2
- class Screen
3
- def initialize(stdscr)
4
- @stdscr = stdscr
2
+ module Screen
3
+ class Base
4
+ def initialize(stdscr)
5
+ @stdscr = stdscr
5
6
 
6
- @min_y = 1
7
- @max_y = @stdscr.getmaxy
7
+ @min_y = 1
8
+ @max_y = @stdscr.getmaxy
8
9
 
9
- @min_limit = @min_y - 1
10
- @max_limit = @max_y - 5
10
+ @min_limit = @min_y - 1
11
+ @max_limit = @max_y - 5
11
12
 
12
- @cur_y = 1
13
- @cur_x = 0
14
- end
13
+ @cur_y = 1
14
+ @cur_x = 0
15
+ end
15
16
 
16
- def incr_page
17
- @min_limit = @max_limit
18
- @max_limit += (@max_y - 5)
19
- end
17
+ def incr_page
18
+ @min_limit = @max_limit
19
+ @max_limit += (@max_y - 5)
20
+ end
20
21
 
21
- def decr_page
22
- @max_limit = @min_limit
23
- @min_limit -= (@max_y - 5)
22
+ def decr_page
23
+ @max_limit = @min_limit
24
+ @min_limit -= (@max_y - 5)
24
25
 
25
- if @max_limit <= 0
26
- @min_limit = @min_y - 1
27
- @max_limit = @max_y - 5
26
+ if @max_limit <= 0
27
+ @min_limit = @min_y - 1
28
+ @max_limit = @max_y - 5
29
+ end
28
30
  end
29
- end
30
31
 
31
- def display_menu
32
- @stdscr.clear
33
- @stdscr.move(0, 0)
34
- @stdscr.addstr(" rutt #{@menu}\n")
35
- end
32
+ def display_menu
33
+ @stdscr.clear
34
+ @stdscr.move(0, 0)
35
+ @stdscr.addstr(" rutt #{@menu}\n")
36
+ end
36
37
 
37
- def move_pointer(pos, move_to=false)
38
- @stdscr.move(@cur_y, 0)
39
- @stdscr.addstr(" ")
38
+ def move_pointer(pos, move_to=false)
39
+ @stdscr.move(@cur_y, 0)
40
+ @stdscr.addstr(" ")
40
41
 
41
- if move_to == true
42
- @cur_y = pos
43
- else
44
- @cur_y += pos
45
- end
42
+ if move_to == true
43
+ @cur_y = pos
44
+ else
45
+ @cur_y += pos
46
+ end
46
47
 
47
- @stdscr.move(@cur_y, 0)
48
- @stdscr.addstr(">")
48
+ @stdscr.move(@cur_y, 0)
49
+ @stdscr.addstr(">")
50
+ end
49
51
  end
50
52
  end
51
53
  end
@@ -0,0 +1,64 @@
1
+ module Rutt
2
+ module Screen
3
+ class Content < Base
4
+ def initialize(stdscr, item)
5
+ @item = item
6
+ @menu = "i:back b:open in browser"
7
+
8
+ super(stdscr)
9
+ end
10
+
11
+ def display_content
12
+ # @content = `elinks -dump -dump-charset ascii -force-html #{@item['url']}`
13
+ source = open(@item['url']).read
14
+ content = Nokogiri::HTML(::Readability::Document.new(source).content).text
15
+
16
+ @content = content.split("\n").map do |s|
17
+ s.gsub(/.{0,74}(?:\s|\Z)/){($& + 5.chr).gsub(/\n\005/,"\n")}.gsub(/((\n|^)[>|\s]*[>|].*?)\005/, "\\1").gsub(/\005/,"\n").split("\n") << "\n"
18
+ end.flatten
19
+
20
+ @stdscr.addstr(" #{@item['title']} (#{@item['url']})\n\n")
21
+
22
+ lines = @content[@min_limit..@max_limit]
23
+ lines.each { |line| @stdscr.addstr(" #{line}\n") } if lines
24
+
25
+ @stdscr.refresh
26
+ end
27
+
28
+ def window
29
+ @stdscr.clear
30
+ display_menu
31
+ display_content
32
+ end
33
+
34
+ def loop
35
+ @cur_line = 0
36
+
37
+ window
38
+
39
+ while true do
40
+ c = @stdscr.getch
41
+ if c > 0 && c < 255
42
+ case c.chr
43
+ when /[iq]/i
44
+ DB::Item::mark_as_read(@item)
45
+ break
46
+ when /b/i
47
+ Launchy.open(@item['url'])
48
+ end
49
+ else
50
+ case c
51
+ when Ncurses::KEY_UP
52
+ decr_page
53
+ window
54
+ when Ncurses::KEY_DOWN
55
+ incr_page
56
+ window
57
+ end
58
+ end
59
+ end
60
+
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,95 @@
1
+ module Rutt
2
+ module Screen
3
+ class Feed < Base
4
+ def initialize(stdscr)
5
+ @menu = "q:Quit d:delete r:refresh all"
6
+
7
+ super(stdscr)
8
+ end
9
+
10
+ def display_feeds
11
+ @cur_y = @min_y
12
+
13
+ @feeds = DB::Feed::all(@min_limit, @max_limit)
14
+ @feeds.each do |feed|
15
+ # next if feed['unread'] == 0 # This should be configurable: feed.showread
16
+
17
+ @stdscr.move(@cur_y, 0)
18
+ @stdscr.addstr(" #{feed['unread']}/#{feed['num_items']}\t\t#{feed['title']}\n")
19
+
20
+ @cur_y += 1
21
+ end
22
+
23
+ @cur_y = @min_y
24
+ @stdscr.refresh
25
+ end
26
+
27
+ def window
28
+ @stdscr.clear
29
+
30
+ display_menu
31
+ display_feeds
32
+ move_pointer(0)
33
+ end
34
+
35
+ def loop
36
+ window
37
+
38
+ while true do
39
+ c = @stdscr.getch
40
+
41
+ if c > 0 && c < 255
42
+ case c.chr
43
+ when /q/i
44
+ break
45
+ when /a/i
46
+ # no-op
47
+ when /r/i
48
+ cur_y = @cur_y
49
+
50
+ DB::Feed::refresh
51
+
52
+ window
53
+ move_pointer(cur_y, move_to=true)
54
+ when /d/i
55
+ cur_y = @cur_y - 1
56
+
57
+ @stdscr.clear
58
+ display_menu
59
+ feed = @feeds[cur_y]
60
+ @stdscr.move(2, 0)
61
+ @stdscr.addstr("Are you sure you want to delete #{feed['title']}? ")
62
+ d = @stdscr.getch
63
+ if d.chr =~ /y/i
64
+ DB::Feed::delete(feed)
65
+ end
66
+ window
67
+ move_pointer(cur_y, move_to=true)
68
+ when /p/i
69
+ decr_page
70
+ window
71
+ when /n/i
72
+ incr_page
73
+ window
74
+ when / /
75
+ cur_y = @cur_y
76
+ @stdscr.addstr("#{@feeds[cur_y]}")
77
+ item_screen = Item.new(@stdscr, @feeds[cur_y - 1])
78
+ item_screen.loop
79
+
80
+ window
81
+ move_pointer(cur_y, move_to=true)
82
+ end
83
+ else
84
+ case c
85
+ when Ncurses::KEY_UP
86
+ move_pointer(-1)
87
+ when Ncurses::KEY_DOWN
88
+ move_pointer(1)
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,105 @@
1
+ module Rutt
2
+ module Screen
3
+ class Item < Base
4
+ def initialize(stdscr, feed)
5
+ @feed = feed
6
+ @menu = " i:quit r:refresh m:mark as read u:mark as unread a:mark all as read b:open in browser"
7
+
8
+ super(stdscr)
9
+ end
10
+
11
+ def display_items
12
+ @cur_y = @min_y
13
+
14
+ @items = DB::Item::all(@feed, @min_limit, @max_limit)
15
+ @items.each do |item|
16
+ item_status = case item['read'].to_i
17
+ when 0 then 'N'
18
+ when 1 then ' '
19
+ when 2 then 'I'
20
+ else ' '
21
+ end
22
+ @stdscr.addstr(" #{item_status}\t#{Time.at(item['published_at']).strftime("%b %d, %Y %R:%M")}\t#{item['title']}\n")
23
+ @cur_y += 1
24
+ end
25
+
26
+ @cur_y = @min_y
27
+ @stdscr.refresh
28
+ end
29
+
30
+ def window
31
+ @stdscr.clear
32
+
33
+ display_menu
34
+ display_items
35
+ move_pointer(0)
36
+ end
37
+
38
+ def loop
39
+ window
40
+
41
+ while true do
42
+ c = @stdscr.getch
43
+
44
+ if c > 0 && c < 255
45
+ case c.chr
46
+ when /[iq]/i
47
+ break
48
+ when /s/i
49
+ cur_y = @cur_y - 1
50
+ $instapaper.request('/api/1/bookmarks/add', {
51
+ 'url' => @items[cur_y]['url'],
52
+ 'title' => @items[cur_y]['title'],
53
+ })
54
+ DB::Item::sent_to_instapaper(@items[cur_y])
55
+ window
56
+ move_pointer(@cur_y, move_to=true)
57
+ when /a/i
58
+ DB::Feed::mark_as_read(@feed)
59
+ window
60
+ move_pointer(@cur_y, move_to=true)
61
+ when /p/i
62
+ decr_page
63
+ window
64
+ when /n/i
65
+ incr_page
66
+ window
67
+ when /b/i
68
+ cur_y = @cur_y - 1
69
+ DB::Item::mark_as_read(@items[cur_y])
70
+ Launchy.open(@items[cur_y]['url'])
71
+ window
72
+ move_pointer(@cur_y, move_to=true)
73
+ when /m/i
74
+ cur_y = @cur_y - 1
75
+ DB::Item::mark_as_read(@items[cur_y])
76
+ window
77
+ move_pointer(cur_y + 1, move_to=true)
78
+ when /u/i
79
+ cur_y = @cur_y - 1
80
+ DB::Item::mark_as_unread(@items[cur_y])
81
+ window
82
+ move_pointer(cur_y + 1, move_to=true)
83
+ when /r/i
84
+ DB::Feed::refresh_for(@feed)
85
+ window
86
+ when / /
87
+ content_screen = Content.new(@stdscr, @items[@cur_y - 1])
88
+ content_screen.loop
89
+
90
+ window
91
+ move_pointer(@cur_y, move_to=true)
92
+ end
93
+ else
94
+ case c
95
+ when Ncurses::KEY_UP
96
+ move_pointer(-1)
97
+ when Ncurses::KEY_DOWN
98
+ move_pointer(1)
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
File without changes
data/rutt.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{rutt}
8
- s.version = "0.3.3"
8
+ s.version = "0.4.0"
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
- s.date = %q{2011-04-23}
12
+ s.date = %q{2011-05-19}
13
13
  s.default_executable = %q{rutt}
14
14
  s.description = %q{The Mutt of RSS/Atom feeds.}
15
15
  s.email = %q{abhi@berkeley.edu}
@@ -29,15 +29,15 @@ Gem::Specification.new do |s|
29
29
  "VERSION",
30
30
  "bin/rutt",
31
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",
32
+ "lib/rutt/db/config.rb",
33
+ "lib/rutt/db/feed.rb",
34
+ "lib/rutt/db/item.rb",
39
35
  "lib/rutt/opml.rb",
40
36
  "lib/rutt/screen.rb",
37
+ "lib/rutt/screen/content.rb",
38
+ "lib/rutt/screen/feed.rb",
39
+ "lib/rutt/screen/item.rb",
40
+ "lib/rutt/share/instapaper.rb",
41
41
  "rutt.gemspec",
42
42
  "spec/rutt_spec.rb",
43
43
  "spec/spec_helper.rb"
@@ -63,6 +63,7 @@ Gem::Specification.new do |s|
63
63
  s.add_runtime_dependency(%q<ruby-feedparser>, [">= 0"])
64
64
  s.add_runtime_dependency(%q<sqlite3>, [">= 0"])
65
65
  s.add_runtime_dependency(%q<oauth>, [">= 0"])
66
+ s.add_runtime_dependency(%q<ruby-readability>, [">= 0"])
66
67
  s.add_development_dependency(%q<rspec>, ["~> 2.3.0"])
67
68
  s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
68
69
  s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
@@ -75,6 +76,7 @@ Gem::Specification.new do |s|
75
76
  s.add_dependency(%q<ruby-feedparser>, [">= 0"])
76
77
  s.add_dependency(%q<sqlite3>, [">= 0"])
77
78
  s.add_dependency(%q<oauth>, [">= 0"])
79
+ s.add_dependency(%q<ruby-readability>, [">= 0"])
78
80
  s.add_dependency(%q<rspec>, ["~> 2.3.0"])
79
81
  s.add_dependency(%q<bundler>, ["~> 1.0.0"])
80
82
  s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
@@ -88,6 +90,7 @@ Gem::Specification.new do |s|
88
90
  s.add_dependency(%q<ruby-feedparser>, [">= 0"])
89
91
  s.add_dependency(%q<sqlite3>, [">= 0"])
90
92
  s.add_dependency(%q<oauth>, [">= 0"])
93
+ s.add_dependency(%q<ruby-readability>, [">= 0"])
91
94
  s.add_dependency(%q<rspec>, ["~> 2.3.0"])
92
95
  s.add_dependency(%q<bundler>, ["~> 1.0.0"])
93
96
  s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
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: 21
4
+ hash: 15
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 3
9
- - 3
10
- version: 0.3.3
8
+ - 4
9
+ - 0
10
+ version: 0.4.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Abhi Yerra
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-04-23 00:00:00 +00:00
18
+ date: 2011-05-19 00:00:00 +00:00
19
19
  default_executable: rutt
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -117,8 +117,22 @@ dependencies:
117
117
  version_requirements: *id007
118
118
  prerelease: false
119
119
  - !ruby/object:Gem::Dependency
120
- type: :development
120
+ type: :runtime
121
121
  requirement: &id008 !ruby/object:Gem::Requirement
122
+ none: false
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ hash: 3
127
+ segments:
128
+ - 0
129
+ version: "0"
130
+ name: ruby-readability
131
+ version_requirements: *id008
132
+ prerelease: false
133
+ - !ruby/object:Gem::Dependency
134
+ type: :development
135
+ requirement: &id009 !ruby/object:Gem::Requirement
122
136
  none: false
123
137
  requirements:
124
138
  - - ~>
@@ -130,11 +144,11 @@ dependencies:
130
144
  - 0
131
145
  version: 2.3.0
132
146
  name: rspec
133
- version_requirements: *id008
147
+ version_requirements: *id009
134
148
  prerelease: false
135
149
  - !ruby/object:Gem::Dependency
136
150
  type: :development
137
- requirement: &id009 !ruby/object:Gem::Requirement
151
+ requirement: &id010 !ruby/object:Gem::Requirement
138
152
  none: false
139
153
  requirements:
140
154
  - - ~>
@@ -146,11 +160,11 @@ dependencies:
146
160
  - 0
147
161
  version: 1.0.0
148
162
  name: bundler
149
- version_requirements: *id009
163
+ version_requirements: *id010
150
164
  prerelease: false
151
165
  - !ruby/object:Gem::Dependency
152
166
  type: :development
153
- requirement: &id010 !ruby/object:Gem::Requirement
167
+ requirement: &id011 !ruby/object:Gem::Requirement
154
168
  none: false
155
169
  requirements:
156
170
  - - ~>
@@ -162,11 +176,11 @@ dependencies:
162
176
  - 2
163
177
  version: 1.5.2
164
178
  name: jeweler
165
- version_requirements: *id010
179
+ version_requirements: *id011
166
180
  prerelease: false
167
181
  - !ruby/object:Gem::Dependency
168
182
  type: :development
169
- requirement: &id011 !ruby/object:Gem::Requirement
183
+ requirement: &id012 !ruby/object:Gem::Requirement
170
184
  none: false
171
185
  requirements:
172
186
  - - ">="
@@ -176,7 +190,7 @@ dependencies:
176
190
  - 0
177
191
  version: "0"
178
192
  name: rcov
179
- version_requirements: *id011
193
+ version_requirements: *id012
180
194
  prerelease: false
181
195
  description: The Mutt of RSS/Atom feeds.
182
196
  email: abhi@berkeley.edu
@@ -198,15 +212,15 @@ files:
198
212
  - VERSION
199
213
  - bin/rutt
200
214
  - 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
215
+ - lib/rutt/db/config.rb
216
+ - lib/rutt/db/feed.rb
217
+ - lib/rutt/db/item.rb
208
218
  - lib/rutt/opml.rb
209
219
  - lib/rutt/screen.rb
220
+ - lib/rutt/screen/content.rb
221
+ - lib/rutt/screen/feed.rb
222
+ - lib/rutt/screen/item.rb
223
+ - lib/rutt/share/instapaper.rb
210
224
  - rutt.gemspec
211
225
  - spec/rutt_spec.rb
212
226
  - spec/spec_helper.rb
data/lib/rutt/config.rb DELETED
@@ -1,23 +0,0 @@
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
@@ -1,57 +0,0 @@
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 DELETED
@@ -1,74 +0,0 @@
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
@@ -1,93 +0,0 @@
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
data/lib/rutt/item.rb DELETED
@@ -1,40 +0,0 @@
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
@@ -1,103 +0,0 @@
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