commondream-google-spreadsheet-ruby 0.0.4

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