google-spreadsheet-ruby 0.1.8 → 0.2.1

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.
@@ -0,0 +1,84 @@
1
+ # Author: Hiroshi Ichikawa <http://gimite.net/>
2
+ # The license of this source is "New BSD Licence"
3
+
4
+ require "forwardable"
5
+
6
+ require "google_spreadsheet/util"
7
+ require "google_spreadsheet/error"
8
+
9
+
10
+ module GoogleSpreadsheet
11
+
12
+ # Hash-like object returned by GoogleSpreadsheet::List#[].
13
+ class ListRow
14
+
15
+ include(Enumerable)
16
+ extend(Forwardable)
17
+
18
+ def_delegators(:to_hash,
19
+ :keys, :values, :each_key, :each_value, :each, :each_pair, :hash,
20
+ :assoc, :fetch, :flatten, :key, :invert, :size, :length, :rassoc,
21
+ :merge, :reject, :select, :sort, :to_a, :values_at)
22
+
23
+ def initialize(list, index) #:nodoc:
24
+ @list = list
25
+ @index = index
26
+ end
27
+
28
+ def [](key)
29
+ return @list.get(@index, key)
30
+ end
31
+
32
+ def []=(key, value)
33
+ @list.set(@index, key, value)
34
+ end
35
+
36
+ def has_key?(key)
37
+ return @list.keys.include?(key)
38
+ end
39
+
40
+ alias include? has_key?
41
+ alias key? has_key?
42
+ alias member? has_key?
43
+
44
+ def update(hash)
45
+ for k, v in hash
46
+ self[k] = v
47
+ end
48
+ end
49
+
50
+ alias merge! update
51
+
52
+ def replace(hash)
53
+ clear()
54
+ update(hash)
55
+ end
56
+
57
+ def clear()
58
+ for key in @list.keys
59
+ self[key] = ""
60
+ end
61
+ end
62
+
63
+ def to_hash()
64
+ result = {}
65
+ for key in @list.keys
66
+ result[key] = self[key]
67
+ end
68
+ return result
69
+ end
70
+
71
+ def ==(other)
72
+ return self.class == other.class && self.to_hash() == other.to_hash()
73
+ end
74
+
75
+ alias === ==
76
+ alias eql? ==
77
+
78
+ def inspect
79
+ return "\#<%p %p>" % [self.class, to_hash()]
80
+ end
81
+
82
+ end
83
+
84
+ end
@@ -0,0 +1,26 @@
1
+ # Author: Hiroshi Ichikawa <http://gimite.net/>
2
+ # The license of this source is "New BSD Licence"
3
+
4
+ require "rubygems"
5
+ require "oauth"
6
+
7
+
8
+ module GoogleSpreadsheet
9
+
10
+ class OAuth1Fetcher #:nodoc:
11
+
12
+ def initialize(oauth1_token)
13
+ @oauth1_token = oauth1_token
14
+ end
15
+
16
+ def request_raw(method, url, data, extra_header, auth)
17
+ if method == :delete || method == :get
18
+ return @oauth1_token.__send__(method, url, extra_header)
19
+ else
20
+ return @oauth1_token.__send__(method, url, data, extra_header)
21
+ end
22
+ end
23
+
24
+ end
25
+
26
+ end
@@ -0,0 +1,29 @@
1
+ # Author: Hiroshi Ichikawa <http://gimite.net/>
2
+ # The license of this source is "New BSD Licence"
3
+
4
+ require "rubygems"
5
+ require "oauth2"
6
+
7
+
8
+ module GoogleSpreadsheet
9
+
10
+ class OAuth2Fetcher #:nodoc:
11
+
12
+ Response = Struct.new(:code, :body)
13
+
14
+ def initialize(oauth2_token)
15
+ @oauth2_token = oauth2_token
16
+ end
17
+
18
+ def request_raw(method, url, data, extra_header, auth)
19
+ if method == :delete || method == :get
20
+ raw_res = @oauth2_token.request(method, url, {:header => extra_header})
21
+ else
22
+ raw_res = @oauth2_token.request(method, url, {:header => extra_header, :body => data})
23
+ end
24
+ return Response.new(raw_res.status.to_s(), raw_res.body)
25
+ end
26
+
27
+ end
28
+
29
+ end
@@ -0,0 +1,31 @@
1
+ # Author: Hiroshi Ichikawa <http://gimite.net/>
2
+ # The license of this source is "New BSD Licence"
3
+
4
+ require "google_spreadsheet/util"
5
+ require "google_spreadsheet/error"
6
+
7
+
8
+ module GoogleSpreadsheet
9
+
10
+ # DEPRECATED: Table and Record feeds are deprecated and they will not be available after
11
+ # March 2012.
12
+ #
13
+ # Use GoogleSpreadsheet::Table#records to get GoogleSpreadsheet::Record objects.
14
+ class Record < Hash
15
+ include(Util)
16
+
17
+ def initialize(session, entry) #:nodoc:
18
+ @session = session
19
+ entry.css("gs|field").each() do |field|
20
+ self[field["name"]] = field.inner_text
21
+ end
22
+ end
23
+
24
+ def inspect #:nodoc:
25
+ content = self.map(){ |k, v| "%p => %p" % [k, v] }.join(", ")
26
+ return "\#<%p:{%s}>" % [self.class, content]
27
+ end
28
+
29
+ end
30
+
31
+ end
@@ -0,0 +1,297 @@
1
+ # Author: Hiroshi Ichikawa <http://gimite.net/>
2
+ # The license of this source is "New BSD Licence"
3
+
4
+ require "cgi"
5
+
6
+ require "rubygems"
7
+ require "nokogiri"
8
+
9
+ require "google_spreadsheet/util"
10
+ require "google_spreadsheet/client_login_fetcher"
11
+ require "google_spreadsheet/oauth1_fetcher"
12
+ require "google_spreadsheet/oauth2_fetcher"
13
+ require "google_spreadsheet/error"
14
+ require "google_spreadsheet/authentication_error"
15
+ require "google_spreadsheet/spreadsheet"
16
+ require "google_spreadsheet/worksheet"
17
+ require "google_spreadsheet/collection"
18
+
19
+
20
+ module GoogleSpreadsheet
21
+
22
+ # Use GoogleSpreadsheet.login or GoogleSpreadsheet.saved_session to get
23
+ # GoogleSpreadsheet::Session object.
24
+ class Session
25
+
26
+ include(Util)
27
+ extend(Util)
28
+
29
+ # The same as GoogleSpreadsheet.login.
30
+ def self.login(mail, password, proxy = nil)
31
+ session = Session.new(nil, ClientLoginFetcher.new({}, proxy))
32
+ session.login(mail, password)
33
+ return session
34
+ end
35
+
36
+ # The same as GoogleSpreadsheet.login_with_oauth.
37
+ def self.login_with_oauth(oauth_token)
38
+ case oauth_token
39
+ when OAuth::AccessToken
40
+ fetcher = OAuth1Fetcher.new(oauth_token)
41
+ when OAuth2::AccessToken
42
+ fetcher = OAuth2Fetcher.new(oauth_token)
43
+ else
44
+ raise(GoogleSpreadsheet::Error,
45
+ "oauth_token is neither OAuth::Token nor OAuth2::Token: %p" % oauth_token)
46
+ end
47
+ return Session.new(nil, fetcher)
48
+ end
49
+
50
+ # The same as GoogleSpreadsheet.restore_session.
51
+ def self.restore_session(auth_tokens, proxy = nil)
52
+ return Session.new(auth_tokens, nil, proxy)
53
+ end
54
+
55
+ # Creates a dummy GoogleSpreadsheet::Session object for testing.
56
+ def self.new_dummy()
57
+ return Session.new(nil, Object.new())
58
+ end
59
+
60
+ # DEPRECATED: Use GoogleSpreadsheet.restore_session instead.
61
+ def initialize(auth_tokens = nil, fetcher = nil, proxy = nil)
62
+ if fetcher
63
+ @fetcher = fetcher
64
+ else
65
+ @fetcher = ClientLoginFetcher.new(auth_tokens || {}, proxy)
66
+ end
67
+ end
68
+
69
+ # Authenticates with given +mail+ and +password+, and updates current session object
70
+ # if succeeds. Raises GoogleSpreadsheet::AuthenticationError if fails.
71
+ # Google Apps account is supported.
72
+ def login(mail, password)
73
+ if !@fetcher.is_a?(ClientLoginFetcher)
74
+ raise(GoogleSpreadsheet::Error,
75
+ "Cannot call login for session created by login_with_oauth.")
76
+ end
77
+ begin
78
+ @fetcher.auth_tokens = {
79
+ :wise => authenticate(mail, password, :wise),
80
+ :writely => authenticate(mail, password, :writely),
81
+ }
82
+ rescue GoogleSpreadsheet::Error => ex
83
+ return true if @on_auth_fail && @on_auth_fail.call()
84
+ raise(AuthenticationError, "Authentication failed for #{mail}: #{ex.message}")
85
+ end
86
+ end
87
+
88
+ # Authentication tokens.
89
+ def auth_tokens
90
+ if !@fetcher.is_a?(ClientLoginFetcher)
91
+ raise(GoogleSpreadsheet::Error,
92
+ "Cannot call auth_tokens for session created by " +
93
+ "login_with_oauth.")
94
+ end
95
+ return @fetcher.auth_tokens
96
+ end
97
+
98
+ # Authentication token.
99
+ def auth_token(auth = :wise)
100
+ return self.auth_tokens[auth]
101
+ end
102
+
103
+ # Proc or Method called when authentication has failed.
104
+ # When this function returns +true+, it tries again.
105
+ attr_accessor :on_auth_fail
106
+
107
+ # Returns list of spreadsheets for the user as array of GoogleSpreadsheet::Spreadsheet.
108
+ # You can specify query parameters described at
109
+ # http://code.google.com/apis/spreadsheets/docs/2.0/reference.html#Parameters
110
+ #
111
+ # e.g.
112
+ # session.spreadsheets
113
+ # session.spreadsheets("title" => "hoge")
114
+ def spreadsheets(params = {})
115
+ query = encode_query(params)
116
+ doc = request(
117
+ :get, "https://spreadsheets.google.com/feeds/spreadsheets/private/full?#{query}")
118
+ result = []
119
+ doc.css("feed > entry").each() do |entry|
120
+ title = entry.css("title").text
121
+ url = entry.css(
122
+ "link[rel='http://schemas.google.com/spreadsheets/2006#worksheetsfeed']")[0]["href"]
123
+ result.push(Spreadsheet.new(self, url, title))
124
+ end
125
+ return result
126
+ end
127
+
128
+ # Returns GoogleSpreadsheet::Spreadsheet with given +key+.
129
+ #
130
+ # e.g.
131
+ # # http://spreadsheets.google.com/ccc?key=pz7XtlQC-PYx-jrVMJErTcg&hl=ja
132
+ # session.spreadsheet_by_key("pz7XtlQC-PYx-jrVMJErTcg")
133
+ def spreadsheet_by_key(key)
134
+ url = "https://spreadsheets.google.com/feeds/worksheets/#{key}/private/full"
135
+ return Spreadsheet.new(self, url)
136
+ end
137
+
138
+ # Returns GoogleSpreadsheet::Spreadsheet with given +url+. You must specify either of:
139
+ # - URL of the page you open to access the spreadsheet in your browser
140
+ # - URL of worksheet-based feed of the spreadseet
141
+ #
142
+ # e.g.
143
+ # session.spreadsheet_by_url(
144
+ # "https://docs.google.com/spreadsheet/ccc?key=pz7XtlQC-PYx-jrVMJErTcg")
145
+ # session.spreadsheet_by_url(
146
+ # "https://spreadsheets.google.com/feeds/" +
147
+ # "worksheets/pz7XtlQC-PYx-jrVMJErTcg/private/full")
148
+ def spreadsheet_by_url(url)
149
+ # Tries to parse it as URL of human-readable spreadsheet.
150
+ uri = URI.parse(url)
151
+ if ["spreadsheets.google.com", "docs.google.com"].include?(uri.host) &&
152
+ uri.path =~ /\/ccc$/
153
+ if (uri.query || "").split(/&/).find(){ |s| s=~ /^key=(.*)$/ }
154
+ return spreadsheet_by_key($1)
155
+ end
156
+ end
157
+ # Assumes the URL is worksheets feed URL.
158
+ return Spreadsheet.new(self, url)
159
+ end
160
+
161
+ # Returns GoogleSpreadsheet::Spreadsheet with given +title+.
162
+ # Returns nil if not found. If multiple spreadsheets with the +title+ are found, returns
163
+ # one of them.
164
+ def spreadsheet_by_title(title)
165
+ return spreadsheets({"title" => title})[0]
166
+ end
167
+
168
+ # Returns GoogleSpreadsheet::Worksheet with given +url+.
169
+ # You must specify URL of cell-based feed of the worksheet.
170
+ #
171
+ # e.g.
172
+ # session.worksheet_by_url(
173
+ # "http://spreadsheets.google.com/feeds/" +
174
+ # "cells/pz7XtlQC-PYxNmbBVgyiNWg/od6/private/full")
175
+ def worksheet_by_url(url)
176
+ return Worksheet.new(self, nil, url)
177
+ end
178
+
179
+ # Returns GoogleSpreadsheet::Collection with given +url+.
180
+ # You must specify either of:
181
+ # - URL of the page you get when you go to https://docs.google.com/ with your browser and
182
+ # open a collection
183
+ # - URL of collection (folder) feed
184
+ #
185
+ # e.g.
186
+ # session.collection_by_url(
187
+ # "https://docs.google.com/?pli=1&authuser=0#folders/" +
188
+ # "0B9GfDpQ2pBVUODNmOGE0NjIzMWU3ZC00NmUyLTk5NzEtYaFkZjY1MjAyxjMc")
189
+ # session.collection_by_url(
190
+ # "http://docs.google.com/feeds/default/private/full/folder%3A" +
191
+ # "0B9GfDpQ2pBVUODNmOGE0NjIzMWU3ZC00NmUyLTk5NzEtYaFkZjY1MjAyxjMc")
192
+ def collection_by_url(url)
193
+ uri = URI.parse(url)
194
+ if uri.host == "docs.google.com" && uri.fragment =~ /^folders\/(.+)$/
195
+ # Looks like a URL of human-readable collection page. Converts to collection feed URL.
196
+ url = "https://docs.google.com/feeds/default/private/full/folder%3A#{$1}"
197
+ end
198
+ return Collection.new(self, url)
199
+ end
200
+
201
+ # Creates new spreadsheet and returns the new GoogleSpreadsheet::Spreadsheet.
202
+ #
203
+ # e.g.
204
+ # session.create_spreadsheet("My new sheet")
205
+ def create_spreadsheet(
206
+ title = "Untitled",
207
+ feed_url = "https://docs.google.com/feeds/documents/private/full")
208
+
209
+ xml = <<-"EOS"
210
+ <atom:entry
211
+ xmlns:atom="http://www.w3.org/2005/Atom"
212
+ xmlns:docs="http://schemas.google.com/docs/2007">
213
+ <atom:category
214
+ scheme="http://schemas.google.com/g/2005#kind"
215
+ term="http://schemas.google.com/docs/2007#spreadsheet"
216
+ label="spreadsheet"/>
217
+ <atom:title>#{h(title)}</atom:title>
218
+ </atom:entry>
219
+ EOS
220
+
221
+ doc = request(:post, feed_url, :data => xml, :auth => :writely)
222
+ ss_url = doc.css(
223
+ "link[rel='http://schemas.google.com/spreadsheets/2006#worksheetsfeed']")[0]["href"]
224
+ return Spreadsheet.new(self, ss_url, title)
225
+
226
+ end
227
+
228
+ def request(method, url, params = {}) #:nodoc:
229
+
230
+ # Always uses HTTPS.
231
+ url = url.gsub(%r{^http://}, "https://")
232
+ data = params[:data]
233
+ auth = params[:auth] || :wise
234
+ if params[:header]
235
+ extra_header = params[:header]
236
+ elsif data
237
+ extra_header = {"Content-Type" => "application/atom+xml"}
238
+ else
239
+ extra_header = {}
240
+ end
241
+ response_type = params[:response_type] || :xml
242
+
243
+ while true
244
+ response = @fetcher.request_raw(method, url, data, extra_header, auth)
245
+ if response.code == "401" && @on_auth_fail && @on_auth_fail.call()
246
+ next
247
+ end
248
+ if !(response.code =~ /^2/)
249
+ raise(
250
+ response.code == "401" ? AuthenticationError : GoogleSpreadsheet::Error,
251
+ "Response code #{response.code} for #{method} #{url}: " +
252
+ CGI.unescapeHTML(response.body))
253
+ end
254
+ return convert_response(response, response_type)
255
+ end
256
+
257
+ end
258
+
259
+ def inspect
260
+ return "#<%p:0x%x>" % [self.class, self.object_id]
261
+ end
262
+
263
+ private
264
+
265
+ def convert_response(response, response_type)
266
+ case response_type
267
+ when :xml
268
+ return Nokogiri.XML(response.body)
269
+ when :raw
270
+ return response.body
271
+ else
272
+ raise(GoogleSpreadsheet::Error,
273
+ "Unknown params[:response_type]: %s" % response_type)
274
+ end
275
+ end
276
+
277
+ def authenticate(mail, password, auth)
278
+ params = {
279
+ "accountType" => "HOSTED_OR_GOOGLE",
280
+ "Email" => mail,
281
+ "Passwd" => password,
282
+ "service" => auth.to_s(),
283
+ "source" => "Gimite-RubyGoogleSpreadsheet-1.00",
284
+ }
285
+ header = {"Content-Type" => "application/x-www-form-urlencoded"}
286
+ response = request(:post,
287
+ "https://www.google.com/accounts/ClientLogin",
288
+ :data => encode_query(params),
289
+ :auth => :none,
290
+ :header => header,
291
+ :response_type => :raw)
292
+ return response.slice(/^Auth=(.*)$/, 1)
293
+ end
294
+
295
+ end
296
+
297
+ end