sheets_v4 0.1.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.markdownlint.yml +25 -0
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +18 -0
- data/README.md +31 -7
- data/Rakefile +19 -6
- data/examples/README.md +47 -0
- data/lib/sheets_v4/color.rb +32 -0
- data/lib/sheets_v4/validate_api_object.rb +132 -0
- data/lib/sheets_v4/version.rb +2 -1
- data/lib/sheets_v4.rb +236 -2
- metadata +34 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 27e014c65904a88e3fb4df001a8b28c64c307363b3cd658da252a951c55da992
|
4
|
+
data.tar.gz: c6900f0d163abdb621a49080295ca7e59f82b89bae69d5e5729aa74aac765a1b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e60614708c5f348d05388a1bfe7b51603a92797e852aa20445d37451a493655aac69ea86fd901d500a9898a289f469d9a3780fdbe2f2756cfb80a65110641455
|
7
|
+
data.tar.gz: 71ea62ff3e93faf9d9ece533ccd98f5491dea2f04d00d01c01020dd501a251a3b0b61544e0e57e85643fc0afedbfe97e92250d71b5968c63650822f0e3338433
|
data/.markdownlint.yml
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
default: true
|
2
|
+
|
3
|
+
# Unordered list indentation
|
4
|
+
MD007: { indent: 2 }
|
5
|
+
|
6
|
+
# Line length
|
7
|
+
MD013: { line_length: 90, tables: false, code_blocks: false }
|
8
|
+
|
9
|
+
# Heading duplication is allowed for non-sibling headings
|
10
|
+
MD024: { siblings_only: true }
|
11
|
+
|
12
|
+
# Do not allow the specified trailig punctuation in a header
|
13
|
+
MD026: { punctuation: '.,;:' }
|
14
|
+
|
15
|
+
# Order list items must have a prefix that increases in numerical order
|
16
|
+
MD029: { style: 'ordered' }
|
17
|
+
|
18
|
+
# Lists do not need to be surrounded by blank lines
|
19
|
+
MD032: false
|
20
|
+
|
21
|
+
# Allow raw HTML in Markdown
|
22
|
+
MD033: false
|
23
|
+
|
24
|
+
# Allow emphasis to be used instead of a heading
|
25
|
+
MD036: false
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,24 @@ 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.3.0 (2023-09-28)
|
8
|
+
|
9
|
+
[Full Changelog](https://github.com/main-branch/sheets_v4/compare/v0.2.0..v0.3.0)
|
10
|
+
|
11
|
+
Changes since v0.2.0:
|
12
|
+
|
13
|
+
* 0fb4579 Add predefined color objects (#8)
|
14
|
+
|
15
|
+
## v0.2.0 (2023-09-26)
|
16
|
+
|
17
|
+
[Full Changelog](https://github.com/main-branch/sheets_v4/compare/v0.1.1..v0.2.0)
|
18
|
+
|
19
|
+
Changes since v0.1.1:
|
20
|
+
|
21
|
+
* 489ff50 Add SheetsV4.validate_api_object (#6)
|
22
|
+
* b44eba4 List all examples that should be created (#5)
|
23
|
+
* 2edd688 Add links to Google Sheets documentation to the README.md (#4)
|
24
|
+
|
7
25
|
## v0.1.1 (2023-09-22)
|
8
26
|
|
9
27
|
[Full Changelog](https://github.com/main-branch/sheets_v4/compare/v0.1.0..v0.1.1)
|
data/README.md
CHANGED
@@ -9,12 +9,33 @@
|
|
9
9
|
|
10
10
|
Unofficial helpers for the Google Sheets V4 API
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
+
* [Installation](#installation)
|
17
|
+
* [Usage](#usage)
|
18
|
+
* [Development](#development)
|
19
|
+
* [Creating a Google API Service Account](#creating-a-google-api-service-account)
|
20
|
+
* [Contributing](#contributing)
|
21
|
+
* [License](#license)
|
22
|
+
|
23
|
+
## Important Links for Programming Google Sheets
|
24
|
+
|
25
|
+
### General API Documentation
|
26
|
+
|
27
|
+
* [Google Sheets API Overview](https://developers.google.com/sheets/api)
|
28
|
+
* [Google Sheets API Reference](https://developers.google.com/sheets/api/reference/rest)
|
29
|
+
* [Batch Update Requests](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request)
|
30
|
+
|
31
|
+
### Ruby Implementation of the Sheets API
|
32
|
+
|
33
|
+
* [SheetsService Class](https://github.com/googleapis/google-api-ruby-client/blob/main/generated/google-apis-sheets_v4/lib/google/apis/sheets_v4/service.rb)
|
34
|
+
* [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)
|
35
|
+
|
36
|
+
### Other Links
|
37
|
+
|
38
|
+
* [Apps Script for Sheets](https://developers.google.com/apps-script/guides/sheets)
|
18
39
|
|
19
40
|
## Installation
|
20
41
|
|
@@ -46,6 +67,9 @@ release a new version, update the version number in `version.rb`, and then run
|
|
46
67
|
commits and the created tag, and push the `.gem` file to
|
47
68
|
[rubygems.org](https://rubygems.org).
|
48
69
|
|
70
|
+
## Creating a Google API Service Account
|
71
|
+
|
72
|
+
|
49
73
|
## Contributing
|
50
74
|
|
51
75
|
Bug reports and pull requests are welcome on [the main-branch/sheets_v4 GitHub project](https://github.com/main-branch/sheets_v4).
|
@@ -53,4 +77,4 @@ Bug reports and pull requests are welcome on [the main-branch/sheets_v4 GitHub p
|
|
53
77
|
## License
|
54
78
|
|
55
79
|
The gem is available as open source under the terms of the
|
56
|
-
[MIT License](https://opensource.org/licenses/MIT).
|
80
|
+
[MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
CHANGED
@@ -4,7 +4,7 @@ desc 'Run the same tasks that the CI build will run'
|
|
4
4
|
if RUBY_PLATFORM == 'java'
|
5
5
|
task default: %w[spec rubocop bundle:audit build]
|
6
6
|
else
|
7
|
-
task default: %w[spec rubocop yard
|
7
|
+
task default: %w[spec rubocop yard bundle:audit build]
|
8
8
|
end
|
9
9
|
|
10
10
|
# Bundler Audit
|
@@ -58,28 +58,41 @@ end
|
|
58
58
|
CLEAN << 'rubocop-report.json'
|
59
59
|
|
60
60
|
unless RUBY_PLATFORM == 'java'
|
61
|
-
#
|
61
|
+
# yard:build
|
62
62
|
|
63
63
|
require 'yard'
|
64
|
-
|
65
|
-
|
64
|
+
|
65
|
+
YARD::Rake::YardocTask.new('yard:build') do |t|
|
66
|
+
t.files = %w[lib/**/*.rb]
|
67
|
+
t.stats_options = ['--list-undoc']
|
66
68
|
end
|
67
69
|
|
68
70
|
CLEAN << '.yardoc'
|
69
71
|
CLEAN << 'doc'
|
70
72
|
|
71
|
-
#
|
73
|
+
# yard:audit
|
72
74
|
|
73
75
|
desc 'Run yardstick to show missing YARD doc elements'
|
74
76
|
task :'yard:audit' do
|
75
77
|
sh "yardstick 'lib/**/*.rb'"
|
76
78
|
end
|
77
79
|
|
78
|
-
#
|
80
|
+
# yard:coverage
|
79
81
|
|
80
82
|
require 'yardstick/rake/verify'
|
81
83
|
|
82
84
|
Yardstick::Rake::Verify.new(:'yard:coverage') do |verify|
|
83
85
|
verify.threshold = 100
|
86
|
+
verify.require_exact_threshold = false
|
87
|
+
end
|
88
|
+
|
89
|
+
task yard: %i[yard:build yard:audit yard:coverage]
|
90
|
+
|
91
|
+
# github-pages:publish
|
92
|
+
|
93
|
+
require 'github_pages_rake_tasks'
|
94
|
+
GithubPagesRakeTasks::PublishTask.new do |task|
|
95
|
+
# task.doc_dir = 'documentation'
|
96
|
+
task.verbose = true
|
84
97
|
end
|
85
98
|
end
|
data/examples/README.md
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# Google Sheets Examples
|
2
|
+
|
3
|
+
* [ ] Creating a Google API service account [1](https://www.youtube.com/watch?v=sAgWCbGMzTo&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=1)
|
4
|
+
[2](https://www.youtube.com/watch?v=sVURhxyc6jE&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=43)
|
5
|
+
* [ ] Create a SheetsService instance
|
6
|
+
* [ ] Creating Google Sheets files [1](https://www.youtube.com/watch?v=JRUxeQ6ZCy0&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=2)
|
7
|
+
* [ ] Writing data to a sheet [1](https://www.youtube.com/watch?v=YF7Ad-7pvks&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=3)
|
8
|
+
* [ ] Reading data from a sheet [1](https://www.youtube.com/watch?v=gkglr8GID5E&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=4)
|
9
|
+
* Reading data
|
10
|
+
* Reading formulas
|
11
|
+
* [ ] Data formatting basics [1](https://www.youtube.com/watch?v=R4EN3iPRris&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=5)
|
12
|
+
* [ ] Creating charts [1](https://www.youtube.com/watch?v=xt3p5I8mNWE&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=6)
|
13
|
+
* [ ] Data validation [1](https://www.youtube.com/watch?v=n_Z2565gu6Y&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=7)
|
14
|
+
* [ ] Cut, copy, and paste [1](https://www.youtube.com/watch?v=r8GWH2E_ehw&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=8)
|
15
|
+
* [ ] Duplicate sheets [1](https://www.youtube.com/watch?v=BgQoPcoOiGY&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=9)
|
16
|
+
* [ ] List sheets in a spreadsheet [1](https://www.youtube.com/watch?v=BgQoPcoOiGY&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=9)
|
17
|
+
* [ ] Set column width and row height [1](https://www.youtube.com/watch?v=H3uMEaPqTVE&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=11)
|
18
|
+
* [ ] Append rows and columns [1](https://www.youtube.com/watch?v=txfiwEjb7sk&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=12)
|
19
|
+
* [ ] Delete rows and columns [1](https://www.youtube.com/watch?v=w1jrCxWx7Tc&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=13)
|
20
|
+
* [ ] Insert rows and columns [1](https://www.youtube.com/watch?v=FL7WSsO5EVs&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=14)
|
21
|
+
* [ ] Move rows and columns [1](https://www.youtube.com/watch?v=YHk3305dkOc&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=15)
|
22
|
+
* [ ] Clear data [1](https://www.youtube.com/watch?v=mvbnhfdDrro&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=16)
|
23
|
+
* [ ] Add and delete sheets [1](https://www.youtube.com/watch?v=X9PVQQVoJFc&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=17)
|
24
|
+
* [ ] Copy sheet from one spreadsheet to another [1](https://www.youtube.com/watch?v=aIEM7Ts4n-c&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=18)
|
25
|
+
* [ ] Add a new sheet to an existing spreadsheet
|
26
|
+
* [ ] Export a sheet to a CSV file [1](https://www.youtube.com/watch?v=Dz22fsWsLhI&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=25)
|
27
|
+
* [ ] Sort sheets [1](https://www.youtube.com/watch?v=qbBZX7uBM1M&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=27)
|
28
|
+
* [ ] Add calculated fields into a pivot table [1](https://www.youtube.com/watch?v=VR8zOz5ATLU&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=32)
|
29
|
+
* [ ] Named ranges [1](https://www.youtube.com/watch?v=LTPdfXS_LHA&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=42)
|
30
|
+
* [ ] Create a pivot table [1](https://www.youtube.com/watch?v=preFnuL7ua0&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=28)
|
31
|
+
* [ ] Calculated pivot fields [1](https://www.youtube.com/watch?v=QLssI4uvjk4&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=33)
|
32
|
+
* [ ] Delete a pivot table
|
33
|
+
* [ ] Add pivot fields [1](https://www.youtube.com/watch?v=VR8zOz5ATLU&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=32)
|
34
|
+
* [ ] Add pivot filters [1](https://www.youtube.com/watch?v=EKikw-eIcbY&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=29)
|
35
|
+
* [ ] Collapse/expand pivot table groups [1](https://www.youtube.com/watch?v=-S9bs5-ZJ5E&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=31)
|
36
|
+
* [ ] Extract pivot table metadata [1](https://www.youtube.com/watch?v=H1SGdqbaL4w&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=30)
|
37
|
+
* [ ] Filter views [1](https://www.youtube.com/watch?v=GyRxsSlx0GU&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=34)
|
38
|
+
* [ ] Locate the last row in a column [1](https://www.youtube.com/watch?v=NWWHleJll28&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=35)
|
39
|
+
* [ ] Autofill [1](https://www.youtube.com/watch?v=guHGNmODdpM&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=36)
|
40
|
+
* [ ] Rename a sheet [1](https://www.youtube.com/watch?v=iuiDUJ4NrQI&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=37)
|
41
|
+
* [ ] Find and replace [1](https://www.youtube.com/watch?v=YaFR0bu5CrY&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=39)
|
42
|
+
* [ ] Add and delete sheets [1](https://www.youtube.com/watch?v=gMD4v8F8vlc&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=38)
|
43
|
+
* [ ] Sum across sheets [1](https://www.youtube.com/watch?v=7QNk-MXkPC4&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=44)
|
44
|
+
* [ ] Freeze rows / columns
|
45
|
+
* [ ] Protected ranges
|
46
|
+
* [ ] Resize a sheet
|
47
|
+
* [ ] Retrying on error
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json_schemer'
|
4
|
+
|
5
|
+
module SheetsV4
|
6
|
+
# Predefined color objects
|
7
|
+
# @api public
|
8
|
+
class Color
|
9
|
+
class << self
|
10
|
+
# Return a color object for the given name from SheetsV4::COLORS or call super
|
11
|
+
#
|
12
|
+
# @param method_name [Symbol] the name of the color
|
13
|
+
# @param arguments [Array] ignored
|
14
|
+
# @param block [Proc] ignored
|
15
|
+
# @return [Hash] the color object
|
16
|
+
# @api private
|
17
|
+
def method_missing(method_name, *arguments, &)
|
18
|
+
SheetsV4::COLORS[method_name.to_sym] || super
|
19
|
+
end
|
20
|
+
|
21
|
+
# Return true if the given method name is a color name or call super
|
22
|
+
#
|
23
|
+
# @param method_name [Symbol] the name of the color
|
24
|
+
# @param include_private [Boolean] ignored
|
25
|
+
# @return [Boolean] true if the method name is a color name
|
26
|
+
# @api private
|
27
|
+
def respond_to_missing?(method_name, include_private = false)
|
28
|
+
SheetsV4::COLORS.key?(method_name.to_sym) || super
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json_schemer'
|
4
|
+
|
5
|
+
module SheetsV4
|
6
|
+
# Validate objects against a Google Sheets API request object schema
|
7
|
+
#
|
8
|
+
# @api public
|
9
|
+
#
|
10
|
+
class ValidateApiObject
|
11
|
+
# Create a new validator
|
12
|
+
#
|
13
|
+
# By default, a nil logger is used. This means that no messages are logged.
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# logger = Logger.new(STDOUT, :level => Logger::INFO)
|
17
|
+
# validator = SheetsV4::ValidateApiObject.new(logger)
|
18
|
+
#
|
19
|
+
# @param logger [Logger] the logger to use
|
20
|
+
#
|
21
|
+
def initialize(logger = Logger.new(nil))
|
22
|
+
@logger = logger
|
23
|
+
end
|
24
|
+
|
25
|
+
# The logger to use internally
|
26
|
+
#
|
27
|
+
# Validation errors are logged at the error level. Other messages are logged
|
28
|
+
# at the debug level.
|
29
|
+
#
|
30
|
+
# @example
|
31
|
+
# logger = Logger.new(STDOUT, :level => Logger::INFO)
|
32
|
+
# validator = SheetsV4::ValidateApiObject.new(logger)
|
33
|
+
# validator.logger == logger # => true
|
34
|
+
# validator.logger.debug { "Debug message" }
|
35
|
+
#
|
36
|
+
# @return [Logger]
|
37
|
+
#
|
38
|
+
attr_reader :logger
|
39
|
+
|
40
|
+
# Validate the object using the JSON schema named schema_name
|
41
|
+
#
|
42
|
+
# @example
|
43
|
+
# schema_name = 'BatchUpdateSpreadsheetRequest'
|
44
|
+
# object = { 'requests' => [] }
|
45
|
+
# validator = SheetsV4::ValidateApiObject.new
|
46
|
+
# validator.call(schema_name, object)
|
47
|
+
#
|
48
|
+
# @param schema_name [String] the name of the schema to validate against
|
49
|
+
# @param object [Object] the object to validate
|
50
|
+
#
|
51
|
+
# @raise [RuntimeError] if the object does not conform to the schema
|
52
|
+
#
|
53
|
+
# @return [void]
|
54
|
+
#
|
55
|
+
def call(schema_name, object)
|
56
|
+
logger.debug { "Validating #{object} against #{schema_name}" }
|
57
|
+
|
58
|
+
schema = { '$ref' => schema_name }
|
59
|
+
schemer = JSONSchemer.schema(schema, ref_resolver:)
|
60
|
+
errors = schemer.validate(object)
|
61
|
+
raise_error!(schema_name, object, errors) if errors.any?
|
62
|
+
|
63
|
+
logger.debug { "Object #{object} conforms to #{schema_name}" }
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
# The resolver to use to resolve JSON schema references
|
69
|
+
# @return [SchemaRefResolver]
|
70
|
+
# @api private
|
71
|
+
def ref_resolver = @ref_resolver ||= SchemaRefResolver.new(logger)
|
72
|
+
|
73
|
+
# Raise an error when the object does not conform to the schema
|
74
|
+
# @return [void]
|
75
|
+
# @raise [RuntimeError]
|
76
|
+
# @api private
|
77
|
+
def raise_error!(schema_name, object, errors)
|
78
|
+
error = errors.first['error']
|
79
|
+
error_message = "Object #{object} does not conform to #{schema_name}: #{error}"
|
80
|
+
logger.error(error_message)
|
81
|
+
raise error_message
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Resolve JSON schema references to Google Sheets API schemas
|
86
|
+
#
|
87
|
+
# Uses the Google Discovery API to get the schemas. This is an implementation
|
88
|
+
# detail used to interact with JSONSchemer.
|
89
|
+
#
|
90
|
+
# @api private
|
91
|
+
#
|
92
|
+
class SchemaRefResolver
|
93
|
+
# Create a new schema resolver
|
94
|
+
#
|
95
|
+
# @param logger [Logger] the logger to use
|
96
|
+
#
|
97
|
+
# @api private
|
98
|
+
#
|
99
|
+
def initialize(logger)
|
100
|
+
@logger = logger
|
101
|
+
end
|
102
|
+
|
103
|
+
# The logger to use internally
|
104
|
+
#
|
105
|
+
# Currently, only info messages are logged.
|
106
|
+
#
|
107
|
+
# @return [Logger]
|
108
|
+
#
|
109
|
+
# @api private
|
110
|
+
#
|
111
|
+
attr_reader :logger
|
112
|
+
|
113
|
+
# Resolve a JSON schema reference
|
114
|
+
#
|
115
|
+
# @param ref [String] the reference to resolve usually in the form "#/definitions/schema_name"
|
116
|
+
#
|
117
|
+
# @return [Hash] the schema object as a hash
|
118
|
+
#
|
119
|
+
# @api private
|
120
|
+
#
|
121
|
+
def call(ref)
|
122
|
+
schema_name = ref.path[1..]
|
123
|
+
logger.debug { "Reading schema #{schema_name}" }
|
124
|
+
schema_object = SheetsV4.api_object_schemas[schema_name]
|
125
|
+
raise "Schema for #{ref} not found" unless schema_object
|
126
|
+
|
127
|
+
schema_object.to_h.tap do |schema|
|
128
|
+
schema['unevaluatedProperties'] = false
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
data/lib/sheets_v4/version.rb
CHANGED
data/lib/sheets_v4.rb
CHANGED
@@ -1,8 +1,242 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'sheets_v4/version'
|
4
|
+
require_relative 'sheets_v4/color'
|
5
|
+
require_relative 'sheets_v4/validate_api_object'
|
4
6
|
|
7
|
+
require 'json'
|
8
|
+
require 'logger'
|
9
|
+
require 'net/http'
|
10
|
+
|
11
|
+
# Unofficial helpers for the Google Sheets V4 API
|
12
|
+
#
|
13
|
+
# @api public
|
14
|
+
#
|
5
15
|
module SheetsV4
|
6
|
-
|
7
|
-
#
|
16
|
+
# Validate the object using the named JSON schema
|
17
|
+
#
|
18
|
+
# The JSON schemas are loaded from the Google Disocvery API. The schemas names are
|
19
|
+
# returned by `SheetsV4.api_object_schemas.keys`.
|
20
|
+
#
|
21
|
+
# @example
|
22
|
+
# schema_name = 'BatchUpdateSpreadsheetRequest'
|
23
|
+
# object = { 'requests' => [] }
|
24
|
+
# SheetsV4.validate_api_object(schema_name:, object:)
|
25
|
+
#
|
26
|
+
# @param schema_name [String] the name of the schema to validate against
|
27
|
+
# @param object [Object] the object to validate
|
28
|
+
# @param logger [Logger] the logger to use for logging error, info, and debug message
|
29
|
+
#
|
30
|
+
# @raise [RuntimeError] if the object does not conform to the schema
|
31
|
+
#
|
32
|
+
# @return [void]
|
33
|
+
#
|
34
|
+
def self.validate_api_object(schema_name:, object:, logger: Logger.new(nil))
|
35
|
+
ValidateApiObject.new(logger).call(schema_name, object)
|
36
|
+
end
|
37
|
+
|
38
|
+
# A hash of schemas keyed by the schema name loaded from the Google Discovery API
|
39
|
+
#
|
40
|
+
# @example
|
41
|
+
# SheetsV4.api_object_schemas #=> { 'PersonSchema' => { 'type' => 'object', ... } ... }
|
42
|
+
#
|
43
|
+
# @return [Hash<String, Object>] a hash of schemas keyed by schema name
|
44
|
+
#
|
45
|
+
def self.api_object_schemas
|
46
|
+
schema_load_semaphore.synchronize { @api_object_schemas ||= load_api_object_schemas }
|
47
|
+
end
|
48
|
+
|
49
|
+
# Validate
|
50
|
+
# A mutex used to synchronize access to the schemas so they are only loaded
|
51
|
+
# once.
|
52
|
+
#
|
53
|
+
@schema_load_semaphore = Thread::Mutex.new
|
54
|
+
|
55
|
+
# A mutex used to synchronize access to the schemas so they are only loaded once
|
56
|
+
#
|
57
|
+
# @return [Thread::Mutex]
|
58
|
+
#
|
59
|
+
# @api private
|
60
|
+
#
|
61
|
+
def self.schema_load_semaphore = @schema_load_semaphore
|
62
|
+
|
63
|
+
# Load the schemas from the Google Discovery API
|
64
|
+
#
|
65
|
+
# @return [Hash<String, Object>] a hash of schemas keyed by schema name
|
66
|
+
#
|
67
|
+
# @api private
|
68
|
+
#
|
69
|
+
def self.load_api_object_schemas
|
70
|
+
source = 'https://sheets.googleapis.com/$discovery/rest?version=v4'
|
71
|
+
resp = Net::HTTP.get_response(URI.parse(source))
|
72
|
+
data = resp.body
|
73
|
+
JSON.parse(data)['schemas'].tap do |schemas|
|
74
|
+
schemas.each { |_name, schema| schema['unevaluatedProperties'] = false }
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Return a color object for the given name from SheetsV4::COLORS
|
79
|
+
#
|
80
|
+
# @example
|
81
|
+
# SheetsV4::Color.color(:red) #=> { "red": 1.0, "green": 0.0, "blue": 0.0 }
|
82
|
+
#
|
83
|
+
# @param name [Symbol] the name of the color
|
84
|
+
# @return [Hash] the color object
|
85
|
+
# @api public
|
86
|
+
def self.color(name)
|
87
|
+
SheetsV4::COLORS[name.to_sym]
|
88
|
+
end
|
89
|
+
|
90
|
+
# Colors to use in the Google Sheets API
|
91
|
+
COLORS = {
|
92
|
+
# Standard Google Sheets colors
|
93
|
+
|
94
|
+
black: { red: 0.000000000, green: 0.000000000, blue: 0.000000000 },
|
95
|
+
dark_gray4: { red: 0.262745098, green: 0.262745098, blue: 0.262745098 },
|
96
|
+
dark_gray3: { red: 0.400000000, green: 0.400000000, blue: 0.400000000 },
|
97
|
+
dark_gray2: { red: 0.600000000, green: 0.600000000, blue: 0.600000000 },
|
98
|
+
dark_gray1: { red: 0.717647059, green: 0.717647059, blue: 0.717647059 },
|
99
|
+
gray: { red: 0.800000000, green: 0.800000000, blue: 0.800000000 },
|
100
|
+
light_gray1: { red: 0.850980392, green: 0.850980392, blue: 0.850980392 },
|
101
|
+
light_gray2: { red: 0.937254902, green: 0.937254902, blue: 0.937254902 },
|
102
|
+
light_gray3: { red: 0.952941176, green: 0.952941176, blue: 0.952941176 },
|
103
|
+
white: { red: 1.000000000, green: 1.000000000, blue: 1.000000000 },
|
104
|
+
red_berry: { red: 0.596078431, green: 0.000000000, blue: 0.000000000 },
|
105
|
+
red: { red: 1.000000000, green: 0.000000000, blue: 0.000000000 },
|
106
|
+
orange: { red: 1.000000000, green: 0.600000000, blue: 0.000000000 },
|
107
|
+
yellow: { red: 1.000000000, green: 1.000000000, blue: 0.000000000 },
|
108
|
+
green: { red: 0.000000000, green: 1.000000000, blue: 0.000000000 },
|
109
|
+
cyan: { red: 0.000000000, green: 1.000000000, blue: 1.000000000 },
|
110
|
+
cornflower_blue: { red: 0.290196078, green: 0.525490196, blue: 0.909803922 },
|
111
|
+
blue: { red: 0.000000000, green: 0.000000000, blue: 1.000000000 },
|
112
|
+
purple: { red: 0.600000000, green: 0.000000000, blue: 1.000000000 },
|
113
|
+
magenta: { red: 1.000000000, green: 0.000000000, blue: 1.000000000 },
|
114
|
+
light_red_berry3: { red: 0.901960784, green: 0.721568627, blue: 0.686274510 },
|
115
|
+
light_red3: { red: 0.956862745, green: 0.800000000, blue: 0.800000000 },
|
116
|
+
light_orange3: { red: 0.988235294, green: 0.898039216, blue: 0.803921569 },
|
117
|
+
light_yellow3: { red: 1.000000000, green: 0.949019608, blue: 0.800000000 },
|
118
|
+
light_green3: { red: 0.850980392, green: 0.917647059, blue: 0.827450980 },
|
119
|
+
light_cyan3: { red: 0.815686275, green: 0.878431373, blue: 0.890196078 },
|
120
|
+
light_cornflower_blue3: { red: 0.788235294, green: 0.854901961, blue: 0.972549020 },
|
121
|
+
light_blue3: { red: 0.811764706, green: 0.886274510, blue: 0.952941176 },
|
122
|
+
light_purple3: { red: 0.850980392, green: 0.823529412, blue: 0.913725490 },
|
123
|
+
light_magenta3: { red: 0.917647059, green: 0.819607843, blue: 0.862745098 },
|
124
|
+
light_red_berry2: { red: 0.866666667, green: 0.494117647, blue: 0.419607843 },
|
125
|
+
light_red2: { red: 0.917647059, green: 0.600000000, blue: 0.600000000 },
|
126
|
+
light_orange2: { red: 0.976470588, green: 0.796078431, blue: 0.611764706 },
|
127
|
+
light_yellow2: { red: 1.000000000, green: 0.898039216, blue: 0.600000000 },
|
128
|
+
light_green2: { red: 0.713725490, green: 0.843137255, blue: 0.658823529 },
|
129
|
+
light_cyan2: { red: 0.635294118, green: 0.768627451, blue: 0.788235294 },
|
130
|
+
light_cornflower_blue2: { red: 0.643137255, green: 0.760784314, blue: 0.956862745 },
|
131
|
+
light_blue2: { red: 0.623529412, green: 0.772549020, blue: 0.909803922 },
|
132
|
+
light_purple2: { red: 0.705882353, green: 0.654901961, blue: 0.839215686 },
|
133
|
+
light_magenta2: { red: 0.835294118, green: 0.650980392, blue: 0.741176471 },
|
134
|
+
light_red_berry1: { red: 0.800000000, green: 0.254901961, blue: 0.145098039 },
|
135
|
+
light_red1: { red: 0.878431373, green: 0.400000000, blue: 0.400000000 },
|
136
|
+
light_orange1: { red: 0.964705882, green: 0.698039216, blue: 0.419607843 },
|
137
|
+
light_yellow1: { red: 1.000000000, green: 0.850980392, blue: 0.400000000 },
|
138
|
+
light_green1: { red: 0.576470588, green: 0.768627451, blue: 0.490196078 },
|
139
|
+
light_cyan1: { red: 0.462745098, green: 0.647058824, blue: 0.686274510 },
|
140
|
+
light_cornflower_blue1: { red: 0.427450980, green: 0.619607843, blue: 0.921568627 },
|
141
|
+
light_blue1: { red: 0.435294118, green: 0.658823529, blue: 0.862745098 },
|
142
|
+
light_purple1: { red: 0.556862745, green: 0.486274510, blue: 0.764705882 },
|
143
|
+
light_magenta1: { red: 0.760784314, green: 0.482352941, blue: 0.627450980 },
|
144
|
+
dark_red_berry1: { red: 0.650980392, green: 0.109803922, blue: 0.000000000 },
|
145
|
+
dark_red1: { red: 0.800000000, green: 0.000000000, blue: 0.000000000 },
|
146
|
+
dark_orange1: { red: 0.901960784, green: 0.568627451, blue: 0.219607843 },
|
147
|
+
dark_yellow1: { red: 0.945098039, green: 0.760784314, blue: 0.196078431 },
|
148
|
+
dark_green1: { red: 0.415686275, green: 0.658823529, blue: 0.309803922 },
|
149
|
+
dark_cyan1: { red: 0.270588235, green: 0.505882353, blue: 0.556862745 },
|
150
|
+
dark_cornflower_blue1: { red: 0.235294118, green: 0.470588235, blue: 0.847058824 },
|
151
|
+
dark_blue1: { red: 0.239215686, green: 0.521568627, blue: 0.776470588 },
|
152
|
+
dark_purple1: { red: 0.403921569, green: 0.305882353, blue: 0.654901961 },
|
153
|
+
dark_magenta1: { red: 0.650980392, green: 0.301960784, blue: 0.474509804 },
|
154
|
+
dark_red_berry2: { red: 0.521568627, green: 0.125490196, blue: 0.047058824 },
|
155
|
+
dark_red2: { red: 0.600000000, green: 0.000000000, blue: 0.000000000 },
|
156
|
+
dark_orange2: { red: 0.705882353, green: 0.372549020, blue: 0.023529412 },
|
157
|
+
dark_yellow2: { red: 0.749019608, green: 0.564705882, blue: 0.000000000 },
|
158
|
+
dark_green2: { red: 0.219607843, green: 0.462745098, blue: 0.113725490 },
|
159
|
+
dark_cyan2: { red: 0.074509804, green: 0.309803922, blue: 0.360784314 },
|
160
|
+
dark_cornflower_blue2: { red: 0.066666667, green: 0.333333333, blue: 0.800000000 },
|
161
|
+
dark_blue2: { red: 0.043137255, green: 0.325490196, blue: 0.580392157 },
|
162
|
+
dark_purple2: { red: 0.207843137, green: 0.109803922, blue: 0.458823529 },
|
163
|
+
dark_magenta2: { red: 0.454901961, green: 0.105882353, blue: 0.278431373 },
|
164
|
+
dark_red_berry3: { red: 0.356862745, green: 0.058823529, blue: 0.000000000 },
|
165
|
+
dark_red3: { red: 0.400000000, green: 0.000000000, blue: 0.000000000 },
|
166
|
+
dark_orange3: { red: 0.470588235, green: 0.247058824, blue: 0.015686275 },
|
167
|
+
dark_yellow3: { red: 0.498039216, green: 0.376470588, blue: 0.000000000 },
|
168
|
+
dark_green3: { red: 0.152941176, green: 0.305882353, blue: 0.074509804 },
|
169
|
+
darn_cyan3: { red: 0.047058824, green: 0.203921569, blue: 0.239215686 },
|
170
|
+
dark_cornflower_blue3: { red: 0.109803922, green: 0.270588235, blue: 0.529411765 },
|
171
|
+
dark_blue3: { red: 0.027450980, green: 0.215686275, blue: 0.388235294 },
|
172
|
+
dark_purple3: { red: 0.125490196, green: 0.070588235, blue: 0.301960784 },
|
173
|
+
dark_magenta3: { red: 0.298039216, green: 0.066666667, blue: 0.188235294 },
|
174
|
+
|
175
|
+
# Yahoo brand colors
|
176
|
+
|
177
|
+
grape_jelly: { red: 0.376470588, green: 0.003921569, blue: 0.823529412 },
|
178
|
+
hulk_pants: { red: 0.494117647, green: 0.121568627, blue: 1.000000000 },
|
179
|
+
malbec: { red: 0.223529412, green: 0.000000000, blue: 0.490196078 },
|
180
|
+
tumeric: { red: 1.000000000, green: 0.654901961, blue: 0.000000000 },
|
181
|
+
mulah: { red: 0.101960784, green: 0.772549020, blue: 0.403921569 },
|
182
|
+
dory: { red: 0.058823529, green: 0.411764706, blue: 1.000000000 },
|
183
|
+
malibu: { red: 1.000000000, green: 0.000000000, blue: 0.501960784 },
|
184
|
+
sea_foam: { red: 0.066666667, green: 0.827450980, blue: 0.803921569 },
|
185
|
+
tumeric_tint: { red: 0.980392157, green: 0.866666667, blue: 0.694117647 },
|
186
|
+
mulah_tint: { red: 0.733333333, green: 0.901960784, blue: 0.776470588 },
|
187
|
+
dory_tint: { red: 0.662745098, green: 0.772549020, blue: 0.984313725 },
|
188
|
+
malibu_tint: { red: 0.968627451, green: 0.682352941, blue: 0.800000000 },
|
189
|
+
sea_foam_tint: { red: 0.749019608, green: 0.925490196, blue: 0.921568627 },
|
190
|
+
|
191
|
+
# Yahoo health colors
|
192
|
+
|
193
|
+
health_green: { red: 0.000000000, green: 0.690196078, blue: 0.313725490 },
|
194
|
+
health_yellow: { red: 1.000000000, green: 0.654901961, blue: 0.000000000 },
|
195
|
+
health_red: { red: 1.000000000, green: 0.000000000, blue: 0.000000000 },
|
196
|
+
|
197
|
+
# Yahoo Fuji design color palette
|
198
|
+
|
199
|
+
fuji_color_watermelon: { red: 1.000000000, green: 0.321568627, blue: 0.341176471 },
|
200
|
+
fuji_color_solo_cup: { red: 0.921568627, green: 0.058823529, blue: 0.160784314 },
|
201
|
+
fuji_color_malibu: { red: 1.000000000, green: 0.000000000, blue: 0.501960784 },
|
202
|
+
fuji_color_barney: { red: 0.800000000, green: 0.000000000, blue: 0.549019608 },
|
203
|
+
fuji_color_mimosa: { red: 1.000000000, green: 0.827450980, blue: 0.200000000 },
|
204
|
+
fuji_color_turmeric: { red: 1.000000000, green: 0.654901961, blue: 0.000000000 },
|
205
|
+
fuji_color_cheetos: { red: 0.992156863, green: 0.380392157, blue: 0.000000000 },
|
206
|
+
fuji_color_carrot_juice: { red: 1.000000000, green: 0.321568627, blue: 0.050980392 },
|
207
|
+
fuji_color_mulah: { red: 0.101960784, green: 0.772549020, blue: 0.403921569 },
|
208
|
+
fuji_color_bonsai: { red: 0.000000000, green: 0.529411765, blue: 0.317647059 },
|
209
|
+
fuji_color_spirulina: { red: 0.000000000, green: 0.627450980, blue: 0.627450980 },
|
210
|
+
fuji_color_sea_foam: { red: 0.066666667, green: 0.827450980, blue: 0.803921569 },
|
211
|
+
fuji_color_peeps: { red: 0.490196078, green: 0.796078431, blue: 1.000000000 },
|
212
|
+
fuji_color_sky: { red: 0.070588235, green: 0.662745098, blue: 1.000000000 },
|
213
|
+
fuji_color_dory: { red: 0.058823529, green: 0.411764706, blue: 1.000000000 },
|
214
|
+
fuji_color_scooter: { red: 0.000000000, green: 0.388235294, blue: 0.921568627 },
|
215
|
+
fuji_color_cobalt: { red: 0.000000000, green: 0.227450980, blue: 0.737254902 },
|
216
|
+
fuji_color_denim: { red: 0.101960784, green: 0.050980392, blue: 0.670588235 },
|
217
|
+
fuji_color_blurple: { red: 0.364705882, green: 0.368627451, blue: 1.000000000 },
|
218
|
+
fuji_color_hendrix: { red: 0.972549020, green: 0.956862745, blue: 1.000000000 },
|
219
|
+
fuji_color_thanos: { red: 0.564705882, green: 0.486274510, blue: 1.000000000 },
|
220
|
+
fuji_color_starfish: { red: 0.466666667, green: 0.349019608, blue: 1.000000000 },
|
221
|
+
fuji_color_hulk_pants: { red: 0.494117647, green: 0.121568627, blue: 1.000000000 },
|
222
|
+
fuji_color_grape_jelly: { red: 0.376470588, green: 0.003921569, blue: 0.823529412 },
|
223
|
+
fuji_color_mulberry: { red: 0.313725490, green: 0.082352941, blue: 0.690196078 },
|
224
|
+
fuji_color_malbec: { red: 0.223529412, green: 0.000000000, blue: 0.490196078 },
|
225
|
+
fuji_grayscale_black: { red: 0.000000000, green: 0.000000000, blue: 0.000000000 },
|
226
|
+
fuji_grayscale_midnight: { red: 0.062745098, green: 0.082352941, blue: 0.094117647 },
|
227
|
+
fuji_grayscale_inkwell: { red: 0.113725490, green: 0.133333333, blue: 0.156862745 },
|
228
|
+
fuji_grayscale_batcave: { red: 0.137254902, green: 0.164705882, blue: 0.192156863 },
|
229
|
+
fuji_grayscale_ramones: { red: 0.172549020, green: 0.211764706, blue: 0.247058824 },
|
230
|
+
fuji_grayscale_charcoal: { red: 0.274509804, green: 0.305882353, blue: 0.337254902 },
|
231
|
+
fuji_grayscale_battleship: { red: 0.356862745, green: 0.388235294, blue: 0.415686275 },
|
232
|
+
fuji_grayscale_dolphin: { red: 0.431372549, green: 0.466666667, blue: 0.501960784 },
|
233
|
+
fuji_grayscale_shark: { red: 0.509803922, green: 0.541176471, blue: 0.576470588 },
|
234
|
+
fuji_grayscale_gandalf: { red: 0.592156863, green: 0.619607843, blue: 0.658823529 },
|
235
|
+
fuji_grayscale_bob: { red: 0.690196078, green: 0.725490196, blue: 0.756862745 },
|
236
|
+
fuji_grayscale_pebble: { red: 0.780392157, green: 0.803921569, blue: 0.823529412 },
|
237
|
+
fuji_grayscale_dirty_seagull: { red: 0.878431373, green: 0.894117647, blue: 0.913725490 },
|
238
|
+
fuji_grayscale_grey_hair: { red: 0.941176471, green: 0.952941176, blue: 0.960784314 },
|
239
|
+
fuji_grayscale_marshmallow: { red: 0.960784314, green: 0.972549020, blue: 0.980392157 },
|
240
|
+
fuji_grayscale_white: { red: 1.000000000, green: 1.000000000, blue: 1.000000000 }
|
241
|
+
}.freeze
|
8
242
|
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.3.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-09-
|
11
|
+
date: 2023-09-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler-audit
|
@@ -170,6 +170,20 @@ dependencies:
|
|
170
170
|
- - "~>"
|
171
171
|
- !ruby/object:Gem::Version
|
172
172
|
version: '7.0'
|
173
|
+
- !ruby/object:Gem::Dependency
|
174
|
+
name: github_pages_rake_tasks
|
175
|
+
requirement: !ruby/object:Gem::Requirement
|
176
|
+
requirements:
|
177
|
+
- - "~>"
|
178
|
+
- !ruby/object:Gem::Version
|
179
|
+
version: '0.1'
|
180
|
+
type: :development
|
181
|
+
prerelease: false
|
182
|
+
version_requirements: !ruby/object:Gem::Requirement
|
183
|
+
requirements:
|
184
|
+
- - "~>"
|
185
|
+
- !ruby/object:Gem::Version
|
186
|
+
version: '0.1'
|
173
187
|
- !ruby/object:Gem::Dependency
|
174
188
|
name: google-apis-sheets_v4
|
175
189
|
requirement: !ruby/object:Gem::Requirement
|
@@ -198,6 +212,20 @@ dependencies:
|
|
198
212
|
- - ">="
|
199
213
|
- !ruby/object:Gem::Version
|
200
214
|
version: '0'
|
215
|
+
- !ruby/object:Gem::Dependency
|
216
|
+
name: json_schemer
|
217
|
+
requirement: !ruby/object:Gem::Requirement
|
218
|
+
requirements:
|
219
|
+
- - "~>"
|
220
|
+
- !ruby/object:Gem::Version
|
221
|
+
version: '2.0'
|
222
|
+
type: :runtime
|
223
|
+
prerelease: false
|
224
|
+
version_requirements: !ruby/object:Gem::Requirement
|
225
|
+
requirements:
|
226
|
+
- - "~>"
|
227
|
+
- !ruby/object:Gem::Version
|
228
|
+
version: '2.0'
|
201
229
|
- !ruby/object:Gem::Dependency
|
202
230
|
name: rltk
|
203
231
|
requirement: !ruby/object:Gem::Requirement
|
@@ -219,6 +247,7 @@ executables: []
|
|
219
247
|
extensions: []
|
220
248
|
extra_rdoc_files: []
|
221
249
|
files:
|
250
|
+
- ".markdownlint.yml"
|
222
251
|
- ".rspec"
|
223
252
|
- ".rubocop.yml"
|
224
253
|
- ".yardopts"
|
@@ -227,7 +256,10 @@ files:
|
|
227
256
|
- LICENSE.txt
|
228
257
|
- README.md
|
229
258
|
- Rakefile
|
259
|
+
- examples/README.md
|
230
260
|
- lib/sheets_v4.rb
|
261
|
+
- lib/sheets_v4/color.rb
|
262
|
+
- lib/sheets_v4/validate_api_object.rb
|
231
263
|
- lib/sheets_v4/version.rb
|
232
264
|
homepage: https://github.com/main-branch/sheets_v4
|
233
265
|
licenses:
|