google_drive 2.1.12 → 3.0.7

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