google_reader 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/Gemfile +2 -0
- data/LICENSE +20 -0
- data/README.textile +56 -0
- data/autotest/discover.rb +1 -0
- data/google_reader.gemspec +25 -0
- data/lib/google_reader.rb +4 -0
- data/lib/google_reader/client.rb +58 -0
- data/lib/google_reader/entry.rb +87 -0
- data/lib/google_reader/version.rb +3 -0
- data/spec/client_spec.rb +99 -0
- data/spec/entry_spec.rb +110 -0
- data/spec/fixtures/entry-with-no-likes.xml +29 -0
- data/spec/fixtures/entry-with-no-original-id.xml +6 -0
- data/spec/fixtures/entry-with-unknown-author.xml +32 -0
- data/spec/fixtures/entry.xml +32 -0
- data/spec/spec_helper.rb +3 -0
- metadata +122 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008-2009 François Beausoleil (francois@teksol.info)
|
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 NONINFRINGEMENT.
|
17
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
18
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
19
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
20
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.textile
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
h1. Google Reader Client
|
2
|
+
|
3
|
+
Access your Google Reader account from Ruby:
|
4
|
+
|
5
|
+
<pre><code>require "google_reader"
|
6
|
+
# Get an instance of a client like this
|
7
|
+
client = GoogleReader::Client.authenticate(USERNAME, PASSWORD)
|
8
|
+
|
9
|
+
# Then, call any of these
|
10
|
+
client.unread_items
|
11
|
+
client.read_items
|
12
|
+
client.broadcast_items
|
13
|
+
client.body_link_used_items
|
14
|
+
client.item_link_used_items
|
15
|
+
client.clicked_through_items
|
16
|
+
client.emailed_items
|
17
|
+
client.starred_items
|
18
|
+
|
19
|
+
# To receive an Array of Atom Entries:
|
20
|
+
item = client.items.unread
|
21
|
+
p item.title
|
22
|
+
p item.categories
|
23
|
+
p item.id
|
24
|
+
p item.published_at
|
25
|
+
p item.updated_at
|
26
|
+
p item.href
|
27
|
+
p item.author_unknown?
|
28
|
+
p item.author
|
29
|
+
p item.source
|
30
|
+
p item.liking_users
|
31
|
+
</code></pre>
|
32
|
+
|
33
|
+
h2. LICENSE
|
34
|
+
|
35
|
+
(The MIT License)
|
36
|
+
|
37
|
+
Copyright (c) 2008-2009 François Beausoleil (francois@teksol.info)
|
38
|
+
|
39
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
40
|
+
a copy of this software and associated documentation files (the
|
41
|
+
'Software'), to deal in the Software without restriction, including
|
42
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
43
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
44
|
+
permit persons to whom the Software is furnished to do so, subject to
|
45
|
+
the following conditions:
|
46
|
+
|
47
|
+
The above copyright notice and this permission notice shall be
|
48
|
+
included in all copies or substantial portions of the Software.
|
49
|
+
|
50
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
51
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
52
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
53
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
54
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
55
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
56
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -0,0 +1 @@
|
|
1
|
+
Autotest.add_discovery { "rspec2" }
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "google_reader/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "google_reader"
|
7
|
+
s.version = GoogleReader::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["François Beausoleil"]
|
10
|
+
s.email = ["francois@teksol.info"]
|
11
|
+
s.homepage = "https://github.com/francois/google_reader"
|
12
|
+
s.summary = %q{An unofficial Ruby client for Google Reader }
|
13
|
+
s.description = %q{Access Google Reader in a quick and simple way, using plain Ruby.}
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
|
20
|
+
s.add_dependency "rest-client", "~> 1.6.1"
|
21
|
+
s.add_dependency "nokogiri", "~> 1.4.4"
|
22
|
+
|
23
|
+
s.add_development_dependency "rspec"
|
24
|
+
s.add_development_dependency "ruby-debug19"
|
25
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require "rest_client"
|
2
|
+
require "nokogiri"
|
3
|
+
require "cgi"
|
4
|
+
|
5
|
+
module GoogleReader
|
6
|
+
class Client
|
7
|
+
def self.authenticate(username, password)
|
8
|
+
resp = RestClient.post("https://www.google.com/accounts/ClientLogin",
|
9
|
+
:Email => username,
|
10
|
+
:Passwd => password,
|
11
|
+
:service => "reader")
|
12
|
+
|
13
|
+
dict = resp.split.inject(Hash.new) do |memo, encoded_pair|
|
14
|
+
key, value = encoded_pair.split("=").map {|val| CGI.unescape(val)}
|
15
|
+
memo[key] = value
|
16
|
+
memo
|
17
|
+
end
|
18
|
+
|
19
|
+
token = dict["Auth"]
|
20
|
+
new("Authorization" => "GoogleLogin auth=#{token}", "Accept" => "application/xml")
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_reader :headers
|
24
|
+
|
25
|
+
def initialize(headers)
|
26
|
+
@headers = headers
|
27
|
+
end
|
28
|
+
|
29
|
+
BASE_URL = "http://www.google.com/reader/atom/user/-/".freeze
|
30
|
+
STATE_URL = BASE_URL + "state/com.google/".freeze
|
31
|
+
|
32
|
+
%w(
|
33
|
+
read
|
34
|
+
broadcast
|
35
|
+
starred
|
36
|
+
subscriptions
|
37
|
+
tracking-emailed
|
38
|
+
tracking-item-link-used
|
39
|
+
tracking-body-link-used).each do |suffix|
|
40
|
+
|
41
|
+
method_name = suffix.gsub("-", "_") + "_items"
|
42
|
+
define_method(method_name) do |count=20|
|
43
|
+
content = RestClient.get(STATE_URL + suffix + "?n=#{CGI.escape(count.to_s)}", headers)
|
44
|
+
parse_atom_feed(content)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def unread_items
|
49
|
+
content = RestClient.get(STATE_URL + suffix + "?n=#{CGI.escape(count.to_s)}&xt=#{CGI.escape("state/com.google/read")}", headers)
|
50
|
+
parse_atom_feed(content)
|
51
|
+
end
|
52
|
+
|
53
|
+
def parse_atom_feed(feed)
|
54
|
+
doc = Nokogiri::XML(feed)
|
55
|
+
doc.search("entry").map {|entry| Entry.new(entry)}
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require "ostruct"
|
2
|
+
require "time"
|
3
|
+
|
4
|
+
module GoogleReader
|
5
|
+
class Entry
|
6
|
+
def initialize(entry)
|
7
|
+
@entry = entry
|
8
|
+
end
|
9
|
+
|
10
|
+
def id
|
11
|
+
id_node.text
|
12
|
+
end
|
13
|
+
|
14
|
+
def original_id
|
15
|
+
oid_node = id_node.attribute_with_ns("original-id", GOOGLE_ATOM_NAMESPACE)
|
16
|
+
oid_node ? oid_node.text : id
|
17
|
+
end
|
18
|
+
|
19
|
+
def title
|
20
|
+
unhtml(@entry.search("title").first.text)
|
21
|
+
end
|
22
|
+
|
23
|
+
def categories
|
24
|
+
@entry.search("category").reject do |node|
|
25
|
+
node["scheme"] == "http://www.google.com/reader/"
|
26
|
+
end.map do |node|
|
27
|
+
node["label"] || node["term"]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def published_at
|
32
|
+
Time.parse(@entry.search("published").first.text)
|
33
|
+
end
|
34
|
+
|
35
|
+
def updated_at
|
36
|
+
node = @entry.search("updated").first
|
37
|
+
node ? Time.parse(@entry.search("updated").first.text) : published_at
|
38
|
+
end
|
39
|
+
|
40
|
+
def href
|
41
|
+
@entry.search("link[rel=alternate]").reject do |node|
|
42
|
+
node["href"].to_s.empty?
|
43
|
+
end.detect do |node|
|
44
|
+
node["type"] == "text/html"
|
45
|
+
end["href"]
|
46
|
+
end
|
47
|
+
|
48
|
+
def has_known_author?
|
49
|
+
node = @entry.search("author").first
|
50
|
+
return false unless node
|
51
|
+
attr = node.attribute_with_ns("unknown-author", GOOGLE_ATOM_NAMESPACE)
|
52
|
+
return true unless attr
|
53
|
+
attr.text != "true"
|
54
|
+
end
|
55
|
+
|
56
|
+
def author
|
57
|
+
@entry.search("author name").first.text
|
58
|
+
end
|
59
|
+
|
60
|
+
def source
|
61
|
+
node = @entry.search("source").first
|
62
|
+
OpenStruct.new(:title => unhtml(node.search("title").first.text),
|
63
|
+
:href => node.search("link[rel=alternate]").first["href"],
|
64
|
+
:id => node.search("id").first.text,
|
65
|
+
:stream_id => node.attribute_with_ns("stream-id", GOOGLE_ATOM_NAMESPACE).text)
|
66
|
+
end
|
67
|
+
|
68
|
+
def liking_users
|
69
|
+
# NOTE: CSS namespaces don't work all that well: must use XPath here
|
70
|
+
@entry.search("./gr:likingUser", "gr" => GOOGLE_ATOM_NAMESPACE).map do |node|
|
71
|
+
node.text
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def id_node
|
76
|
+
nodes = @entry.search("id")
|
77
|
+
return nil if nodes.empty?
|
78
|
+
nodes.first
|
79
|
+
end
|
80
|
+
|
81
|
+
def unhtml(text)
|
82
|
+
text.gsub("<", "<").gsub(">", ">").gsub("&", "&").gsub(""", '"')
|
83
|
+
end
|
84
|
+
|
85
|
+
GOOGLE_ATOM_NAMESPACE = "http://www.google.com/schemas/reader/atom/".freeze
|
86
|
+
end
|
87
|
+
end
|
data/spec/client_spec.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe GoogleReader::Client, "#authenticate" do
|
4
|
+
it "should immediately authenticate using Google's client login" do
|
5
|
+
RestClient.should_receive(:post).and_return("Auth=abc")
|
6
|
+
GoogleReader::Client.authenticate("abc", "123")
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should use the provided username and password during the authentication process" do
|
10
|
+
RestClient.should_receive(:post).with("https://www.google.com/accounts/ClientLogin", :Email => "user", :Passwd => "pass", :service => "reader").and_return("Auth=abc")
|
11
|
+
GoogleReader::Client.authenticate("user", "pass")
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should instantiate a new GoogleReader::Client instance with the correct authentication header" do
|
15
|
+
RestClient.stub(:post).and_return("Auth=my-fancy-token")
|
16
|
+
GoogleReader::Client.should_receive(:new).with(hash_including("Authorization" => "GoogleLogin auth=my-fancy-token"))
|
17
|
+
GoogleReader::Client.authenticate("a", "b")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe GoogleReader::Client do
|
22
|
+
subject do
|
23
|
+
GoogleReader::Client.new("Authorization" => "GoogleLogin auth=my-fancy-token")
|
24
|
+
end
|
25
|
+
|
26
|
+
let :xml_content do
|
27
|
+
File.read(File.dirname(__FILE__) + "/fixtures/entry.xml")
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should fetch the default 20 items from #read_items" do
|
31
|
+
RestClient.should_receive(:get).with("http://www.google.com/reader/atom/user/-/state/com.google/read?n=20", anything).and_return(xml_content)
|
32
|
+
subject.read_items
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should fetch the requested number of items from #read_items" do
|
36
|
+
RestClient.should_receive(:get).with("http://www.google.com/reader/atom/user/-/state/com.google/read?n=59", anything).and_return(xml_content)
|
37
|
+
subject.read_items(59)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should fetch the default 20 items from #broadcast_items" do
|
41
|
+
RestClient.should_receive(:get).with("http://www.google.com/reader/atom/user/-/state/com.google/broadcast?n=20", anything).and_return(xml_content)
|
42
|
+
subject.broadcast_items
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should fetch the requested number of items from #broadcast_items" do
|
46
|
+
RestClient.should_receive(:get).with("http://www.google.com/reader/atom/user/-/state/com.google/broadcast?n=59", anything).and_return(xml_content)
|
47
|
+
subject.broadcast_items(59)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should fetch the default 20 items from #starred_items" do
|
51
|
+
RestClient.should_receive(:get).with("http://www.google.com/reader/atom/user/-/state/com.google/starred?n=20", anything).and_return(xml_content)
|
52
|
+
subject.starred_items
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should fetch the requested number of items from #starred_items" do
|
56
|
+
RestClient.should_receive(:get).with("http://www.google.com/reader/atom/user/-/state/com.google/starred?n=31", anything).and_return(xml_content)
|
57
|
+
subject.starred_items(31)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should fetch the default 20 items from #subscriptions_items" do
|
61
|
+
RestClient.should_receive(:get).with("http://www.google.com/reader/atom/user/-/state/com.google/subscriptions?n=20", anything).and_return(xml_content)
|
62
|
+
subject.subscriptions_items
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should fetch the requested number of items from #subscriptions_items" do
|
66
|
+
RestClient.should_receive(:get).with("http://www.google.com/reader/atom/user/-/state/com.google/subscriptions?n=19", anything).and_return(xml_content)
|
67
|
+
subject.subscriptions_items(19)
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should fetch the default 20 items from #tracking_emailed_items" do
|
71
|
+
RestClient.should_receive(:get).with("http://www.google.com/reader/atom/user/-/state/com.google/tracking-emailed?n=20", anything).and_return(xml_content)
|
72
|
+
subject.tracking_emailed_items
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should fetch the requested number of items from #tracking_emailed_items" do
|
76
|
+
RestClient.should_receive(:get).with("http://www.google.com/reader/atom/user/-/state/com.google/tracking-emailed?n=43", anything).and_return(xml_content)
|
77
|
+
subject.tracking_emailed_items(43)
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should fetch the default 20 items from #tracking_item_link_used_items" do
|
81
|
+
RestClient.should_receive(:get).with("http://www.google.com/reader/atom/user/-/state/com.google/tracking-item-link-used?n=20", anything).and_return(xml_content)
|
82
|
+
subject.tracking_item_link_used_items
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should fetch the requested number of items from #tracking_item_link_used_items" do
|
86
|
+
RestClient.should_receive(:get).with("http://www.google.com/reader/atom/user/-/state/com.google/tracking-item-link-used?n=43", anything).and_return(xml_content)
|
87
|
+
subject.tracking_item_link_used_items(43)
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should fetch the default 20 items from #tracking_body_link_used_items" do
|
91
|
+
RestClient.should_receive(:get).with("http://www.google.com/reader/atom/user/-/state/com.google/tracking-body-link-used?n=20", anything).and_return(xml_content)
|
92
|
+
subject.tracking_body_link_used_items
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should fetch the requested number of items from #tracking_body_link_used_items" do
|
96
|
+
RestClient.should_receive(:get).with("http://www.google.com/reader/atom/user/-/state/com.google/tracking-body-link-used?n=43", anything).and_return(xml_content)
|
97
|
+
subject.tracking_body_link_used_items(43)
|
98
|
+
end
|
99
|
+
end
|
data/spec/entry_spec.rb
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "set"
|
3
|
+
|
4
|
+
describe GoogleReader::Entry, "where the author is known, and the updated timestamp is different from the published timestamp, and with 3 liking users" do
|
5
|
+
let :entry_xml do
|
6
|
+
File.read(File.dirname(__FILE__) + "/fixtures/entry.xml")
|
7
|
+
end
|
8
|
+
|
9
|
+
let :entry do
|
10
|
+
Nokogiri::XML(entry_xml).root.search("entry").first
|
11
|
+
end
|
12
|
+
|
13
|
+
let :liking_users do
|
14
|
+
expected = Set.new
|
15
|
+
expected << "06592066937024742113"
|
16
|
+
expected << "07355112106994622540"
|
17
|
+
expected << "09036194539349082984"
|
18
|
+
end
|
19
|
+
|
20
|
+
subject do
|
21
|
+
GoogleReader::Entry.new(entry)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should HTML unescape the title" do
|
25
|
+
subject.title.should == "Find cheapest combination of rooms in hotels & other entries"
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should HTML unescape the source's title" do
|
29
|
+
subject.source.title.should == "select * from depesz where 1 < 41 and 1 > 41 and \"public\".sometable = 'asdf';"
|
30
|
+
end
|
31
|
+
|
32
|
+
it { subject.published_at.should == Time.utc(2011, 4, 27, 13, 28, 53) }
|
33
|
+
it { subject.updated_at.should == Time.utc(2011, 4, 28, 14, 28, 53) }
|
34
|
+
it { subject.href.should == "http://www.depesz.com/index.php/2011/04/27/find-cheapest-combination-of-rooms-in-hotels/" }
|
35
|
+
it { subject.should have_known_author }
|
36
|
+
it { subject.author.should == "depesz" }
|
37
|
+
it { subject.source.id.should == "tag:google.com,2005:reader/feed/http://www.depesz.com/index.php/feed/" }
|
38
|
+
it { subject.source.href == "http://www.depesz.com" }
|
39
|
+
it { subject.id.should == "tag:google.com,2005:reader/item/d5dee0c34e012ddb" }
|
40
|
+
it { subject.original_id.should == "http://www.depesz.com/?p=2149" }
|
41
|
+
|
42
|
+
it "should ignore Google categories" do
|
43
|
+
subject.categories.should_not include("read")
|
44
|
+
subject.categories.should_not include("fresh")
|
45
|
+
subject.categories.should_not include("tracking-item-link-used")
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should find the entry's categories" do
|
49
|
+
subject.categories.should include("Uncategorized")
|
50
|
+
subject.categories.should include("combinations")
|
51
|
+
subject.categories.should include("cte")
|
52
|
+
subject.categories.should include("postgresql")
|
53
|
+
subject.categories.should include("recursive")
|
54
|
+
subject.categories.should include("stackoverflow")
|
55
|
+
subject.categories.should include("with recursive")
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should find liking users" do
|
59
|
+
subject.liking_users.to_set.should == liking_users
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe GoogleReader::Entry, "where the author is unknown" do
|
64
|
+
let :entry_xml do
|
65
|
+
File.read(File.dirname(__FILE__) + "/fixtures/entry-with-unknown-author.xml")
|
66
|
+
end
|
67
|
+
|
68
|
+
let :entry do
|
69
|
+
Nokogiri::XML(entry_xml).root.search("entry").first
|
70
|
+
end
|
71
|
+
|
72
|
+
subject do
|
73
|
+
GoogleReader::Entry.new(entry)
|
74
|
+
end
|
75
|
+
|
76
|
+
it { subject.should_not have_known_author }
|
77
|
+
it { subject.author.should == "(author unknown)" }
|
78
|
+
end
|
79
|
+
|
80
|
+
describe GoogleReader::Entry, "where no users have liked the entry" do
|
81
|
+
let :entry_xml do
|
82
|
+
File.read(File.dirname(__FILE__) + "/fixtures/entry-with-no-likes.xml")
|
83
|
+
end
|
84
|
+
|
85
|
+
let :entry do
|
86
|
+
Nokogiri::XML(entry_xml).root.search("entry").first
|
87
|
+
end
|
88
|
+
|
89
|
+
subject do
|
90
|
+
GoogleReader::Entry.new(entry)
|
91
|
+
end
|
92
|
+
|
93
|
+
it { subject.liking_users.should be_empty }
|
94
|
+
end
|
95
|
+
|
96
|
+
describe GoogleReader::Entry, "when no original ID is present" do
|
97
|
+
let :entry_xml do
|
98
|
+
File.read(File.dirname(__FILE__) + "/fixtures/entry-with-no-original-id.xml")
|
99
|
+
end
|
100
|
+
|
101
|
+
let :entry do
|
102
|
+
Nokogiri::XML(entry_xml).root.search("entry").first
|
103
|
+
end
|
104
|
+
|
105
|
+
subject do
|
106
|
+
GoogleReader::Entry.new(entry)
|
107
|
+
end
|
108
|
+
|
109
|
+
it { subject.original_id.should == subject.id }
|
110
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
<?xml version="1.0"?>
|
2
|
+
<feed xmlns:idx="urn:atom-extension:indexing" xmlns:media="http://search.yahoo.com/mrss/" xmlns:gr="http://www.google.com/schemas/reader/atom/" xmlns="http://www.w3.org/2005/Atom" idx:index="no" gr:dir="ltr">
|
3
|
+
<entry gr:crawl-timestamp-msec="1303995951053">
|
4
|
+
<id gr:original-id="http://www.depesz.com/?p=2149">tag:google.com,2005:reader/item/d5dee0c34e012ddb</id>
|
5
|
+
<category term="user/10212770223479967035/state/com.google/read" scheme="http://www.google.com/reader/" label="read"/>
|
6
|
+
<category term="user/10212770223479967035/state/com.google/tracking-item-link-used" scheme="http://www.google.com/reader/" label="tracking-item-link-used"/>
|
7
|
+
<category term="user/10212770223479967035/state/com.google/fresh" scheme="http://www.google.com/reader/" label="fresh"/>
|
8
|
+
<category term="Uncategorized"/>
|
9
|
+
<category term="combinations"/>
|
10
|
+
<category term="cte"/>
|
11
|
+
<category term="postgresql"/>
|
12
|
+
<category term="recursive"/>
|
13
|
+
<category term="stackoverflow"/>
|
14
|
+
<category term="with recursive"/>
|
15
|
+
<title type="html">Find cheapest combination of rooms in hotels & other entries</title>
|
16
|
+
<published>2011-04-27T13:28:53Z</published>
|
17
|
+
<updated>2011-04-27T14:28:53Z</updated>
|
18
|
+
<link rel="alternate" href="http://www.depesz.com/index.php/2011/04/27/find-cheapest-combination-of-rooms-in-hotels/" type="text/html"/>
|
19
|
+
<summary xml:base="http://www.depesz.com/" type="html">Today, on Stack Overflow there was interesting question. Generally, given table that looks like this: room | people | price | hotel 1 | 1 | 200 | A 2 | 2 | 99 | A 3 | 3 | 95 | A 4 | 1 | 90 | B 5 | 6 | 300 [...]<img src="http://feeds.feedburner.com/~r/depesz/~4/5LKjrsu7JnQ" height="1" width="1"></summary>
|
20
|
+
<author gr:unknown-author="true">
|
21
|
+
<name>(author unknown)</name>
|
22
|
+
</author>
|
23
|
+
<source gr:stream-id="feed/http://www.depesz.com/index.php/feed/">
|
24
|
+
<id>tag:google.com,2005:reader/feed/http://www.depesz.com/index.php/feed/</id>
|
25
|
+
<title type="html">select * from depesz where 1 < 41 and 1 > 41 and "public".sometable = 'asdf';</title>
|
26
|
+
<link rel="alternate" href="http://www.depesz.com" type="text/html"/>
|
27
|
+
</source>
|
28
|
+
</entry>
|
29
|
+
</feed>
|
@@ -0,0 +1,6 @@
|
|
1
|
+
<?xml version="1.0"?>
|
2
|
+
<feed xmlns:idx="urn:atom-extension:indexing" xmlns:media="http://search.yahoo.com/mrss/" xmlns:gr="http://www.google.com/schemas/reader/atom/" xmlns="http://www.w3.org/2005/Atom" idx:index="no" gr:dir="ltr">
|
3
|
+
<entry gr:crawl-timestamp-msec="1303995951053">
|
4
|
+
<id>tag:google.com,2005:reader/item/d5dee0c34e012ddb</id>
|
5
|
+
</entry>
|
6
|
+
</feed>
|
@@ -0,0 +1,32 @@
|
|
1
|
+
<?xml version="1.0"?>
|
2
|
+
<feed xmlns:idx="urn:atom-extension:indexing" xmlns:media="http://search.yahoo.com/mrss/" xmlns:gr="http://www.google.com/schemas/reader/atom/" xmlns="http://www.w3.org/2005/Atom" idx:index="no" gr:dir="ltr">
|
3
|
+
<entry gr:crawl-timestamp-msec="1303995951053">
|
4
|
+
<id gr:original-id="http://www.depesz.com/?p=2149">tag:google.com,2005:reader/item/d5dee0c34e012ddb</id>
|
5
|
+
<category term="user/10212770223479967035/state/com.google/read" scheme="http://www.google.com/reader/" label="read"/>
|
6
|
+
<category term="user/10212770223479967035/state/com.google/tracking-item-link-used" scheme="http://www.google.com/reader/" label="tracking-item-link-used"/>
|
7
|
+
<category term="user/10212770223479967035/state/com.google/fresh" scheme="http://www.google.com/reader/" label="fresh"/>
|
8
|
+
<category term="Uncategorized"/>
|
9
|
+
<category term="combinations"/>
|
10
|
+
<category term="cte"/>
|
11
|
+
<category term="postgresql"/>
|
12
|
+
<category term="recursive"/>
|
13
|
+
<category term="stackoverflow"/>
|
14
|
+
<category term="with recursive"/>
|
15
|
+
<title type="html">Find cheapest combination of rooms in hotels & other entries</title>
|
16
|
+
<published>2011-04-27T13:28:53Z</published>
|
17
|
+
<updated>2011-04-27T14:28:53Z</updated>
|
18
|
+
<link rel="alternate" href="http://www.depesz.com/index.php/2011/04/27/find-cheapest-combination-of-rooms-in-hotels/" type="text/html"/>
|
19
|
+
<summary xml:base="http://www.depesz.com/" type="html">Today, on Stack Overflow there was interesting question. Generally, given table that looks like this: room | people | price | hotel 1 | 1 | 200 | A 2 | 2 | 99 | A 3 | 3 | 95 | A 4 | 1 | 90 | B 5 | 6 | 300 [...]<img src="http://feeds.feedburner.com/~r/depesz/~4/5LKjrsu7JnQ" height="1" width="1"></summary>
|
20
|
+
<author gr:unknown-author="true">
|
21
|
+
<name>(author unknown)</name>
|
22
|
+
</author>
|
23
|
+
<source gr:stream-id="feed/http://www.depesz.com/index.php/feed/">
|
24
|
+
<id>tag:google.com,2005:reader/feed/http://www.depesz.com/index.php/feed/</id>
|
25
|
+
<title type="html">select * from depesz where 1 < 41 and 1 > 41 and "public".sometable = 'asdf';</title>
|
26
|
+
<link rel="alternate" href="http://www.depesz.com" type="text/html"/>
|
27
|
+
</source>
|
28
|
+
<gr:likingUser>06592066937024742113</gr:likingUser>
|
29
|
+
<gr:likingUser>07355112106994622540</gr:likingUser>
|
30
|
+
<gr:likingUser>09036194539349082984</gr:likingUser>
|
31
|
+
</entry>
|
32
|
+
</feed>
|
@@ -0,0 +1,32 @@
|
|
1
|
+
<?xml version="1.0"?>
|
2
|
+
<feed xmlns:idx="urn:atom-extension:indexing" xmlns:media="http://search.yahoo.com/mrss/" xmlns:gr="http://www.google.com/schemas/reader/atom/" xmlns="http://www.w3.org/2005/Atom" idx:index="no" gr:dir="ltr">
|
3
|
+
<entry gr:crawl-timestamp-msec="1303995951053">
|
4
|
+
<id gr:original-id="http://www.depesz.com/?p=2149">tag:google.com,2005:reader/item/d5dee0c34e012ddb</id>
|
5
|
+
<category term="user/10212770223479967035/state/com.google/read" scheme="http://www.google.com/reader/" label="read"/>
|
6
|
+
<category term="user/10212770223479967035/state/com.google/tracking-item-link-used" scheme="http://www.google.com/reader/" label="tracking-item-link-used"/>
|
7
|
+
<category term="user/10212770223479967035/state/com.google/fresh" scheme="http://www.google.com/reader/" label="fresh"/>
|
8
|
+
<category term="Uncategorized"/>
|
9
|
+
<category term="combinations"/>
|
10
|
+
<category term="cte"/>
|
11
|
+
<category term="postgresql"/>
|
12
|
+
<category term="recursive"/>
|
13
|
+
<category term="stackoverflow"/>
|
14
|
+
<category term="with recursive"/>
|
15
|
+
<title type="html">Find cheapest combination of rooms in hotels & other entries</title>
|
16
|
+
<published>2011-04-27T13:28:53Z</published>
|
17
|
+
<updated>2011-04-28T14:28:53Z</updated>
|
18
|
+
<link rel="alternate" href="http://www.depesz.com/index.php/2011/04/27/find-cheapest-combination-of-rooms-in-hotels/" type="text/html"/>
|
19
|
+
<summary xml:base="http://www.depesz.com/" type="html">Today, on Stack Overflow there was interesting question. Generally, given table that looks like this: room | people | price | hotel 1 | 1 | 200 | A 2 | 2 | 99 | A 3 | 3 | 95 | A 4 | 1 | 90 | B 5 | 6 | 300 [...]<img src="http://feeds.feedburner.com/~r/depesz/~4/5LKjrsu7JnQ" height="1" width="1"></summary>
|
20
|
+
<author>
|
21
|
+
<name>depesz</name>
|
22
|
+
</author>
|
23
|
+
<gr:likingUser>06592066937024742113</gr:likingUser>
|
24
|
+
<gr:likingUser>07355112106994622540</gr:likingUser>
|
25
|
+
<gr:likingUser>09036194539349082984</gr:likingUser>
|
26
|
+
<source gr:stream-id="feed/http://www.depesz.com/index.php/feed/">
|
27
|
+
<id>tag:google.com,2005:reader/feed/http://www.depesz.com/index.php/feed/</id>
|
28
|
+
<title type="html">select * from depesz where 1 < 41 and 1 > 41 and "public".sometable = 'asdf';</title>
|
29
|
+
<link rel="alternate" href="http://www.depesz.com" type="text/html"/>
|
30
|
+
</source>
|
31
|
+
</entry>
|
32
|
+
</feed>
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: google_reader
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 1.0.0
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- "Fran\xC3\xA7ois Beausoleil"
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-05-03 00:00:00 -04:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: rest-client
|
18
|
+
prerelease: false
|
19
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
|
+
none: false
|
21
|
+
requirements:
|
22
|
+
- - ~>
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: 1.6.1
|
25
|
+
type: :runtime
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: nokogiri
|
29
|
+
prerelease: false
|
30
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
31
|
+
none: false
|
32
|
+
requirements:
|
33
|
+
- - ~>
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: 1.4.4
|
36
|
+
type: :runtime
|
37
|
+
version_requirements: *id002
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: rspec
|
40
|
+
prerelease: false
|
41
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: "0"
|
47
|
+
type: :development
|
48
|
+
version_requirements: *id003
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: ruby-debug19
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: "0"
|
58
|
+
type: :development
|
59
|
+
version_requirements: *id004
|
60
|
+
description: Access Google Reader in a quick and simple way, using plain Ruby.
|
61
|
+
email:
|
62
|
+
- francois@teksol.info
|
63
|
+
executables: []
|
64
|
+
|
65
|
+
extensions: []
|
66
|
+
|
67
|
+
extra_rdoc_files: []
|
68
|
+
|
69
|
+
files:
|
70
|
+
- .gitignore
|
71
|
+
- Gemfile
|
72
|
+
- LICENSE
|
73
|
+
- README.textile
|
74
|
+
- autotest/discover.rb
|
75
|
+
- google_reader.gemspec
|
76
|
+
- lib/google_reader.rb
|
77
|
+
- lib/google_reader/client.rb
|
78
|
+
- lib/google_reader/entry.rb
|
79
|
+
- lib/google_reader/version.rb
|
80
|
+
- spec/client_spec.rb
|
81
|
+
- spec/entry_spec.rb
|
82
|
+
- spec/fixtures/entry-with-no-likes.xml
|
83
|
+
- spec/fixtures/entry-with-no-original-id.xml
|
84
|
+
- spec/fixtures/entry-with-unknown-author.xml
|
85
|
+
- spec/fixtures/entry.xml
|
86
|
+
- spec/spec_helper.rb
|
87
|
+
has_rdoc: true
|
88
|
+
homepage: https://github.com/francois/google_reader
|
89
|
+
licenses: []
|
90
|
+
|
91
|
+
post_install_message:
|
92
|
+
rdoc_options: []
|
93
|
+
|
94
|
+
require_paths:
|
95
|
+
- lib
|
96
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: "0"
|
102
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
103
|
+
none: false
|
104
|
+
requirements:
|
105
|
+
- - ">="
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: "0"
|
108
|
+
requirements: []
|
109
|
+
|
110
|
+
rubyforge_project:
|
111
|
+
rubygems_version: 1.6.2
|
112
|
+
signing_key:
|
113
|
+
specification_version: 3
|
114
|
+
summary: An unofficial Ruby client for Google Reader
|
115
|
+
test_files:
|
116
|
+
- spec/client_spec.rb
|
117
|
+
- spec/entry_spec.rb
|
118
|
+
- spec/fixtures/entry-with-no-likes.xml
|
119
|
+
- spec/fixtures/entry-with-no-original-id.xml
|
120
|
+
- spec/fixtures/entry-with-unknown-author.xml
|
121
|
+
- spec/fixtures/entry.xml
|
122
|
+
- spec/spec_helper.rb
|