google_reader 1.0.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/.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
|