sheets_v4 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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:
|