google_drive 2.1.12 → 3.0.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: a6e8bc240087cb507a378a4ff02886cad3e2b7ed
4
- data.tar.gz: 07a8f238c8cc89072248fd6db3b2ca0b7ab18d37
2
+ SHA256:
3
+ metadata.gz: 8579ea758131c51c083928052abf518b1f327b9b9666e798c17007801f0c1476
4
+ data.tar.gz: c04c47fe2d0337bb2e471227ad66e90b857b33784f01f8d60d7406f63fcad92b
5
5
  SHA512:
6
- metadata.gz: 55ff686961a787c15738c8177f6aada5b4df2851019116e120310e2523684fed2d389b9658ad7f7d832ce125bcaf608467e7f7244a76109c0c649491d14ac773
7
- data.tar.gz: b89df0376ae01ef915ae88193059a809eccbfa410f2a84a0e30c35415f08789f4021567d8da52a85e8f7cddd53319b084ddbc161deeb6ba19f0a0dd072e3cfec
6
+ metadata.gz: d52bac1e4e05bd2dee4503a67a4c9da7eaeee700dfa9bca2f668bc88c04df065b5a7c209a4808d98a63610cb27318df3bef859e6e016ba4fbe1029782440aa58
7
+ data.tar.gz: 24facac6a6116210ea76b82f139fcd5c93e5624fbdb889f2fb89de65c98a6eb11ae31fb0c892cb221a5b689e2fc5271bb6636ad1215559e2689a8ef9b5edbf85
data/README.md CHANGED
@@ -5,7 +5,7 @@ This is a Ruby library to read/write files/spreadsheets in Google Drive/Docs.
5
5
  NOTE: This is NOT a library to create Google Drive App.
6
6
 
7
7
 
8
- * [Migration from ver. 0.x.x / 1.x.x](#migration)
8
+ * [Migration from ver. 2.x.x or before](#migration)
9
9
  * [How to install](#install)
10
10
  * [How to use](#use)
11
11
  * [API documentation](http://www.rubydoc.info/gems/google_drive)
@@ -16,7 +16,7 @@ NOTE: This is NOT a library to create Google Drive App.
16
16
  * [Author](#author)
17
17
 
18
18
 
19
- ## <a name="migration">Migration from ver. 0.x.x / 1.x.x</a>
19
+ ## <a name="migration">Migration from ver. 2.x.x or before</a>
20
20
 
21
21
  There are some incompatible API changes. See
22
22
  [MIGRATING.md](https://github.com/gimite/google-drive-ruby/blob/master/MIGRATING.md).
@@ -21,8 +21,8 @@ module GoogleDrive
21
21
  def initialize(session, file)
22
22
  @session = session
23
23
  @file = file
24
- api_permissions = @session.drive.list_permissions(
25
- @file.id, fields: '*', supports_team_drives: true
24
+ api_permissions = @session.drive_service.list_permissions(
25
+ @file.id, fields: '*', supports_all_drives: true
26
26
  )
27
27
  @entries =
28
28
  api_permissions.permissions.map { |perm| AclEntry.new(perm, self) }
@@ -70,10 +70,10 @@ module GoogleDrive
70
70
  def push(params_or_entry, options = {})
71
71
  entry = params_or_entry.is_a?(AclEntry) ?
72
72
  params_or_entry : AclEntry.new(params_or_entry)
73
- api_permission = @session.drive.create_permission(
73
+ api_permission = @session.drive_service.create_permission(
74
74
  @file.id,
75
75
  entry.params,
76
- { fields: '*', supports_team_drives: true }.merge(options)
76
+ **{ fields: '*', supports_all_drives: true }.merge(options)
77
77
  )
78
78
  new_entry = AclEntry.new(api_permission, self)
79
79
  @entries.push(new_entry)
@@ -85,20 +85,20 @@ module GoogleDrive
85
85
  # e.g.
86
86
  # spreadsheet.acl.delete(spreadsheet.acl[1])
87
87
  def delete(entry)
88
- @session.drive.delete_permission(
89
- @file.id, entry.id, supports_team_drives: true
88
+ @session.drive_service.delete_permission(
89
+ @file.id, entry.id, supports_all_drives: true
90
90
  )
91
91
  @entries.delete(entry)
92
92
  end
93
93
 
94
94
  # @api private
95
95
  def update_role(entry)
96
- api_permission = @session.drive.update_permission(
96
+ api_permission = @session.drive_service.update_permission(
97
97
  @file.id,
98
98
  entry.id,
99
99
  { role: entry.role },
100
100
  fields: '*',
101
- supports_team_drives: true
101
+ supports_all_drives: true
102
102
  )
103
103
  entry.api_permission = api_permission
104
104
  entry
@@ -4,6 +4,7 @@
4
4
  require 'net/https'
5
5
  require 'uri'
6
6
  require 'google/apis/drive_v3'
7
+ require 'google/apis/sheets_v4'
7
8
  Net::HTTP.version_1_2
8
9
 
9
10
  module GoogleDrive
@@ -19,30 +20,35 @@ module GoogleDrive
19
20
 
20
21
  def initialize(authorization, client_options, request_options)
21
22
  @drive = Google::Apis::DriveV3::DriveService.new
22
- @drive.authorization = authorization
23
-
24
- # Make the timeout virtually infinite because some of the operations
25
- # (e.g., uploading a large file) can take very long.
26
- # This value is the maximal allowed timeout in seconds on JRuby.
27
- t = (2**31 - 1) / 1000
28
- @drive.client_options.open_timeout_sec = t
29
- @drive.client_options.read_timeout_sec = t
30
- @drive.client_options.send_timeout_sec = t
31
-
32
- if client_options
33
- @drive.client_options.members.each do |name|
34
- if !client_options[name].nil?
35
- @drive.client_options[name] = client_options[name]
23
+ @sheets = Google::Apis::SheetsV4::SheetsService.new
24
+
25
+ [@drive, @sheets].each do |service|
26
+ service.authorization = authorization
27
+
28
+ # Make the timeout virtually infinite because some of the operations
29
+ # (e.g., uploading a large file) can take very long.
30
+ # This value is the maximal allowed timeout in seconds on JRuby.
31
+ t = (2**31 - 1) / 1000
32
+ service.client_options.open_timeout_sec = t
33
+ service.client_options.read_timeout_sec = t
34
+ service.client_options.send_timeout_sec = t
35
+
36
+ if client_options
37
+ service.client_options.members.each do |name|
38
+ if !client_options[name].nil?
39
+ service.client_options[name] = client_options[name]
40
+ end
36
41
  end
37
42
  end
38
- end
39
43
 
40
- if request_options
41
- @drive.request_options = @drive.request_options.merge(request_options)
44
+ if request_options
45
+ service.request_options = service.request_options.merge(request_options)
46
+ end
42
47
  end
43
48
  end
44
49
 
45
50
  attr_reader(:drive)
51
+ attr_reader(:sheets)
46
52
 
47
53
  def request_raw(method, url, data, extra_header, _auth)
48
54
  options = @drive.request_options.merge(header: extra_header)
@@ -19,16 +19,16 @@ module GoogleDrive
19
19
 
20
20
  # Adds the given GoogleDrive::File to the folder.
21
21
  def add(file)
22
- @session.drive.update_file(
23
- file.id, add_parents: id, fields: '', supports_team_drives: true
22
+ @session.drive_service.update_file(
23
+ file.id, add_parents: id, fields: '', supports_all_drives: true
24
24
  )
25
25
  nil
26
26
  end
27
27
 
28
28
  # Removes the given GoogleDrive::File from the folder.
29
29
  def remove(file)
30
- @session.drive.update_file(
31
- file.id, remove_parents: id, fields: '', supports_team_drives: true
30
+ @session.drive_service.update_file(
31
+ file.id, remove_parents: id, fields: '', supports_all_drives: true
32
32
  )
33
33
  end
34
34
 
@@ -61,8 +61,8 @@ module GoogleDrive
61
61
  parents: [id]
62
62
  }.merge(file_properties)
63
63
 
64
- file = @session.drive.create_file(
65
- file_metadata, fields: '*', supports_team_drives: true
64
+ file = @session.drive_service.create_file(
65
+ file_metadata, fields: '*', supports_all_drives: true
66
66
  )
67
67
 
68
68
  @session.wrap_api_file(file)
@@ -38,8 +38,8 @@ module GoogleDrive
38
38
 
39
39
  # Reloads file metadata such as title and acl.
40
40
  def reload_metadata
41
- @api_file = @session.drive.get_file(
42
- id, fields: '*', supports_team_drives: true
41
+ @api_file = @session.drive_service.get_file(
42
+ id, fields: '*', supports_all_drives: true
43
43
  )
44
44
  @acl = Acl.new(@session, self) if @acl
45
45
  end
@@ -94,9 +94,9 @@ module GoogleDrive
94
94
  #
95
95
  # To export the file in other formats, use export_as_file.
96
96
  def download_to_file(path, params = {})
97
- @session.drive.get_file(
97
+ @session.drive_service.get_file(
98
98
  id,
99
- { download_dest: path, supports_team_drives: true }.merge(params)
99
+ { download_dest: path, supports_all_drives: true }.merge(params)
100
100
  )
101
101
  end
102
102
 
@@ -113,9 +113,9 @@ module GoogleDrive
113
113
  #
114
114
  # To export the file in other formats, use export_to_io.
115
115
  def download_to_io(io, params = {})
116
- @session.drive.get_file(
116
+ @session.drive_service.get_file(
117
117
  id,
118
- { download_dest: io, supports_team_drives: true }.merge(params)
118
+ **{ download_dest: io, supports_all_drives: true }.merge(params)
119
119
  )
120
120
  end
121
121
 
@@ -184,8 +184,8 @@ module GoogleDrive
184
184
 
185
185
  # Reads content from +io+ and updates the file with the content.
186
186
  def update_from_io(io, params = {})
187
- params = { upload_source: io, supports_team_drives: true }.merge(params)
188
- @session.drive.update_file(id, nil, params)
187
+ params = { upload_source: io, supports_all_drives: true }.merge(params)
188
+ @session.drive_service.update_file(id, nil, **params)
189
189
  nil
190
190
  end
191
191
 
@@ -193,10 +193,10 @@ module GoogleDrive
193
193
  # If +permanent+ is +true+, deletes the file permanently.
194
194
  def delete(permanent = false)
195
195
  if permanent
196
- @session.drive.delete_file(id, supports_team_drives: true)
196
+ @session.drive_service.delete_file(id, supports_all_drives: true)
197
197
  else
198
- @session.drive.update_file(
199
- id, { trashed: true }, supports_team_drives: true
198
+ @session.drive_service.update_file(
199
+ id, { trashed: true }, supports_all_drives: true
200
200
  )
201
201
  end
202
202
  nil
@@ -204,8 +204,8 @@ module GoogleDrive
204
204
 
205
205
  # Renames title of the file.
206
206
  def rename(title)
207
- @session.drive.update_file(
208
- id, { name: title }, supports_team_drives: true
207
+ @session.drive_service.update_file(
208
+ id, { name: title }, supports_all_drives: true
209
209
  )
210
210
  nil
211
211
  end
@@ -214,8 +214,8 @@ module GoogleDrive
214
214
 
215
215
  # Creates copy of this file with the given title.
216
216
  def copy(title, file_properties = {})
217
- api_file = @session.drive.copy_file(
218
- id, { name: title }.merge(file_properties), fields: '*', supports_team_drives: true
217
+ api_file = @session.drive_service.copy_file(
218
+ id, { name: title }.merge(file_properties), fields: '*', supports_all_drives: true
219
219
  )
220
220
  @session.wrap_api_file(api_file)
221
221
  end
@@ -259,7 +259,7 @@ module GoogleDrive
259
259
 
260
260
  def export_to_dest(dest, format)
261
261
  mime_type = EXT_TO_CONTENT_TYPE['.' + format] || format
262
- @session.drive.export_file(id, mime_type, download_dest: dest)
262
+ @session.drive_service.export_file(id, mime_type, download_dest: dest)
263
263
  nil
264
264
  end
265
265
  end
@@ -141,15 +141,13 @@ module GoogleDrive
141
141
  config.client_id = options[:client_id]
142
142
  config.client_secret = options[:client_secret]
143
143
  end
144
- if !config.client_id && !config.client_secret
145
- config.client_id =
146
- '452925651630-egr1f18o96acjjvphpbbd1qlsevkho1d.' \
147
- 'apps.googleusercontent.com'
148
- config.client_secret = '1U3-Krii5x1oLPrwD5zgn-ry'
149
- elsif !config.client_id || !config.client_secret
144
+ if !config.client_id || !config.client_secret
150
145
  raise(
151
146
  ArgumentError,
152
- 'client_id and client_secret must be both specified or both omitted'
147
+ 'client_id or client_secret is missing in the config. Follow ' \
148
+ 'https://github.com/gimite/google-drive-ruby/blob/master/doc/authorization.md ' \
149
+ 'to provide a valid config. google_drive library no longer provides ' \
150
+ 'the default credential due to a limitation of Google API.'
153
151
  )
154
152
  end
155
153
 
@@ -219,10 +217,17 @@ module GoogleDrive
219
217
  attr_accessor :on_auth_fail
220
218
 
221
219
  # Returns an instance of Google::Apis::DriveV3::DriveService.
222
- def drive
220
+ def drive_service
223
221
  @fetcher.drive
224
222
  end
225
223
 
224
+ alias drive drive_service
225
+
226
+ # Returns an instance of Google::Apis::SheetsV4::SheetsService.
227
+ def sheets_service
228
+ @fetcher.sheets
229
+ end
230
+
226
231
  # Returns list of files for the user as array of GoogleDrive::File or its
227
232
  # subclass. You can specify parameters documented at
228
233
  # https://developers.google.com/drive/v3/web/search-parameters
@@ -247,8 +252,8 @@ module GoogleDrive
247
252
  def files(params = {}, &block)
248
253
  params = convert_params(params)
249
254
  execute_paged!(
250
- method: drive.method(:list_files),
251
- parameters: { fields: '*', supports_team_drives: true }.merge(params),
255
+ method: drive_service.method(:list_files),
256
+ parameters: { fields: '*', supports_all_drives: true, include_items_from_all_drives: true }.merge(params),
252
257
  items_method_name: :files,
253
258
  converter: proc { |af| wrap_api_file(af) },
254
259
  &block
@@ -280,7 +285,7 @@ module GoogleDrive
280
285
  # Returns an instance of GoogleDrive::File or its subclass
281
286
  # (GoogleDrive::Spreadsheet, GoogleDrive::Collection).
282
287
  def file_by_id(id)
283
- api_file = drive.get_file(id, fields: '*', supports_team_drives: true)
288
+ api_file = drive_service.get_file(id, fields: '*', supports_all_drives: true)
284
289
  wrap_api_file(api_file)
285
290
  end
286
291
 
@@ -379,13 +384,12 @@ module GoogleDrive
379
384
  # "1smypkyAz4STrKO4Zkos5Z4UPUJKvvgIza32LnlQ7OGw/od7/private/full")
380
385
  def worksheet_by_url(url)
381
386
  case url
382
- when %r{^https?://spreadsheets.google.com/feeds/worksheets/.*/.*/full/.*$}
383
- worksheet_feed_url = url
384
- when %r{^https?://spreadsheets.google.com/feeds/cells/(.*)/(.*)/private/full((\?.*)?)$}
385
- worksheet_feed_url =
386
- 'https://spreadsheets.google.com/feeds/worksheets/' \
387
- "#{Regexp.last_match(1)}/private/full/" \
388
- "#{Regexp.last_match(2)}#{Regexp.last_match(3)}"
387
+ when %r{^https?://spreadsheets.google.com/feeds/worksheets/(.*)/.*/full/(.*)$}
388
+ spreadsheet_id = Regexp.last_match(1)
389
+ worksheet_feed_id = Regexp.last_match(2)
390
+ when %r{^https?://spreadsheets.google.com/feeds/cells/(.*)/(.*)/private/full(\?.*)?$}
391
+ spreadsheet_id = Regexp.last_match(1)
392
+ worksheet_feed_id = Regexp.last_match(2)
389
393
  else
390
394
  raise(
391
395
  GoogleDrive::Error,
@@ -394,8 +398,15 @@ module GoogleDrive
394
398
  )
395
399
  end
396
400
 
397
- worksheet_feed_entry = request(:get, worksheet_feed_url)
398
- Worksheet.new(self, nil, worksheet_feed_entry)
401
+ spreadsheet = spreadsheet_by_key(spreadsheet_id)
402
+ worksheet = spreadsheet.worksheets.find{ |ws| ws.worksheet_feed_id == worksheet_feed_id }
403
+ unless worksheet
404
+ raise(
405
+ GoogleDrive::Error,
406
+ "Worksheet not found for the given URL: #{url}"
407
+ )
408
+ end
409
+ worksheet
399
410
  end
400
411
 
401
412
  # Returns the root folder.
@@ -496,8 +507,8 @@ module GoogleDrive
496
507
  name: title,
497
508
  }.merge(file_properties)
498
509
 
499
- file = drive.create_file(
500
- file_metadata, fields: '*', supports_team_drives: true
510
+ file = drive_service.create_file(
511
+ file_metadata, fields: '*', supports_all_drives: true
501
512
  )
502
513
 
503
514
  wrap_api_file(file)
@@ -587,7 +598,7 @@ module GoogleDrive
587
598
  end
588
599
 
589
600
  elsif opts[:parameters] && opts[:parameters].key?(:page_token)
590
- response = opts[:method].call(opts[:parameters])
601
+ response = opts[:method].call(**opts[:parameters])
591
602
  items = response.__send__(opts[:items_method_name]).map do |item|
592
603
  opts[:converter] ? opts[:converter].call(item) : item
593
604
  end
@@ -643,7 +654,7 @@ module GoogleDrive
643
654
  upload_source: source,
644
655
  content_type: 'application/octet-stream',
645
656
  fields: '*',
646
- supports_team_drives: true
657
+ supports_all_drives: true
647
658
  }
648
659
  for k, v in params
649
660
  unless %i[convert convert_mime_type parents].include?(k)
@@ -661,7 +672,7 @@ module GoogleDrive
661
672
  end
662
673
  file_metadata[:parents] = params[:parents] if params[:parents]
663
674
 
664
- file = drive.create_file(file_metadata, api_params)
675
+ file = drive_service.create_file(file_metadata, **api_params)
665
676
  wrap_api_file(file)
666
677
  end
667
678
 
@@ -16,6 +16,10 @@ module GoogleDrive
16
16
  # create_spreadsheet in GoogleDrive::Session to get GoogleDrive::Spreadsheet
17
17
  # object.
18
18
  class Spreadsheet < GoogleDrive::File
19
+
20
+ # TODO: Bump up the major version before switching the existing methods to
21
+ # v4 API because it requires to turn on a new API in the API console.
22
+
19
23
  include(Util)
20
24
 
21
25
  SUPPORTED_EXPORT_FORMAT = Set.new(%w[xlsx csv pdf])
@@ -45,16 +49,8 @@ module GoogleDrive
45
49
 
46
50
  # Returns worksheets of the spreadsheet as array of GoogleDrive::Worksheet.
47
51
  def worksheets
48
- doc = @session.request(:get, worksheets_feed_url)
49
- if doc.root.name != 'feed'
50
- raise(GoogleDrive::Error,
51
- format(
52
- "%s doesn't look like a worksheets feed URL because its root " \
53
- 'is not <feed>.',
54
- worksheets_feed_url
55
- ))
56
- end
57
- doc.css('entry').map { |e| Worksheet.new(@session, self, e) }.freeze
52
+ api_spreadsheet = @session.sheets_service.get_spreadsheet(id, fields: 'sheets.properties')
53
+ api_spreadsheet.sheets.map{ |s| Worksheet.new(@session, self, s.properties) }
58
54
  end
59
55
 
60
56
  # Returns a GoogleDrive::Worksheet with the given title in the spreadsheet.
@@ -68,24 +64,32 @@ module GoogleDrive
68
64
  # Returns a GoogleDrive::Worksheet with the given gid.
69
65
  #
70
66
  # Returns nil if not found.
71
- def worksheet_by_gid(gid)
72
- gid = gid.to_s
73
- worksheets.find { |ws| ws.gid == gid }
67
+ def worksheet_by_sheet_id(sheet_id)
68
+ sheet_id = sheet_id.to_i
69
+ worksheets.find { |ws| ws.sheet_id == sheet_id }
74
70
  end
75
71
 
72
+ alias worksheet_by_gid worksheet_by_sheet_id
73
+
76
74
  # Adds a new worksheet to the spreadsheet. Returns added
77
75
  # GoogleDrive::Worksheet.
78
- def add_worksheet(title, max_rows = 100, max_cols = 20)
79
- xml = <<-"EOS"
80
- <entry xmlns='http://www.w3.org/2005/Atom'
81
- xmlns:gs='http://schemas.google.com/spreadsheets/2006'>
82
- <title>#{h(title)}</title>
83
- <gs:rowCount>#{h(max_rows)}</gs:rowCount>
84
- <gs:colCount>#{h(max_cols)}</gs:colCount>
85
- </entry>
86
- EOS
87
- doc = @session.request(:post, worksheets_feed_url, data: xml)
88
- Worksheet.new(@session, self, doc.root)
76
+ #
77
+ # When +index+ is specified, the worksheet is inserted at the given
78
+ # +index+.
79
+ def add_worksheet(title, max_rows = 100, max_cols = 20, index: nil)
80
+ (response,) = batch_update([{
81
+ add_sheet: {
82
+ properties: {
83
+ title: title,
84
+ index: index,
85
+ grid_properties: {
86
+ row_count: max_rows,
87
+ column_count: max_cols,
88
+ },
89
+ },
90
+ },
91
+ }])
92
+ Worksheet.new(@session, self, response.add_sheet.properties)
89
93
  end
90
94
 
91
95
  # Not available for GoogleDrive::Spreadsheet. Use export_as_file instead.
@@ -114,5 +118,18 @@ module GoogleDrive
114
118
  'Use export_to_io instead.'
115
119
  )
116
120
  end
121
+
122
+ # Performs batch update of the spreadsheet.
123
+ #
124
+ # +requests+ is an Array of Google::Apis::SheetsV4::Request or its Hash
125
+ # equivalent. Returns an Array of Google::Apis::SheetsV4::Response.
126
+ def batch_update(requests)
127
+ batch_request =
128
+ Google::Apis::SheetsV4::BatchUpdateSpreadsheetRequest.new(
129
+ requests: requests)
130
+ batch_response =
131
+ @session.sheets_service.batch_update_spreadsheet(id, batch_request)
132
+ batch_response.replies
133
+ end
117
134
  end
118
135
  end
@@ -16,51 +16,104 @@ module GoogleDrive
16
16
  class Worksheet
17
17
  include(Util)
18
18
 
19
+ # A few default color instances that match the colors from the Google Sheets web UI.
20
+ #
21
+ # TODO: Add more colors from
22
+ # https://github.com/denilsonsa/gimp-palettes/blob/master/palettes/Google-Drive.gpl
23
+ module Colors
24
+ RED = Google::Apis::SheetsV4::Color.new(red: 1.0)
25
+ DARK_RED_1 = Google::Apis::SheetsV4::Color.new(red: 0.8)
26
+ RED_BERRY = Google::Apis::SheetsV4::Color.new(red: 0.596)
27
+ DARK_RED_BERRY_1 = Google::Apis::SheetsV4::Color.new(red: 0.659, green: 0.11)
28
+ ORANGE = Google::Apis::SheetsV4::Color.new(red: 1.0, green: 0.6)
29
+ DARK_ORANGE_1 = Google::Apis::SheetsV4::Color.new(red: 0.9, green: 0.569, blue: 0.22)
30
+ YELLOW = Google::Apis::SheetsV4::Color.new(red: 1.0, green: 1.0)
31
+ DARK_YELLOW_1 = Google::Apis::SheetsV4::Color.new(red: 0.945, green: 0.76, blue: 0.196)
32
+ GREEN = Google::Apis::SheetsV4::Color.new(green: 1.0)
33
+ DARK_GREEN_1 = Google::Apis::SheetsV4::Color.new(red: 0.416, green: 0.659, blue: 0.31)
34
+ CYAN = Google::Apis::SheetsV4::Color.new(green: 1.0, blue: 1.0)
35
+ DARK_CYAN_1 = Google::Apis::SheetsV4::Color.new(red: 0.27, green: 0.506, blue: 0.557)
36
+ CORNFLOWER_BLUE = Google::Apis::SheetsV4::Color.new(red: 0.29, green: 0.525, blue: 0.91)
37
+ DARK_CORNFLOWER_BLUE_1 = Google::Apis::SheetsV4::Color.new(red: 0.235, green: 0.47, blue: 0.847)
38
+ BLUE = Google::Apis::SheetsV4::Color.new(blue: 1.0)
39
+ DARK_BLUE_1 = Google::Apis::SheetsV4::Color.new(red: 0.239, green: 0.522, blue: 0.776)
40
+ PURPLE = Google::Apis::SheetsV4::Color.new(red: 0.6, blue: 1.0)
41
+ DARK_PURPLE_1 = Google::Apis::SheetsV4::Color.new(red: 0.404, green: 0.306, blue: 0.655)
42
+ MAGENTA = Google::Apis::SheetsV4::Color.new(red: 1.0, blue: 1.0)
43
+ DARK_MAGENTA_1 = Google::Apis::SheetsV4::Color.new(red: 0.651, green: 0.302, blue: 0.475)
44
+ WHITE = Google::Apis::SheetsV4::Color.new(red: 1.0, green: 1.0, blue: 1.0)
45
+ BLACK = Google::Apis::SheetsV4::Color.new(red: 0.0, green: 0.0, blue: 0.0)
46
+ GRAY = Google::Apis::SheetsV4::Color.new(red: 0.8, green: 0.8, blue: 0.8)
47
+ DARK_GRAY_1 = Google::Apis::SheetsV4::Color.new(red: 0.714, green: 0.714, blue: 0.714)
48
+ end
49
+
19
50
  # @api private
20
51
  # A regexp which matches an invalid character in XML 1.0:
21
52
  # https://en.wikipedia.org/wiki/Valid_characters_in_XML#XML_1.0
22
- XML_INVAILD_CHAR_REGEXP =
53
+ XML_INVALID_CHAR_REGEXP =
23
54
  /[^\u0009\u000a\u000d\u0020-\ud7ff\ue000-\ufffd\u{10000}-\u{10ffff}]/
24
55
 
25
56
  # @api private
26
- def initialize(session, spreadsheet, worksheet_feed_entry)
57
+ def initialize(session, spreadsheet, properties)
27
58
  @session = session
28
59
  @spreadsheet = spreadsheet
29
- set_worksheet_feed_entry(worksheet_feed_entry)
30
-
60
+ set_properties(properties)
31
61
  @cells = nil
32
62
  @input_values = nil
33
63
  @numeric_values = nil
34
64
  @modified = Set.new
35
65
  @list = nil
66
+ @v4_requests = []
36
67
  end
37
68
 
38
69
  # Nokogiri::XML::Element object of the <entry> element in a worksheets feed.
39
- attr_reader(:worksheet_feed_entry)
70
+ #
71
+ # DEPRECATED: This method is deprecated, and now requires additional
72
+ # network fetch. Consider using properties instead.
73
+ def worksheet_feed_entry
74
+ @worksheet_feed_entry ||= @session.request(:get, worksheet_feed_url).root
75
+ end
76
+
77
+ # Google::Apis::SheetsV4::SheetProperties object for this worksheet.
78
+ attr_reader :properties
40
79
 
41
80
  # Title of the worksheet (shown as tab label in Web interface).
42
- attr_reader(:title)
81
+ attr_reader :title
82
+
83
+ # Index of the worksheet (affects tab order in web interface).
84
+ attr_reader :index
85
+
86
+ # GoogleDrive::Spreadsheet which this worksheet belongs to.
87
+ attr_reader :spreadsheet
43
88
 
44
89
  # Time object which represents the time the worksheet was last updated.
45
- attr_reader(:updated)
90
+ #
91
+ # DEPRECATED: From google_drive 3.0.0, it returns the time the
92
+ # *spreadsheet* was last updated, instead of the worksheet. This is because
93
+ # it looks the information is not available in Sheets v4 API.
94
+ def updated
95
+ spreadsheet.modified_time.to_time
96
+ end
46
97
 
47
98
  # URL of cell-based feed of the worksheet.
99
+ #
100
+ # DEPRECATED: This method is deprecated, and now requires additional
101
+ # network fetch.
48
102
  def cells_feed_url
49
- @worksheet_feed_entry.css(
103
+ worksheet_feed_entry.css(
50
104
  "link[rel='http://schemas.google.com/spreadsheets/2006#cellsfeed']"
51
105
  )[0]['href']
52
106
  end
53
107
 
54
108
  # URL of worksheet feed URL of the worksheet.
55
109
  def worksheet_feed_url
56
- @worksheet_feed_entry.css("link[rel='self']")[0]['href']
110
+ return '%s/%s' % [spreadsheet.worksheets_feed_url, worksheet_feed_id]
57
111
  end
58
112
 
59
113
  # URL to export the worksheet as CSV.
60
114
  def csv_export_url
61
- @worksheet_feed_entry.css(
62
- "link[rel='http://schemas.google.com/spreadsheets/2006#exportcsv']"
63
- )[0]['href']
115
+ 'https://docs.google.com/spreadsheets/d/%s/export?gid=%s&format=csv' %
116
+ [spreadsheet.id, gid]
64
117
  end
65
118
 
66
119
  # Exports the worksheet as String in CSV format.
@@ -74,10 +127,14 @@ module GoogleDrive
74
127
  open(path, 'wb') { |f| f.write(data) }
75
128
  end
76
129
 
77
- # gid of the worksheet.
130
+ # ID of the worksheet.
131
+ def sheet_id
132
+ @properties.sheet_id
133
+ end
134
+
135
+ # Returns sheet_id.to_s.
78
136
  def gid
79
- # A bit tricky but couldn't find a better way.
80
- CGI.parse(URI.parse(csv_export_url).query)['gid'].last
137
+ sheet_id.to_s
81
138
  end
82
139
 
83
140
  # URL to view/edit the worksheet in a Web browser.
@@ -85,18 +142,22 @@ module GoogleDrive
85
142
  format("%s\#gid=%s", spreadsheet.human_url, gid)
86
143
  end
87
144
 
88
- # GoogleDrive::Spreadsheet which this worksheet belongs to.
89
- def spreadsheet
90
- unless @spreadsheet
91
- unless worksheet_feed_url =~
92
- %r{https?://spreadsheets\.google\.com/feeds/worksheets/(.*)/(.*)$}
93
- raise(GoogleDrive::Error,
94
- 'Worksheet feed URL is in unknown format: ' \
95
- "#{worksheet_feed_url}")
96
- end
97
- @spreadsheet = @session.file_by_id(Regexp.last_match(1))
98
- end
99
- @spreadsheet
145
+ # Copy worksheet to specified spreadsheet.
146
+ # This method can take either instance of GoogleDrive::Spreadsheet or its id.
147
+ def copy_to(spreadsheet_or_id)
148
+ destination_spreadsheet_id =
149
+ spreadsheet_or_id.respond_to?(:id) ?
150
+ spreadsheet_or_id.id : spreadsheet_or_id
151
+ request = Google::Apis::SheetsV4::CopySheetToAnotherSpreadsheetRequest.new(
152
+ destination_spreadsheet_id: destination_spreadsheet_id,
153
+ )
154
+ @session.sheets_service.copy_spreadsheet(spreadsheet.id, sheet_id, request)
155
+ nil
156
+ end
157
+
158
+ # Copy worksheet to owner spreadsheet.
159
+ def duplicate
160
+ copy_to(spreadsheet)
100
161
  end
101
162
 
102
163
  # Returns content of the cell as String. Arguments must be either
@@ -240,6 +301,13 @@ module GoogleDrive
240
301
  @meta_modified = true
241
302
  end
242
303
 
304
+ # Updates index of the worksheet.
305
+ # Note that update is not sent to the server until you call save().
306
+ def index=(index)
307
+ @index = index
308
+ @meta_modified = true
309
+ end
310
+
243
311
  # @api private
244
312
  def cells
245
313
  reload_cells unless @cells
@@ -314,8 +382,19 @@ module GoogleDrive
314
382
  # Note that changes you made by []= etc. is discarded if you haven't called
315
383
  # save().
316
384
  def reload
317
- set_worksheet_feed_entry(@session.request(:get, worksheet_feed_url).root)
318
- reload_cells
385
+ api_spreadsheet =
386
+ @session.sheets_service.get_spreadsheet(
387
+ spreadsheet.id,
388
+ ranges: "'%s'" % @title,
389
+ fields:
390
+ 'sheets(properties,data.rowData.values' \
391
+ '(formattedValue,userEnteredValue,effectiveValue))'
392
+ )
393
+ api_sheet = api_spreadsheet.sheets[0]
394
+ set_properties(api_sheet.properties)
395
+ update_cells_from_api_sheet(api_sheet)
396
+ @v4_requests = []
397
+ @worksheet_feed_entry = nil
319
398
  true
320
399
  end
321
400
 
@@ -324,122 +403,59 @@ module GoogleDrive
324
403
  sent = false
325
404
 
326
405
  if @meta_modified
406
+ add_request({
407
+ update_sheet_properties: {
408
+ properties: {
409
+ sheet_id: sheet_id,
410
+ title: title,
411
+ index: index,
412
+ grid_properties: {row_count: max_rows, column_count: max_cols},
413
+ },
414
+ fields: '*',
415
+ },
416
+ })
417
+ end
327
418
 
328
- edit_url = @worksheet_feed_entry.css("link[rel='edit']")[0]['href']
329
- xml = <<-"EOS"
330
- <entry xmlns='http://www.w3.org/2005/Atom'
331
- xmlns:gs='http://schemas.google.com/spreadsheets/2006'>
332
- <title>#{h(title)}</title>
333
- <gs:rowCount>#{h(max_rows)}</gs:rowCount>
334
- <gs:colCount>#{h(max_cols)}</gs:colCount>
335
- </entry>
336
- EOS
337
-
338
- result = @session.request(
339
- :put,
340
- edit_url,
341
- data: xml,
342
- header: {
343
- 'Content-Type' => 'application/atom+xml;charset=utf-8',
344
- 'If-Match' => '*'
345
- }
346
- )
347
- set_worksheet_feed_entry(result.root)
348
-
419
+ if !@v4_requests.empty?
420
+ self.spreadsheet.batch_update(@v4_requests)
421
+ @v4_requests = []
349
422
  sent = true
350
423
  end
351
424
 
352
- unless @modified.empty?
353
- # Gets id and edit URL for each cell.
354
- # Note that return-empty=true is required to get those info for empty cells.
355
- cell_entries = {}
356
- rows = @modified.map { |r, _c| r }
357
- cols = @modified.map { |_r, c| c }
358
- url = concat_url(
359
- cells_feed_url,
360
- "?return-empty=true&min-row=#{rows.min}&max-row=#{rows.max}" \
361
- "&min-col=#{cols.min}&max-col=#{cols.max}"
362
- )
363
- doc = @session.request(:get, url)
425
+ @remote_title = @title
364
426
 
365
- doc.css('entry').each do |entry|
366
- row = entry.css('gs|cell')[0]['row'].to_i
367
- col = entry.css('gs|cell')[0]['col'].to_i
368
- cell_entries[[row, col]] = entry
427
+ unless @modified.empty?
428
+ min_modified_row = 1.0 / 0.0
429
+ max_modified_row = 0
430
+ min_modified_col = 1.0 / 0.0
431
+ max_modified_col = 0
432
+ @modified.each do |r, c|
433
+ min_modified_row = r if r < min_modified_row
434
+ max_modified_row = r if r > max_modified_row
435
+ min_modified_col = c if c < min_modified_col
436
+ max_modified_col = c if c > max_modified_col
369
437
  end
370
438
 
371
- xml = <<-EOS
372
- <feed xmlns="http://www.w3.org/2005/Atom"
373
- xmlns:batch="http://schemas.google.com/gdata/batch"
374
- xmlns:gs="http://schemas.google.com/spreadsheets/2006">
375
- <id>#{h(cells_feed_url)}</id>
376
- EOS
377
- @modified.each do |row, col|
378
- value = @cells[[row, col]]
379
- entry = cell_entries[[row, col]]
380
- id = entry.css('id').text
381
- edit_link = entry.css("link[rel='edit']")[0]
382
- unless edit_link
383
- raise(
384
- GoogleDrive::Error,
385
- format(
386
- "The user doesn't have write permission to the spreadsheet: %p",
387
- spreadsheet
388
- )
389
- )
439
+ # Uses update_spreadsheet_value instead batch_update_spreadsheet with
440
+ # update_cells. batch_update_spreadsheet has benefit that the request
441
+ # can be batched with other requests. But it has drawback that the
442
+ # type of the value (string_value, number_value, etc.) must be
443
+ # explicitly specified in user_entered_value. Since I don't know exact
444
+ # logic to determine the type from text, I chose to use
445
+ # update_spreadsheet_value here.
446
+ range = "'%s'!R%dC%d:R%dC%d" %
447
+ [@title, min_modified_row, min_modified_col, max_modified_row, max_modified_col]
448
+ values = (min_modified_row..max_modified_row).map do |r|
449
+ (min_modified_col..max_modified_col).map do |c|
450
+ @modified.include?([r, c]) ? (@cells[[r, c]] || '') : nil
390
451
  end
391
- edit_url = edit_link['href']
392
- xml << <<-EOS
393
- <entry>
394
- <batch:id>#{h(row)},#{h(col)}</batch:id>
395
- <batch:operation type="update"/>
396
- <id>#{h(id)}</id>
397
- <link
398
- rel="edit"
399
- type="application/atom+xml"
400
- href="#{h(edit_url)}"/>
401
- <gs:cell
402
- row="#{h(row)}"
403
- col="#{h(col)}"
404
- inputValue="#{h(value)}"/>
405
- </entry>
406
- EOS
407
- end
408
- xml << <<-"EOS"
409
- </feed>
410
- EOS
411
-
412
- batch_url = concat_url(cells_feed_url, '/batch')
413
- result = @session.request(
414
- :post,
415
- batch_url,
416
- data: xml,
417
- header: {
418
- 'Content-Type' => 'application/atom+xml;charset=utf-8',
419
- 'If-Match' => '*'
420
- }
421
- )
422
- result.css('entry').each do |entry|
423
- interrupted = entry.css('batch|interrupted')[0]
424
- if interrupted
425
- raise(
426
- GoogleDrive::Error,
427
- format('Update has failed: %s', interrupted['reason'])
428
- )
429
- end
430
- next if entry.css('batch|status').first['code'] =~ /^2/
431
- raise(
432
- GoogleDrive::Error,
433
- format(
434
- 'Updating cell %s has failed: %s',
435
- entry.css('id').text, entry.css('batch|status')[0]['reason']
436
- )
437
- )
438
452
  end
453
+ value_range = Google::Apis::SheetsV4::ValueRange.new(values: values)
454
+ @session.sheets_service.update_spreadsheet_value(
455
+ spreadsheet.id, range, value_range, value_input_option: 'USER_ENTERED')
439
456
 
440
457
  @modified.clear
441
458
  sent = true
442
-
443
459
  end
444
460
 
445
461
  sent
@@ -454,17 +470,20 @@ module GoogleDrive
454
470
  # Deletes this worksheet. Deletion takes effect right away without calling
455
471
  # save().
456
472
  def delete
457
- ws_doc = @session.request(:get, worksheet_feed_url)
458
- edit_url = ws_doc.css("link[rel='edit']")[0]['href']
459
- @session.request(:delete, edit_url)
473
+ spreadsheet.batch_update([{
474
+ delete_sheet: Google::Apis::SheetsV4::DeleteSheetRequest.new(sheet_id: sheet_id),
475
+ }])
460
476
  end
461
477
 
462
- # Returns true if you have changes made by []= which haven't been saved.
478
+ # Returns true if you have changes made by []= etc. which haven't been saved.
463
479
  def dirty?
464
- !@modified.empty?
480
+ !@modified.empty? || !@v4_requests.empty?
465
481
  end
466
482
 
467
483
  # List feed URL of the worksheet.
484
+ #
485
+ # DEPRECATED: This method is deprecated, and now requires additional
486
+ # network fetch.
468
487
  def list_feed_url
469
488
  @worksheet_feed_entry.css(
470
489
  "link[rel='http://schemas.google.com/spreadsheets/2006#listfeed']"
@@ -517,7 +536,7 @@ module GoogleDrive
517
536
  end
518
537
 
519
538
  def inspect
520
- fields = { worksheet_feed_url: worksheet_feed_url }
539
+ fields = { spreadsheet_id: spreadsheet.id, gid: gid }
521
540
  fields[:title] = @title if @title
522
541
  format(
523
542
  "\#<%p %s>",
@@ -526,39 +545,170 @@ module GoogleDrive
526
545
  )
527
546
  end
528
547
 
529
- private
548
+ # Merges a range of cells together. "MERGE_COLUMNS" is another option for merge_type
549
+ def merge_cells(top_row, left_col, num_rows, num_cols, merge_type: 'MERGE_ALL')
550
+ range = v4_range_object(top_row, left_col, num_rows, num_cols)
551
+ add_request({
552
+ merge_cells:
553
+ Google::Apis::SheetsV4::MergeCellsRequest.new(
554
+ range: range, merge_type: merge_type),
555
+ })
556
+ end
557
+
558
+ # Changes the formatting of a range of cells to match the given number format.
559
+ # For example to change A1 to a percentage with 1 decimal point:
560
+ # worksheet.set_number_format(1, 1, 1, 1, "##.#%")
561
+ # Google API reference: https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets#numberformat
562
+ def set_number_format(top_row, left_col, num_rows, num_cols, pattern, type: "NUMBER")
563
+ number_format = Google::Apis::SheetsV4::NumberFormat.new(type: type, pattern: pattern)
564
+ format = Google::Apis::SheetsV4::CellFormat.new(number_format: number_format)
565
+ fields = 'userEnteredFormat(numberFormat)'
566
+ format_cells(top_row, left_col, num_rows, num_cols, format, fields)
567
+ end
568
+
569
+ # Changes text alignment of a range of cells.
570
+ # Horizontal alignment can be "LEFT", "CENTER", or "RIGHT".
571
+ # Vertical alignment can be "TOP", "MIDDLE", or "BOTTOM".
572
+ # Google API reference: https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets#HorizontalAlign
573
+ def set_text_alignment(
574
+ top_row, left_col, num_rows, num_cols,
575
+ horizontal: nil, vertical: nil)
576
+ return if horizontal.nil? && vertical.nil?
577
+
578
+ format = Google::Apis::SheetsV4::CellFormat.new(
579
+ horizontal_alignment: horizontal, vertical_alignment: vertical)
580
+ subfields =
581
+ (horizontal.nil? ? [] : ['horizontalAlignment']) +
582
+ (vertical.nil? ? [] : ['verticalAlignment'])
583
+
584
+ fields = 'userEnteredFormat(%s)' % subfields.join(',')
585
+ format_cells(top_row, left_col, num_rows, num_cols, format, fields)
586
+ end
587
+
588
+ # Changes the background color on a range of cells. e.g.:
589
+ # worksheet.set_background_color(1, 1, 1, 1, GoogleDrive::Worksheet::Colors::DARK_YELLOW_1)
590
+ #
591
+ # background_color is an instance of Google::Apis::SheetsV4::Color.
592
+ def set_background_color(top_row, left_col, num_rows, num_cols, background_color)
593
+ format = Google::Apis::SheetsV4::CellFormat.new(background_color: background_color)
594
+ fields = 'userEnteredFormat(backgroundColor)'
595
+ format_cells(top_row, left_col, num_rows, num_cols, format, fields)
596
+ end
597
+
598
+ # Change the text formatting on a range of cells. e.g., To set cell
599
+ # A1 to have red text that is bold and italic:
600
+ # worksheet.set_text_format(
601
+ # 1, 1, 1, 1,
602
+ # bold: true,
603
+ # italic: true,
604
+ # foreground_color: GoogleDrive::Worksheet::Colors::RED_BERRY)
605
+ #
606
+ # foreground_color is an instance of Google::Apis::SheetsV4::Color.
607
+ # Google API reference:
608
+ # https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets#textformat
609
+ def set_text_format(top_row, left_col, num_rows, num_cols, bold: false,
610
+ italic: false, strikethrough: false, font_size: nil,
611
+ font_family: nil, foreground_color: nil)
612
+ text_format = Google::Apis::SheetsV4::TextFormat.new(
613
+ bold: bold,
614
+ italic: italic,
615
+ strikethrough: strikethrough,
616
+ font_size: font_size,
617
+ font_family: font_family,
618
+ foreground_color: foreground_color
619
+ )
530
620
 
531
- def set_worksheet_feed_entry(entry)
532
- @worksheet_feed_entry = entry
533
- @title = entry.css('title').text
534
- set_max_values(entry)
535
- @updated = Time.parse(entry.css('updated').text)
536
- @meta_modified = false
621
+ format = Google::Apis::SheetsV4::CellFormat.new(text_format: text_format)
622
+ fields = 'userEnteredFormat(textFormat)'
623
+ format_cells(top_row, left_col, num_rows, num_cols, format, fields)
624
+ end
625
+
626
+ # Update the border styles for a range of cells.
627
+ # borders is a Hash of Google::Apis::SheetsV4::Border keyed with the
628
+ # following symbols: :top, :bottom, :left, :right, :innerHorizontal, :innerVertical
629
+ # e.g., To set a black double-line on the bottom of A1:
630
+ # update_borders(
631
+ # 1, 1, 1, 1,
632
+ # {bottom: Google::Apis::SheetsV4::Border.new(
633
+ # style: "DOUBLE", color: GoogleDrive::Worksheet::Colors::BLACK)})
634
+ def update_borders(top_row, left_col, num_rows, num_cols, borders)
635
+ request = Google::Apis::SheetsV4::UpdateBordersRequest.new(borders)
636
+ request.range = v4_range_object(top_row, left_col, num_rows, num_cols)
637
+ add_request({update_borders: request})
638
+ end
639
+
640
+ # Add an instance of Google::Apis::SheetsV4::Request (or its Hash
641
+ # equivalent) which will be applied on the next call to the save method.
642
+ def add_request(request)
643
+ @v4_requests.push(request)
537
644
  end
538
645
 
539
- def set_max_values(entry)
540
- @max_rows = entry.css('gs|rowCount').text.to_i
541
- @max_cols = entry.css('gs|colCount').text.to_i
646
+ # @api private
647
+ def worksheet_feed_id
648
+ gid_int = sheet_id
649
+ xor_val = gid_int > 31578 ? 474 : 31578
650
+ letter = gid_int > 31578 ? 'o' : ''
651
+ letter + (gid_int ^ xor_val).to_s(36)
652
+ end
653
+
654
+ private
655
+
656
+ def format_cells(top_row, left_col, num_rows, num_cols, format, fields)
657
+ add_request({
658
+ repeat_cell:
659
+ Google::Apis::SheetsV4::RepeatCellRequest.new(
660
+ range: v4_range_object(top_row, left_col, num_rows, num_cols),
661
+ cell: Google::Apis::SheetsV4::CellData.new(user_entered_format: format),
662
+ fields: fields
663
+ ),
664
+ })
665
+ end
666
+
667
+ def set_properties(properties)
668
+ @properties = properties
669
+ @title = @remote_title = properties.title
670
+ @index = properties.index
671
+ if properties.grid_properties.nil?
672
+ @max_rows = @max_cols = 0
673
+ else
674
+ @max_rows = properties.grid_properties.row_count
675
+ @max_cols = properties.grid_properties.column_count
676
+ end
677
+ @meta_modified = false
542
678
  end
543
679
 
544
680
  def reload_cells
545
- doc = @session.request(:get, cells_feed_url)
681
+ response =
682
+ @session.sheets_service.get_spreadsheet(
683
+ spreadsheet.id,
684
+ ranges: "'%s'" % @remote_title,
685
+ fields: 'sheets.data.rowData.values(formattedValue,userEnteredValue,effectiveValue)'
686
+ )
687
+ update_cells_from_api_sheet(response.sheets[0])
688
+ end
546
689
 
547
- @num_cols = nil
548
- @num_rows = nil
690
+ def update_cells_from_api_sheet(api_sheet)
691
+ rows_data = api_sheet.data[0].row_data || []
549
692
 
693
+ @num_rows = rows_data.size
694
+ @num_cols = 0
550
695
  @cells = {}
551
696
  @input_values = {}
552
697
  @numeric_values = {}
553
- doc.css('feed > entry').each do |entry|
554
- cell = entry.css('gs|cell')[0]
555
- row = cell['row'].to_i
556
- col = cell['col'].to_i
557
- @cells[[row, col]] = cell.inner_text
558
- @input_values[[row, col]] = cell['inputValue'] || cell.inner_text
559
- numeric_value = cell['numericValue']
560
- @numeric_values[[row, col]] = numeric_value ? numeric_value.to_f : nil
698
+
699
+ rows_data.each_with_index do |row_data, r|
700
+ next if !row_data.values
701
+ @num_cols = row_data.values.size if row_data.values.size > @num_cols
702
+ row_data.values.each_with_index do |cell_data, c|
703
+ k = [r + 1, c + 1]
704
+ @cells[k] = cell_data.formatted_value || ''
705
+ @input_values[k] = extended_value_to_str(cell_data.user_entered_value)
706
+ @numeric_values[k] =
707
+ cell_data.effective_value && cell_data.effective_value.number_value ?
708
+ cell_data.effective_value.number_value.to_f : nil
709
+ end
561
710
  end
711
+
562
712
  @modified.clear
563
713
  end
564
714
 
@@ -589,12 +739,32 @@ module GoogleDrive
589
739
  end
590
740
 
591
741
  def validate_cell_value(value)
592
- if value =~ XML_INVAILD_CHAR_REGEXP
742
+ if value =~ XML_INVALID_CHAR_REGEXP
593
743
  raise(
594
744
  ArgumentError,
595
745
  format('Contains invalid character %p for XML 1.0: %p', $&, value)
596
746
  )
597
747
  end
598
748
  end
749
+
750
+ def v4_range_object(top_row, left_col, num_rows, num_cols)
751
+ Google::Apis::SheetsV4::GridRange.new(
752
+ sheet_id: sheet_id,
753
+ start_row_index: top_row - 1,
754
+ start_column_index: left_col - 1,
755
+ end_row_index: top_row + num_rows - 1,
756
+ end_column_index: left_col + num_cols - 1
757
+ )
758
+ end
759
+
760
+ def extended_value_to_str(extended_value)
761
+ return '' if !extended_value
762
+ value =
763
+ extended_value.number_value ||
764
+ extended_value.string_value ||
765
+ extended_value.bool_value ||
766
+ extended_value.formula_value
767
+ value.to_s
768
+ end
599
769
  end
600
770
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: google_drive
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.12
4
+ version: 3.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hiroshi Ichikawa
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-09-10 00:00:00.000000000 Z
11
+ date: 2021-04-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -31,25 +31,45 @@ dependencies:
31
31
  - !ruby/object:Gem::Version
32
32
  version: 2.0.0
33
33
  - !ruby/object:Gem::Dependency
34
- name: google-api-client
34
+ name: google-apis-drive_v3
35
35
  requirement: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - ">="
38
38
  - !ruby/object:Gem::Version
39
- version: 0.11.0
39
+ version: 0.5.0
40
40
  - - "<"
41
41
  - !ruby/object:Gem::Version
42
- version: 0.22.0
42
+ version: 1.0.0
43
43
  type: :runtime
44
44
  prerelease: false
45
45
  version_requirements: !ruby/object:Gem::Requirement
46
46
  requirements:
47
47
  - - ">="
48
48
  - !ruby/object:Gem::Version
49
- version: 0.11.0
49
+ version: 0.5.0
50
+ - - "<"
51
+ - !ruby/object:Gem::Version
52
+ version: 1.0.0
53
+ - !ruby/object:Gem::Dependency
54
+ name: google-apis-sheets_v4
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: 0.4.0
50
60
  - - "<"
51
61
  - !ruby/object:Gem::Version
52
- version: 0.22.0
62
+ version: 1.0.0
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: 0.4.0
70
+ - - "<"
71
+ - !ruby/object:Gem::Version
72
+ version: 1.0.0
53
73
  - !ruby/object:Gem::Dependency
54
74
  name: googleauth
55
75
  requirement: !ruby/object:Gem::Requirement
@@ -168,8 +188,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
168
188
  - !ruby/object:Gem::Version
169
189
  version: '0'
170
190
  requirements: []
171
- rubyforge_project:
172
- rubygems_version: 2.6.14
191
+ rubygems_version: 3.2.3
173
192
  signing_key:
174
193
  specification_version: 4
175
194
  summary: A library to read/write files/spreadsheets in Google Drive/Docs.