rutt 0.3.3 → 0.4.0

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