stympy-google-spreadsheet-ruby 0.0.3

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.
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
+