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 +4 -4
- data/CHANGELOG.md +19 -0
- data/README.md +260 -25
- data/examples/README.md +1 -0
- data/lib/sheets_v4/{validate_api_objects → api_object_validation}/load_schemas.rb +7 -7
- data/lib/sheets_v4/{validate_api_objects → api_object_validation}/resolve_schema_ref.rb +6 -6
- data/lib/sheets_v4/{validate_api_objects → api_object_validation}/traverse_object_tree.rb +2 -2
- data/lib/sheets_v4/api_object_validation/validate_api_object.rb +87 -0
- data/lib/sheets_v4/api_object_validation.rb +20 -0
- data/lib/sheets_v4/color.rb +0 -1
- data/lib/sheets_v4/convert_dates_and_times.rb +243 -0
- data/lib/sheets_v4/{credential_creator.rb → create_credential.rb} +2 -2
- data/lib/sheets_v4/validate_api_objects/{validate.rb → validate_api_object.rb} +7 -6
- data/lib/sheets_v4/validate_api_objects.rb +6 -6
- data/lib/sheets_v4/version.rb +1 -1
- data/lib/sheets_v4.rb +220 -81
- metadata +10 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b3ed35f7562643f623e68d6ad41c497f1d92ea7b2d77f4b6f0d5e02586109897
|
4
|
+
data.tar.gz: 6687dfc41d70870694a2085e749d803f7062de8d2486866e4e44ea54b1eec95f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
##
|
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
|
-
|
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
|
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
|
-
##
|
66
|
+
## Getting Started
|
44
67
|
|
45
|
-
|
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
|
-
|
48
|
-
bundle add sheets_v4
|
49
|
-
```
|
71
|
+
### Creating a Google Cloud project
|
50
72
|
|
51
|
-
|
73
|
+
Create a Google Cloud project using [these directions](https://developers.google.com/workspace/guides/create-project).
|
52
74
|
|
53
|
-
|
54
|
-
|
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(
|
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(
|
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
|
-
|
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
|
118
|
-
`rake
|
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
@@ -1,12 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module SheetsV4
|
4
|
-
module
|
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::
|
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::
|
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
|
-
#
|
34
|
-
#
|
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
|
-
# @
|
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::
|
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
|
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::
|
12
|
+
# `SheetsV4::ApiObjectValidation::LoadSchemas.new(logger:).call['cell_data']`.
|
13
13
|
#
|
14
|
-
# An RuntimeError is raised if `SheetsV4::
|
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::
|
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/
|
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::
|
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
|
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::
|
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'
|
data/lib/sheets_v4/color.rb
CHANGED
@@ -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
|
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 =
|
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
|
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
|
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::
|
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::
|
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::
|
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::
|
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::
|
10
|
+
# SheetsV4::ApiObjectValidation::ValidateApiObject.new(logger:).call(schema_name:, object:)
|
11
11
|
#
|
12
12
|
# @api public
|
13
13
|
#
|
14
|
-
module
|
14
|
+
module ApiObjectValidation; end
|
15
15
|
end
|
16
16
|
|
17
|
-
require_relative '
|
18
|
-
require_relative '
|
19
|
-
require_relative '
|
20
|
-
require_relative '
|
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'
|
data/lib/sheets_v4/version.rb
CHANGED
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/
|
6
|
-
require_relative 'sheets_v4/
|
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
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
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
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
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.
|
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-
|
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/
|
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/
|
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:
|