stympy-google-spreadsheet-ruby 0.0.3

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 +56 -0
  2. data/lib/google_spreadsheet.rb +528 -0
  3. metadata +65 -0
data/README.txt ADDED
@@ -0,0 +1,56 @@
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
+ # Dumps all cells.
32
+ for row in 1..ws.num_rows
33
+ for col in 1..ws.num_cols
34
+ p ws[row, col]
35
+ end
36
+ end
37
+
38
+ # Yet another way to do so.
39
+ p ws.rows #==> [["fuga", ""], ["foo", "bar]]
40
+
41
+ # Reloads the worksheet to get changes by other clients.
42
+ ws.reload()
43
+
44
+ API document: http://gimite.net/gimite/rubymess/google-spreadsheet-ruby/
45
+
46
+
47
+ = Source code
48
+
49
+ http://github.com/gimite/google-spreadsheet-ruby/tree/master
50
+
51
+ The license of this source is "New BSD Licence"
52
+
53
+
54
+ = Author
55
+
56
+ Hiroshi Ichikawa - http://gimite.net/en/index.php?Contact
@@ -0,0 +1,528 @@
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 or authentication has failed, prompts mail and password on console,
24
+ # authenticates with them, 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
+ session = Session.new(File.exist?(path) ? File.read(path) : nil)
29
+ session.on_auth_fail = proc() do
30
+ require "password"
31
+ $stderr.print("Mail: ")
32
+ mail = $stdin.gets().chomp()
33
+ password = Password.get()
34
+ session.login(mail, password)
35
+ open(path, "w", 0600){ |f| f.write(session.auth_token) }
36
+ true
37
+ end
38
+ if !session.auth_token
39
+ session.on_auth_fail.call()
40
+ end
41
+ return session
42
+ end
43
+
44
+
45
+ module Util #:nodoc:
46
+
47
+ module_function
48
+
49
+ def http_request(method, url, data, header = {})
50
+ uri = URI.parse(url)
51
+ http = Net::HTTP.new(uri.host, uri.port)
52
+ http.use_ssl = uri.scheme == "https"
53
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
54
+ http.start() do
55
+ path = uri.path + (uri.query ? "?#{uri.query}" : "")
56
+ response = http.__send__(method, path, data, header)
57
+ if !(response.code =~ /^2/)
58
+ raise(GoogleSpreadsheet::Error, "Response code #{response.code} for POST #{url}: " +
59
+ CGI.unescapeHTML(response.body))
60
+ end
61
+ return response.body
62
+ end
63
+ end
64
+
65
+ def encode_query(params)
66
+ return params.map(){ |k, v| uri_encode(k) + "=" + uri_encode(v) }.join("&")
67
+ end
68
+
69
+ def uri_encode(str)
70
+ return URI.encode(str, /#{URI::UNSAFE}|&/n)
71
+ end
72
+
73
+ def h(str)
74
+ return CGI.escapeHTML(str.to_s())
75
+ end
76
+
77
+ end
78
+
79
+
80
+ # Raised when spreadsheets.google.com has returned error.
81
+ class Error < RuntimeError
82
+
83
+ end
84
+
85
+
86
+ # Raised when GoogleSpreadsheet.login has failed.
87
+ class AuthenticationError < GoogleSpreadsheet::Error
88
+
89
+ end
90
+
91
+
92
+ # Use GoogleSpreadsheet.login or GoogleSpreadsheet.saved_session to get
93
+ # GoogleSpreadsheet::Session object.
94
+ class Session
95
+
96
+ include(Util)
97
+ extend(Util)
98
+
99
+ # The same as GoogleSpreadsheet.login.
100
+ def self.login(mail, password)
101
+ session = Session.new()
102
+ session.login(mail, password)
103
+ return session
104
+ end
105
+
106
+ # Creates session object with given authentication token.
107
+ def initialize(auth_token = nil)
108
+ @auth_token = auth_token
109
+ end
110
+
111
+ # Authenticates with given +mail+ and +password+, and updates current session object
112
+ # if succeeds. Raises GoogleSpreadsheet::AuthenticationError if fails.
113
+ # Google Apps account is supported.
114
+ def login(mail, password)
115
+ begin
116
+ @auth_token = nil
117
+ params = {
118
+ "accountType" => "HOSTED_OR_GOOGLE",
119
+ "Email" => mail,
120
+ "Passwd" => password,
121
+ "service" => "wise",
122
+ "source" => "Gimite-RubyGoogleSpreadsheet-1.00",
123
+ }
124
+ response = http_request(:post,
125
+ "https://www.google.com/accounts/ClientLogin", encode_query(params))
126
+ @auth_token = response.slice(/^Auth=(.*)$/, 1)
127
+ rescue GoogleSpreadsheet::Error => ex
128
+ return true if @on_auth_fail && @on_auth_fail.call()
129
+ raise(AuthenticationError, "authentication failed for #{mail}: #{ex.message}")
130
+ end
131
+ end
132
+
133
+ # Authentication token.
134
+ attr_accessor(:auth_token)
135
+
136
+ # Proc or Method called when authentication has failed.
137
+ # When this function returns +true+, it tries again.
138
+ attr_accessor(:on_auth_fail)
139
+
140
+ def get(url) #:nodoc:
141
+ while true
142
+ begin
143
+ response = open(url, self.http_header){ |f| f.read() }
144
+ rescue OpenURI::HTTPError => ex
145
+ if ex.message =~ /^401/ && @on_auth_fail && @on_auth_fail.call()
146
+ next
147
+ end
148
+ raise(ex.message =~ /^401/ ? AuthenticationError : GoogleSpreadsheet::Error,
149
+ "Error #{ex.message} for GET #{url}: " + ex.io.read())
150
+ end
151
+ return Hpricot.XML(response)
152
+ end
153
+ end
154
+
155
+ def post(url, data) #:nodoc:
156
+ header = self.http_header.merge({"Content-Type" => "application/atom+xml"})
157
+ response = http_request(:post, url, data, header)
158
+ return Hpricot.XML(response)
159
+ end
160
+
161
+ def put(url, data) #:nodoc:
162
+ header = self.http_header.merge({"Content-Type" => "application/atom+xml"})
163
+ response = http_request(:put, url, data, header)
164
+ return Hpricot.XML(response)
165
+ end
166
+
167
+ def http_header #:nodoc:
168
+ return {"Authorization" => "GoogleLogin auth=#{@auth_token}"}
169
+ end
170
+
171
+ # Returns list of spreadsheets for the user as array of GoogleSpreadsheet::Spreadsheet.
172
+ # You can specify query parameters described at
173
+ # http://code.google.com/apis/spreadsheets/docs/2.0/reference.html#Parameters
174
+ #
175
+ # e.g.
176
+ # session.spreadsheets
177
+ # session.spreadsheets("title" => "hoge")
178
+ def spreadsheets(params = {})
179
+ query = encode_query(params)
180
+ doc = get("http://spreadsheets.google.com/feeds/spreadsheets/private/full?#{query}")
181
+ result = []
182
+ for entry in doc.search("entry")
183
+ title = entry.search("title").text
184
+ url = entry.search(
185
+ "link[@rel='http://schemas.google.com/spreadsheets/2006#worksheetsfeed']")[0]["href"]
186
+ result.push(Spreadsheet.new(self, url, title))
187
+ end
188
+ return result
189
+ end
190
+
191
+ # Returns GoogleSpreadsheet::Spreadsheet with given +key+.
192
+ #
193
+ # e.g.
194
+ # # http://spreadsheets.google.com/ccc?key=pz7XtlQC-PYx-jrVMJErTcg&hl=ja
195
+ # session.spreadsheet_by_key("pz7XtlQC-PYx-jrVMJErTcg")
196
+ def spreadsheet_by_key(key)
197
+ url = "http://spreadsheets.google.com/feeds/worksheets/#{key}/private/full"
198
+ return Spreadsheet.new(self, url)
199
+ end
200
+
201
+ # Returns GoogleSpreadsheet::Spreadsheet with given +url+. You must specify either of:
202
+ # - URL of the page you open to access the spreadsheet in your browser
203
+ # - URL of worksheet-based feed of the spreadseet
204
+ #
205
+ # e.g.
206
+ # session.spreadsheet_by_url(
207
+ # "http://spreadsheets.google.com/ccc?key=pz7XtlQC-PYx-jrVMJErTcg&hl=en")
208
+ # session.spreadsheet_by_url(
209
+ # "http://spreadsheets.google.com/feeds/worksheets/pz7XtlQC-PYx-jrVMJErTcg/private/full")
210
+ def spreadsheet_by_url(url)
211
+ # Tries to parse it as URL of human-readable spreadsheet.
212
+ uri = URI.parse(url)
213
+ if uri.host == "spreadsheets.google.com" && uri.path =~ /\/ccc$/
214
+ if (uri.query || "").split(/&/).find(){ |s| s=~ /^key=(.*)$/ }
215
+ return spreadsheet_by_key($1)
216
+ end
217
+ end
218
+ # Assumes the URL is worksheets feed URL.
219
+ return Spreadsheet.new(self, url)
220
+ end
221
+
222
+ # Returns GoogleSpreadsheet::Worksheet with given +url+.
223
+ # You must specify URL of cell-based feed of the worksheet.
224
+ #
225
+ # e.g.
226
+ # session.worksheet_by_url(
227
+ # "http://spreadsheets.google.com/feeds/cells/pz7XtlQC-PYxNmbBVgyiNWg/od6/private/full")
228
+ def worksheet_by_url(url)
229
+ return Worksheet.new(self, url)
230
+ end
231
+
232
+ end
233
+
234
+
235
+ # Use methods in GoogleSpreadsheet::Session to get GoogleSpreadsheet::Spreadsheet object.
236
+ class Spreadsheet
237
+
238
+ include(Util)
239
+
240
+ def initialize(session, worksheets_feed_url, title = nil) #:nodoc:
241
+ @session = session
242
+ @worksheets_feed_url = worksheets_feed_url
243
+ @title = title
244
+ end
245
+
246
+ # URL of worksheet-based feed of the spreadsheet.
247
+ attr_reader(:worksheets_feed_url)
248
+
249
+ # Title of the spreadsheet. So far only available if you get this object by
250
+ # GoogleSpreadsheet::Session#spreadsheets.
251
+ attr_reader(:title)
252
+
253
+ # Returns worksheets of the spreadsheet as array of GoogleSpreadsheet::Worksheet.
254
+ def worksheets
255
+ doc = @session.get(@worksheets_feed_url)
256
+ result = []
257
+ for entry in doc.search("entry")
258
+ title = entry.search("title").text
259
+ url = entry.search(
260
+ "link[@rel='http://schemas.google.com/spreadsheets/2006#cellsfeed']")[0]["href"]
261
+ result.push(Worksheet.new(@session, url, title))
262
+ end
263
+ return result.freeze()
264
+ end
265
+
266
+ # Adds a new worksheet to the spreadsheet. Returns added GoogleSpreadsheet::Worksheet.
267
+ def add_worksheet(title, max_rows = 100, max_cols = 20)
268
+ xml = <<-"EOS"
269
+ <entry xmlns='http://www.w3.org/2005/Atom'
270
+ xmlns:gs='http://schemas.google.com/spreadsheets/2006'>
271
+ <title>#{h(title)}</title>
272
+ <gs:rowCount>#{h(max_rows)}</gs:rowCount>
273
+ <gs:colCount>#{h(max_cols)}</gs:colCount>
274
+ </entry>
275
+ EOS
276
+ doc = @session.post(@worksheets_feed_url, xml)
277
+ url = doc.search(
278
+ "link[@rel='http://schemas.google.com/spreadsheets/2006#cellsfeed']")[0]["href"]
279
+ return Worksheet.new(@session, url, title)
280
+ end
281
+
282
+ end
283
+
284
+
285
+ # Use GoogleSpreadsheet::Spreadsheet#worksheets to get GoogleSpreadsheet::Worksheet object.
286
+ class Worksheet
287
+
288
+ include(Util)
289
+
290
+ def initialize(session, cells_feed_url, title = nil) #:nodoc:
291
+ @session = session
292
+ @cells_feed_url = cells_feed_url
293
+ @title = title
294
+ @cells = nil
295
+ @input_values = nil
296
+ @modified = Set.new()
297
+ end
298
+
299
+ # URL of cell-based feed of the spreadsheet.
300
+ attr_reader(:cells_feed_url)
301
+
302
+ # Returns content of the cell as String. Top-left cell is [1, 1].
303
+ def [](row, col)
304
+ return self.cells[[row, col]] || ""
305
+ end
306
+
307
+ # Updates content of the cell.
308
+ # Note that update is not sent to the server until you call save().
309
+ # Top-left cell is [1, 1].
310
+ #
311
+ # e.g.
312
+ # worksheet[2, 1] = "hoge"
313
+ # worksheet[1, 3] = "=A1+B1"
314
+ def []=(row, col, value)
315
+ reload() if !@cells
316
+ @cells[[row, col]] = value
317
+ @input_values[[row, col]] = value
318
+ @modified.add([row, col])
319
+ self.max_rows = row if row > @max_rows
320
+ self.max_cols = col if col > @max_cols
321
+ end
322
+
323
+ # Returns the value or the formula of the cell. Top-left cell is [1, 1].
324
+ #
325
+ # If user input "=A1+B1" to cell [1, 3], worksheet[1, 3] is "3" for example and
326
+ # worksheet.input_value(1, 3) is "=RC[-2]+RC[-1]".
327
+ def input_value(row, col)
328
+ reload() if !@cells
329
+ return @input_values[[row, col]] || ""
330
+ end
331
+
332
+ # Row number of the bottom-most non-empty row.
333
+ def num_rows
334
+ reload() if !@cells
335
+ return @cells.keys.map(){ |r, c| r }.max || 0
336
+ end
337
+
338
+ # Column number of the right-most non-empty column.
339
+ def num_cols
340
+ reload() if !@cells
341
+ return @cells.keys.map(){ |r, c| c }.max || 0
342
+ end
343
+
344
+ # Number of rows including empty rows.
345
+ def max_rows
346
+ reload() if !@cells
347
+ return @max_rows
348
+ end
349
+
350
+ # Updates number of rows.
351
+ # Note that update is not sent to the server until you call save().
352
+ def max_rows=(rows)
353
+ @max_rows = rows
354
+ @meta_modified = true
355
+ end
356
+
357
+ # Number of columns including empty columns.
358
+ def max_cols
359
+ reload() if !@cells
360
+ return @max_cols
361
+ end
362
+
363
+ # Updates number of columns.
364
+ # Note that update is not sent to the server until you call save().
365
+ def max_cols=(cols)
366
+ @max_cols = cols
367
+ @meta_modified = true
368
+ end
369
+
370
+ # Title of the worksheet (shown as tab label in Web interface).
371
+ def title
372
+ reload() if !@title
373
+ return @title
374
+ end
375
+
376
+ # Updates title of the worksheet.
377
+ # Note that update is not sent to the server until you call save().
378
+ def title=(title)
379
+ @title = title
380
+ @meta_modified = true
381
+ end
382
+
383
+ def cells #:nodoc:
384
+ reload() if !@cells
385
+ return @cells
386
+ end
387
+
388
+ # An array of spreadsheet rows. Each row contains an array of
389
+ # columns. Note that resulting array is 0-origin so
390
+ # worksheet.rows[0][0] == worksheet[1, 1].
391
+ def rows(skip = 0)
392
+ nc = self.num_cols
393
+ result = ((1 + skip)..self.num_rows).map() do |row|
394
+ (1..nc).map(){ |col| self[row, col] }.freeze()
395
+ end
396
+ return result.freeze()
397
+ end
398
+
399
+ # Reloads content of the worksheets from the server.
400
+ # Note that changes you made by []= is discarded if you haven't called save().
401
+ def reload()
402
+ doc = @session.get(@cells_feed_url)
403
+ @max_rows = doc.search("gs:rowCount").text.to_i()
404
+ @max_cols = doc.search("gs:colCount").text.to_i()
405
+ @title = doc.search("title").text
406
+ @cells = {}
407
+ @input_values = {}
408
+ for entry in doc.search("entry")
409
+ cell = entry.search("gs:cell")[0]
410
+ row = cell["row"].to_i()
411
+ col = cell["col"].to_i()
412
+ @cells[[row, col]] = cell.inner_text
413
+ @input_values[[row, col]] = cell["inputValue"]
414
+ end
415
+ @modified.clear()
416
+ @meta_modified = false
417
+ return true
418
+ end
419
+
420
+ # Saves your changes made by []= to the server.
421
+ def save()
422
+ sent = false
423
+
424
+ if @meta_modified
425
+
426
+ # I don't know good way to get worksheet feed URL from cells feed URL.
427
+ # Probably it would be cleaner to keep worksheet feed URL and get cells feed URL
428
+ # from it.
429
+ if !(@cells_feed_url =~
430
+ %r{^http://spreadsheets.google.com/feeds/cells/(.*)/(.*)/private/full$})
431
+ raise(GoogleSpreadsheet::Error,
432
+ "cells feed URL is in unknown format: #{@cells_feed_url}")
433
+ end
434
+ ws_doc = @session.get(
435
+ "http://spreadsheets.google.com/feeds/worksheets/#{$1}/private/full/#{$2}")
436
+ edit_url = ws_doc.search("link[@rel='edit']")[0]["href"]
437
+ xml = <<-"EOS"
438
+ <entry xmlns='http://www.w3.org/2005/Atom'
439
+ xmlns:gs='http://schemas.google.com/spreadsheets/2006'>
440
+ <title>#{h(@title)}</title>
441
+ <gs:rowCount>#{h(@max_rows)}</gs:rowCount>
442
+ <gs:colCount>#{h(@max_cols)}</gs:colCount>
443
+ </entry>
444
+ EOS
445
+ @session.put(edit_url, xml)
446
+
447
+ @meta_modified = false
448
+ sent = true
449
+
450
+ end
451
+
452
+ if !@modified.empty?
453
+
454
+ # Gets id and edit URL for each cell.
455
+ # Note that return-empty=true is required to get those info for empty cells.
456
+ cell_entries = {}
457
+ rows = @modified.map(){ |r, c| r }
458
+ cols = @modified.map(){ |r, c| c }
459
+ url = "#{@cells_feed_url}?return-empty=true&min-row=#{rows.min}&max-row=#{rows.max}" +
460
+ "&min-col=#{cols.min}&max-col=#{cols.max}"
461
+ doc = @session.get(url)
462
+ for entry in doc.search("entry")
463
+ row = entry.search("gs:cell")[0]["row"].to_i()
464
+ col = entry.search("gs:cell")[0]["col"].to_i()
465
+ cell_entries[[row, col]] = entry
466
+ end
467
+
468
+ # Updates cell values using batch operation.
469
+ xml = <<-"EOS"
470
+ <feed xmlns="http://www.w3.org/2005/Atom"
471
+ xmlns:batch="http://schemas.google.com/gdata/batch"
472
+ xmlns:gs="http://schemas.google.com/spreadsheets/2006">
473
+ <id>#{h(@cells_feed_url)}</id>
474
+ EOS
475
+ for row, col in @modified
476
+ value = @cells[[row, col]]
477
+ entry = cell_entries[[row, col]]
478
+ id = entry.search("id").text
479
+ edit_url = entry.search("link[@rel='edit']")[0]["href"]
480
+ xml << <<-"EOS"
481
+ <entry>
482
+ <batch:id>#{h(row)},#{h(col)}</batch:id>
483
+ <batch:operation type="update"/>
484
+ <id>#{h(id)}</id>
485
+ <link rel="edit" type="application/atom+xml"
486
+ href="#{h(edit_url)}"/>
487
+ <gs:cell row="#{h(row)}" col="#{h(col)}" inputValue="#{h(value)}"/>
488
+ </entry>
489
+ EOS
490
+ end
491
+ xml << <<-"EOS"
492
+ </feed>
493
+ EOS
494
+ result = @session.post("#{@cells_feed_url}/batch", xml)
495
+ for entry in result.search("atom:entry")
496
+ interrupted = entry.search("batch:interrupted")[0]
497
+ if interrupted
498
+ raise(GoogleSpreadsheet::Error, "Update has failed: %s" %
499
+ interrupted["reason"])
500
+ end
501
+ if !(entry.search("batch:status")[0]["code"] =~ /^2/)
502
+ raise(GoogleSpreadsheet::Error, "Updating cell %s has failed: %s" %
503
+ [entry.search("atom:id").text, entry.search("batch:status")[0]["reason"]])
504
+ end
505
+ end
506
+
507
+ @modified.clear()
508
+ sent = true
509
+
510
+ end
511
+ return sent
512
+ end
513
+
514
+ # Calls save() and reload().
515
+ def synchronize()
516
+ save()
517
+ reload()
518
+ end
519
+
520
+ # Returns true if you have changes made by []= which haven't been saved.
521
+ def dirty?
522
+ return !@modified.empty?
523
+ end
524
+
525
+ end
526
+
527
+
528
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: stympy-google-spreadsheet-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ platform: ruby
6
+ authors:
7
+ - Hiroshi Ichikawa
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-07-13 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: hpricot
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0.3"
24
+ version:
25
+ description: This is a library to read/write Google Spreadsheet.
26
+ email:
27
+ - gimite+github@gmail.com
28
+ executables: []
29
+
30
+ extensions: []
31
+
32
+ extra_rdoc_files:
33
+ - README.txt
34
+ files:
35
+ - README.txt
36
+ - lib/google_spreadsheet.rb
37
+ has_rdoc: true
38
+ homepage: http://github.com/gimite/google-spreadsheet-ruby/tree/master
39
+ post_install_message:
40
+ rdoc_options:
41
+ - --main
42
+ - README.txt
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: "0"
50
+ version:
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: "0"
56
+ version:
57
+ requirements: []
58
+
59
+ rubyforge_project:
60
+ rubygems_version: 1.2.0
61
+ signing_key:
62
+ specification_version: 2
63
+ summary: This is a library to read/write Google Spreadsheet.
64
+ test_files: []
65
+