sheets_v4 0.5.0 → 0.7.0

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
2
  SHA256:
3
- metadata.gz: ea8bb19ec472d573c796d277e420c088273c087aa9ffe4ebbb63df0a2ffb69ae
4
- data.tar.gz: 5a2dca82c26a9efdd7584a5c33f4e4d9bfa1f8ac0003cf504d936554e6bb9f7d
3
+ metadata.gz: b3ed35f7562643f623e68d6ad41c497f1d92ea7b2d77f4b6f0d5e02586109897
4
+ data.tar.gz: 6687dfc41d70870694a2085e749d803f7062de8d2486866e4e44ea54b1eec95f
5
5
  SHA512:
6
- metadata.gz: 0676d4dcff6bd008104638a1a593cfcf638ad990d15a6f1a2f71cba44e0a387e5c3e527978cb3fc9e0517ecf9ef35dd8fac2348ac993f58f28147769261d3412
7
- data.tar.gz: ab5ae3a72384e45ee10411d1f1a89231ef9092393e686e81b0cb9e7734b216c039589697f2ef5e7d3313b677f5ea022ac8b2135adaa57e7e01c6032a12de0bb0
6
+ metadata.gz: 00334f58b9080916f1239671e106292742a00bbd931582577ae8a0664c25ea83f1ecaf5154996a0f28deffe9e8aaf58e4963dd94160095853d7d3454ef9e1b82
7
+ data.tar.gz: a89714057c1aa145ce2b5eaa5a05776b8593c76d6e944b1b2b868b1ddd16a4247bf05ac13b218b74addb0204a3644cb6657e30e2331e725ca68467359cfd66a9
data/CHANGELOG.md CHANGED
@@ -4,6 +4,25 @@ Changes for each release are listed in this file.
4
4
 
5
5
  This project adheres to [Semantic Versioning](https://semver.org/) for its releases.
6
6
 
7
+ ## v0.7.0 (2023-10-08)
8
+
9
+ [Full Changelog](https://github.com/main-branch/sheets_v4/compare/v0.6.0..v0.7.0)
10
+
11
+ Changes since v0.6.0:
12
+
13
+ * 616fe1f Add conversions bewteen Date/DateTime and spreadsheet values (#22)
14
+ * 6f37337 Rename SheetsV4::ValidateApiObjects to SheetsV4::ApiObjectValidation (#21)
15
+ * 0f76992 Rename SheetsV4::ValidateApiObjects::Validate to SheetsV4::ValidateApiObjects::ValidateApiObject (#20)
16
+ * e80040c Rename SheetsV4::CredentialCreator to SheetsV4::CreateCredential (#19)
17
+
18
+ ## v0.6.0 (2023-10-03)
19
+
20
+ [Full Changelog](https://github.com/main-branch/sheets_v4/compare/v0.5.0..v0.6.0)
21
+
22
+ Changes since v0.5.0:
23
+
24
+ * 2eab61d Update documentation explaining how to construct and validate a request (#17)
25
+
7
26
  ## v0.5.0 (2023-10-01)
8
27
 
9
28
  [Full Changelog](https://github.com/main-branch/sheets_v4/compare/v0.4.0..v0.5.0)
data/README.md CHANGED
@@ -9,29 +9,52 @@
9
9
 
10
10
  Unofficial helpers for the Google Sheets V4 API
11
11
 
12
- * [Important Links for Programming Google Sheets](#important-links-for-programming-google-sheets)
13
- * [General API Documentation](#general-api-documentation)
14
- * [Ruby Implementation of the Sheets API](#ruby-implementation-of-the-sheets-api)
15
- * [Other Links](#other-links)
16
12
  * [Installation](#installation)
13
+ * [Important links for programming Google Sheets](#important-links-for-programming-google-sheets)
14
+ * [General API documentation](#general-api-documentation)
15
+ * [Ruby implementation of the Sheets API](#ruby-implementation-of-the-sheets-api)
16
+ * [Other Links](#other-links)
17
+ * [Getting Started](#getting-started)
18
+ * [Creating a Google Cloud project](#creating-a-google-cloud-project)
19
+ * [Enable the APIs you want to use](#enable-the-apis-you-want-to-use)
20
+ * [Create a Google API credentials](#create-a-google-api-credentials)
17
21
  * [Usage](#usage)
18
22
  * [Obtaining an authenticated SheetsService](#obtaining-an-authenticated-sheetsservice)
23
+ * [Building a request](#building-a-request)
24
+ * [Method 1: constructing requests using `Google::Apis::SheetsV4::*` objects](#method-1-constructing-requests-using-googleapissheetsv4-objects)
25
+ * [Method 2: constructing requests using hashes](#method-2-constructing-requests-using-hashes)
26
+ * [Which method should be used?](#which-method-should-be-used)
27
+ * [Validating requests](#validating-requests)
28
+ * [Working with dates and times](#working-with-dates-and-times)
19
29
  * [Colors](#colors)
20
30
  * [Development](#development)
21
- * [Creating a Google API Service Account](#creating-a-google-api-service-account)
22
31
  * [Contributing](#contributing)
23
32
  * [License](#license)
24
33
 
25
- ## Important Links for Programming Google Sheets
34
+ ## Installation
35
+
36
+ Install the gem and add to the application's Gemfile by executing:
37
+
38
+ ```shell
39
+ bundle add sheets_v4
40
+ ```
41
+
42
+ If bundler is not being used to manage dependencies, install the gem by executing:
26
43
 
27
- ### General API Documentation
44
+ ```shell
45
+ gem install sheets_v4
46
+ ```
47
+
48
+ ## Important links for programming Google Sheets
49
+
50
+ ### General API documentation
28
51
 
29
52
  * [Google Sheets API Overview](https://developers.google.com/sheets/api)
30
53
  * [Google Sheets API Reference](https://developers.google.com/sheets/api/reference/rest)
31
54
  * [Batch Update Requests](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request)
32
55
  * [Discovery Document for the Sheets API](https://sheets.googleapis.com/$discovery/rest?version=v4)
33
56
 
34
- ### Ruby Implementation of the Sheets API
57
+ ### Ruby implementation of the Sheets API
35
58
 
36
59
  * [SheetsService Class](https://github.com/googleapis/google-api-ruby-client/blob/main/generated/google-apis-sheets_v4/lib/google/apis/sheets_v4/service.rb)
37
60
  * [All Other Sheets Classes](https://github.com/googleapis/google-api-ruby-client/blob/main/generated/google-apis-sheets_v4/lib/google/apis/sheets_v4/classes.rb)
@@ -40,19 +63,25 @@ Unofficial helpers for the Google Sheets V4 API
40
63
 
41
64
  * [Apps Script for Sheets](https://developers.google.com/apps-script/guides/sheets)
42
65
 
43
- ## Installation
66
+ ## Getting Started
44
67
 
45
- Install the gem and add to the application's Gemfile by executing:
68
+ In order to use this gem, you will need to obtain a Google API sheets service
69
+ credential following the instructions below.
46
70
 
47
- ```shell
48
- bundle add sheets_v4
49
- ```
71
+ ### Creating a Google Cloud project
50
72
 
51
- If bundler is not being used to manage dependencies, install the gem by executing:
73
+ Create a Google Cloud project using [these directions](https://developers.google.com/workspace/guides/create-project).
52
74
 
53
- ```shell
54
- gem install sheets_v4
55
- ```
75
+ ### Enable the APIs you want to use
76
+
77
+ Enable the Sheets API for this project using [these directions](https://developers.google.com/workspace/guides/enable-apis).
78
+
79
+ ### Create a Google API credentials
80
+
81
+ Create a service account and download credentials using [these directions](https://developers.google.com/workspace/guides/create-credentials#service-account).
82
+
83
+ You can store the download credential files anywhere on your system. The recommended
84
+ location is `~/.google-api-credential.json`.
56
85
 
57
86
  ## Usage
58
87
 
@@ -86,18 +115,227 @@ If the credential is stored elsewhere, pass the credential_source to `SheetsV4.s
86
115
  manually. `credential_source` can be a String:
87
116
 
88
117
  ```Ruby
89
- sheets_service = SheetsV4.sheets_service(credential_sourvce: File.read('credential.json'))
118
+ sheets_service = SheetsV4.sheets_service(credential_source: File.read('credential.json'))
90
119
  ```
91
120
 
92
121
  an IO object:
93
122
 
94
123
  ```Ruby
95
124
  sheets_service = File.open('credential.json') do |credential_source|
96
- SheetsV4.sheets_service(credential_sourvce:)
125
+ SheetsV4.sheets_service(credential_source:)
126
+ end
127
+ ```
128
+
129
+ or an already constructed `Google::Auth::*` object.
130
+
131
+ ### Building a request
132
+
133
+ To use the Sheets API, you need to construct JSON formatted requests.
134
+
135
+ These requests can be constructed using two different methods:
136
+ 1. constructing requests using `Google::Apis::SheetsV4::*` objects or
137
+ 2. constructing requests using hashes
138
+
139
+ The following two sections show how each method can be used to construct
140
+ a request to update a row of values in a sheet.
141
+
142
+ For these two examples, values in the `values` array will be written to the
143
+ sheet whose ID is 0. The values will be written one per row starting at cell A1.
144
+
145
+ ```Ruby
146
+ values = %w[one two three four] # 'one' goes in A1, 'two' goes in A2, etc.
147
+ ```
148
+
149
+ The method `SheetsService#batch_update_spreadsheet` will be used to write the values. This
150
+ method takes a `batch_update_spreadsheet_request` object with a `update_cells` request
151
+ that defines the update to perform.
152
+
153
+ #### Method 1: constructing requests using `Google::Apis::SheetsV4::*` objects
154
+
155
+ When using this method, keep the Ruby source file containing the SheetsService class
156
+ ([google/apis/sheets_v4/service.rb](https://github.com/googleapis/google-api-ruby-client/blob/main/generated/google-apis-sheets_v4/lib/google/apis/sheets_v4/service.rb))
157
+ and the Ruby source file containing the defitions of the request & data classes
158
+ ([lib/google/apis/sheets_v4/classes.rb](https://github.com/googleapis/google-api-ruby-client/blob/main/generated/google-apis-sheets_v4/lib/google/apis/sheets_v4/classes.rb))
159
+ open for easy searching. These files will give you all the information you need
160
+ to construct valid requests.
161
+
162
+ Here is the example constructing requests using `Google::Apis::SheetsV4::*` objects
163
+
164
+ ```Ruby
165
+ def values = %w[one two three four]
166
+
167
+ def row_data(value)
168
+ Google::Apis::SheetsV4::RowData.new(
169
+ values: [
170
+ Google::Apis::SheetsV4::CellData.new(
171
+ user_entered_value:
172
+ Google::Apis::SheetsV4::ExtendedValue.new(string_value: value.to_s)
173
+ )
174
+ ]
175
+ )
176
+ end
177
+
178
+ def rows
179
+ values.map { |value| row_data(value) }
180
+ end
181
+
182
+ def write_values_request
183
+ fields = 'user_entered_value'
184
+ start = Google::Apis::SheetsV4::GridCoordinate.new(
185
+ sheet_id: 0, row_index: 0, column_index: 0
186
+ )
187
+ Google::Apis::SheetsV4::Request.new(
188
+ update_cells: Google::Apis::SheetsV4::UpdateCellsRequest.new(rows:, fields:, start:)
189
+ )
190
+ end
191
+
192
+ requests = Google::Apis::SheetsV4::BatchUpdateSpreadsheetRequest.new(requests: [write_values_request])
193
+
194
+ spreadsheet_id = '18FAcgotK7nDfLTOTQuGCIjKwxkJMAguhn1OVzpFFgWY'
195
+ SheetsV4.sheets_service.batch_update_spreadsheet(spreadsheet_id, requests)
196
+ ```
197
+
198
+ #### Method 2: constructing requests using hashes
199
+
200
+ When constructing requests using this method, keep the [Google Sheets Rest API Reference](https://developers.google.com/sheets/api/reference/rest)
201
+ documentation open. In particular, [the Batch Update Requests page](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#cutpasterequest)
202
+ is particularly useful for building spreadsheet batch update requests.
203
+
204
+ One caveat to keep in mind is that the Rest API documents object properties using
205
+ Camel case BUT the Ruby API requires snake case.
206
+
207
+ For instance, the Rest API documents the properties for a grid coordinate to be
208
+ "sheetId", "rowIndex", and "columnIndex". However, in the Ruby API, you should
209
+ construct this object using snake case:
210
+
211
+ ```Ruby
212
+ grid_coordinate = { sheet_id: 0, row_index: 0, column_index: 0 }
213
+ ```
214
+
215
+ Here is the example constructing requests using hashes:
216
+
217
+ ```Ruby
218
+ def values = %w[one two three four]
219
+
220
+ def rows
221
+ values.map do |value|
222
+ { values: [{ user_entered_value: { string_value: value } }] }
223
+ end
224
+ end
225
+
226
+ def write_values_request
227
+ fields = 'user_entered_value'
228
+ start = { sheet_id: 0, row_index: 0, column_index: 0 }
229
+ { update_cells: { rows:, fields:, start: } }
97
230
  end
231
+
232
+ requests = { requests: [write_values_request] }
233
+
234
+ spreadsheet_id = '18FAcgotK7nDfLTOTQuGCIjKwxkJMAguhn1OVzpFFgWY'
235
+ response = SheetsV4.sheets_service.batch_update_spreadsheet(spreadsheet_id, requests)
98
236
  ```
99
237
 
100
- or an already constructed `Google::Auth::*`` object.
238
+ #### Which method should be used?
239
+
240
+ Either method will do the same job. I prefer "Method 2: constructing requests using
241
+ hashes" because my code is more concise and easy to read.
242
+
243
+ While either method can produce a malformed request, "Method 2" is more likely to
244
+ result in malformed requests. Unfortunately, when given a malformed request, the
245
+ Google Sheets API will do one of following depending on the nature of the problem:
246
+
247
+ 1. Raise a `Google::Apis::ClientError` with some additional information
248
+ 2. Raise a `Google::Apis::ClientError` with no additional information (this the most
249
+ common result)
250
+ 3. Not return an error with some of the batch requests not having the expected outcome
251
+
252
+ Luckily, this library provides a way to validate that requests are valid and
253
+ identifies precisely where the request objects do not conform to the API description.
254
+ That is the subject of the next section [Validating requests](#validating-requests).
255
+
256
+ ### Validating requests
257
+
258
+ The [`SheetsV4.validate_api_object`](https://rubydoc.info/gems/sheets_v4/SheetsV4#validate_api_object-class_method)
259
+ method can be used to validate request objects prior to using them in the Google
260
+ Sheets API.
261
+
262
+ This method takes a `schema_name` and an `object` to validate. Schema names can be
263
+ listed using [`SheetsV4.api_object_schema_names`](https://rubydoc.info/gems/sheets_v4/SheetsV4#api_object_schema_names-class_method).
264
+
265
+ This method will either return `true` if `object` conforms to the schema OR it
266
+ will raise a RuntimeError noting where the object structure did not conform to
267
+ the schema.
268
+
269
+ In the previous examples (see [Building a request](#building-a-request)), the
270
+ following line can be inserted after the `requests = ...` line to validate the
271
+ request:
272
+
273
+ ```Ruby
274
+ SheetsV4.validate_api_object(schema: 'batch_update_spreadsheet_request', object: requests)
275
+ ```
276
+
277
+ ### Working with dates and times
278
+
279
+ Google Sheets, similar to other spreadsheet programs, stores dates and date-time
280
+ values as numbers. This system makes it easier to perform calculations with
281
+ dates and times.
282
+
283
+ This gem provides two sets of equavalent conversion methods. The first set is defined
284
+ as class methods on the `SheetsV4` class.
285
+
286
+ * `SheetsV4.date_to_gs(date)` returns a numeric cell value
287
+ * `SheetsV4.gs_to_date(cell_value)` returns a Date object
288
+ * `SheetsV4.datetime_to_gs(datetime)` returns a numeric cell value
289
+ * `SheetsV4.gs_to_datetime(cell_value)` returns a DateTime object
290
+
291
+ In order to convert to and from spreadsheet values, the spreadsheet timezone must
292
+ be known. A spreadsheet's timezone is found in the Google Sheets spreadsheet object's
293
+ properties:
294
+
295
+ ```Ruby
296
+ SheetsV4.default_spreadsheet_tz = spreadsheet.properties.time_zone
297
+ ```
298
+
299
+ If a time zone is not set using `SheetsV4.default_spreadsheet_tz`, a RuntimeError
300
+ will be raised when any of the above methods are used.
301
+
302
+ Here is an example of how the timezone can change the values fetched from the
303
+ spreadsheet:
304
+
305
+ ```Ruby
306
+ cell_value = 44333.191666666666
307
+
308
+ SheetsV4.default_spreadsheet_tz = 'America/New_York'
309
+ datetime = SheetsV4.gs_to_datetime(cell_value) #=> Mon, 17 May 2021 04:36:00 -0400
310
+ datetime.utc #=> 2021-05-17 08:36:00 UTC
311
+
312
+ SheetsV4.default_spreadsheet_tz = 'America/Los_Angeles'
313
+ datetime = SheetsV4.gs_to_datetime(cell_value) #=> Mon, 17 May 2021 04:36:00 -0700
314
+ datetime.utc #=> 2021-05-17 11:36:00 UTC
315
+ ```
316
+
317
+ Valid time zone names are those listed in one of these two sources:
318
+
319
+ * `ActiveSupport::TimeZone.all.map { |tz| tz.tzinfo.name }`
320
+ * `ActiveSupport::TimeZone.all.map(&:name)`
321
+
322
+ The `SheetsV4` methods works well if the spreadsheet timezone is constant through
323
+ the run of the program. If this is not the case -- for instance when working with
324
+ multiple spreadsheets whose timezones may be different -- then use
325
+ `SheetsV4::ConvertDatesAndTimes`.
326
+
327
+ Each instance of `SheetsV4::ConvertDatesAndTimes` has it's own spreadsheet timezone
328
+ used in the conversions. Instance methods for this class are the same as the
329
+ date conversion methods on the SheetsV4 class.
330
+
331
+ Example:
332
+
333
+ ```Ruby
334
+ cell_value = 44333.191666666666
335
+ converter = SheetsV4::ConvertDatesAndTimes.new('America/Los_Angeles')
336
+ datetime = SheetsV4.gs_to_datetime(cell_value) #=> Mon, 17 May 2021 04:36:00 -0700
337
+ datetime.utc #=> 2021-05-17 11:36:00 UTC
338
+ ```
101
339
 
102
340
  ### Colors
103
341
 
@@ -114,8 +352,8 @@ SheetsV4::Color.black #=> {:red=>0.0, :green=>0.0, :blue=>0.0}
114
352
 
115
353
  ## Development
116
354
 
117
- After checking out the repo, run `bin/setup` to install dependencies. Then, run
118
- `rake spec` to run the tests. You can also run `bin/console` for an interactive
355
+ After checking out the repo, run `bin/setup` to install dependencies and then, run
356
+ `rake` to run the tests, static analysis, etc. You can also run `bin/console` for an interactive
119
357
  prompt that will allow you to experiment.
120
358
 
121
359
  To install this gem onto your local machine, run `bundle exec rake install`. To
@@ -124,9 +362,6 @@ release a new version, update the version number in `version.rb`, and then run
124
362
  commits and the created tag, and push the `.gem` file to
125
363
  [rubygems.org](https://rubygems.org).
126
364
 
127
- ## Creating a Google API Service Account
128
-
129
-
130
365
  ## Contributing
131
366
 
132
367
  Bug reports and pull requests are welcome on [the main-branch/sheets_v4 GitHub project](https://github.com/main-branch/sheets_v4).
data/examples/README.md CHANGED
@@ -46,3 +46,4 @@
46
46
  * [ ] Protected ranges
47
47
  * [ ] Resize a sheet
48
48
  * [ ] Retrying on error
49
+ * [ ] Set a custom datetime or decimal format for a range [1](https://developers.google.com/sheets/api/samples/formatting#set_a_custom_datetime_or_decimal_format_for_a_range)
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SheetsV4
4
- module ValidateApiObjects
4
+ module ApiObjectValidation
5
5
  # Load the Google Discovery API description for the Sheets V4 API
6
6
  #
7
7
  # @example
8
8
  # logger = Logger.new(STDOUT, :level => Logger::ERROR)
9
- # schemas = SheetsV4::ValidateApiObjects::LoadSchemas.new(logger:).call
9
+ # schemas = SheetsV4::ApiObjectValidation::LoadSchemas.new(logger:).call
10
10
  #
11
11
  # @api private
12
12
  #
@@ -18,7 +18,7 @@ module SheetsV4
18
18
  # The schemas are only loaded once and cached.
19
19
  #
20
20
  # @example
21
- # schema_loader = SheetsV4::ValidateApiObjects::LoadSchemas.new
21
+ # schema_loader = SheetsV4::ApiObjectValidation::LoadSchemas.new
22
22
  #
23
23
  # @param logger [Logger] the logger to use
24
24
  #
@@ -30,8 +30,8 @@ module SheetsV4
30
30
  #
31
31
  # @example
32
32
  # logger = Logger.new(STDOUT, :level => Logger::INFO)
33
- # validator = SheetsV4::ValidateApiObjects::LoadSchemas.new(logger)
34
- # validator.logger == logger # => true
33
+ # schema_loader = SheetsV4::ApiObjectValidation::LoadSchemas.new(logger)
34
+ # schema_loader.logger == logger # => true
35
35
  #
36
36
  # @return [Logger]
37
37
  #
@@ -84,7 +84,7 @@ module SheetsV4
84
84
  # Log an error and raise a RuntimeError based on the HTTP response code
85
85
  # @param http_response [Net::HTTPResponse] the HTTP response
86
86
  # @return [void]
87
- # @raises [RuntimeError]
87
+ # @raise [RuntimeError]
88
88
  # @api private
89
89
  def raise_error(http_response)
90
90
  message = "HTTP Error '#{http_response.code}' loading schemas from '#{http_response.uri}'"
@@ -146,7 +146,7 @@ module SheetsV4
146
146
  # @return [void]
147
147
  # @api private
148
148
  def post_process_schemas(schemas)
149
- SheetsV4::ValidateApiObjects::TraverseObjectTree.call(
149
+ SheetsV4::ApiObjectValidation::TraverseObjectTree.call(
150
150
  object: schemas, visitor: ->(path:, object:) { schema_visitor(path:, object:) }
151
151
  )
152
152
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SheetsV4
4
- module ValidateApiObjects
4
+ module ApiObjectValidation
5
5
  # Resolve a JSON schema reference to a Google Sheets API schema
6
6
  #
7
7
  # This class uses the Google Discovery API to get the schemas. Any schema reference
@@ -9,14 +9,14 @@ module SheetsV4
9
9
  # name in the Google Discovery API and returning the schema object (as a Hash).
10
10
  #
11
11
  # This means that `{ "$ref": "cell_data" }` is resolved by returning
12
- # `SheetsV4::ValidateApiObjects::LoadSchemas.new(logger:).call['cell_data']`.
12
+ # `SheetsV4::ApiObjectValidation::LoadSchemas.new(logger:).call['cell_data']`.
13
13
  #
14
- # An RuntimeError is raised if `SheetsV4::ValidateApiObjects::LoadSchemas.new.call`
14
+ # An RuntimeError is raised if `SheetsV4::ApiObjectValidation::LoadSchemas.new.call`
15
15
  # does not have a key matching the schema name.
16
16
  #
17
17
  # @example
18
18
  # logger = Logger.new(STDOUT, level: Logger::INFO)
19
- # ref_resolver = SheetsV4::ValidateApiObjects::ResolveSchemaRef.new(logger:)
19
+ # ref_resolver = SheetsV4::ApiObjectValidation::ResolveSchemaRef.new(logger:)
20
20
  # people_schema = { 'type' => 'array', 'items' => { '$ref' => 'person' } }
21
21
  # json_validator = JSONSchemer.schema(people_schema, ref_resolver:)
22
22
  # people_json = [{ 'name' => { 'first' => 'John', 'last' => 'Doe' } }]
@@ -53,7 +53,7 @@ module SheetsV4
53
53
 
54
54
  # Resolve a JSON schema reference
55
55
  #
56
- # @param ref [URI] the reference to resolve usually in the form "json-schemer://schema/{name}"
56
+ # @param ref [URI] the reference to resolve usually in the form "json-schemer://schema/[name]"
57
57
  #
58
58
  # @return [Hash] the schema object as a hash
59
59
  #
@@ -62,7 +62,7 @@ module SheetsV4
62
62
  def call(ref)
63
63
  schema_name = ref.path[1..]
64
64
  logger.debug { "Reading schema #{schema_name}" }
65
- schemas = SheetsV4::ValidateApiObjects::LoadSchemas.new(logger:).call
65
+ schemas = SheetsV4::ApiObjectValidation::LoadSchemas.new(logger:).call
66
66
  schemas[schema_name].tap do |schema_object|
67
67
  raise "Schema for #{ref} not found" unless schema_object
68
68
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SheetsV4
4
- module ValidateApiObjects
4
+ module ApiObjectValidation
5
5
  # Visit all objects in arbitrarily nested object tree of hashes and/or arrays
6
6
  #
7
7
  # @api public
@@ -16,7 +16,7 @@ module SheetsV4
16
16
  #
17
17
  # ```Ruby
18
18
  # visitor = -> (path:, object:) { puts "path: #{path}, object: #{obj}" }
19
- # SheetsV4::ValidateApiObjects::TraverseObjectTree.call(object:, visitor:)
19
+ # SheetsV4::ApiObjectValidation::TraverseObjectTree.call(object:, visitor:)
20
20
  # ```
21
21
  #
22
22
  # @example Given a simple object (not very exciting)
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+ require 'active_support/inflector'
5
+ require 'json_schemer'
6
+
7
+ module SheetsV4
8
+ module ApiObjectValidation
9
+ # Validate objects against a Google Sheets API request object schema
10
+ #
11
+ # @api public
12
+ #
13
+ class ValidateApiObject
14
+ # Create a new api object validator
15
+ #
16
+ # By default, a nil logger is used. This means that no messages are logged.
17
+ #
18
+ # @example
19
+ # validator = SheetsV4::ApiObjectValidation::ValidateApiObject.new
20
+ #
21
+ # @param logger [Logger] the logger to use
22
+ #
23
+ def initialize(logger: Logger.new(nil))
24
+ @logger = logger
25
+ end
26
+
27
+ # The logger to use internally
28
+ #
29
+ # Validation errors are logged at the error level. Other messages are logged
30
+ # at the debug level.
31
+ #
32
+ # @example
33
+ # logger = Logger.new(STDOUT, :level => Logger::INFO)
34
+ # validator = SheetsV4::ApiObjectValidation::ValidateApiObject.new(logger)
35
+ # validator.logger == logger # => true
36
+ # validator.logger.debug { "Debug message" }
37
+ #
38
+ # @return [Logger]
39
+ #
40
+ attr_reader :logger
41
+
42
+ # Validate the object using the JSON schema named schema_name
43
+ #
44
+ # @example
45
+ # schema_name = 'batch_update_spreadsheet_request'
46
+ # object = { 'requests' => [] }
47
+ # validator = SheetsV4::ApiObjectValidation::ValidateApiObject.new
48
+ # validator.call(schema_name:, object:)
49
+ #
50
+ # @param schema_name [String] the name of the schema to validate against
51
+ # @param object [Object] the object to validate
52
+ #
53
+ # @raise [RuntimeError] if the object does not conform to the schema
54
+ #
55
+ # @return [void]
56
+ #
57
+ def call(schema_name:, object:)
58
+ logger.debug { "Validating #{object} against #{schema_name}" }
59
+
60
+ schema = { '$ref' => schema_name }
61
+ schemer = JSONSchemer.schema(schema, ref_resolver:)
62
+ errors = schemer.validate(object)
63
+ raise_error!(schema_name, object, errors) if errors.any?
64
+
65
+ logger.debug { "Object #{object} conforms to #{schema_name}" }
66
+ end
67
+
68
+ private
69
+
70
+ # The resolver to use to resolve JSON schema references
71
+ # @return [ResolveSchemaRef]
72
+ # @api private
73
+ def ref_resolver = @ref_resolver ||= SheetsV4::ApiObjectValidation::ResolveSchemaRef.new(logger:)
74
+
75
+ # Raise an error when the object does not conform to the schema
76
+ # @return [void]
77
+ # @raise [RuntimeError]
78
+ # @api private
79
+ def raise_error!(schema_name, object, errors)
80
+ error = errors.first['error']
81
+ error_message = "Object #{object} does not conform to #{schema_name}: #{error}"
82
+ logger.error(error_message)
83
+ raise error_message
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SheetsV4
4
+ # Validate API Objects against the Google Discovery API
5
+ #
6
+ # @example
7
+ # logger = Logger.new(STDOUT, :level => Logger::ERROR)
8
+ # schema_name = 'batch_update_spreadsheet_request'
9
+ # object = { 'requests' => [] }
10
+ # SheetsV4::ApiObjectValidation::ValidateApiObject.new(logger:).call(schema_name:, object:)
11
+ #
12
+ # @api public
13
+ #
14
+ module ApiObjectValidation; end
15
+ end
16
+
17
+ require_relative 'api_object_validation/load_schemas'
18
+ require_relative 'api_object_validation/resolve_schema_ref'
19
+ require_relative 'api_object_validation/traverse_object_tree'
20
+ require_relative 'api_object_validation/validate_api_object'
@@ -23,7 +23,6 @@ module SheetsV4
23
23
  #
24
24
  # @param method_name [#to_sym] the name of the color
25
25
  # @param arguments [Array] ignored
26
- # @param block [Proc] ignored
27
26
  # @return [Hash] the color object
28
27
  # @api private
29
28
  def method_missing(method_name, *arguments, &)
@@ -0,0 +1,243 @@
1
+ # Copyright (c) 2023 Yahoo
2
+
3
+ # frozen_string_literal: true
4
+
5
+ require 'active_support'
6
+ require 'active_support/values/time_zone'
7
+ require 'active_support/core_ext/numeric/time'
8
+ require 'active_support/core_ext/string/zones'
9
+
10
+ module SheetsV4
11
+ # Convert between Ruby Date and DateTime objects and Google Sheets values
12
+ #
13
+ # Google Sheets uses decimal values to represent dates and times. These
14
+ # conversion routines allows converting from Ruby Date and DateTime objects
15
+ # and values that Google Sheets recognize.
16
+ #
17
+ # DateTime objects passed to `datetime_to_gs` or `date_to_gs` are converted to
18
+ # the time zone of the spreadsheet given in the initializer. DateTime objects
19
+ # returned by `gs_to_datetime` are always in the spreadsheet's time zone.
20
+ #
21
+ # Valid time zone names are those listed in one of these two sources:
22
+ # * `ActiveSupport::TimeZone.all.map { |tz| tz.tzinfo.name }`
23
+ # * `ActiveSupport::TimeZone.all.map(&:name)`
24
+ #
25
+ # @example
26
+ # tz = spreadsheet.properties.time_zone #=> e.g. 'America/Los_Angeles'
27
+ # converter = SheetsV4::ConvertDatesAndTimes.new(tz)
28
+ # date = Date.parse('1967-03-15')
29
+ # converter.date_to_gs(date) #=> 24546
30
+ # converter.gs_to_date(24546) #=> #<Date: 1967-03-15 ((2439565j,0s,0n),+0s,2299161j)>
31
+ #
32
+ # date_time = DateTime.parse('2021-05-17 11:36:00 UTC')
33
+ # converter.datetime_to_gs(date_time) #=> 44333.191666666666
34
+ # converter.gs_to_datetime(44333.191666666666)
35
+ # #=> #<DateTime: 2021-05-17T11:36:00+00:00 ((2459352j,41760s,0n),+0s,2299161j)>
36
+ #
37
+ # @api public
38
+ #
39
+ class ConvertDatesAndTimes
40
+ # The time zone passed into the initializer
41
+ #
42
+ # @example
43
+ # time_zone = 'UTC'
44
+ # converter = SheetsV4::ConvertDatesAndTimes.new(time_zone)
45
+ # converter.spreadsheet_tz
46
+ # #=> #<ActiveSupport::TimeZone:0x00007fe39000f908 @name="America/Los_Angeles", ...>
47
+ #
48
+ # @return [ActiveSupport::TimeZone] the time zone
49
+ #
50
+ attr_reader :spreadsheet_tz
51
+
52
+ # Initialize the conversion routines for a spreadsheet
53
+ #
54
+ # @example
55
+ # time_zone = 'America/Los_Angeles'
56
+ # converter = SheetsV4::ConvertDatesAndTimes.new(time_zone)
57
+ #
58
+ # @param spreadsheet_tz [String] the time zone set in the spreadsheet properties
59
+ #
60
+ # @raise [RuntimeError] if the time zone is not valid
61
+ #
62
+ def initialize(spreadsheet_tz)
63
+ @spreadsheet_tz = ActiveSupport::TimeZone.new(spreadsheet_tz)
64
+ raise "Invalid time zone '#{spreadsheet_tz}'" unless @spreadsheet_tz
65
+ end
66
+
67
+ # Convert a Ruby DateTime object to a Google Sheets date time value
68
+ #
69
+ # @example
70
+ # time_zone = 'America/Los_Angeles'
71
+ # converter = SheetsV4::ConvertDatesAndTimes.new(time_zone)
72
+ # date_time = DateTime.parse('2021-05-17 11:36:00 UTC')
73
+ # converter.datetime_to_gs(date_time) #=> 44333.191666666666
74
+ #
75
+ # @param datetime [DateTime, nil] the date and time to convert
76
+ #
77
+ # @return [Float, String] the value to store in a Google Sheets cell
78
+ # Returns a Float if datetime is not nil; otherwise, returns an empty string.
79
+ #
80
+ def datetime_to_gs(datetime)
81
+ return '' unless datetime
82
+
83
+ time = datetime.to_time.in_time_zone(spreadsheet_tz)
84
+ unix_to_gs_epoch(replace_time_zone(time, 'UTC').to_i)
85
+ end
86
+
87
+ # Convert a Google Sheets date time value to a DateTime object
88
+ #
89
+ # Time is rounded to the nearest second. The DateTime object returned is in
90
+ # the spreadsheet's time zone given in the initiaizer.
91
+ #
92
+ # @example
93
+ # time_zone = 'America/Los_Angeles'
94
+ # converter = SheetsV4::ConvertDatesAndTimes.new(time_zone)
95
+ # gs_value = 44333.191666666666
96
+ # converter.gs_to_datetime(gs_value) #=> #<DateTime: 2021-05-17T04:35:59-07:00 ...>
97
+ #
98
+ # @param gs_datetime [Float, "", nil] the value from the Google Sheets cell
99
+ #
100
+ # @return [DateTime, nil] the value represented by gs_datetime
101
+ # Returns a DateTime object if a Float was given; otherwise, returns nil if an
102
+ # empty string or nil was given.
103
+ #
104
+ def gs_to_datetime(gs_datetime)
105
+ return nil if gs_datetime.nil? || gs_datetime == ''
106
+
107
+ raise 'gs_datetime is a string' if gs_datetime.is_a?(String)
108
+
109
+ unix_epoch_datetime = gs_to_unix_epoch(gs_datetime.to_f)
110
+ time = Time.at_without_coercion(unix_epoch_datetime, in: 'UTC')
111
+ replace_time_zone(time, spreadsheet_tz).to_datetime
112
+ end
113
+
114
+ # Convert a Ruby Date object to a Google Sheets date value
115
+ #
116
+ # The Google Sheets date value is a float.
117
+ #
118
+ # @example with a Date object
119
+ # time_zone = 'America/Los_Angeles'
120
+ # converter = SheetsV4::ConvertDatesAndTimes.new(time_zone)
121
+ # date = Date.parse('2021-05-17')
122
+ # converter.date_to_gs(date) #=> 44333
123
+ #
124
+ # @example with a DateTime object
125
+ # time_zone = 'America/Los_Angeles'
126
+ # converter = SheetsV4::ConvertDatesAndTimes.new(time_zone)
127
+ # date_time = DateTime.parse('2021-05-17 11:36:00 UTC')
128
+ # converter.date_to_gs(date_time) #=> 44333
129
+ #
130
+ # @param date [DateTime, Date, nil] the date to convert
131
+ #
132
+ # @return [Float, String] the value to sstore in a Google Sheets cell
133
+ # Returns a Float if date is not nil; otherwise, returns an empty string
134
+ #
135
+ def date_to_gs(date)
136
+ return datetime_to_gs(date).to_i if date.is_a?(DateTime)
137
+
138
+ return (date - gs_epoch_start_date).to_i if date.is_a?(Date)
139
+
140
+ ''
141
+ end
142
+
143
+ # Convert a Google Sheets date value to a Ruby Date object
144
+ #
145
+ # @example with a Date value
146
+ # time_zone = 'America/Los_Angeles'
147
+ # converter = SheetsV4::ConvertDatesAndTimes.new(time_zone)
148
+ # gs_value = 44333
149
+ # converter.gs_to_date(gs_value) #=> #<Date: 2021-05-17 ...>
150
+ #
151
+ # @example with a Date and Time value
152
+ # time_zone = 'America/Los_Angeles'
153
+ # converter = SheetsV4::ConvertDatesAndTimes.new(time_zone)
154
+ # gs_value = 44333.191666666666
155
+ # converter.gs_to_date(gs_value) #=> #<Date: 2021-05-17 ...>
156
+ #
157
+ # @param gs_date [Float, "", nil] the value from the Google Sheets cell
158
+ #
159
+ # @return [Date, nil] the value represented by gs_date
160
+ # Returns a Date object if a Float was given; otherwise, returns nil if an
161
+ # empty string or nil was given.
162
+ #
163
+ def gs_to_date(gs_date)
164
+ return nil if gs_date.nil? || gs_date == ''
165
+
166
+ raise 'gs_date is a string' if gs_date.is_a?(String)
167
+
168
+ (gs_epoch_start_date + gs_date.to_i)
169
+ end
170
+
171
+ private
172
+
173
+ # The Google Sheets epoch start Date to use when calculating dates
174
+ # @return [Date] the 'zero' Date
175
+ # @api private
176
+ def gs_epoch_start_date
177
+ @gs_epoch_start_date ||= Date.parse('1899-12-30')
178
+ end
179
+
180
+ # The number of seconds in a day
181
+ #
182
+ # @return [Integer] number of seconds in a day
183
+ #
184
+ SECONDS_PER_DAY = 86_400
185
+
186
+ # The number of seconds between the Google Sheets Epoch and Unix Epoch
187
+ #
188
+ # Effectively the number of seconds between 1899-12-30 00:00:00 UTC and
189
+ # 1970-01-01 00:00:00 UTC.
190
+ #
191
+ # @return [Integer] the number of seconds
192
+ #
193
+ SECONDS_BETWEEN_GS_AND_UNIX_EPOCHS = 25_569 * 86_400
194
+
195
+ # Convert a gs_datetime to unix time
196
+ #
197
+ # @param gs_datetime [Float] time relative to the Google Sheets Epoch
198
+ #
199
+ # gs_datetime is a float representing the number of days since the start of
200
+ # the Google Sheets epoch (1899-12-30 00:00:00 UTC).
201
+ #
202
+ # @return [Integer] the same datetime in Unix Time rounded to the nearest second
203
+ #
204
+ # Unix time is an integer representing the number of seconds since the start of
205
+ # the Unix Epoch (1970-01-01 00:00:00 UTC). The number returned is rounded
206
+ # to the nearest second.
207
+ #
208
+ # @api private
209
+ #
210
+ def gs_to_unix_epoch(gs_datetime)
211
+ (gs_datetime * SECONDS_PER_DAY).round - SECONDS_BETWEEN_GS_AND_UNIX_EPOCHS
212
+ end
213
+
214
+ # Convert a unix time to gs_datetime
215
+ #
216
+ # @param unix_time [Integer] seconds since the Unix epoch 1970-01-01 00:00:00 UTC
217
+ #
218
+ # @return [Float] days since the Google Sheets epoch 1899-12-30 00:00:00 UTC
219
+ #
220
+ # @api private
221
+ #
222
+ def unix_to_gs_epoch(unix_time)
223
+ (unix_time + SECONDS_BETWEEN_GS_AND_UNIX_EPOCHS).to_f / SECONDS_PER_DAY
224
+ end
225
+
226
+ # Given a time, change the time zone without impacting the displayed date/time
227
+ #
228
+ # @example
229
+ # replace_time_zone(Time.parse('2021-05-21 11:40 UTC'), 'America/Los_Angeles')
230
+ # #=> '2021-05-21 11:40 -0700'
231
+ #
232
+ # @param time [Time] the time object to adjust
233
+ # @param time_zone_name [String] the desired time zone
234
+ #
235
+ # @return [Time] the resulting time object
236
+ #
237
+ # @api private
238
+ #
239
+ def replace_time_zone(time, time_zone_name)
240
+ time.asctime.in_time_zone(time_zone_name)
241
+ end
242
+ end
243
+ end
@@ -5,7 +5,7 @@ require 'json_schemer'
5
5
  module SheetsV4
6
6
  # Creates a Google API credential with an access token
7
7
  #
8
- class CredentialCreator
8
+ class CreateCredential
9
9
  # Creates a Google API credential with an access token
10
10
  #
11
11
  # This wraps the boiler plate code into one function to make constructing a
@@ -14,7 +14,7 @@ module SheetsV4
14
14
  # @example Constructing a credential from the contents of ~/.credential
15
15
  # credential_source = File.read(File.join(Dir.home, '.credential'))
16
16
  # scope = Google::Apis::SheetsV4::AUTH_SPREADSHEETS
17
- # credential = GoogleApisHelpers.credential(credential_source, scope)
17
+ # credential = SheetsV4::CreateCredential.call(credential_source, scope)
18
18
  #
19
19
  # @param [Google::Auth::*, String, IO, nil] credential_source may be one of four things:
20
20
  # (1) a previously created credential that you want to reuse, (2) a credential read
@@ -1,21 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'active_support'
3
4
  require 'active_support/inflector'
4
5
  require 'json_schemer'
5
6
 
6
7
  module SheetsV4
7
- module ValidateApiObjects
8
+ module ApiObjectValidation
8
9
  # Validate objects against a Google Sheets API request object schema
9
10
  #
10
11
  # @api public
11
12
  #
12
- class Validate
13
+ class ValidateApiObject
13
14
  # Create a new validator
14
15
  #
15
16
  # By default, a nil logger is used. This means that no messages are logged.
16
17
  #
17
18
  # @example
18
- # validator = SheetsV4::ValidateApiObjects::Validator.new
19
+ # validator = SheetsV4::ApiObjectValidation::ValidateApiObject.new
19
20
  #
20
21
  # @param logger [Logger] the logger to use
21
22
  #
@@ -30,7 +31,7 @@ module SheetsV4
30
31
  #
31
32
  # @example
32
33
  # logger = Logger.new(STDOUT, :level => Logger::INFO)
33
- # validator = SheetsV4::ValidateApiObjects::Validator.new(logger)
34
+ # validator = SheetsV4::ApiObjectValidation::ValidateApiObject.new(logger)
34
35
  # validator.logger == logger # => true
35
36
  # validator.logger.debug { "Debug message" }
36
37
  #
@@ -43,7 +44,7 @@ module SheetsV4
43
44
  # @example
44
45
  # schema_name = 'batch_update_spreadsheet_request'
45
46
  # object = { 'requests' => [] }
46
- # validator = SheetsV4::ValidateApiObjects::Validator.new
47
+ # validator = SheetsV4::ApiObjectValidation::ValidateApiObject.new
47
48
  # validator.call(schema_name:, object:)
48
49
  #
49
50
  # @param schema_name [String] the name of the schema to validate against
@@ -69,7 +70,7 @@ module SheetsV4
69
70
  # The resolver to use to resolve JSON schema references
70
71
  # @return [ResolveSchemaRef]
71
72
  # @api private
72
- def ref_resolver = @ref_resolver ||= SheetsV4::ValidateApiObjects::ResolveSchemaRef.new(logger:)
73
+ def ref_resolver = @ref_resolver ||= SheetsV4::ApiObjectValidation::ResolveSchemaRef.new(logger:)
73
74
 
74
75
  # Raise an error when the object does not conform to the schema
75
76
  # @return [void]
@@ -7,14 +7,14 @@ module SheetsV4
7
7
  # logger = Logger.new(STDOUT, :level => Logger::ERROR)
8
8
  # schema_name = 'batch_update_spreadsheet_request'
9
9
  # object = { 'requests' => [] }
10
- # SheetsV4::ValidateApiObjects::Validator.new(logger:).call(schema_name:, object:)
10
+ # SheetsV4::ApiObjectValidation::ValidateApiObject.new(logger:).call(schema_name:, object:)
11
11
  #
12
12
  # @api public
13
13
  #
14
- module ValidateApiObjects; end
14
+ module ApiObjectValidation; end
15
15
  end
16
16
 
17
- require_relative 'validate_api_objects/load_schemas'
18
- require_relative 'validate_api_objects/resolve_schema_ref'
19
- require_relative 'validate_api_objects/traverse_object_tree'
20
- require_relative 'validate_api_objects/validate'
17
+ require_relative 'api_object_validation/load_schemas'
18
+ require_relative 'api_object_validation/resolve_schema_ref'
19
+ require_relative 'api_object_validation/traverse_object_tree'
20
+ require_relative 'api_object_validation/validate_api_object'
@@ -2,5 +2,5 @@
2
2
 
3
3
  module SheetsV4
4
4
  # The version of this gem
5
- VERSION = '0.5.0'
5
+ VERSION = '0.7.0'
6
6
  end
data/lib/sheets_v4.rb CHANGED
@@ -2,9 +2,14 @@
2
2
 
3
3
  require_relative 'sheets_v4/version'
4
4
  require_relative 'sheets_v4/color'
5
- require_relative 'sheets_v4/credential_creator'
6
- require_relative 'sheets_v4/validate_api_objects'
5
+ require_relative 'sheets_v4/convert_dates_and_times'
6
+ require_relative 'sheets_v4/create_credential'
7
+ require_relative 'sheets_v4/api_object_validation'
7
8
 
9
+ require 'active_support'
10
+ require 'active_support/values/time_zone'
11
+ require 'active_support/core_ext/numeric/time'
12
+ require 'active_support/core_ext/string/zones'
8
13
  require 'google/apis/sheets_v4'
9
14
  require 'json'
10
15
  require 'logger'
@@ -15,87 +20,221 @@ require 'net/http'
15
20
  # @api public
16
21
  #
17
22
  module SheetsV4
18
- # Create a new Google::Apis::SheetsV4::SheetsService object
19
- #
20
- # Simplifies creating and configuring a the credential.
21
- #
22
- # @example using the crednetial in `~/.google-api-credential`
23
- # SheetsV4.sheets_service
24
- #
25
- # @example using a credential passed in as a string
26
- # credential_source = File.read(File.join(Dir.home, '.credential'))
27
- # SheetsV4.sheets_service(credential_source:
28
- #
29
- # @param credential_source [nil, String, IO, Google::Auth::*] may
30
- # be either an already constructed credential, the credential read into a String or
31
- # an open file with the credential ready to be read. Passing `nil` will result
32
- # in the credential being read from `~/.google-api-credential.json`.
33
- #
34
- # @param scopes [Object, Array] one or more scopes to access.
35
- #
36
- # @param credential_creator [#credential] Used to inject the credential creator for
37
- # testing.
38
- #
39
- # @return a new SheetsService instance
40
- #
41
- def self.sheets_service(credential_source: nil, scopes: nil, credential_creator: SheetsV4::CredentialCreator)
42
- credential_source ||= File.read(File.expand_path('~/.google-api-credential.json'))
43
- scopes ||= [Google::Apis::SheetsV4::AUTH_SPREADSHEETS]
44
-
45
- Google::Apis::SheetsV4::SheetsService.new.tap do |service|
46
- service.authorization = credential_creator.call(credential_source, scopes)
23
+ class << self
24
+ # Create a new Google::Apis::SheetsV4::SheetsService object
25
+ #
26
+ # Simplifies creating and configuring a the credential.
27
+ #
28
+ # @example using the credential in `~/.google-api-credential`
29
+ # SheetsV4.sheets_service
30
+ #
31
+ # @example using a credential passed in as a string
32
+ # credential_source = File.read(File.expand_path('~/.google-api-credential.json'))
33
+ # SheetsV4.sheets_service(credential_source:)
34
+ #
35
+ # @example using a credential passed in as an IO
36
+ # credential_source = File.open(File.expand_path('~/.google-api-credential.json'))
37
+ # SheetsV4.sheets_service(credential_source:)
38
+ #
39
+ # @param credential_source [nil, String, IO, Google::Auth::*] may
40
+ # be either an already constructed credential, the credential read into a String or
41
+ # an open file with the credential ready to be read. Passing `nil` will result
42
+ # in the credential being read from `~/.google-api-credential.json`.
43
+ #
44
+ # @param scopes [Object, Array] one or more scopes to access.
45
+ #
46
+ # @param credential_creator [#credential] Used to inject the credential creator for
47
+ # testing.
48
+ #
49
+ # @return a new SheetsService instance
50
+ #
51
+ def sheets_service(credential_source: nil, scopes: nil, credential_creator: SheetsV4::CreateCredential)
52
+ credential_source ||= File.read(File.expand_path('~/.google-api-credential.json'))
53
+ scopes ||= [Google::Apis::SheetsV4::AUTH_SPREADSHEETS]
54
+
55
+ Google::Apis::SheetsV4::SheetsService.new.tap do |service|
56
+ service.authorization = credential_creator.call(credential_source, scopes)
57
+ end
47
58
  end
48
- end
49
59
 
50
- # Validate the object using the named JSON schema
51
- #
52
- # The JSON schemas are loaded from the Google Disocvery API. The schemas names are
53
- # returned by `SheetsV4.api_object_schemas.keys`.
54
- #
55
- # @example
56
- # schema_name = 'BatchUpdateSpreadsheetRequest'
57
- # object = { 'requests' => [] }
58
- # SheetsV4.validate_api_object(schema_name:, object:)
59
- #
60
- # @param schema_name [String] the name of the schema to validate against
61
- # @param object [Object] the object to validate
62
- # @param logger [Logger] the logger to use for logging error, info, and debug message
63
- #
64
- # @raise [RuntimeError] if the object does not conform to the schema
65
- #
66
- # @return [void]
67
- #
68
- def self.validate_api_object(schema_name:, object:, logger: Logger.new(nil))
69
- SheetsV4::ValidateApiObjects::Validate.new(logger:).call(schema_name:, object:)
70
- end
60
+ # Validate the object using the named JSON schema
61
+ #
62
+ # The JSON schemas are loaded from the Google Disocvery API. The schemas names are
63
+ # returned by `SheetsV4.api_object_schema_names`.
64
+ #
65
+ # @example
66
+ # schema_name = 'batch_update_spreadsheet_request'
67
+ # object = { 'requests' => [] }
68
+ # SheetsV4.validate_api_object(schema_name:, object:)
69
+ #
70
+ # @param schema_name [String] the name of the schema to validate against
71
+ # @param object [Object] the object to validate
72
+ # @param logger [Logger] the logger to use for logging error, info, and debug message
73
+ #
74
+ # @raise [RuntimeError] if the object does not conform to the schema
75
+ #
76
+ # @return [void]
77
+ #
78
+ def validate_api_object(schema_name:, object:, logger: Logger.new(nil))
79
+ SheetsV4::ApiObjectValidation::ValidateApiObject.new(logger:).call(schema_name:, object:)
80
+ end
71
81
 
72
- # Given the name of the color, return a Google Sheets API color object
73
- #
74
- # Available color names are listed using `SheetsV4.color_names`.
75
- #
76
- # The object returned is frozen. If you want a color you can change (for instance,
77
- # to adjust the `alpha` attribute), you must clone the object returned.
78
- #
79
- # @example
80
- # SheetsV4::Color.color(:red) #=> { "red": 1.0, "green": 0.0, "blue": 0.0 }
81
- #
82
- # @param name [#to_sym] the name of the color requested
83
- #
84
- # @return [Hash] The color requested e.g. `{ "red": 1.0, "green": 0.0, "blue": 0.0 }`
85
- #
86
- # @api public
87
- #
88
- def self.color(name)
89
- SheetsV4::Color::COLORS[name.to_sym] || raise("Color #{name} not found")
90
- end
82
+ # List the names of the schemas available to use in the Google Sheets API
83
+ #
84
+ # @example List the name of the schemas available
85
+ # SheetsV4.api_object_schema_names #=> ["add_banding_request", "add_banding_response", ...]
86
+ #
87
+ # @return [Array<String>] the names of the schemas available
88
+ #
89
+ def api_object_schema_names(logger: Logger.new(nil))
90
+ SheetsV4::ApiObjectValidation::LoadSchemas.new(logger:).call.keys.sort
91
+ end
92
+
93
+ # Given the name of the color, return a Google Sheets API color object
94
+ #
95
+ # Available color names are listed using `SheetsV4.color_names`.
96
+ #
97
+ # The object returned is frozen. If you want a color you can change (for instance,
98
+ # to adjust the `alpha` attribute), you must clone the object returned.
99
+ #
100
+ # @example
101
+ # SheetsV4::Color.color(:red) #=> { "red": 1.0, "green": 0.0, "blue": 0.0 }
102
+ #
103
+ # @param name [#to_sym] the name of the color requested
104
+ #
105
+ # @return [Hash] The color requested e.g. `{ "red": 1.0, "green": 0.0, "blue": 0.0 }`
106
+ #
107
+ def color(name)
108
+ SheetsV4::Color::COLORS[name.to_sym] || raise("Color #{name} not found")
109
+ end
110
+
111
+ # List the names of the colors available to use in the Google Sheets API
112
+ #
113
+ # @example
114
+ # SheetsV4::Color.color_names #=> [:black, :white, :red, :green, :blue, :yellow, :magenta, :cyan, ...]
115
+ #
116
+ # @return [Array<Symbol>] the names of the colors available
117
+ #
118
+ def color_names = SheetsV4::Color::COLORS.keys
119
+
120
+ # @!attribute [rw] default_spreadsheet_tz
121
+ #
122
+ # Set the default time zone for date and time conversions
123
+ #
124
+ # Valid time zone names are those listed in one of these two sources:
125
+ # * `ActiveSupport::TimeZone.all.map { |tz| tz.tzinfo.name }`
126
+ # * `ActiveSupport::TimeZone.all.map(&:name)`
127
+ #
128
+ # If you want to set the timezone to the time zone of the system's local time,
129
+ # you could use the [timezone_local gem](https://rubygems.org/gems/timezone_local).
130
+ #
131
+ # @example
132
+ # SheetsV4.default_spreadsheet_tz = 'America/Los_Angeles'
133
+ #
134
+ # @example Set the time zone to the system's local time
135
+ # require 'timezone_local'
136
+ # SheetsV4.default_spreadsheet_tz = TimeZone::Local.get.name
137
+ #
138
+ # @return [void]
139
+ #
140
+ def default_spreadsheet_tz
141
+ @default_spreadsheet_tz || raise('default_spreadsheet_tz not set')
142
+ end
143
+
144
+ def default_spreadsheet_tz=(time_zone)
145
+ raise "Invalid time zone '#{time_zone}'" unless ActiveSupport::TimeZone.new(time_zone)
91
146
 
92
- # List the names of the colors available to use in the Google Sheets API
93
- #
94
- # @example
95
- # SheetsV4::Color.color_names #=> [:black, :white, :red, :green, :blue, :yellow, :magenta, :cyan, ...]
96
- #
97
- # @return [Array<Symbol>] the names of the colors available
98
- # @api public
99
- #
100
- def self.color_names = SheetsV4::Color::COLORS.keys
147
+ @default_date_and_time_converter = nil unless @default_spreadsheet_tz == time_zone
148
+ @default_spreadsheet_tz = time_zone
149
+ end
150
+
151
+ # The default converter for dates and times
152
+ # @return [SheetsV4::ConvertDatesAndTimes]
153
+ # @api private
154
+ def default_date_and_time_converter
155
+ @default_date_and_time_converter ||= SheetsV4::ConvertDatesAndTimes.new(default_spreadsheet_tz)
156
+ end
157
+
158
+ # Convert a Ruby Date object to a Google Sheet date value
159
+ #
160
+ # Uses the time zone given by `SheetsV4.default_spreadsheet_tz`.
161
+ #
162
+ # @example with a Date object
163
+ # SheetsV4.default_spreadsheet_tz = 'America/Los_Angeles'
164
+ # date = Date.parse('2021-05-17')
165
+ # SheetsV4.date_to_gs(date) #=> 44333
166
+ #
167
+ # @example with a DateTime object
168
+ # SheetsV4.default_spreadsheet_tz = 'America/Los_Angeles'
169
+ # date_time = DateTime.parse('2021-05-17 11:36:00 UTC')
170
+ # SheetsV4.date_to_gs(date_time) #=> 44333
171
+ #
172
+ # @param date [DateTime, Date, nil] the date to convert
173
+ #
174
+ # @return [Float, String] the value to sstore in a Google Sheet cell
175
+ # Returns a Float if date is not nil; otherwise, returns an empty string
176
+ #
177
+ def date_to_gs(date)
178
+ default_date_and_time_converter.date_to_gs(date)
179
+ end
180
+
181
+ # Convert a Google Sheets date value to a Ruby Date object
182
+ #
183
+ # @example with a date value
184
+ # SheetsV4.default_spreadsheet_tz = 'America/Los_Angeles'
185
+ # gs_value = 44333
186
+ # SheetsV4.gs_to_date(gs_value) #=> #<Date: 2021-05-17 ...>
187
+ #
188
+ # @example with a date and time value
189
+ # SheetsV4.default_spreadsheet_tz = 'America/Los_Angeles'
190
+ # gs_value = 44333.191666666666
191
+ # SheetsV4.gs_to_date(gs_value) #=> #<Date: 2021-05-17 ...>
192
+ #
193
+ # @param gs_value [Float, "", nil] the value from the Google Sheets cell
194
+ #
195
+ # @return [Date, nil] the value represented by gs_date
196
+ #
197
+ # Returns a Date object if a Float was given; otherwise, returns nil if an
198
+ # empty string or nil was given.
199
+ #
200
+ def gs_to_date(gs_value)
201
+ default_date_and_time_converter.gs_to_date(gs_value)
202
+ end
203
+
204
+ # Convert a Ruby DateTime object to a Google Sheets value
205
+ #
206
+ # @example
207
+ # SheetsV4.default_spreadsheet_tz = 'America/Los_Angeles'
208
+ # date_time = DateTime.parse('2021-05-17 11:36:00 UTC')
209
+ # SheetsV4.datetime_to_gs(date_time) #=> 44333.191666666666
210
+ #
211
+ # @param datetime [DateTime, nil] the date and time to convert
212
+ #
213
+ # @return [Float, String] the value to store in a Google Sheet cell
214
+ # Returns a Float if datetime is not nil; otherwise, returns an empty string.
215
+ #
216
+ def datetime_to_gs(datetime)
217
+ default_date_and_time_converter.datetime_to_gs(datetime)
218
+ end
219
+
220
+ # Convert a Google Sheets date time value to a DateTime object
221
+ #
222
+ # Time is rounded to the nearest second. The DateTime object returned is in
223
+ # the time zone given by `SheetsV4.default_spreadsheet_tz`.
224
+ #
225
+ # @example
226
+ # SheetsV4.default_spreadsheet_tz = 'America/Los_Angeles'
227
+ # gs_value = 44333.191666666666
228
+ # SheetsV4.gs_to_datetime(gs_value) #=> #<DateTime: 2021-05-17T04:35:59-07:00 ...>
229
+ #
230
+ # @param gs_value [Float, "", nil] the value from the Google Sheets cell
231
+ #
232
+ # @return [DateTime, nil] the value represented by gs_datetime
233
+ # Returns a DateTime object if a Float was given; otherwise, returns nil if an
234
+ # empty string or nil was given.
235
+ #
236
+ def gs_to_datetime(gs_value)
237
+ default_date_and_time_converter.gs_to_datetime(gs_value)
238
+ end
239
+ end
101
240
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sheets_v4
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Couball
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-10-02 00:00:00.000000000 Z
11
+ date: 2023-10-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler-audit
@@ -260,13 +260,16 @@ files:
260
260
  - examples/set_background_color
261
261
  - examples/set_background_color2
262
262
  - lib/sheets_v4.rb
263
+ - lib/sheets_v4/api_object_validation.rb
264
+ - lib/sheets_v4/api_object_validation/load_schemas.rb
265
+ - lib/sheets_v4/api_object_validation/resolve_schema_ref.rb
266
+ - lib/sheets_v4/api_object_validation/traverse_object_tree.rb
267
+ - lib/sheets_v4/api_object_validation/validate_api_object.rb
263
268
  - lib/sheets_v4/color.rb
264
- - lib/sheets_v4/credential_creator.rb
269
+ - lib/sheets_v4/convert_dates_and_times.rb
270
+ - lib/sheets_v4/create_credential.rb
265
271
  - lib/sheets_v4/validate_api_objects.rb
266
- - lib/sheets_v4/validate_api_objects/load_schemas.rb
267
- - lib/sheets_v4/validate_api_objects/resolve_schema_ref.rb
268
- - lib/sheets_v4/validate_api_objects/traverse_object_tree.rb
269
- - lib/sheets_v4/validate_api_objects/validate.rb
272
+ - lib/sheets_v4/validate_api_objects/validate_api_object.rb
270
273
  - lib/sheets_v4/version.rb
271
274
  homepage: https://github.com/main-branch/sheets_v4
272
275
  licenses: