commondream-google-spreadsheet-ruby 0.0.4

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