gimite-google-spreadsheet-ruby 0.0.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.
- data/README.txt +31 -0
- data/lib/google_spreadsheet.rb +390 -0
- metadata +64 -0
data/README.txt
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
This is a library to read/write Google Spreadsheet.
|
2
|
+
|
3
|
+
|
4
|
+
= How to install
|
5
|
+
|
6
|
+
TBD
|
7
|
+
|
8
|
+
|
9
|
+
= How to use
|
10
|
+
|
11
|
+
Example:
|
12
|
+
|
13
|
+
require "rubygems"
|
14
|
+
require "google_spreadsheet"
|
15
|
+
|
16
|
+
# Logs in.
|
17
|
+
session = GoogleSpreadsheet.login("username@gmail.com", "mypassword")
|
18
|
+
|
19
|
+
# First worksheet of http://spreadsheets.google.com/ccc?key=pz7XtlQC-PYx-jrVMJErTcg&hl=en
|
20
|
+
ws = session.spreadsheet_by_key("pz7XtlQC-PYx-jrVMJErTcg").worksheets[0]
|
21
|
+
|
22
|
+
# Gets content of A2 cell.
|
23
|
+
p ws[2, 1] #==> "hoge"
|
24
|
+
|
25
|
+
# Changes content of cells. Changes are not sent to the server until you call ws.save().
|
26
|
+
ws[2, 1] = "foo"
|
27
|
+
ws[2, 2] = "bar"
|
28
|
+
ws.save()
|
29
|
+
|
30
|
+
# Reloads the worksheet to get changes by other clients.
|
31
|
+
ws.reload()
|
@@ -0,0 +1,390 @@
|
|
1
|
+
# Author: Hiroshi Ichikawa <http://gimite.net/>
|
2
|
+
# The license of this source is "New BSD Licence"
|
3
|
+
|
4
|
+
require "set"
|
5
|
+
require "net/https"
|
6
|
+
require "open-uri"
|
7
|
+
require "cgi"
|
8
|
+
require "rubygems"
|
9
|
+
require "hpricot"
|
10
|
+
Net::HTTP.version_1_2
|
11
|
+
|
12
|
+
|
13
|
+
module GoogleSpreadsheet
|
14
|
+
|
15
|
+
# Authenticates with given +mail+ and +password+, and returns GoogleSpreadsheet::Session
|
16
|
+
# if succeeds. Raises GoogleSpreadsheet::AuthenticationError if fails.
|
17
|
+
# Google Apps account is supported.
|
18
|
+
def self.login(mail, password)
|
19
|
+
return Session.login(mail, password)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Restores GoogleSpreadsheet::Session from +path+ and returns it.
|
23
|
+
# If +path+ doesn't exist, prompts mail and password on console, authenticates with them,
|
24
|
+
# stores the session to +path+ and returns it.
|
25
|
+
#
|
26
|
+
# This method requires Ruby/Password library: http://www.caliban.org/ruby/ruby-password.shtml
|
27
|
+
def self.saved_session(path = ENV["HOME"] + "/.ruby_google_spreadsheet.token")
|
28
|
+
if File.exist?(path)
|
29
|
+
return Session.new(File.read(path))
|
30
|
+
else
|
31
|
+
require "password"
|
32
|
+
$stderr.print("Mail: ")
|
33
|
+
mail = $stdin.gets().chomp()
|
34
|
+
password = Password.get()
|
35
|
+
session = Session.login(mail, password)
|
36
|
+
open(path, "w", 0600){ |f| f.write(session.auth_token) }
|
37
|
+
return session
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
module Util #:nodoc:
|
43
|
+
|
44
|
+
module_function
|
45
|
+
|
46
|
+
def http_post(url, data, header = {})
|
47
|
+
uri = URI.parse(url)
|
48
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
49
|
+
http.use_ssl = uri.scheme == "https"
|
50
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
51
|
+
http.start() do
|
52
|
+
path = uri.path + (uri.query ? "?#{uri.query}" : "")
|
53
|
+
response = http.post(path, data, header)
|
54
|
+
if !(response.code =~ /^2/)
|
55
|
+
raise(GoogleSpreadsheet::Error, "Response code #{response.code} for POST #{url}: " +
|
56
|
+
CGI.unescapeHTML(response.body))
|
57
|
+
end
|
58
|
+
return response.body
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def encode_query(params)
|
63
|
+
return params.map(){ |k, v| uri_encode(k) + "=" + uri_encode(v) }.join("&")
|
64
|
+
end
|
65
|
+
|
66
|
+
def uri_encode(str)
|
67
|
+
return URI.encode(str, /#{URI::UNSAFE}|&/n)
|
68
|
+
end
|
69
|
+
|
70
|
+
def h(str)
|
71
|
+
return CGI.escapeHTML(str.to_s())
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
# Raised when spreadsheets.google.com has returned error.
|
78
|
+
class Error < RuntimeError
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
# Raised when GoogleSpreadsheet.login has failed.
|
84
|
+
class AuthenticationError < GoogleSpreadsheet::Error
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
# Use GoogleSpreadsheet.login or GoogleSpreadsheet.saved_session to get
|
90
|
+
# GoogleSpreadsheet::Session object.
|
91
|
+
class Session
|
92
|
+
|
93
|
+
include(Util)
|
94
|
+
extend(Util)
|
95
|
+
|
96
|
+
# The same as GoogleSpreadsheet.login.
|
97
|
+
def self.login(mail, password)
|
98
|
+
begin
|
99
|
+
params = {
|
100
|
+
"accountType" => "HOSTED_OR_GOOGLE",
|
101
|
+
"Email" => mail,
|
102
|
+
"Passwd" => password,
|
103
|
+
"service" => "wise",
|
104
|
+
"source" => "Gimite-RubyGoogleSpreadsheet-1.00",
|
105
|
+
}
|
106
|
+
response = http_post("https://www.google.com/accounts/ClientLogin", encode_query(params))
|
107
|
+
return Session.new(response.slice(/^Auth=(.*)$/, 1))
|
108
|
+
rescue GoogleSpreadsheet::Error => ex
|
109
|
+
raise(AuthenticationError, "authentication failed for #{mail}: #{ex.message}")
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Creates session object with given authentication token.
|
114
|
+
def initialize(auth_token)
|
115
|
+
@auth_token = auth_token
|
116
|
+
end
|
117
|
+
|
118
|
+
# Authentication token.
|
119
|
+
attr_reader(:auth_token)
|
120
|
+
|
121
|
+
def get(url) #:nodoc:
|
122
|
+
response = open(url, self.http_header){ |f| f.read() }
|
123
|
+
return Hpricot.XML(response)
|
124
|
+
end
|
125
|
+
|
126
|
+
def post(url, data) #:nodoc:
|
127
|
+
header = self.http_header.merge({"Content-Type" => "application/atom+xml"})
|
128
|
+
response = http_post(url, data, header)
|
129
|
+
return Hpricot.XML(response)
|
130
|
+
end
|
131
|
+
|
132
|
+
def http_header #:nodoc:
|
133
|
+
return {"Authorization" => "GoogleLogin auth=#{@auth_token}"}
|
134
|
+
end
|
135
|
+
|
136
|
+
# Returns list of spreadsheets for the user as array of GoogleSpreadsheet::Spreadsheet.
|
137
|
+
# You can specify query parameters described at
|
138
|
+
# http://code.google.com/apis/spreadsheets/docs/2.0/reference.html#Parameters
|
139
|
+
#
|
140
|
+
# e.g.
|
141
|
+
# session.spreadsheets
|
142
|
+
# session.spreadsheets("title" => "hoge")
|
143
|
+
def spreadsheets(params = {})
|
144
|
+
query = encode_query(params)
|
145
|
+
doc = get("http://spreadsheets.google.com/feeds/spreadsheets/private/full?#{query}")
|
146
|
+
result = []
|
147
|
+
for entry in doc.search("entry")
|
148
|
+
title = entry.search("title").text
|
149
|
+
url = entry.search(
|
150
|
+
"link[@rel='http://schemas.google.com/spreadsheets/2006#worksheetsfeed']")[0]["href"]
|
151
|
+
result.push(Spreadsheet.new(self, url, title))
|
152
|
+
end
|
153
|
+
return result
|
154
|
+
end
|
155
|
+
|
156
|
+
# Returns GoogleSpreadsheet::Spreadsheet with given +key+.
|
157
|
+
#
|
158
|
+
# e.g.
|
159
|
+
# # http://spreadsheets.google.com/ccc?key=pz7XtlQC-PYx-jrVMJErTcg&hl=ja
|
160
|
+
# session.spreadsheet_by_key("pz7XtlQC-PYx-jrVMJErTcg")
|
161
|
+
def spreadsheet_by_key(key)
|
162
|
+
url = "http://spreadsheets.google.com/feeds/worksheets/#{key}/private/full"
|
163
|
+
return Spreadsheet.new(self, url)
|
164
|
+
end
|
165
|
+
|
166
|
+
# Returns GoogleSpreadsheet::Spreadsheet with given +url+. You must specify either of:
|
167
|
+
# - URL of the page you open to access the spreadsheet in your browser
|
168
|
+
# - URL of worksheet-based feed of the spreadseet
|
169
|
+
#
|
170
|
+
# e.g.
|
171
|
+
# session.spreadsheet_by_url(
|
172
|
+
# "http://spreadsheets.google.com/ccc?key=pz7XtlQC-PYx-jrVMJErTcg&hl=en")
|
173
|
+
# session.spreadsheet_by_url(
|
174
|
+
# "http://spreadsheets.google.com/feeds/worksheets/pz7XtlQC-PYx-jrVMJErTcg/private/full")
|
175
|
+
def spreadsheet_by_url(url)
|
176
|
+
# Tries to parse it as URL of human-readable spreadsheet.
|
177
|
+
uri = URI.parse(url)
|
178
|
+
if uri.host == "spreadsheets.google.com" && uri.path =~ /\/ccc$/
|
179
|
+
if (uri.query || "").split(/&/).find(){ |s| s=~ /^key=(.*)$/ }
|
180
|
+
return spreadsheet_by_key($1)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
# Assumes the URL is worksheets feed URL.
|
184
|
+
return Spreadsheet.new(self, url)
|
185
|
+
end
|
186
|
+
|
187
|
+
# Returns GoogleSpreadsheet::Worksheet with given +url+.
|
188
|
+
# You must specify URL of cell-based feed of the worksheet.
|
189
|
+
#
|
190
|
+
# e.g.
|
191
|
+
# session.worksheet_by_url(
|
192
|
+
# "http://spreadsheets.google.com/feeds/cells/pz7XtlQC-PYxNmbBVgyiNWg/od6/private/full")
|
193
|
+
def worksheet_by_url(url)
|
194
|
+
return Worksheet.new(self, url)
|
195
|
+
end
|
196
|
+
|
197
|
+
end
|
198
|
+
|
199
|
+
|
200
|
+
# Use methods in GoogleSpreadsheet::Session to get GoogleSpreadsheet::Spreadsheet object.
|
201
|
+
class Spreadsheet
|
202
|
+
|
203
|
+
include(Util)
|
204
|
+
|
205
|
+
def initialize(session, worksheets_feed_url, title = nil) #:nodoc:
|
206
|
+
@session = session
|
207
|
+
@worksheets_feed_url = worksheets_feed_url
|
208
|
+
@title = title
|
209
|
+
end
|
210
|
+
|
211
|
+
# URL of worksheet-based feed of the spreadsheet.
|
212
|
+
attr_reader(:worksheets_feed_url)
|
213
|
+
|
214
|
+
# Title of the spreadsheet. So far only available if you get this object by
|
215
|
+
# GoogleSpreadsheet::Session#spreadsheets.
|
216
|
+
attr_reader(:title)
|
217
|
+
|
218
|
+
# Returns worksheets of the spreadsheet as array of GoogleSpreadsheet::Worksheet.
|
219
|
+
def worksheets
|
220
|
+
doc = @session.get(@worksheets_feed_url)
|
221
|
+
result = []
|
222
|
+
for entry in doc.search("entry")
|
223
|
+
title = entry.search("title").text
|
224
|
+
url = entry.search(
|
225
|
+
"link[@rel='http://schemas.google.com/spreadsheets/2006#cellsfeed']")[0]["href"]
|
226
|
+
result.push(Worksheet.new(@session, url, title))
|
227
|
+
end
|
228
|
+
return result.freeze()
|
229
|
+
end
|
230
|
+
|
231
|
+
# Adds a new worksheet to the spreadsheet. Returns added GoogleSpreadsheet::Worksheet.
|
232
|
+
def add_worksheet(title, max_rows = 100, max_cols = 20)
|
233
|
+
xml = <<-"EOS"
|
234
|
+
<entry xmlns='http://www.w3.org/2005/Atom'
|
235
|
+
xmlns:gs='http://schemas.google.com/spreadsheets/2006'>
|
236
|
+
<title>#{h(title)}</title>
|
237
|
+
<gs:rowCount>#{h(max_rows)}</gs:rowCount>
|
238
|
+
<gs:colCount>#{h(max_cols)}</gs:colCount>
|
239
|
+
</entry>
|
240
|
+
EOS
|
241
|
+
doc = @session.post(@worksheets_feed_url, xml)
|
242
|
+
url = doc.search(
|
243
|
+
"link[@rel='http://schemas.google.com/spreadsheets/2006#cellsfeed']")[0]["href"]
|
244
|
+
return Worksheet.new(@session, url, title)
|
245
|
+
end
|
246
|
+
|
247
|
+
end
|
248
|
+
|
249
|
+
|
250
|
+
# Use GoogleSpreadsheet::Spreadsheet#worksheets to get GoogleSpreadsheet::Worksheet object.
|
251
|
+
class Worksheet
|
252
|
+
|
253
|
+
include(Util)
|
254
|
+
|
255
|
+
def initialize(session, cells_feed_url, title = nil) #:nodoc:
|
256
|
+
@session = session
|
257
|
+
@cells_feed_url = cells_feed_url
|
258
|
+
@title = title
|
259
|
+
@cells = nil
|
260
|
+
@modified = Set.new()
|
261
|
+
end
|
262
|
+
|
263
|
+
# URL of cell-based feed of the spreadsheet.
|
264
|
+
attr_reader(:cells_feed_url)
|
265
|
+
|
266
|
+
# Title of the spreadsheet. So far not available if you get this object by
|
267
|
+
# GoogleSpreadsheet::Spreadsheet#worksheet_by_url.
|
268
|
+
attr_reader(:title)
|
269
|
+
|
270
|
+
# Returns content of the cell as String. Top-left cell is [1, 1].
|
271
|
+
def [](row, col)
|
272
|
+
return self.cells[[row, col]] || ""
|
273
|
+
end
|
274
|
+
|
275
|
+
# Updates content of the cell.
|
276
|
+
# Note that update is not sent to the server until you call save().
|
277
|
+
# Top-left cell is [1, 1].
|
278
|
+
#
|
279
|
+
# e.g.
|
280
|
+
# worksheet[2, 1] = "hoge"
|
281
|
+
# worksheet[1, 3] = "=A1+B1"
|
282
|
+
def []=(row, col, value)
|
283
|
+
reload() if !@cells
|
284
|
+
@cells[[row, col]] = value
|
285
|
+
@modified.add([row, col])
|
286
|
+
end
|
287
|
+
|
288
|
+
# Number of the bottom-most non-empty row.
|
289
|
+
def num_rows
|
290
|
+
reload() if !@cells
|
291
|
+
return @cells.keys.map(){ |r, c| r }.max || 0
|
292
|
+
end
|
293
|
+
|
294
|
+
# Number of the right-most non-empty column.
|
295
|
+
def num_cols
|
296
|
+
reload() if !@cells
|
297
|
+
return @cells.keys.map(){ |r, c| c }.max || 0
|
298
|
+
end
|
299
|
+
|
300
|
+
def cells #:nodoc:
|
301
|
+
reload() if !@cells
|
302
|
+
return @cells
|
303
|
+
end
|
304
|
+
|
305
|
+
# Reloads content of the worksheets from the server.
|
306
|
+
# Note that changes you made by []= is discarded if you haven't called save().
|
307
|
+
def reload()
|
308
|
+
doc = @session.get(@cells_feed_url)
|
309
|
+
@cells = {}
|
310
|
+
for entry in doc.search("entry")
|
311
|
+
row = entry.search("gs:cell")[0]["row"].to_i()
|
312
|
+
col = entry.search("gs:cell")[0]["col"].to_i()
|
313
|
+
content = entry.search("content").text
|
314
|
+
@cells[[row, col]] = content
|
315
|
+
end
|
316
|
+
@modified.clear()
|
317
|
+
return true
|
318
|
+
end
|
319
|
+
|
320
|
+
# Saves your changes made by []= to the server.
|
321
|
+
def save()
|
322
|
+
return false if @modified.empty?
|
323
|
+
|
324
|
+
# Gets id and edit URL for each cell.
|
325
|
+
# Note that return-empty=true is required to get those info for empty cells.
|
326
|
+
cell_entries = {}
|
327
|
+
rows = @modified.map(){ |r, c| r }
|
328
|
+
cols = @modified.map(){ |r, c| c }
|
329
|
+
url = "#{@cells_feed_url}?return-empty=true&min-row=#{rows.min}&max-row=#{rows.max}" +
|
330
|
+
"&min-col=#{cols.min}&max-col=#{cols.max}"
|
331
|
+
doc = @session.get(url)
|
332
|
+
for entry in doc.search("entry")
|
333
|
+
row = entry.search("gs:cell")[0]["row"].to_i()
|
334
|
+
col = entry.search("gs:cell")[0]["col"].to_i()
|
335
|
+
cell_entries[[row, col]] = entry
|
336
|
+
end
|
337
|
+
|
338
|
+
# Updates cell values using batch operation.
|
339
|
+
xml = <<-"EOS"
|
340
|
+
<feed xmlns='http://www.w3.org/2005/Atom'
|
341
|
+
xmlns:batch='http://schemas.google.com/gdata/batch'
|
342
|
+
xmlns:gs='http://schemas.google.com/spreadsheets/2006'>
|
343
|
+
<id>#{h(@cells_feed_url)}</id>
|
344
|
+
EOS
|
345
|
+
for row, col in @modified
|
346
|
+
value = @cells[[row, col]]
|
347
|
+
entry = cell_entries[[row, col]]
|
348
|
+
id = entry.search("id").text
|
349
|
+
edit_url = entry.search("link[@rel='edit']")[0]["href"]
|
350
|
+
xml << <<-"EOS"
|
351
|
+
<entry>
|
352
|
+
<batch:id>#{h(row)},#{h(col)}</batch:id>
|
353
|
+
<batch:operation type='update'/>
|
354
|
+
<id>#{h(id)}</id>
|
355
|
+
<link rel='edit' type='application/atom+xml'
|
356
|
+
href='#{h(edit_url)}'/>
|
357
|
+
<gs:cell row='#{h(row)}' col='#{h(col)}' inputValue='#{h(value)}'/>
|
358
|
+
</entry>
|
359
|
+
EOS
|
360
|
+
end
|
361
|
+
xml << <<-"EOS"
|
362
|
+
</feed>
|
363
|
+
EOS
|
364
|
+
result = @session.post("#{@cells_feed_url}/batch", xml)
|
365
|
+
for entry in result.search("atom:entry")
|
366
|
+
if !(entry.search("batch:status")[0]["code"] =~ /^2/)
|
367
|
+
raise(GoogleSpreadsheet::Error, "Updating cell %s has failed: %s" %
|
368
|
+
[entry.search("atom:id").text, entry.search("batch:status")[0]["reason"]])
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
@modified.clear()
|
373
|
+
return true
|
374
|
+
end
|
375
|
+
|
376
|
+
# Calls save() and reload().
|
377
|
+
def synchronize()
|
378
|
+
save()
|
379
|
+
reload()
|
380
|
+
end
|
381
|
+
|
382
|
+
# Returns true if you have changes made by []= which haven't been saved.
|
383
|
+
def dirty?
|
384
|
+
return !@modified.empty?
|
385
|
+
end
|
386
|
+
|
387
|
+
end
|
388
|
+
|
389
|
+
|
390
|
+
end
|
metadata
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gimite-google-spreadsheet-ruby
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Hiroshi Ichikawa
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-12-24 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: hpricot
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: "0.3"
|
23
|
+
version:
|
24
|
+
description: This is a library to read/write Google Spreadsheet.
|
25
|
+
email:
|
26
|
+
- gimite+github@gmail.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- README.txt
|
33
|
+
files:
|
34
|
+
- README.txt
|
35
|
+
- lib/google_spreadsheet.rb
|
36
|
+
has_rdoc: true
|
37
|
+
homepage: http://github.com/gimite/google-spreadsheet-ruby/tree/master
|
38
|
+
post_install_message:
|
39
|
+
rdoc_options:
|
40
|
+
- --main
|
41
|
+
- README.txt
|
42
|
+
require_paths:
|
43
|
+
- lib
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: "0"
|
49
|
+
version:
|
50
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: "0"
|
55
|
+
version:
|
56
|
+
requirements: []
|
57
|
+
|
58
|
+
rubyforge_project:
|
59
|
+
rubygems_version: 1.2.0
|
60
|
+
signing_key:
|
61
|
+
specification_version: 2
|
62
|
+
summary: This is a library to read/write Google Spreadsheet.
|
63
|
+
test_files: []
|
64
|
+
|