bradgessler-google-spreadsheet-ruby 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/README.txt +52 -0
  2. data/lib/google_spreadsheet.rb +410 -0
  3. metadata +66 -0
@@ -0,0 +1,52 @@
1
+ This is a library to read/write Google Spreadsheet.
2
+
3
+
4
+ = How to install
5
+
6
+ $ gem sources -a http://gems.github.com
7
+ $ sudo gem install gimite-google-spreadsheet-ruby
8
+
9
+
10
+ = How to use
11
+
12
+ Example:
13
+
14
+ require "rubygems"
15
+ require "google_spreadsheet"
16
+
17
+ # Logs in.
18
+ session = GoogleSpreadsheet.login("username@gmail.com", "mypassword")
19
+
20
+ # First worksheet of http://spreadsheets.google.com/ccc?key=pz7XtlQC-PYx-jrVMJErTcg&hl=en
21
+ ws = session.spreadsheet_by_key("pz7XtlQC-PYx-jrVMJErTcg").worksheets[0]
22
+
23
+ # Gets content of A2 cell.
24
+ p ws[2, 1] #==> "hoge"
25
+
26
+ # Changes content of cells. Changes are not sent to the server until you call ws.save().
27
+ ws[2, 1] = "foo"
28
+ ws[2, 2] = "bar"
29
+ ws.save()
30
+
31
+ # You can also loop through rows
32
+ ws.rows.each do |row|
33
+ row[2] = "bar"
34
+ end
35
+
36
+ # Reloads the worksheet to get changes by other clients.
37
+ ws.reload()
38
+
39
+ API document: http://gimite.net/gimite/rubymess/google-spreadsheet-ruby/
40
+
41
+
42
+ = Source code
43
+
44
+ http://github.com/gimite/google-spreadsheet-ruby/tree/master
45
+
46
+ The license of this source is "New BSD Licence"
47
+
48
+
49
+ = Author
50
+
51
+ Hiroshi Ichikawa - http://gimite.net/en/index.php?Contact
52
+ Brad Gessler - http://bradgessler.com/
@@ -0,0 +1,410 @@
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
+ # An array of spreadsheet rows. Each row contains an array of
306
+ # columns.
307
+ def rows(options={})
308
+ reload() if !@cells
309
+
310
+ start_row = 1
311
+ start_col = 1
312
+ end_row = num_rows
313
+ end_col = num_cols
314
+ row = start_row
315
+
316
+ @cells.inject([]) do |rows, cell|
317
+ rows << (start_col..end_col).map do |col|
318
+ @cells[[row, col]]
319
+ end
320
+ row = row + 1 # On to the next row my friend!
321
+ rows
322
+ ends
323
+ end
324
+
325
+ # Reloads content of the worksheets from the server.
326
+ # Note that changes you made by []= is discarded if you haven't called save().
327
+ def reload()
328
+ doc = @session.get(@cells_feed_url)
329
+ @cells = {}
330
+ for entry in doc.search("entry")
331
+ row = entry.search("gs:cell")[0]["row"].to_i()
332
+ col = entry.search("gs:cell")[0]["col"].to_i()
333
+ content = entry.search("content").text
334
+ @cells[[row, col]] = content
335
+ end
336
+ @modified.clear()
337
+ return true
338
+ end
339
+
340
+ # Saves your changes made by []= to the server.
341
+ def save()
342
+ return false if @modified.empty?
343
+
344
+ # Gets id and edit URL for each cell.
345
+ # Note that return-empty=true is required to get those info for empty cells.
346
+ cell_entries = {}
347
+ rows = @modified.map(){ |r, c| r }
348
+ cols = @modified.map(){ |r, c| c }
349
+ url = "#{@cells_feed_url}?return-empty=true&min-row=#{rows.min}&max-row=#{rows.max}" +
350
+ "&min-col=#{cols.min}&max-col=#{cols.max}"
351
+ doc = @session.get(url)
352
+ for entry in doc.search("entry")
353
+ row = entry.search("gs:cell")[0]["row"].to_i()
354
+ col = entry.search("gs:cell")[0]["col"].to_i()
355
+ cell_entries[[row, col]] = entry
356
+ end
357
+
358
+ # Updates cell values using batch operation.
359
+ xml = <<-"EOS"
360
+ <feed xmlns='http://www.w3.org/2005/Atom'
361
+ xmlns:batch='http://schemas.google.com/gdata/batch'
362
+ xmlns:gs='http://schemas.google.com/spreadsheets/2006'>
363
+ <id>#{h(@cells_feed_url)}</id>
364
+ EOS
365
+ for row, col in @modified
366
+ value = @cells[[row, col]]
367
+ entry = cell_entries[[row, col]]
368
+ id = entry.search("id").text
369
+ edit_url = entry.search("link[@rel='edit']")[0]["href"]
370
+ xml << <<-"EOS"
371
+ <entry>
372
+ <batch:id>#{h(row)},#{h(col)}</batch:id>
373
+ <batch:operation type='update'/>
374
+ <id>#{h(id)}</id>
375
+ <link rel='edit' type='application/atom+xml'
376
+ href='#{h(edit_url)}'/>
377
+ <gs:cell row='#{h(row)}' col='#{h(col)}' inputValue='#{h(value)}'/>
378
+ </entry>
379
+ EOS
380
+ end
381
+ xml << <<-"EOS"
382
+ </feed>
383
+ EOS
384
+ result = @session.post("#{@cells_feed_url}/batch", xml)
385
+ for entry in result.search("atom:entry")
386
+ if !(entry.search("batch:status")[0]["code"] =~ /^2/)
387
+ raise(GoogleSpreadsheet::Error, "Updating cell %s has failed: %s" %
388
+ [entry.search("atom:id").text, entry.search("batch:status")[0]["reason"]])
389
+ end
390
+ end
391
+
392
+ @modified.clear()
393
+ return true
394
+ end
395
+
396
+ # Calls save() and reload().
397
+ def synchronize()
398
+ save()
399
+ reload()
400
+ end
401
+
402
+ # Returns true if you have changes made by []= which haven't been saved.
403
+ def dirty?
404
+ return !@modified.empty?
405
+ end
406
+
407
+ end
408
+
409
+
410
+ end
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bradgessler-google-spreadsheet-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Hiroshi Ichikawa
8
+ - Brad Gessler
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2008-12-24 00:00:00 -08:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: hpricot
18
+ type: :development
19
+ version_requirement:
20
+ version_requirements: !ruby/object:Gem::Requirement
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: "0.3"
25
+ version:
26
+ description: This is a library to read/write Google Spreadsheet.
27
+ email:
28
+ - gimite+github@gmail.com
29
+ executables: []
30
+
31
+ extensions: []
32
+
33
+ extra_rdoc_files:
34
+ - README.txt
35
+ files:
36
+ - README.txt
37
+ - lib/google_spreadsheet.rb
38
+ has_rdoc: true
39
+ homepage: http://github.com/gimite/google-spreadsheet-ruby/tree/master
40
+ post_install_message:
41
+ rdoc_options:
42
+ - --main
43
+ - README.txt
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: "0"
51
+ version:
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ version:
58
+ requirements: []
59
+
60
+ rubyforge_project:
61
+ rubygems_version: 1.2.0
62
+ signing_key:
63
+ specification_version: 2
64
+ summary: This is a library to read/write Google Spreadsheet.
65
+ test_files: []
66
+